Index: src/wp-admin/includes/class-wp-upgrader-bulk.php
IDEA additional info:
Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP
<+>UTF-8
===================================================================
--- src/wp-admin/includes/class-wp-upgrader-bulk.php	(revision )
+++ src/wp-admin/includes/class-wp-upgrader-bulk.php	(revision )
@@ -0,0 +1,1066 @@
+<?php
+/**
+ * The Bulk handlers for the WordPress Upgrader
+ *
+ * @package    WordPress
+ * @subpackage Upgrader
+ * @since      4.4.1
+ */
+
+/**
+ * Abstract base class for Bulk Upgrades
+ *
+ * @since 4.4.1
+ */
+abstract class WP_Bulk_Upgrader_Composite {
+	/**
+	 * @var WP_Upgrader
+	 */
+	protected $upgrader;
+
+	/**
+	 * @var array Elements to upgrade
+	 */
+	protected $elements = array();
+
+	/**
+	 * WP_Bulk_Upgrader_Composite constructor.
+	 *
+	 * @since 4.4.1
+	 *
+	 * @param WP_Upgrader $upgrader     Upgrader to report back to
+	 * @param array       $elements     List of elements to be bulked.
+	 * @param string      $elementClass Class name to wrap elements in. Extended of WP_Bulk_Upgrader_Element.
+	 */
+	public function __construct( WP_Upgrader $upgrader, array $elements = array(), $elementClass ) {
+		if ( ! is_string( $elementClass ) ) {
+			throw new InvalidArgumentException( 'Expected element class to be string, got ' . gettype( $elementClass ) );
+		}
+
+		if ( ! class_exists( $elementClass ) ) {
+			throw new InvalidArgumentException( sprintf( 'Element class "%s" does not exist.', $elementClass ) );
+		}
+
+		$this->upgrader = $upgrader;
+
+		if ( array() !== $elements ) {
+			foreach ( $elements as $element ) {
+				$this->elements[] = new $elementClass( $element, $upgrader );
+			}
+		}
+
+		$this->upgrader->update_count   = count( $this->elements );
+		$this->upgrader->update_current = 0;
+	}
+
+	/**
+	 * Runs the bulk upgrade
+	 *
+	 * @since  4.4.1
+	 * @access public
+	 *
+	 * @return array
+	 */
+	public function run() {
+		$this->upgrader->clean_upgrade_folder();
+
+		$this->register_on_skin();
+
+		$this->check_up_to_date();
+
+		# Work
+		$this->download();
+		$this->unpack();
+		$this->install();
+		# Done
+
+		$this->upgrader->maintenance_mode( false );
+		$this->upgrader->update_current = false;
+
+		return $this->get_results();
+	}
+
+	/**
+	 * Shorthand retrieval of upgrader skin
+	 *
+	 * @since  4.4.1
+	 * @access protected
+	 *
+	 * @return null|WP_Upgrader_Skin
+	 */
+	protected function get_skin() {
+		return $this->upgrader->get_skin();
+	}
+
+	/**
+	 * Set the current element as active on the upgrader
+	 *
+	 * @since  4.4.1
+	 * @access protected
+	 *
+	 * @param $element
+	 */
+	protected function current_element( $element ) {
+		$index = array_search( $element, $this->elements );
+
+		if ( false !== $index ) {
+			$this->upgrader->update_current = $index + 1;
+		} else {
+			$this->upgrader->update_current = false;
+		}
+	}
+
+	/**
+	 * When the element is already up to date
+	 *
+	 * @since  4.4.1
+	 * @access protected
+	 *
+	 * @param $element
+	 */
+	protected function handle_up_to_date( $element ) {
+	}
+
+	/**
+	 * Optionally let the elements be sorted before installing
+	 *
+	 * @since 4.4.1
+	 * @acces protected
+	 *
+	 * @return array
+	 */
+	protected function sort_install_elements() {
+		return $this->elements;
+	}
+
+	/**
+	 * Register elements on the skin
+	 *
+	 * @since  4.4.1
+	 * @access protected
+	 */
+	protected function register_on_skin() {
+		foreach ( $this->elements as $element ) {
+			$this->current_element( $element );
+
+			$skin = $this->get_skin();
+			$skin->before();
+		}
+	}
+
+	/**
+	 * Check if any of the elements is already up to date
+	 *
+	 * @since  4.4.1
+	 * @access protected
+	 */
+	protected function check_up_to_date() {
+		/** @var $element WP_Bulk_Upgrader_Element */
+		foreach ( $this->elements as $element ) {
+			if ( $element->is_up_to_date() ) {
+				$this->current_element( $element );
+
+				$this->handle_up_to_date( $element );
+
+				$skin = $this->get_skin();
+
+				$skin->feedback( 'up_to_date' );
+				$skin->after();
+			}
+		}
+	}
+
+	/**
+	 * Download all the elements
+	 *
+	 * @since  4.4.1
+	 * @access protected
+	 */
+	protected function download() {
+		/** @var $element WP_Bulk_Upgrader_Element */
+		foreach ( $this->elements as $element ) {
+			if ( $element->is_up_to_date() ) {
+				continue;
+			}
+
+			$this->current_element( $element );
+
+			$result = $element->download();
+			if ( is_wp_error( $result ) ) {
+				$this->show_error( $result, $element );
+			}
+		}
+	}
+
+	/**
+	 * Unpack downloaded elements
+	 *
+	 * @since  4.4.1
+	 * @access protected
+	 */
+	protected function unpack() {
+		/** @var $element WP_Bulk_Upgrader_Element */
+		foreach ( $this->elements as $element ) {
+			if ( $element->is_up_to_date() ) {
+				continue;
+			}
+
+			$this->current_element( $element );
+
+			$result = $element->unpack();
+			if ( is_wp_error( $result ) ) {
+				$this->show_error( $result, $element );
+			}
+		}
+	}
+
+	/**
+	 * Install unpacked elements
+	 *
+	 * @since  4.4.1
+	 * @access protected
+	 */
+	protected function install() {
+		$elements = $this->sort_install_elements();
+
+		/** @var $element WP_Bulk_Upgrader_Element */
+		foreach ( $elements as $element ) {
+			if ( $element->is_up_to_date() ) {
+				continue;
+			}
+
+			$this->current_element( $element );
+
+			$result = $element->install();
+
+			$skin = $this->get_skin();
+			$skin->set_result( $result );
+			if ( is_wp_error( $result ) ) {
+				$skin->error( $result );
+				$skin->feedback( 'process_failed' );
+			} else {
+				// Install succeeded.
+				$skin->feedback( 'process_success' );
+			}
+			$skin->after();
+		}
+	}
+
+	/**
+	 * Get the results of the upgrade process
+	 *
+	 * @since  4.4.1
+	 * @access protected
+	 *
+	 * @return array
+	 */
+	protected function get_results() {
+		$results = array();
+
+		/** @var $element WP_Bulk_Upgrader_Element */
+		foreach ( $this->elements as $element ) {
+			$index = $element->get_result_index();
+			if ( false === $index ) {
+				$results[] = $element->get_result();
+			} else {
+				$results[ $index ] = $element->get_result();
+			}
+		}
+
+		return $results;
+	}
+
+	/**
+	 * Pass error to skin
+	 *
+	 * @since  4.4.1
+	 * @access protected
+	 *
+	 * @param $error
+	 * @param $element
+	 */
+	protected function show_error( $error, $element ) {
+		$this->current_element( $element );
+
+		$skin = $this->get_skin();
+		$skin->error( $error );
+		$skin->after();
+	}
+}
+
+/**
+ * Abstract base class for Bulk Upgrade Elements
+ *
+ * @since 4.4.1
+ */
+abstract class WP_Bulk_Upgrader_Element {
+	/**
+	 * @var bool is this element already up-to-date
+	 */
+	protected $up_to_date = false;
+	/**
+	 * @var bool has the package been downloaded
+	 */
+	protected $downloaded = false;
+	/**
+	 * @var bool was the package unpacked properly
+	 */
+	protected $unpacked = false;
+	/**
+	 * @var bool has the package been installed
+	 */
+	protected $installed = false;
+
+	/**
+	 * @var mixed identifier for this element (source)
+	 */
+	protected $name;
+	/**
+	 * @var WP_Upgrader upgrader to talk back to
+	 */
+	protected $upgrader;
+
+	/**
+	 * @var array options of current element, used throughout the process
+	 */
+	protected $options;
+	/**
+	 * @var mixed info about the current element, used by the upgrader
+	 */
+	protected $info;
+	/**
+	 * @var string name of the download
+	 */
+	protected $download;
+	/**
+	 * @var string working dir for the unpacked package
+	 */
+	protected $working_dir;
+
+	/**
+	 * @var mixed final result of installation (via upgrader)
+	 */
+	protected $result;
+
+	/**
+	 * WP_Bulk_Upgrader_Element constructor.
+	 *
+	 * @since  4.4.1
+	 * @access public
+	 *
+	 * @param mixed       $name
+	 * @param WP_Upgrader $upgrader
+	 */
+	public function __construct( $name, WP_Upgrader $upgrader ) {
+		$this->name     = $name;
+		$this->upgrader = $upgrader;
+	}
+
+	/**
+	 * Get the original identifier data
+	 *
+	 * @since  4.4.1
+	 * @access public
+	 *
+	 * @return mixed
+	 */
+	public function get_name() {
+		return $this->name;
+	}
+
+	/**
+	 * Get the cummulated result of the upgrade
+	 *
+	 * @since  4.4.1
+	 * @access public
+	 *
+	 * @return bool
+	 */
+	public function get_result() {
+		return isset( $this->result ) ? $this->result : $this->installed;
+	}
+
+	/**
+	 * If the result needs to be named, it can be overwriten here
+	 *
+	 * @since  4.4.1
+	 * @access public
+	 *
+	 * @return bool
+	 */
+	public function get_result_index() {
+		return false;
+	}
+
+	/**
+	 * Has been downloaded correctly
+	 *
+	 * @since  4.4.1
+	 * @access public
+	 *
+	 * @return bool
+	 */
+	public function downloaded() {
+		return $this->downloaded;
+	}
+
+	/**
+	 * Has been unpacked succesfully
+	 *
+	 * @since  4.4.1
+	 * @access public
+	 *
+	 * @return bool
+	 */
+	public function unpacked() {
+		return $this->unpacked;
+	}
+
+	/**
+	 * Has been installed succesfully
+	 *
+	 * @since  4.4.1
+	 * @access public
+	 *
+	 * @return bool
+	 */
+	public function installed() {
+		return $this->installed;
+	}
+
+	/**
+	 * Is it already up-to-date
+	 *
+	 * @since  4.4.1
+	 * @access public
+	 *
+	 * @return bool
+	 */
+	public function is_up_to_date() {
+		return $this->up_to_date;
+	}
+
+	/**
+	 * Get element info
+	 *
+	 * @since  4.4.1
+	 * @access public
+	 * @return mixed
+	 */
+	public function get_info() {
+		return $this->info;
+	}
+
+	/**
+	 * Optionally manage maintenance after install
+	 *
+	 * @since  4.4.1
+	 * @access protected
+	 *
+	 * @param $maintenance
+	 */
+	protected function post_install( $maintenance ) {
+	}
+
+	/**
+	 * Apply defaults and filter to options
+	 *
+	 * @since  4.4.1
+	 * @access protected
+	 *
+	 * @param $options
+	 */
+	protected function set_options( $options ) {
+		$defaults = array(
+			'package'                     => '',
+			// Please always pass this.
+			'destination'                 => '',
+			// And this
+			'clear_destination'           => false,
+			'abort_if_destination_exists' => true,
+			// Abort if the Destination directory exists, Pass clear_destination as false please
+			'clear_working'               => true,
+			'is_multi'                    => false,
+			'hook_extra'                  => array()
+			// Pass any extra $hook_extra args here, this will be passed to any hooked filters.
+		);
+
+		$options = wp_parse_args( $options, $defaults );
+
+		/** This filter is documented in wp-admin/includes/class-wp-upgrader.php */
+		$this->options = apply_filters( 'upgrader_package_options', $options );
+	}
+
+	/**
+	 * Does element need maintenance to be enabled
+	 *
+	 * @since  4.4.1
+	 * @access protected
+	 *
+	 * @return bool
+	 */
+	protected function need_maintenance() {
+		return false;
+	}
+
+	/**
+	 * Download this element
+	 *
+	 * @since  4.4.1
+	 * @access public
+	 *
+	 * @return bool|string|WP_Error
+	 */
+	public function download() {
+		if ( $this->is_up_to_date() ) {
+			return false;
+		}
+
+		// Connect to the Filesystem first.
+		$result = $this->upgrader->fs_connect( array( WP_CONTENT_DIR, $this->options['destination'] ) );
+		// Mainly for non-connected filesystem.
+		if ( ! $result || is_wp_error( $result ) ) {
+			return $result;
+		}
+
+		/*
+		 * Download the package (Note, This just returns the filename
+		 * of the file if the package is a local file)
+		 */
+		$result = $this->upgrader->download_package( $this->options['package'] );
+		if ( ! is_wp_error( $result ) ) {
+			$this->downloaded = true;
+			$this->download   = $result;
+		}
+
+		return $result;
+	}
+
+	/**
+	 * Unpack this element
+	 *
+	 * @since  4.4.1
+	 * @access public
+	 *
+	 * @return bool|string|WP_Error
+	 */
+	public function unpack() {
+		if ( ! $this->downloaded() ) {
+			return false;
+		}
+
+		// Do not delete a "local" file
+		$delete_package = ( $this->download !== $this->options['package'] );
+
+		// Unzips the file into a temporary directory.
+		$result = $this->upgrader->unpack_package( $this->download, $delete_package );
+		if ( ! is_wp_error( $result ) ) {
+			$this->unpacked    = true;
+			$this->working_dir = $result;
+		}
+
+		return $result;
+	}
+
+	/**
+	 * Install this element
+	 *
+	 * Enable maintenance mode if required
+	 *
+	 * @since  4.4.1
+	 * @access public
+	 *
+	 * @return array|bool|WP_Error
+	 */
+	public function install() {
+		if ( ! $this->unpacked() ) {
+			return false;
+		}
+
+		$maintenance = $this->need_maintenance();
+		$this->upgrader->maintenance_mode( $maintenance );
+
+		// With the given options, this installs it to the destination directory.
+		$result = $this->upgrader->install_package( array(
+			'source'                      => $this->working_dir,
+			'destination'                 => $this->options['destination'],
+			'clear_destination'           => $this->options['clear_destination'],
+			'abort_if_destination_exists' => $this->options['abort_if_destination_exists'],
+			'clear_working'               => $this->options['clear_working'],
+			'hook_extra'                  => $this->options['hook_extra']
+		) );
+
+		$this->post_install( $maintenance );
+
+		/*
+		 * This does not feel right.
+		 */
+		$this->result = $this->upgrader->result;
+
+		return $result;
+	}
+}
+
+/**
+ * Abstract base class for Bulk Upgrade Elements that are formatted as Plugin/Theme
+ *
+ * @since 4.4.1
+ */
+abstract class WP_Bulk_Upgrader_Element_Plugin_Format extends WP_Bulk_Upgrader_Element {
+	/**
+	 * @var object cache for current element type information
+	 */
+	protected static $current;
+
+	/**
+	 * @var object current element response information from repository
+	 */
+	protected $response;
+
+	/**
+	 * Implement logic to tell if this element is active
+	 *
+	 * @since  4.4.1
+	 * @access public
+	 *
+	 * @return mixed
+	 */
+	abstract public function is_active();
+
+	/**
+	 * Implement logic to get current info on all elements
+	 *
+	 * @since  4.4.1
+	 * @access protected
+	 *
+	 * @return mixed
+	 */
+	abstract protected function get_current();
+
+	/**
+	 * Results are based on plugin name
+	 *
+	 * @return mixed
+	 */
+	public function get_result_index() {
+		return $this->name;
+	}
+
+	/**
+	 * Get plugin repository response
+	 *
+	 * @since  4.4.1
+	 * @access protected
+	 *
+	 * @return bool|object Response from the respository as stdClass
+	 */
+	protected function get_response() {
+		$current = $this->get_current();
+
+		if ( is_object( $current ) && is_array( $current->response ) ) {
+			return $this->response = isset( $current->response[ $this->name ] ) ? $current->response[ $this->name ] : false;
+		}
+
+		return false;
+	}
+}
+
+/**
+ * Plugin Bulk Upgrader implementation
+ *
+ * @since 4.4.1
+ */
+class WP_Plugin_Bulk_Upgrader extends WP_Bulk_Upgrader_Composite {
+	/**
+	 * WP_Plugin_Bulk_Upgrader constructor.
+	 *
+	 * @since  4.4.1
+	 * @access public
+	 *
+	 * @param Plugin_Upgrader $upgrader
+	 * @param array           $elements
+	 */
+	public function __construct( Plugin_Upgrader $upgrader, array $elements = array() ) {
+		parent::__construct( $upgrader, $elements, 'WP_Bulk_Upgrader_Plugin_Element' );
+	}
+
+	/**
+	 * Communicate plugin info to skin
+	 *
+	 * @since  4.4.1
+	 * @access protected
+	 *
+	 * @param WP_Bulk_Upgrader_Element $element
+	 */
+	protected function current_element( WP_Bulk_Upgrader_Element $element ) {
+		parent::current_element( $element );
+
+		$skin = $this->get_skin();
+		if ( $skin instanceof Bulk_Plugin_Upgrader_Skin ) {
+			$skin->plugin_info = $element->get_info();
+		}
+	}
+
+	/**
+	 * Communicate result to skin
+	 *
+	 * @since  4.4.1
+	 * @access public
+	 *
+	 * @param WP_Bulk_Upgrader_Element $element
+	 */
+	public function handle_up_to_date( WP_Bulk_Upgrader_Element $element ) {
+		$skin = $this->get_skin();
+		if ( $skin instanceof WP_Upgrader_Skin ) {
+			$skin->set_result( 'up_to_date' );
+		}
+	}
+
+	/**
+	 * Sort plugin so active ones are bundled at the end
+	 *
+	 * @since  4.4.1
+	 * @access protected
+	 *
+	 * @return array
+	 */
+	protected function sort_install_elements() {
+		$sorted = $this->elements;
+		usort( $sorted, array( $this, 'sort_elements_active_on_bottom' ) );
+
+		return $sorted;
+	}
+
+	/**
+	 * Sort plugins
+	 *
+	 * @since  4.4.1
+	 * @access private
+	 *
+	 * @param WP_Bulk_Upgrader_Element_Plugin_Format $a
+	 * @param WP_Bulk_Upgrader_Element_Plugin_Format $b
+	 *
+	 * @return int 0, -1 or 1
+	 */
+	private function sort_elements_active_on_bottom(
+		WP_Bulk_Upgrader_Element_Plugin_Format $a,
+		WP_Bulk_Upgrader_Element_Plugin_Format $b
+	) {
+		if ( $a->is_active() === $b->is_active() ) {
+			return 0;
+		}
+
+		// Active plugins last:
+		return ( $a->is_active() && ! $b->is_active() ) ? 1 : - 1;
+	}
+}
+
+/**
+ * Plugin implementation of the Bulk Upgrade Element
+ *
+ * @since 4.4.1
+ */
+class WP_Bulk_Upgrader_Plugin_Element extends WP_Bulk_Upgrader_Element_Plugin_Format {
+	/**
+	 * WP_Bulk_Upgrader_Plugin_Element constructor.
+	 *
+	 * @since  4.4.1
+	 * @access public
+	 *
+	 * @param string          $plugin
+	 * @param Plugin_Upgrader $upgrader
+	 */
+	public function __construct( $plugin, Plugin_Upgrader $upgrader ) {
+		parent::__construct( $plugin, $upgrader );
+
+		$this->info = get_plugin_data( WP_PLUGIN_DIR . '/' . $plugin, false, true );
+
+		$response = $this->get_response();
+		if ( false === $response ) {
+			$this->up_to_date = true;
+
+			return;
+		}
+
+		$options = array(
+			'package'           => $response->package,
+			'destination'       => WP_PLUGIN_DIR,
+			'clear_destination' => true,
+			'clear_working'     => true,
+			'is_multi'          => true,
+			'hook_extra'        => array(
+				'plugin' => $plugin
+			)
+		);
+
+		$this->set_options( $options );
+	}
+
+	/**
+	 * Is this plugin activated
+	 *
+	 * @since  4.4.1
+	 * @access public
+	 *
+	 * @return bool
+	 */
+	public function is_active() {
+		return is_plugin_active( $this->name );
+	}
+
+	/**
+	 * Get info from transient update_plugins
+	 *
+	 * @since  4.4.1
+	 * @access protected
+	 *
+	 * @return mixed
+	 */
+	protected function get_current() {
+		if ( ! isset( self::$current ) ) {
+			self::$current = get_site_transient( 'update_plugins' );
+		}
+
+		return self::$current;
+	}
+
+	/**
+	 * Do we need maintenance mode to install
+	 *
+	 * @since  4.4.1
+	 * @access protected
+	 *
+	 * @return bool
+	 */
+	protected function need_maintenance() {
+		return is_multisite() || $this->is_active();
+	}
+}
+
+/**
+ * Theme Bulk Upgrader implementation
+ *
+ * @since 4.4.1
+ */
+class WP_Theme_Bulk_Upgrader extends WP_Bulk_Upgrader_Composite {
+	/**
+	 * WP_Theme_Bulk_Upgrader constructor.
+	 *
+	 * @since  4.4.1
+	 * @access public
+	 *
+	 * @param Theme_Upgrader $upgrader
+	 * @param array          $elements
+	 */
+	public function __construct( Theme_Upgrader $upgrader, array $elements = array() ) {
+		parent::__construct( $upgrader, $elements, 'WP_Bulk_Upgrader_Theme_Element' );
+	}
+
+	/**
+	 * Communicate theme info to skin
+	 *
+	 * @since  4.4.1
+	 * @access protected
+	 *
+	 * @param WP_Bulk_Upgrader_Element $element
+	 */
+	protected function current_element( WP_Bulk_Upgrader_Element $element ) {
+		parent::current_element( $element );
+
+		$skin = $this->get_skin();
+		if ( $skin instanceof Bulk_Theme_Upgrader_Skin ) {
+			$skin->theme_info = $element->get_info();
+		}
+	}
+
+	/**
+	 * Communicate result to skin
+	 *
+	 * @since  4.4.1
+	 * @access public
+	 *
+	 * @param $element
+	 */
+	public function handle_up_to_date( $element ) {
+		$this->get_skin()->set_result( true );
+	}
+}
+
+/**
+ * Theme implementation of the Bulk Upgrade Element
+ *
+ * @since 4.4.1
+ */
+class WP_Bulk_Upgrader_Theme_Element extends WP_Bulk_Upgrader_Element_Plugin_Format {
+	/**
+	 * WP_Bulk_Upgrader_Theme_Element constructor.
+	 *
+	 * @since  4.4.1
+	 * @access public
+	 *
+	 * @param string         $theme
+	 * @param Theme_Upgrader $upgrader
+	 */
+	public function __construct( $theme, Theme_Upgrader $upgrader ) {
+		parent::__construct( $theme, $upgrader );
+
+		$this->info = $upgrader->theme_info( $theme );
+
+		$response = $this->get_response();
+		if ( false === $response ) {
+			$this->up_to_date = true;
+
+			return;
+		}
+
+		$options = array(
+			'package'           => $this->response['package'],
+			'destination'       => get_theme_root( $theme ),
+			'clear_destination' => true,
+			'clear_working'     => true,
+			'is_multi'          => true,
+			'hook_extra'        => array(
+				'theme' => $theme
+			)
+		);
+
+		$this->set_options( $options );
+	}
+
+
+	/**
+	 * Get info from update_themes
+	 *
+	 * @since  4.4.1
+	 * @access protected
+	 *
+	 * @return mixed
+	 */
+	protected function get_current() {
+		// @todo should this be cached or not?
+		if ( ! isset( self::$current ) ) {
+			self::$current = get_site_transient( 'update_themes' );
+		}
+
+		return self::$current;
+	}
+
+	/**
+	 * Is this theme active
+	 *
+	 * @since  4.4.1
+	 * @access public
+	 *
+	 * @return bool
+	 */
+	public function is_active() {
+		return $this->name == get_stylesheet() || $this->name == get_template();
+	}
+
+	/**
+	 * Do we need maintenace mode to install
+	 *
+	 * @since  4.4.1
+	 * @access protected
+	 *
+	 * @return bool
+	 */
+	protected function need_maintenance() {
+		return is_multisite() || $this->is_active();
+	}
+
+	/**
+	 * Disable maintenance mode if it was active during install
+	 *
+	 * There can be only 1 active theme, so disable maintenance after installing
+	 *
+	 * @since  4.4.1
+	 * @access protected
+	 *
+	 * @param $maintenance
+	 */
+	protected function post_install( $maintenance ) {
+		if ( $maintenance ) {
+			$this->upgrader->maintenance_mode( false );
+		}
+	}
+}
+
+/**
+ * Language Bulk Upgrader implementation
+ *
+ * @since 4.4.1
+ */
+class WP_Language_Bulk_Upgrader extends WP_Bulk_Upgrader_Composite {
+	/**
+	 * WP_Language_Bulk_Upgrader constructor.
+	 *
+	 * @since  4.4.1
+	 * @access public
+	 *
+	 * @param Language_Pack_Upgrader $upgrader
+	 * @param array                  $elements
+	 */
+	public function __construct( Language_Pack_Upgrader $upgrader, array $elements = array() ) {
+		parent::__construct( $upgrader, $elements, 'WP_Bulk_Upgrader_Language_Element' );
+	}
+
+	/**
+	 * Communicate language info to skin
+	 *
+	 * @since  4.4.1
+	 * @access protected
+	 *
+	 * @param WP_Bulk_Upgrader_Element $element
+	 */
+	protected function current_element( WP_Bulk_Upgrader_Element $element ) {
+		parent::current_element( $element );
+
+		$skin = $this->get_skin();
+		if ( $skin instanceof Language_Pack_Upgrader_Skin ) {
+			$skin->language_update = $element->get_name();
+		}
+	}
+}
+
+/**
+ * Language implementation of the Bulk Upgrade Element
+ *
+ * @since 4.4.1
+ */
+class WP_Bulk_Upgrader_Language_Element extends WP_Bulk_Upgrader_Element {
+	/**
+	 * WP_Bulk_Upgrader_Language_Element constructor.
+	 *
+	 * @since  4.4.1
+	 * @access public
+	 *
+	 * @param mixed                  $language_update
+	 * @param Language_Pack_Upgrader $upgrader
+	 */
+	public function __construct( $language_update, Language_Pack_Upgrader $upgrader ) {
+		parent::__construct( $language_update, $upgrader );
+
+		$destination = WP_LANG_DIR;
+		if ( 'plugin' == $language_update->type ) {
+			$destination .= '/plugins';
+		} elseif ( 'theme' == $language_update->type ) {
+			$destination .= '/themes';
+		}
+
+		$options = array(
+			'package'                     => $language_update->package,
+			'destination'                 => $destination,
+			'clear_destination'           => false,
+			'abort_if_destination_exists' => false, // We expect the destination to exist.
+			'clear_working'               => true,
+			'is_multi'                    => true,
+			'hook_extra'                  => array(
+				'language_update_type' => $language_update->type,
+				'language_update'      => $language_update,
+			)
+		);
+
+		$this->set_options( $options );
+	}
+}
\ No newline at end of file
Index: src/wp-admin/includes/class-wp-upgrader-skins.php
IDEA additional info:
Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP
<+>UTF-8
===================================================================
--- src/wp-admin/includes/class-wp-upgrader-skins.php	(revision 36000)
+++ src/wp-admin/includes/class-wp-upgrader-skins.php	(revision )
@@ -59,7 +59,7 @@
 	 * @param bool   $error
 	 * @param string $context
 	 * @param bool   $allow_relaxed_file_ownership
