PluginProbe
404 Solution / 4.3.0
404 Solution v4.3.0
4.3.5 4.3.4 4.3.3 4.3.2 4.3.1 4.3.0 4.2.0 4.1.19 4.1.18 4.1.17 4.1.16 4.1.15 4.1.13 4.1.12 4.1.11 4.1.10 4.1.9 4.1.8 4.1.7 4.1.6 4.1.5 4.1.4 4.1.3 trunk 2.30.0 All 109 releases
404-solution / includes / core / SetupWizard.php

SetupWizard.php in 404 Solution 4.3.0, at includes/core/SetupWizard.php

200 lines 6.2 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2
3
4 if (!defined('ABSPATH')) {
5 exit;
6 }
7
8 /**
9 * Raised when a setup wizard presentation asset cannot be loaded.
10 */
11 class ABJ_404_Solution_SetupWizardAssetException extends RuntimeException {
12 }
13
14 /**
15 * Setup Wizard for first-time plugin configuration
16 * Shows a welcome modal on first visit to 404 Solution admin pages
17 *
18 * @since 3.0.5
19 */
20 class ABJ_404_Solution_SetupWizard {
21
22 /**
23 * Option name for storing setup completion date
24 */
25 const OPTION_NAME = 'abj404_setup_completed';
26
27 /**
28 * Initialize the setup wizard functionality
29 * @return void
30 */
31 public static function init(): void {
32 // Handle form submission immediately (must run before any output)
33 // This is called early during plugin load, so we check and handle here
34 if (is_admin() && isset($_POST['abj404_setup_wizard_action'])) {
35 // Use admin_init to ensure WordPress is fully loaded for nonce verification
36 add_action('admin_init', array(__CLASS__, 'handleFormSubmission'), 1);
37 }
38
39 // AJAX handler for skip/close (no page reload needed)
40 add_action('wp_ajax_abj404_dismiss_setup_wizard', array(__CLASS__, 'handleAjaxDismiss'));
41
42 // Enqueue assets and output modal on 404 Solution pages
43 add_action('admin_enqueue_scripts', array(__CLASS__, 'enqueueAssets'));
44 }
45
46 /**
47 * Handle AJAX dismiss (skip/close) - no settings changed, just mark complete
48 * @return void
49 */
50 public static function handleAjaxDismiss(): void {
51 abj_service('ajax_security_gate')->requireAdminWithNonce('abj404_setup_wizard');
52
53 ABJ_404_Solution_SetupWizardOptionStore::markCompleteToday();
54
55 // Response is intentionally minimal; the UI uses a fire-and-forget request.
56 wp_send_json_success(array('message' => ''));
57 }
58
59 /**
60 * Check if setup wizard should be shown
61 *
62 * @return bool True if wizard should display
63 */
64 private static function shouldShowWizard() {
65 return !ABJ_404_Solution_SetupWizardOptionStore::isComplete();
66 }
67
68 /**
69 * Check if current page is a 404 Solution admin page
70 *
71 * @return bool True if on 404 Solution page
72 */
73 private static function isPluginPage() {
74 if (!is_admin()) {
75 return false;
76 }
77
78 // Check for the plugin's page parameter
79 $rawPage = $_GET['page'] ?? '';
80 $page = is_scalar($rawPage) ? sanitize_text_field((string)$rawPage) : '';
81 return $page === 'abj404_solution';
82 }
83
84 /**
85 * Handle form submission for setup wizard
86 * @return void
87 */
88 public static function handleFormSubmission(): void {
89 // Check if this is our form submission
90 if (!isset($_POST['abj404_setup_wizard_action'])) {
91 return;
92 }
93
94 // Verify nonce with error feedback (Bug #10 fix)
95 if (!isset($_POST['abj404_setup_wizard_nonce']) ||
96 !wp_verify_nonce($_POST['abj404_setup_wizard_nonce'], 'abj404_setup_wizard')) {
97 wp_die(
98 esc_html__('Security check failed. Please try again.', '404-solution'),
99 esc_html__('Error', '404-solution'),
100 array('response' => 403, 'back_link' => true)
101 );
102 }
103
104 // Verify plugin-admin access with error feedback (Bug #10 fix)
105 if (!ABJ_404_Solution_PluginAdminAccessPolicy::currentUserCanAccessPluginAdmin()) {
106 wp_die(
107 esc_html__('You do not have permission to access this page.', '404-solution'),
108 esc_html__('Error', '404-solution'),
109 array('response' => 403, 'back_link' => true)
110 );
111 }
112
113 $action = self::submittedAction($_POST);
114 $answers = ABJ_404_Solution_SetupWizardAnswerPolicy::answersFromRequest($_POST);
115
116 // All actions mark setup as complete
117 ABJ_404_Solution_SetupWizardOptionStore::markCompleteToday();
118
119 // If user clicked "Save & Get Started", apply their settings
120 if ($action === 'save') {
121 ABJ_404_Solution_SetupWizardOptionStore::saveAnswers($answers);
122 }
123
124 $redirect_url = ABJ_404_Solution_SetupWizardAnswerPolicy::redirectPath($answers);
125 wp_safe_redirect(admin_url($redirect_url));
126 exit;
127 }
128
129 /**
130 * Return a sanitized setup action or an empty non-save action for malformed input.
131 *
132 * @param array<string,mixed> $post Raw POST payload.
133 * @return string
134 */
135 private static function submittedAction(array $post): string {
136 $rawAction = $post['abj404_setup_wizard_action'] ?? '';
137 if (!is_scalar($rawAction)) {
138 return '';
139 }
140
141 return sanitize_text_field((string)$rawAction);
142 }
143
144 /**
145 * Enqueue assets on 404 Solution admin pages
146 *
147 * @param string $hook Current admin page hook
148 * @return void
149 */
150 public static function enqueueAssets(string $hook): void {
151 // Only load on 404 Solution pages
152 if (!self::isPluginPage()) {
153 return;
154 }
155
156 // Only load if wizard should be shown
157 if (!self::shouldShowWizard()) {
158 return;
159 }
160
161 // Only for users authorized for this plugin admin surface.
162 if (!ABJ_404_Solution_PluginAdminAccessPolicy::currentUserCanAccessPluginAdmin()) {
163 return;
164 }
165
166 // Add inline styles for the modal
167 add_action('admin_head', array(__CLASS__, 'outputStyles'));
168
169 // Output modal HTML in footer
170 add_action('admin_footer', array(__CLASS__, 'outputModalHTML'));
171
172 // Output JavaScript for dismiss functionality
173 add_action('admin_footer', array(__CLASS__, 'outputScript'), 20);
174 }
175
176 /**
177 * Output modal CSS styles
178 * @return void
179 */
180 public static function outputStyles(): void {
181 ABJ_404_Solution_SetupWizardPresenter::outputStyles();
182 }
183
184 /**
185 * Output the modal HTML structure
186 * @return void
187 */
188 public static function outputModalHTML(): void {
189 ABJ_404_Solution_SetupWizardPresenter::outputModalHTML();
190 }
191
192 /**
193 * Output JavaScript for dismiss and save functionality
194 * @return void
195 */
196 public static function outputScript(): void {
197 ABJ_404_Solution_SetupWizardPresenter::outputScript();
198 }
199 }
200