diff --git a/Puc/v4/Plugin/UpdateChecker.php b/Puc/v4/Plugin/UpdateChecker.php index f19f36c..18c2594 100644 --- a/Puc/v4/Plugin/UpdateChecker.php +++ b/Puc/v4/Plugin/UpdateChecker.php @@ -17,8 +17,6 @@ if ( !class_exists('Puc_v4_Plugin_UpdateChecker', false) ): public $pluginFile = ''; //Plugin filename relative to the plugins directory. Many WP APIs use this to identify plugins. public $muPluginFile = ''; //For MU plugins, the plugin filename relative to the mu-plugins directory. - protected $upgraderStatus; - private $debugBarPlugin = null; private $cachedInstalledVersion = null; @@ -61,17 +59,12 @@ if ( !class_exists('Puc_v4_Plugin_UpdateChecker', false) ): $this->muPluginFile = $this->pluginFile; } - $this->upgraderStatus = new Puc_v4_UpgraderStatus(); - - parent::__construct($metadataUrl, $slug, $checkPeriod, $optionName); + parent::__construct($metadataUrl, dirname($this->pluginFile), $slug, $checkPeriod, $optionName); } /** * Create an instance of the scheduler. * - * This is implemented as a method to make it possible for plugins to subclass the update checker - * and substitute their own scheduler. - * * @param int $checkPeriod * @return Puc_v4_Scheduler */ @@ -91,10 +84,6 @@ if ( !class_exists('Puc_v4_Plugin_UpdateChecker', false) ): //Override requests for plugin information add_filter('plugins_api', array($this, 'injectInfo'), 20, 3); - //Insert our update info into the update array maintained by WP. - add_filter('site_transient_update_plugins', array($this,'injectUpdate')); //WP 3.0+ - add_filter('transient_update_plugins', array($this,'injectUpdate')); //WP 2.8+ - add_filter('plugin_row_meta', array($this, 'addCheckForUpdatesLink'), 10, 2); add_action('admin_init', array($this, 'handleManualCheck')); add_action('all_admin_notices', array($this, 'displayManualCheckResult')); @@ -109,13 +98,6 @@ if ( !class_exists('Puc_v4_Plugin_UpdateChecker', false) ): add_action('plugins_loaded', array($this, 'initDebugBarPanel')); } - //Rename the update directory to be the same as the existing directory. - add_filter('upgrader_source_selection', array($this, 'fixDirectoryName'), 10, 3); - - //Enable language support (i18n). - //TODO: The directory path has changed. - load_plugin_textdomain('plugin-update-checker', false, plugin_basename(dirname(__FILE__)) . '/languages'); - parent::installHooks(); } @@ -174,35 +156,6 @@ if ( !class_exists('Puc_v4_Plugin_UpdateChecker', false) ): return $pluginInfo; } - /** - * Check if $result is a successful update API response. - * - * @param array|WP_Error $result - * @return true|WP_Error - */ - protected function validateApiResponse($result) { - if ( is_wp_error($result) ) { /** @var WP_Error $result */ - return new WP_Error($result->get_error_code(), 'WP HTTP Error: ' . $result->get_error_message()); - } - - if ( !isset($result['response']['code']) ) { - return new WP_Error('puc_no_response_code', 'wp_remote_get() returned an unexpected result.'); - } - - if ( $result['response']['code'] !== 200 ) { - return new WP_Error( - 'puc_unexpected_response_code', - 'HTTP response code is ' . $result['response']['code'] . ' (expected: 200)' - ); - } - - if ( empty($result['body']) ) { - return new WP_Error('puc_empty_response', 'The metadata file appears to be empty.'); - } - - return true; - } - /** * Retrieve the latest update (if any) from the configured API endpoint. * @@ -298,7 +251,7 @@ if ( !class_exists('Puc_v4_Plugin_UpdateChecker', false) ): } $pluginInfo = $this->requestInfo(); - $pluginInfo = apply_filters('puc_pre_inject_info-' . $this->slug, $pluginInfo); + $pluginInfo = apply_filters($this->getFilterName('pre_inject_info'), $pluginInfo); if ( $pluginInfo ) { return $pluginInfo->toWpFormat(); } @@ -306,34 +259,6 @@ if ( !class_exists('Puc_v4_Plugin_UpdateChecker', false) ): return $result; } - /** - * Insert the latest update (if any) into the update list maintained by WP. - * - * @param StdClass $updates Update list. - * @return StdClass Modified update list. - */ - public function injectUpdate($updates){ - //TODO: Unify this. - - //Is there an update to insert? - $update = $this->getUpdate(); - - if ( !$this->shouldShowUpdates() ) { - $update = null; - } - - if ( !empty($update) ) { - //Let plugins filter the update info before it's passed on to WordPress. - $update = apply_filters($this->getFilterName('pre_inject_update'), $update); - $updates = $this->addUpdateToList($updates, $update); - } else { - //Clean up any stale update info. - $updates = $this->removeUpdateFromList($updates); - } - - return $updates; - } - protected function shouldShowUpdates() { //No update notifications for mu-plugins unless explicitly enabled. The MU plugin file //is usually different from the main plugin file so the update wouldn't show up properly anyway. @@ -341,142 +266,62 @@ if ( !class_exists('Puc_v4_Plugin_UpdateChecker', false) ): } /** - * @param StdClass|null $updates - * @param Puc_v4_Plugin_Update $updateToAdd - * @return StdClass + * @param stdClass|null $updates + * @param stdClass $updateToAdd + * @return stdClass */ - private function addUpdateToList($updates, $updateToAdd) { - if ( !is_object($updates) ) { - $updates = new stdClass(); - $updates->response = array(); - } - - $wpUpdate = $updateToAdd->toWpFormat(); - $pluginFile = $this->pluginFile; - + protected function addUpdateToList($updates, $updateToAdd) { if ( $this->isMuPlugin() ) { - //WP does not support automatic update installation for mu-plugins, but we can still display a notice. - $wpUpdate->package = null; - $pluginFile = $this->muPluginFile; + //WP does not support automatic update installation for mu-plugins, but we can + //still display a notice. + $updateToAdd->package = null; } - $updates->response[$pluginFile] = $wpUpdate; - return $updates; + return parent::addUpdateToList($updates, $updateToAdd); } /** * @param stdClass|null $updates * @return stdClass|null */ - private function removeUpdateFromList($updates) { - if ( isset($updates, $updates->response) ) { - unset($updates->response[$this->pluginFile]); - if ( !empty($this->muPluginFile) ) { - unset($updates->response[$this->muPluginFile]); - } + protected function removeUpdateFromList($updates) { + $updates = parent::removeUpdateFromList($updates); + if ( !empty($this->muPluginFile) && isset($updates, $updates->response) ) { + unset($updates->response[$this->muPluginFile]); } return $updates; } /** - * Rename the update directory to match the existing plugin directory. + * For plugins, the update array is indexed by the plugin filename relative to the "plugins" + * directory. Example: "plugin-name/plugin.php". * - * When WordPress installs a plugin or theme update, it assumes that the ZIP file will contain - * exactly one directory, and that the directory name will be the same as the directory where - * the plugin/theme is currently installed. - * - * GitHub and other repositories provide ZIP downloads, but they often use directory names like - * "project-branch" or "project-tag-hash". We need to change the name to the actual plugin folder. - * - * This is a hook callback. Don't call it from a plugin. - * - * @param string $source The directory to copy to /wp-content/plugins. Usually a subdirectory of $remoteSource. - * @param string $remoteSource WordPress has extracted the update to this directory. - * @param WP_Upgrader $upgrader - * @return string|WP_Error + * @return string */ - public function fixDirectoryName($source, $remoteSource, $upgrader) { - global $wp_filesystem; /** @var WP_Filesystem_Base $wp_filesystem */ - - //Basic sanity checks. - if ( !isset($source, $remoteSource, $upgrader, $upgrader->skin, $wp_filesystem) ) { - return $source; + protected function getUpdateListKey() { + if ( $this->isMuPlugin() ) { + return $this->muPluginFile; } - - //If WordPress is upgrading anything other than our plugin, leave the directory name unchanged. - if ( !$this->isPluginBeingUpgraded($upgrader) ) { - return $source; - } - - //Rename the source to match the existing plugin directory. - $pluginDirectoryName = dirname($this->pluginFile); - if ( $pluginDirectoryName === '.' ) { - return $source; - } - $correctedSource = trailingslashit($remoteSource) . $pluginDirectoryName . '/'; - if ( $source !== $correctedSource ) { - //The update archive should contain a single directory that contains the rest of plugin files. Otherwise, - //WordPress will try to copy the entire working directory ($source == $remoteSource). We can't rename - //$remoteSource because that would break WordPress code that cleans up temporary files after update. - if ( $this->isBadDirectoryStructure($remoteSource) ) { - return new WP_Error( - 'puc-incorrect-directory-structure', - sprintf( - 'The directory structure of the update is incorrect. All plugin files should be inside ' . - 'a directory named %s, not at the root of the ZIP file.', - htmlentities($this->slug) - ) - ); - } - - /** @var WP_Upgrader_Skin $upgrader->skin */ - $upgrader->skin->feedback(sprintf( - 'Renaming %s to %s…', - '' . basename($source) . '', - '' . $pluginDirectoryName . '' - )); - - if ( $wp_filesystem->move($source, $correctedSource, true) ) { - $upgrader->skin->feedback('Plugin directory successfully renamed.'); - return $correctedSource; - } else { - return new WP_Error( - 'puc-rename-failed', - 'Unable to rename the update to match the existing plugin directory.' - ); - } - } - - return $source; + return $this->pluginFile; } /** - * Check for incorrect update directory structure. An update must contain a single directory, - * all other files should be inside that directory. - * - * @param string $remoteSource Directory path. - * @return bool - */ - private function isBadDirectoryStructure($remoteSource) { - global $wp_filesystem; /** @var WP_Filesystem_Base $wp_filesystem */ - - $sourceFiles = $wp_filesystem->dirlist($remoteSource); - if ( is_array($sourceFiles) ) { - $sourceFiles = array_keys($sourceFiles); - $firstFilePath = trailingslashit($remoteSource) . $sourceFiles[0]; - return (count($sourceFiles) > 1) || (!$wp_filesystem->is_dir($firstFilePath)); - } - - //Assume it's fine. - return false; - } - - /** - * Is there and update being installed RIGHT NOW, for this specific plugin? + * Alias for isBeingUpgraded(). * + * @deprecated * @param WP_Upgrader|null $upgrader The upgrader that's performing the current update. * @return bool */ public function isPluginBeingUpgraded($upgrader = null) { + return $this->isBeingUpgraded($upgrader); + } + + /** + * Is there an update being installed for this plugin, right now? + * + * @param WP_Upgrader|null $upgrader + * @return bool + */ + public function isBeingUpgraded($upgrader = null) { return $this->upgraderStatus->isPluginBeingUpgraded($this->pluginFile, $upgrader); } @@ -494,6 +339,7 @@ if ( !class_exists('Puc_v4_Plugin_UpdateChecker', false) ): public function getUpdate() { $update = parent::getUpdate(); if ( isset($update) ) { + /** @var Puc_v4_Plugin_Update $update */ $update->filename = $this->pluginFile; } return $update; diff --git a/Puc/v4/Theme/Update.php b/Puc/v4/Theme/Update.php index 7543d7a..221f947 100644 --- a/Puc/v4/Theme/Update.php +++ b/Puc/v4/Theme/Update.php @@ -9,6 +9,7 @@ if ( !class_exists('Puc_v4_Theme_Update', false) ): /** * Transform the metadata into the format used by WordPress core. + * Note the inconsistency: WP stores plugin updates as objects and theme updates as arrays. * * @return array */ diff --git a/Puc/v4/Theme/UpdateChecker.php b/Puc/v4/Theme/UpdateChecker.php index 675cca0..5becd39 100644 --- a/Puc/v4/Theme/UpdateChecker.php +++ b/Puc/v4/Theme/UpdateChecker.php @@ -41,38 +41,15 @@ if ( !class_exists('Puc_v4_Theme_UpdateChecker', false) ): protected function installHooks() { parent::installHooks(); - - //Insert our update info into the update list maintained by WP. - add_filter('site_transient_update_themes', array($this, 'injectUpdate')); - - //TODO: Rename the update directory to be the same as the existing directory. - //add_filter('upgrader_source_selection', array($this, 'fixDirectoryName'), 10, 3); } /** - * Insert the latest update (if any) into the update list maintained by WP. + * For themes, the update array is indexed by theme directory name. * - * @param StdClass $updates Update list. - * @return StdClass Modified update list. + * @return string */ - public function injectUpdate($updates) { - //Is there an update to insert? - $update = $this->getUpdate(); - - if ( !$this->shouldShowUpdates() ) { - $update = null; - } - - if ( !empty($update) ) { - //Let themes filter the update info before it's passed on to WordPress. - $update = apply_filters($this->getFilterName('pre_inject_update'), $update); - $updates->response[$this->stylesheet] = $update->toWpFormat(); - } else { - //Clean up any stale update info. - unset($updates->response[$this->stylesheet]); - } - - return $updates; + protected function getUpdateListKey() { + return $this->directoryName; } /** @@ -148,6 +125,16 @@ if ( !class_exists('Puc_v4_Theme_UpdateChecker', false) ): } //TODO: Various add*filter utilities for backwards compatibility. + + /** + * Is there an update being installed right now for this theme? + * + * @param WP_Upgrader|null $upgrader The upgrader that's performing the current update. + * @return bool + */ + public function isBeingUpgraded($upgrader = null) { + return $this->upgraderStatus->isThemeBeingUpgraded($this->stylesheet, $upgrader); + } } endif; \ No newline at end of file diff --git a/Puc/v4/UpdateChecker.php b/Puc/v4/UpdateChecker.php index 04d4e6e..069d2ee 100644 --- a/Puc/v4/UpdateChecker.php +++ b/Puc/v4/UpdateChecker.php @@ -46,6 +46,11 @@ if ( !class_exists('Puc_v4_UpdateChecker', false) ): */ protected $metadataHost = ''; + /** + * @var Puc_v4_UpgraderStatus + */ + protected $upgraderStatus; + public function __construct($metadataUrl, $directoryName, $slug = null, $checkPeriod = 12, $optionName = '') { $this->debugMode = (bool)(constant('WP_DEBUG')); $this->metadataUrl = $metadataUrl; @@ -64,6 +69,7 @@ if ( !class_exists('Puc_v4_UpdateChecker', false) ): } $this->scheduler = $this->createScheduler($checkPeriod); + $this->upgraderStatus = new Puc_v4_UpgraderStatus(); $this->loadTextDomain(); $this->installHooks(); @@ -84,24 +90,29 @@ if ( !class_exists('Puc_v4_UpdateChecker', false) ): } protected function installHooks() { - //TODO: Fix directory name + //Insert our update info into the update array maintained by WP. + add_filter('site_transient_' . $this->updateTransient, array($this,'injectUpdate')); - if ( !empty($this->updateTransient) ) { - //Insert translation updates into the update list. - add_filter('site_transient_' . $this->updateTransient, array($this, 'injectTranslationUpdates')); + //Insert translation updates into the update list. + add_filter('site_transient_' . $this->updateTransient, array($this, 'injectTranslationUpdates')); - //Clear translation updates when WP clears the update cache. - //This needs to be done directly because the library doesn't actually remove obsolete plugin updates, - //it just hides them (see getUpdate()). We can't do that with translations - too much disk I/O. - add_action( - 'delete_site_transient_' . $this->updateTransient, - array($this, 'clearCachedTranslationUpdates') - ); - } + //Clear translation updates when WP clears the update cache. + //This needs to be done directly because the library doesn't actually remove obsolete plugin updates, + //it just hides them (see getUpdate()). We can't do that with translations - too much disk I/O. + add_action( + 'delete_site_transient_' . $this->updateTransient, + array($this, 'clearCachedTranslationUpdates') + ); + + //Rename the update directory to be the same as the existing directory. + add_filter('upgrader_source_selection', array($this, 'fixDirectoryName'), 10, 3); //Allow HTTP requests to the metadata URL even if it's on a local host. $this->metadataHost = @parse_url($this->metadataUrl, PHP_URL_HOST); add_filter('http_request_host_is_external', array($this, 'allowMetadataHost'), 10, 2); + + //TODO: Debugbar + //TODO: Utility functions for adding filters. } /** @@ -132,6 +143,9 @@ if ( !class_exists('Puc_v4_UpdateChecker', false) ): /** * Create an instance of the scheduler. * + * This is implemented as a method to make it possible for plugins to subclass the update checker + * and substitute their own scheduler. + * * @param int $checkPeriod * @return Puc_v4_Scheduler */ @@ -167,6 +181,7 @@ if ( !class_exists('Puc_v4_UpdateChecker', false) ): $state->update = $this->requestUpdate(); if ( isset($state->update, $state->update->translations) ) { + //TODO: Should this be called in requestUpdate, like PluginUpdater does? $state->update->translations = $this->filterApplicableTranslations($state->update->translations); } $this->setUpdateState($state); @@ -225,7 +240,7 @@ if ( !class_exists('Puc_v4_UpdateChecker', false) ): * Uses cached update data. To retrieve update information straight from * the metadata URL, call requestUpdate() instead. * - * @return Puc_v4_Update|Puc_v4_Plugin_Update|Puc_v4_Theme_Update|null + * @return Puc_v4_Update|null */ public function getUpdate() { $state = $this->getUpdateState(); /** @var StdClass $state */ @@ -334,6 +349,67 @@ if ( !class_exists('Puc_v4_UpdateChecker', false) ): * ------------------------------------------------------------------- */ + /** + * Insert the latest update (if any) into the update list maintained by WP. + * + * @param stdClass $updates Update list. + * @return stdClass Modified update list. + */ + public function injectUpdate($updates) { + //Is there an update to insert? + $update = $this->getUpdate(); + + if ( !$this->shouldShowUpdates() ) { + $update = null; + } + + if ( !empty($update) ) { + //Let plugins filter the update info before it's passed on to WordPress. + $update = apply_filters($this->getFilterName('pre_inject_update'), $update); + $updates = $this->addUpdateToList($updates, $update->toWpFormat()); + } else { + //Clean up any stale update info. + $updates = $this->removeUpdateFromList($updates); + } + + return $updates; + } + + /** + * @param stdClass|null $updates + * @param stdClass|array $updateToAdd + * @return stdClass + */ + protected function addUpdateToList($updates, $updateToAdd) { + if ( !is_object($updates) ) { + $updates = new stdClass(); + $updates->response = array(); + } + + $updates->response[$this->getUpdateListKey()] = $updateToAdd; + return $updates; + } + + /** + * @param stdClass|null $updates + * @return stdClass|null + */ + protected function removeUpdateFromList($updates) { + if ( isset($updates, $updates->response) ) { + unset($updates->response[$this->getUpdateListKey()]); + } + return $updates; + } + + /** + * Get the key that will be used when adding updates to the update list that's maintained + * by the WordPress core. The list is always an associative array, but the key is different + * for plugins and themes. + * + * @return string + */ + abstract protected function getUpdateListKey(); + /** * Should we show available updates? * @@ -482,6 +558,116 @@ if ( !class_exists('Puc_v4_UpdateChecker', false) ): $this->setUpdateState($state); } } + + /* ------------------------------------------------------------------- + * Fix directory name when installing updates + * ------------------------------------------------------------------- + */ + + /** + * Rename the update directory to match the existing plugin/theme directory. + * + * When WordPress installs a plugin or theme update, it assumes that the ZIP file will contain + * exactly one directory, and that the directory name will be the same as the directory where + * the plugin or theme is currently installed. + * + * GitHub and other repositories provide ZIP downloads, but they often use directory names like + * "project-branch" or "project-tag-hash". We need to change the name to the actual plugin folder. + * + * This is a hook callback. Don't call it from a plugin. + * + * @access protected + * + * @param string $source The directory to copy to /wp-content/plugins or /wp-content/themes. Usually a subdirectory of $remoteSource. + * @param string $remoteSource WordPress has extracted the update to this directory. + * @param WP_Upgrader $upgrader + * @return string|WP_Error + */ + public function fixDirectoryName($source, $remoteSource, $upgrader) { + global $wp_filesystem; + /** @var WP_Filesystem_Base $wp_filesystem */ + + //Basic sanity checks. + if ( !isset($source, $remoteSource, $upgrader, $upgrader->skin, $wp_filesystem) ) { + return $source; + } + + //If WordPress is upgrading anything other than our plugin/theme, leave the directory name unchanged. + if ( !$this->isBeingUpgraded($upgrader) ) { + return $source; + } + + //Rename the source to match the existing directory. + if ( $this->directoryName === '.' ) { + return $source; + } + $correctedSource = trailingslashit($remoteSource) . $this->directoryName . '/'; + if ( $source !== $correctedSource ) { + //The update archive should contain a single directory that contains the rest of plugin/theme files. + //Otherwise, WordPress will try to copy the entire working directory ($source == $remoteSource). + //We can't rename $remoteSource because that would break WordPress code that cleans up temporary files + //after update. + if ($this->isBadDirectoryStructure($remoteSource)) { + return new WP_Error( + 'puc-incorrect-directory-structure', + sprintf( + 'The directory structure of the update is incorrect. All files should be inside ' . + 'a directory named %s, not at the root of the ZIP archive.', + htmlentities($this->slug) + ) + ); + } + + /** @var WP_Upgrader_Skin $upgrader ->skin */ + $upgrader->skin->feedback(sprintf( + 'Renaming %s to %s…', + '' . basename($source) . '', + '' . $this->directoryName . '' + )); + + if ($wp_filesystem->move($source, $correctedSource, true)) { + $upgrader->skin->feedback('Directory successfully renamed.'); + return $correctedSource; + } else { + return new WP_Error( + 'puc-rename-failed', + 'Unable to rename the update to match the existing directory.' + ); + } + } + + return $source; + } + + /** + * Is there an update being installed right now, for this plugin or theme? + * + * @param WP_Upgrader|null $upgrader The upgrader that's performing the current update. + * @return bool + */ + abstract public function isBeingUpgraded($upgrader = null); + + /** + * Check for incorrect update directory structure. An update must contain a single directory, + * all other files should be inside that directory. + * + * @param string $remoteSource Directory path. + * @return bool + */ + protected function isBadDirectoryStructure($remoteSource) { + global $wp_filesystem; + /** @var WP_Filesystem_Base $wp_filesystem */ + + $sourceFiles = $wp_filesystem->dirlist($remoteSource); + if ( is_array($sourceFiles) ) { + $sourceFiles = array_keys($sourceFiles); + $firstFilePath = trailingslashit($remoteSource) . $sourceFiles[0]; + return (count($sourceFiles) > 1) || (!$wp_filesystem->is_dir($firstFilePath)); + } + + //Assume it's fine. + return false; + } } endif; \ No newline at end of file diff --git a/Puc/v4/UpgraderStatus.php b/Puc/v4/UpgraderStatus.php index 4aa8ec0..94c32a2 100644 --- a/Puc/v4/UpgraderStatus.php +++ b/Puc/v4/UpgraderStatus.php @@ -2,21 +2,22 @@ if ( !class_exists('Puc_v4_UpgraderStatus', false) ): /** - * A utility class that helps figure out which plugin WordPress is upgrading. + * A utility class that helps figure out which plugin or theme WordPress is upgrading. * - * It may seem strange to have an separate class just for that, but the task is surprisingly complicated. + * It may seem strange to have a separate class just for that, but the task is surprisingly complicated. * Core classes like Plugin_Upgrader don't expose the plugin file name during an in-progress update (AFAICT). * This class uses a few workarounds and heuristics to get the file name. */ class Puc_v4_UpgraderStatus { - private $upgradedPluginFile = null; //The plugin that is currently being upgraded by WordPress. + private $currentType = null; //"plugin" or "theme". + private $currentId = null; //Plugin basename or theme directory name. public function __construct() { - //Keep track of which plugin WordPress is currently upgrading. + //Keep track of which plugin/theme WordPress is currently upgrading. add_filter('upgrader_pre_install', array($this, 'setUpgradedPlugin'), 10, 2); add_filter('upgrader_package_options', array($this, 'setUpgradedPluginFromOptions'), 10, 1); - add_filter('upgrader_post_install', array($this, 'clearUpgradedPlugin'), 10, 1); - add_action('upgrader_process_complete', array($this, 'clearUpgradedPlugin'), 10, 1); + add_filter('upgrader_post_install', array($this, 'clearUpgradedThing'), 10, 1); + add_action('upgrader_process_complete', array($this, 'clearUpgradedThing'), 10, 1); } /** @@ -30,33 +31,76 @@ if ( !class_exists('Puc_v4_UpgraderStatus', false) ): * @return bool True if the plugin identified by $pluginFile is being upgraded. */ public function isPluginBeingUpgraded($pluginFile, $upgrader = null) { - if ( isset($upgrader) ) { - $upgradedPluginFile = $this->getPluginBeingUpgradedBy($upgrader); - if ( !empty($upgradedPluginFile) ) { - $this->upgradedPluginFile = $upgradedPluginFile; - } - } - return ( !empty($this->upgradedPluginFile) && ($this->upgradedPluginFile === $pluginFile) ); + return $this->isBeingUpgraded('plugin', $pluginFile, $upgrader); } /** - * Get the file name of the plugin that's currently being upgraded. + * Is there an update being installed for a specific theme? + * + * @param string $stylesheet Theme directory name. + * @param WP_Upgrader|null $upgrader The upgrader that's performing the current update. + * @return bool + */ + public function isThemeBeingUpgraded($stylesheet, $upgrader = null) { + return $this->isBeingUpgraded('theme', $stylesheet, $upgrader); + } + + /** + * Check if a specific theme or plugin is being upgraded. + * + * @param string $type + * @param string $id + * @param Plugin_Upgrader|WP_Upgrader|null $upgrader + * @return bool + */ + protected function isBeingUpgraded($type, $id, $upgrader = null) { + if ( isset($upgrader) ) { + list($currentType, $currentId) = $this->getThingBeingUpgradedBy($upgrader); + if ( $currentType !== null ) { + $this->currentType = $currentType; + $this->currentId = $currentId; + } + } + return ($this->currentType === $type) && ($this->currentId === $id); + } + + /** + * Figure out which theme or plugin is being upgraded by a WP_Upgrader instance. + * + * Returns an array with two items. The first item is the type of the thing that's being + * upgraded: "plugin" or "theme". The second item is either the plugin basename or + * the theme directory name. If we can't determine what the upgrader is doing, both items + * will be NULL. + * + * Examples: + * ['plugin', 'plugin-dir-name/plugin.php'] + * ['theme', 'theme-dir-name'] * * @param Plugin_Upgrader|WP_Upgrader $upgrader - * @return string|null + * @return array */ - private function getPluginBeingUpgradedBy($upgrader) { + private function getThingBeingUpgradedBy($upgrader) { if ( !isset($upgrader, $upgrader->skin) ) { - return null; + return array(null, null); } - //Figure out which plugin is being upgraded. + //Figure out which plugin or theme is being upgraded. $pluginFile = null; + $themeDirectoryName = null; + $skin = $upgrader->skin; if ( $skin instanceof Plugin_Upgrader_Skin ) { if ( isset($skin->plugin) && is_string($skin->plugin) && ($skin->plugin !== '') ) { $pluginFile = $skin->plugin; } + } elseif ( $skin instanceof Theme_Upgrader_Skin ) { + if ( isset($skin->theme) && is_string($skin->theme) && ($skin->theme !== '') ) { + $themeDirectoryName = $skin->theme; + } + } elseif ( $upgrader->skin instanceof Bulk_Theme_Upgrader_Skin ) { + if ( isset($skin->theme_info) && ($skin->theme_info instanceof WP_Theme) ) { + $themeDirectoryName = $skin->theme_info->get_stylesheet(); + } } elseif ( isset($skin->plugin_info) && is_array($skin->plugin_info) ) { //This case is tricky because Bulk_Plugin_Upgrader_Skin (etc) doesn't actually store the plugin //filename anywhere. Instead, it has the plugin headers in $plugin_info. So the best we can @@ -64,7 +108,12 @@ if ( !class_exists('Puc_v4_UpgraderStatus', false) ): $pluginFile = $this->identifyPluginByHeaders($skin->plugin_info); } - return $pluginFile; + if ( $pluginFile !== null ) { + return array('plugin', $pluginFile); + } elseif ( $themeDirectoryName !== null ) { + return array('theme', $themeDirectoryName); + } + return array(null, null); } /** @@ -107,9 +156,11 @@ if ( !class_exists('Puc_v4_UpgraderStatus', false) ): */ public function setUpgradedPlugin($input, $hookExtra) { if (!empty($hookExtra['plugin']) && is_string($hookExtra['plugin'])) { - $this->upgradedPluginFile = $hookExtra['plugin']; + $this->currentId = $hookExtra['plugin']; + $this->currentType = 'plugin'; } else { - $this->upgradedPluginFile = null; + $this->currentType = null; + $this->currentId = null; } return $input; } @@ -122,9 +173,11 @@ if ( !class_exists('Puc_v4_UpgraderStatus', false) ): */ public function setUpgradedPluginFromOptions($options) { if (isset($options['hook_extra']['plugin']) && is_string($options['hook_extra']['plugin'])) { - $this->upgradedPluginFile = $options['hook_extra']['plugin']; + $this->currentType = 'plugin'; + $this->currentId = $options['hook_extra']['plugin']; } else { - $this->upgradedPluginFile = null; + $this->currentType = null; + $this->currentId = null; } return $options; } @@ -135,8 +188,9 @@ if ( !class_exists('Puc_v4_UpgraderStatus', false) ): * @param mixed $input * @return mixed Returns $input unaltered. */ - public function clearUpgradedPlugin($input = null) { - $this->upgradedPluginFile = null; + public function clearUpgradedThing($input = null) { + $this->currentId = null; + $this->currentType = null; return $input; } }