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 exports in modal box
* Improve accordion component
* Offer the possibility to define version compatibility from plugins
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(
'plugins' => $this->pluginLoader->getPlugins(),
'incompatible_plugins' => $this->pluginLoader->getIncompatiblePlugins(),
'title' => t('Installed Plugins'),
'is_configured' => Installer::isConfigured(),
)));

View File

@ -131,4 +131,17 @@ abstract class Base extends \Kanboard\Core\Base
{
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)
{
if (strpos($appVersion, 'master') !== false) {
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;
return Version::isCompatible($plugin['compatible_version'], $appVersion);
}
/**

View File

@ -4,6 +4,7 @@ namespace Kanboard\Core\Plugin;
use Composer\Autoload\ClassLoader;
use DirectoryIterator;
use Exception;
use LogicException;
use Kanboard\Core\Tool;
@ -22,6 +23,7 @@ class Loader extends \Kanboard\Core\Base
* @var array
*/
protected $plugins = array();
protected $incompatiblePlugins = array();
/**
* Get list of loaded plugins
@ -34,6 +36,17 @@ class Loader extends \Kanboard\Core\Base
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
*
@ -51,8 +64,7 @@ class Loader extends \Kanboard\Core\Base
foreach ($dir as $fileInfo) {
if ($fileInfo->isDir() && substr($fileInfo->getFilename(), 0, 1) !== '.') {
$pluginName = $fileInfo->getFilename();
$this->loadSchema($pluginName);
$this->initializePlugin($pluginName, $this->loadPlugin($pluginName));
$this->initializePlugin($pluginName);
}
}
}
@ -85,7 +97,7 @@ class Loader extends \Kanboard\Core\Base
$className = '\Kanboard\Plugin\\'.$pluginName.'\\Plugin';
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);
@ -96,18 +108,30 @@ class Loader extends \Kanboard\Core\Base
*
* @access public
* @param string $pluginName
* @param Base $plugin
*/
public function initializePlugin($pluginName, Base $plugin)
public function initializePlugin($pluginName)
{
if (method_exists($plugin, 'onStartup')) {
$this->dispatcher->addListener('app.bootstrap', array($plugin, 'onStartup'));
try {
$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;
use Exception;
/**
* Class PluginInstallerException
*
* @package Kanboard\Core\Plugin
* @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">
<h2><?= t('Installed Plugins') ?></h2>
</div>

View File

@ -71,6 +71,15 @@ class Plugin extends Base
{
$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)
- `setContentSecurityPolicy(array $rules)`: Override default HTTP CSP rules
- `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.

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'));
}
}