Move update injection and directory name fixes to the base UpdateChecker class.
This commit is contained in:
parent
f5ae142e63
commit
2a176fc665
|
|
@ -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 <span class="code">%s</span>, 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…',
|
||||
'<span class="code">' . basename($source) . '</span>',
|
||||
'<span class="code">' . $pluginDirectoryName . '</span>'
|
||||
));
|
||||
|
||||
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;
|
||||
|
|
|
|||
|
|
@ -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
|
||||
*/
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
@ -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 <span class="code">%s</span>, 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…',
|
||||
'<span class="code">' . basename($source) . '</span>',
|
||||
'<span class="code">' . $this->directoryName . '</span>'
|
||||
));
|
||||
|
||||
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;
|
||||
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in New Issue