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

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