%s captured 404 URL that needs to be processed.', 'There are %s captured 404 URLs to be processed.', $captured, '404-solution'), $captured); $capturedMessage = $this->f->str_replace("", "", $capturedMessage); $capturedMessage = $this->f->str_replace("", "", $capturedMessage); return '

' . PLUGIN_NAME . ": " . $capturedMessage . "

"; } /** Do an action like trash/delete/ignore/edit and display a page like stats/logs/redirects/options. * @return void */ static function handleMainAdminPageActionAndDisplay() { global $abj404view; $instance = self::getInstance(); try { $action = $instance->dao->getPostOrGetSanitize('action'); if (!is_admin() || !$instance->logic->userIsPluginAdmin()) { $instance->logger->logUserCapabilities("handleMainAdminPageActionAndDisplay (" . esc_html($action == '' ? '(none)' : $action) . ")"); return; } $sub = ""; // -------------------------------------------------------------------- // Handle Post Actions $instance->logger->debugMessage("Processing request for action: " . esc_html($action == '' ? '(none)' : $action)); // this should really not pass things by reference so it can be more object oriented (encapsulation etc). $message = ""; $message .= $instance->logic->handlePluginAction($action, $sub); $message .= $instance->logic->hanldeTrashAction(); $message .= $instance->logic->handleDeleteAction(); $message .= $instance->logic->handleIgnoreAction(); $message .= $instance->logic->handleLaterAction(); $message .= $instance->logic->handleActionEdit($sub, $action); $message .= $instance->logic->handleActionImportRedirects(); $instance->logic->handleActionChangeItemsPerRow(); $message .= $instance->logic->handleActionImportFile(); // -------------------------------------------------------------------- // Output the correct page. $abj404view->echoChosenAdminTab($action, $sub, $message); } catch (Exception $e) { $encodedEx = json_encode($e); $instance->logger->errorMessage("Caught exception: " . stripcslashes(wp_kses_post(is_string($encodedEx) ? $encodedEx : ''))); echo '
'; echo '
'; echo '

404 Solution: An error occurred while rendering this page.

