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