PluginProbe
Polylang / 2.9.2
Polylang v2.9.2
3.8.9 3.8.8 3.8.7 3.8.6 3.8.5 3.8.4 3.8.3 2.7 2.7.0.1 2.7.1 2.7.2 2.7.3 2.7.4 2.8 2.8.1 2.8.2 2.8.3 2.8.4 2.9 2.9.1 2.9.2 3.0 3.0.1 3.0.2 3.0.3 All 233 releases
polylang / frontend / choose-lang.php

choose-lang.php in Polylang 2.9.2, at frontend/choose-lang.php

342 lines 11.1 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2 /**
3 * @package Polylang
4 */
5
6 /**
7 * Base class to choose the language
8 *
9 * @since 1.2
10 */
11 abstract class PLL_Choose_Lang {
12 public $links_model, $model, $options;
13 public $curlang;
14
15 /**
16 * Constructor
17 *
18 * @since 1.2
19 *
20 * @param object $polylang
21 */
22 public function __construct( &$polylang ) {
23 $this->links_model = &$polylang->links_model;
24 $this->model = &$polylang->model;
25 $this->options = &$polylang->options;
26
27 $this->curlang = &$polylang->curlang;
28 }
29
30 /**
31 * Sets the language for ajax requests
32 * and setup actions
33 * Any child class must call this method if it overrides it
34 *
35 * @since 1.8
36 */
37 public function init() {
38 if ( Polylang::is_ajax_on_front() || ! wp_using_themes() ) {
39 $this->set_language( empty( $_REQUEST['lang'] ) ? $this->get_preferred_language() : $this->model->get_language( sanitize_key( $_REQUEST['lang'] ) ) ); // phpcs:ignore WordPress.Security.NonceVerification
40 }
41
42 add_action( 'pre_comment_on_post', array( $this, 'pre_comment_on_post' ) ); // sets the language of comment
43 add_action( 'parse_query', array( $this, 'parse_main_query' ), 2 ); // sets the language in special cases
44 add_action( 'wp', array( $this, 'maybe_setcookie' ), 7 );
45 }
46
47 /**
48 * Writes language cookie
49 * Loads user defined translations
50 * Fires the action 'pll_language_defined'
51 *
52 * @since 1.2
53 *
54 * @param object $curlang current language
55 */
56 protected function set_language( $curlang ) {
57 // Don't set the language a second time
58 if ( isset( $this->curlang ) ) {
59 return;
60 }
61
62 // Final check in case $curlang has an unexpected value
63 // See https://wordpress.org/support/topic/detect-browser-language-sometimes-setting-null-language
64 $this->curlang = ( $curlang instanceof PLL_Language ) ? $curlang : $this->model->get_language( $this->options['default_lang'] );
65
66 $GLOBALS['text_direction'] = $this->curlang->is_rtl ? 'rtl' : 'ltr';
67 wp_styles()->text_direction = $GLOBALS['text_direction'];
68
69 /**
70 * Fires when the current language is defined
71 *
72 * @since 0.9.5
73 *
74 * @param string $slug current language code
75 * @param object $curlang current language object
76 */
77 do_action( 'pll_language_defined', $this->curlang->slug, $this->curlang );
78 }
79
80 /**
81 * Set a cookie to remember the language.
82 * Setting PLL_COOKIE to false will disable cookie although it will break some functionalities
83 *
84 * @since 1.5
85 */
86 public function maybe_setcookie() {
87 // Don't set cookie in javascript when a cache plugin is active.
88 if ( ! pll_is_cache_active() && ! empty( $this->curlang ) && ! is_404() ) {
89 $args = array(
90 'domain' => 2 === $this->options['force_lang'] ? wp_parse_url( $this->links_model->home, PHP_URL_HOST ) : COOKIE_DOMAIN,
91 'samesite' => 3 === $this->options['force_lang'] ? 'None' : 'Lax',
92 );
93 PLL_Cookie::set( $this->curlang->slug, $args );
94 }
95 }
96
97 /**
98 * Get the preferred language according to the browser preferences
99 * Code adapted from http://www.thefutureoftheweb.com/blog/use-accept-language-header
100 *
101 * @since 1.8
102 *
103 * @return string|bool the preferred language slug or false
104 */
105 public function get_preferred_browser_language() {
106 $accept_langs = array();
107
108 if ( isset( $_SERVER['HTTP_ACCEPT_LANGUAGE'] ) ) {
109 // Break up string into pieces ( languages and q factors )
110 preg_match_all(
111 '/([a-z]{1,8}(-[a-z]{1,8})?)\s*(;\s*q\s*=\s*((?>1|0)(?>\.[0-9]+)?))?/i',
112 sanitize_text_field( wp_unslash( $_SERVER['HTTP_ACCEPT_LANGUAGE'] ) ),
113 $lang_parse
114 );
115
116 $k = $lang_parse[1];
117 $v = $lang_parse[4];
118
119 if ( $n = count( $k ) ) {
120 // Set default to 1 for any without q factor
121 foreach ( $v as $key => $val ) {
122 if ( '' === $val || (float) $val > 1 ) {
123 $v[ $key ] = 1;
124 }
125 }
126
127 // Bubble sort ( need a stable sort for Android, so can't use a PHP sort function )
128 if ( $n > 1 ) {
129 for ( $i = 2; $i <= $n; $i++ ) {
130 for ( $j = 0; $j <= $n - 2; $j++ ) {
131 if ( $v[ $j ] < $v[ $j + 1 ] ) {
132 // Swap values
133 $temp = $v[ $j ];
134 $v[ $j ] = $v[ $j + 1 ];
135 $v[ $j + 1 ] = $temp;
136 // Swap keys
137 $temp = $k[ $j ];
138 $k[ $j ] = $k[ $j + 1 ];
139 $k[ $j + 1 ] = $temp;
140 }
141 }
142 }
143 }
144 $accept_langs = array_combine( $k, $v );
145 }
146 }
147
148 $accept_langs = array_filter( $accept_langs ); // Remove languages marked as unacceptable (q=0).
149
150 $languages = $this->model->get_languages_list( array( 'hide_empty' => true ) ); // Hides languages with no post
151
152 /**
153 * Filter the list of languages to use to match the browser preferences
154 *
155 * @since 1.9.3
156 *
157 * @param array $languages array of PLL_Language objects
158 */
159 $languages = apply_filters( 'pll_languages_for_browser_preferences', $languages );
160
161 // Looks through sorted list and use first one that matches our language list
162 foreach ( array_keys( $accept_langs ) as $accept_lang ) {
163 // First loop to match the exact locale
164 foreach ( $languages as $language ) {
165 if ( 0 === strcasecmp( $accept_lang, $language->get_locale( 'display' ) ) ) {
166 return $language->slug;
167 }
168 }
169
170 // Second loop to match the language set
171 foreach ( $languages as $language ) {
172 if ( 0 === stripos( $accept_lang, $language->slug ) || 0 === stripos( $language->get_locale( 'display' ), $accept_lang ) ) {
173 return $language->slug;
174 }
175 }
176 }
177 return false;
178 }
179
180 /**
181 * Returns the preferred language
182 * either from the cookie if it's a returning visit
183 * or according to browser preference
184 * or the default language
185 *
186 * @since 0.1
187 *
188 * @return object browser preferred language or default language
189 */
190 public function get_preferred_language() {
191 $language = false;
192 $cookie = false;
193
194 if ( isset( $_COOKIE[ PLL_COOKIE ] ) ) {
195 // Check first if the user was already browsing this site.
196 $language = sanitize_key( $_COOKIE[ PLL_COOKIE ] );
197 $cookie = true;
198 } elseif ( $this->options['browser'] ) {
199 $language = $this->get_preferred_browser_language();
200 }
201
202 /**
203 * Filter the visitor's preferred language (normally set first by cookie
204 * if this is not the first visit, then by the browser preferences).
205 * If no preferred language has been found or set by this filter,
206 * Polylang fallbacks to the default language
207 *
208 * @since 1.0
209 * @since 2.7 Added $cookie parameter.
210 *
211 * @param string|bool $language Preferred language code, false if none has been found.
212 * @param bool $cookie Whether the preferred language has been defined by the cookie.
213 */
214 $slug = apply_filters( 'pll_preferred_language', $language, $cookie );
215
216 // Return default if there is no preferences in the browser or preferences does not match our languages or it is requested not to use the browser preference
217 return ( $lang = $this->model->get_language( $slug ) ) ? $lang : $this->model->get_language( $this->options['default_lang'] );
218 }
219
220 /**
221 * Sets the language when home page is requested
222 *
223 * @since 1.2
224 */
225 protected function home_language() {
226 // Test referer in case PLL_COOKIE is set to false. Since WP 3.6.1, wp_get_referer() validates the host which is exactly what we want
227 // Thanks to Ov3rfly http://wordpress.org/support/topic/enhance-feature-when-front-page-is-visited-set-language-according-to-browser
228 $language = $this->options['hide_default'] && ( wp_get_referer() || ! $this->options['browser'] ) ?
229 $this->model->get_language( $this->options['default_lang'] ) :
230 $this->get_preferred_language(); // Sets the language according to browser preference or default language
231 $this->set_language( $language );
232 }
233
234 /**
235 * To call when the home page has been requested
236 * Make sure to call this after 'setup_theme' has been fired as we need $wp_query
237 * Performs a redirection to the home page in the current language if needed
238 *
239 * @since 0.9
240 */
241 public function home_requested() {
242 // We are already on the right page
243 if ( $this->options['default_lang'] == $this->curlang->slug && $this->options['hide_default'] ) {
244 $this->set_curlang_in_query( $GLOBALS['wp_query'] );
245
246 /**
247 * Fires when the site root page is requested
248 *
249 * @since 1.8
250 */
251 do_action( 'pll_home_requested' );
252 }
253 // Redirect to the home page in the right language
254 // Test to avoid crash if get_home_url returns something wrong
255 // FIXME why this happens? http://wordpress.org/support/topic/polylang-crashes-1
256 // Don't redirect if $_POST is not empty as it could break other plugins
257 elseif ( is_string( $redirect = $this->curlang->home_url ) && empty( $_POST ) ) { // phpcs:ignore WordPress.Security.NonceVerification
258 // Don't forget the query string which may be added by plugins
259 $query_string = wp_parse_url( pll_get_requested_url(), PHP_URL_QUERY );
260 if ( ! empty( $query_string ) ) {
261 $redirect .= ( $this->links_model->using_permalinks ? '?' : '&' ) . $query_string;
262 }
263
264 /**
265 * When a visitor reaches the site home, Polylang redirects to the home page in the correct language.
266 * This filter allows plugins to modify the redirected url or prevent this redirection
267 * /!\ this filter may be fired *before* the theme is loaded
268 *
269 * @since 1.1.1
270 *
271 * @param string $redirect the url the visitor will be redirected to
272 */
273 if ( $redirect = apply_filters( 'pll_redirect_home', $redirect ) ) {
274 $this->maybe_setcookie();
275 header( 'Vary: Accept-Language' );
276 wp_safe_redirect( $redirect, 302, POLYLANG );
277 exit;
278 }
279 }
280 }
281
282 /**
283 * Set the language when posting a comment
284 *
285 * @since 0.8.4
286 *
287 * @param int $post_id the post being commented
288 */
289 public function pre_comment_on_post( $post_id ) {
290 $this->set_language( $this->model->post->get_language( $post_id ) );
291 }
292
293 /**
294 * Modifies some main query vars for home page and page for posts
295 * to enable one home page ( and one page for posts ) per language
296 *
297 * @since 1.2
298 *
299 * @param object $query instance of WP_Query
300 */
301 public function parse_main_query( $query ) {
302 if ( ! $query->is_main_query() ) {
303 return;
304 }
305
306 /**
307 * This filter allows to set the language based on information contained in the main query
308 *
309 * @since 1.8
310 *
311 * @param bool|object $lang false or language object
312 * @param object $query WP_Query object
313 */
314 if ( $lang = apply_filters( 'pll_set_language_from_query', false, $query ) ) {
315 $this->set_language( $lang );
316 $this->set_curlang_in_query( $query );
317 }
318
319 // sets is_home on translated home page when it displays posts
320 // is_home must be true on page 2, 3... too
321 // as well as when searching an empty string: http://wordpress.org/support/topic/plugin-polylang-polylang-breaks-search-in-spun-theme
322 elseif ( ( count( $query->query ) == 1 || ( is_paged() && count( $query->query ) == 2 ) || ( isset( $query->query['s'] ) && ! $query->query['s'] ) ) && $lang = get_query_var( 'lang' ) ) {
323 $lang = $this->model->get_language( $lang );
324 $this->set_language( $lang ); // sets the language now otherwise it will be too late to filter sticky posts !
325 $query->is_home = true;
326 $query->is_archive = $query->is_tax = false;
327 }
328 }
329
330 /**
331 * Sets the current language in the query
332 *
333 * @since 2.2
334 *
335 * @param object $query
336 */
337 protected function set_curlang_in_query( &$query ) {
338 $pll_query = new PLL_Query( $query, $this->model );
339 $pll_query->set_language( $this->curlang );
340 }
341 }
342