[ 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_clear_destination', array( $this, 'delete_old_plugin' ), 10, 4 );
 171          //'source_selection' => array($this, 'source_selection'), //there's a trac ticket to move up the directory for zip's which are made a bit differently, useful for non-.org plugins.
 172          if ( $parsed_args['clear_update_cache'] ) {
 173              // Clear cache so wp_update_plugins() knows about the new plugin.
 174              add_action( 'upgrader_process_complete', 'wp_clean_plugins_cache', 9, 0 );
 175          }
 176  
 177          $this->run(
 178              array(
 179                  'package'           => $r->package,
 180                  'destination'       => WP_PLUGIN_DIR,
 181                  'clear_destination' => true,
 182                  'clear_working'     => true,
 183                  'hook_extra'        => array(
 184                      'plugin' => $plugin,
 185                      'type'   => 'plugin',
 186                      'action' => 'update',
 187                  ),
 188              )
 189          );
 190  
 191          // Cleanup our hooks, in case something else does a upgrade on this connection.
 192          remove_action( 'upgrader_process_complete', 'wp_clean_plugins_cache', 9 );
 193          remove_filter( 'upgrader_pre_install', array( $this, 'deactivate_plugin_before_upgrade' ) );
 194          remove_filter( 'upgrader_clear_destination', array( $this, 'delete_old_plugin' ) );
 195  
 196          if ( ! $this->result || is_wp_error( $this->result ) ) {
 197              return $this->result;
 198          }
 199  
 200          // Force refresh of plugin update information
 201          wp_clean_plugins_cache( $parsed_args['clear_update_cache'] );
 202  
 203          return true;
 204      }
 205  
 206      /**
 207       * Bulk upgrade several plugins at once.
 208       *
 209       * @since 2.8.0
 210       * @since 3.7.0 The `$args` parameter was added, making clearing the plugin update cache optional.
 211       *
 212       * @param string[] $plugins Array of paths to plugin files relative to the plugins directory.
 213       * @param array    $args {
 214       *     Optional. Other arguments for upgrading several plugins at once.
 215       *
 216       *     @type bool $clear_update_cache Whether to clear the plugin updates cache if successful. Default true.
 217       * }
 218       * @return array|false An array of results indexed by plugin file, or false if unable to connect to the filesystem.
 219       */
 220  	public function bulk_upgrade( $plugins, $args = array() ) {
 221  
 222          $defaults    = array(
 223              'clear_update_cache' => true,
 224          );
 225          $parsed_args = wp_parse_args( $args, $defaults );
 226  
 227          $this->init();
 228          $this->bulk = true;
 229          $this->upgrade_strings();
 230  
 231          $current = get_site_transient( 'update_plugins' );
 232  
 233          add_filter( 'upgrader_clear_destination', array( $this, 'delete_old_plugin' ), 10, 4 );
 234  
 235          $this->skin->header();
 236  
 237          // Connect to the Filesystem first.
 238          $res = $this->fs_connect( array( WP_CONTENT_DIR, WP_PLUGIN_DIR ) );
 239          if ( ! $res ) {
 240              $this->skin->footer();
 241              return false;
 242          }
 243  
 244          $this->skin->bulk_header();
 245  
 246          /*
 247           * Only start maintenance mode if:
 248           * - running Multisite and there are one or more plugins specified, OR
 249           * - a plugin with an update available is currently active.
 250           * @TODO: For multisite, maintenance mode should only kick in for individual sites if at all possible.
 251           */
 252          $maintenance = ( is_multisite() && ! empty( $plugins ) );
 253          foreach ( $plugins as $plugin ) {
 254              $maintenance = $maintenance || ( is_plugin_active( $plugin ) && isset( $current->response[ $plugin ] ) );
 255          }
 256          if ( $maintenance ) {
 257              $this->maintenance_mode( true );
 258          }
 259  
 260          $results = array();
 261  
 262          $this->update_count   = count( $plugins );
 263          $this->update_current = 0;
 264          foreach ( $plugins as $plugin ) {
 265              $this->update_current++;
 266              $this->skin->plugin_info = get_plugin_data( WP_PLUGIN_DIR . '/' . $plugin, false, true );
 267  
 268              if ( ! isset( $current->response[ $plugin ] ) ) {
 269                  $this->skin->set_result( 'up_to_date' );
 270                  $this->skin->before();
 271                  $this->skin->feedback( 'up_to_date' );
 272                  $this->skin->after();
 273                  $results[ $plugin ] = true;
 274                  continue;
 275              }
 276  
 277              // Get the URL to the zip file.
 278              $r = $current->response[ $plugin ];
 279  
 280              $this->skin->plugin_active = is_plugin_active( $plugin );
 281  
 282              $result = $this->run(
 283                  array(
 284                      'package'           => $r->package,
 285                      'destination'       => WP_PLUGIN_DIR,
 286                      'clear_destination' => true,
 287                      'clear_working'     => true,
 288                      'is_multi'          => true,
 289                      'hook_extra'        => array(
 290                          'plugin' => $plugin,
 291                      ),
 292                  )
 293              );
 294  
 295              $results[ $plugin ] = $this->result;
 296  
 297              // Prevent credentials auth screen from displaying multiple times
 298              if ( false === $result ) {
 299                  break;
 300              }
 301          } //end foreach $plugins
 302  
 303          $this->maintenance_mode( false );
 304  
 305          // Force refresh of plugin update information.
 306          wp_clean_plugins_cache( $parsed_args['clear_update_cache'] );
 307  
 308          /** This action is documented in wp-admin/includes/class-wp-upgrader.php */
 309          do_action(
 310              'upgrader_process_complete',
 311              $this,
 312              array(
 313                  'action'  => 'update',
 314                  'type'    => 'plugin',
 315                  'bulk'    => true,
 316                  'plugins' => $plugins,
 317              )
 318          );
 319  
 320          $this->skin->bulk_footer();
 321  
 322          $this->skin->footer();
 323  
 324          // Cleanup our hooks, in case something else does a upgrade on this connection.
 325          remove_filter( 'upgrader_clear_destination', array( $this, 'delete_old_plugin' ) );
 326  
 327          return $results;
 328      }
 329  
 330      /**
 331       * Check a source package to be sure it contains a plugin.
 332       *
 333       * This function is added to the {@see 'upgrader_source_selection'} filter by
 334       * Plugin_Upgrader::install().
 335       *
 336       * @since 3.3.0
 337       *
 338       * @global WP_Filesystem_Base $wp_filesystem WordPress filesystem subclass.
 339       *
 340       * @param string $source The path to the downloaded package source.
 341       * @return string|WP_Error The source as passed, or a WP_Error object
 342       *                         if no plugins were found.
 343       */
 344  	public function check_package( $source ) {
 345          global $wp_filesystem;
 346  
 347          if ( is_wp_error( $source ) ) {
 348              return $source;
 349          }
 350  
 351          $working_directory = str_replace( $wp_filesystem->wp_content_dir(), trailingslashit( WP_CONTENT_DIR ), $source );
 352          if ( ! is_dir( $working_directory ) ) { // Sanity check, if the above fails, let's not prevent installation.
 353              return $source;
 354          }
 355  
 356          // Check the folder contains at least 1 valid plugin.
 357          $plugins_found = false;
 358          $files         = glob( $working_directory . '*.php' );
 359          if ( $files ) {
 360              foreach ( $files as $file ) {
 361                  $info = get_plugin_data( $file, false, false );
 362                  if ( ! empty( $info['Name'] ) ) {
 363                      $plugins_found = true;
 364                      break;
 365                  }
 366              }
 367          }
 368  
 369          if ( ! $plugins_found ) {
 370              return new WP_Error( 'incompatible_archive_no_plugins', $this->strings['incompatible_archive'], __( 'No valid plugins were found.' ) );
 371          }
 372  
 373          return $source;
 374      }
 375  
 376      /**
 377       * Retrieve the path to the file that contains the plugin info.
 378       *
 379       * This isn't used internally in the class, but is called by the skins.
 380       *
 381       * @since 2.8.0
 382       *
 383       * @return string|false The full path to the main plugin file, or false.
 384       */
 385  	public function plugin_info() {
 386          if ( ! is_array( $this->result ) ) {
 387              return false;
 388          }
 389          if ( empty( $this->result['destination_name'] ) ) {
 390              return false;
 391          }
 392  
 393          $plugin = get_plugins( '/' . $this->result['destination_name'] ); //Ensure to pass with leading slash
 394          if ( empty( $plugin ) ) {
 395              return false;
 396          }
 397  
 398          $pluginfiles = array_keys( $plugin ); //Assume the requested plugin is the first in the list
 399  
 400          return $this->result['destination_name'] . '/' . $pluginfiles[0];
 401      }
 402  
 403      /**
 404       * Deactivates a plugin before it is upgraded.
 405       *
 406       * Hooked to the {@see 'upgrader_pre_install'} filter by Plugin_Upgrader::upgrade().
 407       *
 408       * @since 2.8.0
 409       * @since 4.1.0 Added a return value.
 410       *
 411       * @param bool|WP_Error  $return Upgrade offer return.
 412       * @param array          $plugin Plugin package arguments.
 413       * @return bool|WP_Error The passed in $return param or WP_Error.
 414       */
 415  	public function deactivate_plugin_before_upgrade( $return, $plugin ) {
 416  
 417          if ( is_wp_error( $return ) ) { //Bypass.
 418              return $return;
 419          }
 420  
 421          // When in cron (background updates) don't deactivate the plugin, as we require a browser to reactivate it
 422          if ( wp_doing_cron() ) {
 423              return $return;
 424          }
 425  
 426          $plugin = isset( $plugin['plugin'] ) ? $plugin['plugin'] : '';
 427          if ( empty( $plugin ) ) {
 428              return new WP_Error( 'bad_request', $this->strings['bad_request'] );
 429          }
 430  
 431          if ( is_plugin_active( $plugin ) ) {
 432              //Deactivate the plugin silently, Prevent deactivation hooks from running.
 433              deactivate_plugins( $plugin, true );
 434          }
 435  
 436          return $return;
 437      }
 438  
 439      /**
 440       * Delete the old plugin during an upgrade.
 441       *
 442       * Hooked to the {@see 'upgrader_clear_destination'} filter by
 443       * Plugin_Upgrader::upgrade() and Plugin_Upgrader::bulk_upgrade().
 444       *
 445       * @since 2.8.0
 446       *
 447       * @global WP_Filesystem_Base $wp_filesystem WordPress filesystem subclass.
 448       *
 449       * @param bool|WP_Error $removed
 450       * @param string        $local_destination
 451       * @param string        $remote_destination
 452       * @param array         $plugin
 453       * @return bool|WP_Error
 454       */
 455  	public function delete_old_plugin( $removed, $local_destination, $remote_destination, $plugin ) {
 456          global $wp_filesystem;
 457  
 458          if ( is_wp_error( $removed ) ) {
 459              return $removed; //Pass errors through.
 460          }
 461  
 462          $plugin = isset( $plugin['plugin'] ) ? $plugin['plugin'] : '';
 463          if ( empty( $plugin ) ) {
 464              return new WP_Error( 'bad_request', $this->strings['bad_request'] );
 465          }
 466  
 467          $plugins_dir     = $wp_filesystem->wp_plugins_dir();
 468          $this_plugin_dir = trailingslashit( dirname( $plugins_dir . $plugin ) );
 469  
 470          if ( ! $wp_filesystem->exists( $this_plugin_dir ) ) { //If it's already vanished.
 471              return $removed;
 472          }
 473  
 474          // If plugin is in its own directory, recursively delete the directory.
 475          if ( strpos( $plugin, '/' ) && $this_plugin_dir != $plugins_dir ) { //base check on if plugin includes directory separator AND that it's not the root plugin folder
 476              $deleted = $wp_filesystem->delete( $this_plugin_dir, true );
 477          } else {
 478              $deleted = $wp_filesystem->delete( $plugins_dir . $plugin );
 479          }
 480  
 481          if ( ! $deleted ) {
 482              return new WP_Error( 'remove_old_failed', $this->strings['remove_old_failed'] );
 483          }
 484  
 485          return true;
 486      }
 487  }


Generated: Fri Nov 15 01:00:03 2019 Cross-referenced by PHPXref 0.7.1