PluginProbe
WebberZone Top 10 — Popular Posts / 4.5.2
WebberZone Top 10 — Popular Posts v4.5.2
4.5.2 4.5.1 4.5.0 4.4.3 4.4.2 4.4.1 4.4.0 4.3.4 4.3.3 4.3.2 4.3.1 4.3.0 trunk 1.0 1.0.1 1.1 1.2 1.3 1.4 1.4.1 1.5 1.5.1 1.5.2 1.5.3 1.6 All 118 releases
top-10 / includes / frontend / class-language-handler.php

class-language-handler.php in WebberZone Top 10 — Popular Posts 4.5.2, at includes/frontend/class-language-handler.php

492 lines 15.1 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2 /**
3 * Language Handler class.
4 *
5 * @package WebberZone\Top_Ten\Frontend
6 */
7
8 namespace WebberZone\Top_Ten\Frontend;
9
10 use WebberZone\Top_Ten\Util\Hook_Registry;
11
12 // If this file is called directly, abort.
13 if ( ! defined( 'WPINC' ) ) {
14 die;
15 }
16
17 /**
18 * Language handler class.
19 *
20 * @since 3.3.3
21 */
22 class Language_Handler {
23
24 /**
25 * Constructor.
26 *
27 * @since 3.3.3
28 */
29 public function __construct() {
30 Hook_Registry::add_filter( 'top_ten_query_the_posts', array( $this, 'translate_ids' ), 999 );
31 Hook_Registry::add_filter( 'top_ten_query_posts_pre_query', array( $this, 'translate_ids' ), 999 );
32 Hook_Registry::add_filter( 'rest_pre_echo_response', array( $this, 'translate_rest_response' ), 10, 3 );
33 }
34
35 /**
36 * Get the ID of a post in the current language. Works with WPML and PolyLang.
37 *
38 * @since 3.3.3
39 *
40 * @param int[] $results Arry of Posts.
41 * @return \WP_Post[] Updated array of WP_Post objects.
42 */
43 public static function translate_ids( $results ) {
44
45 if ( empty( $results ) ) {
46 return $results;
47 }
48
49 $processed_ids = array();
50 $processed_results = array();
51
52 foreach ( $results as $result ) {
53
54 $result = self::object_id_cur_lang( $result );
55 if ( ! $result ) {
56 continue;
57 }
58
59 // If this is NULL or already processed ID or matches current post then skip processing this loop.
60 if ( ! $result->ID || in_array( $result->ID, $processed_ids ) ) { // phpcs:ignore WordPress.PHP.StrictInArray.MissingTrueStrict
61 continue;
62 }
63
64 // Push the current ID into the array to ensure we're not repeating it.
65 array_push( $processed_ids, $result->ID );
66
67 // Let's get the Post using the ID.
68 $result = get_post( $result );
69 array_push( $processed_results, $result );
70 }
71 return $processed_results;
72 }
73
74 /**
75 * Returns the object identifier for the current language (WPML).
76 *
77 * @since 3.3.3
78 *
79 * @param int|string|\WP_Post $post Post object or Post ID.
80 * @return \WP_Post|array|null Post opbject, updated if needed.
81 */
82 public static function object_id_cur_lang( $post ) {
83
84 $return_original_if_missing = false;
85
86 $post = get_post( $post );
87 $current_lang = apply_filters( 'wpml_current_language', null );
88
89 // Polylang implementation.
90 if ( function_exists( 'pll_get_post' ) ) {
91 $post = \pll_get_post( $post->ID );
92 $post = get_post( $post );
93 }
94
95 // WPML implementation.
96 /**
97 * Filter to modify if the original language ID is returned.
98 *
99 * @since 2.2.3
100 *
101 * @param bool $return_original_if_missing Flag to return original post ID if translated post ID is missing.
102 * @param int $id Post ID
103 */
104 $return_original_if_missing = apply_filters( 'tptn_wpml_return_original', $return_original_if_missing, $post->ID );
105
106 $post = apply_filters( 'wpml_object_id', $post->ID, $post->post_type, $return_original_if_missing, $current_lang );
107 $post = get_post( $post );
108
109 /**
110 * Filters Post object for current language.
111 *
112 * @since 2.1.0
113 *
114 * @param \WP_Post|array|null $id Post object.
115 */
116 return apply_filters( 'tptn_object_id_cur_lang', $post );
117 }
118
119 /**
120 * Whether TranslatePress is active and exposes the API this integration needs.
121 *
122 * @since 4.5.1
123 *
124 * @return bool True if TranslatePress can be used.
125 */
126 public static function is_translatepress_active(): bool {
127 return function_exists( 'trp_translate' ) && class_exists( 'TRP_Translate_Press' );
128 }
129
130 /**
131 * Fetch a TranslatePress component instance.
132 *
133 * @since 4.5.1
134 *
135 * @param string $component Component name, e.g. `url_converter`.
136 * @return object|null Component instance or null when unavailable.
137 */
138 public static function get_trp_component( string $component ) {
139 if ( ! self::is_translatepress_active() ) {
140 return null;
141 }
142
143 $trp = \TRP_Translate_Press::get_trp_instance();
144 if ( ! is_object( $trp ) || ! method_exists( $trp, 'get_component' ) ) {
145 return null;
146 }
147
148 $instance = $trp->get_component( $component );
149
150 return is_object( $instance ) ? $instance : null;
151 }
152
153 /**
154 * Get the TranslatePress settings array.
155 *
156 * @since 4.5.1
157 *
158 * @return array TranslatePress settings.
159 */
160 public static function get_trp_settings(): array {
161 $settings = get_option( 'trp_settings', array() );
162
163 return is_array( $settings ) ? $settings : array();
164 }
165
166 /**
167 * Get the TranslatePress language the current front-end request is rendering in.
168 *
169 * @since 4.5.1
170 *
171 * @return string Language code, or an empty string when TranslatePress is inactive
172 * or the request is in the default language.
173 */
174 public static function get_trp_current_language(): string {
175 if ( ! self::is_translatepress_active() ) {
176 return '';
177 }
178
179 global $TRP_LANGUAGE; // phpcs:ignore WordPress.NamingConventions.ValidVariableName.VariableNotSnakeCase -- TranslatePress global.
180
181 $language = is_string( $TRP_LANGUAGE ) ? $TRP_LANGUAGE : ''; // phpcs:ignore WordPress.NamingConventions.ValidVariableName.VariableNotSnakeCase -- TranslatePress global.
182 $settings = self::get_trp_settings();
183 $default = isset( $settings['default-language'] ) ? (string) $settings['default-language'] : '';
184
185 return ( '' === $language || $language === $default ) ? '' : $language;
186 }
187
188 /**
189 * Get a language identifier for cache keys.
190 *
191 * Rendered output is language-specific — TranslatePress filters `home_url()`, and
192 * WPML/Polylang resolve different post IDs — so cached HTML and post lists must not
193 * be shared between languages.
194 *
195 * @since 4.5.1
196 *
197 * @return string Current language code, or an empty string when the site is monolingual.
198 */
199 public static function get_cache_language(): string {
200 $language = self::get_trp_current_language();
201
202 if ( '' === $language && function_exists( 'pll_current_language' ) ) {
203 $language = (string) \pll_current_language( 'locale' );
204 }
205
206 if ( '' === $language && class_exists( 'SitePress' ) ) {
207 $language = (string) apply_filters( 'wpml_current_language', null );
208 }
209
210 /**
211 * Filters the language component added to Top 10 cache keys.
212 *
213 * @since 4.5.1
214 *
215 * @param string $language Current language code, or an empty string.
216 */
217 return (string) apply_filters( 'tptn_cache_language', $language );
218 }
219
220 /**
221 * Get the referring URL, but only when it points at this site.
222 *
223 * TranslatePress resolves a language from any URL containing a known slug, so an
224 * off-site referer would otherwise let a cross-site request choose the language.
225 *
226 * @since 4.5.1
227 *
228 * @return string Referring URL on this host, or an empty string.
229 */
230 protected static function get_same_origin_referer(): string {
231 $referer = function_exists( 'wp_get_raw_referer' ) ? wp_get_raw_referer() : '';
232
233 if ( ! is_string( $referer ) || '' === $referer ) {
234 return '';
235 }
236
237 $referer_host = wp_parse_url( $referer, PHP_URL_HOST );
238 $home_host = wp_parse_url( home_url(), PHP_URL_HOST );
239
240 if ( empty( $referer_host ) || empty( $home_host ) || strtolower( (string) $referer_host ) !== strtolower( (string) $home_host ) ) {
241 return '';
242 }
243
244 return $referer;
245 }
246
247 /**
248 * Resolve the TranslatePress language a REST request should be rendered in.
249 *
250 * TranslatePress derives the language from the page URL, but REST routes carry no
251 * language prefix, so the language is taken from an explicit `lang` parameter and
252 * falls back to the referring front-end URL.
253 *
254 * @since 4.5.1
255 *
256 * @param \WP_REST_Request|null $request REST request.
257 * @return string Language code, or an empty string when no translation is needed.
258 */
259 public static function get_trp_rest_language( $request = null ): string {
260 if ( ! self::is_translatepress_active() ) {
261 return '';
262 }
263
264 $settings = self::get_trp_settings();
265 $default = isset( $settings['default-language'] ) ? (string) $settings['default-language'] : '';
266
267 // Mirrors TRP_Url_Converter::get_lang_from_url_string(): unpublished languages are for translators only.
268 $available = isset( $settings['publish-languages'] ) ? (array) $settings['publish-languages'] : array();
269
270 if ( current_user_can( (string) apply_filters( 'trp_translating_capability', 'manage_options' ) ) ) {
271 $available = array_merge( $available, isset( $settings['translation-languages'] ) ? (array) $settings['translation-languages'] : array() );
272 }
273 $available = array_values( array_unique( array_map( 'strval', $available ) ) );
274
275 $language = '';
276
277 if ( $request instanceof \WP_REST_Request ) {
278 $raw_language = $request->get_param( 'lang' );
279 $language = is_string( $raw_language ) ? sanitize_text_field( $raw_language ) : '';
280 }
281
282 // Accept either a TranslatePress locale (fr_FR) or its URL slug (fr).
283 $url_slugs = isset( $settings['url-slugs'] ) && is_array( $settings['url-slugs'] ) ? $settings['url-slugs'] : array();
284 foreach ( $url_slugs as $locale => $slug ) {
285 if ( is_string( $slug ) && $slug === $language ) {
286 $language = (string) $locale;
287 break;
288 }
289 }
290
291 if ( '' === $language ) {
292 $url_converter = self::get_trp_component( 'url_converter' );
293
294 if ( $url_converter && method_exists( $url_converter, 'get_lang_from_url_string' ) ) {
295 // TranslatePress filters home_url(), so rest_url() carries the language prefix of the page that enqueued it.
296 if ( method_exists( $url_converter, 'cur_page_url' ) ) {
297 $language = (string) $url_converter->get_lang_from_url_string( $url_converter->cur_page_url() );
298 }
299
300 if ( '' === $language ) {
301 $referer = self::get_same_origin_referer();
302
303 if ( '' !== $referer ) {
304 $language = (string) $url_converter->get_lang_from_url_string( $referer );
305 }
306 }
307 }
308 }
309
310 /**
311 * Filters the TranslatePress language used to render Top 10 REST responses.
312 *
313 * @since 4.5.1
314 *
315 * @param string $language Language code resolved from the request.
316 * @param \WP_REST_Request|null $request REST request.
317 */
318 $language = (string) apply_filters( 'tptn_trp_rest_language', $language, $request );
319
320 if ( '' === $language || $language === $default || ! in_array( $language, $available, true ) ) {
321 return '';
322 }
323
324 return $language;
325 }
326
327 /**
328 * Translate a string with TranslatePress.
329 *
330 * @since 4.5.1
331 *
332 * @param string $content Content in the default language. Text or HTML.
333 * @param string $language Target language code.
334 * @return string Translated content.
335 */
336 public static function trp_translate_content( string $content, string $language ): string {
337 if ( '' === $content || '' === $language || ! self::is_translatepress_active() ) {
338 return $content;
339 }
340
341 // $prevent_over_translation wraps the return in a span; REST output never passes through TranslatePress's page buffer.
342 return (string) \trp_translate( $content, $language, false );
343 }
344
345 /**
346 * Convert a URL to its TranslatePress equivalent in the given language.
347 *
348 * @since 4.5.1
349 *
350 * @param string $url URL in the default language.
351 * @param string $language Target language code.
352 * @return string Converted URL.
353 */
354 public static function trp_translate_url( string $url, string $language ): string {
355 if ( '' === $url || '' === $language ) {
356 return $url;
357 }
358
359 $url_converter = self::get_trp_component( 'url_converter' );
360
361 if ( ! $url_converter || ! method_exists( $url_converter, 'get_url_for_language' ) ) {
362 return $url;
363 }
364
365 $converted = $url_converter->get_url_for_language( $language, $url, '' );
366
367 return is_string( $converted ) && '' !== $converted ? $converted : $url;
368 }
369
370 /**
371 * Translate the Top 10 REST API response with TranslatePress.
372 *
373 * TranslatePress's own page output buffer never runs for REST requests, so the
374 * output returned over REST would otherwise always be served in the default
375 * language.
376 *
377 * @since 4.5.1
378 *
379 * @param mixed $result Response data to send to the client.
380 * @param mixed $server Server instance.
381 * @param mixed $request Request used to generate the response.
382 * @return mixed Response data, translated where applicable.
383 */
384 public static function translate_rest_response( $result, $server, $request ) {
385 if ( ! $request instanceof \WP_REST_Request ) {
386 return $result;
387 }
388
389 if ( ! is_array( $result ) && ! is_string( $result ) ) {
390 return $result;
391 }
392
393 $route = (string) $request->get_route();
394 $namespace = '/' . self::get_rest_namespace();
395 if ( $route !== $namespace && 0 !== strpos( $route, $namespace . '/' ) ) {
396 return $result;
397 }
398
399 $language = self::get_trp_rest_language( $request );
400
401 if ( '' === $language ) {
402 return $result;
403 }
404
405 // Some routes return a bare HTML string rather than a structure of fields.
406 if ( is_string( $result ) ) {
407 return self::trp_translate_content( $result, $language );
408 }
409
410 /**
411 * Filters the response keys Top 10 translates with TranslatePress.
412 *
413 * @since 4.5.1
414 *
415 * @param array $keys Associative array of `content` and `url` key names.
416 */
417 $keys = apply_filters(
418 'tptn_trp_rest_translatable_keys',
419 array(
420 'content' => array( 'html', 'title', 'excerpt', 'content', 'name', 'description' ),
421 // `guid` is deliberately absent: it is an immutable identifier, not a navigable URL.
422 'url' => array( 'link', 'permalink' ),
423 )
424 );
425 $keys = (array) $keys;
426 $keys = array(
427 'content' => isset( $keys['content'] ) && is_array( $keys['content'] ) ? array_values( array_map( 'strval', array_filter( $keys['content'], 'is_scalar' ) ) ) : array(),
428 'url' => isset( $keys['url'] ) && is_array( $keys['url'] ) ? array_values( array_map( 'strval', array_filter( $keys['url'], 'is_scalar' ) ) ) : array(),
429 );
430
431 return self::translate_rest_data( $result, $language, $keys );
432 }
433
434 /**
435 * The REST namespace whose responses this plugin translates.
436 *
437 * @since 4.5.1
438 *
439 * @return string REST namespace.
440 */
441 protected static function get_rest_namespace(): string {
442 return 'top-10/v1';
443 }
444
445 /**
446 * Walk a Top 10 REST payload translating the fields TranslatePress can handle.
447 *
448 * @since 4.5.1
449 *
450 * @param array $data Response data.
451 * @param string $language Target language code.
452 * @param array $keys Translatable `content` and `url` key names.
453 * @param int $depth Current recursion depth.
454 * @return array Translated response data.
455 */
456 protected static function translate_rest_data( array $data, string $language, array $keys, int $depth = 0 ): array {
457 if ( $depth > 5 ) {
458 return $data;
459 }
460
461 foreach ( $data as $key => $value ) {
462 if ( is_array( $value ) ) {
463 if ( isset( $value['rendered'] ) && is_string( $value['rendered'] ) ) {
464 if ( in_array( (string) $key, $keys['content'], true ) ) {
465 $data[ $key ]['rendered'] = self::trp_translate_content( $value['rendered'], $language );
466 continue;
467 }
468 if ( in_array( (string) $key, $keys['url'], true ) ) {
469 $data[ $key ]['rendered'] = self::trp_translate_url( $value['rendered'], $language );
470 continue;
471 }
472 }
473
474 $data[ $key ] = self::translate_rest_data( $value, $language, $keys, $depth + 1 );
475 continue;
476 }
477
478 if ( ! is_string( $value ) || '' === $value ) {
479 continue;
480 }
481
482 if ( in_array( (string) $key, $keys['content'], true ) ) {
483 $data[ $key ] = self::trp_translate_content( $value, $language );
484 } elseif ( in_array( (string) $key, $keys['url'], true ) ) {
485 $data[ $key ] = self::trp_translate_url( $value, $language );
486 }
487 }
488
489 return $data;
490 }
491 }
492