PluginProbe
Polylang / 1.1
Polylang v1.1
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 / include / core.php

core.php in Polylang 1.1, at include/core.php

1,014 lines 44.0 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2 // filters posts and terms by language, rewrites links to include the language, etc...
3 // used only for frontend
4 class Polylang_Core extends Polylang_base {
5 public $curlang; // current language
6
7 private $default_locale;
8 private $list_textdomains = array(); // all text domains
9 private $labels; // post types and taxonomies labels to translate
10 private $first_query = true;
11
12 // options often needed
13 private $page_for_posts;
14 private $page_on_front;
15
16 // used to cache results
17 private $posts = array();
18 private $translation_url = array();
19 private $home_urls = array();
20
21 function __construct() {
22 parent::__construct();
23
24 // init options often needed
25 $this->page_for_posts = get_option('page_for_posts');
26 $this->page_on_front = get_option('page_on_front');
27
28 // if no language found, choose the preferred one
29 add_filter('pll_get_current_language', array(&$this, 'pll_get_current_language'));
30
31 // sets the language of comment
32 add_action('pre_comment_on_post', array(&$this, 'pre_comment_on_post'));
33
34 // text domain management
35 if ($this->options['force_lang'] && get_option('permalink_structure')) {
36 add_action('plugins_loaded', array(&$this, 'setup_theme'), 1);
37 add_action('wp_loaded', array(&$this, 'add_language_filters'), 5); // after Polylang_Base::add_post_types_taxonomies
38 }
39 else {
40 add_filter('override_load_textdomain', array(&$this, 'mofile'), 10, 3);
41 add_filter('gettext', array(&$this, 'gettext'), 10, 3);
42 add_filter('gettext_with_context', array(&$this, 'gettext_with_context'), 10, 4);
43 }
44
45 add_action('init', array(&$this, 'init'));
46 foreach (array('wp', 'login_init', 'admin_init') as $filter) // admin_init for ajax thanks to g100g
47 add_action($filter, array(&$this, 'load_textdomains'), 5); // priority 5 for post types and taxonomies registered in wp hook with default priority
48
49 // filters the WordPress locale
50 add_filter('locale', array(&$this, 'get_locale'));
51
52 // filters posts according to the language
53 add_filter('pre_get_posts', array(&$this, 'pre_get_posts'), 5);
54
55 // filter sticky posts by current language
56 add_filter('option_sticky_posts', array(&$this, 'option_sticky_posts'));
57
58 // translates page for posts and page on front
59 add_filter('option_page_for_posts', array(&$this, 'translate_page'));
60 add_filter('option_page_on_front', array(&$this, 'translate_page'));
61 }
62
63 // set these filters and actions only once the current language has been defined
64 function add_language_filters() {
65 if (!$this->get_languages_list() || empty($this->curlang))
66 return;
67
68 // modifies the language information in rss feed (useful if WP < 3.4)
69 add_filter('option_rss_language', array(&$this, 'option_rss_language'));
70
71 // filters categories and post tags by language
72 add_filter('terms_clauses', array(&$this, 'terms_clauses'), 10, 3);
73
74 // meta in the html head section
75 add_action('wp_head', array(&$this, 'wp_head'));
76
77 // modifies the page link in case the front page is not in the default language
78 add_filter('page_link', array(&$this, 'page_link'), 10, 2);
79
80 // manages the redirection of the homepage
81 add_filter('redirect_canonical', array(&$this, 'redirect_canonical'), 10, 2);
82
83 // adds javascript at the end of the document
84 if (!$this->using_permalinks && (!defined('PLL_SEARCH_FORM_JS') || PLL_SEARCH_FORM_JS))
85 add_action('wp_footer', array(&$this, 'wp_print_footer_scripts'));
86
87 // adds the language information in the search form
88 // low priority in case the search form is created using the same filter as described in http://codex.wordpress.org/Function_Reference/get_search_form
89 add_filter('get_search_form', array(&$this, 'get_search_form'), 99);
90
91 // adds the language information in admin bar search form
92 remove_action('admin_bar_menu', 'wp_admin_bar_search_menu', 4);
93 add_action('admin_bar_menu', array(&$this, 'admin_bar_search_menu'), 4);
94
95 // filters the pages according to the current language in wp_list_pages
96 add_filter('wp_list_pages_excludes', array(&$this, 'wp_list_pages_excludes'));
97
98 // filters the comments according to the current language
99 add_filter('comments_clauses', array(&$this, 'comments_clauses'), 10, 2);
100
101 // rewrites archives, next and previous post links to filter them by language
102 foreach (array('getarchives', 'get_previous_post', 'get_next_post') as $filter)
103 foreach (array('_join', '_where') as $clause)
104 add_filter($filter.$clause, array(&$this, 'posts'.$clause));
105
106 // rewrites author and date links to filter them by language
107 foreach (array('feed_link', 'author_link', 'post_type_archive_link', 'year_link', 'month_link', 'day_link') as $filter)
108 add_filter($filter, array(&$this, 'archive_link'));
109
110 $this->add_post_term_link_filters(); // these filters are in base as they may be used on admin side too
111
112 // filters the widgets according to the current language
113 add_filter('widget_display_callback', array(&$this, 'widget_display_callback'), 10, 3);
114
115 // strings translation (must be applied before WordPress applies its default formatting filters)
116 foreach (array('widget_title', 'option_blogname', 'option_blogdescription', 'option_date_format', 'option_time_format') as $filter)
117 add_filter($filter, 'pll__', 1);
118
119 // translates biography
120 add_filter('get_user_metadata', array(&$this,'get_user_metadata'), 10, 4);
121
122 // modifies the home url
123 if (!defined('PLL_FILTER_HOME_URL') || PLL_FILTER_HOME_URL)
124 add_filter('home_url', array(&$this, 'home_url'), 10, 2);
125
126 // set posts and terms language when created from frontend (ex with P2 theme)
127 add_action('save_post', array(&$this, 'save_post'), 200, 2);
128 add_action('create_term', array(&$this, 'save_term'), 10, 3);
129 add_action('edit_term', array(&$this, 'save_term'), 10, 3);
130 }
131
132 // returns the language according to browser preference or the default language
133 function get_preferred_language() {
134 // check first is the user was already browsing this site
135 if (isset($_COOKIE[PLL_COOKIE]))
136 return $this->get_language($_COOKIE[PLL_COOKIE]);
137
138 // compatibility with old cookie removed in 1.0
139 if (isset($_COOKIE['wordpress_polylang']))
140 return $this->get_language($_COOKIE['wordpress_polylang']);
141
142 // sets the browsing language according to the browser preferences
143 // code adapted from http://www.thefutureoftheweb.com/blog/use-accept-language-header
144 if ($this->options['browser']) {
145 $accept_langs = array();
146
147 if (isset($_SERVER['HTTP_ACCEPT_LANGUAGE'])) {
148 // break up string into pieces (languages and q factors)
149 preg_match_all('/([a-z]{1,8}(-[a-z]{1,8})?)\s*(;\s*q\s*=\s*(1|0\.[0-9]+))?/i', $_SERVER['HTTP_ACCEPT_LANGUAGE'], $lang_parse);
150
151 $k = $lang_parse[1];
152 $v = $lang_parse[4];
153
154 if ($n = count($k)) {
155 // set default to 1 for any without q factor
156 foreach ($v as $key => $val)
157 if ($val === '') $v[$key] = 1;
158
159 // bubble sort (need a stable sort for Android, so can't use a PHP sort function)
160 if ($n > 1) {
161 for ($i = 2; $i <= $n; $i++)
162 for ($j = 0; $j <= $n-2; $j++)
163 if ( $v[$j] < $v[$j + 1]) {
164 // swap values
165 $temp = $v[$j];
166 $v[$j] = $v[$j + 1];
167 $v[$j + 1] = $temp;
168 //swap keys
169 $temp = $k[$j];
170 $k[$j] = $k[$j + 1];
171 $k[$j + 1] = $temp;
172 }
173 }
174 // NOTE: array_combine => PHP5
175 $accept_langs = array_combine($k,$v);
176 }
177 }
178
179 // looks through sorted list and use first one that matches our language list
180 $listlanguages = $this->get_languages_list(array('hide_empty' => true)); // hides languages with no post
181 foreach (array_keys($accept_langs) as $accept_lang) {
182 foreach ($listlanguages as $language) {
183 if (stripos($accept_lang, $language->slug) === 0 && !isset($pref_lang)) {
184 $pref_lang = $language;
185 }
186 }
187 }
188 } // options['browser']
189
190 // allow plugin to modify the preferred language (useful for example to have a different fallback than the default language)
191 $slug = apply_filters('pll_preferred_language', isset($pref_lang) ? $pref_lang->slug : false);
192
193 // 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
194 return ($lang = $this->get_language($slug)) ? $lang : $this->get_language($this->options['default_lang']);
195 }
196
197 // returns the current language
198 function get_current_language() {
199 if ($this->curlang)
200 return $this->curlang;
201
202 // no language set for 404
203 if (is_404() || current_filter() == 'login_init')
204 return $this->get_preferred_language();
205
206 if ($var = get_query_var('lang'))
207 $lang = $this->get_language(reset(explode(',',$var))); // choose the first queried language
208
209 // Ajax thanks to g100g
210 elseif ($this->is_ajax_on_front)
211 $lang = empty($_REQUEST['lang']) ? $this->get_preferred_language() : $this->get_language($_REQUEST['lang']);
212
213 elseif ((is_single() || is_page() || (is_attachment() && $this->options['media_support'])) && ( ($var = get_queried_object_id()) || ($var = get_query_var('p')) || ($var = get_query_var('page_id')) || ($var = get_query_var('attachment_id')) ))
214 $lang = $this->get_post_language($var);
215
216 elseif (isset($this->taxonomies)) {
217 foreach ($this->taxonomies as $taxonomy) {
218 if ($var = get_query_var(get_taxonomy($taxonomy)->query_var))
219 $lang = $this->get_term_language($var, $taxonomy);
220 }
221 }
222 // allows plugins to set the language
223 return apply_filters('pll_get_current_language', isset($lang) ? $lang : false);
224 }
225
226 // if no language found, return the preferred one
227 function pll_get_current_language($lang) {
228 return !$lang ? $this->get_preferred_language() : $lang;
229 }
230
231 // sets the language of comment
232 function pre_comment_on_post($post_id) {
233 $this->curlang = $this->get_post_language($post_id);
234 add_filter('page_link', array(&$this, 'page_link'), 10, 2); // useful when posting a comment on static front page in non default language
235 $this->add_post_term_link_filters(); // useful to redirect to correct post comment url when adding the language to all url
236 }
237
238 // sets the language when it is always included in the url
239 function setup_theme() {
240 // this function was hooked to setup_theme with priority 5 (after Polylang::init)
241 // has been moved to plugins_loaded, 1 due to WPSEO and to be consistent with WPML
242 // but $wp_rewrite is not defined yet, so let register our taxonomy partially
243 $index = 'index.php'; // $wp_rewrite->index is hardcoded in wp-includes/rewrite.php
244 register_taxonomy('language', null , array('label' => false, 'query_var'=>'lang', 'rewrite'=>false));
245
246 if (!$languages_list = $this->get_languages_list())
247 return;
248
249 // special case for ajax request
250 if (isset($_REQUEST['pll_load_front']))
251 $this->curlang = empty($_REQUEST['lang']) ? $this->get_preferred_language() : $this->get_language($_REQUEST['lang']);
252
253 // standard case
254 else {
255 foreach ($languages_list as $language)
256 $languages[] = $language->slug;
257
258 $root = $this->options['rewrite'] ? '' : 'language/';
259 $languages = $this->using_permalinks ? '#\/'.$root.'('.implode('|', $languages).')\/#' : '#lang=('.implode('|', $languages).')#';
260 preg_match($languages, trailingslashit($_SERVER['REQUEST_URI']), $matches);
261
262 // home is resquested
263 // some PHP setups turn requests for / into /index.php in REQUEST_URI
264 // thanks to Gonçalo Peres for pointing out the issue with queries unknown to WP
265 // http://wordpress.org/support/topic/plugin-polylang-language-homepage-redirection-problem-and-solution-but-incomplete?replies=4#post-2729566
266 if (str_replace('www.', '', home_url('/')) == trailingslashit((is_ssl() ? 'https://' : 'http://').str_replace('www.', '', $_SERVER['HTTP_HOST']).str_replace(array($index, '?'.$_SERVER['QUERY_STRING']), array('', ''), $_SERVER['REQUEST_URI']))) {
267 // take care to post & page preview http://wordpress.org/support/topic/static-frontpage-url-parameter-url-language-information
268 if (isset($_GET['preview']) && ( (isset($_GET['p']) && $id = $_GET['p']) || (isset($_GET['page_id']) && $id = $_GET['page_id']) ))
269 $this->curlang = ($lg = $this->get_post_language($id)) ? $lg : $this->get_language($this->options['default_lang']);
270
271 // take care to (unattached) attachments
272 elseif (isset($_GET['attachment_id']) && $id = $_GET['attachment_id'])
273 $this->curlang = ($lg = $this->get_post_language($id)) ? $lg : $this->get_preferred_language();
274
275 else
276 $this->home_requested();
277 }
278
279 // $matches[1] is the slug of the requested language
280 elseif ($matches)
281 $this->curlang = $this->get_language($matches[1]);
282
283 // first test for wp-login, wp-signup, wp-activate
284 // stripos for case insensitive file systems
285 elseif (false === stripos($_SERVER['SCRIPT_NAME'], $index) || !$this->options['hide_default'])
286 $this->curlang = $this->get_preferred_language();
287
288 else
289 $this->curlang = $this->get_language($this->options['default_lang']);
290
291 if ($this->using_permalinks)
292 add_action('wp', array(&$this, 'check_language_code_in_url')); // before Wordpress redirect_canonical
293 }
294
295 $GLOBALS['text_direction'] = get_metadata('term', $this->curlang->term_id, '_rtl', true) ? 'rtl' : 'ltr';
296 $GLOBALS['l10n']['pll_string'] = $this->mo_import($this->curlang);
297 do_action('pll_language_defined');
298 }
299
300 // save the default locale before we start any language manipulation
301 function init() {
302 $this->default_locale = get_locale();
303 }
304
305 // returns the locale based on current language
306 function get_locale($locale) {
307 return $this->curlang ? $this->curlang->description : $locale;
308 }
309
310 // modifies the language information in rss feed
311 function option_rss_language($value) {
312 return get_bloginfo_rss('language');
313 }
314
315 // saves all text domains in a table for later usage
316 function mofile($bool, $domain, $mofile) {
317 $this->list_textdomains[] = array ('mo' => $mofile, 'domain' => $domain);
318 return true; // prevents WP loading text domains as we will load them all later
319 }
320
321 // saves post types and taxonomies labels for a later usage
322 function gettext($translation, $text, $domain) {
323 $this->labels[$text] = array('domain' => $domain);
324 return $translation;
325 }
326
327 // saves post types and taxonomies labels for a later usage
328 function gettext_with_context($translation, $text, $context, $domain) {
329 $this->labels[$text] = array('domain' => $domain, 'context' => $context);
330 return $translation;
331 }
332
333 // translates post types and taxonomies labels once the language is known
334 function translate_labels($type) {
335 foreach($type->labels as $key=>$label)
336 if (is_string($label) && isset($this->labels[$label]))
337 $type->labels->$key = isset($this->labels[$label]['context']) ?
338 _x($label, $this->labels[$label]['context'], $this->labels[$label]['domain']) :
339 __($label, $this->labels[$label]['domain']);
340 }
341
342 // NOTE: I believe there are two ways for a plugin to force the WP language
343 // as done by xili_language: load text domains and reinitialize wp_locale with the action 'wp'
344 // as done by qtranslate: define the locale with the action 'plugins_loaded', but in this case, the language must be specified in the url.
345 function load_textdomains() {
346 // our override_load_textdomain filter has done its job. let's remove it before calling load_textdomain
347 remove_filter('override_load_textdomain', array(&$this, 'mofile'));
348 remove_filter('gettext', array(&$this, 'gettext'), 10, 3);
349 remove_filter('gettext_with_context', array(&$this, 'gettext_with_context'), 10, 4);
350
351 // check there is at least one language defined and sets the current language
352 if ($this->get_languages_list() && $this->curlang = $this->get_current_language()) {
353 // since 1.0: suppress old cookie which conflicts with quick cache
354 if (!headers_sent() && isset($_COOKIE['wordpress_polylang']))
355 setcookie('wordpress_polylang', '', time() - 3600, COOKIEPATH, COOKIE_DOMAIN);
356
357 // set a cookie to remember the language. check headers have not been sent to avoid ugly error
358 // possibility to set PLL_COOKIE to false will disable cookie although it will break some functionalities
359 if (!headers_sent() && PLL_COOKIE !== false && (!isset($_COOKIE[PLL_COOKIE]) || $_COOKIE[PLL_COOKIE] != $this->curlang->slug))
360 setcookie(PLL_COOKIE, $this->curlang->slug, time() + 31536000 /* 1 year */, COOKIEPATH, COOKIE_DOMAIN);
361
362 if (!($this->options['force_lang'] && $this->using_permalinks)) {
363 // set all our language filters and actions
364 $this->add_language_filters();
365
366 // now we can load text domains with the right language
367 $new_locale = get_locale();
368 foreach ($this->list_textdomains as $textdomain) {
369 $mo = str_replace("{$this->default_locale}.mo", "{$new_locale}.mo", $textdomain['mo']);
370 // since WP3.5 themes may store languages files in /wp-content/languages/themes
371 $mo = file_exists($mo) ? $mo : WP_LANG_DIR . "/themes/{$textdomain['domain']}-{$new_locale}.mo";
372 load_textdomain($textdomain['domain'], $mo);
373 }
374 // reinitializes wp_locale for weekdays and months, as well as for text direction
375 unset($GLOBALS['wp_locale']);
376 $GLOBALS['wp_locale'] = new WP_Locale();
377 $GLOBALS['wp_locale']->text_direction = get_metadata('term', $this->curlang->term_id, '_rtl', true) ? 'rtl' : 'ltr';
378
379 // translate labels of post types and taxonomies
380 foreach ($GLOBALS['wp_taxonomies'] as $tax)
381 $this->translate_labels($tax);
382 foreach ($GLOBALS['wp_post_types'] as $pt)
383 $this->translate_labels($pt);
384
385 // and finally load user defined strings
386 $GLOBALS['l10n']['pll_string'] = $this->mo_import($this->curlang);
387 do_action('pll_language_defined');
388 }
389 }
390
391 else {
392 // can't work so load the text domains with WordPress default language
393 foreach ($this->list_textdomains as $textdomain)
394 load_textdomain($textdomain['domain'], $textdomain['mo']);
395 }
396
397 // free memory
398 unset($this->list_textdomains);
399 unset($this->labels);
400 }
401
402 // special actions when home page is requested
403 function home_requested($query = false) {
404 // need this filter to get the right url when adding language code to all urls
405 if ($this->options['force_lang'] && $this->using_permalinks)
406 add_filter('_get_page_link', array(&$this, 'post_link'), 10, 2);
407
408 // FIXME cookie wordpress_polylang removed since 1.0
409 // test referer in case PLL_COOKIE is set to false
410 // thanks to Ov3rfly http://wordpress.org/support/topic/enhance-feature-when-front-page-is-visited-set-language-according-to-browser
411 $this->curlang = $this->options['hide_default'] && ((isset($_SERVER['HTTP_REFERER']) && strpos($_SERVER['HTTP_REFERER'], $this->home) !== false)) ?
412 $this->get_language($this->options['default_lang']) :
413 $this->get_preferred_language(); // sets the language according to browser preference or default language
414
415 if ($query)
416 $this->_home_requested($query);
417 else
418 add_action('setup_theme', array(&$this, '_home_requested')); // delays actions which need $wp_query & $wp_rewrite
419 }
420
421 // sets the correct query var for home page
422 // optionally redirects to the home page in the preferred language
423 function _home_requested($query = false) {
424 // we are already on the right page
425 if ($this->options['default_lang'] == $this->curlang->slug && $this->options['hide_default']) {
426 if ($this->page_on_front && $link_id = $this->get_post($this->page_on_front, $this->curlang))
427 $query ? $query->set('page_id', $link_id) : set_query_var('page_id', $link_id);
428 else
429 $query ? $query->set('lang', $this->curlang->slug) : set_query_var('lang', $this->curlang->slug);
430 }
431
432 // redirect to the home page in the right language
433 // test to avoid crash if get_home_url returns something wrong
434 // FIXME why this happens? http://wordpress.org/support/topic/polylang-crashes-1
435 // don't redirect if $_POST is not empty as it could break other plugins
436 // don't forget the query string which may be added by plugins
437 elseif (is_string($redirect = $this->get_home_url($this->curlang)) && empty($_POST)) {
438 wp_redirect(empty($_SERVER['QUERY_STRING']) ? $redirect : $redirect . ($this->using_permalinks ? '?' : '&') . $_SERVER['QUERY_STRING']);
439 exit;
440 }
441 }
442
443 // filters posts according to the language
444 function pre_get_posts($query) {
445 // don't make anything if no language has been defined yet
446 // $this->post_types & $this->taxonomies are defined only once the action 'wp_loaded' has been fired
447 // don't honor suppress_filters as it breaks adjacent_image_link when post_parent == 0
448 if (!$this->get_languages_list() || !did_action('wp_loaded'))
449 return;
450
451 $qv = $query->query_vars;
452
453 // do not filter if lang is set to an empty value
454 if (isset($qv['lang']) && !$qv['lang'])
455 return;
456
457 // users may want to display content in a different language than the current one by setting it explicitely in the query
458 if (!$this->first_query && $this->curlang && !empty($qv['lang']))
459 return;
460
461 $is_post_type = isset($qv['post_type']) && (
462 in_array($qv['post_type'], $this->post_types) ||
463 (is_array($qv['post_type']) && array_intersect($qv['post_type'], $this->post_types))
464 );
465
466 // don't filters post types not in our list
467 if (isset($qv['post_type']) && !$is_post_type)
468 return;
469
470 $this->first_query = false;
471
472 // special case for wp-signup.php & wp-activate.php
473 // stripos for case insensitive file systems
474 // $wp_rewrite->index is hardcoded in wp-includes/rewrite.php
475 if (false === stripos($_SERVER['SCRIPT_NAME'], 'index.php')) {
476 $this->curlang = $this->get_preferred_language();
477 return;
478 }
479
480 // homepage is requested, let's set the language
481 // take care to avoid posts page for which is_home = 1
482 if (!$this->curlang && empty($query->query) && (is_home() || (is_page() && $qv['page_id'] == $this->page_on_front)))
483 $this->home_requested($query);
484
485 // redirect the language page to the homepage
486 if ($this->options['redirect_lang'] && is_tax('language') && $this->page_on_front && (count($query->query) == 1 || (is_paged() && count($query->query) == 2))) {
487 $this->curlang = $this->get_language(get_query_var('lang'));
488 if ($page_id = $this->get_post($this->page_on_front, $this->get_language(get_query_var('lang')))) {
489 $query->set('page_id', $page_id);
490 $query->is_singular = $query->is_page = true;
491 $query->is_archive = $query->is_tax = false;
492 unset($query->queried_object); // reset queried object
493 return;
494 }
495 // else : the static front page is not translated
496 // let's things as is and the list of posts in the current language will be displayed
497 }
498
499 // sets is_home on translated home page when it displays posts
500 // is_home must be true on page 2, 3... too
501 if (!$this->page_on_front && is_tax('language') && (count($query->query) == 1 || (is_paged() && count($query->query) == 2))) {
502 $this->curlang = $this->get_language(get_query_var('lang')); // sets the language now otherwise it will be too late to filter sticky posts !
503 $query->is_home = true;
504 $query->is_archive = $query->is_tax = false;
505 }
506
507 // sets the language for posts page in case the front page displays a static page
508 if ($this->page_for_posts) {
509 // If permalinks are used, WordPress does set and use $query->queried_object_id and sets $query->query_vars['page_id'] to 0
510 // and does set and use $query->query_vars['page_id'] if permalinks are not used :(
511 if (!empty($qv['pagename']) && isset($query->queried_object_id))
512 $page_id = $query->queried_object_id;
513
514 elseif (isset($qv['page_id']))
515 $page_id = $qv['page_id'];
516
517 if (!empty($page_id) && $this->get_post($page_id, $this->get_post_language($this->page_for_posts)) == $this->page_for_posts) {
518 $this->page_for_posts = $page_id;
519 $this->curlang = $this->get_post_language($page_id);
520 $query->set('lang', $this->curlang->slug);
521 $query->is_singular = $query->is_page = false;
522 $query->is_home = $query->is_posts_page = true;
523 }
524 }
525
526 $is_archive = (count($query->query) == 1 && !empty($qv['paged'])) ||
527 $query->is_date ||
528 $query->is_author ||
529 (isset($qv['post_type']) && $query->is_post_type_archive && $is_post_type);
530
531 // sets 404 when the language is not set for archives needing the language in the url
532 if (!$this->options['hide_default'] && !isset($qv['lang']) && !$this->using_permalinks && $is_archive)
533 $query->set_404();
534
535 // sets the language in case we hide the default language
536 if ($this->options['hide_default'] && !isset($qv['lang']) && ($is_archive || $query->is_search || (count($query->query) == 1 && !empty($qv['feed'])) ))
537 $query->set('lang', $this->options['default_lang']);
538
539 // allow filtering recent posts and secondary queries by the current language
540 // take care not to break queries for non visible post types such as nav_menu_items, attachments...
541 if (/*$query->is_home && */$this->curlang && (!isset($qv['post_type']) || $is_post_type ))
542 $query->set('lang', $this->curlang->slug);
543
544 // remove pages query when the language is set unless we do a search
545 // FIXME is only search broken by this ?
546 if (!empty($qv['lang']) && !isset($qv['post_type']) && !is_search())
547 $query->set('post_type', 'post');
548
549 // unset the is_archive flag for language pages to prevent loading the archive template
550 // keep archive flag for comment feed otherwise the language filter does not work
551 if (!empty($qv['lang']) && !is_comment_feed() &&
552 !is_post_type_archive() && !is_date() && !is_author() && !is_category() && !is_tag() && !is_tax('post_format'))
553 $query->is_archive = false;
554
555 // unset the is_tax flag for authors pages and post types archives
556 // FIXME Probably I should do this for other cases
557 if (!empty($qv['lang']) && (is_author() || is_post_type_archive() || is_date() || is_search())) {
558 $query->is_tax = false;
559 unset($query->queried_object);
560 }
561
562 // sets a language for theme preview
563 if (is_preview() && is_front_page()) {
564 $this->curlang = $this->get_current_language();
565 $query->set('lang', $this->curlang->slug);
566 }
567
568 // sets the language for an empty string search when hiding the code for default language
569 // http://wordpress.org/support/topic/search-for-empty-string-in-default-language
570 if (!$this->curlang && !get_query_var('lang') && $this->options['hide_default'] && isset($query->query['s']) && !$query->query['s'])
571 $query->set('lang', $this->options['default_lang']);
572
573 // to avoid conflict beetwen taxonomies
574 if (isset($query->tax_query->queries))
575 foreach ($query->tax_query->queries as $tax)
576 if (in_array($tax['taxonomy'], $this->taxonomies))
577 unset($query->query_vars['lang']);
578 }
579
580 // filter sticky posts by current language
581 function option_sticky_posts($posts) {
582 if ($this->curlang && !empty($posts)) {
583 foreach ($posts as $key=>$post_id) {
584 if ($this->get_post_language($post_id)->term_id != $this->curlang->term_id)
585 unset($posts[$key]);
586 }
587 }
588 return $posts;
589 }
590
591 // filters categories and post tags by language when needed
592 function terms_clauses($clauses, $taxonomies, $args) {
593 // does nothing except on taxonomies which are filterable
594 foreach ($taxonomies as $tax) {
595 if (!in_array($tax, $this->taxonomies))
596 return $clauses;
597 }
598
599 // adds our clauses to filter by language
600 return $this->_terms_clauses($clauses, isset($args['lang']) ? $args['lang'] : $this->curlang);
601 }
602
603 // meta in the html head section
604 function wp_head() {
605 // outputs references to translated pages (if exists) in the html head section
606 foreach ($this->get_languages_list() as $language) {
607 if ($language->slug != $this->curlang->slug && $url = $this->get_translation_url($language))
608 printf("<link hreflang='%s' href='%s' rel='alternate' />\n", esc_attr($language->slug), esc_url($url));
609 }
610 }
611
612 // modifies the page link in case the front page is not in the default language
613 function page_link($link, $id) {
614 if ($this->options['redirect_lang'] && $this->page_on_front && $lang = $this->get_post_language($id)) {
615 if (!isset($this->posts[$lang->slug][$this->page_on_front]))
616 $this->posts[$lang->slug][$this->page_on_front] = $this->get_post($this->page_on_front, $lang);
617 if ($id == $this->posts[$lang->slug][$this->page_on_front])
618 return $this->options['hide_default'] && $lang->slug == $this->options['default_lang'] ? trailingslashit($this->home) : get_term_link($lang, 'language');
619 }
620
621 if ($this->page_on_front && $this->options['hide_default']) {
622 if (!isset($this->posts[$this->options['default_lang']][$this->page_on_front]))
623 $this->posts[$this->options['default_lang']][$this->page_on_front] = $this->get_post($this->page_on_front, $this->options['default_lang']);
624 if ($id == $this->posts[$this->options['default_lang']][$this->page_on_front])
625 return trailingslashit($this->home);
626 }
627
628 return _get_page_link($id);
629 }
630
631 // manages canonical redirection of the homepage when using page on front
632 function redirect_canonical($redirect_url, $requested_url) {
633 global $wp_query;
634 if (is_page() && !is_feed() && isset($wp_query->queried_object) && 'page' == get_option('show_on_front') && $wp_query->queried_object->ID == get_option('page_on_front'))
635 return $this->options['redirect_lang'] ? $this->get_home_url() : false;
636 return $redirect_url;
637 }
638
639 // redirects incoming links to the proper URL when adding the language code to all urls
640 function check_language_code_in_url() {
641 if (is_single() || is_page()) {
642 global $post;
643 if (isset($post->ID) && in_array($post->post_type, $this->post_types))
644 $language = $this->get_post_language((int)$post->ID);
645 }
646 elseif (is_category() || is_tag() || is_tax()) {
647 $obj = $GLOBALS['wp_query']->get_queried_object();
648 if (in_array($obj->taxonomy, $this->taxonomies))
649 $language = $this->get_term_language((int)$obj->term_id);
650 }
651
652 // the language is not correctly set so let's redirect to the correct url for this object
653 if (isset($language) && $language->slug != $this->curlang->slug) {
654 $root = $this->options['rewrite'] ? '/' : '/language/';
655 foreach ($this->get_languages_list() as $lang)
656 $languages[] = $root . $lang->slug;
657
658 $requested_url = (is_ssl() ? 'https://' : 'http://') . $_SERVER['HTTP_HOST'] . str_replace($languages, '', $_SERVER['REQUEST_URI']);
659 $redirect_url = $this->add_language_to_link($requested_url, $language);
660 wp_redirect($redirect_url, 301);
661 exit;
662 }
663 }
664
665 // adds some javascript workaround knowing it's not perfect...
666 function wp_print_footer_scripts() {
667 // modifies the search form since filtering get_search_form won't work if the template uses searchform.php (prior to WP 3.6) or the search form is hardcoded
668 // don't use directly e[0] just in case there is somewhere else an element named 's'
669 // check before if the hidden input has not already been introduced by get_search_form (FIXME: is there a way to improve this) ?
670 // thanks to AndyDeGroo for improving the code for compatility with old browsers
671 // http://wordpress.org/support/topic/development-of-polylang-version-08?replies=6#post-2645559
672
673 $lang = esc_js($this->curlang->slug);
674 $js = "//<![CDATA[
675 e = document.getElementsByName('s');
676 for (i = 0; i < e.length; i++) {
677 if (e[i].tagName.toUpperCase() == 'INPUT') {
678 s = e[i].parentNode.parentNode.children;
679 l = 0;
680 for (j = 0; j < s.length; j++) {
681 if (s[j].name == 'lang') {
682 l = 1;
683 }
684 }
685 if ( l == 0) {
686 var ih = document.createElement('input');
687 ih.type = 'hidden';
688 ih.name = 'lang';
689 ih.value = '$lang';
690 e[i].parentNode.appendChild(ih);
691 }
692 }
693 }
694 //]]>";
695 echo "<script type='text/javascript'>" .$js. "</script>";
696 }
697
698 // adds the language information in the search form
699 // does not work if searchform.php (prior to WP 3.6) is used or if the search form is hardcoded in another template file
700 function get_search_form($form) {
701 if ($form)
702 $form = $this->using_permalinks ?
703 str_replace(trailingslashit($this->home), $this->get_home_url($this->curlang, true), $form) :
704 str_replace('</form>', '<input type="hidden" name="lang" value="'.esc_attr($this->curlang->slug).'" /></form>', $form);
705
706 return $form;
707 }
708
709 // rewrites the admin bar search form to include the language
710 function admin_bar_search_menu($wp_admin_bar) {
711 $title = sprintf('
712 <form action="%s" method="get" id="adminbarsearch">
713 <input class="adminbar-input" name="s" id="adminbar-search" tabindex="10" type="text" value="" maxlength="150" />
714 <input type="submit" class="adminbar-button" value="%s"/>
715 %s
716 </form>',
717 $this->using_permalinks ? $this->get_home_url($this->curlang, true) : esc_url($this->home),
718 __('Search'),
719 $this->using_permalinks ? '' : sprintf('<input type="hidden" name="lang" value="%s" />', esc_attr($this->curlang->slug))
720 );
721
722 $wp_admin_bar->add_menu(array(
723 'parent' => 'top-secondary',
724 'id' => 'search',
725 'title' => $title,
726 'meta' => array('class' => 'admin-bar-search', 'tabindex' => -1)
727 ));
728 }
729
730 // excludes pages which are not in the current language for wp_list_pages
731 // useful for the pages widget
732 function wp_list_pages_excludes($pages) {
733 return array_merge($pages, $this->exclude_pages($this->curlang->term_id));
734 }
735
736 // filters the comments according to the current language mainly for the recent comments widget
737 function comments_clauses($clauses, $query) {
738 return $this->_comments_clauses($clauses, isset($query->query_vars['lang']) ? $query->query_vars['lang'] : $this->curlang);
739 }
740
741 // modifies the sql request for wp_get_archives an get_adjacent_post to filter by the current language
742 function posts_join($sql) {
743 global $wpdb;
744 return $sql . " INNER JOIN $wpdb->term_relationships AS pll_tr ON pll_tr.object_id = ID";
745 }
746
747 // modifies the sql request for wp_get_archives and get_adjacent_post to filter by the current language
748 function posts_where($sql) {
749 global $wpdb;
750 preg_match("#post_type = '([^']+)'#", $sql, $matches); // find the queried post type
751 return !empty($matches[1]) && in_array($matches[1], $this->post_types) ? $sql . $wpdb->prepare(" AND pll_tr.term_taxonomy_id IN (%s)", $this->curlang->term_taxonomy_id) : $sql;
752 }
753
754 // modifies the author and date links to add the language parameter (as well as feed link)
755 function archive_link($link) {
756 return $this->add_language_to_link($link, $this->curlang);
757 }
758
759 // returns the url of the translation (if exists) of the current page
760 function get_translation_url($language) {
761 if (isset($this->translation_url[$language->slug]))
762 return $this->translation_url[$language->slug];
763
764 global $wp_query;
765 $qv = $wp_query->query;
766 $hide = $this->options['default_lang'] == $language->slug && $this->options['hide_default'];
767
768 // post and attachment
769 if (is_single() && ($this->options['media_support'] || !is_attachment()) && $id = $this->get_post($wp_query->queried_object_id, $language))
770 $url = get_permalink($id);
771
772 // page for posts
773 elseif (get_option('show_on_front') == 'page' && !empty($wp_query->queried_object_id) && $wp_query->queried_object_id == $this->page_for_posts && ($id = $this->get_post($this->page_for_posts, $language)))
774 $url = get_permalink($id);
775
776 elseif (is_page() && $id = $this->get_post($wp_query->queried_object_id, $language))
777 $url = $hide && $id == $this->get_post($this->page_on_front, $language) ? $this->home : get_page_link($id);
778
779 elseif (!is_tax('post_format') && !is_tax('language') && (is_category() || is_tag() || is_tax()) ) {
780 $term = get_queried_object();
781 $lang = $this->get_term_language($term->term_id);
782
783 if (!$lang || $language->slug == $lang->slug)
784 $url = get_term_link($term, $term->taxonomy); // self link
785 elseif ($link_id = $this->get_translation('term', $term->term_id, $language))
786 $url = get_term_link(get_term($link_id, $term->taxonomy), $term->taxonomy);
787 }
788
789 // don't test if there are existing translations before creating the url as it would be very expensive in sql queries
790 elseif (is_archive()) {
791 if ($this->using_permalinks) {
792 $filters = array('author_link', 'post_type_archive_link', 'year_link', 'month_link', 'day_link');
793
794 // prevents filtering links by current language
795 remove_filter('term_link', array(&$this, 'term_link')); // for post format
796 foreach ($filters as $filter)
797 remove_filter($filter, array(&$this, 'archive_link'));
798
799 if (is_author())
800 $url = $this->add_language_to_link(get_author_posts_url(0, $qv['author_name']), $language);
801
802 elseif (is_year())
803 $url = $this->add_language_to_link(get_year_link($qv['year']), $language);
804
805 elseif (is_month())
806 $url = $this->add_language_to_link(get_month_link($qv['year'], $qv['monthnum']), $language);
807
808 elseif (is_day())
809 $url = $this->add_language_to_link(get_day_link($qv['year'], $qv['monthnum'], $qv['day']), $language);
810
811 elseif (is_post_type_archive())
812 $url = $this->add_language_to_link(get_post_type_archive_link($qv['post_type']), $language);
813
814 elseif (is_tax('post_format'))
815 $url = $this->add_language_to_link(get_post_format_link($qv['post_format']), $language);
816
817 // put our language filters again
818 add_filter('term_link', array(&$this, 'term_link'), 10, 3);
819 foreach ($filters as $filter)
820 add_filter($filter, array(&$this, 'archive_link'));
821 }
822 else {
823 $url = $hide ? remove_query_arg('lang') : add_query_arg('lang', $language->slug);
824 $url = remove_query_arg('paged', $url);
825 }
826 }
827
828 elseif (is_search()) {
829 if ($this->using_permalinks)
830 $url = $this->add_language_to_link($this->home.'/?'.$_SERVER['QUERY_STRING'], $language);
831 else {
832 $url = add_query_arg('lang', $language->slug);
833 $url = remove_query_arg('paged', $url);
834 }
835 }
836
837 elseif (is_home() || is_tax('language') )
838 $url = $this->get_home_url($language);
839
840 return $this->translation_url[$language->slug] = (isset($url) && !is_wp_error($url) ? $url : null);
841 }
842
843 // filters the widgets according to the current language
844 // don't display if a language filter is set and this is not the current one
845 function widget_display_callback($instance, $widget, $args) {
846 return !empty($this->options['widgets'][$widget->id]) && $this->options['widgets'][$widget->id] != $this->curlang->slug ? false : $instance;
847 }
848
849 // translates biography
850 function get_user_metadata($null, $id, $meta_key, $single) {
851 return $meta_key == 'description' ? get_user_meta($id, 'description_'.$this->curlang->slug, true) : $null;
852 }
853
854 // translates page for posts and page on front
855 function translate_page($v) {
856 // returns the current page if there is no translation to avoid ugly notices
857 // the fonction is often called so let's store the result
858 return isset($this->curlang) && $v && (isset($this->posts[$v]) || $this->posts[$v] = $this->get_post($v, $this->curlang)) ? $this->posts[$v] : $v;
859 }
860
861 // filters the home url to get the right language
862 function home_url($url, $path) {
863 if (!(did_action('template_redirect') || did_action('login_init')) || rtrim($url,'/') != $this->home)
864 return $url;
865
866 $theme = get_theme_root();
867 $is_get_search_form = false;
868
869 // FIXME can I decrease the size of the array to improve speed?
870 foreach (array_reverse(debug_backtrace(/*!DEBUG_BACKTRACE_PROVIDE_OBJECT|DEBUG_BACKTRACE_IGNORE_ARGS*/)) as $trace) {
871 // searchform.php is not passed through get_search_form filter prior to WP 3.6
872 if (isset($trace['file']) && strpos($trace['file'], 'searchform.php'))
873 return $this->using_permalinks && version_compare($GLOBALS['wp_version'], '3.6', '<') ? $this->get_home_url($this->curlang, true) : $url;
874
875 // don't interfere with get_search_form filter which I prefer to use when possible
876 if ($trace['function'] == 'get_search_form')
877 $is_get_search_form = true;
878
879 if ($trace['function'] == 'wp_nav_menu' || $trace['function'] == 'login_footer' ||
880 // direct call from the theme
881 ( !$is_get_search_form && isset($trace['file']) && strpos($trace['file'], $theme) !== false && in_array($trace['function'], array('home_url', 'get_home_url', 'bloginfo', 'get_bloginfo')) ))
882 // remove trailing slash if there is none in requested url
883 return empty($path) ? rtrim($this->get_home_url($this->curlang), '/') : $this->get_home_url($this->curlang);
884 }
885
886 return $url;
887 }
888
889 // returns the home url in the right language
890 function get_home_url($language = '', $is_search = false) {
891 if (empty($language))
892 $language = $this->curlang;
893
894 if (isset($this->home_urls[$language->slug][$is_search]))
895 return $this->home_urls[$language->slug][$is_search];
896
897 if ($this->options['default_lang'] == $language->slug && $this->options['hide_default'])
898 return $this->home_urls[$language->slug][$is_search] = trailingslashit($this->home);
899
900 // a static page is used as front page : /!\ don't use get_page_link to avoid infinite loop
901 // don't use this for search form
902 if (!$is_search && $this->page_on_front && $id = $this->get_post($this->page_on_front, $language))
903 return $this->home_urls[$language->slug][$is_search] = $this->page_link('', $id);
904
905 $link = get_term_link($language, 'language');
906 // add a trailing slash as done by WP on homepage (otherwise could break the search form when the permalink structure does not include one)
907 // only for pretty permalinks
908 return $this->home_urls[$language->slug][$is_search] = $this->using_permalinks ? trailingslashit($link) : $link;
909 }
910
911 // called when a post (or page) is saved, published or updated
912 // does nothing except on post types which are filterable
913 function save_post($post_id, $post) {
914 if (in_array($post->post_type, $this->post_types)) {
915 if (isset($_REQUEST['lang']))
916 $this->set_post_language($post_id, $_REQUEST['lang']);
917
918 elseif ($this->get_post_language($post_id))
919 {}
920
921 else
922 $this->set_post_language($post_id, get_current_language());
923 }
924 }
925
926 // called when a category or post tag is created or edited
927 // does nothing except on taxonomies which are filterable
928 function save_term($term_id, $tt_id, $taxonomy) {
929 if (in_array($taxonomy, $this->taxonomies)) {
930 if (isset($_REQUEST['lang']))
931 $this->set_term_language($term_id, $_REQUEST['lang']);
932
933 elseif ($this->get_term_language($term_id))
934 {}
935
936 else
937 $this->set_term_language($term_id, get_current_language());
938 }
939 }
940
941 // displays (or returns) the language switcher
942 function the_languages($args = '') {
943 $defaults = array(
944 'dropdown' => 0, // display as list and not as dropdown
945 'echo' => 1, // echoes the list
946 'hide_if_empty' => 1, // hides languages with no posts (or pages)
947 'menu' => 0, // not for nav menu
948 'show_flags' => 0, // don't show flags
949 'show_names' => 1, // show language names
950 'display_names_as' => 'name', // valid options are slug and name
951 'force_home' => 0, // tries to find a translation
952 'hide_if_no_translation' => 0, // don't hide the link if there is no translation
953 'hide_current' => 0, // don't hide current language
954 'post_id' => null, // if not null, link to translations of post defined by post_id
955 );
956 extract(wp_parse_args($args, $defaults));
957
958 if ($dropdown)
959 $output = $this->dropdown_languages(array('hide_empty' => $hide_if_empty, 'selected' => $this->curlang->slug));
960
961 else {
962 $output = '';
963
964 foreach ($this->get_languages_list(array('hide_empty' => $hide_if_empty)) as $language) {
965 // hide current language
966 if ($this->curlang->term_id == $language->term_id && $hide_current)
967 continue;
968
969 $url = $post_id !== null && ($tr_id = $this->get_post($post_id, $language)) ? get_permalink($tr_id) :
970 $post_id === null && !$force_home ? $this->get_translation_url($language) : null;
971
972 $class = isset($url) ? '' : 'no-translation ';
973
974 $url = apply_filters('pll_the_language_link', $url, $language->slug, $language->description);
975
976 // hide if no translation exists
977 if (!isset($url) && $hide_if_no_translation)
978 continue;
979
980 $url = isset($url) ? $url : $this->get_home_url($language); // if the page is not translated, link to the home page
981
982 $class .= sprintf('lang-item lang-item-%d lang-item-%s', esc_attr($language->term_id), esc_attr($language->slug));
983 $class .= $language->term_id == $this->curlang->term_id ? ' current-lang' : '';
984 $class .= $menu ? ' menu-item' : '';
985
986 $flag = $show_flags ? $this->get_flag($language) : '';
987 $name = $show_names || !$show_flags ? esc_html($display_names_as == 'slug' ? $language->slug : $language->name) : '';
988
989 if ($menu && isset($item)) {
990 $i = clone $item;
991 $i->title = $show_flags && $show_names ? $flag.'&nbsp;'.$name : $flag.$name;
992 $i->url = $url;
993 $i->lang = $language->slug; // save this for use in nav_menu_link_attributes
994 $i->classes = $class;
995 $output[] = $i;
996 }
997 else
998 $output .= sprintf("<li class='%s'><a hreflang='%s' href='%s'>%s</a></li>\n",
999 $class,
1000 esc_attr($language->slug),
1001 esc_url($url),
1002 $show_flags && $show_names ? $flag.'&nbsp;'.$name : $flag.$name
1003 );
1004 }
1005 }
1006
1007 $output = apply_filters('pll_the_languages', $output, $args);
1008
1009 if(!$echo || ($menu && isset($item)))
1010 return $output;
1011 echo $output;
1012 }
1013 }
1014