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