]> git.mxchange.org Git - friendica.git/blob - src/Core/Addon.php
46ba0eec2b2347efd39a80f8a431de205485799f
[friendica.git] / src / Core / Addon.php
1 <?php
2 /**
3  * @copyright Copyright (C) 2010-2023, the Friendica project
4  *
5  * @license GNU AGPL version 3 or any later version
6  *
7  * This program is free software: you can redistribute it and/or modify
8  * it under the terms of the GNU Affero General Public License as
9  * published by the Free Software Foundation, either version 3 of the
10  * License, or (at your option) any later version.
11  *
12  * This program is distributed in the hope that it will be useful,
13  * but WITHOUT ANY WARRANTY; without even the implied warranty of
14  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
15  * GNU Affero General Public License for more details.
16  *
17  * You should have received a copy of the GNU Affero General Public License
18  * along with this program.  If not, see <https://www.gnu.org/licenses/>.
19  *
20  */
21
22 namespace Friendica\Core;
23
24 use Friendica\DI;
25 use Friendica\Model\Contact;
26 use Friendica\Util\Strings;
27
28 /**
29  * Some functions to handle addons
30  */
31 class Addon
32 {
33         /**
34          * The addon sub-directory
35          * @var string
36          */
37         const DIRECTORY = 'addon';
38
39         /**
40          * List of the names of enabled addons
41          *
42          * @var array
43          */
44         private static $addons = [];
45
46         /**
47          * Returns the list of available addons with their current status and info.
48          * This list is made from scanning the addon/ folder.
49          * Unsupported addons are excluded unless they already are enabled or system.show_unsupported_addon is set.
50          *
51          * @return array
52          * @throws \Exception
53          */
54         public static function getAvailableList()
55         {
56                 $addons = [];
57                 $files = glob('addon/*/');
58                 if (is_array($files)) {
59                         foreach ($files as $file) {
60                                 if (is_dir($file)) {
61                                         list($tmp, $addon) = array_map('trim', explode('/', $file));
62                                         $info = self::getInfo($addon);
63
64                                         if (DI::config()->get('system', 'show_unsupported_addons')
65                                                 || strtolower($info['status']) != 'unsupported'
66                                                 || self::isEnabled($addon)
67                                         ) {
68                                                 $addons[] = [$addon, (self::isEnabled($addon) ? 'on' : 'off'), $info];
69                                         }
70                                 }
71                         }
72                 }
73
74                 return $addons;
75         }
76
77         /**
78          * Returns a list of addons that can be configured at the node level.
79          * The list is formatted for display in the admin panel aside.
80          *
81          * @return array
82          * @throws \Exception
83          */
84         public static function getAdminList()
85         {
86                 $addons_admin = [];
87                 $addons = DI::config()->get('addons');
88
89                 if (empty($addons)) {
90                         return $addons_admin;
91                 }
92
93                 ksort($addons);
94                 foreach ($addons as $name => $data) {
95                         if (empty($data['admin'])) {
96                                 continue;
97                         }
98
99                         $addons_admin[$name] = [
100                                 'url' => 'admin/addons/' . $name,
101                                 'name' => $name,
102                                 'class' => 'addon'
103                         ];
104                 }
105
106                 return $addons_admin;
107         }
108
109
110         /**
111          * Synchronize addons:
112          *
113          * system.addon contains a comma-separated list of names
114          * of addons which are used on this system.
115          * Go through the database list of already installed addons, and if we have
116          * an entry, but it isn't in the config list, call the uninstall procedure
117          * and mark it uninstalled in the database (for now we'll remove it).
118          * Then go through the config list and if we have a addon that isn't installed,
119          * call the install procedure and add it to the database.
120          *
121          */
122         public static function loadAddons()
123         {
124                 self::$addons = array_keys(DI::config()->get('addons') ?? []);
125         }
126
127         /**
128          * uninstalls an addon.
129          *
130          * @param string $addon name of the addon
131          * @return void
132          * @throws \Exception
133          */
134         public static function uninstall(string $addon)
135         {
136                 $addon = Strings::sanitizeFilePathItem($addon);
137
138                 Logger::debug("Addon {addon}: {action}", ['action' => 'uninstall', 'addon' => $addon]);
139                 DI::config()->delete('addons', $addon);
140
141                 @include_once('addon/' . $addon . '/' . $addon . '.php');
142                 if (function_exists($addon . '_uninstall')) {
143                         $func = $addon . '_uninstall';
144                         $func();
145                 }
146
147                 Hook::delete(['file' => 'addon/' . $addon . '/' . $addon . '.php']);
148
149                 unset(self::$addons[array_search($addon, self::$addons)]);
150         }
151
152         /**
153          * installs an addon.
154          *
155          * @param string $addon name of the addon
156          * @return bool
157          * @throws \Exception
158          */
159         public static function install(string $addon): bool
160         {
161                 $addon = Strings::sanitizeFilePathItem($addon);
162
163                 $addon_file_path = 'addon/' . $addon . '/' . $addon . '.php';
164
165                 // silently fail if addon was removed of if $addon is funky
166                 if (!file_exists($addon_file_path)) {
167                         return false;
168                 }
169
170                 Logger::debug("Addon {addon}: {action}", ['action' => 'install', 'addon' => $addon]);
171                 $t = @filemtime($addon_file_path);
172                 @include_once($addon_file_path);
173                 if (function_exists($addon . '_install')) {
174                         $func = $addon . '_install';
175                         $func(DI::app());
176                 }
177
178                 DI::config()->set('addons', $addon, [
179                         'last_update' => $t,
180                         'admin' => function_exists($addon . '_addon_admin'),
181                 ]);
182
183                 if (!self::isEnabled($addon)) {
184                         self::$addons[] = $addon;
185                 }
186
187                 return true;
188         }
189
190         /**
191          * reload all updated addons
192          *
193          * @return void
194          */
195         public static function reload()
196         {
197                 $addons = DI::config()->get('addons');
198
199                 if (empty($addons)) {
200                         return;
201                 }
202
203                 foreach ($addons as $name => $data) {
204                         $addonname = Strings::sanitizeFilePathItem(trim($name));
205                         $addon_file_path = 'addon/' . $addonname . '/' . $addonname . '.php';
206                         if (file_exists($addon_file_path) && $data['last_update'] == filemtime($addon_file_path)) {
207                                 // Addon unmodified, skipping
208                                 continue;
209                         }
210
211                         Logger::debug("Addon {addon}: {action}", ['action' => 'reload', 'addon' => $name]);
212
213                         self::uninstall($name);
214                         self::install($name);
215                 }
216         }
217
218         /**
219          * Parse addon comment in search of addon infos.
220          *
221          * like
222          * \code
223          *   * Name: addon
224          *   * Description: An addon which plugs in
225          * . * Version: 1.2.3
226          *   * Author: John <profile url>
227          *   * Author: Jane <email>
228          *   * Maintainer: Jess <email>
229          *   *
230          *   *\endcode
231          * @param string $addon the name of the addon
232          * @return array with the addon information
233          * @throws \Exception
234          */
235         public static function getInfo(string $addon): array
236         {
237                 $addon = Strings::sanitizeFilePathItem($addon);
238
239                 $info = [
240                         'name' => $addon,
241                         'description' => "",
242                         'author' => [],
243                         'maintainer' => [],
244                         'version' => "",
245                         'status' => ""
246                 ];
247
248                 if (!is_file("addon/$addon/$addon.php")) {
249                         return $info;
250                 }
251
252                 DI::profiler()->startRecording('file');
253                 $f = file_get_contents("addon/$addon/$addon.php");
254                 DI::profiler()->stopRecording();
255
256                 $r = preg_match("|/\*.*\*/|msU", $f, $m);
257
258                 if ($r) {
259                         $ll = explode("\n", $m[0]);
260                         foreach ($ll as $l) {
261                                 $l = trim($l, "\t\n\r */");
262                                 if ($l != "") {
263                                         $addon_info = array_map("trim", explode(":", $l, 2));
264                                         if (count($addon_info) < 2) {
265                                                 continue;
266                                         }
267
268                                         list($type, $v) = $addon_info;
269                                         $type = strtolower($type);
270                                         if ($type == "author" || $type == "maintainer") {
271                                                 $r = preg_match("|([^<]+)<([^>]+)>|", $v, $m);
272                                                 if ($r) {
273                                                         if (!empty($m[2]) && empty(parse_url($m[2], PHP_URL_SCHEME))) {
274                                                                 $contact = Contact::getByURL($m[2], false);
275                                                                 if (!empty($contact['url'])) {
276                                                                         $m[2] = $contact['url'];
277                                                                 }
278                                                         }
279                                                         $info[$type][] = ['name' => $m[1], 'link' => $m[2]];
280                                                 } else {
281                                                         $info[$type][] = ['name' => $v];
282                                                 }
283                                         } else {
284                                                 if (array_key_exists($type, $info)) {
285                                                         $info[$type] = $v;
286                                                 }
287                                         }
288                                 }
289                         }
290                 }
291                 return $info;
292         }
293
294         /**
295          * Checks if the provided addon is enabled
296          *
297          * @param string $addon
298          * @return boolean
299          */
300         public static function isEnabled(string $addon): bool
301         {
302                 return in_array($addon, self::$addons);
303         }
304
305         /**
306          * Returns a list of the enabled addon names
307          *
308          * @return array
309          */
310         public static function getEnabledList(): array
311         {
312                 return self::$addons;
313         }
314
315         /**
316          * Returns the list of non-hidden enabled addon names
317          *
318          * @return array
319          * @throws \Exception
320          */
321         public static function getVisibleList(): array
322         {
323                 $visible_addons = [];
324                 $addons = DI::config()->get('addons');
325
326                 if (empty($addons)) {
327                         return $visible_addons;
328                 }
329
330                 foreach ($addons as $name => $data) {
331                         $visible_addons[] = $name;
332                 }
333
334                 return $visible_addons;
335         }
336 }