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
← All changes | includes/frontend/class-language-handler.php +374 -0 4.3.4 → 4.5.2 View file →
@@ -28,8 +28,9 @@
28 28 */
29 29 public function __construct() {
30 30 Hook_Registry::add_filter( 'top_ten_query_the_posts', array( $this, 'translate_ids' ), 999 );
31 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 );
32 33 }
33 34
34 35 /**
35 36 * Get the ID of a post in the current language. Works with WPML and PolyLang.
@@ -112,6 +113,379 @@
112 113 *
113 114 * @param \WP_Post|array|null $id Post object.
114 115 */
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;
116 490 }
117 491 }