Offer the possibility to define version compatibility from plugins

This commit is contained in:
Frederic Guillot
2017-01-08 17:02:31 -05:00
parent 17ac414d74
commit 07f9700179
11 changed files with 186 additions and 28 deletions

View File

@@ -14,6 +14,7 @@ Improvements:
* Display project analytics in modal box * Display project analytics in modal box
* Display project exports in modal box * Display project exports in modal box
* Improve accordion component * Improve accordion component
* Offer the possibility to define version compatibility from plugins
Version 1.0.36 (Dec 30, 2016) Version 1.0.36 (Dec 30, 2016)
----------------------------- -----------------------------

View File

@@ -23,6 +23,7 @@ class PluginController extends BaseController
{ {
$this->response->html($this->helper->layout->plugin('plugin/show', array( $this->response->html($this->helper->layout->plugin('plugin/show', array(
'plugins' => $this->pluginLoader->getPlugins(), 'plugins' => $this->pluginLoader->getPlugins(),
'incompatible_plugins' => $this->pluginLoader->getIncompatiblePlugins(),
'title' => t('Installed Plugins'), 'title' => t('Installed Plugins'),
'is_configured' => Installer::isConfigured(), 'is_configured' => Installer::isConfigured(),
))); )));

View File

@@ -131,4 +131,17 @@ abstract class Base extends \Kanboard\Core\Base
{ {
return ''; return '';
} }
/**
* Get application compatibility version
*
* Examples: >=1.0.36, 1.0.37, APP_VERSION
*
* @access public
* @return string
*/
public function getCompatibleVersion()
{
return APP_VERSION;
}
} }

View File

@@ -36,18 +36,7 @@ class Directory extends BaseCore
*/ */
public function isCompatible(array $plugin, $appVersion = APP_VERSION) public function isCompatible(array $plugin, $appVersion = APP_VERSION)
{ {
if (strpos($appVersion, 'master') !== false) { return Version::isCompatible($plugin['compatible_version'], $appVersion);
return true;
}
foreach (array('>=', '>') as $operator) {
if (strpos($plugin['compatible_version'], $operator) === 0) {
$pluginVersion = substr($plugin['compatible_version'], strlen($operator));
return version_compare($appVersion, $pluginVersion, $operator);
}
}
return $plugin['compatible_version'] === $appVersion;
} }
/** /**

View File

@@ -4,6 +4,7 @@ namespace Kanboard\Core\Plugin;
use Composer\Autoload\ClassLoader; use Composer\Autoload\ClassLoader;
use DirectoryIterator; use DirectoryIterator;
use Exception;
use LogicException; use LogicException;
use Kanboard\Core\Tool; use Kanboard\Core\Tool;
@@ -22,6 +23,7 @@ class Loader extends \Kanboard\Core\Base
* @var array * @var array
*/ */
protected $plugins = array(); protected $plugins = array();
protected $incompatiblePlugins = array();
/** /**
* Get list of loaded plugins * Get list of loaded plugins
@@ -34,6 +36,17 @@ class Loader extends \Kanboard\Core\Base
return $this->plugins; return $this->plugins;
} }
/**
* Get list of not compatible plugins
*
* @access public
* @return Base[]
*/
public function getIncompatiblePlugins()
{
return $this->incompatiblePlugins;
}
/** /**
* Scan plugin folder and load plugins * Scan plugin folder and load plugins
* *
@@ -51,8 +64,7 @@ class Loader extends \Kanboard\Core\Base
foreach ($dir as $fileInfo) { foreach ($dir as $fileInfo) {
if ($fileInfo->isDir() && substr($fileInfo->getFilename(), 0, 1) !== '.') { if ($fileInfo->isDir() && substr($fileInfo->getFilename(), 0, 1) !== '.') {
$pluginName = $fileInfo->getFilename(); $pluginName = $fileInfo->getFilename();
$this->loadSchema($pluginName); $this->initializePlugin($pluginName);
$this->initializePlugin($pluginName, $this->loadPlugin($pluginName));
} }
} }
} }
@@ -85,7 +97,7 @@ class Loader extends \Kanboard\Core\Base
$className = '\Kanboard\Plugin\\'.$pluginName.'\\Plugin'; $className = '\Kanboard\Plugin\\'.$pluginName.'\\Plugin';
if (! class_exists($className)) { if (! class_exists($className)) {
throw new LogicException('Unable to load this plugin class '.$className); throw new LogicException('Unable to load this plugin class: '.$className);
} }
return new $className($this->container); return new $className($this->container);
@@ -96,18 +108,30 @@ class Loader extends \Kanboard\Core\Base
* *
* @access public * @access public
* @param string $pluginName * @param string $pluginName
* @param Base $plugin
*/ */
public function initializePlugin($pluginName, Base $plugin) public function initializePlugin($pluginName)
{ {
if (method_exists($plugin, 'onStartup')) { try {
$this->dispatcher->addListener('app.bootstrap', array($plugin, 'onStartup')); $plugin = $this->loadPlugin($pluginName);
if (Version::isCompatible($plugin->getCompatibleVersion(), APP_VERSION)) {
$this->loadSchema($pluginName);
if (method_exists($plugin, 'onStartup')) {
$this->dispatcher->addListener('app.bootstrap', array($plugin, 'onStartup'));
}
Tool::buildDIC($this->container, $plugin->getClasses());
Tool::buildDICHelpers($this->container, $plugin->getHelpers());
$plugin->initialize();
$this->plugins[$pluginName] = $plugin;
} else {
$this->incompatiblePlugins[$pluginName] = $plugin;
$this->logger->error($pluginName.' is not compatible with this version');
}
} catch (Exception $e) {
$this->logger->critical($pluginName.': '.$e->getMessage());
} }
Tool::buildDIC($this->container, $plugin->getClasses());
Tool::buildDICHelpers($this->container, $plugin->getHelpers());
$plugin->initialize();
$this->plugins[$pluginName] = $plugin;
} }
} }