'; echo '
Show error details'; echo '
' . esc_html($e->getMessage() . "\n" . $e->getTraceAsString()) . '
'; echo '
'; echo '
'; echo '
'; } } /** Display the chosen admin page. * @param string $action * @param string $sub * @param string $message * @return void */ function echoChosenAdminTab($action, $sub, $message) { global $abj404view; // If globals are not set, use sensible defaults if ($abj404view === null) { $abj404view = $this; } // Deal With Page Tabs if ($sub == "") { $sub = $this->f->strtolower($this->dao->getPostOrGetSanitize('subpage')); } if ($sub == "") { $sub = 'abj404_redirects'; $this->logger->debugMessage('No tab selected. Displaying the "redirects" tab.'); } // Check if we're returning from a successful redirect update $updated = $this->dao->getPostOrGetSanitize('updated'); if ($updated == '1') { $message .= __('Redirect Information Updated Successfully!', '404-solution'); } $this->logger->debugMessage("Displaying sub page: " . esc_html($sub == '' ? '(none)' : $sub)); $abj404view->outputAdminHeaderTabs($sub, $message); $abj404action = $this->dao->getPostOrGetSanitize('abj404action'); if (($action == 'editRedirect') || ($abj404action == 'editRedirect') || ($sub == 'abj404_edit')) { $abj404view->echoAdminEditRedirectPage(); } else if ($sub == 'abj404_redirects') { $abj404view->echoAdminRedirectsPage(); } else if ($sub == 'abj404_captured') { $abj404view->echoAdminCapturedURLsPage(); } else if ($sub == "abj404_options") { $abj404view->echoAdminOptionsPage(); } else if ($sub == 'abj404_logs') { $abj404view->echoAdminLogsPage(); } else if ($sub == 'abj404_stats') { $abj404view->outputAdminStatsPage(); } else if ($sub == 'abj404_tools') { $abj404view->echoAdminToolsPage(); } else if ($sub == 'abj404_debugfile') { $abj404view->echoAdminDebugFile(); } else { $this->logger->debugMessage('No tab selected. Displaying the "redirects" tab.'); $abj404view->echoAdminRedirectsPage(); } $abj404view->echoAdminFooter(); } /** * Echo the text that appears at the bottom of each admin page. * @return void */ function echoAdminFooter() { // read the html content. $html = ABJ_404_Solution_Functions::readFileContents(__DIR__ . "/html/adminFooter.html"); $html = $this->f->str_replace('{JAPANESE_FLASHCARDS_URL}', ABJ404_FC_URL, $html); // constants and translations. $html = $this->f->doNormalReplacements($html); echo $html; } /** Output the tabs at the top of the plugin page. * @param string $sub * @param string $message * @return void */ function outputAdminHeaderTabs($sub = 'list', $message = '') { ABJ_404_Solution_WPNotices::echoAdminNotices(); echo "
"; if ($message != "") { $allowed_tags = array( 'br' => array(), 'em' => array(), 'strong' => array(), ); if (($this->f->strlen($message) >= 6) && ($this->f->substr($this->f->strtolower($message), 0, 6) == 'error:')) { $cssClasses = 'notice notice-error'; } else { $cssClasses = 'notice notice-success'; } echo '

' . wp_kses($message, $allowed_tags) . "

\n"; } echo ''; } /** This outputs a box with a title and some content in it. * It's used on the Stats, Options and Tools page (for example). * @param int|string $id * @param string $title * @param string $content * @return void */ function echoPostBox($id, $title, $content) { echo "
"; echo "

" . esc_html($title) . "

"; echo "
" . $content /* Can't escape here, as contains forms */ . "
"; echo "
"; } /** * Echo an accordion section using card-based layout * @param string $sectionId The section identifier * @param string $postboxId The ID for the postbox * @param string $title The title of the section * @param string $content The content to display * @param bool $initiallyVisible Whether the card starts expanded (default: false) * @param string $icon The SVG icon for the card header (optional) * @param string $badge Optional info badge text * @return void */ function echoOptionsSection($sectionId, $postboxId, $title, $content, $initiallyVisible = false, $icon = '', $badge = '') { $expandedClass = $initiallyVisible ? ' expanded' : ''; echo "
"; echo "
"; echo "

"; if ($icon) { echo $icon; // Icon is pre-sanitized SVG } echo esc_html($title); if ($badge) { echo "" . esc_html($badge) . ""; } echo "

"; echo ""; echo ""; echo ""; echo "
"; echo "
"; echo $content; /* Can't escape here, as contains forms */ echo "
"; echo "
"; } /** * Get SVG icon for a section * @param string $iconName The icon name * @return string The SVG icon markup */ function getCardIcon($iconName) { $icons = array( 'lightning' => '', 'gear' => '', 'filter' => '', 'document' => '', 'sliders' => '', 'lightbulb' => '', // Stats page icons 'chart' => '', 'warning' => '', 'clock' => '', // Tools page icons 'download' => '', 'upload' => '', 'trash' => '', 'database' => '', 'cog' => '' ); return isset($icons[$iconName]) ? $icons[$iconName] : ''; } /** * Echo the sticky save bar * @return void */ function echoStickySaveBar() { $version = ABJ404_VERSION; echo '
'; echo '
'; /* translators: %s: plugin version number */ echo esc_html(sprintf(__('Plugin v%s', '404-solution'), $version)); echo '
'; echo '
'; echo ''; echo '
'; echo '
'; } /** * Echo the success toast notification container * @return void */ function echoToastNotification() { echo '
'; echo '✓ '; echo '' . esc_html__('Settings saved successfully!', '404-solution') . ''; echo '
'; } /** * Echo the expand/collapse all button and save button * @param bool $showSuggestions Not used (kept for compatibility) * @return void */ function echoExpandCollapseButton($showSuggestions = true) { ?>
f->str_replace('{nonce}', wp_create_nonce('abj404_mode_toggle'), $html); $html = $this->f->str_replace('{simpleActive}', $simpleActive, $html); $html = $this->f->str_replace('{advancedActive}', $advancedActive, $html); $html = $this->f->str_replace('{simplePressedState}', $simplePressedState, $html); $html = $this->f->str_replace('{advancedPressedState}', $advancedPressedState, $html); $html = $this->f->str_replace('{Simple Mode}', __('Simple Mode', '404-solution'), $html); $html = $this->f->str_replace('{Advanced Mode}', __('Advanced Mode', '404-solution'), $html); $html = $this->f->str_replace('{mode_description}', $modeDescription, $html); echo $html; } /** * Echo an inline (compact) mode toggle for the header row. * This is a smaller version without the description text. * @return void */ function echoInlineModeToggle() { $currentMode = $this->logic->getSettingsMode(); $simpleActive = ($currentMode === 'simple') ? 'active' : ''; $advancedActive = ($currentMode === 'advanced') ? 'active' : ''; $simplePressedState = ($currentMode === 'simple') ? 'true' : 'false'; $advancedPressedState = ($currentMode === 'advanced') ? 'true' : 'false'; echo '
'; echo '
'; echo ''; echo ''; echo '
'; echo '
'; } /** * Echo the Simple Mode options page. * @param array $options The plugin options * @return void */ function echoSimpleModeOptions($options) { // Build behavior tiles HTML (replaces old dropdown) $behaviorTilesHtml = $this->getBehaviorTilesHTML($options); // Build selected states for checkboxes and dropdowns $selectedAutoRedirects = $this->getCheckedAttr($options, 'auto_redirects'); $selectedCapture404 = $this->getCheckedAttr($options, 'capture_404'); $selectedDefaultRedirect301 = ($options['default_redirect'] == '301') ? 'selected' : ''; $selectedDefaultRedirect302 = ($options['default_redirect'] == '302') ? 'selected' : ''; $selectedDefaultRedirect307 = ($options['default_redirect'] == '307') ? 'selected' : ''; $selectedDefaultRedirect308 = ($options['default_redirect'] == '308') ? 'selected' : ''; // Theme selections $selectedThemeDefault = ($options['admin_theme'] == 'default') ? 'selected' : ''; $selectedThemeCalm = ($options['admin_theme'] == 'calm') ? 'selected' : ''; $selectedThemeMono = ($options['admin_theme'] == 'mono') ? 'selected' : ''; $selectedThemeNeon = ($options['admin_theme'] == 'neon') ? 'selected' : ''; $selectedThemeObsidian = ($options['admin_theme'] == 'obsidian') ? 'selected' : ''; // Notification frequency selections $notifyFrequency = isset($options['admin_notification_frequency']) ? (string)(is_scalar($options['admin_notification_frequency']) ? $options['admin_notification_frequency'] : 'instant') : 'instant'; $selectedNotifyInstant = ($notifyFrequency === 'instant') ? 'selected' : ''; $selectedNotifyDaily = ($notifyFrequency === 'daily') ? 'selected' : ''; $selectedNotifyWeekly = ($notifyFrequency === 'weekly') ? 'selected' : ''; // Read and build the simple options template $html = ABJ_404_Solution_Functions::readFileContents(__DIR__ . "/html/adminOptionsSimple.html"); // Replace dest404page tiles $html = $this->f->str_replace('{behaviorTiles}', $behaviorTilesHtml, $html); // Replace checkbox states $html = $this->f->str_replace('{selectedAutoRedirects}', $selectedAutoRedirects, $html); $html = $this->f->str_replace('{selectedCapture404}', $selectedCapture404, $html); $html = $this->f->str_replace('{selectedDefaultRedirect301}', $selectedDefaultRedirect301, $html); $html = $this->f->str_replace('{selectedDefaultRedirect302}', $selectedDefaultRedirect302, $html); $html = $this->f->str_replace('{selectedDefaultRedirect307}', $selectedDefaultRedirect307, $html); $html = $this->f->str_replace('{selectedDefaultRedirect308}', $selectedDefaultRedirect308, $html); // Replace values $html = $this->f->str_replace('{capture_deletion}', esc_attr($this->optStr($options, 'capture_deletion')), $html); $html = $this->f->str_replace('{admin_notification}', esc_attr($this->optStr($options, 'admin_notification')), $html); $html = $this->f->str_replace('{maximum_log_disk_usage}', esc_attr($this->optStr($options, 'maximum_log_disk_usage')), $html); // Theme selections $html = $this->f->str_replace('{selectedThemeDefault}', $selectedThemeDefault, $html); $html = $this->f->str_replace('{selectedThemeCalm}', $selectedThemeCalm, $html); $html = $this->f->str_replace('{selectedThemeMono}', $selectedThemeMono, $html); $html = $this->f->str_replace('{selectedThemeNeon}', $selectedThemeNeon, $html); $html = $this->f->str_replace('{selectedThemeObsidian}', $selectedThemeObsidian, $html); $html = $this->f->str_replace('{theme_default}', __('Default (Follows WordPress)', '404-solution'), $html); // Translate labels $html = $this->f->str_replace('{Core Settings}', __('Core Settings', '404-solution'), $html); $html = $this->f->str_replace('{404 Capture}', __('404 Capture', '404-solution'), $html); $html = $this->f->str_replace('{Maintenance}', __('Maintenance', '404-solution'), $html); $html = $this->f->str_replace('{Default 404 destination}', __('Default 404 destination', '404-solution'), $html); $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); $html = $this->f->str_replace('{Create automatic redirects}', __('Create automatic redirects', '404-solution'), $html); $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); $html = $this->f->str_replace('{Redirect type}', __('Redirect type', '404-solution'), $html); $html = $this->f->str_replace('{Permanent 301}', __('Permanent 301', '404-solution'), $html); $html = $this->f->str_replace('{Temporary 302}', __('Temporary 302', '404-solution'), $html); $html = $this->f->str_replace('{301 for SEO, 302 for temporary changes}', __('301 for SEO, 302 for temporary changes', '404-solution'), $html); $html = $this->f->str_replace('{Collect incoming 404 URLs}', __('Collect incoming 404 URLs', '404-solution'), $html); $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); $html = $this->f->str_replace('{Delete captured URLs after}', __('Delete captured URLs after', '404-solution'), $html); $html = $this->f->str_replace('{days}', __('days', '404-solution'), $html); $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); $html = $this->f->str_replace('{Notify me when captured URLs exceed}', __('Notify me when captured URLs exceed', '404-solution'), $html); $html = $this->f->str_replace('{URLs}', __('URLs', '404-solution'), $html); $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); $html = $this->f->str_replace('{Maximum log storage}', __('Maximum log storage', '404-solution'), $html); $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); $html = $this->f->str_replace('{Admin theme}', __('Admin theme', '404-solution'), $html); $html = $this->f->str_replace('{Calm Ops (Light)}', __('Calm Ops (Light)', '404-solution'), $html); $html = $this->f->str_replace('{Monochrome Minimal (Light)}', __('Monochrome Minimal (Light)', '404-solution'), $html); $html = $this->f->str_replace('{Neon Slate (Dark)}', __('Neon Slate (Dark)', '404-solution'), $html); $html = $this->f->str_replace('{Obsidian Blue (Dark)}', __('Obsidian Blue (Dark)', '404-solution'), $html); $html = $this->f->str_replace('{Save Settings}', __('Save Settings', '404-solution'), $html); $html = $this->f->str_replace('{Need more control?}', __('Need more control?', '404-solution'), $html); $html = $this->f->str_replace('{Switch to Advanced Mode}', __('Switch to Advanced Mode', '404-solution'), $html); $html = $this->f->str_replace('{for 30+ additional options.}', __('for 30+ additional options.', '404-solution'), $html); // Frequency dropdown selections and labels $html = $this->f->str_replace('{selectedNotifyInstant}', $selectedNotifyInstant, $html); $html = $this->f->str_replace('{selectedNotifyDaily}', $selectedNotifyDaily, $html); $html = $this->f->str_replace('{selectedNotifyWeekly}', $selectedNotifyWeekly, $html); $html = $this->f->str_replace('{Email notification frequency}', __('Email notification frequency', '404-solution'), $html); $html = $this->f->str_replace('{Instant (when threshold exceeded)}', __('Instant (when threshold exceeded)', '404-solution'), $html); $html = $this->f->str_replace('{Daily digest}', __('Daily digest', '404-solution'), $html); $html = $this->f->str_replace('{Weekly digest}', __('Weekly digest', '404-solution'), $html); $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); // Also handle the Temporary 307/308 options (present in simple mode too) $html = $this->f->str_replace('{Temporary 307 (preserve method)}', __('Temporary 307 (preserve method)', '404-solution'), $html); $html = $this->f->str_replace('{Permanent 308 (preserve method)}', __('Permanent 308 (preserve method)', '404-solution'), $html); echo $html; } }