PluginProbe
Polylang / 0.4
Polylang v0.4
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 / polylang.php

polylang.php in Polylang 0.4, at polylang.php

795 lines 31.3 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2 /*
3 Plugin Name: Polylang
4 Plugin URI: http://wordpress.org/extend/plugins/polylang/
5 Version: 0.4
6 Author: F. Demarle
7 Description: Adds multilingual capability to Wordpress
8 */
9
10 /* Copyright 2011 F. Demarle
11
12 This program is free software; you can redistribute it and/or modify
13 it under the terms of the GNU General Public License, version 2, as
14 published by the Free Software Foundation.
15
16 This program is distributed in the hope that it will be useful,
17 but WITHOUT ANY WARRANTY; without even the implied warranty of
18 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
19 GNU General Public License for more details.
20
21 You should have received a copy of the GNU General Public License
22 along with this program; if not, write to the Free Software
23 Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
24 */
25
26 define('POLYLANG_VERSION', '0.4');
27
28 define('POLYLANG_DIR', dirname(__FILE__));
29 define('INC_DIR', POLYLANG_DIR.'/include');
30
31 require_once(INC_DIR.'/base.php');
32 require_once(INC_DIR.'/admin.php');
33 require_once(INC_DIR.'/widget.php');
34
35 class Polylang extends Polylang_Base {
36 var $curlang; // current language
37 var $default_locale;
38 var $list_textdomains = array(); // all text domains
39 var $search_form_filter = false; // did we pass our get_search_form filter ?
40
41 function __construct() {
42 // manages plugin activation and deactivation
43 register_activation_hook( __FILE__, array(&$this, 'activate') );
44 register_deactivation_hook( __FILE__, array(&$this, 'deactivate') );
45
46 // plugin and widget initialization
47 add_action('init', array(&$this, 'init'));
48 add_action('widgets_init', array(&$this, 'widgets_init'));
49
50 // rewrite rules
51 add_filter('rewrite_rules_array', array(&$this, 'rewrite_rules_array' ));
52
53 // filters categories and post tags by language
54 add_filter('terms_clauses', array(&$this, 'terms_clauses'), 10, 3);
55
56 if (is_admin())
57 new Polylang_Admin();
58 else {
59 // text domain management
60 add_filter('locale', array(&$this, 'get_locale'));
61 add_filter('override_load_textdomain', array(&$this, 'mofile'), 10, 3);
62 add_action('wp', array(&$this, 'load_textdomains'));
63
64 // filters posts according to the language
65 add_filter('pre_get_posts', array(&$this, 'pre_get_posts'));
66
67 // meta in the html head section
68 remove_action('wp_head', 'rel_canonical');
69 add_action('wp_head', array(&$this, 'wp_head'));
70
71 // prevents redirection of the homepage
72 add_filter('redirect_canonical', array(&$this, 'redirect_canonical'), 10, 2);
73
74 // adds javascript at the end of the document
75 add_action('wp_print_footer_scripts', array(&$this, 'wp_print_footer_scripts'));
76
77 // adds the language information in the search form
78 add_filter('get_search_form', array(&$this, 'get_search_form'));
79
80 // filters the pages according to the current language in wp_list_pages
81 add_filter('wp_list_pages_excludes', array(&$this, 'wp_list_pages_excludes'));
82
83 // filters the comments according to the current language
84 add_filter('comments_clauses', array(&$this, 'comments_clauses'), 10, 2);
85
86 // rewrites feed links to filter them by language
87 add_filter('feed_link', array(&$this, 'feed_link'), 10, 2);
88
89 // rewrites archives links to filter them by language
90 add_filter('getarchives_join', array(&$this, 'posts_join'));
91 add_filter('getarchives_where', array(&$this, 'posts_where'));
92
93 // rewrites author and date links to filter them by language
94 add_filter('author_link', array(&$this, 'archive_link'));
95 add_filter('year_link', array(&$this, 'archive_link'));
96 add_filter('month_link', array(&$this, 'archive_link'));
97 add_filter('day_link', array(&$this, 'archive_link'));
98
99 // modifies the calendar to filter posts by language
100 add_filter('get_calendar', array(&$this, 'get_calendar'));
101
102 // rewrites next and previous post links to filter them by language
103 add_filter('get_previous_post_join', array(&$this, 'posts_join'));
104 add_filter('get_next_post_join', array(&$this, 'posts_join'));
105 add_filter('get_previous_post_where', array(&$this, 'posts_where'));
106 add_filter('get_next_post_where', array(&$this, 'posts_where'));
107
108 // filters the nav menus according to the current language
109 add_filter('wp_nav_menu_args', array(&$this, 'wp_nav_menu_args'));
110
111 // filters the widgets according to the current language
112 add_filter('widget_display_callback', array(&$this, 'widget_display_callback'), 10, 3);
113
114 // modifies the home url
115 add_filter('home_url', array(&$this, 'home_url'));
116
117 // allows a new value for the 'show' parameter to display the homepage url according to the current language
118 add_filter('bloginfo_url', array(&$this, 'bloginfo_url'), 10, 2);
119
120 // Template tags
121 add_action('the_languages', array(&$this, 'the_languages'));
122 }
123 }
124
125 // plugin activation
126 function activate() {
127 // create the termmeta table - not provided by WP by default - if it does not already exists
128 // uses exactly the same model as other meta tables to be able to use access functions provided by WP
129 global $wpdb;
130 $charset_collate = '';
131 if ( ! empty($wpdb->charset) )
132 $charset_collate = "DEFAULT CHARACTER SET $wpdb->charset";
133 if ( ! empty($wpdb->collate) )
134 $charset_collate .= " COLLATE $wpdb->collate";
135
136 $table = $wpdb->prefix . 'termmeta';
137
138 $tables = $wpdb->get_results("show tables like '$table'");
139 if (!count($tables))
140 $wpdb->query("CREATE TABLE $table (
141 meta_id bigint(20) unsigned NOT NULL auto_increment,
142 term_id bigint(20) unsigned NOT NULL default '0',
143 meta_key varchar(255) default NULL,
144 meta_value longtext,
145 PRIMARY KEY (meta_id),
146 KEY term_id (term_id),
147 KEY meta_key (meta_key)
148 ) $charset_collate;");
149
150 // codex tells to use the init action to call register_taxonomy but I need it now for my rewrite rules
151 register_taxonomy('language', get_post_types(array('show_ui' => true)), array('label' => false, 'query_var'=>'lang'));
152
153 // defines default values for options in case this is the first installation
154 $options = get_option('polylang');
155 if (!$options) {
156 $options['browser'] = 1; // default language for the front page is set by browser preference
157 $options['rewrite'] = 0; // do not remove /language/ in permalinks
158 $options['hide_default'] = 0; // do not remove URL language information for default language
159 }
160 $options['version'] = POLYLANG_VERSION;
161 update_option('polylang', $options);
162
163 // add our rewrite rules
164 global $wp_rewrite;
165 $wp_rewrite->flush_rules();
166 }
167
168 // plugin deactivation
169 function deactivate() {
170 // delete our rewrite rules
171 remove_filter('rewrite_rules_array', array(&$this,'rewrite_rules_array' ));
172 global $wp_rewrite;
173 $wp_rewrite->flush_rules();
174 }
175
176 // some initialization
177 function init() {
178 global $wpdb;
179 $wpdb->termmeta = $wpdb->prefix . 'termmeta'; // registers the termmeta table in wpdb
180
181 // registers the language taxonomy
182 // codex: use the init action to call this function
183 register_taxonomy('language', get_post_types(array('show_ui' => true)), array(
184 'label' => false,
185 'public' => false, // avoid displaying the 'like post tags text box' in the quick edit
186 'query_var'=>'lang',
187 'update_count_callback' => '_update_post_term_count'));
188
189 // optionaly removes 'language' in permalinks so that we get http://www.myblog/en/ instead of http://www.myblog/language/en/
190 // the simple line of code is inspired by the WP No Category Base plugin: http://wordpresssupplies.com/wordpress-plugins/no-category-base/
191 global $wp_rewrite;
192 $options = get_option('polylang');
193 if ($options['rewrite'] && $wp_rewrite->extra_permastructs)
194 $wp_rewrite->extra_permastructs['language'][0] = '%language%';
195
196 $this->default_locale = get_locale(); // save the default locale before we start any language manipulation
197 load_plugin_textdomain('polylang', false, basename(POLYLANG_DIR).'/languages'); // plugin i18n
198 }
199
200 // registers our widget
201 function widgets_init() {
202 register_widget('Polylang_Widget');
203 }
204
205 // rewrites rules if pretty permalinks are used
206 function rewrite_rules_array($rules) {
207 $options = get_option('polylang');
208 $newrules = array();
209 $listlanguages = $this->get_languages_list();
210
211 // modifies the rules created by WordPress when '/language/' is removed in permalinks
212 if ($options['rewrite']) {
213 foreach ($listlanguages as $language) {
214 $slug = $options['default_lang'] == $language->slug && $options['hide_default'] ? '' : $language->slug . '/';
215 $newrules[$slug.'feed/(feed|rdf|rss|rss2|atom)/?$'] = 'index.php?lang='.$language->slug.'&feed=$matches[1]';
216 $newrules[$slug.'(feed|rdf|rss|rss2|atom)/?$'] = 'index.php?lang='.$language->slug.'&feed=$matches[1]';
217 $newrules[$slug.'page/?([0-9]{1,})/?$'] = 'index.php?lang='.$language->slug.'&paged=$matches[1]';
218 if ($slug)
219 $newrules[$slug.'?$'] = 'index.php?lang='.$language->slug;
220 }
221 unset($rules['([^/]+)/feed/(feed|rdf|rss|rss2|atom)/?$']); // => index.php?lang=$matches[1]&feed=$matches[2]
222 unset($rules['([^/]+)/(feed|rdf|rss|rss2|atom)/?$']); // => index.php?lang=$matches[1]&feed=$matches[2]
223 unset($rules['([^/]+)/page/?([0-9]{1,})/?$']); // => index.php?lang=$matches[1]&paged=$matches[2]
224 unset($rules['([^/]+)/?$']); // => index.php?lang=$matches[1]
225 }
226
227 $base = $options['rewrite'] ? '' : 'language/';
228
229 // rewrite rules for comments feed filtered by language
230 foreach ($listlanguages as $language) {
231 $slug = $options['default_lang'] == $language->slug && $options['hide_default'] ? '' : $base.$language->slug . '/';
232 $newrules[$slug.'comments/feed/(feed|rdf|rss|rss2|atom)/?$'] = 'index.php?lang='.$language->slug.'&feed=$matches[1]&withcomments=1';
233 $newrules[$slug.'comments/(feed|rdf|rss|rss2|atom)/?$'] = 'index.php?lang='.$language->slug.'&feed=$matches[1]&withcomments=1';
234 }
235 unset($rules['comments/feed/(feed|rdf|rss|rss2|atom)/?$']); // => index.php?&feed=$matches[1]&withcomments=1
236 unset($rules['comments/(feed|rdf|rss|rss2|atom)/?$']); // => index.php?&feed=$matches[1]&withcomments=1
237
238 // rewrite rules for archives filtered by language
239 foreach ($rules as $key => $rule) {
240 $is_archive = strpos($rule, 'author_name=') || strpos($rule, 'year=') && !(
241 strpos($rule, 'p=') ||
242 strpos($rule, 'name=') ||
243 strpos($rule, 'page=') ||
244 strpos($rule, 'cpage=') );
245
246 if ($is_archive) {
247 foreach ($listlanguages as $language) {
248 $slug = $options['default_lang'] == $language->slug && $options['hide_default'] ? '' : $base.$language->slug . '/';
249 $newrules[$slug.$key] = str_replace('?', '?lang='.$language->slug.'&', $rule);
250 }
251 unset($rules[$key]); // now useless
252 }
253 }
254 return $newrules + $rules;
255 }
256
257 // filters categories and post tags by language when needed (both in admin panels and frontend)
258 function terms_clauses($clauses, $taxonomies, $args) {
259 // does nothing except on taxonomies which have show_ui set to 1 (includes category and post_tags)
260 foreach ($taxonomies as $tax) {
261 if(!get_taxonomy($tax)->show_ui)
262 return $clauses;
263 }
264
265 if (is_admin()) {
266 $screen = get_current_screen(); // since WP 3.1 // FIXME breaks a user's admin -> use pagenow instead ?
267
268 // NOTE: $screen is not defined in the tag cloud of the Edit Post panel ($pagenow set to admin-ajax.php)
269 if (isset($screen))
270 // does nothing in the Categories, Post tags, Languages and Posts* admin panels
271 if ($screen->base == 'edit-tags' || $screen->base == 'toplevel_page_mlang' || $screen->base == 'edit')
272 return $clauses;
273
274 // *FIXME I want all categories in the dropdown list and only the ones in the right language in the inline edit
275 // It seems that I need javascript to get the post_id as inline edit data are manipulated in inline-edit-post.js
276
277 $this->curlang = $this->get_current_language();
278 }
279
280 // adds our clauses to filter by current language
281 if ($this->curlang) {
282 global $wpdb;
283 $clauses['join'] .= " INNER JOIN $wpdb->termmeta AS tm ON t.term_id = tm.term_id";
284 $clauses['where'] .= $wpdb->prepare(" AND tm.meta_key = '_language' AND tm.meta_value = %d", $this->curlang->term_id);
285 }
286 return $clauses;
287 }
288
289 // returns the language according to browser preference or the default language
290 function get_preferred_language() {
291 // check first is the user was already browsing this site
292 if (isset($_COOKIE['wordpress_polylang']))
293 return $this->get_language($_COOKIE['wordpress_polylang']);
294
295 // sets the browsing language according to the browser preferences
296 // code adapted from http://www.thefutureoftheweb.com/blog/use-accept-language-header
297 $options = get_option('polylang');
298 if ($options['browser']) {
299 $accept_langs = array();
300
301 if (isset($_SERVER['HTTP_ACCEPT_LANGUAGE'])) {
302 // break up string into pieces (languages and q factors)
303 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);
304
305 if (count($lang_parse[1])) {
306 // NOTE: array_combine => PHP5
307 $accept_langs = array_combine($lang_parse[1], $lang_parse[4]); // create a list like "en" => 0.8
308 // set default to 1 for any without q factor
309 foreach ($accept_langs as $accept_lang => $val) {
310 if ($val === '') $accept_langs[$accept_lang] = 1;
311 }
312 arsort($accept_langs, SORT_NUMERIC); // sort list based on value
313 }
314 }
315
316 // looks through sorted list and use first one that matches our language list
317 $listlanguages = $this->get_languages_list(true); // hides languages with no post
318 foreach ($accept_langs as $accept_lang => $val) {
319 foreach ($listlanguages as $language) {
320 if (strpos($accept_lang, $language->slug) === 0 && !isset($pref_lang)) {
321 $pref_lang = $language;
322 }
323 }
324 }
325 } // options['browser']
326
327 // either there is no preference in the browser or preferences does not match our language list or it is requested not to use the browser preference
328 if (!isset($pref_lang))
329 $pref_lang = $this->get_language($options['default_lang']);
330
331 return $pref_lang;
332 }
333
334 // returns the current language
335 function get_current_language() {
336 global $post_ID;
337
338 if($this->curlang)
339 return $this->curlang;
340
341 // no language set for 404 and attachment
342 if (is_404() || is_attachment())
343 return $this->get_preferred_language();
344
345 if (is_admin()) {
346 if (isset($post_ID))
347 $lang = $this->get_post_language($post_ID);
348
349 if (isset($_POST['action']) && $_POST['action'] == 'get-tagcloud') {
350 // to get the language in the Post Tags metabox in the Edit Post screen (as $post_ID not defined)
351 $qvars = $this->get_referer_vars();
352 if (isset($qvars['new_lang']))
353 $lang= $this->get_language($qvars['new_lang']); // post has been created with 'add new' (translation)
354 elseif (isset($qvars['post']))
355 $lang = $this->get_post_language($qvars['post']); // edit post
356 }
357
358 // the post is created with the 'add new' (translation) link
359 if (isset($_GET['new_lang']))
360 $lang = $this->get_language($_GET['new_lang']);
361 }
362 elseif ($var = get_query_var('lang'))
363 $lang = $this->get_language($var);
364
365 elseif (is_single() || is_page() && $var = get_queried_object_id())
366 $lang = $this->get_post_language($var);
367
368 elseif ($var = get_query_var('cat'))
369 $lang = $this->get_term_language($var);
370
371 elseif ($var = get_query_var('tag_id'))
372 $lang = $this->get_term_language($var);
373
374 return (isset($lang)) ? $lang : NULL;
375 }
376
377 // returns the locale based on current language
378 function get_locale($locale) {
379 if ($this->curlang)
380 $locale = $this->curlang->description;
381 return $locale;
382 }
383
384 // saves all text domains in a table for later usage
385 function mofile($bool, $domain, $mofile) {
386 $this->list_textdomains[] = array ('mo' => $mofile, 'domain' => $domain);
387 return true; // prevents WP loading text domains as we will load them all later
388 }
389
390 // NOTE: I believe there are two ways for a plugin to force the WP language
391 // as done by xili_language and here: load text domains and reinitialize wp_locale with the action 'wp'
392 // as done by qtranslate: define the locale with the action 'plugins_loaded', but in this case, the language must be specified in the url.
393 function load_textdomains() {
394 // sets the current language and set a cookie to remember it
395 if ($this->curlang = $this->get_current_language())
396 setcookie('wordpress_polylang', $this->curlang->slug, time() + 31536000 /* 1 year */, COOKIEPATH, COOKIE_DOMAIN);
397
398 // our override_load_textdomain has done its job. let's remove it before calling load_textdomain
399 remove_filter('override_load_textdomain', array(&$this, 'mofile'));
400
401 // now we can load text domains with the right language
402 $new_locale = get_locale();
403 foreach ($this->list_textdomains as $textdomain)
404 load_textdomain( $textdomain['domain'], str_replace($this->default_locale, $new_locale, $textdomain['mo']));
405
406 global $wp_locale;
407 $wp_locale->init(); // reinitializes wp_locale for weekdays and months
408 }
409
410 // filters posts according to the language
411 function pre_get_posts($query) {
412 $options = get_option('polylang');
413 $qvars = $query->query_vars;
414
415 // detect our exclude pages query and returns to avoid conflicts
416 // this test should be sufficient
417 if (isset($qvars['tax_query'][0]) && isset($qvars['tax_query'][0]['taxonomy']) && isset($qvars['tax_query'][0]['operator']))
418 return;
419
420 if (empty($query->query)) {
421 if ( $options['hide_default'] && isset($_COOKIE['wordpress_polylang']) )
422 $this->curlang = $this->get_language($options['default_lang']);
423 else
424 $this->curlang = $this->get_preferred_language(); // sets the language according to browser preference or default language
425
426 if ($options['default_lang'] == $this->curlang->slug && $options['hide_default']) {
427 if (($post_id = get_option('page_on_front')) && $link_id = $this->get_post($post_id, $this->curlang))
428 $query->set('page_id', $link_id);
429 else
430 $query->set('lang', $this->curlang->slug);
431 }
432 else {
433 if (($post_id = get_option('page_on_front')) && $link_id = $this->get_post($post_id, $this->curlang))
434 $url = _get_page_link($link_id);
435 else
436 $url = home_url('?lang='.$this->curlang->slug);
437
438 wp_redirect($url);
439 exit;
440 }
441 }
442
443 // FIXME to generalize as I probably forget things
444 // sets the language in case we hide the default language
445 if ( $options['hide_default'] && !isset($qvars['lang']) && (
446 (count($query->query) == 1 && isset($qvars['paged']) && $qvars['paged']) ||
447 isset($qvars['m']) && $qvars['m'] ||
448 isset($qvars['feed']) && $qvars['feed'] ||
449 isset($qvars['author']) && $qvars['author']))
450 $query->set('lang', $options['default_lang']);
451
452 // filters recent posts to the current language
453 // FIXME if $qvars['post_type'] == 'nav_menu_item', setting lang breaks custom menus.
454 // since to get nav_menu_items, get_post is used and no language is linked to nav_menu_items
455 if ($query->is_home && $this->curlang && !isset($qvars['post_type']))
456 $query->set('lang', $this->curlang->slug);
457
458 // remove pages query when the language is set unless we do a search
459 // FIXME is only search broken by this ?
460 if (isset($qvars['lang']) && $qvars['lang'] && !isset($qvars['post_type']) && !is_search())
461 $query->set('post_type', 'post');
462
463 // unset the is_archive flag for language pages to prevent loading the archive template
464 // keep archive flag for comment feed otherwise the language filter does not work
465 if (isset($qvars['lang']) && $qvars['lang'] && !is_post_type_archive() && !is_date() && !is_author() && !is_category() && !is_tag() && !is_comment_feed())
466 $query->is_archive = false;
467
468 // unset the is_tax flag for authors pages
469 // FIXME Probably I should do this for other cases
470 if (isset($qvars['lang']) && $qvars['lang'] && is_author())
471 $query->is_tax = false;
472 }
473
474 function wp_head() {
475 // modifies the canonical link to the homepage
476 if (is_singular()) {
477 global $wp_the_query;
478 if ($id = $wp_the_query->get_queried_object_id()) {
479 if (is_page())
480 $link = _get_page_link($id); // ignores page_on_front unlike get_permalink
481 else
482 $link = get_permalink ($id);
483 echo "<link rel='canonical' href='$link' />\n";
484 }
485 }
486
487 // outputs references to translated pages (if exists) in the html head section
488 foreach ($this->get_languages_list() as $language) {
489 if ($language->slug != $this->curlang->slug && $url = $this->get_translation_url($language))
490 printf("<link hreflang='%s' href='%s' rel='alternate' />\n", esc_attr($language->slug), $url);
491 }
492 }
493
494 // prevents redirection of the homepage
495 function redirect_canonical($redirect_url, $requested_url) {
496 if($requested_url == _get_page_link(get_option('page_on_front')))
497 return false;
498 return $redirect_url;
499 }
500
501 // adds some javascript workaround knowing it's not perfect...
502 function wp_print_footer_scripts() {
503 if ($this->curlang) {
504 $js = '';
505
506 // modifies the search form since filtering get_search_form won't work if the template uses searchform.php
507 // don't use directly e[0] just in case there is somewhere else an element named 's'
508 // check before if the hidden input has not already been introduced by get_search_form
509 if (!$this->search_form_filter) {
510 $lang = esc_js($this->curlang->slug);
511 $js .= "e = document.getElementsByName('s');
512 for (i = 0; i < e.length; i++) {
513 if (e[i] == '[object HTMLInputElement]') {
514 var ih = document.createElement('input');
515 ih.type = 'hidden';
516 ih.name = 'lang';
517 ih.value = '$lang';
518 e[i].parentNode.appendChild(ih);
519 }
520 }";
521 }
522
523 if ($js)
524 echo "<script type='text/javascript'>" .$js. "</script>";
525 }
526 }
527
528 // adds the language information in the search form
529 // does not work if searchform.php is used
530 function get_search_form($form) {
531 if ($form) {
532 $this->search_form_filter = true;
533 $form = str_replace('</form>', '<input type="hidden" name="lang" value="'.esc_attr($this->curlang->slug).'" /></form>', $form);
534 }
535 return $form;
536 }
537
538 // excludes pages which are not in the current language for wp_list_pages
539 // useful for the pages widget
540 function wp_list_pages_excludes($pages) {
541 if (isset($this->curlang)) {
542 $q = array(
543 'numberposts'=>-1,
544 'post_type' => 'page',
545 'fields' => 'ids',
546 'tax_query' => array(array(
547 'taxonomy'=>'language',
548 'fields' => 'id',
549 'terms'=>$this->curlang->term_id,
550 'operator'=>'NOT IN'
551 ))
552 );
553 $pages = array_merge($pages, get_posts($q));
554 }
555 return $pages;
556 }
557
558 // filters the comments according to the current language mainly for the recent comments widget
559 function comments_clauses($clauses, $comment_query) {
560 // first test if wp_posts.ID already available in the query
561 if ($this->curlang && strpos($clauses['join'], '.ID')) {
562 global $wpdb;
563 $clauses['join'] .= " INNER JOIN $wpdb->term_relationships AS tr ON tr.object_id = ID";
564 $clauses['join'] .= " INNER JOIN $wpdb->term_taxonomy AS tt ON tr.term_taxonomy_id = tt.term_taxonomy_id";
565 $clauses['where'] .= $wpdb->prepare(" AND tt.term_id = %d", $this->curlang->term_id);
566 }
567 return $clauses;
568 }
569
570 // Modifies the feed link to add the language parameter
571 function feed_link($url, $feed) {
572 global $wp_rewrite;
573 $options = get_option('polylang');
574
575 if ($this->curlang) {
576 if ($wp_rewrite->using_permalinks()) {
577 $home = get_option('home');
578 $base = $options['rewrite'] ? '/' : '/language/';
579 $slug = $options['default_lang'] == $this->curlang->slug && $options['hide_default'] ? '' : $base.$this->curlang->slug;
580 $url = esc_url(str_replace($home, $home.$slug, $url));
581 }
582 elseif ($feed)
583 $url = esc_url(home_url('?lang='.$this->curlang->slug.'&feed='.$feed));
584
585 }
586 return $url;
587 }
588
589 // modifies the sql request for wp_get_archives an get_adjacent_post to filter by the current language
590 function posts_join($sql) {
591 if ($this->curlang) {
592 global $wpdb;
593 $sql .= " INNER JOIN $wpdb->term_relationships ON object_id = ID";
594 }
595 return $sql;
596 }
597
598 // modifies the sql request for wp_get_archives and get_adjacent_post to filter by the current language
599 function posts_where($sql) {
600 if ($this->curlang) {
601 global $wpdb;
602 $sql .= $wpdb->prepare(" AND term_taxonomy_id = %d", $this->curlang->term_id);
603 }
604 return $sql;
605 }
606
607 // modifies the author and date links to add the language parameter
608 function archive_link($link) {
609 if ($this->curlang) {
610 global $wp_rewrite;
611 $options = get_option('polylang');
612 $home = get_option('home');
613
614 if ($wp_rewrite->using_permalinks()) {
615 $base = $options['rewrite'] ? '/' : '/language/';
616 $slug = $options['default_lang'] == $this->curlang->slug && $options['hide_default'] ? '' : $base.$this->curlang->slug;
617 $link = esc_url(str_replace($home, $home.$slug, $link));
618 }
619 else
620 $link = esc_url(str_replace($home.'/?', $home.'/?lang='.$this->curlang->slug.'&amp;', $link));
621 }
622 return $link;
623 }
624
625 // modifies the calendar to filter posts by language
626 function get_calendar() {
627 require_once(INC_DIR.'/calendar.php');
628 return $calendar_output;
629 }
630
631 // returns the url of the translation (if exists) of the current page
632 function get_translation_url($language) {
633 global $wp_query, $wp_rewrite;
634 $qvars = $wp_query->query;
635 $options = get_option('polylang');
636 $hide = $options['default_lang'] == $language->slug && $options['hide_default'];
637
638 // is_single is set to 1 for attachment but no language is set
639 if (is_single() && !is_attachment() && $id = $this->get_post(get_the_ID(), $language))
640 $url = get_permalink($id);
641
642 elseif (is_page() && $id = $this->get_post(get_the_ID(), $language))
643 $url = $hide && $id == $this->get_post(get_option('page_on_front'), $language) ?
644 get_option('home') :
645 _get_page_link($id);
646
647 elseif ( is_category() || is_tag() ) {
648 $term = get_queried_object();
649 $lang = $this->get_term_language($term->term_id);
650 $taxonomy = $term->taxonomy;
651
652 if ($language->slug == $lang->slug)
653 $url = get_term_link($term, $taxonomy); // self link
654 elseif ($link_id = $this->get_translated_term($term->term_id, $language))
655 $url = get_term_link(get_term($link_id, $taxonomy), $taxonomy);
656 }
657
658 // don't test if there are existing translations before creating the url as it would be very expensive in sql queries
659 elseif(is_archive()) {
660 if ($wp_rewrite->using_permalinks()) {
661 $base = $options['rewrite'] ? '/' : '/language/';
662 $base = $hide ? '' : $base.$language->slug;
663 $base = get_option('home').$base.'/';
664
665 if (is_author())
666 $url = esc_url($base.'author/'.$qvars['author_name'].'/');
667
668 if (is_year())
669 $url = esc_url($base.$qvars['year'].'/');
670
671 if (is_month())
672 $url = esc_url($base.$qvars['year'].'/'.$qvars['monthnum'].'/');
673
674 if (is_day())
675 $url = esc_url($base.$qvars['year'].'/'.$qvars['monthnum'].'/'.$qvars['day'].'/');
676 }
677 else
678 $url = $hide ? remove_query_arg('lang') : add_query_arg('lang', $language->slug);
679 }
680
681 elseif (is_home() || is_tax('language') )
682 $url = $hide ? get_option('home') : get_term_link($language, 'language');
683
684 return isset($url) ? $url : null;
685 }
686
687 // filters the nav menus according to the current language
688 function wp_nav_menu_args($args) {
689 if (!$args['menu'] && $args['theme_location'] && $this->curlang) {
690 $menu_lang = get_option('polylang_nav_menus');
691 $args['menu'] = $menu_lang[$args['theme_location']][$this->curlang->slug];
692 }
693 return $args;
694 }
695
696 // filters the widgets according to the current language
697 function widget_display_callback($instance, $widget, $args) {
698 $widget_lang = get_option('polylang_widgets');
699 // don't display if a language filter is set and this is not the current one
700 if (isset($this->curlang) && isset($widget_lang[$widget->id]) && $widget_lang[$widget->id] && $widget_lang[$widget->id] != $this->curlang->slug)
701 return false;
702
703 return $instance;
704 }
705
706 // filters the home url to get the right language
707 function home_url($url) {
708 if ( !(did_action('template_redirect') && rtrim($url,'/') == rtrim(get_option('home'),'/') && $this->curlang) )
709 return $url;
710
711 // don't like this but at least for WP_Widget_Categories::widget, it seems to be the only solution
712 // FIXME are there other exceptions ?
713 foreach (debug_backtrace() as $trace) {
714 $exceptions = $trace['function'] == 'get_pagenum_link' ||
715 $trace['function'] == 'get_author_posts_url' ||
716 ($trace['function'] == 'widget' && $trace['class'] == 'WP_Widget_Categories');
717 if ($exceptions)
718 return $url;
719 }
720
721 return $this->get_home_url($this->curlang);
722 }
723
724 // returns the home url in the right language
725 function get_home_url($language) {
726 $options = get_option('polylang');
727 if ($options['default_lang'] == $language->slug && $options['hide_default'])
728 return trailingslashit(get_option('home'));
729
730 // a static page is used as front page
731 if (($post_id = get_option('page_on_front')) && $id = $this->get_post($post_id, $language))
732 $url = _get_page_link($id);
733
734 return isset($url) ? $url : get_term_link($language, 'language');
735 }
736
737 // adds 'lang_url' as possible value to the 'show' parameter of bloginfo to display the home url in the correct language
738 // FIXME not tested
739 function bloginfo_url($output, $show) {
740 if ($show == 'lang_url' && $this->curlang) {
741 $url = $this->get_home_url($this->curlang);
742 $output = isset($url) ? $url : get_option('home');
743 }
744 return $output;
745 }
746
747 // Template tag: Displays links to the current page in other languages
748 // Usage: do_action('the_languages');
749 function the_languages($args = '') {
750 $defaults = array(
751 'dropdown' => 0, // display as list and not as dropdown
752 'show_names' => 1, // show language names
753 'show_flags' => 0, // don't show flags
754 'hide_if_empty' => 1 // hides languages with no posts (or pages)
755 );
756
757 extract(wp_parse_args($args, $defaults));
758
759 $listlanguages = $this->get_languages_list($hide_if_empty);
760 $output = $dropdown ? '<select name="lang_choice" id="lang_choice">' : "<ul>\n";
761 foreach ($listlanguages as $language) {
762 $url = $this->get_translation_url($language);
763 $url = isset($url) ? $url : $this->get_home_url($language); // if the page is not translated, link to the home page
764
765 $class = 'lang-item lang-item-'.esc_attr($language->term_id);
766 if ($language->slug == $this->curlang->slug)
767 $class .= ' current-lang';
768
769 $flag = $show_flags &&
770 file_exists(POLYLANG_DIR.($file = '/local_flags/'.$language->description.'.png')) ||
771 file_exists(POLYLANG_DIR.($file = '/flags/'.$language->description.'.png')) ?
772 '<img src="'.WP_PLUGIN_URL.'/polylang'.$file.'" alt="'.$language->name.'" />' : '';
773
774 $name = $show_names || !$show_flags ? esc_attr($language->name) : '';
775
776 $output .= $dropdown ?
777 sprintf(
778 "<option value='%s'%s>%s</option>\n",
779 esc_attr($language->slug),
780 $language->slug == $this->curlang->slug ? ' selected="selected"' : '',
781 $name // FIXME flag does not work for the dropdown list
782 ) :
783 '<li class="'.$class.'"><a href="'.$url.'">'.($show_flags && $show_names ? $flag.'&nbsp;'.$name : $flag.$name)."</a></li>\n";
784 }
785 $output .= $dropdown ? '</select>' : "</ul>\n";
786 echo $output;
787 }
788
789 } // class Polylang
790
791 if (class_exists("Polylang"))
792 new Polylang();
793
794 ?>
795