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

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

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