Make WordPress Core


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

Upgrader: Copy WP_Upgrader subclasses into one file per class.

Part 4/8.
See #36618.

File:
1 copied

Legend:

Unmodified
Added
Removed
  • trunk/src/wp-admin/includes/class-wp-automatic-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 }
    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 );
    2332         if ( ! $lock ) {
    2333             return new WP_Error( 'locked', $this->strings['locked'] );
    2334         }
    2335 
    2336         $download = $this->download_package( $current->packages->$to_download );
    2337         if ( is_wp_error( $download ) ) {
    2338             WP_Upgrader::release_lock( 'core_updater' );
    2339             return $download;
    2340         }
    2341 
    2342         $working_dir = $this->unpack_package( $download );
    2343         if ( is_wp_error( $working_dir ) ) {
    2344             WP_Upgrader::release_lock( 'core_updater' );
    2345             return $working_dir;
    2346         }
    2347 
    2348         // Copy update-core.php from the new version into place.
    2349         if ( !$wp_filesystem->copy($working_dir . '/wordpress/wp-admin/includes/update-core.php', $wp_dir . 'wp-admin/includes/update-core.php', true) ) {
    2350             $wp_filesystem->delete($working_dir, true);
    2351             WP_Upgrader::release_lock( 'core_updater' );
    2352             return new WP_Error( 'copy_failed_for_update_core_file', __( 'The update cannot be installed because we will be unable to copy some files. This is usually due to inconsistent file permissions.' ), 'wp-admin/includes/update-core.php' );
    2353         }
    2354         $wp_filesystem->chmod($wp_dir . 'wp-admin/includes/update-core.php', FS_CHMOD_FILE);
    2355 
    2356         require_once( ABSPATH . 'wp-admin/includes/update-core.php' );
    2357 
    2358         if ( ! function_exists( 'update_core' ) ) {
    2359             WP_Upgrader::release_lock( 'core_updater' );
    2360             return new WP_Error( 'copy_failed_space', $this->strings['copy_failed_space'] );
    2361         }
    2362 
    2363         $result = update_core( $working_dir, $wp_dir );
    2364 
    2365         // In the event of an issue, we may be able to roll back.
    2366         if ( $parsed_args['attempt_rollback'] && $current->packages->rollback && ! $parsed_args['do_rollback'] ) {
    2367             $try_rollback = false;
    2368             if ( is_wp_error( $result ) ) {
    2369                 $error_code = $result->get_error_code();
    2370                 /*
    2371                  * Not all errors are equal. These codes are critical: copy_failed__copy_dir,
    2372                  * mkdir_failed__copy_dir, copy_failed__copy_dir_retry, and disk_full.
    2373                  * do_rollback allows for update_core() to trigger a rollback if needed.
    2374                  */
    2375                 if ( false !== strpos( $error_code, 'do_rollback' ) )
    2376                     $try_rollback = true;
    2377                 elseif ( false !== strpos( $error_code, '__copy_dir' ) )
    2378                     $try_rollback = true;
    2379                 elseif ( 'disk_full' === $error_code )
    2380                     $try_rollback = true;
    2381             }
    2382 
    2383             if ( $try_rollback ) {
    2384                 /** This filter is documented in wp-admin/includes/update-core.php */
    2385                 apply_filters( 'update_feedback', $result );
    2386 
    2387                 /** This filter is documented in wp-admin/includes/update-core.php */
    2388                 apply_filters( 'update_feedback', $this->strings['start_rollback'] );
    2389 
    2390                 $rollback_result = $this->upgrade( $current, array_merge( $parsed_args, array( 'do_rollback' => true ) ) );
    2391 
    2392                 $original_result = $result;
    2393                 $result = new WP_Error( 'rollback_was_required', $this->strings['rollback_was_required'], (object) array( 'update' => $original_result, 'rollback' => $rollback_result ) );
    2394             }
    2395         }
    2396 
    2397         /** This action is documented in wp-admin/includes/class-wp-upgrader.php */
    2398         do_action( 'upgrader_process_complete', $this, array( 'action' => 'update', 'type' => 'core' ) );
    2399 
    2400         // Clear the current updates
    2401         delete_site_transient( 'update_core' );
    2402 
    2403         if ( ! $parsed_args['do_rollback'] ) {
    2404             $stats = array(
    2405                 'update_type'      => $current->response,
    2406                 'success'          => true,
    2407                 'fs_method'        => $wp_filesystem->method,
    2408                 'fs_method_forced' => defined( 'FS_METHOD' ) || has_filter( 'filesystem_method' ),
    2409                 'fs_method_direct' => !empty( $GLOBALS['_wp_filesystem_direct_method'] ) ? $GLOBALS['_wp_filesystem_direct_method'] : '',
    2410                 'time_taken'       => time() - $start_time,
    2411                 'reported'         => $wp_version,
    2412                 'attempted'        => $current->version,
    2413             );
    2414 
    2415             if ( is_wp_error( $result ) ) {
    2416                 $stats['success'] = false;
    2417                 // Did a rollback occur?
    2418                 if ( ! empty( $try_rollback ) ) {
    2419                     $stats['error_code'] = $original_result->get_error_code();
    2420                     $stats['error_data'] = $original_result->get_error_data();
    2421                     // Was the rollback successful? If not, collect its error too.
    2422                     $stats['rollback'] = ! is_wp_error( $rollback_result );
    2423                     if ( is_wp_error( $rollback_result ) ) {
    2424                         $stats['rollback_code'] = $rollback_result->get_error_code();
    2425                         $stats['rollback_data'] = $rollback_result->get_error_data();
    2426                     }
    2427                 } else {
    2428                     $stats['error_code'] = $result->get_error_code();
    2429                     $stats['error_data'] = $result->get_error_data();
    2430                 }
    2431             }
    2432 
    2433             wp_version_check( $stats );
    2434         }
    2435 
    2436         WP_Upgrader::release_lock( 'core_updater' );
    2437 
    2438         return $result;
    2439     }
    2440 
    2441     /**
    2442      * Determines if this WordPress Core version should update to an offered version or not.
    2443      *
    2444      * @since 3.7.0
    2445      * @access public
    2446      *
    2447      * @static
    2448      *
    2449      * @param string $offered_ver The offered version, of the format x.y.z.
    2450      * @return bool True if we should update to the offered version, otherwise false.
    2451      */
    2452     public static function should_update_to_version( $offered_ver ) {
    2453         include( ABSPATH . WPINC . '/version.php' ); // $wp_version; // x.y.z
    2454 
    2455         $current_branch = implode( '.', array_slice( preg_split( '/[.-]/', $wp_version  ), 0, 2 ) ); // x.y
    2456         $new_branch     = implode( '.', array_slice( preg_split( '/[.-]/', $offered_ver ), 0, 2 ) ); // x.y
    2457         $current_is_development_version = (bool) strpos( $wp_version, '-' );
    2458 
    2459         // Defaults:
    2460         $upgrade_dev   = true;
    2461         $upgrade_minor = true;
    2462         $upgrade_major = false;
    2463 
    2464         // WP_AUTO_UPDATE_CORE = true (all), 'minor', false.
    2465         if ( defined( 'WP_AUTO_UPDATE_CORE' ) ) {
    2466             if ( false === WP_AUTO_UPDATE_CORE ) {
    2467                 // Defaults to turned off, unless a filter allows it
    2468                 $upgrade_dev = $upgrade_minor = $upgrade_major = false;
    2469             } elseif ( true === WP_AUTO_UPDATE_CORE ) {
    2470                 // ALL updates for core
    2471                 $upgrade_dev = $upgrade_minor = $upgrade_major = true;
    2472             } elseif ( 'minor' === WP_AUTO_UPDATE_CORE ) {
    2473                 // Only minor updates for core
    2474                 $upgrade_dev = $upgrade_major = false;
    2475                 $upgrade_minor = true;
    2476             }
    2477         }
    2478 
    2479         // 1: If we're already on that version, not much point in updating?
    2480         if ( $offered_ver == $wp_version )
    2481             return false;
    2482 
    2483         // 2: If we're running a newer version, that's a nope
    2484         if ( version_compare( $wp_version, $offered_ver, '>' ) )
    2485             return false;
    2486 
    2487         $failure_data = get_site_option( 'auto_core_update_failed' );
    2488         if ( $failure_data ) {
    2489             // If this was a critical update failure, cannot update.
    2490             if ( ! empty( $failure_data['critical'] ) )
    2491                 return false;
    2492 
    2493             // Don't claim we can update on update-core.php if we have a non-critical failure logged.
    2494             if ( $wp_version == $failure_data['current'] && false !== strpos( $offered_ver, '.1.next.minor' ) )
    2495                 return false;
    2496 
    2497             // Cannot update if we're retrying the same A to B update that caused a non-critical failure.
    2498             // Some non-critical failures do allow retries, like download_failed.
    2499             // 3.7.1 => 3.7.2 resulted in files_not_writable, if we are still on 3.7.1 and still trying to update to 3.7.2.
    2500             if ( empty( $failure_data['retry'] ) && $wp_version == $failure_data['current'] && $offered_ver == $failure_data['attempted'] )
    2501                 return false;
    2502         }
    2503 
    2504         // 3: 3.7-alpha-25000 -> 3.7-alpha-25678 -> 3.7-beta1 -> 3.7-beta2
    2505         if ( $current_is_development_version ) {
    2506 
    2507             /**
    2508              * Filter whether to enable automatic core updates for development versions.
    2509              *
    2510              * @since 3.7.0
    2511              *
    2512              * @param bool $upgrade_dev Whether to enable automatic updates for
    2513              *                          development versions.
    2514              */
    2515             if ( ! apply_filters( 'allow_dev_auto_core_updates', $upgrade_dev ) )
    2516                 return false;
    2517             // Else fall through to minor + major branches below.
    2518         }
    2519 
    2520         // 4: Minor In-branch updates (3.7.0 -> 3.7.1 -> 3.7.2 -> 3.7.4)
    2521         if ( $current_branch == $new_branch ) {
    2522 
    2523             /**
    2524              * Filter whether to enable minor automatic core updates.
    2525              *
    2526              * @since 3.7.0
    2527              *
    2528              * @param bool $upgrade_minor Whether to enable minor automatic core updates.
    2529              */
    2530             return apply_filters( 'allow_minor_auto_core_updates', $upgrade_minor );
    2531         }
    2532 
    2533         // 5: Major version updates (3.7.0 -> 3.8.0 -> 3.9.1)
    2534         if ( version_compare( $new_branch, $current_branch, '>' ) ) {
    2535 
    2536             /**
    2537              * Filter whether to enable major automatic core updates.
    2538              *
    2539              * @since 3.7.0
    2540              *
    2541              * @param bool $upgrade_major Whether to enable major automatic core updates.
    2542              */
    2543             return apply_filters( 'allow_major_auto_core_updates', $upgrade_major );
    2544         }
    2545 
    2546         // If we're not sure, we don't want it
    2547         return false;
    2548     }
    2549 
    2550     /**
    2551      * Compare the disk file checksums against the expected checksums.
    2552      *
    2553      * @since 3.7.0
    2554      * @access public
    2555      *
    2556      * @global string $wp_version
    2557      * @global string $wp_local_package
    2558      *
    2559      * @return bool True if the checksums match, otherwise false.
    2560      */
    2561     public function check_files() {
    2562         global $wp_version, $wp_local_package;
    2563 
    2564         $checksums = get_core_checksums( $wp_version, isset( $wp_local_package ) ? $wp_local_package : 'en_US' );
    2565 
    2566         if ( ! is_array( $checksums ) )
    2567             return false;
    2568 
    2569         foreach ( $checksums as $file => $checksum ) {
    2570             // Skip files which get updated
    2571             if ( 'wp-content' == substr( $file, 0, 10 ) )
    2572                 continue;
    2573             if ( ! file_exists( ABSPATH . $file ) || md5_file( ABSPATH . $file ) !== $checksum )
    2574                 return false;
    2575         }
    2576 
    2577         return true;
    2578     }
    2579 }
    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 }
    269613
    269714/**
Note: See TracChangeset for help on using the changeset viewer.