__('Invalid security token', '404-solution')), 403); } // Verify user capabilities if (!current_user_can('manage_options')) { wp_send_json_error(array('message' => __('Insufficient permissions', '404-solution')), 403); } // Mark setup as complete update_option(self::OPTION_NAME, gmdate('Y-m-d')); // Response is intentionally minimal; the UI uses a fire-and-forget request. wp_send_json_success(array('message' => '')); } /** * Check if setup wizard should be shown * * @return bool True if wizard should display */ private static function shouldShowWizard() { // Only show if setup hasn't been completed // Existing users upgrading from <3.0.7 have this set via migration in PluginLogic.php $completed = get_option(self::OPTION_NAME, ''); return empty($completed); } /** * Check if current page is a 404 Solution admin page * * @return bool True if on 404 Solution page */ private static function isPluginPage() { if (!is_admin()) { return false; } // Check for the plugin's page parameter $page = isset($_GET['page']) ? sanitize_text_field($_GET['page']) : ''; return $page === 'abj404_solution'; } /** * Handle form submission for setup wizard * @return void */ public static function handleFormSubmission(): void { // Check if this is our form submission if (!isset($_POST['abj404_setup_wizard_action'])) { return; } // Verify nonce with error feedback (Bug #10 fix) if (!isset($_POST['abj404_setup_wizard_nonce']) || !wp_verify_nonce($_POST['abj404_setup_wizard_nonce'], 'abj404_setup_wizard')) { wp_die( esc_html__('Security check failed. Please try again.', '404-solution'), esc_html__('Error', '404-solution'), array('response' => 403, 'back_link' => true) ); } // Verify user capabilities with error feedback (Bug #10 fix) if (!current_user_can('manage_options')) { wp_die( esc_html__('You do not have permission to access this page.', '404-solution'), esc_html__('Error', '404-solution'), array('response' => 403, 'back_link' => true) ); } $action = sanitize_text_field($_POST['abj404_setup_wizard_action']); // All actions mark setup as complete update_option(self::OPTION_NAME, gmdate('Y-m-d')); // If user clicked "Save & Get Started", apply their settings if ($action === 'save') { self::applySettings(); } // Determine redirect destination based on logging choice $q2_answer = isset($_POST['abj404_setup_q2']) ? sanitize_text_field($_POST['abj404_setup_q2']) : 'yes'; $redirect_url = 'options-general.php?page=abj404_solution&setup_complete=1'; // If logging 404s, take them to Captured 404s tab; otherwise Page Redirects if ($q2_answer === 'yes') { $redirect_url .= '&subpage=abj404_captured'; } wp_safe_redirect(admin_url($redirect_url)); exit; } /** Allowed values for Q1 (Bug #13 fix) * @var array */ private static $allowedQ1Values = ['redirect', 'default']; /** Allowed values for Q2 (Bug #13 fix) * @var array */ private static $allowedQ2Values = ['yes', 'no']; /** Allowed values for Q3 * @var array */ private static $allowedQ3Values = ['yes', 'no']; /** * Apply settings from wizard form * @return void */ private static function applySettings(): void { $abj404logic = abj_service('plugin_logic'); $options = $abj404logic->getOptions(); // Question 1: What happens when page not found // Validate against whitelist (Bug #13 fix) $q1_answer = isset($_POST['abj404_setup_q1']) ? sanitize_text_field($_POST['abj404_setup_q1']) : 'redirect'; if (!in_array($q1_answer, self::$allowedQ1Values, true)) { $q1_answer = 'redirect'; // Default to safe value } if ($q1_answer === 'redirect') { // Automatically redirect to similar page when a match is found $options['auto_redirects'] = '1'; $options['auto_cats'] = '1'; $options['auto_tags'] = '1'; } else { // Just show the default 404 page - only use manual redirects $options['auto_redirects'] = '0'; $options['auto_cats'] = '0'; $options['auto_tags'] = '0'; } $options['dest404page'] = '0|' . ABJ404_TYPE_404_DISPLAYED; // Question 2: Log 404s // Validate against whitelist (Bug #13 fix) $q2_answer = isset($_POST['abj404_setup_q2']) ? sanitize_text_field($_POST['abj404_setup_q2']) : 'yes'; if (!in_array($q2_answer, self::$allowedQ2Values, true)) { $q2_answer = 'yes'; // Default to safe value } $options['capture_404'] = ($q2_answer === 'yes') ? '1' : '0'; // Question 3: Email alerts $q3_answer = isset($_POST['abj404_setup_q3']) ? sanitize_text_field($_POST['abj404_setup_q3']) : 'yes'; if (!in_array($q3_answer, self::$allowedQ3Values, true)) { $q3_answer = 'yes'; } if ($q3_answer === 'yes') { $options['admin_notification'] = '50'; $options['admin_notification_frequency'] = 'weekly'; $admin_email = get_option('admin_email'); $options['admin_notification_email'] = is_string($admin_email) ? $admin_email : ''; } // Save options $abj404logic->updateOptions($options); } /** * Enqueue assets on 404 Solution admin pages * * @param string $hook Current admin page hook * @return void */ public static function enqueueAssets(string $hook): void { // Only load on 404 Solution pages if (!self::isPluginPage()) { return; } // Only load if wizard should be shown if (!self::shouldShowWizard()) { return; } // Only for users who can manage options if (!current_user_can('manage_options')) { return; } // Add inline styles for the modal add_action('admin_head', array(__CLASS__, 'outputStyles')); // Output modal HTML in footer add_action('admin_footer', array(__CLASS__, 'outputModalHTML')); // Output JavaScript for dismiss functionality add_action('admin_footer', array(__CLASS__, 'outputScript'), 20); } /** * Output modal CSS styles * @return void */ public static function outputStyles(): void { $css = ABJ_404_Solution_Functions::readFileContents(__DIR__ . '/html/setupWizardStyles.css'); echo ''; } /** * Output the modal HTML structure * @return void */ public static function outputModalHTML(): void { ?>