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 / admin / SetupWizardPresenter.php

SetupWizardPresenter.php in 404 Solution 4.3.0, at includes/admin/SetupWizardPresenter.php

199 lines 8.4 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2
3 // allow-no-test-found: covered by tests/SetupWizardTest.php through setup wizard admin render entry points.
4
5 if (!defined('ABSPATH')) {
6 exit;
7 }
8
9 /**
10 * Renders setup wizard admin presentation assets.
11 */
12 class ABJ_404_Solution_SetupWizardPresenter {
13
14 /**
15 * Output modal CSS styles.
16 *
17 * @return void
18 */
19 public static function outputStyles(): void {
20 $cssPath = self::filteredAssetPath(
21 'abj404_setup_wizard_stylesheet_path',
22 dirname(__DIR__) . '/html/setupWizardStyles.css'
23 );
24 $wrapperPath = self::filteredAssetPath(
25 'abj404_setup_wizard_styles_template_path',
26 dirname(__DIR__) . '/html/setupWizardStyles.html'
27 );
28
29 echo self::fillTpl($wrapperPath, 'setup wizard style wrapper', array(
30 'css' => self::readSetupWizardAsset($cssPath, 'setup wizard stylesheet'),
31 ));
32 }
33
34 /**
35 * Output the modal HTML structure.
36 *
37 * @return void
38 */
39 public static function outputModalHTML(): void {
40 $templatePath = self::filteredAssetPath(
41 'abj404_setup_wizard_modal_template_path',
42 dirname(__DIR__) . '/html/setupWizardModal.html'
43 );
44 $answers = ABJ_404_Solution_SetupWizardAnswerPolicy::defaultAnswers();
45
46 echo self::fillTpl($templatePath, 'setup wizard modal template', array(
47 'nonce_field' => self::renderNonceField(),
48 'welcome_heading' => esc_html__('Welcome to 404 Solution', '404-solution'),
49 'close_label' => esc_attr__('Close', '404-solution'),
50 'intro_primary' => esc_html__('404 Solution helps you automatically handle 404 errors and broken links on your site.', '404-solution'),
51 'intro_secondary' => esc_html__("Let's configure how it handles missing pages. You can always change these settings later.", '404-solution'),
52 'q1_heading' => esc_html__('When a page is not found, what should happen?', '404-solution'),
53 'q1_redirect_checked' => self::checkedAttribute($answers['q1'], 'redirect'),
54 'q1_redirect_label' => esc_html__('Automatically redirect to similar page (recommended)', '404-solution'),
55 'q1_redirect_desc' => esc_html__('When a match is found, redirect visitors automatically', '404-solution'),
56 'q1_default_checked' => self::checkedAttribute($answers['q1'], 'default'),
57 'q1_default_label' => esc_html__('Just show the default 404 page', '404-solution'),
58 'q1_default_desc' => esc_html__("Use WordPress's standard \"Page not found\" screen. Manual redirects still work.", '404-solution'),
59 'q2_heading' => esc_html__('Log 404 errors for review?', '404-solution'),
60 'q2_yes_checked' => self::checkedAttribute($answers['q2'], 'yes'),
61 'q2_yes_label' => esc_html__('Yes, log 404 errors', '404-solution'),
62 'q2_yes_desc' => esc_html__('Track missing pages so you can create redirects later', '404-solution'),
63 'q2_no_checked' => self::checkedAttribute($answers['q2'], 'no'),
64 'q2_no_label' => esc_html__("No, don't log 404s", '404-solution'),
65 'q2_no_desc' => esc_html__('Only handle manually created redirects', '404-solution'),
66 'q3_heading' => esc_html__('Get email alerts about 404 problems?', '404-solution'),
67 'q3_yes_checked' => self::checkedAttribute($answers['q3'], 'yes'),
68 'q3_yes_label' => esc_html__('Yes, email me a weekly summary (recommended)', '404-solution'),
69 'q3_yes_desc' => esc_html__('Get notified when captured 404 URLs exceed 50', '404-solution'),
70 'q3_no_checked' => self::checkedAttribute($answers['q3'], 'no'),
71 'q3_no_label' => esc_html__("No, I'll check manually", '404-solution'),
72 'q3_no_desc' => esc_html__('You can always enable email alerts later in Options', '404-solution'),
73 'skip_label' => esc_html__('Skip Setup', '404-solution'),
74 'save_label' => esc_html__('Save & Get Started', '404-solution'),
75 ));
76 }
77
78 /**
79 * Render a template with string placeholders.
80 *
81 * @param string $path Absolute template path.
82 * @param string $assetLabel Human-readable asset label for diagnostics.
83 * @param array<string,string> $vars Escaped placeholder values.
84 * @return string Rendered template.
85 */
86 private static function fillTpl(string $path, string $assetLabel, array $vars): string {
87 $template = self::readSetupWizardAsset($path, $assetLabel);
88 $replacements = array();
89 foreach ($vars as $key => $value) {
90 $replacements['{' . $key . '}'] = $value;
91 }
92
93 return strtr($template, $replacements);
94 }
95
96 /**
97 * Read a setup wizard asset and preserve the failed path in diagnostics.
98 *
99 * @param string $path Absolute asset path.
100 * @param string $assetLabel Human-readable asset label.
101 * @return string Asset contents.
102 */
103 private static function readSetupWizardAsset(string $path, string $assetLabel): string {
104 try {
105 return ABJ_404_Solution_FileSystemService::readFileContents($path, false);
106 } catch (Throwable $e) {
107 throw new ABJ_404_Solution_SetupWizardAssetException(
108 'Could not load ' . $assetLabel . ' from ' . $path . ': ' . $e->getMessage(),
109 0,
110 $e
111 );
112 }
113 }
114
115 /**
116 * Apply a string-valued asset path filter.
117 *
118 * @param non-empty-string $hook Filter hook name.
119 * @param string $defaultPath Default absolute path.
120 * @return string Filtered path when valid, otherwise the default.
121 */
122 private static function filteredAssetPath(string $hook, string $defaultPath): string {
123 if (!function_exists('apply_filters')) {
124 return $defaultPath;
125 }
126
127 $filtered = apply_filters($hook, $defaultPath);
128 if (!is_string($filtered) || $filtered === '') {
129 return $defaultPath;
130 }
131
132 return $filtered;
133 }
134
135 /**
136 * Return a leading-space checked attribute for selected radio options.
137 *
138 * @param string $current Current option value.
139 * @param string $candidate Candidate option value.
140 * @return string Attribute fragment.
141 */
142 private static function checkedAttribute(string $current, string $candidate): string {
143 return $current === $candidate ? ' checked' : '';
144 }
145
146 /**
147 * Capture WordPress nonce field output so it can be inserted into the template.
148 *
149 * @return string Nonce input HTML.
150 */
151 private static function renderNonceField(): string {
152 ob_start();
153 wp_nonce_field('abj404_setup_wizard', 'abj404_setup_wizard_nonce');
154 return (string)ob_get_clean();
155 }
156
157 /**
158 * Output JavaScript for dismiss and save functionality.
159 *
160 * @return void
161 */
162 public static function outputScript(): void {
163 $template = ABJ_404_Solution_FileSystemService::readFileContents(
164 dirname(__DIR__) . '/html/setupWizardScript.html',
165 false
166 );
167 echo str_replace(
168 array(
169 '{missing_nonce_message}',
170 '{dismissal_failed_message}',
171 '{dismissal_prefix}',
172 '{dismissal_suffix}',
173 '{network_error_message}',
174 '{close_label}',
175 '{saving_label}',
176 ),
177 array(
178 self::jsonStringLiteral(__('Could not save settings - missing security token. The wizard may appear again on next visit.', '404-solution')),
179 self::jsonStringLiteral(__('Could not save dismissal. The wizard may appear again on next visit.', '404-solution')),
180 self::jsonStringLiteral(__('Could not save dismissal: ', '404-solution')),
181 self::jsonStringLiteral(__(' The wizard may appear again on next visit.', '404-solution')),
182 self::jsonStringLiteral(__('Network error - could not save dismissal. The wizard may appear again on next visit.', '404-solution')),
183 self::jsonStringLiteral(__('Close', '404-solution')),
184 self::jsonStringLiteral(__('Saving...', '404-solution')),
185 ),
186 $template
187 );
188 }
189
190 /**
191 * @param string $text
192 * @return string
193 */
194 private static function jsonStringLiteral(string $text): string {
195 $encoded = wp_json_encode($text);
196 return is_string($encoded) ? $encoded : '""';
197 }
198 }
199