From 9ba7839af8db3f107f8484929215869a89228999 Mon Sep 17 00:00:00 2001 From: ronchambers Date: Tue, 25 Feb 2025 12:14:59 -0800 Subject: [PATCH 1/2] Feat: Guest Contributors Create function --- .../class-guest-contributor-role.php | 162 ++++++++++++++++++ 1 file changed, 162 insertions(+) diff --git a/includes/plugins/co-authors-plus/class-guest-contributor-role.php b/includes/plugins/co-authors-plus/class-guest-contributor-role.php index 99da0a3967..2854ee9e23 100644 --- a/includes/plugins/co-authors-plus/class-guest-contributor-role.php +++ b/includes/plugins/co-authors-plus/class-guest-contributor-role.php @@ -271,6 +271,47 @@ public static function get_dummy_email_address( $user_or_name ) { return $user_or_name->user_login . '@' . $email_domain; } + /** + * Generate a unique dummy email address with a random suffix. + * + * @param string $display_name The user display name. + * @return string|WP_Error + */ + public static function generate_dummy_email_with_random( $display_name ): string|WP_Error { + + // sanitize input. + $sanitized_display_name = sanitize_title( sanitize_user( trim( $display_name ), true ) ); + if ( empty( $sanitized_display_name ) ) { + return new WP_Error( 'Sanitization created a blank string.' ); + } + + // hard code email column char length from db. + $db_max_chars = 100; + + // initial dummy email suffix. + $email_suffix = '@' . Guest_Contributor_Role::get_dummy_email_domain(); + + // stop infinite loops. + $attempts = 0; + + do { + + if( ++$attempts > 9999 ) { + // stop...this could cause an ininite loop. + return new WP_Error( 'Might be in an infinite loop.' ); + } + + // try a different random suffix on each loop + $suffix = '-' . rand( 11111, 99999 ) . $email_suffix; + + // make room if needed for the random suffix, then add it to the string. + $email_out = mb_substr( $sanitized_display_name, 0, $db_max_chars - mb_strlen( $suffix ) ) . $suffix; + + } while( \email_exists( $email_out ) ); + + return $email_out; + } + /** * Filters user validation to allow empty emails for guest authors * @@ -333,6 +374,44 @@ public static function generate_username( $display_name ) { return $username; } + /** + * Generate a unique username with a random suffix. + * + * @param string $display_name The user display name. + * @return string|WP_Error + */ + public static function generate_username_with_random( $display_name ): string|WP_Error { + + // sanitize in the same way wp_insert_user would. + $sanitized_display_name = sanitize_title( sanitize_user( trim( $display_name ), true ) ); + if ( empty( $sanitized_display_name ) ) { + return new WP_Error( 'Sanitization created a blank string.' ); + } + + // hard code char length from db. + $db_max_chars = 60; + + // stop infinite loops. + $attempts = 0; + + do { + + if( ++$attempts > 9999 ) { + // stop...this could cause an ininite loop. + return new WP_Error( 'Might be in an infinite loop.' ); + } + + // try a different random suffix on each loop + $suffix = '-' . rand( 11111, 99999 ); + + // make room in the username if needed for the random suffix, then add it to the string. + $username_out = mb_substr( $sanitized_display_name, 0, $db_max_chars - mb_strlen( $suffix ) ) . $suffix; + + } while( \username_exists( $username_out ) ); + + return $username_out; + } + /** * Enqueues the JS that modifies the user profile and user creation forms. * @@ -549,5 +628,88 @@ public static function should_display_author_email( $value ) { } return $value; } + + /** + * Create a Guest Contributor by Display Name. + * + * @param string $display_name The Display Name of the new user. + * @param bool $force Force the creation even if existing user(s) found. + * @return int|array|WP_error Inserted user ID, array of existing user ID(s), or WP_Error. + */ + public static function create_guest_contributor_by_display_name( $display_name, $force = false ): int|array|WP_Error { + + // Trim value for better matching (since the DB value will have already been trimmed). + $display_name = trim( $display_name ); + + // Check for core bug when display name is > 250: https://core.trac.wordpress.org/ticket/53109 + if ( empty( $display_name) || mb_strlen( $display_name ) > 250 ) { + return new WP_Error( 'Display Name must be between 1 and 250 characters.' ); + } + + // If we're not forcing user creation, check for existing user(s) - could be multiple. + if ( ! $force ) { + // SQL match is case-insensitive, and also if display name starts/ends with "*" + // like " ** Special Person ** " then sql will wildcard match. + // To fix both these issues, exact match will be performed in foreach below. + $get_users = get_users( array( + 'search' => $display_name, + 'search_columns' => array( 'display_name' ), + 'role' => self::CONTRIBUTOR_NO_EDIT_ROLE_NAME, + 'fields' => array( 'ID', 'display_name' ), + 'orderby' => 'ID', + ) ); + // Get ids of users with exact match on display name. + $user_matches = array_map( + fn( $user ) => $user->ID, + array_filter( $get_users, fn( $user ) => $user->display_name === $display_name ) + ); + if ( ! empty( $user_matches ) ) { + return $user_matches; + } + } + + // New user data. + $userdata = [ + 'display_name' => $display_name, + 'nickname' => $display_name, // set value so it doesn't get set to user_login. + 'user_pass' => wp_generate_password(), // generate else wp will write to debug.log. + 'role' => Guest_Contributor_Role::CONTRIBUTOR_NO_EDIT_ROLE_NAME, + ]; + + // Cut down on insert errors and increase security by getting a unique user login with random value. + $userdata['user_login'] = self::generate_username_with_random( $display_name ); + if ( is_wp_error( $userdata['user_login'] ) ) { + return new WP_Error( "Function generate_username_with_random() failed with wp_error: " . json_encode( $userdata['user_login'] ) ); + } + + // Cut down on insert errors and increase security by getting a unique user email with random value. + $userdata['user_email'] = self::generate_dummy_email_with_random( $display_name ); + if ( is_wp_error( $userdata['user_email'] ) ) { + return new WP_Error( "Function generate_dummy_email_with_random() failed with wp_error: " . json_encode( $userdata['user_email'] ) ); + } + + // Set user_nicename ourselves for better security and nicer urls otherwise it will be created from user_login. + // If duplicate already in db, wordpress will add -2, -3, etc. + $userdata['user_nicename'] = mb_substr( sanitize_title( sanitize_user( $display_name, true ) ), 0, 50 ); + if ( empty( $userdata['user_nicename'] ) ) { + // this can happen if sanitization produced empty string. Ex: $display_name = " 
" + return new WP_Error( "User nicename can not be blank." ); + } + + // Insert. + $user_id = wp_insert_user( $userdata ); + + // Fail on any errors. + if ( is_wp_error( $user_id ) ) { + return new WP_Error( "wp_insert_user failed with wp_error: " . json_encode( $user_id ) ); + } + // Fail if wp_insert_user didn't return a positive int (return of 0 can happen on other failures...) + // core bug that results in 0 integer value: https://core.trac.wordpress.org/ticket/53109 + if ( ! is_int( $user_id ) || ! ( $user_id > 0 ) ) { + return new WP_Error( "wp_insert_user returned a non-positive integer: " . json_encode( $user_id ) ); + } + + return $user_id; + } } Guest_Contributor_Role::initialize(); From b304da016dcbe6fb37085487bdf42735239fa0de Mon Sep 17 00:00:00 2001 From: root Date: Sat, 1 Mar 2025 18:29:41 +0000 Subject: [PATCH 2/2] Add Guest Contributors: testing --- includes/cli/class-co-authors-plus.php | 31 ++++++++++++++++++++++++++ includes/cli/class-initializer.php | 1 + 2 files changed, 32 insertions(+) diff --git a/includes/cli/class-co-authors-plus.php b/includes/cli/class-co-authors-plus.php index aaf1c227f6..3cdc960d57 100644 --- a/includes/cli/class-co-authors-plus.php +++ b/includes/cli/class-co-authors-plus.php @@ -124,6 +124,37 @@ public function backfill_non_editing_contributor( $args, $assoc_args ) { WP_CLI::line( '' ); } + /** + * Create Guest Contributor( Non-Editing Contributor role). + * + * ## OPTIONS + * + * [--live] + * : Run the command in live mode to create user, otherwise default is a dry run. + * + * @param array $args Positional arguments. + * @param array $assoc_args Assoc arguments. + * @return void + */ + public function create_guest_contributor( $args, $assoc_args ) { + WP_CLI::line( '' ); + + // move this stuff to phpunit... + $display_name = 'ron asdf'; + // $display_name = "* Ron Chambers *"; + // $display_name = str_repeat( "José Sànch", 25); // strlen = 12 chars. + // $display_name = ' 
'; // how does sanitize handle this? + // $display_name = ' 
'; // how does sanitize handle this? + + var_dump( \Newspack\Guest_Contributor_Role::create_guest_contributor_by_display_name( $display_name ) ); + // var_dump( \Newspack\Guest_Contributor_Role::generate_username_with_random( $display_name ) ); + // var_dump( \Newspack\Guest_Contributor_Role::generate_dummy_email_with_random( $display_name ) ); + + // assign_authors_to_post + + WP_CLI::line( '' ); + } + /** * Migrate unlinked guest authors to regular users. * diff --git a/includes/cli/class-initializer.php b/includes/cli/class-initializer.php index eaa8dad84d..448f9b09e4 100644 --- a/includes/cli/class-initializer.php +++ b/includes/cli/class-initializer.php @@ -76,6 +76,7 @@ public static function register_comands() { WP_CLI::add_command( 'newspack migrate-co-authors-guest-authors', [ 'Newspack\CLI\Co_Authors_Plus', 'migrate_guest_authors' ] ); WP_CLI::add_command( 'newspack backfill-non-editing-contributors', [ 'Newspack\CLI\Co_Authors_Plus', 'backfill_non_editing_contributor' ] ); + WP_CLI::add_command( 'newspack create-guest-contributor', [ 'Newspack\CLI\Co_Authors_Plus', 'create_guest_contributor' ] ); WP_CLI::add_command( 'newspack migrate-expired-subscriptions', [ 'Newspack\CLI\WooCommerce_Subscriptions', 'migrate_expired_subscriptions' ] ); } }