'ABJ_404_Solution_Functions', 'ABJ_404_Solution_FunctionsPreg' => 'ABJ_404_Solution_Functions', ]; // only pay attention if it's for us. don't bother for other things. if (substr($class, 0, 16) !== 'ABJ_404_Solution') { return; } // Use a deterministic classmap to avoid runtime glob() scans on real sites. static $abj404_autoLoaderClassMap = null; if ($abj404_autoLoaderClassMap === null) { $mapFile = __DIR__ . '/includes/classmap.php'; $abj404_autoLoaderClassMap = file_exists($mapFile) ? require $mapFile : array(); } if (!array_key_exists($class, $abj404_autoLoaderClassMap)) { return; } // Ensure the parent class is loaded first. if (array_key_exists($class, $childParentMap)) { $parentClass = $childParentMap[$class]; if (!class_exists($parentClass, false) && array_key_exists($parentClass, $abj404_autoLoaderClassMap)) { require_once $abj404_autoLoaderClassMap[$parentClass]; } } require_once $abj404_autoLoaderClassMap[$class]; } spl_autoload_register('abj404_autoloader'); add_action('doing_it_wrong_run', function($function_name, $message, $version) { if (strpos($message, '404-solution') !== false && $function_name == '_load_textdomain_just_in_time') { try { $backtrace = debug_backtrace(DEBUG_BACKTRACE_IGNORE_ARGS); // Prepare the plugin path from ABJ404_FILE $pluginPath = trailingslashit(plugin_dir_path(ABJ404_FILE)); // e.g., /var/www/html/wp-content/plugins/404-solution/ $logMessage = ''; $isOurPlugin = false; foreach ($backtrace as $index => $frame) { $file = isset($frame['file']) ? $frame['file'] : '[internal function]'; $line = isset($frame['line']) ? $frame['line'] : ''; $func = isset($frame['function']) ? $frame['function'] : '[unknown function]'; if (!$isOurPlugin && is_string($file) && strpos($file, $pluginPath) !== false) { $isOurPlugin = true; } $logMessage .= "#$index $func at [$file:$line]\n"; } if ($isOurPlugin) { $header = "=== Detected Early Translation ===\n" . "Function: $function_name\n" . "Message: $message\n" . "Version: $version\n"; if (!isset($GLOBALS['abj404_pending_errors'])) { $GLOBALS['abj404_pending_errors'] = []; } $GLOBALS['abj404_pending_errors'][] = $header . $logMessage; } } catch (Throwable $e) { // error_log('Failed to log early translation stack trace: ' . $e->getMessage()); } } }, 10, 3); // shortcode add_shortcode(ABJ404_SHORTCODE_NAME, 'abj404_shortCodeListener'); if (!function_exists('abj404_shortCodeListener')) { function abj404_shortCodeListener($atts) { abj404_load_textdomain_if_needed(); require_once(plugin_dir_path( __FILE__ ) . "includes/Loader.php"); return ABJ_404_Solution_ShortCode::shortcodePageSuggestions($atts); } if (!function_exists('abj404_get_required_runtime_files')) { /** * Files required for a healthy runtime/plugin package. * * @return array */ function abj404_get_required_runtime_files() { return array( ABJ404_PATH . 'includes/sql/getPermalinkFromURL.sql', ABJ404_PATH . 'includes/sql/getRedirectsForView.sql', ABJ404_PATH . 'includes/sql/createRedirectsTable.sql', ABJ404_PATH . 'includes/sql/createLogTable.sql', ABJ404_PATH . 'includes/WordPress_Connector.php', ABJ404_PATH . 'includes/FrontendRequestPipeline.php', ABJ404_PATH . 'includes/ImportExportService.php', ); } } if (!function_exists('abj404_verify_runtime_integrity')) { /** * Validate that required plugin files are present. * * @return array Missing file paths. */ function abj404_verify_runtime_integrity() { $missing = array(); foreach (abj404_get_required_runtime_files() as $path) { if (!file_exists($path)) { $missing[] = $path; } } return $missing; } } if (!function_exists('abj404_is_benchmark_request')) { /** * Benchmark instrumentation is disabled by default and only enabled per-request. * * @return bool */ function abj404_is_benchmark_request() { return isset($_GET['abj404_bench']) && (string)$_GET['abj404_bench'] === '1'; } } if (!function_exists('abj404_benchmark_bootstrap_start')) { function abj404_benchmark_bootstrap_start() { if (!abj404_is_benchmark_request()) { return; } if (!isset($GLOBALS['abj404_benchmark_state']) || !is_array($GLOBALS['abj404_benchmark_state'])) { $GLOBALS['abj404_benchmark_state'] = array( 'start' => microtime(true), 'bootstrap_done' => 0.0, 'db_query_count' => 0, 'db_query_ms' => 0.0, 'redirect_lookup_ms' => 0.0, ); } } } if (!function_exists('abj404_benchmark_mark_bootstrap_done')) { function abj404_benchmark_mark_bootstrap_done() { if (!abj404_is_benchmark_request() || !isset($GLOBALS['abj404_benchmark_state'])) { return; } $GLOBALS['abj404_benchmark_state']['bootstrap_done'] = microtime(true); } } if (!function_exists('abj404_benchmark_record_db_query')) { /** * @param float $elapsedMs * @return void */ function abj404_benchmark_record_db_query($elapsedMs) { if (!abj404_is_benchmark_request() || !isset($GLOBALS['abj404_benchmark_state'])) { return; } $elapsedMs = max(0.0, (float)$elapsedMs); $GLOBALS['abj404_benchmark_state']['db_query_count']++; $GLOBALS['abj404_benchmark_state']['db_query_ms'] += $elapsedMs; } } if (!function_exists('abj404_benchmark_record_redirect_lookup')) { /** * @param float $elapsedMs * @return void */ function abj404_benchmark_record_redirect_lookup($elapsedMs) { if (!abj404_is_benchmark_request() || !isset($GLOBALS['abj404_benchmark_state'])) { return; } $GLOBALS['abj404_benchmark_state']['redirect_lookup_ms'] += max(0.0, (float)$elapsedMs); } } if (!function_exists('abj404_benchmark_emit_headers')) { function abj404_benchmark_emit_headers() { if (!abj404_is_benchmark_request() || headers_sent() || !isset($GLOBALS['abj404_benchmark_state'])) { return; } $state = $GLOBALS['abj404_benchmark_state']; $start = isset($state['start']) ? (float)$state['start'] : 0.0; $bootstrapDone = isset($state['bootstrap_done']) ? (float)$state['bootstrap_done'] : 0.0; $now = microtime(true); $totalMs = ($start > 0.0) ? (($now - $start) * 1000.0) : 0.0; $bootstrapMs = ($start > 0.0 && $bootstrapDone > 0.0) ? (($bootstrapDone - $start) * 1000.0) : 0.0; $dbQueryCount = isset($state['db_query_count']) ? (int)$state['db_query_count'] : 0; $dbQueryMs = isset($state['db_query_ms']) ? (float)$state['db_query_ms'] : 0.0; $redirectLookupMs = isset($state['redirect_lookup_ms']) ? (float)$state['redirect_lookup_ms'] : 0.0; header( 'X-ABJ404-Benchmark: ' . 'total_ms=' . round($totalMs, 3) . ';' . 'bootstrap_ms=' . round($bootstrapMs, 3) . ';' . 'db_query_count=' . $dbQueryCount . ';' . 'db_query_ms=' . round($dbQueryMs, 3) . ';' . 'redirect_lookup_ms=' . round($redirectLookupMs, 3) ); } } abj404_benchmark_bootstrap_start(); if (abj404_is_benchmark_request()) { add_action('send_headers', 'abj404_benchmark_emit_headers', PHP_INT_MAX); } } // admin if (is_admin()) { require_once(plugin_dir_path( __FILE__ ) . "includes/Loader.php"); ABJ_404_Solution_WordPress_Connector::init(); ABJ_404_Solution_ViewUpdater::init(); } // ---- // get the plugin priority to use before adding the template_redirect action. $__abj404_options = abj404_get_settings_options(); $__abj404_template_redirect_priority = absint($__abj404_options['template_redirect_priority'] ?? 9); $GLOBALS['abj404_frontend_runtime_flags'] = array( 'redirect_all_requests' => (is_array($__abj404_options) && array_key_exists('redirect_all_requests', $__abj404_options) && (string)$__abj404_options['redirect_all_requests'] === '1'), 'update_suggest_url' => (is_array($__abj404_options) && array_key_exists('update_suggest_url', $__abj404_options) && (string)$__abj404_options['update_suggest_url'] === '1'), ); $GLOBALS['abj404_plugin_language_override'] = ( is_array($__abj404_options) && !empty($__abj404_options['plugin_language_override']) ) ? (string)$__abj404_options['plugin_language_override'] : ''; add_action('template_redirect', 'abj404_404listener', $__abj404_template_redirect_priority); unset($__abj404_options); unset($__abj404_template_redirect_priority); abj404_benchmark_mark_bootstrap_done(); // --- // 404 if (!function_exists('abj404_404listener')) { function abj404_404listener() { $is404 = is_404(); if (!$is404) { // Performance: do NOT load the whole plugin on every frontend request unless we must. if (!empty($GLOBALS['abj404_frontend_runtime_flags']['redirect_all_requests'])) { require_once(plugin_dir_path( __FILE__ ) . "includes/Loader.php"); $connector = ABJ_404_Solution_WordPress_Connector::getInstance(); $connector->processRedirectAllRequests(); return; } $updateSuggestEnabled = !empty($GLOBALS['abj404_frontend_runtime_flags']['update_suggest_url']); $cookieName404 = ABJ404_PP . '_STATUS_404'; $has404StatusCookie = (isset($_COOKIE[$cookieName404]) && $_COOKIE[$cookieName404] == 'true'); // Fast path: if none of the non-404 features are active, bail immediately. if (!$updateSuggestEnabled && !$has404StatusCookie) { return; } /** If we're currently redirecting to a custom 404 page and we are about to show page * suggestions then update the URL displayed to the user. */ $cookieName = ABJ404_PP . '_REQUEST_URI_UPDATE_URL'; $queryParamName = ABJ404_PP . '_ref'; $hasUpdateCookie = !empty($_COOKIE[$cookieName]); $hasUpdateParam = !empty($_GET[$queryParamName]); // Fast path: nothing pending from prior plugin-driven redirects. if (!$hasUpdateCookie && !$hasUpdateParam && !$has404StatusCookie) { return; } if ($has404StatusCookie) { // clear the cookie setcookie($cookieName404, 'false', time() - 5, "/"); // we're going to a custom 404 page so set the status to 404. status_header(404); } if (!$updateSuggestEnabled) { return; } // Check cookie first, then query param fallback (for 301 redirects where cookies don't survive) $originalURL = null; if ($hasUpdateCookie) { $originalURL = $_COOKIE[$cookieName]; } elseif ($hasUpdateParam) { $originalURL = urldecode($_GET[$queryParamName]); } if ($originalURL !== null) { // clear the cookie - sanitize before writing to $_REQUEST $_REQUEST[ABJ404_PP . '_REQUEST_URI'] = sanitize_text_field($originalURL); setcookie($cookieName, '', time() - 5, "/"); require_once(plugin_dir_path( __FILE__ ) . "includes/Loader.php"); add_action('wp_head', 'ABJ_404_Solution_ShortCode::updateURLbarIfNecessary'); } return; } // ignore admin screens and login requests on 404 processing path. // $_SERVER['SCRIPT_NAME'] is not guaranteed (CLI, some test runners, some proxies). // Use a direct script-name check to avoid invoking wp_login_url() filters. $scriptName = $_SERVER['SCRIPT_NAME'] ?? ''; $requestUri = $_SERVER['REQUEST_URI'] ?? ''; $isLoginScreen = ( ($scriptName !== '' && stripos($scriptName, 'wp-login.php') !== false) || ($requestUri !== '' && stripos($requestUri, 'wp-login.php') !== false) ); if (is_admin() || $isLoginScreen) { return; } require_once(plugin_dir_path( __FILE__ ) . "includes/Loader.php"); $connector = ABJ_404_Solution_WordPress_Connector::getInstance(); return $connector->process404(); } } if (!function_exists('abj404_is_redirect_all_requests_enabled')) { /** * Small helper for testability and to keep option-parsing logic consistent. * * @param mixed $options Value returned by get_option('abj404_settings') * @return bool */ function abj404_is_redirect_all_requests_enabled($options) { return is_array($options) && array_key_exists('redirect_all_requests', $options) && (string)$options['redirect_all_requests'] === '1'; } } if (!function_exists('abj404_dailyMaintenanceCronJobListener')) { function abj404_dailyMaintenanceCronJobListener() { require_once(plugin_dir_path( __FILE__ ) . "includes/Loader.php"); $abj404dao = ABJ_404_Solution_DataAccess::getInstance(); $abj404dao->deleteOldRedirectsCron(); $dbUpgrades = ABJ_404_Solution_DatabaseUpgradesEtc::getInstance(); $dbUpgrades->runDatabaseMaintenanceTasks(); } } if (!function_exists('abj404_updateLogsHitsTableListener')) { function abj404_updateLogsHitsTableListener() { require_once(plugin_dir_path( __FILE__ ) . "includes/Loader.php"); $abj404dao = ABJ_404_Solution_DataAccess::getInstance(); $abj404dao->createRedirectsForViewHitsTable(); } } if (!function_exists('abj404_updatePermalinkCacheListener')) { function abj404_updatePermalinkCacheListener($maxExecutionTime, $executionCount = 1) { require_once(plugin_dir_path( __FILE__ ) . "includes/Loader.php"); $permalinkCache = ABJ_404_Solution_PermalinkCache::getInstance(); $permalinkCache->updatePermalinkCache($maxExecutionTime, $executionCount); } } if (!function_exists('abj404_rebuildNGramCacheListener')) { function abj404_rebuildNGramCacheListener($offset = 0) { require_once(plugin_dir_path( __FILE__ ) . "includes/Loader.php"); $dbUpgrades = ABJ_404_Solution_DatabaseUpgradesEtc::getInstance(); $dbUpgrades->rebuildNGramCacheAsync($offset); } } if (!function_exists('abj404_networkActivationListener')) { function abj404_networkActivationListener() { require_once(plugin_dir_path( __FILE__ ) . "includes/Loader.php"); ABJ_404_Solution_PluginLogic::networkActivationCronHandler(); } } if (!function_exists('abj404_networkActivationBackgroundListener')) { function abj404_networkActivationBackgroundListener() { require_once(plugin_dir_path( __FILE__ ) . "includes/Loader.php"); $upgradesEtc = ABJ_404_Solution_DatabaseUpgradesEtc::getInstance(); $upgradesEtc->processMultisiteActivationBatch(); } } add_action('abj404_cleanupCronAction', 'abj404_dailyMaintenanceCronJobListener'); add_action('abj404_updateLogsHitsTableAction', 'abj404_updateLogsHitsTableListener'); add_action('abj404_updatePermalinkCacheAction', 'abj404_updatePermalinkCacheListener', 10, 2); add_action('abj404_rebuild_ngram_cache_hook', 'abj404_rebuildNGramCacheListener', 10, 1); add_action('abj404_network_activation_hook', 'abj404_networkActivationListener'); add_action('abj404_network_activation_background', 'abj404_networkActivationBackgroundListener'); /** * Override the locale for this plugin if user has configured a language override. * This allows users to use a different language for the 404 Solution plugin * than their WordPress site language or user language preference. * * @param string $locale The current locale. * @param string $domain The text domain. * @return string The locale to use for translation loading. */ if (!function_exists('abj404_override_plugin_locale')) { function abj404_override_plugin_locale($locale, $domain) { // Only override for our plugin's text domain if ($domain === '404-solution') { $options = abj404_get_settings_options(); // Check if language override is set and not empty if (is_array($options) && !empty($options['plugin_language_override'])) { return $options['plugin_language_override']; } } return $locale; } } add_filter('plugin_locale', 'abj404_override_plugin_locale', 999, 2); if (!function_exists('abj404_show_runtime_integrity_notice')) { function abj404_show_runtime_integrity_notice() { if (!is_admin() || !current_user_can('manage_options')) { return; } $missing = get_transient('abj404_runtime_missing_files'); if (!is_array($missing) || count($missing) === 0) { return; } echo '
404 Solution: '; echo esc_html(__('Some required plugin files are missing. Please reinstall the plugin package.', '404-solution')); echo '
' . esc_html(implode(', ', array_map('basename', $missing))) . '
404 Solution: ' . esc_html($notice['message']) . '