PluginProbe
404 Solution / 4.3.3
404 Solution v4.3.3
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 / view / View_Settings.php

View_Settings.php in 404 Solution 4.3.3, at includes/view/View_Settings.php

245 lines 12.1 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2
3 if (!defined('ABSPATH')) {
4 exit;
5 }
6
7 /**
8 * ViewTrait_Settings methods.
9 */
10 class ABJ_404_Solution_View_Settings extends ABJ_404_Solution_ViewComponent {
11
12 /**
13 * Load a settings page template fragment from includes/html/ and trim
14 * the trailing newline added by the editor. Matches the pattern used by
15 * View_RedirectsTable::tpl() and View_Logs::tpl() so all admin pages share
16 * one template-load convention.
17 *
18 * @param string $name Filename relative to includes/html/
19 * @return string Raw template body with the trailing newline removed.
20 */
21 private function tpl($name) {
22 $raw = ABJ_404_Solution_FileSystemService::readFileContents(dirname(__DIR__) . '/html/' . $name, false);
23 return rtrim((string)$raw, "\n");
24 }
25
26 /**
27 * Load a settings page template fragment and substitute an associative
28 * map of placeholders. Values are passed through unchanged; callers are
29 * responsible for applying the same esc_*() / wp_create_nonce()
30 * wrapping that the inline echo'd HTML used before externalization.
31 *
32 * @param string $name Filename relative to includes/html/
33 * @param array<string,string> $vars Placeholder map (keys WITH braces, e.g. '{nonce}')
34 * @return string Template body with placeholders substituted.
35 */
36 private function fillTpl($name, array $vars) {
37 return (string)$this->f->str_replace(array_keys($vars), array_values($vars), $this->tpl($name));
38 }
39
40
41 /** @return void */
42 function echoAdminOptionsPage() {
43 global $abj404view;
44 global $abj404viewSuggestions;
45
46 // If globals are not set, use sensible defaults
47 if ($abj404view === null) {
48 $abj404view = $this->view;
49 }
50
51 $options = $this->optionsPresenter->getOptionsWithDefaults();
52
53 // Get the current user's settings mode preference
54 $settingsMode = abj_service('settings_mode_preference')->getMode();
55
56 // if the current URL does not match the chosen menuLocation then redirect to the correct URL
57 $urlParts = parse_url(abj_service('sanitizer')->normalizeUrlString($_SERVER['REQUEST_URI'] ?? ''));
58 $currentURL = (is_array($urlParts) && isset($urlParts['path'])) ? $urlParts['path'] : '';
59 if (is_array($options) && isset($options['menuLocation']) &&
60 $options['menuLocation'] == 'settingsLevel') {
61 if ($this->f->strpos($currentURL, 'options-general.php') !== false) {
62 // the option changed and we're at the wrong URL now, so we redirect to the correct one.
63 abj_service('not_found_response')->forceRedirect(admin_url() . "admin.php?page=" .
64 ABJ404_PP . '&subpage=abj404_options');
65 }
66 } else if ($this->f->strpos($currentURL, 'admin.php') !== false) {
67 // if the current URL has admin.php then the URLs don't match and we need to reload.
68 abj_service('not_found_response')->forceRedirect(admin_url() . "options-general.php?page=" .
69 ABJ404_PP . '&subpage=abj404_options');
70 }
71
72 // Toast notification container
73 $this->adminChrome->echoToastNotification();
74
75 // Options page header: header-row open + h2 + header-controls open,
76 // then the inline mode toggle (which echoes its own block), then the
77 // expand/collapse button and the closing tags. Split into two
78 // templates around the toggle so we do not have to capture its
79 // output buffer (and so View_UI keeps its existing echo-based API).
80 echo $this->fillTpl('viewSettingsHeaderRowOpen.html', array(
81 '{titleOptions}' => esc_html__('Options', '404-solution'),
82 ));
83 $this->adminChrome->echoInlineModeToggle();
84 // Expand/Collapse All button for both Simple and Advanced modes
85 echo $this->fillTpl('viewSettingsExpandCollapseButton.html', array(
86 '{labelExpandAll}' => esc_html__('Expand All', '404-solution'),
87 ));
88 echo $this->tpl('viewSettingsHeaderRowClose.html');
89
90 // Main container
91 echo $this->tpl('viewSettingsContainerOpen.html');
92
93 // Form opening: the data-url and nonce are populated at render time.
94 // wp_create_nonce() and the action URL match the values the prior
95 // inline echo produced, so the rendered output is unchanged.
96 echo $this->fillTpl('viewSettingsFormOpen.html', array(
97 '{data-url}' => 'admin-ajax.php?action=updateOptions',
98 '{nonce}' => wp_create_nonce('abj404UpdateOptions'),
99 ));
100
101 // Loading overlay shown while a settings save is in flight.
102 echo $this->fillTpl('viewSettingsSaveOverlay.html', array(
103 '{labelSaving}' => esc_html__('Saving settings...', '404-solution'),
104 ));
105
106 if ($settingsMode === 'simple') {
107 // Simple Mode: Show streamlined options with card layout
108 $this->simpleSettings->echoSimpleModeOptions($options);
109 } else {
110 // Advanced Mode: Show all card sections with icons
111
112 // Render each section with card structure and icons
113 $contentAutomaticRedirects = $this->settingsSections->getAdminOptionsPageAutoRedirects($options);
114 $this->adminChrome->echoOptionsSection(new ABJ_404_Solution_OptionsSectionView(
115 "abj404-autooptions",
116 "abj404-autooptions",
117 __('Automatic Redirects', '404-solution'),
118 $contentAutomaticRedirects,
119 true,
120 $this->adminChrome->getCardIcon('lightning')
121 ));
122
123 $contentGeneralSettings = $this->settingsSections->getAdminOptionsPageGeneralSettings($options);
124 $this->adminChrome->echoOptionsSection(new ABJ_404_Solution_OptionsSectionView(
125 "abj404-generaloptions",
126 "abj404-generaloptions",
127 __('General Settings', '404-solution'),
128 $contentGeneralSettings,
129 true,
130 $this->adminChrome->getCardIcon('gear')
131 ));
132
133 $contentAdvancedContent = $this->settingsSections->getAdminOptionsPageAdvancedContent($options);
134 $this->adminChrome->echoOptionsSection(new ABJ_404_Solution_OptionsSectionView(
135 "abj404-advanced-content",
136 "abj404-advanced-content",
137 __('Content & URL Filtering', '404-solution'),
138 $contentAdvancedContent,
139 true,
140 $this->adminChrome->getCardIcon('filter')
141 ));
142
143 $contentAdvancedLogging = $this->settingsSections->getAdminOptionsPageAdvancedLogging($options);
144 $this->adminChrome->echoOptionsSection(new ABJ_404_Solution_OptionsSectionView(
145 "abj404-advanced-logging",
146 "abj404-advanced-logging",
147 __('Logging & Privacy', '404-solution'),
148 $contentAdvancedLogging,
149 true,
150 $this->adminChrome->getCardIcon('document')
151 ));
152
153 $diagnosticDataSectionHtml = $this->settingsSections->getAdminOptionsPageDiagnosticData($options);
154 $this->adminChrome->echoOptionsSection(new ABJ_404_Solution_OptionsSectionView(
155 "abj404-diagnostic-data",
156 "abj404-diagnostic-data",
157 __('Your Diagnostic Data', '404-solution'),
158 $diagnosticDataSectionHtml,
159 true,
160 $this->adminChrome->getCardIcon('download')
161 ));
162
163 // "Need help?" support-request section. Anchored at
164 // #abj404-support-request so the plugins-page row action
165 // (which appends that fragment) lands the admin right here
166 // and the JS component auto-opens the modal on arrival.
167 // Lives on the plugin's own Settings page so it does not
168 // violate CLAUDE.md Self-Healing §4 (no support buttons
169 // on screens outside abj404_solution).
170 $supportButton = class_exists('ABJ_404_Solution_SupportRequestButton')
171 ? ABJ_404_Solution_SupportRequestButton::render('settings_debug')
172 : '';
173 $supportSectionHtml = $this->fillTpl('viewSettingsSupportSection.html', array(
174 '{supportDescription}' => esc_html__('Having trouble? Send your debug log to the developer. This sends a one-time diagnostic report (URLs, PHP/WP/DB versions, a debug log excerpt, active plugins, site URL) so we can diagnose the issue without asking you to copy-paste anything.', '404-solution'),
175 '{supportButton}' => $supportButton,
176 ));
177 $this->adminChrome->echoOptionsSection(new ABJ_404_Solution_OptionsSectionView(
178 "abj404-support-request-section",
179 "abj404-support-request-section",
180 __('Need help? Contact the developer', '404-solution'),
181 $supportSectionHtml,
182 true,
183 $this->adminChrome->getCardIcon('lightbulb')
184 ));
185
186 $contentAdvancedSystem = $this->settingsSections->getAdminOptionsPageAdvancedSystem($options);
187 $this->adminChrome->echoOptionsSection(new ABJ_404_Solution_OptionsSectionView(
188 "abj404-advanced-system",
189 "abj404-advanced-system",
190 __('Advanced Configuration', '404-solution'),
191 $contentAdvancedSystem,
192 true,
193 $this->adminChrome->getCardIcon('sliders')
194 ));
195
196 // Only render suggestions section if the suggestions view is available
197 if ($abj404viewSuggestions !== null && is_object($abj404viewSuggestions) && method_exists($abj404viewSuggestions, 'getAdminOptionsPage404Suggestions')) {
198 /** @var ABJ_404_Solution_View_Suggestions $abj404viewSuggestions */
199 $content404PageSuggestions = $abj404viewSuggestions->getAdminOptionsPage404Suggestions($options);
200 $this->adminChrome->echoOptionsSection(new ABJ_404_Solution_OptionsSectionView(
201 "abj404-suggestoptions",
202 "abj404-suggestoptions",
203 __('404 Page Suggestions', '404-solution'),
204 $content404PageSuggestions,
205 true,
206 $this->adminChrome->getCardIcon('lightbulb')
207 ));
208 }
209 }
210
211 echo $this->tpl('viewSettingsFormClose.html');
212
213 // Engine Profiles and GSC are advanced features — hidden in simple mode
214 if ($settingsMode === 'advanced') {
215 // Engine Profiles — outside the main form (uses its own AJAX save)
216 $epHtml = ABJ_404_Solution_FileSystemService::readFileContents(dirname(__DIR__) . '/html/engineProfilesSection.html');
217 $epHtml = $this->f->doNormalReplacements($epHtml);
218 $this->adminChrome->echoOptionsSection(new ABJ_404_Solution_OptionsSectionView('settings-engine-profiles', 'abj404-engineProfiles', __('Engine Profiles', '404-solution'), $epHtml, false, $this->adminChrome->getCardIcon('filter')));
219
220 // Google Search Console — deferred via AJAX so the options page shell
221 // renders immediately and is not blocked by logs/GSC data fetches.
222 $gscPlaceholder = $this->fillTpl('viewSettingsGscDeferred.html', array(
223 '{gscNonce}' => esc_attr(wp_create_nonce('abj404_gsc_deferred')),
224 // allow-em-dash: pre-existing translation string with U+2026 ellipsis in published .po files
225 '{labelLoadingGsc}' => esc_html__('Loading Google Search Console section…', '404-solution'),
226 ));
227 $this->adminChrome->echoOptionsSection(new ABJ_404_Solution_OptionsSectionView(
228 'settings-gsc',
229 'abj404-gsc-section',
230 __('Google Search Console', '404-solution'),
231 $gscPlaceholder,
232 true,
233 $this->adminChrome->getCardIcon('chart')
234 ));
235 }
236
237 // Sticky save bar — outside the form but linked via form="admin-options-page" on the submit button
238 $this->adminChrome->echoStickySaveBar();
239
240 // Close abj404-settings-content and abj404-container
241 echo $this->tpl('viewSettingsContainerClose.html');
242 }
243
244 }
245