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

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