PluginProbe
Polylang / 1.1.6
Polylang v1.1.6
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.6, at include/core.php

1,047 lines 45.6 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 static $once = false;
351 if ($once)
352 return;
353 $once = true;
354
355 // our override_load_textdomain filter has done its job. let's remove it before calling load_textdomain
356 remove_filter('override_load_textdomain', array(&$this, 'mofile'));
357 remove_filter('gettext', array(&$this, 'gettext'), 10, 3);
358 remove_filter('gettext_with_context', array(&$this, 'gettext_with_context'), 10, 4);
359
360 // check there is at least one language defined and sets the current language
361 if ($this->get_languages_list() && $this->curlang = $this->get_current_language()) {
362 // since 1.0: suppress old cookie which conflicts with quick cache
363 if (!headers_sent() && isset($_COOKIE['wordpress_polylang']))
364 setcookie('wordpress_polylang', '', time() - 3600, COOKIEPATH, COOKIE_DOMAIN);
365
366 // set a cookie to remember the language. check headers have not been sent to avoid ugly error
367 // possibility to set PLL_COOKIE to false will disable cookie although it will break some functionalities
368 if (!headers_sent() && PLL_COOKIE !== false && (!isset($_COOKIE[PLL_COOKIE]) || $_COOKIE[PLL_COOKIE] != $this->curlang->slug))
369 setcookie(PLL_COOKIE, $this->curlang->slug, time() + 31536000 /* 1 year */, COOKIEPATH, COOKIE_DOMAIN);
370
371 if (!($this->options['force_lang'] && $this->using_permalinks)) {
372 // set all our language filters and actions
373 $this->add_language_filters();
374
375 // now we can load text domains with the right language
376 $new_locale = get_locale();
377 foreach ($this->list_textdomains as $textdomain) {
378 $mo = str_replace("{$this->default_locale}.mo", "{$new_locale}.mo", $textdomain['mo']);
379 // since WP3.5 themes may store languages files in /wp-content/languages/themes
380 $mo = file_exists($mo) ? $mo : WP_LANG_DIR . "/themes/{$textdomain['domain']}-{$new_locale}.mo";
381 load_textdomain($textdomain['domain'], $mo);
382 }
383 // reinitializes wp_locale for weekdays and months, as well as for text direction
384 unset($GLOBALS['wp_locale']);
385 $GLOBALS['wp_locale'] = new WP_Locale();
386 $GLOBALS['wp_locale']->text_direction = get_metadata('term', $this->curlang->term_id, '_rtl', true) ? 'rtl' : 'ltr';
387
388 // translate labels of post types and taxonomies
389 foreach ($GLOBALS['wp_taxonomies'] as $tax)
390 $this->translate_labels($tax);
391 foreach ($GLOBALS['wp_post_types'] as $pt)
392 $this->translate_labels($pt);
393
394 // and finally load user defined strings
395 $GLOBALS['l10n']['pll_string'] = $this->mo_import($this->curlang);
396 do_action('pll_language_defined');
397 }
398 }
399
400 else {
401 // can't work so load the text domains with WordPress default language
402 foreach ($this->list_textdomains as $textdomain)
403 load_textdomain($textdomain['domain'], $textdomain['mo']);
404 }
405
406 // free memory
407 unset($this->list_textdomains);
408 unset($this->labels);
409 }
410
411 // special actions when home page is requested
412 function home_requested($query = false) {
413 // need this filter to get the right url when adding language code to all urls
414 if ($this->options['force_lang'] && $this->using_permalinks)
415 add_filter('_get_page_link', array(&$this, 'post_link'), 10, 2);
416
417 // FIXME cookie wordpress_polylang removed since 1.0
418 // test referer in case PLL_COOKIE is set to false
419 // thanks to Ov3rfly http://wordpress.org/support/topic/enhance-feature-when-front-page-is-visited-set-language-according-to-browser
420 $this->curlang = $this->options['hide_default'] && ((isset($_SERVER['HTTP_REFERER']) && strpos($_SERVER['HTTP_REFERER'], $this->home) !== false)) ?
421 $this->get_language($this->options['default_lang']) :
422 $this->get_preferred_language(); // sets the language according to browser preference or default language
423
424 if ($query)
425 $this->_home_requested($query);
426 else
427 add_action('setup_theme', array(&$this, '_home_requested')); // delays actions which need $wp_query & $wp_rewrite
428 }
429
430 // sets the correct query var for home page
431 // optionally redirects to the home page in the preferred language
432 function _home_requested($query = false) {
433 // we are already on the right page
434 if ($this->options['default_lang'] == $this->curlang->slug && $this->options['hide_default']) {
435 if ($this->page_on_front && $link_id = $this->get_post($this->page_on_front, $this->curlang))
436 $query ? $query->set('page_id', $link_id) : set_query_var('page_id', $link_id);
437 else
438 $query ? $query->set('lang', $this->curlang->slug) : set_query_var('lang', $this->curlang->slug);
439 }
440
441 // redirect to the home page in the right language
442 // test to avoid crash if get_home_url returns something wrong
443 // FIXME why this happens? http://wordpress.org/support/topic/polylang-crashes-1
444 // don't redirect if $_POST is not empty as it could break other plugins
445 // don't forget the query string which may be added by plugins
446 elseif (is_string($redirect = $this->get_home_url($this->curlang)) && empty($_POST)) {
447 $redirect = empty($_SERVER['QUERY_STRING']) ? $redirect : $redirect . ($this->using_permalinks ? '?' : '&') . $_SERVER['QUERY_STRING'];
448 if ($redirect = apply_filters('pll_redirect_home', $redirect)) {
449 wp_redirect($redirect);
450 exit;
451 }
452 }
453 }
454
455 // filters posts according to the language
456 function pre_get_posts($query) {
457 // don't make anything if no language has been defined yet
458 // $this->post_types & $this->taxonomies are defined only once the action 'wp_loaded' has been fired
459 // don't honor suppress_filters as it breaks adjacent_image_link when post_parent == 0
460 if (!$this->get_languages_list() || !did_action('wp_loaded'))
461 return;
462
463 $qv = $query->query_vars;
464
465 // do not filter if lang is set to an empty value
466 if (isset($qv['lang']) && !$qv['lang'])
467 return;
468
469 // users may want to display content in a different language than the current one by setting it explicitely in the query
470 if (!$this->first_query && $this->curlang && !empty($qv['lang']))
471 return;
472
473 $is_post_type = !empty($qv['post_type']) && (
474 in_array($qv['post_type'], $this->post_types) ||
475 (is_array($qv['post_type']) && array_intersect($qv['post_type'], $this->post_types))
476 );
477
478 // don't filters post types not in our list
479 if (!empty($qv['post_type']) && !$is_post_type)
480 return;
481
482 $this->first_query = false;
483
484 // special case for wp-signup.php & wp-activate.php
485 // stripos for case insensitive file systems
486 if (false === stripos($_SERVER['SCRIPT_NAME'], $GLOBALS['wp_rewrite']->index)) {
487 $this->curlang = $this->get_preferred_language();
488 return;
489 }
490
491 // homepage is requested, let's set the language
492 // take care to avoid posts page for which is_home = 1
493 if (!$this->curlang && empty($query->query) && (is_home() || (is_page() && $qv['page_id'] == $this->page_on_front)))
494 $this->home_requested($query);
495
496 // redirect the language page to the homepage
497 if ($this->options['redirect_lang'] && is_tax('language') && $this->page_on_front && (count($query->query) == 1 || (is_paged() && count($query->query) == 2))) {
498 $this->curlang = $this->get_language(get_query_var('lang'));
499 if ($page_id = $this->get_post($this->page_on_front, $this->get_language(get_query_var('lang')))) {
500 $query->set('page_id', $page_id);
501 $query->is_singular = $query->is_page = true;
502 $query->is_archive = $query->is_tax = false;
503 unset($query->queried_object); // reset queried object
504 return;
505 }
506 // else : the static front page is not translated
507 // let's things as is and the list of posts in the current language will be displayed
508 }
509
510 // sets is_home on translated home page when it displays posts
511 // is_home must be true on page 2, 3... too
512 // as well as when searching an empty string: http://wordpress.org/support/topic/plugin-polylang-polylang-breaks-search-in-spun-theme
513 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']))) {
514 $this->curlang = $this->get_language(get_query_var('lang')); // sets the language now otherwise it will be too late to filter sticky posts !
515 $query->is_home = true;
516 $query->is_archive = $query->is_tax = false;
517 }
518
519 // sets the language for posts page in case the front page displays a static page
520 if ($this->page_for_posts) {
521 // If permalinks are used, WordPress does set and use $query->queried_object_id and sets $query->query_vars['page_id'] to 0
522 // and does set and use $query->query_vars['page_id'] if permalinks are not used :(
523 if (!empty($qv['pagename']) && isset($query->queried_object_id))
524 $page_id = $query->queried_object_id;
525
526 elseif (isset($qv['page_id']))
527 $page_id = $qv['page_id'];
528
529 if (!empty($page_id) && $this->get_post($page_id, $this->get_post_language($this->page_for_posts)) == $this->page_for_posts) {
530 $this->page_for_posts = $page_id;
531 $this->curlang = $this->get_post_language($page_id);
532 $query->set('lang', $this->curlang->slug);
533 $query->is_singular = $query->is_page = false;
534 $query->is_home = $query->is_posts_page = true;
535 }
536 }
537
538 $is_archive = (count($query->query) == 1 && !empty($qv['paged'])) ||
539 $query->is_date ||
540 $query->is_author ||
541 (!empty($qv['post_type']) && $query->is_post_type_archive && $is_post_type);
542
543 // sets 404 when the language is not set for archives needing the language in the url
544 if (!$this->options['hide_default'] && !isset($qv['lang']) && !$this->using_permalinks && $is_archive)
545 $query->set_404();
546
547 // sets the language in case we hide the default language
548 if ($this->options['hide_default'] && !isset($qv['lang']) && ($is_archive || $query->is_search || (count($query->query) == 1 && !empty($qv['feed'])) ))
549 $query->set('lang', $this->options['default_lang']);
550
551 // allow filtering recent posts and secondary queries by the current language
552 // take care not to break queries for non visible post types such as nav_menu_items, attachments...
553 if (/*$query->is_home && */$this->curlang && (empty($qv['post_type']) || $is_post_type ))
554 $query->set('lang', $this->curlang->slug);
555
556 // remove pages query when the language is set unless we do a search
557 // FIXME is only search broken by this ?
558 if (!empty($qv['lang']) && empty($qv['post_type']) && !$query->is_search)
559 $query->set('post_type', 'post');
560
561 // unset the is_archive flag for language pages to prevent loading the archive template
562 // keep archive flag for comment feed otherwise the language filter does not work
563 if (!empty($qv['lang']) && !is_comment_feed() &&
564 !is_post_type_archive() && !is_date() && !is_author() && !is_category() && !is_tag() && !is_tax('post_format'))
565 $query->is_archive = false;
566
567 // unset the is_tax flag for authors pages and post types archives
568 // FIXME Probably I should do this for other cases
569 if (!empty($qv['lang']) && (is_author() || is_post_type_archive() || is_date() || is_search())) {
570 $query->is_tax = false;
571 unset($query->queried_object);
572 }
573
574 // sets a language for theme preview
575 if (is_preview() && is_front_page()) {
576 $this->curlang = $this->get_current_language();
577 $query->set('lang', $this->curlang->slug);
578 }
579
580 // sets the language for an empty string search when hiding the code for default language
581 // http://wordpress.org/support/topic/search-for-empty-string-in-default-language
582 if (!$this->curlang && !get_query_var('lang') && $this->options['hide_default'] && isset($query->query['s']) && !$query->query['s'])
583 $query->set('lang', $this->options['default_lang']);
584
585 // to avoid conflict beetwen taxonomies
586 if (isset($query->tax_query->queries))
587 foreach ($query->tax_query->queries as $tax)
588 if (in_array($tax['taxonomy'], $this->taxonomies))
589 unset($query->query_vars['lang']);
590 }
591
592 // filter sticky posts by current language
593 function option_sticky_posts($posts) {
594 if ($this->curlang && !empty($posts)) {
595 foreach ($posts as $key=>$post_id) {
596 if ($this->get_post_language($post_id)->term_id != $this->curlang->term_id) // FIXME query in foreach
597 unset($posts[$key]);
598 }
599 }
600 return $posts;
601 }
602
603 // filters categories and post tags by language when needed
604 function terms_clauses($clauses, $taxonomies, $args) {
605 // does nothing except on taxonomies which are filterable
606 if (!array_intersect($taxonomies, $this->taxonomies))
607 return $clauses;
608
609 // adds our clauses to filter by language
610 return $this->_terms_clauses($clauses, isset($args['lang']) ? $args['lang'] : $this->curlang);
611 }
612
613 // meta in the html head section
614 function wp_head() {
615 // outputs references to translated pages (if exists) in the html head section
616 foreach ($this->get_languages_list() as $language) {
617 if ($language->slug != $this->curlang->slug && $url = $this->get_translation_url($language))
618 printf('<link hreflang="%s" href="%s" rel="alternate" />'."\n", esc_attr($language->slug), esc_url($url));
619 }
620 }
621
622 // modifies the page link in case the front page is not in the default language
623 function page_link($link, $id) {
624 if ($this->options['redirect_lang'] && $this->page_on_front && $lang = $this->get_post_language($id)) {
625 if (!isset($this->posts[$lang->slug][$this->page_on_front]))
626 $this->posts[$lang->slug][$this->page_on_front] = $this->get_post($this->page_on_front, $lang);
627 if ($id == $this->posts[$lang->slug][$this->page_on_front])
628 return $this->options['hide_default'] && $lang->slug == $this->options['default_lang'] ? trailingslashit($this->home) : get_term_link($lang, 'language');
629 }
630
631 if ($this->page_on_front && $this->options['hide_default']) {
632 if (!isset($this->posts[$this->options['default_lang']][$this->page_on_front]))
633 $this->posts[$this->options['default_lang']][$this->page_on_front] = $this->get_post($this->page_on_front, $this->options['default_lang']);
634 if ($id == $this->posts[$this->options['default_lang']][$this->page_on_front])
635 return trailingslashit($this->home);
636 }
637
638 return _get_page_link($id);
639 }
640
641 // manages canonical redirection of the homepage when using page on front
642 function redirect_canonical($redirect_url, $requested_url) {
643 global $wp_query;
644 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'))
645 return $this->options['redirect_lang'] ? $this->get_home_url() : false;
646 return $redirect_url;
647 }
648
649 // redirects incoming links to the proper URL when adding the language code to all urls
650 function check_language_code_in_url() {
651 if (is_single() || is_page()) {
652 global $post;
653 if (isset($post->ID) && in_array($post->post_type, $this->post_types))
654 $language = $this->get_post_language((int)$post->ID);
655 }
656 elseif (is_category() || is_tag() || is_tax()) {
657 $obj = $GLOBALS['wp_query']->get_queried_object();
658 if (in_array($obj->taxonomy, $this->taxonomies))
659 $language = $this->get_term_language((int)$obj->term_id);
660 }
661
662 // the language is not correctly set so let's redirect to the correct url for this object
663 if (isset($language) && $language->slug != $this->curlang->slug) {
664 $root = $this->options['rewrite'] ? '/' : '/language/';
665 foreach ($this->get_languages_list() as $lang)
666 $languages[] = $root . $lang->slug;
667
668 $requested_url = (is_ssl() ? 'https://' : 'http://') . $_SERVER['HTTP_HOST'] . str_replace($languages, '', $_SERVER['REQUEST_URI']);
669 $redirect_url = $this->add_language_to_link($requested_url, $language);
670 wp_redirect($redirect_url, 301);
671 exit;
672 }
673 }
674
675 // adds some javascript workaround knowing it's not perfect...
676 function wp_print_footer_scripts() {
677 // 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
678 // don't use directly e[0] just in case there is somewhere else an element named 's'
679 // check before if the hidden input has not already been introduced by get_search_form (FIXME: is there a way to improve this) ?
680 // thanks to AndyDeGroo for improving the code for compatility with old browsers
681 // http://wordpress.org/support/topic/development-of-polylang-version-08?replies=6#post-2645559
682
683 $lang = esc_js($this->curlang->slug);
684 $js = "//<![CDATA[
685 e = document.getElementsByName('s');
686 for (i = 0; i < e.length; i++) {
687 if (e[i].tagName.toUpperCase() == 'INPUT') {
688 s = e[i].parentNode.parentNode.children;
689 l = 0;
690 for (j = 0; j < s.length; j++) {
691 if (s[j].name == 'lang') {
692 l = 1;
693 }
694 }
695 if ( l == 0) {
696 var ih = document.createElement('input');
697 ih.type = 'hidden';
698 ih.name = 'lang';
699 ih.value = '$lang';
700 e[i].parentNode.appendChild(ih);
701 }
702 }
703 }
704 //]]>";
705 echo "<script type='text/javascript'>" .$js. "</script>";
706 }
707
708 // adds the language information in the search form
709 // does not work if searchform.php (prior to WP 3.6) is used or if the search form is hardcoded in another template file
710 function get_search_form($form) {
711 if ($form) {
712 if ($this->using_permalinks) {
713 preg_match('#<form.+>#', $form, $matches);
714 $old = reset($matches);
715 $new = str_replace(trailingslashit($this->home), $this->get_home_url($this->curlang, true), $old);
716 $form = str_replace($old, $new, $form);
717 }
718 else
719 $form = str_replace('</form>', '<input type="hidden" name="lang" value="'.esc_attr($this->curlang->slug).'" /></form>', $form);
720 }
721
722 return $form;
723 }
724
725 // rewrites the admin bar search form to include the language
726 function admin_bar_search_menu($wp_admin_bar) {
727 $title = sprintf('
728 <form action="%s" method="get" id="adminbarsearch">
729 <input class="adminbar-input" name="s" id="adminbar-search" tabindex="10" type="text" value="" maxlength="150" />
730 <input type="submit" class="adminbar-button" value="%s"/>
731 %s
732 </form>',
733 $this->using_permalinks ? $this->get_home_url($this->curlang, true) : esc_url($this->home),
734 __('Search'),
735 $this->using_permalinks ? '' : sprintf('<input type="hidden" name="lang" value="%s" />', esc_attr($this->curlang->slug))
736 );
737
738 $wp_admin_bar->add_menu(array(
739 'parent' => 'top-secondary',
740 'id' => 'search',
741 'title' => $title,
742 'meta' => array('class' => 'admin-bar-search', 'tabindex' => -1)
743 ));
744 }
745
746 // excludes pages which are not in the current language for wp_list_pages
747 // useful for the pages widget
748 function wp_list_pages_excludes($pages) {
749 return array_merge($pages, $this->exclude_pages($this->curlang->term_id));
750 }
751
752 // filters the comments according to the current language mainly for the recent comments widget
753 function comments_clauses($clauses, $query) {
754 return $this->_comments_clauses($clauses, isset($query->query_vars['lang']) ? $query->query_vars['lang'] : $this->curlang);
755 }
756
757 // modifies the sql request for wp_get_archives an get_adjacent_post to filter by the current language
758 function posts_join($sql) {
759 global $wpdb;
760 return $sql . " INNER JOIN $wpdb->term_relationships AS pll_tr ON pll_tr.object_id = ID";
761 }
762
763 // modifies the sql request for wp_get_archives and get_adjacent_post to filter by the current language
764 function posts_where($sql) {
765 global $wpdb;
766 preg_match("#post_type = '([^']+)'#", $sql, $matches); // find the queried post type
767 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;
768 }
769
770 // modifies the author and date links to add the language parameter (as well as feed link)
771 function archive_link($link) {
772 return $this->add_language_to_link($link, $this->curlang);
773 }
774
775 // returns the url of the translation (if exists) of the current page
776 function get_translation_url($language) {
777 if (isset($this->translation_url[$language->slug]))
778 return $this->translation_url[$language->slug];
779
780 global $wp_query;
781 $qv = $wp_query->query;
782 $hide = $this->options['default_lang'] == $language->slug && $this->options['hide_default'];
783
784 // post and attachment
785 if (is_single() && ($this->options['media_support'] || !is_attachment()) && $id = $this->get_post($wp_query->queried_object_id, $language))
786 $url = get_permalink($id);
787
788 // page for posts
789 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)))
790 $url = get_permalink($id);
791
792 elseif (is_page() && $id = $this->get_post($wp_query->queried_object_id, $language))
793 $url = $hide && $id == $this->get_post($this->page_on_front, $language) ? $this->home : get_page_link($id);
794
795 elseif (!is_tax('post_format') && !is_tax('language') && (is_category() || is_tag() || is_tax()) ) {
796 $term = get_queried_object();
797 $lang = $this->get_term_language($term->term_id);
798
799 if (!$lang || $language->slug == $lang->slug)
800 $url = get_term_link($term, $term->taxonomy); // self link
801 elseif ($link_id = $this->get_translation('term', $term->term_id, $language))
802 $url = get_term_link(get_term($link_id, $term->taxonomy), $term->taxonomy);
803 }
804
805 // don't test if there are existing translations before creating the url as it would be very expensive in sql queries
806 elseif (is_archive()) {
807 if ($this->using_permalinks) {
808 $filters = array('author_link', 'post_type_archive_link', 'year_link', 'month_link', 'day_link');
809
810 // prevents filtering links by current language
811 remove_filter('term_link', array(&$this, 'term_link')); // for post format
812 foreach ($filters as $filter)
813 remove_filter($filter, array(&$this, 'archive_link'));
814
815 if (is_author())
816 $url = $this->add_language_to_link(get_author_posts_url(0, $qv['author_name']), $language);
817
818 elseif (is_year())
819 $url = $this->add_language_to_link(get_year_link($qv['year']), $language);
820
821 elseif (is_month())
822 $url = $this->add_language_to_link(get_month_link($qv['year'], $qv['monthnum']), $language);
823
824 elseif (is_day())
825 $url = $this->add_language_to_link(get_day_link($qv['year'], $qv['monthnum'], $qv['day']), $language);
826
827 elseif (is_post_type_archive())
828 $url = $this->add_language_to_link(get_post_type_archive_link($qv['post_type']), $language);
829
830 elseif (is_tax('post_format'))
831 $url = $this->add_language_to_link(get_post_format_link($qv['post_format']), $language);
832
833 // put our language filters again
834 add_filter('term_link', array(&$this, 'term_link'), 10, 3);
835 foreach ($filters as $filter)
836 add_filter($filter, array(&$this, 'archive_link'));
837 }
838 else {
839 $url = $hide ? remove_query_arg('lang') : add_query_arg('lang', $language->slug);
840 $url = remove_query_arg('paged', $url);
841 }
842 }
843
844 elseif (is_search()) {
845 if ($this->using_permalinks)
846 $url = $this->add_language_to_link($this->home.'/?'.$_SERVER['QUERY_STRING'], $language);
847 else {
848 $url = add_query_arg('lang', $language->slug);
849 $url = remove_query_arg('paged', $url);
850 }
851 }
852
853 elseif (is_home() || is_tax('language') )
854 $url = $this->get_home_url($language);
855
856 return $this->translation_url[$language->slug] = apply_filters('pll_translation_url', (isset($url) && !is_wp_error($url) ? $url : null), $language->slug);
857 }
858
859 // filters the widgets according to the current language
860 // don't display if a language filter is set and this is not the current one
861 function widget_display_callback($instance, $widget, $args) {
862 return !empty($this->options['widgets'][$widget->id]) && $this->options['widgets'][$widget->id] != $this->curlang->slug ? false : $instance;
863 }
864
865 // translates biography
866 function get_user_metadata($null, $id, $meta_key, $single) {
867 return $meta_key == 'description' ? get_user_meta($id, 'description_'.$this->curlang->slug, true) : $null;
868 }
869
870 // translates page for posts and page on front
871 function translate_page($v) {
872 // returns the current page if there is no translation to avoid ugly notices
873 // the fonction is often called so let's store the result
874 return isset($this->curlang) && $v && (isset($this->posts[$v]) || $this->posts[$v] = $this->get_post($v, $this->curlang)) ? $this->posts[$v] : $v;
875 }
876
877 // filters the home url to get the right language
878 function home_url($url, $path) {
879 if (!(did_action('template_redirect') || did_action('login_init')) || rtrim($url,'/') != $this->home)
880 return $url;
881
882 $white_list = apply_filters('pll_home_url_white_list', array(
883 array('file' => get_theme_root()),
884 array('function' => 'wp_nav_menu'),
885 array('function' => 'login_footer')
886 ));
887
888 $black_list = apply_filters('pll_home_url_black_list', array(array('function' => 'get_search_form')));
889
890 foreach (array_reverse(debug_backtrace(/*!DEBUG_BACKTRACE_PROVIDE_OBJECT|DEBUG_BACKTRACE_IGNORE_ARGS*/)) as $trace) {
891 // searchform.php is not passed through get_search_form filter prior to WP 3.6
892 if (isset($trace['file']) && strpos($trace['file'], 'searchform.php'))
893 return $this->using_permalinks && version_compare($GLOBALS['wp_version'], '3.6', '<') ? $this->get_home_url($this->curlang, true) : $url;
894
895 foreach ($black_list as $v) {
896 if ((isset($trace['file'], $v['file']) && strpos($trace['file'], $v['file']) !== false) || (isset($trace['function'], $v['function']) && $trace['function'] == $v['function']))
897 return $url;
898 }
899
900 foreach ($white_list as $v) {
901 if ((isset($trace['function'], $v['function']) && $trace['function'] == $v['function']) ||
902 (isset($trace['file'], $v['file']) && strpos($trace['file'], $v['file']) !== false && in_array($trace['function'], array('home_url', 'get_home_url', 'bloginfo', 'get_bloginfo'))))
903 $ok = true;
904 }
905 }
906
907 return empty($ok) ? $url : (empty($path) ? rtrim($this->get_home_url($this->curlang), '/') : $this->get_home_url($this->curlang));
908 }
909
910 // returns the home url in the right language
911 function get_home_url($language = '', $is_search = false) {
912 if (empty($language))
913 $language = $this->curlang;
914
915 if (isset($this->home_urls[$language->slug][$is_search]))
916 return $this->home_urls[$language->slug][$is_search];
917
918 if ($this->options['default_lang'] == $language->slug && $this->options['hide_default'])
919 return $this->home_urls[$language->slug][$is_search] = trailingslashit($this->home);
920
921 // a static page is used as front page : /!\ don't use get_page_link to avoid infinite loop
922 // don't use this for search form
923 if (!$is_search && $this->page_on_front && $id = $this->get_post($this->page_on_front, $language))
924 return $this->home_urls[$language->slug][$is_search] = $this->page_link('', $id);
925
926 $link = get_term_link($language, 'language');
927 // add a trailing slash as done by WP on homepage (otherwise could break the search form when the permalink structure does not include one)
928 // only for pretty permalinks
929 return $this->home_urls[$language->slug][$is_search] = $this->using_permalinks ? trailingslashit($link) : $link;
930 }
931
932 // called when a post (or page) is saved, published or updated
933 // does nothing except on post types which are filterable
934 function save_post($post_id, $post) {
935 if (in_array($post->post_type, $this->post_types)) {
936 if (isset($_REQUEST['lang']))
937 $this->set_post_language($post_id, $_REQUEST['lang']);
938
939 elseif ($this->get_post_language($post_id))
940 {}
941
942 elseif (($parent_id = wp_get_post_parent_id($post_id)) && $parent_lang = $this->get_post_language($parent_id))
943 $this->set_post_language($post_id, $parent_lang);
944
945 else
946 $this->set_post_language($post_id, $this->get_current_language());
947 }
948 }
949
950 // called when a category or post tag is created or edited
951 // does nothing except on taxonomies which are filterable
952 function save_term($term_id, $tt_id, $taxonomy) {
953 if (in_array($taxonomy, $this->taxonomies)) {
954 if (isset($_REQUEST['lang']))
955 $this->set_term_language($term_id, $_REQUEST['lang']);
956
957 elseif ($this->get_term_language($term_id))
958 {}
959
960 elseif (($term = get_term($term_id, $taxonomy)) && !empty($term->parent) && $parent_lang = $this->get_term_language($term->parent))
961 $this->set_term_language($term_id, $parent_lang);
962
963 else
964 $this->set_term_language($term_id, $this->get_current_language());
965 }
966 }
967
968 // displays (or returns) the language switcher
969 function the_languages($args = '') {
970 $defaults = array(
971 'dropdown' => 0, // display as list and not as dropdown
972 'echo' => 1, // echoes the list
973 'hide_if_empty' => 1, // hides languages with no posts (or pages)
974 'menu' => 0, // not for nav menu (this argument is deprecated since v1.1.1)
975 'show_flags' => 0, // don't show flags
976 'show_names' => 1, // show language names
977 'display_names_as' => 'name', // valid options are slug and name
978 'force_home' => 0, // tries to find a translation
979 'hide_if_no_translation' => 0, // don't hide the link if there is no translation
980 'hide_current' => 0, // don't hide current language
981 'post_id' => null, // if not null, link to translations of post defined by post_id
982 'raw' => 0, // set this to true to build your own custom language switcher
983 );
984 extract(wp_parse_args($args, $defaults));
985
986 if ($dropdown)
987 $output = $this->dropdown_languages(array('hide_empty' => $hide_if_empty, 'selected' => $this->curlang->slug));
988
989 else {
990 $output = !empty($raw) ? array() : '';
991
992 foreach ($this->get_languages_list(array('hide_empty' => $hide_if_empty)) as $language) {
993 $id = (int) $language->term_id;
994 $slug = $language->slug;
995
996 // hide current language
997 if ($this->curlang->term_id == $id && $hide_current)
998 continue;
999
1000 $url = $post_id !== null && ($tr_id = $this->get_post($post_id, $language)) ? get_permalink($tr_id) :
1001 ($post_id === null && !$force_home ? $this->get_translation_url($language) : null);
1002
1003 $no_translation = empty($url); // keep this for future
1004 $url = apply_filters('pll_the_language_link', $url, $slug, $language->description);
1005
1006 // hide if no translation exists
1007 if (empty($url) && $hide_if_no_translation)
1008 continue;
1009
1010 $url = empty($url) ? $this->get_home_url($language) : $url ; // if the page is not translated, link to the home page
1011
1012 $name = $show_names || !$show_flags || $raw ? esc_html($display_names_as == 'slug' ? $slug : $language->name) : '';
1013 $flag = $show_flags || $raw ? $this->get_flag($language, $raw && !$show_flags) : '';
1014 $current_lang = $id == $this->curlang->term_id;
1015
1016 // classes
1017 $classes = array('lang-item', 'lang-item-' . esc_attr($id), 'lang-item-' . esc_attr($slug));
1018 if ($no_translation)
1019 $classes[] = 'no-translation';
1020 if ($current_lang)
1021 $classes[] = 'current-lang';
1022
1023 if (!empty($raw))
1024 $output[] = compact('id', 'slug', 'name', 'url', 'flag', 'current_lang', 'no_translation', 'classes');
1025
1026 else {
1027 if ($menu)
1028 $classes[] = 'menu-item'; // backward compatibility < 1.1
1029
1030 $output .= sprintf('<li class="%s"><a hreflang="%s" href="%s">%s</a></li>'."\n",
1031 implode(' ', $classes),
1032 esc_attr($slug),
1033 esc_url($url),
1034 $show_flags && $show_names ? $flag.'&nbsp;'.$name : $flag.$name
1035 );
1036 }
1037 }
1038 }
1039
1040 $output = apply_filters('pll_the_languages', $output, $args);
1041
1042 if (!$echo || !empty($raw))
1043 return $output;
1044 echo $output;
1045 }
1046 }
1047