Manage plugins from the user interface and from the command line

This commit is contained in:
Frederic Guillot
2016-05-20 12:51:05 -04:00
parent cbf896e74e
commit 8d69c49da5
20 changed files with 563 additions and 61 deletions

View File

@@ -0,0 +1,162 @@
<?php
namespace Kanboard\Core\Plugin;
use RecursiveDirectoryIterator;
use RecursiveIteratorIterator;
use ZipArchive;
/**
* Class Installer
*
* @package Kanboard\Core\Plugin
* @author Frederic Guillot
*/
class Installer extends \Kanboard\Core\Base
{
/**
* Return true if Kanboard is configured to install plugins
*
* @static
* @access public
* @return bool
*/
public static function isConfigured()
{
return PLUGIN_INSTALLER && is_writable(PLUGINS_DIR) && extension_loaded('zip');
}
/**
* Install a plugin
*
* @access public
* @param string $archiveUrl
* @throws PluginInstallerException
*/
public function install($archiveUrl)
{
$zip = $this->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);
}
}

View File

@@ -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;
}
}

View File

@@ -0,0 +1,15 @@
<?php
namespace Kanboard\Core\Plugin;
use Exception;
/**
* Class PluginInstallerException
*
* @package Kanboard\Core\Plugin
* @author Frederic Guillot
*/
class PluginInstallerException extends Exception
{
}