[ Index ] |
PHP Cross Reference of WordPress Trunk (Updated Daily) |
[Summary view] [Print] [Text view]
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 */ 19 class 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…' ), '<span class="code pre">%s</span>' ); 118 $this->strings['unpack_package'] = __( 'Unpacking the update…' ); 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…' ); 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 }
title
Description
Body
title
Description
Body
title
Description
Body
title
Body
Generated : Thu Nov 21 08:20:01 2024 | Cross-referenced by PHPXref |