View File

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

View File

@@ -2,14 +2,12 @@
namespace Kanboard\Core\Plugin; namespace Kanboard\Core\Plugin;
use Exception;
/** /**
* Class PluginInstallerException * Class PluginInstallerException
* *
* @package Kanboard\Core\Plugin * @package Kanboard\Core\Plugin
* @author Frederic Guillot * @author Frederic Guillot
*/ */
class PluginInstallerException extends Exception class PluginInstallerException extends PluginException
{ {
} }

View File

@@ -0,0 +1,38 @@
<?php
namespace Kanboard\Core\Plugin;
/**
* Class Version
*
* @package Kanboard\Core\Plugin
* @author Frederic Guillot
*/
class Version
{
/**
* Check plugin version compatibility with application version
*
* @param string $pluginCompatibleVersion
* @param string $appVersion
* @return bool
*/
public static function isCompatible($pluginCompatibleVersion, $appVersion = APP_VERSION)
{
if (strpos($appVersion, 'master') !== false) {
return true;
}
$appVersion = str_replace('v', '', $appVersion);
$pluginCompatibleVersion = str_replace('v', '', $pluginCompatibleVersion);
foreach (array('>=', '>', '<=', '<') as $operator) {
if (strpos($pluginCompatibleVersion, $operator) === 0) {
$pluginVersion = substr($pluginCompatibleVersion, strlen($operator));
return version_compare($appVersion, $pluginVersion, $operator);
}
}
return $pluginCompatibleVersion === $appVersion;
}
}

View File

