PluginProbe
Polylang / trunk
Polylang vtrunk
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 / src / frontend / choose-lang.php

choose-lang.php in Polylang trunk, at src/frontend/choose-lang.php

387 lines 11.5 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 use WP_Syntex\Polylang\Model\Languages;
7
8 /**
9 * Base class to choose the language
10 *
11 * @since 1.2
12 */
13 abstract class PLL_Choose_Lang {
14 /**
15 * Stores the plugin options.
16 *
17 * @var \WP_Syntex\Polylang\Options\Options
18 */
19 public $options;
20
21 /**
22 * @var PLL_Model
23 */
24 public $model;
25
26 /**
27 * Instance of a child class of PLL_Links_Model.
28 *
29 * @var PLL_Links_Model
30 */
31 public $links_model;
32
33 /**
34 * Current language.
35 *
36 * @var PLL_Language|null
37 */
38 public $curlang;
39
40 /**
41 * Constructor
42 *
43 * @since 1.2
44 *
45 * @param PLL_Frontend $polylang The Polylang object.
46 */
47 public function __construct( PLL_Frontend &$polylang ) {
48 $this->links_model = &$polylang->links_model;
49 $this->model = &$polylang->model;
50 $this->options = $polylang->options;
51
52 $this->curlang = &$polylang->curlang;
53 }
54
55 /**
56 * Sets the language for ajax requests
57 * and setup actions
58 * Any child class must call this method if it overrides it
59 *
60 * @since 1.8
61 *
62 * @return void
63 */
64 public function init() {
65 add_action( 'pre_comment_on_post', array( $this, 'pre_comment_on_post' ) ); // sets the language of comment.
66 add_action( 'parse_query', array( $this, 'parse_main_query' ), 2 ); // sets the language in special cases.
67 add_action( 'wp', array( $this, 'maybe_setcookie' ), 7 );
68
69 if ( ! Polylang::is_ajax_on_front() && wp_using_themes() ) {
70 return;
71 }
72
73 if ( isset( $_REQUEST['lang'] ) && is_string( $_REQUEST['lang'] ) ) { // phpcs:ignore WordPress.Security.NonceVerification
74 // Let's accept either WordPress locales or language codes.
75 $lang = $this->model->languages->get( Languages::is_locale( $_REQUEST['lang'] ) ? $_REQUEST['lang'] : sanitize_key( $_REQUEST['lang'] ) ); // phpcs:ignore WordPress.Security.NonceVerification, WordPress.Security.ValidatedSanitizedInput.InputNotSanitized
76 }
77
78 $this->set_language( empty( $lang ) ? $this->get_preferred_language() : $lang );
79 }
80
81 /**
82 * Sets the current language.
83 * Also fires the action 'pll_language_defined'.
84 *
85 * @since 1.2
86 *
87 * @param PLL_Language|false $curlang Optional. Current language. Default is `false`.
88 * @return void
89 */
90 protected function set_language( $curlang = false ): void {
91 // Don't set the language a second time.
92 if ( isset( $this->curlang ) ) {
93 return;
94 }
95
96 if ( ! $curlang instanceof PLL_Language ) {
97 $curlang = $this->get_current_language();
98
99 if ( ! $curlang instanceof PLL_Language ) {
100 $curlang = $this->get_preferred_language();
101 }
102 }
103
104 /**
105 * Filters the language before it is set.
106 *
107 * @since 0.9
108 * @since 3.8 Is used all the time, not only when the language is defined by the content.
109 *
110 * @param PLL_Language|false $curlang Language object or false if none was found.
111 */
112 $curlang = apply_filters( 'pll_get_current_language', $curlang ?? false );
113
114 if ( ! $curlang instanceof PLL_Language ) {
115 $curlang = $this->model->get_default_language();
116
117 if ( ! $curlang instanceof PLL_Language ) {
118 return;
119 }
120 }
121
122 $this->curlang = $curlang;
123
124 $GLOBALS['text_direction'] = $this->curlang->is_rtl ? 'rtl' : 'ltr';
125 if ( did_action( 'wp_default_styles' ) ) {
126 wp_styles()->text_direction = $GLOBALS['text_direction'];
127 }
128
129 /**
130 * Fires when the current language is defined.
131 *
132 * @since 0.9.5
133 *
134 * @param string $slug Current language code.
135 * @param PLL_Language $curlang Current language object.
136 */
137 do_action( 'pll_language_defined', $this->curlang->slug, $this->curlang );
138 }
139
140 /**
141 * Returns the language to assign as the current one.
142 *
143 * @since 3.8
144 *
145 * @return PLL_Language|false
146 */
147 abstract protected function get_current_language();
148
149 /**
150 * Set a cookie to remember the language.
151 * Setting PLL_COOKIE to false will disable cookie although it will break some functionalities
152 *
153 * @since 1.5
154 *
155 * @return void
156 */
157 public function maybe_setcookie() {
158 // Don't set cookie in javascript when a cache plugin is active.
159 if ( ! pll_is_cache_active() && ! empty( $this->curlang ) && ! is_404() && ! is_favicon() ) {
160 $args = array(
161 'domain' => 2 === $this->options['force_lang'] ? wp_parse_url( $this->links_model->home, PHP_URL_HOST ) : COOKIE_DOMAIN,
162 'samesite' => 3 === $this->options['force_lang'] ? 'None' : 'Lax',
163 );
164 PLL_Cookie::set( $this->curlang->slug, $args );
165 }
166 }
167
168 /**
169 * Get the preferred language according to the browser preferences.
170 *
171 * @since 1.8
172 *
173 * @return string|bool The preferred language slug or false.
174 */
175 public function get_preferred_browser_language() {
176 if ( isset( $_SERVER['HTTP_ACCEPT_LANGUAGE'] ) ) {
177 $accept_langs = PLL_Accept_Languages_Collection::from_accept_language_header( sanitize_text_field( wp_unslash( $_SERVER['HTTP_ACCEPT_LANGUAGE'] ) ) );
178
179 $accept_langs->bubble_sort();
180
181 $languages = $this->model->languages->filter( 'hide_empty' )->get_list(); // Hides languages with no post.
182
183 /**
184 * Filters the list of languages to use to match the browser preferences.
185 *
186 * @since 1.9.3
187 *
188 * @param array $languages Array of PLL_Language objects.
189 */
190 $languages = apply_filters( 'pll_languages_for_browser_preferences', $languages );
191
192 return $accept_langs->find_best_match( $languages );
193 }
194
195 return false;
196 }
197
198 /**
199 * Returns the preferred language
200 * either from the cookie if it's a returning visit
201 * or according to browser preference
202 * or the default language
203 *
204 * @since 0.1
205 *
206 * @return PLL_Language|false browser preferred language or default language
207 */
208 public function get_preferred_language() {
209 $language = false;
210 $cookie = false;
211
212 if ( isset( $_COOKIE[ PLL_COOKIE ] ) ) {
213 // Check first if the user was already browsing this site.
214 $language = sanitize_key( $_COOKIE[ PLL_COOKIE ] );
215 $cookie = true;
216 } elseif ( $this->options['browser'] ) {
217 $language = $this->get_preferred_browser_language();
218 }
219
220 /**
221 * Filter the visitor's preferred language (normally set first by cookie
222 * if this is not the first visit, then by the browser preferences).
223 * If no preferred language has been found or set by this filter,
224 * Polylang fallbacks to the default language
225 *
226 * @since 1.0
227 * @since 2.7 Added $cookie parameter.
228 *
229 * @param string|bool $language Preferred language code, false if none has been found.
230 * @param bool $cookie Whether the preferred language has been defined by the cookie.
231 */
232 $slug = apply_filters( 'pll_preferred_language', $language, $cookie );
233
234 // 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.
235 $lang = $this->model->get_language( $slug );
236 return $lang ?: $this->model->get_default_language();
237 }
238
239 /**
240 * Returns the language when home page is requested.
241 *
242 * @since 3.8
243 *
244 * @return PLL_Language|false
245 */
246 protected function get_home_language() {
247 // 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.
248 // Thanks to Ov3rfly http://wordpress.org/support/topic/enhance-feature-when-front-page-is-visited-set-language-according-to-browser.
249 if ( $this->options['hide_default'] && ( wp_get_referer() || ! $this->options['browser'] ) ) {
250 return $this->model->get_default_language();
251 }
252 return $this->get_preferred_language(); // Returns the language according to browser preference or default language.
253 }
254
255 /**
256 * To call when the home page has been requested
257 * Make sure to call this after 'setup_theme' has been fired as we need $wp_query
258 * Performs a redirection to the home page in the current language if needed
259 *
260 * @since 0.9
261 *
262 * @return void
263 */
264 public function home_requested() {
265 if ( empty( $this->curlang ) ) {
266 return;
267 }
268
269 // We are already on the right page
270 if ( $this->curlang->is_default && $this->options['hide_default'] ) {
271 $this->set_curlang_in_query( $GLOBALS['wp_query'] );
272
273 /**
274 * Fires when the site root page is requested
275 *
276 * @since 1.8
277 */
278 do_action( 'pll_home_requested' );
279 }
280 // Redirect to the home page in the right language
281 // Test to avoid crash if get_home_url returns something wrong
282 // FIXME why this happens? http://wordpress.org/support/topic/polylang-crashes-1
283 // Don't redirect if $_POST is not empty as it could break other plugins
284 elseif ( is_string( $redirect = $this->curlang->get_home_url() ) && empty( $_POST ) ) { // phpcs:ignore WordPress.Security.NonceVerification
285 // Don't forget the query string which may be added by plugins
286 $query_string = wp_parse_url( pll_get_requested_url(), PHP_URL_QUERY );
287 if ( ! empty( $query_string ) ) {
288 $redirect .= ( $this->links_model->using_permalinks ? '?' : '&' ) . $query_string;
289 }
290
291 /**
292 * When a visitor reaches the site home, Polylang redirects to the home page in the correct language.
293 * This filter allows plugins to modify the redirected url or prevent this redirection
294 * /!\ this filter may be fired *before* the theme is loaded
295 *
296 * @since 1.1.1
297 *
298 * @param string $redirect the url the visitor will be redirected to
299 */
300 $redirect = apply_filters( 'pll_redirect_home', $redirect );
301 if ( $redirect && wp_validate_redirect( $redirect ) ) {
302 $this->maybe_setcookie();
303 header( 'Vary: Accept-Language' );
304 wp_safe_redirect( $redirect, 302, POLYLANG );
305 exit;
306 }
307 }
308 }
309
310 /**
311 * Set the language when posting a comment
312 *
313 * @since 0.8.4
314 *
315 * @param int $post_id the post being commented
316 * @return void
317 */
318 public function pre_comment_on_post( $post_id ) {
319 $this->set_language( $this->model->post->get_language( $post_id ) );
320 }
321
322 /**
323 * Modifies some main query vars for the home page and the page for posts
324 * to enable one home page (and one page for posts) per language.
325 *
326 * @since 1.2
327 *
328 * @param WP_Query $query Instance of WP_Query.
329 * @return void
330 */
331 public function parse_main_query( $query ) {
332 if ( ! $query->is_main_query() ) {
333 return;
334 }
335
336 /**
337 * This filter allows to set the language based on information contained in the main query
338 *
339 * @since 1.8
340 *
341 * @param PLL_Language|false $lang Language object or false.
342 * @param WP_Query $query WP_Query object.
343 */
344 if ( $lang = apply_filters( 'pll_set_language_from_query', false, $query ) ) {
345 $this->set_language( $lang );
346 $this->set_curlang_in_query( $query );
347 } elseif ( ( count( (array) $query->query ) == 1 || ( is_paged() && count( (array) $query->query ) == 2 ) ) && $lang = get_query_var( 'lang' ) ) {
348 $lang = $this->model->get_language( $lang );
349 $this->set_language( $lang ); // Set the language now otherwise it will be too late to filter sticky posts!
350
351 // Set is_home on translated home page when it displays posts. It must be true on page 2, 3... too.
352 $query->is_home = true;
353 $query->is_tax = false;
354 $query->is_archive = false;
355
356 // Filters is_front_page() in case a static front page is not translated in this language.
357 add_filter( 'option_show_on_front', array( $this, 'filter_option_show_on_front' ) );
358 }
359 }
360
361 /**
362 * Filters the option show_on_front when the current front page displays posts.
363 *
364 * This is useful when a static front page is not translated in all languages.
365 *
366 * @return string
367 */
368 public function filter_option_show_on_front() {
369 return 'posts';
370 }
371
372 /**
373 * Sets the current language in the query.
374 *
375 * @since 2.2
376 *
377 * @param WP_Query $query Instance of WP_Query.
378 * @return void
379 */
380 protected function set_curlang_in_query( &$query ) {
381 if ( ! empty( $this->curlang ) ) {
382 $pll_query = new PLL_Query( $query, $this->model );
383 $pll_query->set_language( $this->curlang );
384 }
385 }
386 }
387