[ Index ]

PHP Cross Reference of WordPress Trunk (Updated Daily)

Search

title

Body

[close]

/wp-admin/includes/ -> class-wp-upgrader.php (source)

   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  #[AllowDynamicProperties]
  52  class WP_Upgrader {
  53  
  54      /**
  55       * The error/notification strings used to update the user on the progress.
  56       *
  57       * @since 2.8.0
  58       * @var array $strings
  59       */
  60      public $strings = array();
  61  
  62      /**
  63       * The upgrader skin being used.
  64       *
  65       * @since 2.8.0
  66       * @var Automatic_Upgrader_Skin|WP_Upgrader_Skin $skin
  67       */
  68      public $skin = null;
  69  
  70      /**
  71       * The result of the installation.
  72       *
  73       * This is set by WP_Upgrader::install_package(), only when the package is installed
  74       * successfully. It will then be an array, unless a WP_Error is returned by the
  75       * {@see 'upgrader_post_install'} filter. In that case, the WP_Error will be assigned to
  76       * it. Until then, it is an empty array, or null in subclasses which redeclare it.
  77       *
  78       * @since 2.8.0
  79       *
  80       * @var array|WP_Error|null $result {
  81       *     @type string $source             The full path to the source the files were installed from.
  82       *     @type string $source_files       List of all the files in the source directory.
  83       *     @type string $destination        The full path to the installation destination folder.
  84       *     @type string $destination_name   The name of the destination folder, or empty if `$destination`
  85       *                                      and `$local_destination` are the same.
  86       *     @type string $local_destination  The full local path to the destination folder. This is usually
  87       *                                      the same as `$destination`.
  88       *     @type string $remote_destination The full remote path to the destination folder
  89       *                                      (i.e., from `$wp_filesystem`).
  90       *     @type bool   $clear_destination  Whether the destination folder was cleared.
  91       * }
  92       */
  93      public $result = array();
  94  
  95      /**
  96       * The total number of updates being performed.
  97       *
  98       * Set by the bulk update methods.
  99       *
 100       * @since 3.0.0
 101       * @var int $update_count
 102       */
 103      public $update_count = 0;
 104  
 105      /**
 106       * The current update if multiple updates are being performed.
 107       *
 108       * Used by the bulk update methods, and incremented for each update.
 109       *
 110       * @since 3.0.0
 111       * @var int
 112       */
 113      public $update_current = 0;
 114  
 115      /**
 116       * Stores the list of plugins or themes added to temporary backup directory.
 117       *
 118       * Used by the rollback functions.
 119       *
 120       * @since 6.3.0
 121       * @var array
 122       */
 123      private $temp_backups = array();
 124  
 125      /**
 126       * Stores the list of plugins or themes to be restored from temporary backup directory.
 127       *
 128       * Used by the rollback functions.
 129       *
 130       * @since 6.3.0
 131       * @var array
 132       */
 133      private $temp_restores = array();
 134  
 135      /**
 136       * Construct the upgrader with a skin.
 137       *
 138       * @since 2.8.0
 139       *
 140       * @param WP_Upgrader_Skin $skin The upgrader skin to use. Default is a WP_Upgrader_Skin
 141       *                               instance.
 142       */
 143  	public function __construct( $skin = null ) {
 144          if ( null === $skin ) {
 145              $this->skin = new WP_Upgrader_Skin();
 146          } else {
 147              $this->skin = $skin;
 148          }
 149      }
 150  
 151      /**
 152       * Initializes the upgrader.
 153       *
 154       * This will set the relationship between the skin being used and this upgrader,
 155       * and also add the generic strings to `WP_Upgrader::$strings`.
 156       *
 157       * Additionally, it will schedule a weekly task to clean up the temporary backup directory.
 158       *
 159       * @since 2.8.0
 160       * @since 6.3.0 Added the `schedule_temp_backup_cleanup()` task.
 161       */
 162  	public function init() {
 163          $this->skin->set_upgrader( $this );
 164          $this->generic_strings();
 165  
 166          if ( ! wp_installing() ) {
 167              $this->schedule_temp_backup_cleanup();
 168          }
 169      }
 170  
 171      /**
 172       * Schedules the cleanup of the temporary backup directory.
 173       *
 174       * @since 6.3.0
 175       */
 176  	protected function schedule_temp_backup_cleanup() {
 177          if ( false === wp_next_scheduled( 'wp_delete_temp_updater_backups' ) ) {
 178              wp_schedule_event( time(), 'weekly', 'wp_delete_temp_updater_backups' );
 179          }
 180      }
 181  
 182      /**
 183       * Adds the generic strings to WP_Upgrader::$strings.
 184       *
 185       * @since 2.8.0
 186       */
 187  	public function generic_strings() {
 188          $this->strings['bad_request']    = __( 'Invalid data provided.' );
 189          $this->strings['fs_unavailable'] = __( 'Could not access filesystem.' );
 190          $this->strings['fs_error']       = __( 'Filesystem error.' );
 191          $this->strings['fs_no_root_dir'] = __( 'Unable to locate WordPress root directory.' );
 192          /* translators: %s: Directory name. */
 193          $this->strings['fs_no_content_dir'] = sprintf( __( 'Unable to locate WordPress content directory (%s).' ), 'wp-content' );
 194          $this->strings['fs_no_plugins_dir'] = __( 'Unable to locate WordPress plugin directory.' );
 195          $this->strings['fs_no_themes_dir']  = __( 'Unable to locate WordPress theme directory.' );
 196          /* translators: %s: Directory name. */
 197          $this->strings['fs_no_folder'] = __( 'Unable to locate needed folder (%s).' );
 198  
 199          $this->strings['no_package']           = __( 'Package not available.' );
 200          $this->strings['download_failed']      = __( 'Download failed.' );
 201          $this->strings['installing_package']   = __( 'Installing the latest version&#8230;' );
 202          $this->strings['no_files']             = __( 'The package contains no files.' );
 203          $this->strings['folder_exists']        = __( 'Destination folder already exists.' );
 204          $this->strings['mkdir_failed']         = __( 'Could not create directory.' );
 205          $this->strings['incompatible_archive'] = __( 'The package could not be installed.' );
 206          $this->strings['files_not_writable']   = __( 'The update cannot be installed because some files could not be copied. This is usually due to inconsistent file permissions.' );
 207          $this->strings['dir_not_readable']     = __( 'A directory could not be read.' );
 208  
 209          $this->strings['maintenance_start'] = __( 'Enabling Maintenance mode&#8230;' );
 210          $this->strings['maintenance_end']   = __( 'Disabling Maintenance mode&#8230;' );
 211  
 212          /* translators: %s: upgrade-temp-backup */
 213          $this->strings['temp_backup_mkdir_failed'] = sprintf( __( 'Could not create the %s directory.' ), 'upgrade-temp-backup' );
 214          /* translators: %s: upgrade-temp-backup */
 215          $this->strings['temp_backup_move_failed'] = sprintf( __( 'Could not move the old version to the %s directory.' ), 'upgrade-temp-backup' );
 216          /* translators: %s: The plugin or theme slug. */
 217          $this->strings['temp_backup_restore_failed'] = __( 'Could not restore the original version of %s.' );
 218          /* translators: %s: The plugin or theme slug. */
 219          $this->strings['temp_backup_delete_failed'] = __( 'Could not delete the temporary backup directory for %s.' );
 220      }
 221  
 222      /**
 223       * Connects to the filesystem.
 224       *
 225       * @since 2.8.0
 226       *
 227       * @global WP_Filesystem_Base $wp_filesystem WordPress filesystem subclass.
 228       *
 229       * @param string[] $directories                  Optional. Array of directories. If any of these do
 230       *                                               not exist, a WP_Error object will be returned.
 231       *                                               Default empty array.
 232       * @param bool     $allow_relaxed_file_ownership Whether to allow relaxed file ownership.
 233       *                                               Default false.
 234       * @return bool|WP_Error True if able to connect, false or a WP_Error otherwise.
 235       */
 236  	public function fs_connect( $directories = array(), $allow_relaxed_file_ownership = false ) {
 237          global $wp_filesystem;
 238  
 239          $credentials = $this->skin->request_filesystem_credentials( false, $directories[0], $allow_relaxed_file_ownership );
 240          if ( false === $credentials ) {
 241              return false;
 242          }
 243  
 244          if ( ! WP_Filesystem( $credentials, $directories[0], $allow_relaxed_file_ownership ) ) {
 245              $error = true;
 246              if ( is_object( $wp_filesystem ) && $wp_filesystem->errors->has_errors() ) {
 247                  $error = $wp_filesystem->errors;
 248              }
 249              // Failed to connect. Error and request again.
 250              $this->skin->request_filesystem_credentials( $error, $directories[0], $allow_relaxed_file_ownership );
 251              return false;
 252          }
 253  
 254          if ( ! is_object( $wp_filesystem ) ) {
 255              return new WP_Error( 'fs_unavailable', $this->strings['fs_unavailable'] );
 256          }
 257  
 258          if ( is_wp_error( $wp_filesystem->errors ) && $wp_filesystem->errors->has_errors() ) {
 259              return new WP_Error( 'fs_error', $this->strings['fs_error'], $wp_filesystem->errors );
 260          }
 261  
 262          foreach ( (array) $directories as $dir ) {
 263              switch ( $dir ) {
 264                  case ABSPATH:
 265                      if ( ! $wp_filesystem->abspath() ) {
 266                          return new WP_Error( 'fs_no_root_dir', $this->strings['fs_no_root_dir'] );
 267                      }
 268                      break;
 269                  case WP_CONTENT_DIR:
 270                      if ( ! $wp_filesystem->wp_content_dir() ) {
 271                          return new WP_Error( 'fs_no_content_dir', $this->strings['fs_no_content_dir'] );
 272                      }
 273                      break;
 274                  case WP_PLUGIN_DIR:
 275                      if ( ! $wp_filesystem->wp_plugins_dir() ) {
 276                          return new WP_Error( 'fs_no_plugins_dir', $this->strings['fs_no_plugins_dir'] );
 277                      }
 278                      break;
 279                  case get_theme_root():
 280                      if ( ! $wp_filesystem->wp_themes_dir() ) {
 281                          return new WP_Error( 'fs_no_themes_dir', $this->strings['fs_no_themes_dir'] );
 282                      }
 283                      break;
 284                  default:
 285                      if ( ! $wp_filesystem->find_folder( $dir ) ) {
 286                          return new WP_Error( 'fs_no_folder', sprintf( $this->strings['fs_no_folder'], esc_html( basename( $dir ) ) ) );
 287                      }
 288                      break;
 289              }
 290          }
 291          return true;
 292      }
 293  
 294      /**
 295       * Downloads a package for a WordPress core, plugin, theme, or translation upgrade.
 296       *
 297       * @since 2.8.0
 298       * @since 5.2.0 Added the `$check_signatures` parameter.
 299       * @since 5.5.0 Added the `$hook_extra` parameter.
 300       *
 301       * @param string $package          The URI of the package. May be a remote URL or local file path. If this is the full
 302       *                                 path to an existing local file, it will be returned untouched.
 303       * @param bool   $check_signatures Whether to validate file signatures. Default false.
 304       * @param array  $hook_extra       Extra arguments to pass to the filter hooks. Default empty array.
 305       * @return string|WP_Error The full path to the downloaded package file, or a WP_Error object.
 306       */
 307  	public function download_package( $package, $check_signatures = false, $hook_extra = array() ) {
 308          /**
 309           * Filters whether to download a package for a WordPress core, plugin, theme, or translation upgrade.
 310           *
 311           * Return a non-false value to short-circuit the download and return that value instead.
 312           *
 313           * @since 3.7.0
 314           * @since 5.5.0 Added the `$hook_extra` parameter.
 315           *
 316           * @param false|string|WP_Error $reply      Whether to short-circuit the download, the path to the downloaded package,
 317           *                                          or a WP_Error object. Default false.
 318           * @param string                $package    The package URI. May be a remote URL or local file path.
 319           * @param WP_Upgrader           $upgrader   The WP_Upgrader instance.
 320           * @param array                 $hook_extra Extra arguments passed to hooked filters.
 321           */
 322          $reply = apply_filters( 'upgrader_pre_download', false, $package, $this, $hook_extra );
 323          if ( false !== $reply ) {
 324              return $reply;
 325          }
 326  
 327          if ( ! preg_match( '!^(http|https|ftp)://!i', $package ) && file_exists( $package ) ) { // Local file or remote?
 328              return $package; // Must be a local file.
 329          }
 330  
 331          if ( empty( $package ) ) {
 332              return new WP_Error( 'no_package', $this->strings['no_package'] );
 333          }
 334  
 335          $this->skin->feedback( 'downloading_package', $package );
 336  
 337          $download_file = download_url( $package, 300, $check_signatures );
 338  
 339          if ( is_wp_error( $download_file ) && ! $download_file->get_error_data( 'softfail-filename' ) ) {
 340              return new WP_Error( 'download_failed', $this->strings['download_failed'], $download_file->get_error_message() );
 341          }
 342  
 343          return $download_file;
 344      }
 345  
 346      /**
 347       * Builds an unpredictable name for the directory a package is unpacked into.
 348       *
 349       * @since 7.1.4
 350       *
 351       * @param string $package The full local path or URI of the package.
 352       * @return string The directory name, without a path.
 353       */
 354  	private function get_working_dir_name( $package ) {
 355          // Strip off any .tmp or .zip suffixes.
 356          $name = basename( basename( $package, '.tmp' ), '.zip' );
 357  
 358          return $name . '-' . wp_generate_password( 16, false );
 359      }
 360  
 361      /**
 362       * Unpacks a compressed package file.
 363       *
 364       * @since 2.8.0
 365       *
 366       * @global WP_Filesystem_Base $wp_filesystem WordPress filesystem subclass.
 367       *
 368       * @param string $package        Full path to the package file.
 369       * @param bool   $delete_package Optional. Whether to delete the package file after attempting
 370       *                               to unpack it. Default true.
 371       * @return string|WP_Error The path to the unpacked contents, or a WP_Error on failure.
 372       */
 373  	public function unpack_package( $package, $delete_package = true ) {
 374          global $wp_filesystem;
 375  
 376          $this->skin->feedback( 'unpack_package' );
 377  
 378          if ( ! $wp_filesystem->wp_content_dir() ) {
 379              return new WP_Error( 'fs_no_content_dir', $this->strings['fs_no_content_dir'] );
 380          }
 381  
 382          $upgrade_folder = $wp_filesystem->wp_content_dir() . 'upgrade/';
 383  
 384          // Clean up contents of upgrade directory beforehand, keeping its index.php.
 385          $upgrade_files = $wp_filesystem->dirlist( $upgrade_folder );
 386          if ( ! empty( $upgrade_files ) ) {
 387              foreach ( $upgrade_files as $file ) {
 388                  if ( 'index.php' === $file['name'] ) {
 389                      continue;
 390                  }
 391  
 392                  $wp_filesystem->delete( $upgrade_folder . $file['name'], true );
 393              }
 394          }
 395  
 396          /*
 397           * The upgrade directory is web-served. Keep it from being listed, as core does
 398           * for wp-content and its plugins and themes directories, so the name of the
 399           * working directory created below cannot be discovered from a directory index.
 400           */
 401          if ( ! $wp_filesystem->is_dir( $upgrade_folder ) ) {
 402              $wp_filesystem->mkdir( $upgrade_folder, FS_CHMOD_DIR );
 403          }
 404          if ( ! $wp_filesystem->exists( $upgrade_folder . 'index.php' ) ) {
 405              $wp_filesystem->put_contents( $upgrade_folder . 'index.php', "<?php\n// Silence is golden.\n", FS_CHMOD_FILE );
 406          }
 407  
 408          // We need a working directory.
 409          $working_dir = $upgrade_folder . $this->get_working_dir_name( $package );
 410  
 411          // Clean up working directory.
 412          if ( $wp_filesystem->is_dir( $working_dir ) ) {
 413              $wp_filesystem->delete( $working_dir, true );
 414          }
 415  
 416          // Unzip package to working directory.
 417          $result = unzip_file( $package, $working_dir );
 418  
 419          // Once extracted, delete the package if required.
 420          if ( $delete_package ) {
 421              unlink( $package );
 422          }
 423  
 424          if ( is_wp_error( $result ) ) {
 425              $wp_filesystem->delete( $working_dir, true );
 426              if ( 'incompatible_archive' === $result->get_error_code() ) {
 427                  return new WP_Error( 'incompatible_archive', $this->strings['incompatible_archive'], $result->get_error_data() );
 428              }
 429              return $result;
 430          }
 431  
 432          return $working_dir;
 433      }
 434  
 435      /**
 436       * Flattens the results of WP_Filesystem_Base::dirlist() for iterating over.
 437       *
 438       * @since 4.9.0
 439       *
 440       * @param array  $nested_files Array of files as returned by WP_Filesystem_Base::dirlist().
 441       * @param string $path         Relative path to prepend to child nodes. Optional.
 442       * @return array A flattened array of the $nested_files specified.
 443       */
 444  	protected function flatten_dirlist( $nested_files, $path = '' ) {
 445          $files = array();
 446  
 447          foreach ( $nested_files as $name => $details ) {
 448              $files[ $path . $name ] = $details;
 449  
 450              // Append children recursively.
 451              if ( ! empty( $details['files'] ) ) {
 452                  $children = $this->flatten_dirlist( $details['files'], $path . $name . '/' );
 453  
 454                  // Merge keeping possible numeric keys, which array_merge() will reindex from 0..n.
 455                  $files = $files + $children;
 456              }
 457          }
 458  
 459          return $files;
 460      }
 461  
 462      /**
 463       * Clears the directory where this item is going to be installed into.
 464       *
 465       * @since 4.3.0
 466       *
 467       * @global WP_Filesystem_Base $wp_filesystem WordPress filesystem subclass.
 468       *
 469       * @param string $remote_destination The location on the remote filesystem to be cleared.
 470       * @return true|WP_Error True upon success, WP_Error on failure.
 471       */
 472  	public function clear_destination( $remote_destination ) {
 473          global $wp_filesystem;
 474  
 475          $files = $wp_filesystem->dirlist( $remote_destination, true, true );
 476  
 477          // False indicates that the $remote_destination doesn't exist.
 478          if ( false === $files ) {
 479              return true;
 480          }
 481  
 482          // Flatten the file list to iterate over.
 483          $files = $this->flatten_dirlist( $files );
 484  
 485          // Check all files are writable before attempting to clear the destination.
 486          $unwritable_files = array();
 487  
 488          // Check writability.
 489          foreach ( $files as $filename => $file_details ) {
 490              if ( ! $wp_filesystem->is_writable( $remote_destination . $filename ) ) {
 491                  // Attempt to alter permissions to allow writes and try again.
 492                  $wp_filesystem->chmod( $remote_destination . $filename, ( 'd' === $file_details['type'] ? FS_CHMOD_DIR : FS_CHMOD_FILE ) );
 493                  if ( ! $wp_filesystem->is_writable( $remote_destination . $filename ) ) {
 494                      $unwritable_files[] = $filename;
 495                  }
 496              }
 497          }
 498  
 499          if ( ! empty( $unwritable_files ) ) {
 500              return new WP_Error( 'files_not_writable', $this->strings['files_not_writable'], implode( ', ', $unwritable_files ) );
 501          }
 502  
 503          if ( ! $wp_filesystem->delete( $remote_destination, true ) ) {
 504              return new WP_Error( 'remove_old_failed', $this->strings['remove_old_failed'] );
 505          }
 506  
 507          return true;
 508      }
 509  
 510      /**
 511       * Install a package.
 512       *
 513       * Copies the contents of a package from a source directory, and installs them in
 514       * a destination directory. Optionally removes the source. It can also optionally
 515       * clear out the destination folder if it already exists.
 516       *
 517       * @since 2.8.0
 518       * @since 6.2.0 Use move_dir() instead of copy_dir() when possible.
 519       *
 520       * @global WP_Filesystem_Base $wp_filesystem        WordPress filesystem subclass.
 521       * @global string[]           $wp_theme_directories
 522       *
 523       * @param array|string $args {
 524       *     Optional. Array or string of arguments for installing a package. Default empty array.
 525       *
 526       *     @type string $source                      Required path to the package source. Default empty.
 527       *     @type string $destination                 Required path to a folder to install the package in.
 528       *                                               Default empty.
 529       *     @type bool   $clear_destination           Whether to delete any files already in the destination
 530       *                                               folder. Default false.
 531       *     @type bool   $clear_working               Whether to delete the files from the working directory
 532       *                                               after copying them to the destination. Default false.
 533       *     @type bool   $abort_if_destination_exists Whether to abort the installation if
 534       *                                               the destination folder already exists. Default true.
 535       *     @type array  $hook_extra                  Extra arguments to pass to the filter hooks called by
 536       *                                               WP_Upgrader::install_package(). Default empty array.
 537       * }
 538       *
 539       * @return array|WP_Error The result (also stored in `WP_Upgrader::$result`), or a WP_Error on failure.
 540       */
 541  	public function install_package( $args = array() ) {
 542          global $wp_filesystem, $wp_theme_directories;
 543  
 544          $defaults = array(
 545              'source'                      => '', // Please always pass this.
 546              'destination'                 => '', // ...and this.
 547              'clear_destination'           => false,
 548              'clear_working'               => false,
 549              'abort_if_destination_exists' => true,
 550              'hook_extra'                  => array(),
 551          );
 552  
 553          $args = wp_parse_args( $args, $defaults );
 554  
 555          $source            = $args['source'];
 556          $destination       = $args['destination'];
 557          $clear_destination = $args['clear_destination'];
 558  
 559          /*
 560           * Give the upgrade an additional 300 seconds (5 minutes) to ensure the install
 561           * doesn't prematurely timeout having used up the maximum script execution time
 562           * downloading and unpacking in WP_Upgrader->run().
 563           */
 564          if ( function_exists( 'set_time_limit' ) ) {
 565              set_time_limit( 300 );
 566          }
 567  
 568          if (
 569              ( ! is_string( $source ) || '' === $source || trim( $source ) !== $source ) ||
 570              ( ! is_string( $destination ) || '' === $destination || trim( $destination ) !== $destination )
 571          ) {
 572              return new WP_Error( 'bad_request', $this->strings['bad_request'] );
 573          }
 574          $this->skin->feedback( 'installing_package' );
 575  
 576          /**
 577           * Filters the installation response before the installation has started.
 578           *
 579           * Returning a value that could be evaluated as a `WP_Error` will effectively
 580           * short-circuit the installation, returning that value instead.
 581           *
 582           * @since 2.8.0
 583           *
 584           * @param bool|WP_Error $response   Installation response.
 585           * @param array         $hook_extra Extra arguments passed to hooked filters.
 586           */
 587          $res = apply_filters( 'upgrader_pre_install', true, $args['hook_extra'] );
 588  
 589          if ( is_wp_error( $res ) ) {
 590              return $res;
 591          }
 592  
 593          // Retain the original source and destinations.
 594          $remote_source     = $args['source'];
 595          $local_destination = $destination;
 596  
 597          $dirlist = $wp_filesystem->dirlist( $remote_source );
 598  
 599          if ( false === $dirlist ) {
 600              return new WP_Error( 'source_read_failed', $this->strings['fs_error'], $this->strings['dir_not_readable'] );
 601          }
 602  
 603          $source_files       = array_keys( $dirlist );
 604          $remote_destination = $wp_filesystem->find_folder( $local_destination );
 605  
 606          // Locate which directory to copy to the new folder. This is based on the actual folder holding the files.
 607          if ( 1 === count( $source_files ) && $wp_filesystem->is_dir( trailingslashit( $args['source'] ) . $source_files[0] . '/' ) ) {
 608              // Only one folder? Then we want its contents.
 609              $source = trailingslashit( $args['source'] ) . trailingslashit( $source_files[0] );
 610          } elseif ( 0 === count( $source_files ) ) {
 611              // There are no files?
 612              return new WP_Error( 'incompatible_archive_empty', $this->strings['incompatible_archive'], $this->strings['no_files'] );
 613          } else {
 614              /*
 615               * It's only a single file, the upgrader will use the folder name of this file as the destination folder.
 616               * Folder name is based on zip filename.
 617               */
 618              $source = trailingslashit( $args['source'] );
 619          }
 620  
 621          /**
 622           * Filters the source file location for the upgrade package.
 623           *
 624           * @since 2.8.0
 625           * @since 4.4.0 The `$hook_extra` parameter became available.
 626           *
 627           * @param string|WP_Error $source        File source location or a WP_Error object.
 628           * @param string          $remote_source Remote file source location.
 629           * @param WP_Upgrader     $upgrader      WP_Upgrader instance.
 630           * @param array           $hook_extra    Extra arguments passed to hooked filters.
 631           */
 632          $source = apply_filters( 'upgrader_source_selection', $source, $remote_source, $this, $args['hook_extra'] );
 633  
 634          if ( is_wp_error( $source ) ) {
 635              return $source;
 636          }
 637  
 638          if ( ! empty( $args['hook_extra']['temp_backup'] ) ) {
 639              $temp_backup = $this->move_to_temp_backup_dir( $args['hook_extra']['temp_backup'] );
 640  
 641              if ( is_wp_error( $temp_backup ) ) {
 642                  return $temp_backup;
 643              }
 644  
 645              $this->temp_backups[] = $args['hook_extra']['temp_backup'];
 646          }
 647  
 648          // Has the source location changed? If so, we need a new source_files list.
 649          if ( $source !== $remote_source ) {
 650              $dirlist = $wp_filesystem->dirlist( $source );
 651  
 652              if ( false === $dirlist ) {
 653                  return new WP_Error( 'new_source_read_failed', $this->strings['fs_error'], $this->strings['dir_not_readable'] );
 654              }
 655  
 656              $source_files = array_keys( $dirlist );
 657          }
 658  
 659          /*
 660           * Protection against deleting files in any important base directories.
 661           * Theme_Upgrader & Plugin_Upgrader also trigger this, as they pass the
 662           * destination directory (WP_PLUGIN_DIR / wp-content/themes) intending
 663           * to copy the directory into the directory, whilst they pass the source
 664           * as the actual files to copy.
 665           */
 666          $protected_directories = array( ABSPATH, WP_CONTENT_DIR, WP_PLUGIN_DIR, WP_CONTENT_DIR . '/themes' );
 667  
 668          if ( is_array( $wp_theme_directories ) ) {
 669              $protected_directories = array_merge( $protected_directories, $wp_theme_directories );
 670          }
 671  
 672          if ( in_array( $destination, $protected_directories, true ) ) {
 673              $remote_destination = trailingslashit( $remote_destination ) . trailingslashit( basename( $source ) );
 674              $destination        = trailingslashit( $destination ) . trailingslashit( basename( $source ) );
 675          }
 676  
 677          if ( $clear_destination ) {
 678              // We're going to clear the destination if there's something there.
 679              $this->skin->feedback( 'remove_old' );
 680  
 681              $removed = $this->clear_destination( $remote_destination );
 682  
 683              /**
 684               * Filters whether the upgrader cleared the destination.
 685               *
 686               * @since 2.8.0
 687               *
 688               * @param true|WP_Error $removed            Whether the destination was cleared.
 689               *                                          True upon success, WP_Error on failure.
 690               * @param string        $local_destination  The local package destination.
 691               * @param string        $remote_destination The remote package destination.
 692               * @param array         $hook_extra         Extra arguments passed to hooked filters.
 693               */
 694              $removed = apply_filters( 'upgrader_clear_destination', $removed, $local_destination, $remote_destination, $args['hook_extra'] );
 695  
 696              if ( is_wp_error( $removed ) ) {
 697                  return $removed;
 698              }
 699          } elseif ( $args['abort_if_destination_exists'] && $wp_filesystem->exists( $remote_destination ) ) {
 700              /*
 701               * If we're not clearing the destination folder and something exists there already, bail.
 702               * But first check to see if there are actually any files in the folder.
 703               */
 704              $_files = $wp_filesystem->dirlist( $remote_destination );
 705              if ( ! empty( $_files ) ) {
 706                  $wp_filesystem->delete( $remote_source, true ); // Clear out the source files.
 707                  return new WP_Error( 'folder_exists', $this->strings['folder_exists'], $remote_destination );
 708              }
 709          }
 710  
 711          /*
 712           * If 'clear_working' is false, the source should not be removed, so use copy_dir() instead.
 713           *
 714           * Partial updates, like language packs, may want to retain the destination.
 715           * If the destination exists or has contents, this may be a partial update,
 716           * and the destination should not be removed, so use copy_dir() instead.
 717           */
 718          if ( $args['clear_working']
 719              && (
 720                  // Destination does not exist or has no contents.
 721                  ! $wp_filesystem->exists( $remote_destination )
 722                  || empty( $wp_filesystem->dirlist( $remote_destination ) )
 723              )
 724          ) {
 725              $result = move_dir( $source, $remote_destination, true );
 726          } else {
 727              // Create destination if needed.
 728              if ( ! $wp_filesystem->exists( $remote_destination ) ) {
 729                  if ( ! $wp_filesystem->mkdir( $remote_destination, FS_CHMOD_DIR ) ) {
 730                      return new WP_Error( 'mkdir_failed_destination', $this->strings['mkdir_failed'], $remote_destination );
 731                  }
 732              }
 733              $result = copy_dir( $source, $remote_destination );
 734          }
 735  
 736          // Clear the working directory?
 737          if ( $args['clear_working'] ) {
 738              $wp_filesystem->delete( $remote_source, true );
 739          }
 740  
 741          if ( is_wp_error( $result ) ) {
 742              return $result;
 743          }
 744  
 745          $destination_name = basename( str_replace( $local_destination, '', $destination ) );
 746          if ( '.' === $destination_name ) {
 747              $destination_name = '';
 748          }
 749  
 750          $this->result = compact( 'source', 'source_files', 'destination', 'destination_name', 'local_destination', 'remote_destination', 'clear_destination' );
 751  
 752          /**
 753           * Filters the installation response after the installation has finished.
 754           *
 755           * @since 2.8.0
 756           *
 757           * @param bool  $response   Installation response.
 758           * @param array $hook_extra Extra arguments passed to hooked filters.
 759           * @param array $result     Installation result data.
 760           */
 761          $res = apply_filters( 'upgrader_post_install', true, $args['hook_extra'], $this->result );
 762  
 763          if ( is_wp_error( $res ) ) {
 764              $this->result = $res;
 765              return $res;
 766          }
 767  
 768          // Bombard the calling function will all the info which we've just used.
 769          return $this->result;
 770      }
 771  
 772      /**
 773       * Runs an upgrade/installation.
 774       *
 775       * Attempts to download the package (if it is not a local file), unpack it, and
 776       * install it in the destination folder.
 777       *
 778       * @since 2.8.0
 779       *
 780       * @param array $options {
 781       *     Array or string of arguments for upgrading/installing a package.
 782       *
 783       *     @type string $package                     The full path or URI of the package to install.
 784       *                                               Default empty.
 785       *     @type string $destination                 The full path to the destination folder.
 786       *                                               Default empty.
 787       *     @type bool   $clear_destination           Whether to delete any files already in the
 788       *                                               destination folder. Default false.
 789       *     @type bool   $clear_working               Whether to delete the files from the working
 790       *                                               directory after copying them to the destination.
 791       *                                               Default true.
 792       *     @type bool   $abort_if_destination_exists Whether to abort the installation if the destination
 793       *                                               folder already exists. When true, `$clear_destination`
 794       *                                               should be false. Default true.
 795       *     @type bool   $is_multi                    Whether this run is one of multiple upgrade/installation
 796       *                                               actions being performed in bulk. When true, the skin
 797       *                                               WP_Upgrader::header() and WP_Upgrader::footer()
 798       *                                               aren't called. Default false.
 799       *     @type array  $hook_extra                  Extra arguments to pass to the filter hooks called by
 800       *                                               WP_Upgrader::run().
 801       * }
 802       * @return array|false|WP_Error The result from self::install_package() on success, otherwise a WP_Error,
 803       *                              or false if unable to connect to the filesystem.
 804       */
 805  	public function run( $options ) {
 806  
 807          $defaults = array(
 808              'package'                     => '', // Please always pass this.
 809              'destination'                 => '', // ...and this.
 810              'clear_destination'           => false,
 811              'clear_working'               => true,
 812              'abort_if_destination_exists' => true, // Abort if the destination directory exists. Pass clear_destination as false please.
 813              'is_multi'                    => false,
 814              'hook_extra'                  => array(), // Pass any extra $hook_extra args here, this will be passed to any hooked filters.
 815          );
 816  
 817          $options = wp_parse_args( $options, $defaults );
 818  
 819          /**
 820           * Filters the package options before running an update.
 821           *
 822           * See also {@see 'upgrader_process_complete'}.
 823           *
 824           * @since 4.3.0
 825           *
 826           * @param array $options {
 827           *     Options used by the upgrader.
 828           *
 829           *     @type string $package                     Package for update.
 830           *     @type string $destination                 Update location.
 831           *     @type bool   $clear_destination           Clear the destination resource.
 832           *     @type bool   $clear_working               Clear the working resource.
 833           *     @type bool   $abort_if_destination_exists Abort if the Destination directory exists.
 834           *     @type bool   $is_multi                    Whether the upgrader is running multiple times.
 835           *     @type array  $hook_extra {
 836           *         Extra hook arguments.
 837           *
 838           *         @type string $action               Type of action. Default 'update'.
 839           *         @type string $type                 Type of update process. Accepts 'plugin', 'theme', or 'core'.
 840           *         @type bool   $bulk                 Whether the update process is a bulk update. Default true.
 841           *         @type string $plugin               Path to the plugin file relative to the plugins directory.
 842           *         @type string $theme                The stylesheet or template name of the theme.
 843           *         @type string $language_update_type The language pack update type. Accepts 'plugin', 'theme',
 844           *                                            or 'core'.
 845           *         @type object $language_update      The language pack update offer.
 846           *     }
 847           * }
 848           */
 849          $options = apply_filters( 'upgrader_package_options', $options );
 850  
 851          if ( ! $options['is_multi'] ) { // Call $this->header separately if running multiple times.
 852              $this->skin->header();
 853          }
 854  
 855          // Connect to the filesystem first.
 856          $res = $this->fs_connect( array( WP_CONTENT_DIR, $options['destination'] ) );
 857          // Mainly for non-connected filesystem.
 858          if ( ! $res ) {
 859              if ( ! $options['is_multi'] ) {
 860                  $this->skin->footer();
 861              }
 862              return false;
 863          }
 864  
 865          $this->skin->before();
 866  
 867          if ( is_wp_error( $res ) ) {
 868              $this->skin->error( $res );
 869              $this->skin->after();
 870              if ( ! $options['is_multi'] ) {
 871                  $this->skin->footer();
 872              }
 873              return $res;
 874          }
 875  
 876          /*
 877           * Download the package. Note: If the package is the full path
 878           * to an existing local file, it will be returned untouched.
 879           */
 880          $download = $this->download_package( $options['package'], false, $options['hook_extra'] );
 881  
 882          /*
 883           * Allow for signature soft-fail.
 884           * WARNING: This may be removed in the future.
 885           */
 886          if ( is_wp_error( $download ) && $download->get_error_data( 'softfail-filename' ) ) {
 887  
 888              // Don't output the 'no signature could be found' failure message for now.
 889              if ( 'signature_verification_no_signature' !== $download->get_error_code() || WP_DEBUG ) {
 890                  // Output the failure error as a normal feedback, and not as an error.
 891                  $this->skin->feedback( $download->get_error_message() );
 892  
 893                  // Report this failure back to WordPress.org for debugging purposes.
 894                  wp_version_check(
 895                      array(
 896                          'signature_failure_code' => $download->get_error_code(),
 897                          'signature_failure_data' => $download->get_error_data(),
 898                      )
 899                  );
 900              }
 901  
 902              // Pretend this error didn't happen.
 903              $download = $download->get_error_data( 'softfail-filename' );
 904          }
 905  
 906          if ( is_wp_error( $download ) ) {
 907              $this->skin->error( $download );
 908              $this->skin->after();
 909              if ( ! $options['is_multi'] ) {
 910                  $this->skin->footer();
 911              }
 912              return $download;
 913          }
 914  
 915          $delete_package = ( $download !== $options['package'] ); // Do not delete a "local" file.
 916  
 917          // Unzips the file into a temporary directory.
 918          $working_dir = $this->unpack_package( $download, $delete_package );
 919          if ( is_wp_error( $working_dir ) ) {
 920              $this->skin->error( $working_dir );
 921              $this->skin->after();
 922              if ( ! $options['is_multi'] ) {
 923                  $this->skin->footer();
 924              }
 925              return $working_dir;
 926          }
 927  
 928          // With the given options, this installs it to the destination directory.
 929          $result = $this->install_package(
 930              array(
 931                  'source'                      => $working_dir,
 932                  'destination'                 => $options['destination'],
 933                  'clear_destination'           => $options['clear_destination'],
 934                  'abort_if_destination_exists' => $options['abort_if_destination_exists'],
 935                  'clear_working'               => $options['clear_working'],
 936                  'hook_extra'                  => $options['hook_extra'],
 937              )
 938          );
 939  
 940          /**
 941           * Filters the result of WP_Upgrader::install_package().
 942           *
 943           * @since 5.7.0
 944           *
 945           * @param array|WP_Error $result     Result from WP_Upgrader::install_package().
 946           * @param array          $hook_extra Extra arguments passed to hooked filters.
 947           */
 948          $result = apply_filters( 'upgrader_install_package_result', $result, $options['hook_extra'] );
 949  
 950          $this->skin->set_result( $result );
 951  
 952          if ( is_wp_error( $result ) ) {
 953              // An automatic plugin update will have already performed its rollback.
 954              if ( ! empty( $options['hook_extra']['temp_backup'] ) ) {
 955                  $this->temp_restores[] = $options['hook_extra']['temp_backup'];
 956  
 957                  /*
 958                   * Restore the backup on shutdown.
 959                   * Actions running on `shutdown` are immune to PHP timeouts,
 960                   * so in case the failure was due to a PHP timeout,
 961                   * it will still be able to properly restore the previous version.
 962                   *
 963                   * Zero arguments are accepted as a string can sometimes be passed
 964                   * internally during actions, causing an error because
 965                   * `WP_Upgrader::restore_temp_backup()` expects an array.
 966                   */
 967                  add_action( 'shutdown', array( $this, 'restore_temp_backup' ), 10, 0 );
 968              }
 969              $this->skin->error( $result );
 970  
 971              if ( ! method_exists( $this->skin, 'hide_process_failed' ) || ! $this->skin->hide_process_failed( $result ) ) {
 972                  $this->skin->feedback( 'process_failed' );
 973              }
 974          } else {
 975              // Installation succeeded.
 976              $this->skin->feedback( 'process_success' );
 977          }
 978  
 979          $this->skin->after();
 980  
 981          // Clean up the backup kept in the temporary backup directory.
 982          if ( ! empty( $options['hook_extra']['temp_backup'] ) ) {
 983              // Delete the backup on `shutdown` to avoid a PHP timeout.
 984              add_action( 'shutdown', array( $this, 'delete_temp_backup' ), 100, 0 );
 985          }
 986  
 987          if ( ! $options['is_multi'] ) {
 988  
 989              /**
 990               * Fires when the upgrader process is complete.
 991               *
 992               * See also {@see 'upgrader_package_options'}.
 993               *
 994               * @since 3.6.0
 995               * @since 3.7.0 Added to WP_Upgrader::run().
 996               * @since 4.6.0 `$translations` was added as a possible argument to `$hook_extra`.
 997               *
 998               * @param WP_Upgrader $upgrader   WP_Upgrader instance. In other contexts this might be a
 999               *                                Theme_Upgrader, Plugin_Upgrader, Core_Upgrade, or Language_Pack_Upgrader instance.
1000               * @param array       $hook_extra {
1001               *     Array of bulk item update data.
1002               *
1003               *     @type string $action       Type of action. Default 'update'.
1004               *     @type string $type         Type of update process. Accepts 'plugin', 'theme', 'translation', or 'core'.
1005               *     @type bool   $bulk         Whether the update process is a bulk update. Default true.
1006               *     @type array  $plugins      Array of the basename paths of the plugins' main files.
1007               *     @type array  $themes       The theme slugs.
1008               *     @type array  $translations {
1009               *         Array of translations update data.
1010               *
1011               *         @type string $language The locale the translation is for.
1012               *         @type string $type     Type of translation. Accepts 'plugin', 'theme', or 'core'.
1013               *         @type string $slug     Text domain the translation is for. The slug of a theme/plugin or
1014               *                                'default' for core translations.
1015               *         @type string $version  The version of a theme, plugin, or core.
1016               *     }
1017               * }
1018               */
1019              do_action( 'upgrader_process_complete', $this, $options['hook_extra'] );
1020  
1021              $this->skin->footer();
1022          }
1023  
1024          return $result;
1025      }
1026  
1027      /**
1028       * Toggles maintenance mode for the site.
1029       *
1030       * Creates/deletes the maintenance file to enable/disable maintenance mode.
1031       *
1032       * @since 2.8.0
1033       *
1034       * @global WP_Filesystem_Base $wp_filesystem WordPress filesystem subclass.
1035       *
1036       * @param bool $enable True to enable maintenance mode, false to disable.
1037       */
1038  	public function maintenance_mode( $enable = false ) {
1039          global $wp_filesystem;
1040  
1041          if ( ! $wp_filesystem ) {
1042              if ( ! function_exists( 'WP_Filesystem' ) ) {
1043                  require_once  ABSPATH . 'wp-admin/includes/file.php';
1044              }
1045  
1046              ob_start();
1047              $credentials = request_filesystem_credentials( '' );
1048              ob_end_clean();
1049  
1050              if ( false === $credentials || ! WP_Filesystem( $credentials ) ) {
1051                  wp_trigger_error( __FUNCTION__, __( 'Could not access filesystem.' ) );
1052                  return;
1053              }
1054          }
1055  
1056          $file = $wp_filesystem->abspath() . '.maintenance';
1057  
1058          if ( $enable ) {
1059              if ( ! wp_doing_cron() ) {
1060                  $this->skin->feedback( 'maintenance_start' );
1061              }
1062  
1063              // Create maintenance file to signal that we are upgrading.
1064              $maintenance_string = '<?php $upgrading = ' . time() . '; ?>';
1065              $wp_filesystem->delete( $file );
1066              $wp_filesystem->put_contents( $file, $maintenance_string, FS_CHMOD_FILE );
1067          } elseif ( $wp_filesystem->exists( $file ) ) {
1068              if ( ! wp_doing_cron() ) {
1069                  $this->skin->feedback( 'maintenance_end' );
1070              }
1071  
1072              $wp_filesystem->delete( $file );
1073          }
1074      }
1075  
1076      /**
1077       * Creates a lock using WordPress options.
1078       *
1079       * @since 4.5.0
1080       *
1081       * @global wpdb $wpdb The WordPress database abstraction object.
1082       *
1083       * @param string $lock_name       The name of this unique lock.
1084       * @param int    $release_timeout Optional. The duration in seconds to respect an existing lock.
1085       *                                Default: 1 hour.
1086       * @return bool False if a lock couldn't be created or if the lock is still valid. True otherwise.
1087       */
1088  	public static function create_lock( $lock_name, $release_timeout = null ) {
1089          global $wpdb;
1090          if ( ! $release_timeout ) {
1091              $release_timeout = HOUR_IN_SECONDS;
1092          }
1093          $lock_option = $lock_name . '.lock';
1094  
1095          // Try to lock.
1096          $lock_result = $wpdb->query( $wpdb->prepare( "INSERT IGNORE INTO `$wpdb->options` ( `option_name`, `option_value`, `autoload` ) VALUES (%s, %s, 'off') /* LOCK */", $lock_option, time() ) );
1097  
1098          if ( ! $lock_result ) {
1099              $lock_result = get_option( $lock_option );
1100  
1101              // If a lock couldn't be created, and there isn't a lock, bail.
1102              if ( ! $lock_result ) {
1103                  return false;
1104              }
1105  
1106              // Check to see if the lock is still valid. If it is, bail.
1107              if ( $lock_result > ( time() - $release_timeout ) ) {
1108                  return false;
1109              }
1110  
1111              // There must exist an expired lock, clear it and re-gain it.
1112              WP_Upgrader::release_lock( $lock_name );
1113  
1114              return WP_Upgrader::create_lock( $lock_name, $release_timeout );
1115          }
1116  
1117          // Update the lock, as by this point we've definitely got a lock, just need to fire the actions.
1118          update_option( $lock_option, time(), false );
1119  
1120          return true;
1121      }
1122  
1123      /**
1124       * Releases an upgrader lock.
1125       *
1126       * @since 4.5.0
1127       *
1128       * @see WP_Upgrader::create_lock()
1129       *
1130       * @param string $lock_name The name of this unique lock.
1131       * @return bool True if the lock was successfully released. False on failure.
1132       */
1133  	public static function release_lock( $lock_name ) {
1134          return delete_option( $lock_name . '.lock' );
1135      }
1136  
1137      /**
1138       * Moves the plugin or theme being updated into a temporary backup directory.
1139       *
1140       * @since 6.3.0
1141       *
1142       * @global WP_Filesystem_Base $wp_filesystem WordPress filesystem subclass.
1143       *
1144       * @param string[] $args {
1145       *     Array of data for the temporary backup.
1146       *
1147       *     @type string $slug Plugin or theme slug.
1148       *     @type string $src  Path to the root directory for plugins or themes.
1149       *     @type string $dir  Destination subdirectory name. Accepts 'plugins' or 'themes'.
1150       * }
1151       *
1152       * @return bool|WP_Error True on success, false on early exit, otherwise WP_Error.
1153       */
1154  	public function move_to_temp_backup_dir( $args ) {
1155          global $wp_filesystem;
1156  
1157          if ( empty( $args['slug'] ) || empty( $args['src'] ) || empty( $args['dir'] ) ) {
1158              return false;
1159          }
1160  
1161          /*
1162           * Skip any plugin that has "." as its slug.
1163           * A slug of "." will result in a `$src` value ending in a period.
1164           *
1165           * On Windows, this will cause the 'plugins' folder to be moved,
1166           * and will cause a failure when attempting to call `mkdir()`.
1167           */
1168          if ( '.' === $args['slug'] ) {
1169              return false;
1170          }
1171  
1172          if ( ! $wp_filesystem->wp_content_dir() ) {
1173              return new WP_Error( 'fs_no_content_dir', $this->strings['fs_no_content_dir'] );
1174          }
1175  
1176          $dest_dir = $wp_filesystem->wp_content_dir() . 'upgrade-temp-backup/';
1177          $sub_dir  = $dest_dir . $args['dir'] . '/';
1178  
1179          // Create the temporary backup directory if it does not exist.
1180          if ( ! $wp_filesystem->is_dir( $sub_dir ) ) {
1181              if ( ! $wp_filesystem->is_dir( $dest_dir ) ) {
1182                  $wp_filesystem->mkdir( $dest_dir, FS_CHMOD_DIR );
1183              }
1184  
1185              if ( ! $wp_filesystem->mkdir( $sub_dir, FS_CHMOD_DIR ) ) {
1186                  // Could not create the backup directory.
1187                  return new WP_Error( 'fs_temp_backup_mkdir', $this->strings['temp_backup_mkdir_failed'] );
1188              }
1189          }
1190  
1191          $src_dir = $wp_filesystem->find_folder( $args['src'] );
1192          $src     = trailingslashit( $src_dir ) . $args['slug'];
1193          $dest    = $dest_dir . trailingslashit( $args['dir'] ) . $args['slug'];
1194  
1195          // Delete the temporary backup directory if it already exists.
1196          if ( $wp_filesystem->is_dir( $dest ) ) {
1197              $wp_filesystem->delete( $dest, true );
1198          }
1199  
1200          // Move to the temporary backup directory.
1201          $result = move_dir( $src, $dest, true );
1202          if ( is_wp_error( $result ) ) {
1203              return new WP_Error( 'fs_temp_backup_move', $this->strings['temp_backup_move_failed'] );
1204          }
1205  
1206          return true;
1207      }
1208  
1209      /**
1210       * Restores the plugin or theme from temporary backup.
1211       *
1212       * @since 6.3.0
1213       * @since 6.6.0 Added the `$temp_backups` parameter.
1214       *
1215       * @global WP_Filesystem_Base $wp_filesystem WordPress filesystem subclass.
1216       *
1217       * @param array[] $temp_backups {
1218       *     Optional. An array of temporary backups.
1219       *
1220       *     @type array ...$0 {
1221       *         Information about the backup.
1222       *
1223       *         @type string $dir  The temporary backup location in the upgrade-temp-backup directory.
1224       *         @type string $slug The item's slug.
1225       *         @type string $src  The directory where the original is stored. For example, `WP_PLUGIN_DIR`.
1226       *     }
1227       * }
1228       * @return bool|WP_Error True on success, false on early exit, otherwise WP_Error.
1229       */
1230  	public function restore_temp_backup( array $temp_backups = array() ) {
1231          global $wp_filesystem;
1232  
1233          $errors = new WP_Error();
1234  
1235          if ( empty( $temp_backups ) ) {
1236              $temp_backups = $this->temp_restores;
1237          }
1238  
1239          foreach ( $temp_backups as $args ) {
1240              if ( empty( $args['slug'] ) || empty( $args['src'] ) || empty( $args['dir'] ) ) {
1241                  return false;
1242              }
1243  
1244              if ( ! $wp_filesystem->wp_content_dir() ) {
1245                  $errors->add( 'fs_no_content_dir', $this->strings['fs_no_content_dir'] );
1246                  return $errors;
1247              }
1248  
1249              $src      = $wp_filesystem->wp_content_dir() . 'upgrade-temp-backup/' . $args['dir'] . '/' . $args['slug'];
1250              $dest_dir = $wp_filesystem->find_folder( $args['src'] );
1251              $dest     = trailingslashit( $dest_dir ) . $args['slug'];
1252  
1253              if ( $wp_filesystem->is_dir( $src ) ) {
1254                  // Cleanup.
1255                  if ( $wp_filesystem->is_dir( $dest ) && ! $wp_filesystem->delete( $dest, true ) ) {
1256                      $errors->add(
1257                          'fs_temp_backup_delete',
1258                          sprintf( $this->strings['temp_backup_restore_failed'], $args['slug'] )
1259                      );
1260                      continue;
1261                  }
1262  
1263                  // Move it.
1264                  $result = move_dir( $src, $dest, true );
1265                  if ( is_wp_error( $result ) ) {
1266                      $errors->add(
1267                          'fs_temp_backup_delete',
1268                          sprintf( $this->strings['temp_backup_restore_failed'], $args['slug'] )
1269                      );
1270                      continue;
1271                  }
1272              }
1273          }
1274  
1275          return $errors->has_errors() ? $errors : true;
1276      }
1277  
1278      /**
1279       * Deletes a temporary backup.
1280       *
1281       * @since 6.3.0
1282       * @since 6.6.0 Added the `$temp_backups` parameter.
1283       *
1284       * @global WP_Filesystem_Base $wp_filesystem WordPress filesystem subclass.
1285       *
1286       * @param array[] $temp_backups {
1287       *     Optional. An array of temporary backups.
1288       *
1289       *     @type array ...$0 {
1290       *         Information about the backup.
1291       *
1292       *         @type string $dir  The temporary backup location in the upgrade-temp-backup directory.
1293       *         @type string $slug The item's slug.
1294       *         @type string $src  The directory where the original is stored. For example, `WP_PLUGIN_DIR`.
1295       *     }
1296       * }
1297       * @return bool|WP_Error True on success, false on early exit, otherwise WP_Error.
1298       */
1299  	public function delete_temp_backup( array $temp_backups = array() ) {
1300          global $wp_filesystem;
1301  
1302          $errors = new WP_Error();
1303  
1304          if ( empty( $temp_backups ) ) {
1305              $temp_backups = $this->temp_backups;
1306          }
1307  
1308          foreach ( $temp_backups as $args ) {
1309              if ( empty( $args['slug'] ) || empty( $args['dir'] ) ) {
1310                  return false;
1311              }
1312  
1313              if ( ! $wp_filesystem->wp_content_dir() ) {
1314                  $errors->add( 'fs_no_content_dir', $this->strings['fs_no_content_dir'] );
1315                  return $errors;
1316              }
1317  
1318              $temp_backup_dir = $wp_filesystem->wp_content_dir() . "upgrade-temp-backup/{$args['dir']}/{$args['slug']}";
1319  
1320              if ( ! $wp_filesystem->delete( $temp_backup_dir, true ) ) {
1321                  $errors->add(
1322                      'temp_backup_delete_failed',
1323                      sprintf( $this->strings['temp_backup_delete_failed'], $args['slug'] )
1324                  );
1325                  continue;
1326              }
1327          }
1328  
1329          return $errors->has_errors() ? $errors : true;
1330      }
1331  }
1332  
1333  /** Plugin_Upgrader class */
1334  require_once  ABSPATH . 'wp-admin/includes/class-plugin-upgrader.php';
1335  
1336  /** Theme_Upgrader class */
1337  require_once  ABSPATH . 'wp-admin/includes/class-theme-upgrader.php';
1338  
1339  /** Language_Pack_Upgrader class */
1340  require_once  ABSPATH . 'wp-admin/includes/class-language-pack-upgrader.php';
1341  
1342  /** Core_Upgrader class */
1343  require_once  ABSPATH . 'wp-admin/includes/class-core-upgrader.php';
1344  
1345  /** File_Upload_Upgrader class */
1346  require_once  ABSPATH . 'wp-admin/includes/class-file-upload-upgrader.php';
1347  
1348  /** WP_Automatic_Updater class */
1349  require_once  ABSPATH . 'wp-admin/includes/class-wp-automatic-updater.php';


Generated : Fri Nov 13 08:20:01 2026 Cross-referenced by PHPXref