PluginManager.php 6.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235
  1. <?php
  2. /**
  3. * Class PluginManager
  4. *
  5. * Use to manage, load and execute plugins.
  6. *
  7. * Using Singleton design pattern.
  8. */
  9. class PluginManager
  10. {
  11. /**
  12. * PluginManager singleton instance.
  13. * @var PluginManager $instance
  14. */
  15. private static $instance;
  16. /**
  17. * List of authorized plugins from configuration file.
  18. * @var array $authorizedPlugins
  19. */
  20. private $authorizedPlugins;
  21. /**
  22. * List of loaded plugins.
  23. * @var array $loadedPlugins
  24. */
  25. private $loadedPlugins = array();
  26. /**
  27. * Plugins subdirectory.
  28. * @var string $PLUGINS_PATH
  29. */
  30. public static $PLUGINS_PATH = 'plugins';
  31. /**
  32. * Plugins meta files extension.
  33. * @var string $META_EXT
  34. */
  35. public static $META_EXT = 'meta';
  36. /**
  37. * Private constructor: new instances not allowed.
  38. */
  39. private function __construct()
  40. {
  41. }
  42. /**
  43. * Cloning isn't allowed either.
  44. *
  45. * @return void
  46. */
  47. private function __clone()
  48. {
  49. }
  50. /**
  51. * Return existing instance of PluginManager, or create it.
  52. *
  53. * @return PluginManager instance.
  54. */
  55. public static function getInstance()
  56. {
  57. if (!(self::$instance instanceof self)) {
  58. self::$instance = new self();
  59. }
  60. return self::$instance;
  61. }
  62. /**
  63. * Load plugins listed in $authorizedPlugins.
  64. *
  65. * @param array $authorizedPlugins Names of plugin authorized to be loaded.
  66. *
  67. * @return void
  68. */
  69. public function load($authorizedPlugins)
  70. {
  71. $this->authorizedPlugins = $authorizedPlugins;
  72. $dirs = glob(self::$PLUGINS_PATH . '/*', GLOB_ONLYDIR);
  73. $dirnames = array_map('basename', $dirs);
  74. foreach ($this->authorizedPlugins as $plugin) {
  75. $index = array_search($plugin, $dirnames);
  76. // plugin authorized, but its folder isn't listed
  77. if ($index === false) {
  78. continue;
  79. }
  80. try {
  81. $this->loadPlugin($dirs[$index], $plugin);
  82. }
  83. catch (PluginFileNotFoundException $e) {
  84. error_log($e->getMessage());
  85. }
  86. }
  87. }
  88. /**
  89. * Execute all plugins registered hook.
  90. *
  91. * @param string $hook name of the hook to trigger.
  92. * @param array $data list of data to manipulate passed by reference.
  93. * @param array $params additional parameters such as page target.
  94. *
  95. * @return void
  96. */
  97. public function executeHooks($hook, &$data, $params = array())
  98. {
  99. if (!empty($params['target'])) {
  100. $data['_PAGE_'] = $params['target'];
  101. }
  102. if (isset($params['loggedin'])) {
  103. $data['_LOGGEDIN_'] = $params['loggedin'];
  104. }
  105. foreach ($this->loadedPlugins as $plugin) {
  106. $hookFunction = $this->buildHookName($hook, $plugin);
  107. if (function_exists($hookFunction)) {
  108. $data = call_user_func($hookFunction, $data);
  109. }
  110. }
  111. }
  112. /**
  113. * Load a single plugin from its files.
  114. * Add them in $loadedPlugins if successful.
  115. *
  116. * @param string $dir plugin's directory.
  117. * @param string $pluginName plugin's name.
  118. *
  119. * @return void
  120. * @throws PluginFileNotFoundException - plugin files not found.
  121. */
  122. private function loadPlugin($dir, $pluginName)
  123. {
  124. if (!is_dir($dir)) {
  125. throw new PluginFileNotFoundException($pluginName);
  126. }
  127. $pluginFilePath = $dir . '/' . $pluginName . '.php';
  128. if (!is_file($pluginFilePath)) {
  129. throw new PluginFileNotFoundException($pluginName);
  130. }
  131. include_once $pluginFilePath;
  132. $this->loadedPlugins[] = $pluginName;
  133. }
  134. /**
  135. * Construct normalize hook name for a specific plugin.
  136. *
  137. * Format:
  138. * hook_<plugin_name>_<hook_name>
  139. *
  140. * @param string $hook hook name.
  141. * @param string $pluginName plugin name.
  142. *
  143. * @return string - plugin's hook name.
  144. */
  145. public function buildHookName($hook, $pluginName)
  146. {
  147. return 'hook_' . $pluginName . '_' . $hook;
  148. }
  149. /**
  150. * Retrieve plugins metadata from *.meta (INI) files into an array.
  151. * Metadata contains:
  152. * - plugin description [description]
  153. * - parameters split with ';' [parameters]
  154. *
  155. * Respects plugins order from settings.
  156. *
  157. * @return array plugins metadata.
  158. */
  159. public function getPluginsMeta()
  160. {
  161. $metaData = array();
  162. $dirs = glob(self::$PLUGINS_PATH . '/*', GLOB_ONLYDIR | GLOB_MARK);
  163. // Browse all plugin directories.
  164. foreach ($dirs as $pluginDir) {
  165. $plugin = basename($pluginDir);
  166. $metaFile = $pluginDir . $plugin . '.' . self::$META_EXT;
  167. if (!is_file($metaFile) || !is_readable($metaFile)) {
  168. continue;
  169. }
  170. $metaData[$plugin] = parse_ini_file($metaFile);
  171. $metaData[$plugin]['order'] = array_search($plugin, $this->authorizedPlugins);
  172. // Read parameters and format them into an array.
  173. if (isset($metaData[$plugin]['parameters'])) {
  174. $params = explode(';', $metaData[$plugin]['parameters']);
  175. } else {
  176. $params = array();
  177. }
  178. $metaData[$plugin]['parameters'] = array();
  179. foreach ($params as $param) {
  180. if (empty($param)) {
  181. continue;
  182. }
  183. $metaData[$plugin]['parameters'][$param] = '';
  184. }
  185. }
  186. return $metaData;
  187. }
  188. }
  189. /**
  190. * Class PluginFileNotFoundException
  191. *
  192. * Raise when plugin files can't be found.
  193. */
  194. class PluginFileNotFoundException extends Exception
  195. {
  196. /**
  197. * Construct exception with plugin name.
  198. * Generate message.
  199. *
  200. * @param string $pluginName name of the plugin not found
  201. */
  202. public function __construct($pluginName)
  203. {
  204. $this->message = 'Plugin "'. $pluginName .'" files not found.';
  205. }
  206. }