@@ -1,3 +1,43 @@
<?php if (! empty($incompatible_plugins)): ?>
<div class="page-header">
<h2><?= t('Incompatible Plugins') ?></h2>
</div>
<table>
<tr>
<th class="column-35"><?= t('Name') ?></th>
<th class="column-25"><?= t('Author') ?></th>
<th class="column-10"><?= t('Version') ?></th>
<th class="column-12"><?= t('Compatibility') ?></th>
<?php if ($is_configured): ?>
<th><?= t('Action') ?></th>
<?php endif ?>
</tr>
<?php foreach ($incompatible_plugins as $pluginFolder => $plugin): ?>
<tr>
<td>
<?php if ($plugin->getPluginHomepage()): ?>
<a href="<?= $plugin->getPluginHomepage() ?>" target="_blank" rel="noreferrer"><?= $this->text->e($plugin->getPluginName()) ?></a>
<?php else: ?>
<?= $this->text->e($plugin->getPluginName()) ?>
<?php endif ?>
</td>
<td><?= $this->text->e($plugin->getPluginAuthor()) ?></td>
<td><?= $this->text->e($plugin->getPluginVersion()) ?></td>
<td><?= $this->text->e($plugin->getCompatibleVersion()) ?></td>
<?php if ($is_configured): ?>
<td>
<?= $this->modal->confirm('trash-o', t('Uninstall'), 'PluginController', 'confirm', array('pluginId' => $pluginFolder)) ?>
</td>
<?php endif ?>
</tr>
<tr>
<td colspan="<?= $is_configured ? 6 : 5 ?>"><?= $this->text->e($plugin->getPluginDescription()) ?></td>
</tr>
<?php endforeach ?>
</table>
<?php endif ?>
<div class="page-header"> <div class="page-header">
<h2><?= t('Installed Plugins') ?></h2> <h2><?= t('Installed Plugins') ?></h2>
</div> </div>

View File

@@ -71,6 +71,15 @@ class Plugin extends Base
{ {
$this->template->hook->attach('template:layout:head', 'theme:layout/head'); $this->template->hook->attach('template:layout:head', 'theme:layout/head');
} }
public function getCompatibleVersion()
{
// Examples:
// >=1.0.37
// <1.0.37
// <=1.0.37
return '1.0.37';
}
} }
``` ```
@@ -93,6 +102,7 @@ Available methods from `Kanboard\Core\Plugin\Base`:
- `getPluginHomepage()`: Should return plugin Homepage (link) - `getPluginHomepage()`: Should return plugin Homepage (link)
- `setContentSecurityPolicy(array $rules)`: Override default HTTP CSP rules - `setContentSecurityPolicy(array $rules)`: Override default HTTP CSP rules
- `onStartup()`: If present, this method is executed automatically when the event "app.bootstrap" is triggered - `onStartup()`: If present, this method is executed automatically when the event "app.bootstrap" is triggered
- `getCompatibleVersion()`: You may want to specify the Kanboard version compatible with the plugin
Your plugin registration class can also inherit from Kanboard\Core\Base, that way you can access all classes and methods of Kanboard easily. Your plugin registration class can also inherit from Kanboard\Core\Base, that way you can access all classes and methods of Kanboard easily.

View File

@@ -0,0 +1,29 @@
<?php
use Kanboard\Core\Plugin\Version;
require_once __DIR__.'/../../Base.php';
class VersionTest extends Base
{
public function testIsCompatible()
{
$this->assertFalse(Version::isCompatible('1.0.29', '1.0.28'));
$this->assertTrue(Version::isCompatible('1.0.28', '1.0.28'));
$this->assertTrue(Version::isCompatible('1.0.28', 'master.1234'));
$this->assertTrue(Version::isCompatible('>=1.0.32', 'master'));
$this->assertTrue(Version::isCompatible('>=1.0.32', '1.0.32'));
$this->assertTrue(Version::isCompatible('>=1.0.32', '1.0.33'));
$this->assertTrue(Version::isCompatible('>1.0.32', '1.0.33'));
$this->assertFalse(Version::isCompatible('>1.0.32', '1.0.32'));
$this->assertTrue(Version::isCompatible('1.0.32', 'v1.0.32'));
$this->assertTrue(Version::isCompatible('>=v1.0.32', 'v1.0.32'));
$this->assertTrue(Version::isCompatible('<=v1.0.36', 'v1.0.36'));
$this->assertFalse(Version::isCompatible('<1.0.36', 'v1.0.36'));
$this->assertTrue(Version::isCompatible('<1.0.40', '1.0.36'));
$this->assertTrue(Version::isCompatible('<=1.0.40', '1.0.36'));
$this->assertFalse(Version::isCompatible('<1.0.40', '1.0.40'));
$this->assertFalse(Version::isCompatible('1.0.40', 'v1.0.36'));
$this->assertTrue(Version::isCompatible('<1.1.0', 'v1.0.36'));
}
}