]> scripts.mit.edu Git - autoinstalls/wordpress.git/blobdiff - wp-admin/includes/class-wp-upgrader.php
WordPress 4.5
[autoinstalls/wordpress.git] / wp-admin / includes / class-wp-upgrader.php
index 5d2370d090f732a37ba2205a5ad1022ea2961e94..246f6467609750865faf331078ccc316b12a12ed 100644 (file)
@@ -1,10 +1,10 @@
 <?php
 /**
- * A File upgrader class for WordPress.
+ * Upgrade API: WP_Upgrader, Plugin_Upgrader, Theme_Upgrader, Language_Pack_Upgrader,
+ * Core_Upgrader, File_Upload_Upgrader, and WP_Automatic_Updater classes
  *
- * This set of classes are designed to be used to upgrade/install a local set of files on the filesystem via the Filesystem Abstraction classes.
- *
- * @link https://core.trac.wordpress.org/ticket/7875 consolidate plugin/theme/core upgrade/install functions
+ * This set of classes are designed to be used to upgrade/install a local set of files
+ * on the filesystem via the Filesystem Abstraction classes.
  *
  * @package WordPress
  * @subpackage Upgrader
 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.
+ * Core class used for upgrading/installing a local set of files via
+ * the Filesystem Abstraction classes from a Zip file.
  *
- * @package WordPress
- * @subpackage Upgrader
  * @since 2.8.0
  */
 class WP_Upgrader {
@@ -26,6 +25,7 @@ class WP_Upgrader {
         * The error/notification strings used to update the user on the progress.
         *
         * @since 2.8.0
+        * @access public
         * @var string $strings
         */
        public $strings = array();
@@ -34,6 +34,7 @@ class WP_Upgrader {
         * The upgrader skin being used.
         *
         * @since 2.8.0
+        * @access public
         * @var WP_Upgrader_Skin $skin
         */
        public $skin = null;
@@ -47,6 +48,8 @@ class WP_Upgrader {
         * it.
         *
         * @since 2.8.0
+        * @access public
+        *
         * @var WP_Error|array $result {
         *      @type string $source             The full path to the source the files were installed from.
         *      @type string $source_files       List of all the files in the source directory.
@@ -68,6 +71,7 @@ class WP_Upgrader {
         * Set by the bulk update methods.
         *
         * @since 3.0.0
+        * @access public
         * @var int $update_count
         */
        public $update_count = 0;
@@ -78,6 +82,7 @@ class WP_Upgrader {
         * Used by the bulk update methods, and incremented for each update.
         *
         * @since 3.0.0
+        * @access public
         * @var int
         */
        public $update_current = 0;
@@ -86,6 +91,7 @@ class WP_Upgrader {
         * Construct the upgrader with a skin.
         *
         * @since 2.8.0
+        * @access public
         *
         * @param WP_Upgrader_Skin $skin The upgrader skin to use. Default is a {@see WP_Upgrader_Skin}
         *                               instance.
@@ -104,6 +110,7 @@ class WP_Upgrader {
         * and also add the generic strings to `WP_Upgrader::$strings`.
         *
         * @since 2.8.0
+        * @access public
         */
        public function init() {
                $this->skin->set_upgrader($this);
@@ -114,6 +121,7 @@ class WP_Upgrader {
         * Add the generic strings to WP_Upgrader::$strings.
         *
         * @since 2.8.0
+        * @access public
         */
        public function generic_strings() {
                $this->strings['bad_request'] = __('Invalid Data provided.');
@@ -132,6 +140,7 @@ class WP_Upgrader {
                $this->strings['folder_exists'] = __('Destination folder already exists.');
                $this->strings['mkdir_failed'] = __('Could not create directory.');
                $this->strings['incompatible_archive'] = __('The package could not be installed.');
+               $this->strings['files_not_writable'] = __( 'The update cannot be installed because we will be unable to copy some files. This is usually due to inconsistent file permissions.' );
 
                $this->strings['maintenance_start'] = __('Enabling Maintenance mode&#8230;');
                $this->strings['maintenance_end'] = __('Disabling Maintenance mode&#8230;');
@@ -141,6 +150,9 @@ class WP_Upgrader {
         * Connect to the filesystem.
         *
         * @since 2.8.0
+        * @access public
+        *
+        * @global WP_Filesystem_Base $wp_filesystem Subclass
         *
         * @param array $directories                  Optional. A list of directories. If any of these do
         *                                            not exist, a {@see WP_Error} object will be returned.
@@ -202,6 +214,7 @@ class WP_Upgrader {
         * Download a package.
         *
         * @since 2.8.0
+        * @access public
         *
         * @param string $package The URI of the package. If this is the full path to an
         *                        existing local file, it will be returned untouched.
@@ -213,6 +226,7 @@ class WP_Upgrader {
                 * Filter whether to return the package.
                 *
                 * @since 3.7.0
+                * @access public
                 *
                 * @param bool        $reply   Whether to bail without returning the package.
                 *                             Default false.
@@ -243,6 +257,9 @@ class WP_Upgrader {
         * Unpack a compressed package file.
         *
         * @since 2.8.0
+        * @access public
+        *
+        * @global WP_Filesystem_Base $wp_filesystem Subclass
         *
         * @param string $package        Full path to the package file.
         * @param bool   $delete_package Optional. Whether to delete the package file after attempting
@@ -288,6 +305,66 @@ class WP_Upgrader {
                return $working_dir;
        }
 
+       /**
+        * Clears the directory where this item is going to be installed into.
+        *
+        * @since 4.3.0
+        * @access public
+        *
+        * @global WP_Filesystem_Base $wp_filesystem Subclass
+        *
+        * @param string $remote_destination The location on the remote filesystem to be cleared
+        * @return bool|WP_Error True upon success, WP_Error on failure.
+        */
+       public function clear_destination( $remote_destination ) {
+               global $wp_filesystem;
+
+               if ( ! $wp_filesystem->exists( $remote_destination ) ) {
+                       return true;
+               }
+
+               // Check all files are writable before attempting to clear the destination.
+               $unwritable_files = array();
+
+               $_files = $wp_filesystem->dirlist( $remote_destination, true, true );
+
+               // Flatten the resulting array, iterate using each as we append to the array during iteration.
+               while ( $f = each( $_files ) ) {
+                       $file = $f['value'];
+                       $name = $f['key'];
+
+                       if ( ! isset( $file['files'] ) ) {
+                               continue;
+                       }
+
+                       foreach ( $file['files'] as $filename => $details ) {
+                               $_files[ $name . '/' . $filename ] = $details;
+                       }
+               }
+
+               // Check writability.
+               foreach ( $_files as $filename => $file_details ) {
+                       if ( ! $wp_filesystem->is_writable( $remote_destination . $filename ) ) {
+
+                               // Attempt to alter permissions to allow writes and try again.
+                               $wp_filesystem->chmod( $remote_destination . $filename, ( 'd' == $file_details['type'] ? FS_CHMOD_DIR : FS_CHMOD_FILE ) );
+                               if ( ! $wp_filesystem->is_writable( $remote_destination . $filename ) ) {
+                                       $unwritable_files[] = $filename;
+                               }
+                       }
+               }
+
+               if ( ! empty( $unwritable_files ) ) {
+                       return new WP_Error( 'files_not_writable', $this->strings['files_not_writable'], implode( ', ', $unwritable_files ) );
+               }
+
+               if ( ! $wp_filesystem->delete( $remote_destination, true ) ) {
+                       return new WP_Error( 'remove_old_failed', $this->strings['remove_old_failed'] );
+               }
+
+               return true;
+       }
+
        /**
         * Install a package.
         *
@@ -296,6 +373,10 @@ class WP_Upgrader {
         * clear out the destination folder if it already exists.
         *
         * @since 2.8.0
+        * @access public
+        *
+        * @global WP_Filesystem_Base $wp_filesystem Subclass
+        * @global array              $wp_theme_directories
         *
         * @param array|string $args {
         *     Optional. Array or string of arguments for installing a package. Default empty array.
@@ -354,6 +435,7 @@ class WP_Upgrader {
                 * @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;
                }
@@ -370,7 +452,7 @@ class WP_Upgrader {
                        $source = trailingslashit( $args['source'] ) . trailingslashit( $source_files[0] );
                } elseif ( count( $source_files ) == 0 ) {
                        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.
+               } 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'] );
                }
 
@@ -378,12 +460,15 @@ class WP_Upgrader {
                 * Filter 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 Remove file source location.
+                * @param string      $remote_source Remote file source location.
                 * @param WP_Upgrader $this          WP_Upgrader instance.
+                * @param array       $hook_extra    Extra arguments passed to hooked filters.
                 */
-               $source = apply_filters( 'upgrader_source_selection', $source, $remote_source, $this );
+               $source = apply_filters( 'upgrader_source_selection', $source, $remote_source, $this, $args['hook_extra'] );
+
                if ( is_wp_error( $source ) ) {
                        return $source;
                }
@@ -392,6 +477,7 @@ class WP_Upgrader {
                if ( $source !== $remote_source ) {
                        $source_files = array_keys( $wp_filesystem->dirlist( $source ) );
                }
+
                /*
                 * Protection against deleting files in any important base directories.
                 * Theme_Upgrader & Plugin_Upgrader also trigger this, as they pass the
@@ -404,35 +490,32 @@ class WP_Upgrader {
                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 ) );
                }
 
                if ( $clear_destination ) {
-                       //We're going to clear the destination if there's something there
+                       // We're going to clear the destination if there's something there.
                        $this->skin->feedback('remove_old');
-                       $removed = true;
-                       if ( $wp_filesystem->exists( $remote_destination ) ) {
-                               $removed = $wp_filesystem->delete( $remote_destination, true );
-                       }
+
+                       $removed = $this->clear_destination( $remote_destination );
 
                        /**
                         * Filter whether the upgrader cleared the destination.
                         *
                         * @since 2.8.0
                         *
-                        * @param bool   $removed            Whether the destination was cleared.
+                        * @param mixed  $removed            Whether the destination was cleared. true on 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) ) {
+                       if ( is_wp_error( $removed ) ) {
                                return $removed;
-                       } elseif ( ! $removed ) {
-                               return new WP_Error('remove_old_failed', $this->strings['remove_old_failed']);
                        }
                } 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.
@@ -498,6 +581,7 @@ class WP_Upgrader {
         * install it in the destination folder.
         *
         * @since 2.8.0
+        * @access public
         *
         * @param array $options {
         *     Array or string of arguments for upgrading/installing a package.
@@ -521,7 +605,6 @@ class WP_Upgrader {
         *     @type array  $hook_extra                  Extra arguments to pass to the filter hooks called by
         *                                               {@see WP_Upgrader::run()}.
         * }
-        *
         * @return array|false|WP_error The result from self::install_package() on success, otherwise a WP_Error,
         *                              or false if unable to connect to the filesystem.
         */
@@ -539,6 +622,25 @@ class WP_Upgrader {
 
                $options = wp_parse_args( $options, $defaults );
 
+               /**
+                * Filter the package options before running an update.
+                *
+                * @since 4.3.0
+                *
+                * @param array $options {
+                *     Options used by the upgrader.
+                *
+                *     @type string $package                     Package for update.
+                *     @type string $destination                 Update location.
+                *     @type bool   $clear_destination           Clear the destination resource.
+                *     @type bool   $clear_working               Clear the working resource.
+                *     @type bool   $abort_if_destination_exists Abort if the Destination directory exists.
+                *     @type bool   $is_multi                    Whether the upgrader is running multiple times.
+                *     @type array  $hook_extra                  Extra hook arguments.
+                * }
+                */
+               $options = apply_filters( 'upgrader_package_options', $options );
+
                if ( ! $options['is_multi'] ) { // call $this->header separately if running multiple times
                        $this->skin->header();
                }
@@ -564,7 +666,10 @@ class WP_Upgrader {
                        return $res;
                }
 
-               //Download the package (Note, This just returns the filename of the file if the package is a local file)
+               /*
+                * Download the package (Note, This just returns the filename
+                * of the file if the package is a local file)
+                */
                $download = $this->download_package( $options['package'] );
                if ( is_wp_error($download) ) {
                        $this->skin->error($download);
@@ -577,7 +682,7 @@ class WP_Upgrader {
 
                $delete_package = ( $download != $options['package'] ); // Do not delete a "local" file
 
-               //Unzips the file into a temporary directory
+               // Unzips the file into a temporary directory.
                $working_dir = $this->unpack_package( $download, $delete_package );
                if ( is_wp_error($working_dir) ) {
                        $this->skin->error($working_dir);
@@ -588,7 +693,7 @@ class WP_Upgrader {
                        return $working_dir;
                }
 
-               //With the given options, this installs it to the destination directory.
+               // With the given options, this installs it to the destination directory.
                $result = $this->install_package( array(
                        'source' => $working_dir,
                        'destination' => $options['destination'],
@@ -603,7 +708,7 @@ class WP_Upgrader {
                        $this->skin->error($result);
                        $this->skin->feedback('process_failed');
                } else {
-                       //Install Succeeded
+                       // Install succeeded.
                        $this->skin->feedback('process_success');
                }
 
@@ -625,6 +730,9 @@ class WP_Upgrader {
         * Creates/deletes the maintenance file to enable/disable maintenance mode.
         *
         * @since 2.8.0
+        * @access public
+        *
+        * @global WP_Filesystem_Base $wp_filesystem Subclass
         *
         * @param bool $enable True to enable maintenance mode, false to disable.
         */
@@ -643,14 +751,80 @@ class WP_Upgrader {
                }
        }
 
+       /**
+        * Creates a lock using WordPress options.
+        *
+        * @since 4.5.0
+        * @access public
+        * @static
+        *
+        * @param string $lock_name       The name of this unique lock.
+        * @param int    $release_timeout Optional. The duration in seconds to respect an existing lock.
+        *                                Default: 1 hour.
+        * @return bool False if a lock couldn't be created or if the lock is no longer valid. True otherwise.
+        */
+       public static function create_lock( $lock_name, $release_timeout = null ) {
+               global $wpdb;
+               if ( ! $release_timeout ) {
+                       $release_timeout = HOUR_IN_SECONDS;
+               }
+               $lock_option = $lock_name . '.lock';
+
+               // Try to lock.
+               $lock_result = $wpdb->query( $wpdb->prepare( "INSERT IGNORE INTO `$wpdb->options` ( `option_name`, `option_value`, `autoload` ) VALUES (%s, %s, 'no') /* LOCK */", $lock_option, time() ) );
+
+               if ( ! $lock_result ) {
+                       $lock_result = get_option( $lock_option );
+
+                       // If a lock couldn't be created, and there isn't a lock, bail.
+                       if ( ! $lock_result ) {
+                               return false;
+                       }
+
+                       // Check to see if the lock is still valid. If not, bail.
+                       if ( $lock_result > ( time() - $release_timeout ) ) {
+                               return false;
+                       }
+
+                       // There must exist an expired lock, clear it and re-gain it.
+                       WP_Upgrader::release_lock( $lock_name );
+
+                       return WP_Upgrader::create_lock( $lock_name, $release_timeout );
+               }
+
+               // Update the lock, as by this point we've definitely got a lock, just need to fire the actions.
+               update_option( $lock_option, time() );
+
+               return true;
+       }
+
+       /**
+        * Releases an upgrader lock.
+        *
+        * @since 4.5.0
+        * @access public
+        * @static
+        *
+        * @see WP_Upgrader::create_lock()
+        *
+        * @param string $lock_name The name of this unique lock.
+        * @return bool True if the lock was successfully released. False on failure.
+        */
+       public static function release_lock( $lock_name ) {
+               return delete_option( $lock_name . '.lock' );
+       }
+
 }
 
 /**
- * Plugin Upgrader class for WordPress Plugins, It is designed to upgrade/install plugins from a local zip, remote zip URL, or uploaded zip file.
+ * Core class used for upgrading/installing plugins.
+ *
+ * It is designed to upgrade/install plugins from a local zip, remote zip URL,
+ * or uploaded zip file.
  *
- * @package WordPress
- * @subpackage Upgrader
  * @since 2.8.0
+ *
+ * @see WP_Upgrader
  */
 class Plugin_Upgrader extends WP_Upgrader {
 
@@ -658,7 +832,9 @@ class Plugin_Upgrader extends WP_Upgrader {
         * Plugin upgrade result.
         *
         * @since 2.8.0
+        * @access public
         * @var array|WP_Error $result
+        *
         * @see WP_Upgrader::$result
         */
        public $result;
@@ -667,6 +843,7 @@ class Plugin_Upgrader extends WP_Upgrader {
         * Whether a bulk upgrade/install is being performed.
         *
         * @since 2.9.0
+        * @access public
         * @var bool $bulk
         */
        public $bulk = false;
@@ -675,6 +852,7 @@ class Plugin_Upgrader extends WP_Upgrader {
         * Initialize the upgrade strings.
         *
         * @since 2.8.0
+        * @access public
         */
        public function upgrade_strings() {
                $this->strings['up_to_date'] = __('The plugin is at the latest version.');
@@ -692,6 +870,7 @@ class Plugin_Upgrader extends WP_Upgrader {
         * Initialize the install strings.
         *
         * @since 2.8.0
+        * @access public
         */
        public function install_strings() {
                $this->strings['no_package'] = __('Install package not available.');
@@ -708,6 +887,7 @@ class Plugin_Upgrader extends WP_Upgrader {
         *
         * @since 2.8.0
         * @since 3.7.0 The `$args` parameter was added, making clearing the plugin update cache optional.
+        * @access public
         *
         * @param string $package The full local path or URI of the package.
         * @param array  $args {
@@ -716,7 +896,6 @@ class Plugin_Upgrader extends WP_Upgrader {
         *     @type bool $clear_update_cache Whether to clear the plugin updates cache if successful.
         *                                    Default true.
         * }
-        *
         * @return bool|WP_Error True if the install was successful, false or a WP_Error otherwise.
         */
        public function install( $package, $args = array() ) {
@@ -730,6 +909,8 @@ class Plugin_Upgrader extends WP_Upgrader {
                $this->install_strings();
 
                add_filter('upgrader_source_selection', array($this, 'check_package') );
+               // Clear cache so wp_update_plugins() knows about the new plugin.
+               add_action( 'upgrader_process_complete', 'wp_clean_plugins_cache', 9, 0 );
 
                $this->run( array(
                        'package' => $package,
@@ -742,6 +923,7 @@ class Plugin_Upgrader extends WP_Upgrader {
                        )
                ) );
 
+               remove_action( 'upgrader_process_complete', 'wp_clean_plugins_cache', 9 );
                remove_filter('upgrader_source_selection', array($this, 'check_package') );
 
                if ( ! $this->result || is_wp_error($this->result) )
@@ -758,10 +940,11 @@ class Plugin_Upgrader extends WP_Upgrader {
         *
         * @since 2.8.0
         * @since 3.7.0 The `$args` parameter was added, making clearing the plugin update cache optional.
+        * @access public
         *
         * @param string $plugin The basename path to the main plugin file.
         * @param array  $args {
-        *     Optional. Other arguments for upgrading a plugin package. Defualt empty array.
+        *     Optional. Other arguments for upgrading a plugin package. Default empty array.
         *
         *     @type bool $clear_update_cache Whether to clear the plugin updates cache if successful.
         *                                    Default true.
@@ -824,6 +1007,7 @@ class Plugin_Upgrader extends WP_Upgrader {
         *
         * @since 2.8.0
         * @since 3.7.0 The `$args` parameter was added, making clearing the plugin update cache optional.
+        * @access public
         *
         * @param array $plugins Array of the basename paths of the plugins' main files.
         * @param array $args {
@@ -832,7 +1016,6 @@ class Plugin_Upgrader extends WP_Upgrader {
         *     @type bool $clear_update_cache Whether to clear the plugin updates cache if successful.
         *                                    Default true.
         * }
-        *
         * @return array|false An array of results indexed by plugin file, or false if unable to connect to the filesystem.
         */
        public function bulk_upgrade( $plugins, $args = array() ) {
@@ -861,10 +1044,12 @@ class Plugin_Upgrader extends WP_Upgrader {
 
                $this->skin->bulk_header();
 
-               // 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.
+               /*
+                * 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] ) );
@@ -888,7 +1073,7 @@ class Plugin_Upgrader extends WP_Upgrader {
                                continue;
                        }
 
-                       // Get the URL to the zip file
+                       // Get the URL to the zip file.
                        $r = $current->response[ $plugin ];
 
                        $this->skin->plugin_active = is_plugin_active($plugin);
@@ -943,7 +1128,7 @@ class Plugin_Upgrader extends WP_Upgrader {
                // Cleanup our hooks, in case something else does a upgrade on this connection.
                remove_filter('upgrader_clear_destination', array($this, 'delete_old_plugin'));
 
-               // Force refresh of plugin update information
+               // Force refresh of plugin update information.
                wp_clean_plugins_cache( $parsed_args['clear_update_cache'] );
 
                return $results;
@@ -956,9 +1141,13 @@ class Plugin_Upgrader extends WP_Upgrader {
         * {@see Plugin_Upgrader::install()}.
         *
         * @since 3.3.0
+        * @access public
+        *
+        * @global WP_Filesystem_Base $wp_filesystem Subclass
         *
         * @param string $source The path to the downloaded package source.
-        * @return string|WP_Error The source as passed, or a {@see WP_Error} object if no plugins were found.
+        * @return string|WP_Error The source as passed, or a {@see WP_Error} object
+        *                         if no plugins were found.
         */
        public function check_package($source) {
                global $wp_filesystem;
@@ -995,6 +1184,7 @@ class Plugin_Upgrader extends WP_Upgrader {
         * This isn't used internally in the class, but is called by the skins.
         *
         * @since 2.8.0
+        * @access public
         *
         * @return string|false The full path to the main plugin file, or false.
         */
@@ -1020,6 +1210,7 @@ class Plugin_Upgrader extends WP_Upgrader {
         *
         * @since 2.8.0
         * @since 4.1.0 Added a return value.
+        * @access public
         *
         * @param bool|WP_Error  $return Upgrade offer return.
         * @param array          $plugin Plugin package arguments.
@@ -1053,6 +1244,15 @@ class Plugin_Upgrader extends WP_Upgrader {
         * {@see Plugin_Upgrader::upgrade()} and {@see Plugin_Upgrader::bulk_upgrade()}.
         *
         * @since 2.8.0
+        * @access public
+        *
+        * @global WP_Filesystem_Base $wp_filesystem Subclass
+     *
+        * @param bool|WP_Error $removed
+        * @param string        $local_destination
+        * @param string        $remote_destination
+        * @param array         $plugin
+        * @return WP_Error|bool
         */
        public function delete_old_plugin($removed, $local_destination, $remote_destination, $plugin) {
                global $wp_filesystem;
@@ -1084,11 +1284,14 @@ 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.
+ * Core class used for upgrading/installing themes.
+ *
+ * It is designed to upgrade/install themes from a local zip, remote zip URL,
+ * or uploaded zip file.
  *
- * @package WordPress
- * @subpackage Upgrader
  * @since 2.8.0
+ *
+ * @see WP_Upgrader
  */
 class Theme_Upgrader extends WP_Upgrader {
 
@@ -1096,15 +1299,17 @@ class Theme_Upgrader extends WP_Upgrader {
         * Result of the theme upgrade offer.
         *
         * @since 2.8.0
-        * @var array|WP_Erorr $result
+        * @access public
+        * @var array|WP_Error $result
         * @see WP_Upgrader::$result
         */
        public $result;
 
        /**
-        * Whether multiple plugins are being upgraded/installed in bulk.
+        * Whether multiple themes are being upgraded/installed in bulk.
         *
         * @since 2.9.0
+        * @access public
         * @var bool $bulk
         */
        public $bulk = false;
@@ -1113,6 +1318,7 @@ class Theme_Upgrader extends WP_Upgrader {
         * Initialize the upgrade strings.
         *
         * @since 2.8.0
+        * @access public
         */
        public function upgrade_strings() {
                $this->strings['up_to_date'] = __('The theme is at the latest version.');
@@ -1129,6 +1335,7 @@ class Theme_Upgrader extends WP_Upgrader {
         * Initialize the install strings.
         *
         * @since 2.8.0
+        * @access public
         */
        public function install_strings() {
                $this->strings['no_package'] = __('Install package not available.');
@@ -1156,6 +1363,12 @@ class Theme_Upgrader extends WP_Upgrader {
         * Hooked to the {@see 'upgrader_post_install'} filter by {@see Theme_Upgrader::install()}.
         *
         * @since 3.4.0
+        * @access public
+        *
+        * @param bool  $install_result
+        * @param array $hook_extra
+        * @param array $child_result
+        * @return type
         */
        public function check_parent_theme_filter( $install_result, $hook_extra, $child_result ) {
                // Check to see if we need to install a parent theme
@@ -1224,8 +1437,10 @@ class Theme_Upgrader extends WP_Upgrader {
         * a child theme and installing the parent theme fails.
         *
         * @since 3.4.0
+        * @access public
         *
         * @param array $actions Preview actions.
+        * @return array
         */
        public function hide_activate_preview_actions( $actions ) {
                unset($actions['activate'], $actions['preview']);
@@ -1237,6 +1452,7 @@ class Theme_Upgrader extends WP_Upgrader {
         *
         * @since 2.8.0
         * @since 3.7.0 The `$args` parameter was added, making clearing the update cache optional.
+        * @access public
         *
         * @param string $package The full local path or URI of the package.
         * @param array  $args {
@@ -1260,6 +1476,8 @@ class Theme_Upgrader extends WP_Upgrader {
 
                add_filter('upgrader_source_selection', array($this, 'check_package') );
                add_filter('upgrader_post_install', array($this, 'check_parent_theme_filter'), 10, 3);
+               // Clear cache so wp_update_themes() knows about the new theme.
+               add_action( 'upgrader_process_complete', 'wp_clean_themes_cache', 9, 0 );
 
                $this->run( array(
                        'package' => $package,
@@ -1272,6 +1490,7 @@ class Theme_Upgrader extends WP_Upgrader {
                        ),
                ) );
 
+               remove_action( 'upgrader_process_complete', 'wp_clean_themes_cache', 9 );
                remove_filter('upgrader_source_selection', array($this, 'check_package') );
                remove_filter('upgrader_post_install', array($this, 'check_parent_theme_filter'));
 
@@ -1289,6 +1508,7 @@ class Theme_Upgrader extends WP_Upgrader {
         *
         * @since 2.8.0
         * @since 3.7.0 The `$args` parameter was added, making clearing the update cache optional.
+        * @access public
         *
         * @param string $theme The theme slug.
         * @param array  $args {
@@ -1354,6 +1574,7 @@ class Theme_Upgrader extends WP_Upgrader {
         *
         * @since 3.0.0
         * @since 3.7.0 The `$args` parameter was added, making clearing the update cache optional.
+        * @access public
         *
         * @param array $themes The theme slugs.
         * @param array $args {
@@ -1474,6 +1695,9 @@ class Theme_Upgrader extends WP_Upgrader {
         * files.
         *
         * @since 3.3.0
+        * @access public
+        *
+        * @global WP_Filesystem_Base $wp_filesystem Subclass
         *
         * @param string $source The full path to the package source.
         * @return string|WP_Error The source or a WP_Error.
@@ -1490,17 +1714,35 @@ class Theme_Upgrader extends WP_Upgrader {
                        return $source;
 
                // 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_theme_no_style', $this->strings['incompatible_archive'], __( 'The theme is missing the <code>style.css</code> stylesheet.' ) );
+               if ( ! file_exists( $working_directory . 'style.css' ) ) {
+                       return new WP_Error( 'incompatible_archive_theme_no_style', $this->strings['incompatible_archive'],
+                               /* translators: %s: style.css */
+                               sprintf( __( 'The theme is missing the %s stylesheet.' ),
+                                       '<code>style.css</code>'
+                               )
+                       );
+               }
 
                $info = get_file_data( $working_directory . 'style.css', array( 'Name' => 'Theme Name', 'Template' => 'Template' ) );
 
-               if ( empty( $info['Name'] ) )
-                       return new WP_Error( 'incompatible_archive_theme_no_name', $this->strings['incompatible_archive'], __( "The <code>style.css</code> stylesheet doesn't contain a valid theme header." ) );
+               if ( empty( $info['Name'] ) ) {
+                       return new WP_Error( 'incompatible_archive_theme_no_name', $this->strings['incompatible_archive'],
+                               /* translators: %s: style.css */
+                               sprintf( __( 'The %s stylesheet doesn&#8217;t contain a valid theme header.' ),
+                                       '<code>style.css</code>'
+                               )
+                       );
+               }
 
                // 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_theme_no_index', $this->strings['incompatible_archive'], __( 'The theme is missing the <code>index.php</code> file.' ) );
+               if ( empty( $info['Template'] ) && ! file_exists( $working_directory . 'index.php' ) ) {
+                       return new WP_Error( 'incompatible_archive_theme_no_index', $this->strings['incompatible_archive'],
+                               /* translators: %s: index.php */
+                               sprintf( __( 'The theme is missing the %s file.' ),
+                                       '<code>index.php</code>'
+                               )
+                       );
+               }
 
                return $source;
        }
@@ -1512,9 +1754,13 @@ class Theme_Upgrader extends WP_Upgrader {
         * {@see Theme_Upgrader::bulk_upgrade()}.
         *
         * @since 2.8.0
+        * @access public
+        *
+        * @param bool|WP_Error  $return
+        * @param array          $theme
+        * @return bool|WP_Error
         */
        public function current_before($return, $theme) {
-
                if ( is_wp_error($return) )
                        return $return;
 
@@ -1536,6 +1782,11 @@ class Theme_Upgrader extends WP_Upgrader {
         * and {@see Theme_Upgrader::bulk_upgrade()}.
         *
         * @since 2.8.0
+        * @access public
+        *
+        * @param bool|WP_Error  $return
+        * @param array          $theme
+        * @return bool|WP_Error
         */
        public function current_after($return, $theme) {
                if ( is_wp_error($return) )
@@ -1566,6 +1817,15 @@ class Theme_Upgrader extends WP_Upgrader {
         * and {@see Theme_Upgrader::bulk_upgrade()}.
         *
         * @since 2.8.0
+        * @access public
+        *
+        * @global WP_Filesystem_Base $wp_filesystem Subclass
+        *
+        * @param bool   $removed
+        * @param string $local_destination
+        * @param string $remote_destination
+        * @param array  $theme
+        * @return bool
         */
        public function delete_old_theme( $removed, $local_destination, $remote_destination, $theme ) {
                global $wp_filesystem;
@@ -1591,6 +1851,7 @@ class Theme_Upgrader extends WP_Upgrader {
         *
         * @since 2.8.0
         * @since 3.0.0 The `$theme` argument was added.
+        * @access public
         *
         * @param string $theme The directory name of the theme. This is optional, and if not supplied,
         *                      the directory name from the last result will be used.
@@ -1610,14 +1871,13 @@ class Theme_Upgrader extends WP_Upgrader {
 
 }
 
-add_action( 'upgrader_process_complete', array( 'Language_Pack_Upgrader', 'async_upgrade' ), 20 );
-
 /**
- * Language pack upgrader, for updating translations of plugins, themes, and core.
+ * Core class used for updating/installing language packs (translations)
+ * for plugins, themes, and core.
  *
- * @package WordPress
- * @subpackage Upgrader
  * @since 3.7.0
+ *
+ * @see WP_Upgrader
  */
 class Language_Pack_Upgrader extends WP_Upgrader {
 
@@ -1625,6 +1885,7 @@ class Language_Pack_Upgrader extends WP_Upgrader {
         * Result of the language pack upgrade.
         *
         * @since 3.7.0
+        * @access public
         * @var array|WP_Error $result
         * @see WP_Upgrader::$result
         */
@@ -1634,16 +1895,23 @@ class Language_Pack_Upgrader extends WP_Upgrader {
         * Whether a bulk upgrade/install is being performed.
         *
         * @since 3.7.0
+        * @access public
         * @var bool $bulk
         */
        public $bulk = true;
 
        /**
-        * Asynchronously upgrade language packs after other upgrades have been made.
+        * Asynchronously upgrades language packs after other upgrades have been made.
         *
         * Hooked to the {@see 'upgrader_process_complete'} action by default.
         *
         * @since 3.7.0
+        * @access public
+        * @static
+        *
+        * @param false|WP_Upgrader $upgrader Optional. WP_Upgrader instance or false. If `$upgrader` is
+        *                                    a Language_Pack_Upgrader instance, the method will bail to
+        *                                    avoid recursion. Otherwise unused. Default false.
         */
        public static function async_upgrade( $upgrader = false ) {
                // Avoid recursion.
@@ -1657,8 +1925,10 @@ class Language_Pack_Upgrader extends WP_Upgrader {
                        return;
                }
 
-               // Avoid messing with VCS installs, at least for now.
-               // Noted: this is not the ideal way to accomplish this.
+               /*
+                * Avoid messing with VCS installs, at least for now.
+                * Noted: this is not the ideal way to accomplish this.
+                */
                $check_vcs = new WP_Automatic_Updater;
                if ( $check_vcs->is_vcs_checkout( WP_CONTENT_DIR ) ) {
                        return;
@@ -1686,9 +1956,14 @@ class Language_Pack_Upgrader extends WP_Upgrader {
                        return;
                }
 
-               $skin = new Language_Pack_Upgrader_Skin( array(
-                       'skip_header_footer' => true,
-               ) );
+               // Re-use the automatic upgrader skin if the parent upgrader is using it.
+               if ( $upgrader && $upgrader->skin instanceof Automatic_Upgrader_Skin ) {
+                       $skin = $upgrader->skin;
+               } else {
+                       $skin = new Language_Pack_Upgrader_Skin( array(
+                               'skip_header_footer' => true,
+                       ) );
+               }
 
                $lp_upgrader = new Language_Pack_Upgrader( $skin );
                $lp_upgrader->bulk_upgrade( $language_updates );
@@ -1698,6 +1973,7 @@ class Language_Pack_Upgrader extends WP_Upgrader {
         * Initialize the upgrade strings.
         *
         * @since 3.7.0
+        * @access public
         */
        public 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.' );
@@ -1713,11 +1989,12 @@ class Language_Pack_Upgrader extends WP_Upgrader {
         * Upgrade a language pack.
         *
         * @since 3.7.0
+        * @access public
         *
         * @param string|false $update Optional. Whether an update offer is available. Default false.
         * @param array        $args   Optional. Other optional arguments, see
         *                             {@see Language_Pack_Upgrader::bulk_upgrade()}. Default empty array.
-        * @return array|WP_Error The result of the upgrade, or a {@see wP_Error} object instead.
+        * @return array|bool|WP_Error The result of the upgrade, or a {@see wP_Error} object instead.
         */
        public function upgrade( $update = false, $args = array() ) {
                if ( $update ) {
@@ -1737,6 +2014,9 @@ class Language_Pack_Upgrader extends WP_Upgrader {
         * Bulk upgrade language packs.
         *
         * @since 3.7.0
+        * @access public
+        *
+        * @global WP_Filesystem_Base $wp_filesystem Subclass
         *
         * @param array $language_updates Optional. Language pack updates. Default empty array.
         * @param array $args {
@@ -1745,7 +2025,7 @@ class Language_Pack_Upgrader extends WP_Upgrader {
         *     @type bool $clear_update_cache Whether to clear the update cache when done.
         *                                    Default true.
         * }
-        * @return array|true|false|WP_Error Will return an array of results, or true if there are no updates,
+        * @return array|bool|WP_Error Will return an array of results, or true if there are no updates,
         *                                   false or WP_Error for initial errors.
         */
        public function bulk_upgrade( $language_updates = array(), $args = array() ) {
@@ -1779,7 +2059,7 @@ class Language_Pack_Upgrader extends WP_Upgrader {
                // Remove any existing upgrade filters from the plugin/theme upgraders #WP29425 & #WP29230
                remove_all_filters( 'upgrader_pre_install' );
                remove_all_filters( 'upgrader_clear_destination' );
-               remove_all_filterS( 'upgrader_post_install' );
+               remove_all_filters( 'upgrader_post_install' );
                remove_all_filters( 'upgrader_source_selection' );
 
                add_filter( 'upgrader_source_selection', array( $this, 'check_package' ), 10, 2 );
@@ -1862,6 +2142,12 @@ class Language_Pack_Upgrader extends WP_Upgrader {
         * {@see Language_Pack_Upgrader::bulk_upgrade()}.
         *
         * @since 3.7.0
+        * @access public
+        *
+        * @global WP_Filesystem_Base $wp_filesystem Subclass
+        *
+        * @param string|WP_Error $source
+        * @param string          $remote_source
         */
        public function check_package( $source, $remote_source ) {
                global $wp_filesystem;
@@ -1881,9 +2167,15 @@ class Language_Pack_Upgrader extends WP_Upgrader {
                                $mo = true;
                }
 
-               if ( ! $mo || ! $po )
+               if ( ! $mo || ! $po ) {
                        return new WP_Error( 'incompatible_archive_pomo', $this->strings['incompatible_archive'],
-                               __( 'The language pack is missing either the <code>.po</code> or <code>.mo</code> files.' ) );
+                               /* translators: 1: .po 2: .mo */
+                               sprintf( __( 'The language pack is missing either the %1$s or %2$s files.' ),
+                                       '<code>.po</code>',
+                                       '<code>.mo</code>'
+                               )
+                       );
+               }
 
                return $source;
        }
@@ -1892,8 +2184,9 @@ class Language_Pack_Upgrader extends WP_Upgrader {
         * Get the name of an item being updated.
         *
         * @since 3.7.0
+        * @access public
         *
-        * @param object The data for an update.
+        * @param object $update The data for an update.
         * @return string The name of the item being updated.
         */
        public function get_name_for_update( $update ) {
@@ -1919,11 +2212,14 @@ class Language_Pack_Upgrader extends WP_Upgrader {
 }
 
 /**
- * Core Upgrader class for WordPress. It allows for WordPress to upgrade itself in combination with the wp-admin/includes/update-core.php file
+ * Core class used for updating core.
+ *
+ * It allows for WordPress to upgrade itself in combination with
+ * the wp-admin/includes/update-core.php file.
  *
- * @package WordPress
- * @subpackage Upgrader
  * @since 2.8.0
+ *
+ * @see WP_Upgrader
  */
 class Core_Upgrader extends WP_Upgrader {
 
@@ -1931,9 +2227,11 @@ class Core_Upgrader extends WP_Upgrader {
         * Initialize the upgrade strings.
         *
         * @since 2.8.0
+        * @access public
         */
        public function upgrade_strings() {
                $this->strings['up_to_date'] = __('WordPress is at the latest version.');
+               $this->strings['locked'] = __('Another update is currently in progress.');
                $this->strings['no_package'] = __('Update package not available.');
                $this->strings['downloading_package'] = __('Downloading update from <span class="code">%s</span>&#8230;');
                $this->strings['unpack_package'] = __('Unpacking the update&#8230;');
@@ -1947,6 +2245,10 @@ class Core_Upgrader extends WP_Upgrader {
         * Upgrade WordPress core.
         *
         * @since 2.8.0
+        * @access public
+        *
+        * @global WP_Filesystem_Base $wp_filesystem Subclass
+        * @global callable           $_wp_filesystem_direct_method
         *
         * @param object $current Response object for whether WordPress is current.
         * @param array  $args {
@@ -2014,25 +2316,38 @@ class Core_Upgrader extends WP_Upgrader {
                else
                        $to_download = 'full';
 
+               // Lock to prevent multiple Core Updates occuring
+               $lock = WP_Upgrader::create_lock( 'core_updater', 15 * MINUTE_IN_SECONDS );
+               if ( ! $lock ) {
+                       return new WP_Error( 'locked', $this->strings['locked'] );
+               }
+
                $download = $this->download_package( $current->packages->$to_download );
-               if ( is_wp_error($download) )
+               if ( is_wp_error( $download ) ) {
+                       WP_Upgrader::release_lock( 'core_updater' );
                        return $download;
+               }
 
                $working_dir = $this->unpack_package( $download );
-               if ( is_wp_error($working_dir) )
+               if ( is_wp_error( $working_dir ) ) {
+                       WP_Upgrader::release_lock( 'core_updater' );
                        return $working_dir;
+               }
 
                // 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);
+                       WP_Upgrader::release_lock( 'core_updater' );
                        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_once( ABSPATH . 'wp-admin/includes/update-core.php' );
 
-               if ( ! function_exists( 'update_core' ) )
+               if ( ! function_exists( 'update_core' ) ) {
+                       WP_Upgrader::release_lock( 'core_updater' );
                        return new WP_Error( 'copy_failed_space', $this->strings['copy_failed_space'] );
+               }
 
                $result = update_core( $working_dir, $wp_dir );
 
@@ -2107,6 +2422,8 @@ class Core_Upgrader extends WP_Upgrader {
                        wp_version_check( $stats );
                }
 
+               WP_Upgrader::release_lock( 'core_updater' );
+
                return $result;
        }
 
@@ -2114,6 +2431,9 @@ class Core_Upgrader extends WP_Upgrader {
         * Determines if this WordPress Core version should update to an offered version or not.
         *
         * @since 3.7.0
+        * @access public
+        *
+        * @static
         *
         * @param string $offered_ver The offered version, of the format x.y.z.
         * @return bool True if we should update to the offered version, otherwise false.
@@ -2217,9 +2537,13 @@ class Core_Upgrader extends WP_Upgrader {
        }
 
        /**
-        * Compare the disk file checksums agains the expected checksums.
+        * Compare the disk file checksums against the expected checksums.
         *
         * @since 3.7.0
+        * @access public
+        *
+        * @global string $wp_version
+        * @global string $wp_local_package
         *
         * @return bool True if the checksums match, otherwise false.
         */
@@ -2244,10 +2568,11 @@ class Core_Upgrader extends WP_Upgrader {
 }
 
 /**
- * Upgrade Skin helper for File uploads. This class handles the upload process and passes it as if it's a local file to the Upgrade/Installer functions.
+ * Core class used for handling file uploads.
+ *
+ * This class handles the upload process and passes it as if it's a local file
+ * to the Upgrade/Installer functions.
  *
- * @package WordPress
- * @subpackage Upgrader
  * @since 2.8.0
  */
 class File_Upload_Upgrader {
@@ -2256,6 +2581,7 @@ class File_Upload_Upgrader {
         * The full path to the file package.
         *
         * @since 2.8.0
+        * @access public
         * @var string $package
         */
        public $package;
@@ -2264,6 +2590,7 @@ class File_Upload_Upgrader {
         * The name of the file.
         *
         * @since 2.8.0
+        * @access public
         * @var string $filename
         */
        public $filename;
@@ -2272,6 +2599,7 @@ class File_Upload_Upgrader {
         * The ID of the attachment post for this file.
         *
         * @since 3.3.0
+        * @access public
         * @var int $id
         */
        public $id = 0;
@@ -2280,6 +2608,7 @@ class File_Upload_Upgrader {
         * Construct the upgrader for a form.
         *
         * @since 2.8.0
+        * @access public
         *
         * @param string $form      The name of the form the file was uploaded from.
         * @param string $urlholder The name of the `GET` parameter that holds the filename.
@@ -2339,6 +2668,7 @@ class File_Upload_Upgrader {
         * Delete the attachment/uploaded file.
         *
         * @since 3.2.2
+        * @access public
         *
         * @return bool Whether the cleanup was successful.
         */
@@ -2354,10 +2684,8 @@ class File_Upload_Upgrader {
 }
 
 /**
- * The WordPress automatic background updater.
+ * Core class used for handling automatic background updates.
  *
- * @package WordPress
- * @subpackage Upgrader
  * @since 3.7.0
  */
 class WP_Automatic_Updater {
@@ -2366,6 +2694,7 @@ class WP_Automatic_Updater {
         * Tracks update results during processing.
         *
         * @var array
+        * @access protected
         */
        protected $update_results = array();
 
@@ -2373,13 +2702,14 @@ class WP_Automatic_Updater {
         * Whether the entire automatic updater is disabled.
         *
         * @since 3.7.0
+        * @access public
         */
        public function is_disabled() {
                // Background updates are disabled if you don't want file changes.
                if ( defined( 'DISALLOW_FILE_MODS' ) && DISALLOW_FILE_MODS )
                        return true;
 
-               if ( defined( 'WP_INSTALLING' ) )
+               if ( wp_installing() )
                        return true;
 
                // More fine grained control can be done through the WP_AUTO_UPDATE_CORE constant and filters.
@@ -2413,6 +2743,7 @@ class WP_Automatic_Updater {
         * how things get updated.
         *
         * @since 3.7.0
+        * @access public
         *
         * @param string $context The filesystem path to check, in addition to ABSPATH.
         */
@@ -2465,6 +2796,9 @@ class WP_Automatic_Updater {
         * Tests to see if we can and should update a specific item.
         *
         * @since 3.7.0
+        * @access public
+        *
+        * @global wpdb $wpdb WordPress database abstraction object.
         *
         * @param string $type    The type of update being checked: 'core', 'theme',
         *                        'plugin', 'translation'.
@@ -2546,6 +2880,7 @@ class WP_Automatic_Updater {
         * Notifies an administrator of a core update.
         *
         * @since 3.7.0
+        * @access protected
         *
         * @param object $item The update offer.
         */
@@ -2589,9 +2924,12 @@ class WP_Automatic_Updater {
         * Update an item, if appropriate.
         *
         * @since 3.7.0
+        * @access public
         *
         * @param string $type The type of update being checked: 'core', 'theme', 'plugin', 'translation'.
         * @param object $item The update offer.
+        *
+        * @return null|WP_Error
         */
        public function update( $type, $item ) {
                $skin = new Automatic_Upgrader_Skin;
@@ -2621,6 +2959,18 @@ class WP_Automatic_Updater {
                if ( ! $this->should_update( $type, $item, $context ) )
                        return false;
 
+               /**
+                * Fires immediately prior to an auto-update.
+                *
+                * @since 4.4.0
+                *
+                * @param string $type    The type of update being checked: 'core', 'theme', 'plugin', or 'translation'.
+                * @param object $item    The update offer.
+                * @param string $context The filesystem context (a path) against which filesystem access and status
+                *                        should be checked.
+                */
+               do_action( 'pre_auto_update', $type, $item, $context );
+
                $upgrader_item = $item;
                switch ( $type ) {
                        case 'core':
@@ -2667,8 +3017,13 @@ class WP_Automatic_Updater {
                        $upgrade_result = new WP_Error( 'fs_unavailable', __( 'Could not access filesystem.' ) );
                }
 
-               // Core doesn't output this, so let's append it so we don't get confused.
                if ( 'core' == $type ) {
+                       if ( is_wp_error( $upgrade_result ) && ( 'up_to_date' == $upgrade_result->get_error_code() || 'locked' == $upgrade_result->get_error_code() ) ) {
+                               // These aren't actual errors, treat it as a skipped-update instead to avoid triggering the post-core update failure routines.
+                               return false;
+                       }
+
+                       // Core doesn't output this, so let's append it so we don't get confused.
                        if ( is_wp_error( $upgrade_result ) ) {
                                $skin->error( __( 'Installation Failed' ), $upgrade_result );
                        } else {
@@ -2690,6 +3045,10 @@ class WP_Automatic_Updater {
         * Kicks off the background update process, looping through all pending updates.
         *
         * @since 3.7.0
+        * @access public
+        *
+        * @global wpdb   $wpdb
+        * @global string $wp_version
         */
        public function run() {
                global $wpdb, $wp_version;
@@ -2700,25 +3059,8 @@ class WP_Automatic_Updater {
                if ( ! is_main_network() || ! is_main_site() )
                        return;
 
-               $lock_name = 'auto_updater.lock';
-
-               // Try to lock
-               $lock_result = $wpdb->query( $wpdb->prepare( "INSERT IGNORE INTO `$wpdb->options` ( `option_name`, `option_value`, `autoload` ) VALUES (%s, %s, 'no') /* LOCK */", $lock_name, time() ) );
-
-               if ( ! $lock_result ) {
-                       $lock_result = get_option( $lock_name );
-
-                       // If we couldn't create a lock, and there isn't a lock, bail
-                       if ( ! $lock_result )
-                               return;
-
-                       // Check to see if the lock is still valid
-                       if ( $lock_result > ( time() - HOUR_IN_SECONDS ) )
-                               return;
-               }
-
-               // Update the lock, as by this point we've definitely got a lock, just need to fire the actions
-               update_option( $lock_name, time() );
+               if ( ! WP_Upgrader::create_lock( 'auto_updater' ) )
+                       return;
 
                // Don't automatically run these thins, as we'll handle it ourselves
                remove_action( 'upgrader_process_complete', array( 'Language_Pack_Upgrader', 'async_upgrade' ), 20 );
@@ -2817,14 +3159,18 @@ class WP_Automatic_Updater {
                        do_action( 'automatic_updates_complete', $this->update_results );
                }
 
-               // Clear the lock
-               delete_option( $lock_name );
+               WP_Upgrader::release_lock( 'auto_updater' );
        }
 
        /**
         * If we tried to perform a core update, check if we should send an email,
         * and if we need to avoid processing future updates.
         *
+        * @since Unknown
+        * @access protected
+        *
+        * @global string $wp_version
+        *
         * @param object $update_result The result of the core update. Includes the update offer and result.
         */
        protected function after_core_update( $update_result ) {
@@ -2883,7 +3229,7 @@ class WP_Automatic_Updater {
                 * the issue could actually be on WordPress.org's side.) If that one fails, then email.
                 */
                $send = true;
-               $transient_failures = array( 'incompatible_archive', 'download_failed', 'insane_distro' );
+               $transient_failures = array( 'incompatible_archive', 'download_failed', 'insane_distro', 'locked' );
                if ( in_array( $error_code, $transient_failures ) && ! get_site_option( 'auto_core_update_failed' ) ) {
                        wp_schedule_single_event( time() + HOUR_IN_SECONDS, 'wp_maybe_auto_update' );
                        $send = false;
@@ -2911,6 +3257,9 @@ class WP_Automatic_Updater {
         * Sends an email upon the completion or failure of a background core update.
         *
         * @since 3.7.0
+        * @access protected
+        *
+        * @global string $wp_version
         *
         * @param string $type        The type of email to send. Can be one of 'success', 'fail', 'manual', 'critical'.
         * @param object $core_update The update offer that was attempted.
@@ -3108,6 +3457,7 @@ class WP_Automatic_Updater {
         * Prepares and sends an email of a full log of background update results, useful for debugging and geekery.
         *
         * @since 3.7.0
+        * @access protected
         */
        protected function send_debug_email() {
                $update_count = 0;