[ Index ] |
PHP Cross Reference of WordPress |
[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 * Initialize 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">%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 * Upgrade 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 * Bulk upgrade language packs. 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 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 a .po and .mo exist in the folder. 336 $po = false; 337 $mo = false; 338 foreach ( (array) $files as $file => $filedata ) { 339 if ( '.po' === substr( $file, -3 ) ) { 340 $po = true; 341 } elseif ( '.mo' === substr( $file, -3 ) ) { 342 $mo = true; 343 } 344 } 345 346 if ( ! $mo || ! $po ) { 347 return new WP_Error( 348 'incompatible_archive_pomo', 349 $this->strings['incompatible_archive'], 350 sprintf( 351 /* translators: 1: .po, 2: .mo */ 352 __( 'The language pack is missing either the %1$s or %2$s files.' ), 353 '<code>.po</code>', 354 '<code>.mo</code>' 355 ) 356 ); 357 } 358 359 return $source; 360 } 361 362 /** 363 * Get the name of an item being updated. 364 * 365 * @since 3.7.0 366 * 367 * @param object $update The data for an update. 368 * @return string The name of the item being updated. 369 */ 370 public function get_name_for_update( $update ) { 371 switch ( $update->type ) { 372 case 'core': 373 return 'WordPress'; // Not translated. 374 375 case 'theme': 376 $theme = wp_get_theme( $update->slug ); 377 if ( $theme->exists() ) { 378 return $theme->Get( 'Name' ); 379 } 380 break; 381 case 'plugin': 382 $plugin_data = get_plugins( '/' . $update->slug ); 383 $plugin_data = reset( $plugin_data ); 384 if ( $plugin_data ) { 385 return $plugin_data['Name']; 386 } 387 break; 388 } 389 return ''; 390 } 391 392 /** 393 * Clears existing translations where this item is going to be installed into. 394 * 395 * @since 5.1.0 396 * 397 * @global WP_Filesystem_Base $wp_filesystem WordPress filesystem subclass. 398 * 399 * @param string $remote_destination The location on the remote filesystem to be cleared. 400 * @return bool|WP_Error True upon success, WP_Error on failure. 401 */ 402 public function clear_destination( $remote_destination ) { 403 global $wp_filesystem; 404 405 $language_update = $this->skin->language_update; 406 $language_directory = WP_LANG_DIR . '/'; // Local path for use with glob(). 407 408 if ( 'core' === $language_update->type ) { 409 $files = array( 410 $remote_destination . $language_update->language . '.po', 411 $remote_destination . $language_update->language . '.mo', 412 $remote_destination . 'admin-' . $language_update->language . '.po', 413 $remote_destination . 'admin-' . $language_update->language . '.mo', 414 $remote_destination . 'admin-network-' . $language_update->language . '.po', 415 $remote_destination . 'admin-network-' . $language_update->language . '.mo', 416 $remote_destination . 'continents-cities-' . $language_update->language . '.po', 417 $remote_destination . 'continents-cities-' . $language_update->language . '.mo', 418 ); 419 420 $json_translation_files = glob( $language_directory . $language_update->language . '-*.json' ); 421 if ( $json_translation_files ) { 422 foreach ( $json_translation_files as $json_translation_file ) { 423 $files[] = str_replace( $language_directory, $remote_destination, $json_translation_file ); 424 } 425 } 426 } else { 427 $files = array( 428 $remote_destination . $language_update->slug . '-' . $language_update->language . '.po', 429 $remote_destination . $language_update->slug . '-' . $language_update->language . '.mo', 430 ); 431 432 $language_directory = $language_directory . $language_update->type . 's/'; 433 $json_translation_files = glob( $language_directory . $language_update->slug . '-' . $language_update->language . '-*.json' ); 434 if ( $json_translation_files ) { 435 foreach ( $json_translation_files as $json_translation_file ) { 436 $files[] = str_replace( $language_directory, $remote_destination, $json_translation_file ); 437 } 438 } 439 } 440 441 $files = array_filter( $files, array( $wp_filesystem, 'exists' ) ); 442 443 // No files to delete. 444 if ( ! $files ) { 445 return true; 446 } 447 448 // Check all files are writable before attempting to clear the destination. 449 $unwritable_files = array(); 450 451 // Check writability. 452 foreach ( $files as $file ) { 453 if ( ! $wp_filesystem->is_writable( $file ) ) { 454 // Attempt to alter permissions to allow writes and try again. 455 $wp_filesystem->chmod( $file, FS_CHMOD_FILE ); 456 if ( ! $wp_filesystem->is_writable( $file ) ) { 457 $unwritable_files[] = $file; 458 } 459 } 460 } 461 462 if ( ! empty( $unwritable_files ) ) { 463 return new WP_Error( 'files_not_writable', $this->strings['files_not_writable'], implode( ', ', $unwritable_files ) ); 464 } 465 466 foreach ( $files as $file ) { 467 if ( ! $wp_filesystem->delete( $file ) ) { 468 return new WP_Error( 'remove_old_failed', $this->strings['remove_old_failed'] ); 469 } 470 } 471 472 return true; 473 } 474 }
title
Description
Body
title
Description
Body
title
Description
Body
title
Body
Generated: Tue Dec 3 01:00:02 2024 | Cross-referenced by PHPXref 0.7.1 |