diff --git a/lib/AbstractPicoPlugin.php b/lib/AbstractPicoPlugin.php index 4acbde5..d767579 100644 --- a/lib/AbstractPicoPlugin.php +++ b/lib/AbstractPicoPlugin.php @@ -37,6 +37,14 @@ abstract class AbstractPicoPlugin implements PicoPluginInterface */ protected $statusChanged = false; + /** + * Boolean indicating whether this plugin matches Pico's API version + * + * @see AbstractPicoPlugin::checkCompatibility() + * @var boolean|null + */ + protected $nativePlugin; + /** * List of plugins which this plugin depends on * @@ -81,6 +89,7 @@ abstract class AbstractPicoPlugin implements PicoPluginInterface // make sure dependencies are already fulfilled, // otherwise the plugin needs to be enabled manually try { + $this->checkCompatibility(); $this->checkDependencies(false); } catch (RuntimeException $e) { $this->enabled = false; @@ -105,6 +114,7 @@ abstract class AbstractPicoPlugin implements PicoPluginInterface $this->enabled = (bool) $enabled; if ($enabled) { + $this->checkCompatibility(); $this->checkDependencies($recursive); } else { $this->checkDependants($recursive); @@ -283,4 +293,35 @@ abstract class AbstractPicoPlugin implements PicoPluginInterface return $this->dependants; } + + /** + * Checks compatibility with Pico's API version + * + * Pico automatically adds a dependency to {@see PicoDeprecated} when the + * plugin's API is older than Pico's API. {@see PicoDeprecated} furthermore + * throws a exception when it can't provide compatibility in such cases. + * However, we still have to decide whether this plugin is compatible to + * newer API versions, what defaults to "no" by default. + * + * @return void + * @throws RuntimeException thrown when the plugin's and Pico's API + * aren't compatible + */ + protected function checkCompatibility() + { + if ($this->nativePlugin === null) { + $picoClassName = get_class($this->pico); + $picoApiVersion = defined($picoClassName . '::API_VERSION') ? $picoClassName::API_VERSION : 1; + $pluginApiVersion = defined('static::API_VERSION') ? static::API_VERSION : 1; + + $this->nativePlugin = ($pluginApiVersion === $picoApiVersion); + + if (!$this->nativePlugin && ($pluginApiVersion > $picoApiVersion)) { + throw new RuntimeException( + "Unable to enable plugin '" . get_called_class() . "': The plugin's API (version " + . $pluginApiVersion . ") isn't compatible with Pico's API (version " . $picoApiVersion . ")" + ); + } + } + } } diff --git a/lib/Pico.php b/lib/Pico.php index c35fd93..09dbfe7 100644 --- a/lib/Pico.php +++ b/lib/Pico.php @@ -42,6 +42,13 @@ class Pico */ const VERSION_ID = 20000; + /** + * Pico API version + * + * @var int + */ + const API_VERSION = 2; + /** * Sort files in alphabetical ascending order * @@ -121,6 +128,13 @@ class Pico */ protected $plugins = array(); + /** + * List of loaded plugins using the current API version + * + * @var object[] + */ + protected $nativePlugins = array(); + /** * Current configuration of this Pico instance * @@ -523,6 +537,12 @@ class Pico } $this->plugins[$className] = $plugin; + + if ($plugin instanceof PicoPluginInterface) { + if (defined($className . '::API_VERSION') && ($className::API_VERSION >= static::API_VERSION)) { + $this->nativePlugins[$className] = $plugin; + } + } } else { throw new RuntimeException( "Unable to load plugin '" . $className . "' " @@ -567,6 +587,10 @@ class Pico $this->plugins[$className] = $plugin; + if (defined($className . '::API_VERSION') && ($className::API_VERSION >= static::API_VERSION)) { + $this->nativePlugins[$className] = $plugin; + } + return $plugin; } @@ -613,8 +637,9 @@ class Pico $dependencies = array(); if ($plugin instanceof PicoPluginInterface) { $dependencies = $plugin->getDependencies(); - } else { - $dependencies = array('PicoDeprecated'); + } + if (!isset($this->nativePlugins[$pluginName])) { + $dependencies[] = 'PicoDeprecated'; } foreach ($dependencies as $dependency) { @@ -1996,10 +2021,14 @@ class Pico } /** - * Triggers events on plugins which implement PicoPluginInterface + * Triggers events on plugins using the current API version + * + * Plugins using older API versions are handled by {@see PicoDeprecated}. + * Please note that {@see PicoDeprecated} also triggers custom events on + * plugins using older API versions, thus you can safely use this method + * to trigger custom events on all loaded plugins, no matter what API + * version - the event will be triggered in any case * - * Deprecated events (as used by plugins not implementing - * {@link PicoPluginInterface}) are triggered by {@link PicoDeprecated}. * You MUST NOT trigger events of Pico's core with a plugin! * * @see PicoPluginInterface @@ -2011,12 +2040,8 @@ class Pico */ public function triggerEvent($eventName, array $params = array()) { - foreach ($this->plugins as $plugin) { - // only trigger events for plugins that implement PicoPluginInterface - // deprecated events (plugins for Pico 0.9 and older) will be triggered by `PicoDeprecated` - if ($plugin instanceof PicoPluginInterface) { - $plugin->handleEvent($eventName, $params); - } + foreach ($this->nativePlugins as $plugin) { + $plugin->handleEvent($eventName, $params); } } } diff --git a/plugins/DummyPlugin.php b/plugins/DummyPlugin.php index 1fa56f8..4cd0210 100644 --- a/plugins/DummyPlugin.php +++ b/plugins/DummyPlugin.php @@ -13,6 +13,13 @@ */ class DummyPlugin extends AbstractPicoPlugin { + /** + * API version used by this plugin + * + * @var int + */ + const API_VERSION = 2; + /** * This plugin is disabled by default *