Changeset 3442686
- Timestamp:
- 01/19/2026 05:02:29 PM (8 weeks ago)
- Location:
- 404-solution
- Files:
-
- 8 edited
- 6 copied
-
tags/3.1.8 (copied) (copied from 404-solution/trunk)
-
tags/3.1.8/404-solution.php (copied) (copied from 404-solution/trunk/404-solution.php) (1 diff)
-
tags/3.1.8/CHANGELOG.md (copied) (copied from 404-solution/trunk/CHANGELOG.md) (1 diff)
-
tags/3.1.8/README.md (copied) (copied from 404-solution/trunk/README.md) (1 diff)
-
tags/3.1.8/includes/DataAccess.php (modified) (5 diffs)
-
tags/3.1.8/includes/DatabaseUpgradesEtc.php (copied) (copied from 404-solution/trunk/includes/DatabaseUpgradesEtc.php)
-
tags/3.1.8/includes/PluginLogic.php (modified) (4 diffs)
-
tags/3.1.8/readme.txt (copied) (copied from 404-solution/trunk/readme.txt) (2 diffs)
-
trunk/404-solution.php (modified) (1 diff)
-
trunk/CHANGELOG.md (modified) (1 diff)
-
trunk/README.md (modified) (1 diff)
-
trunk/includes/DataAccess.php (modified) (5 diffs)
-
trunk/includes/PluginLogic.php (modified) (4 diffs)
-
trunk/readme.txt (modified) (2 diffs)
Legend:
- Unmodified
- Added
- Removed
-
404-solution/tags/3.1.8/404-solution.php
r3423944 r3442686 8 8 Author URI: https://www.ajexperience.com/404-solution/ 9 9 10 Version: 3.1. 710 Version: 3.1.8 11 11 Requires at least: 5.0 12 12 Requires PHP: 7.4 -
404-solution/tags/3.1.8/CHANGELOG.md
r3423944 r3442686 1 1 # Changelog # 2 3 ## Version 3.1.8 (Jan 19, 2026) ## 4 * FIX: Preserve Unicode slugs during redirect lookups and allow manual redirect source paths with non-ASCII characters. 5 * FIX: TranslatePress-aware redirect translation for localized paths with a filter hook for other multilingual plugins. 2 6 3 7 ## Version 3.1.7 (Dec 19, 2025) ## -
404-solution/tags/3.1.8/README.md
r3423944 r3442686 81 81 ## Changelog ## 82 82 83 ## Version 3.1.8 (Jan 19, 2026) ## 84 * FIX: Preserve Unicode slugs during redirect lookups and allow manual redirect source paths with non-ASCII characters. 85 * FIX: TranslatePress-aware redirect translation for localized paths with a filter hook for other multilingual plugins. 86 83 87 ## Version 3.1.7 (Dec 19, 2025) ## 84 88 * FIX: Prevent invalid SQL during missing-index creation by parsing index definitions from the plugin SQL templates and emitting structured `ALTER TABLE ... ADD INDEX ...` statements. -
404-solution/tags/3.1.8/includes/DataAccess.php
r3423122 r3442686 2783 2783 */ 2784 2784 function getActiveRedirectForURL($url) { 2785 $redirect = array(); 2786 2787 // remove ridiculous non-printable characters 2788 $url = preg_replace('/[^\x20-\x7E]/', '', $url); // Remove non-printable ASCII characters 2785 // Strip invalid UTF-8/control bytes but keep valid unicode for multilingual slugs. 2786 $url = $this->f->sanitizeInvalidUTF8($url); 2789 2787 2790 2788 // Normalize to relative path before querying (Issue #24) … … 2798 2796 } 2799 2797 $url = $abj404logic->normalizeToRelativePath($url); 2798 2799 $redirect = $this->getActiveRedirectForNormalizedUrl($url); 2800 if ($redirect['id'] !== 0) { 2801 return $redirect; 2802 } 2803 2804 if (strpos($url, '%') !== false) { 2805 $decodedUrl = rawurldecode($url); 2806 if ($decodedUrl !== $url) { 2807 $decodedUrl = $this->f->sanitizeInvalidUTF8($decodedUrl); 2808 $decodedUrl = $abj404logic->normalizeToRelativePath($decodedUrl); 2809 $redirect = $this->getActiveRedirectForNormalizedUrl($decodedUrl); 2810 } 2811 } 2812 2813 return $redirect; 2814 } 2815 2816 /** Get the redirect for the URL. 2817 * @param string $url 2818 * @return array 2819 */ 2820 function getExistingRedirectForURL($url) { 2821 // Strip invalid UTF-8/control bytes but keep valid unicode for multilingual slugs. 2822 $url = $this->f->sanitizeInvalidUTF8($url); 2823 2824 // Normalize to relative path before querying (Issue #24) 2825 // Fix HIGH #1 (5th review): Abort operation if normalization fails 2826 // Querying with un-normalized URLs causes lookup failures 2827 $abj404logic = ABJ_404_Solution_PluginLogic::getInstance(); 2828 if ($abj404logic === null) { 2829 $abj404logging = ABJ_404_Solution_Logging::getInstance(); 2830 $abj404logging->errorMessage("CRITICAL: PluginLogic singleton not initialized in getExistingRedirectForURL()! Cannot normalize URL, aborting: " . $url); 2831 return array('id' => 0); // Return empty result - no redirect found 2832 } 2833 $url = $abj404logic->normalizeToRelativePath($url); 2834 2835 $redirect = $this->getExistingRedirectForNormalizedUrl($url); 2836 if ($redirect['id'] !== 0) { 2837 return $redirect; 2838 } 2839 2840 if (strpos($url, '%') !== false) { 2841 $decodedUrl = rawurldecode($url); 2842 if ($decodedUrl !== $url) { 2843 $decodedUrl = $this->f->sanitizeInvalidUTF8($decodedUrl); 2844 $decodedUrl = $abj404logic->normalizeToRelativePath($decodedUrl); 2845 $redirect = $this->getExistingRedirectForNormalizedUrl($decodedUrl); 2846 } 2847 } 2848 2849 return $redirect; 2850 } 2851 2852 private function getActiveRedirectForNormalizedUrl($url) { 2853 $redirect = array(); 2800 2854 2801 2855 // we look for two URLs that might match. one with a trailing slash and one without. … … 2809 2863 $url2 = $url2 . '/'; 2810 2864 } 2811 2865 2812 2866 // join to the wp_posts table to make sure the post exists. 2813 2867 $query = ABJ_404_Solution_Functions::readFileContents(__DIR__ . "/sql/getPermalinkFromURL.sql"); … … 2820 2874 2821 2875 if (is_array($rows)) { 2822 if (empty($rows)) { 2823 $redirect['id'] = 0; 2824 2825 } else { 2826 foreach ($rows[0] as $key => $value) { 2827 $redirect[$key] = $value; 2828 } 2829 } 2830 } 2876 if (empty($rows)) { 2877 $redirect['id'] = 0; 2878 } else { 2879 foreach ($rows[0] as $key => $value) { 2880 $redirect[$key] = $value; 2881 } 2882 } 2883 } 2884 2885 if (!isset($redirect['id'])) { 2886 $redirect['id'] = 0; 2887 } 2888 2831 2889 return $redirect; 2832 2890 } 2833 2891 2834 /** Get the redirect for the URL. 2835 * @param string $url 2836 * @return array 2837 */ 2838 function getExistingRedirectForURL($url) { 2892 private function getExistingRedirectForNormalizedUrl($url) { 2839 2893 $redirect = array(); 2840 2894 2841 // remove ridiculous non-printable characters2842 $url = preg_replace('/[^\x20-\x7E]/', '', $url); // Remove non-printable ASCII characters2843 2844 // Normalize to relative path before querying (Issue #24)2845 // Fix HIGH #1 (5th review): Abort operation if normalization fails2846 // Querying with un-normalized URLs causes lookup failures2847 $abj404logic = ABJ_404_Solution_PluginLogic::getInstance();2848 if ($abj404logic === null) {2849 $abj404logging = ABJ_404_Solution_Logging::getInstance();2850 $abj404logging->errorMessage("CRITICAL: PluginLogic singleton not initialized in getExistingRedirectForURL()! Cannot normalize URL, aborting: " . $url);2851 return array('id' => 0); // Return empty result - no redirect found2852 }2853 $url = $abj404logic->normalizeToRelativePath($url);2854 2855 2895 // a disabled value of '1' means in the trash. 2856 $query = $this->prepare_query_wp('select * from {wp_abj404_redirects} where BINARY url = BINARY {url} ' . 2896 $query = $this->prepare_query_wp('select * from {wp_abj404_redirects} where BINARY url = BINARY {url} ' . 2857 2897 " and disabled = 0 ", array("url" => $url)); 2858 2898 $results = $this->queryAndGetResults($query); … … 2860 2900 2861 2901 if (is_array($rows)) { 2862 if (empty($rows)) { 2863 $redirect['id'] = 0; 2864 2865 } else { 2866 foreach ($rows[0] as $key => $value) { 2867 $redirect[$key] = $value; 2868 } 2869 } 2870 } 2902 if (empty($rows)) { 2903 $redirect['id'] = 0; 2904 } else { 2905 foreach ($rows[0] as $key => $value) { 2906 $redirect[$key] = $value; 2907 } 2908 } 2909 } 2910 2911 if (!isset($redirect['id'])) { 2912 $redirect['id'] = 0; 2913 } 2914 2871 2915 return $redirect; 2872 2916 } -
404-solution/tags/3.1.8/includes/PluginLogic.php
r3421367 r3442686 351 351 352 352 return $relativePath; 353 } 354 355 /** 356 * Translate a redirect destination URL to the current language when possible. 357 * 358 * @param string $location Full URL or path to redirect to. 359 * @param string $requestedURL Original requested path/URL that triggered the 404. 360 * @return string URL to use for redirect. 361 */ 362 function maybeTranslateRedirectUrl($location, $requestedURL = '') { 363 if (!is_string($location) || $location === '') { 364 return $location; 365 } 366 367 $translated = $this->translatePressRedirectUrl($location, $requestedURL); 368 if ($translated !== null && $translated !== '') { 369 $location = $translated; 370 } 371 372 // Allow other multilingual plugins/themes to override redirect destinations. 373 return apply_filters('abj404_translate_redirect_url', $location, $requestedURL); 374 } 375 376 private function translatePressRedirectUrl($location, $requestedURL) { 377 if (!$this->translatePressIntegrationAvailable()) { 378 return null; 379 } 380 381 if (!$this->isLocalUrl($location)) { 382 return null; 383 } 384 385 $language = $this->getTranslatePressLanguageFromRequest($requestedURL); 386 if ($language === '') { 387 return null; 388 } 389 390 $translated = $this->translatePressTranslateUrl($location, $language); 391 if (!is_string($translated) || $translated === '' || $translated === $location) { 392 return null; 393 } 394 395 if (!$this->isLocalUrl($translated)) { 396 return null; 397 } 398 399 return $translated; 400 } 401 402 private function translatePressIntegrationAvailable() { 403 return function_exists('trp_get_language_from_url') || 404 function_exists('trp_get_current_language') || 405 function_exists('trp_get_url_for_language') || 406 function_exists('trp_translate_url') || 407 has_filter('trp_translate_url'); 408 } 409 410 private function translatePressTranslateUrl($url, $language) { 411 if (function_exists('trp_get_url_for_language')) { 412 return trp_get_url_for_language($language, $url); 413 } 414 415 if (function_exists('trp_translate_url')) { 416 return trp_translate_url($url, $language); 417 } 418 419 return apply_filters('trp_translate_url', $url, $language); 420 } 421 422 private function getTranslatePressLanguageFromRequest($requestedURL) { 423 $fullRequestedUrl = $this->buildFullUrlFromRequest($requestedURL); 424 425 if (function_exists('trp_get_language_from_url')) { 426 $language = trp_get_language_from_url($fullRequestedUrl); 427 if (is_string($language) && $language !== '') { 428 return $language; 429 } 430 } 431 432 if (function_exists('trp_get_current_language')) { 433 $language = trp_get_current_language(); 434 if (is_string($language) && $language !== '') { 435 return $language; 436 } 437 } 438 439 return ''; 440 } 441 442 private function buildFullUrlFromRequest($requestedURL) { 443 $path = $requestedURL; 444 if ($path === '' || $path === null) { 445 $userRequest = ABJ_404_Solution_UserRequest::getInstance(); 446 $path = $userRequest->getPathWithSortedQueryString(); 447 } 448 449 if ($path === '' || $path === null) { 450 return home_url('/'); 451 } 452 453 if (preg_match('#^https?://#i', $path)) { 454 return $path; 455 } 456 457 return home_url($path); 458 } 459 460 private function isLocalUrl($url) { 461 if (!is_string($url) || $url === '') { 462 return false; 463 } 464 465 $parsedUrl = function_exists('wp_parse_url') ? wp_parse_url($url) : parse_url($url); 466 if (!is_array($parsedUrl) || !isset($parsedUrl['host'])) { 467 // Relative URLs are treated as local. 468 return true; 469 } 470 471 $siteUrl = home_url(); 472 $parsedSite = function_exists('wp_parse_url') ? wp_parse_url($siteUrl) : parse_url($siteUrl); 473 $siteHost = is_array($parsedSite) && isset($parsedSite['host']) ? strtolower($parsedSite['host']) : ''; 474 475 return $siteHost !== '' && strtolower($parsedUrl['host']) === $siteHost; 353 476 } 354 477 /** Forward to a real page for queries like ?p=10 … … 2061 2184 } 2062 2185 2063 $manualURL = isset($_POST['manual_redirect_url']) ? $_POST['manual_redirect_url'] : ''; 2186 $manualURL = isset($_POST['manual_redirect_url']) ? wp_unslash($_POST['manual_redirect_url']) : ''; 2187 $manualURL = $this->f->sanitizeInvalidUTF8($manualURL); 2188 $manualURL = sanitize_text_field($manualURL); 2189 $manualURL = trim($manualURL); 2064 2190 if ($this->f->substr($manualURL, 0, 1) != "/") { 2065 2191 $message .= __('Error: URL must start with /', '404-solution') . "<BR/>"; … … 2084 2210 $code = isset($_POST['code']) && !empty($_POST['code']) ? $_POST['code'] : ABJ404_STATUS_MANUAL; 2085 2211 2086 $this->dao->setupRedirect( esc_url($_POST['manual_redirect_url']), $statusType,2212 $this->dao->setupRedirect($manualURL, $statusType, 2087 2213 $typeAndDest['type'], $typeAndDest['dest'], 2088 2214 sanitize_text_field($code), 0); … … 2746 2872 */ 2747 2873 function forceRedirect($location, $status = 302, $type = -1, $requestedURL = '', $isCustom404 = false) { 2874 // Translate redirect destination for multilingual sites (TranslatePress, etc.) 2875 $location = $this->maybeTranslateRedirectUrl($location, $requestedURL); 2748 2876 2749 2877 $commentPartAndQueryPart = $this->getCommentPartAndQueryPartOfRequest(); -
404-solution/tags/3.1.8/readme.txt
r3423944 r3442686 6 6 Requires PHP: 7.4 7 7 Tested up to: 6.9 8 Stable tag: 3.1. 78 Stable tag: 3.1.8 9 9 License: GPL-3.0-or-later 10 10 License URI: https://www.gnu.org/licenses/gpl-3.0.html … … 237 237 238 238 == Changelog == 239 240 = Version 3.1.8 (Jan 19, 2026) = 241 * FIX: Preserve Unicode slugs during redirect lookups and allow manual redirect source paths with non-ASCII characters. 242 * FIX: TranslatePress-aware redirect translation for localized paths with a filter hook for other multilingual plugins. 239 243 240 244 = Version 3.1.7 (Dec 19, 2025) = -
404-solution/trunk/404-solution.php
r3423944 r3442686 8 8 Author URI: https://www.ajexperience.com/404-solution/ 9 9 10 Version: 3.1. 710 Version: 3.1.8 11 11 Requires at least: 5.0 12 12 Requires PHP: 7.4 -
404-solution/trunk/CHANGELOG.md
r3423944 r3442686 1 1 # Changelog # 2 3 ## Version 3.1.8 (Jan 19, 2026) ## 4 * FIX: Preserve Unicode slugs during redirect lookups and allow manual redirect source paths with non-ASCII characters. 5 * FIX: TranslatePress-aware redirect translation for localized paths with a filter hook for other multilingual plugins. 2 6 3 7 ## Version 3.1.7 (Dec 19, 2025) ## -
404-solution/trunk/README.md
r3423944 r3442686 81 81 ## Changelog ## 82 82 83 ## Version 3.1.8 (Jan 19, 2026) ## 84 * FIX: Preserve Unicode slugs during redirect lookups and allow manual redirect source paths with non-ASCII characters. 85 * FIX: TranslatePress-aware redirect translation for localized paths with a filter hook for other multilingual plugins. 86 83 87 ## Version 3.1.7 (Dec 19, 2025) ## 84 88 * FIX: Prevent invalid SQL during missing-index creation by parsing index definitions from the plugin SQL templates and emitting structured `ALTER TABLE ... ADD INDEX ...` statements. -
404-solution/trunk/includes/DataAccess.php
r3423122 r3442686 2783 2783 */ 2784 2784 function getActiveRedirectForURL($url) { 2785 $redirect = array(); 2786 2787 // remove ridiculous non-printable characters 2788 $url = preg_replace('/[^\x20-\x7E]/', '', $url); // Remove non-printable ASCII characters 2785 // Strip invalid UTF-8/control bytes but keep valid unicode for multilingual slugs. 2786 $url = $this->f->sanitizeInvalidUTF8($url); 2789 2787 2790 2788 // Normalize to relative path before querying (Issue #24) … … 2798 2796 } 2799 2797 $url = $abj404logic->normalizeToRelativePath($url); 2798 2799 $redirect = $this->getActiveRedirectForNormalizedUrl($url); 2800 if ($redirect['id'] !== 0) { 2801 return $redirect; 2802 } 2803 2804 if (strpos($url, '%') !== false) { 2805 $decodedUrl = rawurldecode($url); 2806 if ($decodedUrl !== $url) { 2807 $decodedUrl = $this->f->sanitizeInvalidUTF8($decodedUrl); 2808 $decodedUrl = $abj404logic->normalizeToRelativePath($decodedUrl); 2809 $redirect = $this->getActiveRedirectForNormalizedUrl($decodedUrl); 2810 } 2811 } 2812 2813 return $redirect; 2814 } 2815 2816 /** Get the redirect for the URL. 2817 * @param string $url 2818 * @return array 2819 */ 2820 function getExistingRedirectForURL($url) { 2821 // Strip invalid UTF-8/control bytes but keep valid unicode for multilingual slugs. 2822 $url = $this->f->sanitizeInvalidUTF8($url); 2823 2824 // Normalize to relative path before querying (Issue #24) 2825 // Fix HIGH #1 (5th review): Abort operation if normalization fails 2826 // Querying with un-normalized URLs causes lookup failures 2827 $abj404logic = ABJ_404_Solution_PluginLogic::getInstance(); 2828 if ($abj404logic === null) { 2829 $abj404logging = ABJ_404_Solution_Logging::getInstance(); 2830 $abj404logging->errorMessage("CRITICAL: PluginLogic singleton not initialized in getExistingRedirectForURL()! Cannot normalize URL, aborting: " . $url); 2831 return array('id' => 0); // Return empty result - no redirect found 2832 } 2833 $url = $abj404logic->normalizeToRelativePath($url); 2834 2835 $redirect = $this->getExistingRedirectForNormalizedUrl($url); 2836 if ($redirect['id'] !== 0) { 2837 return $redirect; 2838 } 2839 2840 if (strpos($url, '%') !== false) { 2841 $decodedUrl = rawurldecode($url); 2842 if ($decodedUrl !== $url) { 2843 $decodedUrl = $this->f->sanitizeInvalidUTF8($decodedUrl); 2844 $decodedUrl = $abj404logic->normalizeToRelativePath($decodedUrl); 2845 $redirect = $this->getExistingRedirectForNormalizedUrl($decodedUrl); 2846 } 2847 } 2848 2849 return $redirect; 2850 } 2851 2852 private function getActiveRedirectForNormalizedUrl($url) { 2853 $redirect = array(); 2800 2854 2801 2855 // we look for two URLs that might match. one with a trailing slash and one without. … … 2809 2863 $url2 = $url2 . '/'; 2810 2864 } 2811 2865 2812 2866 // join to the wp_posts table to make sure the post exists. 2813 2867 $query = ABJ_404_Solution_Functions::readFileContents(__DIR__ . "/sql/getPermalinkFromURL.sql"); … … 2820 2874 2821 2875 if (is_array($rows)) { 2822 if (empty($rows)) { 2823 $redirect['id'] = 0; 2824 2825 } else { 2826 foreach ($rows[0] as $key => $value) { 2827 $redirect[$key] = $value; 2828 } 2829 } 2830 } 2876 if (empty($rows)) { 2877 $redirect['id'] = 0; 2878 } else { 2879 foreach ($rows[0] as $key => $value) { 2880 $redirect[$key] = $value; 2881 } 2882 } 2883 } 2884 2885 if (!isset($redirect['id'])) { 2886 $redirect['id'] = 0; 2887 } 2888 2831 2889 return $redirect; 2832 2890 } 2833 2891 2834 /** Get the redirect for the URL. 2835 * @param string $url 2836 * @return array 2837 */ 2838 function getExistingRedirectForURL($url) { 2892 private function getExistingRedirectForNormalizedUrl($url) { 2839 2893 $redirect = array(); 2840 2894 2841 // remove ridiculous non-printable characters2842 $url = preg_replace('/[^\x20-\x7E]/', '', $url); // Remove non-printable ASCII characters2843 2844 // Normalize to relative path before querying (Issue #24)2845 // Fix HIGH #1 (5th review): Abort operation if normalization fails2846 // Querying with un-normalized URLs causes lookup failures2847 $abj404logic = ABJ_404_Solution_PluginLogic::getInstance();2848 if ($abj404logic === null) {2849 $abj404logging = ABJ_404_Solution_Logging::getInstance();2850 $abj404logging->errorMessage("CRITICAL: PluginLogic singleton not initialized in getExistingRedirectForURL()! Cannot normalize URL, aborting: " . $url);2851 return array('id' => 0); // Return empty result - no redirect found2852 }2853 $url = $abj404logic->normalizeToRelativePath($url);2854 2855 2895 // a disabled value of '1' means in the trash. 2856 $query = $this->prepare_query_wp('select * from {wp_abj404_redirects} where BINARY url = BINARY {url} ' . 2896 $query = $this->prepare_query_wp('select * from {wp_abj404_redirects} where BINARY url = BINARY {url} ' . 2857 2897 " and disabled = 0 ", array("url" => $url)); 2858 2898 $results = $this->queryAndGetResults($query); … … 2860 2900 2861 2901 if (is_array($rows)) { 2862 if (empty($rows)) { 2863 $redirect['id'] = 0; 2864 2865 } else { 2866 foreach ($rows[0] as $key => $value) { 2867 $redirect[$key] = $value; 2868 } 2869 } 2870 } 2902 if (empty($rows)) { 2903 $redirect['id'] = 0; 2904 } else { 2905 foreach ($rows[0] as $key => $value) { 2906 $redirect[$key] = $value; 2907 } 2908 } 2909 } 2910 2911 if (!isset($redirect['id'])) { 2912 $redirect['id'] = 0; 2913 } 2914 2871 2915 return $redirect; 2872 2916 } -
404-solution/trunk/includes/PluginLogic.php
r3421367 r3442686 351 351 352 352 return $relativePath; 353 } 354 355 /** 356 * Translate a redirect destination URL to the current language when possible. 357 * 358 * @param string $location Full URL or path to redirect to. 359 * @param string $requestedURL Original requested path/URL that triggered the 404. 360 * @return string URL to use for redirect. 361 */ 362 function maybeTranslateRedirectUrl($location, $requestedURL = '') { 363 if (!is_string($location) || $location === '') { 364 return $location; 365 } 366 367 $translated = $this->translatePressRedirectUrl($location, $requestedURL); 368 if ($translated !== null && $translated !== '') { 369 $location = $translated; 370 } 371 372 // Allow other multilingual plugins/themes to override redirect destinations. 373 return apply_filters('abj404_translate_redirect_url', $location, $requestedURL); 374 } 375 376 private function translatePressRedirectUrl($location, $requestedURL) { 377 if (!$this->translatePressIntegrationAvailable()) { 378 return null; 379 } 380 381 if (!$this->isLocalUrl($location)) { 382 return null; 383 } 384 385 $language = $this->getTranslatePressLanguageFromRequest($requestedURL); 386 if ($language === '') { 387 return null; 388 } 389 390 $translated = $this->translatePressTranslateUrl($location, $language); 391 if (!is_string($translated) || $translated === '' || $translated === $location) { 392 return null; 393 } 394 395 if (!$this->isLocalUrl($translated)) { 396 return null; 397 } 398 399 return $translated; 400 } 401 402 private function translatePressIntegrationAvailable() { 403 return function_exists('trp_get_language_from_url') || 404 function_exists('trp_get_current_language') || 405 function_exists('trp_get_url_for_language') || 406 function_exists('trp_translate_url') || 407 has_filter('trp_translate_url'); 408 } 409 410 private function translatePressTranslateUrl($url, $language) { 411 if (function_exists('trp_get_url_for_language')) { 412 return trp_get_url_for_language($language, $url); 413 } 414 415 if (function_exists('trp_translate_url')) { 416 return trp_translate_url($url, $language); 417 } 418 419 return apply_filters('trp_translate_url', $url, $language); 420 } 421 422 private function getTranslatePressLanguageFromRequest($requestedURL) { 423 $fullRequestedUrl = $this->buildFullUrlFromRequest($requestedURL); 424 425 if (function_exists('trp_get_language_from_url')) { 426 $language = trp_get_language_from_url($fullRequestedUrl); 427 if (is_string($language) && $language !== '') { 428 return $language; 429 } 430 } 431 432 if (function_exists('trp_get_current_language')) { 433 $language = trp_get_current_language(); 434 if (is_string($language) && $language !== '') { 435 return $language; 436 } 437 } 438 439 return ''; 440 } 441 442 private function buildFullUrlFromRequest($requestedURL) { 443 $path = $requestedURL; 444 if ($path === '' || $path === null) { 445 $userRequest = ABJ_404_Solution_UserRequest::getInstance(); 446 $path = $userRequest->getPathWithSortedQueryString(); 447 } 448 449 if ($path === '' || $path === null) { 450 return home_url('/'); 451 } 452 453 if (preg_match('#^https?://#i', $path)) { 454 return $path; 455 } 456 457 return home_url($path); 458 } 459 460 private function isLocalUrl($url) { 461 if (!is_string($url) || $url === '') { 462 return false; 463 } 464 465 $parsedUrl = function_exists('wp_parse_url') ? wp_parse_url($url) : parse_url($url); 466 if (!is_array($parsedUrl) || !isset($parsedUrl['host'])) { 467 // Relative URLs are treated as local. 468 return true; 469 } 470 471 $siteUrl = home_url(); 472 $parsedSite = function_exists('wp_parse_url') ? wp_parse_url($siteUrl) : parse_url($siteUrl); 473 $siteHost = is_array($parsedSite) && isset($parsedSite['host']) ? strtolower($parsedSite['host']) : ''; 474 475 return $siteHost !== '' && strtolower($parsedUrl['host']) === $siteHost; 353 476 } 354 477 /** Forward to a real page for queries like ?p=10 … … 2061 2184 } 2062 2185 2063 $manualURL = isset($_POST['manual_redirect_url']) ? $_POST['manual_redirect_url'] : ''; 2186 $manualURL = isset($_POST['manual_redirect_url']) ? wp_unslash($_POST['manual_redirect_url']) : ''; 2187 $manualURL = $this->f->sanitizeInvalidUTF8($manualURL); 2188 $manualURL = sanitize_text_field($manualURL); 2189 $manualURL = trim($manualURL); 2064 2190 if ($this->f->substr($manualURL, 0, 1) != "/") { 2065 2191 $message .= __('Error: URL must start with /', '404-solution') . "<BR/>"; … … 2084 2210 $code = isset($_POST['code']) && !empty($_POST['code']) ? $_POST['code'] : ABJ404_STATUS_MANUAL; 2085 2211 2086 $this->dao->setupRedirect( esc_url($_POST['manual_redirect_url']), $statusType,2212 $this->dao->setupRedirect($manualURL, $statusType, 2087 2213 $typeAndDest['type'], $typeAndDest['dest'], 2088 2214 sanitize_text_field($code), 0); … … 2746 2872 */ 2747 2873 function forceRedirect($location, $status = 302, $type = -1, $requestedURL = '', $isCustom404 = false) { 2874 // Translate redirect destination for multilingual sites (TranslatePress, etc.) 2875 $location = $this->maybeTranslateRedirectUrl($location, $requestedURL); 2748 2876 2749 2877 $commentPartAndQueryPart = $this->getCommentPartAndQueryPartOfRequest(); -
404-solution/trunk/readme.txt
r3423944 r3442686 6 6 Requires PHP: 7.4 7 7 Tested up to: 6.9 8 Stable tag: 3.1. 78 Stable tag: 3.1.8 9 9 License: GPL-3.0-or-later 10 10 License URI: https://www.gnu.org/licenses/gpl-3.0.html … … 237 237 238 238 == Changelog == 239 240 = Version 3.1.8 (Jan 19, 2026) = 241 * FIX: Preserve Unicode slugs during redirect lookups and allow manual redirect source paths with non-ASCII characters. 242 * FIX: TranslatePress-aware redirect translation for localized paths with a filter hook for other multilingual plugins. 239 243 240 244 = Version 3.1.7 (Dec 19, 2025) =
Note: See TracChangeset
for help on using the changeset viewer.