wp/wp-admin/includes/class-wp-upgrader.php
changeset 16 a86126ab1dd4
parent 9 177826044cd9
child 18 be944660c56a
equal deleted inserted replaced
15:3d4e9c994f10 16:a86126ab1dd4
    74 	 * {@see 'upgrader_post_install'} filter. In that case, the WP_Error will be assigned to
    74 	 * {@see 'upgrader_post_install'} filter. In that case, the WP_Error will be assigned to
    75 	 * it.
    75 	 * it.
    76 	 *
    76 	 *
    77 	 * @since 2.8.0
    77 	 * @since 2.8.0
    78 	 *
    78 	 *
    79 	 * @var WP_Error|array $result {
    79 	 * @var array|WP_Error $result {
    80 	 *      @type string $source             The full path to the source the files were installed from.
    80 	 *     @type string $source             The full path to the source the files were installed from.
    81 	 *      @type string $source_files       List of all the files in the source directory.
    81 	 *     @type string $source_files       List of all the files in the source directory.
    82 	 *      @type string $destination        The full path to the installation destination folder.
    82 	 *     @type string $destination        The full path to the installation destination folder.
    83 	 *      @type string $destination_name   The name of the destination folder, or empty if `$destination`
    83 	 *     @type string $destination_name   The name of the destination folder, or empty if `$destination`
    84 	 *                                       and `$local_destination` are the same.
    84 	 *                                      and `$local_destination` are the same.
    85 	 *      @type string $local_destination  The full local path to the destination folder. This is usually
    85 	 *     @type string $local_destination  The full local path to the destination folder. This is usually
    86 	 *                                       the same as `$destination`.
    86 	 *                                      the same as `$destination`.
    87 	 *      @type string $remote_destination The full remote path to the destination folder
    87 	 *     @type string $remote_destination The full remote path to the destination folder
    88 	 *                                       (i.e., from `$wp_filesystem`).
    88 	 *                                      (i.e., from `$wp_filesystem`).
    89 	 *      @type bool   $clear_destination  Whether the destination folder was cleared.
    89 	 *     @type bool   $clear_destination  Whether the destination folder was cleared.
    90 	 * }
    90 	 * }
    91 	 */
    91 	 */
    92 	public $result = array();
    92 	public $result = array();
    93 
    93 
    94 	/**
    94 	/**
   114 	/**
   114 	/**
   115 	 * Construct the upgrader with a skin.
   115 	 * Construct the upgrader with a skin.
   116 	 *
   116 	 *
   117 	 * @since 2.8.0
   117 	 * @since 2.8.0
   118 	 *
   118 	 *
   119 	 * @param WP_Upgrader_Skin $skin The upgrader skin to use. Default is a WP_Upgrader_Skin.
   119 	 * @param WP_Upgrader_Skin $skin The upgrader skin to use. Default is a WP_Upgrader_Skin
   120 	 *                               instance.
   120 	 *                               instance.
   121 	 */
   121 	 */
   122 	public function __construct( $skin = null ) {
   122 	public function __construct( $skin = null ) {
   123 		if ( null == $skin ) {
   123 		if ( null == $skin ) {
   124 			$this->skin = new WP_Upgrader_Skin();
   124 			$this->skin = new WP_Upgrader_Skin();
   151 		$this->strings['fs_error']          = __( 'Filesystem error.' );
   151 		$this->strings['fs_error']          = __( 'Filesystem error.' );
   152 		$this->strings['fs_no_root_dir']    = __( 'Unable to locate WordPress root directory.' );
   152 		$this->strings['fs_no_root_dir']    = __( 'Unable to locate WordPress root directory.' );
   153 		$this->strings['fs_no_content_dir'] = __( 'Unable to locate WordPress content directory (wp-content).' );
   153 		$this->strings['fs_no_content_dir'] = __( 'Unable to locate WordPress content directory (wp-content).' );
   154 		$this->strings['fs_no_plugins_dir'] = __( 'Unable to locate WordPress plugin directory.' );
   154 		$this->strings['fs_no_plugins_dir'] = __( 'Unable to locate WordPress plugin directory.' );
   155 		$this->strings['fs_no_themes_dir']  = __( 'Unable to locate WordPress theme directory.' );
   155 		$this->strings['fs_no_themes_dir']  = __( 'Unable to locate WordPress theme directory.' );
   156 		/* translators: %s: directory name */
   156 		/* translators: %s: Directory name. */
   157 		$this->strings['fs_no_folder'] = __( 'Unable to locate needed folder (%s).' );
   157 		$this->strings['fs_no_folder'] = __( 'Unable to locate needed folder (%s).' );
   158 
   158 
   159 		$this->strings['download_failed']      = __( 'Download failed.' );
   159 		$this->strings['download_failed']      = __( 'Download failed.' );
   160 		$this->strings['installing_package']   = __( 'Installing the latest version…' );
   160 		$this->strings['installing_package']   = __( 'Installing the latest version…' );
   161 		$this->strings['no_files']             = __( 'The package contains no files.' );
   161 		$this->strings['no_files']             = __( 'The package contains no files.' );
   173 	 *
   173 	 *
   174 	 * @since 2.8.0
   174 	 * @since 2.8.0
   175 	 *
   175 	 *
   176 	 * @global WP_Filesystem_Base $wp_filesystem WordPress filesystem subclass.
   176 	 * @global WP_Filesystem_Base $wp_filesystem WordPress filesystem subclass.
   177 	 *
   177 	 *
   178 	 * @param array $directories                  Optional. A list of directories. If any of these do
   178 	 * @param string[] $directories                  Optional. Array of directories. If any of these do
   179 	 *                                            not exist, a WP_Error object will be returned.
   179 	 *                                               not exist, a WP_Error object will be returned.
   180 	 *                                            Default empty array.
   180 	 *                                               Default empty array.
   181 	 * @param bool  $allow_relaxed_file_ownership Whether to allow relaxed file ownership.
   181 	 * @param bool     $allow_relaxed_file_ownership Whether to allow relaxed file ownership.
   182 	 *                                            Default false.
   182 	 *                                               Default false.
   183 	 * @return bool|WP_Error True if able to connect, false or a WP_Error otherwise.
   183 	 * @return bool|WP_Error True if able to connect, false or a WP_Error otherwise.
   184 	 */
   184 	 */
   185 	public function fs_connect( $directories = array(), $allow_relaxed_file_ownership = false ) {
   185 	public function fs_connect( $directories = array(), $allow_relaxed_file_ownership = false ) {
   186 		global $wp_filesystem;
   186 		global $wp_filesystem;
   187 
   187 
   188 		if ( false === ( $credentials = $this->skin->request_filesystem_credentials( false, $directories[0], $allow_relaxed_file_ownership ) ) ) {
   188 		$credentials = $this->skin->request_filesystem_credentials( false, $directories[0], $allow_relaxed_file_ownership );
       
   189 		if ( false === $credentials ) {
   189 			return false;
   190 			return false;
   190 		}
   191 		}
   191 
   192 
   192 		if ( ! WP_Filesystem( $credentials, $directories[0], $allow_relaxed_file_ownership ) ) {
   193 		if ( ! WP_Filesystem( $credentials, $directories[0], $allow_relaxed_file_ownership ) ) {
   193 			$error = true;
   194 			$error = true;
   194 			if ( is_object( $wp_filesystem ) && $wp_filesystem->errors->has_errors() ) {
   195 			if ( is_object( $wp_filesystem ) && $wp_filesystem->errors->has_errors() ) {
   195 				$error = $wp_filesystem->errors;
   196 				$error = $wp_filesystem->errors;
   196 			}
   197 			}
   197 			// Failed to connect, Error and request again
   198 			// Failed to connect. Error and request again.
   198 			$this->skin->request_filesystem_credentials( $error, $directories[0], $allow_relaxed_file_ownership );
   199 			$this->skin->request_filesystem_credentials( $error, $directories[0], $allow_relaxed_file_ownership );
   199 			return false;
   200 			return false;
   200 		}
   201 		}
   201 
   202 
   202 		if ( ! is_object( $wp_filesystem ) ) {
   203 		if ( ! is_object( $wp_filesystem ) ) {
   235 					}
   236 					}
   236 					break;
   237 					break;
   237 			}
   238 			}
   238 		}
   239 		}
   239 		return true;
   240 		return true;
   240 	} //end fs_connect();
   241 	}
   241 
   242 
   242 	/**
   243 	/**
   243 	 * Download a package.
   244 	 * Download a package.
   244 	 *
   245 	 *
   245 	 * @since 2.8.0
   246 	 * @since 2.8.0
       
   247 	 * @since 5.5.0 Added the `$hook_extra` parameter.
   246 	 *
   248 	 *
   247 	 * @param string $package          The URI of the package. If this is the full path to an
   249 	 * @param string $package          The URI of the package. If this is the full path to an
   248 	 *                                 existing local file, it will be returned untouched.
   250 	 *                                 existing local file, it will be returned untouched.
   249 	 * @param bool   $check_signatures Whether to validate file signatures. Default false.
   251 	 * @param bool   $check_signatures Whether to validate file signatures. Default false.
       
   252 	 * @param array  $hook_extra       Extra arguments to pass to the filter hooks. Default empty array.
   250 	 * @return string|WP_Error The full path to the downloaded package file, or a WP_Error object.
   253 	 * @return string|WP_Error The full path to the downloaded package file, or a WP_Error object.
   251 	 */
   254 	 */
   252 	public function download_package( $package, $check_signatures = false ) {
   255 	public function download_package( $package, $check_signatures = false, $hook_extra = array() ) {
   253 
       
   254 		/**
   256 		/**
   255 		 * Filters whether to return the package.
   257 		 * Filters whether to return the package.
   256 		 *
   258 		 *
   257 		 * @since 3.7.0
   259 		 * @since 3.7.0
   258 		 *
   260 		 * @since 5.5.0 Added the `$hook_extra` parameter.
   259 		 * @param bool        $reply   Whether to bail without returning the package.
   261 		 *
   260 		 *                             Default false.
   262 		 * @param bool        $reply      Whether to bail without returning the package.
   261 		 * @param string      $package The package file name.
   263 		 *                                Default false.
   262 		 * @param WP_Upgrader $this    The WP_Upgrader instance.
   264 		 * @param string      $package    The package file name.
       
   265 		 * @param WP_Upgrader $this       The WP_Upgrader instance.
       
   266 		 * @param array       $hook_extra Extra arguments passed to hooked filters.
   263 		 */
   267 		 */
   264 		$reply = apply_filters( 'upgrader_pre_download', false, $package, $this );
   268 		$reply = apply_filters( 'upgrader_pre_download', false, $package, $this, $hook_extra );
   265 		if ( false !== $reply ) {
   269 		if ( false !== $reply ) {
   266 			return $reply;
   270 			return $reply;
   267 		}
   271 		}
   268 
   272 
   269 		if ( ! preg_match( '!^(http|https|ftp)://!i', $package ) && file_exists( $package ) ) { //Local file or remote?
   273 		if ( ! preg_match( '!^(http|https|ftp)://!i', $package ) && file_exists( $package ) ) { // Local file or remote?
   270 			return $package; //must be a local file..
   274 			return $package; // Must be a local file.
   271 		}
   275 		}
   272 
   276 
   273 		if ( empty( $package ) ) {
   277 		if ( empty( $package ) ) {
   274 			return new WP_Error( 'no_package', $this->strings['no_package'] );
   278 			return new WP_Error( 'no_package', $this->strings['no_package'] );
   275 		}
   279 		}
   302 
   306 
   303 		$this->skin->feedback( 'unpack_package' );
   307 		$this->skin->feedback( 'unpack_package' );
   304 
   308 
   305 		$upgrade_folder = $wp_filesystem->wp_content_dir() . 'upgrade/';
   309 		$upgrade_folder = $wp_filesystem->wp_content_dir() . 'upgrade/';
   306 
   310 
   307 		//Clean up contents of upgrade directory beforehand.
   311 		// Clean up contents of upgrade directory beforehand.
   308 		$upgrade_files = $wp_filesystem->dirlist( $upgrade_folder );
   312 		$upgrade_files = $wp_filesystem->dirlist( $upgrade_folder );
   309 		if ( ! empty( $upgrade_files ) ) {
   313 		if ( ! empty( $upgrade_files ) ) {
   310 			foreach ( $upgrade_files as $file ) {
   314 			foreach ( $upgrade_files as $file ) {
   311 				$wp_filesystem->delete( $upgrade_folder . $file['name'], true );
   315 				$wp_filesystem->delete( $upgrade_folder . $file['name'], true );
   312 			}
   316 			}
   313 		}
   317 		}
   314 
   318 
   315 		// We need a working directory - Strip off any .tmp or .zip suffixes
   319 		// We need a working directory - strip off any .tmp or .zip suffixes.
   316 		$working_dir = $upgrade_folder . basename( basename( $package, '.tmp' ), '.zip' );
   320 		$working_dir = $upgrade_folder . basename( basename( $package, '.tmp' ), '.zip' );
   317 
   321 
   318 		// Clean up working directory
   322 		// Clean up working directory.
   319 		if ( $wp_filesystem->is_dir( $working_dir ) ) {
   323 		if ( $wp_filesystem->is_dir( $working_dir ) ) {
   320 			$wp_filesystem->delete( $working_dir, true );
   324 			$wp_filesystem->delete( $working_dir, true );
   321 		}
   325 		}
   322 
   326 
   323 		// Unzip package to working directory
   327 		// Unzip package to working directory.
   324 		$result = unzip_file( $package, $working_dir );
   328 		$result = unzip_file( $package, $working_dir );
   325 
   329 
   326 		// Once extracted, delete the package if required.
   330 		// Once extracted, delete the package if required.
   327 		if ( $delete_package ) {
   331 		if ( $delete_package ) {
   328 			unlink( $package );
   332 			unlink( $package );
   329 		}
   333 		}
   330 
   334 
   331 		if ( is_wp_error( $result ) ) {
   335 		if ( is_wp_error( $result ) ) {
   332 			$wp_filesystem->delete( $working_dir, true );
   336 			$wp_filesystem->delete( $working_dir, true );
   333 			if ( 'incompatible_archive' == $result->get_error_code() ) {
   337 			if ( 'incompatible_archive' === $result->get_error_code() ) {
   334 				return new WP_Error( 'incompatible_archive', $this->strings['incompatible_archive'], $result->get_error_data() );
   338 				return new WP_Error( 'incompatible_archive', $this->strings['incompatible_archive'], $result->get_error_data() );
   335 			}
   339 			}
   336 			return $result;
   340 			return $result;
   337 		}
   341 		}
   338 
   342 
   343 	 * Flatten the results of WP_Filesystem::dirlist() for iterating over.
   347 	 * Flatten the results of WP_Filesystem::dirlist() for iterating over.
   344 	 *
   348 	 *
   345 	 * @since 4.9.0
   349 	 * @since 4.9.0
   346 	 * @access protected
   350 	 * @access protected
   347 	 *
   351 	 *
   348 	 * @param  array  $nested_files  Array of files as returned by WP_Filesystem::dirlist()
   352 	 * @param array  $nested_files Array of files as returned by WP_Filesystem::dirlist().
   349 	 * @param  string $path          Relative path to prepend to child nodes. Optional.
   353 	 * @param string $path         Relative path to prepend to child nodes. Optional.
   350 	 * @return array $files A flattened array of the $nested_files specified.
   354 	 * @return array A flattened array of the $nested_files specified.
   351 	 */
   355 	 */
   352 	protected function flatten_dirlist( $nested_files, $path = '' ) {
   356 	protected function flatten_dirlist( $nested_files, $path = '' ) {
   353 		$files = array();
   357 		$files = array();
   354 
   358 
   355 		foreach ( $nested_files as $name => $details ) {
   359 		foreach ( $nested_files as $name => $details ) {
   356 			$files[ $path . $name ] = $details;
   360 			$files[ $path . $name ] = $details;
   357 
   361 
   358 			// Append children recursively
   362 			// Append children recursively.
   359 			if ( ! empty( $details['files'] ) ) {
   363 			if ( ! empty( $details['files'] ) ) {
   360 				$children = $this->flatten_dirlist( $details['files'], $path . $name . '/' );
   364 				$children = $this->flatten_dirlist( $details['files'], $path . $name . '/' );
   361 
   365 
   362 				// Merge keeping possible numeric keys, which array_merge() will reindex from 0..n
   366 				// Merge keeping possible numeric keys, which array_merge() will reindex from 0..n.
   363 				$files = $files + $children;
   367 				$files = $files + $children;
   364 			}
   368 			}
   365 		}
   369 		}
   366 
   370 
   367 		return $files;
   371 		return $files;
   385 		// False indicates that the $remote_destination doesn't exist.
   389 		// False indicates that the $remote_destination doesn't exist.
   386 		if ( false === $files ) {
   390 		if ( false === $files ) {
   387 			return true;
   391 			return true;
   388 		}
   392 		}
   389 
   393 
   390 		// Flatten the file list to iterate over
   394 		// Flatten the file list to iterate over.
   391 		$files = $this->flatten_dirlist( $files );
   395 		$files = $this->flatten_dirlist( $files );
   392 
   396 
   393 		// Check all files are writable before attempting to clear the destination.
   397 		// Check all files are writable before attempting to clear the destination.
   394 		$unwritable_files = array();
   398 		$unwritable_files = array();
   395 
   399 
   396 		// Check writability.
   400 		// Check writability.
   397 		foreach ( $files as $filename => $file_details ) {
   401 		foreach ( $files as $filename => $file_details ) {
   398 			if ( ! $wp_filesystem->is_writable( $remote_destination . $filename ) ) {
   402 			if ( ! $wp_filesystem->is_writable( $remote_destination . $filename ) ) {
   399 				// Attempt to alter permissions to allow writes and try again.
   403 				// Attempt to alter permissions to allow writes and try again.
   400 				$wp_filesystem->chmod( $remote_destination . $filename, ( 'd' == $file_details['type'] ? FS_CHMOD_DIR : FS_CHMOD_FILE ) );
   404 				$wp_filesystem->chmod( $remote_destination . $filename, ( 'd' === $file_details['type'] ? FS_CHMOD_DIR : FS_CHMOD_FILE ) );
   401 				if ( ! $wp_filesystem->is_writable( $remote_destination . $filename ) ) {
   405 				if ( ! $wp_filesystem->is_writable( $remote_destination . $filename ) ) {
   402 					$unwritable_files[] = $filename;
   406 					$unwritable_files[] = $filename;
   403 				}
   407 				}
   404 			}
   408 			}
   405 		}
   409 		}
   447 	 */
   451 	 */
   448 	public function install_package( $args = array() ) {
   452 	public function install_package( $args = array() ) {
   449 		global $wp_filesystem, $wp_theme_directories;
   453 		global $wp_filesystem, $wp_theme_directories;
   450 
   454 
   451 		$defaults = array(
   455 		$defaults = array(
   452 			'source'                      => '', // Please always pass this
   456 			'source'                      => '', // Please always pass this.
   453 			'destination'                 => '', // and this
   457 			'destination'                 => '', // ...and this.
   454 			'clear_destination'           => false,
   458 			'clear_destination'           => false,
   455 			'clear_working'               => false,
   459 			'clear_working'               => false,
   456 			'abort_if_destination_exists' => true,
   460 			'abort_if_destination_exists' => true,
   457 			'hook_extra'                  => array(),
   461 			'hook_extra'                  => array(),
   458 		);
   462 		);
   462 		// These were previously extract()'d.
   466 		// These were previously extract()'d.
   463 		$source            = $args['source'];
   467 		$source            = $args['source'];
   464 		$destination       = $args['destination'];
   468 		$destination       = $args['destination'];
   465 		$clear_destination = $args['clear_destination'];
   469 		$clear_destination = $args['clear_destination'];
   466 
   470 
   467 		@set_time_limit( 300 );
   471 		set_time_limit( 300 );
   468 
   472 
   469 		if ( empty( $source ) || empty( $destination ) ) {
   473 		if ( empty( $source ) || empty( $destination ) ) {
   470 			return new WP_Error( 'bad_request', $this->strings['bad_request'] );
   474 			return new WP_Error( 'bad_request', $this->strings['bad_request'] );
   471 		}
   475 		}
   472 		$this->skin->feedback( 'installing_package' );
   476 		$this->skin->feedback( 'installing_package' );
   487 
   491 
   488 		if ( is_wp_error( $res ) ) {
   492 		if ( is_wp_error( $res ) ) {
   489 			return $res;
   493 			return $res;
   490 		}
   494 		}
   491 
   495 
   492 		//Retain the Original source and destinations
   496 		// Retain the original source and destinations.
   493 		$remote_source     = $args['source'];
   497 		$remote_source     = $args['source'];
   494 		$local_destination = $destination;
   498 		$local_destination = $destination;
   495 
   499 
   496 		$source_files       = array_keys( $wp_filesystem->dirlist( $remote_source ) );
   500 		$source_files       = array_keys( $wp_filesystem->dirlist( $remote_source ) );
   497 		$remote_destination = $wp_filesystem->find_folder( $local_destination );
   501 		$remote_destination = $wp_filesystem->find_folder( $local_destination );
   498 
   502 
   499 		//Locate which directory to copy to the new folder, This is based on the actual folder holding the files.
   503 		// Locate which directory to copy to the new folder. This is based on the actual folder holding the files.
   500 		if ( 1 == count( $source_files ) && $wp_filesystem->is_dir( trailingslashit( $args['source'] ) . $source_files[0] . '/' ) ) { //Only one folder? Then we want its contents.
   504 		if ( 1 === count( $source_files ) && $wp_filesystem->is_dir( trailingslashit( $args['source'] ) . $source_files[0] . '/' ) ) {
       
   505 			// Only one folder? Then we want its contents.
   501 			$source = trailingslashit( $args['source'] ) . trailingslashit( $source_files[0] );
   506 			$source = trailingslashit( $args['source'] ) . trailingslashit( $source_files[0] );
   502 		} elseif ( count( $source_files ) == 0 ) {
   507 		} elseif ( count( $source_files ) == 0 ) {
   503 			return new WP_Error( 'incompatible_archive_empty', $this->strings['incompatible_archive'], $this->strings['no_files'] ); // There are no files?
   508 			// There are no files?
   504 		} else { // It's only a single file, the upgrader will use the folder name of this file as the destination folder. Folder name is based on zip filename.
   509 			return new WP_Error( 'incompatible_archive_empty', $this->strings['incompatible_archive'], $this->strings['no_files'] );
       
   510 		} else {
       
   511 			// It's only a single file, the upgrader will use the folder name of this file as the destination folder.
       
   512 			// Folder name is based on zip filename.
   505 			$source = trailingslashit( $args['source'] );
   513 			$source = trailingslashit( $args['source'] );
   506 		}
   514 		}
   507 
   515 
   508 		/**
   516 		/**
   509 		 * Filters the source file location for the upgrade package.
   517 		 * Filters the source file location for the upgrade package.
   538 
   546 
   539 		if ( is_array( $wp_theme_directories ) ) {
   547 		if ( is_array( $wp_theme_directories ) ) {
   540 			$protected_directories = array_merge( $protected_directories, $wp_theme_directories );
   548 			$protected_directories = array_merge( $protected_directories, $wp_theme_directories );
   541 		}
   549 		}
   542 
   550 
   543 		if ( in_array( $destination, $protected_directories ) ) {
   551 		if ( in_array( $destination, $protected_directories, true ) ) {
   544 			$remote_destination = trailingslashit( $remote_destination ) . trailingslashit( basename( $source ) );
   552 			$remote_destination = trailingslashit( $remote_destination ) . trailingslashit( basename( $source ) );
   545 			$destination        = trailingslashit( $destination ) . trailingslashit( basename( $source ) );
   553 			$destination        = trailingslashit( $destination ) . trailingslashit( basename( $source ) );
   546 		}
   554 		}
   547 
   555 
   548 		if ( $clear_destination ) {
   556 		if ( $clear_destination ) {
   554 			/**
   562 			/**
   555 			 * Filters whether the upgrader cleared the destination.
   563 			 * Filters whether the upgrader cleared the destination.
   556 			 *
   564 			 *
   557 			 * @since 2.8.0
   565 			 * @since 2.8.0
   558 			 *
   566 			 *
   559 			 * @param mixed  $removed            Whether the destination was cleared. true on success, WP_Error on failure
   567 			 * @param true|WP_Error $removed            Whether the destination was cleared. true upon success, WP_Error on failure.
   560 			 * @param string $local_destination  The local package destination.
   568 			 * @param string        $local_destination  The local package destination.
   561 			 * @param string $remote_destination The remote package destination.
   569 			 * @param string        $remote_destination The remote package destination.
   562 			 * @param array  $hook_extra         Extra arguments passed to hooked filters.
   570 			 * @param array         $hook_extra         Extra arguments passed to hooked filters.
   563 			 */
   571 			 */
   564 			$removed = apply_filters( 'upgrader_clear_destination', $removed, $local_destination, $remote_destination, $args['hook_extra'] );
   572 			$removed = apply_filters( 'upgrader_clear_destination', $removed, $local_destination, $remote_destination, $args['hook_extra'] );
   565 
   573 
   566 			if ( is_wp_error( $removed ) ) {
   574 			if ( is_wp_error( $removed ) ) {
   567 				return $removed;
   575 				return $removed;
   568 			}
   576 			}
   569 		} elseif ( $args['abort_if_destination_exists'] && $wp_filesystem->exists( $remote_destination ) ) {
   577 		} elseif ( $args['abort_if_destination_exists'] && $wp_filesystem->exists( $remote_destination ) ) {
   570 			//If we're not clearing the destination folder and something exists there already, Bail.
   578 			// If we're not clearing the destination folder and something exists there already, bail.
   571 			//But first check to see if there are actually any files in the folder.
   579 			// But first check to see if there are actually any files in the folder.
   572 			$_files = $wp_filesystem->dirlist( $remote_destination );
   580 			$_files = $wp_filesystem->dirlist( $remote_destination );
   573 			if ( ! empty( $_files ) ) {
   581 			if ( ! empty( $_files ) ) {
   574 				$wp_filesystem->delete( $remote_source, true ); //Clear out the source files.
   582 				$wp_filesystem->delete( $remote_source, true ); // Clear out the source files.
   575 				return new WP_Error( 'folder_exists', $this->strings['folder_exists'], $remote_destination );
   583 				return new WP_Error( 'folder_exists', $this->strings['folder_exists'], $remote_destination );
   576 			}
   584 			}
   577 		}
   585 		}
   578 
   586 
   579 		//Create destination if needed
   587 		// Create destination if needed.
   580 		if ( ! $wp_filesystem->exists( $remote_destination ) ) {
   588 		if ( ! $wp_filesystem->exists( $remote_destination ) ) {
   581 			if ( ! $wp_filesystem->mkdir( $remote_destination, FS_CHMOD_DIR ) ) {
   589 			if ( ! $wp_filesystem->mkdir( $remote_destination, FS_CHMOD_DIR ) ) {
   582 				return new WP_Error( 'mkdir_failed_destination', $this->strings['mkdir_failed'], $remote_destination );
   590 				return new WP_Error( 'mkdir_failed_destination', $this->strings['mkdir_failed'], $remote_destination );
   583 			}
   591 			}
   584 		}
   592 		}
       
   593 
   585 		// Copy new version of item into place.
   594 		// Copy new version of item into place.
   586 		$result = copy_dir( $source, $remote_destination );
   595 		$result = copy_dir( $source, $remote_destination );
   587 		if ( is_wp_error( $result ) ) {
   596 		if ( is_wp_error( $result ) ) {
   588 			if ( $args['clear_working'] ) {
   597 			if ( $args['clear_working'] ) {
   589 				$wp_filesystem->delete( $remote_source, true );
   598 				$wp_filesystem->delete( $remote_source, true );
   590 			}
   599 			}
   591 			return $result;
   600 			return $result;
   592 		}
   601 		}
   593 
   602 
   594 		//Clear the Working folder?
   603 		// Clear the working folder?
   595 		if ( $args['clear_working'] ) {
   604 		if ( $args['clear_working'] ) {
   596 			$wp_filesystem->delete( $remote_source, true );
   605 			$wp_filesystem->delete( $remote_source, true );
   597 		}
   606 		}
   598 
   607 
   599 		$destination_name = basename( str_replace( $local_destination, '', $destination ) );
   608 		$destination_name = basename( str_replace( $local_destination, '', $destination ) );
   600 		if ( '.' == $destination_name ) {
   609 		if ( '.' === $destination_name ) {
   601 			$destination_name = '';
   610 			$destination_name = '';
   602 		}
   611 		}
   603 
   612 
   604 		$this->result = compact( 'source', 'source_files', 'destination', 'destination_name', 'local_destination', 'remote_destination', 'clear_destination' );
   613 		$this->result = compact( 'source', 'source_files', 'destination', 'destination_name', 'local_destination', 'remote_destination', 'clear_destination' );
   605 
   614 
   617 		if ( is_wp_error( $res ) ) {
   626 		if ( is_wp_error( $res ) ) {
   618 			$this->result = $res;
   627 			$this->result = $res;
   619 			return $res;
   628 			return $res;
   620 		}
   629 		}
   621 
   630 
   622 		//Bombard the calling function will all the info which we've just used.
   631 		// Bombard the calling function will all the info which we've just used.
   623 		return $this->result;
   632 		return $this->result;
   624 	}
   633 	}
   625 
   634 
   626 	/**
   635 	/**
   627 	 * Run an upgrade/installation.
   636 	 * Run an upgrade/installation.
   651 	 *                                               WP_Upgrader::header() and WP_Upgrader::footer()
   660 	 *                                               WP_Upgrader::header() and WP_Upgrader::footer()
   652 	 *                                               aren't called. Default false.
   661 	 *                                               aren't called. Default false.
   653 	 *     @type array  $hook_extra                  Extra arguments to pass to the filter hooks called by
   662 	 *     @type array  $hook_extra                  Extra arguments to pass to the filter hooks called by
   654 	 *                                               WP_Upgrader::run().
   663 	 *                                               WP_Upgrader::run().
   655 	 * }
   664 	 * }
   656 	 * @return array|false|WP_error The result from self::install_package() on success, otherwise a WP_Error,
   665 	 * @return array|false|WP_Error The result from self::install_package() on success, otherwise a WP_Error,
   657 	 *                              or false if unable to connect to the filesystem.
   666 	 *                              or false if unable to connect to the filesystem.
   658 	 */
   667 	 */
   659 	public function run( $options ) {
   668 	public function run( $options ) {
   660 
   669 
   661 		$defaults = array(
   670 		$defaults = array(
   662 			'package'                     => '', // Please always pass this.
   671 			'package'                     => '', // Please always pass this.
   663 			'destination'                 => '', // And this
   672 			'destination'                 => '', // ...and this.
   664 			'clear_destination'           => false,
   673 			'clear_destination'           => false,
   665 			'abort_if_destination_exists' => true, // Abort if the Destination directory exists, Pass clear_destination as false please
   674 			'abort_if_destination_exists' => true, // Abort if the destination directory exists. Pass clear_destination as false please.
   666 			'clear_working'               => true,
   675 			'clear_working'               => true,
   667 			'is_multi'                    => false,
   676 			'is_multi'                    => false,
   668 			'hook_extra'                  => array(), // Pass any extra $hook_extra args here, this will be passed to any hooked filters.
   677 			'hook_extra'                  => array(), // Pass any extra $hook_extra args here, this will be passed to any hooked filters.
   669 		);
   678 		);
   670 
   679 
   700 		 *     }
   709 		 *     }
   701 		 * }
   710 		 * }
   702 		 */
   711 		 */
   703 		$options = apply_filters( 'upgrader_package_options', $options );
   712 		$options = apply_filters( 'upgrader_package_options', $options );
   704 
   713 
   705 		if ( ! $options['is_multi'] ) { // call $this->header separately if running multiple times
   714 		if ( ! $options['is_multi'] ) { // Call $this->header separately if running multiple times.
   706 			$this->skin->header();
   715 			$this->skin->header();
   707 		}
   716 		}
   708 
   717 
   709 		// Connect to the Filesystem first.
   718 		// Connect to the filesystem first.
   710 		$res = $this->fs_connect( array( WP_CONTENT_DIR, $options['destination'] ) );
   719 		$res = $this->fs_connect( array( WP_CONTENT_DIR, $options['destination'] ) );
   711 		// Mainly for non-connected filesystem.
   720 		// Mainly for non-connected filesystem.
   712 		if ( ! $res ) {
   721 		if ( ! $res ) {
   713 			if ( ! $options['is_multi'] ) {
   722 			if ( ! $options['is_multi'] ) {
   714 				$this->skin->footer();
   723 				$this->skin->footer();
   729 
   738 
   730 		/*
   739 		/*
   731 		 * Download the package (Note, This just returns the filename
   740 		 * Download the package (Note, This just returns the filename
   732 		 * of the file if the package is a local file)
   741 		 * of the file if the package is a local file)
   733 		 */
   742 		 */
   734 		$download = $this->download_package( $options['package'], true );
   743 		$download = $this->download_package( $options['package'], true, $options['hook_extra'] );
   735 
   744 
   736 		// Allow for signature soft-fail.
   745 		// Allow for signature soft-fail.
   737 		// WARNING: This may be removed in the future.
   746 		// WARNING: This may be removed in the future.
   738 		if ( is_wp_error( $download ) && $download->get_error_data( 'softfail-filename' ) ) {
   747 		if ( is_wp_error( $download ) && $download->get_error_data( 'softfail-filename' ) ) {
   739 
   748 
   740 			// Don't output the 'no signature could be found' failure message for now.
   749 			// Don't output the 'no signature could be found' failure message for now.
   741 			if ( 'signature_verification_no_signature' != $download->get_error_code() || WP_DEBUG ) {
   750 			if ( 'signature_verification_no_signature' !== $download->get_error_code() || WP_DEBUG ) {
   742 				// Outout the failure error as a normal feedback, and not as an error:
   751 				// Output the failure error as a normal feedback, and not as an error.
   743 				$this->skin->feedback( $download->get_error_message() );
   752 				$this->skin->feedback( $download->get_error_message() );
   744 
   753 
   745 				// Report this failure back to WordPress.org for debugging purposes.
   754 				// Report this failure back to WordPress.org for debugging purposes.
   746 				wp_version_check(
   755 				wp_version_check(
   747 					array(
   756 					array(
   762 				$this->skin->footer();
   771 				$this->skin->footer();
   763 			}
   772 			}
   764 			return $download;
   773 			return $download;
   765 		}
   774 		}
   766 
   775 
   767 		$delete_package = ( $download != $options['package'] ); // Do not delete a "local" file
   776 		$delete_package = ( $download != $options['package'] ); // Do not delete a "local" file.
   768 
   777 
   769 		// Unzips the file into a temporary directory.
   778 		// Unzips the file into a temporary directory.
   770 		$working_dir = $this->unpack_package( $download, $delete_package );
   779 		$working_dir = $this->unpack_package( $download, $delete_package );
   771 		if ( is_wp_error( $working_dir ) ) {
   780 		if ( is_wp_error( $working_dir ) ) {
   772 			$this->skin->error( $working_dir );
   781 			$this->skin->error( $working_dir );
   790 		);
   799 		);
   791 
   800 
   792 		$this->skin->set_result( $result );
   801 		$this->skin->set_result( $result );
   793 		if ( is_wp_error( $result ) ) {
   802 		if ( is_wp_error( $result ) ) {
   794 			$this->skin->error( $result );
   803 			$this->skin->error( $result );
   795 			$this->skin->feedback( 'process_failed' );
   804 
       
   805 			if ( ! method_exists( $this->skin, 'hide_process_failed' ) || ! $this->skin->hide_process_failed( $result ) ) {
       
   806 				$this->skin->feedback( 'process_failed' );
       
   807 			}
   796 		} else {
   808 		} else {
   797 			// Installation succeeded.
   809 			// Installation succeeded.
   798 			$this->skin->feedback( 'process_success' );
   810 			$this->skin->feedback( 'process_success' );
   799 		}
   811 		}
   800 
   812 
   854 	public function maintenance_mode( $enable = false ) {
   866 	public function maintenance_mode( $enable = false ) {
   855 		global $wp_filesystem;
   867 		global $wp_filesystem;
   856 		$file = $wp_filesystem->abspath() . '.maintenance';
   868 		$file = $wp_filesystem->abspath() . '.maintenance';
   857 		if ( $enable ) {
   869 		if ( $enable ) {
   858 			$this->skin->feedback( 'maintenance_start' );
   870 			$this->skin->feedback( 'maintenance_start' );
   859 			// Create maintenance file to signal that we are upgrading
   871 			// Create maintenance file to signal that we are upgrading.
   860 			$maintenance_string = '<?php $upgrading = ' . time() . '; ?>';
   872 			$maintenance_string = '<?php $upgrading = ' . time() . '; ?>';
   861 			$wp_filesystem->delete( $file );
   873 			$wp_filesystem->delete( $file );
   862 			$wp_filesystem->put_contents( $file, $maintenance_string, FS_CHMOD_FILE );
   874 			$wp_filesystem->put_contents( $file, $maintenance_string, FS_CHMOD_FILE );
   863 		} elseif ( ! $enable && $wp_filesystem->exists( $file ) ) {
   875 		} elseif ( ! $enable && $wp_filesystem->exists( $file ) ) {
   864 			$this->skin->feedback( 'maintenance_end' );
   876 			$this->skin->feedback( 'maintenance_end' );