PLL_Frontend_Filters_Links

Manages links filters on frontend.

Defined (1)

The class is defined in the following location(s).

/frontend/frontend-filters-links.php  
  1. class PLL_Frontend_Filters_Links extends PLL_Filters_Links { 
  2. public $curlang; 
  3. public $cache; // Our internal non persistent cache object 
  4.  
  5. /** 
  6. * Constructor 
  7. * Adds filters once the language is defined 
  8. * Low priority on links filters to come after any other modification 
  9. * @since 1.8 
  10. * @param object $polylang 
  11. */ 
  12. public function __construct( &$polylang ) { 
  13. parent::__construct( $polylang ); 
  14.  
  15. $this->curlang = &$polylang->curlang; 
  16. $this->cache = new PLL_Cache(); 
  17.  
  18. // Rewrites author and date links to filter them by language 
  19. foreach ( array( 'feed_link', 'author_link', 'search_link', 'year_link', 'month_link', 'day_link' ) as $filter ) { 
  20. add_filter( $filter, array( $this, 'archive_link' ), 20 ); 
  21.  
  22. // Rewrites post types archives links to filter them by language 
  23. add_filter( 'post_type_archive_link', array( $this, 'post_type_archive_link' ), 20, 2 ); 
  24.  
  25. // Meta in the html head section 
  26. add_action( 'wp_head', array( $this, 'wp_head' ) ); 
  27.  
  28. // Modifies the home url 
  29. if ( ! defined( 'PLL_FILTER_HOME_URL' ) || PLL_FILTER_HOME_URL ) { 
  30. add_filter( 'home_url', array( $this, 'home_url' ), 10, 2 ); 
  31.  
  32. if ( $this->options['force_lang'] > 1 ) { 
  33. // Rewrites next and previous post links when not automatically done by WordPress 
  34. add_filter( 'get_pagenum_link', array( $this, 'archive_link' ), 20 ); 
  35.  
  36. // Rewrites ajax url 
  37. add_filter( 'admin_url', array( $this, 'admin_url' ), 10, 2 ); 
  38.  
  39. // Redirects to canonical url before WordPress redirect_canonical 
  40. // but after Nextgen Gallery which hacks $_SERVER['REQUEST_URI'] !!! and restores it in 'template_redirect' with priority 1 
  41. add_action( 'template_redirect', array( $this, 'check_canonical_url' ), 4 ); 
  42.  
  43. /** 
  44. * Modifies the author and date links to add the language parameter ( as well as feed link ) 
  45. * @since 0.4 
  46. * @param string $link 
  47. * @return string modified link 
  48. */ 
  49. public function archive_link( $link ) { 
  50. return $this->links_model->add_language_to_link( $link, $this->curlang ); 
  51.  
  52. /** 
  53. * Modifies the post type archive links to add the language parameter 
  54. * only if the post type is translated 
  55. * @since 1.7.6 
  56. * @param string $link 
  57. * @param string $post_type 
  58. * @return string modified link 
  59. */ 
  60. public function post_type_archive_link( $link, $post_type ) { 
  61. return $this->model->is_translated_post_type( $post_type ) && 'post' !== $post_type ? $this->links_model->add_language_to_link( $link, $this->curlang ) : $link; 
  62.  
  63. /** 
  64. * Modifies post & page links 
  65. * and caches the result 
  66. * @since 0.7 
  67. * @param string $link post link 
  68. * @param object $post post object 
  69. * @return string modified post link 
  70. */ 
  71. public function post_link( $link, $post ) { 
  72. $cache_key = 'post:' . $post->ID; 
  73. if ( false === $_link = $this->cache->get( $cache_key ) ) { 
  74. $_link = parent::post_link( $link, $post ); 
  75. $this->cache->set( $cache_key, $_link ); 
  76. return $_link; 
  77.  
  78. /** 
  79. * Modifies page links 
  80. * and caches the result 
  81. * @since 1.7 
  82. * @param string $link post link 
  83. * @param int $post_id post ID 
  84. * @return string modified post link 
  85. */ 
  86. public function _get_page_link( $link, $post_id ) { 
  87. $cache_key = 'post:' . $post_id; 
  88. if ( false === $_link = $this->cache->get( $cache_key ) ) { 
  89. $_link = parent::_get_page_link( $link, $post_id ); 
  90. $this->cache->set( $cache_key, $_link ); 
  91. return $_link; 
  92.  
  93. /** 
  94. * Modifies attachment links 
  95. * and caches the result 
  96. * @since 1.6.2 
  97. * @param string $link attachment link 
  98. * @param int $post_id attachment link 
  99. * @return string modified attachment link 
  100. */ 
  101. public function attachment_link( $link, $post_id ) { 
  102. $cache_key = 'post:' . $post_id; 
  103. if ( false === $_link = $this->cache->get( $cache_key ) ) { 
  104. $_link = parent::attachment_link( $link, $post_id ); 
  105. $this->cache->set( $cache_key, $_link ); 
  106. return $_link; 
  107.  
  108. /** 
  109. * Modifies custom posts links 
  110. * and caches the result 
  111. * @since 1.6 
  112. * @param string $link post link 
  113. * @param object $post post object 
  114. * @return string modified post link 
  115. */ 
  116. public function post_type_link( $link, $post ) { 
  117. $cache_key = 'post:' . $post->ID; 
  118. if ( false === $_link = $this->cache->get( $cache_key ) ) { 
  119. $_link = parent::post_type_link( $link, $post ); 
  120. $this->cache->set( $cache_key, $_link ); 
  121. return $_link; 
  122.  
  123. /** 
  124. * Modifies filtered taxonomies ( post format like ) and translated taxonomies links 
  125. * and caches the result 
  126. * @since 0.7 
  127. * @param string $link 
  128. * @param object $term term object 
  129. * @param string $tax taxonomy name 
  130. * @return string modified link 
  131. */ 
  132. public function term_link( $link, $term, $tax ) { 
  133. $cache_key = 'term:' . $term->term_id; 
  134. if ( false === $_link = $this->cache->get( $cache_key ) ) { 
  135. if ( in_array( $tax, $this->model->get_filtered_taxonomies() ) ) { 
  136. $_link = $this->links_model->add_language_to_link( $link, $this->curlang ); 
  137.  
  138. /** This filter is documented in include/filters-links.php */ 
  139. $_link = apply_filters( 'pll_term_link', $_link, $this->curlang, $term ); 
  140.  
  141. else { 
  142. $_link = parent::term_link( $link, $term, $tax ); 
  143. $this->cache->set( $cache_key, $_link ); 
  144. return $_link; 
  145.  
  146. /** 
  147. * Outputs references to translated pages ( if exists ) in the html head section 
  148. * @since 0.1 
  149. */ 
  150. public function wp_head() { 
  151. // Don't output anything on paged archives: see https://wordpress.org/support/topic/hreflang-on-page2 
  152. // Don't output anything on paged pages and paged posts 
  153. if ( is_paged() || ( is_singular() && ( $page = get_query_var( 'page' ) ) && $page > 1 ) ) { 
  154. return; 
  155.  
  156. // Google recommends to include self link https://support.google.com/webmasters/answer/189077?hl=en 
  157. foreach ( $this->model->get_languages_list() as $language ) { 
  158. if ( $url = $this->links->get_translation_url( $language ) ) { 
  159. $urls[ $language->get_locale( 'display' ) ] = $url; 
  160.  
  161. // Ouptputs the section only if there are translations ( $urls always contains self link ) 
  162. if ( ! empty( $urls ) && count( $urls ) > 1 ) { 
  163.  
  164. // Prepare the list of languages to remove the country code 
  165. foreach ( array_keys( $urls ) as $locale ) { 
  166. $split = explode( '-', $locale ); 
  167. $languages[ $locale ] = reset( $split ); 
  168.  
  169. $count = array_count_values( $languages ); 
  170.  
  171. foreach ( $urls as $locale => $url ) { 
  172. $lang = $count[ $languages[ $locale ] ] > 1 ? $locale : $languages[ $locale ]; // Output the country code only when necessary 
  173. $hreflangs[ $lang ] = $url; 
  174.  
  175. // Adds the site root url when the default language code is not hidden 
  176. // See https://wordpress.org/support/topic/implementation-of-hreflangx-default 
  177. if ( is_front_page() && ! $this->options['hide_default'] && $this->options['force_lang'] < 3 ) { 
  178. $hreflangs['x-default'] = home_url( '/' ); 
  179.  
  180. /** 
  181. * Filters the list of rel hreflang attributes 
  182. * @since 2.1 
  183. * @param array $hreflangs Array of urls with language codes as keys 
  184. */ 
  185. $hreflangs = apply_filters( 'pll_rel_hreflang_attributes', $hreflangs ); 
  186.  
  187. foreach ( $hreflangs as $lang => $url ) { 
  188. printf( '<link rel="alternate" href="%s" hreflang="%s" />'."\n", esc_url( $url ), esc_attr( $lang ) ); 
  189.  
  190. /** 
  191. * Filters the home url to get the right language 
  192. * @since 0.4 
  193. * @param string $url 
  194. * @param string $path 
  195. * @return string 
  196. */ 
  197. public function home_url( $url, $path ) { 
  198. if ( ! ( did_action( 'template_redirect' ) || did_action( 'login_init' ) ) || rtrim( $url, '/' ) != $this->links_model->home ) { 
  199. return $url; 
  200.  
  201. static $white_list, $black_list; // Avoid evaluating this at each function call 
  202.  
  203. // We *want* to filter the home url in these cases 
  204. if ( empty( $white_list ) ) { 
  205. // On Windows get_theme_root() mixes / and \ 
  206. // We want only \ for the comparison with debug_backtrace 
  207. $theme_root = get_theme_root(); 
  208. $theme_root = ( false === strpos( $theme_root, '\\' ) ) ? $theme_root : str_replace( '/', '\\', $theme_root ); 
  209.  
  210. /** 
  211. * Filter the white list of the Polylang 'home_url' filter 
  212. * The $args contains an array of arrays each of them having 
  213. * a 'file' key and/or a 'function' key to decide which functions in 
  214. * which files using home_url() calls must be filtered 
  215. * @since 1.1.2 
  216. * @param array $args 
  217. */ 
  218. $white_list = apply_filters( 'pll_home_url_white_list', array( 
  219. array( 'file' => $theme_root ),  
  220. array( 'function' => 'wp_nav_menu' ),  
  221. array( 'function' => 'login_footer' ),  
  222. array( 'function' => 'get_custom_logo' ),  
  223. ) ); 
  224.  
  225. // We don't want to filter the home url in these cases 
  226. if ( empty( $black_list ) ) { 
  227.  
  228. /** 
  229. * Filter the black list of the Polylang 'home_url' filter 
  230. * The $args contains an array of arrays each of them having 
  231. * a 'file' key and/or a 'function' key to decide which functions in 
  232. * which files using home_url() calls must be filtered 
  233. * @since 1.1.2 
  234. * @param array $args 
  235. */ 
  236. $black_list = apply_filters( 'pll_home_url_black_list', array( 
  237. array( 'file' => 'searchform.php' ), // Since WP 3.6 searchform.php is passed through get_search_form 
  238. array( 'function' => 'get_search_form' ),  
  239. ) ); 
  240.  
  241. $traces = version_compare( PHP_VERSION, '5.2.5', '>=' ) ? debug_backtrace( false ) : debug_backtrace(); 
  242. unset( $traces[0], $traces[1] ); // We don't need the last 2 calls: this function + call_user_func_array (or apply_filters on PHP7+) 
  243.  
  244. foreach ( $traces as $trace ) { 
  245. // Black list first 
  246. foreach ( $black_list as $v ) { 
  247. if ( ( isset( $trace['file'], $v['file'] ) && false !== strpos( $trace['file'], $v['file'] ) ) || ( isset( $trace['function'], $v['function'] ) && $trace['function'] == $v['function'] ) ) { 
  248. return $url; 
  249.  
  250. foreach ( $white_list as $v ) { 
  251. if ( ( isset( $trace['function'], $v['function'] ) && $trace['function'] == $v['function'] ) || 
  252. ( isset( $trace['file'], $v['file'] ) && false !== strpos( $trace['file'], $v['file'] ) && in_array( $trace['function'], array( 'home_url', 'get_home_url', 'bloginfo', 'get_bloginfo' ) ) ) ) { 
  253. $ok = true; 
  254.  
  255. return empty( $ok ) ? $url : ( empty( $path ) ? rtrim( $this->links->get_home_url( $this->curlang ), '/' ) : $this->links->get_home_url( $this->curlang ) ); 
  256.  
  257. /** 
  258. * Rewrites ajax url when using domains or subdomains 
  259. * @since 1.5 
  260. * @param string $url admin url with path evaluated by WordPress 
  261. * @param string $path admin path 
  262. * @return string 
  263. */ 
  264. public function admin_url( $url, $path ) { 
  265. return 'admin-ajax.php' === $path ? $this->links_model->switch_language_in_link( $url, $this->curlang ) : $url; 
  266.  
  267. /** 
  268. * If the language code is not in agreement with the language of the content 
  269. * redirects incoming links to the proper URL to avoid duplicate content 
  270. * @since 0.9.6 
  271. * @param string $requested_url optional 
  272. * @param bool $do_redirect optional, whether to perform the redirection or not 
  273. * @return string if redirect is not performed 
  274. */ 
  275. public function check_canonical_url( $requested_url = '', $do_redirect = true ) { 
  276. global $wp_query, $post, $is_IIS; 
  277.  
  278. // Don't redirect in same cases as WP 
  279. if ( is_trackback() || is_search() || is_admin() || is_preview() || is_robots() || ( $is_IIS && ! iis7_supports_permalinks() ) ) { 
  280. return; 
  281.  
  282. // Don't redirect mysite.com/?attachment_id= to mysite.com/en/?attachment_id= 
  283. if ( 1 == $this->options['force_lang'] && is_attachment() && isset( $_GET['attachment_id'] ) ) { 
  284. return; 
  285.  
  286. // If the default language code is not hidden and the static front page url contains the page name 
  287. // the customizer lands here and the code below would redirect to the list of posts 
  288. if ( is_customize_preview() ) { 
  289. return; 
  290.  
  291. if ( empty( $requested_url ) ) { 
  292. $requested_url = ( is_ssl() ? 'https://' : 'http://' ) . $_SERVER['HTTP_HOST'] . $_SERVER['REQUEST_URI']; 
  293.  
  294. if ( is_single() || is_page() ) { 
  295. if ( isset( $post->ID ) && $this->model->is_translated_post_type( $post->post_type ) ) { 
  296. $language = $this->model->post->get_language( (int) $post->ID ); 
  297.  
  298. elseif ( is_category() || is_tag() || is_tax() ) { 
  299. $obj = $wp_query->get_queried_object(); 
  300. if ( $this->model->is_translated_taxonomy( $obj->taxonomy ) ) { 
  301. $language = $this->model->term->get_language( (int) $obj->term_id ); 
  302.  
  303. elseif ( $wp_query->is_posts_page ) { 
  304. $obj = $wp_query->get_queried_object(); 
  305. $language = $this->model->post->get_language( (int) $obj->ID ); 
  306.  
  307. elseif ( is_404() && ! empty( $wp_query->query['page_id'] ) && $id = get_query_var( 'page_id' ) ) { 
  308. // Special case for page shortlinks when using subdomains or multiple domains 
  309. // Needed because redirect_canonical doesn't accept to change the domain name 
  310. $language = $this->model->post->get_language( (int) $id ); 
  311.  
  312. if ( empty( $language ) ) { 
  313. $language = $this->curlang; 
  314. $redirect_url = $requested_url; 
  315. } else { 
  316. // First get the canonical url evaluated by WP 
  317. // Workaround a WP bug wich removes the port for some urls and get it back at second call to redirect_canonical 
  318. $_redirect_url = ( ! $_redirect_url = redirect_canonical( $requested_url, false ) ) ? $requested_url : $_redirect_url; 
  319. $redirect_url = ( ! $redirect_url = redirect_canonical( $_redirect_url, false ) ) ? $_redirect_url : $redirect_url; 
  320.  
  321. // Then get the right language code in url 
  322. $redirect_url = $this->options['force_lang'] ? 
  323. $this->links_model->switch_language_in_link( $redirect_url, $language ) : 
  324. $this->links_model->remove_language_from_link( $redirect_url ); // Works only for default permalinks 
  325.  
  326. /** 
  327. * Filters the canonical url detected by Polylang 
  328. * @since 1.6 
  329. * @param bool|string $redirect_url false or the url to redirect to 
  330. * @param object $language the language detected 
  331. */ 
  332. $redirect_url = apply_filters( 'pll_check_canonical_url', $redirect_url, $language ); 
  333.  
  334. // The language is not correctly set so let's redirect to the correct url for this object 
  335. if ( $do_redirect && $redirect_url && $requested_url != $redirect_url ) { 
  336. wp_redirect( $redirect_url, 301 ); 
  337. exit; 
  338.  
  339. return $redirect_url;