X-Git-Url: https://scripts.mit.edu/gitweb/autoinstalls/wordpress.git/blobdiff_plain/61343b82c4f0da4c68e4c6373daafff4a81efdd1..42aebe6945a3a60c8f73853bea2c8b202d64a20b:/wp-admin/includes/class-wp-upgrader.php
diff --git a/wp-admin/includes/class-wp-upgrader.php b/wp-admin/includes/class-wp-upgrader.php
index 6c399c7e..330f5ea0 100644
--- a/wp-admin/includes/class-wp-upgrader.php
+++ b/wp-admin/includes/class-wp-upgrader.php
@@ -11,11 +11,11 @@
* @since 2.8.0
*/
+require ABSPATH . 'wp-admin/includes/class-wp-upgrader-skins.php';
+
/**
* WordPress Upgrader class for Upgrading/Installing a local set of files via the Filesystem Abstraction classes from a Zip file.
*
- * @TODO More Detailed docs, for methods as well.
- *
* @package WordPress
* @subpackage Upgrader
* @since 2.8.0
@@ -93,8 +93,8 @@ class WP_Upgrader {
if ( ! $wp_filesystem->wp_plugins_dir() )
return new WP_Error('fs_no_plugins_dir', $this->strings['fs_no_plugins_dir']);
break;
- case WP_CONTENT_DIR . '/themes':
- if ( ! $wp_filesystem->find_folder(WP_CONTENT_DIR . '/themes') )
+ case get_theme_root():
+ if ( ! $wp_filesystem->wp_themes_dir() )
return new WP_Error('fs_no_themes_dir', $this->strings['fs_no_themes_dir']);
break;
default:
@@ -108,6 +108,19 @@ class WP_Upgrader {
function download_package($package) {
+ /**
+ * Filter whether to return the package.
+ *
+ * @since 3.7.0
+ *
+ * @param bool $reply Whether to bail without returning the package. Default is false.
+ * @param string $package The package file name.
+ * @param object $this The WP_Upgrader instance.
+ */
+ $reply = apply_filters( 'upgrader_pre_download', false, $package, $this );
+ if ( false !== $reply )
+ return $reply;
+
if ( ! preg_match('!^(http|https|ftp)://!i', $package) && file_exists($package) ) //Local file or remote?
return $package; //must be a local file..
@@ -146,7 +159,7 @@ class WP_Upgrader {
$wp_filesystem->delete($working_dir, true);
// Unzip package to working directory
- $result = unzip_file($package, $working_dir); //TODO optimizations, Copy when Move/Rename would suffice?
+ $result = unzip_file( $package, $working_dir );
// Once extracted, delete the package if required.
if ( $delete_package )
@@ -163,12 +176,17 @@ class WP_Upgrader {
return $working_dir;
}
- function install_package($args = array()) {
- global $wp_filesystem;
- $defaults = array( 'source' => '', 'destination' => '', //Please always pass these
- 'clear_destination' => false, 'clear_working' => false,
- 'abort_if_destination_exists' => true,
- 'hook_extra' => array());
+ 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);
extract($args);
@@ -195,7 +213,7 @@ class WP_Upgrader {
if ( 1 == count($source_files) && $wp_filesystem->is_dir( trailingslashit($source) . $source_files[0] . '/') ) //Only one folder? Then we want its contents.
$source = trailingslashit($source) . trailingslashit($source_files[0]);
elseif ( count($source_files) == 0 )
- return new WP_Error( 'incompatible_archive', $this->strings['incompatible_archive'], $this->strings['no_files'] ); //There are no files?
+ return new WP_Error( 'incompatible_archive_empty', $this->strings['incompatible_archive'], $this->strings['no_files'] ); // There are no files?
else //It's only a single file, the upgrader will use the foldername of this file as the destination folder. foldername is based on zip filename.
$source = trailingslashit($source);
@@ -208,8 +226,13 @@ class WP_Upgrader {
if ( $source !== $remote_source )
$source_files = array_keys( $wp_filesystem->dirlist($source) );
- //Protection against deleting files in any important base directories.
- if ( in_array( $destination, array(ABSPATH, WP_CONTENT_DIR, WP_PLUGIN_DIR, WP_CONTENT_DIR . '/themes') ) ) {
+ // 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 ) ) {
$remote_destination = trailingslashit($remote_destination) . trailingslashit(basename($source));
$destination = trailingslashit($destination) . trailingslashit(basename($source));
}
@@ -239,7 +262,7 @@ class WP_Upgrader {
//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', $this->strings['mkdir_failed'], $remote_destination);
+ return new WP_Error( 'mkdir_failed_destination', $this->strings['mkdir_failed'], $remote_destination );
// Copy new version of item into place.
$result = copy_dir($source, $remote_destination);
@@ -271,38 +294,48 @@ class WP_Upgrader {
function run($options) {
- $defaults = array( 'package' => '', //Please always pass this.
- 'destination' => '', //And this
- 'clear_destination' => false,
- 'abort_if_destination_exists' => true, // Abort if the Destination directory exists, Pass clear_destination as false please
- 'clear_working' => true,
- 'is_multi' => false,
- 'hook_extra' => array() //Pass any extra $hook_extra args here, this will be passed to any hooked filters.
- );
+ $defaults = array(
+ 'package' => '', // Please always pass this.
+ 'destination' => '', // And this
+ 'clear_destination' => false,
+ 'abort_if_destination_exists' => true, // Abort if the Destination directory exists, Pass clear_destination as false please
+ 'clear_working' => true,
+ 'is_multi' => false,
+ 'hook_extra' => array() // Pass any extra $hook_extra args here, this will be passed to any hooked filters.
+ );
$options = wp_parse_args($options, $defaults);
extract($options);
- //Connect to the Filesystem first.
+ if ( ! $is_multi ) // call $this->header separately if running multiple times
+ $this->skin->header();
+
+ // Connect to the Filesystem first.
$res = $this->fs_connect( array(WP_CONTENT_DIR, $destination) );
- if ( ! $res ) //Mainly for non-connected filesystem.
+ // Mainly for non-connected filesystem.
+ if ( ! $res ) {
+ if ( ! $is_multi )
+ $this->skin->footer();
return false;
+ }
+
+ $this->skin->before();
if ( is_wp_error($res) ) {
$this->skin->error($res);
+ $this->skin->after();
+ if ( ! $is_multi )
+ $this->skin->footer();
return $res;
}
- if ( !$is_multi ) // call $this->header separately if running multiple times
- $this->skin->header();
-
- $this->skin->before();
-
//Download the package (Note, This just returns the filename of the file if the package is a local file)
$download = $this->download_package( $package );
if ( is_wp_error($download) ) {
$this->skin->error($download);
$this->skin->after();
+ if ( ! $is_multi )
+ $this->skin->footer();
return $download;
}
@@ -313,18 +346,21 @@ class WP_Upgrader {
if ( is_wp_error($working_dir) ) {
$this->skin->error($working_dir);
$this->skin->after();
+ if ( ! $is_multi )
+ $this->skin->footer();
return $working_dir;
}
//With the given options, this installs it to the destination directory.
$result = $this->install_package( array(
- 'source' => $working_dir,
- 'destination' => $destination,
- 'clear_destination' => $clear_destination,
- 'abort_if_destination_exists' => $abort_if_destination_exists,
- 'clear_working' => $clear_working,
- 'hook_extra' => $hook_extra
- ) );
+ 'source' => $working_dir,
+ 'destination' => $destination,
+ 'clear_destination' => $clear_destination,
+ 'abort_if_destination_exists' => $abort_if_destination_exists,
+ 'clear_working' => $clear_working,
+ 'hook_extra' => $hook_extra
+ ) );
+
$this->skin->set_result($result);
if ( is_wp_error($result) ) {
$this->skin->error($result);
@@ -333,10 +369,13 @@ class WP_Upgrader {
//Install Succeeded
$this->skin->feedback('process_success');
}
+
$this->skin->after();
- if ( !$is_multi )
+ if ( ! $is_multi ) {
+ do_action( 'upgrader_process_complete', $this, $hook_extra );
$this->skin->footer();
+ }
return $result;
}
@@ -361,8 +400,6 @@ class WP_Upgrader {
/**
* Plugin Upgrader class for WordPress Plugins, It is designed to upgrade/install plugins from a local zip, remote zip URL, or uploaded zip file.
*
- * @TODO More Detailed docs, for methods as well.
- *
* @package WordPress
* @subpackage Upgrader
* @since 2.8.0
@@ -394,35 +431,46 @@ class Plugin_Upgrader extends WP_Upgrader {
$this->strings['process_success'] = __('Plugin installed successfully.');
}
- function install($package) {
+ function install( $package, $args = array() ) {
+
+ $defaults = array(
+ 'clear_update_cache' => true,
+ );
+ $parsed_args = wp_parse_args( $args, $defaults );
$this->init();
$this->install_strings();
- add_filter('upgrader_source_selection', array(&$this, 'check_package') );
-
- $this->run(array(
- 'package' => $package,
- 'destination' => WP_PLUGIN_DIR,
- 'clear_destination' => false, //Do not overwrite files.
- 'clear_working' => true,
- 'hook_extra' => array()
- ));
+ add_filter('upgrader_source_selection', array($this, 'check_package') );
+
+ $this->run( array(
+ 'package' => $package,
+ 'destination' => WP_PLUGIN_DIR,
+ 'clear_destination' => false, // Do not overwrite files.
+ 'clear_working' => true,
+ 'hook_extra' => array(
+ 'type' => 'plugin',
+ 'action' => 'install',
+ )
+ ) );
- remove_filter('upgrader_source_selection', array(&$this, 'check_package') );
+ remove_filter('upgrader_source_selection', array($this, 'check_package') );
if ( ! $this->result || is_wp_error($this->result) )
return $this->result;
// Force refresh of plugin update information
- delete_site_transient('update_plugins');
- wp_cache_delete( 'plugins', 'plugins' );
- do_action( 'upgrader_process_complete', $this, array( 'action' => 'install', 'type' => 'plugin' ), $package );
+ wp_clean_plugins_cache( $parsed_args['clear_update_cache'] );
return true;
}
- function upgrade($plugin) {
+ function upgrade( $plugin, $args = array() ) {
+
+ $defaults = array(
+ 'clear_update_cache' => true,
+ );
+ $parsed_args = wp_parse_args( $args, $defaults );
$this->init();
$this->upgrade_strings();
@@ -439,34 +487,41 @@ class Plugin_Upgrader extends WP_Upgrader {
// Get the URL to the zip file
$r = $current->response[ $plugin ];
- add_filter('upgrader_pre_install', array(&$this, 'deactivate_plugin_before_upgrade'), 10, 2);
- add_filter('upgrader_clear_destination', array(&$this, 'delete_old_plugin'), 10, 4);
- //'source_selection' => array(&$this, 'source_selection'), //there's a trac ticket to move up the directory for zip's which are made a bit differently, useful for non-.org plugins.
-
- $this->run(array(
- 'package' => $r->package,
- 'destination' => WP_PLUGIN_DIR,
- 'clear_destination' => true,
- 'clear_working' => true,
- 'hook_extra' => array(
- 'plugin' => $plugin
- )
- ));
+ add_filter('upgrader_pre_install', array($this, 'deactivate_plugin_before_upgrade'), 10, 2);
+ add_filter('upgrader_clear_destination', array($this, 'delete_old_plugin'), 10, 4);
+ //'source_selection' => array($this, 'source_selection'), //there's a trac ticket to move up the directory for zip's which are made a bit differently, useful for non-.org plugins.
+
+ $this->run( array(
+ 'package' => $r->package,
+ 'destination' => WP_PLUGIN_DIR,
+ 'clear_destination' => true,
+ 'clear_working' => true,
+ 'hook_extra' => array(
+ 'plugin' => $plugin,
+ 'type' => 'plugin',
+ 'action' => 'update',
+ ),
+ ) );
// Cleanup our hooks, in case something else does a upgrade on this connection.
- remove_filter('upgrader_pre_install', array(&$this, 'deactivate_plugin_before_upgrade'));
- remove_filter('upgrader_clear_destination', array(&$this, 'delete_old_plugin'));
+ remove_filter('upgrader_pre_install', array($this, 'deactivate_plugin_before_upgrade'));
+ remove_filter('upgrader_clear_destination', array($this, 'delete_old_plugin'));
if ( ! $this->result || is_wp_error($this->result) )
return $this->result;
// Force refresh of plugin update information
- delete_site_transient('update_plugins');
- wp_cache_delete( 'plugins', 'plugins' );
- do_action( 'upgrader_process_complete', $this, array( 'action' => 'update', 'type' => 'plugin' ), $plugin );
+ wp_clean_plugins_cache( $parsed_args['clear_update_cache'] );
+
+ return true;
}
- function bulk_upgrade($plugins) {
+ function bulk_upgrade( $plugins, $args = array() ) {
+
+ $defaults = array(
+ 'clear_update_cache' => true,
+ );
+ $parsed_args = wp_parse_args( $args, $defaults );
$this->init();
$this->bulk = true;
@@ -474,7 +529,7 @@ class Plugin_Upgrader extends WP_Upgrader {
$current = get_site_transient( 'update_plugins' );
- add_filter('upgrader_clear_destination', array(&$this, 'delete_old_plugin'), 10, 4);
+ add_filter('upgrader_clear_destination', array($this, 'delete_old_plugin'), 10, 4);
$this->skin->header();
@@ -487,10 +542,13 @@ class Plugin_Upgrader extends WP_Upgrader {
$this->skin->bulk_header();
- // Only start maintenance mode if running in Multisite OR the plugin is in use
- $maintenance = is_multisite(); // @TODO: This should only kick in for individual sites if at all possible.
+ // Only start maintenance mode if:
+ // - running Multisite and there are one or more plugins specified, OR
+ // - a plugin with an update available is currently active.
+ // @TODO: For multisite, maintenance mode should only kick in for individual sites if at all possible.
+ $maintenance = ( is_multisite() && ! empty( $plugins ) );
foreach ( $plugins as $plugin )
- $maintenance = $maintenance || (is_plugin_active($plugin) && isset($current->response[ $plugin ]) ); // Only activate Maintenance mode if a plugin is active AND has an update available
+ $maintenance = $maintenance || ( is_plugin_active( $plugin ) && isset( $current->response[ $plugin] ) );
if ( $maintenance )
$this->maintenance_mode(true);
@@ -516,16 +574,16 @@ class Plugin_Upgrader extends WP_Upgrader {
$this->skin->plugin_active = is_plugin_active($plugin);
- $result = $this->run(array(
- 'package' => $r->package,
- 'destination' => WP_PLUGIN_DIR,
- 'clear_destination' => true,
- 'clear_working' => true,
- 'is_multi' => true,
- 'hook_extra' => array(
- 'plugin' => $plugin
- )
- ));
+ $result = $this->run( array(
+ 'package' => $r->package,
+ 'destination' => WP_PLUGIN_DIR,
+ 'clear_destination' => true,
+ 'clear_working' => true,
+ 'is_multi' => true,
+ 'hook_extra' => array(
+ 'plugin' => $plugin
+ )
+ ) );
$results[$plugin] = $this->result;
@@ -536,17 +594,22 @@ class Plugin_Upgrader extends WP_Upgrader {
$this->maintenance_mode(false);
+ do_action( 'upgrader_process_complete', $this, array(
+ 'action' => 'update',
+ 'type' => 'plugin',
+ 'bulk' => true,
+ 'plugins' => $plugins,
+ ) );
+
$this->skin->bulk_footer();
$this->skin->footer();
// Cleanup our hooks, in case something else does a upgrade on this connection.
- remove_filter('upgrader_clear_destination', array(&$this, 'delete_old_plugin'));
+ remove_filter('upgrader_clear_destination', array($this, 'delete_old_plugin'));
// Force refresh of plugin update information
- delete_site_transient('update_plugins');
- wp_cache_delete( 'plugins', 'plugins' );
- do_action( 'upgrader_process_complete', $this, array( 'action' => 'update', 'type' => 'plugin', 'bulk' => true ), $plugins );
+ wp_clean_plugins_cache( $parsed_args['clear_update_cache'] );
return $results;
}
@@ -572,7 +635,7 @@ class Plugin_Upgrader extends WP_Upgrader {
}
if ( ! $plugins_found )
- return new WP_Error( 'incompatible_archive', $this->strings['incompatible_archive'], __('No valid plugins were found.') );
+ return new WP_Error( 'incompatible_archive_no_plugins', $this->strings['incompatible_archive'], __( 'No valid plugins were found.' ) );
return $source;
}
@@ -599,6 +662,10 @@ class Plugin_Upgrader extends WP_Upgrader {
if ( is_wp_error($return) ) //Bypass.
return $return;
+ // When in cron (background updates) don't deactivate the plugin, as we require a browser to reactivate it
+ if ( defined( 'DOING_CRON' ) && DOING_CRON )
+ return $return;
+
$plugin = isset($plugin['plugin']) ? $plugin['plugin'] : '';
if ( empty($plugin) )
return new WP_Error('bad_request', $this->strings['bad_request']);
@@ -642,8 +709,6 @@ class Plugin_Upgrader extends WP_Upgrader {
/**
* Theme Upgrader class for WordPress Themes, It is designed to upgrade/install themes from a local zip, remote zip URL, or uploaded zip file.
*
- * @TODO More Detailed docs, for methods as well.
- *
* @package WordPress
* @subpackage Upgrader
* @since 2.8.0
@@ -705,7 +770,7 @@ class Theme_Upgrader extends WP_Upgrader {
if ( ! $api || is_wp_error($api) ) {
$this->skin->feedback( 'parent_theme_not_found', $theme_info->get('Template') );
// Don't show activate or preview actions after install
- add_filter('install_theme_complete_actions', array(&$this, 'hide_activate_preview_actions') );
+ add_filter('install_theme_complete_actions', array($this, 'hide_activate_preview_actions') );
return $install_result;
}
@@ -724,13 +789,13 @@ class Theme_Upgrader extends WP_Upgrader {
// Install the parent theme
$parent_result = $this->run( array(
'package' => $api->download_link,
- 'destination' => WP_CONTENT_DIR . '/themes',
+ 'destination' => get_theme_root(),
'clear_destination' => false, //Do not overwrite files.
'clear_working' => true
) );
if ( is_wp_error($parent_result) )
- add_filter('install_theme_complete_actions', array(&$this, 'hide_activate_preview_actions') );
+ add_filter('install_theme_complete_actions', array($this, 'hide_activate_preview_actions') );
// Start cleaning up after the parents installation
remove_filter('install_theme_complete_actions', '__return_false', 999);
@@ -748,37 +813,48 @@ class Theme_Upgrader extends WP_Upgrader {
return $actions;
}
- function install($package) {
+ function install( $package, $args = array() ) {
+
+ $defaults = array(
+ 'clear_update_cache' => true,
+ );
+ $parsed_args = wp_parse_args( $args, $defaults );
$this->init();
$this->install_strings();
- add_filter('upgrader_source_selection', array(&$this, 'check_package') );
- add_filter('upgrader_post_install', array(&$this, 'check_parent_theme_filter'), 10, 3);
+ add_filter('upgrader_source_selection', array($this, 'check_package') );
+ add_filter('upgrader_post_install', array($this, 'check_parent_theme_filter'), 10, 3);
- $options = array(
- 'package' => $package,
- 'destination' => WP_CONTENT_DIR . '/themes',
- 'clear_destination' => false, //Do not overwrite files.
- 'clear_working' => true
- );
-
- $this->run($options);
+ $this->run( array(
+ 'package' => $package,
+ 'destination' => get_theme_root(),
+ 'clear_destination' => false, //Do not overwrite files.
+ 'clear_working' => true,
+ 'hook_extra' => array(
+ 'type' => 'theme',
+ 'action' => 'install',
+ ),
+ ) );
- remove_filter('upgrader_source_selection', array(&$this, 'check_package') );
- remove_filter('upgrader_post_install', array(&$this, 'check_parent_theme_filter'));
+ remove_filter('upgrader_source_selection', array($this, 'check_package') );
+ remove_filter('upgrader_post_install', array($this, 'check_parent_theme_filter'));
if ( ! $this->result || is_wp_error($this->result) )
return $this->result;
- // Force refresh of theme update information
- wp_clean_themes_cache();
- do_action( 'upgrader_process_complete', $this, array( 'action' => 'install', 'type' => 'theme' ), $package );
+ // Refresh the Theme Update information
+ wp_clean_themes_cache( $parsed_args['clear_update_cache'] );
return true;
}
- function upgrade($theme) {
+ function upgrade( $theme, $args = array() ) {
+
+ $defaults = array(
+ 'clear_update_cache' => true,
+ );
+ $parsed_args = wp_parse_args( $args, $defaults );
$this->init();
$this->upgrade_strings();
@@ -795,37 +871,40 @@ class Theme_Upgrader extends WP_Upgrader {
$r = $current->response[ $theme ];
- add_filter('upgrader_pre_install', array(&$this, 'current_before'), 10, 2);
- add_filter('upgrader_post_install', array(&$this, 'current_after'), 10, 2);
- add_filter('upgrader_clear_destination', array(&$this, 'delete_old_theme'), 10, 4);
-
- $options = array(
- 'package' => $r['package'],
- 'destination' => WP_CONTENT_DIR . '/themes',
- 'clear_destination' => true,
- 'clear_working' => true,
- 'hook_extra' => array(
- 'theme' => $theme
- )
- );
-
- $this->run($options);
+ add_filter('upgrader_pre_install', array($this, 'current_before'), 10, 2);
+ add_filter('upgrader_post_install', array($this, 'current_after'), 10, 2);
+ add_filter('upgrader_clear_destination', array($this, 'delete_old_theme'), 10, 4);
+
+ $this->run( array(
+ 'package' => $r['package'],
+ 'destination' => get_theme_root( $theme ),
+ 'clear_destination' => true,
+ 'clear_working' => true,
+ 'hook_extra' => array(
+ 'theme' => $theme,
+ 'type' => 'theme',
+ 'action' => 'update',
+ ),
+ ) );
- remove_filter('upgrader_pre_install', array(&$this, 'current_before'));
- remove_filter('upgrader_post_install', array(&$this, 'current_after'));
- remove_filter('upgrader_clear_destination', array(&$this, 'delete_old_theme'));
+ remove_filter('upgrader_pre_install', array($this, 'current_before'));
+ remove_filter('upgrader_post_install', array($this, 'current_after'));
+ remove_filter('upgrader_clear_destination', array($this, 'delete_old_theme'));
if ( ! $this->result || is_wp_error($this->result) )
return $this->result;
- // Force refresh of theme update information
- wp_clean_themes_cache();
- do_action( 'upgrader_process_complete', $this, array( 'action' => 'update', 'type' => 'theme' ), $theme );
+ wp_clean_themes_cache( $parsed_args['clear_update_cache'] );
return true;
}
- function bulk_upgrade($themes) {
+ function bulk_upgrade( $themes, $args = array() ) {
+
+ $defaults = array(
+ 'clear_update_cache' => true,
+ );
+ $parsed_args = wp_parse_args( $args, $defaults );
$this->init();
$this->bulk = true;
@@ -833,9 +912,9 @@ class Theme_Upgrader extends WP_Upgrader {
$current = get_site_transient( 'update_themes' );
- add_filter('upgrader_pre_install', array(&$this, 'current_before'), 10, 2);
- add_filter('upgrader_post_install', array(&$this, 'current_after'), 10, 2);
- add_filter('upgrader_clear_destination', array(&$this, 'delete_old_theme'), 10, 4);
+ add_filter('upgrader_pre_install', array($this, 'current_before'), 10, 2);
+ add_filter('upgrader_post_install', array($this, 'current_after'), 10, 2);
+ add_filter('upgrader_clear_destination', array($this, 'delete_old_theme'), 10, 4);
$this->skin->header();
@@ -848,8 +927,11 @@ class Theme_Upgrader extends WP_Upgrader {
$this->skin->bulk_header();
- // Only start maintenance mode if running in Multisite OR the theme is in use
- $maintenance = is_multisite(); // @TODO: This should only kick in for individual sites if at all possible.
+ // Only start maintenance mode if:
+ // - running Multisite and there are one or more themes specified, OR
+ // - a theme with an update available is currently in use.
+ // @TODO: For multisite, maintenance mode should only kick in for individual sites if at all possible.
+ $maintenance = ( is_multisite() && ! empty( $themes ) );
foreach ( $themes as $theme )
$maintenance = $maintenance || $theme == get_stylesheet() || $theme == get_template();
if ( $maintenance )
@@ -876,17 +958,15 @@ class Theme_Upgrader extends WP_Upgrader {
// Get the URL to the zip file
$r = $current->response[ $theme ];
- $options = array(
- 'package' => $r['package'],
- 'destination' => WP_CONTENT_DIR . '/themes',
- 'clear_destination' => true,
- 'clear_working' => true,
- 'hook_extra' => array(
- 'theme' => $theme
- )
- );
-
- $result = $this->run($options);
+ $result = $this->run( array(
+ 'package' => $r['package'],
+ 'destination' => get_theme_root( $theme ),
+ 'clear_destination' => true,
+ 'clear_working' => true,
+ 'hook_extra' => array(
+ 'theme' => $theme
+ ),
+ ) );
$results[$theme] = $this->result;
@@ -897,18 +977,24 @@ class Theme_Upgrader extends WP_Upgrader {
$this->maintenance_mode(false);
+ do_action( 'upgrader_process_complete', $this, array(
+ 'action' => 'update',
+ 'type' => 'plugin',
+ 'bulk' => true,
+ 'themes' => $themes,
+ ) );
+
$this->skin->bulk_footer();
$this->skin->footer();
// Cleanup our hooks, in case something else does a upgrade on this connection.
- remove_filter('upgrader_pre_install', array(&$this, 'current_before'));
- remove_filter('upgrader_post_install', array(&$this, 'current_after'));
- remove_filter('upgrader_clear_destination', array(&$this, 'delete_old_theme'));
+ remove_filter('upgrader_pre_install', array($this, 'current_before'));
+ remove_filter('upgrader_post_install', array($this, 'current_after'));
+ remove_filter('upgrader_clear_destination', array($this, 'delete_old_theme'));
- // Force refresh of theme update information
- wp_clean_themes_cache();
- do_action( 'upgrader_process_complete', $this, array( 'action' => 'update', 'type' => 'theme', 'bulk' => true ), $themes );
+ // Refresh the Theme Update information
+ wp_clean_themes_cache( $parsed_args['clear_update_cache'] );
return $results;
}
@@ -926,16 +1012,16 @@ class Theme_Upgrader extends WP_Upgrader {
// A proper archive should have a style.css file in the single subdirectory
if ( ! file_exists( $working_directory . 'style.css' ) )
- return new WP_Error( 'incompatible_archive', $this->strings['incompatible_archive'], __('The theme is missing the style.css
stylesheet.') );
+ return new WP_Error( 'incompatible_archive_theme_no_style', $this->strings['incompatible_archive'], __( 'The theme is missing the style.css
stylesheet.' ) );
$info = get_file_data( $working_directory . 'style.css', array( 'Name' => 'Theme Name', 'Template' => 'Template' ) );
if ( empty( $info['Name'] ) )
- return new WP_Error( 'incompatible_archive', $this->strings['incompatible_archive'], __("The style.css
stylesheet doesn't contain a valid theme header.") );
+ return new WP_Error( 'incompatible_archive_theme_no_name', $this->strings['incompatible_archive'], __( "The style.css
stylesheet doesn't contain a valid theme header." ) );
// If it's not a child theme, it must have at least an index.php to be legit.
if ( empty( $info['Template'] ) && ! file_exists( $working_directory . 'index.php' ) )
- return new WP_Error( 'incompatible_archive', $this->strings['incompatible_archive'], __('The theme is missing the index.php
file.') );
+ return new WP_Error( 'incompatible_archive_theme_no_index', $this->strings['incompatible_archive'], __( 'The theme is missing the index.php
file.' ) );
return $source;
}
@@ -978,18 +1064,22 @@ class Theme_Upgrader extends WP_Upgrader {
return $return;
}
- function delete_old_theme($removed, $local_destination, $remote_destination, $theme) {
+ function delete_old_theme( $removed, $local_destination, $remote_destination, $theme ) {
global $wp_filesystem;
- $theme = isset($theme['theme']) ? $theme['theme'] : '';
+ if ( is_wp_error( $removed ) )
+ return $removed; // Pass errors through.
- if ( is_wp_error($removed) || empty($theme) )
- return $removed; //Pass errors through.
+ if ( ! isset( $theme['theme'] ) )
+ return $removed;
- $themes_dir = $wp_filesystem->wp_themes_dir();
- if ( $wp_filesystem->exists( trailingslashit($themes_dir) . $theme ) )
- if ( ! $wp_filesystem->delete( trailingslashit($themes_dir) . $theme, true ) )
+ $theme = $theme['theme'];
+ $themes_dir = trailingslashit( $wp_filesystem->wp_themes_dir( $theme ) );
+ if ( $wp_filesystem->exists( $themes_dir . $theme ) ) {
+ if ( ! $wp_filesystem->delete( $themes_dir . $theme, true ) )
return false;
+ }
+
return true;
}
@@ -1001,15 +1091,210 @@ class Theme_Upgrader extends WP_Upgrader {
else
return false;
}
- return wp_get_theme( $theme, WP_CONTENT_DIR . '/themes/' );
+ return wp_get_theme( $theme );
}
}
+add_action( 'upgrader_process_complete', array( 'Language_Pack_Upgrader', 'async_upgrade' ), 20 );
+
/**
- * Core Upgrader class for WordPress. It allows for WordPress to upgrade itself in combination with the wp-admin/includes/update-core.php file
+ * Language pack upgrader, for updating translations of plugins, themes, and core.
*
- * @TODO More Detailed docs, for methods as well.
+ * @package WordPress
+ * @subpackage Upgrader
+ * @since 3.7.0
+ */
+class Language_Pack_Upgrader extends WP_Upgrader {
+
+ var $result;
+ var $bulk = true;
+
+ static function async_upgrade( $upgrader = false ) {
+ // Avoid recursion.
+ if ( $upgrader && $upgrader instanceof Language_Pack_Upgrader )
+ return;
+
+ // Nothing to do?
+ $language_updates = wp_get_translation_updates();
+ if ( ! $language_updates )
+ return;
+
+ $skin = new Language_Pack_Upgrader_Skin( array(
+ 'skip_header_footer' => true,
+ ) );
+
+ $lp_upgrader = new Language_Pack_Upgrader( $skin );
+ $lp_upgrader->upgrade();
+ }
+
+ function upgrade_strings() {
+ $this->strings['starting_upgrade'] = __( 'Some of your translations need updating. Sit tight for a few more seconds while we update them as well.' );
+ $this->strings['up_to_date'] = __( 'The translation is up to date.' ); // We need to silently skip this case
+ $this->strings['no_package'] = __( 'Update package not available.' );
+ $this->strings['downloading_package'] = __( 'Downloading translation from %s…' );
+ $this->strings['unpack_package'] = __( 'Unpacking the update…' );
+ $this->strings['process_failed'] = __( 'Translation update failed.' );
+ $this->strings['process_success'] = __( 'Translation updated successfully.' );
+ }
+
+ function upgrade( $update = false, $args = array() ) {
+ if ( $update )
+ $update = array( $update );
+ $results = $this->bulk_upgrade( $update, $args );
+ return $results[0];
+ }
+
+ function bulk_upgrade( $language_updates = array(), $args = array() ) {
+ global $wp_filesystem;
+
+ $defaults = array(
+ 'clear_update_cache' => true,
+ );
+ $parsed_args = wp_parse_args( $args, $defaults );
+
+ $this->init();
+ $this->upgrade_strings();
+
+ if ( ! $language_updates )
+ $language_updates = wp_get_translation_updates();
+
+ if ( empty( $language_updates ) ) {
+ $this->skin->header();
+ $this->skin->before();
+ $this->skin->set_result( true );
+ $this->skin->feedback( 'up_to_date' );
+ $this->skin->after();
+ $this->skin->bulk_footer();
+ $this->skin->footer();
+ return true;
+ }
+
+ if ( 'upgrader_process_complete' == current_filter() )
+ $this->skin->feedback( 'starting_upgrade' );
+
+ add_filter( 'upgrader_source_selection', array( &$this, 'check_package' ), 10, 3 );
+
+ $this->skin->header();
+
+ // Connect to the Filesystem first.
+ $res = $this->fs_connect( array( WP_CONTENT_DIR, WP_LANG_DIR ) );
+ if ( ! $res ) {
+ $this->skin->footer();
+ return false;
+ }
+
+ $results = array();
+
+ $this->update_count = count( $language_updates );
+ $this->update_current = 0;
+
+ // The filesystem's mkdir() is not recursive. Make sure WP_LANG_DIR exists,
+ // as we then may need to create a /plugins or /themes directory inside of it.
+ $remote_destination = $wp_filesystem->find_folder( WP_LANG_DIR );
+ if ( ! $wp_filesystem->exists( $remote_destination ) )
+ if ( ! $wp_filesystem->mkdir( $remote_destination, FS_CHMOD_DIR ) )
+ return new WP_Error( 'mkdir_failed_lang_dir', $this->strings['mkdir_failed'], $remote_destination );
+
+ foreach ( $language_updates as $language_update ) {
+
+ $this->skin->language_update = $language_update;
+
+ $destination = WP_LANG_DIR;
+ if ( 'plugin' == $language_update->type )
+ $destination .= '/plugins';
+ elseif ( 'theme' == $language_update->type )
+ $destination .= '/themes';
+
+ $this->update_current++;
+
+ $options = array(
+ 'package' => $language_update->package,
+ 'destination' => $destination,
+ 'clear_destination' => false,
+ 'abort_if_destination_exists' => false, // We expect the destination to exist.
+ 'clear_working' => true,
+ 'is_multi' => true,
+ 'hook_extra' => array(
+ 'language_update_type' => $language_update->type,
+ 'language_update' => $language_update,
+ )
+ );
+
+ $result = $this->run( $options );
+
+ $results[] = $this->result;
+
+ // Prevent credentials auth screen from displaying multiple times.
+ if ( false === $result )
+ break;
+ }
+
+ $this->skin->bulk_footer();
+
+ $this->skin->footer();
+
+ // Clean up our hooks, in case something else does an upgrade on this connection.
+ remove_filter( 'upgrader_source_selection', array( &$this, 'check_package' ), 10, 2 );
+
+ if ( $parsed_args['clear_update_cache'] ) {
+ wp_clean_themes_cache( true );
+ wp_clean_plugins_cache( true );
+ delete_site_transient( 'update_core' );
+ }
+
+ return $results;
+ }
+
+ function check_package( $source, $remote_source ) {
+ global $wp_filesystem;
+
+ if ( is_wp_error( $source ) )
+ return $source;
+
+ // Check that the folder contains a valid language.
+ $files = $wp_filesystem->dirlist( $remote_source );
+
+ // Check to see if a .po and .mo exist in the folder.
+ $po = $mo = false;
+ foreach ( (array) $files as $file => $filedata ) {
+ if ( '.po' == substr( $file, -3 ) )
+ $po = true;
+ elseif ( '.mo' == substr( $file, -3 ) )
+ $mo = true;
+ }
+
+ if ( ! $mo || ! $po )
+ return new WP_Error( 'incompatible_archive_pomo', $this->strings['incompatible_archive'],
+ __( 'The language pack is missing either the .po
or .mo
files.' ) );
+
+ return $source;
+ }
+
+ function get_name_for_update( $update ) {
+ switch ( $update->type ) {
+ case 'core':
+ return 'WordPress'; // Not translated
+ break;
+ case 'theme':
+ $theme = wp_get_theme( $update->slug );
+ if ( $theme->exists() )
+ return $theme->Get( 'Name' );
+ break;
+ case 'plugin':
+ $plugin_data = get_plugins( '/' . $update->slug );
+ $plugin_data = array_shift( $plugin_data );
+ if ( $plugin_data )
+ return $plugin_data['Name'];
+ break;
+ }
+ return '';
+ }
+
+}
+
+/**
+ * Core Upgrader class for WordPress. It allows for WordPress to upgrade itself in combination with the wp-admin/includes/update-core.php file
*
* @package WordPress
* @subpackage Upgrader
@@ -1024,10 +1309,23 @@ class Core_Upgrader extends WP_Upgrader {
$this->strings['unpack_package'] = __('Unpacking the update…');
$this->strings['copy_failed'] = __('Could not copy files.');
$this->strings['copy_failed_space'] = __('Could not copy files. You may have run out of disk space.' );
+ $this->strings['start_rollback'] = __( 'Attempting to roll back to previous version.' );
+ $this->strings['rollback_was_required'] = __( 'Due to an error during updating, WordPress has rolled back to your previous version.' );
}
- function upgrade($current) {
- global $wp_filesystem, $wp_version;
+ function upgrade( $current, $args = array() ) {
+ global $wp_filesystem;
+
+ include ABSPATH . WPINC . '/version.php'; // $wp_version;
+
+ $start_time = time();
+
+ $defaults = array(
+ 'pre_check_md5' => true,
+ 'attempt_rollback' => false,
+ 'do_rollback' => false,
+ );
+ $parsed_args = wp_parse_args( $args, $defaults );
$this->init();
$this->upgrade_strings();
@@ -1037,16 +1335,25 @@ class Core_Upgrader extends WP_Upgrader {
return new WP_Error('up_to_date', $this->strings['up_to_date']);
$res = $this->fs_connect( array(ABSPATH, WP_CONTENT_DIR) );
- if ( is_wp_error($res) )
+ if ( ! $res || is_wp_error( $res ) ) {
return $res;
+ }
$wp_dir = trailingslashit($wp_filesystem->abspath());
+ $partial = true;
+ if ( $parsed_args['do_rollback'] )
+ $partial = false;
+ elseif ( $parsed_args['pre_check_md5'] && ! $this->check_files() )
+ $partial = false;
+
// If partial update is returned from the API, use that, unless we're doing a reinstall.
// If we cross the new_bundled version number, then use the new_bundled zip.
// Don't though if the constant is set to skip bundled items.
// If the API returns a no_content zip, go with it. Finally, default to the full zip.
- if ( $current->packages->partial && 'reinstall' != $current->response && $wp_version == $current->partial_version )
+ if ( $parsed_args['do_rollback'] && $current->packages->rollback )
+ $to_download = 'rollback';
+ elseif ( $current->packages->partial && 'reinstall' != $current->response && $wp_version == $current->partial_version && $partial )
$to_download = 'partial';
elseif ( $current->packages->new_bundled && version_compare( $wp_version, $current->new_bundled, '<' )
&& ( ! defined( 'CORE_UPGRADE_SKIP_NEW_BUNDLED' ) || ! CORE_UPGRADE_SKIP_NEW_BUNDLED ) )
@@ -1067,625 +1374,1053 @@ class Core_Upgrader extends WP_Upgrader {
// Copy update-core.php from the new version into place.
if ( !$wp_filesystem->copy($working_dir . '/wordpress/wp-admin/includes/update-core.php', $wp_dir . 'wp-admin/includes/update-core.php', true) ) {
$wp_filesystem->delete($working_dir, true);
- return new WP_Error('copy_failed', $this->strings['copy_failed']);
+ return new WP_Error( 'copy_failed_for_update_core_file', __( 'The update cannot be installed because we will be unable to copy some files. This is usually due to inconsistent file permissions.' ), 'wp-admin/includes/update-core.php' );
}
$wp_filesystem->chmod($wp_dir . 'wp-admin/includes/update-core.php', FS_CHMOD_FILE);
- require(ABSPATH . 'wp-admin/includes/update-core.php');
+ require_once( ABSPATH . 'wp-admin/includes/update-core.php' );
if ( ! function_exists( 'update_core' ) )
return new WP_Error( 'copy_failed_space', $this->strings['copy_failed_space'] );
$result = update_core( $working_dir, $wp_dir );
- do_action( 'upgrader_process_complete', $this, array( 'action' => 'update', 'type' => 'core' ), $result );
- return $result;
- }
-}
+ // In the event of an issue, we may be able to roll back.
+ if ( $parsed_args['attempt_rollback'] && $current->packages->rollback && ! $parsed_args['do_rollback'] ) {
+ $try_rollback = false;
+ if ( is_wp_error( $result ) ) {
+ $error_code = $result->get_error_code();
+ // Not all errors are equal. These codes are critical: copy_failed__copy_dir,
+ // mkdir_failed__copy_dir, copy_failed__copy_dir_retry, and disk_full.
+ // do_rollback allows for update_core() to trigger a rollback if needed.
+ if ( false !== strpos( $error_code, 'do_rollback' ) )
+ $try_rollback = true;
+ elseif ( false !== strpos( $error_code, '__copy_dir' ) )
+ $try_rollback = true;
+ elseif ( 'disk_full' === $error_code )
+ $try_rollback = true;
+ }
-/**
- * Generic Skin for the WordPress Upgrader classes. This skin is designed to be extended for specific purposes.
- *
- * @TODO More Detailed docs, for methods as well.
- *
- * @package WordPress
- * @subpackage Upgrader
- * @since 2.8.0
- */
-class WP_Upgrader_Skin {
+ if ( $try_rollback ) {
+ apply_filters( 'update_feedback', $result );
+ apply_filters( 'update_feedback', $this->strings['start_rollback'] );
- var $upgrader;
- var $done_header = false;
- var $result = false;
+ $rollback_result = $this->upgrade( $current, array_merge( $parsed_args, array( 'do_rollback' => true ) ) );
- function __construct($args = array()) {
- $defaults = array( 'url' => '', 'nonce' => '', 'title' => '', 'context' => false );
- $this->options = wp_parse_args($args, $defaults);
- }
+ $original_result = $result;
+ $result = new WP_Error( 'rollback_was_required', $this->strings['rollback_was_required'], (object) array( 'update' => $original_result, 'rollback' => $rollback_result ) );
+ }
+ }
- function set_upgrader(&$upgrader) {
- if ( is_object($upgrader) )
- $this->upgrader =& $upgrader;
- $this->add_strings();
- }
+ do_action( 'upgrader_process_complete', $this, array( 'action' => 'update', 'type' => 'core' ) );
- function add_strings() {
- }
+ // Clear the current updates
+ delete_site_transient( 'update_core' );
- function set_result($result) {
- $this->result = $result;
- }
+ if ( ! $parsed_args['do_rollback'] ) {
+ $stats = array(
+ 'update_type' => $current->response,
+ 'success' => true,
+ 'fs_method' => $wp_filesystem->method,
+ 'fs_method_forced' => defined( 'FS_METHOD' ) || has_filter( 'filesystem_method' ),
+ 'time_taken' => time() - $start_time,
+ 'reported' => $wp_version,
+ 'attempted' => $current->version,
+ );
- function request_filesystem_credentials($error = false) {
- $url = $this->options['url'];
- $context = $this->options['context'];
- if ( !empty($this->options['nonce']) )
- $url = wp_nonce_url($url, $this->options['nonce']);
- return request_filesystem_credentials($url, '', $error, $context); //Possible to bring inline, Leaving as is for now.
- }
+ if ( is_wp_error( $result ) ) {
+ $stats['success'] = false;
+ // Did a rollback occur?
+ if ( ! empty( $try_rollback ) ) {
+ $stats['error_code'] = $original_result->get_error_code();
+ $stats['error_data'] = $original_result->get_error_data();
+ // Was the rollback successful? If not, collect its error too.
+ $stats['rollback'] = ! is_wp_error( $rollback_result );
+ if ( is_wp_error( $rollback_result ) ) {
+ $stats['rollback_code'] = $rollback_result->get_error_code();
+ $stats['rollback_data'] = $rollback_result->get_error_data();
+ }
+ } else {
+ $stats['error_code'] = $result->get_error_code();
+ $stats['error_data'] = $result->get_error_data();
+ }
+ }
- function header() {
- if ( $this->done_header )
- return;
- $this->done_header = true;
- echo '
$string
\n"; - } - function header() { - // Nothing, This will be displayed within a iframe. - } + /** + * Filter whether the automatic updater should consider a filesystem location to be potentially + * managed by a version control system. + * + * @since 3.7.0 + * + * @param bool $checkout Whether a VCS checkout was discovered at $context or ABSPATH, or anywhere higher. + * @param string $context The filesystem context (a path) against which filesystem status should be checked. + */ + return apply_filters( 'automatic_updates_is_vcs_checkout', $checkout, $context ); + } + + /** + * Tests to see if we can and should update a specific item. + * + * @since 3.7.0 + * + * @param string $type The type of update being checked: 'core', 'theme', 'plugin', 'translation'. + * @param object $item The update offer. + * @param string $context The filesystem context (a path) against which filesystem access and status + * should be checked. + */ + public function should_update( $type, $item, $context ) { + // Used to see if WP_Filesystem is set up to allow unattended updates. + $skin = new Automatic_Upgrader_Skin; + + if ( $this->is_disabled() ) + return false; - function footer() { - // Nothing, This will be displayed within a iframe. - } - function error($error) { - if ( is_string($error) && isset( $this->upgrader->strings[$error] ) ) - $this->error = $this->upgrader->strings[$error]; - - if ( is_wp_error($error) ) { - foreach ( $error->get_error_messages() as $emessage ) { - if ( $error->get_error_data() ) - $messages[] = $emessage . ' ' . esc_html( $error->get_error_data() ); - else - $messages[] = $emessage; - } - $this->error = implode(', ', $messages); + // If we can't do an auto core update, we may still be able to email the user. + if ( ! $skin->request_filesystem_credentials( false, $context ) || $this->is_vcs_checkout( $context ) ) { + if ( 'core' == $type ) + $this->send_core_update_notification_email( $item ); + return false; } - echo ''; - } - function bulk_header() { - $this->feedback('skin_upgrade_start'); - } - - function bulk_footer() { - $this->feedback('skin_upgrade_end'); - } + // Next up, is this an item we can update? + if ( 'core' == $type ) + $update = Core_Upgrader::should_update_to_version( $item->current ); + else + $update = ! empty( $item->autoupdate ); + + /** + * Filter whether to automatically update core, a plugin, a theme, or a language. + * + * The dynamic portion of the hook name, $type, refers to the type of update + * being checked. Can be 'core', 'theme', 'plugin', or 'translation'. + * + * Generally speaking, plugins, themes, and major core versions are not updated by default, + * while translations and minor and development versions for core are updated by default. + * + * See the filters allow_dev_auto_core_updates, allow_minor_auto_core_updates, and + * allow_major_auto_core_updates more straightforward filters to adjust core updates. + * + * @since 3.7.0 + * + * @param bool $update Whether to update. + * @param object $item The update offer. + */ + $update = apply_filters( 'auto_update_' . $type, $update, $item ); + + if ( ! $update ) { + if ( 'core' == $type ) + $this->send_core_update_notification_email( $item ); + return false; + } - function before($title = '') { - $this->in_loop = true; - printf( '' . sprintf($this->upgrader->strings['skin_update_failed_error'], $title, $this->error) . '
' . sprintf($this->upgrader->strings['skin_update_failed'], $title) . '
' . sprintf($this->upgrader->strings['skin_update_successful'], $title, 'jQuery(\'#progress-' . esc_js($this->upgrader->update_current) . '\').toggle();jQuery(\'span\', this).toggle(); return false;') . '
\n" . implode( "\n", $body ) . "\n"; + + wp_mail( get_site_option( 'admin_email' ), $subject, implode( "\n", $body ) ); } }