id <= 0 ) { return null; } $hash = Tokens::email_hash( $email ); $now = Clock::now(); $first = mb_substr( trim( sanitize_text_field( (string) ( $person['first_name'] ?? '' ) ) ), 0, 100 ); $last = mb_substr( trim( sanitize_text_field( (string) ( $person['last_name'] ?? '' ) ) ), 0, 100 ); $lang = mb_substr( Language::normalize( (string) ( $person['lang'] ?? '' ) ), 0, 12 ); $added = Db::query( SqlPart::of( "INSERT IGNORE INTO %i (invitation_id, user_id, email_hash, email, first_name, last_name, lang, source, token_expires_at, sent_version, state, sent_at, created_at) SELECT %d, COALESCE( acc.ID, 0 ), %s, IF( acc.ID IS NULL, %s, '' ), IF( acc.ID IS NULL, %s, '' ), IF( acc.ID IS NULL, %s, '' ), LEFT( COALESCE( NULLIF( p.lang, '' ), %s ), 12 ), %d, %s, %d, %d, %s, %s FROM ( SELECT 1 AS one ) o LEFT JOIN ( SELECT ua.ID FROM %i ua WHERE ua.user_email = %s ORDER BY ua.ID LIMIT 1 ) acc ON 1 = 1 LEFT JOIN %i p ON p.user_id = acc.ID WHERE NOT ", Db::table( 'invitees' ), $inv->id, $hash, $email, $first, $last, $lang, $source->value, $expiry, $inv->version, InviteeState::SENT->value, $now, $now, $wpdb->users, $email, Db::table( 'people' ) )->add( self::blocked_sql( $hash, $email ) ) ); if ( $added > 0 ) { $id = Db::insert_id(); if ( $id <= 0 ) { return null; } $seed = Tokens::seed(); $done = Db::query( 'UPDATE %i SET token_hash = %s, token_seed = %s WHERE id = %d AND token_hash IS NULL', Db::table( 'invitees' ), Tokens::hash( Tokens::for_invitee( $id, $seed ) ), $seed, $id ); if ( $done <= 0 ) { return null; } return array( 'id' => $id, 'seed' => $seed, 'created' => true, ); } $row = Db::row( SqlPart::of( 'SELECT /* outside-tx */ i.id, i.user_id, i.state, i.token_hash, i.token_seed, COALESCE( p.notify_offers, 0 ) AS offers, ( CASE WHEN ' )->add( self::blocked_sql( $hash, $email ) )->add( SqlPart::of( ' THEN 1 ELSE 0 END ) AS blocked FROM %i i LEFT JOIN %i p ON p.user_id = i.user_id WHERE i.invitation_id = %d AND i.email_hash = %s LIMIT 1', Db::table( 'invitees' ), Db::table( 'people' ), $inv->id, $hash ) ) ); if ( null === $row || 1 === (int) $row['blocked'] || OffersConsent::NO->value === (int) $row['offers'] ) { return null; } $id = (int) $row['id']; $seed = (string) ( $row['token_seed'] ?? '' ); $token = Tokens::for_invitee( $id, $seed ); if ( '' !== $token && hash_equals( (string) ( $row['token_hash'] ?? '' ), Tokens::hash( $token ) ) ) { return array( 'id' => $id, 'seed' => $seed, 'created' => false, ); } $seed = Tokens::seed(); $old = (string) ( $row['token_hash'] ?? '' ); $sql = SqlPart::of( 'UPDATE %i SET token_hash = %s, token_seed = %s, token_expires_at = %s, state = %d, sent_version = %d, sent_at = %s WHERE id = %d AND ', Db::table( 'invitees' ), Tokens::hash( Tokens::for_invitee( $id, $seed ) ), $seed, $expiry, InviteeState::SENT->value, $inv->version, $now, $id ); $sql = $sql->add( '' === $old ? SqlPart::of( 'token_hash IS NULL' ) : SqlPart::of( 'token_hash = %s', $old ) ); if ( Db::query( $sql ) <= 0 ) { return null; } return array( 'id' => $id, 'seed' => $seed, 'created' => false, ); } private static function blocked_sql( string $hash, string $email ): SqlPart { global $wpdb; return SqlPart::of( '( ' )->add( Suppressions::exists_sql( SqlPart::of( '%s', $hash ) ) )->add( SqlPart::of( ' OR EXISTS ( SELECT 1 FROM %i sx WHERE sx.email_hash = %s AND sx.user_id = 0 AND sx.state = %d )', Db::table( 'invitees' ), $hash, InviteeState::SUPPRESSED->value ) )->add( SqlPart::of( ' OR EXISTS ( SELECT 1 FROM %i ua2 JOIN %i pa ON pa.user_id = ua2.ID WHERE ua2.user_email = %s AND pa.notify_offers = %d ) )', $wpdb->users, Db::table( 'people' ), $email, OffersConsent::NO->value ) ); } }