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