wp/wp-admin/includes/class-plugin-upgrader.php
changeset 21 48c4eec2b7e6
parent 19 3d72ae0968f4
child 22 8c2e4d02f4ef
equal deleted inserted replaced
20:7b1b88e27a20 21:48c4eec2b7e6
    47 	 * @see check_package()
    47 	 * @see check_package()
    48 	 */
    48 	 */
    49 	public $new_plugin_data = array();
    49 	public $new_plugin_data = array();
    50 
    50 
    51 	/**
    51 	/**
    52 	 * Initialize the upgrade strings.
    52 	 * Initializes the upgrade strings.
    53 	 *
    53 	 *
    54 	 * @since 2.8.0
    54 	 * @since 2.8.0
    55 	 */
    55 	 */
    56 	public function upgrade_strings() {
    56 	public function upgrade_strings() {
    57 		$this->strings['up_to_date'] = __( 'The plugin is at the latest version.' );
    57 		$this->strings['up_to_date'] = __( 'The plugin is at the latest version.' );
    58 		$this->strings['no_package'] = __( 'Update package not available.' );
    58 		$this->strings['no_package'] = __( 'Update package not available.' );
    59 		/* translators: %s: Package URL. */
    59 		/* translators: %s: Package URL. */
    60 		$this->strings['downloading_package']  = sprintf( __( 'Downloading update from %s&#8230;' ), '<span class="code">%s</span>' );
    60 		$this->strings['downloading_package']  = sprintf( __( 'Downloading update from %s&#8230;' ), '<span class="code pre">%s</span>' );
    61 		$this->strings['unpack_package']       = __( 'Unpacking the update&#8230;' );
    61 		$this->strings['unpack_package']       = __( 'Unpacking the update&#8230;' );
    62 		$this->strings['remove_old']           = __( 'Removing the old version of the plugin&#8230;' );
    62 		$this->strings['remove_old']           = __( 'Removing the old version of the plugin&#8230;' );
    63 		$this->strings['remove_old_failed']    = __( 'Could not remove the old plugin.' );
    63 		$this->strings['remove_old_failed']    = __( 'Could not remove the old plugin.' );
    64 		$this->strings['process_failed']       = __( 'Plugin update failed.' );
    64 		$this->strings['process_failed']       = __( 'Plugin update failed.' );
    65 		$this->strings['process_success']      = __( 'Plugin updated successfully.' );
    65 		$this->strings['process_success']      = __( 'Plugin updated successfully.' );
    66 		$this->strings['process_bulk_success'] = __( 'Plugins updated successfully.' );
    66 		$this->strings['process_bulk_success'] = __( 'Plugins updated successfully.' );
    67 	}
    67 	}
    68 
    68 
    69 	/**
    69 	/**
    70 	 * Initialize the installation strings.
    70 	 * Initializes the installation strings.
    71 	 *
    71 	 *
    72 	 * @since 2.8.0
    72 	 * @since 2.8.0
    73 	 */
    73 	 */
    74 	public function install_strings() {
    74 	public function install_strings() {
    75 		$this->strings['no_package'] = __( 'Installation package not available.' );
    75 		$this->strings['no_package'] = __( 'Installation package not available.' );
    76 		/* translators: %s: Package URL. */
    76 		/* translators: %s: Package URL. */
    77 		$this->strings['downloading_package'] = sprintf( __( 'Downloading installation package from %s&#8230;' ), '<span class="code">%s</span>' );
    77 		$this->strings['downloading_package'] = sprintf( __( 'Downloading installation package from %s&#8230;' ), '<span class="code pre">%s</span>' );
    78 		$this->strings['unpack_package']      = __( 'Unpacking the package&#8230;' );
    78 		$this->strings['unpack_package']      = __( 'Unpacking the package&#8230;' );
    79 		$this->strings['installing_package']  = __( 'Installing the plugin&#8230;' );
    79 		$this->strings['installing_package']  = __( 'Installing the plugin&#8230;' );
    80 		$this->strings['remove_old']          = __( 'Removing the current plugin&#8230;' );
    80 		$this->strings['remove_old']          = __( 'Removing the current plugin&#8230;' );
    81 		$this->strings['remove_old_failed']   = __( 'Could not remove the current plugin.' );
    81 		$this->strings['remove_old_failed']   = __( 'Could not remove the current plugin.' );
    82 		$this->strings['no_files']            = __( 'The plugin contains no files.' );
    82 		$this->strings['no_files']            = __( 'The plugin contains no files.' );
   171 
   171 
   172 		return true;
   172 		return true;
   173 	}
   173 	}
   174 
   174 
   175 	/**
   175 	/**
   176 	 * Upgrade a plugin.
   176 	 * Upgrades a plugin.
   177 	 *
   177 	 *
   178 	 * @since 2.8.0
   178 	 * @since 2.8.0
   179 	 * @since 3.7.0 The `$args` parameter was added, making clearing the plugin update cache optional.
   179 	 * @since 3.7.0 The `$args` parameter was added, making clearing the plugin update cache optional.
   180 	 *
   180 	 *
   181 	 * @param string $plugin Path to the plugin file relative to the plugins directory.
   181 	 * @param string $plugin Path to the plugin file relative to the plugins directory.
   210 
   210 
   211 		add_filter( 'upgrader_pre_install', array( $this, 'deactivate_plugin_before_upgrade' ), 10, 2 );
   211 		add_filter( 'upgrader_pre_install', array( $this, 'deactivate_plugin_before_upgrade' ), 10, 2 );
   212 		add_filter( 'upgrader_pre_install', array( $this, 'active_before' ), 10, 2 );
   212 		add_filter( 'upgrader_pre_install', array( $this, 'active_before' ), 10, 2 );
   213 		add_filter( 'upgrader_clear_destination', array( $this, 'delete_old_plugin' ), 10, 4 );
   213 		add_filter( 'upgrader_clear_destination', array( $this, 'delete_old_plugin' ), 10, 4 );
   214 		add_filter( 'upgrader_post_install', array( $this, 'active_after' ), 10, 2 );
   214 		add_filter( 'upgrader_post_install', array( $this, 'active_after' ), 10, 2 );
   215 		// There's a Trac ticket to move up the directory for zips which are made a bit differently, useful for non-.org plugins.
   215 		/*
   216 		// 'source_selection' => array( $this, 'source_selection' ),
   216 		 * There's a Trac ticket to move up the directory for zips which are made a bit differently, useful for non-.org plugins.
       
   217 		 * 'source_selection' => array( $this, 'source_selection' ),
       
   218 		 */
   217 		if ( $parsed_args['clear_update_cache'] ) {
   219 		if ( $parsed_args['clear_update_cache'] ) {
   218 			// Clear cache so wp_update_plugins() knows about the new plugin.
   220 			// Clear cache so wp_update_plugins() knows about the new plugin.
   219 			add_action( 'upgrader_process_complete', 'wp_clean_plugins_cache', 9, 0 );
   221 			add_action( 'upgrader_process_complete', 'wp_clean_plugins_cache', 9, 0 );
   220 		}
   222 		}
   221 
   223 
   224 				'package'           => $r->package,
   226 				'package'           => $r->package,
   225 				'destination'       => WP_PLUGIN_DIR,
   227 				'destination'       => WP_PLUGIN_DIR,
   226 				'clear_destination' => true,
   228 				'clear_destination' => true,
   227 				'clear_working'     => true,
   229 				'clear_working'     => true,
   228 				'hook_extra'        => array(
   230 				'hook_extra'        => array(
   229 					'plugin' => $plugin,
   231 					'plugin'      => $plugin,
   230 					'type'   => 'plugin',
   232 					'type'        => 'plugin',
   231 					'action' => 'update',
   233 					'action'      => 'update',
       
   234 					'temp_backup' => array(
       
   235 						'slug' => dirname( $plugin ),
       
   236 						'src'  => WP_PLUGIN_DIR,
       
   237 						'dir'  => 'plugins',
       
   238 					),
   232 				),
   239 				),
   233 			)
   240 			)
   234 		);
   241 		);
   235 
   242 
   236 		// Cleanup our hooks, in case something else does a upgrade on this connection.
   243 		// Cleanup our hooks, in case something else does an upgrade on this connection.
   237 		remove_action( 'upgrader_process_complete', 'wp_clean_plugins_cache', 9 );
   244 		remove_action( 'upgrader_process_complete', 'wp_clean_plugins_cache', 9 );
   238 		remove_filter( 'upgrader_pre_install', array( $this, 'deactivate_plugin_before_upgrade' ) );
   245 		remove_filter( 'upgrader_pre_install', array( $this, 'deactivate_plugin_before_upgrade' ) );
   239 		remove_filter( 'upgrader_pre_install', array( $this, 'active_before' ) );
   246 		remove_filter( 'upgrader_pre_install', array( $this, 'active_before' ) );
   240 		remove_filter( 'upgrader_clear_destination', array( $this, 'delete_old_plugin' ) );
   247 		remove_filter( 'upgrader_clear_destination', array( $this, 'delete_old_plugin' ) );
   241 		remove_filter( 'upgrader_post_install', array( $this, 'active_after' ) );
   248 		remove_filter( 'upgrader_post_install', array( $this, 'active_after' ) );
   245 		}
   252 		}
   246 
   253 
   247 		// Force refresh of plugin update information.
   254 		// Force refresh of plugin update information.
   248 		wp_clean_plugins_cache( $parsed_args['clear_update_cache'] );
   255 		wp_clean_plugins_cache( $parsed_args['clear_update_cache'] );
   249 
   256 
   250 		// Ensure any future auto-update failures trigger a failure email by removing
   257 		/*
   251 		// the last failure notification from the list when plugins update successfully.
   258 		 * Ensure any future auto-update failures trigger a failure email by removing
       
   259 		 * the last failure notification from the list when plugins update successfully.
       
   260 		 */
   252 		$past_failure_emails = get_option( 'auto_plugin_theme_update_emails', array() );
   261 		$past_failure_emails = get_option( 'auto_plugin_theme_update_emails', array() );
   253 
   262 
   254 		if ( isset( $past_failure_emails[ $plugin ] ) ) {
   263 		if ( isset( $past_failure_emails[ $plugin ] ) ) {
   255 			unset( $past_failure_emails[ $plugin ] );
   264 			unset( $past_failure_emails[ $plugin ] );
   256 			update_option( 'auto_plugin_theme_update_emails', $past_failure_emails );
   265 			update_option( 'auto_plugin_theme_update_emails', $past_failure_emails );
   258 
   267 
   259 		return true;
   268 		return true;
   260 	}
   269 	}
   261 
   270 
   262 	/**
   271 	/**
   263 	 * Bulk upgrade several plugins at once.
   272 	 * Upgrades several plugins at once.
   264 	 *
   273 	 *
   265 	 * @since 2.8.0
   274 	 * @since 2.8.0
   266 	 * @since 3.7.0 The `$args` parameter was added, making clearing the plugin update cache optional.
   275 	 * @since 3.7.0 The `$args` parameter was added, making clearing the plugin update cache optional.
       
   276 	 *
       
   277 	 * @global string $wp_version The WordPress version string.
   267 	 *
   278 	 *
   268 	 * @param string[] $plugins Array of paths to plugin files relative to the plugins directory.
   279 	 * @param string[] $plugins Array of paths to plugin files relative to the plugins directory.
   269 	 * @param array    $args {
   280 	 * @param array    $args {
   270 	 *     Optional. Other arguments for upgrading several plugins at once.
   281 	 *     Optional. Other arguments for upgrading several plugins at once.
   271 	 *
   282 	 *
   272 	 *     @type bool $clear_update_cache Whether to clear the plugin updates cache if successful. Default true.
   283 	 *     @type bool $clear_update_cache Whether to clear the plugin updates cache if successful. Default true.
   273 	 * }
   284 	 * }
   274 	 * @return array|false An array of results indexed by plugin file, or false if unable to connect to the filesystem.
   285 	 * @return array|false An array of results indexed by plugin file, or false if unable to connect to the filesystem.
   275 	 */
   286 	 */
   276 	public function bulk_upgrade( $plugins, $args = array() ) {
   287 	public function bulk_upgrade( $plugins, $args = array() ) {
       
   288 		global $wp_version;
       
   289 
   277 		$defaults    = array(
   290 		$defaults    = array(
   278 			'clear_update_cache' => true,
   291 			'clear_update_cache' => true,
   279 		);
   292 		);
   280 		$parsed_args = wp_parse_args( $args, $defaults );
   293 		$parsed_args = wp_parse_args( $args, $defaults );
   281 
   294 
   315 		$results = array();
   328 		$results = array();
   316 
   329 
   317 		$this->update_count   = count( $plugins );
   330 		$this->update_count   = count( $plugins );
   318 		$this->update_current = 0;
   331 		$this->update_current = 0;
   319 		foreach ( $plugins as $plugin ) {
   332 		foreach ( $plugins as $plugin ) {
   320 			$this->update_current++;
   333 			++$this->update_current;
   321 			$this->skin->plugin_info = get_plugin_data( WP_PLUGIN_DIR . '/' . $plugin, false, true );
   334 			$this->skin->plugin_info = get_plugin_data( WP_PLUGIN_DIR . '/' . $plugin, false, true );
   322 
   335 
   323 			if ( ! isset( $current->response[ $plugin ] ) ) {
   336 			if ( ! isset( $current->response[ $plugin ] ) ) {
   324 				$this->skin->set_result( 'up_to_date' );
   337 				$this->skin->set_result( 'up_to_date' );
   325 				$this->skin->before();
   338 				$this->skin->before();
   332 			// Get the URL to the zip file.
   345 			// Get the URL to the zip file.
   333 			$r = $current->response[ $plugin ];
   346 			$r = $current->response[ $plugin ];
   334 
   347 
   335 			$this->skin->plugin_active = is_plugin_active( $plugin );
   348 			$this->skin->plugin_active = is_plugin_active( $plugin );
   336 
   349 
   337 			$result = $this->run(
   350 			if ( isset( $r->requires ) && ! is_wp_version_compatible( $r->requires ) ) {
   338 				array(
   351 				$result = new WP_Error(
   339 					'package'           => $r->package,
   352 					'incompatible_wp_required_version',
   340 					'destination'       => WP_PLUGIN_DIR,
   353 					sprintf(
   341 					'clear_destination' => true,
   354 						/* translators: 1: Current WordPress version, 2: WordPress version required by the new plugin version. */
   342 					'clear_working'     => true,
   355 						__( 'Your WordPress version is %1$s, however the new plugin version requires %2$s.' ),
   343 					'is_multi'          => true,
   356 						$wp_version,
   344 					'hook_extra'        => array(
   357 						$r->requires
   345 						'plugin' => $plugin,
   358 					)
   346 					),
   359 				);
   347 				)
   360 
   348 			);
   361 				$this->skin->before( $result );
       
   362 				$this->skin->error( $result );
       
   363 				$this->skin->after();
       
   364 			} elseif ( isset( $r->requires_php ) && ! is_php_version_compatible( $r->requires_php ) ) {
       
   365 				$result = new WP_Error(
       
   366 					'incompatible_php_required_version',
       
   367 					sprintf(
       
   368 						/* translators: 1: Current PHP version, 2: PHP version required by the new plugin version. */
       
   369 						__( 'The PHP version on your server is %1$s, however the new plugin version requires %2$s.' ),
       
   370 						PHP_VERSION,
       
   371 						$r->requires_php
       
   372 					)
       
   373 				);
       
   374 
       
   375 				$this->skin->before( $result );
       
   376 				$this->skin->error( $result );
       
   377 				$this->skin->after();
       
   378 			} else {
       
   379 				add_filter( 'upgrader_source_selection', array( $this, 'check_package' ) );
       
   380 				$result = $this->run(
       
   381 					array(
       
   382 						'package'           => $r->package,
       
   383 						'destination'       => WP_PLUGIN_DIR,
       
   384 						'clear_destination' => true,
       
   385 						'clear_working'     => true,
       
   386 						'is_multi'          => true,
       
   387 						'hook_extra'        => array(
       
   388 							'plugin'      => $plugin,
       
   389 							'temp_backup' => array(
       
   390 								'slug' => dirname( $plugin ),
       
   391 								'src'  => WP_PLUGIN_DIR,
       
   392 								'dir'  => 'plugins',
       
   393 							),
       
   394 						),
       
   395 					)
       
   396 				);
       
   397 				remove_filter( 'upgrader_source_selection', array( $this, 'check_package' ) );
       
   398 			}
   349 
   399 
   350 			$results[ $plugin ] = $result;
   400 			$results[ $plugin ] = $result;
   351 
   401 
   352 			// Prevent credentials auth screen from displaying multiple times.
   402 			// Prevent credentials auth screen from displaying multiple times.
   353 			if ( false === $result ) {
   403 			if ( false === $result ) {
   374 
   424 
   375 		$this->skin->bulk_footer();
   425 		$this->skin->bulk_footer();
   376 
   426 
   377 		$this->skin->footer();
   427 		$this->skin->footer();
   378 
   428 
   379 		// Cleanup our hooks, in case something else does a upgrade on this connection.
   429 		// Cleanup our hooks, in case something else does an upgrade on this connection.
   380 		remove_filter( 'upgrader_clear_destination', array( $this, 'delete_old_plugin' ) );
   430 		remove_filter( 'upgrader_clear_destination', array( $this, 'delete_old_plugin' ) );
   381 
   431 
   382 		// Ensure any future auto-update failures trigger a failure email by removing
   432 		/*
   383 		// the last failure notification from the list when plugins update successfully.
   433 		 * Ensure any future auto-update failures trigger a failure email by removing
       
   434 		 * the last failure notification from the list when plugins update successfully.
       
   435 		 */
   384 		$past_failure_emails = get_option( 'auto_plugin_theme_update_emails', array() );
   436 		$past_failure_emails = get_option( 'auto_plugin_theme_update_emails', array() );
   385 
   437 
   386 		foreach ( $results as $plugin => $result ) {
   438 		foreach ( $results as $plugin => $result ) {
   387 			// Maintain last failure notification when plugins failed to update manually.
   439 			// Maintain last failure notification when plugins failed to update manually.
   388 			if ( ! $result || is_wp_error( $result ) || ! isset( $past_failure_emails[ $plugin ] ) ) {
   440 			if ( ! $result || is_wp_error( $result ) || ! isset( $past_failure_emails[ $plugin ] ) ) {
   418 		if ( is_wp_error( $source ) ) {
   470 		if ( is_wp_error( $source ) ) {
   419 			return $source;
   471 			return $source;
   420 		}
   472 		}
   421 
   473 
   422 		$working_directory = str_replace( $wp_filesystem->wp_content_dir(), trailingslashit( WP_CONTENT_DIR ), $source );
   474 		$working_directory = str_replace( $wp_filesystem->wp_content_dir(), trailingslashit( WP_CONTENT_DIR ), $source );
   423 		if ( ! is_dir( $working_directory ) ) { // Sanity check, if the above fails, let's not prevent installation.
   475 		if ( ! is_dir( $working_directory ) ) { // Confidence check, if the above fails, let's not prevent installation.
   424 			return $source;
   476 			return $source;
   425 		}
   477 		}
   426 
   478 
   427 		// Check that the folder contains at least 1 valid plugin.
   479 		// Check that the folder contains at least 1 valid plugin.
   428 		$files = glob( $working_directory . '*.php' );
   480 		$files = glob( $working_directory . '*.php' );
   445 
   497 
   446 		if ( ! is_php_version_compatible( $requires_php ) ) {
   498 		if ( ! is_php_version_compatible( $requires_php ) ) {
   447 			$error = sprintf(
   499 			$error = sprintf(
   448 				/* translators: 1: Current PHP version, 2: Version required by the uploaded plugin. */
   500 				/* translators: 1: Current PHP version, 2: Version required by the uploaded plugin. */
   449 				__( 'The PHP version on your server is %1$s, however the uploaded plugin requires %2$s.' ),
   501 				__( 'The PHP version on your server is %1$s, however the uploaded plugin requires %2$s.' ),
   450 				phpversion(),
   502 				PHP_VERSION,
   451 				$requires_php
   503 				$requires_php
   452 			);
   504 			);
   453 
   505 
   454 			return new WP_Error( 'incompatible_php_required_version', $this->strings['incompatible_archive'], $error );
   506 			return new WP_Error( 'incompatible_php_required_version', $this->strings['incompatible_archive'], $error );
   455 		}
   507 		}
   467 
   519 
   468 		return $source;
   520 		return $source;
   469 	}
   521 	}
   470 
   522 
   471 	/**
   523 	/**
   472 	 * Retrieve the path to the file that contains the plugin info.
   524 	 * Retrieves the path to the file that contains the plugin info.
   473 	 *
   525 	 *
   474 	 * This isn't used internally in the class, but is called by the skins.
   526 	 * This isn't used internally in the class, but is called by the skins.
   475 	 *
   527 	 *
   476 	 * @since 2.8.0
   528 	 * @since 2.8.0
   477 	 *
   529 	 *
   639 
   691 
   640 		if ( ! $wp_filesystem->exists( $this_plugin_dir ) ) { // If it's already vanished.
   692 		if ( ! $wp_filesystem->exists( $this_plugin_dir ) ) { // If it's already vanished.
   641 			return $removed;
   693 			return $removed;
   642 		}
   694 		}
   643 
   695 
   644 		// If plugin is in its own directory, recursively delete the directory.
   696 		/*
   645 		// Base check on if plugin includes directory separator AND that it's not the root plugin folder.
   697 		 * If plugin is in its own directory, recursively delete the directory.
       
   698 		 * Base check on if plugin includes directory separator AND that it's not the root plugin folder.
       
   699 		 */
   646 		if ( strpos( $plugin, '/' ) && $this_plugin_dir !== $plugins_dir ) {
   700 		if ( strpos( $plugin, '/' ) && $this_plugin_dir !== $plugins_dir ) {
   647 			$deleted = $wp_filesystem->delete( $this_plugin_dir, true );
   701 			$deleted = $wp_filesystem->delete( $this_plugin_dir, true );
   648 		} else {
   702 		} else {
   649 			$deleted = $wp_filesystem->delete( $plugins_dir . $plugin );
   703 			$deleted = $wp_filesystem->delete( $plugins_dir . $plugin );
   650 		}
   704 		}