]> git.mxchange.org Git - friendica.git/blob - src/Core/Addon.php
b1c1ad2e606808c2fc3c8349bada54d241bed7d4
[friendica.git] / src / Core / Addon.php
1 <?php
2 /**
3  * @file src/Core/Addon.php
4  */
5
6 namespace Friendica\Core;
7
8 use Friendica\BaseObject;
9 use Friendica\Database\DBA;
10 use Friendica\DI;
11 use Friendica\Util\Strings;
12
13 /**
14  * Some functions to handle addons
15  */
16 class Addon extends BaseObject
17 {
18         /**
19          * The addon sub-directory
20          * @var string
21          */
22         const DIRECTORY = 'addon';
23
24         /**
25          * List of the names of enabled addons
26          *
27          * @var array
28          */
29         private static $addons = [];
30
31         /**
32          * Returns the list of available addons with their current status and info.
33          * This list is made from scanning the addon/ folder.
34          * Unsupported addons are excluded unless they already are enabled or system.show_unsupported_addon is set.
35          *
36          * @return array
37          * @throws \Exception
38          */
39         public static function getAvailableList()
40         {
41                 $addons = [];
42                 $files = glob('addon/*/');
43                 if (is_array($files)) {
44                         foreach ($files as $file) {
45                                 if (is_dir($file)) {
46                                         list($tmp, $addon) = array_map('trim', explode('/', $file));
47                                         $info = self::getInfo($addon);
48
49                                         if (Config::get('system', 'show_unsupported_addons')
50                                                 || strtolower($info['status']) != 'unsupported'
51                                                 || self::isEnabled($addon)
52                                         ) {
53                                                 $addons[] = [$addon, (self::isEnabled($addon) ? 'on' : 'off'), $info];
54                                         }
55                                 }
56                         }
57                 }
58
59                 return $addons;
60         }
61
62         /**
63          * Returns a list of addons that can be configured at the node level.
64          * The list is formatted for display in the admin panel aside.
65          *
66          * @return array
67          * @throws \Exception
68          */
69         public static function getAdminList()
70         {
71                 $addons_admin = [];
72                 $addonsAdminStmt = DBA::select('addon', ['name'], ['plugin_admin' => 1], ['order' => ['name']]);
73                 while ($addon = DBA::fetch($addonsAdminStmt)) {
74                         $addons_admin[$addon['name']] = [
75                                 'url' => 'admin/addons/' . $addon['name'],
76                                 'name' => $addon['name'],
77                                 'class' => 'addon'
78                         ];
79                 }
80                 DBA::close($addonsAdminStmt);
81
82                 return $addons_admin;
83         }
84
85
86         /**
87          * @brief Synchronize addons:
88          *
89          * system.addon contains a comma-separated list of names
90          * of addons which are used on this system.
91          * Go through the database list of already installed addons, and if we have
92          * an entry, but it isn't in the config list, call the uninstall procedure
93          * and mark it uninstalled in the database (for now we'll remove it).
94          * Then go through the config list and if we have a addon that isn't installed,
95          * call the install procedure and add it to the database.
96          *
97          */
98         public static function loadAddons()
99         {
100                 $installed_addons = [];
101
102                 $r = DBA::select('addon', [], ['installed' => 1]);
103                 if (DBA::isResult($r)) {
104                         $installed_addons = DBA::toArray($r);
105                 }
106
107                 $addons = Config::get('system', 'addon');
108                 $addons_arr = [];
109
110                 if ($addons) {
111                         $addons_arr = explode(',', str_replace(' ', '', $addons));
112                 }
113
114                 self::$addons = $addons_arr;
115
116                 $installed_arr = [];
117
118                 foreach ($installed_addons as $addon) {
119                         if (!self::isEnabled($addon['name'])) {
120                                 self::uninstall($addon['name']);
121                         } else {
122                                 $installed_arr[] = $addon['name'];
123                         }
124                 }
125
126                 foreach (self::$addons as $p) {
127                         if (!in_array($p, $installed_arr)) {
128                                 self::install($p);
129                         }
130                 }
131         }
132
133         /**
134          * @brief uninstalls an addon.
135          *
136          * @param string $addon name of the addon
137          * @return void
138          * @throws \Exception
139          */
140         public static function uninstall($addon)
141         {
142                 $addon = Strings::sanitizeFilePathItem($addon);
143
144                 Logger::notice("Addon {addon}: {action}", ['action' => 'uninstall', 'addon' => $addon]);
145                 DBA::delete('addon', ['name' => $addon]);
146
147                 @include_once('addon/' . $addon . '/' . $addon . '.php');
148                 if (function_exists($addon . '_uninstall')) {
149                         $func = $addon . '_uninstall';
150                         $func();
151                 }
152
153                 DBA::delete('hook', ['file' => 'addon/' . $addon . '/' . $addon . '.php']);
154
155                 unset(self::$addons[array_search($addon, self::$addons)]);
156
157                 Addon::saveEnabledList();
158         }
159
160         /**
161          * @brief installs an addon.
162          *
163          * @param string $addon name of the addon
164          * @return bool
165          * @throws \Exception
166          */
167         public static function install($addon)
168         {
169                 $addon = Strings::sanitizeFilePathItem($addon);
170
171                 // silently fail if addon was removed of if $addon is funky
172                 if (!file_exists('addon/' . $addon . '/' . $addon . '.php')) {
173                         return false;
174                 }
175
176                 Logger::notice("Addon {addon}: {action}", ['action' => 'install', 'addon' => $addon]);
177                 $t = @filemtime('addon/' . $addon . '/' . $addon . '.php');
178                 @include_once('addon/' . $addon . '/' . $addon . '.php');
179                 if (function_exists($addon . '_install')) {
180                         $func = $addon . '_install';
181                         $func(DI::app());
182
183                         $addon_admin = (function_exists($addon . "_addon_admin") ? 1 : 0);
184
185                         DBA::insert('addon', ['name' => $addon, 'installed' => true,
186                                 'timestamp' => $t, 'plugin_admin' => $addon_admin]);
187
188                         // we can add the following with the previous SQL
189                         // once most site tables have been updated.
190                         // This way the system won't fall over dead during the update.
191
192                         if (file_exists('addon/' . $addon . '/.hidden')) {
193                                 DBA::update('addon', ['hidden' => true], ['name' => $addon]);
194                         }
195
196                         if (!self::isEnabled($addon)) {
197                                 self::$addons[] = $addon;
198                         }
199
200                         Addon::saveEnabledList();
201
202                         return true;
203                 } else {
204                         Logger::error("Addon {addon}: {action} failed", ['action' => 'install', 'addon' => $addon]);
205                         return false;
206                 }
207         }
208
209         /**
210          * reload all updated addons
211          */
212         public static function reload()
213         {
214                 $addons = Config::get('system', 'addon');
215                 if (strlen($addons)) {
216                         $r = DBA::select('addon', [], ['installed' => 1]);
217                         if (DBA::isResult($r)) {
218                                 $installed = DBA::toArray($r);
219                         } else {
220                                 $installed = [];
221                         }
222
223                         $addon_list = explode(',', $addons);
224
225                         foreach ($addon_list as $addon) {
226                                 $addon = Strings::sanitizeFilePathItem(trim($addon));
227                                 $fname = 'addon/' . $addon . '/' . $addon . '.php';
228                                 if (file_exists($fname)) {
229                                         $t = @filemtime($fname);
230                                         foreach ($installed as $i) {
231                                                 if (($i['name'] == $addon) && ($i['timestamp'] != $t)) {
232
233                                                         Logger::notice("Addon {addon}: {action}", ['action' => 'reload', 'addon' => $i['name']]);
234                                                         @include_once($fname);
235
236                                                         if (function_exists($addon . '_uninstall')) {
237                                                                 $func = $addon . '_uninstall';
238                                                                 $func(DI::app());
239                                                         }
240                                                         if (function_exists($addon . '_install')) {
241                                                                 $func = $addon . '_install';
242                                                                 $func(DI::app());
243                                                         }
244                                                         DBA::update('addon', ['timestamp' => $t], ['id' => $i['id']]);
245                                                 }
246                                         }
247                                 }
248                         }
249                 }
250         }
251
252         /**
253          * @brief Parse addon comment in search of addon infos.
254          *
255          * like
256          * \code
257          *   * Name: addon
258          *   * Description: An addon which plugs in
259          * . * Version: 1.2.3
260          *   * Author: John <profile url>
261          *   * Author: Jane <email>
262          *   * Maintainer: Jess <email>
263          *   *
264          *   *\endcode
265          * @param string $addon the name of the addon
266          * @return array with the addon information
267          * @throws \Exception
268          */
269         public static function getInfo($addon)
270         {
271                 $a = DI::app();
272
273                 $addon = Strings::sanitizeFilePathItem($addon);
274
275                 $info = [
276                         'name' => $addon,
277                         'description' => "",
278                         'author' => [],
279                         'maintainer' => [],
280                         'version' => "",
281                         'status' => ""
282                 ];
283
284                 if (!is_file("addon/$addon/$addon.php")) {
285                         return $info;
286                 }
287
288                 $stamp1 = microtime(true);
289                 $f = file_get_contents("addon/$addon/$addon.php");
290                 $a->getProfiler()->saveTimestamp($stamp1, "file", System::callstack());
291
292                 $r = preg_match("|/\*.*\*/|msU", $f, $m);
293
294                 if ($r) {
295                         $ll = explode("\n", $m[0]);
296                         foreach ($ll as $l) {
297                                 $l = trim($l, "\t\n\r */");
298                                 if ($l != "") {
299                                         $addon_info = array_map("trim", explode(":", $l, 2));
300                                         if (count($addon_info) < 2) {
301                                                 continue;
302                                         }
303
304                                         list($type, $v) = $addon_info;
305                                         $type = strtolower($type);
306                                         if ($type == "author" || $type == "maintainer") {
307                                                 $r = preg_match("|([^<]+)<([^>]+)>|", $v, $m);
308                                                 if ($r) {
309                                                         $info[$type][] = ['name' => $m[1], 'link' => $m[2]];
310                                                 } else {
311                                                         $info[$type][] = ['name' => $v];
312                                                 }
313                                         } else {
314                                                 if (array_key_exists($type, $info)) {
315                                                         $info[$type] = $v;
316                                                 }
317                                         }
318                                 }
319                         }
320                 }
321                 return $info;
322         }
323
324         /**
325          * Checks if the provided addon is enabled
326          *
327          * @param string $addon
328          * @return boolean
329          */
330         public static function isEnabled($addon)
331         {
332                 return in_array($addon, self::$addons);
333         }
334
335         /**
336          * Returns a list of the enabled addon names
337          *
338          * @return array
339          */
340         public static function getEnabledList()
341         {
342                 return self::$addons;
343         }
344
345         /**
346          * Saves the current enabled addon list in the system.addon config key
347          *
348          * @return boolean
349          */
350         public static function saveEnabledList()
351         {
352                 return Config::set('system', 'addon', implode(',', self::$addons));
353         }
354
355         /**
356          * Returns the list of non-hidden enabled addon names
357          *
358          * @return array
359          * @throws \Exception
360          */
361         public static function getVisibleList()
362         {
363                 $visible_addons = [];
364                 $stmt = DBA::select('addon', ['name'], ['hidden' => false, 'installed' => true]);
365                 if (DBA::isResult($stmt)) {
366                         foreach (DBA::toArray($stmt) as $addon) {
367                                 $visible_addons[] = $addon['name'];
368                         }
369                 }
370
371                 return $visible_addons;
372         }
373
374         /**
375          * Shim of Hook::register left for backward compatibility purpose.
376          *
377          * @see        Hook::register
378          * @deprecated since version 2018.12
379          * @param string $hook     the name of the hook
380          * @param string $file     the name of the file that hooks into
381          * @param string $function the name of the function that the hook will call
382          * @param int    $priority A priority (defaults to 0)
383          * @return mixed|bool
384          * @throws \Friendica\Network\HTTPException\InternalServerErrorException
385          */
386         public static function registerHook($hook, $file, $function, $priority = 0)
387         {
388                 return Hook::register($hook, $file, $function, $priority);
389         }
390
391         /**
392          * Shim of Hook::unregister left for backward compatibility purpose.
393          *
394          * @see        Hook::unregister
395          * @deprecated since version 2018.12
396          * @param string $hook     the name of the hook
397          * @param string $file     the name of the file that hooks into
398          * @param string $function the name of the function that the hook called
399          * @return boolean
400          * @throws \Friendica\Network\HTTPException\InternalServerErrorException
401          */
402         public static function unregisterHook($hook, $file, $function)
403         {
404                 return Hook::unregister($hook, $file, $function);
405         }
406
407         /**
408          * Shim of Hook::callAll left for backward-compatibility purpose.
409          *
410          * @see        Hook::callAll
411          * @deprecated since version 2018.12
412          * @param string        $name of the hook to call
413          * @param string|array &$data to transmit to the callback handler
414          * @throws \Friendica\Network\HTTPException\InternalServerErrorException
415          */
416         public static function callHooks($name, &$data = null)
417         {
418                 Hook::callAll($name, $data);
419         }
420 }