|
1 <?php |
|
2 /** |
|
3 * Locale API: WP_Textdomain_Registry class. |
|
4 * |
|
5 * This file uses rtrim() instead of untrailingslashit() and trailingslashit() |
|
6 * to avoid formatting.php dependency. |
|
7 * |
|
8 * @package WordPress |
|
9 * @subpackage i18n |
|
10 * @since 6.1.0 |
|
11 */ |
|
12 |
|
13 /** |
|
14 * Core class used for registering text domains. |
|
15 * |
|
16 * @since 6.1.0 |
|
17 */ |
|
18 #[AllowDynamicProperties] |
|
19 class WP_Textdomain_Registry { |
|
20 /** |
|
21 * List of domains and all their language directory paths for each locale. |
|
22 * |
|
23 * @since 6.1.0 |
|
24 * |
|
25 * @var array |
|
26 */ |
|
27 protected $all = array(); |
|
28 |
|
29 /** |
|
30 * List of domains and their language directory path for the current (most recent) locale. |
|
31 * |
|
32 * @since 6.1.0 |
|
33 * |
|
34 * @var array |
|
35 */ |
|
36 protected $current = array(); |
|
37 |
|
38 /** |
|
39 * List of domains and their custom language directory paths. |
|
40 * |
|
41 * @see load_plugin_textdomain() |
|
42 * @see load_theme_textdomain() |
|
43 * |
|
44 * @since 6.1.0 |
|
45 * |
|
46 * @var array |
|
47 */ |
|
48 protected $custom_paths = array(); |
|
49 |
|
50 /** |
|
51 * Holds a cached list of available .mo files to improve performance. |
|
52 * |
|
53 * @since 6.1.0 |
|
54 * @since 6.5.0 This property is no longer used. |
|
55 * |
|
56 * @var array |
|
57 * |
|
58 * @deprecated |
|
59 */ |
|
60 protected $cached_mo_files = array(); |
|
61 |
|
62 /** |
|
63 * Holds a cached list of domains with translations to improve performance. |
|
64 * |
|
65 * @since 6.2.0 |
|
66 * |
|
67 * @var string[] |
|
68 */ |
|
69 protected $domains_with_translations = array(); |
|
70 |
|
71 /** |
|
72 * Initializes the registry. |
|
73 * |
|
74 * Hooks into the {@see 'upgrader_process_complete'} filter |
|
75 * to invalidate MO files caches. |
|
76 * |
|
77 * @since 6.5.0 |
|
78 */ |
|
79 public function init() { |
|
80 add_action( 'upgrader_process_complete', array( $this, 'invalidate_mo_files_cache' ), 10, 2 ); |
|
81 } |
|
82 |
|
83 /** |
|
84 * Returns the languages directory path for a specific domain and locale. |
|
85 * |
|
86 * @since 6.1.0 |
|
87 * |
|
88 * @param string $domain Text domain. |
|
89 * @param string $locale Locale. |
|
90 * |
|
91 * @return string|false Languages directory path or false if there is none available. |
|
92 */ |
|
93 public function get( $domain, $locale ) { |
|
94 $path = $this->all[ $domain ][ $locale ] ?? $this->get_path_from_lang_dir( $domain, $locale ); |
|
95 |
|
96 /** |
|
97 * Filters the determined languages directory path for a specific domain and locale. |
|
98 * |
|
99 * @since 6.6.0 |
|
100 * |
|
101 * @param string|false $path Languages directory path for the given domain and locale. |
|
102 * @param string $domain Text domain. |
|
103 * @param string $locale Locale. |
|
104 */ |
|
105 return apply_filters( 'lang_dir_for_domain', $path, $domain, $locale ); |
|
106 } |
|
107 |
|
108 /** |
|
109 * Determines whether any MO file paths are available for the domain. |
|
110 * |
|
111 * This is the case if a path has been set for the current locale, |
|
112 * or if there is no information stored yet, in which case |
|
113 * {@see _load_textdomain_just_in_time()} will fetch the information first. |
|
114 * |
|
115 * @since 6.1.0 |
|
116 * |
|
117 * @param string $domain Text domain. |
|
118 * @return bool Whether any MO file paths are available for the domain. |
|
119 */ |
|
120 public function has( $domain ) { |
|
121 return ( |
|
122 isset( $this->current[ $domain ] ) || |
|
123 empty( $this->all[ $domain ] ) || |
|
124 in_array( $domain, $this->domains_with_translations, true ) |
|
125 ); |
|
126 } |
|
127 |
|
128 /** |
|
129 * Sets the language directory path for a specific domain and locale. |
|
130 * |
|
131 * Also sets the 'current' property for direct access |
|
132 * to the path for the current (most recent) locale. |
|
133 * |
|
134 * @since 6.1.0 |
|
135 * |
|
136 * @param string $domain Text domain. |
|
137 * @param string $locale Locale. |
|
138 * @param string|false $path Language directory path or false if there is none available. |
|
139 */ |
|
140 public function set( $domain, $locale, $path ) { |
|
141 $this->all[ $domain ][ $locale ] = $path ? rtrim( $path, '/' ) . '/' : false; |
|
142 $this->current[ $domain ] = $this->all[ $domain ][ $locale ]; |
|
143 } |
|
144 |
|
145 /** |
|
146 * Sets the custom path to the plugin's/theme's languages directory. |
|
147 * |
|
148 * Used by {@see load_plugin_textdomain()} and {@see load_theme_textdomain()}. |
|
149 * |
|
150 * @since 6.1.0 |
|
151 * |
|
152 * @param string $domain Text domain. |
|
153 * @param string $path Language directory path. |
|
154 */ |
|
155 public function set_custom_path( $domain, $path ) { |
|
156 $this->custom_paths[ $domain ] = rtrim( $path, '/' ); |
|
157 } |
|
158 |
|
159 /** |
|
160 * Retrieves translation files from the specified path. |
|
161 * |
|
162 * Allows early retrieval through the {@see 'pre_get_mo_files_from_path'} filter to optimize |
|
163 * performance, especially in directories with many files. |
|
164 * |
|
165 * @since 6.5.0 |
|
166 * |
|
167 * @param string $path The directory path to search for translation files. |
|
168 * @return array Array of translation file paths. Can contain .mo and .l10n.php files. |
|
169 */ |
|
170 public function get_language_files_from_path( $path ) { |
|
171 $path = rtrim( $path, '/' ) . '/'; |
|
172 |
|
173 /** |
|
174 * Filters the translation files retrieved from a specified path before the actual lookup. |
|
175 * |
|
176 * Returning a non-null value from the filter will effectively short-circuit |
|
177 * the MO files lookup, returning that value instead. |
|
178 * |
|
179 * This can be useful in situations where the directory contains a large number of files |
|
180 * and the default glob() function becomes expensive in terms of performance. |
|
181 * |
|
182 * @since 6.5.0 |
|
183 * |
|
184 * @param null|array $files List of translation files. Default null. |
|
185 * @param string $path The path from which translation files are being fetched. |
|
186 **/ |
|
187 $files = apply_filters( 'pre_get_language_files_from_path', null, $path ); |
|
188 |
|
189 if ( null !== $files ) { |
|
190 return $files; |
|
191 } |
|
192 |
|
193 $cache_key = md5( $path ); |
|
194 $files = wp_cache_get( $cache_key, 'translation_files' ); |
|
195 |
|
196 if ( false === $files ) { |
|
197 $files = glob( $path . '*.mo' ); |
|
198 if ( false === $files ) { |
|
199 $files = array(); |
|
200 } |
|
201 |
|
202 $php_files = glob( $path . '*.l10n.php' ); |
|
203 if ( is_array( $php_files ) ) { |
|
204 $files = array_merge( $files, $php_files ); |
|
205 } |
|
206 |
|
207 wp_cache_set( $cache_key, $files, 'translation_files', HOUR_IN_SECONDS ); |
|
208 } |
|
209 |
|
210 return $files; |
|
211 } |
|
212 |
|
213 /** |
|
214 * Invalidate the cache for .mo files. |
|
215 * |
|
216 * This function deletes the cache entries related to .mo files when triggered |
|
217 * by specific actions, such as the completion of an upgrade process. |
|
218 * |
|
219 * @since 6.5.0 |
|
220 * |
|
221 * @param WP_Upgrader $upgrader Unused. WP_Upgrader instance. In other contexts this might be a |
|
222 * Theme_Upgrader, Plugin_Upgrader, Core_Upgrade, or Language_Pack_Upgrader instance. |
|
223 * @param array $hook_extra { |
|
224 * Array of bulk item update data. |
|
225 * |
|
226 * @type string $action Type of action. Default 'update'. |
|
227 * @type string $type Type of update process. Accepts 'plugin', 'theme', 'translation', or 'core'. |
|
228 * @type bool $bulk Whether the update process is a bulk update. Default true. |
|
229 * @type array $plugins Array of the basename paths of the plugins' main files. |
|
230 * @type array $themes The theme slugs. |
|
231 * @type array $translations { |
|
232 * Array of translations update data. |
|
233 * |
|
234 * @type string $language The locale the translation is for. |
|
235 * @type string $type Type of translation. Accepts 'plugin', 'theme', or 'core'. |
|
236 * @type string $slug Text domain the translation is for. The slug of a theme/plugin or |
|
237 * 'default' for core translations. |
|
238 * @type string $version The version of a theme, plugin, or core. |
|
239 * } |
|
240 * } |
|
241 */ |
|
242 public function invalidate_mo_files_cache( $upgrader, $hook_extra ) { |
|
243 if ( |
|
244 ! isset( $hook_extra['type'] ) || |
|
245 'translation' !== $hook_extra['type'] || |
|
246 array() === $hook_extra['translations'] |
|
247 ) { |
|
248 return; |
|
249 } |
|
250 |
|
251 $translation_types = array_unique( wp_list_pluck( $hook_extra['translations'], 'type' ) ); |
|
252 |
|
253 foreach ( $translation_types as $type ) { |
|
254 switch ( $type ) { |
|
255 case 'plugin': |
|
256 wp_cache_delete( md5( WP_LANG_DIR . '/plugins/' ), 'translation_files' ); |
|
257 break; |
|
258 case 'theme': |
|
259 wp_cache_delete( md5( WP_LANG_DIR . '/themes/' ), 'translation_files' ); |
|
260 break; |
|
261 default: |
|
262 wp_cache_delete( md5( WP_LANG_DIR . '/' ), 'translation_files' ); |
|
263 break; |
|
264 } |
|
265 } |
|
266 } |
|
267 |
|
268 /** |
|
269 * Returns possible language directory paths for a given text domain. |
|
270 * |
|
271 * @since 6.2.0 |
|
272 * |
|
273 * @param string $domain Text domain. |
|
274 * @return string[] Array of language directory paths. |
|
275 */ |
|
276 private function get_paths_for_domain( $domain ) { |
|
277 $locations = array( |
|
278 WP_LANG_DIR . '/plugins', |
|
279 WP_LANG_DIR . '/themes', |
|
280 ); |
|
281 |
|
282 if ( isset( $this->custom_paths[ $domain ] ) ) { |
|
283 $locations[] = $this->custom_paths[ $domain ]; |
|
284 } |
|
285 |
|
286 return $locations; |
|
287 } |
|
288 |
|
289 /** |
|
290 * Gets the path to the language directory for the current domain and locale. |
|
291 * |
|
292 * Checks the plugins and themes language directories as well as any |
|
293 * custom directory set via {@see load_plugin_textdomain()} or {@see load_theme_textdomain()}. |
|
294 * |
|
295 * @since 6.1.0 |
|
296 * |
|
297 * @see _get_path_to_translation_from_lang_dir() |
|
298 * |
|
299 * @param string $domain Text domain. |
|
300 * @param string $locale Locale. |
|
301 * @return string|false Language directory path or false if there is none available. |
|
302 */ |
|
303 private function get_path_from_lang_dir( $domain, $locale ) { |
|
304 $locations = $this->get_paths_for_domain( $domain ); |
|
305 |
|
306 $found_location = false; |
|
307 |
|
308 foreach ( $locations as $location ) { |
|
309 $files = $this->get_language_files_from_path( $location ); |
|
310 |
|
311 $mo_path = "$location/$domain-$locale.mo"; |
|
312 $php_path = "$location/$domain-$locale.l10n.php"; |
|
313 |
|
314 foreach ( $files as $file_path ) { |
|
315 if ( |
|
316 ! in_array( $domain, $this->domains_with_translations, true ) && |
|
317 str_starts_with( str_replace( "$location/", '', $file_path ), "$domain-" ) |
|
318 ) { |
|
319 $this->domains_with_translations[] = $domain; |
|
320 } |
|
321 |
|
322 if ( $file_path === $mo_path || $file_path === $php_path ) { |
|
323 $found_location = rtrim( $location, '/' ) . '/'; |
|
324 break 2; |
|
325 } |
|
326 } |
|
327 } |
|
328 |
|
329 if ( $found_location ) { |
|
330 $this->set( $domain, $locale, $found_location ); |
|
331 |
|
332 return $found_location; |
|
333 } |
|
334 |
|
335 /* |
|
336 * If no path is found for the given locale and a custom path has been set |
|
337 * using load_plugin_textdomain/load_theme_textdomain, use that one. |
|
338 */ |
|
339 if ( 'en_US' !== $locale && isset( $this->custom_paths[ $domain ] ) ) { |
|
340 $fallback_location = rtrim( $this->custom_paths[ $domain ], '/' ) . '/'; |
|
341 $this->set( $domain, $locale, $fallback_location ); |
|
342 return $fallback_location; |
|
343 } |
|
344 |
|
345 $this->set( $domain, $locale, false ); |
|
346 |
|
347 return false; |
|
348 } |
|
349 } |