signer = $signer; } /** * @param string $token The signed cookie value "id.signature". */ public function findByToken( string $token ): ?array { $id = $this->signer->verify( $token ); return null === $id ? null : $this->find( $id ); } public function find( int $id ): ?array { global $wpdb; // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching -- custom table, per-request lookup. $row = $wpdb->get_row( $wpdb->prepare( "SELECT id, email FROM {$wpdb->prefix}advently_subscribers WHERE id = %d", $id ), ARRAY_A ); return $row ? self::shape( $row ) : null; } public function findByEmail( string $email ): ?array { global $wpdb; // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching -- custom table, per-request lookup. $row = $wpdb->get_row( $wpdb->prepare( "SELECT id, email FROM {$wpdb->prefix}advently_subscribers WHERE email = %s", strtolower( trim( $email ) ) ), ARRAY_A ); return $row ? self::shape( $row ) : null; } /** * Finds or creates the row for an email. Consent is recorded the first time it is given. */ public function subscribe( string $email, bool $consent, string $ipHash ): array { return $this->upsert( $email, null, $consent, $ipHash ); } /** * The subscriber row of a logged-in customer (login mode). */ public function forUser( \WP_User $user, string $ipHash ): ?array { if ( ! is_email( $user->user_email ) ) { return null; } return $this->upsert( $user->user_email, (int) $user->ID, false, $ipHash ); } public function token( int $id ): string { return $this->signer->sign( $id ); } private function upsert( string $email, ?int $userId, bool $consent, string $ipHash ): array { global $wpdb; $email = strtolower( trim( $email ) ); $now = current_time( 'mysql', true ); // The unique email key makes this safe against two parallel first visits. // NULLIF turns the 0 / '' placeholders into real NULLs (prepare() has no NULL). // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching -- custom table write. $wpdb->query( $wpdb->prepare( "INSERT INTO {$wpdb->prefix}advently_subscribers (email, user_id, created_at, consent_at, ip_hash) VALUES (%s, NULLIF(%d, 0), %s, NULLIF(%s, ''), %s) ON DUPLICATE KEY UPDATE user_id = COALESCE(VALUES(user_id), user_id), consent_at = COALESCE(consent_at, VALUES(consent_at))", $email, (int) $userId, $now, $consent ? $now : '', $ipHash ) ); // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching -- custom table, read-after-write. $row = $wpdb->get_row( $wpdb->prepare( "SELECT id, email FROM {$wpdb->prefix}advently_subscribers WHERE email = %s", $email ), ARRAY_A ); return self::shape( $row ); } private static function shape( array $row ): array { return [ 'id' => (int) $row['id'], 'email' => (string) $row['email'], ]; } }