-	 * @return type
+	 * @return bool
 	 */
 	public function request_filesystem_credentials( $error = false, $context = false, $allow_relaxed_file_ownership = false ) {
 		$url = $this->options['url'];
@@ -309,12 +309,23 @@
 		}
 		if ( empty($string) )
 			return;
-		if ( $this->in_loop )
+
+		if ( $this->upgrader->update_current > 0 ) {
+			$target_id = sprintf( '#progress-%d p:last-child', $this->upgrader->update_current );
+			$output    = "$string<br />";
+
+			echo '<script type="text/javascript">jQuery(\'' . $target_id . '\').append(\'' . str_replace( "'", "\\'", $output ) . '\');</script>';
+		} else {
+
+			if ( $this->in_loop ) {
-			echo "$string<br />\n";
+				echo "$string<br />\n";
-		else
+			} else {
-			echo "<p>$string</p>\n";
-	}
+				echo "<p>$string</p>\n";
+			}
+		}
 
+	}
+
 	/**
 	 * @access public
 	 */
@@ -372,7 +383,7 @@
 		$this->in_loop = true;
 		printf( '<h2>' . $this->upgrader->strings['skin_before_update_header'] . ' <span class="spinner waiting-' . $this->upgrader->update_current . '"></span></h2>', $title, $this->upgrader->update_current, $this->upgrader->update_count );
 		echo '<script type="text/javascript">jQuery(\'.waiting-' . esc_js($this->upgrader->update_current) . '\').css("display", "inline-block");</script>';
