run:R W Run
7.85 KB
2026-03-11 16:18:51
R W Run
3.54 KB
2026-03-11 16:18:51
R W Run
148.33 KB
2026-03-11 16:18:51
R W Run
11.45 KB
2026-03-11 16:18:51
R W Run
3.58 KB
2026-03-11 16:18:51
R W Run
2.53 KB
2026-03-11 16:18:51
R W Run
2.6 KB
2026-03-11 16:18:51
R W Run
6.59 KB
2026-03-11 16:18:51
R W Run
14.83 KB
2026-03-11 16:18:51
R W Run
21.18 KB
2026-03-11 16:18:51
R W Run
48.13 KB
2026-03-11 16:18:51
R W Run
4.07 KB
2026-03-11 16:18:51
R W Run
5.3 KB
2026-03-11 16:18:51
R W Run
8.28 KB
2026-03-11 16:18:51
R W Run
26.73 KB
2026-03-11 16:18:51
R W Run
2.8 KB
2026-03-11 16:18:51
R W Run
15.2 KB
2026-03-11 16:18:51
R W Run
192.08 KB
2026-03-11 16:18:51
R W Run
11.77 KB
2026-03-11 16:18:51
R W Run
3.2 KB
2026-03-11 16:18:51
R W Run
22.89 KB
2026-03-11 16:18:51
R W Run
12.77 KB
2026-03-11 16:18:51
R W Run
4.08 KB
2026-03-11 16:18:51
R W Run
26.27 KB
2026-03-11 16:18:51
R W Run
4.97 KB
2026-03-11 16:18:51
R W Run
5.57 KB
2026-03-11 16:18:51
R W Run
13.93 KB
2026-03-11 16:18:51
R W Run
4.09 KB
2026-03-11 16:18:51
R W Run
6.79 KB
2026-03-11 16:18:51
R W Run
60.45 KB
2026-03-11 16:18:51
R W Run
32.4 KB
2026-03-11 16:18:51
R W Run
18.24 KB
2026-03-11 16:18:51
R W Run
66.01 KB
2026-03-11 16:18:51
R W Run
23.84 KB
2026-03-11 16:18:51
R W Run
17.72 KB
2026-03-11 16:18:51
R W Run
22.71 KB
2026-03-11 16:18:51
R W Run
18.05 KB
2026-03-11 16:18:51
R W Run
22.76 KB
2026-03-11 16:18:51
R W Run
7.34 KB
2026-03-11 16:18:51
R W Run
4.51 KB
2026-03-11 16:18:51
R W Run
9.02 KB
2026-03-11 16:18:51
R W Run
1.46 KB
2026-03-11 16:18:51
R W Run
51.76 KB
2026-03-11 16:18:51
R W Run
25.29 KB
2026-03-11 16:18:51
R W Run
21.61 KB
2026-03-11 16:18:51
R W Run
27.77 KB
2026-03-11 16:18:51
R W Run
15.35 KB
2026-03-11 16:18:51
R W Run
24.54 KB
2026-03-11 16:18:51
R W Run
56.44 KB
2026-03-11 16:18:51
R W Run
1.42 KB
2026-03-11 16:18:51
R W Run
63.66 KB
2026-03-11 16:18:51
R W Run
31.9 KB
2026-03-11 16:18:51
R W Run
14.44 KB
2026-03-11 16:18:51
R W Run
36.47 KB
2026-03-11 16:18:51
R W Run
14 KB
2026-03-11 16:18:51
R W Run
121.89 KB
2026-03-11 16:18:51
R W Run
6.26 KB
2026-03-11 16:18:51
R W Run
20.73 KB
2026-03-11 16:18:51
R W Run
15.23 KB
2026-03-11 16:18:51
R W Run
10.14 KB
2026-03-11 16:18:51
R W Run
6.94 KB
2026-03-11 16:18:51
R W Run
1.44 KB
2026-03-11 16:18:51
R W Run
46.85 KB
2026-03-11 16:18:51
R W Run
18.61 KB
2026-03-11 16:18:51
R W Run
6.08 KB
2026-03-11 16:18:51
R W Run
20.06 KB
2026-03-11 16:18:51
R W Run
5.73 KB
2026-03-11 16:18:51
R W Run
68.18 KB
2026-03-11 16:18:51
R W Run
40.8 KB
2026-03-11 16:18:51
R W Run
1.44 KB
2026-03-11 16:18:51
R W Run
25.26 KB
2026-03-11 16:18:51
R W Run
95.94 KB
2026-03-11 16:18:51
R W Run
43.12 KB
2026-03-11 16:18:51
R W Run
41.73 KB
2026-03-11 16:18:51
R W Run
6.46 KB
2026-03-11 16:18:51
R W Run
3.71 KB
2026-03-11 16:18:51
R W Run
116.31 KB
2026-03-11 16:18:51
R W Run
9.39 KB
2026-03-11 16:18:51
R W Run
64.34 KB
2026-03-11 16:18:51
R W Run
44.73 KB
2026-03-11 16:18:51
R W Run
1.27 KB
2026-03-11 16:18:51
R W Run
3.68 KB
2026-03-11 16:18:51
R W Run
33.53 KB
2026-03-11 16:18:51
R W Run
48.84 KB
2026-03-11 16:18:51
R W Run
26.35 KB
2026-03-11 16:18:51
R W Run
1.12 KB
2026-03-11 16:18:51
R W Run
4.19 KB
2026-03-11 16:18:51
R W Run
38.19 KB
2026-03-11 16:18:51
R W Run
91.33 KB
2026-03-11 16:18:51
R W Run
80.39 KB
2026-03-11 16:18:51
R W Run
32.67 KB
2026-03-11 16:18:51
R W Run
16.18 KB
2026-03-11 16:18:51
R W Run
44.46 KB
2026-03-11 16:18:51
R W Run
6.23 KB
2026-03-11 16:18:51
R W Run
8.23 KB
2026-03-11 16:18:51
R W Run
96.96 KB
2026-03-11 16:18:51
R W Run
6.83 KB
2026-03-11 16:18:51
R W Run
46.62 KB
2026-03-11 16:18:51
R W Run
10.82 KB
2026-03-11 16:18:51
R W Run
68.86 KB
2026-03-11 16:18:51
R W Run
33.63 KB
2026-03-11 16:18:51
R W Run
113.3 KB
2026-03-11 16:18:51
R W Run
22.98 KB
2026-03-11 16:18:51
R W Run
10.66 KB
2026-03-11 16:18:51
R W Run
error_log
📄class-language-pack-upgrader.php
1<?php
2/**
3 * Upgrade API: Language_Pack_Upgrader class
4 *
5 * @package WordPress
6 * @subpackage Upgrader
7 * @since 4.6.0
8 */
9
10/**
11 * Core class used for updating/installing language packs (translations)
12 * for plugins, themes, and core.
13 *
14 * @since 3.7.0
15 * @since 4.6.0 Moved to its own file from wp-admin/includes/class-wp-upgrader.php.
16 *
17 * @see WP_Upgrader
18 */
19class Language_Pack_Upgrader extends WP_Upgrader {
20
21 /**
22 * Result of the language pack upgrade.
23 *
24 * @since 3.7.0
25 * @var array|WP_Error $result
26 * @see WP_Upgrader::$result
27 */
28 public $result;
29
30 /**
31 * Whether a bulk upgrade/installation is being performed.
32 *
33 * @since 3.7.0
34 * @var bool $bulk
35 */
36 public $bulk = true;
37
38 /**
39 * Asynchronously upgrades language packs after other upgrades have been made.
40 *
41 * Hooked to the {@see 'upgrader_process_complete'} action by default.
42 *
43 * @since 3.7.0
44 *
45 * @param false|WP_Upgrader $upgrader Optional. WP_Upgrader instance or false. If `$upgrader` is
46 * a Language_Pack_Upgrader instance, the method will bail to
47 * avoid recursion. Otherwise unused. Default false.
48 */
49 public static function async_upgrade( $upgrader = false ) {
50 // Avoid recursion.
51 if ( $upgrader && $upgrader instanceof Language_Pack_Upgrader ) {
52 return;
53 }
54
55 // Nothing to do?
56 $language_updates = wp_get_translation_updates();
57 if ( ! $language_updates ) {
58 return;
59 }
60
61 /*
62 * Avoid messing with VCS installations, at least for now.
63 * Noted: this is not the ideal way to accomplish this.
64 */
65 $check_vcs = new WP_Automatic_Updater();
66 if ( $check_vcs->is_vcs_checkout( WP_CONTENT_DIR ) ) {
67 return;
68 }
69
70 foreach ( $language_updates as $key => $language_update ) {
71 $update = ! empty( $language_update->autoupdate );
72
73 /**
74 * Filters whether to asynchronously update translation for core, a plugin, or a theme.
75 *
76 * @since 4.0.0
77 *
78 * @param bool $update Whether to update.
79 * @param object $language_update The update offer.
80 */
81 $update = apply_filters( 'async_update_translation', $update, $language_update );
82
83 if ( ! $update ) {
84 unset( $language_updates[ $key ] );
85 }
86 }
87
88 if ( empty( $language_updates ) ) {
89 return;
90 }
91
92 // Re-use the automatic upgrader skin if the parent upgrader is using it.
93 if ( $upgrader && $upgrader->skin instanceof Automatic_Upgrader_Skin ) {
94 $skin = $upgrader->skin;
95 } else {
96 $skin = new Language_Pack_Upgrader_Skin(
97 array(
98 'skip_header_footer' => true,
99 )
100 );
101 }
102
103 $lp_upgrader = new Language_Pack_Upgrader( $skin );
104 $lp_upgrader->bulk_upgrade( $language_updates );
105 }
106
107 /**
108 * Initializes the upgrade strings.
109 *
110 * @since 3.7.0
111 */
112 public function upgrade_strings() {
113 $this->strings['starting_upgrade'] = __( 'Some of your translations need updating. Sit tight for a few more seconds while they are updated as well.' );
114 $this->strings['up_to_date'] = __( 'Your translations are all up to date.' );
115 $this->strings['no_package'] = __( 'Update package not available.' );
116 /* translators: %s: Package URL. */
117 $this->strings['downloading_package'] = sprintf( __( 'Downloading translation from %s&#8230;' ), '<span class="code pre">%s</span>' );
118 $this->strings['unpack_package'] = __( 'Unpacking the update&#8230;' );
119 $this->strings['process_failed'] = __( 'Translation update failed.' );
120 $this->strings['process_success'] = __( 'Translation updated successfully.' );
121 $this->strings['remove_old'] = __( 'Removing the old version of the translation&#8230;' );
122 $this->strings['remove_old_failed'] = __( 'Could not remove the old translation.' );
123 }
124
125 /**
126 * Upgrades a language pack.
127 *
128 * @since 3.7.0
129 *
130 * @param string|false $update Optional. Whether an update offer is available. Default false.
131 * @param array $args Optional. Other optional arguments, see
132 * Language_Pack_Upgrader::bulk_upgrade(). Default empty array.
133 * @return array|bool|WP_Error The result of the upgrade, or a WP_Error object instead.
134 */
135 public function upgrade( $update = false, $args = array() ) {
136 if ( $update ) {
137 $update = array( $update );
138 }
139
140 $results = $this->bulk_upgrade( $update, $args );
141
142 if ( ! is_array( $results ) ) {
143 return $results;
144 }
145
146 return $results[0];
147 }
148
149 /**
150 * Upgrades several language packs at once.
151 *
152 * @since 3.7.0
153 *
154 * @global WP_Filesystem_Base $wp_filesystem WordPress filesystem subclass.
155 *
156 * @param object[] $language_updates Optional. Array of language packs to update. See {@see wp_get_translation_updates()}.
157 * Default empty array.
158 * @param array $args {
159 * Other arguments for upgrading multiple language packs. Default empty array.
160 *
161 * @type bool $clear_update_cache Whether to clear the update cache when done.
162 * Default true.
163 * }
164 * @return array|bool|WP_Error Will return an array of results, or true if there are no updates,
165 * false or WP_Error for initial errors.
166 */
167 public function bulk_upgrade( $language_updates = array(), $args = array() ) {
168 global $wp_filesystem;
169
170 $defaults = array(
171 'clear_update_cache' => true,
172 );
173 $parsed_args = wp_parse_args( $args, $defaults );
174
175 $this->init();
176 $this->upgrade_strings();
177
178 if ( ! $language_updates ) {
179 $language_updates = wp_get_translation_updates();
180 }
181
182 if ( empty( $language_updates ) ) {
183 $this->skin->header();
184 $this->skin->set_result( true );
185 $this->skin->feedback( 'up_to_date' );
186 $this->skin->bulk_footer();
187 $this->skin->footer();
188 return true;
189 }
190
191 if ( 'upgrader_process_complete' === current_filter() ) {
192 $this->skin->feedback( 'starting_upgrade' );
193 }
194
195 // Remove any existing upgrade filters from the plugin/theme upgraders #WP29425 & #WP29230.
196 remove_all_filters( 'upgrader_pre_install' );
197 remove_all_filters( 'upgrader_clear_destination' );
198 remove_all_filters( 'upgrader_post_install' );
199 remove_all_filters( 'upgrader_source_selection' );
200
201 add_filter( 'upgrader_source_selection', array( $this, 'check_package' ), 10, 2 );
202
203 $this->skin->header();
204
205 // Connect to the filesystem first.
206 $res = $this->fs_connect( array( WP_CONTENT_DIR, WP_LANG_DIR ) );
207 if ( ! $res ) {
208 $this->skin->footer();
209 return false;
210 }
211
212 $results = array();
213
214 $this->update_count = count( $language_updates );
215 $this->update_current = 0;
216
217 /*
218 * The filesystem's mkdir() is not recursive. Make sure WP_LANG_DIR exists,
219 * as we then may need to create a /plugins or /themes directory inside of it.
220 */
221 $remote_destination = $wp_filesystem->find_folder( WP_LANG_DIR );
222 if ( ! $wp_filesystem->exists( $remote_destination ) ) {
223 if ( ! $wp_filesystem->mkdir( $remote_destination, FS_CHMOD_DIR ) ) {
224 return new WP_Error( 'mkdir_failed_lang_dir', $this->strings['mkdir_failed'], $remote_destination );
225 }
226 }
227
228 $language_updates_results = array();
229
230 foreach ( $language_updates as $language_update ) {
231
232 $this->skin->language_update = $language_update;
233
234 $destination = WP_LANG_DIR;
235 if ( 'plugin' === $language_update->type ) {
236 $destination .= '/plugins';
237 } elseif ( 'theme' === $language_update->type ) {
238 $destination .= '/themes';
239 }
240
241 ++$this->update_current;
242
243 $options = array(
244 'package' => $language_update->package,
245 'destination' => $destination,
246 'clear_destination' => true,
247 'abort_if_destination_exists' => false, // We expect the destination to exist.
248 'clear_working' => true,
249 'is_multi' => true,
250 'hook_extra' => array(
251 'language_update_type' => $language_update->type,
252 'language_update' => $language_update,
253 ),
254 );
255
256 $result = $this->run( $options );
257
258 $results[] = $this->result;
259
260 // Prevent credentials auth screen from displaying multiple times.
261 if ( false === $result ) {
262 break;
263 }
264
265 $language_updates_results[] = array(
266 'language' => $language_update->language,
267 'type' => $language_update->type,
268 'slug' => isset( $language_update->slug ) ? $language_update->slug : 'default',
269 'version' => $language_update->version,
270 );
271 }
272
273 // Remove upgrade hooks which are not required for translation updates.
274 remove_action( 'upgrader_process_complete', array( 'Language_Pack_Upgrader', 'async_upgrade' ), 20 );
275 remove_action( 'upgrader_process_complete', 'wp_version_check' );
276 remove_action( 'upgrader_process_complete', 'wp_update_plugins' );
277 remove_action( 'upgrader_process_complete', 'wp_update_themes' );
278
279 /** This action is documented in wp-admin/includes/class-wp-upgrader.php */
280 do_action(
281 'upgrader_process_complete',
282 $this,
283 array(
284 'action' => 'update',
285 'type' => 'translation',
286 'bulk' => true,
287 'translations' => $language_updates_results,
288 )
289 );
290
291 // Re-add upgrade hooks.
292 add_action( 'upgrader_process_complete', array( 'Language_Pack_Upgrader', 'async_upgrade' ), 20 );
293 add_action( 'upgrader_process_complete', 'wp_version_check', 10, 0 );
294 add_action( 'upgrader_process_complete', 'wp_update_plugins', 10, 0 );
295 add_action( 'upgrader_process_complete', 'wp_update_themes', 10, 0 );
296
297 $this->skin->bulk_footer();
298
299 $this->skin->footer();
300
301 // Clean up our hooks, in case something else does an upgrade on this connection.
302 remove_filter( 'upgrader_source_selection', array( $this, 'check_package' ) );
303
304 if ( $parsed_args['clear_update_cache'] ) {
305 wp_clean_update_cache();
306 }
307
308 return $results;
309 }
310
311 /**
312 * Checks that the package source contains .mo and .po files.
313 *
314 * Hooked to the {@see 'upgrader_source_selection'} filter by
315 * Language_Pack_Upgrader::bulk_upgrade().
316 *
317 * @since 3.7.0
318 *
319 * @global WP_Filesystem_Base $wp_filesystem WordPress filesystem subclass.
320 *
321 * @param string|WP_Error $source The path to the downloaded package source.
322 * @param string $remote_source Remote file source location.
323 * @return string|WP_Error The source as passed, or a WP_Error object on failure.
324 */
325 public function check_package( $source, $remote_source ) {
326 global $wp_filesystem;
327
328 if ( is_wp_error( $source ) ) {
329 return $source;
330 }
331
332 // Check that the folder contains a valid language.
333 $files = $wp_filesystem->dirlist( $remote_source );
334
335 // Check to see if the expected files exist in the folder.
336 $po = false;
337 $mo = false;
338 $php = false;
339 foreach ( (array) $files as $file => $filedata ) {
340 if ( str_ends_with( $file, '.po' ) ) {
341 $po = true;
342 } elseif ( str_ends_with( $file, '.mo' ) ) {
343 $mo = true;
344 } elseif ( str_ends_with( $file, '.l10n.php' ) ) {
345 $php = true;
346 }
347 }
348
349 if ( $php ) {
350 return $source;
351 }
352
353 if ( ! $mo || ! $po ) {
354 return new WP_Error(
355 'incompatible_archive_pomo',
356 $this->strings['incompatible_archive'],
357 sprintf(
358 /* translators: 1: .po, 2: .mo, 3: .l10n.php */
359 __( 'The language pack is missing either the %1$s, %2$s, or %3$s files.' ),
360 '<code>.po</code>',
361 '<code>.mo</code>',
362 '<code>.l10n.php</code>'
363 )
364 );
365 }
366
367 return $source;
368 }
369
370 /**
371 * Gets the name of an item being updated.
372 *
373 * @since 3.7.0
374 *
375 * @param object $update The data for an update.
376 * @return string The name of the item being updated.
377 */
378 public function get_name_for_update( $update ) {
379 switch ( $update->type ) {
380 case 'core':
381 return 'WordPress'; // Not translated.
382
383 case 'theme':
384 $theme = wp_get_theme( $update->slug );
385 if ( $theme->exists() ) {
386 return $theme->get( 'Name' );
387 }
388 break;
389 case 'plugin':
390 $plugin_data = get_plugins( '/' . $update->slug );
391 $plugin_data = reset( $plugin_data );
392 if ( $plugin_data ) {
393 return $plugin_data['Name'];
394 }
395 break;
396 }
397 return '';
398 }
399
400 /**
401 * Clears existing translations where this item is going to be installed into.
402 *
403 * @since 5.1.0
404 *
405 * @global WP_Filesystem_Base $wp_filesystem WordPress filesystem subclass.
406 *
407 * @param string $remote_destination The location on the remote filesystem to be cleared.
408 * @return bool|WP_Error True upon success, WP_Error on failure.
409 */
410 public function clear_destination( $remote_destination ) {
411 global $wp_filesystem;
412
413 $language_update = $this->skin->language_update;
414 $language_directory = WP_LANG_DIR . '/'; // Local path for use with glob().
415
416 if ( 'core' === $language_update->type ) {
417 $files = array(
418 $remote_destination . $language_update->language . '.po',
419 $remote_destination . $language_update->language . '.mo',
420 $remote_destination . $language_update->language . '.l10n.php',
421 $remote_destination . 'admin-' . $language_update->language . '.po',
422 $remote_destination . 'admin-' . $language_update->language . '.mo',
423 $remote_destination . 'admin-' . $language_update->language . '.l10n.php',
424 $remote_destination . 'admin-network-' . $language_update->language . '.po',
425 $remote_destination . 'admin-network-' . $language_update->language . '.mo',
426 $remote_destination . 'admin-network-' . $language_update->language . '.l10n.php',
427 $remote_destination . 'continents-cities-' . $language_update->language . '.po',
428 $remote_destination . 'continents-cities-' . $language_update->language . '.mo',
429 $remote_destination . 'continents-cities-' . $language_update->language . '.l10n.php',
430 );
431
432 $json_translation_files = glob( $language_directory . $language_update->language . '-*.json' );
433 if ( $json_translation_files ) {
434 foreach ( $json_translation_files as $json_translation_file ) {
435 $files[] = str_replace( $language_directory, $remote_destination, $json_translation_file );
436 }
437 }
438 } else {
439 $files = array(
440 $remote_destination . $language_update->slug . '-' . $language_update->language . '.po',
441 $remote_destination . $language_update->slug . '-' . $language_update->language . '.mo',
442 $remote_destination . $language_update->slug . '-' . $language_update->language . '.l10n.php',
443 );
444
445 $language_directory = $language_directory . $language_update->type . 's/';
446 $json_translation_files = glob( $language_directory . $language_update->slug . '-' . $language_update->language . '-*.json' );
447 if ( $json_translation_files ) {
448 foreach ( $json_translation_files as $json_translation_file ) {
449 $files[] = str_replace( $language_directory, $remote_destination, $json_translation_file );
450 }
451 }
452 }
453
454 $files = array_filter( $files, array( $wp_filesystem, 'exists' ) );
455
456 // No files to delete.
457 if ( ! $files ) {
458 return true;
459 }
460
461 // Check all files are writable before attempting to clear the destination.
462 $unwritable_files = array();
463
464 // Check writability.
465 foreach ( $files as $file ) {
466 if ( ! $wp_filesystem->is_writable( $file ) ) {
467 // Attempt to alter permissions to allow writes and try again.
468 $wp_filesystem->chmod( $file, FS_CHMOD_FILE );
469 if ( ! $wp_filesystem->is_writable( $file ) ) {
470 $unwritable_files[] = $file;
471 }
472 }
473 }
474
475 if ( ! empty( $unwritable_files ) ) {
476 return new WP_Error( 'files_not_writable', $this->strings['files_not_writable'], implode( ', ', $unwritable_files ) );
477 }
478
479 foreach ( $files as $file ) {
480 if ( ! $wp_filesystem->delete( $file ) ) {
481 return new WP_Error( 'remove_old_failed', $this->strings['remove_old_failed'] );
482 }
483 }
484
485 return true;
486 }
487}
488