Pastebin launched a little side project called VERYVIRAL.com, check it out ;-) Want more features on Pastebin? Sign Up, it's FREE!
Guest

Polylang sets the language in plugins_loaded

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