PluginProbe
Polylang / 1.0.1
Polylang v1.0.1
3.8.9 3.8.8 3.8.7 3.8.6 3.8.5 3.8.4 3.8.3 2.7 2.7.0.1 2.7.1 2.7.2 2.7.3 2.7.4 2.8 2.8.1 2.8.2 2.8.3 2.8.4 2.9 2.9.1 2.9.2 3.0 3.0.1 3.0.2 3.0.3 All 233 releases
polylang / include / core.php

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

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