PluginProbe
Polylang / 3.7.1
Polylang v3.7.1
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 / frontend / canonical.php

canonical.php in Polylang 3.7.1, at frontend/canonical.php

270 lines 7.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 canonical redirect on frontend.
8 *
9 * @since 3.3
10 */
11 class PLL_Canonical {
12 /**
13 * Stores the plugin options.
14 *
15 * @var array
16 */
17 protected $options;
18
19 /**
20 * @var PLL_Model
21 */
22 protected $model;
23
24 /**
25 * Instance of a child class of PLL_Links_Model.
26 *
27 * @var PLL_Links_Model
28 */
29 protected $links_model;
30
31 /**
32 * Current language.
33 *
34 * @var PLL_Language
35 */
36 protected $curlang;
37
38 /**
39 * Constructor.
40 *
41 * @since 3.3
42 *
43 * @param object $polylang Main Polylang object.
44 */
45 public function __construct( &$polylang ) {
46 $this->links_model = &$polylang->links_model;
47 $this->model = &$polylang->model;
48 $this->options = &$polylang->options;
49 $this->curlang = &$polylang->curlang;
50 }
51
52 /**
53 * If the language code is not in agreement with the language of the content,
54 * redirects incoming links to the proper URL to avoid duplicate content.
55 *
56 * @since 0.9.6
57 *
58 * @global WP_Query $wp_query WordPress Query object.
59 * @global bool $is_IIS
60 *
61 * @param string $requested_url Optional, defaults to requested url.
62 * @param bool $do_redirect Optional, whether to perform the redirect or not.
63 * @return string|void Returns if redirect is not performed.
64 */
65 public function check_canonical_url( $requested_url = '', $do_redirect = true ) {
66 global $wp_query;
67
68 // Don't redirect in same cases as WP.
69 if ( is_trackback() || is_search() || is_admin() || is_preview() || is_robots() || ( $GLOBALS['is_IIS'] && ! iis7_supports_permalinks() ) ) {
70 return;
71 }
72
73 // Don't redirect mysite.com/?attachment_id= to mysite.com/en/?attachment_id=.
74 if ( 1 == $this->options['force_lang'] && is_attachment() && isset( $_GET['attachment_id'] ) ) { // phpcs:ignore WordPress.Security.NonceVerification
75 return;
76 }
77
78 /*
79 * If the default language code is not hidden and the static front page url contains the page name,
80 * the customizer lands here and the code below would redirect to the list of posts.
81 */
82 if ( is_customize_preview() ) {
83 return;
84 }
85
86 if ( empty( $requested_url ) ) {
87 $requested_url = pll_get_requested_url();
88 }
89
90 if ( ( is_single() && ( ! is_attachment() || get_option( 'wp_attachment_pages_enabled' ) ) ) || ( is_page() && ! is_front_page() ) ) {
91 $post = get_post();
92 if ( $post instanceof WP_Post && $this->model->is_translated_post_type( $post->post_type ) ) {
93 $language = $this->model->post->get_language( (int) $post->ID );
94 }
95 }
96
97 if ( ! empty( $wp_query->tax_query ) ) {
98 if ( $this->model->is_translated_taxonomy( $this->get_queried_taxonomy( $wp_query->tax_query ) ) ) {
99 $term_id = $this->get_queried_term_id( $wp_query->tax_query );
100 if ( $term_id ) {
101 $language = $this->model->term->get_language( $term_id );
102 }
103 }
104 }
105
106 if ( $wp_query->is_posts_page ) {
107 $page_id = get_query_var( 'page_id' );
108 if ( ! $page_id ) {
109 $page_id = get_queried_object_id();
110 }
111 if ( $page_id && is_numeric( $page_id ) ) {
112 $language = $this->model->post->get_language( (int) $page_id );
113 }
114 }
115
116 if ( 3 === $this->options['force_lang'] ) {
117 $requested_host = wp_parse_url( $requested_url, PHP_URL_HOST );
118 foreach ( $this->options['domains'] as $lang => $domain ) {
119 $host = wp_parse_url( $domain, PHP_URL_HOST );
120 if ( $requested_host && $host && ltrim( $requested_host, 'w.' ) === ltrim( $host, 'w.' ) ) {
121 $language = $this->model->get_language( $lang );
122 }
123 }
124 }
125
126 if ( empty( $language ) ) {
127 $language = $this->curlang;
128 $redirect_url = $requested_url;
129 } else {
130 $redirect_url = $this->redirect_canonical( $requested_url, $language );
131 $redirect_url = $this->options['force_lang'] ?
132 $this->links_model->switch_language_in_link( $redirect_url, $language ) :
133 $this->links_model->remove_language_from_link( $redirect_url ); // Works only for default permalinks.
134 }
135
136
137 /**
138 * Filters the canonical url detected by Polylang.
139 *
140 * @since 1.6
141 *
142 * @param string|false $redirect_url False or the url to redirect to.
143 * @param PLL_Language $language The language detected.
144 */
145 $redirect_url = apply_filters( 'pll_check_canonical_url', $redirect_url, $language );
146
147 if ( ! $redirect_url || $requested_url === $redirect_url ) {
148 return $requested_url;
149 }
150
151 if ( ! $do_redirect ) {
152 return $redirect_url;
153 }
154
155 // Protect against chained redirects.
156 if ( $redirect_url === $this->check_canonical_url( $redirect_url, false ) && wp_validate_redirect( $redirect_url ) ) {
157 wp_safe_redirect( $redirect_url, 301, POLYLANG );
158 exit;
159 }
160 }
161
162 /**
163 * Returns the term_id of the requested term.
164 *
165 * @since 2.9
166 *
167 * @param WP_Tax_Query $tax_query An instance of WP_Tax_Query.
168 * @return int
169 */
170 protected function get_queried_term_id( $tax_query ) {
171 $queried_terms = $tax_query->queried_terms;
172 $taxonomy = $this->get_queried_taxonomy( $tax_query );
173
174 if ( ! is_array( $queried_terms[ $taxonomy ]['terms'] ) ) {
175 return 0;
176 }
177 $field = $queried_terms[ $taxonomy ]['field'];
178 $term = reset( $queried_terms[ $taxonomy ]['terms'] );
179 $lang = isset( $queried_terms['language']['terms'] ) ? reset( $queried_terms['language']['terms'] ) : '';
180
181 // We can get a term_id when requesting a plain permalink, eg /?cat=1.
182 if ( 'term_id' === $field ) {
183 return $term;
184 }
185
186 // We get a slug when requesting a pretty permalink. Let's query all corresponding terms.
187 $args = array(
188 'lang' => '',
189 'taxonomy' => $taxonomy,
190 $field => $term,
191 'hide_empty' => false,
192 'fields' => 'ids',
193 );
194 $term_ids = get_terms( $args );
195
196 if ( ! is_array( $term_ids ) || empty( $term_ids ) ) {
197 return 0;
198 }
199
200 $term_ids = array_filter( $term_ids, 'is_numeric' );
201
202 $filtered_terms_by_lang = array_filter(
203 $term_ids,
204 function ( $term_id ) use ( $lang ) {
205 $term_lang = $this->model->term->get_language( (int) $term_id );
206
207 return ! empty( $term_lang ) && $term_lang->slug === $lang;
208 }
209 );
210
211 $tr_term = (int) reset( $filtered_terms_by_lang );
212
213 if ( ! empty( $tr_term ) ) {
214 // The queried term exists in the desired language.
215 return $tr_term;
216 }
217
218 // The queried term doesn't exist in the desired language, let's return the first one retrieved.
219 return (int) reset( $term_ids );
220 }
221
222 /**
223 * Find the taxonomy being queried.
224 *
225 * @since 2.9
226 *
227 * @param WP_Tax_Query $tax_query An instance of WP_Tax_Query.
228 * @return string A taxonomy slug
229 */
230 protected function get_queried_taxonomy( $tax_query ) {
231 $queried_terms = $tax_query->queried_terms;
232 unset( $queried_terms['language'] );
233
234 return (string) key( $queried_terms );
235 }
236
237 /**
238 * Evaluates the canonical redirect url through the deidcated WP function.
239 *
240 * @since 3.3
241 *
242 * @global WP_Query $wp_query WordPress Query object.
243 *
244 * @param string $url Requested url.
245 * @param PLL_Language $language Language of the queried object.
246 * @return string
247 */
248 protected function redirect_canonical( $url, $language ) {
249 /**
250 * @var WP_Query
251 */
252 global $wp_query;
253
254 $this->curlang = $language; // Hack to filter the `page_for_posts` option in the correct language.
255
256 $backup_wp_query = $wp_query;
257
258 if ( isset( $wp_query->tax_query ) ) {
259 unset( $wp_query->tax_query->queried_terms['language'] );
260 unset( $wp_query->query['lang'] );
261 }
262
263 $redirect_url = redirect_canonical( $url, false );
264
265 $wp_query = $backup_wp_query;
266
267 return $redirect_url ?: $url;
268 }
269 }
270