Install a package.
Copies the contents of a package from a source directory, and installs them in a destination directory. Optionally removes the source. It can also optionally clear out the destination folder if it already exists.
$argsarray|stringoptional
source stringdestination stringclear_destination boolclear_working boolabort_if_destination_exists boolhook_extra arrayDefault:array()
WP_Upgrader::$result), or a WP_Error on failure.public function install_package( $args = array() ) {
global $wp_filesystem, $wp_theme_directories;
$defaults = array(
'source' => '', // Please always pass this.
'destination' => '', // ...and this.
'clear_destination' => false,
'clear_working' => false,
'abort_if_destination_exists' => true,
'hook_extra' => array(),
);
$args = wp_parse_args( $args, $defaults );
// These were previously extract()'d.
$source = $args['source'];
$destination = $args['destination'];
$clear_destination = $args['clear_destination'];
// Give the upgrade an additional 300 seconds(5 minutes) to ensure the install doesn't prematurely timeout having used up the maximum script execution time upacking and downloading in WP_Upgrader->run.
if ( function_exists( 'set_time_limit' ) ) {
set_time_limit( 300 );
}
if (
( ! is_string( $source ) || '' === $source || trim( $source ) !== $source ) ||
( ! is_string( $destination ) || '' === $destination || trim( $destination ) !== $destination )
) {
return new WP_Error( 'bad_request', $this->strings['bad_request'] );
}
$this->skin->feedback( 'installing_package' );
/**
* Filters the installation response before the installation has started.
*
* Returning a value that could be evaluated as a `WP_Error` will effectively
* short-circuit the installation, returning that value instead.
*
* @since 2.8.0
*
* @param bool|WP_Error $response Installation response.
* @param array $hook_extra Extra arguments passed to hooked filters.
*/
$res = apply_filters( 'upgrader_pre_install', true, $args['hook_extra'] );
if ( is_wp_error( $res ) ) {
return $res;
}
// Retain the original source and destinations.
$remote_source = $args['source'];
$local_destination = $destination;
$dirlist = $wp_filesystem->dirlist( $remote_source );
if ( false === $dirlist ) {
return new WP_Error( 'source_read_failed', $this->strings['fs_error'], $this->strings['dir_not_readable'] );
}
$source_files = array_keys( $dirlist );
$remote_destination = $wp_filesystem->find_folder( $local_destination );
// Locate which directory to copy to the new folder. This is based on the actual folder holding the files.
if ( 1 === count( $source_files ) && $wp_filesystem->is_dir( trailingslashit( $args['source'] ) . $source_files[0] . '/' ) ) {
// Only one folder? Then we want its contents.
$source = trailingslashit( $args['source'] ) . trailingslashit( $source_files[0] );
} elseif ( 0 === count( $source_files ) ) {
// There are no files?
return new WP_Error( 'incompatible_archive_empty', $this->strings['incompatible_archive'], $this->strings['no_files'] );
} 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.
*/
$source = trailingslashit( $args['source'] );
}
/**
* Filters the source file location for the upgrade package.
*
* @since 2.8.0
* @since 4.4.0 The $hook_extra parameter became available.
*
* @param string $source File source location.
* @param string $remote_source Remote file source location.
* @param WP_Upgrader $upgrader WP_Upgrader instance.
* @param array $hook_extra Extra arguments passed to hooked filters.
*/
$source = apply_filters( 'upgrader_source_selection', $source, $remote_source, $this, $args['hook_extra'] );
if ( is_wp_error( $source ) ) {
return $source;
}
if ( ! empty( $args['hook_extra']['temp_backup'] ) ) {
$temp_backup = $this->move_to_temp_backup_dir( $args['hook_extra']['temp_backup'] );
if ( is_wp_error( $temp_backup ) ) {
return $temp_backup;
}
$this->temp_backups[] = $args['hook_extra']['temp_backup'];
}
// Has the source location changed? If so, we need a new source_files list.
if ( $source !== $remote_source ) {
$dirlist = $wp_filesystem->dirlist( $source );
if ( false === $dirlist ) {
return new WP_Error( 'new_source_read_failed', $this->strings['fs_error'], $this->strings['dir_not_readable'] );
}
$source_files = array_keys( $dirlist );
}
/*
* Protection against deleting files in any important base directories.
* Theme_Upgrader & Plugin_Upgrader also trigger this, as they pass the
* destination directory (WP_PLUGIN_DIR / wp-content/themes) intending
* to copy the directory into the directory, whilst they pass the source
* as the actual files to copy.
*/
$protected_directories = array( ABSPATH, WP_CONTENT_DIR, WP_PLUGIN_DIR, WP_CONTENT_DIR . '/themes' );
if ( is_array( $wp_theme_directories ) ) {
$protected_directories = array_merge( $protected_directories, $wp_theme_directories );
}
if ( in_array( $destination, $protected_directories, true ) ) {
$remote_destination = trailingslashit( $remote_destination ) . trailingslashit( basename( $source ) );
$destination = trailingslashit( $destination ) . trailingslashit( basename( $source ) );
}
if ( $clear_destination ) {
// We're going to clear the destination if there's something there.
$this->skin->feedback( 'remove_old' );
$removed = $this->clear_destination( $remote_destination );
/**
* Filters whether the upgrader cleared the destination.
*
* @since 2.8.0
*
* @param true|WP_Error $removed Whether the destination was cleared.
* True upon success, WP_Error on failure.
* @param string $local_destination The local package destination.
* @param string $remote_destination The remote package destination.
* @param array $hook_extra Extra arguments passed to hooked filters.
*/
$removed = apply_filters( 'upgrader_clear_destination', $removed, $local_destination, $remote_destination, $args['hook_extra'] );
if ( is_wp_error( $removed ) ) {
return $removed;
}
} elseif ( $args['abort_if_destination_exists'] && $wp_filesystem->exists( $remote_destination ) ) {
/*
* If we're not clearing the destination folder and something exists there already, bail.
* But first check to see if there are actually any files in the folder.
*/
$_files = $wp_filesystem->dirlist( $remote_destination );
if ( ! empty( $_files ) ) {
$wp_filesystem->delete( $remote_source, true ); // Clear out the source files.
return new WP_Error( 'folder_exists', $this->strings['folder_exists'], $remote_destination );
}
}
/*
* If 'clear_working' is false, the source should not be removed, so use copy_dir() instead.
*
* Partial updates, like language packs, may want to retain the destination.
* If the destination exists or has contents, this may be a partial update,
* and the destination should not be removed, so use copy_dir() instead.
*/
if ( $args['clear_working']
&& (
// Destination does not exist or has no contents.
! $wp_filesystem->exists( $remote_destination )
|| empty( $wp_filesystem->dirlist( $remote_destination ) )
)
) {
$result = move_dir( $source, $remote_destination, true );
} else {
// Create destination if needed.
if ( ! $wp_filesystem->exists( $remote_destination ) ) {
if ( ! $wp_filesystem->mkdir( $remote_destination, FS_CHMOD_DIR ) ) {
return new WP_Error( 'mkdir_failed_destination', $this->strings['mkdir_failed'], $remote_destination );
}
}
$result = copy_dir( $source, $remote_destination );
}
// Clear the working directory?
if ( $args['clear_working'] ) {
$wp_filesystem->delete( $remote_source, true );
}
if ( is_wp_error( $result ) ) {
return $result;
}
$destination_name = basename( str_replace( $local_destination, '', $destination ) );
if ( '.' === $destination_name ) {
$destination_name = '';
}
$this->result = compact( 'source', 'source_files', 'destination', 'destination_name', 'local_destination', 'remote_destination', 'clear_destination' );
/**
* Filters the installation response after the installation has finished.
*
* @since 2.8.0
*
* @param bool $response Installation response.
* @param array $hook_extra Extra arguments passed to hooked filters.
* @param array $result Installation result data.
*/
$res = apply_filters( 'upgrader_post_install', true, $args['hook_extra'], $this->result );
if ( is_wp_error( $res ) ) {
$this->result = $res;
return $res;
}
// Bombard the calling function will all the info which we've just used.
return $this->result;
}
Filters whether the upgrader cleared the destination.
Filters the installation response after the installation has finished.
Filters the installation response before the installation has started.
Filters the source file location for the upgrade package.
| Version | Description |
|---|---|
| 6.2.0 | Use move_dir() instead of copy_dir() when possible. |
| 2.8.0 | Introduced. |
© 2003–2024 WordPress Foundation
Licensed under the GNU GPLv2+ License.
https://developer.wordpress.org/reference/classes/wp_upgrader/install_package