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-auto-translate.php

frontend-auto-translate.php in Polylang trunk, at src/frontend/frontend-auto-translate.php

323 lines 9.0 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 * Auto translates the posts and terms ids
8 * Useful for example for themes querying a specific cat
9 *
10 * @since 1.1
11 */
12 class PLL_Frontend_Auto_Translate {
13 /**
14 * @var PLL_Model
15 */
16 public $model;
17
18 /**
19 * Current language.
20 *
21 * @var PLL_Language|null
22 */
23 public $curlang;
24
25 /**
26 * Constructor
27 *
28 * @since 1.1
29 *
30 * @param object $polylang The Polylang object.
31 */
32 public function __construct( &$polylang ) {
33 $this->model = &$polylang->model;
34 $this->curlang = &$polylang->curlang;
35
36 add_action( 'parse_query', array( $this, 'translate_included_ids_in_query' ), 100 ); // After all Polylang filters.
37 add_filter( 'get_terms_args', array( $this, 'get_terms_args' ), 20, 2 );
38 }
39
40 /**
41 * Helper function to get the translated post in the current language.
42 *
43 * @since 1.8
44 *
45 * @param int $post_id The ID of the post to translate.
46 * @return int
47 *
48 * @phpstan-return int<0, max>
49 */
50 protected function get_post( $post_id ) {
51 return $this->model->post->get( $post_id, $this->curlang );
52 }
53
54 /**
55 * Helper function to get the translated term in the current language.
56 *
57 * @since 1.8
58 *
59 * @param int $term_id The ID of the term to translate.
60 * @return int
61 *
62 * @phpstan-return int<0, max>
63 */
64 protected function get_term( $term_id ) {
65 return $this->model->term->get( $term_id, $this->curlang );
66 }
67
68 /**
69 * Filters posts query to automatically translate included ids
70 *
71 * @since 1.1
72 *
73 * @param WP_Query $query WP_Query object
74 * @return void
75 */
76 public function translate_included_ids_in_query( $query ) {
77 global $wpdb;
78 $qv = &$query->query_vars;
79
80 if ( $query->is_main_query() || isset( $qv['lang'] ) || empty( $this->curlang ) || ( ! empty( $qv['post_type'] ) && ! $this->model->is_translated_post_type( $qv['post_type'] ) ) ) {
81 return;
82 }
83
84 // /!\ always keep untranslated as is
85
86 // Term ids separated by a comma
87 $arr = array();
88 if ( ! empty( $qv['cat'] ) ) {
89 foreach ( explode( ',', $qv['cat'] ) as $cat ) {
90 $tr = $this->get_term( abs( (int) $cat ) );
91 $arr[] = $cat < 0 ? -$tr : $tr;
92 }
93
94 $qv['cat'] = implode( ',', $arr );
95 }
96
97 // Category_name
98 $arr = array();
99 if ( ! empty( $qv['category_name'] ) ) {
100 foreach ( explode( ',', $qv['category_name'] ) as $slug ) {
101 $arr[] = $this->model->term->get_by( 'slug', $slug, $this->curlang, 'category' );
102 }
103
104 $qv['category_name'] = implode( ',', array_filter( $arr ) );
105 }
106
107 // Array of term ids
108 foreach ( array( 'category__and', 'category__in', 'category__not_in', 'tag__and', 'tag__in', 'tag__not_in' ) as $key ) {
109 $arr = array();
110 if ( ! empty( $qv[ $key ] ) ) {
111 foreach ( $qv[ $key ] as $cat ) {
112 $tr = $this->get_term( $cat );
113 $arr[] = $tr ?: $cat;
114 }
115 $qv[ $key ] = $arr;
116 }
117 }
118
119 // Tag
120 if ( ! empty( $qv['tag'] ) ) {
121 $qv['tag'] = $this->translate_terms_list( $qv['tag'], 'post_tag' );
122 }
123
124 // tag_id can only take one id
125 if ( ! empty( $qv['tag_id'] ) && $tr_id = $this->get_term( $qv['tag_id'] ) ) {
126 $qv['tag_id'] = $tr_id;
127 }
128
129 // Array of tag slugs
130 foreach ( array( 'tag_slug__and', 'tag_slug__in' ) as $key ) {
131 $arr = array();
132 if ( ! empty( $qv[ $key ] ) ) {
133 foreach ( $qv[ $key ] as $slug ) {
134 $arr[] = $this->model->term->get_by( 'slug', $slug, $this->curlang, 'post_tag' );
135 }
136
137 $qv[ $key ] = array_filter( $arr );
138 }
139 }
140
141 // Custom taxonomies
142 // According to the codex, this type of query is deprecated as of WP 3.1 but it does not appear in WP 3.5 source code
143 foreach ( array_intersect( $this->model->get_translated_taxonomies(), get_taxonomies( array( '_builtin' => false ) ) ) as $taxonomy ) {
144 $tax = get_taxonomy( $taxonomy );
145 if ( ! empty( $tax ) && ! empty( $tax->query_var ) && ! empty( $qv[ $tax->query_var ] ) ) {
146 $qv[ $tax->query_var ] = $this->translate_terms_list( $qv[ $tax->query_var ], $taxonomy );
147 }
148 }
149
150 // Tax_query since WP 3.1
151 if ( ! empty( $qv['tax_query'] ) && is_array( $qv['tax_query'] ) ) {
152 $qv['tax_query'] = $this->translate_tax_query_recursive( $qv['tax_query'] );
153 }
154
155 // p, page_id, post_parent can only take one id
156 foreach ( array( 'p', 'page_id', 'post_parent' ) as $key ) {
157 if ( ! empty( $qv[ $key ] ) && $tr_id = $this->get_post( $qv[ $key ] ) ) {
158 $qv[ $key ] = $tr_id;
159 }
160 }
161
162 // name, can only take one slug
163 if ( ! empty( $qv['name'] ) && is_string( $qv['name'] ) ) {
164 if ( empty( $qv['post_type'] ) ) {
165 $post_types = array( 'post' );
166 } elseif ( 'any' === $qv['post_type'] ) {
167 $post_types = get_post_types( array( 'exclude_from_search' => false ) ); // May return a empty array
168 } else {
169 $post_types = (array) $qv['post_type'];
170 }
171
172 if ( ! empty( $post_types ) ) {
173 // No function to get post by name except get_posts itself
174 $id = $wpdb->get_var(
175 sprintf(
176 "SELECT ID from {$wpdb->posts}
177 WHERE {$wpdb->posts}.post_type IN ( '%s' )
178 AND post_name='%s'",
179 implode( "', '", esc_sql( $post_types ) ),
180 esc_sql( $qv['name'] )
181 )
182 );
183 if ( $id ) {
184 $tr_id = $this->get_post( $id );
185 if ( $tr_id ) {
186 $tr = get_post( $tr_id );
187 if ( $tr ) {
188 $qv['name'] = $tr->post_name;
189 }
190 }
191 }
192 }
193 }
194
195 // pagename, the page id is already available in queried_object_id
196 if ( ! empty( $qv['pagename'] ) && ! empty( $query->queried_object_id ) && $tr_id = $this->get_post( $query->queried_object_id ) ) {
197 $query->queried_object_id = $tr_id;
198 $qv['pagename'] = get_page_uri( $tr_id );
199 }
200
201 // Array of post ids
202 // post_parent__in & post_parent__not_in since WP 3.6
203 foreach ( array( 'post__in', 'post__not_in', 'post_parent__in', 'post_parent__not_in' ) as $key ) { // phpcs:ignore WordPressVIPMinimum.Performance.WPQueryParams.PostNotIn
204 $arr = array();
205 if ( ! empty( $qv[ $key ] ) ) {
206 // post__in used by the 2 functions below
207 // Useless to filter them as output is already in the right language and would result in performance loss
208 foreach ( debug_backtrace( DEBUG_BACKTRACE_IGNORE_ARGS ) as $trace ) { // phpcs:ignore WordPress.PHP.DevelopmentFunctions
209 if ( in_array( $trace['function'], array( 'wp_nav_menu', 'gallery_shortcode' ) ) ) {
210 return;
211 }
212 }
213
214 foreach ( $qv[ $key ] as $p ) {
215 $tr = $this->get_post( $p );
216 $arr[] = $tr ?: $p;
217 }
218
219 $qv[ $key ] = $arr;
220 }
221 }
222 }
223
224 /**
225 * Filters the terms query to automatically translate included ids.
226 *
227 * @since 1.1.1
228 *
229 * @param array $args An array of get_terms() arguments.
230 * @param array $taxonomies An array of taxonomy names.
231 * @return array
232 */
233 public function get_terms_args( $args, $taxonomies ) {
234 if ( ! isset( $args['lang'] ) && ! empty( $args['include'] ) && ( empty( $taxonomies ) || $this->model->is_translated_taxonomy( $taxonomies ) ) ) {
235 $arr = array();
236
237 foreach ( wp_parse_id_list( $args['include'] ) as $id ) {
238 $tr = $this->get_term( $id );
239 $arr[] = $tr ?: $id;
240 }
241
242 $args['include'] = $arr;
243 }
244 return $args;
245 }
246
247 /**
248 * Translates tax queries
249 * Compatible with nested tax queries introduced in WP 4.1
250 *
251 * @since 1.7
252 *
253 * @param array $tax_queries An array of tax queries.
254 * @return array Translated tax queries.
255 */
256 protected function translate_tax_query_recursive( $tax_queries ) {
257 if ( empty( $this->curlang ) ) {
258 return $tax_queries;
259 }
260
261 foreach ( $tax_queries as $key => $q ) {
262 if ( ! is_array( $q ) ) {
263 continue;
264 }
265
266 if ( isset( $q['taxonomy'], $q['terms'] ) && $this->model->is_translated_taxonomy( $q['taxonomy'] ) ) {
267 $arr = array();
268 $field = isset( $q['field'] ) && in_array( $q['field'], array( 'slug', 'name' ) ) ? $q['field'] : 'term_id';
269 foreach ( (array) $q['terms'] as $term ) {
270 $translated = $this->model->term->get_by( $field, $term, $this->curlang, $q['taxonomy'] );
271 $arr[] = $translated ?: $term;
272 }
273
274 $tax_queries[ $key ]['terms'] = $arr;
275 } else {
276 // Nested queries.
277 $tax_queries[ $key ] = $this->translate_tax_query_recursive( $q );
278 }
279 }
280
281 return $tax_queries;
282 }
283
284 /**
285 * Translates a list of term slugs provided either as an array or a string
286 * with slugs separated by a comma or a '+'.
287 *
288 * @since 3.2.8
289 *
290 * @param string|string[] $query_var The list of term slugs.
291 * @param string $taxonomy The taxonomy for terms.
292 * @return string|string[] The translated list.
293 */
294 protected function translate_terms_list( $query_var, $taxonomy ) {
295 $slugs = array();
296
297 if ( is_array( $query_var ) ) {
298 $slugs = $query_var;
299 } elseif ( is_string( $query_var ) ) {
300 $sep = strpos( $query_var, ',' ) !== false ? ',' : '+'; // Two possible separators.
301 $slugs = explode( $sep, $query_var );
302 }
303
304 if ( ! empty( $this->curlang ) ) {
305 foreach ( $slugs as $key => $slug ) {
306 if ( ! is_string( $slug ) ) {
307 // We got an unexpected query var, let return it unchanged.
308 return $query_var;
309 }
310 $slugs[ $key ] = $this->model->term->get_by( 'slug', $slug, $this->curlang, $taxonomy );
311 }
312 }
313
314 $slugs = array_filter( $slugs );
315
316 if ( ! empty( $sep ) ) {
317 return implode( $sep, $slugs );
318 }
319
320 return $slugs;
321 }
322 }
323