]> git.mxchange.org Git - friendica.git/blob - src/Core/Addon.php
moving boot::check_addons to Friendica\Core\Addon::check
[friendica.git] / src / Core / Addon.php
1 <?php
2 /**
3  * @file src/Core/Addon.php
4  */
5 namespace Friendica\Core;
6
7 use Friendica\App;
8 use Friendica\BaseObject;
9 use Friendica\Database\DBA;
10
11 require_once 'include/dba.php';
12
13 /**
14  * Some functions to handle addons
15  */
16 class Addon extends BaseObject
17 {
18         /**
19          * @brief Synchronise addons:
20          *
21          * system.addon contains a comma-separated list of names
22          * of addons which are used on this system.
23          * Go through the database list of already installed addons, and if we have
24          * an entry, but it isn't in the config list, call the uninstall procedure
25          * and mark it uninstalled in the database (for now we'll remove it).
26          * Then go through the config list and if we have a addon that isn't installed,
27          * call the install procedure and add it to the database.
28          *
29          */
30         public static function check()
31         {
32                 $a = self::getApp();
33
34                 $r = q("SELECT * FROM `addon` WHERE `installed` = 1");
35                 if (DBA::isResult($r)) {
36                         $installed = $r;
37                 } else {
38                         $installed = [];
39                 }
40
41                 $addons = Config::get('system', 'addon');
42                 $addons_arr = [];
43
44                 if ($addons) {
45                         $addons_arr = explode(',', str_replace(' ', '', $addons));
46                 }
47
48                 $a->addons = $addons_arr;
49
50                 $installed_arr = [];
51
52                 if (count($installed)) {
53                         foreach ($installed as $i) {
54                                 if (!in_array($i['name'], $addons_arr)) {
55                                         self::uninstall($i['name']);
56                                 } else {
57                                         $installed_arr[] = $i['name'];
58                                 }
59                         }
60                 }
61
62                 if (count($addons_arr)) {
63                         foreach ($addons_arr as $p) {
64                                 if (!in_array($p, $installed_arr)) {
65                                         self::install($p);
66                                 }
67                         }
68                 }
69
70                 self::loadHooks();
71
72                 return;
73         }
74
75         /**
76          * @brief uninstalls an addon.
77          *
78          * @param string $addon name of the addon
79          * @return boolean
80          */
81         public static function uninstall($addon)
82         {
83                 logger("Addons: uninstalling " . $addon);
84                 DBA::delete('addon', ['name' => $addon]);
85
86                 @include_once('addon/' . $addon . '/' . $addon . '.php');
87                 if (function_exists($addon . '_uninstall')) {
88                         $func = $addon . '_uninstall';
89                         $func();
90                 }
91         }
92
93         /**
94          * @brief installs an addon.
95          *
96          * @param string $addon name of the addon
97          * @return bool
98          */
99         public static function install($addon)
100         {
101                 // silently fail if addon was removed
102
103                 if (!file_exists('addon/' . $addon . '/' . $addon . '.php')) {
104                         return false;
105                 }
106                 logger("Addons: installing " . $addon);
107                 $t = @filemtime('addon/' . $addon . '/' . $addon . '.php');
108                 @include_once('addon/' . $addon . '/' . $addon . '.php');
109                 if (function_exists($addon . '_install')) {
110                         $func = $addon . '_install';
111                         $func();
112
113                         $addon_admin = (function_exists($addon."_addon_admin") ? 1 : 0);
114
115                         DBA::insert('addon', ['name' => $addon, 'installed' => true,
116                                                 'timestamp' => $t, 'plugin_admin' => $addon_admin]);
117
118                         // we can add the following with the previous SQL
119                         // once most site tables have been updated.
120                         // This way the system won't fall over dead during the update.
121
122                         if (file_exists('addon/' . $addon . '/.hidden')) {
123                                 DBA::update('addon', ['hidden' => true], ['name' => $addon]);
124                         }
125                         return true;
126                 } else {
127                         logger("Addons: FAILED installing " . $addon);
128                         return false;
129                 }
130         }
131
132         /**
133          * reload all updated addons
134          */
135         public static function reload()
136         {
137                 $addons = Config::get('system', 'addon');
138                 if (strlen($addons)) {
139                         $r = DBA::select('addon', [], ['installed' => 1]);
140                         if (DBA::isResult($r)) {
141                                 $installed = DBA::toArray($r);
142                         } else {
143                                 $installed = [];
144                         }
145
146                         $addon_list = explode(',', $addons);
147
148                         if (count($addon_list)) {
149                                 foreach ($addon_list as $addon) {
150                                         $addon = trim($addon);
151                                         $fname = 'addon/' . $addon . '/' . $addon . '.php';
152
153                                         if (file_exists($fname)) {
154                                                 $t = @filemtime($fname);
155                                                 foreach ($installed as $i) {
156                                                         if (($i['name'] == $addon) && ($i['timestamp'] != $t)) {
157                                                                 logger('Reloading addon: ' . $i['name']);
158                                                                 @include_once($fname);
159
160                                                                 if (function_exists($addon . '_uninstall')) {
161                                                                         $func = $addon . '_uninstall';
162                                                                         $func();
163                                                                 }
164                                                                 if (function_exists($addon . '_install')) {
165                                                                         $func = $addon . '_install';
166                                                                         $func();
167                                                                 }
168                                                                 DBA::update('addon', ['timestamp' => $t], ['id' => $i['id']]);
169                                                         }
170                                                 }
171                                         }
172                                 }
173                         }
174                 }
175         }
176
177         /**
178          * @brief check if addon is enabled
179          *
180          * @param string $addon
181          * @return boolean
182          */
183         public static function isEnabled($addon)
184         {
185                 return DBA::exists('addon', ['installed' => true, 'name' => $addon]);
186         }
187
188
189         /**
190          * @brief registers a hook.
191          *
192          * @param string $hook the name of the hook
193          * @param string $file the name of the file that hooks into
194          * @param string $function the name of the function that the hook will call
195          * @param int $priority A priority (defaults to 0)
196          * @return mixed|bool
197          */
198         public static function registerHook($hook, $file, $function, $priority = 0)
199         {
200                 $file = str_replace(self::getApp()->get_basepath() . DIRECTORY_SEPARATOR, '', $file);
201
202                 $condition = ['hook' => $hook, 'file' => $file, 'function' => $function];
203                 $exists = DBA::exists('hook', $condition);
204                 if ($exists) {
205                         return true;
206                 }
207
208                 $r = DBA::insert('hook', ['hook' => $hook, 'file' => $file, 'function' => $function, 'priority' => $priority]);
209
210                 return $r;
211         }
212
213         /**
214          * @brief unregisters a hook.
215          *
216          * @param string $hook the name of the hook
217          * @param string $file the name of the file that hooks into
218          * @param string $function the name of the function that the hook called
219          * @return array
220          */
221         public static function unregisterHook($hook, $file, $function)
222         {
223                 $relative_file = str_replace(self::getApp()->get_basepath() . DIRECTORY_SEPARATOR, '', $file);
224
225                 // This here is only needed for fixing a problem that existed on the develop branch
226                 $condition = ['hook' => $hook, 'file' => $file, 'function' => $function];
227                 DBA::delete('hook', $condition);
228
229                 $condition = ['hook' => $hook, 'file' => $relative_file, 'function' => $function];
230                 $r = DBA::delete('hook', $condition);
231                 return $r;
232         }
233
234         /**
235          * Load hooks
236          */
237         public static function loadHooks()
238         {
239                 $a = self::getApp();
240                 $a->hooks = [];
241                 $r = DBA::select('hook', ['hook', 'file', 'function'], [], ['order' => ['priority' => 'desc', 'file']]);
242
243                 while ($rr = DBA::fetch($r)) {
244                         if (! array_key_exists($rr['hook'], $a->hooks)) {
245                                 $a->hooks[$rr['hook']] = [];
246                         }
247                         $a->hooks[$rr['hook']][] = [$rr['file'],$rr['function']];
248                 }
249                 DBA::close($r);
250         }
251
252         /**
253          * @brief Forks a hook.
254          *
255          * Use this function when you want to fork a hook via the worker.
256          *
257          * @param string $name of the hook to call
258          * @param string|array $data to transmit to the callback handler
259          */
260         public static function forkHooks($priority, $name, $data = null)
261         {
262                 $a = self::getApp();
263
264                 if (is_array($a->hooks) && array_key_exists($name, $a->hooks)) {
265                         foreach ($a->hooks[$name] as $hook) {
266                                 Worker::add($priority, 'ForkHook', $name, $hook, $data);
267                         }
268                 }
269         }
270
271         /**
272          * @brief Calls a hook.
273          *
274          * Use this function when you want to be able to allow a hook to manipulate
275          * the provided data.
276          *
277          * @param string $name of the hook to call
278          * @param string|array &$data to transmit to the callback handler
279          */
280         public static function callHooks($name, &$data = null)
281         {
282                 $a = self::getApp();
283
284                 if (is_array($a->hooks) && array_key_exists($name, $a->hooks)) {
285                         foreach ($a->hooks[$name] as $hook) {
286                                 self::callSingleHook($a, $name, $hook, $data);
287                         }
288                 }
289         }
290
291         /**
292          * @brief Calls a single hook.
293          *
294          * @param App $a
295          * @param string         $name of the hook to call
296          * @param array          $hook Hook data
297          * @param string|array   &$data to transmit to the callback handler
298          */
299         public static function callSingleHook(App $a, $name, $hook, &$data = null)
300         {
301                 // Don't run a theme's hook if the user isn't using the theme
302                 if (strpos($hook[0], 'view/theme/') !== false && strpos($hook[0], 'view/theme/' . $a->getCurrentTheme()) === false) {
303                         return;
304                 }
305
306                 @include_once($hook[0]);
307                 if (function_exists($hook[1])) {
308                         $func = $hook[1];
309                         $func($a, $data);
310                 } else {
311                         // remove orphan hooks
312                         $condition = ['hook' => $name, 'file' => $hook[0], 'function' => $hook[1]];
313                         DBA::delete('hook', $condition, ['cascade' => false]);
314                 }
315         }
316
317         /**
318          * check if an app_menu hook exist for addon $name.
319          * Return true if the addon is an app
320          */
321         public static function isApp($name)
322         {
323                 $a = self::getApp();
324
325                 if (is_array($a->hooks) && (array_key_exists('app_menu', $a->hooks))) {
326                         foreach ($a->hooks['app_menu'] as $hook) {
327                                 if ($hook[0] == 'addon/'.$name.'/'.$name.'.php') {
328                                         return true;
329                                 }
330                         }
331                 }
332
333                 return false;
334         }
335
336         /**
337          * @brief Parse addon comment in search of addon infos.
338          *
339          * like
340          * \code
341          *   * Name: addon
342          *   * Description: An addon which plugs in
343          * . * Version: 1.2.3
344          *   * Author: John <profile url>
345          *   * Author: Jane <email>
346          *   * Maintainer: Jess <email>
347          *   *
348          *   *\endcode
349          * @param string $addon the name of the addon
350          * @return array with the addon information
351          */
352         public static function getInfo($addon)
353         {
354                 $a = self::getApp();
355
356                 $info = [
357                         'name' => $addon,
358                         'description' => "",
359                         'author' => [],
360                         'maintainer' => [],
361                         'version' => "",
362                         'status' => ""
363                 ];
364
365                 if (!is_file("addon/$addon/$addon.php")) {
366                         return $info;
367                 }
368
369                 $stamp1 = microtime(true);
370                 $f = file_get_contents("addon/$addon/$addon.php");
371                 $a->save_timestamp($stamp1, "file");
372
373                 $r = preg_match("|/\*.*\*/|msU", $f, $m);
374
375                 if ($r) {
376                         $ll = explode("\n", $m[0]);
377                         foreach ($ll as $l) {
378                                 $l = trim($l, "\t\n\r */");
379                                 if ($l != "") {
380                                         $addon_info = array_map("trim", explode(":", $l, 2));
381                                         if (count($addon_info) < 2) {
382                                                 continue;
383                                         }
384
385                                         list($type, $v) = $addon_info;
386                                         $type = strtolower($type);
387                                         if ($type == "author" || $type == "maintainer") {
388                                                 $r = preg_match("|([^<]+)<([^>]+)>|", $v, $m);
389                                                 if ($r) {
390                                                         $info[$type][] = ['name' => $m[1], 'link' => $m[2]];
391                                                 } else {
392                                                         $info[$type][] = ['name' => $v];
393                                                 }
394                                         } else {
395                                                 if (array_key_exists($type, $info)) {
396                                                         $info[$type] = $v;
397                                                 }
398                                         }
399                                 }
400                         }
401                 }
402                 return $info;
403         }
404 }