angelovcom.net

Unnamed repository; edit this file 'description' to name the repository.
Log | Files | Refs

class-plugin-upgrader.php (21468B)


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