Pico::loadPlugins(): Load composer-plugins first and skip conflicting plugins in the plugins/ dir
This commit is contained in:
parent
fc76d37dbc
commit
7087573aed
223
lib/Pico.php
223
lib/Pico.php
@ -444,9 +444,13 @@ class Pico
|
|||||||
/**
|
/**
|
||||||
* Loads plugins from vendor/pico-plugin.php and Pico::$pluginsDir
|
* Loads plugins from vendor/pico-plugin.php and Pico::$pluginsDir
|
||||||
*
|
*
|
||||||
* See {@see Pico::loadLocalPlugins()} for details about plugins installed
|
* See {@see Pico::loadComposerPlugins()} for details about plugins loaded
|
||||||
* to {@see Pico::$pluginsDir}, and {@see Pico::loadComposerPlugins()} for
|
* from `vendor/pico-plugin.php` (i.e. plugins that were installed using
|
||||||
* details about plugins installed using `composer`.
|
* `composer`), and {@see Pico::loadLocalPlugins()} for details about
|
||||||
|
* plugins installed to {@see Pico::$pluginsDir}.
|
||||||
|
*
|
||||||
|
* Pico always loads plugins from `vendor/pico-plugin.php` first and
|
||||||
|
* ignores conflicting plugins in {@see Pico::$pluginsDir}.
|
||||||
*
|
*
|
||||||
* Please note that Pico will change the processing order when needed to
|
* Please note that Pico will change the processing order when needed to
|
||||||
* incorporate plugin dependencies. See {@see Pico::sortPlugins()} for
|
* incorporate plugin dependencies. See {@see Pico::sortPlugins()} for
|
||||||
@ -460,108 +464,16 @@ class Pico
|
|||||||
*/
|
*/
|
||||||
protected function loadPlugins()
|
protected function loadPlugins()
|
||||||
{
|
{
|
||||||
$this->loadLocalPlugins();
|
|
||||||
$this->loadComposerPlugins();
|
$this->loadComposerPlugins();
|
||||||
}
|
$this->loadLocalPlugins();
|
||||||
|
|
||||||
/**
|
|
||||||
* Loads plugins from Pico::$pluginsDir in alphabetical order
|
|
||||||
*
|
|
||||||
* Pico tries to load plugins from `<plugin name>/<plugin name>.php` and
|
|
||||||
* `<plugin name>.php` only. Plugin names are treated case insensitive.
|
|
||||||
* Pico will throw a RuntimeException if it can't load a plugin.
|
|
||||||
*
|
|
||||||
* Plugin files MAY be prefixed by a number (e.g. 00-PicoDeprecated.php)
|
|
||||||
* to indicate their processing order. Plugins without a prefix will be
|
|
||||||
* loaded last. If you want to use a prefix, you MUST NOT use the reserved
|
|
||||||
* prefixes `00` to `09`. Prefixes are completely optional, however, you
|
|
||||||
* SHOULD take the following prefix classification into consideration:
|
|
||||||
* - 10 to 19: Reserved
|
|
||||||
* - 20 to 39: Low level code helper plugins
|
|
||||||
* - 40 to 59: Plugins manipulating routing or the pages array
|
|
||||||
* - 60 to 79: Plugins hooking into template or markdown parsing
|
|
||||||
* - 80 to 99: Plugins using the `onPageRendered` event
|
|
||||||
*
|
|
||||||
* @see Pico::loadPlugins()
|
|
||||||
* @see Pico::loadComposerPlugins()
|
|
||||||
* @return void
|
|
||||||
* @throws RuntimeException thrown when a plugin couldn't be loaded
|
|
||||||
*/
|
|
||||||
protected function loadLocalPlugins()
|
|
||||||
{
|
|
||||||
$pluginFiles = array();
|
|
||||||
$files = scandir($this->getPluginsDir());
|
|
||||||
if ($files !== false) {
|
|
||||||
foreach ($files as $file) {
|
|
||||||
if ($file[0] === '.') {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (is_dir($this->getPluginsDir() . $file)) {
|
|
||||||
$className = preg_replace('/^[0-9]+-/', '', $file);
|
|
||||||
|
|
||||||
$subdirFiles = $this->getFilesGlob($this->getPluginsDir() . $file . '/?*.php', self::SORT_NONE);
|
|
||||||
foreach ($subdirFiles as $subdirFile) {
|
|
||||||
$subdirFile = basename($subdirFile, '.php');
|
|
||||||
if (strcasecmp($className, $subdirFile) === 0) {
|
|
||||||
$pluginFiles[$className] = $file . '/' . $subdirFile . '.php';
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!isset($pluginFiles[$className])) {
|
|
||||||
throw new RuntimeException(
|
|
||||||
"Unable to load plugin '" . $className . "' from "
|
|
||||||
. "'" . $file . "/" . $className . ".php': File not found"
|
|
||||||
);
|
|
||||||
}
|
|
||||||
} elseif (substr($file, -4) === '.php') {
|
|
||||||
$className = preg_replace('/^[0-9]+-/', '', substr($file, 0, -4));
|
|
||||||
$pluginFiles[$className] = $file;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// scope isolated require()
|
|
||||||
$includeClosure = function ($pluginFile) {
|
|
||||||
require($pluginFile);
|
|
||||||
};
|
|
||||||
if (PHP_VERSION_ID >= 50400) {
|
|
||||||
$includeClosure = $includeClosure->bindTo(null);
|
|
||||||
}
|
|
||||||
|
|
||||||
foreach ($pluginFiles as $className => $pluginFile) {
|
|
||||||
$includeClosure($this->getPluginsDir() . $pluginFile);
|
|
||||||
|
|
||||||
if (class_exists($className, false)) {
|
|
||||||
// class name and file name can differ regarding case sensitivity
|
|
||||||
$plugin = new $className($this);
|
|
||||||
$className = get_class($plugin);
|
|
||||||
|
|
||||||
if (isset($this->plugins[$className])) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
$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 . "' from '" . $pluginFile . "'");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Loads plugins from vendor/pico-plugin.php
|
* Loads plugins from vendor/pico-plugin.php
|
||||||
*
|
*
|
||||||
* This method loads all plugins installed using `composer` and Pico's
|
* This method loads all plugins installed using `composer` and Pico's
|
||||||
* `picocms/pico-composer` installer by reading the `pico-plugin.php` in
|
* `picocms/pico-installer` installer by reading the `pico-plugin.php` in
|
||||||
* composer's `vendor` dir. Using composer enables plugin developers to
|
* composer's `vendor` dir.
|
||||||
* load multiple plugins and their dependencies using a single composer
|
|
||||||
* package.
|
|
||||||
*
|
*
|
||||||
* @see Pico::loadPlugins()
|
* @see Pico::loadPlugins()
|
||||||
* @see Pico::loadLocalPlugins()
|
* @see Pico::loadLocalPlugins()
|
||||||
@ -581,6 +493,119 @@ class Pico
|
|||||||
foreach ($composerPlugins as $package => $classNames) {
|
foreach ($composerPlugins as $package => $classNames) {
|
||||||
foreach ($classNames as $className) {
|
foreach ($classNames as $className) {
|
||||||
$plugin = new $className($this);
|
$plugin = new $className($this);
|
||||||
|
$className = get_class($plugin);
|
||||||
|
|
||||||
|
if (isset($this->plugins[$className])) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!($plugin instanceof PicoPluginInterface)) {
|
||||||
|
throw new RuntimeException(
|
||||||
|
"Unable to load plugin '" . $className . "' via 'vendor/pico-plugin.php': "
|
||||||
|
. "Plugins installed by composer must implement 'PicoPluginInterface'"
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
$this->plugins[$className] = $plugin;
|
||||||
|
|
||||||
|
if (defined($className . '::API_VERSION') && ($className::API_VERSION >= static::API_VERSION)) {
|
||||||
|
$this->nativePlugins[$className] = $plugin;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Loads plugins from Pico::$pluginsDir in alphabetical order
|
||||||
|
*
|
||||||
|
* Pico tries to load plugins from `<plugin name>/<plugin name>.php` and
|
||||||
|
* `<plugin name>.php` only. Plugin names are treated case insensitive.
|
||||||
|
* Pico will throw a RuntimeException if it can't load a plugin.
|
||||||
|
*
|
||||||
|
* Plugin files MAY be prefixed by a number (e.g. `00-PicoDeprecated.php`)
|
||||||
|
* to indicate their processing order. Plugins without a prefix will be
|
||||||
|
* loaded last. If you want to use a prefix, you MUST NOT use the reserved
|
||||||
|
* prefixes `00` to `09`. Prefixes are completely optional, however, you
|
||||||
|
* SHOULD take the following prefix classification into consideration:
|
||||||
|
* - 10 to 19: Reserved
|
||||||
|
* - 20 to 39: Low level code helper plugins
|
||||||
|
* - 40 to 59: Plugins manipulating routing or the pages array
|
||||||
|
* - 60 to 79: Plugins hooking into template or markdown parsing
|
||||||
|
* - 80 to 99: Plugins using the `onPageRendered` event
|
||||||
|
*
|
||||||
|
* @see Pico::loadPlugins()
|
||||||
|
* @see Pico::loadComposerPlugins()
|
||||||
|
* @return void
|
||||||
|
* @throws RuntimeException thrown when a plugin couldn't be loaded
|
||||||
|
*/
|
||||||
|
protected function loadLocalPlugins()
|
||||||
|
{
|
||||||
|
$pluginsLowered = array_change_key_case($this->plugins, CASE_LOWER);
|
||||||
|
|
||||||
|
$pluginFiles = array();
|
||||||
|
$files = scandir($this->getPluginsDir()) ?: array();
|
||||||
|
foreach ($files as $file) {
|
||||||
|
if ($file[0] === '.') {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (is_dir($this->getPluginsDir() . $file)) {
|
||||||
|
$className = preg_replace('/^[0-9]+-/', '', $file);
|
||||||
|
$classNameLowered = strtolower($className);
|
||||||
|
|
||||||
|
if (isset($pluginsLowered[$classNameLowered])) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (file_exists($this->getPluginsDir() . $file . '/' . $className . '.php')) {
|
||||||
|
$pluginFiles[$className] = $file . '/' . $className . '.php';
|
||||||
|
} else {
|
||||||
|
$subdirFiles = $this->getFilesGlob($this->getPluginsDir() . $file . '/?*.php', self::SORT_NONE);
|
||||||
|
foreach ($subdirFiles as $subdirFile) {
|
||||||
|
$subdirFile = basename($subdirFile, '.php');
|
||||||
|
if ($classNameLowered === strtolower($subdirFile)) {
|
||||||
|
$pluginFiles[$className] = $file . '/' . $subdirFile . '.php';
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!isset($pluginFiles[$className])) {
|
||||||
|
throw new RuntimeException(
|
||||||
|
"Unable to load plugin '" . $className . "' from "
|
||||||
|
. "'" . $file . "/" . $className . ".php': File not found"
|
||||||
|
);
|
||||||
|
}
|
||||||
|
} elseif (substr($file, -4) === '.php') {
|
||||||
|
$className = preg_replace('/^[0-9]+-/', '', substr($file, 0, -4));
|
||||||
|
$classNameLowered = strtolower($className);
|
||||||
|
|
||||||
|
if (isset($pluginsLowered[$classNameLowered])) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
$pluginFiles[$className] = $file;
|
||||||
|
} else {
|
||||||
|
throw new RuntimeException("Unable to load plugin from '" . $file . "': Not a valid plugin file");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// scope isolated require()
|
||||||
|
$includeClosure = function ($pluginFile) {
|
||||||
|
require($pluginFile);
|
||||||
|
};
|
||||||
|
if (PHP_VERSION_ID >= 50400) {
|
||||||
|
$includeClosure = $includeClosure->bindTo(null);
|
||||||
|
}
|
||||||
|
|
||||||
|
foreach ($pluginFiles as $className => $pluginFile) {
|
||||||
|
$includeClosure($this->getPluginsDir() . $pluginFile);
|
||||||
|
|
||||||
|
if (class_exists($className, false)) {
|
||||||
|
// class name and file name can differ regarding case sensitivity
|
||||||
|
$plugin = new $className($this);
|
||||||
|
$className = get_class($plugin);
|
||||||
|
|
||||||
$this->plugins[$className] = $plugin;
|
$this->plugins[$className] = $plugin;
|
||||||
|
|
||||||
if ($plugin instanceof PicoPluginInterface) {
|
if ($plugin instanceof PicoPluginInterface) {
|
||||||
@ -588,6 +613,8 @@ class Pico
|
|||||||
$this->nativePlugins[$className] = $plugin;
|
$this->nativePlugins[$className] = $plugin;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
} else {
|
||||||
|
throw new RuntimeException("Unable to load plugin '" . $className . "' from '" . $pluginFile . "'");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
Loading…
x
Reference in New Issue
Block a user