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 / view / View_UI.php

View_UI.php in 404 Solution 4.3.0, at includes/view/View_UI.php

202 lines 8.7 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 * View UI component. Owns the admin-page chrome (wrap open, header tabs,
9 * message notices, regex auto-promote notice, postbox / options-section /
10 * sticky save bar / toast / restore-defaults modal / mode toggles).
11 *
12 * HTML for every rendered fragment lives in includes/html/*.html with
13 * placeholder substitution at render time. This class is the PHP side of
14 * that contract: it loads the template, substitutes values, and echoes.
15 * Do not add raw inline HTML strings here. New surface gets a new template.
16 */
17 class ABJ_404_Solution_View_UI extends ABJ_404_Solution_ViewComponent {
18
19 /**
20 * Load a template file from includes/html/ and trim its trailing newline.
21 * Passes $appendExtraData=false to suppress the BEGIN/END HTML comment
22 * markers that readFileContents() adds by default, since these templates
23 * are spliced into tight inline contexts where stray comments would alter
24 * the rendered chrome.
25 *
26 * @param string $name Template filename under includes/html/
27 * @return string Template contents, no trailing newline.
28 */
29 private function tpl(string $name): string {
30 $raw = ABJ_404_Solution_FileSystemService::readFileContents(dirname(__DIR__) . '/html/' . $name, false);
31 return rtrim((string)$raw, "\n");
32 }
33
34 /**
35 * Load a template and substitute an associative array of placeholders.
36 * Keys are placeholder tokens (without braces). Substitution is plain
37 * string replacement; callers must apply esc_html / esc_attr / esc_url
38 * BEFORE passing the value in.
39 *
40 * @param string $name Template filename under includes/html/
41 * @param array<string,string> $vars Placeholder name (without braces) to substituted value.
42 * @return string Filled template, no trailing newline.
43 */
44 private function fillTpl(string $name, array $vars): string {
45 $tpl = $this->tpl($name);
46 $search = array();
47 $replace = array();
48 foreach ($vars as $k => $v) {
49 $search[] = '{' . $k . '}';
50 $replace[] = $v;
51 }
52 // Fall back to native str_replace when the Functions helper isn't wired
53 // (e.g. reflection tests using newInstanceWithoutConstructor()).
54 if (is_object($this->f)) {
55 return (string)$this->f->str_replace($search, $replace, $tpl);
56 }
57 return (string)str_replace($search, $replace, $tpl);
58 }
59
60 /**
61 * Render a native-shape error notice (`.notice.notice-error`) on a
62 * plugin admin page with the "Send debug log to developer" support
63 * affordance as a small inline text link inside the notice body.
64 * Shape matches wp-admin/css/common.css:1441-1580 (white background,
65 * 4px red left border, 13px near-black text, no bold heading wall,
66 * no green CTA button. See docs/ui-aesthetic/UI_AESTHETIC.md).
67 *
68 * The link is the same anchor mountAll() attaches to via attachLink(),
69 * so a JS regression that breaks the modal still leaves a clickable
70 * fallback link to the Settings support section. Trigger sources are
71 * allowlisted server-side by ALLOWED_TRIGGER_SOURCES; an off-list
72 * slug renders a link whose AJAX 400s on click.
73 *
74 * Only call this from screens that live under the plugin's own pages
75 * (CLAUDE.md Self-Healing §4 bans support buttons on generic wp-admin
76 * notices).
77 *
78 * @param string $messageHtml Pre-escaped/-allowed inner HTML
79 * for the <p> inside the notice.
80 * Callers escape per their own
81 * needs (esc_html / wp_kses_post).
82 * @param string $triggeredFrom One of
83 * ABJ_404_Solution_Ajax_SupportRequest::ALLOWED_TRIGGER_SOURCES.
84 * @param string|null $contextSummary Optional one-line summary shown
85 * in the support-request modal.
86 * @return string Notice HTML, ready to echo.
87 */
88 public static function renderErrorNoticeWithSupportButton(string $messageHtml,
89 string $triggeredFrom, ?string $contextSummary = null): string {
90 $linkHtml = class_exists('ABJ_404_Solution_SupportRequestButton')
91 ? ABJ_404_Solution_SupportRequestButton::renderInlineLink($triggeredFrom, $contextSummary)
92 : '';
93 $tpl = dirname(__DIR__) . '/html/ajaxErrorNoticeWithSupportLink.html';
94 $template = is_readable($tpl) ? (string)@file_get_contents($tpl) : '';
95 return str_replace(['{message}', '{support_link}'], [$messageHtml, $linkHtml], $template);
96 }
97
98 /** Get the text to notify the user when some URLs have been captured and need attention.
99 * @param int $captured the number of captured URLs
100 * @return string html
101 */
102 function getDashboardNotificationCaptured($captured) {
103 /* Translators: %s is the number of captured 404 URLs. */
104 $capturedMessage = sprintf( _n( 'There is <a>%s captured 404 URL</a> that needs to be processed.',
105 'There are <a>%s captured 404 URLs</a> to be processed.',
106 $captured, '404-solution'), $captured);
107 $capturedMessage = $this->f->str_replace("<a>",
108 "<a href=\"options-general.php?page=" . ABJ404_PP . "&subpage=abj404_captured\" >",
109 $capturedMessage);
110 $capturedMessage = $this->f->str_replace("</a>", "</a>", $capturedMessage);
111
112 return $this->fillTpl('dashboardNotificationCaptured.html', array(
113 'plugin_name' => PLUGIN_NAME,
114 'message' => $capturedMessage,
115 ));
116 }
117
118 /** Display the chosen admin page.
119 * @param string $action
120 * @param string $sub
121 * @param string $message
122 * @return void
123 */
124 function echoChosenAdminTab($action, $sub, $message) {
125 global $abj404view;
126
127 // If globals are not set, use sensible defaults
128 if ($abj404view === null) {
129 $abj404view = $this->view;
130 }
131
132 // Deal With Page Tabs
133 if ($sub == "") {
134 $sub = $this->f->strtolower($this->shared->viewGetPostOrGetSanitize('subpage'));
135 }
136 if ($sub == "") {
137 $sub = 'abj404_redirects';
138 $this->logger->debugMessage('No tab selected. Displaying the "redirects" tab.');
139 }
140
141 // Check if we're returning from a successful redirect update
142 $updated = $this->shared->viewGetPostOrGetSanitize('updated');
143 if ($updated == '1') {
144 $message .= __('Redirect Information Updated Successfully!', '404-solution');
145 }
146
147 $this->logger->debugMessage("Displaying sub page: " . esc_html($sub == '' ? '(none)' : $sub));
148
149 $abj404view->outputAdminHeaderTabs($sub, $message);
150
151 $abj404action = $this->shared->viewGetPostOrGetSanitize('abj404action');
152 if ($this->isEditRedirectScreen($action, $sub, $abj404action)) {
153 $abj404view->echoAdminEditRedirectPage();
154 } else if ($sub == 'abj404_redirects') {
155 $abj404view->echoAdminRedirectsPage();
156 } else if ($sub == 'abj404_captured') {
157 $abj404view->echoAdminCapturedURLsPage();
158 } else if ($sub == "abj404_options") {
159 $abj404view->echoAdminOptionsPage();
160 } else if ($sub == 'abj404_logs') {
161 $abj404view->echoAdminLogsPage();
162 } else if ($sub == 'abj404_stats') {
163 $abj404view->outputAdminStatsPage();
164 } else if ($sub == 'abj404_tools') {
165 $abj404view->echoAdminToolsPage();
166 } else if ($sub == 'abj404_debugfile') {
167 $abj404view->echoAdminDebugFile();
168 } else {
169 $this->logger->debugMessage('No tab selected. Displaying the "redirects" tab.');
170 $abj404view->echoAdminRedirectsPage();
171 }
172
173 $abj404view->echoAdminFooter();
174 }
175
176 /**
177 * Identify all request shapes that should render the edit redirect form.
178 *
179 * @param string $action Current request action.
180 * @param string $sub Current plugin subpage.
181 * @param string $bulkAction Current list-table bulk action.
182 * @return bool
183 */
184 private function isEditRedirectScreen(string $action, string $sub, string $bulkAction): bool {
185 if ($action == 'editRedirect' || $bulkAction == 'editRedirect' || $sub == 'abj404_edit') {
186 return true;
187 }
188
189 $requestAction = $action !== '' ? $action : (string)$this->shared->viewGetPostOrGetSanitize('action');
190 if ($requestAction !== 'edit') {
191 return false;
192 }
193
194 if (!in_array($sub, array('abj404_redirects', 'abj404_captured'), true)) {
195 return false;
196 }
197
198 return isset($_GET['id']) || isset($_POST['id']) || isset($_GET['idnum']) || isset($_POST['idnum']);
199 }
200
201 }
202