-		echo '<div class="update-messages hide-if-js" id="progress-' . esc_attr($this->upgrader->update_current) . '"><p>';
+		echo '<div class="update-messages hide-if-js" id="progress-' . esc_attr($this->upgrader->update_current) . '"><p></p></div>';
 		$this->flush_output();
 	}
 
@@ -381,7 +392,7 @@
 	 * @param string $title
 	 */
 	public function after($title = '') {
-		echo '</p></div>';
+
 		if ( $this->error || ! $this->result ) {
 			if ( $this->error ) {
 				echo '<div class="error"><p>' . sprintf($this->upgrader->strings['skin_update_failed_error'], $title, '<strong>' . $this->error . '</strong>' ) . '</p></div>';
Index: src/wp-admin/includes/class-wp-upgrader.php
IDEA additional info:
Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP
<+>UTF-8
===================================================================
--- src/wp-admin/includes/class-wp-upgrader.php	(revision 36000)
+++ src/wp-admin/includes/class-wp-upgrader.php	(revision )
@@ -12,6 +12,7 @@
  */
 
 require ABSPATH . 'wp-admin/includes/class-wp-upgrader-skins.php';
+require ABSPATH . 'wp-admin/includes/class-wp-upgrader-bulk.php';
 
 /**
  * Core class used for upgrading/installing a local set of files via
@@ -254,6 +255,27 @@
 	}
 
 	/**
+	 * Clears optional remnants of previous upgrades
+	 *
+	 * @since 4.4.1
+	 * @access public
+	 *
+	 * @global WP_Filesystem_Base $wp_filesystem Subclass
+	 */
+	public function clean_upgrade_folder() {
+		global $wp_filesystem;
+
+		//Clean up contents of upgrade directory.
+		$upgrade_folder = $wp_filesystem->wp_content_dir() . 'upgrade/';
+		$upgrade_files  = $wp_filesystem->dirlist( $upgrade_folder );
+		if ( ! empty( $upgrade_files ) ) {
+			foreach ( $upgrade_files as $file ) {
+				$wp_filesystem->delete( $upgrade_folder . $file['name'], true );
+			}
+		}
+	}
+
+	/**
 	 * Unpack a compressed package file.
 	 *
 	 * @since 2.8.0
@@ -274,11 +296,13 @@
 		$upgrade_folder = $wp_filesystem->wp_content_dir() . 'upgrade/';
 
 		//Clean up contents of upgrade directory beforehand.
+		/*
 		$upgrade_files = $wp_filesystem->dirlist($upgrade_folder);
 		if ( !empty($upgrade_files) ) {
 			foreach ( $upgrade_files as $file )
 				$wp_filesystem->delete($upgrade_folder . $file['name'], true);
 		}
+		*/
 
 		// We need a working directory - Strip off any .tmp or .zip suffixes
 		$working_dir = $upgrade_folder . basename( basename( $package, '.tmp' ), '.zip' );
@@ -641,9 +665,7 @@
 		 */
 		$options = apply_filters( 'upgrader_package_options', $options );
 
-		if ( ! $options['is_multi'] ) { // call $this->header separately if running multiple times
-			$this->skin->header();
+		$this->skin->header();
-		}
 
 		// Connect to the Filesystem first.
 		$res = $this->fs_connect( array( WP_CONTENT_DIR, $options['destination'] ) );
@@ -666,6 +688,8 @@
 			return $res;
 		}
 
+		$this->clean_upgrade_folder();
+
 		/*
 		 * Download the package (Note, This just returns the filename
 		 * of the file if the package is a local file)
@@ -751,8 +775,20 @@
 		}
 	}
 
+	/**
+	 * Get the used skin
+	 *
+	 * @since 4.4.1
+	 * @access public
+	 *
+	 * @return null|WP_Upgrader_Skin
+	 */
+	public function get_skin() {
+		return $this->skin;
-}
+	}
 
+}
+
 /**
  * Core class used for upgrading/installing plugins.
  *
@@ -950,14 +986,14 @@
 	 * @param array $args {
 	 *     Optional. Other arguments for upgrading several plugins at once. Default empty array.
 	 *
-	 *     @type bool $clear_update_cache Whether to clear the plugin updates cache if successful.
+	 * @type bool $clear_update_cache Whether to clear the plugin updates cache if successful.
 	 *                                    Default true.
 	 * }
 	 * @return array|false An array of results indexed by plugin file, or false if unable to connect to the filesystem.
 	 */
 	public function bulk_upgrade( $plugins, $args = array() ) {
 
-		$defaults = array(
+		$defaults    = array(
 			'clear_update_cache' => true,
 		);
 		$parsed_args = wp_parse_args( $args, $defaults );
@@ -968,73 +1004,23 @@
 
 		$current = get_site_transient( 'update_plugins' );
 
-		add_filter('upgrader_clear_destination', array($this, 'delete_old_plugin'), 10, 4);
+		add_filter( 'upgrader_clear_destination', array( $this, 'delete_old_plugin' ), 10, 4 );
 
 		$this->skin->header();
 
 		// Connect to the Filesystem first.
-		$res = $this->fs_connect( array(WP_CONTENT_DIR, WP_PLUGIN_DIR) );
+		$res = $this->fs_connect( array( WP_CONTENT_DIR, WP_PLUGIN_DIR ) );
 		if ( ! $res ) {
 			$this->skin->footer();
+
 			return false;
 		}
 
 		$this->skin->bulk_header();
 
-		/*
-		 * Only start maintenance mode if:
-		 * - running Multisite and there are one or more plugins specified, OR
-		 * - a plugin with an update available is currently active.
-		 * @TODO: For multisite, maintenance mode should only kick in for individual sites if at all possible.
-		 */
-		$maintenance = ( is_multisite() && ! empty( $plugins ) );
-		foreach ( $plugins as $plugin )
-			$maintenance = $maintenance || ( is_plugin_active( $plugin ) && isset( $current->response[ $plugin] ) );
-		if ( $maintenance )
-			$this->maintenance_mode(true);
+		$plugin_bulk_upgrader = new WP_Plugin_Bulk_Upgrader( $this, $plugins );
+		$results = $plugin_bulk_upgrader->run();
 
-		$results = array();
-
-		$this->update_count = count($plugins);
-		$this->update_current = 0;
-		foreach ( $plugins as $plugin ) {
-			$this->update_current++;
-			$this->skin->plugin_info = get_plugin_data( WP_PLUGIN_DIR . '/' . $plugin, false, true);
-
-			if ( !isset( $current->response[ $plugin ] ) ) {
-				$this->skin->set_result('up_to_date');
-				$this->skin->before();
-				$this->skin->feedback('up_to_date');
-				$this->skin->after();
-				$results[$plugin] = true;
-				continue;
-			}
-
-			// Get the URL to the zip file.
-			$r = $current->response[ $plugin ];
-
-			$this->skin->plugin_active = is_plugin_active($plugin);
-
-			$result = $this->run( array(
-				'package' => $r->package,
-				'destination' => WP_PLUGIN_DIR,
-				'clear_destination' => true,
-				'clear_working' => true,
-				'is_multi' => true,
-				'hook_extra' => array(
-					'plugin' => $plugin
-				)
-			) );
-
-			$results[$plugin] = $this->result;
-
-			// Prevent credentials auth screen from displaying multiple times
-			if ( false === $result )
-				break;
-		} //end foreach $plugins
-
-		$this->maintenance_mode(false);
-
 		/**
 		 * Fires when the bulk upgrader process is complete.
 		 *
@@ -1042,19 +1028,19 @@
 		 *
 		 * @param Plugin_Upgrader $this Plugin_Upgrader instance. In other contexts, $this, might
 		 *                              be a Theme_Upgrader or Core_Upgrade instance.
-		 * @param array           $data {
+		 * @param array $data {
 		 *     Array of bulk item update data.
 		 *
-		 *     @type string $action   Type of action. Default 'update'.
+		 * @type string $action Type of action. Default 'update'.
-		 *     @type string $type     Type of update process. Accepts 'plugin', 'theme', or 'core'.
+		 * @type string $type Type of update process. Accepts 'plugin', 'theme', or 'core'.
-		 *     @type bool   $bulk     Whether the update process is a bulk update. Default true.
+		 * @type bool $bulk Whether the update process is a bulk update. Default true.
-		 *     @type array  $packages Array of plugin, theme, or core packages to update.
+		 * @type array $packages Array of plugin, theme, or core packages to update.
 		 * }
 		 */
 		do_action( 'upgrader_process_complete', $this, array(
-			'action' => 'update',
+			'action'  => 'update',
-			'type' => 'plugin',
+			'type'    => 'plugin',
-			'bulk' => true,
+			'bulk'    => true,
 			'plugins' => $plugins,
 		) );
 
@@ -1063,7 +1049,7 @@
 		$this->skin->footer();
 
 		// Cleanup our hooks, in case something else does a upgrade on this connection.
-		remove_filter('upgrader_clear_destination', array($this, 'delete_old_plugin'));
+		remove_filter( 'upgrader_clear_destination', array( $this, 'delete_old_plugin' ) );
 
 		// Force refresh of plugin update information.
 		wp_clean_plugins_cache( $parsed_args['clear_update_cache'] );
@@ -1550,57 +1536,9 @@
 
 		$this->skin->bulk_header();
 
-		// Only start maintenance mode if:
-		// - running Multisite and there are one or more themes specified, OR
-		// - a theme with an update available is currently in use.
-		// @TODO: For multisite, maintenance mode should only kick in for individual sites if at all possible.
-		$maintenance = ( is_multisite() && ! empty( $themes ) );
-		foreach ( $themes as $theme )
-			$maintenance = $maintenance || $theme == get_stylesheet() || $theme == get_template();
-		if ( $maintenance )
-			$this->maintenance_mode(true);
+		$theme_bulk_upgrader = new WP_Theme_Bulk_Upgrader( $this, $themes );
+		$results = $theme_bulk_upgrader->run();
 
-		$results = array();
-
-		$this->update_count = count($themes);
-		$this->update_current = 0;
-		foreach ( $themes as $theme ) {
-			$this->update_current++;
-
-			$this->skin->theme_info = $this->theme_info($theme);
-
-			if ( !isset( $current->response[ $theme ] ) ) {
-				$this->skin->set_result(true);
-				$this->skin->before();
-				$this->skin->feedback( 'up_to_date' );
-				$this->skin->after();
-				$results[$theme] = true;
-				continue;
-			}
-
-			// Get the URL to the zip file
-			$r = $current->response[ $theme ];
-
-			$result = $this->run( array(
-				'package' => $r['package'],
-				'destination' => get_theme_root( $theme ),
-				'clear_destination' => true,
-				'clear_working' => true,
-				'is_multi' => true,
-				'hook_extra' => array(
-					'theme' => $theme
-				),
-			) );
-
-			$results[$theme] = $this->result;
-
-			// Prevent credentials auth screen from displaying multiple times
-			if ( false === $result )
-				break;
-		} //end foreach $plugins
-
-		$this->maintenance_mode(false);
-
 		/** This action is documented in wp-admin/includes/class-wp-upgrader.php */
 		do_action( 'upgrader_process_complete', $this, array(
 			'action' => 'update',
@@ -2010,11 +1948,6 @@
 			return false;
 		}
 
-		$results = array();
-
-		$this->update_count = count( $language_updates );
-		$this->update_current = 0;
-
 		/*
 		 * The filesystem's mkdir() is not recursive. Make sure WP_LANG_DIR exists,
 		 * as we then may need to create a /plugins or /themes directory inside of it.
@@ -2024,40 +1957,9 @@
 			if ( ! $wp_filesystem->mkdir( $remote_destination, FS_CHMOD_DIR ) )
 				return new WP_Error( 'mkdir_failed_lang_dir', $this->strings['mkdir_failed'], $remote_destination );
 
-		foreach ( $language_updates as $language_update ) {
+		$language_bulk_upgrader = new WP_Language_Bulk_Upgrader( $this, $language_updates );
+		$results = $language_bulk_upgrader->run();
 
-			$this->skin->language_update = $language_update;
-
-			$destination = WP_LANG_DIR;
-			if ( 'plugin' == $language_update->type )
-				$destination .= '/plugins';
-			elseif ( 'theme' == $language_update->type )
-				$destination .= '/themes';
-
-			$this->update_current++;
-
-			$options = array(
-				'package' => $language_update->package,
-				'destination' => $destination,
-				'clear_destination' => false,
-				'abort_if_destination_exists' => false, // We expect the destination to exist.
-				'clear_working' => true,
-				'is_multi' => true,
-				'hook_extra' => array(
-					'language_update_type' => $language_update->type,
-					'language_update' => $language_update,
-				)
-			);
-
-			$result = $this->run( $options );
-
-			$results[] = $this->result;
-
-			// Prevent credentials auth screen from displaying multiple times.
-			if ( false === $result )
-				break;
-		}
-
 		$this->skin->bulk_footer();
 
 		$this->skin->footer();
@@ -2078,13 +1980,15 @@
 	 * Hooked to the {@see 'upgrader_source_selection'} filter by
 	 * {@see Language_Pack_Upgrader::bulk_upgrade()}.
 	 *
-	 * @since 3.7.0
+	 * @since  3.7.0
 	 * @access public
 	 *
 	 * @global WP_Filesystem_Base $wp_filesystem Subclass
 	 *
-	 * @param string|WP_Error $source
+	 * @param string|WP_Error     $source
-	 * @param string          $remote_source
+	 * @param string              $remote_source
+	 *
+	 * @return string|WP_Error
 	 */
 	public function check_package( $source, $remote_source ) {
 		global $wp_filesystem;
@@ -3538,4 +3442,4 @@
 
 		wp_mail( $email['to'], wp_specialchars_decode( $email['subject'] ), $email['body'], $email['headers'] );
 	}
-}
+}
\ No newline at end of file
