Make WordPress Core

Changeset 37409


Ignore:
Timestamp:
05/10/2016 11:44:06 AM (9 years ago)
Author:
ocean90
Message:

Upgrader: Copy WP_Upgrader subclasses into one file per class.

Part 4/8.
See #36618.

Location:
trunk/src/wp-admin/includes
Files:
6 copied

Legend:

Unmodified
Added
Removed
  • trunk/src/wp-admin/includes/class-core-upgrader.php

    r37383 r37409  
    1111 * @since 2.8.0
    1212 */
    13 
    14 require ABSPATH . 'wp-admin/includes/class-wp-upgrader-skins.php';
    15 
    16 /**
    17  * Core class used for upgrading/installing a local set of files via
    18  * the Filesystem Abstraction classes from a Zip file.
    19  *
    20  * @since 2.8.0
    21  */
    22 class WP_Upgrader {
    23 
    24     /**
    25      * The error/notification strings used to update the user on the progress.
    26      *
    27      * @since 2.8.0
    28      * @access public
    29      * @var string $strings
    30      */
    31     public $strings = array();
    32 
    33     /**
    34      * The upgrader skin being used.
    35      *
    36      * @since 2.8.0
    37      * @access public
    38      * @var WP_Upgrader_Skin $skin
    39      */
    40     public $skin = null;
    41 
    42     /**
    43      * The result of the installation.
    44      *
    45      * This is set by WP_Upgrader::install_package(), only when the package is installed
    46      * successfully. It will then be an array, unless a WP_Error is returned by the
    47      * {@see 'upgrader_post_install'} filter. In that case, the WP_Error will be assigned to
    48      * it.
    49      *
    50      * @since 2.8.0
    51      * @access public
    52      *
    53      * @var WP_Error|array $result {
    54      *      @type string $source             The full path to the source the files were installed from.
    55      *      @type string $source_files       List of all the files in the source directory.
    56      *      @type string $destination        The full path to the install destination folder.
    57      *      @type string $destination_name   The name of the destination folder, or empty if `$destination`
    58      *                                       and `$local_destination` are the same.
    59      *      @type string $local_destination  The full local path to the destination folder. This is usually
    60      *                                       the same as `$destination`.
    61      *      @type string $remote_destination The full remote path to the destination folder
    62      *                                       (i.e., from `$wp_filesystem`).
    63      *      @type bool   $clear_destination  Whether the destination folder was cleared.
    64      * }
    65      */
    66     public $result = array();
    67 
    68     /**
    69      * The total number of updates being performed.
    70      *
    71      * Set by the bulk update methods.
    72      *
    73      * @since 3.0.0
    74      * @access public
    75      * @var int $update_count
    76      */
    77     public $update_count = 0;
    78 
    79     /**
    80      * The current update if multiple updates are being performed.
    81      *
    82      * Used by the bulk update methods, and incremented for each update.
    83      *
    84      * @since 3.0.0
    85      * @access public
    86      * @var int
    87      */
    88     public $update_current = 0;
    89 
    90     /**
    91      * Construct the upgrader with a skin.
    92      *
    93      * @since 2.8.0
    94      * @access public
    95      *
    96      * @param WP_Upgrader_Skin $skin The upgrader skin to use. Default is a WP_Upgrader_Skin.
    97      *                               instance.
    98      */
    99     public function __construct( $skin = null ) {
    100         if ( null == $skin )
    101             $this->skin = new WP_Upgrader_Skin();
    102         else
    103             $this->skin = $skin;
    104     }
    105 
    106     /**
    107      * Initialize the upgrader.
    108      *
    109      * This will set the relationship between the skin being used and this upgrader,
    110      * and also add the generic strings to `WP_Upgrader::$strings`.
    111      *
    112      * @since 2.8.0
    113      * @access public
    114      */
    115     public function init() {
    116         $this->skin->set_upgrader($this);
    117         $this->generic_strings();
    118     }
    119 
    120     /**
    121      * Add the generic strings to WP_Upgrader::$strings.
    122      *
    123      * @since 2.8.0
    124      * @access public
    125      */
    126     public function generic_strings() {
    127         $this->strings['bad_request'] = __('Invalid Data provided.');
    128         $this->strings['fs_unavailable'] = __('Could not access filesystem.');
    129         $this->strings['fs_error'] = __('Filesystem error.');
    130         $this->strings['fs_no_root_dir'] = __('Unable to locate WordPress Root directory.');
    131         $this->strings['fs_no_content_dir'] = __('Unable to locate WordPress Content directory (wp-content).');
    132         $this->strings['fs_no_plugins_dir'] = __('Unable to locate WordPress Plugin directory.');
    133         $this->strings['fs_no_themes_dir'] = __('Unable to locate WordPress Theme directory.');
    134         /* translators: %s: directory name */
    135         $this->strings['fs_no_folder'] = __('Unable to locate needed folder (%s).');
    136 
    137         $this->strings['download_failed'] = __('Download failed.');
    138         $this->strings['installing_package'] = __('Installing the latest version…');
    139         $this->strings['no_files'] = __('The package contains no files.');
    140         $this->strings['folder_exists'] = __('Destination folder already exists.');
    141         $this->strings['mkdir_failed'] = __('Could not create directory.');
    142         $this->strings['incompatible_archive'] = __('The package could not be installed.');
    143         $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.' );
    144 
    145         $this->strings['maintenance_start'] = __('Enabling Maintenance mode…');
    146         $this->strings['maintenance_end'] = __('Disabling Maintenance mode…');
    147     }
    148 
    149     /**
    150      * Connect to the filesystem.
    151      *
    152      * @since 2.8.0
    153      * @access public
    154      *
    155      * @global WP_Filesystem_Base $wp_filesystem Subclass
    156      *
    157      * @param array $directories                  Optional. A list of directories. If any of these do
    158      *                                            not exist, a WP_Error object will be returned.
    159      *                                            Default empty array.
    160      * @param bool  $allow_relaxed_file_ownership Whether to allow relaxed file ownership.
    161      *                                            Default false.
    162      * @return bool|WP_Error True if able to connect, false or a WP_Error otherwise.
    163      */
    164     public function fs_connect( $directories = array(), $allow_relaxed_file_ownership = false ) {
    165         global $wp_filesystem;
    166 
    167         if ( false === ( $credentials = $this->skin->request_filesystem_credentials( false, $directories[0], $allow_relaxed_file_ownership ) ) ) {
    168             return false;
    169         }
    170 
    171         if ( ! WP_Filesystem( $credentials, $directories[0], $allow_relaxed_file_ownership ) ) {
    172             $error = true;
    173             if ( is_object($wp_filesystem) && $wp_filesystem->errors->get_error_code() )
    174                 $error = $wp_filesystem->errors;
    175             // Failed to connect, Error and request again
    176             $this->skin->request_filesystem_credentials( $error, $directories[0], $allow_relaxed_file_ownership );
    177             return false;
    178         }
    179 
    180         if ( ! is_object($wp_filesystem) )
    181             return new WP_Error('fs_unavailable', $this->strings['fs_unavailable'] );
    182 
    183         if ( is_wp_error($wp_filesystem->errors) && $wp_filesystem->errors->get_error_code() )
    184             return new WP_Error('fs_error', $this->strings['fs_error'], $wp_filesystem->errors);
    185 
    186         foreach ( (array)$directories as $dir ) {
    187             switch ( $dir ) {
    188                 case ABSPATH:
    189                     if ( ! $wp_filesystem->abspath() )
    190                         return new WP_Error('fs_no_root_dir', $this->strings['fs_no_root_dir']);
    191                     break;
    192                 case WP_CONTENT_DIR:
    193                     if ( ! $wp_filesystem->wp_content_dir() )
    194                         return new WP_Error('fs_no_content_dir', $this->strings['fs_no_content_dir']);
    195                     break;
    196                 case WP_PLUGIN_DIR:
    197                     if ( ! $wp_filesystem->wp_plugins_dir() )
    198                         return new WP_Error('fs_no_plugins_dir', $this->strings['fs_no_plugins_dir']);
    199                     break;
    200                 case get_theme_root():
    201                     if ( ! $wp_filesystem->wp_themes_dir() )
    202                         return new WP_Error('fs_no_themes_dir', $this->strings['fs_no_themes_dir']);
    203                     break;
    204                 default:
    205                     if ( ! $wp_filesystem->find_folder($dir) )
    206                         return new WP_Error( 'fs_no_folder', sprintf( $this->strings['fs_no_folder'], esc_html( basename( $dir ) ) ) );
    207                     break;
    208             }
    209         }
    210         return true;
    211     } //end fs_connect();
    212 
    213     /**
    214      * Download a package.
    215      *
    216      * @since 2.8.0
    217      * @access public
    218      *
    219      * @param string $package The URI of the package. If this is the full path to an
    220      *                        existing local file, it will be returned untouched.
    221      * @return string|WP_Error The full path to the downloaded package file, or a WP_Error object.
    222      */
    223     public function download_package( $package ) {
    224 
    225         /**
    226          * Filter whether to return the package.
    227          *
    228          * @since 3.7.0
    229          * @access public
    230          *
    231          * @param bool        $reply   Whether to bail without returning the package.
    232          *                             Default false.
    233          * @param string      $package The package file name.
    234          * @param WP_Upgrader $this    The WP_Upgrader instance.
    235          */
    236         $reply = apply_filters( 'upgrader_pre_download', false, $package, $this );
    237         if ( false !== $reply )
    238             return $reply;
    239 
    240         if ( ! preg_match('!^(http|https|ftp)://!i', $package) && file_exists($package) ) //Local file or remote?
    241             return $package; //must be a local file..
    242 
    243         if ( empty($package) )
    244             return new WP_Error('no_package', $this->strings['no_package']);
    245 
    246         $this->skin->feedback('downloading_package', $package);
    247 
    248         $download_file = download_url($package);
    249 
    250         if ( is_wp_error($download_file) )
    251             return new WP_Error('download_failed', $this->strings['download_failed'], $download_file->get_error_message());
    252 
    253         return $download_file;
    254     }
    255 
    256     /**
    257      * Unpack a compressed package file.
    258      *
    259      * @since 2.8.0
    260      * @access public
    261      *
    262      * @global WP_Filesystem_Base $wp_filesystem Subclass
    263      *
    264      * @param string $package        Full path to the package file.
    265      * @param bool   $delete_package Optional. Whether to delete the package file after attempting
    266      *                               to unpack it. Default true.
    267      * @return string|WP_Error The path to the unpacked contents, or a WP_Error on failure.
    268      */
    269     public function unpack_package( $package, $delete_package = true ) {
    270         global $wp_filesystem;
    271 
    272         $this->skin->feedback('unpack_package');
    273 
    274         $upgrade_folder = $wp_filesystem->wp_content_dir() . 'upgrade/';
    275 
    276         //Clean up contents of upgrade directory beforehand.
    277         $upgrade_files = $wp_filesystem->dirlist($upgrade_folder);
    278         if ( !empty($upgrade_files) ) {
    279             foreach ( $upgrade_files as $file )
    280                 $wp_filesystem->delete($upgrade_folder . $file['name'], true);
    281         }
    282 
    283         // We need a working directory - Strip off any .tmp or .zip suffixes
    284         $working_dir = $upgrade_folder . basename( basename( $package, '.tmp' ), '.zip' );
    285 
    286         // Clean up working directory
    287         if ( $wp_filesystem->is_dir($working_dir) )
    288             $wp_filesystem->delete($working_dir, true);
    289 
    290         // Unzip package to working directory
    291         $result = unzip_file( $package, $working_dir );
    292 
    293         // Once extracted, delete the package if required.
    294         if ( $delete_package )
    295             unlink($package);
    296 
    297         if ( is_wp_error($result) ) {
    298             $wp_filesystem->delete($working_dir, true);
    299             if ( 'incompatible_archive' == $result->get_error_code() ) {
    300                 return new WP_Error( 'incompatible_archive', $this->strings['incompatible_archive'], $result->get_error_data() );
    301             }
    302             return $result;
    303         }
    304 
    305         return $working_dir;
    306     }
    307 
    308     /**
    309      * Clears the directory where this item is going to be installed into.
    310      *
    311      * @since 4.3.0
    312      * @access public
    313      *
    314      * @global WP_Filesystem_Base $wp_filesystem Subclass
    315      *
    316      * @param string $remote_destination The location on the remote filesystem to be cleared
    317      * @return bool|WP_Error True upon success, WP_Error on failure.
    318      */
    319     public function clear_destination( $remote_destination ) {
    320         global $wp_filesystem;
    321 
    322         if ( ! $wp_filesystem->exists( $remote_destination ) ) {
    323             return true;
    324         }
    325 
    326         // Check all files are writable before attempting to clear the destination.
    327         $unwritable_files = array();
    328 
    329         $_files = $wp_filesystem->dirlist( $remote_destination, true, true );
    330 
    331         // Flatten the resulting array, iterate using each as we append to the array during iteration.
    332         while ( $f = each( $_files ) ) {
    333             $file = $f['value'];
    334             $name = $f['key'];
    335 
    336             if ( ! isset( $file['files'] ) ) {
    337                 continue;
    338             }
    339 
    340             foreach ( $file['files'] as $filename => $details ) {
    341                 $_files[ $name . '/' . $filename ] = $details;
    342             }
    343         }
    344 
    345         // Check writability.
    346         foreach ( $_files as $filename => $file_details ) {
    347             if ( ! $wp_filesystem->is_writable( $remote_destination . $filename ) ) {
    348 
    349                 // Attempt to alter permissions to allow writes and try again.
    350                 $wp_filesystem->chmod( $remote_destination . $filename, ( 'd' == $file_details['type'] ? FS_CHMOD_DIR : FS_CHMOD_FILE ) );
    351                 if ( ! $wp_filesystem->is_writable( $remote_destination . $filename ) ) {
    352                     $unwritable_files[] = $filename;
    353                 }
    354             }
    355         }
    356 
    357         if ( ! empty( $unwritable_files ) ) {
    358             return new WP_Error( 'files_not_writable', $this->strings['files_not_writable'], implode( ', ', $unwritable_files ) );
    359         }
    360 
    361         if ( ! $wp_filesystem->delete( $remote_destination, true ) ) {
    362             return new WP_Error( 'remove_old_failed', $this->strings['remove_old_failed'] );
    363         }
    364 
    365         return true;
    366     }
    367 
    368     /**
    369      * Install a package.
    370      *
    371      * Copies the contents of a package form a source directory, and installs them in
    372      * a destination directory. Optionally removes the source. It can also optionally
    373      * clear out the destination folder if it already exists.
    374      *
    375      * @since 2.8.0
    376      * @access public
    377      *
    378      * @global WP_Filesystem_Base $wp_filesystem Subclass
    379      * @global array              $wp_theme_directories
    380      *
    381      * @param array|string $args {
    382      *     Optional. Array or string of arguments for installing a package. Default empty array.
    383      *
    384      *     @type string $source                      Required path to the package source. Default empty.
    385      *     @type string $destination                 Required path to a folder to install the package in.
    386      *                                               Default empty.
    387      *     @type bool   $clear_destination           Whether to delete any files already in the destination
    388      *                                               folder. Default false.
    389      *     @type bool   $clear_working               Whether to delete the files form the working directory
    390      *                                               after copying to the destination. Default false.
    391      *     @type bool   $abort_if_destination_exists Whether to abort the installation if
    392      *                                               the destination folder already exists. Default true.
    393      *     @type array  $hook_extra                  Extra arguments to pass to the filter hooks called by
    394      *                                               WP_Upgrader::install_package(). Default empty array.
    395      * }
    396      *
    397      * @return array|WP_Error The result (also stored in `WP_Upgrader::$result`), or a WP_Error on failure.
    398      */
    399     public function install_package( $args = array() ) {
    400         global $wp_filesystem, $wp_theme_directories;
    401 
    402         $defaults = array(
    403             'source' => '', // Please always pass this
    404             'destination' => '', // and this
    405             'clear_destination' => false,
    406             'clear_working' => false,
    407             'abort_if_destination_exists' => true,
    408             'hook_extra' => array()
    409         );
    410 
    411         $args = wp_parse_args($args, $defaults);
    412 
    413         // These were previously extract()'d.
    414         $source = $args['source'];
    415         $destination = $args['destination'];
    416         $clear_destination = $args['clear_destination'];
    417 
    418         @set_time_limit( 300 );
    419 
    420         if ( empty( $source ) || empty( $destination ) ) {
    421             return new WP_Error( 'bad_request', $this->strings['bad_request'] );
    422         }
    423         $this->skin->feedback( 'installing_package' );
    424 
    425         /**
    426          * Filter the install response before the installation has started.
    427          *
    428          * Returning a truthy value, or one that could be evaluated as a WP_Error
    429          * will effectively short-circuit the installation, returning that value
    430          * instead.
    431          *
    432          * @since 2.8.0
    433          *
    434          * @param bool|WP_Error $response   Response.
    435          * @param array         $hook_extra Extra arguments passed to hooked filters.
    436          */
    437         $res = apply_filters( 'upgrader_pre_install', true, $args['hook_extra'] );
    438 
    439         if ( is_wp_error( $res ) ) {
    440             return $res;
    441         }
    442 
    443         //Retain the Original source and destinations
    444         $remote_source = $args['source'];
    445         $local_destination = $destination;
    446 
    447         $source_files = array_keys( $wp_filesystem->dirlist( $remote_source ) );
    448         $remote_destination = $wp_filesystem->find_folder( $local_destination );
    449 
    450         //Locate which directory to copy to the new folder, This is based on the actual folder holding the files.
    451         if ( 1 == count( $source_files ) && $wp_filesystem->is_dir( trailingslashit( $args['source'] ) . $source_files[0] . '/' ) ) { //Only one folder? Then we want its contents.
    452             $source = trailingslashit( $args['source'] ) . trailingslashit( $source_files[0] );
    453         } elseif ( count( $source_files ) == 0 ) {
    454             return new WP_Error( 'incompatible_archive_empty', $this->strings['incompatible_archive'], $this->strings['no_files'] ); // There are no files?
    455         } 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.
    456             $source = trailingslashit( $args['source'] );
    457         }
    458 
    459         /**
    460          * Filter the source file location for the upgrade package.
    461          *
    462          * @since 2.8.0
    463          * @since 4.4.0 The $hook_extra parameter became available.
    464          *
    465          * @param string      $source        File source location.
    466          * @param string      $remote_source Remote file source location.
    467          * @param WP_Upgrader $this          WP_Upgrader instance.
    468          * @param array       $hook_extra    Extra arguments passed to hooked filters.
    469          */
    470         $source = apply_filters( 'upgrader_source_selection', $source, $remote_source, $this, $args['hook_extra'] );
    471 
    472         if ( is_wp_error( $source ) ) {
    473             return $source;
    474         }
    475 
    476         // Has the source location changed? If so, we need a new source_files list.
    477         if ( $source !== $remote_source ) {
    478             $source_files = array_keys( $wp_filesystem->dirlist( $source ) );
    479         }
    480 
    481         /*
    482          * Protection against deleting files in any important base directories.
    483          * Theme_Upgrader & Plugin_Upgrader also trigger this, as they pass the
    484          * destination directory (WP_PLUGIN_DIR / wp-content/themes) intending
    485          * to copy the directory into the directory, whilst they pass the source
    486          * as the actual files to copy.
    487          */
    488         $protected_directories = array( ABSPATH, WP_CONTENT_DIR, WP_PLUGIN_DIR, WP_CONTENT_DIR . '/themes' );
    489 
    490         if ( is_array( $wp_theme_directories ) ) {
    491             $protected_directories = array_merge( $protected_directories, $wp_theme_directories );
    492         }
    493 
    494         if ( in_array( $destination, $protected_directories ) ) {
    495             $remote_destination = trailingslashit( $remote_destination ) . trailingslashit( basename( $source ) );
    496             $destination = trailingslashit( $destination ) . trailingslashit( basename( $source ) );
    497         }
    498 
    499         if ( $clear_destination ) {
    500             // We're going to clear the destination if there's something there.
    501             $this->skin->feedback('remove_old');
    502 
    503             $removed = $this->clear_destination( $remote_destination );
    504 
    505             /**
    506              * Filter whether the upgrader cleared the destination.
    507              *
    508              * @since 2.8.0
    509              *
    510              * @param mixed  $removed            Whether the destination was cleared. true on success, WP_Error on failure
    511              * @param string $local_destination  The local package destination.
    512              * @param string $remote_destination The remote package destination.
    513              * @param array  $hook_extra         Extra arguments passed to hooked filters.
    514              */
    515             $removed = apply_filters( 'upgrader_clear_destination', $removed, $local_destination, $remote_destination, $args['hook_extra'] );
    516 
    517             if ( is_wp_error( $removed ) ) {
    518                 return $removed;
    519             }
    520         } elseif ( $args['abort_if_destination_exists'] && $wp_filesystem->exists($remote_destination) ) {
    521             //If we're not clearing the destination folder and something exists there already, Bail.
    522             //But first check to see if there are actually any files in the folder.
    523             $_files = $wp_filesystem->dirlist($remote_destination);
    524             if ( ! empty($_files) ) {
    525                 $wp_filesystem->delete($remote_source, true); //Clear out the source files.
    526                 return new WP_Error('folder_exists', $this->strings['folder_exists'], $remote_destination );
    527             }
    528         }
    529 
    530         //Create destination if needed
    531         if ( ! $wp_filesystem->exists( $remote_destination ) ) {
    532             if ( ! $wp_filesystem->mkdir( $remote_destination, FS_CHMOD_DIR ) ) {
    533                 return new WP_Error( 'mkdir_failed_destination', $this->strings['mkdir_failed'], $remote_destination );
    534             }
    535         }
    536         // Copy new version of item into place.
    537         $result = copy_dir($source, $remote_destination);
    538         if ( is_wp_error($result) ) {
    539             if ( $args['clear_working'] ) {
    540                 $wp_filesystem->delete( $remote_source, true );
    541             }
    542             return $result;
    543         }
    544 
    545         //Clear the Working folder?
    546         if ( $args['clear_working'] ) {
    547             $wp_filesystem->delete( $remote_source, true );
    548         }
    549 
    550         $destination_name = basename( str_replace($local_destination, '', $destination) );
    551         if ( '.' == $destination_name ) {
    552             $destination_name = '';
    553         }
    554 
    555         $this->result = compact( 'source', 'source_files', 'destination', 'destination_name', 'local_destination', 'remote_destination', 'clear_destination' );
    556 
    557         /**
    558          * Filter the install response after the installation has finished.
    559          *
    560          * @since 2.8.0
    561          *
    562          * @param bool  $response   Install response.
    563          * @param array $hook_extra Extra arguments passed to hooked filters.
    564          * @param array $result     Installation result data.
    565          */
    566         $res = apply_filters( 'upgrader_post_install', true, $args['hook_extra'], $this->result );
    567 
    568         if ( is_wp_error($res) ) {
    569             $this->result = $res;
    570             return $res;
    571         }
    572 
    573         //Bombard the calling function will all the info which we've just used.
    574         return $this->result;
    575     }
    576 
    577     /**
    578      * Run an upgrade/install.
    579      *
    580      * Attempts to download the package (if it is not a local file), unpack it, and
    581      * install it in the destination folder.
    582      *
    583      * @since 2.8.0
    584      * @access public
    585      *
    586      * @param array $options {
    587      *     Array or string of arguments for upgrading/installing a package.
    588      *
    589      *     @type string $package                     The full path or URI of the package to install.
    590      *                                               Default empty.
    591      *     @type string $destination                 The full path to the destination folder.
    592      *                                               Default empty.
    593      *     @type bool   $clear_destination           Whether to delete any files already in the
    594      *                                               destination folder. Default false.
    595      *     @type bool   $clear_working               Whether to delete the files form the working
    596      *                                               directory after copying to the destination.
    597      *                                               Default false.
    598      *     @type bool   $abort_if_destination_exists Whether to abort the installation if the destination
    599      *                                               folder already exists. When true, `$clear_destination`
    600      *                                               should be false. Default true.
    601      *     @type bool   $is_multi                    Whether this run is one of multiple upgrade/install
    602      *                                               actions being performed in bulk. When true, the skin
    603      *                                               WP_Upgrader::header() and WP_Upgrader::footer()
    604      *                                               aren't called. Default false.
    605      *     @type array  $hook_extra                  Extra arguments to pass to the filter hooks called by
    606      *                                               WP_Upgrader::run().
    607      * }
    608      * @return array|false|WP_error The result from self::install_package() on success, otherwise a WP_Error,
    609      *                              or false if unable to connect to the filesystem.
    610      */
    611     public function run( $options ) {
    612 
    613         $defaults = array(
    614             'package' => '', // Please always pass this.
    615             'destination' => '', // And this
    616             'clear_destination' => false,
    617             'abort_if_destination_exists' => true, // Abort if the Destination directory exists, Pass clear_destination as false please
    618             'clear_working' => true,
    619             'is_multi' => false,
    620             'hook_extra' => array() // Pass any extra $hook_extra args here, this will be passed to any hooked filters.
    621         );
    622 
    623         $options = wp_parse_args( $options, $defaults );
    624 
    625         /**
    626          * Filter the package options before running an update.
    627          *
    628          * @since 4.3.0
    629          *
    630          * @param array $options {
    631          *     Options used by the upgrader.
    632          *
    633          *     @type string $package                     Package for update.
    634          *     @type string $destination                 Update location.
    635          *     @type bool   $clear_destination           Clear the destination resource.
    636          *     @type bool   $clear_working               Clear the working resource.
    637          *     @type bool   $abort_if_destination_exists Abort if the Destination directory exists.
    638          *     @type bool   $is_multi                    Whether the upgrader is running multiple times.
    639          *     @type array  $hook_extra                  Extra hook arguments.
    640          * }
    641          */
    642         $options = apply_filters( 'upgrader_package_options', $options );
    643 
    644         if ( ! $options['is_multi'] ) { // call $this->header separately if running multiple times
    645             $this->skin->header();
    646         }
    647 
    648         // Connect to the Filesystem first.
    649         $res = $this->fs_connect( array( WP_CONTENT_DIR, $options['destination'] ) );
    650         // Mainly for non-connected filesystem.
    651         if ( ! $res ) {
    652             if ( ! $options['is_multi'] ) {
    653                 $this->skin->footer();
    654             }
    655             return false;
    656         }
    657 
    658         $this->skin->before();
    659 
    660         if ( is_wp_error($res) ) {
    661             $this->skin->error($res);
    662             $this->skin->after();
    663             if ( ! $options['is_multi'] ) {
    664                 $this->skin->footer();
    665             }
    666             return $res;
    667         }
    668 
    669         /*
    670          * Download the package (Note, This just returns the filename
    671          * of the file if the package is a local file)
    672          */
    673         $download = $this->download_package( $options['package'] );
    674         if ( is_wp_error($download) ) {
    675             $this->skin->error($download);
    676             $this->skin->after();
    677             if ( ! $options['is_multi'] ) {
    678                 $this->skin->footer();
    679             }
    680             return $download;
    681         }
    682 
    683         $delete_package = ( $download != $options['package'] ); // Do not delete a "local" file
    684 
    685         // Unzips the file into a temporary directory.
    686         $working_dir = $this->unpack_package( $download, $delete_package );
    687         if ( is_wp_error($working_dir) ) {
    688             $this->skin->error($working_dir);
    689             $this->skin->after();
    690             if ( ! $options['is_multi'] ) {
    691                 $this->skin->footer();
    692             }
    693             return $working_dir;
    694         }
    695 
    696         // With the given options, this installs it to the destination directory.
    697         $result = $this->install_package( array(
    698             'source' => $working_dir,
    699             'destination' => $options['destination'],
    700             'clear_destination' => $options['clear_destination'],
    701             'abort_if_destination_exists' => $options['abort_if_destination_exists'],
    702             'clear_working' => $options['clear_working'],
    703             'hook_extra' => $options['hook_extra']
    704         ) );
    705 
    706         $this->skin->set_result($result);
    707         if ( is_wp_error($result) ) {
    708             $this->skin->error($result);
    709             $this->skin->feedback('process_failed');
    710         } else {
    711             // Install succeeded.
    712             $this->skin->feedback('process_success');
    713         }
    714 
    715         $this->skin->after();
    716 
    717         if ( ! $options['is_multi'] ) {
    718 
    719             /** This action is documented in wp-admin/includes/class-wp-upgrader.php */
    720             do_action( 'upgrader_process_complete', $this, $options['hook_extra'] );
    721             $this->skin->footer();
    722         }
    723 
    724         return $result;
    725     }
    726 
    727     /**
    728      * Toggle maintenance mode for the site.
    729      *
    730      * Creates/deletes the maintenance file to enable/disable maintenance mode.
    731      *
    732      * @since 2.8.0
    733      * @access public
    734      *
    735      * @global WP_Filesystem_Base $wp_filesystem Subclass
    736      *
    737      * @param bool $enable True to enable maintenance mode, false to disable.
    738      */
    739     public function maintenance_mode( $enable = false ) {
    740         global $wp_filesystem;
    741         $file = $wp_filesystem->abspath() . '.maintenance';
    742         if ( $enable ) {
    743             $this->skin->feedback('maintenance_start');
    744             // Create maintenance file to signal that we are upgrading
    745             $maintenance_string = '<?php $upgrading = ' . time() . '; ?>';
    746             $wp_filesystem->delete($file);
    747             $wp_filesystem->put_contents($file, $maintenance_string, FS_CHMOD_FILE);
    748         } elseif ( ! $enable && $wp_filesystem->exists( $file ) ) {
    749             $this->skin->feedback('maintenance_end');
    750             $wp_filesystem->delete($file);
    751         }
    752     }
    753 
    754     /**
    755      * Creates a lock using WordPress options.
    756      *
    757      * @since 4.5.0
    758      * @access public
    759      * @static
    760      *
    761      * @param string $lock_name       The name of this unique lock.
    762      * @param int    $release_timeout Optional. The duration in seconds to respect an existing lock.
    763      *                                Default: 1 hour.
    764      * @return bool False if a lock couldn't be created or if the lock is no longer valid. True otherwise.
    765      */
    766     public static function create_lock( $lock_name, $release_timeout = null ) {
    767         global $wpdb;
    768         if ( ! $release_timeout ) {
    769             $release_timeout = HOUR_IN_SECONDS;
    770         }
    771         $lock_option = $lock_name . '.lock';
    772 
    773         // Try to lock.
    774         $lock_result = $wpdb->query( $wpdb->prepare( "INSERT IGNORE INTO `$wpdb->options` ( `option_name`, `option_value`, `autoload` ) VALUES (%s, %s, 'no') /* LOCK */", $lock_option, time() ) );
    775 
    776         if ( ! $lock_result ) {
    777             $lock_result = get_option( $lock_option );
    778 
    779             // If a lock couldn't be created, and there isn't a lock, bail.
    780             if ( ! $lock_result ) {
    781                 return false;
    782             }
    783 
    784             // Check to see if the lock is still valid. If not, bail.
    785             if ( $lock_result > ( time() - $release_timeout ) ) {
    786                 return false;
    787             }
    788 
    789             // There must exist an expired lock, clear it and re-gain it.
    790             WP_Upgrader::release_lock( $lock_name );
    791 
    792             return WP_Upgrader::create_lock( $lock_name, $release_timeout );
    793         }
    794 
    795         // Update the lock, as by this point we've definitely got a lock, just need to fire the actions.
    796         update_option( $lock_option, time() );
    797 
    798         return true;
    799     }
    800 
    801     /**
    802      * Releases an upgrader lock.
    803      *
    804      * @since 4.5.0
    805      * @access public
    806      * @static
    807      *
    808      * @see WP_Upgrader::create_lock()
    809      *
    810      * @param string $lock_name The name of this unique lock.
    811      * @return bool True if the lock was successfully released. False on failure.
    812      */
    813     public static function release_lock( $lock_name ) {
    814         return delete_option( $lock_name . '.lock' );
    815     }
    816 
    817 }
    818 
    819 /**
    820  * Core class used for upgrading/installing plugins.
    821  *
    822  * It is designed to upgrade/install plugins from a local zip, remote zip URL,
    823  * or uploaded zip file.
    824  *
    825  * @since 2.8.0
    826  *
    827  * @see WP_Upgrader
    828  */
    829 class Plugin_Upgrader extends WP_Upgrader {
    830 
    831     /**
    832      * Plugin upgrade result.
    833      *
    834      * @since 2.8.0
    835      * @access public
    836      * @var array|WP_Error $result
    837      *
    838      * @see WP_Upgrader::$result
    839      */
    840     public $result;
    841 
    842     /**
    843      * Whether a bulk upgrade/install is being performed.
    844      *
    845      * @since 2.9.0
    846      * @access public
    847      * @var bool $bulk
    848      */
    849     public $bulk = false;
    850 
    851     /**
    852      * Initialize the upgrade strings.
    853      *
    854      * @since 2.8.0
    855      * @access public
    856      */
    857     public function upgrade_strings() {
    858         $this->strings['up_to_date'] = __('The plugin is at the latest version.');
    859         $this->strings['no_package'] = __('Update package not available.');
    860         $this->strings['downloading_package'] = __('Downloading update from <span class="code">%s</span>&#8230;');
    861         $this->strings['unpack_package'] = __('Unpacking the update&#8230;');
    862         $this->strings['remove_old'] = __('Removing the old version of the plugin&#8230;');
    863         $this->strings['remove_old_failed'] = __('Could not remove the old plugin.');
    864         $this->strings['process_failed'] = __('Plugin update failed.');
    865         $this->strings['process_success'] = __('Plugin updated successfully.');
    866         $this->strings['process_bulk_success'] = __('Plugins updated successfully.');
    867     }
    868 
    869     /**
    870      * Initialize the install strings.
    871      *
    872      * @since 2.8.0
    873      * @access public
    874      */
    875     public function install_strings() {
    876         $this->strings['no_package'] = __('Install package not available.');
    877         $this->strings['downloading_package'] = __('Downloading install package from <span class="code">%s</span>&#8230;');
    878         $this->strings['unpack_package'] = __('Unpacking the package&#8230;');
    879         $this->strings['installing_package'] = __('Installing the plugin&#8230;');
    880         $this->strings['no_files'] = __('The plugin contains no files.');
    881         $this->strings['process_failed'] = __('Plugin install failed.');
    882         $this->strings['process_success'] = __('Plugin installed successfully.');
    883     }
    884 
    885     /**
    886      * Install a plugin package.
    887      *
    888      * @since 2.8.0
    889      * @since 3.7.0 The `$args` parameter was added, making clearing the plugin update cache optional.
    890      * @access public
    891      *
    892      * @param string $package The full local path or URI of the package.
    893      * @param array  $args {
    894      *     Optional. Other arguments for installing a plugin package. Default empty array.
    895      *
    896      *     @type bool $clear_update_cache Whether to clear the plugin updates cache if successful.
    897      *                                    Default true.
    898      * }
    899      * @return bool|WP_Error True if the install was successful, false or a WP_Error otherwise.
    900      */
    901     public function install( $package, $args = array() ) {
    902 
    903         $defaults = array(
    904             'clear_update_cache' => true,
    905         );
    906         $parsed_args = wp_parse_args( $args, $defaults );
    907 
    908         $this->init();
    909         $this->install_strings();
    910 
    911         add_filter('upgrader_source_selection', array($this, 'check_package') );
    912         // Clear cache so wp_update_plugins() knows about the new plugin.
    913         add_action( 'upgrader_process_complete', 'wp_clean_plugins_cache', 9, 0 );
    914 
    915         $this->run( array(
    916             'package' => $package,
    917             'destination' => WP_PLUGIN_DIR,
    918             'clear_destination' => false, // Do not overwrite files.
    919             'clear_working' => true,
    920             'hook_extra' => array(
    921                 'type' => 'plugin',
    922                 'action' => 'install',
    923             )
    924         ) );
    925 
    926         remove_action( 'upgrader_process_complete', 'wp_clean_plugins_cache', 9 );
    927         remove_filter('upgrader_source_selection', array($this, 'check_package') );
    928 
    929         if ( ! $this->result || is_wp_error($this->result) )
    930             return $this->result;
    931 
    932         // Force refresh of plugin update information
    933         wp_clean_plugins_cache( $parsed_args['clear_update_cache'] );
    934 
    935         return true;
    936     }
    937 
    938     /**
    939      * Upgrade a plugin.
    940      *
    941      * @since 2.8.0
    942      * @since 3.7.0 The `$args` parameter was added, making clearing the plugin update cache optional.
    943      * @access public
    944      *
    945      * @param string $plugin The basename path to the main plugin file.
    946      * @param array  $args {
    947      *     Optional. Other arguments for upgrading a plugin package. Default empty array.
    948      *
    949      *     @type bool $clear_update_cache Whether to clear the plugin updates cache if successful.
    950      *                                    Default true.
    951      * }
    952      * @return bool|WP_Error True if the upgrade was successful, false or a WP_Error object otherwise.
    953      */
    954     public function upgrade( $plugin, $args = array() ) {
    955 
    956         $defaults = array(
    957             'clear_update_cache' => true,
    958         );
    959         $parsed_args = wp_parse_args( $args, $defaults );
    960 
    961         $this->init();
    962         $this->upgrade_strings();
    963 
    964         $current = get_site_transient( 'update_plugins' );
    965         if ( !isset( $current->response[ $plugin ] ) ) {
    966             $this->skin->before();
    967             $this->skin->set_result(false);
    968             $this->skin->error('up_to_date');
    969             $this->skin->after();
    970             return false;
    971         }
    972 
    973         // Get the URL to the zip file
    974         $r = $current->response[ $plugin ];
    975 
    976         add_filter('upgrader_pre_install', array($this, 'deactivate_plugin_before_upgrade'), 10, 2);
    977         add_filter('upgrader_clear_destination', array($this, 'delete_old_plugin'), 10, 4);
    978         //'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.
    979         // Clear cache so wp_update_plugins() knows about the new plugin.
    980         add_action( 'upgrader_process_complete', 'wp_clean_plugins_cache', 9, 0 );
    981 
    982         $this->run( array(
    983             'package' => $r->package,
    984             'destination' => WP_PLUGIN_DIR,
    985             'clear_destination' => true,
    986             'clear_working' => true,
    987             'hook_extra' => array(
    988                 'plugin' => $plugin,
    989                 'type' => 'plugin',
    990                 'action' => 'update',
    991             ),
    992         ) );
    993 
    994         // Cleanup our hooks, in case something else does a upgrade on this connection.
    995         remove_action( 'upgrader_process_complete', 'wp_clean_plugins_cache', 9 );
    996         remove_filter('upgrader_pre_install', array($this, 'deactivate_plugin_before_upgrade'));
    997         remove_filter('upgrader_clear_destination', array($this, 'delete_old_plugin'));
    998 
    999         if ( ! $this->result || is_wp_error($this->result) )
    1000             return $this->result;
    1001 
    1002         // Force refresh of plugin update information
    1003         wp_clean_plugins_cache( $parsed_args['clear_update_cache'] );
    1004 
    1005         return true;
    1006     }
    1007 
    1008     /**
    1009      * Bulk upgrade several plugins at once.
    1010      *
    1011      * @since 2.8.0
    1012      * @since 3.7.0 The `$args` parameter was added, making clearing the plugin update cache optional.
    1013      * @access public
    1014      *
    1015      * @param array $plugins Array of the basename paths of the plugins' main files.
    1016      * @param array $args {
    1017      *     Optional. Other arguments for upgrading several plugins at once. Default empty array.
    1018      *
    1019      *     @type bool $clear_update_cache Whether to clear the plugin updates cache if successful.
    1020      *                                    Default true.
    1021      * }
    1022      * @return array|false An array of results indexed by plugin file, or false if unable to connect to the filesystem.
    1023      */
    1024     public function bulk_upgrade( $plugins, $args = array() ) {
    1025 
    1026         $defaults = array(
    1027             'clear_update_cache' => true,
    1028         );
    1029         $parsed_args = wp_parse_args( $args, $defaults );
    1030 
    1031         $this->init();
    1032         $this->bulk = true;
    1033         $this->upgrade_strings();
    1034 
    1035         $current = get_site_transient( 'update_plugins' );
    1036 
    1037         add_filter('upgrader_clear_destination', array($this, 'delete_old_plugin'), 10, 4);
    1038         add_action( 'upgrader_process_complete', 'wp_clean_plugins_cache', 9, 0 );
    1039 
    1040         $this->skin->header();
    1041 
    1042         // Connect to the Filesystem first.
    1043         $res = $this->fs_connect( array(WP_CONTENT_DIR, WP_PLUGIN_DIR) );
    1044         if ( ! $res ) {
    1045             $this->skin->footer();
    1046             return false;
    1047         }
    1048 
    1049         $this->skin->bulk_header();
    1050 
    1051         /*
    1052          * Only start maintenance mode if:
    1053          * - running Multisite and there are one or more plugins specified, OR
    1054          * - a plugin with an update available is currently active.
    1055          * @TODO: For multisite, maintenance mode should only kick in for individual sites if at all possible.
    1056          */
    1057         $maintenance = ( is_multisite() && ! empty( $plugins ) );
    1058         foreach ( $plugins as $plugin )
    1059             $maintenance = $maintenance || ( is_plugin_active( $plugin ) && isset( $current->response[ $plugin] ) );
    1060         if ( $maintenance )
    1061             $this->maintenance_mode(true);
    1062 
    1063         $results = array();
    1064 
    1065         $this->update_count = count($plugins);
    1066         $this->update_current = 0;
    1067         foreach ( $plugins as $plugin ) {
    1068             $this->update_current++;
    1069             $this->skin->plugin_info = get_plugin_data( WP_PLUGIN_DIR . '/' . $plugin, false, true);
    1070 
    1071             if ( !isset( $current->response[ $plugin ] ) ) {
    1072                 $this->skin->set_result('up_to_date');
    1073                 $this->skin->before();
    1074                 $this->skin->feedback('up_to_date');
    1075                 $this->skin->after();
    1076                 $results[$plugin] = true;
    1077                 continue;
    1078             }
    1079 
    1080             // Get the URL to the zip file.
    1081             $r = $current->response[ $plugin ];
    1082 
    1083             $this->skin->plugin_active = is_plugin_active($plugin);
    1084 
    1085             $result = $this->run( array(
    1086                 'package' => $r->package,
    1087                 'destination' => WP_PLUGIN_DIR,
    1088                 'clear_destination' => true,
    1089                 'clear_working' => true,
    1090                 'is_multi' => true,
    1091                 'hook_extra' => array(
    1092                     'plugin' => $plugin
    1093                 )
    1094             ) );
    1095 
    1096             $results[$plugin] = $this->result;
    1097 
    1098             // Prevent credentials auth screen from displaying multiple times
    1099             if ( false === $result )
    1100                 break;
    1101         } //end foreach $plugins
    1102 
    1103         $this->maintenance_mode(false);
    1104 
    1105         /**
    1106          * Fires when the bulk upgrader process is complete.
    1107          *
    1108          * @since 3.6.0
    1109          *
    1110          * @param Plugin_Upgrader $this Plugin_Upgrader instance. In other contexts, $this, might
    1111          *                              be a Theme_Upgrader or Core_Upgrade instance.
    1112          * @param array           $data {
    1113          *     Array of bulk item update data.
    1114          *
    1115          *     @type string $action   Type of action. Default 'update'.
    1116          *     @type string $type     Type of update process. Accepts 'plugin', 'theme', or 'core'.
    1117          *     @type bool   $bulk     Whether the update process is a bulk update. Default true.
    1118          *     @type array  $packages Array of plugin, theme, or core packages to update.
    1119          * }
    1120          */
    1121         do_action( 'upgrader_process_complete', $this, array(
    1122             'action' => 'update',
    1123             'type' => 'plugin',
    1124             'bulk' => true,
    1125             'plugins' => $plugins,
    1126         ) );
    1127 
    1128         $this->skin->bulk_footer();
    1129 
    1130         $this->skin->footer();
    1131 
    1132         // Cleanup our hooks, in case something else does a upgrade on this connection.
    1133         remove_action( 'upgrader_process_complete', 'wp_clean_plugins_cache', 9 );
    1134         remove_filter('upgrader_clear_destination', array($this, 'delete_old_plugin'));
    1135 
    1136         // Force refresh of plugin update information.
    1137         wp_clean_plugins_cache( $parsed_args['clear_update_cache'] );
    1138 
    1139         return $results;
    1140     }
    1141 
    1142     /**
    1143      * Check a source package to be sure it contains a plugin.
    1144      *
    1145      * This function is added to the {@see 'upgrader_source_selection'} filter by
    1146      * Plugin_Upgrader::install().
    1147      *
    1148      * @since 3.3.0
    1149      * @access public
    1150      *
    1151      * @global WP_Filesystem_Base $wp_filesystem Subclass
    1152      *
    1153      * @param string $source The path to the downloaded package source.
    1154      * @return string|WP_Error The source as passed, or a WP_Error object
    1155      *                         if no plugins were found.
    1156      */
    1157     public function check_package($source) {
    1158         global $wp_filesystem;
    1159 
    1160         if ( is_wp_error($source) )
    1161             return $source;
    1162 
    1163         $working_directory = str_replace( $wp_filesystem->wp_content_dir(), trailingslashit(WP_CONTENT_DIR), $source);
    1164         if ( ! is_dir($working_directory) ) // Sanity check, if the above fails, let's not prevent installation.
    1165             return $source;
    1166 
    1167         // Check the folder contains at least 1 valid plugin.
    1168         $plugins_found = false;
    1169         $files = glob( $working_directory . '*.php' );
    1170         if ( $files ) {
    1171             foreach ( $files as $file ) {
    1172                 $info = get_plugin_data( $file, false, false );
    1173                 if ( ! empty( $info['Name'] ) ) {
    1174                     $plugins_found = true;
    1175                     break;
    1176                 }
    1177             }
    1178         }
    1179 
    1180         if ( ! $plugins_found )
    1181             return new WP_Error( 'incompatible_archive_no_plugins', $this->strings['incompatible_archive'], __( 'No valid plugins were found.' ) );
    1182 
    1183         return $source;
    1184     }
    1185 
    1186     /**
    1187      * Retrieve the path to the file that contains the plugin info.
    1188      *
    1189      * This isn't used internally in the class, but is called by the skins.
    1190      *
    1191      * @since 2.8.0
    1192      * @access public
    1193      *
    1194      * @return string|false The full path to the main plugin file, or false.
    1195      */
    1196     public function plugin_info() {
    1197         if ( ! is_array($this->result) )
    1198             return false;
    1199         if ( empty($this->result['destination_name']) )
    1200             return false;
    1201 
    1202         $plugin = get_plugins('/' . $this->result['destination_name']); //Ensure to pass with leading slash
    1203         if ( empty($plugin) )
    1204             return false;
    1205 
    1206         $pluginfiles = array_keys($plugin); //Assume the requested plugin is the first in the list
    1207 
    1208         return $this->result['destination_name'] . '/' . $pluginfiles[0];
    1209     }
    1210 
    1211     /**
    1212      * Deactivates a plugin before it is upgraded.
    1213      *
    1214      * Hooked to the {@see 'upgrader_pre_install'} filter by Plugin_Upgrader::upgrade().
    1215      *
    1216      * @since 2.8.0
    1217      * @since 4.1.0 Added a return value.
    1218      * @access public
    1219      *
    1220      * @param bool|WP_Error  $return Upgrade offer return.
    1221      * @param array          $plugin Plugin package arguments.
    1222      * @return bool|WP_Error The passed in $return param or WP_Error.
    1223      */
    1224     public function deactivate_plugin_before_upgrade($return, $plugin) {
    1225 
    1226         if ( is_wp_error($return) ) //Bypass.
    1227             return $return;
    1228 
    1229         // When in cron (background updates) don't deactivate the plugin, as we require a browser to reactivate it
    1230         if ( defined( 'DOING_CRON' ) && DOING_CRON )
    1231             return $return;
    1232 
    1233         $plugin = isset($plugin['plugin']) ? $plugin['plugin'] : '';
    1234         if ( empty($plugin) )
    1235             return new WP_Error('bad_request', $this->strings['bad_request']);
    1236 
    1237         if ( is_plugin_active($plugin) ) {
    1238             //Deactivate the plugin silently, Prevent deactivation hooks from running.
    1239             deactivate_plugins($plugin, true);
    1240         }
    1241 
    1242         return $return;
    1243     }
    1244 
    1245     /**
    1246      * Delete the old plugin during an upgrade.
    1247      *
    1248      * Hooked to the {@see 'upgrader_clear_destination'} filter by
    1249      * Plugin_Upgrader::upgrade() and Plugin_Upgrader::bulk_upgrade().
    1250      *
    1251      * @since 2.8.0
    1252      * @access public
    1253      *
    1254      * @global WP_Filesystem_Base $wp_filesystem Subclass
    1255      *
    1256      * @param bool|WP_Error $removed
    1257      * @param string        $local_destination
    1258      * @param string        $remote_destination
    1259      * @param array         $plugin
    1260      * @return WP_Error|bool
    1261      */
    1262     public function delete_old_plugin($removed, $local_destination, $remote_destination, $plugin) {
    1263         global $wp_filesystem;
    1264 
    1265         if ( is_wp_error($removed) )
    1266             return $removed; //Pass errors through.
    1267 
    1268         $plugin = isset($plugin['plugin']) ? $plugin['plugin'] : '';
    1269         if ( empty($plugin) )
    1270             return new WP_Error('bad_request', $this->strings['bad_request']);
    1271 
    1272         $plugins_dir = $wp_filesystem->wp_plugins_dir();
    1273         $this_plugin_dir = trailingslashit( dirname($plugins_dir . $plugin) );
    1274 
    1275         if ( ! $wp_filesystem->exists($this_plugin_dir) ) //If it's already vanished.
    1276             return $removed;
    1277 
    1278         // If plugin is in its own directory, recursively delete the directory.
    1279         if ( strpos($plugin, '/') && $this_plugin_dir != $plugins_dir ) //base check on if plugin includes directory separator AND that it's not the root plugin folder
    1280             $deleted = $wp_filesystem->delete($this_plugin_dir, true);
    1281         else
    1282             $deleted = $wp_filesystem->delete($plugins_dir . $plugin);
    1283 
    1284         if ( ! $deleted )
    1285             return new WP_Error('remove_old_failed', $this->strings['remove_old_failed']);
    1286 
    1287         return true;
    1288     }
    1289 }
    1290 
    1291 /**
    1292  * Core class used for upgrading/installing themes.
    1293  *
    1294  * It is designed to upgrade/install themes from a local zip, remote zip URL,
    1295  * or uploaded zip file.
    1296  *
    1297  * @since 2.8.0
    1298  *
    1299  * @see WP_Upgrader
    1300  */
    1301 class Theme_Upgrader extends WP_Upgrader {
    1302 
    1303     /**
    1304      * Result of the theme upgrade offer.
    1305      *
    1306      * @since 2.8.0
    1307      * @access public
    1308      * @var array|WP_Error $result
    1309      * @see WP_Upgrader::$result
    1310      */
    1311     public $result;
    1312 
    1313     /**
    1314      * Whether multiple themes are being upgraded/installed in bulk.
    1315      *
    1316      * @since 2.9.0
    1317      * @access public
    1318      * @var bool $bulk
    1319      */
    1320     public $bulk = false;
    1321 
    1322     /**
    1323      * Initialize the upgrade strings.
    1324      *
    1325      * @since 2.8.0
    1326      * @access public
    1327      */
    1328     public function upgrade_strings() {
    1329         $this->strings['up_to_date'] = __('The theme is at the latest version.');
    1330         $this->strings['no_package'] = __('Update package not available.');
    1331         $this->strings['downloading_package'] = __('Downloading update from <span class="code">%s</span>&#8230;');
    1332         $this->strings['unpack_package'] = __('Unpacking the update&#8230;');
    1333         $this->strings['remove_old'] = __('Removing the old version of the theme&#8230;');
    1334         $this->strings['remove_old_failed'] = __('Could not remove the old theme.');
    1335         $this->strings['process_failed'] = __('Theme update failed.');
    1336         $this->strings['process_success'] = __('Theme updated successfully.');
    1337     }
    1338 
    1339     /**
    1340      * Initialize the install strings.
    1341      *
    1342      * @since 2.8.0
    1343      * @access public
    1344      */
    1345     public function install_strings() {
    1346         $this->strings['no_package'] = __('Install package not available.');
    1347         $this->strings['downloading_package'] = __('Downloading install package from <span class="code">%s</span>&#8230;');
    1348         $this->strings['unpack_package'] = __('Unpacking the package&#8230;');
    1349         $this->strings['installing_package'] = __('Installing the theme&#8230;');
    1350         $this->strings['no_files'] = __('The theme contains no files.');
    1351         $this->strings['process_failed'] = __('Theme install failed.');
    1352         $this->strings['process_success'] = __('Theme installed successfully.');
    1353         /* translators: 1: theme name, 2: version */
    1354         $this->strings['process_success_specific'] = __('Successfully installed the theme <strong>%1$s %2$s</strong>.');
    1355         $this->strings['parent_theme_search'] = __('This theme requires a parent theme. Checking if it is installed&#8230;');
    1356         /* translators: 1: theme name, 2: version */
    1357         $this->strings['parent_theme_prepare_install'] = __('Preparing to install <strong>%1$s %2$s</strong>&#8230;');
    1358         /* translators: 1: theme name, 2: version */
    1359         $this->strings['parent_theme_currently_installed'] = __('The parent theme, <strong>%1$s %2$s</strong>, is currently installed.');
    1360         /* translators: 1: theme name, 2: version */
    1361         $this->strings['parent_theme_install_success'] = __('Successfully installed the parent theme, <strong>%1$s %2$s</strong>.');
    1362         $this->strings['parent_theme_not_found'] = __('<strong>The parent theme could not be found.</strong> You will need to install the parent theme, <strong>%s</strong>, before you can use this child theme.');
    1363     }
    1364 
    1365     /**
    1366      * Check if a child theme is being installed and we need to install its parent.
    1367      *
    1368      * Hooked to the {@see 'upgrader_post_install'} filter by Theme_Upgrader::install().
    1369      *
    1370      * @since 3.4.0
    1371      * @access public
    1372      *
    1373      * @param bool  $install_result
    1374      * @param array $hook_extra
    1375      * @param array $child_result
    1376      * @return type
    1377      */
    1378     public function check_parent_theme_filter( $install_result, $hook_extra, $child_result ) {
    1379         // Check to see if we need to install a parent theme
    1380         $theme_info = $this->theme_info();
    1381 
    1382         if ( ! $theme_info->parent() )
    1383             return $install_result;
    1384 
    1385         $this->skin->feedback( 'parent_theme_search' );
    1386 
    1387         if ( ! $theme_info->parent()->errors() ) {
    1388             $this->skin->feedback( 'parent_theme_currently_installed', $theme_info->parent()->display('Name'), $theme_info->parent()->display('Version') );
    1389             // We already have the theme, fall through.
    1390             return $install_result;
    1391         }
    1392 
    1393         // We don't have the parent theme, let's install it.
    1394         $api = themes_api('theme_information', array('slug' => $theme_info->get('Template'), 'fields' => array('sections' => false, 'tags' => false) ) ); //Save on a bit of bandwidth.
    1395 
    1396         if ( ! $api || is_wp_error($api) ) {
    1397             $this->skin->feedback( 'parent_theme_not_found', $theme_info->get('Template') );
    1398             // Don't show activate or preview actions after install
    1399             add_filter('install_theme_complete_actions', array($this, 'hide_activate_preview_actions') );
    1400             return $install_result;
    1401         }
    1402 
    1403         // Backup required data we're going to override:
    1404         $child_api = $this->skin->api;
    1405         $child_success_message = $this->strings['process_success'];
    1406 
    1407         // Override them
    1408         $this->skin->api = $api;
    1409         $this->strings['process_success_specific'] = $this->strings['parent_theme_install_success'];//, $api->name, $api->version);
    1410 
    1411         $this->skin->feedback('parent_theme_prepare_install', $api->name, $api->version);
    1412 
    1413         add_filter('install_theme_complete_actions', '__return_false', 999); // Don't show any actions after installing the theme.
    1414 
    1415         // Install the parent theme
    1416         $parent_result = $this->run( array(
    1417             'package' => $api->download_link,
    1418             'destination' => get_theme_root(),
    1419             'clear_destination' => false, //Do not overwrite files.
    1420             'clear_working' => true
    1421         ) );
    1422 
    1423         if ( is_wp_error($parent_result) )
    1424             add_filter('install_theme_complete_actions', array($this, 'hide_activate_preview_actions') );
    1425 
    1426         // Start cleaning up after the parents installation
    1427         remove_filter('install_theme_complete_actions', '__return_false', 999);
    1428 
    1429         // Reset child's result and data
    1430         $this->result = $child_result;
    1431         $this->skin->api = $child_api;
    1432         $this->strings['process_success'] = $child_success_message;
    1433 
    1434         return $install_result;
    1435     }
    1436 
    1437     /**
    1438      * Don't display the activate and preview actions to the user.
    1439      *
    1440      * Hooked to the {@see 'install_theme_complete_actions'} filter by
    1441      * Theme_Upgrader::check_parent_theme_filter() when installing
    1442      * a child theme and installing the parent theme fails.
    1443      *
    1444      * @since 3.4.0
    1445      * @access public
    1446      *
    1447      * @param array $actions Preview actions.
    1448      * @return array
    1449      */
    1450     public function hide_activate_preview_actions( $actions ) {
    1451         unset($actions['activate'], $actions['preview']);
    1452         return $actions;
    1453     }
    1454 
    1455     /**
    1456      * Install a theme package.
    1457      *
    1458      * @since 2.8.0
    1459      * @since 3.7.0 The `$args` parameter was added, making clearing the update cache optional.
    1460      * @access public
    1461      *
    1462      * @param string $package The full local path or URI of the package.
    1463      * @param array  $args {
    1464      *     Optional. Other arguments for installing a theme package. Default empty array.
    1465      *
    1466      *     @type bool $clear_update_cache Whether to clear the updates cache if successful.
    1467      *                                    Default true.
    1468      * }
    1469      *
    1470      * @return bool|WP_Error True if the install was successful, false or a WP_Error object otherwise.
    1471      */
    1472     public function install( $package, $args = array() ) {
    1473 
    1474         $defaults = array(
    1475             'clear_update_cache' => true,
    1476         );
    1477         $parsed_args = wp_parse_args( $args, $defaults );
    1478 
    1479         $this->init();
    1480         $this->install_strings();
    1481 
    1482         add_filter('upgrader_source_selection', array($this, 'check_package') );
    1483         add_filter('upgrader_post_install', array($this, 'check_parent_theme_filter'), 10, 3);
    1484         // Clear cache so wp_update_themes() knows about the new theme.
    1485         add_action( 'upgrader_process_complete', 'wp_clean_themes_cache', 9, 0 );
    1486 
    1487         $this->run( array(
    1488             'package' => $package,
    1489             'destination' => get_theme_root(),
    1490             'clear_destination' => false, //Do not overwrite files.
    1491             'clear_working' => true,
    1492             'hook_extra' => array(
    1493                 'type' => 'theme',
    1494                 'action' => 'install',
    1495             ),
    1496         ) );
    1497 
    1498         remove_action( 'upgrader_process_complete', 'wp_clean_themes_cache', 9 );
    1499         remove_filter('upgrader_source_selection', array($this, 'check_package') );
    1500         remove_filter('upgrader_post_install', array($this, 'check_parent_theme_filter'));
    1501 
    1502         if ( ! $this->result || is_wp_error($this->result) )
    1503             return $this->result;
    1504 
    1505         // Refresh the Theme Update information
    1506         wp_clean_themes_cache( $parsed_args['clear_update_cache'] );
    1507 
    1508         return true;
    1509     }
    1510 
    1511     /**
    1512      * Upgrade a theme.
    1513      *
    1514      * @since 2.8.0
    1515      * @since 3.7.0 The `$args` parameter was added, making clearing the update cache optional.
    1516      * @access public
    1517      *
    1518      * @param string $theme The theme slug.
    1519      * @param array  $args {
    1520      *     Optional. Other arguments for upgrading a theme. Default empty array.
    1521      *
    1522      *     @type bool $clear_update_cache Whether to clear the update cache if successful.
    1523      *                                    Default true.
    1524      * }
    1525      * @return bool|WP_Error True if the upgrade was successful, false or a WP_Error object otherwise.
    1526      */
    1527     public function upgrade( $theme, $args = array() ) {
    1528 
    1529         $defaults = array(
    1530             'clear_update_cache' => true,
    1531         );
    1532         $parsed_args = wp_parse_args( $args, $defaults );
    1533 
    1534         $this->init();
    1535         $this->upgrade_strings();
    1536 
    1537         // Is an update available?
    1538         $current = get_site_transient( 'update_themes' );
    1539         if ( !isset( $current->response[ $theme ] ) ) {
    1540             $this->skin->before();
    1541             $this->skin->set_result(false);
    1542             $this->skin->error( 'up_to_date' );
    1543             $this->skin->after();
    1544             return false;
    1545         }
    1546 
    1547         $r = $current->response[ $theme ];
    1548 
    1549         add_filter('upgrader_pre_install', array($this, 'current_before'), 10, 2);
    1550         add_filter('upgrader_post_install', array($this, 'current_after'), 10, 2);
    1551         add_filter('upgrader_clear_destination', array($this, 'delete_old_theme'), 10, 4);
    1552         // Clear cache so wp_update_themes() knows about the new theme.
    1553         add_action( 'upgrader_process_complete', 'wp_clean_themes_cache', 9, 0 );
    1554 
    1555         $this->run( array(
    1556             'package' => $r['package'],
    1557             'destination' => get_theme_root( $theme ),
    1558             'clear_destination' => true,
    1559             'clear_working' => true,
    1560             'hook_extra' => array(
    1561                 'theme' => $theme,
    1562                 'type' => 'theme',
    1563                 'action' => 'update',
    1564             ),
    1565         ) );
    1566 
    1567         remove_action( 'upgrader_process_complete', 'wp_clean_themes_cache', 9 );
    1568         remove_filter('upgrader_pre_install', array($this, 'current_before'));
    1569         remove_filter('upgrader_post_install', array($this, 'current_after'));
    1570         remove_filter('upgrader_clear_destination', array($this, 'delete_old_theme'));
    1571 
    1572         if ( ! $this->result || is_wp_error($this->result) )
    1573             return $this->result;
    1574 
    1575         wp_clean_themes_cache( $parsed_args['clear_update_cache'] );
    1576 
    1577         return true;
    1578     }
    1579 
    1580     /**
    1581      * Upgrade several themes at once.
    1582      *
    1583      * @since 3.0.0
    1584      * @since 3.7.0 The `$args` parameter was added, making clearing the update cache optional.
    1585      * @access public
    1586      *
    1587      * @param array $themes The theme slugs.
    1588      * @param array $args {
    1589      *     Optional. Other arguments for upgrading several themes at once. Default empty array.
    1590      *
    1591      *     @type bool $clear_update_cache Whether to clear the update cache if successful.
    1592      *                                    Default true.
    1593      * }
    1594      * @return array[]|false An array of results, or false if unable to connect to the filesystem.
    1595      */
    1596     public function bulk_upgrade( $themes, $args = array() ) {
    1597 
    1598         $defaults = array(
    1599             'clear_update_cache' => true,
    1600         );
    1601         $parsed_args = wp_parse_args( $args, $defaults );
    1602 
    1603         $this->init();
    1604         $this->bulk = true;
    1605         $this->upgrade_strings();
    1606 
    1607         $current = get_site_transient( 'update_themes' );
    1608 
    1609         add_filter('upgrader_pre_install', array($this, 'current_before'), 10, 2);
    1610         add_filter('upgrader_post_install', array($this, 'current_after'), 10, 2);
    1611         add_filter('upgrader_clear_destination', array($this, 'delete_old_theme'), 10, 4);
    1612         // Clear cache so wp_update_themes() knows about the new theme.
    1613         add_action( 'upgrader_process_complete', 'wp_clean_themes_cache', 9, 0 );
    1614 
    1615         $this->skin->header();
    1616 
    1617         // Connect to the Filesystem first.
    1618         $res = $this->fs_connect( array(WP_CONTENT_DIR) );
    1619         if ( ! $res ) {
    1620             $this->skin->footer();
    1621             return false;
    1622         }
    1623 
    1624         $this->skin->bulk_header();
    1625 
    1626         // Only start maintenance mode if:
    1627         // - running Multisite and there are one or more themes specified, OR
    1628         // - a theme with an update available is currently in use.
    1629         // @TODO: For multisite, maintenance mode should only kick in for individual sites if at all possible.
    1630         $maintenance = ( is_multisite() && ! empty( $themes ) );
    1631         foreach ( $themes as $theme )
    1632             $maintenance = $maintenance || $theme == get_stylesheet() || $theme == get_template();
    1633         if ( $maintenance )
    1634             $this->maintenance_mode(true);
    1635 
    1636         $results = array();
    1637 
    1638         $this->update_count = count($themes);
    1639         $this->update_current = 0;
    1640         foreach ( $themes as $theme ) {
    1641             $this->update_current++;
    1642 
    1643             $this->skin->theme_info = $this->theme_info($theme);
    1644 
    1645             if ( !isset( $current->response[ $theme ] ) ) {
    1646                 $this->skin->set_result(true);
    1647                 $this->skin->before();
    1648                 $this->skin->feedback( 'up_to_date' );
    1649                 $this->skin->after();
    1650                 $results[$theme] = true;
    1651                 continue;
    1652             }
    1653 
    1654             // Get the URL to the zip file
    1655             $r = $current->response[ $theme ];
    1656 
    1657             $result = $this->run( array(
    1658                 'package' => $r['package'],
    1659                 'destination' => get_theme_root( $theme ),
    1660                 'clear_destination' => true,
    1661                 'clear_working' => true,
    1662                 'is_multi' => true,
    1663                 'hook_extra' => array(
    1664                     'theme' => $theme
    1665                 ),
    1666             ) );
    1667 
    1668             $results[$theme] = $this->result;
    1669 
    1670             // Prevent credentials auth screen from displaying multiple times
    1671             if ( false === $result )
    1672                 break;
    1673         } //end foreach $plugins
    1674 
    1675         $this->maintenance_mode(false);
    1676 
    1677         /** This action is documented in wp-admin/includes/class-wp-upgrader.php */
    1678         do_action( 'upgrader_process_complete', $this, array(
    1679             'action' => 'update',
    1680             'type' => 'theme',
    1681             'bulk' => true,
    1682             'themes' => $themes,
    1683         ) );
    1684 
    1685         $this->skin->bulk_footer();
    1686 
    1687         $this->skin->footer();
    1688 
    1689         // Cleanup our hooks, in case something else does a upgrade on this connection.
    1690         remove_action( 'upgrader_process_complete', 'wp_clean_themes_cache', 9 );
    1691         remove_filter('upgrader_pre_install', array($this, 'current_before'));
    1692         remove_filter('upgrader_post_install', array($this, 'current_after'));
    1693         remove_filter('upgrader_clear_destination', array($this, 'delete_old_theme'));
    1694 
    1695         // Refresh the Theme Update information
    1696         wp_clean_themes_cache( $parsed_args['clear_update_cache'] );
    1697 
    1698         return $results;
    1699     }
    1700 
    1701     /**
    1702      * Check that the package source contains a valid theme.
    1703      *
    1704      * Hooked to the {@see 'upgrader_source_selection'} filter by Theme_Upgrader::install().
    1705      * It will return an error if the theme doesn't have style.css or index.php
    1706      * files.
    1707      *
    1708      * @since 3.3.0
    1709      * @access public
    1710      *
    1711      * @global WP_Filesystem_Base $wp_filesystem Subclass
    1712      *
    1713      * @param string $source The full path to the package source.
    1714      * @return string|WP_Error The source or a WP_Error.
    1715      */
    1716     public function check_package( $source ) {
    1717         global $wp_filesystem;
    1718 
    1719         if ( is_wp_error($source) )
    1720             return $source;
    1721 
    1722         // Check the folder contains a valid theme
    1723         $working_directory = str_replace( $wp_filesystem->wp_content_dir(), trailingslashit(WP_CONTENT_DIR), $source);
    1724         if ( ! is_dir($working_directory) ) // Sanity check, if the above fails, let's not prevent installation.
    1725             return $source;
    1726 
    1727         // A proper archive should have a style.css file in the single subdirectory
    1728         if ( ! file_exists( $working_directory . 'style.css' ) ) {
    1729             return new WP_Error( 'incompatible_archive_theme_no_style', $this->strings['incompatible_archive'],
    1730                 /* translators: %s: style.css */
    1731                 sprintf( __( 'The theme is missing the %s stylesheet.' ),
    1732                     '<code>style.css</code>'
    1733                 )
    1734             );
    1735         }
    1736 
    1737         $info = get_file_data( $working_directory . 'style.css', array( 'Name' => 'Theme Name', 'Template' => 'Template' ) );
    1738 
    1739         if ( empty( $info['Name'] ) ) {
    1740             return new WP_Error( 'incompatible_archive_theme_no_name', $this->strings['incompatible_archive'],
    1741                 /* translators: %s: style.css */
    1742                 sprintf( __( 'The %s stylesheet doesn&#8217;t contain a valid theme header.' ),
    1743                     '<code>style.css</code>'
    1744                 )
    1745             );
    1746         }
    1747 
    1748         // If it's not a child theme, it must have at least an index.php to be legit.
    1749         if ( empty( $info['Template'] ) && ! file_exists( $working_directory . 'index.php' ) ) {
    1750             return new WP_Error( 'incompatible_archive_theme_no_index', $this->strings['incompatible_archive'],
    1751                 /* translators: %s: index.php */
    1752                 sprintf( __( 'The theme is missing the %s file.' ),
    1753                     '<code>index.php</code>'
    1754                 )
    1755             );
    1756         }
    1757 
    1758         return $source;
    1759     }
    1760 
    1761     /**
    1762      * Turn on maintenance mode before attempting to upgrade the current theme.
    1763      *
    1764      * Hooked to the {@see 'upgrader_pre_install'} filter by Theme_Upgrader::upgrade() and
    1765      * Theme_Upgrader::bulk_upgrade().
    1766      *
    1767      * @since 2.8.0
    1768      * @access public
    1769      *
    1770      * @param bool|WP_Error  $return
    1771      * @param array          $theme
    1772      * @return bool|WP_Error
    1773      */
    1774     public function current_before($return, $theme) {
    1775         if ( is_wp_error($return) )
    1776             return $return;
    1777 
    1778         $theme = isset($theme['theme']) ? $theme['theme'] : '';
    1779 
    1780         if ( $theme != get_stylesheet() ) //If not current
    1781             return $return;
    1782         //Change to maintenance mode now.
    1783         if ( ! $this->bulk )
    1784             $this->maintenance_mode(true);
    1785 
    1786         return $return;
    1787     }
    1788 
    1789     /**
    1790      * Turn off maintenance mode after upgrading the current theme.
    1791      *
    1792      * Hooked to the {@see 'upgrader_post_install'} filter by Theme_Upgrader::upgrade()
    1793      * and Theme_Upgrader::bulk_upgrade().
    1794      *
    1795      * @since 2.8.0
    1796      * @access public
    1797      *
    1798      * @param bool|WP_Error  $return
    1799      * @param array          $theme
    1800      * @return bool|WP_Error
    1801      */
    1802     public function current_after($return, $theme) {
    1803         if ( is_wp_error($return) )
    1804             return $return;
    1805 
    1806         $theme = isset($theme['theme']) ? $theme['theme'] : '';
    1807 
    1808         if ( $theme != get_stylesheet() ) // If not current
    1809             return $return;
    1810 
    1811         // Ensure stylesheet name hasn't changed after the upgrade:
    1812         if ( $theme == get_stylesheet() && $theme != $this->result['destination_name'] ) {
    1813             wp_clean_themes_cache();
    1814             $stylesheet = $this->result['destination_name'];
    1815             switch_theme( $stylesheet );
    1816         }
    1817 
    1818         //Time to remove maintenance mode
    1819         if ( ! $this->bulk )
    1820             $this->maintenance_mode(false);
    1821         return $return;
    1822     }
    1823 
    1824     /**
    1825      * Delete the old theme during an upgrade.
    1826      *
    1827      * Hooked to the {@see 'upgrader_clear_destination'} filter by Theme_Upgrader::upgrade()
    1828      * and Theme_Upgrader::bulk_upgrade().
    1829      *
    1830      * @since 2.8.0
    1831      * @access public
    1832      *
    1833      * @global WP_Filesystem_Base $wp_filesystem Subclass
    1834      *
    1835      * @param bool   $removed
    1836      * @param string $local_destination
    1837      * @param string $remote_destination
    1838      * @param array  $theme
    1839      * @return bool
    1840      */
    1841     public function delete_old_theme( $removed, $local_destination, $remote_destination, $theme ) {
    1842         global $wp_filesystem;
    1843 
    1844         if ( is_wp_error( $removed ) )
    1845             return $removed; // Pass errors through.
    1846 
    1847         if ( ! isset( $theme['theme'] ) )
    1848             return $removed;
    1849 
    1850         $theme = $theme['theme'];
    1851         $themes_dir = trailingslashit( $wp_filesystem->wp_themes_dir( $theme ) );
    1852         if ( $wp_filesystem->exists( $themes_dir . $theme ) ) {
    1853             if ( ! $wp_filesystem->delete( $themes_dir . $theme, true ) )
    1854                 return false;
    1855         }
    1856 
    1857         return true;
    1858     }
    1859 
    1860     /**
    1861      * Get the WP_Theme object for a theme.
    1862      *
    1863      * @since 2.8.0
    1864      * @since 3.0.0 The `$theme` argument was added.
    1865      * @access public
    1866      *
    1867      * @param string $theme The directory name of the theme. This is optional, and if not supplied,
    1868      *                      the directory name from the last result will be used.
    1869      * @return WP_Theme|false The theme's info object, or false `$theme` is not supplied
    1870      *                        and the last result isn't set.
    1871      */
    1872     public function theme_info($theme = null) {
    1873 
    1874         if ( empty($theme) ) {
    1875             if ( !empty($this->result['destination_name']) )
    1876                 $theme = $this->result['destination_name'];
    1877             else
    1878                 return false;
    1879         }
    1880         return wp_get_theme( $theme );
    1881     }
    1882 
    1883 }
    1884 
    1885 /**
    1886  * Core class used for updating/installing language packs (translations)
    1887  * for plugins, themes, and core.
    1888  *
    1889  * @since 3.7.0
    1890  *
    1891  * @see WP_Upgrader
    1892  */
    1893 class Language_Pack_Upgrader extends WP_Upgrader {
    1894 
    1895     /**
    1896      * Result of the language pack upgrade.
    1897      *
    1898      * @since 3.7.0
    1899      * @access public
    1900      * @var array|WP_Error $result
    1901      * @see WP_Upgrader::$result
    1902      */
    1903     public $result;
    1904 
    1905     /**
    1906      * Whether a bulk upgrade/install is being performed.
    1907      *
    1908      * @since 3.7.0
    1909      * @access public
    1910      * @var bool $bulk
    1911      */
    1912     public $bulk = true;
    1913 
    1914     /**
    1915      * Asynchronously upgrades language packs after other upgrades have been made.
    1916      *
    1917      * Hooked to the {@see 'upgrader_process_complete'} action by default.
    1918      *
    1919      * @since 3.7.0
    1920      * @access public
    1921      * @static
    1922      *
    1923      * @param false|WP_Upgrader $upgrader Optional. WP_Upgrader instance or false. If `$upgrader` is
    1924      *                                    a Language_Pack_Upgrader instance, the method will bail to
    1925      *                                    avoid recursion. Otherwise unused. Default false.
    1926      */
    1927     public static function async_upgrade( $upgrader = false ) {
    1928         // Avoid recursion.
    1929         if ( $upgrader && $upgrader instanceof Language_Pack_Upgrader ) {
    1930             return;
    1931         }
    1932 
    1933         // Nothing to do?
    1934         $language_updates = wp_get_translation_updates();
    1935         if ( ! $language_updates ) {
    1936             return;
    1937         }
    1938 
    1939         /*
    1940          * Avoid messing with VCS installs, at least for now.
    1941          * Noted: this is not the ideal way to accomplish this.
    1942          */
    1943         $check_vcs = new WP_Automatic_Updater;
    1944         if ( $check_vcs->is_vcs_checkout( WP_CONTENT_DIR ) ) {
    1945             return;
    1946         }
    1947 
    1948         foreach ( $language_updates as $key => $language_update ) {
    1949             $update = ! empty( $language_update->autoupdate );
    1950 
    1951             /**
    1952              * Filter whether to asynchronously update translation for core, a plugin, or a theme.
    1953              *
    1954              * @since 4.0.0
    1955              *
    1956              * @param bool   $update          Whether to update.
    1957              * @param object $language_update The update offer.
    1958              */
    1959             $update = apply_filters( 'async_update_translation', $update, $language_update );
    1960 
    1961             if ( ! $update ) {
    1962                 unset( $language_updates[ $key ] );
    1963             }
    1964         }
    1965 
    1966         if ( empty( $language_updates ) ) {
    1967             return;
    1968         }
    1969 
    1970         // Re-use the automatic upgrader skin if the parent upgrader is using it.
    1971         if ( $upgrader && $upgrader->skin instanceof Automatic_Upgrader_Skin ) {
    1972             $skin = $upgrader->skin;
    1973         } else {
    1974             $skin = new Language_Pack_Upgrader_Skin( array(
    1975                 'skip_header_footer' => true,
    1976             ) );
    1977         }
    1978 
    1979         $lp_upgrader = new Language_Pack_Upgrader( $skin );
    1980         $lp_upgrader->bulk_upgrade( $language_updates );
    1981     }
    1982 
    1983     /**
    1984      * Initialize the upgrade strings.
    1985      *
    1986      * @since 3.7.0
    1987      * @access public
    1988      */
    1989     public function upgrade_strings() {
    1990         $this->strings['starting_upgrade'] = __( 'Some of your translations need updating. Sit tight for a few more seconds while we update them as well.' );
    1991         $this->strings['up_to_date'] = __( 'The translation is up to date.' ); // We need to silently skip this case
    1992         $this->strings['no_package'] = __( 'Update package not available.' );
    1993         $this->strings['downloading_package'] = __( 'Downloading translation from <span class="code">%s</span>&#8230;' );
    1994         $this->strings['unpack_package'] = __( 'Unpacking the update&#8230;' );
    1995         $this->strings['process_failed'] = __( 'Translation update failed.' );
    1996         $this->strings['process_success'] = __( 'Translation updated successfully.' );
    1997     }
    1998 
    1999     /**
    2000      * Upgrade a language pack.
    2001      *
    2002      * @since 3.7.0
    2003      * @access public
    2004      *
    2005      * @param string|false $update Optional. Whether an update offer is available. Default false.
    2006      * @param array        $args   Optional. Other optional arguments, see
    2007      *                             Language_Pack_Upgrader::bulk_upgrade(). Default empty array.
    2008      * @return array|bool|WP_Error The result of the upgrade, or a WP_Error object instead.
    2009      */
    2010     public function upgrade( $update = false, $args = array() ) {
    2011         if ( $update ) {
    2012             $update = array( $update );
    2013         }
    2014 
    2015         $results = $this->bulk_upgrade( $update, $args );
    2016 
    2017         if ( ! is_array( $results ) ) {
    2018             return $results;
    2019         }
    2020 
    2021         return $results[0];
    2022     }
    2023 
    2024     /**
    2025      * Bulk upgrade language packs.
    2026      *
    2027      * @since 3.7.0
    2028      * @access public
    2029      *
    2030      * @global WP_Filesystem_Base $wp_filesystem Subclass
    2031      *
    2032      * @param array $language_updates Optional. Language pack updates. Default empty array.
    2033      * @param array $args {
    2034      *     Optional. Other arguments for upgrading multiple language packs. Default empty array
    2035      *
    2036      *     @type bool $clear_update_cache Whether to clear the update cache when done.
    2037      *                                    Default true.
    2038      * }
    2039      * @return array|bool|WP_Error Will return an array of results, or true if there are no updates,
    2040      *                                   false or WP_Error for initial errors.
    2041      */
    2042     public function bulk_upgrade( $language_updates = array(), $args = array() ) {
    2043         global $wp_filesystem;
    2044 
    2045         $defaults = array(
    2046             'clear_update_cache' => true,
    2047         );
    2048         $parsed_args = wp_parse_args( $args, $defaults );
    2049 
    2050         $this->init();
    2051         $this->upgrade_strings();
    2052 
    2053         if ( ! $language_updates )
    2054             $language_updates = wp_get_translation_updates();
    2055 
    2056         if ( empty( $language_updates ) ) {
    2057             $this->skin->header();
    2058             $this->skin->before();
    2059             $this->skin->set_result( true );
    2060             $this->skin->feedback( 'up_to_date' );
    2061             $this->skin->after();
    2062             $this->skin->bulk_footer();
    2063             $this->skin->footer();
    2064             return true;
    2065         }
    2066 
    2067         if ( 'upgrader_process_complete' == current_filter() )
    2068             $this->skin->feedback( 'starting_upgrade' );
    2069 
    2070         // Remove any existing upgrade filters from the plugin/theme upgraders #WP29425 & #WP29230
    2071         remove_all_filters( 'upgrader_pre_install' );
    2072         remove_all_filters( 'upgrader_clear_destination' );
    2073         remove_all_filters( 'upgrader_post_install' );
    2074         remove_all_filters( 'upgrader_source_selection' );
    2075 
    2076         add_filter( 'upgrader_source_selection', array( $this, 'check_package' ), 10, 2 );
    2077 
    2078         $this->skin->header();
    2079 
    2080         // Connect to the Filesystem first.
    2081         $res = $this->fs_connect( array( WP_CONTENT_DIR, WP_LANG_DIR ) );
    2082         if ( ! $res ) {
    2083             $this->skin->footer();
    2084             return false;
    2085         }
    2086 
    2087         $results = array();
    2088 
    2089         $this->update_count = count( $language_updates );
    2090         $this->update_current = 0;
    2091 
    2092         /*
    2093          * The filesystem's mkdir() is not recursive. Make sure WP_LANG_DIR exists,
    2094          * as we then may need to create a /plugins or /themes directory inside of it.
    2095          */
    2096         $remote_destination = $wp_filesystem->find_folder( WP_LANG_DIR );
    2097         if ( ! $wp_filesystem->exists( $remote_destination ) )
    2098             if ( ! $wp_filesystem->mkdir( $remote_destination, FS_CHMOD_DIR ) )
    2099                 return new WP_Error( 'mkdir_failed_lang_dir', $this->strings['mkdir_failed'], $remote_destination );
    2100 
    2101         foreach ( $language_updates as $language_update ) {
    2102 
    2103             $this->skin->language_update = $language_update;
    2104 
    2105             $destination = WP_LANG_DIR;
    2106             if ( 'plugin' == $language_update->type )
    2107                 $destination .= '/plugins';
    2108             elseif ( 'theme' == $language_update->type )
    2109                 $destination .= '/themes';
    2110 
    2111             $this->update_current++;
    2112 
    2113             $options = array(
    2114                 'package' => $language_update->package,
    2115                 'destination' => $destination,
    2116                 'clear_destination' => false,
    2117                 'abort_if_destination_exists' => false, // We expect the destination to exist.
    2118                 'clear_working' => true,
    2119                 'is_multi' => true,
    2120                 'hook_extra' => array(
    2121                     'language_update_type' => $language_update->type,
    2122                     'language_update' => $language_update,
    2123                 )
    2124             );
    2125 
    2126             $result = $this->run( $options );
    2127 
    2128             $results[] = $this->result;
    2129 
    2130             // Prevent credentials auth screen from displaying multiple times.
    2131             if ( false === $result )
    2132                 break;
    2133         }
    2134 
    2135         $this->skin->bulk_footer();
    2136 
    2137         $this->skin->footer();
    2138 
    2139         // Clean up our hooks, in case something else does an upgrade on this connection.
    2140         remove_filter( 'upgrader_source_selection', array( $this, 'check_package' ) );
    2141 
    2142         if ( $parsed_args['clear_update_cache'] ) {
    2143             wp_clean_update_cache();
    2144         }
    2145 
    2146         return $results;
    2147     }
    2148 
    2149     /**
    2150      * Check the package source to make sure there are .mo and .po files.
    2151      *
    2152      * Hooked to the {@see 'upgrader_source_selection'} filter by
    2153      * Language_Pack_Upgrader::bulk_upgrade().
    2154      *
    2155      * @since 3.7.0
    2156      * @access public
    2157      *
    2158      * @global WP_Filesystem_Base $wp_filesystem Subclass
    2159      *
    2160      * @param string|WP_Error $source
    2161      * @param string          $remote_source
    2162      */
    2163     public function check_package( $source, $remote_source ) {
    2164         global $wp_filesystem;
    2165 
    2166         if ( is_wp_error( $source ) )
    2167             return $source;
    2168 
    2169         // Check that the folder contains a valid language.
    2170         $files = $wp_filesystem->dirlist( $remote_source );
    2171 
    2172         // Check to see if a .po and .mo exist in the folder.
    2173         $po = $mo = false;
    2174         foreach ( (array) $files as $file => $filedata ) {
    2175             if ( '.po' == substr( $file, -3 ) )
    2176                 $po = true;
    2177             elseif ( '.mo' == substr( $file, -3 ) )
    2178                 $mo = true;
    2179         }
    2180 
    2181         if ( ! $mo || ! $po ) {
    2182             return new WP_Error( 'incompatible_archive_pomo', $this->strings['incompatible_archive'],
    2183                 /* translators: 1: .po 2: .mo */
    2184                 sprintf( __( 'The language pack is missing either the %1$s or %2$s files.' ),
    2185                     '<code>.po</code>',
    2186                     '<code>.mo</code>'
    2187                 )
    2188             );
    2189         }
    2190 
    2191         return $source;
    2192     }
    2193 
    2194     /**
    2195      * Get the name of an item being updated.
    2196      *
    2197      * @since 3.7.0
    2198      * @access public
    2199      *
    2200      * @param object $update The data for an update.
    2201      * @return string The name of the item being updated.
    2202      */
    2203     public function get_name_for_update( $update ) {
    2204         switch ( $update->type ) {
    2205             case 'core':
    2206                 return 'WordPress'; // Not translated
    2207 
    2208             case 'theme':
    2209                 $theme = wp_get_theme( $update->slug );
    2210                 if ( $theme->exists() )
    2211                     return $theme->Get( 'Name' );
    2212                 break;
    2213             case 'plugin':
    2214                 $plugin_data = get_plugins( '/' . $update->slug );
    2215                 $plugin_data = reset( $plugin_data );
    2216                 if ( $plugin_data )
    2217                     return $plugin_data['Name'];
    2218                 break;
    2219         }
    2220         return '';
    2221     }
    2222 
    2223 }
    222413
    222514/**
     
    2578367    }
    2579368}
    2580 
    2581 /**
    2582  * Core class used for handling file uploads.
    2583  *
    2584  * This class handles the upload process and passes it as if it's a local file
    2585  * to the Upgrade/Installer functions.
    2586  *
    2587  * @since 2.8.0
    2588  */
    2589 class File_Upload_Upgrader {
    2590 
    2591     /**
    2592      * The full path to the file package.
    2593      *
    2594      * @since 2.8.0
    2595      * @access public
    2596      * @var string $package
    2597      */
    2598     public $package;
    2599 
    2600     /**
    2601      * The name of the file.
    2602      *
    2603      * @since 2.8.0
    2604      * @access public
    2605      * @var string $filename
    2606      */
    2607     public $filename;
    2608 
    2609     /**
    2610      * The ID of the attachment post for this file.
    2611      *
    2612      * @since 3.3.0
    2613      * @access public
    2614      * @var int $id
    2615      */
    2616     public $id = 0;
    2617 
    2618     /**
    2619      * Construct the upgrader for a form.
    2620      *
    2621      * @since 2.8.0
    2622      * @access public
    2623      *
    2624      * @param string $form      The name of the form the file was uploaded from.
    2625      * @param string $urlholder The name of the `GET` parameter that holds the filename.
    2626      */
    2627     public function __construct( $form, $urlholder ) {
    2628 
    2629         if ( empty($_FILES[$form]['name']) && empty($_GET[$urlholder]) )
    2630             wp_die(__('Please select a file'));
    2631 
    2632         //Handle a newly uploaded file, Else assume it's already been uploaded
    2633         if ( ! empty($_FILES) ) {
    2634             $overrides = array( 'test_form' => false, 'test_type' => false );
    2635             $file = wp_handle_upload( $_FILES[$form], $overrides );
    2636 
    2637             if ( isset( $file['error'] ) )
    2638                 wp_die( $file['error'] );
    2639 
    2640             $this->filename = $_FILES[$form]['name'];
    2641             $this->package = $file['file'];
    2642 
    2643             // Construct the object array
    2644             $object = array(
    2645                 'post_title' => $this->filename,
    2646                 'post_content' => $file['url'],
    2647                 'post_mime_type' => $file['type'],
    2648                 'guid' => $file['url'],
    2649                 'context' => 'upgrader',
    2650                 'post_status' => 'private'
    2651             );
    2652 
    2653             // Save the data.
    2654             $this->id = wp_insert_attachment( $object, $file['file'] );
    2655 
    2656             // Schedule a cleanup for 2 hours from now in case of failed install.
    2657             wp_schedule_single_event( time() + 2 * HOUR_IN_SECONDS, 'upgrader_scheduled_cleanup', array( $this->id ) );
    2658 
    2659         } elseif ( is_numeric( $_GET[$urlholder] ) ) {
    2660             // Numeric Package = previously uploaded file, see above.
    2661             $this->id = (int) $_GET[$urlholder];
    2662             $attachment = get_post( $this->id );
    2663             if ( empty($attachment) )
    2664                 wp_die(__('Please select a file'));
    2665 
    2666             $this->filename = $attachment->post_title;
    2667             $this->package = get_attached_file( $attachment->ID );
    2668         } else {
    2669             // Else, It's set to something, Back compat for plugins using the old (pre-3.3) File_Uploader handler.
    2670             if ( ! ( ( $uploads = wp_upload_dir() ) && false === $uploads['error'] ) )
    2671                 wp_die( $uploads['error'] );
    2672 
    2673             $this->filename = $_GET[$urlholder];
    2674             $this->package = $uploads['basedir'] . '/' . $this->filename;
    2675         }
    2676     }
    2677 
    2678     /**
    2679      * Delete the attachment/uploaded file.
    2680      *
    2681      * @since 3.2.2
    2682      * @access public
    2683      *
    2684      * @return bool Whether the cleanup was successful.
    2685      */
    2686     public function cleanup() {
    2687         if ( $this->id )
    2688             wp_delete_attachment( $this->id );
    2689 
    2690         elseif ( file_exists( $this->package ) )
    2691             return @unlink( $this->package );
    2692 
    2693         return true;
    2694     }
    2695 }
    2696 
    2697 /**
    2698  * Core class used for handling automatic background updates.
    2699  *
    2700  * @since 3.7.0
    2701  */
    2702 class WP_Automatic_Updater {
    2703 
    2704     /**
    2705      * Tracks update results during processing.
    2706      *
    2707      * @var array
    2708      * @access protected
    2709      */
    2710     protected $update_results = array();
    2711 
    2712     /**
    2713      * Whether the entire automatic updater is disabled.
    2714      *
    2715      * @since 3.7.0
    2716      * @access public
    2717      */
    2718     public function is_disabled() {
    2719         // Background updates are disabled if you don't want file changes.
    2720         if ( defined( 'DISALLOW_FILE_MODS' ) && DISALLOW_FILE_MODS )
    2721             return true;
    2722 
    2723         if ( wp_installing() )
    2724             return true;
    2725 
    2726         // More fine grained control can be done through the WP_AUTO_UPDATE_CORE constant and filters.
    2727         $disabled = defined( 'AUTOMATIC_UPDATER_DISABLED' ) && AUTOMATIC_UPDATER_DISABLED;
    2728 
    2729         /**
    2730          * Filter whether to entirely disable background updates.
    2731          *
    2732          * There are more fine-grained filters and controls for selective disabling.
    2733          * This filter parallels the AUTOMATIC_UPDATER_DISABLED constant in name.
    2734          *
    2735          * This also disables update notification emails. That may change in the future.
    2736          *
    2737          * @since 3.7.0
    2738          *
    2739          * @param bool $disabled Whether the updater should be disabled.
    2740          */
    2741         return apply_filters( 'automatic_updater_disabled', $disabled );
    2742     }
    2743 
    2744     /**
    2745      * Check for version control checkouts.
    2746      *
    2747      * Checks for Subversion, Git, Mercurial, and Bazaar. It recursively looks up the
    2748      * filesystem to the top of the drive, erring on the side of detecting a VCS
    2749      * checkout somewhere.
    2750      *
    2751      * ABSPATH is always checked in addition to whatever $context is (which may be the
    2752      * wp-content directory, for example). The underlying assumption is that if you are
    2753      * using version control *anywhere*, then you should be making decisions for
    2754      * how things get updated.
    2755      *
    2756      * @since 3.7.0
    2757      * @access public
    2758      *
    2759      * @param string $context The filesystem path to check, in addition to ABSPATH.
    2760      */
    2761     public function is_vcs_checkout( $context ) {
    2762         $context_dirs = array( untrailingslashit( $context ) );
    2763         if ( $context !== ABSPATH )
    2764             $context_dirs[] = untrailingslashit( ABSPATH );
    2765 
    2766         $vcs_dirs = array( '.svn', '.git', '.hg', '.bzr' );
    2767         $check_dirs = array();
    2768 
    2769         foreach ( $context_dirs as $context_dir ) {
    2770             // Walk up from $context_dir to the root.
    2771             do {
    2772                 $check_dirs[] = $context_dir;
    2773 
    2774                 // Once we've hit '/' or 'C:\', we need to stop. dirname will keep returning the input here.
    2775                 if ( $context_dir == dirname( $context_dir ) )
    2776                     break;
    2777 
    2778             // Continue one level at a time.
    2779             } while ( $context_dir = dirname( $context_dir ) );
    2780         }
    2781 
    2782         $check_dirs = array_unique( $check_dirs );
    2783 
    2784         // Search all directories we've found for evidence of version control.
    2785         foreach ( $vcs_dirs as $vcs_dir ) {
    2786             foreach ( $check_dirs as $check_dir ) {
    2787                 if ( $checkout = @is_dir( rtrim( $check_dir, '\\/' ) . "/$vcs_dir" ) )
    2788                     break 2;
    2789             }
    2790         }
    2791 
    2792         /**
    2793          * Filter whether the automatic updater should consider a filesystem
    2794          * location to be potentially managed by a version control system.
    2795          *
    2796          * @since 3.7.0
    2797          *
    2798          * @param bool $checkout  Whether a VCS checkout was discovered at $context
    2799          *                        or ABSPATH, or anywhere higher.
    2800          * @param string $context The filesystem context (a path) against which
    2801          *                        filesystem status should be checked.
    2802          */
    2803         return apply_filters( 'automatic_updates_is_vcs_checkout', $checkout, $context );
    2804     }
    2805 
    2806     /**
    2807      * Tests to see if we can and should update a specific item.
    2808      *
    2809      * @since 3.7.0
    2810      * @access public
    2811      *
    2812      * @global wpdb $wpdb WordPress database abstraction object.
    2813      *
    2814      * @param string $type    The type of update being checked: 'core', 'theme',
    2815      *                        'plugin', 'translation'.
    2816      * @param object $item    The update offer.
    2817      * @param string $context The filesystem context (a path) against which filesystem
    2818      *                        access and status should be checked.
    2819      */
    2820     public function should_update( $type, $item, $context ) {
    2821         // Used to see if WP_Filesystem is set up to allow unattended updates.
    2822         $skin = new Automatic_Upgrader_Skin;
    2823 
    2824         if ( $this->is_disabled() )
    2825             return false;
    2826 
    2827         // Only relax the filesystem checks when the update doesn't include new files
    2828         $allow_relaxed_file_ownership = false;
    2829         if ( 'core' == $type && isset( $item->new_files ) && ! $item->new_files ) {
    2830             $allow_relaxed_file_ownership = true;
    2831         }
    2832 
    2833         // If we can't do an auto core update, we may still be able to email the user.
    2834         if ( ! $skin->request_filesystem_credentials( false, $context, $allow_relaxed_file_ownership ) || $this->is_vcs_checkout( $context ) ) {
    2835             if ( 'core' == $type )
    2836                 $this->send_core_update_notification_email( $item );
    2837             return false;
    2838         }
    2839 
    2840         // Next up, is this an item we can update?
    2841         if ( 'core' == $type )
    2842             $update = Core_Upgrader::should_update_to_version( $item->current );
    2843         else
    2844             $update = ! empty( $item->autoupdate );
    2845 
    2846         /**
    2847          * Filter whether to automatically update core, a plugin, a theme, or a language.
    2848          *
    2849          * The dynamic portion of the hook name, `$type`, refers to the type of update
    2850          * being checked. Can be 'core', 'theme', 'plugin', or 'translation'.
    2851          *
    2852          * Generally speaking, plugins, themes, and major core versions are not updated
    2853          * by default, while translations and minor and development versions for core
    2854          * are updated by default.
    2855          *
    2856          * See the {@see 'allow_dev_auto_core_updates', {@see 'allow_minor_auto_core_updates'},
    2857          * and {@see 'allow_major_auto_core_updates'} filters for a more straightforward way to
    2858          * adjust core updates.
    2859          *
    2860          * @since 3.7.0
    2861          *
    2862          * @param bool   $update Whether to update.
    2863          * @param object $item   The update offer.
    2864          */
    2865         $update = apply_filters( 'auto_update_' . $type, $update, $item );
    2866 
    2867         if ( ! $update ) {
    2868             if ( 'core' == $type )
    2869                 $this->send_core_update_notification_email( $item );
    2870             return false;
    2871         }
    2872 
    2873         // If it's a core update, are we actually compatible with its requirements?
    2874         if ( 'core' == $type ) {
    2875             global $wpdb;
    2876 
    2877             $php_compat = version_compare( phpversion(), $item->php_version, '>=' );
    2878             if ( file_exists( WP_CONTENT_DIR . '/db.php' ) && empty( $wpdb->is_mysql ) )
    2879                 $mysql_compat = true;
    2880             else
    2881                 $mysql_compat = version_compare( $wpdb->db_version(), $item->mysql_version, '>=' );
    2882 
    2883             if ( ! $php_compat || ! $mysql_compat )
    2884                 return false;
    2885         }
    2886 
    2887         return true;
    2888     }
    2889 
    2890     /**
    2891      * Notifies an administrator of a core update.
    2892      *
    2893      * @since 3.7.0
    2894      * @access protected
    2895      *
    2896      * @param object $item The update offer.
    2897      */
    2898     protected function send_core_update_notification_email( $item ) {
    2899         $notified = get_site_option( 'auto_core_update_notified' );
    2900 
    2901         // Don't notify if we've already notified the same email address of the same version.
    2902         if ( $notified && $notified['email'] == get_site_option( 'admin_email' ) && $notified['version'] == $item->current )
    2903             return false;
    2904 
    2905         // See if we need to notify users of a core update.
    2906         $notify = ! empty( $item->notify_email );
    2907 
    2908         /**
    2909          * Filter whether to notify the site administrator of a new core update.
    2910          *
    2911          * By default, administrators are notified when the update offer received
    2912          * from WordPress.org sets a particular flag. This allows some discretion
    2913          * in if and when to notify.
    2914          *
    2915          * This filter is only evaluated once per release. If the same email address
    2916          * was already notified of the same new version, WordPress won't repeatedly
    2917          * email the administrator.
    2918          *
    2919          * This filter is also used on about.php to check if a plugin has disabled
    2920          * these notifications.
    2921          *
    2922          * @since 3.7.0
    2923          *
    2924          * @param bool   $notify Whether the site administrator is notified.
    2925          * @param object $item   The update offer.
    2926          */
    2927         if ( ! apply_filters( 'send_core_update_notification_email', $notify, $item ) )
    2928             return false;
    2929 
    2930         $this->send_email( 'manual', $item );
    2931         return true;
    2932     }
    2933 
    2934     /**
    2935      * Update an item, if appropriate.
    2936      *
    2937      * @since 3.7.0
    2938      * @access public
    2939      *
    2940      * @param string $type The type of update being checked: 'core', 'theme', 'plugin', 'translation'.
    2941      * @param object $item The update offer.
    2942      *
    2943      * @return null|WP_Error
    2944      */
    2945     public function update( $type, $item ) {
    2946         $skin = new Automatic_Upgrader_Skin;
    2947 
    2948         switch ( $type ) {
    2949             case 'core':
    2950                 // The Core upgrader doesn't use the Upgrader's skin during the actual main part of the upgrade, instead, firing a filter.
    2951                 add_filter( 'update_feedback', array( $skin, 'feedback' ) );
    2952                 $upgrader = new Core_Upgrader( $skin );
    2953                 $context  = ABSPATH;
    2954                 break;
    2955             case 'plugin':
    2956                 $upgrader = new Plugin_Upgrader( $skin );
    2957                 $context  = WP_PLUGIN_DIR; // We don't support custom Plugin directories, or updates for WPMU_PLUGIN_DIR
    2958                 break;
    2959             case 'theme':
    2960                 $upgrader = new Theme_Upgrader( $skin );
    2961                 $context  = get_theme_root( $item->theme );
    2962                 break;
    2963             case 'translation':
    2964                 $upgrader = new Language_Pack_Upgrader( $skin );
    2965                 $context  = WP_CONTENT_DIR; // WP_LANG_DIR;
    2966                 break;
    2967         }
    2968 
    2969         // Determine whether we can and should perform this update.
    2970         if ( ! $this->should_update( $type, $item, $context ) )
    2971             return false;
    2972 
    2973         /**
    2974          * Fires immediately prior to an auto-update.
    2975          *
    2976          * @since 4.4.0
    2977          *
    2978          * @param string $type    The type of update being checked: 'core', 'theme', 'plugin', or 'translation'.
    2979          * @param object $item    The update offer.
    2980          * @param string $context The filesystem context (a path) against which filesystem access and status
    2981          *                        should be checked.
    2982          */
    2983         do_action( 'pre_auto_update', $type, $item, $context );
    2984 
    2985         $upgrader_item = $item;
    2986         switch ( $type ) {
    2987             case 'core':
    2988                 $skin->feedback( __( 'Updating to WordPress %s' ), $item->version );
    2989                 $item_name = sprintf( __( 'WordPress %s' ), $item->version );
    2990                 break;
    2991             case 'theme':
    2992                 $upgrader_item = $item->theme;
    2993                 $theme = wp_get_theme( $upgrader_item );
    2994                 $item_name = $theme->Get( 'Name' );
    2995                 $skin->feedback( __( 'Updating theme: %s' ), $item_name );
    2996                 break;
    2997             case 'plugin':
    2998                 $upgrader_item = $item->plugin;
    2999                 $plugin_data = get_plugin_data( $context . '/' . $upgrader_item );
    3000                 $item_name = $plugin_data['Name'];
    3001                 $skin->feedback( __( 'Updating plugin: %s' ), $item_name );
    3002                 break;
    3003             case 'translation':
    3004                 $language_item_name = $upgrader->get_name_for_update( $item );
    3005                 $item_name = sprintf( __( 'Translations for %s' ), $language_item_name );
    3006                 $skin->feedback( sprintf( __( 'Updating translations for %1$s (%2$s)&#8230;' ), $language_item_name, $item->language ) );
    3007                 break;
    3008         }
    3009 
    3010         $allow_relaxed_file_ownership = false;
    3011         if ( 'core' == $type && isset( $item->new_files ) && ! $item->new_files ) {
    3012             $allow_relaxed_file_ownership = true;
    3013         }
    3014 
    3015         // Boom, This sites about to get a whole new splash of paint!
    3016         $upgrade_result = $upgrader->upgrade( $upgrader_item, array(
    3017             'clear_update_cache' => false,
    3018             // Always use partial builds if possible for core updates.
    3019             'pre_check_md5'      => false,
    3020             // Only available for core updates.
    3021             'attempt_rollback'   => true,
    3022             // Allow relaxed file ownership in some scenarios
    3023             'allow_relaxed_file_ownership' => $allow_relaxed_file_ownership,
    3024         ) );
    3025 
    3026         // If the filesystem is unavailable, false is returned.
    3027         if ( false === $upgrade_result ) {
    3028             $upgrade_result = new WP_Error( 'fs_unavailable', __( 'Could not access filesystem.' ) );
    3029         }
    3030 
    3031         if ( 'core' == $type ) {
    3032             if ( is_wp_error( $upgrade_result ) && ( 'up_to_date' == $upgrade_result->get_error_code() || 'locked' == $upgrade_result->get_error_code() ) ) {
    3033                 // These aren't actual errors, treat it as a skipped-update instead to avoid triggering the post-core update failure routines.
    3034                 return false;
    3035             }
    3036 
    3037             // Core doesn't output this, so let's append it so we don't get confused.
    3038             if ( is_wp_error( $upgrade_result ) ) {
    3039                 $skin->error( __( 'Installation Failed' ), $upgrade_result );
    3040             } else {
    3041                 $skin->feedback( __( 'WordPress updated successfully' ) );
    3042             }
    3043         }
    3044 
    3045         $this->update_results[ $type ][] = (object) array(
    3046             'item'     => $item,
    3047             'result'   => $upgrade_result,
    3048             'name'     => $item_name,
    3049             'messages' => $skin->get_upgrade_messages()
    3050         );
    3051 
    3052         return $upgrade_result;
    3053     }
    3054 
    3055     /**
    3056      * Kicks off the background update process, looping through all pending updates.
    3057      *
    3058      * @since 3.7.0
    3059      * @access public
    3060      *
    3061      * @global wpdb   $wpdb
    3062      * @global string $wp_version
    3063      */
    3064     public function run() {
    3065         global $wpdb, $wp_version;
    3066 
    3067         if ( $this->is_disabled() )
    3068             return;
    3069 
    3070         if ( ! is_main_network() || ! is_main_site() )
    3071             return;
    3072 
    3073         if ( ! WP_Upgrader::create_lock( 'auto_updater' ) )
    3074             return;
    3075 
    3076         // Don't automatically run these thins, as we'll handle it ourselves
    3077         remove_action( 'upgrader_process_complete', array( 'Language_Pack_Upgrader', 'async_upgrade' ), 20 );
    3078         remove_action( 'upgrader_process_complete', 'wp_version_check' );
    3079         remove_action( 'upgrader_process_complete', 'wp_update_plugins' );
    3080         remove_action( 'upgrader_process_complete', 'wp_update_themes' );
    3081 
    3082         // Next, Plugins
    3083         wp_update_plugins(); // Check for Plugin updates
    3084         $plugin_updates = get_site_transient( 'update_plugins' );
    3085         if ( $plugin_updates && !empty( $plugin_updates->response ) ) {
    3086             foreach ( $plugin_updates->response as $plugin ) {
    3087                 $this->update( 'plugin', $plugin );
    3088             }
    3089             // Force refresh of plugin update information
    3090             wp_clean_plugins_cache();
    3091         }
    3092 
    3093         // Next, those themes we all love
    3094         wp_update_themes();  // Check for Theme updates
    3095         $theme_updates = get_site_transient( 'update_themes' );
    3096         if ( $theme_updates && !empty( $theme_updates->response ) ) {
    3097             foreach ( $theme_updates->response as $theme ) {
    3098                 $this->update( 'theme', (object) $theme );
    3099             }
    3100             // Force refresh of theme update information
    3101             wp_clean_themes_cache();
    3102         }
    3103 
    3104         // Next, Process any core update
    3105         wp_version_check(); // Check for Core updates
    3106         $core_update = find_core_auto_update();
    3107 
    3108         if ( $core_update )
    3109             $this->update( 'core', $core_update );
    3110 
    3111         // Clean up, and check for any pending translations
    3112         // (Core_Upgrader checks for core updates)
    3113         $theme_stats = array();
    3114         if ( isset( $this->update_results['theme'] ) ) {
    3115             foreach ( $this->update_results['theme'] as $upgrade ) {
    3116                 $theme_stats[ $upgrade->item->theme ] = ( true === $upgrade->result );
    3117             }
    3118         }
    3119         wp_update_themes( $theme_stats );  // Check for Theme updates
    3120 
    3121         $plugin_stats = array();
    3122         if ( isset( $this->update_results['plugin'] ) ) {
    3123             foreach ( $this->update_results['plugin'] as $upgrade ) {
    3124                 $plugin_stats[ $upgrade->item->plugin ] = ( true === $upgrade->result );
    3125             }
    3126         }
    3127         wp_update_plugins( $plugin_stats ); // Check for Plugin updates
    3128 
    3129         // Finally, Process any new translations
    3130         $language_updates = wp_get_translation_updates();
    3131         if ( $language_updates ) {
    3132             foreach ( $language_updates as $update ) {
    3133                 $this->update( 'translation', $update );
    3134             }
    3135 
    3136             // Clear existing caches
    3137             wp_clean_update_cache();
    3138 
    3139             wp_version_check();  // check for Core updates
    3140             wp_update_themes();  // Check for Theme updates
    3141             wp_update_plugins(); // Check for Plugin updates
    3142         }
    3143 
    3144         // Send debugging email to all development installs.
    3145         if ( ! empty( $this->update_results ) ) {
    3146             $development_version = false !== strpos( $wp_version, '-' );
    3147 
    3148             /**
    3149              * Filter whether to send a debugging email for each automatic background update.
    3150              *
    3151              * @since 3.7.0
    3152              *
    3153              * @param bool $development_version By default, emails are sent if the
    3154              *                                  install is a development version.
    3155              *                                  Return false to avoid the email.
    3156              */
    3157             if ( apply_filters( 'automatic_updates_send_debug_email', $development_version ) )
    3158                 $this->send_debug_email();
    3159 
    3160             if ( ! empty( $this->update_results['core'] ) )
    3161                 $this->after_core_update( $this->update_results['core'][0] );
    3162 
    3163             /**
    3164              * Fires after all automatic updates have run.
    3165              *
    3166              * @since 3.8.0
    3167              *
    3168              * @param array $update_results The results of all attempted updates.
    3169              */
    3170             do_action( 'automatic_updates_complete', $this->update_results );
    3171         }
    3172 
    3173         WP_Upgrader::release_lock( 'auto_updater' );
    3174     }
    3175 
    3176     /**
    3177      * If we tried to perform a core update, check if we should send an email,
    3178      * and if we need to avoid processing future updates.
    3179      *
    3180      * @since Unknown
    3181      * @access protected
    3182      *
    3183      * @global string $wp_version
    3184      *
    3185      * @param object $update_result The result of the core update. Includes the update offer and result.
    3186      */
    3187     protected function after_core_update( $update_result ) {
    3188         global $wp_version;
    3189 
    3190         $core_update = $update_result->item;
    3191         $result      = $update_result->result;
    3192 
    3193         if ( ! is_wp_error( $result ) ) {
    3194             $this->send_email( 'success', $core_update );
    3195             return;
    3196         }
    3197 
    3198         $error_code = $result->get_error_code();
    3199 
    3200         // Any of these WP_Error codes are critical failures, as in they occurred after we started to copy core files.
    3201         // We should not try to perform a background update again until there is a successful one-click update performed by the user.
    3202         $critical = false;
    3203         if ( $error_code === 'disk_full' || false !== strpos( $error_code, '__copy_dir' ) ) {
    3204             $critical = true;
    3205         } elseif ( $error_code === 'rollback_was_required' && is_wp_error( $result->get_error_data()->rollback ) ) {
    3206             // A rollback is only critical if it failed too.
    3207             $critical = true;
    3208             $rollback_result = $result->get_error_data()->rollback;
    3209         } elseif ( false !== strpos( $error_code, 'do_rollback' ) ) {
    3210             $critical = true;
    3211         }
    3212 
    3213         if ( $critical ) {
    3214             $critical_data = array(
    3215                 'attempted'  => $core_update->current,
    3216                 'current'    => $wp_version,
    3217                 'error_code' => $error_code,
    3218                 'error_data' => $result->get_error_data(),
    3219                 'timestamp'  => time(),
    3220                 'critical'   => true,
    3221             );
    3222             if ( isset( $rollback_result ) ) {
    3223                 $critical_data['rollback_code'] = $rollback_result->get_error_code();
    3224                 $critical_data['rollback_data'] = $rollback_result->get_error_data();
    3225             }
    3226             update_site_option( 'auto_core_update_failed', $critical_data );
    3227             $this->send_email( 'critical', $core_update, $result );
    3228             return;
    3229         }
    3230 
    3231         /*
    3232          * Any other WP_Error code (like download_failed or files_not_writable) occurs before
    3233          * we tried to copy over core files. Thus, the failures are early and graceful.
    3234          *
    3235          * We should avoid trying to perform a background update again for the same version.
    3236          * But we can try again if another version is released.
    3237          *
    3238          * For certain 'transient' failures, like download_failed, we should allow retries.
    3239          * In fact, let's schedule a special update for an hour from now. (It's possible
    3240          * the issue could actually be on WordPress.org's side.) If that one fails, then email.
    3241          */
    3242         $send = true;
    3243         $transient_failures = array( 'incompatible_archive', 'download_failed', 'insane_distro', 'locked' );
    3244         if ( in_array( $error_code, $transient_failures ) && ! get_site_option( 'auto_core_update_failed' ) ) {
    3245             wp_schedule_single_event( time() + HOUR_IN_SECONDS, 'wp_maybe_auto_update' );
    3246             $send = false;
    3247         }
    3248 
    3249         $n = get_site_option( 'auto_core_update_notified' );
    3250         // Don't notify if we've already notified the same email address of the same version of the same notification type.
    3251         if ( $n && 'fail' == $n['type'] && $n['email'] == get_site_option( 'admin_email' ) && $n['version'] == $core_update->current )
    3252             $send = false;
    3253 
    3254         update_site_option( 'auto_core_update_failed', array(
    3255             'attempted'  => $core_update->current,
    3256             'current'    => $wp_version,
    3257             'error_code' => $error_code,
    3258             'error_data' => $result->get_error_data(),
    3259             'timestamp'  => time(),
    3260             'retry'      => in_array( $error_code, $transient_failures ),
    3261         ) );
    3262 
    3263         if ( $send )
    3264             $this->send_email( 'fail', $core_update, $result );
    3265     }
    3266 
    3267     /**
    3268      * Sends an email upon the completion or failure of a background core update.
    3269      *
    3270      * @since 3.7.0
    3271      * @access protected
    3272      *
    3273      * @global string $wp_version
    3274      *
    3275      * @param string $type        The type of email to send. Can be one of 'success', 'fail', 'manual', 'critical'.
    3276      * @param object $core_update The update offer that was attempted.
    3277      * @param mixed  $result      Optional. The result for the core update. Can be WP_Error.
    3278      */
    3279     protected function send_email( $type, $core_update, $result = null ) {
    3280         update_site_option( 'auto_core_update_notified', array(
    3281             'type'      => $type,
    3282             'email'     => get_site_option( 'admin_email' ),
    3283             'version'   => $core_update->current,
    3284             'timestamp' => time(),
    3285         ) );
    3286 
    3287         $next_user_core_update = get_preferred_from_update_core();
    3288         // If the update transient is empty, use the update we just performed
    3289         if ( ! $next_user_core_update )
    3290             $next_user_core_update = $core_update;
    3291         $newer_version_available = ( 'upgrade' == $next_user_core_update->response && version_compare( $next_user_core_update->version, $core_update->version, '>' ) );
    3292 
    3293         /**
    3294          * Filter whether to send an email following an automatic background core update.
    3295          *
    3296          * @since 3.7.0
    3297          *
    3298          * @param bool   $send        Whether to send the email. Default true.
    3299          * @param string $type        The type of email to send. Can be one of
    3300          *                            'success', 'fail', 'critical'.
    3301          * @param object $core_update The update offer that was attempted.
    3302          * @param mixed  $result      The result for the core update. Can be WP_Error.
    3303          */
    3304         if ( 'manual' !== $type && ! apply_filters( 'auto_core_update_send_email', true, $type, $core_update, $result ) )
    3305             return;
    3306 
    3307         switch ( $type ) {
    3308             case 'success' : // We updated.
    3309                 /* translators: 1: Site name, 2: WordPress version number. */
    3310                 $subject = __( '[%1$s] Your site has updated to WordPress %2$s' );
    3311                 break;
    3312 
    3313             case 'fail' :   // We tried to update but couldn't.
    3314             case 'manual' : // We can't update (and made no attempt).
    3315                 /* translators: 1: Site name, 2: WordPress version number. */
    3316                 $subject = __( '[%1$s] WordPress %2$s is available. Please update!' );
    3317                 break;
    3318 
    3319             case 'critical' : // We tried to update, started to copy files, then things went wrong.
    3320                 /* translators: 1: Site name. */
    3321                 $subject = __( '[%1$s] URGENT: Your site may be down due to a failed update' );
    3322                 break;
    3323 
    3324             default :
    3325                 return;
    3326         }
    3327 
    3328         // If the auto update is not to the latest version, say that the current version of WP is available instead.
    3329         $version = 'success' === $type ? $core_update->current : $next_user_core_update->current;
    3330         $subject = sprintf( $subject, wp_specialchars_decode( get_option( 'blogname' ), ENT_QUOTES ), $version );
    3331 
    3332         $body = '';
    3333 
    3334         switch ( $type ) {
    3335             case 'success' :
    3336                 $body .= sprintf( __( 'Howdy! Your site at %1$s has been updated automatically to WordPress %2$s.' ), home_url(), $core_update->current );
    3337                 $body .= "\n\n";
    3338                 if ( ! $newer_version_available )
    3339                     $body .= __( 'No further action is needed on your part.' ) . ' ';
    3340 
    3341                 // Can only reference the About screen if their update was successful.
    3342                 list( $about_version ) = explode( '-', $core_update->current, 2 );
    3343                 $body .= sprintf( __( "For more on version %s, see the About WordPress screen:" ), $about_version );
    3344                 $body .= "\n" . admin_url( 'about.php' );
    3345 
    3346                 if ( $newer_version_available ) {
    3347                     $body .= "\n\n" . sprintf( __( 'WordPress %s is also now available.' ), $next_user_core_update->current ) . ' ';
    3348                     $body .= __( 'Updating is easy and only takes a few moments:' );
    3349                     $body .= "\n" . network_admin_url( 'update-core.php' );
    3350                 }
    3351 
    3352                 break;
    3353 
    3354             case 'fail' :
    3355             case 'manual' :
    3356                 $body .= sprintf( __( 'Please update your site at %1$s to WordPress %2$s.' ), home_url(), $next_user_core_update->current );
    3357 
    3358                 $body .= "\n\n";
    3359 
    3360                 // Don't show this message if there is a newer version available.
    3361                 // Potential for confusion, and also not useful for them to know at this point.
    3362                 if ( 'fail' == $type && ! $newer_version_available )
    3363                     $body .= __( 'We tried but were unable to update your site automatically.' ) . ' ';
    3364 
    3365                 $body .= __( 'Updating is easy and only takes a few moments:' );
    3366                 $body .= "\n" . network_admin_url( 'update-core.php' );
    3367                 break;
    3368 
    3369             case 'critical' :
    3370                 if ( $newer_version_available )
    3371                     $body .= sprintf( __( 'Your site at %1$s experienced a critical failure while trying to update WordPress to version %2$s.' ), home_url(), $core_update->current );
    3372                 else
    3373                     $body .= sprintf( __( 'Your site at %1$s experienced a critical failure while trying to update to the latest version of WordPress, %2$s.' ), home_url(), $core_update->current );
    3374 
    3375                 $body .= "\n\n" . __( "This means your site may be offline or broken. Don't panic; this can be fixed." );
    3376 
    3377                 $body .= "\n\n" . __( "Please check out your site now. It's possible that everything is working. If it says you need to update, you should do so:" );
    3378                 $body .= "\n" . network_admin_url( 'update-core.php' );
    3379                 break;
    3380         }
    3381 
    3382         $critical_support = 'critical' === $type && ! empty( $core_update->support_email );
    3383         if ( $critical_support ) {
    3384             // Support offer if available.
    3385             $body .= "\n\n" . sprintf( __( "The WordPress team is willing to help you. Forward this email to %s and the team will work with you to make sure your site is working." ), $core_update->support_email );
    3386         } else {
    3387             // Add a note about the support forums.
    3388             $body .= "\n\n" . __( 'If you experience any issues or need support, the volunteers in the WordPress.org support forums may be able to help.' );
    3389             $body .= "\n" . __( 'https://wordpress.org/support/' );
    3390         }
    3391 
    3392         // Updates are important!
    3393         if ( $type != 'success' || $newer_version_available ) {
    3394             $body .= "\n\n" . __( 'Keeping your site updated is important for security. It also makes the internet a safer place for you and your readers.' );
    3395         }
    3396 
    3397         if ( $critical_support ) {
    3398             $body .= " " . __( "If you reach out to us, we'll also ensure you'll never have this problem again." );
    3399         }
    3400 
    3401         // If things are successful and we're now on the latest, mention plugins and themes if any are out of date.
    3402         if ( $type == 'success' && ! $newer_version_available && ( get_plugin_updates() || get_theme_updates() ) ) {
    3403             $body .= "\n\n" . __( 'You also have some plugins or themes with updates available. Update them now:' );
    3404             $body .= "\n" . network_admin_url();
    3405         }
    3406 
    3407         $body .= "\n\n" . __( 'The WordPress Team' ) . "\n";
    3408 
    3409         if ( 'critical' == $type && is_wp_error( $result ) ) {
    3410             $body .= "\n***\n\n";
    3411             $body .= sprintf( __( 'Your site was running version %s.' ), $GLOBALS['wp_version'] );
    3412             $body .= ' ' . __( 'We have some data that describes the error your site encountered.' );
    3413             $body .= ' ' . __( 'Your hosting company, support forum volunteers, or a friendly developer may be able to use this information to help you:' );
    3414 
    3415             // If we had a rollback and we're still critical, then the rollback failed too.
    3416             // Loop through all errors (the main WP_Error, the update result, the rollback result) for code, data, etc.
    3417             if ( 'rollback_was_required' == $result->get_error_code() )
    3418                 $errors = array( $result, $result->get_error_data()->update, $result->get_error_data()->rollback );
    3419             else
    3420                 $errors = array( $result );
    3421 
    3422             foreach ( $errors as $error ) {
    3423                 if ( ! is_wp_error( $error ) )
    3424                     continue;
    3425                 $error_code = $error->get_error_code();
    3426                 $body .= "\n\n" . sprintf( __( "Error code: %s" ), $error_code );
    3427                 if ( 'rollback_was_required' == $error_code )
    3428                     continue;
    3429                 if ( $error->get_error_message() )
    3430                     $body .= "\n" . $error->get_error_message();
    3431                 $error_data = $error->get_error_data();
    3432                 if ( $error_data )
    3433                     $body .= "\n" . implode( ', ', (array) $error_data );
    3434             }
    3435             $body .= "\n";
    3436         }
    3437 
    3438         $to  = get_site_option( 'admin_email' );
    3439         $headers = '';
    3440 
    3441         $email = compact( 'to', 'subject', 'body', 'headers' );
    3442 
    3443         /**
    3444          * Filter the email sent following an automatic background core update.
    3445          *
    3446          * @since 3.7.0
    3447          *
    3448          * @param array $email {
    3449          *     Array of email arguments that will be passed to wp_mail().
    3450          *
    3451          *     @type string $to      The email recipient. An array of emails
    3452          *                            can be returned, as handled by wp_mail().
    3453          *     @type string $subject The email's subject.
    3454          *     @type string $body    The email message body.
    3455          *     @type string $headers Any email headers, defaults to no headers.
    3456          * }
    3457          * @param string $type        The type of email being sent. Can be one of
    3458          *                            'success', 'fail', 'manual', 'critical'.
    3459          * @param object $core_update The update offer that was attempted.
    3460          * @param mixed  $result      The result for the core update. Can be WP_Error.
    3461          */
    3462         $email = apply_filters( 'auto_core_update_email', $email, $type, $core_update, $result );
    3463 
    3464         wp_mail( $email['to'], wp_specialchars_decode( $email['subject'] ), $email['body'], $email['headers'] );
    3465     }
    3466 
    3467     /**
    3468      * Prepares and sends an email of a full log of background update results, useful for debugging and geekery.
    3469      *
    3470      * @since 3.7.0
    3471      * @access protected
    3472      */
    3473     protected function send_debug_email() {
    3474         $update_count = 0;
    3475         foreach ( $this->update_results as $type => $updates )
    3476             $update_count += count( $updates );
    3477 
    3478         $body = array();
    3479         $failures = 0;
    3480 
    3481         $body[] = sprintf( __( 'WordPress site: %s' ), network_home_url( '/' ) );
    3482 
    3483         // Core
    3484         if ( isset( $this->update_results['core'] ) ) {
    3485             $result = $this->update_results['core'][0];
    3486             if ( $result->result && ! is_wp_error( $result->result ) ) {
    3487                 $body[] = sprintf( __( 'SUCCESS: WordPress was successfully updated to %s' ), $result->name );
    3488             } else {
    3489                 $body[] = sprintf( __( 'FAILED: WordPress failed to update to %s' ), $result->name );
    3490                 $failures++;
    3491             }
    3492             $body[] = '';
    3493         }
    3494 
    3495         // Plugins, Themes, Translations
    3496         foreach ( array( 'plugin', 'theme', 'translation' ) as $type ) {
    3497             if ( ! isset( $this->update_results[ $type ] ) )
    3498                 continue;
    3499             $success_items = wp_list_filter( $this->update_results[ $type ], array( 'result' => true ) );
    3500             if ( $success_items ) {
    3501                 $messages = array(
    3502                     'plugin'      => __( 'The following plugins were successfully updated:' ),
    3503                     'theme'       => __( 'The following themes were successfully updated:' ),
    3504                     'translation' => __( 'The following translations were successfully updated:' ),
    3505                 );
    3506 
    3507                 $body[] = $messages[ $type ];
    3508                 foreach ( wp_list_pluck( $success_items, 'name' ) as $name ) {
    3509                     $body[] = ' * ' . sprintf( __( 'SUCCESS: %s' ), $name );
    3510                 }
    3511             }
    3512             if ( $success_items != $this->update_results[ $type ] ) {
    3513                 // Failed updates
    3514                 $messages = array(
    3515                     'plugin'      => __( 'The following plugins failed to update:' ),
    3516                     'theme'       => __( 'The following themes failed to update:' ),
    3517                     'translation' => __( 'The following translations failed to update:' ),
    3518                 );
    3519 
    3520                 $body[] = $messages[ $type ];
    3521                 foreach ( $this->update_results[ $type ] as $item ) {
    3522                     if ( ! $item->result || is_wp_error( $item->result ) ) {
    3523                         $body[] = ' * ' . sprintf( __( 'FAILED: %s' ), $item->name );
    3524                         $failures++;
    3525                     }
    3526                 }
    3527             }
    3528             $body[] = '';
    3529         }
    3530 
    3531         $site_title = wp_specialchars_decode( get_bloginfo( 'name' ), ENT_QUOTES );
    3532         if ( $failures ) {
    3533             $body[] = trim( __(
    3534 "BETA TESTING?
    3535 =============
    3536 
    3537 This debugging email is sent when you are using a development version of WordPress.
    3538 
    3539 If you think these failures might be due to a bug in WordPress, could you report it?
    3540  * Open a thread in the support forums: https://wordpress.org/support/forum/alphabeta
    3541  * Or, if you're comfortable writing a bug report: https://core.trac.wordpress.org/
    3542 
    3543 Thanks! -- The WordPress Team" ) );
    3544             $body[] = '';
    3545 
    3546             $subject = sprintf( __( '[%s] There were failures during background updates' ), $site_title );
    3547         } else {
    3548             $subject = sprintf( __( '[%s] Background updates have finished' ), $site_title );
    3549         }
    3550 
    3551         $body[] = trim( __(
    3552 'UPDATE LOG
    3553 ==========' ) );
    3554         $body[] = '';
    3555 
    3556         foreach ( array( 'core', 'plugin', 'theme', 'translation' ) as $type ) {
    3557             if ( ! isset( $this->update_results[ $type ] ) )
    3558                 continue;
    3559             foreach ( $this->update_results[ $type ] as $update ) {
    3560                 $body[] = $update->name;
    3561                 $body[] = str_repeat( '-', strlen( $update->name ) );
    3562                 foreach ( $update->messages as $message )
    3563                     $body[] = "  " . html_entity_decode( str_replace( '&#8230;', '...', $message ) );
    3564                 if ( is_wp_error( $update->result ) ) {
    3565                     $results = array( 'update' => $update->result );
    3566                     // If we rolled back, we want to know an error that occurred then too.
    3567                     if ( 'rollback_was_required' === $update->result->get_error_code() )
    3568                         $results = (array) $update->result->get_error_data();
    3569                     foreach ( $results as $result_type => $result ) {
    3570                         if ( ! is_wp_error( $result ) )
    3571                             continue;
    3572 
    3573                         if ( 'rollback' === $result_type ) {
    3574                             /* translators: 1: Error code, 2: Error message. */
    3575                             $body[] = '  ' . sprintf( __( 'Rollback Error: [%1$s] %2$s' ), $result->get_error_code(), $result->get_error_message() );
    3576                         } else {
    3577                             /* translators: 1: Error code, 2: Error message. */
    3578                             $body[] = '  ' . sprintf( __( 'Error: [%1$s] %2$s' ), $result->get_error_code(), $result->get_error_message() );
    3579                         }
    3580 
    3581                         if ( $result->get_error_data() )
    3582                             $body[] = '         ' . implode( ', ', (array) $result->get_error_data() );
    3583                     }
    3584                 }
    3585                 $body[] = '';
    3586             }
    3587         }
    3588 
    3589         $email = array(
    3590             'to'      => get_site_option( 'admin_email' ),
    3591             'subject' => $subject,
    3592             'body'    => implode( "\n", $body ),
    3593             'headers' => ''
    3594         );
    3595 
    3596         /**
    3597          * Filter the debug email that can be sent following an automatic
    3598          * background core update.
    3599          *
    3600          * @since 3.8.0
    3601          *
    3602          * @param array $email {
    3603          *     Array of email arguments that will be passed to wp_mail().
    3604          *
    3605          *     @type string $to      The email recipient. An array of emails
    3606          *                           can be returned, as handled by wp_mail().
    3607          *     @type string $subject Email subject.
    3608          *     @type string $body    Email message body.
    3609          *     @type string $headers Any email headers. Default empty.
    3610          * }
    3611          * @param int   $failures The number of failures encountered while upgrading.
    3612          * @param mixed $results  The results of all attempted updates.
    3613          */
    3614         $email = apply_filters( 'automatic_updates_debug_email', $email, $failures, $this->update_results );
    3615 
    3616         wp_mail( $email['to'], wp_specialchars_decode( $email['subject'] ), $email['body'], $email['headers'] );
    3617     }
    3618 }
  • trunk/src/wp-admin/includes/class-file-upload-upgrader.php

    r37383 r37409  
    1111 * @since 2.8.0
    1212 */
    13 
    14 require ABSPATH . 'wp-admin/includes/class-wp-upgrader-skins.php';
    15 
    16 /**
    17  * Core class used for upgrading/installing a local set of files via
    18  * the Filesystem Abstraction classes from a Zip file.
    19  *
    20  * @since 2.8.0
    21  */
    22 class WP_Upgrader {
    23 
    24     /**
    25      * The error/notification strings used to update the user on the progress.
    26      *
    27      * @since 2.8.0
    28      * @access public
    29      * @var string $strings
    30      */
    31     public $strings = array();
    32 
    33     /**
    34      * The upgrader skin being used.
    35      *
    36      * @since 2.8.0
    37      * @access public
    38      * @var WP_Upgrader_Skin $skin
    39      */
    40     public $skin = null;
    41 
    42     /**
    43      * The result of the installation.
    44      *
    45      * This is set by WP_Upgrader::install_package(), only when the package is installed
    46      * successfully. It will then be an array, unless a WP_Error is returned by the
    47      * {@see 'upgrader_post_install'} filter. In that case, the WP_Error will be assigned to
    48      * it.
    49      *
    50      * @since 2.8.0
    51      * @access public
    52      *
    53      * @var WP_Error|array $result {
    54      *      @type string $source             The full path to the source the files were installed from.
    55      *      @type string $source_files       List of all the files in the source directory.
    56      *      @type string $destination        The full path to the install destination folder.
    57      *      @type string $destination_name   The name of the destination folder, or empty if `$destination`
    58      *                                       and `$local_destination` are the same.
    59      *      @type string $local_destination  The full local path to the destination folder. This is usually
    60      *                                       the same as `$destination`.
    61      *      @type string $remote_destination The full remote path to the destination folder
    62      *                                       (i.e., from `$wp_filesystem`).
    63      *      @type bool   $clear_destination  Whether the destination folder was cleared.
    64      * }
    65      */
    66     public $result = array();
    67 
    68     /**
    69      * The total number of updates being performed.
    70      *
    71      * Set by the bulk update methods.
    72      *
    73      * @since 3.0.0
    74      * @access public
    75      * @var int $update_count
    76      */
    77     public $update_count = 0;
    78 
    79     /**
    80      * The current update if multiple updates are being performed.
    81      *
    82      * Used by the bulk update methods, and incremented for each update.
    83      *
    84      * @since 3.0.0
    85      * @access public
    86      * @var int
    87      */
    88     public $update_current = 0;
    89 
    90     /**
    91      * Construct the upgrader with a skin.
    92      *
    93      * @since 2.8.0
    94      * @access public
    95      *
    96      * @param WP_Upgrader_Skin $skin The upgrader skin to use. Default is a WP_Upgrader_Skin.
    97      *                               instance.
    98      */
    99     public function __construct( $skin = null ) {
    100         if ( null == $skin )
    101             $this->skin = new WP_Upgrader_Skin();
    102         else
    103             $this->skin = $skin;
    104     }
    105 
    106     /**
    107      * Initialize the upgrader.
    108      *
    109      * This will set the relationship between the skin being used and this upgrader,
    110      * and also add the generic strings to `WP_Upgrader::$strings`.
    111      *
    112      * @since 2.8.0
    113      * @access public
    114      */
    115     public function init() {
    116         $this->skin->set_upgrader($this);
    117         $this->generic_strings();
    118     }
    119 
    120     /**
    121      * Add the generic strings to WP_Upgrader::$strings.
    122      *
    123      * @since 2.8.0
    124      * @access public
    125      */
    126     public function generic_strings() {
    127         $this->strings['bad_request'] = __('Invalid Data provided.');
    128         $this->strings['fs_unavailable'] = __('Could not access filesystem.');
    129         $this->strings['fs_error'] = __('Filesystem error.');
    130         $this->strings['fs_no_root_dir'] = __('Unable to locate WordPress Root directory.');
    131         $this->strings['fs_no_content_dir'] = __('Unable to locate WordPress Content directory (wp-content).');
    132         $this->strings['fs_no_plugins_dir'] = __('Unable to locate WordPress Plugin directory.');
    133         $this->strings['fs_no_themes_dir'] = __('Unable to locate WordPress Theme directory.');
    134         /* translators: %s: directory name */
    135         $this->strings['fs_no_folder'] = __('Unable to locate needed folder (%s).');
    136 
    137         $this->strings['download_failed'] = __('Download failed.');
    138         $this->strings['installing_package'] = __('Installing the latest version&#8230;');
    139         $this->strings['no_files'] = __('The package contains no files.');
    140         $this->strings['folder_exists'] = __('Destination folder already exists.');
    141         $this->strings['mkdir_failed'] = __('Could not create directory.');
    142         $this->strings['incompatible_archive'] = __('The package could not be installed.');
    143         $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.' );
    144 
    145         $this->strings['maintenance_start'] = __('Enabling Maintenance mode&#8230;');
    146         $this->strings['maintenance_end'] = __('Disabling Maintenance mode&#8230;');
    147     }
    148 
    149     /**
    150      * Connect to the filesystem.
    151      *
    152      * @since 2.8.0
    153      * @access public
    154      *
    155      * @global WP_Filesystem_Base $wp_filesystem Subclass
    156      *
    157      * @param array $directories                  Optional. A list of directories. If any of these do
    158      *                                            not exist, a WP_Error object will be returned.
    159      *                                            Default empty array.
    160      * @param bool  $allow_relaxed_file_ownership Whether to allow relaxed file ownership.
    161      *                                            Default false.
    162      * @return bool|WP_Error True if able to connect, false or a WP_Error otherwise.
    163      */
    164     public function fs_connect( $directories = array(), $allow_relaxed_file_ownership = false ) {
    165         global $wp_filesystem;
    166 
    167         if ( false === ( $credentials = $this->skin->request_filesystem_credentials( false, $directories[0], $allow_relaxed_file_ownership ) ) ) {
    168             return false;
    169         }
    170 
    171         if ( ! WP_Filesystem( $credentials, $directories[0], $allow_relaxed_file_ownership ) ) {
    172             $error = true;
    173             if ( is_object($wp_filesystem) && $wp_filesystem->errors->get_error_code() )
    174                 $error = $wp_filesystem->errors;
    175             // Failed to connect, Error and request again
    176             $this->skin->request_filesystem_credentials( $error, $directories[0], $allow_relaxed_file_ownership );
    177             return false;
    178         }
    179 
    180         if ( ! is_object($wp_filesystem) )
    181             return new WP_Error('fs_unavailable', $this->strings['fs_unavailable'] );
    182 
    183         if ( is_wp_error($wp_filesystem->errors) && $wp_filesystem->errors->get_error_code() )
    184             return new WP_Error('fs_error', $this->strings['fs_error'], $wp_filesystem->errors);
    185 
    186         foreach ( (array)$directories as $dir ) {
    187             switch ( $dir ) {
    188                 case ABSPATH:
    189                     if ( ! $wp_filesystem->abspath() )
    190                         return new WP_Error('fs_no_root_dir', $this->strings['fs_no_root_dir']);
    191                     break;
    192                 case WP_CONTENT_DIR:
    193                     if ( ! $wp_filesystem->wp_content_dir() )
    194                         return new WP_Error('fs_no_content_dir', $this->strings['fs_no_content_dir']);
    195                     break;
    196                 case WP_PLUGIN_DIR:
    197                     if ( ! $wp_filesystem->wp_plugins_dir() )
    198                         return new WP_Error('fs_no_plugins_dir', $this->strings['fs_no_plugins_dir']);
    199                     break;
    200                 case get_theme_root():
    201                     if ( ! $wp_filesystem->wp_themes_dir() )
    202                         return new WP_Error('fs_no_themes_dir', $this->strings['fs_no_themes_dir']);
    203                     break;
    204                 default:
    205                     if ( ! $wp_filesystem->find_folder($dir) )
    206                         return new WP_Error( 'fs_no_folder', sprintf( $this->strings['fs_no_folder'], esc_html( basename( $dir ) ) ) );
    207                     break;
    208             }
    209         }
    210         return true;
    211     } //end fs_connect();
    212 
    213     /**
    214      * Download a package.
    215      *
    216      * @since 2.8.0
    217      * @access public
    218      *
    219      * @param string $package The URI of the package. If this is the full path to an
    220      *                        existing local file, it will be returned untouched.
    221      * @return string|WP_Error The full path to the downloaded package file, or a WP_Error object.
    222      */
    223     public function download_package( $package ) {
    224 
    225         /**
    226          * Filter whether to return the package.
    227          *
    228          * @since 3.7.0
    229          * @access public
    230          *
    231          * @param bool        $reply   Whether to bail without returning the package.
    232          *                             Default false.
    233          * @param string      $package The package file name.
    234          * @param WP_Upgrader $this    The WP_Upgrader instance.
    235          */
    236         $reply = apply_filters( 'upgrader_pre_download', false, $package, $this );
    237         if ( false !== $reply )
    238             return $reply;
    239 
    240         if ( ! preg_match('!^(http|https|ftp)://!i', $package) && file_exists($package) ) //Local file or remote?
    241             return $package; //must be a local file..
    242 
    243         if ( empty($package) )
    244             return new WP_Error('no_package', $this->strings['no_package']);
    245 
    246         $this->skin->feedback('downloading_package', $package);
    247 
    248         $download_file = download_url($package);
    249 
    250         if ( is_wp_error($download_file) )
    251             return new WP_Error('download_failed', $this->strings['download_failed'], $download_file->get_error_message());
    252 
    253         return $download_file;
    254     }
    255 
    256     /**
    257      * Unpack a compressed package file.
    258      *
    259      * @since 2.8.0
    260      * @access public
    261      *
    262      * @global WP_Filesystem_Base $wp_filesystem Subclass
    263      *
    264      * @param string $package        Full path to the package file.
    265      * @param bool   $delete_package Optional. Whether to delete the package file after attempting
    266      *                               to unpack it. Default true.
    267      * @return string|WP_Error The path to the unpacked contents, or a WP_Error on failure.
    268      */
    269     public function unpack_package( $package, $delete_package = true ) {
    270         global $wp_filesystem;
    271 
    272         $this->skin->feedback('unpack_package');
    273 
    274         $upgrade_folder = $wp_filesystem->wp_content_dir() . 'upgrade/';
    275 
    276         //Clean up contents of upgrade directory beforehand.
    277         $upgrade_files = $wp_filesystem->dirlist($upgrade_folder);
    278         if ( !empty($upgrade_files) ) {
    279             foreach ( $upgrade_files as $file )
    280                 $wp_filesystem->delete($upgrade_folder . $file['name'], true);
    281         }
    282 
    283         // We need a working directory - Strip off any .tmp or .zip suffixes
    284         $working_dir = $upgrade_folder . basename( basename( $package, '.tmp' ), '.zip' );
    285 
    286         // Clean up working directory
    287         if ( $wp_filesystem->is_dir($working_dir) )
    288             $wp_filesystem->delete($working_dir, true);
    289 
    290         // Unzip package to working directory
    291         $result = unzip_file( $package, $working_dir );
    292 
    293         // Once extracted, delete the package if required.
    294         if ( $delete_package )
    295             unlink($package);
    296 
    297         if ( is_wp_error($result) ) {
    298             $wp_filesystem->delete($working_dir, true);
    299             if ( 'incompatible_archive' == $result->get_error_code() ) {
    300                 return new WP_Error( 'incompatible_archive', $this->strings['incompatible_archive'], $result->get_error_data() );
    301             }
    302             return $result;
    303         }
    304 
    305         return $working_dir;
    306     }
    307 
    308     /**
    309      * Clears the directory where this item is going to be installed into.
    310      *
    311      * @since 4.3.0
    312      * @access public
    313      *
    314      * @global WP_Filesystem_Base $wp_filesystem Subclass
    315      *
    316      * @param string $remote_destination The location on the remote filesystem to be cleared
    317      * @return bool|WP_Error True upon success, WP_Error on failure.
    318      */
    319     public function clear_destination( $remote_destination ) {
    320         global $wp_filesystem;
    321 
    322         if ( ! $wp_filesystem->exists( $remote_destination ) ) {
    323             return true;
    324         }
    325 
    326         // Check all files are writable before attempting to clear the destination.
    327         $unwritable_files = array();
    328 
    329         $_files = $wp_filesystem->dirlist( $remote_destination, true, true );
    330 
    331         // Flatten the resulting array, iterate using each as we append to the array during iteration.
    332         while ( $f = each( $_files ) ) {
    333             $file = $f['value'];
    334             $name = $f['key'];
    335 
    336             if ( ! isset( $file['files'] ) ) {
    337                 continue;
    338             }
    339 
    340             foreach ( $file['files'] as $filename => $details ) {
    341                 $_files[ $name . '/' . $filename ] = $details;
    342             }
    343         }
    344 
    345         // Check writability.
    346         foreach ( $_files as $filename => $file_details ) {
    347             if ( ! $wp_filesystem->is_writable( $remote_destination . $filename ) ) {
    348 
    349                 // Attempt to alter permissions to allow writes and try again.
    350                 $wp_filesystem->chmod( $remote_destination . $filename, ( 'd' == $file_details['type'] ? FS_CHMOD_DIR : FS_CHMOD_FILE ) );
    351                 if ( ! $wp_filesystem->is_writable( $remote_destination . $filename ) ) {
    352                     $unwritable_files[] = $filename;
    353                 }
    354             }
    355         }
    356 
    357         if ( ! empty( $unwritable_files ) ) {
    358             return new WP_Error( 'files_not_writable', $this->strings['files_not_writable'], implode( ', ', $unwritable_files ) );
    359         }
    360 
    361         if ( ! $wp_filesystem->delete( $remote_destination, true ) ) {
    362             return new WP_Error( 'remove_old_failed', $this->strings['remove_old_failed'] );
    363         }
    364 
    365         return true;
    366     }
    367 
    368     /**
    369      * Install a package.
    370      *
    371      * Copies the contents of a package form a source directory, and installs them in
    372      * a destination directory. Optionally removes the source. It can also optionally
    373      * clear out the destination folder if it already exists.
    374      *
    375      * @since 2.8.0
    376      * @access public
    377      *
    378      * @global WP_Filesystem_Base $wp_filesystem Subclass
    379      * @global array              $wp_theme_directories
    380      *
    381      * @param array|string $args {
    382      *     Optional. Array or string of arguments for installing a package. Default empty array.
    383      *
    384      *     @type string $source                      Required path to the package source. Default empty.
    385      *     @type string $destination                 Required path to a folder to install the package in.
    386      *                                               Default empty.
    387      *     @type bool   $clear_destination           Whether to delete any files already in the destination
    388      *                                               folder. Default false.
    389      *     @type bool   $clear_working               Whether to delete the files form the working directory
    390      *                                               after copying to the destination. Default false.
    391      *     @type bool   $abort_if_destination_exists Whether to abort the installation if
    392      *                                               the destination folder already exists. Default true.
    393      *     @type array  $hook_extra                  Extra arguments to pass to the filter hooks called by
    394      *                                               WP_Upgrader::install_package(). Default empty array.
    395      * }
    396      *
    397      * @return array|WP_Error The result (also stored in `WP_Upgrader::$result`), or a WP_Error on failure.
    398      */
    399     public function install_package( $args = array() ) {
    400         global $wp_filesystem, $wp_theme_directories;
    401 
    402         $defaults = array(
    403             'source' => '', // Please always pass this
    404             'destination' => '', // and this
    405             'clear_destination' => false,
    406             'clear_working' => false,
    407             'abort_if_destination_exists' => true,
    408             'hook_extra' => array()
    409         );
    410 
    411         $args = wp_parse_args($args, $defaults);
    412 
    413         // These were previously extract()'d.
    414         $source = $args['source'];
    415         $destination = $args['destination'];
    416         $clear_destination = $args['clear_destination'];
    417 
    418         @set_time_limit( 300 );
    419 
    420         if ( empty( $source ) || empty( $destination ) ) {
    421             return new WP_Error( 'bad_request', $this->strings['bad_request'] );
    422         }
    423         $this->skin->feedback( 'installing_package' );
    424 
    425         /**
    426          * Filter the install response before the installation has started.
    427          *
    428          * Returning a truthy value, or one that could be evaluated as a WP_Error
    429          * will effectively short-circuit the installation, returning that value
    430          * instead.
    431          *
    432          * @since 2.8.0
    433          *
    434          * @param bool|WP_Error $response   Response.
    435          * @param array         $hook_extra Extra arguments passed to hooked filters.
    436          */
    437         $res = apply_filters( 'upgrader_pre_install', true, $args['hook_extra'] );
    438 
    439         if ( is_wp_error( $res ) ) {
    440             return $res;
    441         }
    442 
    443         //Retain the Original source and destinations
    444         $remote_source = $args['source'];
    445         $local_destination = $destination;
    446 
    447         $source_files = array_keys( $wp_filesystem->dirlist( $remote_source ) );
    448         $remote_destination = $wp_filesystem->find_folder( $local_destination );
    449 
    450         //Locate which directory to copy to the new folder, This is based on the actual folder holding the files.
    451         if ( 1 == count( $source_files ) && $wp_filesystem->is_dir( trailingslashit( $args['source'] ) . $source_files[0] . '/' ) ) { //Only one folder? Then we want its contents.
    452             $source = trailingslashit( $args['source'] ) . trailingslashit( $source_files[0] );
    453         } elseif ( count( $source_files ) == 0 ) {
    454             return new WP_Error( 'incompatible_archive_empty', $this->strings['incompatible_archive'], $this->strings['no_files'] ); // There are no files?
    455         } 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.
    456             $source = trailingslashit( $args['source'] );
    457         }
    458 
    459         /**
    460          * Filter the source file location for the upgrade package.
    461          *
    462          * @since 2.8.0
    463          * @since 4.4.0 The $hook_extra parameter became available.
    464          *
    465          * @param string      $source        File source location.
    466          * @param string      $remote_source Remote file source location.
    467          * @param WP_Upgrader $this          WP_Upgrader instance.
    468          * @param array       $hook_extra    Extra arguments passed to hooked filters.
    469          */
    470         $source = apply_filters( 'upgrader_source_selection', $source, $remote_source, $this, $args['hook_extra'] );
    471 
    472         if ( is_wp_error( $source ) ) {
    473             return $source;
    474         }
    475 
    476         // Has the source location changed? If so, we need a new source_files list.
    477         if ( $source !== $remote_source ) {
    478             $source_files = array_keys( $wp_filesystem->dirlist( $source ) );
    479         }
    480 
    481         /*
    482          * Protection against deleting files in any important base directories.
    483          * Theme_Upgrader & Plugin_Upgrader also trigger this, as they pass the
    484          * destination directory (WP_PLUGIN_DIR / wp-content/themes) intending
    485          * to copy the directory into the directory, whilst they pass the source
    486          * as the actual files to copy.
    487          */
    488         $protected_directories = array( ABSPATH, WP_CONTENT_DIR, WP_PLUGIN_DIR, WP_CONTENT_DIR . '/themes' );
    489 
    490         if ( is_array( $wp_theme_directories ) ) {
    491             $protected_directories = array_merge( $protected_directories, $wp_theme_directories );
    492         }
    493 
    494         if ( in_array( $destination, $protected_directories ) ) {
    495             $remote_destination = trailingslashit( $remote_destination ) . trailingslashit( basename( $source ) );
    496             $destination = trailingslashit( $destination ) . trailingslashit( basename( $source ) );
    497         }
    498 
    499         if ( $clear_destination ) {
    500             // We're going to clear the destination if there's something there.
    501             $this->skin->feedback('remove_old');
    502 
    503             $removed = $this->clear_destination( $remote_destination );
    504 
    505             /**
    506              * Filter whether the upgrader cleared the destination.
    507              *
    508              * @since 2.8.0
    509              *
    510              * @param mixed  $removed            Whether the destination was cleared. true on success, WP_Error on failure
    511              * @param string $local_destination  The local package destination.
    512              * @param string $remote_destination The remote package destination.
    513              * @param array  $hook_extra         Extra arguments passed to hooked filters.
    514              */
    515             $removed = apply_filters( 'upgrader_clear_destination', $removed, $local_destination, $remote_destination, $args['hook_extra'] );
    516 
    517             if ( is_wp_error( $removed ) ) {
    518                 return $removed;
    519             }
    520         } elseif ( $args['abort_if_destination_exists'] && $wp_filesystem->exists($remote_destination) ) {
    521             //If we're not clearing the destination folder and something exists there already, Bail.
    522             //But first check to see if there are actually any files in the folder.
    523             $_files = $wp_filesystem->dirlist($remote_destination);
    524             if ( ! empty($_files) ) {
    525                 $wp_filesystem->delete($remote_source, true); //Clear out the source files.
    526                 return new WP_Error('folder_exists', $this->strings['folder_exists'], $remote_destination );
    527             }
    528         }
    529 
    530         //Create destination if needed
    531         if ( ! $wp_filesystem->exists( $remote_destination ) ) {
    532             if ( ! $wp_filesystem->mkdir( $remote_destination, FS_CHMOD_DIR ) ) {
    533                 return new WP_Error( 'mkdir_failed_destination', $this->strings['mkdir_failed'], $remote_destination );
    534             }
    535         }
    536         // Copy new version of item into place.
    537         $result = copy_dir($source, $remote_destination);
    538         if ( is_wp_error($result) ) {
    539             if ( $args['clear_working'] ) {
    540                 $wp_filesystem->delete( $remote_source, true );
    541             }
    542             return $result;
    543         }
    544 
    545         //Clear the Working folder?
    546         if ( $args['clear_working'] ) {
    547             $wp_filesystem->delete( $remote_source, true );
    548         }
    549 
    550         $destination_name = basename( str_replace($local_destination, '', $destination) );
    551         if ( '.' == $destination_name ) {
    552             $destination_name = '';
    553         }
    554 
    555         $this->result = compact( 'source', 'source_files', 'destination', 'destination_name', 'local_destination', 'remote_destination', 'clear_destination' );
    556 
    557         /**
    558          * Filter the install response after the installation has finished.
    559          *
    560          * @since 2.8.0
    561          *
    562          * @param bool  $response   Install response.
    563          * @param array $hook_extra Extra arguments passed to hooked filters.
    564          * @param array $result     Installation result data.
    565          */
    566         $res = apply_filters( 'upgrader_post_install', true, $args['hook_extra'], $this->result );
    567 
    568         if ( is_wp_error($res) ) {
    569             $this->result = $res;
    570             return $res;
    571         }
    572 
    573         //Bombard the calling function will all the info which we've just used.
    574         return $this->result;
    575     }
    576 
    577     /**
    578      * Run an upgrade/install.
    579      *
    580      * Attempts to download the package (if it is not a local file), unpack it, and
    581      * install it in the destination folder.
    582      *
    583      * @since 2.8.0
    584      * @access public
    585      *
    586      * @param array $options {
    587      *     Array or string of arguments for upgrading/installing a package.
    588      *
    589      *     @type string $package                     The full path or URI of the package to install.
    590      *                                               Default empty.
    591      *     @type string $destination                 The full path to the destination folder.
    592      *                                               Default empty.
    593      *     @type bool   $clear_destination           Whether to delete any files already in the
    594      *                                               destination folder. Default false.
    595      *     @type bool   $clear_working               Whether to delete the files form the working
    596      *                                               directory after copying to the destination.
    597      *                                               Default false.
    598      *     @type bool   $abort_if_destination_exists Whether to abort the installation if the destination
    599      *                                               folder already exists. When true, `$clear_destination`
    600      *                                               should be false. Default true.
    601      *     @type bool   $is_multi                    Whether this run is one of multiple upgrade/install
    602      *                                               actions being performed in bulk. When true, the skin
    603      *                                               WP_Upgrader::header() and WP_Upgrader::footer()
    604      *                                               aren't called. Default false.
    605      *     @type array  $hook_extra                  Extra arguments to pass to the filter hooks called by
    606      *                                               WP_Upgrader::run().
    607      * }
    608      * @return array|false|WP_error The result from self::install_package() on success, otherwise a WP_Error,
    609      *                              or false if unable to connect to the filesystem.
    610      */
    611     public function run( $options ) {
    612 
    613         $defaults = array(
    614             'package' => '', // Please always pass this.
    615             'destination' => '', // And this
    616             'clear_destination' => false,
    617             'abort_if_destination_exists' => true, // Abort if the Destination directory exists, Pass clear_destination as false please
    618             'clear_working' => true,
    619             'is_multi' => false,
    620             'hook_extra' => array() // Pass any extra $hook_extra args here, this will be passed to any hooked filters.
    621         );
    622 
    623         $options = wp_parse_args( $options, $defaults );
    624 
    625         /**
    626          * Filter the package options before running an update.
    627          *
    628          * @since 4.3.0
    629          *
    630          * @param array $options {
    631          *     Options used by the upgrader.
    632          *
    633          *     @type string $package                     Package for update.
    634          *     @type string $destination                 Update location.
    635          *     @type bool   $clear_destination           Clear the destination resource.
    636          *     @type bool   $clear_working               Clear the working resource.
    637          *     @type bool   $abort_if_destination_exists Abort if the Destination directory exists.
    638          *     @type bool   $is_multi                    Whether the upgrader is running multiple times.
    639          *     @type array  $hook_extra                  Extra hook arguments.
    640          * }
    641          */
    642         $options = apply_filters( 'upgrader_package_options', $options );
    643 
    644         if ( ! $options['is_multi'] ) { // call $this->header separately if running multiple times
    645             $this->skin->header();
    646         }
    647 
    648         // Connect to the Filesystem first.
    649         $res = $this->fs_connect( array( WP_CONTENT_DIR, $options['destination'] ) );
    650         // Mainly for non-connected filesystem.
    651         if ( ! $res ) {
    652             if ( ! $options['is_multi'] ) {
    653                 $this->skin->footer();
    654             }
    655             return false;
    656         }
    657 
    658         $this->skin->before();
    659 
    660         if ( is_wp_error($res) ) {
    661             $this->skin->error($res);
    662             $this->skin->after();
    663             if ( ! $options['is_multi'] ) {
    664                 $this->skin->footer();
    665             }
    666             return $res;
    667         }
    668 
    669         /*
    670          * Download the package (Note, This just returns the filename
    671          * of the file if the package is a local file)
    672          */
    673         $download = $this->download_package( $options['package'] );
    674         if ( is_wp_error($download) ) {
    675             $this->skin->error($download);
    676             $this->skin->after();
    677             if ( ! $options['is_multi'] ) {
    678                 $this->skin->footer();
    679             }
    680             return $download;
    681         }
    682 
    683         $delete_package = ( $download != $options['package'] ); // Do not delete a "local" file
    684 
    685         // Unzips the file into a temporary directory.
    686         $working_dir = $this->unpack_package( $download, $delete_package );
    687         if ( is_wp_error($working_dir) ) {
    688             $this->skin->error($working_dir);
    689             $this->skin->after();
    690             if ( ! $options['is_multi'] ) {
    691                 $this->skin->footer();
    692             }
    693             return $working_dir;
    694         }
    695 
    696         // With the given options, this installs it to the destination directory.
    697         $result = $this->install_package( array(
    698             'source' => $working_dir,
    699             'destination' => $options['destination'],
    700             'clear_destination' => $options['clear_destination'],
    701             'abort_if_destination_exists' => $options['abort_if_destination_exists'],
    702             'clear_working' => $options['clear_working'],
    703             'hook_extra' => $options['hook_extra']
    704         ) );
    705 
    706         $this->skin->set_result($result);
    707         if ( is_wp_error($result) ) {
    708             $this->skin->error($result);
    709             $this->skin->feedback('process_failed');
    710         } else {
    711             // Install succeeded.
    712             $this->skin->feedback('process_success');
    713         }
    714 
    715         $this->skin->after();
    716 
    717         if ( ! $options['is_multi'] ) {
    718 
    719             /** This action is documented in wp-admin/includes/class-wp-upgrader.php */
    720             do_action( 'upgrader_process_complete', $this, $options['hook_extra'] );
    721             $this->skin->footer();
    722         }
    723 
    724         return $result;
    725     }
    726 
    727     /**
    728      * Toggle maintenance mode for the site.
    729      *
    730      * Creates/deletes the maintenance file to enable/disable maintenance mode.
    731      *
    732      * @since 2.8.0
    733      * @access public
    734      *
    735      * @global WP_Filesystem_Base $wp_filesystem Subclass
    736      *
    737      * @param bool $enable True to enable maintenance mode, false to disable.
    738      */
    739     public function maintenance_mode( $enable = false ) {
    740         global $wp_filesystem;
    741         $file = $wp_filesystem->abspath() . '.maintenance';
    742         if ( $enable ) {
    743             $this->skin->feedback('maintenance_start');
    744             // Create maintenance file to signal that we are upgrading
    745             $maintenance_string = '<?php $upgrading = ' . time() . '; ?>';
    746             $wp_filesystem->delete($file);
    747             $wp_filesystem->put_contents($file, $maintenance_string, FS_CHMOD_FILE);
    748         } elseif ( ! $enable && $wp_filesystem->exists( $file ) ) {
    749             $this->skin->feedback('maintenance_end');
    750             $wp_filesystem->delete($file);
    751         }
    752     }
    753 
    754     /**
    755      * Creates a lock using WordPress options.
    756      *
    757      * @since 4.5.0
    758      * @access public
    759      * @static
    760      *
    761      * @param string $lock_name       The name of this unique lock.
    762      * @param int    $release_timeout Optional. The duration in seconds to respect an existing lock.
    763      *                                Default: 1 hour.
    764      * @return bool False if a lock couldn't be created or if the lock is no longer valid. True otherwise.
    765      */
    766     public static function create_lock( $lock_name, $release_timeout = null ) {
    767         global $wpdb;
    768         if ( ! $release_timeout ) {
    769             $release_timeout = HOUR_IN_SECONDS;
    770         }
    771         $lock_option = $lock_name . '.lock';
    772 
    773         // Try to lock.
    774         $lock_result = $wpdb->query( $wpdb->prepare( "INSERT IGNORE INTO `$wpdb->options` ( `option_name`, `option_value`, `autoload` ) VALUES (%s, %s, 'no') /* LOCK */", $lock_option, time() ) );
    775 
    776         if ( ! $lock_result ) {
    777             $lock_result = get_option( $lock_option );
    778 
    779             // If a lock couldn't be created, and there isn't a lock, bail.
    780             if ( ! $lock_result ) {
    781                 return false;
    782             }
    783 
    784             // Check to see if the lock is still valid. If not, bail.
    785             if ( $lock_result > ( time() - $release_timeout ) ) {
    786                 return false;
    787             }
    788 
    789             // There must exist an expired lock, clear it and re-gain it.
    790             WP_Upgrader::release_lock( $lock_name );
    791 
    792             return WP_Upgrader::create_lock( $lock_name, $release_timeout );
    793         }
    794 
    795         // Update the lock, as by this point we've definitely got a lock, just need to fire the actions.
    796         update_option( $lock_option, time() );
    797 
    798         return true;
    799     }
    800 
    801     /**
    802      * Releases an upgrader lock.
    803      *
    804      * @since 4.5.0
    805      * @access public
    806      * @static
    807      *
    808      * @see WP_Upgrader::create_lock()
    809      *
    810      * @param string $lock_name The name of this unique lock.
    811      * @return bool True if the lock was successfully released. False on failure.
    812      */
    813     public static function release_lock( $lock_name ) {
    814         return delete_option( $lock_name . '.lock' );
    815     }
    816 
    817 }
    818 
    819 /**
    820  * Core class used for upgrading/installing plugins.
    821  *
    822  * It is designed to upgrade/install plugins from a local zip, remote zip URL,
    823  * or uploaded zip file.
    824  *
    825  * @since 2.8.0
    826  *
    827  * @see WP_Upgrader
    828  */
    829 class Plugin_Upgrader extends WP_Upgrader {
    830 
    831     /**
    832      * Plugin upgrade result.
    833      *
    834      * @since 2.8.0
    835      * @access public
    836      * @var array|WP_Error $result
    837      *
    838      * @see WP_Upgrader::$result
    839      */
    840     public $result;
    841 
    842     /**
    843      * Whether a bulk upgrade/install is being performed.
    844      *
    845      * @since 2.9.0
    846      * @access public
    847      * @var bool $bulk
    848      */
    849     public $bulk = false;
    850 
    851     /**
    852      * Initialize the upgrade strings.
    853      *
    854      * @since 2.8.0
    855      * @access public
    856      */
    857     public function upgrade_strings() {
    858         $this->strings['up_to_date'] = __('The plugin is at the latest version.');
    859         $this->strings['no_package'] = __('Update package not available.');
    860         $this->strings['downloading_package'] = __('Downloading update from <span class="code">%s</span>&#8230;');
    861         $this->strings['unpack_package'] = __('Unpacking the update&#8230;');
    862         $this->strings['remove_old'] = __('Removing the old version of the plugin&#8230;');
    863         $this->strings['remove_old_failed'] = __('Could not remove the old plugin.');
    864         $this->strings['process_failed'] = __('Plugin update failed.');
    865         $this->strings['process_success'] = __('Plugin updated successfully.');
    866         $this->strings['process_bulk_success'] = __('Plugins updated successfully.');
    867     }
    868 
    869     /**
    870      * Initialize the install strings.
    871      *
    872      * @since 2.8.0
    873      * @access public
    874      */
    875     public function install_strings() {
    876         $this->strings['no_package'] = __('Install package not available.');
    877         $this->strings['downloading_package'] = __('Downloading install package from <span class="code">%s</span>&#8230;');
    878         $this->strings['unpack_package'] = __('Unpacking the package&#8230;');
    879         $this->strings['installing_package'] = __('Installing the plugin&#8230;');
    880         $this->strings['no_files'] = __('The plugin contains no files.');
    881         $this->strings['process_failed'] = __('Plugin install failed.');
    882         $this->strings['process_success'] = __('Plugin installed successfully.');
    883     }
    884 
    885     /**
    886      * Install a plugin package.
    887      *
    888      * @since 2.8.0
    889      * @since 3.7.0 The `$args` parameter was added, making clearing the plugin update cache optional.
    890      * @access public
    891      *
    892      * @param string $package The full local path or URI of the package.
    893      * @param array  $args {
    894      *     Optional. Other arguments for installing a plugin package. Default empty array.
    895      *
    896      *     @type bool $clear_update_cache Whether to clear the plugin updates cache if successful.
    897      *                                    Default true.
    898      * }
    899      * @return bool|WP_Error True if the install was successful, false or a WP_Error otherwise.
    900      */
    901     public function install( $package, $args = array() ) {
    902 
    903         $defaults = array(
    904             'clear_update_cache' => true,
    905         );
    906         $parsed_args = wp_parse_args( $args, $defaults );
    907 
    908         $this->init();
    909         $this->install_strings();
    910 
    911         add_filter('upgrader_source_selection', array($this, 'check_package') );
    912         // Clear cache so wp_update_plugins() knows about the new plugin.
    913         add_action( 'upgrader_process_complete', 'wp_clean_plugins_cache', 9, 0 );
    914 
    915         $this->run( array(
    916             'package' => $package,
    917             'destination' => WP_PLUGIN_DIR,
    918             'clear_destination' => false, // Do not overwrite files.
    919             'clear_working' => true,
    920             'hook_extra' => array(
    921                 'type' => 'plugin',
    922                 'action' => 'install',
    923             )
    924         ) );
    925 
    926         remove_action( 'upgrader_process_complete', 'wp_clean_plugins_cache', 9 );
    927         remove_filter('upgrader_source_selection', array($this, 'check_package') );
    928 
    929         if ( ! $this->result || is_wp_error($this->result) )
    930             return $this->result;
    931 
    932         // Force refresh of plugin update information
    933         wp_clean_plugins_cache( $parsed_args['clear_update_cache'] );
    934 
    935         return true;
    936     }
    937 
    938     /**
    939      * Upgrade a plugin.
    940      *
    941      * @since 2.8.0
    942      * @since 3.7.0 The `$args` parameter was added, making clearing the plugin update cache optional.
    943      * @access public
    944      *
    945      * @param string $plugin The basename path to the main plugin file.
    946      * @param array  $args {
    947      *     Optional. Other arguments for upgrading a plugin package. Default empty array.
    948      *
    949      *     @type bool $clear_update_cache Whether to clear the plugin updates cache if successful.
    950      *                                    Default true.
    951      * }
    952      * @return bool|WP_Error True if the upgrade was successful, false or a WP_Error object otherwise.
    953      */
    954     public function upgrade( $plugin, $args = array() ) {
    955 
    956         $defaults = array(
    957             'clear_update_cache' => true,
    958         );
    959         $parsed_args = wp_parse_args( $args, $defaults );
    960 
    961         $this->init();
    962         $this->upgrade_strings();
    963 
    964         $current = get_site_transient( 'update_plugins' );
    965         if ( !isset( $current->response[ $plugin ] ) ) {
    966             $this->skin->before();
    967             $this->skin->set_result(false);
    968             $this->skin->error('up_to_date');
    969             $this->skin->after();
    970             return false;
    971         }
    972 
    973         // Get the URL to the zip file
    974         $r = $current->response[ $plugin ];
    975 
    976         add_filter('upgrader_pre_install', array($this, 'deactivate_plugin_before_upgrade'), 10, 2);
    977         add_filter('upgrader_clear_destination', array($this, 'delete_old_plugin'), 10, 4);
    978         //'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.
    979         // Clear cache so wp_update_plugins() knows about the new plugin.
    980         add_action( 'upgrader_process_complete', 'wp_clean_plugins_cache', 9, 0 );
    981 
    982         $this->run( array(
    983             'package' => $r->package,
    984             'destination' => WP_PLUGIN_DIR,
    985             'clear_destination' => true,
    986             'clear_working' => true,
    987             'hook_extra' => array(
    988                 'plugin' => $plugin,
    989                 'type' => 'plugin',
    990                 'action' => 'update',
    991             ),
    992         ) );
    993 
    994         // Cleanup our hooks, in case something else does a upgrade on this connection.
    995         remove_action( 'upgrader_process_complete', 'wp_clean_plugins_cache', 9 );
    996         remove_filter('upgrader_pre_install', array($this, 'deactivate_plugin_before_upgrade'));
    997         remove_filter('upgrader_clear_destination', array($this, 'delete_old_plugin'));
    998 
    999         if ( ! $this->result || is_wp_error($this->result) )
    1000             return $this->result;
    1001 
    1002         // Force refresh of plugin update information
    1003         wp_clean_plugins_cache( $parsed_args['clear_update_cache'] );
    1004 
    1005         return true;
    1006     }
    1007 
    1008     /**
    1009      * Bulk upgrade several plugins at once.
    1010      *
    1011      * @since 2.8.0
    1012      * @since 3.7.0 The `$args` parameter was added, making clearing the plugin update cache optional.
    1013      * @access public
    1014      *
    1015      * @param array $plugins Array of the basename paths of the plugins' main files.
    1016      * @param array $args {
    1017      *     Optional. Other arguments for upgrading several plugins at once. Default empty array.
    1018      *
    1019      *     @type bool $clear_update_cache Whether to clear the plugin updates cache if successful.
    1020      *                                    Default true.
    1021      * }
    1022      * @return array|false An array of results indexed by plugin file, or false if unable to connect to the filesystem.
    1023      */
    1024     public function bulk_upgrade( $plugins, $args = array() ) {
    1025 
    1026         $defaults = array(
    1027             'clear_update_cache' => true,
    1028         );
    1029         $parsed_args = wp_parse_args( $args, $defaults );
    1030 
    1031         $this->init();
    1032         $this->bulk = true;
    1033         $this->upgrade_strings();
    1034 
    1035         $current = get_site_transient( 'update_plugins' );
    1036 
    1037         add_filter('upgrader_clear_destination', array($this, 'delete_old_plugin'), 10, 4);
    1038         add_action( 'upgrader_process_complete', 'wp_clean_plugins_cache', 9, 0 );
    1039 
    1040         $this->skin->header();
    1041 
    1042         // Connect to the Filesystem first.
    1043         $res = $this->fs_connect( array(WP_CONTENT_DIR, WP_PLUGIN_DIR) );
    1044         if ( ! $res ) {
    1045             $this->skin->footer();
    1046             return false;
    1047         }
    1048 
    1049         $this->skin->bulk_header();
    1050 
    1051         /*
    1052          * Only start maintenance mode if:
    1053          * - running Multisite and there are one or more plugins specified, OR
    1054          * - a plugin with an update available is currently active.
    1055          * @TODO: For multisite, maintenance mode should only kick in for individual sites if at all possible.
    1056          */
    1057         $maintenance = ( is_multisite() && ! empty( $plugins ) );
    1058         foreach ( $plugins as $plugin )
    1059             $maintenance = $maintenance || ( is_plugin_active( $plugin ) && isset( $current->response[ $plugin] ) );
    1060         if ( $maintenance )
    1061             $this->maintenance_mode(true);
    1062 
    1063         $results = array();
    1064 
    1065         $this->update_count = count($plugins);
    1066         $this->update_current = 0;
    1067         foreach ( $plugins as $plugin ) {
    1068             $this->update_current++;
    1069             $this->skin->plugin_info = get_plugin_data( WP_PLUGIN_DIR . '/' . $plugin, false, true);
    1070 
    1071             if ( !isset( $current->response[ $plugin ] ) ) {
    1072                 $this->skin->set_result('up_to_date');
    1073                 $this->skin->before();
    1074                 $this->skin->feedback('up_to_date');
    1075                 $this->skin->after();
    1076                 $results[$plugin] = true;
    1077                 continue;
    1078             }
    1079 
    1080             // Get the URL to the zip file.
    1081             $r = $current->response[ $plugin ];
    1082 
    1083             $this->skin->plugin_active = is_plugin_active($plugin);
    1084 
    1085             $result = $this->run( array(
    1086                 'package' => $r->package,
    1087                 'destination' => WP_PLUGIN_DIR,
    1088                 'clear_destination' => true,
    1089                 'clear_working' => true,
    1090                 'is_multi' => true,
    1091                 'hook_extra' => array(
    1092                     'plugin' => $plugin
    1093                 )
    1094             ) );
    1095 
    1096             $results[$plugin] = $this->result;
    1097 
    1098             // Prevent credentials auth screen from displaying multiple times
    1099             if ( false === $result )
    1100                 break;
    1101         } //end foreach $plugins
    1102 
    1103         $this->maintenance_mode(false);
    1104 
    1105         /**
    1106          * Fires when the bulk upgrader process is complete.
    1107          *
    1108          * @since 3.6.0
    1109          *
    1110          * @param Plugin_Upgrader $this Plugin_Upgrader instance. In other contexts, $this, might
    1111          *                              be a Theme_Upgrader or Core_Upgrade instance.
    1112          * @param array           $data {
    1113          *     Array of bulk item update data.
    1114          *
    1115          *     @type string $action   Type of action. Default 'update'.
    1116          *     @type string $type     Type of update process. Accepts 'plugin', 'theme', or 'core'.
    1117          *     @type bool   $bulk     Whether the update process is a bulk update. Default true.
    1118          *     @type array  $packages Array of plugin, theme, or core packages to update.
    1119          * }
    1120          */
    1121         do_action( 'upgrader_process_complete', $this, array(
    1122             'action' => 'update',
    1123             'type' => 'plugin',
    1124             'bulk' => true,
    1125             'plugins' => $plugins,
    1126         ) );
    1127 
    1128         $this->skin->bulk_footer();
    1129 
    1130         $this->skin->footer();
    1131 
    1132         // Cleanup our hooks, in case something else does a upgrade on this connection.
    1133         remove_action( 'upgrader_process_complete', 'wp_clean_plugins_cache', 9 );
    1134         remove_filter('upgrader_clear_destination', array($this, 'delete_old_plugin'));
    1135 
    1136         // Force refresh of plugin update information.
    1137         wp_clean_plugins_cache( $parsed_args['clear_update_cache'] );
    1138 
    1139         return $results;
    1140     }
    1141 
    1142     /**
    1143      * Check a source package to be sure it contains a plugin.
    1144      *
    1145      * This function is added to the {@see 'upgrader_source_selection'} filter by
    1146      * Plugin_Upgrader::install().
    1147      *
    1148      * @since 3.3.0
    1149      * @access public
    1150      *
    1151      * @global WP_Filesystem_Base $wp_filesystem Subclass
    1152      *
    1153      * @param string $source The path to the downloaded package source.
    1154      * @return string|WP_Error The source as passed, or a WP_Error object
    1155      *                         if no plugins were found.
    1156      */
    1157     public function check_package($source) {
    1158         global $wp_filesystem;
    1159 
    1160         if ( is_wp_error($source) )
    1161             return $source;
    1162 
    1163         $working_directory = str_replace( $wp_filesystem->wp_content_dir(), trailingslashit(WP_CONTENT_DIR), $source);
    1164         if ( ! is_dir($working_directory) ) // Sanity check, if the above fails, let's not prevent installation.
    1165             return $source;
    1166 
    1167         // Check the folder contains at least 1 valid plugin.
    1168         $plugins_found = false;
    1169         $files = glob( $working_directory . '*.php' );
    1170         if ( $files ) {
    1171             foreach ( $files as $file ) {
    1172                 $info = get_plugin_data( $file, false, false );
    1173                 if ( ! empty( $info['Name'] ) ) {
    1174                     $plugins_found = true;
    1175                     break;
    1176                 }
    1177             }
    1178         }
    1179 
    1180         if ( ! $plugins_found )
    1181             return new WP_Error( 'incompatible_archive_no_plugins', $this->strings['incompatible_archive'], __( 'No valid plugins were found.' ) );
    1182 
    1183         return $source;
    1184     }
    1185 
    1186     /**
    1187      * Retrieve the path to the file that contains the plugin info.
    1188      *
    1189      * This isn't used internally in the class, but is called by the skins.
    1190      *
    1191      * @since 2.8.0
    1192      * @access public
    1193      *
    1194      * @return string|false The full path to the main plugin file, or false.
    1195      */
    1196     public function plugin_info() {
    1197         if ( ! is_array($this->result) )
    1198             return false;
    1199         if ( empty($this->result['destination_name']) )
    1200             return false;
    1201 
    1202         $plugin = get_plugins('/' . $this->result['destination_name']); //Ensure to pass with leading slash
    1203         if ( empty($plugin) )
    1204             return false;
    1205 
    1206         $pluginfiles = array_keys($plugin); //Assume the requested plugin is the first in the list
    1207 
    1208         return $this->result['destination_name'] . '/' . $pluginfiles[0];
    1209     }
    1210 
    1211     /**
    1212      * Deactivates a plugin before it is upgraded.
    1213      *
    1214      * Hooked to the {@see 'upgrader_pre_install'} filter by Plugin_Upgrader::upgrade().
    1215      *
    1216      * @since 2.8.0
    1217      * @since 4.1.0 Added a return value.
    1218      * @access public
    1219      *
    1220      * @param bool|WP_Error  $return Upgrade offer return.
    1221      * @param array          $plugin Plugin package arguments.
    1222      * @return bool|WP_Error The passed in $return param or WP_Error.
    1223      */
    1224     public function deactivate_plugin_before_upgrade($return, $plugin) {
    1225 
    1226         if ( is_wp_error($return) ) //Bypass.
    1227             return $return;
    1228 
    1229         // When in cron (background updates) don't deactivate the plugin, as we require a browser to reactivate it
    1230         if ( defined( 'DOING_CRON' ) && DOING_CRON )
    1231             return $return;
    1232 
    1233         $plugin = isset($plugin['plugin']) ? $plugin['plugin'] : '';
    1234         if ( empty($plugin) )
    1235             return new WP_Error('bad_request', $this->strings['bad_request']);
    1236 
    1237         if ( is_plugin_active($plugin) ) {
    1238             //Deactivate the plugin silently, Prevent deactivation hooks from running.
    1239             deactivate_plugins($plugin, true);
    1240         }
    1241 
    1242         return $return;
    1243     }
    1244 
    1245     /**
    1246      * Delete the old plugin during an upgrade.
    1247      *
    1248      * Hooked to the {@see 'upgrader_clear_destination'} filter by
    1249      * Plugin_Upgrader::upgrade() and Plugin_Upgrader::bulk_upgrade().
    1250      *
    1251      * @since 2.8.0
    1252      * @access public
    1253      *
    1254      * @global WP_Filesystem_Base $wp_filesystem Subclass
    1255      *
    1256      * @param bool|WP_Error $removed
    1257      * @param string        $local_destination
    1258      * @param string        $remote_destination
    1259      * @param array         $plugin
    1260      * @return WP_Error|bool
    1261      */
    1262     public function delete_old_plugin($removed, $local_destination, $remote_destination, $plugin) {
    1263         global $wp_filesystem;
    1264 
    1265         if ( is_wp_error($removed) )
    1266             return $removed; //Pass errors through.
    1267 
    1268         $plugin = isset($plugin['plugin']) ? $plugin['plugin'] : '';
    1269         if ( empty($plugin) )
    1270             return new WP_Error('bad_request', $this->strings['bad_request']);
    1271 
    1272         $plugins_dir = $wp_filesystem->wp_plugins_dir();
    1273         $this_plugin_dir = trailingslashit( dirname($plugins_dir . $plugin) );
    1274 
    1275         if ( ! $wp_filesystem->exists($this_plugin_dir) ) //If it's already vanished.
    1276             return $removed;
    1277 
    1278         // If plugin is in its own directory, recursively delete the directory.
    1279         if ( strpos($plugin, '/') && $this_plugin_dir != $plugins_dir ) //base check on if plugin includes directory separator AND that it's not the root plugin folder
    1280             $deleted = $wp_filesystem->delete($this_plugin_dir, true);
    1281         else
    1282             $deleted = $wp_filesystem->delete($plugins_dir . $plugin);
    1283 
    1284         if ( ! $deleted )
    1285             return new WP_Error('remove_old_failed', $this->strings['remove_old_failed']);
    1286 
    1287         return true;
    1288     }
    1289 }
    1290 
    1291 /**
    1292  * Core class used for upgrading/installing themes.
    1293  *
    1294  * It is designed to upgrade/install themes from a local zip, remote zip URL,
    1295  * or uploaded zip file.
    1296  *
    1297  * @since 2.8.0
    1298  *
    1299  * @see WP_Upgrader
    1300  */
    1301 class Theme_Upgrader extends WP_Upgrader {
    1302 
    1303     /**
    1304      * Result of the theme upgrade offer.
    1305      *
    1306      * @since 2.8.0
    1307      * @access public
    1308      * @var array|WP_Error $result
    1309      * @see WP_Upgrader::$result
    1310      */
    1311     public $result;
    1312 
    1313     /**
    1314      * Whether multiple themes are being upgraded/installed in bulk.
    1315      *
    1316      * @since 2.9.0
    1317      * @access public
    1318      * @var bool $bulk
    1319      */
    1320     public $bulk = false;
    1321 
    1322     /**
    1323      * Initialize the upgrade strings.
    1324      *
    1325      * @since 2.8.0
    1326      * @access public
    1327      */
    1328     public function upgrade_strings() {
    1329         $this->strings['up_to_date'] = __('The theme is at the latest version.');
    1330         $this->strings['no_package'] = __('Update package not available.');
    1331         $this->strings['downloading_package'] = __('Downloading update from <span class="code">%s</span>&#8230;');
    1332         $this->strings['unpack_package'] = __('Unpacking the update&#8230;');
    1333         $this->strings['remove_old'] = __('Removing the old version of the theme&#8230;');
    1334         $this->strings['remove_old_failed'] = __('Could not remove the old theme.');
    1335         $this->strings['process_failed'] = __('Theme update failed.');
    1336         $this->strings['process_success'] = __('Theme updated successfully.');
    1337     }
    1338 
    1339     /**
    1340      * Initialize the install strings.
    1341      *
    1342      * @since 2.8.0
    1343      * @access public
    1344      */
    1345     public function install_strings() {
    1346         $this->strings['no_package'] = __('Install package not available.');
    1347         $this->strings['downloading_package'] = __('Downloading install package from <span class="code">%s</span>&#8230;');
    1348         $this->strings['unpack_package'] = __('Unpacking the package&#8230;');
    1349         $this->strings['installing_package'] = __('Installing the theme&#8230;');
    1350         $this->strings['no_files'] = __('The theme contains no files.');
    1351         $this->strings['process_failed'] = __('Theme install failed.');
    1352         $this->strings['process_success'] = __('Theme installed successfully.');
    1353         /* translators: 1: theme name, 2: version */
    1354         $this->strings['process_success_specific'] = __('Successfully installed the theme <strong>%1$s %2$s</strong>.');
    1355         $this->strings['parent_theme_search'] = __('This theme requires a parent theme. Checking if it is installed&#8230;');
    1356         /* translators: 1: theme name, 2: version */
    1357         $this->strings['parent_theme_prepare_install'] = __('Preparing to install <strong>%1$s %2$s</strong>&#8230;');
    1358         /* translators: 1: theme name, 2: version */
    1359         $this->strings['parent_theme_currently_installed'] = __('The parent theme, <strong>%1$s %2$s</strong>, is currently installed.');
    1360         /* translators: 1: theme name, 2: version */
    1361         $this->strings['parent_theme_install_success'] = __('Successfully installed the parent theme, <strong>%1$s %2$s</strong>.');
    1362         $this->strings['parent_theme_not_found'] = __('<strong>The parent theme could not be found.</strong> You will need to install the parent theme, <strong>%s</strong>, before you can use this child theme.');
    1363     }
    1364 
    1365     /**
    1366      * Check if a child theme is being installed and we need to install its parent.
    1367      *
    1368      * Hooked to the {@see 'upgrader_post_install'} filter by Theme_Upgrader::install().
    1369      *
    1370      * @since 3.4.0
    1371      * @access public
    1372      *
    1373      * @param bool  $install_result
    1374      * @param array $hook_extra
    1375      * @param array $child_result
    1376      * @return type
    1377      */
    1378     public function check_parent_theme_filter( $install_result, $hook_extra, $child_result ) {
    1379         // Check to see if we need to install a parent theme
    1380         $theme_info = $this->theme_info();
    1381 
    1382         if ( ! $theme_info->parent() )
    1383             return $install_result;
    1384 
    1385         $this->skin->feedback( 'parent_theme_search' );
    1386 
    1387         if ( ! $theme_info->parent()->errors() ) {
    1388             $this->skin->feedback( 'parent_theme_currently_installed', $theme_info->parent()->display('Name'), $theme_info->parent()->display('Version') );
    1389             // We already have the theme, fall through.
    1390             return $install_result;
    1391         }
    1392 
    1393         // We don't have the parent theme, let's install it.
    1394         $api = themes_api('theme_information', array('slug' => $theme_info->get('Template'), 'fields' => array('sections' => false, 'tags' => false) ) ); //Save on a bit of bandwidth.
    1395 
    1396         if ( ! $api || is_wp_error($api) ) {
    1397             $this->skin->feedback( 'parent_theme_not_found', $theme_info->get('Template') );
    1398             // Don't show activate or preview actions after install
    1399             add_filter('install_theme_complete_actions', array($this, 'hide_activate_preview_actions') );
    1400             return $install_result;
    1401         }
    1402 
    1403         // Backup required data we're going to override:
    1404         $child_api = $this->skin->api;
    1405         $child_success_message = $this->strings['process_success'];
    1406 
    1407         // Override them
    1408         $this->skin->api = $api;
    1409         $this->strings['process_success_specific'] = $this->strings['parent_theme_install_success'];//, $api->name, $api->version);
    1410 
    1411         $this->skin->feedback('parent_theme_prepare_install', $api->name, $api->version);
    1412 
    1413         add_filter('install_theme_complete_actions', '__return_false', 999); // Don't show any actions after installing the theme.
    1414 
    1415         // Install the parent theme
    1416         $parent_result = $this->run( array(
    1417             'package' => $api->download_link,
    1418             'destination' => get_theme_root(),
    1419             'clear_destination' => false, //Do not overwrite files.
    1420             'clear_working' => true
    1421         ) );
    1422 
    1423         if ( is_wp_error($parent_result) )
    1424             add_filter('install_theme_complete_actions', array($this, 'hide_activate_preview_actions') );
    1425 
    1426         // Start cleaning up after the parents installation
    1427         remove_filter('install_theme_complete_actions', '__return_false', 999);
    1428 
    1429         // Reset child's result and data
    1430         $this->result = $child_result;
    1431         $this->skin->api = $child_api;
    1432         $this->strings['process_success'] = $child_success_message;
    1433 
    1434         return $install_result;
    1435     }
    1436 
    1437     /**
    1438      * Don't display the activate and preview actions to the user.
    1439      *
    1440      * Hooked to the {@see 'install_theme_complete_actions'} filter by
    1441      * Theme_Upgrader::check_parent_theme_filter() when installing
    1442      * a child theme and installing the parent theme fails.
    1443      *
    1444      * @since 3.4.0
    1445      * @access public
    1446      *
    1447      * @param array $actions Preview actions.
    1448      * @return array
    1449      */
    1450     public function hide_activate_preview_actions( $actions ) {
    1451         unset($actions['activate'], $actions['preview']);
    1452         return $actions;
    1453     }
    1454 
    1455     /**
    1456      * Install a theme package.
    1457      *
    1458      * @since 2.8.0
    1459      * @since 3.7.0 The `$args` parameter was added, making clearing the update cache optional.
    1460      * @access public
    1461      *
    1462      * @param string $package The full local path or URI of the package.
    1463      * @param array  $args {
    1464      *     Optional. Other arguments for installing a theme package. Default empty array.
    1465      *
    1466      *     @type bool $clear_update_cache Whether to clear the updates cache if successful.
    1467      *                                    Default true.
    1468      * }
    1469      *
    1470      * @return bool|WP_Error True if the install was successful, false or a WP_Error object otherwise.
    1471      */
    1472     public function install( $package, $args = array() ) {
    1473 
    1474         $defaults = array(
    1475             'clear_update_cache' => true,
    1476         );
    1477         $parsed_args = wp_parse_args( $args, $defaults );
    1478 
    1479         $this->init();
    1480         $this->install_strings();
    1481 
    1482         add_filter('upgrader_source_selection', array($this, 'check_package') );
    1483         add_filter('upgrader_post_install', array($this, 'check_parent_theme_filter'), 10, 3);
    1484         // Clear cache so wp_update_themes() knows about the new theme.
    1485         add_action( 'upgrader_process_complete', 'wp_clean_themes_cache', 9, 0 );
    1486 
    1487         $this->run( array(
    1488             'package' => $package,
    1489             'destination' => get_theme_root(),
    1490             'clear_destination' => false, //Do not overwrite files.
    1491             'clear_working' => true,
    1492             'hook_extra' => array(
    1493                 'type' => 'theme',
    1494                 'action' => 'install',
    1495             ),
    1496         ) );
    1497 
    1498         remove_action( 'upgrader_process_complete', 'wp_clean_themes_cache', 9 );
    1499         remove_filter('upgrader_source_selection', array($this, 'check_package') );
    1500         remove_filter('upgrader_post_install', array($this, 'check_parent_theme_filter'));
    1501 
    1502         if ( ! $this->result || is_wp_error($this->result) )
    1503             return $this->result;
    1504 
    1505         // Refresh the Theme Update information
    1506         wp_clean_themes_cache( $parsed_args['clear_update_cache'] );
    1507 
    1508         return true;
    1509     }
    1510 
    1511     /**
    1512      * Upgrade a theme.
    1513      *
    1514      * @since 2.8.0
    1515      * @since 3.7.0 The `$args` parameter was added, making clearing the update cache optional.
    1516      * @access public
    1517      *
    1518      * @param string $theme The theme slug.
    1519      * @param array  $args {
    1520      *     Optional. Other arguments for upgrading a theme. Default empty array.
    1521      *
    1522      *     @type bool $clear_update_cache Whether to clear the update cache if successful.
    1523      *                                    Default true.
    1524      * }
    1525      * @return bool|WP_Error True if the upgrade was successful, false or a WP_Error object otherwise.
    1526      */
    1527     public function upgrade( $theme, $args = array() ) {
    1528 
    1529         $defaults = array(
    1530             'clear_update_cache' => true,
    1531         );
    1532         $parsed_args = wp_parse_args( $args, $defaults );
    1533 
    1534         $this->init();
    1535         $this->upgrade_strings();
    1536 
    1537         // Is an update available?
    1538         $current = get_site_transient( 'update_themes' );
    1539         if ( !isset( $current->response[ $theme ] ) ) {
    1540             $this->skin->before();
    1541             $this->skin->set_result(false);
    1542             $this->skin->error( 'up_to_date' );
    1543             $this->skin->after();
    1544             return false;
    1545         }
    1546 
    1547         $r = $current->response[ $theme ];
    1548 
    1549         add_filter('upgrader_pre_install', array($this, 'current_before'), 10, 2);
    1550         add_filter('upgrader_post_install', array($this, 'current_after'), 10, 2);
    1551         add_filter('upgrader_clear_destination', array($this, 'delete_old_theme'), 10, 4);
    1552         // Clear cache so wp_update_themes() knows about the new theme.
    1553         add_action( 'upgrader_process_complete', 'wp_clean_themes_cache', 9, 0 );
    1554 
    1555         $this->run( array(
    1556             'package' => $r['package'],
    1557             'destination' => get_theme_root( $theme ),
    1558             'clear_destination' => true,
    1559             'clear_working' => true,
    1560             'hook_extra' => array(
    1561                 'theme' => $theme,
    1562                 'type' => 'theme',
    1563                 'action' => 'update',
    1564             ),
    1565         ) );
    1566 
    1567         remove_action( 'upgrader_process_complete', 'wp_clean_themes_cache', 9 );
    1568         remove_filter('upgrader_pre_install', array($this, 'current_before'));
    1569         remove_filter('upgrader_post_install', array($this, 'current_after'));
    1570         remove_filter('upgrader_clear_destination', array($this, 'delete_old_theme'));
    1571 
    1572         if ( ! $this->result || is_wp_error($this->result) )
    1573             return $this->result;
    1574 
    1575         wp_clean_themes_cache( $parsed_args['clear_update_cache'] );
    1576 
    1577         return true;
    1578     }
    1579 
    1580     /**
    1581      * Upgrade several themes at once.
    1582      *
    1583      * @since 3.0.0
    1584      * @since 3.7.0 The `$args` parameter was added, making clearing the update cache optional.
    1585      * @access public
    1586      *
    1587      * @param array $themes The theme slugs.
    1588      * @param array $args {
    1589      *     Optional. Other arguments for upgrading several themes at once. Default empty array.
    1590      *
    1591      *     @type bool $clear_update_cache Whether to clear the update cache if successful.
    1592      *                                    Default true.
    1593      * }
    1594      * @return array[]|false An array of results, or false if unable to connect to the filesystem.
    1595      */
    1596     public function bulk_upgrade( $themes, $args = array() ) {
    1597 
    1598         $defaults = array(
    1599             'clear_update_cache' => true,
    1600         );
    1601         $parsed_args = wp_parse_args( $args, $defaults );
    1602 
    1603         $this->init();
    1604         $this->bulk = true;
    1605         $this->upgrade_strings();
    1606 
    1607         $current = get_site_transient( 'update_themes' );
    1608 
    1609         add_filter('upgrader_pre_install', array($this, 'current_before'), 10, 2);
    1610         add_filter('upgrader_post_install', array($this, 'current_after'), 10, 2);
    1611         add_filter('upgrader_clear_destination', array($this, 'delete_old_theme'), 10, 4);
    1612         // Clear cache so wp_update_themes() knows about the new theme.
    1613         add_action( 'upgrader_process_complete', 'wp_clean_themes_cache', 9, 0 );
    1614 
    1615         $this->skin->header();
    1616 
    1617         // Connect to the Filesystem first.
    1618         $res = $this->fs_connect( array(WP_CONTENT_DIR) );
    1619         if ( ! $res ) {
    1620             $this->skin->footer();
    1621             return false;
    1622         }
    1623 
    1624         $this->skin->bulk_header();
    1625 
    1626         // Only start maintenance mode if:
    1627         // - running Multisite and there are one or more themes specified, OR
    1628         // - a theme with an update available is currently in use.
    1629         // @TODO: For multisite, maintenance mode should only kick in for individual sites if at all possible.
    1630         $maintenance = ( is_multisite() && ! empty( $themes ) );
    1631         foreach ( $themes as $theme )
    1632             $maintenance = $maintenance || $theme == get_stylesheet() || $theme == get_template();
    1633         if ( $maintenance )
    1634             $this->maintenance_mode(true);
    1635 
    1636         $results = array();
    1637 
    1638         $this->update_count = count($themes);
    1639         $this->update_current = 0;
    1640         foreach ( $themes as $theme ) {
    1641             $this->update_current++;
    1642 
    1643             $this->skin->theme_info = $this->theme_info($theme);
    1644 
    1645             if ( !isset( $current->response[ $theme ] ) ) {
    1646                 $this->skin->set_result(true);
    1647                 $this->skin->before();
    1648                 $this->skin->feedback( 'up_to_date' );
    1649                 $this->skin->after();
    1650                 $results[$theme] = true;
    1651                 continue;
    1652             }
    1653 
    1654             // Get the URL to the zip file
    1655             $r = $current->response[ $theme ];
    1656 
    1657             $result = $this->run( array(
    1658                 'package' => $r['package'],
    1659                 'destination' => get_theme_root( $theme ),
    1660                 'clear_destination' => true,
    1661                 'clear_working' => true,
    1662                 'is_multi' => true,
    1663                 'hook_extra' => array(
    1664                     'theme' => $theme
    1665                 ),
    1666             ) );
    1667 
    1668             $results[$theme] = $this->result;
    1669 
    1670             // Prevent credentials auth screen from displaying multiple times
    1671             if ( false === $result )
    1672                 break;
    1673         } //end foreach $plugins
    1674 
    1675         $this->maintenance_mode(false);
    1676 
    1677         /** This action is documented in wp-admin/includes/class-wp-upgrader.php */
    1678         do_action( 'upgrader_process_complete', $this, array(
    1679             'action' => 'update',
    1680             'type' => 'theme',
    1681             'bulk' => true,
    1682             'themes' => $themes,
    1683         ) );
    1684 
    1685         $this->skin->bulk_footer();
    1686 
    1687         $this->skin->footer();
    1688 
    1689         // Cleanup our hooks, in case something else does a upgrade on this connection.
    1690         remove_action( 'upgrader_process_complete', 'wp_clean_themes_cache', 9 );
    1691         remove_filter('upgrader_pre_install', array($this, 'current_before'));
    1692         remove_filter('upgrader_post_install', array($this, 'current_after'));
    1693         remove_filter('upgrader_clear_destination', array($this, 'delete_old_theme'));
    1694 
    1695         // Refresh the Theme Update information
    1696         wp_clean_themes_cache( $parsed_args['clear_update_cache'] );
    1697 
    1698         return $results;
    1699     }
    1700 
    1701     /**
    1702      * Check that the package source contains a valid theme.
    1703      *
    1704      * Hooked to the {@see 'upgrader_source_selection'} filter by Theme_Upgrader::install().
    1705      * It will return an error if the theme doesn't have style.css or index.php
    1706      * files.
    1707      *
    1708      * @since 3.3.0
    1709      * @access public
    1710      *
    1711      * @global WP_Filesystem_Base $wp_filesystem Subclass
    1712      *
    1713      * @param string $source The full path to the package source.
    1714      * @return string|WP_Error The source or a WP_Error.
    1715      */
    1716     public function check_package( $source ) {
    1717         global $wp_filesystem;
    1718 
    1719         if ( is_wp_error($source) )
    1720             return $source;
    1721 
    1722         // Check the folder contains a valid theme
    1723         $working_directory = str_replace( $wp_filesystem->wp_content_dir(), trailingslashit(WP_CONTENT_DIR), $source);
    1724         if ( ! is_dir($working_directory) ) // Sanity check, if the above fails, let's not prevent installation.
    1725             return $source;
    1726 
    1727         // A proper archive should have a style.css file in the single subdirectory
    1728         if ( ! file_exists( $working_directory . 'style.css' ) ) {
    1729             return new WP_Error( 'incompatible_archive_theme_no_style', $this->strings['incompatible_archive'],
    1730                 /* translators: %s: style.css */
    1731                 sprintf( __( 'The theme is missing the %s stylesheet.' ),
    1732                     '<code>style.css</code>'
    1733                 )
    1734             );
    1735         }
    1736 
    1737         $info = get_file_data( $working_directory . 'style.css', array( 'Name' => 'Theme Name', 'Template' => 'Template' ) );
    1738 
    1739         if ( empty( $info['Name'] ) ) {
    1740             return new WP_Error( 'incompatible_archive_theme_no_name', $this->strings['incompatible_archive'],
    1741                 /* translators: %s: style.css */
    1742                 sprintf( __( 'The %s stylesheet doesn&#8217;t contain a valid theme header.' ),
    1743                     '<code>style.css</code>'
    1744                 )
    1745             );
    1746         }
    1747 
    1748         // If it's not a child theme, it must have at least an index.php to be legit.
    1749         if ( empty( $info['Template'] ) && ! file_exists( $working_directory . 'index.php' ) ) {
    1750             return new WP_Error( 'incompatible_archive_theme_no_index', $this->strings['incompatible_archive'],
    1751                 /* translators: %s: index.php */
    1752                 sprintf( __( 'The theme is missing the %s file.' ),
    1753                     '<code>index.php</code>'
    1754                 )
    1755             );
    1756         }
    1757 
    1758         return $source;
    1759     }
    1760 
    1761     /**
    1762      * Turn on maintenance mode before attempting to upgrade the current theme.
    1763      *
    1764      * Hooked to the {@see 'upgrader_pre_install'} filter by Theme_Upgrader::upgrade() and
    1765      * Theme_Upgrader::bulk_upgrade().
    1766      *
    1767      * @since 2.8.0
    1768      * @access public
    1769      *
    1770      * @param bool|WP_Error  $return
    1771      * @param array          $theme
    1772      * @return bool|WP_Error
    1773      */
    1774     public function current_before($return, $theme) {
    1775         if ( is_wp_error($return) )
    1776             return $return;
    1777 
    1778         $theme = isset($theme['theme']) ? $theme['theme'] : '';
    1779 
    1780         if ( $theme != get_stylesheet() ) //If not current
    1781             return $return;
    1782         //Change to maintenance mode now.
    1783         if ( ! $this->bulk )
    1784             $this->maintenance_mode(true);
    1785 
    1786         return $return;
    1787     }
    1788 
    1789     /**
    1790      * Turn off maintenance mode after upgrading the current theme.
    1791      *
    1792      * Hooked to the {@see 'upgrader_post_install'} filter by Theme_Upgrader::upgrade()
    1793      * and Theme_Upgrader::bulk_upgrade().
    1794      *
    1795      * @since 2.8.0
    1796      * @access public
    1797      *
    1798      * @param bool|WP_Error  $return
    1799      * @param array          $theme
    1800      * @return bool|WP_Error
    1801      */
    1802     public function current_after($return, $theme) {
    1803         if ( is_wp_error($return) )
    1804             return $return;
    1805 
    1806         $theme = isset($theme['theme']) ? $theme['theme'] : '';
    1807 
    1808         if ( $theme != get_stylesheet() ) // If not current
    1809             return $return;
    1810 
    1811         // Ensure stylesheet name hasn't changed after the upgrade:
    1812         if ( $theme == get_stylesheet() && $theme != $this->result['destination_name'] ) {
    1813             wp_clean_themes_cache();
    1814             $stylesheet = $this->result['destination_name'];
    1815             switch_theme( $stylesheet );
    1816         }
    1817 
    1818         //Time to remove maintenance mode
    1819         if ( ! $this->bulk )
    1820             $this->maintenance_mode(false);
    1821         return $return;
    1822     }
    1823 
    1824     /**
    1825      * Delete the old theme during an upgrade.
    1826      *
    1827      * Hooked to the {@see 'upgrader_clear_destination'} filter by Theme_Upgrader::upgrade()
    1828      * and Theme_Upgrader::bulk_upgrade().
    1829      *
    1830      * @since 2.8.0
    1831      * @access public
    1832      *
    1833      * @global WP_Filesystem_Base $wp_filesystem Subclass
    1834      *
    1835      * @param bool   $removed
    1836      * @param string $local_destination
    1837      * @param string $remote_destination
    1838      * @param array  $theme
    1839      * @return bool
    1840      */
    1841     public function delete_old_theme( $removed, $local_destination, $remote_destination, $theme ) {
    1842         global $wp_filesystem;
    1843 
    1844         if ( is_wp_error( $removed ) )
    1845             return $removed; // Pass errors through.
    1846 
    1847         if ( ! isset( $theme['theme'] ) )
    1848             return $removed;
    1849 
    1850         $theme = $theme['theme'];
    1851         $themes_dir = trailingslashit( $wp_filesystem->wp_themes_dir( $theme ) );
    1852         if ( $wp_filesystem->exists( $themes_dir . $theme ) ) {
    1853             if ( ! $wp_filesystem->delete( $themes_dir . $theme, true ) )
    1854                 return false;
    1855         }
    1856 
    1857         return true;
    1858     }
    1859 
    1860     /**
    1861      * Get the WP_Theme object for a theme.
    1862      *
    1863      * @since 2.8.0
    1864      * @since 3.0.0 The `$theme` argument was added.
    1865      * @access public
    1866      *
    1867      * @param string $theme The directory name of the theme. This is optional, and if not supplied,
    1868      *                      the directory name from the last result will be used.
    1869      * @return WP_Theme|false The theme's info object, or false `$theme` is not supplied
    1870      *                        and the last result isn't set.
    1871      */
    1872     public function theme_info($theme = null) {
    1873 
    1874         if ( empty($theme) ) {
    1875             if ( !empty($this->result['destination_name']) )
    1876                 $theme = $this->result['destination_name'];
    1877             else
    1878                 return false;
    1879         }
    1880         return wp_get_theme( $theme );
    1881     }
    1882 
    1883 }
    1884 
    1885 /**
    1886  * Core class used for updating/installing language packs (translations)
    1887  * for plugins, themes, and core.
    1888  *
    1889  * @since 3.7.0
    1890  *
    1891  * @see WP_Upgrader
    1892  */
    1893 class Language_Pack_Upgrader extends WP_Upgrader {
    1894 
    1895     /**
    1896      * Result of the language pack upgrade.
    1897      *
    1898      * @since 3.7.0
    1899      * @access public
    1900      * @var array|WP_Error $result
    1901      * @see WP_Upgrader::$result
    1902      */
    1903     public $result;
    1904 
    1905     /**
    1906      * Whether a bulk upgrade/install is being performed.
    1907      *
    1908      * @since 3.7.0
    1909      * @access public
    1910      * @var bool $bulk
    1911      */
    1912     public $bulk = true;
    1913 
    1914     /**
    1915      * Asynchronously upgrades language packs after other upgrades have been made.
    1916      *
    1917      * Hooked to the {@see 'upgrader_process_complete'} action by default.
    1918      *
    1919      * @since 3.7.0
    1920      * @access public
    1921      * @static
    1922      *
    1923      * @param false|WP_Upgrader $upgrader Optional. WP_Upgrader instance or false. If `$upgrader` is
    1924      *                                    a Language_Pack_Upgrader instance, the method will bail to
    1925      *                                    avoid recursion. Otherwise unused. Default false.
    1926      */
    1927     public static function async_upgrade( $upgrader = false ) {
    1928         // Avoid recursion.
    1929         if ( $upgrader && $upgrader instanceof Language_Pack_Upgrader ) {
    1930             return;
    1931         }
    1932 
    1933         // Nothing to do?
    1934         $language_updates = wp_get_translation_updates();
    1935         if ( ! $language_updates ) {
    1936             return;
    1937         }
    1938 
    1939         /*
    1940          * Avoid messing with VCS installs, at least for now.
    1941          * Noted: this is not the ideal way to accomplish this.
    1942          */
    1943         $check_vcs = new WP_Automatic_Updater;
    1944         if ( $check_vcs->is_vcs_checkout( WP_CONTENT_DIR ) ) {
    1945             return;
    1946         }
    1947 
    1948         foreach ( $language_updates as $key => $language_update ) {
    1949             $update = ! empty( $language_update->autoupdate );
    1950 
    1951             /**
    1952              * Filter whether to asynchronously update translation for core, a plugin, or a theme.
    1953              *
    1954              * @since 4.0.0
    1955              *
    1956              * @param bool   $update          Whether to update.
    1957              * @param object $language_update The update offer.
    1958              */
    1959             $update = apply_filters( 'async_update_translation', $update, $language_update );
    1960 
    1961             if ( ! $update ) {
    1962                 unset( $language_updates[ $key ] );
    1963             }
    1964         }
    1965 
    1966         if ( empty( $language_updates ) ) {
    1967             return;
    1968         }
    1969 
    1970         // Re-use the automatic upgrader skin if the parent upgrader is using it.
    1971         if ( $upgrader && $upgrader->skin instanceof Automatic_Upgrader_Skin ) {
    1972             $skin = $upgrader->skin;
    1973         } else {
    1974             $skin = new Language_Pack_Upgrader_Skin( array(
    1975                 'skip_header_footer' => true,
    1976             ) );
    1977         }
    1978 
    1979         $lp_upgrader = new Language_Pack_Upgrader( $skin );
    1980         $lp_upgrader->bulk_upgrade( $language_updates );
    1981     }
    1982 
    1983     /**
    1984      * Initialize the upgrade strings.
    1985      *
    1986      * @since 3.7.0
    1987      * @access public
    1988      */
    1989     public function upgrade_strings() {
    1990         $this->strings['starting_upgrade'] = __( 'Some of your translations need updating. Sit tight for a few more seconds while we update them as well.' );
    1991         $this->strings['up_to_date'] = __( 'The translation is up to date.' ); // We need to silently skip this case
    1992         $this->strings['no_package'] = __( 'Update package not available.' );
    1993         $this->strings['downloading_package'] = __( 'Downloading translation from <span class="code">%s</span>&#8230;' );
    1994         $this->strings['unpack_package'] = __( 'Unpacking the update&#8230;' );
    1995         $this->strings['process_failed'] = __( 'Translation update failed.' );
    1996         $this->strings['process_success'] = __( 'Translation updated successfully.' );
    1997     }
    1998 
    1999     /**
    2000      * Upgrade a language pack.
    2001      *
    2002      * @since 3.7.0
    2003      * @access public
    2004      *
    2005      * @param string|false $update Optional. Whether an update offer is available. Default false.
    2006      * @param array        $args   Optional. Other optional arguments, see
    2007      *                             Language_Pack_Upgrader::bulk_upgrade(). Default empty array.
    2008      * @return array|bool|WP_Error The result of the upgrade, or a WP_Error object instead.
    2009      */
    2010     public function upgrade( $update = false, $args = array() ) {
    2011         if ( $update ) {
    2012             $update = array( $update );
    2013         }
    2014 
    2015         $results = $this->bulk_upgrade( $update, $args );
    2016 
    2017         if ( ! is_array( $results ) ) {
    2018             return $results;
    2019         }
    2020 
    2021         return $results[0];
    2022     }
    2023 
    2024     /**
    2025      * Bulk upgrade language packs.
    2026      *
    2027      * @since 3.7.0
    2028      * @access public
    2029      *
    2030      * @global WP_Filesystem_Base $wp_filesystem Subclass
    2031      *
    2032      * @param array $language_updates Optional. Language pack updates. Default empty array.
    2033      * @param array $args {
    2034      *     Optional. Other arguments for upgrading multiple language packs. Default empty array
    2035      *
    2036      *     @type bool $clear_update_cache Whether to clear the update cache when done.
    2037      *                                    Default true.
    2038      * }
    2039      * @return array|bool|WP_Error Will return an array of results, or true if there are no updates,
    2040      *                                   false or WP_Error for initial errors.
    2041      */
    2042     public function bulk_upgrade( $language_updates = array(), $args = array() ) {
    2043         global $wp_filesystem;
    2044 
    2045         $defaults = array(
    2046             'clear_update_cache' => true,
    2047         );
    2048         $parsed_args = wp_parse_args( $args, $defaults );
    2049 
    2050         $this->init();
    2051         $this->upgrade_strings();
    2052 
    2053         if ( ! $language_updates )
    2054             $language_updates = wp_get_translation_updates();
    2055 
    2056         if ( empty( $language_updates ) ) {
    2057             $this->skin->header();
    2058             $this->skin->before();
    2059             $this->skin->set_result( true );
    2060             $this->skin->feedback( 'up_to_date' );
    2061             $this->skin->after();
    2062             $this->skin->bulk_footer();
    2063             $this->skin->footer();
    2064             return true;
    2065         }
    2066 
    2067         if ( 'upgrader_process_complete' == current_filter() )
    2068             $this->skin->feedback( 'starting_upgrade' );
    2069 
    2070         // Remove any existing upgrade filters from the plugin/theme upgraders #WP29425 & #WP29230
    2071         remove_all_filters( 'upgrader_pre_install' );
    2072         remove_all_filters( 'upgrader_clear_destination' );
    2073         remove_all_filters( 'upgrader_post_install' );
    2074         remove_all_filters( 'upgrader_source_selection' );
    2075 
    2076         add_filter( 'upgrader_source_selection', array( $this, 'check_package' ), 10, 2 );
    2077 
    2078         $this->skin->header();
    2079 
    2080         // Connect to the Filesystem first.
    2081         $res = $this->fs_connect( array( WP_CONTENT_DIR, WP_LANG_DIR ) );
    2082         if ( ! $res ) {
    2083             $this->skin->footer();
    2084             return false;
    2085         }
    2086 
    2087         $results = array();
    2088 
    2089         $this->update_count = count( $language_updates );
    2090         $this->update_current = 0;
    2091 
    2092         /*
    2093          * The filesystem's mkdir() is not recursive. Make sure WP_LANG_DIR exists,
    2094          * as we then may need to create a /plugins or /themes directory inside of it.
    2095          */
    2096         $remote_destination = $wp_filesystem->find_folder( WP_LANG_DIR );
    2097         if ( ! $wp_filesystem->exists( $remote_destination ) )
    2098             if ( ! $wp_filesystem->mkdir( $remote_destination, FS_CHMOD_DIR ) )
    2099                 return new WP_Error( 'mkdir_failed_lang_dir', $this->strings['mkdir_failed'], $remote_destination );
    2100 
    2101         foreach ( $language_updates as $language_update ) {
    2102 
    2103             $this->skin->language_update = $language_update;
    2104 
    2105             $destination = WP_LANG_DIR;
    2106             if ( 'plugin' == $language_update->type )
    2107                 $destination .= '/plugins';
    2108             elseif ( 'theme' == $language_update->type )
    2109                 $destination .= '/themes';
    2110 
    2111             $this->update_current++;
    2112 
    2113             $options = array(
    2114                 'package' => $language_update->package,
    2115                 'destination' => $destination,
    2116                 'clear_destination' => false,
    2117                 'abort_if_destination_exists' => false, // We expect the destination to exist.
    2118                 'clear_working' => true,
    2119                 'is_multi' => true,
    2120                 'hook_extra' => array(
    2121                     'language_update_type' => $language_update->type,
    2122                     'language_update' => $language_update,
    2123                 )
    2124             );
    2125 
    2126             $result = $this->run( $options );
    2127 
    2128             $results[] = $this->result;
    2129 
    2130             // Prevent credentials auth screen from displaying multiple times.
    2131             if ( false === $result )
    2132                 break;
    2133         }
    2134 
    2135         $this->skin->bulk_footer();
    2136 
    2137         $this->skin->footer();
    2138 
    2139         // Clean up our hooks, in case something else does an upgrade on this connection.
    2140         remove_filter( 'upgrader_source_selection', array( $this, 'check_package' ) );
    2141 
    2142         if ( $parsed_args['clear_update_cache'] ) {
    2143             wp_clean_update_cache();
    2144         }
    2145 
    2146         return $results;
    2147     }
    2148 
    2149     /**
    2150      * Check the package source to make sure there are .mo and .po files.
    2151      *
    2152      * Hooked to the {@see 'upgrader_source_selection'} filter by
    2153      * Language_Pack_Upgrader::bulk_upgrade().
    2154      *
    2155      * @since 3.7.0
    2156      * @access public
    2157      *
    2158      * @global WP_Filesystem_Base $wp_filesystem Subclass
    2159      *
    2160      * @param string|WP_Error $source
    2161      * @param string          $remote_source
    2162      */
    2163     public function check_package( $source, $remote_source ) {
    2164         global $wp_filesystem;
    2165 
    2166         if ( is_wp_error( $source ) )
    2167             return $source;
    2168 
    2169         // Check that the folder contains a valid language.
    2170         $files = $wp_filesystem->dirlist( $remote_source );
    2171 
    2172         // Check to see if a .po and .mo exist in the folder.
    2173         $po = $mo = false;
    2174         foreach ( (array) $files as $file => $filedata ) {
    2175             if ( '.po' == substr( $file, -3 ) )
    2176                 $po = true;
    2177             elseif ( '.mo' == substr( $file, -3 ) )
    2178                 $mo = true;
    2179         }
    2180 
    2181         if ( ! $mo || ! $po ) {
    2182             return new WP_Error( 'incompatible_archive_pomo', $this->strings['incompatible_archive'],
    2183                 /* translators: 1: .po 2: .mo */
    2184                 sprintf( __( 'The language pack is missing either the %1$s or %2$s files.' ),
    2185                     '<code>.po</code>',
    2186                     '<code>.mo</code>'
    2187                 )
    2188             );
    2189         }
    2190 
    2191         return $source;
    2192     }
    2193 
    2194     /**
    2195      * Get the name of an item being updated.
    2196      *
    2197      * @since 3.7.0
    2198      * @access public
    2199      *
    2200      * @param object $update The data for an update.
    2201      * @return string The name of the item being updated.
    2202      */
    2203     public function get_name_for_update( $update ) {
    2204         switch ( $update->type ) {
    2205             case 'core':
    2206                 return 'WordPress'; // Not translated
    2207 
    2208             case 'theme':
    2209                 $theme = wp_get_theme( $update->slug );
    2210                 if ( $theme->exists() )
    2211                     return $theme->Get( 'Name' );
    2212                 break;
    2213             case 'plugin':
    2214                 $plugin_data = get_plugins( '/' . $update->slug );
    2215                 $plugin_data = reset( $plugin_data );
    2216                 if ( $plugin_data )
    2217                     return $plugin_data['Name'];
    2218                 break;
    2219         }
    2220         return '';
    2221     }
    2222 
    2223 }
    2224 
    2225 /**
    2226  * Core class used for updating core.
    2227  *
    2228  * It allows for WordPress to upgrade itself in combination with
    2229  * the wp-admin/includes/update-core.php file.
    2230  *
    2231  * @since 2.8.0
    2232  *
    2233  * @see WP_Upgrader
    2234  */
    2235 class Core_Upgrader extends WP_Upgrader {
    2236 
    2237     /**
    2238      * Initialize the upgrade strings.
    2239      *
    2240      * @since 2.8.0
    2241      * @access public
    2242      */
    2243     public function upgrade_strings() {
    2244         $this->strings['up_to_date'] = __('WordPress is at the latest version.');
    2245         $this->strings['locked'] = __('Another update is currently in progress.');
    2246         $this->strings['no_package'] = __('Update package not available.');
    2247         $this->strings['downloading_package'] = __('Downloading update from <span class="code">%s</span>&#8230;');
    2248         $this->strings['unpack_package'] = __('Unpacking the update&#8230;');
    2249         $this->strings['copy_failed'] = __('Could not copy files.');
    2250         $this->strings['copy_failed_space'] = __('Could not copy files. You may have run out of disk space.' );
    2251         $this->strings['start_rollback'] = __( 'Attempting to roll back to previous version.' );
    2252         $this->strings['rollback_was_required'] = __( 'Due to an error during updating, WordPress has rolled back to your previous version.' );
    2253     }
    2254 
    2255     /**
    2256      * Upgrade WordPress core.
    2257      *
    2258      * @since 2.8.0
    2259      * @access public
    2260      *
    2261      * @global WP_Filesystem_Base $wp_filesystem Subclass
    2262      * @global callable           $_wp_filesystem_direct_method
    2263      *
    2264      * @param object $current Response object for whether WordPress is current.
    2265      * @param array  $args {
    2266      *        Optional. Arguments for upgrading WordPress core. Default empty array.
    2267      *
    2268      *        @type bool $pre_check_md5    Whether to check the file checksums before
    2269      *                                     attempting the upgrade. Default true.
    2270      *        @type bool $attempt_rollback Whether to attempt to rollback the chances if
    2271      *                                     there is a problem. Default false.
    2272      *        @type bool $do_rollback      Whether to perform this "upgrade" as a rollback.
    2273      *                                     Default false.
    2274      * }
    2275      * @return null|false|WP_Error False or WP_Error on failure, null on success.
    2276      */
    2277     public function upgrade( $current, $args = array() ) {
    2278         global $wp_filesystem;
    2279 
    2280         include( ABSPATH . WPINC . '/version.php' ); // $wp_version;
    2281 
    2282         $start_time = time();
    2283 
    2284         $defaults = array(
    2285             'pre_check_md5'    => true,
    2286             'attempt_rollback' => false,
    2287             'do_rollback'      => false,
    2288             'allow_relaxed_file_ownership' => false,
    2289         );
    2290         $parsed_args = wp_parse_args( $args, $defaults );
    2291 
    2292         $this->init();
    2293         $this->upgrade_strings();
    2294 
    2295         // Is an update available?
    2296         if ( !isset( $current->response ) || $current->response == 'latest' )
    2297             return new WP_Error('up_to_date', $this->strings['up_to_date']);
    2298 
    2299         $res = $this->fs_connect( array( ABSPATH, WP_CONTENT_DIR ), $parsed_args['allow_relaxed_file_ownership'] );
    2300         if ( ! $res || is_wp_error( $res ) ) {
    2301             return $res;
    2302         }
    2303 
    2304         $wp_dir = trailingslashit($wp_filesystem->abspath());
    2305 
    2306         $partial = true;
    2307         if ( $parsed_args['do_rollback'] )
    2308             $partial = false;
    2309         elseif ( $parsed_args['pre_check_md5'] && ! $this->check_files() )
    2310             $partial = false;
    2311 
    2312         /*
    2313          * If partial update is returned from the API, use that, unless we're doing
    2314          * a reinstall. If we cross the new_bundled version number, then use
    2315          * the new_bundled zip. Don't though if the constant is set to skip bundled items.
    2316          * If the API returns a no_content zip, go with it. Finally, default to the full zip.
    2317          */
    2318         if ( $parsed_args['do_rollback'] && $current->packages->rollback )
    2319             $to_download = 'rollback';
    2320         elseif ( $current->packages->partial && 'reinstall' != $current->response && $wp_version == $current->partial_version && $partial )
    2321             $to_download = 'partial';
    2322         elseif ( $current->packages->new_bundled && version_compare( $wp_version, $current->new_bundled, '<' )
    2323             && ( ! defined( 'CORE_UPGRADE_SKIP_NEW_BUNDLED' ) || ! CORE_UPGRADE_SKIP_NEW_BUNDLED ) )
    2324             $to_download = 'new_bundled';
    2325         elseif ( $current->packages->no_content )
    2326             $to_download = 'no_content';
    2327         else
    2328             $to_download = 'full';
    2329 
    2330         // Lock to prevent multiple Core Updates occuring
    2331         $lock = WP_Upgrader::create_lock( 'core_updater', 15 * MINUTE_IN_SECONDS );