balmet.com

Unnamed repository; edit this file 'description' to name the repository.
Log | Files | Refs

class-language-pack-upgrader.php (14922B)


      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 we update them 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">%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 	 * 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 }