From 5fdabbab8000b23e0d3c5d0063de396a01bf4fbc Mon Sep 17 00:00:00 2001 From: Faisal Ahammad Date: Fri, 17 Jul 2026 17:46:01 +0600 Subject: [PATCH] feat: add user scrambler for demoing cloned sites Adds a FakerPress > Scramble admin page that replaces the names and emails of existing users with realistic fake data, so a site owner can demo a clone of their real site without exposing real customer info. - New Scramble_View, modeled on the Settings erase-all flow: gated by a typed confirmation phrase, manage_options capability, and a nonce. - Queries real users with its own WP_User_Query, excluding the current admin, already-scrambled users, and FakerPress-generated users. - Updates first_name, last_name, display_name, nickname and user_email via wp_update_user; leaves user_login and user_pass intact so logins keep working and no password reset emails fire. - Marks each scrambled user with an internal _fakerpress_scrambled meta flag so re-runs are idempotent. Batch cap of 500 users per run. Closes #175 --- src/FakerPress/Admin/View/Factory.php | 1 + src/FakerPress/Admin/View/Scramble_View.php | 237 ++++++++++++++++++++ src/templates/pages/scramble.php | 46 ++++ 3 files changed, 284 insertions(+) create mode 100644 src/FakerPress/Admin/View/Scramble_View.php create mode 100644 src/templates/pages/scramble.php diff --git a/src/FakerPress/Admin/View/Factory.php b/src/FakerPress/Admin/View/Factory.php index b0bfbe2..ef93890 100644 --- a/src/FakerPress/Admin/View/Factory.php +++ b/src/FakerPress/Admin/View/Factory.php @@ -37,6 +37,7 @@ public function get_all(): array { Comment_View::class, Post_View::class, Settings_View::class, + Scramble_View::class, Error_View::class, Changelog_View::class, Term_View::class, diff --git a/src/FakerPress/Admin/View/Scramble_View.php b/src/FakerPress/Admin/View/Scramble_View.php new file mode 100644 index 0000000..f9e866c --- /dev/null +++ b/src/FakerPress/Admin/View/Scramble_View.php @@ -0,0 +1,237 @@ +get_capability_required() ) ) { + return Admin::add_message( __( 'You do not have the permissions to scramble users!', 'fakerpress' ), 'error' ); + } + + $scrambled = 0; + $failed = 0; + + // Never scramble the admin running the action, and skip users FakerPress + // already generated as well as users scrambled on a previous run. + $query = new WP_User_Query( + [ + 'fields' => 'ID', + 'number' => static::BATCH_LIMIT, + 'exclude' => [ get_current_user_id() ], + 'meta_query' => [ + 'relation' => 'AND', + [ + 'key' => static::SCRAMBLED_FLAG, + 'compare' => 'NOT EXISTS', + ], + [ + 'key' => User::get_flag(), + 'compare' => 'NOT EXISTS', + ], + ], + ] + ); + + $user_ids = array_map( 'absint', $query->get_results() ); + $faker = Faker_Factory::create(); + + foreach ( $user_ids as $user_id ) { + $first = $faker->firstName(); + $last = $faker->lastName(); + + $login = strtolower( "{$first}.{$last}" ); + + $update = [ + 'ID' => $user_id, + 'first_name' => $first, + 'last_name' => $last, + 'display_name' => "$first $last", + 'nickname' => $login, + // user_login is left intact on purpose: changing it breaks logins + // and display_name is what shows publicly anyway. + ]; + + // Emails must be unique in wp_users, so retry on a collision. + $result = null; + for ( $attempt = 0; $attempt < static::EMAIL_RETRIES; $attempt++ ) { + $update['user_email'] = $faker->safeEmail(); + + $result = wp_update_user( $update ); + + if ( ! is_wp_error( $result ) ) { + break; + } + } + + if ( is_wp_error( $result ) ) { + ++$failed; + continue; + } + + update_user_meta( $user_id, static::SCRAMBLED_FLAG, 1 ); + ++$scrambled; + } + + $total = count( $user_ids ); + + if ( $scrambled ) { + Admin::add_message( + sprintf( + /* translators: %d: number of users scrambled. */ + _n( 'Successfully scrambled %d user.', 'Successfully scrambled %d users.', $scrambled, 'fakerpress' ), + $scrambled + ), + 'success' + ); + } + + if ( $failed ) { + Admin::add_message( + sprintf( + /* translators: %d: number of users that could not be scrambled. */ + _n( '%d user could not be scrambled (skipped, remains eligible).', '%d users could not be scrambled (skipped, remain eligible).', $failed, 'fakerpress' ), + $failed + ), + 'error' + ); + } + + if ( 0 === $scrambled && 0 === $failed ) { + Admin::add_message( __( 'No users were available to scramble. Users already scrambled or generated by FakerPress are skipped.', 'fakerpress' ), 'info' ); + } + + // Let the admin know if the batch limit was reached and more remain. + if ( $total >= static::BATCH_LIMIT ) { + Admin::add_message( + sprintf( + /* translators: %d: batch limit reached, more users remain. */ + __( 'Reached the batch limit of %d users; run again to scramble any remaining users.', 'fakerpress' ), + static::BATCH_LIMIT + ), + 'info' + ); + } + + return true; + } +} diff --git a/src/templates/pages/scramble.php b/src/templates/pages/scramble.php new file mode 100644 index 0000000..09a55b9 --- /dev/null +++ b/src/templates/pages/scramble.php @@ -0,0 +1,46 @@ + 'scramble_phrase', + 'placeholder' => 'Scramble the real data away!', + ], + [ + 'label' => __( 'Scramble real users', 'fakerpress' ), + 'description' => __( 'Replaces the names and emails of existing users with realistic fake data so you can demo this site without exposing real people. To proceed type "Scramble". This cannot be undone, back up your database first!', 'fakerpress' ), + 'actions' => [ + 'scramble' => __( 'Scramble!', 'fakerpress' ), + ], + ] +); + +?> +
+

get_title() ); ?>

+ +
+ + + + + output( true ); ?> + + +
+
+