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 / language.php

language.php in Polylang trunk, at src/language.php

748 lines 20.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 * A language object is made of two terms in 'language' and 'term_language' taxonomies.
8 * Manipulating only one object per language instead of two terms should make things easier.
9 *
10 * @since 1.2
11 * @immutable
12 *
13 * @phpstan-type LanguagePropData array{
14 * term_id: positive-int,
15 * term_taxonomy_id: positive-int,
16 * count: int<0, max>
17 * }
18 * @phpstan-type LanguageData array{
19 * term_props: array{
20 * language: LanguagePropData,
21 * }&array<non-empty-string, LanguagePropData>,
22 * name: non-empty-string,
23 * slug: non-empty-string,
24 * locale: non-empty-string,
25 * w3c: non-empty-string,
26 * flag_code: string,
27 * term_group: int,
28 * is_rtl: int<0, 1>,
29 * facebook?: string,
30 * home_url: non-empty-string,
31 * search_url: non-empty-string,
32 * host: non-empty-string,
33 * flag_url: string,
34 * flag: string,
35 * custom_flag_url?: string,
36 * custom_flag?: string,
37 * page_on_front?: int<0, max>,
38 * page_for_posts?: int<0, max>,
39 * active?: bool,
40 * fallbacks?: array<non-empty-string>,
41 * is_default: bool,
42 * admin_flag: array{'aria-hidden': non-empty-string, '': non-empty-string}
43 * }
44 */
45 class PLL_Language extends PLL_Language_Deprecated {
46
47 /**
48 * Language name. Ex: English.
49 *
50 * @var string
51 *
52 * @phpstan-var non-empty-string
53 */
54 public $name;
55
56 /**
57 * Language code used in URL. Ex: en.
58 *
59 * @var string
60 *
61 * @phpstan-var non-empty-string
62 */
63 public $slug;
64
65 /**
66 * Order of the language when displayed in a list of languages.
67 *
68 * @var int
69 */
70 public $term_group;
71
72 /**
73 * ID of the term in 'language' taxonomy.
74 * Duplicated from `$this->term_props['language']['term_id'],
75 * but kept to facilitate the use of it.
76 *
77 * @var int
78 *
79 * @phpstan-var int<1, max>
80 */
81 public $term_id;
82
83 /**
84 * WordPress language locale. Ex: en_US.
85 *
86 * @var string
87 *
88 * @phpstan-var non-empty-string
89 */
90 public $locale;
91
92 /**
93 * 1 if the language is rtl, 0 otherwise.
94 *
95 * @var int
96 *
97 * @phpstan-var int<0, 1>
98 */
99 public $is_rtl;
100
101 /**
102 * W3C locale.
103 *
104 * @var string
105 *
106 * @phpstan-var non-empty-string
107 */
108 public $w3c;
109
110 /**
111 * Facebook locale.
112 *
113 * @var string
114 */
115 public $facebook;
116
117 /**
118 * Home URL in this language.
119 *
120 * @var string
121 *
122 * @phpstan-var non-empty-string
123 */
124 private $home_url;
125
126 /**
127 * Home URL to use in search forms.
128 *
129 * @var string
130 *
131 * @phpstan-var non-empty-string
132 */
133 private $search_url;
134
135 /**
136 * Host corresponding to this language.
137 *
138 * @var string
139 *
140 * @phpstan-var non-empty-string
141 */
142 public $host;
143
144 /**
145 * ID of the page on front in this language (set from pll_additional_language_data filter).
146 *
147 * @var int
148 *
149 * @phpstan-var int<0, max>
150 */
151 public $page_on_front;
152
153 /**
154 * ID of the page for posts in this language (set from pll_additional_language_data filter).
155 *
156 * @var int
157 *
158 * @phpstan-var int<0, max>
159 */
160 public $page_for_posts;
161
162 /**
163 * Code of the flag.
164 *
165 * @var string
166 */
167 public $flag_code;
168
169 /**
170 * URL of the flag. Always set to the main domain.
171 *
172 * @var string
173 */
174 public $flag_url;
175
176 /**
177 * HTML markup of the flag.
178 *
179 * @var string
180 */
181 public $flag;
182
183 /**
184 * URL of the custom flag if it exists. Always set to the main domain.
185 *
186 * @var string
187 */
188 public $custom_flag_url;
189
190 /**
191 * HTML markup of the custom flag if it exists.
192 *
193 * @var string
194 */
195 public $custom_flag;
196
197 /**
198 * Whether or not the language is active. Default `true`.
199 *
200 * @var bool
201 */
202 public $active;
203
204 /**
205 * List of WordPress language locales. Ex: array( 'en_GB' ).
206 *
207 * @var string[]
208 *
209 * @phpstan-var list<non-empty-string>
210 */
211 public $fallbacks;
212
213 /**
214 * Whether the language is the default one.
215 *
216 * @var bool
217 */
218 public $is_default;
219
220 /**
221 * Stores language term properties (like term IDs and counts) for each language taxonomy (`language`,
222 * `term_language`, etc).
223 * This stores the values of the properties `$term_id` + `$term_taxonomy_id` + `$count` (`language`), `$tl_term_id`
224 * + `$tl_term_taxonomy_id` + `$tl_count` (`term_language`), and the `term_id` + `term_taxonomy_id` + `count` for
225 * other language taxonomies.
226 *
227 * @var array[] Array keys are language term names.
228 *
229 * @example array(
230 * 'language' => array(
231 * 'term_id' => 7,
232 * 'term_taxonomy_id' => 8,
233 * 'count' => 11,
234 * ),
235 * 'term_language' => array(
236 * 'term_id' => 11,
237 * 'term_taxonomy_id' => 12,
238 * 'count' => 6,
239 * ),
240 * 'foo_language' => array(
241 * 'term_id' => 33,
242 * 'term_taxonomy_id' => 34,
243 * 'count' => 0,
244 * ),
245 * )
246 *
247 * @phpstan-var array{
248 * language: LanguagePropData,
249 * }
250 * &array<non-empty-string, LanguagePropData>
251 */
252 protected $term_props;
253
254 /**
255 * @var array
256 *
257 * @phpstan-var array{'aria-hidden': non-empty-string, '': non-empty-string}
258 */
259 private $admin_flag;
260
261 /**
262 * Constructor: builds a language object given the corresponding data.
263 *
264 * @since 1.2
265 * @since 3.4 Only accepts one argument.
266 *
267 * @param array $language_data {
268 * Language object properties stored as an array.
269 *
270 * @type array[] $term_props An array of language term properties. Array keys are language taxonomy names
271 * (`language` and `term_language` are mandatory), array values are arrays of
272 * language term properties (`term_id`, `term_taxonomy_id`, and `count`).
273 * @type string $name Language name. Ex: English.
274 * @type string $slug Language code used in URL. Ex: en.
275 * @type string $locale WordPress language locale. Ex: en_US.
276 * @type string $w3c W3C locale.
277 * @type string $flag_code Code of the flag.
278 * @type int $term_group Order of the language when displayed in a list of languages.
279 * @type int $is_rtl `1` if the language is rtl, `0` otherwise.
280 * @type string $facebook Optional. Facebook locale.
281 * @type string $home_url Home URL in this language.
282 * @type string $search_url Home URL to use in search forms.
283 * @type string $host Host corresponding to this language.
284 * @type string $flag_url URL of the flag.
285 * @type string $flag HTML markup of the flag.
286 * @type string $custom_flag_url Optional. URL of the custom flag if it exists.
287 * @type string $custom_flag Optional. HTML markup of the custom flag if it exists.
288 * @type int $page_on_front Optional. ID of the page on front in this language.
289 * @type int $page_for_posts Optional. ID of the page for posts in this language.
290 * @type bool $active Optional. Whether or not the language is active. Default `true`.
291 * @type string[] $fallbacks Optional. List of WordPress language locales. Ex: array( 'en_GB' ).
292 * @type bool $is_default Whether or not the language is the default one.
293 * @type array $admin_flag An array containing the keys `''` (empty string) for the "normal" flag, and
294 * `'aria-hidden'` for the flag hidden to screen readers.
295 * }
296 *
297 * @phpstan-param LanguageData $language_data
298 */
299 public function __construct( array $language_data ) {
300 // Default values for optional params.
301 $defaults = array(
302 'facebook' => '',
303 'page_on_front' => 0,
304 'page_for_posts' => 0,
305 'custom_flag_url' => '',
306 'custom_flag' => '',
307 'active' => true,
308 'fallbacks' => array(),
309 );
310
311 foreach ( array_merge( $defaults, $language_data ) as $prop => $value ) {
312 $this->$prop = $value;
313 }
314
315 $this->term_id = $this->term_props['language']['term_id'];
316 }
317
318 /**
319 * Returns a language term property value (term ID, term taxonomy ID, or count).
320 *
321 * @since 3.4
322 *
323 * @param string $taxonomy_name Name of the taxonomy.
324 * @param string $prop_name Name of the property: 'term_taxonomy_id', 'term_id', 'count'.
325 * @return int
326 *
327 * @phpstan-param non-empty-string $taxonomy_name
328 * @phpstan-param 'term_taxonomy_id'|'term_id'|'count' $prop_name
329 * @phpstan-return int<0, max>
330 */
331 public function get_tax_prop( $taxonomy_name, $prop_name ) {
332 return $this->term_props[ $taxonomy_name ][ $prop_name ] ?? 0;
333 }
334
335 /**
336 * Returns the language term props for all content types.
337 *
338 * @since 3.4
339 *
340 * @param string $property Name of the field to return. An empty string to return them all.
341 * @return (int[]|int)[] Array keys are taxonomy names, array values depend of `$property`.
342 *
343 * @phpstan-param 'term_taxonomy_id'|'term_id'|'count'|'' $property
344 * @phpstan-return array<non-empty-string, (
345 * $property is non-empty-string ?
346 * (
347 * $property is 'count' ?
348 * int<0, max> :
349 * positive-int
350 * ) :
351 * LanguagePropData
352 * )>
353 */
354 public function get_tax_props( $property = '' ) {
355 if ( empty( $property ) ) {
356 return $this->term_props;
357 }
358
359 $term_props = array();
360
361 foreach ( $this->term_props as $taxonomy_name => $props ) {
362 $term_props[ $taxonomy_name ] = $props[ $property ];
363 }
364
365 return $term_props;
366 }
367
368 /**
369 * Returns a predefined HTML flag.
370 *
371 * @since 3.4
372 *
373 * @param string $flag_code Flag code to render.
374 * @return string HTML code for the flag.
375 */
376 public static function get_predefined_flag( $flag_code ) {
377 return self::get_flag_html( self::get_flag_information( $flag_code ) );
378 }
379
380 /**
381 * Returns the flag information.
382 *
383 * @since 2.6
384 *
385 * @param string $code Flag code.
386 * @return array {
387 * Flag information.
388 *
389 * @type string $url Flag url.
390 * @type string $src Optional, src attribute value if different of the url, for example if base64 encoded.
391 * @type int $width Optional, flag width in pixels.
392 * @type int $height Optional, flag height in pixels.
393 * }
394 *
395 * @phpstan-return array{
396 * url: string,
397 * src: string,
398 * width?: positive-int,
399 * height?: positive-int
400 * }
401 */
402 public static function get_flag_information( $code ) {
403 $default_flag = array(
404 'url' => '',
405 'src' => '',
406 );
407
408 if ( empty( $code ) ) {
409 return $default_flag;
410 }
411
412 // Polylang builtin flags.
413 $file = "/vendor/wpsyntex/flags/{$code}.svg";
414 if ( is_readable( POLYLANG_ROOT_DIR . $file ) ) {
415 $default_flag = array(
416 'url' => plugins_url( $file, POLYLANG_ROOT_FILE ),
417 'src' => '',
418 'width' => 18,
419 'height' => 12,
420 );
421
422 // If base64 encoded flags are preferred.
423 if ( pll_get_constant( 'PLL_ENCODED_FLAGS', true ) ) {
424 $content = file_get_contents( POLYLANG_ROOT_DIR . $file ); // phpcs:ignore WordPress.WP.AlternativeFunctions.file_get_contents_file_get_contents
425
426 if ( ! empty( $content ) ) {
427 $default_flag['src'] = 'data:image/svg+xml,' . self::encode_svg( $content );
428 }
429 }
430 }
431
432 /**
433 * Filters flag information:
434 *
435 * @since 2.4
436 *
437 * @param array $flag {
438 * Information about the flag.
439 *
440 * @type string $url Flag url.
441 * @type string $src Optional, src attribute value if different of the url, for example if base64 encoded.
442 * @type int $width Optional, flag width in pixels.
443 * @type int $height Optional, flag height in pixels.
444 * }
445 * @param string $code Flag code.
446 */
447 $flag = apply_filters( 'pll_flag', $default_flag, $code );
448
449 $flag['url'] = sanitize_url( $flag['url'] );
450
451 if ( empty( $flag['src'] ) || ( $flag['src'] === $default_flag['src'] && $flag['url'] !== $default_flag['url'] ) ) {
452 $flag['src'] = esc_url( set_url_scheme( $flag['url'], 'relative' ) );
453 }
454
455 if ( isset( $flag['width'] ) ) {
456 $flag['width'] = absint( $flag['width'] );
457 }
458
459 if ( isset( $flag['height'] ) ) {
460 $flag['height'] = absint( $flag['height'] );
461 }
462
463 return $flag;
464 }
465
466 /**
467 * Returns HTML code for flag.
468 *
469 * @since 2.7
470 * @since 3.9 Removed param `$title` and pass `$alt` as second param.
471 *
472 * @param array $flag Flag properties: src, width and height.
473 * @param string $alt Optional alt attribute.
474 * @return string
475 *
476 * @phpstan-param array{
477 * src: string,
478 * width?: int|numeric-string,
479 * height?: int|numeric-string
480 * } $flag
481 */
482 public static function get_flag_html( $flag, $alt = '' ) {
483 if ( empty( $flag['src'] ) ) {
484 return '';
485 }
486
487 // Backward compatibility.
488 if ( func_num_args() > 2 ) {
489 _deprecated_argument( __METHOD__ . '()', '3.9', 'The parameter `$title` has been removed and replaced by `$alt` in second position' );
490 /** @var string */
491 $alt = func_get_arg( 2 );
492 }
493
494 $alt_attr = empty( $alt ) ? '' : sprintf( ' alt="%s"', esc_attr( $alt ) );
495 $width_attr = empty( $flag['width'] ) ? '' : sprintf( ' width="%s"', (int) $flag['width'] );
496 $height_attr = empty( $flag['height'] ) ? '' : sprintf( ' height="%s"', (int) $flag['height'] );
497
498 $style = '';
499 $sizes = array_intersect_key( $flag, array_flip( array( 'width', 'height' ) ) );
500
501 if ( ! empty( $sizes ) ) {
502 array_walk(
503 $sizes,
504 function ( &$value, $key ) {
505 $value = sprintf( '%s: %dpx;', esc_attr( $key ), (int) $value );
506 }
507 );
508 $style = sprintf( ' style="%s"', implode( ' ', $sizes ) );
509 }
510
511 return sprintf(
512 '<img src="%s"%s%s%s%s />',
513 $flag['src'],
514 $alt_attr,
515 $width_attr,
516 $height_attr,
517 $style
518 );
519 }
520
521 /**
522 * Returns the language flag or the language slug if there is no flag.
523 *
524 * @since 3.9
525 *
526 * @param string $mode Optional. Allows to modify the markup depending on how the flag is used. Possible values are:
527 * - Empty string: the flag can be seen by screen readers,
528 * - `aria-hidden`: the flag is hidden from screen readers: it is preceded or followed by a
529 * text (language name for example) that would make the information redundant.
530 * Default is an empty string.
531 * @return string
532 *
533 * @phpstan-param ''|'aria-hidden' $mode
534 */
535 public function get_admin_flag( string $mode = '' ): string {
536 return $this->admin_flag[ $mode ];
537 }
538
539 /**
540 * Returns the html of the custom flag if any, or the default flag otherwise.
541 *
542 * @since 2.8
543 * @since 3.5.3 Added the `$alt` parameter.
544 *
545 * @param string $alt Whether or not the alternative text should be set. Accepts 'alt' and 'no-alt'.
546 *
547 * @return string
548 *
549 * @phpstan-param 'alt'|'no-alt' $alt
550 */
551 public function get_display_flag( $alt = 'alt' ) {
552 $flag = empty( $this->custom_flag ) ? $this->flag : $this->custom_flag;
553
554 if ( 'alt' === $alt ) {
555 return $flag;
556 }
557
558 return (string) preg_replace( '/(?<=\salt=\")([^"]+)(?=\")/', '', $flag );
559 }
560
561 /**
562 * Returns the url of the custom flag if any, or the default flag otherwise.
563 *
564 * @since 2.8
565 *
566 * @return string
567 */
568 public function get_display_flag_url() {
569 $flag_url = empty( $this->custom_flag_url ) ? $this->flag_url : $this->custom_flag_url;
570
571 /**
572 * Filters `flag_url` property.
573 *
574 * @since 3.4.4
575 *
576 * @param string $flag_url Flag URL.
577 * @param PLL_Language $language Current `PLL_language` instance.
578 */
579 return apply_filters( 'pll_language_flag_url', $flag_url, $this );
580 }
581
582 /**
583 * Updates post and term count.
584 *
585 * @since 1.2
586 *
587 * @return void
588 */
589 public function update_count() {
590 foreach ( $this->term_props as $taxonomy => $props ) {
591 wp_update_term_count( $props['term_taxonomy_id'], $taxonomy );
592 }
593 }
594
595 /**
596 * Returns the language locale.
597 * Converts WP locales to W3C valid locales for display.
598 *
599 * @since 1.8
600 *
601 * @param string $filter Either 'display' or 'raw', defaults to raw.
602 * @return string
603 *
604 * @phpstan-param 'display'|'raw' $filter
605 * @phpstan-return non-empty-string
606 */
607 public function get_locale( $filter = 'raw' ) {
608 return 'display' === $filter ? $this->w3c : $this->locale;
609 }
610
611 /**
612 * Returns the values of this instance's properties, which can be filtered if required.
613 *
614 * @since 3.4
615 *
616 * @param string $context Whether or not properties should be filtered. Accepts `db` or `display`.
617 * Default to `display` which filters some properties.
618 *
619 * @return array Array of language object properties.
620 *
621 * @phpstan-return LanguageData
622 */
623 public function to_array( $context = 'display' ) {
624 $language = get_object_vars( $this );
625 unset( $language['admin_flag'] );
626
627 if ( 'db' !== $context ) {
628 $language['home_url'] = $this->get_home_url();
629 $language['search_url'] = $this->get_search_url();
630 }
631
632 /** @phpstan-var LanguageData $language */
633 return $language;
634 }
635
636 /**
637 * Converts current `PLL_language` into a `stdClass` object. Mostly used to allow dynamic properties.
638 *
639 * @since 3.4
640 *
641 * @return stdClass Converted `PLL_Language` object.
642 */
643 public function to_std_class() {
644 return (object) $this->to_array();
645 }
646
647 /**
648 * Returns language's home URL. Takes care to render it dynamically if no cache is allowed.
649 *
650 * @since 3.4
651 *
652 * @return string Language home URL.
653 */
654 public function get_home_url() {
655 if ( ! pll_get_constant( 'PLL_CACHE_LANGUAGES', true ) || ! pll_get_constant( 'PLL_CACHE_HOME_URL', true ) ) {
656 /**
657 * Filters current `PLL_Language` instance `home_url` property.
658 *
659 * @since 3.4.4
660 *
661 * @param string $home_url The `home_url` prop.
662 * @param array $language Current Array of `PLL_Language` properties.
663 */
664 return apply_filters( 'pll_language_home_url', $this->home_url, $this->to_array( 'db' ) );
665 }
666
667 return $this->home_url;
668 }
669
670 /**
671 * Returns language's search URL. Takes care to render it dynamically if no cache is allowed.
672 *
673 * @since 3.4
674 *
675 * @return string Language search URL.
676 */
677 public function get_search_url() {
678 if ( ! pll_get_constant( 'PLL_CACHE_LANGUAGES', true ) || ! pll_get_constant( 'PLL_CACHE_HOME_URL', true ) ) {
679 /**
680 * Filters current `PLL_Language` instance `search_url` property.
681 *
682 * @since 3.4.4
683 *
684 * @param string $search_url The `search_url` prop.
685 * @param array $language Current Array of `PLL_Language` properties.
686 */
687 return apply_filters( 'pll_language_search_url', $this->search_url, $this->to_array( 'db' ) );
688 }
689
690 return $this->search_url;
691 }
692
693 /**
694 * Returns the value of a language property.
695 * This is handy to get a property's value without worrying about triggering a deprecation warning or anything.
696 *
697 * @since 3.4
698 *
699 * @param string $property A property name. A composite value can be used for language term property values, in the
700 * form of `{language_taxonomy_name}:{property_name}` (see {@see PLL_Language::get_tax_prop()}
701 * for the possible values). Ex: `term_language:term_taxonomy_id`.
702 * @return string|int|bool|string[] The requested property for the language, `false` if the property doesn't exist.
703 *
704 * @phpstan-return (
705 * $property is 'slug' ? non-empty-string : string|int|bool|list<non-empty-string>
706 * )
707 */
708 public function get_prop( $property ) {
709 // Deprecated property.
710 if ( $this->is_deprecated_term_property( $property ) ) {
711 return $this->get_deprecated_term_property( $property );
712 }
713
714 if ( $this->is_deprecated_url_property( $property ) ) {
715 return $this->get_deprecated_url_property( $property );
716 }
717
718 // Composite property like 'term_language:term_taxonomy_id'.
719 if ( preg_match( '/^(?<tax>.{1,32}):(?<field>term_id|term_taxonomy_id|count)$/', $property, $matches ) ) {
720 /** @var array{tax:non-empty-string, field:'term_id'|'term_taxonomy_id'|'count'} $matches */
721 return $this->get_tax_prop( $matches['tax'], $matches['field'] );
722 }
723
724 return $this->$property ?? false;
725 }
726
727 /**
728 * Prepares a SVG image for use in data uri.
729 *
730 * @see https://codepen.io/tigt/post/optimizing-svgs-in-data-uris.
731 * @since 3.9
732 *
733 * @param string $svg A string representing an SVG image.
734 * @return string Encode SVG.
735 */
736 protected static function encode_svg( string $svg ): string {
737 $to_replace = array(
738 '"' => "'",
739 '<' => '%3C',
740 '>' => '%3E',
741 '#' => '%23',
742 "\n" => '',
743 "\r" => '',
744 );
745 return str_replace( array_keys( $to_replace ), array_values( $to_replace ), $svg );
746 }
747 }
748