PluginProbe
Polylang / trunk
Polylang vtrunk
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 / src / frontend / frontend-filters-links.php

frontend-filters-links.php in Polylang trunk, at src/frontend/frontend-filters-links.php

370 lines 11.3 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2 /**
3 * @package Polylang
4 */
5
6 /**
7 * Manages links filters on frontend
8 *
9 * @since 1.8
10 */
11 class PLL_Frontend_Filters_Links extends PLL_Filters_Links {
12
13 /**
14 * @var PLL_Frontend_Links|null
15 */
16 public $links;
17
18 /**
19 * Our internal non persistent cache object
20 *
21 * @var PLL_Cache<string>
22 */
23 public $cache;
24
25 /**
26 * Stores a list of files and functions that home_url() must not filter.
27 *
28 * @var array
29 */
30 private $black_list = array();
31
32 /**
33 * Stores a list of files and functions that home_url() must filter.
34 *
35 * @var array
36 */
37 private $white_list = array();
38
39 /**
40 * Constructor
41 * Adds filters once the language is defined
42 * Low priority on links filters to come after any other modification
43 *
44 * @since 1.8
45 *
46 * @param PLL_Frontend $polylang The Polylang object.
47 */
48 public function __construct( &$polylang ) {
49 parent::__construct( $polylang );
50
51 $this->curlang = &$polylang->curlang;
52 $this->cache = new PLL_Cache();
53
54 // Rewrites author and date links to filter them by language
55 foreach ( array( 'feed_link', 'author_link', 'search_link', 'year_link', 'month_link', 'day_link' ) as $filter ) {
56 add_filter( $filter, array( $this, 'archive_link' ), 20 );
57 }
58
59 // Meta in the html head section
60 add_action( 'wp_head', array( $this, 'wp_head' ), 1 );
61
62 // Modifies the home url
63 if ( pll_get_constant( 'PLL_FILTER_HOME_URL', true ) ) {
64 add_filter( 'home_url', array( $this, 'home_url' ), 10, 2 );
65 }
66
67 if ( $this->options['force_lang'] > 1 ) {
68 // Rewrites next and previous post links when not automatically done by WordPress
69 add_filter( 'get_pagenum_link', array( $this, 'archive_link' ), 20 );
70
71 add_filter( 'get_shortlink', array( $this, 'shortlink' ), 20, 2 );
72
73 // Rewrites ajax url
74 add_filter( 'admin_url', array( $this, 'admin_url' ), 10, 2 );
75 }
76
77 add_filter( 'oembed_endpoint_url', array( $this, 'add_current_language_url_query' ) );
78 }
79
80 /**
81 * Modifies the author and date links to add the language parameter (as well as feed link).
82 *
83 * @since 0.4
84 *
85 * @param string $link The permalink to the archive.
86 * @return string The modified link.
87 */
88 public function archive_link( $link ) {
89 return $this->links_model->switch_language_in_link( $link, $this->curlang );
90 }
91
92 /**
93 * Modifies page links
94 * and caches the result
95 *
96 * @since 1.7
97 *
98 * @param string $link The page link.
99 * @param int $post_id The post ID.
100 * @return string The modified page link.
101 */
102 public function _get_page_link( $link, $post_id ) {
103 $cache_key = "post:{$post_id}:{$link}";
104 if ( false === $_link = $this->cache->get( $cache_key ) ) {
105 $_link = parent::_get_page_link( $link, $post_id );
106 $this->cache->set( $cache_key, $_link );
107 }
108 return $_link;
109 }
110
111 /**
112 * Modifies attachment links
113 * and caches the result
114 *
115 * @since 1.6.2
116 *
117 * @param string $link The attachment link.
118 * @param int $post_id The attachment post ID.
119 * @return string The modified attachment link.
120 */
121 public function attachment_link( $link, $post_id ) {
122 $cache_key = "post:{$post_id}:{$link}";
123 if ( false === $_link = $this->cache->get( $cache_key ) ) {
124 $_link = parent::attachment_link( $link, $post_id );
125 $this->cache->set( $cache_key, $_link );
126 }
127 return $_link;
128 }
129
130 /**
131 * Modifies custom posts links
132 * and caches the result.
133 *
134 * @since 1.6
135 *
136 * @param string $link The post link.
137 * @param WP_Post $post The post object.
138 * @return string The modified post link.
139 */
140 public function post_type_link( $link, $post ) {
141 $cache_key = "post:{$post->ID}:{$link}";
142 if ( false === $_link = $this->cache->get( $cache_key ) ) {
143 $_link = parent::post_type_link( $link, $post );
144 $this->cache->set( $cache_key, $_link );
145 }
146 return $_link;
147 }
148
149 /**
150 * Modifies filtered taxonomies ( post format like ) and translated taxonomies links
151 * and caches the result.
152 *
153 * @since 0.7
154 *
155 * @param string $link The term link.
156 * @param WP_Term $term The term object.
157 * @param string $tax The taxonomy name.
158 * @return string The modified link.
159 */
160 public function term_link( $link, $term, $tax ) {
161 $cache_key = "term:{$term->term_id}:{$link}";
162 if ( false === $_link = $this->cache->get( $cache_key ) ) {
163 if ( in_array( $tax, $this->model->get_filtered_taxonomies() ) ) {
164 $_link = $this->links_model->switch_language_in_link( $link, $this->curlang );
165
166 /** This filter is documented in src/filters-links.php */
167 $_link = apply_filters( 'pll_term_link', $_link, $this->curlang, $term );
168 }
169
170 else {
171 $_link = parent::term_link( $link, $term, $tax );
172 }
173 $this->cache->set( $cache_key, $_link );
174 }
175 return $_link;
176 }
177
178 /**
179 * Modifies the post short link when using one domain or subdomain per language.
180 *
181 * @since 2.6.9
182 *
183 * @param string $link Post permalink.
184 * @param int $post_id Post id.
185 * @return string Post permalink with the correct domain.
186 */
187 public function shortlink( $link, $post_id ) {
188 $post_type = get_post_type( $post_id );
189 return $this->model->is_translated_post_type( $post_type ) ? $this->links_model->switch_language_in_link( $link, $this->model->post->get_language( $post_id ) ) : $link;
190 }
191
192 /**
193 * Outputs references to translated pages ( if exists ) in the html head section
194 *
195 * @since 0.1
196 *
197 * @return void
198 */
199 public function wp_head() {
200 // Don't output anything on paged archives: see https://wordpress.org/support/topic/hreflang-on-page2
201 // Don't output anything on paged pages and paged posts
202 if ( is_paged() || ( is_singular() && ( $page = get_query_var( 'page' ) ) && $page > 1 ) ) {
203 return;
204 }
205
206 $urls = array();
207
208 // Google recommends to include self link https://support.google.com/webmasters/answer/189077?hl=en
209 foreach ( $this->model->get_languages_list() as $language ) {
210 if ( $url = $this->links->get_translation_url( $language ) ) {
211 $urls[ $language->get_locale( 'display' ) ] = $url;
212 }
213 }
214
215 // Outputs the section only if there are translations ( $urls always contains self link )
216 if ( ! empty( $urls ) && count( $urls ) > 1 ) {
217 $languages = array();
218 $hreflangs = array();
219
220 // Prepare the list of languages to remove the country code
221 foreach ( array_keys( $urls ) as $locale ) {
222 $split = explode( '-', $locale );
223 $languages[ $locale ] = reset( $split );
224 }
225
226 $count = array_count_values( $languages );
227
228 foreach ( $urls as $locale => $url ) {
229 $lang = $count[ $languages[ $locale ] ] > 1 ? $locale : $languages[ $locale ]; // Output the country code only when necessary
230 $hreflangs[ $lang ] = $url;
231 }
232
233 // Adds the site root url when the default language code is not hidden
234 // See https://wordpress.org/support/topic/implementation-of-hreflangx-default
235 if ( is_front_page() && ! $this->options['hide_default'] && $this->options['force_lang'] < 3 ) {
236 $hreflangs['x-default'] = home_url( '/' );
237 }
238
239 /**
240 * Filters the list of rel hreflang attributes
241 *
242 * @since 2.1
243 *
244 * @param array $hreflangs Array of urls with language codes as keys
245 */
246 $hreflangs = apply_filters( 'pll_rel_hreflang_attributes', $hreflangs );
247
248 foreach ( $hreflangs as $lang => $url ) {
249 printf( '<link rel="alternate" href="%s" hreflang="%s" />' . "\n", esc_url( $url ), esc_attr( $lang ) );
250 }
251 }
252 }
253
254 /**
255 * Filters the home url to get the right language.
256 *
257 * @since 0.4
258 *
259 * @param string $url The home URL including scheme and path.
260 * @param string $path Path relative to the home URL.
261 * @return string
262 */
263 public function home_url( $url, $path ) {
264 if ( ! ( did_action( 'template_redirect' ) || did_action( 'login_init' ) ) || rtrim( $url, '/' ) != $this->links_model->home ) {
265 return $url;
266 }
267
268 // We *want* to filter the home url in these cases
269 if ( empty( $this->white_list ) ) {
270 // On Windows get_theme_root() mixes / and \
271 // We want only \ for the comparison with debug_backtrace
272 $theme_root = get_theme_root();
273 $theme_root = ( false === strpos( $theme_root, '\\' ) ) ? $theme_root : str_replace( '/', '\\', $theme_root );
274
275 $white_list = array(
276 array( 'file' => $theme_root ),
277 array( 'function' => 'wp_nav_menu' ),
278 array( 'function' => 'login_footer' ),
279 array( 'function' => 'get_custom_logo' ),
280 array( 'function' => 'render_block_core_site_title' ),
281 array( 'function' => 'render_block_core_home_link' ),
282 );
283
284 if ( 3 === $this->options['force_lang'] ) {
285 $white_list[] = array( 'function' => 'redirect_canonical' );
286 }
287
288 /**
289 * Filters the white list of the Polylang 'home_url' filter.
290 *
291 * @since 1.1.2
292 *
293 * @param string[][] $white_list An array of arrays each of them having a 'file' key
294 * and/or a 'function' key to decide which functions in
295 * which files using home_url() calls must be filtered.
296 */
297 $this->white_list = apply_filters( 'pll_home_url_white_list', $white_list );
298 }
299
300 // We don't want to filter the home url in these cases.
301 if ( empty( $this->black_list ) ) {
302 $black_list = array(
303 array( 'file' => 'searchform.php' ), // Since WP 3.6 searchform.php is passed through get_search_form.
304 array( 'function' => 'get_search_form' ),
305 );
306
307 /**
308 * Filters the black list of the Polylang 'home_url' filter.
309 *
310 * @since 1.1.2
311 *
312 * @param string[][] $black_list An array of arrays each of them having a 'file' key
313 * and/or a 'function' key to decide which functions in
314 * which files using home_url() calls must be filtered.
315 */
316 $this->black_list = apply_filters( 'pll_home_url_black_list', $black_list );
317 }
318
319 $traces = debug_backtrace( DEBUG_BACKTRACE_IGNORE_ARGS ); // phpcs:ignore WordPress.PHP.DevelopmentFunctions
320 unset( $traces[0], $traces[1] ); // We don't need the last 2 calls: this function + call_user_func_array (or apply_filters on PHP7+)
321
322 foreach ( $traces as $trace ) {
323 // Black list first
324 foreach ( $this->black_list as $v ) {
325 if ( ( isset( $trace['file'], $v['file'] ) && false !== strpos( $trace['file'], $v['file'] ) ) || ( ! empty( $v['function'] ) && $trace['function'] === $v['function'] ) ) {
326 return $url;
327 }
328 }
329
330 foreach ( $this->white_list as $v ) {
331 if ( ( ! empty( $v['function'] ) && $trace['function'] === $v['function'] ) ||
332 ( isset( $trace['file'], $v['file'] ) && false !== strpos( $trace['file'], $v['file'] ) && in_array( $trace['function'], array( 'home_url', 'get_home_url', 'bloginfo', 'get_bloginfo' ) ) ) ) {
333 $ok = true;
334 }
335 }
336 }
337
338 return empty( $ok ) ? $url : ( empty( $path ) ? rtrim( $this->links->get_home_url( $this->curlang ), '/' ) : $this->links->get_home_url( $this->curlang ) );
339 }
340
341 /**
342 * Rewrites the ajax url when using domains or subdomains.
343 *
344 * @since 1.5
345 *
346 * @param string $url The admin url with path evaluated by WordPress.
347 * @param string $path Path relative to the admin URL.
348 * @return string
349 */
350 public function admin_url( $url, $path ) {
351 return 'admin-ajax.php' === $path ? $this->links_model->switch_language_in_link( $url, $this->curlang ) : $url;
352 }
353
354 /**
355 * Adds the current language to URL query.
356 *
357 * @since 3.7.5
358 *
359 * @param string $url The oEmbed endpoint URL.
360 * @return string The oEmbed endpoint URL with the language.
361 */
362 public function add_current_language_url_query( $url ) {
363 if ( empty( $this->curlang ) ) {
364 return $url;
365 }
366
367 return add_query_arg( 'lang', $this->curlang->slug, $url );
368 }
369 }
370