]> git.mxchange.org Git - friendica.git/blob - src/Core/Addon.php
Merge pull request #7095 from annando/ap-connect
[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                 unset(self::$addons[array_search($addon, self::$addons)]);
153
154                 Addon::saveEnabledList();
155         }
156
157         /**
158          * @brief installs an addon.
159          *
160          * @param string $addon name of the addon
161          * @return bool
162          * @throws \Exception
163          */
164         public static function install($addon)
165         {
166                 $addon = Strings::sanitizeFilePathItem($addon);
167
168                 // silently fail if addon was removed of if $addon is funky
169                 if (!file_exists('addon/' . $addon . '/' . $addon . '.php')) {
170                         return false;
171                 }
172
173                 Logger::notice("Addon {addon}: {action}", ['action' => 'install', 'addon' => $addon]);
174                 $t = @filemtime('addon/' . $addon . '/' . $addon . '.php');
175                 @include_once('addon/' . $addon . '/' . $addon . '.php');
176                 if (function_exists($addon . '_install')) {
177                         $func = $addon . '_install';
178                         $func(self::getApp());
179
180                         $addon_admin = (function_exists($addon . "_addon_admin") ? 1 : 0);
181
182                         DBA::insert('addon', ['name' => $addon, 'installed' => true,
183                                 'timestamp' => $t, 'plugin_admin' => $addon_admin]);
184
185                         // we can add the following with the previous SQL
186                         // once most site tables have been updated.
187                         // This way the system won't fall over dead during the update.
188
189                         if (file_exists('addon/' . $addon . '/.hidden')) {
190                                 DBA::update('addon', ['hidden' => true], ['name' => $addon]);
191                         }
192
193                         if (!self::isEnabled($addon)) {
194                                 self::$addons[] = $addon;
195                         }
196
197                         Addon::saveEnabledList();
198
199                         return true;
200                 } else {
201                         Logger::error("Addon {addon}: {action} failed", ['action' => 'install', 'addon' => $addon]);
202                         return false;
203                 }
204         }
205
206         /**
207          * reload all updated addons
208          */
209         public static function reload()
210         {
211                 $addons = Config::get('system', 'addon');
212                 if (strlen($addons)) {
213                         $r = DBA::select('addon', [], ['installed' => 1]);
214                         if (DBA::isResult($r)) {
215                                 $installed = DBA::toArray($r);
216                         } else {
217                                 $installed = [];
218                         }
219
220                         $addon_list = explode(',', $addons);
221
222                         foreach ($addon_list as $addon) {
223                                 $addon = Strings::sanitizeFilePathItem(trim($addon));
224                                 $fname = 'addon/' . $addon . '/' . $addon . '.php';
225                                 if (file_exists($fname)) {
226                                         $t = @filemtime($fname);
227                                         foreach ($installed as $i) {
228                                                 if (($i['name'] == $addon) && ($i['timestamp'] != $t)) {
229
230                                                         Logger::notice("Addon {addon}: {action}", ['action' => 'reload', 'addon' => $i['name']]);
231                                                         @include_once($fname);
232
233                                                         if (function_exists($addon . '_uninstall')) {
234                                                                 $func = $addon . '_uninstall';
235                                                                 $func(self::getApp());
236                                                         }
237                                                         if (function_exists($addon . '_install')) {
238                                                                 $func = $addon . '_install';
239                                                                 $func(self::getApp());
240                                                         }
241                                                         DBA::update('addon', ['timestamp' => $t], ['id' => $i['id']]);
242                                                 }
243                                         }
244                                 }
245                         }
246                 }
247         }
248
249         /**
250          * @brief Parse addon comment in search of addon infos.
251          *
252          * like
253          * \code
254          *   * Name: addon
255          *   * Description: An addon which plugs in
256          * . * Version: 1.2.3
257          *   * Author: John <profile url>
258          *   * Author: Jane <email>
259          *   * Maintainer: Jess <email>
260          *   *
261          *   *\endcode
262          * @param string $addon the name of the addon
263          * @return array with the addon information
264          * @throws \Exception
265          */
266         public static function getInfo($addon)
267         {
268                 $a = self::getApp();
269
270                 $addon = Strings::sanitizeFilePathItem($addon);
271
272                 $info = [
273                         'name' => $addon,
274                         'description' => "",
275                         'author' => [],
276                         'maintainer' => [],
277                         'version' => "",
278                         'status' => ""
279                 ];
280
281                 if (!is_file("addon/$addon/$addon.php")) {
282                         return $info;
283                 }
284
285                 $stamp1 = microtime(true);
286                 $f = file_get_contents("addon/$addon/$addon.php");
287                 $a->getProfiler()->saveTimestamp($stamp1, "file", System::callstack());
288
289                 $r = preg_match("|/\*.*\*/|msU", $f, $m);
290
291                 if ($r) {
292                         $ll = explode("\n", $m[0]);
293                         foreach ($ll as $l) {
294                                 $l = trim($l, "\t\n\r */");
295                                 if ($l != "") {
296                                         $addon_info = array_map("trim", explode(":", $l, 2));
297                                         if (count($addon_info) < 2) {
298                                                 continue;
299                                         }
300
301                                         list($type, $v) = $addon_info;
302                                         $type = strtolower($type);
303                                         if ($type == "author" || $type == "maintainer") {
304                                                 $r = preg_match("|([^<]+)<([^>]+)>|", $v, $m);
305                                                 if ($r) {
306                                                         $info[$type][] = ['name' => $m[1], 'link' => $m[2]];
307                                                 } else {
308                                                         $info[$type][] = ['name' => $v];
309                                                 }
310                                         } else {
311                                                 if (array_key_exists($type, $info)) {
312                                                         $info[$type] = $v;
313                                                 }
314                                         }
315                                 }
316                         }
317                 }
318                 return $info;
319         }
320
321         /**
322          * Checks if the provided addon is enabled
323          *
324          * @param string $addon
325          * @return boolean
326          */
327         public static function isEnabled($addon)
328         {
329                 return in_array($addon, self::$addons);
330         }
331
332         /**
333          * Returns a list of the enabled addon names
334          *
335          * @return array
336          */
337         public static function getEnabledList()
338         {
339                 return self::$addons;
340         }
341
342         /**
343          * Saves the current enabled addon list in the system.addon config key
344          *
345          * @return boolean
346          */
347         public static function saveEnabledList()
348         {
349                 return Config::set('system', 'addon', implode(',', self::$addons));
350         }
351
352         /**
353          * Returns the list of non-hidden enabled addon names
354          *
355          * @return array
356          * @throws \Exception
357          */
358         public static function getVisibleList()
359         {
360                 $visible_addons = [];
361                 $stmt = DBA::select('addon', ['name'], ['hidden' => false, 'installed' => true]);
362                 if (DBA::isResult($stmt)) {
363                         foreach (DBA::toArray($stmt) as $addon) {
364                                 $visible_addons[] = $addon['name'];
365                         }
366                 }
367
368                 return $visible_addons;
369         }
370
371         /**
372          * Shim of Hook::register left for backward compatibility purpose.
373          *
374          * @see        Hook::register
375          * @deprecated since version 2018.12
376          * @param string $hook     the name of the hook
377          * @param string $file     the name of the file that hooks into
378          * @param string $function the name of the function that the hook will call
379          * @param int    $priority A priority (defaults to 0)
380          * @return mixed|bool
381          * @throws \Friendica\Network\HTTPException\InternalServerErrorException
382          */
383         public static function registerHook($hook, $file, $function, $priority = 0)
384         {
385                 return Hook::register($hook, $file, $function, $priority);
386         }
387
388         /**
389          * Shim of Hook::unregister left for backward compatibility purpose.
390          *
391          * @see        Hook::unregister
392          * @deprecated since version 2018.12
393          * @param string $hook     the name of the hook
394          * @param string $file     the name of the file that hooks into
395          * @param string $function the name of the function that the hook called
396          * @return boolean
397          * @throws \Friendica\Network\HTTPException\InternalServerErrorException
398          */
399         public static function unregisterHook($hook, $file, $function)
400         {
401                 return Hook::unregister($hook, $file, $function);
402         }
403
404         /**
405          * Shim of Hook::callAll left for backward-compatibility purpose.
406          *
407          * @see        Hook::callAll
408          * @deprecated since version 2018.12
409          * @param string        $name of the hook to call
410          * @param string|array &$data to transmit to the callback handler
411          * @throws \Friendica\Network\HTTPException\InternalServerErrorException
412          */
413         public static function callHooks($name, &$data = null)
414         {
415                 Hook::callAll($name, $data);
416         }
417 }