| 1 |
<?php |
| 2 |
|
| 3 |
// If this file is called directly, abort. |
| 4 |
if ( ! defined( 'WPINC' ) ) { |
| 5 |
die; |
| 6 |
} |
| 7 |
|
| 8 |
|
| 9 |
/** |
| 10 |
* Outputs a JavaScript snippet to handle browser language-based redirection. |
| 11 |
* |
| 12 |
* This function is designed to inject a JavaScript script into the front-end |
| 13 |
* for redirecting users based on their browser's language settings. It ensures |
| 14 |
* that the script is only output under specific conditions: |
| 15 |
* - The user is not logged in with editing permissions. |
| 16 |
* - The user is not a bot. |
| 17 |
* - The current page is the front page. |
| 18 |
* - The current language matches the website's default language. |
| 19 |
* |
| 20 |
* If the JavaScript file is missing or empty, the function exits early. |
| 21 |
* |
| 22 |
* @return void |
| 23 |
*/ |
| 24 |
function wplng_browser_language_redirect_js_only() { |
| 25 |
|
| 26 |
if ( current_user_can( 'edit_posts' ) |
| 27 |
|| wplng_is_bot() |
| 28 |
|| ! is_front_page() |
| 29 |
|| ( wplng_get_language_website_id() !== wplng_get_language_current_id() ) |
| 30 |
) { |
| 31 |
return; |
| 32 |
} |
| 33 |
|
| 34 |
$script = file_get_contents( WPLNG_PLUGIN_DIR . '/assets/js/browser-redirect.js' ); |
| 35 |
|
| 36 |
if ( empty( $script ) ) { |
| 37 |
return; |
| 38 |
} |
| 39 |
|
| 40 |
$script = str_replace( |
| 41 |
array( '[wplng-cookie-path]', '//# sourceMappingURL=browser-redirect.js.map' ), |
| 42 |
array( COOKIEPATH, '' ), |
| 43 |
$script |
| 44 |
); |
| 45 |
|
| 46 |
echo '<script id="wplingue-browser-redirect-js">' . rtrim( $script ) . '</script>'; |
| 47 |
} |
| 48 |
|
| 49 |
|
| 50 |
/** |
| 51 |
* Redirects the user to the translated version of the page based on browser language or a user-defined cookie. |
| 52 |
* |
| 53 |
* This function is designed to run on the 'template_redirect' hook. It handles the logic for |
| 54 |
* redirecting users to the appropriate language version of a page. The redirection is based on |
| 55 |
* a stored cookie (if available) or the browser's 'Accept-Language' header. |
| 56 |
* |
| 57 |
* It prevents redirection for specific user types (e.g., logged-in users with 'edit_posts' capability) |
| 58 |
* and for known web crawlers and bots. |
| 59 |
* |
| 60 |
* @return void |
| 61 |
*/ |
| 62 |
function wplng_browser_language_redirect_php_js() { |
| 63 |
|
| 64 |
// Exit early for users with editing permissions or for bots to prevent redirection loops. |
| 65 |
if ( current_user_can( 'edit_posts' ) |
| 66 |
|| wplng_is_bot() |
| 67 |
) { |
| 68 |
return; |
| 69 |
} |
| 70 |
|
| 71 |
$language_website_id = wplng_get_language_website_id(); |
| 72 |
$language_current_id = wplng_get_language_current_id(); |
| 73 |
$is_translated_page = $language_website_id !== $language_current_id; |
| 74 |
$cookie = wplng_browser_language_cookie_get(); |
| 75 |
|
| 76 |
// If the user is on a translated page. |
| 77 |
if ( $is_translated_page ) { |
| 78 |
// Update the cookie with the selected language code. |
| 79 |
// This is a voluntary choice by the user. |
| 80 |
wplng_browser_language_cookie_set( $language_current_id ); |
| 81 |
return; |
| 82 |
} |
| 83 |
|
| 84 |
// The user is on the original language page (no language prefix). |
| 85 |
|
| 86 |
// Determine the language to redirect to. |
| 87 |
$redirect_language_id = false; |
| 88 |
|
| 89 |
if ( $cookie !== false ) { |
| 90 |
// If the cookie contains a valid language code, use it for redirection. |
| 91 |
// This honors the user's last choice. |
| 92 |
if ( wplng_is_valid_language_id( $cookie ) ) { |
| 93 |
|
| 94 |
if ( $cookie === $language_website_id ) { |
| 95 |
return; |
| 96 |
} else { |
| 97 |
$redirect_language_id = $cookie; |
| 98 |
} |
| 99 |
} |
| 100 |
} else { |
| 101 |
// This is the very first visit (no cookie). Use the browser's language. |
| 102 |
if ( ! empty( $_SERVER['HTTP_ACCEPT_LANGUAGE'] ) ) { |
| 103 |
$langs = explode( ',', sanitize_text_field( $_SERVER['HTTP_ACCEPT_LANGUAGE'] ) ); |
| 104 |
if ( ! empty( $langs ) ) { |
| 105 |
$language_browser_id = strtolower( substr( trim( $langs[0] ), 0, 2 ) ); |
| 106 |
$redirect_language_id = $language_browser_id; |
| 107 |
} |
| 108 |
} |
| 109 |
} |
| 110 |
|
| 111 |
// Check if the determined language is a valid target language. |
| 112 |
if ( empty( $redirect_language_id ) |
| 113 |
|| ( $redirect_language_id === $language_website_id ) |
| 114 |
|| ! in_array( $redirect_language_id, wplng_get_languages_target_ids(), true ) |
| 115 |
) { |
| 116 |
// If not, set the cookie to 'original' to prevent future redirects on the original page. |
| 117 |
wplng_browser_language_cookie_set( $language_website_id ); |
| 118 |
return; |
| 119 |
} |
| 120 |
|
| 121 |
// Check if the URL to redirect is translatable. |
| 122 |
$url_to_redirect = wplng_get_url_current_for_language( $redirect_language_id ); |
| 123 |
|
| 124 |
if ( $url_to_redirect === wplng_get_url_original() ) { |
| 125 |
wplng_browser_language_cookie_set( $language_website_id ); |
| 126 |
return; |
| 127 |
} |
| 128 |
|
| 129 |
// Perform the redirection. |
| 130 |
wplng_browser_language_cookie_set( $redirect_language_id ); |
| 131 |
wp_safe_redirect( |
| 132 |
$url_to_redirect, |
| 133 |
302 |
| 134 |
); |
| 135 |
exit; |
| 136 |
} |
| 137 |
|
| 138 |
|
| 139 |
/** |
| 140 |
* Retrieves the language code from the 'wplingua-lang' cookie. |
| 141 |
* |
| 142 |
* This function safely retrieves the language code stored in the user's cookie, |
| 143 |
* sanitizing the value to prevent security issues. It returns the language code |
| 144 |
* if it's valid ('original' or a known language ID), otherwise it returns false. |
| 145 |
* |
| 146 |
* @return string|false The language code from the cookie, or false if the cookie is not set or invalid. |
| 147 |
*/ |
| 148 |
function wplng_browser_language_cookie_get() { |
| 149 |
$cookie = false; |
| 150 |
if ( ! empty( $_COOKIE['wplingua-lang'] ) ) { |
| 151 |
$cookie = sanitize_text_field( $_COOKIE['wplingua-lang'] ); |
| 152 |
if ( ! wplng_is_valid_language_id( $cookie ) ) { |
| 153 |
$cookie = false; |
| 154 |
} |
| 155 |
} |
| 156 |
|
| 157 |
return $cookie; |
| 158 |
} |
| 159 |
|
| 160 |
|
| 161 |
/** |
| 162 |
* Sets the 'wplingua-lang' cookie with a specified language ID. |
| 163 |
* |
| 164 |
* This function creates or updates a cookie to store the user's preferred language choice. |
| 165 |
* The cookie is set to expire in 30 days and is accessible across the site's domain. |
| 166 |
* |
| 167 |
* @param string $language_id The language code to be stored (e.g., 'en', 'fr', 'es', or 'original'). |
| 168 |
* @return void |
| 169 |
*/ |
| 170 |
function wplng_browser_language_cookie_set( $language_id ) { |
| 171 |
setcookie( |
| 172 |
'wplingua-lang', |
| 173 |
$language_id, |
| 174 |
time() + 30 * DAY_IN_SECONDS, |
| 175 |
COOKIEPATH, |
| 176 |
COOKIE_DOMAIN |
| 177 |
); |
| 178 |
} |
| 179 |
|
| 180 |
|
| 181 |
/** |
| 182 |
* Determines if the current user agent is a bot or a web crawler. |
| 183 |
* |
| 184 |
* This function checks the user agent string against a predefined list of known bots |
| 185 |
* using a regular expression. The result is cached for the duration of the request |
| 186 |
* to improve performance on multiple calls. |
| 187 |
* |
| 188 |
* @return bool True if the user agent is a bot, false otherwise. |
| 189 |
*/ |
| 190 |
function wplng_is_bot() { |
| 191 |
return (bool) preg_match( |
| 192 |
'/googlebot|bingbot|slurp|duckduckbot|baiduspider|yandex|semrushbot|ahrefsbot|mj12bot|dotbot|exabot|facebookexternalhit/i', |
| 193 |
strtolower( $_SERVER['HTTP_USER_AGENT'] ?? '' ) |
| 194 |
); |
| 195 |
} |
| 196 |
|