| 1 |
<?php |
| 2 |
|
| 3 |
if (!defined('ABSPATH')) { |
| 4 |
exit; |
| 5 |
} |
| 6 |
|
| 7 |
/** |
| 8 |
* ViewTrait_UI methods. |
| 9 |
*/ |
| 10 |
trait ViewTrait_UI { |
| 11 |
|
| 12 |
/** |
| 13 |
* Render an error notice (notice notice-error) on a plugin admin page |
| 14 |
* with the "Send debug log to developer" support button appended inside |
| 15 |
* the same notice block. This is the wrapper for the existing |
| 16 |
* <div class="notice notice-error"> pattern across the plugin so every |
| 17 |
* user-facing error notice on a plugin screen ships a one-click way to |
| 18 |
* report the failure. |
| 19 |
* |
| 20 |
* The button mount div renders after the message paragraph so the JS |
| 21 |
* component bootstraps it on DOMContentLoaded. The trigger source is |
| 22 |
* allowlisted server-side by Ajax_SupportRequest::ALLOWED_TRIGGER_SOURCES; |
| 23 |
* passing a slug not in that list will render a button that 400s on |
| 24 |
* click. The integration test pins the matching set. |
| 25 |
* |
| 26 |
* Only call this from screens that live under the plugin's own pages |
| 27 |
* (CLAUDE.md Self-Healing §4 bans support buttons on generic wp-admin |
| 28 |
* notices). |
| 29 |
* |
| 30 |
* @param string $messageHtml Pre-escaped/-allowed inner HTML |
| 31 |
* for the <p> inside the notice. |
| 32 |
* Callers escape per their own |
| 33 |
* needs (esc_html / wp_kses_post). |
| 34 |
* @param string $triggeredFrom One of |
| 35 |
* ABJ_404_Solution_Ajax_SupportRequest::ALLOWED_TRIGGER_SOURCES. |
| 36 |
* @param string|null $contextSummary Optional one-line summary shown |
| 37 |
* in the support-request modal. |
| 38 |
* @return string Notice HTML, ready to echo. |
| 39 |
*/ |
| 40 |
public static function renderErrorNoticeWithSupportButton(string $messageHtml, |
| 41 |
string $triggeredFrom, ?string $contextSummary = null): string { |
| 42 |
$buttonHtml = class_exists('ABJ_404_Solution_SupportRequestButton') |
| 43 |
? ABJ_404_Solution_SupportRequestButton::render($triggeredFrom, $contextSummary) |
| 44 |
: ''; |
| 45 |
return '<div class="notice notice-error"><p>' . $messageHtml . '</p>' |
| 46 |
. $buttonHtml |
| 47 |
. '</div>'; |
| 48 |
} |
| 49 |
|
| 50 |
/** Get the text to notify the user when some URLs have been captured and need attention. |
| 51 |
* @param int $captured the number of captured URLs |
| 52 |
* @return string html |
| 53 |
*/ |
| 54 |
function getDashboardNotificationCaptured($captured) { |
| 55 |
/* Translators: %s is the number of captured 404 URLs. */ |
| 56 |
$capturedMessage = sprintf( _n( 'There is <a>%s captured 404 URL</a> that needs to be processed.', |
| 57 |
'There are <a>%s captured 404 URLs</a> to be processed.', |
| 58 |
$captured, '404-solution'), $captured); |
| 59 |
$capturedMessage = $this->f->str_replace("<a>", |
| 60 |
"<a href=\"options-general.php?page=" . ABJ404_PP . "&subpage=abj404_captured\" >", |
| 61 |
$capturedMessage); |
| 62 |
$capturedMessage = $this->f->str_replace("</a>", "</a>", $capturedMessage); |
| 63 |
|
| 64 |
return '<div class="notice notice-info"><p><strong>' . PLUGIN_NAME . |
| 65 |
":</strong> " . $capturedMessage . "</p></div>"; |
| 66 |
} |
| 67 |
|
| 68 |
/** Do an action like trash/delete/ignore/edit and display a page like stats/logs/redirects/options. |
| 69 |
* @return void |
| 70 |
*/ |
| 71 |
static function handleMainAdminPageActionAndDisplay() { |
| 72 |
global $abj404view; |
| 73 |
$instance = self::getInstance(); |
| 74 |
|
| 75 |
try { |
| 76 |
$action = $instance->viewGetPostOrGetSanitize('action'); |
| 77 |
|
| 78 |
if (!is_admin() || !$instance->logic->userIsPluginAdmin()) { |
| 79 |
$instance->logger->logUserCapabilities("handleMainAdminPageActionAndDisplay (" . |
| 80 |
esc_html($action == '' ? '(none)' : $action) . ")"); |
| 81 |
|
| 82 |
echo '<div class="wrap">'; |
| 83 |
echo '<h1>' . esc_html(PLUGIN_NAME) . '</h1>'; |
| 84 |
$permMessage = '<strong>' . esc_html__('Permission denied.', '404-solution') . '</strong> ' |
| 85 |
. esc_html__('Your user account does not have permission to access this page.', '404-solution') |
| 86 |
. '</p><p>' |
| 87 |
. esc_html__('Please verify that your WordPress role has the', '404-solution') . ' ' |
| 88 |
. '<code>manage_options</code> ' . esc_html__('capability.', '404-solution') . ' ' |
| 89 |
. esc_html__('If you have a security plugin installed, it may be restricting access to this page.', '404-solution'); |
| 90 |
$subpageForContext = (string)$instance->viewGetPostOrGetSanitize('subpage'); |
| 91 |
$triggerForPerm = ($subpageForContext === 'abj404_captured') |
| 92 |
? 'captured_404s_page' : 'redirects_page'; |
| 93 |
echo self::renderErrorNoticeWithSupportButton( |
| 94 |
$permMessage, |
| 95 |
$triggerForPerm, |
| 96 |
'Permission denied on plugin admin page (action=' . |
| 97 |
($action == '' ? '(none)' : $action) . ')' |
| 98 |
); |
| 99 |
echo '</div>'; |
| 100 |
return; |
| 101 |
} |
| 102 |
|
| 103 |
$sub = ""; |
| 104 |
|
| 105 |
// -------------------------------------------------------------------- |
| 106 |
// Handle Post Actions |
| 107 |
$instance->logger->debugMessage("Processing request for action: " . |
| 108 |
esc_html($action == '' ? '(none)' : $action)); |
| 109 |
|
| 110 |
// this should really not pass things by reference so it can be more object oriented (encapsulation etc). |
| 111 |
$message = ""; |
| 112 |
$message .= $instance->logic->handlePluginAction($action, $sub); |
| 113 |
$message .= $instance->logic->hanldeTrashAction(); |
| 114 |
$message .= $instance->logic->handleDeleteAction(); |
| 115 |
$message .= $instance->logic->handleIgnoreAction(); |
| 116 |
$message .= $instance->logic->handleLaterAction(); |
| 117 |
$message .= $instance->logic->handleActionEdit($sub, $action); |
| 118 |
$message .= $instance->logic->handleActionImportRedirects(); |
| 119 |
$instance->logic->handleActionChangeItemsPerRow(); |
| 120 |
$message .= $instance->logic->handleActionImportFile(); |
| 121 |
|
| 122 |
if ($action !== '' && $message !== '') { |
| 123 |
$instance->logger->debugMessage("Admin action completed: " . |
| 124 |
esc_html($action) . " => " . esc_html(substr($message, 0, 200))); |
| 125 |
} |
| 126 |
|
| 127 |
// -------------------------------------------------------------------- |
| 128 |
// Output the correct page. |
| 129 |
$abj404view->echoChosenAdminTab($action, $sub, $message); |
| 130 |
|
| 131 |
} catch (\Throwable $e) { |
| 132 |
$encodedEx = json_encode($e); |
| 133 |
$encodedContext = is_string($encodedEx) ? stripcslashes(wp_kses_post($encodedEx)) : ''; |
| 134 |
$instance->logger->errorMessage( |
| 135 |
"Caught exception (" . get_class($e) . "): " . $e->getMessage() |
| 136 |
. ($encodedContext !== '' ? " | context=" . $encodedContext : '') |
| 137 |
); |
| 138 |
$subpageForContext = (string)$instance->viewGetPostOrGetSanitize('subpage'); |
| 139 |
$triggerForRenderError = ($subpageForContext === 'abj404_captured') |
| 140 |
? 'captured_404s_page' : 'redirects_page'; |
| 141 |
$renderErrorMessage = '<strong>404 Solution:</strong> An error occurred while rendering this page.</p>' |
| 142 |
. '<details><summary>Show error details</summary>' |
| 143 |
. '<pre style="white-space:pre-wrap;word-break:break-all;max-width:100%;margin:6px 0;">' |
| 144 |
. esc_html($e->getMessage() . "\n" . $e->getTraceAsString()) |
| 145 |
. '</pre>' |
| 146 |
. '</details>' |
| 147 |
. '<p>'; |
| 148 |
echo '<div class="wrap">'; |
| 149 |
echo self::renderErrorNoticeWithSupportButton( |
| 150 |
$renderErrorMessage, |
| 151 |
$triggerForRenderError, |
| 152 |
'Render error: ' . substr($e->getMessage(), 0, 200) |
| 153 |
); |
| 154 |
echo '</div>'; |
| 155 |
} |
| 156 |
} |
| 157 |
|
| 158 |
/** Display the chosen admin page. |
| 159 |
* @param string $action |
| 160 |
* @param string $sub |
| 161 |
* @param string $message |
| 162 |
* @return void |
| 163 |
*/ |
| 164 |
function echoChosenAdminTab($action, $sub, $message) { |
| 165 |
global $abj404view; |
| 166 |
|
| 167 |
// If globals are not set, use sensible defaults |
| 168 |
if ($abj404view === null) { |
| 169 |
$abj404view = $this; |
| 170 |
} |
| 171 |
|
| 172 |
// Deal With Page Tabs |
| 173 |
if ($sub == "") { |
| 174 |
$sub = $this->f->strtolower($this->viewGetPostOrGetSanitize('subpage')); |
| 175 |
} |
| 176 |
if ($sub == "") { |
| 177 |
$sub = 'abj404_redirects'; |
| 178 |
$this->logger->debugMessage('No tab selected. Displaying the "redirects" tab.'); |
| 179 |
} |
| 180 |
|
| 181 |
// Check if we're returning from a successful redirect update |
| 182 |
$updated = $this->viewGetPostOrGetSanitize('updated'); |
| 183 |
if ($updated == '1') { |
| 184 |
$message .= __('Redirect Information Updated Successfully!', '404-solution'); |
| 185 |
} |
| 186 |
|
| 187 |
$this->logger->debugMessage("Displaying sub page: " . esc_html($sub == '' ? '(none)' : $sub)); |
| 188 |
|
| 189 |
$abj404view->outputAdminHeaderTabs($sub, $message); |
| 190 |
|
| 191 |
$abj404action = $this->viewGetPostOrGetSanitize('abj404action'); |
| 192 |
if (($action == 'editRedirect') || ($abj404action == 'editRedirect') || ($sub == 'abj404_edit')) { |
| 193 |
$abj404view->echoAdminEditRedirectPage(); |
| 194 |
} else if ($sub == 'abj404_redirects') { |
| 195 |
$abj404view->echoAdminRedirectsPage(); |
| 196 |
} else if ($sub == 'abj404_captured') { |
| 197 |
$abj404view->echoAdminCapturedURLsPage(); |
| 198 |
} else if ($sub == "abj404_options") { |
| 199 |
$abj404view->echoAdminOptionsPage(); |
| 200 |
} else if ($sub == 'abj404_logs') { |
| 201 |
$abj404view->echoAdminLogsPage(); |
| 202 |
} else if ($sub == 'abj404_stats') { |
| 203 |
$abj404view->outputAdminStatsPage(); |
| 204 |
} else if ($sub == 'abj404_tools') { |
| 205 |
$abj404view->echoAdminToolsPage(); |
| 206 |
} else if ($sub == 'abj404_debugfile') { |
| 207 |
$abj404view->echoAdminDebugFile(); |
| 208 |
} else { |
| 209 |
$this->logger->debugMessage('No tab selected. Displaying the "redirects" tab.'); |
| 210 |
$abj404view->echoAdminRedirectsPage(); |
| 211 |
} |
| 212 |
|
| 213 |
$abj404view->echoAdminFooter(); |
| 214 |
} |
| 215 |
|
| 216 |
/** |
| 217 |
* Echo the text that appears at the bottom of each admin page. |
| 218 |
* @return void |
| 219 |
*/ |
| 220 |
function echoAdminFooter() { |
| 221 |
// read the html content. |
| 222 |
$html = ABJ_404_Solution_Functions::readFileContents(__DIR__ . "/html/adminFooter.html"); |
| 223 |
$html = $this->f->str_replace('{JAPANESE_FLASHCARDS_URL}', ABJ404_FC_URL, $html); |
| 224 |
$html = $this->f->str_replace( |
| 225 |
'{ABJ404_VIEW_FRESHNESS}', |
| 226 |
$this->renderViewFreshnessLabel(), |
| 227 |
$html |
| 228 |
); |
| 229 |
|
| 230 |
// constants and translations. |
| 231 |
$html = $this->f->doNormalReplacements($html); |
| 232 |
echo $html; |
| 233 |
} |
| 234 |
|
| 235 |
/** |
| 236 |
* Render a short, human-readable "how long ago was the view_done cache |
| 237 |
* last successfully built" label for the admin footer. Returns one of: |
| 238 |
* - "n/a" when never built or freshness option cleared (post-invalidate); |
| 239 |
* - "Xs" when 0 to 59 seconds old; |
| 240 |
* - "Xm" when 1 to 59 minutes old; |
| 241 |
* - "Xh" when 1 to 23 hours old; |
| 242 |
* - "Xd" when a day or more. |
| 243 |
* |
| 244 |
* The view_done snapshot is what the Redirects / Captured / Logs tables |
| 245 |
* read from; if this label drifts upward into hours when the freshness |
| 246 |
* TTL is 120s, the auto-rebuild path is failing or being blocked. |
| 247 |
* |
| 248 |
* @return string |
| 249 |
*/ |
| 250 |
private function renderViewFreshnessLabel(): string { |
| 251 |
if (!is_object($this->viewBuildOrchestrator) || !method_exists($this->viewBuildOrchestrator, 'getViewDoneBuiltAtTimestamp')) { |
| 252 |
return 'n/a'; |
| 253 |
} |
| 254 |
// Defensive: a unit-test mock may throw BadMethodCallException |
| 255 |
// when called without an expectation; any future implementation |
| 256 |
// could also throw on a transient read failure. The freshness |
| 257 |
// label is a footer cosmetic. Treat any failure as n/a so it |
| 258 |
// never blocks the page render. |
| 259 |
try { |
| 260 |
$builtAt = (int)$this->viewBuildOrchestrator->getViewDoneBuiltAtTimestamp(); |
| 261 |
} catch (\Throwable $e) { // allow-silent-catch: freshness label is a footer cosmetic; DAO stub failures must never block page render |
| 262 |
return 'n/a'; |
| 263 |
} |
| 264 |
if ($builtAt <= 0) { |
| 265 |
return 'n/a'; |
| 266 |
} |
| 267 |
$age = time() - $builtAt; |
| 268 |
if ($age < 0) { |
| 269 |
return 'n/a'; // clock skew; do not surface a negative age |
| 270 |
} |
| 271 |
if ($age < 60) { return $age . 's'; } |
| 272 |
if ($age < 3600) { return intval($age / 60) . 'm'; } |
| 273 |
if ($age < 86400) { return intval($age / 3600) . 'h'; } |
| 274 |
return intval($age / 86400) . 'd'; |
| 275 |
} |
| 276 |
|
| 277 |
/** Output the tabs at the top of the plugin page. |
| 278 |
* @param string $sub |
| 279 |
* @param string $message |
| 280 |
* @return void |
| 281 |
*/ |
| 282 |
function outputAdminHeaderTabs($sub = 'list', $message = '') { |
| 283 |
ABJ_404_Solution_WPNotices::echoAdminNotices(); |
| 284 |
|
| 285 |
echo "<div class=\"wrap\" style='z-index: 1;position: relative;'>"; |
| 286 |
|
| 287 |
// Post-setup welcome banner |
| 288 |
if (isset($_GET['setup_complete']) && $_GET['setup_complete'] === '1') { |
| 289 |
$options = $this->logic->getOptions(); |
| 290 |
$auto = !empty($options['auto_redirects']) && $options['auto_redirects'] !== '0'; |
| 291 |
$notify = !empty($options['admin_notification']) && $options['admin_notification'] !== '0'; |
| 292 |
|
| 293 |
echo '<div class="notice notice-success is-dismissible"><p>'; |
| 294 |
echo '<strong>' . esc_html__("You're all set!", '404-solution') . '</strong> '; |
| 295 |
if ($auto && $notify) { |
| 296 |
echo esc_html__('404 Solution is now monitoring your site. When visitors hit broken links, they will be automatically redirected. We will email you if something needs attention.', '404-solution'); |
| 297 |
} elseif ($auto) { |
| 298 |
echo esc_html__('404 Solution is now monitoring your site. When visitors hit broken links, they will be automatically redirected.', '404-solution'); |
| 299 |
} elseif ($notify) { |
| 300 |
echo esc_html__('404 Solution is now monitoring your site. We will email you if captured 404 URLs need attention.', '404-solution'); |
| 301 |
} else { |
| 302 |
echo esc_html__('404 Solution is now monitoring your site. You can create manual redirects anytime from the Page Redirects tab.', '404-solution'); |
| 303 |
} |
| 304 |
echo '</p></div>' . "\n"; |
| 305 |
} |
| 306 |
|
| 307 |
if ($message != "") { |
| 308 |
$allowed_tags = array( |
| 309 |
'br' => array(), |
| 310 |
'em' => array(), |
| 311 |
'strong' => array(), |
| 312 |
); |
| 313 |
|
| 314 |
if (($this->f->strlen($message) >= 6) && ($this->f->substr($this->f->strtolower($message), 0, 6) == 'error:')) { |
| 315 |
$cssClasses = 'notice notice-error'; |
| 316 |
} else { |
| 317 |
$cssClasses = 'notice notice-success'; |
| 318 |
} |
| 319 |
|
| 320 |
echo '<div class="' . $cssClasses . '"><p>' . wp_kses($message, $allowed_tags) . "</p></div>\n"; |
| 321 |
} |
| 322 |
|
| 323 |
// Regex auto-promote notice. Rendered as a separate info-level |
| 324 |
// banner because it carries an action (Undo) the user can choose |
| 325 |
// not to take. Kept distinct from the success message so the |
| 326 |
// visual hierarchy reads "save succeeded; by the way, we |
| 327 |
// promoted to regex and you can undo it" rather than mixing the |
| 328 |
// two. |
| 329 |
if (class_exists('ABJ_404_Solution_RegexAutoPromote')) { |
| 330 |
$regexNotice = ABJ_404_Solution_RegexAutoPromote::readNotice(); |
| 331 |
if (is_array($regexNotice) && $regexNotice['redirect_id'] > 0) { |
| 332 |
$this->renderRegexAutoPromoteNotice($regexNotice); |
| 333 |
} |
| 334 |
} |
| 335 |
|
| 336 |
$isSimpleMode = $this->logic->getSettingsMode() === 'simple'; |
| 337 |
|
| 338 |
echo '<nav class="abj404-tab-navigation" role="tablist">'; |
| 339 |
|
| 340 |
// Page Redirects tab |
| 341 |
$class = ($sub == 'abj404_redirects') ? "active" : ""; |
| 342 |
echo '<a href="?page=' . ABJ404_PP . '&subpage=abj404_redirects" title="' . esc_attr__('Page Redirects', '404-solution') . '" class="abj404-tab ' . $class . '" role="tab">'; |
| 343 |
echo '<span class="dashicons dashicons-randomize"></span>'; |
| 344 |
echo '<span class="abj404-tab-text">' . esc_html__('Page Redirects', '404-solution') . '</span>'; |
| 345 |
echo '</a>'; |
| 346 |
|
| 347 |
// Captured 404 URLs tab |
| 348 |
$class = ($sub == 'abj404_captured') ? "active" : ""; |
| 349 |
echo '<a href="?page=' . ABJ404_PP . '&subpage=abj404_captured" title="' . esc_attr__('Captured 404 URLs', '404-solution') . '" class="abj404-tab ' . $class . '" role="tab">'; |
| 350 |
echo '<span class="dashicons dashicons-search"></span>'; |
| 351 |
echo '<span class="abj404-tab-text">' . esc_html__('Captured 404s', '404-solution') . '</span>'; |
| 352 |
echo '</a>'; |
| 353 |
|
| 354 |
if (!$isSimpleMode) { |
| 355 |
// Logs tab (hidden in Simple mode) |
| 356 |
$class = ($sub == 'abj404_logs') ? "active" : ""; |
| 357 |
echo '<a href="?page=' . ABJ404_PP . '&subpage=abj404_logs" title="' . esc_attr__('Redirect & Capture Logs', '404-solution') . '" class="abj404-tab ' . $class . '" role="tab">'; |
| 358 |
echo '<span class="dashicons dashicons-list-view"></span>'; |
| 359 |
echo '<span class="abj404-tab-text">' . esc_html__('Logs', '404-solution') . '</span>'; |
| 360 |
echo '</a>'; |
| 361 |
|
| 362 |
// Stats tab (hidden in Simple mode) |
| 363 |
$class = ($sub == 'abj404_stats') ? "active" : ""; |
| 364 |
echo '<a href="?page=' . ABJ404_PP . '&subpage=abj404_stats" title="' . esc_attr__('Stats', '404-solution') . '" class="abj404-tab ' . $class . '" role="tab">'; |
| 365 |
echo '<span class="dashicons dashicons-chart-bar"></span>'; |
| 366 |
echo '<span class="abj404-tab-text">' . esc_html__('Stats', '404-solution') . '</span>'; |
| 367 |
echo '</a>'; |
| 368 |
} |
| 369 |
|
| 370 |
// Tools tab |
| 371 |
$class = ($sub == 'abj404_tools') ? "active" : ""; |
| 372 |
echo '<a href="?page=' . ABJ404_PP . '&subpage=abj404_tools" title="' . esc_attr__('Tools', '404-solution') . '" class="abj404-tab ' . $class . '" role="tab">'; |
| 373 |
echo '<span class="dashicons dashicons-admin-tools"></span>'; |
| 374 |
echo '<span class="abj404-tab-text">' . esc_html__('Tools', '404-solution') . '</span>'; |
| 375 |
echo '</a>'; |
| 376 |
|
| 377 |
// Options tab |
| 378 |
$class = ($sub == "abj404_options") ? "active" : ""; |
| 379 |
echo '<a href="?page=' . ABJ404_PP . '&subpage=abj404_options" title="' . esc_attr__('Options', '404-solution') . '" class="abj404-tab ' . $class . '" role="tab">'; |
| 380 |
echo '<span class="dashicons dashicons-admin-generic"></span>'; |
| 381 |
echo '<span class="abj404-tab-text">' . esc_html__('Options', '404-solution') . '</span>'; |
| 382 |
echo '</a>'; |
| 383 |
|
| 384 |
// Plugin branding - right side of tabs |
| 385 |
echo '<span class="abj404-tab-branding">' . esc_html(PLUGIN_NAME) . '</span>'; |
| 386 |
|
| 387 |
echo '</nav>'; |
| 388 |
} |
| 389 |
|
| 390 |
/** |
| 391 |
* Render the "Detected as a regex pattern" notice with [Edit] and |
| 392 |
* [Undo] links. The notice is shown once per save that triggered |
| 393 |
* server-side regex auto-promotion (admin posted a URL with |
| 394 |
* unambiguous regex metachars but did not check the "Treat as regex" |
| 395 |
* box). The [Undo] link restores the row to status=MANUAL with the |
| 396 |
* original from_url; [Edit] jumps straight to the redirect edit |
| 397 |
* page for further adjustments. |
| 398 |
* |
| 399 |
* @param array{redirect_id: int, original_url: string, new_url: string, url_rewritten: bool, created_at: int} $notice |
| 400 |
* @return void |
| 401 |
*/ |
| 402 |
private function renderRegexAutoPromoteNotice(array $notice) { |
| 403 |
$redirectId = (int)$notice['redirect_id']; |
| 404 |
$originalUrl = (string)$notice['original_url']; |
| 405 |
$newUrl = (string)$notice['new_url']; |
| 406 |
$urlRewritten = !empty($notice['url_rewritten']); |
| 407 |
|
| 408 |
$editUrl = admin_url('admin.php?page=' . ABJ404_PP . '&subpage=abj404_edit&id=' . $redirectId); |
| 409 |
$undoBase = admin_url('admin.php?page=' . ABJ404_PP . '&subpage=abj404_redirects&action=undoRegexAutoPromote'); |
| 410 |
$undoUrl = wp_nonce_url($undoBase, 'abj404undoRegexAutoPromote'); |
| 411 |
|
| 412 |
if ($urlRewritten) { |
| 413 |
$message = sprintf( |
| 414 |
/* translators: %1$s = original URL as typed by the admin; %2$s = the URL after glob-to-regex fixup (e.g. /sales/* becomes /sales/.*) */ |
| 415 |
__('Detected as a regex pattern. Stored "%1$s" as "%2$s".', '404-solution'), |
| 416 |
$originalUrl, |
| 417 |
$newUrl |
| 418 |
); |
| 419 |
} else { |
| 420 |
$message = sprintf( |
| 421 |
/* translators: %s = the URL stored unchanged with Regex status */ |
| 422 |
__('Detected as a regex pattern. Stored "%s" with Regex status.', '404-solution'), |
| 423 |
$originalUrl |
| 424 |
); |
| 425 |
} |
| 426 |
|
| 427 |
echo '<div class="notice notice-info abj404-regex-autopromote-notice"><p>'; |
| 428 |
echo '<strong>' . esc_html__('404 Solution:', '404-solution') . '</strong> '; |
| 429 |
echo esc_html($message); |
| 430 |
echo ' <a href="' . esc_url($editUrl) . '">' . esc_html__('Edit', '404-solution') . '</a>'; |
| 431 |
echo ' | '; |
| 432 |
echo '<a href="' . esc_url($undoUrl) . '" onclick="return confirm(\'' . esc_js(__('Undo regex auto-promotion and restore Manual status?', '404-solution')) . '\');">'; |
| 433 |
echo esc_html__('Undo', '404-solution') . '</a>'; |
| 434 |
echo '</p></div>' . "\n"; |
| 435 |
} |
| 436 |
|
| 437 |
/** This outputs a box with a title and some content in it. |
| 438 |
* It's used on the Stats, Options and Tools page (for example). |
| 439 |
* @param int|string $id |
| 440 |
* @param string $title |
| 441 |
* @param string $content |
| 442 |
* @return void |
| 443 |
*/ |
| 444 |
function echoPostBox($id, $title, $content) { |
| 445 |
echo "<div id=\"" . esc_attr((string)$id) . "\" class=\"postbox\">"; |
| 446 |
echo "<h3 class=\"\" ><span>" . esc_html($title) . "</span></h3>"; |
| 447 |
echo "<div class=\"inside\">" . $content /* Can't escape here, as contains forms */ . "</div>"; |
| 448 |
echo "</div>"; |
| 449 |
} |
| 450 |
|
| 451 |
/** |
| 452 |
* Echo an accordion section using card-based layout |
| 453 |
* @param string $sectionId The section identifier |
| 454 |
* @param string $postboxId The ID for the postbox |
| 455 |
* @param string $title The title of the section |
| 456 |
* @param string $content The content to display |
| 457 |
* @param bool $initiallyVisible Whether the card starts expanded (default: false) |
| 458 |
* @param string $icon The SVG icon for the card header (optional) |
| 459 |
* @param string $badge Optional info badge text |
| 460 |
* @return void |
| 461 |
*/ |
| 462 |
function echoOptionsSection($sectionId, $postboxId, $title, $content, $initiallyVisible = false, $icon = '', $badge = '') { |
| 463 |
$expandedClass = $initiallyVisible ? ' expanded' : ''; |
| 464 |
|
| 465 |
echo "<div class=\"abj404-card" . esc_attr($expandedClass) . "\" data-card=\"" . esc_attr($sectionId) . "\" id=\"" . esc_attr($postboxId) . "\">"; |
| 466 |
echo "<div class=\"abj404-card-header\" onclick=\"abj404ToggleCard(this)\" role=\"button\" aria-expanded=\"" . ($initiallyVisible ? 'true' : 'false') . "\" tabindex=\"0\">"; |
| 467 |
echo "<h2 class=\"abj404-card-title\">"; |
| 468 |
if ($icon) { |
| 469 |
echo $icon; // Icon is pre-sanitized SVG |
| 470 |
} |
| 471 |
echo esc_html($title); |
| 472 |
if ($badge) { |
| 473 |
echo "<span class=\"abj404-info-badge\">" . esc_html($badge) . "</span>"; |
| 474 |
} |
| 475 |
echo "</h2>"; |
| 476 |
echo "<svg class=\"abj404-collapse-icon\" width=\"20\" height=\"20\" fill=\"none\" stroke=\"currentColor\" viewBox=\"0 0 24 24\">"; |
| 477 |
echo "<path stroke-linecap=\"round\" stroke-linejoin=\"round\" stroke-width=\"2\" d=\"M19 9l-7 7-7-7\"></path>"; |
| 478 |
echo "</svg>"; |
| 479 |
echo "</div>"; |
| 480 |
echo "<div class=\"abj404-card-content\">"; |
| 481 |
echo $content; /* Can't escape here, as contains forms */ |
| 482 |
echo "</div>"; |
| 483 |
echo "</div>"; |
| 484 |
} |
| 485 |
|
| 486 |
/** |
| 487 |
* Get SVG icon for a section |
| 488 |
* @param string $iconName The icon name |
| 489 |
* @return string The SVG icon markup |
| 490 |
*/ |
| 491 |
function getCardIcon($iconName) { |
| 492 |
$icons = array( |
| 493 |
'lightning' => '<svg class="abj404-card-icon" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M13 10V3L4 14h7v7l9-11h-7z"></path></svg>', |
| 494 |
'gear' => '<svg class="abj404-card-icon" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M10.325 4.317c.426-1.756 2.924-1.756 3.35 0a1.724 1.724 0 002.573 1.066c1.543-.94 3.31.826 2.37 2.37a1.724 1.724 0 001.065 2.572c1.756.426 1.756 2.924 0 3.35a1.724 1.724 0 00-1.066 2.573c.94 1.543-.826 3.31-2.37 2.37a1.724 1.724 0 00-2.572 1.065c-.426 1.756-2.924 1.756-3.35 0a1.724 1.724 0 00-2.573-1.066c-1.543.94-3.31-.826-2.37-2.37a1.724 1.724 0 00-1.065-2.572c-1.756-.426-1.756-2.924 0-3.35a1.724 1.724 0 001.066-2.573c-.94-1.543.826-3.31 2.37-2.37.996.608 2.296.07 2.572-1.065z"></path><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M15 12a3 3 0 11-6 0 3 3 0 016 0z"></path></svg>', |
| 495 |
'filter' => '<svg class="abj404-card-icon" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M3 4a1 1 0 011-1h16a1 1 0 011 1v2.586a1 1 0 01-.293.707l-6.414 6.414a1 1 0 00-.293.707V17l-4 4v-6.586a1 1 0 00-.293-.707L3.293 7.293A1 1 0 013 6.586V4z"></path></svg>', |
| 496 |
'document' => '<svg class="abj404-card-icon" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9 12h6m-6 4h6m2 5H7a2 2 0 01-2-2V5a2 2 0 012-2h5.586a1 1 0 01.707.293l5.414 5.414a1 1 0 01.293.707V19a2 2 0 01-2 2z"></path></svg>', |
| 497 |
'sliders' => '<svg class="abj404-card-icon" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 6V4m0 2a2 2 0 100 4m0-4a2 2 0 110 4m-6 8a2 2 0 100-4m0 4a2 2 0 110-4m0 4v2m0-6V4m6 6v10m6-2a2 2 0 100-4m0 4a2 2 0 110-4m0 4v2m0-6V4"></path></svg>', |
| 498 |
'lightbulb' => '<svg class="abj404-card-icon" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9.663 17h4.673M12 3v1m6.364 1.636l-.707.707M21 12h-1M4 12H3m3.343-5.657l-.707-.707m2.828 9.9a5 5 0 117.072 0l-.548.547A3.374 3.374 0 0014 18.469V19a2 2 0 11-4 0v-.531c0-.895-.356-1.754-.988-2.386l-.548-.547z"></path></svg>', |
| 499 |
// Stats page icons |
| 500 |
'chart' => '<svg class="abj404-card-icon" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9 19v-6a2 2 0 00-2-2H5a2 2 0 00-2 2v6a2 2 0 002 2h2a2 2 0 002-2zm0 0V9a2 2 0 012-2h2a2 2 0 012 2v10m-6 0a2 2 0 002 2h2a2 2 0 002-2m0 0V5a2 2 0 012-2h2a2 2 0 012 2v14a2 2 0 01-2 2h-2a2 2 0 01-2-2z"></path></svg>', |
| 501 |
'warning' => '<svg class="abj404-card-icon" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 9v2m0 4h.01m-6.938 4h13.856c1.54 0 2.502-1.667 1.732-3L13.732 4c-.77-1.333-2.694-1.333-3.464 0L3.34 16c-.77 1.333.192 3 1.732 3z"></path></svg>', |
| 502 |
'clock' => '<svg class="abj404-card-icon" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 8v4l3 3m6-3a9 9 0 11-18 0 9 9 0 0118 0z"></path></svg>', |
| 503 |
// Tools page icons |
| 504 |
'download' => '<svg class="abj404-card-icon" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M4 16v1a3 3 0 003 3h10a3 3 0 003-3v-1m-4-4l-4 4m0 0l-4-4m4 4V4"></path></svg>', |
| 505 |
'upload' => '<svg class="abj404-card-icon" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M4 16v1a3 3 0 003 3h10a3 3 0 003-3v-1m-4-8l-4-4m0 0L8 8m4-4v12"></path></svg>', |
| 506 |
'trash' => '<svg class="abj404-card-icon" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M19 7l-.867 12.142A2 2 0 0116.138 21H7.862a2 2 0 01-1.995-1.858L5 7m5 4v6m4-6v6m1-10V4a1 1 0 00-1-1h-4a1 1 0 00-1 1v3M4 7h16"></path></svg>', |
| 507 |
'database' => '<svg class="abj404-card-icon" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M4 7v10c0 2.21 3.582 4 8 4s8-1.79 8-4V7M4 7c0 2.21 3.582 4 8 4s8-1.79 8-4M4 7c0-2.21 3.582-4 8-4s8 1.79 8 4m0 5c0 2.21-3.582 4-8 4s-8-1.79-8-4"></path></svg>', |
| 508 |
'cog' => '<svg class="abj404-card-icon" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M10.325 4.317c.426-1.756 2.924-1.756 3.35 0a1.724 1.724 0 002.573 1.066c1.543-.94 3.31.826 2.37 2.37a1.724 1.724 0 001.065 2.572c1.756.426 1.756 2.924 0 3.35a1.724 1.724 0 00-1.066 2.573c.94 1.543-.826 3.31-2.37 2.37a1.724 1.724 0 00-2.572 1.065c-.426 1.756-2.924 1.756-3.35 0a1.724 1.724 0 00-2.573-1.066c-1.543.94-3.31-.826-2.37-2.37a1.724 1.724 0 00-1.065-2.572c-1.756-.426-1.756-2.924 0-3.35a1.724 1.724 0 001.066-2.573c-.94-1.543.826-3.31 2.37-2.37.996.608 2.296.07 2.572-1.065z"></path><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M15 12a3 3 0 11-6 0 3 3 0 016 0z"></path></svg>' |
| 509 |
); |
| 510 |
return isset($icons[$iconName]) ? $icons[$iconName] : ''; |
| 511 |
} |
| 512 |
|
| 513 |
|
| 514 |
/** |
| 515 |
* Echo the sticky save bar |
| 516 |
* @return void |
| 517 |
*/ |
| 518 |
function echoStickySaveBar() { |
| 519 |
$version = ABJ404_VERSION; |
| 520 |
$restoreNonce = wp_create_nonce('abj404_restore_defaults'); |
| 521 |
echo '<div class="abj404-sticky-save-bar">'; |
| 522 |
echo '<div class="abj404-save-bar-status">'; |
| 523 |
/* translators: %s: plugin version number */ |
| 524 |
echo esc_html(sprintf(__('Plugin v%s', '404-solution'), $version)); |
| 525 |
echo '</div>'; |
| 526 |
echo '<div class="abj404-save-bar-actions">'; |
| 527 |
echo '<button type="button" id="abj404-restore-defaults" class="button abj404-btn abj404-btn-secondary" data-nonce="' . esc_attr($restoreNonce) . '">'; |
| 528 |
echo esc_html__('Restore Defaults', '404-solution'); |
| 529 |
echo '</button>'; |
| 530 |
echo '<input type="submit" form="admin-options-page" name="abj404-optionssub" id="abj404-optionssub" value="' . esc_attr__('Save Settings', '404-solution') . '" class="button button-primary abj404-btn abj404-btn-primary">'; |
| 531 |
echo '</div>'; |
| 532 |
echo '</div>'; |
| 533 |
$this->echoRestoreDefaultsModal(); |
| 534 |
} |
| 535 |
|
| 536 |
/** |
| 537 |
* Echo the confirmation modal shown before restoring settings to defaults. |
| 538 |
* Toggled open by adding `.active`; matches the markup of other plugin modals. |
| 539 |
* @return void |
| 540 |
*/ |
| 541 |
function echoRestoreDefaultsModal() { |
| 542 |
?> |
| 543 |
<div id="abj404-restore-defaults-modal" class="abj404-modal" role="dialog" aria-modal="true" aria-labelledby="abj404-restore-defaults-modal-title"> |
| 544 |
<div class="abj404-modal-content"> |
| 545 |
<div class="abj404-modal-header"> |
| 546 |
<h2 id="abj404-restore-defaults-modal-title"><?php echo esc_html__('Restore Default Settings?', '404-solution'); ?></h2> |
| 547 |
<button type="button" id="abj404-restore-defaults-cancel" class="abj404-modal-close" aria-label="<?php echo esc_attr__('Close', '404-solution'); ?>">×</button> |
| 548 |
</div> |
| 549 |
<div class="abj404-modal-body"> |
| 550 |
<p><?php echo esc_html__('This will reset all plugin settings to their default values. This cannot be undone. Your redirect rules and 404 logs will not be affected.', '404-solution'); ?></p> |
| 551 |
</div> |
| 552 |
<div class="abj404-modal-footer"> |
| 553 |
<button type="button" id="abj404-restore-defaults-cancel-2" class="button"><?php echo esc_html__('Cancel', '404-solution'); ?></button> |
| 554 |
<button type="button" id="abj404-restore-defaults-confirm" class="button button-primary"><?php echo esc_html__('Restore Defaults', '404-solution'); ?></button> |
| 555 |
</div> |
| 556 |
</div> |
| 557 |
</div> |
| 558 |
<?php |
| 559 |
} |
| 560 |
|
| 561 |
/** |
| 562 |
* Echo the success toast notification container |
| 563 |
* @return void |
| 564 |
*/ |
| 565 |
function echoToastNotification() { |
| 566 |
echo '<div id="abj404-toast" class="abj404-toast">'; |
| 567 |
echo '<span class="abj404-toast-icon">✓</span> '; |
| 568 |
echo '<span class="abj404-toast-message">' . esc_html__('Settings saved successfully!', '404-solution') . '</span>'; |
| 569 |
echo '</div>'; |
| 570 |
} |
| 571 |
|
| 572 |
|
| 573 |
/** |
| 574 |
* Echo the expand/collapse all button and save button |
| 575 |
* @param bool $showSuggestions Not used (kept for compatibility) |
| 576 |
* @return void |
| 577 |
*/ |
| 578 |
function echoExpandCollapseButton($showSuggestions = true) { |
| 579 |
?> |
| 580 |
<div class="abj404-accordion-controls"> |
| 581 |
<button type="button" id="abj404-expand-collapse-all" class="button"> |
| 582 |
<?php echo esc_html__('Expand All', '404-solution'); ?> |
| 583 |
</button> |
| 584 |
<input type="submit" name="abj404-optionssub" id="abj404-optionssub" value="<?php echo esc_attr__('Save Settings', '404-solution'); ?>" class="button-primary"> |
| 585 |
</div> |
| 586 |
<?php |
| 587 |
} |
| 588 |
|
| 589 |
/** |
| 590 |
* Echo the Simple/Advanced mode toggle control. |
| 591 |
* @param string $currentMode 'simple' or 'advanced' |
| 592 |
* @return void |
| 593 |
*/ |
| 594 |
function echoSettingsModeToggle($currentMode) { |
| 595 |
$simpleActive = ($currentMode === 'simple') ? 'active' : ''; |
| 596 |
$advancedActive = ($currentMode === 'advanced') ? 'active' : ''; |
| 597 |
$simplePressedState = ($currentMode === 'simple') ? 'true' : 'false'; |
| 598 |
$advancedPressedState = ($currentMode === 'advanced') ? 'true' : 'false'; |
| 599 |
|
| 600 |
if ($currentMode === 'simple') { |
| 601 |
$modeDescription = __('Simple Mode shows essential options only. Switch to Advanced Mode for full configuration.', '404-solution'); |
| 602 |
} else { |
| 603 |
$modeDescription = __('Advanced Mode shows all options. Switch to Simple Mode for a streamlined view.', '404-solution'); |
| 604 |
} |
| 605 |
|
| 606 |
$html = ABJ_404_Solution_Functions::readFileContents(__DIR__ . "/html/settingsModeToggle.html"); |
| 607 |
$html = $this->f->str_replace('{nonce}', wp_create_nonce('abj404_mode_toggle'), $html); |
| 608 |
$html = $this->f->str_replace('{simpleActive}', $simpleActive, $html); |
| 609 |
$html = $this->f->str_replace('{advancedActive}', $advancedActive, $html); |
| 610 |
$html = $this->f->str_replace('{simplePressedState}', $simplePressedState, $html); |
| 611 |
$html = $this->f->str_replace('{advancedPressedState}', $advancedPressedState, $html); |
| 612 |
$html = $this->f->str_replace('{Simple Mode}', __('Simple Mode', '404-solution'), $html); |
| 613 |
$html = $this->f->str_replace('{Advanced Mode}', __('Advanced Mode', '404-solution'), $html); |
| 614 |
$html = $this->f->str_replace('{mode_description}', $modeDescription, $html); |
| 615 |
|
| 616 |
echo $html; |
| 617 |
} |
| 618 |
|
| 619 |
/** |
| 620 |
* Echo an inline (compact) mode toggle for the header row. |
| 621 |
* This is a smaller version without the description text. |
| 622 |
* @return void |
| 623 |
*/ |
| 624 |
function echoInlineModeToggle() { |
| 625 |
$currentMode = $this->logic->getSettingsMode(); |
| 626 |
$simpleActive = ($currentMode === 'simple') ? 'active' : ''; |
| 627 |
$advancedActive = ($currentMode === 'advanced') ? 'active' : ''; |
| 628 |
$simplePressedState = ($currentMode === 'simple') ? 'true' : 'false'; |
| 629 |
$advancedPressedState = ($currentMode === 'advanced') ? 'true' : 'false'; |
| 630 |
|
| 631 |
echo '<div class="abj404-mode-toggle abj404-mode-toggle-inline" data-nonce="' . esc_attr(wp_create_nonce('abj404_mode_toggle')) . '">'; |
| 632 |
echo '<div class="abj404-mode-toggle-buttons">'; |
| 633 |
echo '<button type="button" class="abj404-mode-btn ' . esc_attr($simpleActive) . '" data-mode="simple" aria-pressed="' . esc_attr($simplePressedState) . '">'; |
| 634 |
echo '<span class="abj404-mode-btn-text">' . esc_html__('Simple', '404-solution') . '</span>'; |
| 635 |
echo '</button>'; |
| 636 |
echo '<button type="button" class="abj404-mode-btn ' . esc_attr($advancedActive) . '" data-mode="advanced" aria-pressed="' . esc_attr($advancedPressedState) . '">'; |
| 637 |
echo '<span class="abj404-mode-btn-text">' . esc_html__('Advanced', '404-solution') . '</span>'; |
| 638 |
echo '</button>'; |
| 639 |
echo '</div>'; |
| 640 |
echo '</div>'; |
| 641 |
} |
| 642 |
|
| 643 |
/** |
| 644 |
* Echo the Simple Mode options page. |
| 645 |
* @param array<string, mixed> $options The plugin options |
| 646 |
* @return void |
| 647 |
*/ |
| 648 |
function echoSimpleModeOptions($options) { |
| 649 |
// Build behavior tiles HTML (replaces old dropdown) |
| 650 |
$behaviorTilesHtml = $this->getBehaviorTilesHTML($options); |
| 651 |
|
| 652 |
// Build selected states for checkboxes and dropdowns |
| 653 |
$selectedAutoRedirects = $this->getCheckedAttr($options, 'auto_redirects'); |
| 654 |
$selectedCapture404 = $this->getCheckedAttr($options, 'capture_404'); |
| 655 |
$selectedDefaultRedirect301 = ($options['default_redirect'] == '301') ? 'selected' : ''; |
| 656 |
$selectedDefaultRedirect302 = ($options['default_redirect'] == '302') ? 'selected' : ''; |
| 657 |
$selectedDefaultRedirect307 = ($options['default_redirect'] == '307') ? 'selected' : ''; |
| 658 |
$selectedDefaultRedirect308 = ($options['default_redirect'] == '308') ? 'selected' : ''; |
| 659 |
|
| 660 |
// Theme selections |
| 661 |
$selectedThemeDefault = ($options['admin_theme'] == 'default') ? 'selected' : ''; |
| 662 |
$selectedThemeCalm = ($options['admin_theme'] == 'calm') ? 'selected' : ''; |
| 663 |
$selectedThemeMono = ($options['admin_theme'] == 'mono') ? 'selected' : ''; |
| 664 |
$selectedThemeNeon = ($options['admin_theme'] == 'neon') ? 'selected' : ''; |
| 665 |
$selectedThemeObsidian = ($options['admin_theme'] == 'obsidian') ? 'selected' : ''; |
| 666 |
|
| 667 |
// Notification frequency selections |
| 668 |
$notifyFrequency = isset($options['admin_notification_frequency']) ? (string)(is_scalar($options['admin_notification_frequency']) ? $options['admin_notification_frequency'] : 'instant') : 'instant'; |
| 669 |
$selectedNotifyInstant = ($notifyFrequency === 'instant') ? 'selected' : ''; |
| 670 |
$selectedNotifyDaily = ($notifyFrequency === 'daily') ? 'selected' : ''; |
| 671 |
$selectedNotifyWeekly = ($notifyFrequency === 'weekly') ? 'selected' : ''; |
| 672 |
|
| 673 |
// Read and build the simple options template |
| 674 |
$html = ABJ_404_Solution_Functions::readFileContents(__DIR__ . "/html/adminOptionsSimple.html"); |
| 675 |
|
| 676 |
// Replace dest404page tiles |
| 677 |
$html = $this->f->str_replace('{behaviorTiles}', $behaviorTilesHtml, $html); |
| 678 |
|
| 679 |
// Replace checkbox states |
| 680 |
$html = $this->f->str_replace('{selectedAutoRedirects}', $selectedAutoRedirects, $html); |
| 681 |
$html = $this->f->str_replace('{selectedCapture404}', $selectedCapture404, $html); |
| 682 |
$html = $this->f->str_replace('{selectedDefaultRedirect301}', $selectedDefaultRedirect301, $html); |
| 683 |
$html = $this->f->str_replace('{selectedDefaultRedirect302}', $selectedDefaultRedirect302, $html); |
| 684 |
$html = $this->f->str_replace('{selectedDefaultRedirect307}', $selectedDefaultRedirect307, $html); |
| 685 |
$html = $this->f->str_replace('{selectedDefaultRedirect308}', $selectedDefaultRedirect308, $html); |
| 686 |
|
| 687 |
// Replace values |
| 688 |
$html = $this->f->str_replace('{capture_deletion}', esc_attr($this->optStr($options, 'capture_deletion')), $html); |
| 689 |
$html = $this->f->str_replace('{admin_notification}', esc_attr($this->optStr($options, 'admin_notification')), $html); |
| 690 |
$html = $this->f->str_replace('{maximum_log_disk_usage}', esc_attr($this->optStr($options, 'maximum_log_disk_usage')), $html); |
| 691 |
|
| 692 |
// Theme selections |
| 693 |
$html = $this->f->str_replace('{selectedThemeDefault}', $selectedThemeDefault, $html); |
| 694 |
$html = $this->f->str_replace('{selectedThemeCalm}', $selectedThemeCalm, $html); |
| 695 |
$html = $this->f->str_replace('{selectedThemeMono}', $selectedThemeMono, $html); |
| 696 |
$html = $this->f->str_replace('{selectedThemeNeon}', $selectedThemeNeon, $html); |
| 697 |
$html = $this->f->str_replace('{selectedThemeObsidian}', $selectedThemeObsidian, $html); |
| 698 |
$html = $this->f->str_replace('{theme_default}', __('Default (Follows WordPress)', '404-solution'), $html); |
| 699 |
|
| 700 |
// Translate labels |
| 701 |
$html = $this->f->str_replace('{Core Settings}', __('Core Settings', '404-solution'), $html); |
| 702 |
$html = $this->f->str_replace('{404 Capture}', __('404 Capture', '404-solution'), $html); |
| 703 |
$html = $this->f->str_replace('{Maintenance}', __('Maintenance', '404-solution'), $html); |
| 704 |
$html = $this->f->str_replace('{Default 404 destination}', __('Default 404 destination', '404-solution'), $html); |
| 705 |
$html = $this->f->str_replace('{Where to send visitors when a 404 error occurs}', __('Where to send visitors when a 404 error occurs', '404-solution'), $html); |
| 706 |
$html = $this->f->str_replace('{Create automatic redirects}', __('Create automatic redirects', '404-solution'), $html); |
| 707 |
$html = $this->f->str_replace('{Automatically redirect 404s to similar pages when a good match is found}', __('Automatically redirect 404s to similar pages when a good match is found', '404-solution'), $html); |
| 708 |
$html = $this->f->str_replace('{Redirect type}', __('Redirect type', '404-solution'), $html); |
| 709 |
$html = $this->f->str_replace('{Permanent 301}', __('Permanent 301', '404-solution'), $html); |
| 710 |
$html = $this->f->str_replace('{Temporary 302}', __('Temporary 302', '404-solution'), $html); |
| 711 |
$html = $this->f->str_replace('{301 for SEO, 302 for temporary changes}', __('301 for SEO, 302 for temporary changes', '404-solution'), $html); |
| 712 |
$html = $this->f->str_replace('{Collect incoming 404 URLs}', __('Collect incoming 404 URLs', '404-solution'), $html); |
| 713 |
$html = $this->f->str_replace('{Log 404 errors so you can review and fix them}', __('Log 404 errors so you can review and fix them', '404-solution'), $html); |
| 714 |
$html = $this->f->str_replace('{Delete captured URLs after}', __('Delete captured URLs after', '404-solution'), $html); |
| 715 |
$html = $this->f->str_replace('{days}', __('days', '404-solution'), $html); |
| 716 |
$html = $this->f->str_replace('{Auto-remove old 404 records (0 to keep forever)}', __('Auto-remove old 404 records (0 to keep forever)', '404-solution'), $html); |
| 717 |
$html = $this->f->str_replace('{Notify me when captured URLs exceed}', __('Notify me when captured URLs exceed', '404-solution'), $html); |
| 718 |
$html = $this->f->str_replace('{URLs}', __('URLs', '404-solution'), $html); |
| 719 |
$html = $this->f->str_replace('{Show admin notice when 404 count gets high (0 to disable)}', __('Show admin notice when 404 count gets high (0 to disable)', '404-solution'), $html); |
| 720 |
$html = $this->f->str_replace('{Maximum log storage}', __('Maximum log storage', '404-solution'), $html); |
| 721 |
$html = $this->f->str_replace('{Oldest logs are deleted when this limit is reached}', __('Oldest logs are deleted when this limit is reached', '404-solution'), $html); |
| 722 |
$html = $this->f->str_replace('{Admin theme}', __('Admin theme', '404-solution'), $html); |
| 723 |
$html = $this->f->str_replace('{Calm Ops (Light)}', __('Calm Ops (Light)', '404-solution'), $html); |
| 724 |
$html = $this->f->str_replace('{Monochrome Minimal (Light)}', __('Monochrome Minimal (Light)', '404-solution'), $html); |
| 725 |
$html = $this->f->str_replace('{Neon Slate (Dark)}', __('Neon Slate (Dark)', '404-solution'), $html); |
| 726 |
$html = $this->f->str_replace('{Obsidian Blue (Dark)}', __('Obsidian Blue (Dark)', '404-solution'), $html); |
| 727 |
$html = $this->f->str_replace('{Save Settings}', __('Save Settings', '404-solution'), $html); |
| 728 |
$html = $this->f->str_replace('{Need more control?}', __('Need more control?', '404-solution'), $html); |
| 729 |
$html = $this->f->str_replace('{Switch to Advanced Mode}', __('Switch to Advanced Mode', '404-solution'), $html); |
| 730 |
$html = $this->f->str_replace('{for 30+ additional options.}', __('for 30+ additional options.', '404-solution'), $html); |
| 731 |
|
| 732 |
// Frequency dropdown selections and labels |
| 733 |
$html = $this->f->str_replace('{selectedNotifyInstant}', $selectedNotifyInstant, $html); |
| 734 |
$html = $this->f->str_replace('{selectedNotifyDaily}', $selectedNotifyDaily, $html); |
| 735 |
$html = $this->f->str_replace('{selectedNotifyWeekly}', $selectedNotifyWeekly, $html); |
| 736 |
$html = $this->f->str_replace('{Email notification frequency}', __('Email notification frequency', '404-solution'), $html); |
| 737 |
$html = $this->f->str_replace('{Instant (when threshold exceeded)}', __('Instant (when threshold exceeded)', '404-solution'), $html); |
| 738 |
$html = $this->f->str_replace('{Daily digest}', __('Daily digest', '404-solution'), $html); |
| 739 |
$html = $this->f->str_replace('{Weekly digest}', __('Weekly digest', '404-solution'), $html); |
| 740 |
$html = $this->f->str_replace('{Choose how often to receive email notifications about captured 404s.}', __('Choose how often to receive email notifications about captured 404s.', '404-solution'), $html); |
| 741 |
// Also handle the Temporary 307/308 options (present in simple mode too) |
| 742 |
$html = $this->f->str_replace('{Temporary 307 (preserve method)}', __('Temporary 307 (preserve method)', '404-solution'), $html); |
| 743 |
$html = $this->f->str_replace('{Permanent 308 (preserve method)}', __('Permanent 308 (preserve method)', '404-solution'), $html); |
| 744 |
|
| 745 |
echo $html; |
| 746 |
} |
| 747 |
|
| 748 |
|
| 749 |
} |
| 750 |
|