PluginProbe
Polylang / 2.8.3
Polylang v2.8.3
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.8.3, at frontend/choose-lang.php

358 lines 11.7 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 // Check headers have not been sent to avoid ugly error
89 // Cookie domain must be set to false for localhost ( default value for COOKIE_DOMAIN ) thanks to Stephen Harris.
90 if ( ! pll_is_cache_active() && ! headers_sent() && PLL_COOKIE !== false && ! empty( $this->curlang ) && ( ! isset( $_COOKIE[ PLL_COOKIE ] ) || $_COOKIE[ PLL_COOKIE ] != $this->curlang->slug ) && ! is_404() ) {
91
92 /**
93 * Filter the Polylang cookie duration
94 * /!\ this filter may be fired *before* the theme is loaded
95 *
96 * @since 1.8
97 *
98 * @param int $duration cookie duration in seconds
99 */
100 $expiration = apply_filters( 'pll_cookie_expiration', YEAR_IN_SECONDS );
101
102 setcookie(
103 PLL_COOKIE,
104 $this->curlang->slug,
105 time() + $expiration,
106 COOKIEPATH,
107 2 == $this->options['force_lang'] ? wp_parse_url( $this->links_model->home, PHP_URL_HOST ) : COOKIE_DOMAIN,
108 is_ssl()
109 );
110 }
111 }
112
113 /**
114 * Get the preferred language according to the browser preferences
115 * Code adapted from http://www.thefutureoftheweb.com/blog/use-accept-language-header
116 *
117 * @since 1.8
118 *
119 * @return string|bool the preferred language slug or false
120 */
121 public function get_preferred_browser_language() {
122 $accept_langs = array();
123
124 if ( isset( $_SERVER['HTTP_ACCEPT_LANGUAGE'] ) ) {
125 // Break up string into pieces ( languages and q factors )
126 preg_match_all(
127 '/([a-z]{1,8}(-[a-z]{1,8})?)\s*(;\s*q\s*=\s*((?>1|0)(?>\.[0-9]+)?))?/i',
128 sanitize_text_field( wp_unslash( $_SERVER['HTTP_ACCEPT_LANGUAGE'] ) ),
129 $lang_parse
130 );
131
132 $k = $lang_parse[1];
133 $v = $lang_parse[4];
134
135 if ( $n = count( $k ) ) {
136 // Set default to 1 for any without q factor
137 foreach ( $v as $key => $val ) {
138 if ( '' === $val || (float) $val > 1 ) {
139 $v[ $key ] = 1;
140 }
141 }
142
143 // Bubble sort ( need a stable sort for Android, so can't use a PHP sort function )
144 if ( $n > 1 ) {
145 for ( $i = 2; $i <= $n; $i++ ) {
146 for ( $j = 0; $j <= $n - 2; $j++ ) {
147 if ( $v[ $j ] < $v[ $j + 1 ] ) {
148 // Swap values
149 $temp = $v[ $j ];
150 $v[ $j ] = $v[ $j + 1 ];
151 $v[ $j + 1 ] = $temp;
152 // Swap keys
153 $temp = $k[ $j ];
154 $k[ $j ] = $k[ $j + 1 ];
155 $k[ $j + 1 ] = $temp;
156 }
157 }
158 }
159 }
160 $accept_langs = array_combine( $k, $v );
161 }
162 }
163
164 $accept_langs = array_filter( $accept_langs ); // Remove languages marked as unacceptable (q=0).
165
166 $languages = $this->model->get_languages_list( array( 'hide_empty' => true ) ); // Hides languages with no post
167
168 /**
169 * Filter the list of languages to use to match the browser preferences
170 *
171 * @since 1.9.3
172 *
173 * @param array $languages array of PLL_Language objects
174 */
175 $languages = apply_filters( 'pll_languages_for_browser_preferences', $languages );
176
177 // Looks through sorted list and use first one that matches our language list
178 foreach ( array_keys( $accept_langs ) as $accept_lang ) {
179 // First loop to match the exact locale
180 foreach ( $languages as $language ) {
181 if ( 0 === strcasecmp( $accept_lang, $language->get_locale( 'display' ) ) ) {
182 return $language->slug;
183 }
184 }
185
186 // Second loop to match the language set
187 foreach ( $languages as $language ) {
188 if ( 0 === stripos( $accept_lang, $language->slug ) || 0 === stripos( $language->get_locale( 'display' ), $accept_lang ) ) {
189 return $language->slug;
190 }
191 }
192 }
193 return false;
194 }
195
196 /**
197 * Returns the preferred language
198 * either from the cookie if it's a returning visit
199 * or according to browser preference
200 * or the default language
201 *
202 * @since 0.1
203 *
204 * @return object browser preferred language or default language
205 */
206 public function get_preferred_language() {
207 $language = false;
208 $cookie = false;
209
210 if ( isset( $_COOKIE[ PLL_COOKIE ] ) ) {
211 // Check first if the user was already browsing this site.
212 $language = sanitize_key( $_COOKIE[ PLL_COOKIE ] );
213 $cookie = true;
214 } elseif ( $this->options['browser'] ) {
215 $language = $this->get_preferred_browser_language();
216 }
217
218 /**
219 * Filter the visitor's preferred language (normally set first by cookie
220 * if this is not the first visit, then by the browser preferences).
221 * If no preferred language has been found or set by this filter,
222 * Polylang fallbacks to the default language
223 *
224 * @since 1.0
225 * @since 2.7 Added $cookie parameter.
226 *
227 * @param string|bool $language Preferred language code, false if none has been found.
228 * @param bool $cookie Whether the preferred language has been defined by the cookie.
229 */
230 $slug = apply_filters( 'pll_preferred_language', $language, $cookie );
231
232 // 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
233 return ( $lang = $this->model->get_language( $slug ) ) ? $lang : $this->model->get_language( $this->options['default_lang'] );
234 }
235
236 /**
237 * Sets the language when home page is requested
238 *
239 * @since 1.2
240 */
241 protected function home_language() {
242 // 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
243 // Thanks to Ov3rfly http://wordpress.org/support/topic/enhance-feature-when-front-page-is-visited-set-language-according-to-browser
244 $language = $this->options['hide_default'] && ( wp_get_referer() || ! $this->options['browser'] ) ?
245 $this->model->get_language( $this->options['default_lang'] ) :
246 $this->get_preferred_language(); // Sets the language according to browser preference or default language
247 $this->set_language( $language );
248 }
249
250 /**
251 * To call when the home page has been requested
252 * Make sure to call this after 'setup_theme' has been fired as we need $wp_query
253 * Performs a redirection to the home page in the current language if needed
254 *
255 * @since 0.9
256 */
257 public function home_requested() {
258 // We are already on the right page
259 if ( $this->options['default_lang'] == $this->curlang->slug && $this->options['hide_default'] ) {
260 $this->set_curlang_in_query( $GLOBALS['wp_query'] );
261
262 /**
263 * Fires when the site root page is requested
264 *
265 * @since 1.8
266 */
267 do_action( 'pll_home_requested' );
268 }
269 // Redirect to the home page in the right language
270 // Test to avoid crash if get_home_url returns something wrong
271 // FIXME why this happens? http://wordpress.org/support/topic/polylang-crashes-1
272 // Don't redirect if $_POST is not empty as it could break other plugins
273 elseif ( is_string( $redirect = $this->curlang->home_url ) && empty( $_POST ) ) { // phpcs:ignore WordPress.Security.NonceVerification
274 // Don't forget the query string which may be added by plugins
275 $query_string = wp_parse_url( pll_get_requested_url(), PHP_URL_QUERY );
276 if ( ! empty( $query_string ) ) {
277 $redirect .= ( $this->links_model->using_permalinks ? '?' : '&' ) . $query_string;
278 }
279
280 /**
281 * When a visitor reaches the site home, Polylang redirects to the home page in the correct language.
282 * This filter allows plugins to modify the redirected url or prevent this redirection
283 * /!\ this filter may be fired *before* the theme is loaded
284 *
285 * @since 1.1.1
286 *
287 * @param string $redirect the url the visitor will be redirected to
288 */
289 if ( $redirect = apply_filters( 'pll_redirect_home', $redirect ) ) {
290 $this->maybe_setcookie();
291 header( 'Vary: Accept-Language' );
292 wp_safe_redirect( $redirect, 302, POLYLANG );
293 exit;
294 }
295 }
296 }
297
298 /**
299 * Set the language when posting a comment
300 *
301 * @since 0.8.4
302 *
303 * @param int $post_id the post being commented
304 */
305 public function pre_comment_on_post( $post_id ) {
306 $this->set_language( $this->model->post->get_language( $post_id ) );
307 }
308
309 /**
310 * Modifies some main query vars for home page and page for posts
311 * to enable one home page ( and one page for posts ) per language
312 *
313 * @since 1.2
314 *
315 * @param object $query instance of WP_Query
316 */
317 public function parse_main_query( $query ) {
318 if ( ! $query->is_main_query() ) {
319 return;
320 }
321
322 /**
323 * This filter allows to set the language based on information contained in the main query
324 *
325 * @since 1.8
326 *
327 * @param bool|object $lang false or language object
328 * @param object $query WP_Query object
329 */
330 if ( $lang = apply_filters( 'pll_set_language_from_query', false, $query ) ) {
331 $this->set_language( $lang );
332 $this->set_curlang_in_query( $query );
333 }
334
335 // sets is_home on translated home page when it displays posts
336 // is_home must be true on page 2, 3... too
337 // as well as when searching an empty string: http://wordpress.org/support/topic/plugin-polylang-polylang-breaks-search-in-spun-theme
338 elseif ( ( count( $query->query ) == 1 || ( is_paged() && count( $query->query ) == 2 ) || ( isset( $query->query['s'] ) && ! $query->query['s'] ) ) && $lang = get_query_var( 'lang' ) ) {
339 $lang = $this->model->get_language( $lang );
340 $this->set_language( $lang ); // sets the language now otherwise it will be too late to filter sticky posts !
341 $query->is_home = true;
342 $query->is_archive = $query->is_tax = false;
343 }
344 }
345
346 /**
347 * Sets the current language in the query
348 *
349 * @since 2.2
350 *
351 * @param object $query
352 */
353 protected function set_curlang_in_query( &$query ) {
354 $pll_query = new PLL_Query( $query, $this->model );
355 $pll_query->set_language( $this->curlang );
356 }
357 }
358