From 2cfe210dbbcc812be95a66404d87553af8078520 Mon Sep 17 00:00:00 2001 From: mattab <matthieu.aubry@gmail.com> Date: Wed, 17 Jul 2013 11:02:25 +1200 Subject: [PATCH] Refs #546 Adding core "Uninstall" feature for Plugins and Themes + Cleanups + fix build by moving constant to PluginsManager Todo * ask for confirmation: "Do you want to uninstall X?" // warn that plugin-specific data may be deleted as part of calling the plugin's uninstall method, and may not be recoverable. * call uninstall() method on the plugin class * verify core plugins cant be uninstalled --- core/Config.php | 30 ++++------ core/PluginsManager.php | 60 +++++++++++++++++-- core/Twig.php | 9 +-- core/Updates.php | 4 -- core/Updates/1.9.3-b3.php | 4 +- core/testMinimumPhpVersion.php | 15 +++-- plugins/CorePluginsAdmin/Controller.php | 40 ++++++++++--- .../CorePluginsAdmin/templates/macros.twig | 6 +- .../stylesheets/simple_structure.css | 2 +- 9 files changed, 118 insertions(+), 52 deletions(-) diff --git a/core/Config.php b/core/Config.php index 3f8960fe16..354f9e4e37 100644 --- a/core/Config.php +++ b/core/Config.php @@ -133,24 +133,11 @@ class Piwik_Config * * @return string */ - public static function getGlobalConfigPath() + protected static function getGlobalConfigPath() { return PIWIK_USER_PATH . '/config/global.ini.php'; } - /** - * Backward compatibility stub - * - * @todo remove in 2.0 - * @since 1.7 - * @deprecated 1.7 - * @return string - */ - public static function getDefaultDefaultConfigPath() - { - return self::getGlobalConfigPath(); - } - /** * Returns absolute path to the local configuration file * @@ -272,12 +259,7 @@ class Piwik_Config return $tmp; } - $section = null; - - // merge corresponding sections from global and local settings - if (isset($this->configGlobal[$name])) { - $section = $this->configGlobal[$name]; - } + $section = $this->getFromDefaultConfig($name); if (isset($this->configLocal[$name])) { // local settings override the global defaults @@ -297,6 +279,14 @@ class Piwik_Config return $tmp; } + public function getFromDefaultConfig($name) + { + if (isset($this->configGlobal[$name])) { + return $this->configGlobal[$name]; + } + return null; + } + /** * Set value * diff --git a/core/PluginsManager.php b/core/PluginsManager.php index 7b49d9ff30..7cbd3309b7 100644 --- a/core/PluginsManager.php +++ b/core/PluginsManager.php @@ -38,6 +38,10 @@ class Piwik_PluginsManager protected $doLoadPlugins = true; protected $loadedPlugins = array(); + /** + * Default theme used in Piwik. + */ + const DEFAULT_THEME="Zeitgeist"; protected $doLoadAlwaysActivatedPlugins = true; protected $pluginToAlwaysActivate = array( @@ -53,7 +57,7 @@ class Piwik_PluginsManager 'LanguagesManager', // default Piwik theme, always enabled - Piwik_Twig::DEFAULT_THEME, + self::DEFAULT_THEME, ); // If a plugin hooks onto at least an event starting with "Tracker.", we load the plugin during tracker @@ -122,6 +126,21 @@ class Piwik_PluginsManager return in_array($name, $this->pluginToAlwaysActivate); } + /** + * Returns true if the plugin can be uninstalled. Any non-core plugin can be uninstalled. + * + * @param $name + * @return bool + */ + public function isPluginUninstallable($name) + { + // Reading the plugins from the global.ini.php config file + $pluginsBundledWithPiwik = Piwik_Config::getInstance()->getFromDefaultConfig('Plugins'); + $pluginsBundledWithPiwik = $pluginsBundledWithPiwik['Plugins']; + + return !in_array($name, $pluginsBundledWithPiwik); + } + /** * Returns true if plugin has been activated * @@ -188,6 +207,32 @@ class Piwik_PluginsManager return PIWIK_INCLUDE_PATH . '/plugins/'; } + /** + * Uninstalls a Plugin (deletes plugin files from the disk) + * Only deactivated plugins can be uninstalled + * + * @param $pluginName + */ + public function uninstallPlugin($pluginName) + { + if($this->isPluginActivated($pluginName)) { + throw new Exception("To uninstall the plugin $pluginName, first disable it in Piwik > Settings > Plugins"); + } + if(!$this->isPluginInFilesystem($pluginName)) { + throw new Exception("You are trying to uninstall the plugin $pluginName but it was not found in the directory piwik/plugins/"); + } + self::deletePluginFromFilesystem($pluginName); + if($this->isPluginInFilesystem($pluginName)) { + return false; + } + return true; + } + + public static function deletePluginFromFilesystem($plugin) + { + Piwik::unlinkRecursive(PIWIK_INCLUDE_PATH . '/plugins/' . $plugin, $deleteRootToo = true); + } + /** * Deactivate plugin * @@ -253,8 +298,7 @@ class Piwik_PluginsManager throw new Exception("Plugin '$pluginName' already activated."); } - $existingPlugins = $this->readPluginsDirectory(); - if (array_search($pluginName, $existingPlugins) === false) { + if (!$this->isPluginInFilesystem($pluginName)) { // ToDo: This fails in tracker-mode. We should log this however. //Piwik::log(sprintf("Unable to find the plugin '%s' in activatePlugin.", $pluginName)); return; @@ -289,6 +333,14 @@ class Piwik_PluginsManager Piwik::deleteAllCacheOnUpdate(); } + protected function isPluginInFilesystem($pluginName) + { + $existingPlugins = $this->readPluginsDirectory(); + $isPluginInFilesystem = array_search($pluginName, $existingPlugins) !== false; + return Piwik_Common::isValidFilename($pluginName) + && $isPluginInFilesystem; + } + /** * Returns the name of the non default theme currently enabled. * If Zeitgeist is enabled, returns false (nb: Zeitgeist cannot be disabled) @@ -301,7 +353,7 @@ class Piwik_PluginsManager foreach($plugins as $plugin) { /* @var $plugin Piwik_Plugin */ if($plugin->isTheme() - && $plugin->getPluginName() != Piwik_Twig::DEFAULT_THEME) { + && $plugin->getPluginName() != self::DEFAULT_THEME) { return $plugin->getPluginName(); } } diff --git a/core/Twig.php b/core/Twig.php index 9022c31ba7..a3f69dbc7e 100644 --- a/core/Twig.php +++ b/core/Twig.php @@ -23,12 +23,7 @@ class Piwik_Twig */ private $twig; - /** - * Default theme used in Piwik. - */ - const DEFAULT_THEME="Zeitgeist"; - - public function __construct($theme = self::DEFAULT_THEME) + public function __construct() { $loader = $this->getDefaultThemeLoader(); @@ -139,7 +134,7 @@ class Piwik_Twig private function getDefaultThemeLoader() { $themeLoader = new Twig_Loader_Filesystem(array( - sprintf("%s/plugins/%s/templates/", PIWIK_INCLUDE_PATH, self::DEFAULT_THEME) + sprintf("%s/plugins/%s/templates/", PIWIK_INCLUDE_PATH, Piwik_PluginsManager::DEFAULT_THEME) )); return $themeLoader; diff --git a/core/Updates.php b/core/Updates.php index 91bdca17e7..b9409de5ea 100644 --- a/core/Updates.php +++ b/core/Updates.php @@ -108,9 +108,5 @@ abstract class Piwik_Updates } } - public static function deletePluginFromFilesystem($plugin) - { - Piwik::unlinkRecursive(PIWIK_INCLUDE_PATH . '/plugins/' . $plugin, $deleteRootToo = true); - } } diff --git a/core/Updates/1.9.3-b3.php b/core/Updates/1.9.3-b3.php index 65338a44eb..75c6c165c0 100644 --- a/core/Updates/1.9.3-b3.php +++ b/core/Updates/1.9.3-b3.php @@ -19,9 +19,9 @@ class Piwik_Updates_1_9_3_b3 extends Piwik_Updates // Insight was a temporary code name for Overlay $pluginToDelete = 'Insight'; self::deletePluginFromConfigFile($pluginToDelete); - self::deletePluginFromFilesystem($pluginToDelete); + Piwik_PluginsManager::getInstance()->deletePluginFromFilesystem($pluginToDelete); // We also clean up 1.9.1 and delete Feedburner plugin - self::deletePluginFromFilesystem('Feedburner'); + Piwik_PluginsManager::getInstance()->deletePluginFromFilesystem('Feedburner'); } } diff --git a/core/testMinimumPhpVersion.php b/core/testMinimumPhpVersion.php index 53e34374a4..909208a355 100644 --- a/core/testMinimumPhpVersion.php +++ b/core/testMinimumPhpVersion.php @@ -71,8 +71,9 @@ if (!function_exists('Piwik_ExitWithMessage')) { * @param string $message Main message, must be html encoded before calling * @param bool|string $optionalTrace Backtrace; will be displayed in lighter color * @param bool $optionalLinks If true, will show links to the Piwik website for help + * @param bool $goBack if true, displays a link to go back */ - function Piwik_ExitWithMessage($message, $optionalTrace = false, $optionalLinks = false) + function Piwik_ExitWithMessage($message, $optionalTrace = false, $optionalLinks = false, $optionalLinkBack = false) { @header('Content-Type: text/html; charset=utf-8'); if ($optionalTrace) { @@ -87,14 +88,20 @@ if (!function_exists('Piwik_ExitWithMessage')) { <li><a target="_blank" href="http://demo.piwik.org">Piwik Online Demo</a></li> </ul>'; } + if($optionalLinkBack) { + $optionalLinkBack = '<a href="javascript:window.back();">Go Back</a><br/>'; + } $headerPage = file_get_contents(PIWIK_INCLUDE_PATH . '/plugins/Zeitgeist/templates/simpleLayoutHeader.tpl'); $footerPage = file_get_contents(PIWIK_INCLUDE_PATH . '/plugins/Zeitgeist/templates/simpleLayoutFooter.tpl'); $headerPage = str_replace('{$HTML_TITLE}', 'Piwik › Error', $headerPage); $content = '<p>' . $message . '</p> - <p><a href="index.php">Go to Piwik</a><br/> - <a href="index.php?module=Login">Login</a></p> - ' . $optionalTrace . ' ' . $optionalLinks; + <p>' + . $optionalLinkBack + . '<a href="index.php">Go to Piwik</a><br/> + <a href="index.php?module=Login">Login</a>' + . '</p>' + . ' ' . $optionalTrace . ' ' . $optionalLinks; echo $headerPage . $content . $footerPage; exit; diff --git a/plugins/CorePluginsAdmin/Controller.php b/plugins/CorePluginsAdmin/Controller.php index baa04f6da4..d9e6f0fca9 100644 --- a/plugins/CorePluginsAdmin/Controller.php +++ b/plugins/CorePluginsAdmin/Controller.php @@ -44,7 +44,7 @@ class Piwik_CorePluginsAdmin_Controller extends Piwik_Controller_Admin return $view; } - protected function getPluginsInfo( $themesOnly = false ) + protected function getPluginsInfo($themesOnly = false) { $plugins = array(); @@ -58,6 +58,7 @@ class Piwik_CorePluginsAdmin_Controller extends Piwik_Controller_Admin $plugins[$pluginName] = array( 'activated' => Piwik_PluginsManager::getInstance()->isPluginActivated($pluginName), 'alwaysActivated' => Piwik_PluginsManager::getInstance()->isPluginAlwaysActivated($pluginName), + 'uninstallable' => Piwik_PluginsManager::getInstance()->isPluginUninstallable($pluginName), ); } Piwik_PluginsManager::getInstance()->loadPluginTranslations(); @@ -74,7 +75,7 @@ class Piwik_CorePluginsAdmin_Controller extends Piwik_Controller_Admin 'description' => '<strong><em>' . Piwik_Translate('CorePluginsAdmin_PluginCannotBeFound') . '</strong></em>', 'version' => Piwik_Translate('General_Unknown'), - 'theme' => false, + 'theme' => false, ); } } @@ -103,23 +104,46 @@ class Piwik_CorePluginsAdmin_Controller extends Piwik_Controller_Admin public function deactivate($redirectAfter = true) { - Piwik::checkUserIsSuperUser(); - $this->checkTokenInUrl(); - $pluginName = Piwik_Common::getRequestVar('pluginName', null, 'string'); + $pluginName = $this->initPluginModification(); Piwik_PluginsManager::getInstance()->deactivatePlugin($pluginName); + $this->redirectAfterModification($redirectAfter); + } + + protected function redirectAfterModification($redirectAfter) + { if ($redirectAfter) { Piwik_Url::redirectToReferer(); } } - public function activate($redirectAfter = true) + protected function initPluginModification() { Piwik::checkUserIsSuperUser(); $this->checkTokenInUrl(); $pluginName = Piwik_Common::getRequestVar('pluginName', null, 'string'); + return $pluginName; + } + + public function activate($redirectAfter = true) + { + $pluginName = $this->initPluginModification(); Piwik_PluginsManager::getInstance()->activatePlugin($pluginName); - if ($redirectAfter) { - Piwik_Url::redirectToReferer(); + $this->redirectAfterModification($redirectAfter); + } + + public function uninstall($redirectAfter = true) + { + $pluginName = $this->initPluginModification(); + $uninstalled = Piwik_PluginsManager::getInstance()->uninstallPlugin($pluginName); + if(!$uninstalled) { + $path = Piwik_Common::getPathToPiwikRoot() . '/plugins/' . $pluginName . '/'; + $messagePermissions = Piwik::getErrorMessageMissingPermissions($path); + + $messageIntro = Piwik_Translate("Warning: \"%s\" could not be uninstalled. Piwik did not have enough permission to delete the files in $path. ", + $pluginName); + $exitMessage = $messageIntro . "<br/><br/>" . $messagePermissions; + Piwik_ExitWithMessage($exitMessage, $optionalTrace = false, $optionalLinks = false, $optionalLinkBack = true); } + $this->redirectAfterModification($redirectAfter); } } diff --git a/plugins/CorePluginsAdmin/templates/macros.twig b/plugins/CorePluginsAdmin/templates/macros.twig index 7da709fb30..1d3a592159 100644 --- a/plugins/CorePluginsAdmin/templates/macros.twig +++ b/plugins/CorePluginsAdmin/templates/macros.twig @@ -32,7 +32,7 @@ <cite>By {% if plugin.info.author_homepage is defined %} <a title="{{ 'CorePluginsAdmin_AuthorHomepage'|translate }}" href="{{ plugin.info.author_homepage }}" target="_blank"> - {% endif %}{{ plugin.info.author }}{% if plugin.info.author_homepage is defined %}</a>{% endif %} + {% endif %}{{ plugin.info.author }}{% if plugin.info.author_homepage is defined -%}</a>{% endif -%} .</cite> {% endif %} </td> @@ -40,7 +40,9 @@ {% if plugin.activated %} {{ 'CorePluginsAdmin_Active'|translate }} {% else %} - {{ 'CorePluginsAdmin_Inactive'|translate }} + {{ 'CorePluginsAdmin_Inactive'|translate }} <br/> + - {% if plugin.uninstallable %}<a href='index.php?module=CorePluginsAdmin&action=uninstall&pluginName={{ name }}&token_auth={{ + token_auth }}'>uninstall</a>{% endif %} {% endif %} </td> diff --git a/plugins/Zeitgeist/stylesheets/simple_structure.css b/plugins/Zeitgeist/stylesheets/simple_structure.css index c62143a1bb..1582148c9a 100644 --- a/plugins/Zeitgeist/stylesheets/simple_structure.css +++ b/plugins/Zeitgeist/stylesheets/simple_structure.css @@ -36,7 +36,7 @@ body#simple { vertical-align:bottom; } #title { - padding-bottom:5px; + padding-bottom:15px; border-bottom:1px solid #F0F0F0; font:42px Georgia, serif; } -- GitLab