From 8d69c49da595c60dae51c77d48f397ab97fdf318 Mon Sep 17 00:00:00 2001 From: Frederic Guillot Date: Fri, 20 May 2016 12:51:05 -0400 Subject: Manage plugins from the user interface and from the command line --- app/Core/Plugin/Installer.php | 162 +++++++++++++++++++++++++++ app/Core/Plugin/Loader.php | 15 +-- app/Core/Plugin/PluginInstallerException.php | 15 +++ 3 files changed, 185 insertions(+), 7 deletions(-) create mode 100644 app/Core/Plugin/Installer.php create mode 100644 app/Core/Plugin/PluginInstallerException.php (limited to 'app/Core') diff --git a/app/Core/Plugin/Installer.php b/app/Core/Plugin/Installer.php new file mode 100644 index 00000000..48c4d978 --- /dev/null +++ b/app/Core/Plugin/Installer.php @@ -0,0 +1,162 @@ +downloadPluginArchive($archiveUrl); + + if (! $zip->extractTo(PLUGINS_DIR)) { + $this->cleanupArchive($zip); + throw new PluginInstallerException(t('Unable to extract plugin archive.')); + } + + $this->cleanupArchive($zip); + } + + /** + * Uninstall a plugin + * + * @access public + * @param string $pluginId + * @throws PluginInstallerException + */ + public function uninstall($pluginId) + { + $pluginFolder = PLUGINS_DIR.DIRECTORY_SEPARATOR.basename($pluginId); + + if (! file_exists($pluginFolder)) { + throw new PluginInstallerException(t('Plugin not found.')); + } + + if (! is_writable($pluginFolder)) { + throw new PluginInstallerException(e('You don\'t have the permission to remove this plugin.')); + } + + $this->removeAllDirectories($pluginFolder); + } + + /** + * Update a plugin + * + * @access public + * @param string $archiveUrl + * @throws PluginInstallerException + */ + public function update($archiveUrl) + { + $zip = $this->downloadPluginArchive($archiveUrl); + + $firstEntry = $zip->statIndex(0); + $this->uninstall($firstEntry['name']); + + if (! $zip->extractTo(PLUGINS_DIR)) { + $this->cleanupArchive($zip); + throw new PluginInstallerException(t('Unable to extract plugin archive.')); + } + + $this->cleanupArchive($zip); + } + + /** + * Download archive from URL + * + * @access protected + * @param string $archiveUrl + * @return ZipArchive + * @throws PluginInstallerException + */ + protected function downloadPluginArchive($archiveUrl) + { + $zip = new ZipArchive(); + $archiveData = $this->httpClient->get($archiveUrl); + $archiveFile = tempnam(sys_get_temp_dir(), 'kb_plugin'); + + if (empty($archiveData)) { + unlink($archiveFile); + throw new PluginInstallerException(t('Unable to download plugin archive.')); + } + + if (file_put_contents($archiveFile, $archiveData) === false) { + unlink($archiveFile); + throw new PluginInstallerException(t('Unable to write temporary file for plugin.')); + } + + if ($zip->open($archiveFile) !== true) { + unlink($archiveFile); + throw new PluginInstallerException(t('Unable to open plugin archive.')); + } + + if ($zip->numFiles === 0) { + unlink($archiveFile); + throw new PluginInstallerException(t('There is no file in the plugin archive.')); + } + + return $zip; + } + + /** + * Remove archive file + * + * @access protected + * @param ZipArchive $zip + */ + protected function cleanupArchive(ZipArchive $zip) + { + unlink($zip->filename); + $zip->close(); + } + + /** + * Remove recursively a directory + * + * @access protected + * @param string $directory + */ + protected function removeAllDirectories($directory) + { + $it = new RecursiveDirectoryIterator($directory, RecursiveDirectoryIterator::SKIP_DOTS); + $files = new RecursiveIteratorIterator($it, RecursiveIteratorIterator::CHILD_FIRST); + + foreach ($files as $file) { + if ($file->isDir()) { + rmdir($file->getRealPath()); + } else { + unlink($file->getRealPath()); + } + } + + rmdir($directory); + } +} diff --git a/app/Core/Plugin/Loader.php b/app/Core/Plugin/Loader.php index 400517b7..f2f6add7 100644 --- a/app/Core/Plugin/Loader.php +++ b/app/Core/Plugin/Loader.php @@ -18,16 +18,16 @@ class Loader extends \Kanboard\Core\Base /** * Plugin instances * - * @access public + * @access protected * @var array */ - public $plugins = array(); + protected $plugins = array(); /** * Get list of loaded plugins * * @access public - * @return array + * @return Base[] */ public function getPlugins() { @@ -52,7 +52,7 @@ class Loader extends \Kanboard\Core\Base if ($fileInfo->isDir() && substr($fileInfo->getFilename(), 0, 1) !== '.') { $pluginName = $fileInfo->getFilename(); $this->loadSchema($pluginName); - $this->initializePlugin($this->loadPlugin($pluginName)); + $this->initializePlugin($pluginName, $this->loadPlugin($pluginName)); } } } @@ -95,9 +95,10 @@ class Loader extends \Kanboard\Core\Base * Initialize plugin * * @access public - * @param Base $plugin + * @param string $pluginName + * @param Base $plugin */ - public function initializePlugin(Base $plugin) + public function initializePlugin($pluginName, Base $plugin) { if (method_exists($plugin, 'onStartup')) { $this->dispatcher->addListener('app.bootstrap', array($plugin, 'onStartup')); @@ -107,6 +108,6 @@ class Loader extends \Kanboard\Core\Base Tool::buildDICHelpers($this->container, $plugin->getHelpers()); $plugin->initialize(); - $this->plugins[] = $plugin; + $this->plugins[$pluginName] = $plugin; } } diff --git a/app/Core/Plugin/PluginInstallerException.php b/app/Core/Plugin/PluginInstallerException.php new file mode 100644 index 00000000..7d356c9b --- /dev/null +++ b/app/Core/Plugin/PluginInstallerException.php @@ -0,0 +1,15 @@ +