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-factory.php

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

357 lines 9.9 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 use WP_Syntex\Polylang\Options\Options;
7
8 /**
9 * PLL_Language factory.
10 *
11 * @since 3.4
12 *
13 * @phpstan-import-type LanguageData from PLL_Language
14 */
15 class PLL_Language_Factory {
16 /**
17 * Predefined languages.
18 *
19 * @var array[]
20 *
21 * @phpstan-var array<string, array<string, string>>
22 */
23 private static $languages;
24
25 /**
26 * Polylang's options.
27 *
28 * @var Options
29 */
30 private $options;
31
32 /**
33 * Constructor.
34 *
35 * @since 3.4
36 * @since 3.7 The `$options` parameter is an instance of `Options`.
37 *
38 * @param Options $options Poylang's options.
39 * @return void
40 */
41 public function __construct( Options $options ) {
42 $this->options = $options;
43 }
44
45 /**
46 * Returns a language object matching the given data, looking up in cached transient.
47 *
48 * @since 3.4
49 *
50 * @param array $language_data Language object properties stored as an array. See `PLL_Language::__construct()`
51 * for information on accepted properties.
52 *
53 * @return PLL_Language A language object if given data pass sanitization.
54 *
55 * @phpstan-param LanguageData $language_data
56 */
57 public function get( $language_data ) {
58 return new PLL_Language( $this->sanitize_data( $language_data ) );
59 }
60
61 /**
62 * Returns a language object based on terms.
63 *
64 * @since 3.4
65 *
66 * @param WP_Term[] $terms List of language terms, with the language taxonomy names as array keys.
67 * `language` is a mandatory key for the object to be created,
68 * `term_language` should be too in a fully operational environment.
69 * @return PLL_Language|null Language object on success, `null` on failure.
70 *
71 * @phpstan-param array{language?:WP_Term}&array<string, WP_Term> $terms
72 */
73 public function get_from_terms( array $terms ) {
74 if ( ! isset( $terms['language'] ) ) {
75 return null;
76 }
77
78 $languages = $this->get_languages();
79 $data = array(
80 'name' => $terms['language']->name,
81 'slug' => $terms['language']->slug,
82 'term_group' => $terms['language']->term_group,
83 'term_props' => array(),
84 'is_default' => $this->options['default_lang'] === $terms['language']->slug,
85 'flag_code' => '',
86 'locale' => '',
87 );
88
89 foreach ( $terms as $term ) {
90 $data['term_props'][ $term->taxonomy ] = array(
91 'term_id' => $term->term_id,
92 'term_taxonomy_id' => $term->term_taxonomy_id,
93 'count' => $term->count,
94 );
95 }
96
97 // The description fields can contain any property.
98 $description = maybe_unserialize( $terms['language']->description );
99
100 if ( is_array( $description ) ) {
101 $description = array_intersect_key(
102 $description,
103 array( 'locale' => null, 'rtl' => null, 'flag_code' => null, 'active' => null, 'fallbacks' => null )
104 );
105
106 foreach ( $description as $prop => $value ) {
107 if ( 'rtl' === $prop ) {
108 $data['is_rtl'] = $value;
109 } else {
110 $data[ $prop ] = $value;
111 }
112 }
113 }
114
115 if ( ! empty( $data['locale'] ) ) {
116 if ( isset( $languages[ $data['locale'] ]['w3c'] ) ) {
117 $data['w3c'] = $languages[ $data['locale'] ]['w3c'];
118 } else {
119 $data['w3c'] = str_replace( '_', '-', $data['locale'] );
120 }
121
122 if ( isset( $languages[ $data['locale'] ]['facebook'] ) ) {
123 $data['facebook'] = $languages[ $data['locale'] ]['facebook'];
124 }
125 }
126
127 $flag_props = $this->get_flag( $data['flag_code'], $data['name'], $data['slug'], $data['locale'] );
128 $data = array_merge( $data, $flag_props );
129
130 $additional_data = array();
131 /**
132 * Filters additional data to add to the language before it is created.
133 *
134 * `home_url`, `search_url`, `page_on_front` and `page_for_posts` are only allowed.
135 *
136 * @since 3.4
137 *
138 * @param array $additional_data.
139 * @param array $data Language data.
140 *
141 * @phpstan-param array<non-empty-string, mixed> $additional_data
142 * @phpstan-param non-empty-array<non-empty-string, mixed> $data
143 */
144 $additional_data = apply_filters( 'pll_additional_language_data', $additional_data, $data );
145
146 $allowed_additional_data = array(
147 'home_url' => '',
148 'search_url' => '',
149 'page_on_front' => 0,
150 'page_for_posts' => 0,
151 );
152
153 $data = array_merge( $data, array_intersect_key( $additional_data, $allowed_additional_data ) );
154
155 return new PLL_Language( $this->sanitize_data( $data ) );
156 }
157
158 /**
159 * Sanitizes data, to be ready to be used in the constructor.
160 * This doesn't verify that the language terms exist.
161 *
162 * @since 3.4
163 *
164 * @param array $data Data to process. See `PLL_Language::__construct()` for information on accepted data.
165 * @return array Sanitized Data.
166 *
167 * @phpstan-return LanguageData
168 */
169 private function sanitize_data( array $data ) {
170 foreach ( $data['term_props'] as $tax => $props ) {
171 $data['term_props'][ $tax ] = array_map( 'absint', $props );
172 }
173
174 $data['is_rtl'] = ! empty( $data['is_rtl'] ) ? 1 : 0;
175
176 $positive_fields = array( 'term_group', 'page_on_front', 'page_for_posts' );
177
178 foreach ( $positive_fields as $field ) {
179 $data[ $field ] = ! empty( $data[ $field ] ) ? absint( $data[ $field ] ) : 0;
180 }
181
182 $data['active'] = isset( $data['active'] ) ? (bool) $data['active'] : true;
183
184 if ( array_key_exists( 'fallbacks', $data ) && ! is_array( $data['fallbacks'] ) ) {
185 unset( $data['fallbacks'] );
186 }
187
188 $data['admin_flag'] = array();
189
190 if ( ! empty( $data['flag'] ) ) {
191 $data['admin_flag']['aria-hidden'] = str_replace( '<img', '<img aria-hidden="true" tabindex="-1"', $data['flag'] );
192 $data['admin_flag'][''] = str_replace(
193 '<img',
194 sprintf(
195 '<img lang="%1$s" dir="%2$s"',
196 esc_attr( $data['w3c'] ),
197 $data['is_rtl'] ? 'rtl' : 'ltr'
198 ),
199 $data['flag']
200 );
201 } else {
202 $data['admin_flag']['aria-hidden'] = sprintf( '<abbr aria-hidden="true" tabindex="-1">%s</abbr>', esc_html( strtoupper( $data['slug'] ) ) );
203 $data['admin_flag'][''] = sprintf(
204 '<abbr title="%1$s" lang="%2$s" dir="%3$s">%4$s</abbr>',
205 esc_attr( $data['name'] ),
206 esc_attr( $data['w3c'] ),
207 $data['is_rtl'] ? 'rtl' : 'ltr',
208 esc_html( strtoupper( $data['slug'] ) )
209 );
210 }
211
212 /**
213 * @var LanguageData
214 */
215 return $data;
216 }
217
218 /**
219 * Returns predefined languages data.
220 *
221 * @since 3.4
222 *
223 * @return array[]
224 *
225 * @phpstan-return array<string, array<string, string>>
226 */
227 private function get_languages() {
228 if ( empty( self::$languages ) ) {
229 self::$languages = include POLYLANG_DIR . '/src/settings/languages.php';
230 }
231
232 return self::$languages;
233 }
234
235
236 /**
237 * Creates flag_url and flag language properties. Also takes care of custom flag.
238 *
239 * @since 1.2
240 * @since 3.4 Moved from `PLL_Language` to `PLL_Language_Factory` and renamed
241 * in favor of `get_flag()` (formerly `set_flag()`).
242 *
243 * @param string $flag_code Flag code.
244 * @param string $name Language name.
245 * @param string $slug Language slug.
246 * @param string $locale Language locale.
247 * @return array {
248 * Array of the flag properties.
249 * @type string $flag_url URL of the flag.
250 * @type string $flag HTML markup of the flag.
251 * @type string $custom_flag_url Optional. URL of the custom flag if it exists.
252 * @type string $custom_flag Optional. HTML markup of the custom flag if it exists.
253 * }
254 *
255 * @phpstan-return array{
256 * flag_url: string,
257 * flag: string,
258 * custom_flag_url?: non-empty-string,
259 * custom_flag?: non-empty-string
260 * }
261 */
262 private function get_flag( $flag_code, $name, $slug, $locale ) {
263 $flags = array(
264 'flag' => PLL_Language::get_flag_information( $flag_code ),
265 );
266
267 // Custom flags?
268 $directories = array(
269 PLL_LOCAL_DIR,
270 get_stylesheet_directory() . '/polylang',
271 get_template_directory() . '/polylang',
272 );
273
274 foreach ( $directories as $dir ) {
275 if ( is_readable( $file = "{$dir}/{$locale}.png" ) || is_readable( $file = "{$dir}/{$locale}.jpg" ) || is_readable( $file = "{$dir}/{$locale}.jpeg" ) || is_readable( $file = "{$dir}/{$locale}.svg" ) ) {
276 $flags['custom_flag'] = array(
277 'url' => content_url( '/' . str_replace( WP_CONTENT_DIR, '', $file ) ),
278 );
279 break;
280 }
281 }
282
283 /**
284 * Filters the custom flag information.
285 *
286 * @since 2.4
287 *
288 * @param array|null $flag {
289 * Information about the custom flag.
290 *
291 * @type string $url Flag url.
292 * @type string $src Optional, src attribute value if different of the url, for example if base64 encoded.
293 * @type int $width Optional, flag width in pixels.
294 * @type int $height Optional, flag height in pixels.
295 * }
296 * @param string $code Flag code.
297 */
298 $flags['custom_flag'] = apply_filters( 'pll_custom_flag', empty( $flags['custom_flag'] ) ? null : $flags['custom_flag'], $flag_code );
299
300 if ( ! empty( $flags['custom_flag']['url'] ) ) {
301 if ( empty( $flags['custom_flag']['src'] ) ) {
302 $flags['custom_flag']['src'] = esc_url( set_url_scheme( $flags['custom_flag']['url'], 'relative' ) );
303 }
304
305 $flags['custom_flag']['url'] = sanitize_url( $flags['custom_flag']['url'] );
306 } else {
307 unset( $flags['custom_flag'] );
308 }
309
310 $return = array();
311
312 /**
313 * @var array{
314 * flag: array{
315 * url: string,
316 * src: string,
317 * width?: positive-int,
318 * height?: positive-int
319 * },
320 * custom_flag?: array{
321 * url: non-empty-string,
322 * src: non-empty-string,
323 * width?: positive-int,
324 * height?: positive-int
325 * }
326 * } $flags
327 */
328 foreach ( $flags as $key => $flag ) {
329 $return[ "{$key}_url" ] = $flag['url'];
330
331 /**
332 * Filters the html markup of a flag.
333 *
334 * @since 1.0.2
335 *
336 * @param string $flag Html markup of the flag or empty string.
337 * @param string $slug Language code.
338 */
339 $return[ $key ] = apply_filters(
340 'pll_get_flag',
341 PLL_Language::get_flag_html( $flag, $name ),
342 $slug
343 );
344 }
345
346 /**
347 * @var array{
348 * flag_url: string,
349 * flag: string,
350 * custom_flag_url?: non-empty-string,
351 * custom_flag?: non-empty-string
352 * } $return
353 */
354 return $return;
355 }
356 }
357