summaryrefslogtreecommitdiff
path: root/app/Core
diff options
context:
space:
mode:
Diffstat (limited to 'app/Core')
-rw-r--r--app/Core/Plugin/Installer.php162
-rw-r--r--app/Core/Plugin/Loader.php15
-rw-r--r--app/Core/Plugin/PluginInstallerException.php15
3 files changed, 185 insertions, 7 deletions
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 @@
+<?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);
+ }
+}
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 @@
+<?php
+
+namespace Kanboard\Core\Plugin;
+
+use Exception;
+
+/**
+ * Class PluginInstallerException
+ *
+ * @package Kanboard\Core\Plugin
+ * @author Frederic Guillot
+ */
+class PluginInstallerException extends Exception
+{
+}