[ Index ]

PHP Cross Reference of WordPress

title

Body

[close]

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

   1  <?php
   2  /**
   3   * Upgrade API: Plugin_Upgrader class
   4   *
   5   * @package WordPress
   6   * @subpackage Upgrader
   7   * @since 4.6.0
   8   */
   9  
  10  /**
  11   * Core class used for upgrading/installing plugins.
  12   *
  13   * It is designed to upgrade/install plugins from a local zip, remote zip URL,
  14   * or uploaded zip file.
  15   *
  16   * @since 2.8.0
  17   * @since 4.6.0 Moved to its own file from wp-admin/includes/class-wp-upgrader.php.
  18   *
  19   * @see WP_Upgrader
  20   */
  21  class Plugin_Upgrader extends WP_Upgrader {
  22  
  23      /**
  24       * Plugin upgrade result.
  25       *
  26       * @since 2.8.0
  27       * @var array|WP_Error $result
  28       *
  29       * @see WP_Upgrader::$result
  30       */
  31      public $result;
  32  
  33      /**
  34       * Whether a bulk upgrade/installation is being performed.
  35       *
  36       * @since 2.9.0
  37       * @var bool $bulk
  38       */
  39      public $bulk = false;
  40  
  41      /**
  42       * Initialize the upgrade strings.
  43       *
  44       * @since 2.8.0
  45       */
  46  	public function upgrade_strings() {
  47          $this->strings['up_to_date'] = __( 'The plugin is at the latest version.' );
  48          $this->strings['no_package'] = __( 'Update package not available.' );
  49          /* translators: %s: Package URL. */
  50          $this->strings['downloading_package']  = sprintf( __( 'Downloading update from %s&#8230;' ), '<span class="code">%s</span>' );
  51          $this->strings['unpack_package']       = __( 'Unpacking the update&#8230;' );
  52          $this->strings['remove_old']           = __( 'Removing the old version of the plugin&#8230;' );
  53          $this->strings['remove_old_failed']    = __( 'Could not remove the old plugin.' );
  54          $this->strings['process_failed']       = __( 'Plugin update failed.' );
  55          $this->strings['process_success']      = __( 'Plugin updated successfully.' );
  56          $this->strings['process_bulk_success'] = __( 'Plugins updated successfully.' );
  57      }
  58  
  59      /**
  60       * Initialize the installation strings.
  61       *
  62       * @since 2.8.0
  63       */
  64  	public function install_strings() {
  65          $this->strings['no_package'] = __( 'Installation package not available.' );
  66          /* translators: %s: Package URL. */
  67          $this->strings['downloading_package'] = sprintf( __( 'Downloading installation package from %s&#8230;' ), '<span class="code">%s</span>' );
  68          $this->strings['unpack_package']      = __( 'Unpacking the package&#8230;' );
  69          $this->strings['installing_package']  = __( 'Installing the plugin&#8230;' );
  70          $this->strings['no_files']            = __( 'The plugin contains no files.' );
  71          $this->strings['process_failed']      = __( 'Plugin installation failed.' );
  72          $this->strings['process_success']     = __( 'Plugin installed successfully.' );
  73      }
  74  
  75      /**
  76       * Install a plugin package.
  77       *
  78       * @since 2.8.0
  79       * @since 3.7.0 The `$args` parameter was added, making clearing the plugin update cache optional.
  80       *
  81       * @param string $package The full local path or URI of the package.
  82       * @param array  $args {
  83       *     Optional. Other arguments for installing a plugin package. Default empty array.
  84       *
  85       *     @type bool $clear_update_cache Whether to clear the plugin updates cache if successful.
  86       *                                    Default true.
  87       * }
  88       * @return bool|WP_Error True if the installation was successful, false or a WP_Error otherwise.
  89       */
  90  	public function install( $package, $args = array() ) {
  91  
  92          $defaults    = array(
  93              'clear_update_cache' => true,
  94          );
  95          $parsed_args = wp_parse_args( $args, $defaults );
  96  
  97          $this->init();
  98          $this->install_strings();
  99  
 100          add_filter( 'upgrader_source_selection', array( $this, 'check_package' ) );
 101          if ( $parsed_args['clear_update_cache'] ) {
 102              // Clear cache so wp_update_plugins() knows about the new plugin.
 103              add_action( 'upgrader_process_complete', 'wp_clean_plugins_cache', 9, 0 );
 104          }
 105  
 106          $this->run(
 107              array(
 108                  'package'           => $package,
 109                  'destination'       => WP_PLUGIN_DIR,
 110                  'clear_destination' => false, // Do not overwrite files.
 111                  'clear_working'     => true,
 112                  'hook_extra'        => array(
 113                      'type'   => 'plugin',
 114                      'action' => 'install',
 115                  ),
 116              )
 117          );
 118  
 119          remove_action( 'upgrader_process_complete', 'wp_clean_plugins_cache', 9 );
 120          remove_filter( 'upgrader_source_selection', array( $this, 'check_package' ) );
 121  
 122          if ( ! $this->result || is_wp_error( $this->result ) ) {
 123              return $this->result;
 124          }
 125  
 126          // Force refresh of plugin update information.
 127          wp_clean_plugins_cache( $parsed_args['clear_update_cache'] );
 128  
 129          return true;
 130      }
 131  
 132      /**
 133       * Upgrade a plugin.
 134       *
 135       * @since 2.8.0
 136       * @since 3.7.0 The `$args` parameter was added, making clearing the plugin update cache optional.
 137       *
 138       * @param string $plugin Path to the plugin file relative to the plugins directory.
 139       * @param array  $args {
 140       *     Optional. Other arguments for upgrading a plugin package. Default empty array.
 141       *
 142       *     @type bool $clear_update_cache Whether to clear the plugin updates cache if successful.
 143       *                                    Default true.
 144       * }
 145       * @return bool|WP_Error True if the upgrade was successful, false or a WP_Error object otherwise.
 146       */
 147  	public function upgrade( $plugin, $args = array() ) {
 148  
 149          $defaults    = array(
 150              'clear_update_cache' => true,
 151          );
 152          $parsed_args = wp_parse_args( $args, $defaults );
 153  
 154          $this->init();
 155          $this->upgrade_strings();
 156  
 157          $current = get_site_transient( 'update_plugins' );
 158          if ( ! isset( $current->response[ $plugin ] ) ) {
 159              $this->skin->before();
 160              $this->skin->set_result( false );
 161              $this->skin->error( 'up_to_date' );
 162              $this->skin->after();
 163              return false;
 164          }
 165  
 166          // Get the URL to the zip file.
 167          $r = $current->response[ $plugin ];
 168  
 169          add_filter( 'upgrader_pre_install', array( $this, 'deactivate_plugin_before_upgrade' ), 10, 2 );
 170          add_filter( 'upgrader_pre_install', array( $this, 'active_before' ), 10, 2 );
 171          add_filter( 'upgrader_clear_destination', array( $this, 'delete_old_plugin' ), 10, 4 );
 172          add_filter( 'upgrader_post_install', array( $this, 'active_after' ), 10, 2 );
 173          // There's a Trac ticket to move up the directory for zips which are made a bit differently, useful for non-.org plugins.
 174          // 'source_selection' => array( $this, 'source_selection' ),
 175          if ( $parsed_args['clear_update_cache'] ) {
 176              // Clear cache so wp_update_plugins() knows about the new plugin.
 177              add_action( 'upgrader_process_complete', 'wp_clean_plugins_cache', 9, 0 );
 178          }
 179  
 180          $this->run(
 181              array(
 182                  'package'           => $r->package,
 183                  'destination'       => WP_PLUGIN_DIR,
 184                  'clear_destination' => true,
 185                  'clear_working'     => true,
 186                  'hook_extra'        => array(
 187                      'plugin' => $plugin,
 188                      'type'   => 'plugin',
 189                      'action' => 'update',
 190                  ),
 191              )
 192          );
 193  
 194          // Cleanup our hooks, in case something else does a upgrade on this connection.
 195          remove_action( 'upgrader_process_complete', 'wp_clean_plugins_cache', 9 );
 196          remove_filter( 'upgrader_pre_install', array( $this, 'deactivate_plugin_before_upgrade' ) );
 197          remove_filter( 'upgrader_pre_install', array( $this, 'active_before' ) );
 198          remove_filter( 'upgrader_clear_destination', array( $this, 'delete_old_plugin' ) );
 199          remove_filter( 'upgrader_post_install', array( $this, 'active_after' ) );
 200  
 201          if ( ! $this->result || is_wp_error( $this->result ) ) {
 202              return $this->result;
 203          }
 204  
 205          // Force refresh of plugin update information.
 206          wp_clean_plugins_cache( $parsed_args['clear_update_cache'] );
 207  
 208          return true;
 209      }
 210  
 211      /**
 212       * Bulk upgrade several plugins at once.
 213       *
 214       * @since 2.8.0
 215       * @since 3.7.0 The `$args` parameter was added, making clearing the plugin update cache optional.
 216       *
 217       * @param string[] $plugins Array of paths to plugin files relative to the plugins directory.
 218       * @param array    $args {
 219       *     Optional. Other arguments for upgrading several plugins at once.
 220       *
 221       *     @type bool $clear_update_cache Whether to clear the plugin updates cache if successful. Default true.
 222       * }
 223       * @return array|false An array of results indexed by plugin file, or false if unable to connect to the filesystem.
 224       */
 225  	public function bulk_upgrade( $plugins, $args = array() ) {
 226  
 227          $defaults    = array(
 228              'clear_update_cache' => true,
 229          );
 230          $parsed_args = wp_parse_args( $args, $defaults );
 231  
 232          $this->init();
 233          $this->bulk = true;
 234          $this->upgrade_strings();
 235  
 236          $current = get_site_transient( 'update_plugins' );
 237  
 238          add_filter( 'upgrader_clear_destination', array( $this, 'delete_old_plugin' ), 10, 4 );
 239  
 240          $this->skin->header();
 241  
 242          // Connect to the filesystem first.
 243          $res = $this->fs_connect( array( WP_CONTENT_DIR, WP_PLUGIN_DIR ) );
 244          if ( ! $res ) {
 245              $this->skin->footer();
 246              return false;
 247          }
 248  
 249          $this->skin->bulk_header();
 250  
 251          /*
 252           * Only start maintenance mode if:
 253           * - running Multisite and there are one or more plugins specified, OR
 254           * - a plugin with an update available is currently active.
 255           * @todo For multisite, maintenance mode should only kick in for individual sites if at all possible.
 256           */
 257          $maintenance = ( is_multisite() && ! empty( $plugins ) );
 258          foreach ( $plugins as $plugin ) {
 259              $maintenance = $maintenance || ( is_plugin_active( $plugin ) && isset( $current->response[ $plugin ] ) );
 260          }
 261          if ( $maintenance ) {
 262              $this->maintenance_mode( true );
 263          }
 264  
 265          $results = array();
 266  
 267          $this->update_count   = count( $plugins );
 268          $this->update_current = 0;
 269          foreach ( $plugins as $plugin ) {
 270              $this->update_current++;
 271              $this->skin->plugin_info = get_plugin_data( WP_PLUGIN_DIR . '/' . $plugin, false, true );
 272  
 273              if ( ! isset( $current->response[ $plugin ] ) ) {
 274                  $this->skin->set_result( 'up_to_date' );
 275                  $this->skin->before();
 276                  $this->skin->feedback( 'up_to_date' );
 277                  $this->skin->after();
 278                  $results[ $plugin ] = true;
 279                  continue;
 280              }
 281  
 282              // Get the URL to the zip file.
 283              $r = $current->response[ $plugin ];
 284  
 285              $this->skin->plugin_active = is_plugin_active( $plugin );
 286  
 287              $result = $this->run(
 288                  array(
 289                      'package'           => $r->package,
 290                      'destination'       => WP_PLUGIN_DIR,
 291                      'clear_destination' => true,
 292                      'clear_working'     => true,
 293                      'is_multi'          => true,
 294                      'hook_extra'        => array(
 295                          'plugin' => $plugin,
 296                      ),
 297                  )
 298              );
 299  
 300              $results[ $plugin ] = $this->result;
 301  
 302              // Prevent credentials auth screen from displaying multiple times.
 303              if ( false === $result ) {
 304                  break;
 305              }
 306          } // End foreach $plugins.
 307  
 308          $this->maintenance_mode( false );
 309  
 310          // Force refresh of plugin update information.
 311          wp_clean_plugins_cache( $parsed_args['clear_update_cache'] );
 312  
 313          /** This action is documented in wp-admin/includes/class-wp-upgrader.php */
 314          do_action(
 315              'upgrader_process_complete',
 316              $this,
 317              array(
 318                  'action'  => 'update',
 319                  'type'    => 'plugin',
 320                  'bulk'    => true,
 321                  'plugins' => $plugins,
 322              )
 323          );
 324  
 325          $this->skin->bulk_footer();
 326  
 327          $this->skin->footer();
 328  
 329          // Cleanup our hooks, in case something else does a upgrade on this connection.
 330          remove_filter( 'upgrader_clear_destination', array( $this, 'delete_old_plugin' ) );
 331  
 332          return $results;
 333      }
 334  
 335      /**
 336       * Check a source package to be sure it contains a plugin.
 337       *
 338       * This function is added to the {@see 'upgrader_source_selection'} filter by
 339       * Plugin_Upgrader::install().
 340       *
 341       * @since 3.3.0
 342       *
 343       * @global WP_Filesystem_Base $wp_filesystem WordPress filesystem subclass.
 344       *
 345       * @param string $source The path to the downloaded package source.
 346       * @return string|WP_Error The source as passed, or a WP_Error object
 347       *                         if no plugins were found.
 348       */
 349  	public function check_package( $source ) {
 350          global $wp_filesystem;
 351  
 352          if ( is_wp_error( $source ) ) {
 353              return $source;
 354          }
 355  
 356          $working_directory = str_replace( $wp_filesystem->wp_content_dir(), trailingslashit( WP_CONTENT_DIR ), $source );
 357          if ( ! is_dir( $working_directory ) ) { // Sanity check, if the above fails, let's not prevent installation.
 358              return $source;
 359          }
 360  
 361          // Check that the folder contains at least 1 valid plugin.
 362          $plugins_found = false;
 363          $files         = glob( $working_directory . '*.php' );
 364          if ( $files ) {
 365              foreach ( $files as $file ) {
 366                  $info = get_plugin_data( $file, false, false );
 367                  if ( ! empty( $info['Name'] ) ) {
 368                      $plugins_found = true;
 369                      break;
 370                  }
 371              }
 372          }
 373  
 374          if ( ! $plugins_found ) {
 375              return new WP_Error( 'incompatible_archive_no_plugins', $this->strings['incompatible_archive'], __( 'No valid plugins were found.' ) );
 376          }
 377  
 378          return $source;
 379      }
 380  
 381      /**
 382       * Retrieve the path to the file that contains the plugin info.
 383       *
 384       * This isn't used internally in the class, but is called by the skins.
 385       *
 386       * @since 2.8.0
 387       *
 388       * @return string|false The full path to the main plugin file, or false.
 389       */
 390  	public function plugin_info() {
 391          if ( ! is_array( $this->result ) ) {
 392              return false;
 393          }
 394          if ( empty( $this->result['destination_name'] ) ) {
 395              return false;
 396          }
 397  
 398          // Ensure to pass with leading slash.
 399          $plugin = get_plugins( '/' . $this->result['destination_name'] );
 400          if ( empty( $plugin ) ) {
 401              return false;
 402          }
 403  
 404          // Assume the requested plugin is the first in the list.
 405          $pluginfiles = array_keys( $plugin );
 406  
 407          return $this->result['destination_name'] . '/' . $pluginfiles[0];
 408      }
 409  
 410      /**
 411       * Deactivates a plugin before it is upgraded.
 412       *
 413       * Hooked to the {@see 'upgrader_pre_install'} filter by Plugin_Upgrader::upgrade().
 414       *
 415       * @since 2.8.0
 416       * @since 4.1.0 Added a return value.
 417       *
 418       * @param bool|WP_Error $return Upgrade offer return.
 419       * @param array         $plugin Plugin package arguments.
 420       * @return bool|WP_Error The passed in $return param or WP_Error.
 421       */
 422  	public function deactivate_plugin_before_upgrade( $return, $plugin ) {
 423  
 424          if ( is_wp_error( $return ) ) { // Bypass.
 425              return $return;
 426          }
 427  
 428          // When in cron (background updates) don't deactivate the plugin, as we require a browser to reactivate it.
 429          if ( wp_doing_cron() ) {
 430              return $return;
 431          }
 432  
 433          $plugin = isset( $plugin['plugin'] ) ? $plugin['plugin'] : '';
 434          if ( empty( $plugin ) ) {
 435              return new WP_Error( 'bad_request', $this->strings['bad_request'] );
 436          }
 437  
 438          if ( is_plugin_active( $plugin ) ) {
 439              // Deactivate the plugin silently, Prevent deactivation hooks from running.
 440              deactivate_plugins( $plugin, true );
 441          }
 442  
 443          return $return;
 444      }
 445  
 446      /**
 447       * Turns on maintenance mode before attempting to background update an active plugin.
 448       *
 449       * Hooked to the {@see 'upgrader_pre_install'} filter by Plugin_Upgrader::upgrade().
 450       *
 451       * @since 5.4.0
 452       *
 453       * @param bool|WP_Error $return Upgrade offer return.
 454       * @param array         $plugin Plugin package arguments.
 455       * @return bool|WP_Error The passed in $return param or WP_Error.
 456       */
 457  	public function active_before( $return, $plugin ) {
 458          if ( is_wp_error( $return ) ) {
 459              return $return;
 460          }
 461  
 462          // Only enable maintenance mode when in cron (background update).
 463          if ( ! wp_doing_cron() ) {
 464              return $return;
 465          }
 466  
 467          $plugin = isset( $plugin['plugin'] ) ? $plugin['plugin'] : '';
 468  
 469          // Only run if plugin is active.
 470          if ( ! is_plugin_active( $plugin ) ) {
 471              return $return;
 472          }
 473  
 474          // Change to maintenance mode. Bulk edit handles this separately.
 475          if ( ! $this->bulk ) {
 476              $this->maintenance_mode( true );
 477          }
 478  
 479          return $return;
 480      }
 481  
 482      /**
 483       * Turns off maintenance mode after upgrading an active plugin.
 484       *
 485       * Hooked to the {@see 'upgrader_post_install'} filter by Plugin_Upgrader::upgrade().
 486       *
 487       * @since 5.4.0
 488       *
 489       * @param bool|WP_Error $return Upgrade offer return.
 490       * @param array         $plugin Plugin package arguments.
 491       * @return bool|WP_Error The passed in $return param or WP_Error.
 492       */
 493  	public function active_after( $return, $plugin ) {
 494          if ( is_wp_error( $return ) ) {
 495              return $return;
 496          }
 497  
 498          // Only disable maintenance mode when in cron (background update).
 499          if ( ! wp_doing_cron() ) {
 500              return $return;
 501          }
 502  
 503          $plugin = isset( $plugin['plugin'] ) ? $plugin['plugin'] : '';
 504  
 505          // Only run if plugin is active
 506          if ( ! is_plugin_active( $plugin ) ) {
 507              return $return;
 508          }
 509  
 510          // Time to remove maintenance mode. Bulk edit handles this separately.
 511          if ( ! $this->bulk ) {
 512              $this->maintenance_mode( false );
 513          }
 514  
 515          return $return;
 516      }
 517  
 518      /**
 519       * Deletes the old plugin during an upgrade.
 520       *
 521       * Hooked to the {@see 'upgrader_clear_destination'} filter by
 522       * Plugin_Upgrader::upgrade() and Plugin_Upgrader::bulk_upgrade().
 523       *
 524       * @since 2.8.0
 525       *
 526       * @global WP_Filesystem_Base $wp_filesystem WordPress filesystem subclass.
 527       *
 528       * @param bool|WP_Error $removed            Whether the destination was cleared.
 529       *                                          True on success, WP_Error on failure.
 530       * @param string        $local_destination  The local package destination.
 531       * @param string        $remote_destination The remote package destination.
 532       * @param array         $plugin             Extra arguments passed to hooked filters.
 533       * @return bool|WP_Error
 534       */
 535  	public function delete_old_plugin( $removed, $local_destination, $remote_destination, $plugin ) {
 536          global $wp_filesystem;
 537  
 538          if ( is_wp_error( $removed ) ) {
 539              return $removed; // Pass errors through.
 540          }
 541  
 542          $plugin = isset( $plugin['plugin'] ) ? $plugin['plugin'] : '';
 543          if ( empty( $plugin ) ) {
 544              return new WP_Error( 'bad_request', $this->strings['bad_request'] );
 545          }
 546  
 547          $plugins_dir     = $wp_filesystem->wp_plugins_dir();
 548          $this_plugin_dir = trailingslashit( dirname( $plugins_dir . $plugin ) );
 549  
 550          if ( ! $wp_filesystem->exists( $this_plugin_dir ) ) { // If it's already vanished.
 551              return $removed;
 552          }
 553  
 554          // If plugin is in its own directory, recursively delete the directory.
 555          // Base check on if plugin includes directory separator AND that it's not the root plugin folder.
 556          if ( strpos( $plugin, '/' ) && $this_plugin_dir !== $plugins_dir ) {
 557              $deleted = $wp_filesystem->delete( $this_plugin_dir, true );
 558          } else {
 559              $deleted = $wp_filesystem->delete( $plugins_dir . $plugin );
 560          }
 561  
 562          if ( ! $deleted ) {
 563              return new WP_Error( 'remove_old_failed', $this->strings['remove_old_failed'] );
 564          }
 565  
 566          return true;
 567      }
 568  }


Generated: Mon Jun 1 01:00:03 2020 Cross-referenced by PHPXref 0.7.1