PluginProbe
404 Solution / trunk
404 Solution vtrunk
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 trunk, at includes/core/SetupWizard.php

204 lines 6.5 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 = ABJ_404_Solution_SetupWizardOptionStore::OPTION_NAME;
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 $page = ABJ_404_Solution_RequestInputNormalizer::readText($_GET, array('name' => 'page'));
80 return $page === 'abj404_solution';
81 }
82
83 /**
84 * Handle form submission for setup wizard
85 * @return void
86 */
87 public static function handleFormSubmission(): void {
88 // Check if this is our form submission
89 if (!isset($_POST['abj404_setup_wizard_action'])) {
90 return;
91 }
92
93 // Verify nonce with error feedback (Bug #10 fix)
94 if (!isset($_POST['abj404_setup_wizard_nonce']) ||
95 !wp_verify_nonce($_POST['abj404_setup_wizard_nonce'], 'abj404_setup_wizard')) {
96 wp_die(
97 esc_html__('Security check failed. Please try again.', '404-solution'),
98 esc_html__('Error', '404-solution'),
99 array('response' => 403, 'back_link' => true)
100 );
101 }
102
103 // Verify plugin-admin access with error feedback (Bug #10 fix)
104 if (!ABJ_404_Solution_PluginAdminAccessPolicy::currentUserCanAccessPluginAdmin()) {
105 wp_die(
106 esc_html__('You do not have permission to access this page.', '404-solution'),
107 esc_html__('Error', '404-solution'),
108 array('response' => 403, 'back_link' => true)
109 );
110 }
111
112 $action = self::submittedAction($_POST);
113 $answers = ABJ_404_Solution_SetupWizardAnswerPolicy::answersFromRequest($_POST);
114
115 // All actions mark setup as complete
116 ABJ_404_Solution_SetupWizardOptionStore::markCompleteToday();
117
118 // If user clicked "Save & Get Started", apply their settings
119 if ($action === 'save') {
120 $options = ABJ_404_Solution_SetupWizardAnswerPolicy::applyToOptions(
121 ABJ_404_Solution_SetupWizardOptionStore::loadPluginOptions(),
122 $answers,
123 ABJ_404_Solution_SetupWizardOptionStore::adminEmail()
124 );
125 ABJ_404_Solution_SetupWizardOptionStore::savePluginOptions($options);
126 }
127
128 $redirect_url = ABJ_404_Solution_SetupWizardAnswerPolicy::redirectPath($answers);
129 wp_safe_redirect(admin_url($redirect_url));
130 exit;
131 }
132
133 /**
134 * Return a sanitized setup action or an empty non-save action for malformed input.
135 *
136 * @param array<string,mixed> $post Raw POST payload.
137 * @return string
138 */
139 private static function submittedAction(array $post): string {
140 $rawAction = $post['abj404_setup_wizard_action'] ?? '';
141 if (!is_scalar($rawAction)) {
142 return '';
143 }
144
145 return sanitize_text_field((string)$rawAction);
146 }
147
148 /**
149 * Enqueue assets on 404 Solution admin pages
150 *
151 * @param string $hook Current admin page hook
152 * @return void
153 */
154 public static function enqueueAssets(string $hook): void {
155 // Only load on 404 Solution pages
156 if (!self::isPluginPage()) {
157 return;
158 }
159
160 // Only load if wizard should be shown
161 if (!self::shouldShowWizard()) {
162 return;
163 }
164
165 // Only for users authorized for this plugin admin surface.
166 if (!ABJ_404_Solution_PluginAdminAccessPolicy::currentUserCanAccessPluginAdmin()) {
167 return;
168 }
169
170 // Add inline styles for the modal
171 add_action('admin_head', array(__CLASS__, 'outputStyles'));
172
173 // Output modal HTML in footer
174 add_action('admin_footer', array(__CLASS__, 'outputModalHTML'));
175
176 // Output JavaScript for dismiss functionality
177 add_action('admin_footer', array(__CLASS__, 'outputScript'), 20);
178 }
179
180 /**
181 * Output modal CSS styles
182 * @return void
183 */
184 public static function outputStyles(): void {
185 ABJ_404_Solution_SetupWizardPresenter::outputStyles();
186 }
187
188 /**
189 * Output the modal HTML structure
190 * @return void
191 */
192 public static function outputModalHTML(): void {
193 ABJ_404_Solution_SetupWizardPresenter::outputModalHTML();
194 }
195
196 /**
197 * Output JavaScript for dismiss and save functionality
198 * @return void
199 */
200 public static function outputScript(): void {
201 ABJ_404_Solution_SetupWizardPresenter::outputScript();
202 }
203 }
204