]> git.mxchange.org Git - friendica.git/blob - src/Core/Addon.php
Remove confirm template obsolete uses (except for contacts)
[friendica.git] / src / Core / Addon.php
1 <?php
2 /**
3  * @copyright Copyright (C) 2020, Friendica
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\Database\DBA;
25 use Friendica\DI;
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                 $addonsAdminStmt = DBA::select('addon', ['name'], ['plugin_admin' => 1], ['order' => ['name']]);
88                 while ($addon = DBA::fetch($addonsAdminStmt)) {
89                         $addons_admin[$addon['name']] = [
90                                 'url' => 'admin/addons/' . $addon['name'],
91                                 'name' => $addon['name'],
92                                 'class' => 'addon'
93                         ];
94                 }
95                 DBA::close($addonsAdminStmt);
96
97                 return $addons_admin;
98         }
99
100
101         /**
102          * Synchronize addons:
103          *
104          * system.addon contains a comma-separated list of names
105          * of addons which are used on this system.
106          * Go through the database list of already installed addons, and if we have
107          * an entry, but it isn't in the config list, call the uninstall procedure
108          * and mark it uninstalled in the database (for now we'll remove it).
109          * Then go through the config list and if we have a addon that isn't installed,
110          * call the install procedure and add it to the database.
111          *
112          */
113         public static function loadAddons()
114         {
115                 $installed_addons = DBA::selectToArray('addon', ['name'], ['installed' => true]);
116                 self::$addons = array_column($installed_addons, 'name');
117         }
118
119         /**
120          * uninstalls an addon.
121          *
122          * @param string $addon name of the addon
123          * @return void
124          * @throws \Exception
125          */
126         public static function uninstall($addon)
127         {
128                 $addon = Strings::sanitizeFilePathItem($addon);
129
130                 Logger::notice("Addon {addon}: {action}", ['action' => 'uninstall', 'addon' => $addon]);
131                 DBA::delete('addon', ['name' => $addon]);
132
133                 @include_once('addon/' . $addon . '/' . $addon . '.php');
134                 if (function_exists($addon . '_uninstall')) {
135                         $func = $addon . '_uninstall';
136                         $func();
137                 }
138
139                 Hook::delete(['file' => 'addon/' . $addon . '/' . $addon . '.php']);
140
141                 unset(self::$addons[array_search($addon, self::$addons)]);
142         }
143
144         /**
145          * installs an addon.
146          *
147          * @param string $addon name of the addon
148          * @return bool
149          * @throws \Exception
150          */
151         public static function install($addon)
152         {
153                 $addon = Strings::sanitizeFilePathItem($addon);
154
155                 // silently fail if addon was removed of if $addon is funky
156                 if (!file_exists('addon/' . $addon . '/' . $addon . '.php')) {
157                         return false;
158                 }
159
160                 Logger::notice("Addon {addon}: {action}", ['action' => 'install', 'addon' => $addon]);
161                 $t = @filemtime('addon/' . $addon . '/' . $addon . '.php');
162                 @include_once('addon/' . $addon . '/' . $addon . '.php');
163                 if (function_exists($addon . '_install')) {
164                         $func = $addon . '_install';
165                         $func(DI::app());
166
167                         $addon_admin = (function_exists($addon . "_addon_admin") ? 1 : 0);
168
169                         DBA::insert('addon', ['name' => $addon, 'installed' => true,
170                                 'timestamp' => $t, 'plugin_admin' => $addon_admin]);
171
172                         // we can add the following with the previous SQL
173                         // once most site tables have been updated.
174                         // This way the system won't fall over dead during the update.
175
176                         if (file_exists('addon/' . $addon . '/.hidden')) {
177                                 DBA::update('addon', ['hidden' => true], ['name' => $addon]);
178                         }
179
180                         if (!self::isEnabled($addon)) {
181                                 self::$addons[] = $addon;
182                         }
183
184                         return true;
185                 } else {
186                         Logger::error("Addon {addon}: {action} failed", ['action' => 'install', 'addon' => $addon]);
187                         return false;
188                 }
189         }
190
191         /**
192          * reload all updated addons
193          */
194         public static function reload()
195         {
196                 $addons = DBA::selectToArray('addon', [], ['installed' => true]);
197
198                 foreach ($addons as $addon) {
199                         $addonname = Strings::sanitizeFilePathItem(trim($addon['name']));
200                         $fname = 'addon/' . $addonname . '/' . $addonname . '.php';
201                         $t = @filemtime($fname);
202                         if (!file_exists($fname) || ($addon['timestamp'] == $t)) {
203                                 continue;
204                         }
205
206                         Logger::notice("Addon {addon}: {action}", ['action' => 'reload', 'addon' => $addon['name']]);
207
208                         self::uninstall($fname);
209                         self::install($fname);
210                 }
211         }
212
213         /**
214          * Parse addon comment in search of addon infos.
215          *
216          * like
217          * \code
218          *   * Name: addon
219          *   * Description: An addon which plugs in
220          * . * Version: 1.2.3
221          *   * Author: John <profile url>
222          *   * Author: Jane <email>
223          *   * Maintainer: Jess <email>
224          *   *
225          *   *\endcode
226          * @param string $addon the name of the addon
227          * @return array with the addon information
228          * @throws \Exception
229          */
230         public static function getInfo($addon)
231         {
232                 $addon = Strings::sanitizeFilePathItem($addon);
233
234                 $info = [
235                         'name' => $addon,
236                         'description' => "",
237                         'author' => [],
238                         'maintainer' => [],
239                         'version' => "",
240                         'status' => ""
241                 ];
242
243                 if (!is_file("addon/$addon/$addon.php")) {
244                         return $info;
245                 }
246
247                 $stamp1 = microtime(true);
248                 $f = file_get_contents("addon/$addon/$addon.php");
249                 DI::profiler()->saveTimestamp($stamp1, "file");
250
251                 $r = preg_match("|/\*.*\*/|msU", $f, $m);
252
253                 if ($r) {
254                         $ll = explode("\n", $m[0]);
255                         foreach ($ll as $l) {
256                                 $l = trim($l, "\t\n\r */");
257                                 if ($l != "") {
258                                         $addon_info = array_map("trim", explode(":", $l, 2));
259                                         if (count($addon_info) < 2) {
260                                                 continue;
261                                         }
262
263                                         list($type, $v) = $addon_info;
264                                         $type = strtolower($type);
265                                         if ($type == "author" || $type == "maintainer") {
266                                                 $r = preg_match("|([^<]+)<([^>]+)>|", $v, $m);
267                                                 if ($r) {
268                                                         $info[$type][] = ['name' => $m[1], 'link' => $m[2]];
269                                                 } else {
270                                                         $info[$type][] = ['name' => $v];
271                                                 }
272                                         } else {
273                                                 if (array_key_exists($type, $info)) {
274                                                         $info[$type] = $v;
275                                                 }
276                                         }
277                                 }
278                         }
279                 }
280                 return $info;
281         }
282
283         /**
284          * Checks if the provided addon is enabled
285          *
286          * @param string $addon
287          * @return boolean
288          */
289         public static function isEnabled($addon)
290         {
291                 return in_array($addon, self::$addons);
292         }
293
294         /**
295          * Returns a list of the enabled addon names
296          *
297          * @return array
298          */
299         public static function getEnabledList()
300         {
301                 return self::$addons;
302         }
303
304         /**
305          * Returns the list of non-hidden enabled addon names
306          *
307          * @return array
308          * @throws \Exception
309          */
310         public static function getVisibleList()
311         {
312                 $visible_addons = [];
313                 $stmt = DBA::select('addon', ['name'], ['hidden' => false, 'installed' => true]);
314                 if (DBA::isResult($stmt)) {
315                         foreach (DBA::toArray($stmt) as $addon) {
316                                 $visible_addons[] = $addon['name'];
317                         }
318                 }
319
320                 return $visible_addons;
321         }
322
323         /**
324          * Shim of Hook::register left for backward compatibility purpose.
325          *
326          * @see        Hook::register
327          * @deprecated since version 2018.12
328          * @param string $hook     the name of the hook
329          * @param string $file     the name of the file that hooks into
330          * @param string $function the name of the function that the hook will call
331          * @param int    $priority A priority (defaults to 0)
332          * @return mixed|bool
333          * @throws \Friendica\Network\HTTPException\InternalServerErrorException
334          */
335         public static function registerHook($hook, $file, $function, $priority = 0)
336         {
337                 return Hook::register($hook, $file, $function, $priority);
338         }
339
340         /**
341          * Shim of Hook::unregister left for backward compatibility purpose.
342          *
343          * @see        Hook::unregister
344          * @deprecated since version 2018.12
345          * @param string $hook     the name of the hook
346          * @param string $file     the name of the file that hooks into
347          * @param string $function the name of the function that the hook called
348          * @return boolean
349          * @throws \Friendica\Network\HTTPException\InternalServerErrorException
350          */
351         public static function unregisterHook($hook, $file, $function)
352         {
353                 return Hook::unregister($hook, $file, $function);
354         }
355
356         /**
357          * Shim of Hook::callAll left for backward-compatibility purpose.
358          *
359          * @see        Hook::callAll
360          * @deprecated since version 2018.12
361          * @param string        $name of the hook to call
362          * @param string|array &$data to transmit to the callback handler
363          * @throws \Friendica\Network\HTTPException\InternalServerErrorException
364          */
365         public static function callHooks($name, &$data = null)
366         {
367                 Hook::callAll($name, $data);
368         }
369 }