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