PluginProbe
Polylang / 0.9.2
Polylang v0.9.2
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 0.9.2, at include/core.php

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