From 037ab7d2a47b445fd07b06d582a0bf9dabfad77f Mon Sep 17 00:00:00 2001 From: Yahnis Elsts Date: Fri, 6 Feb 2015 18:01:00 +0200 Subject: [PATCH] Automatically rename update to match existing installation Preamble: When WordPress installs a plugin update, it assumes that the update ZIP will contain a directory that has the same name as the currently installed plugin. For example, if the plugin is installed in `/wp-content/plugins/awesome-plugin/`, WP expects that there'll be an "awesome-plugin" directory in the ZIP archive. If the update doesn't contain that directory, the installation process will either fail with a cryptic error message or produce unexpected results. The problem: - Some developers are either unaware of the above, or unable to format their updates accordingly. For example, they might be using GitHub to serve updates. GitHub typically names the directory in the release/branch downloads "repo-branchname" or "repo-tag-hash". WP needs it to be just "repo". - Users can rename the plugin directory. It's very rare, but I've seen it happen. Solution: The `upgrader_source_selection` filter lets you specify the directory that will be used as the "new" version. Using it is a bit tricky because WordPress doesn't actually tell you *which* plugin or theme it's currently upgrading, but with some analysis and heuristics it's possible to figure it out (most of the time). Then you can rename the directory from the update package to match the existing plugin directory. --- plugin-update-checker.php | 97 ++++++++++++++++++++++++++++++++++++++- 1 file changed, 96 insertions(+), 1 deletion(-) diff --git a/plugin-update-checker.php b/plugin-update-checker.php index 979dc84..9ad8f4f 100644 --- a/plugin-update-checker.php +++ b/plugin-update-checker.php @@ -1,6 +1,6 @@ skin, $wp_filesystem) ) { + return $source; + } + + //Figure out which plugin is being upgraded. + $pluginFile = null; + $skin = $upgrader->skin; + if ( $skin instanceof Plugin_Upgrader_Skin ) { + if ( isset($skin->plugin) && is_string($skin->plugin) && ($skin->plugin !== '') ) { + $pluginFile = $skin->plugin; + } + } elseif ( $upgrader->skin instanceof Bulk_Plugin_Upgrader_Skin ) { + //This case is tricky because Bulk_Plugin_Upgrader_Skin doesn't actually store the plugin + //filename anywhere. Instead, it has the plugin headers in $plugin_info. So the best we can + //do is compare those headers to the headers of installed plugins. + if ( isset($skin->plugin_info) && is_array($skin->plugin_info) ) { + if ( !function_exists('get_plugins') ){ + require_once( ABSPATH . '/wp-admin/includes/plugin.php' ); + } + + $installedPlugins = get_plugins(); + $matches = array(); + foreach($installedPlugins as $pluginBasename => $headers) { + $diff1 = array_diff_assoc($headers, $skin->plugin_info); + $diff2 = array_diff_assoc($skin->plugin_info, $headers); + if ( empty($diff1) && empty($diff2) ) { + $matches[] = $pluginBasename; + } + } + + //It's possible (though very unlikely) that there could be two plugins with identical + //headers. In that case, we can't unambiguously identify the plugin that's being upgraded. + if ( count($matches) !== 1 ) { + return $source; + } + + $pluginFile = reset($matches); + } + } + + //If WordPress is upgrading anything other than our plugin, leave the directory name unchanged. + if ( empty($pluginFile) || ($pluginFile !== $this->pluginFile) ) { + return $source; + } + + //Rename the source to match the existing plugin directory. + $pluginDirectoryName = dirname($this->pluginFile); + if ( ($pluginDirectoryName === '.') || ($pluginDirectoryName === '/') ) { + return $source; + } + $correctedSource = trailingslashit($remoteSource) . $pluginDirectoryName . '/'; + if ( $source !== $correctedSource ) { + $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; + } + + /** * Get the details of the currently available update, if any. *