diff --git a/ChangeLog b/ChangeLog index c925b7f64..e833f84c0 100644 --- a/ChangeLog +++ b/ChangeLog @@ -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) ----------------------------- diff --git a/app/Controller/PluginController.php b/app/Controller/PluginController.php index 7b9d64d90..dbb739d6c 100644 --- a/app/Controller/PluginController.php +++ b/app/Controller/PluginController.php @@ -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(), ))); diff --git a/app/Core/Plugin/Base.php b/app/Core/Plugin/Base.php index 9d8167a98..e0b5954aa 100644 --- a/app/Core/Plugin/Base.php +++ b/app/Core/Plugin/Base.php @@ -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; + } } diff --git a/app/Core/Plugin/Directory.php b/app/Core/Plugin/Directory.php index 27c3514ef..dc32e6558 100644 --- a/app/Core/Plugin/Directory.php +++ b/app/Core/Plugin/Directory.php @@ -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); } /** diff --git a/app/Core/Plugin/Loader.php b/app/Core/Plugin/Loader.php index f2f6add73..38f41d39f 100644 --- a/app/Core/Plugin/Loader.php +++ b/app/Core/Plugin/Loader.php @@ -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; } } diff --git a/app/Core/Plugin/PluginException.php b/app/Core/Plugin/PluginException.php new file mode 100644 index 000000000..fae7de356 --- /dev/null +++ b/app/Core/Plugin/PluginException.php @@ -0,0 +1,15 @@ +=', '>', '<=', '<') as $operator) { + if (strpos($pluginCompatibleVersion, $operator) === 0) { + $pluginVersion = substr($pluginCompatibleVersion, strlen($operator)); + return version_compare($appVersion, $pluginVersion, $operator); + } + } + + return $pluginCompatibleVersion === $appVersion; + } +} diff --git a/app/Template/plugin/show.php b/app/Template/plugin/show.php index 0e997c556..266568ac0 100644 --- a/app/Template/plugin/show.php +++ b/app/Template/plugin/show.php @@ -1,3 +1,43 @@ + + + + + + + + + + + + + + $plugin): ?> + + + + + + + + + + + + + +
+ getPluginHomepage()): ?> + text->e($plugin->getPluginName()) ?> + + text->e($plugin->getPluginName()) ?> + + text->e($plugin->getPluginAuthor()) ?>text->e($plugin->getPluginVersion()) ?>text->e($plugin->getCompatibleVersion()) ?> + modal->confirm('trash-o', t('Uninstall'), 'PluginController', 'confirm', array('pluginId' => $pluginFolder)) ?> +
text->e($plugin->getPluginDescription()) ?>
+ + diff --git a/doc/plugin-registration.markdown b/doc/plugin-registration.markdown index 2c80aab3c..5a4a6234d 100644 --- a/doc/plugin-registration.markdown +++ b/doc/plugin-registration.markdown @@ -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. diff --git a/tests/units/Core/Plugin/VersionTest.php b/tests/units/Core/Plugin/VersionTest.php new file mode 100644 index 000000000..78f10d959 --- /dev/null +++ b/tests/units/Core/Plugin/VersionTest.php @@ -0,0 +1,29 @@ +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')); + } +}