MyShaarli/application/PluginManager.php

243 lines
6.6 KiB
PHP

<?php
/**
* Class PluginManager
*
* Use to manage, load and execute plugins.
*/
class PluginManager
{
/**
* List of authorized plugins from configuration file.
* @var array $authorizedPlugins
*/
private $authorizedPlugins;
/**
* List of loaded plugins.
* @var array $loadedPlugins
*/
private $loadedPlugins = array();
/**
* @var ConfigManager Configuration Manager instance.
*/
protected $conf;
/**
* @var array List of plugin errors.
*/
protected $errors;
/**
* Plugins subdirectory.
* @var string $PLUGINS_PATH
*/
public static $PLUGINS_PATH = 'plugins';
/**
* Plugins meta files extension.
* @var string $META_EXT
*/
public static $META_EXT = 'meta';
/**
* Constructor.
*
* @param ConfigManager $conf Configuration Manager instance.
*/
public function __construct(&$conf)
{
$this->conf = $conf;
$this->errors = array();
}
/**
* Load plugins listed in $authorizedPlugins.
*
* @param array $authorizedPlugins Names of plugin authorized to be loaded.
*
* @return void
*/
public function load($authorizedPlugins)
{
$this->authorizedPlugins = $authorizedPlugins;
$dirs = glob(self::$PLUGINS_PATH . '/*', GLOB_ONLYDIR);
$dirnames = array_map('basename', $dirs);
foreach ($this->authorizedPlugins as $plugin) {
$index = array_search($plugin, $dirnames);
// plugin authorized, but its folder isn't listed
if ($index === false) {
continue;
}
try {
$this->loadPlugin($dirs[$index], $plugin);
}
catch (PluginFileNotFoundException $e) {
error_log($e->getMessage());
}
}
}
/**
* Execute all plugins registered hook.
*
* @param string $hook name of the hook to trigger.
* @param array $data list of data to manipulate passed by reference.
* @param array $params additional parameters such as page target.
*
* @return void
*/
public function executeHooks($hook, &$data, $params = array())
{
if (!empty($params['target'])) {
$data['_PAGE_'] = $params['target'];
}
if (isset($params['loggedin'])) {
$data['_LOGGEDIN_'] = $params['loggedin'];
}
foreach ($this->loadedPlugins as $plugin) {
$hookFunction = $this->buildHookName($hook, $plugin);
if (function_exists($hookFunction)) {
$data = call_user_func($hookFunction, $data, $this->conf);
}
}
}
/**
* Load a single plugin from its files.
* Call the init function if it exists, and collect errors.
* Add them in $loadedPlugins if successful.
*
* @param string $dir plugin's directory.
* @param string $pluginName plugin's name.
*
* @return void
* @throws PluginFileNotFoundException - plugin files not found.
*/
private function loadPlugin($dir, $pluginName)
{
if (!is_dir($dir)) {
throw new PluginFileNotFoundException($pluginName);
}
$pluginFilePath = $dir . '/' . $pluginName . '.php';
if (!is_file($pluginFilePath)) {
throw new PluginFileNotFoundException($pluginName);
}
$conf = $this->conf;
include_once $pluginFilePath;
$initFunction = $pluginName . '_init';
if (function_exists($initFunction)) {
$errors = call_user_func($initFunction, $this->conf);
if (!empty($errors)) {
$this->errors = array_merge($this->errors, $errors);
}
}
$this->loadedPlugins[] = $pluginName;
}
/**
* Construct normalize hook name for a specific plugin.
*
* Format:
* hook_<plugin_name>_<hook_name>
*
* @param string $hook hook name.
* @param string $pluginName plugin name.
*
* @return string - plugin's hook name.
*/
public function buildHookName($hook, $pluginName)
{
return 'hook_' . $pluginName . '_' . $hook;
}
/**
* Retrieve plugins metadata from *.meta (INI) files into an array.
* Metadata contains:
* - plugin description [description]
* - parameters split with ';' [parameters]
*
* Respects plugins order from settings.
*
* @return array plugins metadata.
*/
public function getPluginsMeta()
{
$metaData = array();
$dirs = glob(self::$PLUGINS_PATH . '/*', GLOB_ONLYDIR | GLOB_MARK);
// Browse all plugin directories.
foreach ($dirs as $pluginDir) {
$plugin = basename($pluginDir);
$metaFile = $pluginDir . $plugin . '.' . self::$META_EXT;
if (!is_file($metaFile) || !is_readable($metaFile)) {
continue;
}
$metaData[$plugin] = parse_ini_file($metaFile);
$metaData[$plugin]['order'] = array_search($plugin, $this->authorizedPlugins);
// Read parameters and format them into an array.
if (isset($metaData[$plugin]['parameters'])) {
$params = explode(';', $metaData[$plugin]['parameters']);
} else {
$params = array();
}
$metaData[$plugin]['parameters'] = array();
foreach ($params as $param) {
if (empty($param)) {
continue;
}
$metaData[$plugin]['parameters'][$param]['value'] = '';
// Optional parameter description in parameter.PARAM_NAME=
if (isset($metaData[$plugin]['parameter.'. $param])) {
$metaData[$plugin]['parameters'][$param]['desc'] = $metaData[$plugin]['parameter.'. $param];
}
}
}
return $metaData;
}
/**
* Return the list of encountered errors.
*
* @return array List of errors (empty array if none exists).
*/
public function getErrors()
{
return $this->errors;
}
}
/**
* Class PluginFileNotFoundException
*
* Raise when plugin files can't be found.
*/
class PluginFileNotFoundException extends Exception
{
/**
* Construct exception with plugin name.
* Generate message.
*
* @param string $pluginName name of the plugin not found
*/
public function __construct($pluginName)
{
$this->message = 'Plugin "'. $pluginName .'" files not found.';
}
}