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

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

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