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

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