class-settings-page-email.php
1 day ago
class-settings-page-general.php
1 day ago
class-settings-page-new.php
1 day ago
class-settings-page-passkeys.php
1 day ago
class-settings-page-policies-new.php
1 day ago
class-settings-page-policies.php
1 day ago
class-settings-page-render.php
1 day ago
class-settings-page-white-label.php
1 day ago
class-settings-page-white-labeling-new.php
1 day ago
class-setup-wizard-new.php
1 day ago
index.php
1 day ago
class-setup-wizard-new.php
473 lines
| 1 | <?php |
| 2 | /** |
| 3 | * New first-time setup wizard – static class. |
| 4 | * |
| 5 | * Renders a three-screen wizard (Welcome → Steps → Finish) that reuses the |
| 6 | * Settings_Builder components and CSS variables from the new settings UI. |
| 7 | * Step content is rendered by First_Time_Wizard_Steps_New. Data is submitted |
| 8 | * via AJAX so the page never reloads. |
| 9 | * |
| 10 | * @package wp2fa |
| 11 | * @subpackage settings-pages |
| 12 | * @since 4.0.0 |
| 13 | */ |
| 14 | |
| 15 | namespace WP2FA\Admin\SettingsPages; |
| 16 | |
| 17 | use WP2FA\WP2FA; |
| 18 | use WP2FA\Utils\Settings_Utils; |
| 19 | use WP2FA\Admin\Settings_Page; |
| 20 | use WP2FA\Admin\Helpers\User_Helper; |
| 21 | use WP2FA\Admin\Helpers\WP_Helper; |
| 22 | use WP2FA\Admin\Controllers\Settings; |
| 23 | use WP2FA\Admin\Views\First_Time_Wizard_Steps_New; |
| 24 | use WP2FA\Extensions\RoleSettings\Role_Settings_Controller; |
| 25 | use WP2FA\Methods\TOTP; |
| 26 | use WP2FA\Methods\Email; |
| 27 | use WP2FA\Methods\Backup_Codes; |
| 28 | |
| 29 | if ( ! class_exists( '\WP2FA\Admin\SettingsPages\Setup_Wizard_New' ) ) { |
| 30 | |
| 31 | /** |
| 32 | * New first-time wizard rendered as a standalone page. |
| 33 | * |
| 34 | * @since 4.0.0 |
| 35 | */ |
| 36 | class Setup_Wizard_New { |
| 37 | |
| 38 | /** |
| 39 | * AJAX action name. |
| 40 | * |
| 41 | * @since 4.0.0 |
| 42 | */ |
| 43 | private const AJAX_ACTION = 'wp2fa_wizard_save'; |
| 44 | |
| 45 | /** |
| 46 | * Nonce action string. |
| 47 | * |
| 48 | * @since 4.0.0 |
| 49 | */ |
| 50 | private const NONCE_ACTION = 'wp2fa_wizard_new_nonce'; |
| 51 | |
| 52 | /** |
| 53 | * Whether this class should handle the wizard request. |
| 54 | * |
| 55 | * @return bool |
| 56 | * |
| 57 | * @since 4.0.0 |
| 58 | */ |
| 59 | public static function should_render(): bool { |
| 60 | return Settings_Page::is_new_interface_enabled(); |
| 61 | } |
| 62 | |
| 63 | /** |
| 64 | * Render the full wizard page (called from Setup_Wizard::setup_page). |
| 65 | * |
| 66 | * Outputs a complete HTML document and calls exit(). |
| 67 | * |
| 68 | * @since 4.0.0 |
| 69 | */ |
| 70 | public static function render() { |
| 71 | self::render_page(); |
| 72 | exit(); |
| 73 | } |
| 74 | |
| 75 | /** |
| 76 | * Register the AJAX save handler. Called once from WP2FA::init(). |
| 77 | * |
| 78 | * @since 4.0.0 |
| 79 | */ |
| 80 | public static function init() { |
| 81 | \add_action( 'wp_ajax_' . self::AJAX_ACTION, array( __CLASS__, 'ajax_save' ) ); |
| 82 | } |
| 83 | |
| 84 | /** |
| 85 | * Output the full HTML page with three screens. |
| 86 | * |
| 87 | * @since 4.0.0 |
| 88 | */ |
| 89 | private static function render_page() { |
| 90 | // Register dialog assets (normally done on admin_enqueue_scripts, |
| 91 | // but the wizard renders and exits during admin_init). |
| 92 | \WP2FA\Core\register_dialog_assets(); |
| 93 | |
| 94 | // Enqueue assets. |
| 95 | \wp_enqueue_style( |
| 96 | 'wp_2fa_settings_new_css', |
| 97 | WP_2FA_URL . 'includes/assets/css/settings.css', |
| 98 | array(), |
| 99 | WP_2FA_VERSION |
| 100 | ); |
| 101 | |
| 102 | \wp_enqueue_style( |
| 103 | 'wp_2fa_wizard_new_css', |
| 104 | WP_2FA_URL . 'includes/assets/css/wizard-new.css', |
| 105 | array( 'wp_2fa_settings_new_css' ), |
| 106 | WP_2FA_VERSION |
| 107 | ); |
| 108 | |
| 109 | \wp_enqueue_script( 'wp2fa-dialog' ); |
| 110 | \wp_enqueue_style( 'wp2fa-dialog' ); |
| 111 | |
| 112 | \wp_enqueue_script( |
| 113 | 'wp_2fa_wizard_new_js', |
| 114 | WP_2FA_URL . 'includes/assets/js/wizard-new.js', |
| 115 | array( 'wp2fa-dialog' ), |
| 116 | WP_2FA_VERSION, |
| 117 | true |
| 118 | ); |
| 119 | |
| 120 | \wp_enqueue_script( |
| 121 | 'wp_2fa_settings_new', |
| 122 | WP_2FA_URL . 'includes/assets/js/settings-design-logic.js', |
| 123 | array(), |
| 124 | WP_2FA_VERSION, |
| 125 | true |
| 126 | ); |
| 127 | |
| 128 | // "Exclude yourself?" prompt when zero-setup email is toggled on. |
| 129 | \wp_enqueue_script( |
| 130 | 'wp_2fa_zero_email_exclude_self', |
| 131 | WP_2FA_URL . 'extensions/0-setup-email/js/zero-email-exclude-self.js', |
| 132 | array( 'wp_2fa_wizard_new_js' ), |
| 133 | WP_2FA_VERSION, |
| 134 | true |
| 135 | ); |
| 136 | |
| 137 | $user = \wp_get_current_user(); |
| 138 | if ( $user && $user->exists() ) { |
| 139 | \wp_localize_script( |
| 140 | 'wp_2fa_zero_email_exclude_self', |
| 141 | 'wp2faZeroEmailExcludeSelf', |
| 142 | array( |
| 143 | 'currentUserLogin' => \esc_js( $user->user_login ), |
| 144 | 'modalTitle' => \esc_html__( 'Exclude yourself?', 'wp-2fa' ), |
| 145 | 'modalBody' => \esc_html__( 'You are enabling Zero-setup email 2FA. This will enforce 2FA for all users, including your own account, replacing any current 2FA method enabled. That means you will be asked for a one-time code sent by email the next time you log in.', 'wp-2fa' ) |
| 146 | . '<br><br>' |
| 147 | . \esc_html__( "If you don't want this applied to your account, you can exclude yourself from the 2FA policies before continuing.", 'wp-2fa' ), |
| 148 | 'excludeBtnText' => \esc_html__( 'Exclude myself from 2FA policies', 'wp-2fa' ), |
| 149 | 'continueBtnText' => \esc_html__( 'Continue anyway', 'wp-2fa' ), |
| 150 | 'noteText' => \esc_html__( "Note: After closing this prompt, don't forget to save your settings for the changes to take effect.", 'wp-2fa' ), |
| 151 | ) |
| 152 | ); |
| 153 | } |
| 154 | |
| 155 | \wp_localize_script( |
| 156 | 'wp_2fa_wizard_new_js', |
| 157 | 'wp2faWizardNew', |
| 158 | array( |
| 159 | 'ajaxUrl' => \admin_url( 'admin-ajax.php' ), |
| 160 | 'nonce' => \wp_create_nonce( self::NONCE_ACTION ), |
| 161 | 'action' => self::AJAX_ACTION, |
| 162 | 'searchAction' => 'wp2fa_search_policy_items', |
| 163 | 'searchNonce' => \wp_create_nonce( 'wp2fa_save_policies_new_nonce' ), |
| 164 | 'savingText' => \esc_html__( 'Saving…', 'wp-2fa' ), |
| 165 | 'errorText' => \esc_html__( 'An error occurred. Please try again.', 'wp-2fa' ), |
| 166 | 'methodsRequiredText' => \esc_html__( 'Please select at least one 2FA method', 'wp-2fa' ), |
| 167 | 'configure2fa' => \esc_url( Settings::get_setup_page_link() ), |
| 168 | 'settingsUrl' => \esc_url( Settings::get_settings_page_link() ), |
| 169 | 'skipConfirmMessage' => \__( 'If you cancel this wizard, the default plugin settings will be applied. You can always configure the plugin settings and two-factor authentication policies at a later stage from the <b>WP 2FA</b> entry in your WordPress dashboard menu.', 'wp-2fa' ), |
| 170 | 'skipConfirmOk' => \esc_html__( 'OK, close the wizard', 'wp-2fa' ), |
| 171 | 'skipConfirmCancel' => \esc_html__( 'Continue with the wizard', 'wp-2fa' ), |
| 172 | ) |
| 173 | ); |
| 174 | |
| 175 | $skip_url = \esc_url( Settings::get_settings_page_link() ); |
| 176 | $logo_url = \esc_url( WP_2FA_URL . 'dist/images/wizard-logo.png' ); |
| 177 | |
| 178 | $is_excluded = User_Helper::is_excluded( User_Helper::get_user_object()->ID ); |
| 179 | ?> |
| 180 | <!DOCTYPE html> |
| 181 | <html <?php \language_attributes(); ?>> |
| 182 | <head> |
| 183 | <meta charset="utf-8"> |
| 184 | <meta name="viewport" content="width=device-width, initial-scale=1"> |
| 185 | <title><?php \esc_html_e( 'WP 2FA — Setup Wizard', 'wp-2fa' ); ?></title> |
| 186 | <?php |
| 187 | \wp_enqueue_style( 'common' ); |
| 188 | \wp_enqueue_style( 'forms' ); |
| 189 | \wp_enqueue_style( 'buttons' ); |
| 190 | \remove_action( 'admin_print_styles', 'print_emoji_styles' ); |
| 191 | \do_action( 'admin_print_styles' ); |
| 192 | \wp_print_scripts( 'wp2fa-dialog' ); |
| 193 | \wp_print_scripts( 'wp_2fa_settings_new' ); |
| 194 | \wp_print_scripts( 'wp_2fa_wizard_new_js' ); |
| 195 | \wp_print_scripts( 'wp_2fa_zero_email_exclude_self' ); |
| 196 | ?> |
| 197 | </head> |
| 198 | <body class="wp2fa-wizard-new wp-core-ui"> |
| 199 | |
| 200 | <!-- ============ Screen 1: Welcome ============ --> |
| 201 | <div id="wp2fa-wizard-welcome" class="wp2fa-wizard-screen wp2fa-wizard-screen--active"> |
| 202 | <div class="wp2fa-wizard-welcome-card"> |
| 203 | <div class="wp2fa-wizard-logo"> |
| 204 | <img src="<?php echo $logo_url; // phpcs:ignore ?>" alt="WP 2FA"> |
| 205 | <span class="wp2fa-wizard-logo-text"><?php \esc_html_e( 'WP 2FA', 'wp-2fa' ); ?></span> |
| 206 | </div> |
| 207 | <h2><?php \esc_html_e( 'Let\'s get you started', 'wp-2fa' ); ?></h2> |
| 208 | <p><?php \esc_html_e( 'Thank you for installing WP 2FA. This quick setup wizard will guide you through configuring the plugin and setting up two-factor authentication (2FA) for your user account and the users on this website.', 'wp-2fa' ); ?></p> |
| 209 | <div class="wp2fa-wizard-welcome-actions"> |
| 210 | <button type="button" class="button button-primary button-hero js-wizard-start"><?php \esc_html_e( "Let's get started!", 'wp-2fa' ); ?></button> |
| 211 | <a href="<?php echo $skip_url; // phpcs:ignore ?>" class="wp2fa-wizard-skip-link"><?php \esc_html_e( 'Skip Wizard', 'wp-2fa' ); ?></a> |
| 212 | </div> |
| 213 | </div> |
| 214 | </div> |
| 215 | |
| 216 | <!-- ============ Screen 2: Steps ============ --> |
| 217 | <div id="wp2fa-wizard-steps-screen" class="wp2fa-wizard-screen"> |
| 218 | <main class="wp2fa-wizard-main"> |
| 219 | <header class="wp2fa-wizard-header"> |
| 220 | <div class="wp2fa-wizard-header-logo"> |
| 221 | <img src="<?php echo $logo_url; // phpcs:ignore ?>" alt="WP 2FA"> |
| 222 | <span><?php \esc_html_e( 'WP 2FA', 'wp-2fa' ); ?></span> |
| 223 | </div> |
| 224 | <nav class="wp2fa-wizard-nav"> |
| 225 | <ol> |
| 226 | <li data-step="methods" class="is-active"> |
| 227 | <span class="step-indicator"></span> |
| 228 | <span class="step-label"><?php \esc_html_e( '2FA METHODS', 'wp-2fa' ); ?></span> |
| 229 | </li> |
| 230 | <li data-step="alternative"> |
| 231 | <span class="step-indicator"></span> |
| 232 | <span class="step-label"><?php \esc_html_e( 'ALTERNATIVE METHODS', 'wp-2fa' ); ?></span> |
| 233 | </li> |
| 234 | <li data-step="enforcement"> |
| 235 | <span class="step-indicator"></span> |
| 236 | <span class="step-label"><?php \esc_html_e( '2FA POLICY', 'wp-2fa' ); ?></span> |
| 237 | </li> |
| 238 | <li data-step="exclusions"> |
| 239 | <span class="step-indicator"></span> |
| 240 | <span class="step-label"><?php \esc_html_e( 'EXCLUDE USERS', 'wp-2fa' ); ?></span> |
| 241 | </li> |
| 242 | <?php if ( WP_Helper::is_multisite() ) : ?> |
| 243 | <li data-step="exclude-sites"> |
| 244 | <span class="step-indicator"></span> |
| 245 | <span class="step-label"><?php \esc_html_e( 'EXCLUDE SITES', 'wp-2fa' ); ?></span> |
| 246 | </li> |
| 247 | <?php endif; ?> |
| 248 | <li data-step="grace"> |
| 249 | <span class="step-indicator"></span> |
| 250 | <span class="step-label"><?php \esc_html_e( 'SET GRACE PERIOD', 'wp-2fa' ); ?></span> |
| 251 | </li> |
| 252 | </ol> |
| 253 | </nav> |
| 254 | </header> |
| 255 | |
| 256 | <form id="wp2fa-wizard-form" class="wp2fa-wizard-form" autocomplete="off"> |
| 257 | |
| 258 | <div class="wp2fa-wizard-panel" data-panel="methods"> |
| 259 | <?php First_Time_Wizard_Steps_New::step_methods(); ?> |
| 260 | </div> |
| 261 | |
| 262 | <div class="wp2fa-wizard-panel" data-panel="alternative" style="display:none;"> |
| 263 | <?php First_Time_Wizard_Steps_New::step_alternative_methods(); ?> |
| 264 | </div> |
| 265 | |
| 266 | <div class="wp2fa-wizard-panel" data-panel="enforcement" style="display:none;"> |
| 267 | <?php First_Time_Wizard_Steps_New::step_enforcement_policy(); ?> |
| 268 | </div> |
| 269 | |
| 270 | <div class="wp2fa-wizard-panel" data-panel="exclusions" style="display:none;"> |
| 271 | <?php First_Time_Wizard_Steps_New::step_exclude_users(); ?> |
| 272 | </div> |
| 273 | |
| 274 | <?php if ( WP_Helper::is_multisite() ) : ?> |
| 275 | <div class="wp2fa-wizard-panel" data-panel="exclude-sites" style="display:none;"> |
| 276 | <?php First_Time_Wizard_Steps_New::step_exclude_sites(); ?> |
| 277 | </div> |
| 278 | <?php endif; ?> |
| 279 | |
| 280 | <div class="wp2fa-wizard-panel" data-panel="grace" style="display:none;"> |
| 281 | <?php First_Time_Wizard_Steps_New::step_grace_period(); ?> |
| 282 | </div> |
| 283 | |
| 284 | <footer class="wp2fa-wizard-footer"> |
| 285 | <div class="wp2fa-wizard-footer-inner"> |
| 286 | <button type="button" class="button button-primary js-wizard-continue"><?php \esc_html_e( 'Continue', 'wp-2fa' ); ?></button> |
| 287 | <button type="button" class="button button-primary js-wizard-finish" style="display:none;"><?php \esc_html_e( 'Finish Setup', 'wp-2fa' ); ?></button> |
| 288 | <a href="<?php echo $skip_url; // phpcs:ignore ?>" class="wp2fa-wizard-skip-link"><?php \esc_html_e( 'Skip Wizard', 'wp-2fa' ); ?></a> |
| 289 | </div> |
| 290 | </footer> |
| 291 | </form> |
| 292 | </main> |
| 293 | </div> |
| 294 | |
| 295 | <!-- ============ Screen 3: Finish ============ --> |
| 296 | <div id="wp2fa-wizard-finish" class="wp2fa-wizard-screen"> |
| 297 | <div class="wp2fa-wizard-welcome-card"> |
| 298 | <div class="wp2fa-wizard-logo"> |
| 299 | <img src="<?php echo $logo_url; // phpcs:ignore ?>" alt="WP 2FA"> |
| 300 | <span class="wp2fa-wizard-logo-text"><?php \esc_html_e( 'WP 2FA', 'wp-2fa' ); ?></span> |
| 301 | </div> |
| 302 | <div data-finish-excluded <?php echo ( $is_excluded ) ? '' : 'style="display:none;"'; // phpcs:ignore ?> > |
| 303 | <h2><?php \esc_html_e( 'Congratulations.', 'wp-2fa' ); ?></h2> |
| 304 | <p><?php \esc_html_e( 'Great job, the plugin and 2FA policies are now configured. You can always change the plugin settings and 2FA policies at a later stage from the WP 2FA entry in the WordPress menu.', 'wp-2fa' ); ?></p> |
| 305 | <div class="wp2fa-wizard-welcome-actions"> |
| 306 | <a href="<?php echo $skip_url; // phpcs:ignore ?>" class="wp2fa-wizard-skip-link"><?php \esc_html_e( 'Close wizard', 'wp-2fa' ); ?></a> |
| 307 | </div> |
| 308 | </div> |
| 309 | |
| 310 | <div data-finish-normal <?php echo ( $is_excluded ) ? 'style="display:none;"' : ''; // phpcs:ignore ?> > |
| 311 | <h2><?php \esc_html_e( "Congratulations, you're almost there…", 'wp-2fa' ); ?></h2> |
| 312 | <p><?php \esc_html_e( 'Great job, the plugin and 2FA policies are now configured. You can always change the plugin settings and 2FA policies at a later stage from the WP 2FA entry in the WordPress menu.', 'wp-2fa' ); ?></p> |
| 313 | <p><?php \esc_html_e( 'Now you need to configure 2FA for your own user account. You can do this now (recommended) or later.', 'wp-2fa' ); ?></p> |
| 314 | <div class="wp2fa-wizard-welcome-actions"> |
| 315 | <a href="<?php echo \esc_url( Settings::get_setup_page_link() ); ?>" class="button button-primary button-hero"><?php \esc_html_e( 'Configure 2FA Now', 'wp-2fa' ); ?></a> |
| 316 | <a href="<?php echo $skip_url; // phpcs:ignore ?>" class="wp2fa-wizard-skip-link"><?php \esc_html_e( 'Close wizard and configure 2FA Later', 'wp-2fa' ); ?></a> |
| 317 | </div> |
| 318 | </div> |
| 319 | </div> |
| 320 | </div> |
| 321 | |
| 322 | </body> |
| 323 | </html> |
| 324 | <?php |
| 325 | } |
| 326 | |
| 327 | /** |
| 328 | * Handle the wizard save via AJAX. |
| 329 | * |
| 330 | * Collects the policy fields from the form, validates via the existing |
| 331 | * policies validator, merges with existing settings, and marks the |
| 332 | * wizard as finished. |
| 333 | * |
| 334 | * @since 4.0.0 |
| 335 | */ |
| 336 | public static function ajax_save() { |
| 337 | // 1. Nonce. |
| 338 | $nonce = isset( $_POST['nonce'] ) ? \sanitize_text_field( \wp_unslash( $_POST['nonce'] ) ) : ''; |
| 339 | if ( ! \wp_verify_nonce( $nonce, self::NONCE_ACTION ) ) { |
| 340 | \wp_send_json_error( |
| 341 | array( 'message' => \esc_html__( 'Security check failed. Please refresh and try again.', 'wp-2fa' ) ), |
| 342 | 403 |
| 343 | ); |
| 344 | } |
| 345 | |
| 346 | // 2. Capability. |
| 347 | if ( ! \current_user_can( 'manage_options' ) ) { |
| 348 | \wp_send_json_error( |
| 349 | array( 'message' => \esc_html__( 'Permission denied.', 'wp-2fa' ) ), |
| 350 | 403 |
| 351 | ); |
| 352 | } |
| 353 | |
| 354 | // 3. Collect fields. |
| 355 | if ( ! isset( $_POST[ WP_2FA_POLICY_SETTINGS_NAME ] ) || ! \is_array( $_POST[ WP_2FA_POLICY_SETTINGS_NAME ] ) ) { |
| 356 | \wp_send_json_error( |
| 357 | array( 'message' => \esc_html__( 'No data received.', 'wp-2fa' ) ), |
| 358 | 400 |
| 359 | ); |
| 360 | } |
| 361 | |
| 362 | $raw = \wp_unslash( $_POST[ WP_2FA_POLICY_SETTINGS_NAME ] ); // phpcs:ignore WordPress.Security.ValidatedSanitizedInput |
| 363 | |
| 364 | // Ensure method toggles are always present so unchecked boxes can disable methods. |
| 365 | $method_toggle_keys = array( |
| 366 | TOTP::POLICY_SETTINGS_NAME, |
| 367 | Email::POLICY_SETTINGS_NAME, |
| 368 | Backup_Codes::get_settings_name(), |
| 369 | 'enable-email-backup', |
| 370 | 'specify-backup-email', |
| 371 | ); |
| 372 | |
| 373 | $all_methods = \apply_filters( WP_2FA_PREFIX . 'methods_policy_settings_new', array(), null ); |
| 374 | if ( \is_array( $all_methods ) ) { |
| 375 | foreach ( $all_methods as $method ) { |
| 376 | if ( isset( $method['setting'] ) && \is_string( $method['setting'] ) && '' !== $method['setting'] ) { |
| 377 | $method_toggle_keys[] = $method['setting']; |
| 378 | } |
| 379 | } |
| 380 | } |
| 381 | |
| 382 | $method_toggle_keys = \array_values( \array_unique( $method_toggle_keys ) ); |
| 383 | foreach ( $method_toggle_keys as $method_key ) { |
| 384 | if ( ! \array_key_exists( $method_key, $raw ) ) { |
| 385 | $raw[ $method_key ] = ''; |
| 386 | } |
| 387 | } |
| 388 | |
| 389 | // Backup email sub-option must be disabled if the parent method is disabled. |
| 390 | if ( empty( $raw['enable-email-backup'] ) ) { |
| 391 | $raw['specify-backup-email'] = ''; |
| 392 | } |
| 393 | |
| 394 | // Sanitize array fields (multi-select-ajax sends comma-separated strings). |
| 395 | $array_fields = array( 'excluded_users', 'excluded_roles', 'enforced_users', 'enforced_roles', 'included_sites', 'excluded_sites' ); |
| 396 | foreach ( $array_fields as $field ) { |
| 397 | if ( isset( $raw[ $field ] ) ) { |
| 398 | $values = \is_array( $raw[ $field ] ) |
| 399 | ? \array_map( 'sanitize_text_field', \array_map( 'trim', $raw[ $field ] ) ) |
| 400 | : \array_map( 'sanitize_text_field', \array_map( 'trim', explode( ',', $raw[ $field ] ) ) ); |
| 401 | |
| 402 | $raw[ $field ] = \array_values( \array_filter( $values ) ); |
| 403 | } |
| 404 | } |
| 405 | |
| 406 | // Prevent Role_Settings_Controller from wiping role-specific data |
| 407 | // during a global-scope wizard save. |
| 408 | if ( \class_exists( Role_Settings_Controller::class ) ) { |
| 409 | \remove_filter( WP_2FA_PREFIX . 'filter_output_content', array( Role_Settings_Controller::class, 'validate_and_sanitize' ), 10 ); |
| 410 | } |
| 411 | |
| 412 | // 4. Validate via the existing policies validator. |
| 413 | $validated = Settings_Page_Policies::validate_and_sanitize( $raw, 'setup_wizard' ); |
| 414 | |
| 415 | // 5. Merge with existing settings so the wizard doesn't wipe |
| 416 | // values from fields it doesn't display. |
| 417 | $existing = Settings_Utils::get_option( WP_2FA_POLICY_SETTINGS_NAME, array() ); |
| 418 | |
| 419 | if ( \is_array( $validated ) && \is_array( $existing ) ) { |
| 420 | $submitted_keys = \array_keys( $raw ); |
| 421 | |
| 422 | // Some keys are derived/computed from other submitted keys. |
| 423 | $derived_from = array( |
| 424 | 'grace-period-expiry-time' => array( 'grace-period', 'grace-period-denominator' ), |
| 425 | 'custom-user-page-id' => array( 'create-custom-user-page', 'custom-user-page-url' ), |
| 426 | ); |
| 427 | |
| 428 | $merged = $existing; |
| 429 | foreach ( $validated as $key => $value ) { |
| 430 | if ( \in_array( $key, $submitted_keys, true ) ) { |
| 431 | $merged[ $key ] = $value; |
| 432 | } elseif ( isset( $derived_from[ $key ] ) && \array_intersect( $derived_from[ $key ], $submitted_keys ) ) { |
| 433 | $merged[ $key ] = $value; |
| 434 | } elseif ( ! \array_key_exists( $key, $existing ) ) { |
| 435 | $merged[ $key ] = $value; |
| 436 | } |
| 437 | } |
| 438 | |
| 439 | // Guarantee method toggles reflect submitted wizard state even if not present in validator output. |
| 440 | foreach ( $method_toggle_keys as $method_key ) { |
| 441 | if ( \array_key_exists( $method_key, $validated ) ) { |
| 442 | $merged[ $method_key ] = $validated[ $method_key ]; |
| 443 | } else { |
| 444 | $merged[ $method_key ] = ! empty( $raw[ $method_key ] ) ? \sanitize_text_field( (string) $raw[ $method_key ] ) : false; |
| 445 | } |
| 446 | } |
| 447 | } else { |
| 448 | $merged = \is_array( $validated ) ? $validated : $existing; |
| 449 | } |
| 450 | |
| 451 | WP2FA::update_plugin_settings( $merged, false, WP_2FA_POLICY_SETTINGS_NAME ); |
| 452 | |
| 453 | // 6. Mark wizard as finished. |
| 454 | Settings_Utils::delete_option( WP_2FA_PREFIX . 'default_settings_applied' ); |
| 455 | Settings_Utils::delete_option( 'wizard_not_finished' ); |
| 456 | |
| 457 | // 7. Fire extension hook. |
| 458 | \do_action( WP_2FA_PREFIX . 'policies_new_ajax_save', \wp_unslash( $_POST ) ); // phpcs:ignore |
| 459 | |
| 460 | $current_user_id = User_Helper::get_user_object()->ID; |
| 461 | User_Helper::update_user_state( $current_user_id ); |
| 462 | $is_excluded = User_Helper::is_excluded( $current_user_id ); |
| 463 | |
| 464 | \wp_send_json_success( |
| 465 | array( |
| 466 | 'message' => \esc_html__( 'Setup complete.', 'wp-2fa' ), |
| 467 | 'isCurrentUserExcluded' => (bool) $is_excluded, |
| 468 | ) |
| 469 | ); |
| 470 | } |
| 471 | } |
| 472 | } |
| 473 |