home *** CD-ROM | disk | FTP | other *** search
/ HTML Examples / WP.iso / wordpress2 / wp-admin / includes / class-wp-upgrader.php < prev    next >
Encoding:
PHP Script  |  2017-11-20  |  33.1 KB  |  907 lines

  1. <?php
  2. /**
  3.  * Upgrade API: WP_Upgrader class
  4.  *
  5.  * Requires skin classes and WP_Upgrader subclasses for backward compatibility.
  6.  *
  7.  * @package WordPress
  8.  * @subpackage Upgrader
  9.  * @since 2.8.0
  10.  */
  11.  
  12. /** WP_Upgrader_Skin class */
  13. require_once ABSPATH . 'wp-admin/includes/class-wp-upgrader-skin.php';
  14.  
  15. /** Plugin_Upgrader_Skin class */
  16. require_once ABSPATH . 'wp-admin/includes/class-plugin-upgrader-skin.php';
  17.  
  18. /** Theme_Upgrader_Skin class */
  19. require_once ABSPATH . 'wp-admin/includes/class-theme-upgrader-skin.php';
  20.  
  21. /** Bulk_Upgrader_Skin class */
  22. require_once ABSPATH . 'wp-admin/includes/class-bulk-upgrader-skin.php';
  23.  
  24. /** Bulk_Plugin_Upgrader_Skin class */
  25. require_once ABSPATH . 'wp-admin/includes/class-bulk-plugin-upgrader-skin.php';
  26.  
  27. /** Bulk_Theme_Upgrader_Skin class */
  28. require_once ABSPATH . 'wp-admin/includes/class-bulk-theme-upgrader-skin.php';
  29.  
  30. /** Plugin_Installer_Skin class */
  31. require_once ABSPATH . 'wp-admin/includes/class-plugin-installer-skin.php';
  32.  
  33. /** Theme_Installer_Skin class */
  34. require_once ABSPATH . 'wp-admin/includes/class-theme-installer-skin.php';
  35.  
  36. /** Language_Pack_Upgrader_Skin class */
  37. require_once ABSPATH . 'wp-admin/includes/class-language-pack-upgrader-skin.php';
  38.  
  39. /** Automatic_Upgrader_Skin class */
  40. require_once ABSPATH . 'wp-admin/includes/class-automatic-upgrader-skin.php';
  41.  
  42. /** WP_Ajax_Upgrader_Skin class */
  43. require_once ABSPATH . 'wp-admin/includes/class-wp-ajax-upgrader-skin.php';
  44.  
  45. /**
  46.  * Core class used for upgrading/installing a local set of files via
  47.  * the Filesystem Abstraction classes from a Zip file.
  48.  *
  49.  * @since 2.8.0
  50.  */
  51. class WP_Upgrader {
  52.  
  53.     /**
  54.      * The error/notification strings used to update the user on the progress.
  55.      *
  56.      * @since 2.8.0
  57.      * @var array $strings
  58.      */
  59.     public $strings = array();
  60.  
  61.     /**
  62.      * The upgrader skin being used.
  63.      *
  64.      * @since 2.8.0
  65.      * @var Automatic_Upgrader_Skin|WP_Upgrader_Skin $skin
  66.      */
  67.     public $skin = null;
  68.  
  69.     /**
  70.      * The result of the installation.
  71.      *
  72.      * This is set by WP_Upgrader::install_package(), only when the package is installed
  73.      * successfully. It will then be an array, unless a WP_Error is returned by the
  74.      * {@see 'upgrader_post_install'} filter. In that case, the WP_Error will be assigned to
  75.      * it.
  76.      *
  77.      * @since 2.8.0
  78.      *
  79.      * @var WP_Error|array $result {
  80.      *      @type string $source             The full path to the source the files were installed from.
  81.      *      @type string $source_files       List of all the files in the source directory.
  82.      *      @type string $destination        The full path to the installation destination folder.
  83.      *      @type string $destination_name   The name of the destination folder, or empty if `$destination`
  84.      *                                       and `$local_destination` are the same.
  85.      *      @type string $local_destination  The full local path to the destination folder. This is usually
  86.      *                                       the same as `$destination`.
  87.      *      @type string $remote_destination The full remote path to the destination folder
  88.      *                                       (i.e., from `$wp_filesystem`).
  89.      *      @type bool   $clear_destination  Whether the destination folder was cleared.
  90.      * }
  91.      */
  92.     public $result = array();
  93.  
  94.     /**
  95.      * The total number of updates being performed.
  96.      *
  97.      * Set by the bulk update methods.
  98.      *
  99.      * @since 3.0.0
  100.      * @var int $update_count
  101.      */
  102.     public $update_count = 0;
  103.  
  104.     /**
  105.      * The current update if multiple updates are being performed.
  106.      *
  107.      * Used by the bulk update methods, and incremented for each update.
  108.      *
  109.      * @since 3.0.0
  110.      * @var int
  111.      */
  112.     public $update_current = 0;
  113.  
  114.     /**
  115.      * Construct the upgrader with a skin.
  116.      *
  117.      * @since 2.8.0
  118.      *
  119.      * @param WP_Upgrader_Skin $skin The upgrader skin to use. Default is a WP_Upgrader_Skin.
  120.      *                               instance.
  121.      */
  122.     public function __construct( $skin = null ) {
  123.         if ( null == $skin )
  124.             $this->skin = new WP_Upgrader_Skin();
  125.         else
  126.             $this->skin = $skin;
  127.     }
  128.  
  129.     /**
  130.      * Initialize the upgrader.
  131.      *
  132.      * This will set the relationship between the skin being used and this upgrader,
  133.      * and also add the generic strings to `WP_Upgrader::$strings`.
  134.      *
  135.      * @since 2.8.0
  136.      */
  137.     public function init() {
  138.         $this->skin->set_upgrader($this);
  139.         $this->generic_strings();
  140.     }
  141.  
  142.     /**
  143.      * Add the generic strings to WP_Upgrader::$strings.
  144.      *
  145.      * @since 2.8.0
  146.      */
  147.     public function generic_strings() {
  148.         $this->strings['bad_request'] = __('Invalid data provided.');
  149.         $this->strings['fs_unavailable'] = __('Could not access filesystem.');
  150.         $this->strings['fs_error'] = __('Filesystem error.');
  151.         $this->strings['fs_no_root_dir'] = __('Unable to locate WordPress root directory.');
  152.         $this->strings['fs_no_content_dir'] = __('Unable to locate WordPress content directory (wp-content).');
  153.         $this->strings['fs_no_plugins_dir'] = __('Unable to locate WordPress plugin directory.');
  154.         $this->strings['fs_no_themes_dir'] = __('Unable to locate WordPress theme directory.');
  155.         /* translators: %s: directory name */
  156.         $this->strings['fs_no_folder'] = __('Unable to locate needed folder (%s).');
  157.  
  158.         $this->strings['download_failed'] = __('Download failed.');
  159.         $this->strings['installing_package'] = __('Installing the latest version…');
  160.         $this->strings['no_files'] = __('The package contains no files.');
  161.         $this->strings['folder_exists'] = __('Destination folder already exists.');
  162.         $this->strings['mkdir_failed'] = __('Could not create directory.');
  163.         $this->strings['incompatible_archive'] = __('The package could not be installed.');
  164.         $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.' );
  165.  
  166.         $this->strings['maintenance_start'] = __('Enabling Maintenance mode…');
  167.         $this->strings['maintenance_end'] = __('Disabling Maintenance mode…');
  168.     }
  169.  
  170.     /**
  171.      * Connect to the filesystem.
  172.      *
  173.      * @since 2.8.0
  174.      *
  175.      * @global WP_Filesystem_Base $wp_filesystem Subclass
  176.      *
  177.      * @param array $directories                  Optional. A list of directories. If any of these do
  178.      *                                            not exist, a WP_Error object will be returned.
  179.      *                                            Default empty array.
  180.      * @param bool  $allow_relaxed_file_ownership Whether to allow relaxed file ownership.
  181.      *                                            Default false.
  182.      * @return bool|WP_Error True if able to connect, false or a WP_Error otherwise.
  183.      */
  184.     public function fs_connect( $directories = array(), $allow_relaxed_file_ownership = false ) {
  185.         global $wp_filesystem;
  186.  
  187.         if ( false === ( $credentials = $this->skin->request_filesystem_credentials( false, $directories[0], $allow_relaxed_file_ownership ) ) ) {
  188.             return false;
  189.         }
  190.  
  191.         if ( ! WP_Filesystem( $credentials, $directories[0], $allow_relaxed_file_ownership ) ) {
  192.             $error = true;
  193.             if ( is_object($wp_filesystem) && $wp_filesystem->errors->get_error_code() )
  194.                 $error = $wp_filesystem->errors;
  195.             // Failed to connect, Error and request again
  196.             $this->skin->request_filesystem_credentials( $error, $directories[0], $allow_relaxed_file_ownership );
  197.             return false;
  198.         }
  199.  
  200.         if ( ! is_object($wp_filesystem) )
  201.             return new WP_Error('fs_unavailable', $this->strings['fs_unavailable'] );
  202.  
  203.         if ( is_wp_error($wp_filesystem->errors) && $wp_filesystem->errors->get_error_code() )
  204.             return new WP_Error('fs_error', $this->strings['fs_error'], $wp_filesystem->errors);
  205.  
  206.         foreach ( (array)$directories as $dir ) {
  207.             switch ( $dir ) {
  208.                 case ABSPATH:
  209.                     if ( ! $wp_filesystem->abspath() )
  210.                         return new WP_Error('fs_no_root_dir', $this->strings['fs_no_root_dir']);
  211.                     break;
  212.                 case WP_CONTENT_DIR:
  213.                     if ( ! $wp_filesystem->wp_content_dir() )
  214.                         return new WP_Error('fs_no_content_dir', $this->strings['fs_no_content_dir']);
  215.                     break;
  216.                 case WP_PLUGIN_DIR:
  217.                     if ( ! $wp_filesystem->wp_plugins_dir() )
  218.                         return new WP_Error('fs_no_plugins_dir', $this->strings['fs_no_plugins_dir']);
  219.                     break;
  220.                 case get_theme_root():
  221.                     if ( ! $wp_filesystem->wp_themes_dir() )
  222.                         return new WP_Error('fs_no_themes_dir', $this->strings['fs_no_themes_dir']);
  223.                     break;
  224.                 default:
  225.                     if ( ! $wp_filesystem->find_folder($dir) )
  226.                         return new WP_Error( 'fs_no_folder', sprintf( $this->strings['fs_no_folder'], esc_html( basename( $dir ) ) ) );
  227.                     break;
  228.             }
  229.         }
  230.         return true;
  231.     } //end fs_connect();
  232.  
  233.     /**
  234.      * Download a package.
  235.      *
  236.      * @since 2.8.0
  237.      *
  238.      * @param string $package The URI of the package. If this is the full path to an
  239.      *                        existing local file, it will be returned untouched.
  240.      * @return string|WP_Error The full path to the downloaded package file, or a WP_Error object.
  241.      */
  242.     public function download_package( $package ) {
  243.  
  244.         /**
  245.          * Filters whether to return the package.
  246.          *
  247.          * @since 3.7.0
  248.          *
  249.          * @param bool        $reply   Whether to bail without returning the package.
  250.          *                             Default false.
  251.          * @param string      $package The package file name.
  252.          * @param WP_Upgrader $this    The WP_Upgrader instance.
  253.          */
  254.         $reply = apply_filters( 'upgrader_pre_download', false, $package, $this );
  255.         if ( false !== $reply )
  256.             return $reply;
  257.  
  258.         if ( ! preg_match('!^(http|https|ftp)://!i', $package) && file_exists($package) ) //Local file or remote?
  259.             return $package; //must be a local file..
  260.  
  261.         if ( empty($package) )
  262.             return new WP_Error('no_package', $this->strings['no_package']);
  263.  
  264.         $this->skin->feedback('downloading_package', $package);
  265.  
  266.         $download_file = download_url($package);
  267.  
  268.         if ( is_wp_error($download_file) )
  269.             return new WP_Error('download_failed', $this->strings['download_failed'], $download_file->get_error_message());
  270.  
  271.         return $download_file;
  272.     }
  273.  
  274.     /**
  275.      * Unpack a compressed package file.
  276.      *
  277.      * @since 2.8.0
  278.      *
  279.      * @global WP_Filesystem_Base $wp_filesystem Subclass
  280.      *
  281.      * @param string $package        Full path to the package file.
  282.      * @param bool   $delete_package Optional. Whether to delete the package file after attempting
  283.      *                               to unpack it. Default true.
  284.      * @return string|WP_Error The path to the unpacked contents, or a WP_Error on failure.
  285.      */
  286.     public function unpack_package( $package, $delete_package = true ) {
  287.         global $wp_filesystem;
  288.  
  289.         $this->skin->feedback('unpack_package');
  290.  
  291.         $upgrade_folder = $wp_filesystem->wp_content_dir() . 'upgrade/';
  292.  
  293.         //Clean up contents of upgrade directory beforehand.
  294.         $upgrade_files = $wp_filesystem->dirlist($upgrade_folder);
  295.         if ( !empty($upgrade_files) ) {
  296.             foreach ( $upgrade_files as $file )
  297.                 $wp_filesystem->delete($upgrade_folder . $file['name'], true);
  298.         }
  299.  
  300.         // We need a working directory - Strip off any .tmp or .zip suffixes
  301.         $working_dir = $upgrade_folder . basename( basename( $package, '.tmp' ), '.zip' );
  302.  
  303.         // Clean up working directory
  304.         if ( $wp_filesystem->is_dir($working_dir) )
  305.             $wp_filesystem->delete($working_dir, true);
  306.  
  307.         // Unzip package to working directory
  308.         $result = unzip_file( $package, $working_dir );
  309.  
  310.         // Once extracted, delete the package if required.
  311.         if ( $delete_package )
  312.             unlink($package);
  313.  
  314.         if ( is_wp_error($result) ) {
  315.             $wp_filesystem->delete($working_dir, true);
  316.             if ( 'incompatible_archive' == $result->get_error_code() ) {
  317.                 return new WP_Error( 'incompatible_archive', $this->strings['incompatible_archive'], $result->get_error_data() );
  318.             }
  319.             return $result;
  320.         }
  321.  
  322.         return $working_dir;
  323.     }
  324.  
  325.     /**
  326.      * Flatten the results of WP_Filesystem::dirlist() for iterating over.
  327.      *
  328.      * @since 4.9.0
  329.      * @access protected
  330.      *
  331.      * @param  array  $nested_files  Array of files as returned by WP_Filesystem::dirlist()
  332.      * @param  string $path          Relative path to prepend to child nodes. Optional.
  333.      * @return array $files A flattened array of the $nested_files specified.
  334.      */
  335.     protected function flatten_dirlist( $nested_files, $path = '' ) {
  336.         $files = array();
  337.  
  338.         foreach ( $nested_files as $name => $details ) {
  339.             $files[ $path . $name ] = $details;
  340.  
  341.             // Append children recursively
  342.             if ( ! empty( $details['files'] ) ) {
  343.                 $children = $this->flatten_dirlist( $details['files'], $path . $name . '/' );
  344.  
  345.                 // Merge keeping possible numeric keys, which array_merge() will reindex from 0..n
  346.                 $files = $files + $children;
  347.             }
  348.         }
  349.  
  350.         return $files;
  351.     }
  352.  
  353.     /**
  354.      * Clears the directory where this item is going to be installed into.
  355.      *
  356.      * @since 4.3.0
  357.      *
  358.      * @global WP_Filesystem_Base $wp_filesystem Subclass
  359.      *
  360.      * @param string $remote_destination The location on the remote filesystem to be cleared
  361.      * @return bool|WP_Error True upon success, WP_Error on failure.
  362.      */
  363.     public function clear_destination( $remote_destination ) {
  364.         global $wp_filesystem;
  365.  
  366.         $files = $wp_filesystem->dirlist( $remote_destination, true, true );
  367.  
  368.         // False indicates that the $remote_destination doesn't exist.
  369.         if ( false === $files ) {
  370.             return true;
  371.         }
  372.  
  373.         // Flatten the file list to iterate over
  374.         $files = $this->flatten_dirlist( $files );
  375.  
  376.         // Check all files are writable before attempting to clear the destination.
  377.         $unwritable_files = array();
  378.  
  379.         // Check writability.
  380.         foreach ( $files as $filename => $file_details ) {
  381.             if ( ! $wp_filesystem->is_writable( $remote_destination . $filename ) ) {
  382.                 // Attempt to alter permissions to allow writes and try again.
  383.                 $wp_filesystem->chmod( $remote_destination . $filename, ( 'd' == $file_details['type'] ? FS_CHMOD_DIR : FS_CHMOD_FILE ) );
  384.                 if ( ! $wp_filesystem->is_writable( $remote_destination . $filename ) ) {
  385.                     $unwritable_files[] = $filename;
  386.                 }
  387.             }
  388.         }
  389.  
  390.         if ( ! empty( $unwritable_files ) ) {
  391.             return new WP_Error( 'files_not_writable', $this->strings['files_not_writable'], implode( ', ', $unwritable_files ) );
  392.         }
  393.  
  394.         if ( ! $wp_filesystem->delete( $remote_destination, true ) ) {
  395.             return new WP_Error( 'remove_old_failed', $this->strings['remove_old_failed'] );
  396.         }
  397.  
  398.         return true;
  399.     }
  400.  
  401.     /**
  402.      * Install a package.
  403.      *
  404.      * Copies the contents of a package form a source directory, and installs them in
  405.      * a destination directory. Optionally removes the source. It can also optionally
  406.      * clear out the destination folder if it already exists.
  407.      *
  408.      * @since 2.8.0
  409.      *
  410.      * @global WP_Filesystem_Base $wp_filesystem Subclass
  411.      * @global array              $wp_theme_directories
  412.      *
  413.      * @param array|string $args {
  414.      *     Optional. Array or string of arguments for installing a package. Default empty array.
  415.      *
  416.      *     @type string $source                      Required path to the package source. Default empty.
  417.      *     @type string $destination                 Required path to a folder to install the package in.
  418.      *                                               Default empty.
  419.      *     @type bool   $clear_destination           Whether to delete any files already in the destination
  420.      *                                               folder. Default false.
  421.      *     @type bool   $clear_working               Whether to delete the files form the working directory
  422.      *                                               after copying to the destination. Default false.
  423.      *     @type bool   $abort_if_destination_exists Whether to abort the installation if
  424.      *                                               the destination folder already exists. Default true.
  425.      *     @type array  $hook_extra                  Extra arguments to pass to the filter hooks called by
  426.      *                                               WP_Upgrader::install_package(). Default empty array.
  427.      * }
  428.      *
  429.      * @return array|WP_Error The result (also stored in `WP_Upgrader::$result`), or a WP_Error on failure.
  430.      */
  431.     public function install_package( $args = array() ) {
  432.         global $wp_filesystem, $wp_theme_directories;
  433.  
  434.         $defaults = array(
  435.             'source' => '', // Please always pass this
  436.             'destination' => '', // and this
  437.             'clear_destination' => false,
  438.             'clear_working' => false,
  439.             'abort_if_destination_exists' => true,
  440.             'hook_extra' => array()
  441.         );
  442.  
  443.         $args = wp_parse_args($args, $defaults);
  444.  
  445.         // These were previously extract()'d.
  446.         $source = $args['source'];
  447.         $destination = $args['destination'];
  448.         $clear_destination = $args['clear_destination'];
  449.  
  450.         @set_time_limit( 300 );
  451.  
  452.         if ( empty( $source ) || empty( $destination ) ) {
  453.             return new WP_Error( 'bad_request', $this->strings['bad_request'] );
  454.         }
  455.         $this->skin->feedback( 'installing_package' );
  456.  
  457.         /**
  458.          * Filters the install response before the installation has started.
  459.          *
  460.          * Returning a truthy value, or one that could be evaluated as a WP_Error
  461.          * will effectively short-circuit the installation, returning that value
  462.          * instead.
  463.          *
  464.          * @since 2.8.0
  465.          *
  466.          * @param bool|WP_Error $response   Response.
  467.          * @param array         $hook_extra Extra arguments passed to hooked filters.
  468.          */
  469.         $res = apply_filters( 'upgrader_pre_install', true, $args['hook_extra'] );
  470.  
  471.         if ( is_wp_error( $res ) ) {
  472.             return $res;
  473.         }
  474.  
  475.         //Retain the Original source and destinations
  476.         $remote_source = $args['source'];
  477.         $local_destination = $destination;
  478.  
  479.         $source_files = array_keys( $wp_filesystem->dirlist( $remote_source ) );
  480.         $remote_destination = $wp_filesystem->find_folder( $local_destination );
  481.  
  482.         //Locate which directory to copy to the new folder, This is based on the actual folder holding the files.
  483.         if ( 1 == count( $source_files ) && $wp_filesystem->is_dir( trailingslashit( $args['source'] ) . $source_files[0] . '/' ) ) { //Only one folder? Then we want its contents.
  484.             $source = trailingslashit( $args['source'] ) . trailingslashit( $source_files[0] );
  485.         } elseif ( count( $source_files ) == 0 ) {
  486.             return new WP_Error( 'incompatible_archive_empty', $this->strings['incompatible_archive'], $this->strings['no_files'] ); // There are no files?
  487.         } 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.
  488.             $source = trailingslashit( $args['source'] );
  489.         }
  490.  
  491.         /**
  492.          * Filters the source file location for the upgrade package.
  493.          *
  494.          * @since 2.8.0
  495.          * @since 4.4.0 The $hook_extra parameter became available.
  496.          *
  497.          * @param string      $source        File source location.
  498.          * @param string      $remote_source Remote file source location.
  499.          * @param WP_Upgrader $this          WP_Upgrader instance.
  500.          * @param array       $hook_extra    Extra arguments passed to hooked filters.
  501.          */
  502.         $source = apply_filters( 'upgrader_source_selection', $source, $remote_source, $this, $args['hook_extra'] );
  503.  
  504.         if ( is_wp_error( $source ) ) {
  505.             return $source;
  506.         }
  507.  
  508.         // Has the source location changed? If so, we need a new source_files list.
  509.         if ( $source !== $remote_source ) {
  510.             $source_files = array_keys( $wp_filesystem->dirlist( $source ) );
  511.         }
  512.  
  513.         /*
  514.          * Protection against deleting files in any important base directories.
  515.          * Theme_Upgrader & Plugin_Upgrader also trigger this, as they pass the
  516.          * destination directory (WP_PLUGIN_DIR / wp-content/themes) intending
  517.          * to copy the directory into the directory, whilst they pass the source
  518.          * as the actual files to copy.
  519.          */
  520.         $protected_directories = array( ABSPATH, WP_CONTENT_DIR, WP_PLUGIN_DIR, WP_CONTENT_DIR . '/themes' );
  521.  
  522.         if ( is_array( $wp_theme_directories ) ) {
  523.             $protected_directories = array_merge( $protected_directories, $wp_theme_directories );
  524.         }
  525.  
  526.         if ( in_array( $destination, $protected_directories ) ) {
  527.             $remote_destination = trailingslashit( $remote_destination ) . trailingslashit( basename( $source ) );
  528.             $destination = trailingslashit( $destination ) . trailingslashit( basename( $source ) );
  529.         }
  530.  
  531.         if ( $clear_destination ) {
  532.             // We're going to clear the destination if there's something there.
  533.             $this->skin->feedback('remove_old');
  534.  
  535.             $removed = $this->clear_destination( $remote_destination );
  536.  
  537.             /**
  538.              * Filters whether the upgrader cleared the destination.
  539.              *
  540.              * @since 2.8.0
  541.              *
  542.              * @param mixed  $removed            Whether the destination was cleared. true on success, WP_Error on failure
  543.              * @param string $local_destination  The local package destination.
  544.              * @param string $remote_destination The remote package destination.
  545.              * @param array  $hook_extra         Extra arguments passed to hooked filters.
  546.              */
  547.             $removed = apply_filters( 'upgrader_clear_destination', $removed, $local_destination, $remote_destination, $args['hook_extra'] );
  548.  
  549.             if ( is_wp_error( $removed ) ) {
  550.                 return $removed;
  551.             }
  552.         } elseif ( $args['abort_if_destination_exists'] && $wp_filesystem->exists($remote_destination) ) {
  553.             //If we're not clearing the destination folder and something exists there already, Bail.
  554.             //But first check to see if there are actually any files in the folder.
  555.             $_files = $wp_filesystem->dirlist($remote_destination);
  556.             if ( ! empty($_files) ) {
  557.                 $wp_filesystem->delete($remote_source, true); //Clear out the source files.
  558.                 return new WP_Error('folder_exists', $this->strings['folder_exists'], $remote_destination );
  559.             }
  560.         }
  561.  
  562.         //Create destination if needed
  563.         if ( ! $wp_filesystem->exists( $remote_destination ) ) {
  564.             if ( ! $wp_filesystem->mkdir( $remote_destination, FS_CHMOD_DIR ) ) {
  565.                 return new WP_Error( 'mkdir_failed_destination', $this->strings['mkdir_failed'], $remote_destination );
  566.             }
  567.         }
  568.         // Copy new version of item into place.
  569.         $result = copy_dir($source, $remote_destination);
  570.         if ( is_wp_error($result) ) {
  571.             if ( $args['clear_working'] ) {
  572.                 $wp_filesystem->delete( $remote_source, true );
  573.             }
  574.             return $result;
  575.         }
  576.  
  577.         //Clear the Working folder?
  578.         if ( $args['clear_working'] ) {
  579.             $wp_filesystem->delete( $remote_source, true );
  580.         }
  581.  
  582.         $destination_name = basename( str_replace($local_destination, '', $destination) );
  583.         if ( '.' == $destination_name ) {
  584.             $destination_name = '';
  585.         }
  586.  
  587.         $this->result = compact( 'source', 'source_files', 'destination', 'destination_name', 'local_destination', 'remote_destination', 'clear_destination' );
  588.  
  589.         /**
  590.          * Filters the installation response after the installation has finished.
  591.          *
  592.          * @since 2.8.0
  593.          *
  594.          * @param bool  $response   Installation response.
  595.          * @param array $hook_extra Extra arguments passed to hooked filters.
  596.          * @param array $result     Installation result data.
  597.          */
  598.         $res = apply_filters( 'upgrader_post_install', true, $args['hook_extra'], $this->result );
  599.  
  600.         if ( is_wp_error($res) ) {
  601.             $this->result = $res;
  602.             return $res;
  603.         }
  604.  
  605.         //Bombard the calling function will all the info which we've just used.
  606.         return $this->result;
  607.     }
  608.  
  609.     /**
  610.      * Run an upgrade/installation.
  611.      *
  612.      * Attempts to download the package (if it is not a local file), unpack it, and
  613.      * install it in the destination folder.
  614.      *
  615.      * @since 2.8.0
  616.      *
  617.      * @param array $options {
  618.      *     Array or string of arguments for upgrading/installing a package.
  619.      *
  620.      *     @type string $package                     The full path or URI of the package to install.
  621.      *                                               Default empty.
  622.      *     @type string $destination                 The full path to the destination folder.
  623.      *                                               Default empty.
  624.      *     @type bool   $clear_destination           Whether to delete any files already in the
  625.      *                                               destination folder. Default false.
  626.      *     @type bool   $clear_working               Whether to delete the files form the working
  627.      *                                               directory after copying to the destination.
  628.      *                                               Default false.
  629.      *     @type bool   $abort_if_destination_exists Whether to abort the installation if the destination
  630.      *                                               folder already exists. When true, `$clear_destination`
  631.      *                                               should be false. Default true.
  632.      *     @type bool   $is_multi                    Whether this run is one of multiple upgrade/installation
  633.      *                                               actions being performed in bulk. When true, the skin
  634.      *                                               WP_Upgrader::header() and WP_Upgrader::footer()
  635.      *                                               aren't called. Default false.
  636.      *     @type array  $hook_extra                  Extra arguments to pass to the filter hooks called by
  637.      *                                               WP_Upgrader::run().
  638.      * }
  639.      * @return array|false|WP_error The result from self::install_package() on success, otherwise a WP_Error,
  640.      *                              or false if unable to connect to the filesystem.
  641.      */
  642.     public function run( $options ) {
  643.  
  644.         $defaults = array(
  645.             'package' => '', // Please always pass this.
  646.             'destination' => '', // And this
  647.             'clear_destination' => false,
  648.             'abort_if_destination_exists' => true, // Abort if the Destination directory exists, Pass clear_destination as false please
  649.             'clear_working' => true,
  650.             'is_multi' => false,
  651.             'hook_extra' => array() // Pass any extra $hook_extra args here, this will be passed to any hooked filters.
  652.         );
  653.  
  654.         $options = wp_parse_args( $options, $defaults );
  655.  
  656.         /**
  657.          * Filters the package options before running an update.
  658.          *
  659.          * See also {@see 'upgrader_process_complete'}.
  660.          *
  661.          * @since 4.3.0
  662.          *
  663.          * @param array $options {
  664.          *     Options used by the upgrader.
  665.          *
  666.          *     @type string $package                     Package for update.
  667.          *     @type string $destination                 Update location.
  668.          *     @type bool   $clear_destination           Clear the destination resource.
  669.          *     @type bool   $clear_working               Clear the working resource.
  670.          *     @type bool   $abort_if_destination_exists Abort if the Destination directory exists.
  671.          *     @type bool   $is_multi                    Whether the upgrader is running multiple times.
  672.          *     @type array  $hook_extra {
  673.          *         Extra hook arguments.
  674.          *
  675.          *         @type string $action               Type of action. Default 'update'.
  676.          *         @type string $type                 Type of update process. Accepts 'plugin', 'theme', or 'core'.
  677.          *         @type bool   $bulk                 Whether the update process is a bulk update. Default true.
  678.          *         @type string $plugin               The base plugin path from the plugins directory.
  679.          *         @type string $theme                The stylesheet or template name of the theme.
  680.          *         @type string $language_update_type The language pack update type. Accepts 'plugin', 'theme',
  681.          *                                            or 'core'.
  682.          *         @type object $language_update      The language pack update offer.
  683.          *     }
  684.          * }
  685.          */
  686.         $options = apply_filters( 'upgrader_package_options', $options );
  687.  
  688.         if ( ! $options['is_multi'] ) { // call $this->header separately if running multiple times
  689.             $this->skin->header();
  690.         }
  691.  
  692.         // Connect to the Filesystem first.
  693.         $res = $this->fs_connect( array( WP_CONTENT_DIR, $options['destination'] ) );
  694.         // Mainly for non-connected filesystem.
  695.         if ( ! $res ) {
  696.             if ( ! $options['is_multi'] ) {
  697.                 $this->skin->footer();
  698.             }
  699.             return false;
  700.         }
  701.  
  702.         $this->skin->before();
  703.  
  704.         if ( is_wp_error($res) ) {
  705.             $this->skin->error($res);
  706.             $this->skin->after();
  707.             if ( ! $options['is_multi'] ) {
  708.                 $this->skin->footer();
  709.             }
  710.             return $res;
  711.         }
  712.  
  713.         /*
  714.          * Download the package (Note, This just returns the filename
  715.          * of the file if the package is a local file)
  716.          */
  717.         $download = $this->download_package( $options['package'] );
  718.         if ( is_wp_error($download) ) {
  719.             $this->skin->error($download);
  720.             $this->skin->after();
  721.             if ( ! $options['is_multi'] ) {
  722.                 $this->skin->footer();
  723.             }
  724.             return $download;
  725.         }
  726.  
  727.         $delete_package = ( $download != $options['package'] ); // Do not delete a "local" file
  728.  
  729.         // Unzips the file into a temporary directory.
  730.         $working_dir = $this->unpack_package( $download, $delete_package );
  731.         if ( is_wp_error($working_dir) ) {
  732.             $this->skin->error($working_dir);
  733.             $this->skin->after();
  734.             if ( ! $options['is_multi'] ) {
  735.                 $this->skin->footer();
  736.             }
  737.             return $working_dir;
  738.         }
  739.  
  740.         // With the given options, this installs it to the destination directory.
  741.         $result = $this->install_package( array(
  742.             'source' => $working_dir,
  743.             'destination' => $options['destination'],
  744.             'clear_destination' => $options['clear_destination'],
  745.             'abort_if_destination_exists' => $options['abort_if_destination_exists'],
  746.             'clear_working' => $options['clear_working'],
  747.             'hook_extra' => $options['hook_extra']
  748.         ) );
  749.  
  750.         $this->skin->set_result($result);
  751.         if ( is_wp_error($result) ) {
  752.             $this->skin->error($result);
  753.             $this->skin->feedback('process_failed');
  754.         } else {
  755.             // Installation succeeded.
  756.             $this->skin->feedback('process_success');
  757.         }
  758.  
  759.         $this->skin->after();
  760.  
  761.         if ( ! $options['is_multi'] ) {
  762.  
  763.             /**
  764.              * Fires when the upgrader process is complete.
  765.              *
  766.              * See also {@see 'upgrader_package_options'}.
  767.              *
  768.              * @since 3.6.0
  769.              * @since 3.7.0 Added to WP_Upgrader::run().
  770.              * @since 4.6.0 `$translations` was added as a possible argument to `$hook_extra`.
  771.              *
  772.              * @param WP_Upgrader $this WP_Upgrader instance. In other contexts, $this, might be a
  773.              *                          Theme_Upgrader, Plugin_Upgrader, Core_Upgrade, or Language_Pack_Upgrader instance.
  774.              * @param array       $hook_extra {
  775.              *     Array of bulk item update data.
  776.              *
  777.              *     @type string $action       Type of action. Default 'update'.
  778.              *     @type string $type         Type of update process. Accepts 'plugin', 'theme', 'translation', or 'core'.
  779.              *     @type bool   $bulk         Whether the update process is a bulk update. Default true.
  780.              *     @type array  $plugins      Array of the basename paths of the plugins' main files.
  781.              *     @type array  $themes       The theme slugs.
  782.              *     @type array  $translations {
  783.              *         Array of translations update data.
  784.              *
  785.              *         @type string $language The locale the translation is for.
  786.              *         @type string $type     Type of translation. Accepts 'plugin', 'theme', or 'core'.
  787.              *         @type string $slug     Text domain the translation is for. The slug of a theme/plugin or
  788.              *                                'default' for core translations.
  789.              *         @type string $version  The version of a theme, plugin, or core.
  790.              *     }
  791.              * }
  792.              */
  793.             do_action( 'upgrader_process_complete', $this, $options['hook_extra'] );
  794.  
  795.             $this->skin->footer();
  796.         }
  797.  
  798.         return $result;
  799.     }
  800.  
  801.     /**
  802.      * Toggle maintenance mode for the site.
  803.      *
  804.      * Creates/deletes the maintenance file to enable/disable maintenance mode.
  805.      *
  806.      * @since 2.8.0
  807.      *
  808.      * @global WP_Filesystem_Base $wp_filesystem Subclass
  809.      *
  810.      * @param bool $enable True to enable maintenance mode, false to disable.
  811.      */
  812.     public function maintenance_mode( $enable = false ) {
  813.         global $wp_filesystem;
  814.         $file = $wp_filesystem->abspath() . '.maintenance';
  815.         if ( $enable ) {
  816.             $this->skin->feedback('maintenance_start');
  817.             // Create maintenance file to signal that we are upgrading
  818.             $maintenance_string = '<?php $upgrading = ' . time() . '; ?>';
  819.             $wp_filesystem->delete($file);
  820.             $wp_filesystem->put_contents($file, $maintenance_string, FS_CHMOD_FILE);
  821.         } elseif ( ! $enable && $wp_filesystem->exists( $file ) ) {
  822.             $this->skin->feedback('maintenance_end');
  823.             $wp_filesystem->delete($file);
  824.         }
  825.     }
  826.  
  827.     /**
  828.       * Creates a lock using WordPress options.
  829.       *
  830.       * @since 4.5.0
  831.       * @static
  832.       *
  833.       * @param string $lock_name       The name of this unique lock.
  834.       * @param int    $release_timeout Optional. The duration in seconds to respect an existing lock.
  835.      *                                Default: 1 hour.
  836.       * @return bool False if a lock couldn't be created or if the lock is still valid. True otherwise.
  837.       */
  838.     public static function create_lock( $lock_name, $release_timeout = null ) {
  839.         global $wpdb;
  840.         if ( ! $release_timeout ) {
  841.             $release_timeout = HOUR_IN_SECONDS;
  842.         }
  843.         $lock_option = $lock_name . '.lock';
  844.  
  845.         // Try to lock.
  846.         $lock_result = $wpdb->query( $wpdb->prepare( "INSERT IGNORE INTO `$wpdb->options` ( `option_name`, `option_value`, `autoload` ) VALUES (%s, %s, 'no') /* LOCK */", $lock_option, time() ) );
  847.  
  848.         if ( ! $lock_result ) {
  849.             $lock_result = get_option( $lock_option );
  850.  
  851.             // If a lock couldn't be created, and there isn't a lock, bail.
  852.             if ( ! $lock_result ) {
  853.                 return false;
  854.             }
  855.  
  856.             // Check to see if the lock is still valid. If it is, bail.
  857.             if ( $lock_result > ( time() - $release_timeout ) ) {
  858.                 return false;
  859.             }
  860.  
  861.             // There must exist an expired lock, clear it and re-gain it.
  862.             WP_Upgrader::release_lock( $lock_name );
  863.  
  864.             return WP_Upgrader::create_lock( $lock_name, $release_timeout );
  865.         }
  866.  
  867.         // Update the lock, as by this point we've definitely got a lock, just need to fire the actions.
  868.         update_option( $lock_option, time() );
  869.  
  870.         return true;
  871.     }
  872.  
  873.     /**
  874.       * Releases an upgrader lock.
  875.       *
  876.       * @since 4.5.0
  877.       * @static
  878.      *
  879.      * @see WP_Upgrader::create_lock()
  880.       *
  881.       * @param string $lock_name The name of this unique lock.
  882.      * @return bool True if the lock was successfully released. False on failure.
  883.       */
  884.     public static function release_lock( $lock_name ) {
  885.         return delete_option( $lock_name . '.lock' );
  886.     }
  887.  
  888. }
  889.  
  890. /** Plugin_Upgrader class */
  891. require_once ABSPATH . 'wp-admin/includes/class-plugin-upgrader.php';
  892.  
  893. /** Theme_Upgrader class */
  894. require_once ABSPATH . 'wp-admin/includes/class-theme-upgrader.php';
  895.  
  896. /** Language_Pack_Upgrader class */
  897. require_once ABSPATH . 'wp-admin/includes/class-language-pack-upgrader.php';
  898.  
  899. /** Core_Upgrader class */
  900. require_once ABSPATH . 'wp-admin/includes/class-core-upgrader.php';
  901.  
  902. /** File_Upload_Upgrader class */
  903. require_once ABSPATH . 'wp-admin/includes/class-file-upload-upgrader.php';
  904.  
  905. /** WP_Automatic_Updater class */
  906. require_once ABSPATH . 'wp-admin/includes/class-wp-automatic-updater.php';
  907.