3 * @file src/Core/Addon.php
6 namespace Friendica\Core;
8 use Friendica\BaseObject;
9 use Friendica\Database\DBA;
10 use Friendica\Util\Strings;
13 * Some functions to handle addons
15 class Addon extends BaseObject
18 * The addon sub-directory
21 const DIRECTORY = 'addon';
24 * List of the names of enabled addons
28 private static $addons = [];
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.
38 public static function getAvailableList()
41 $files = glob('addon/*/');
42 if (is_array($files)) {
43 foreach ($files as $file) {
45 list($tmp, $addon) = array_map('trim', explode('/', $file));
46 $info = self::getInfo($addon);
48 if (Config::get('system', 'show_unsupported_addons')
49 || strtolower($info['status']) != 'unsupported'
50 || self::isEnabled($addon)
52 $addons[] = [$addon, (self::isEnabled($addon) ? 'on' : 'off'), $info];
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.
68 public static function getAdminList()
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'],
79 DBA::close($addonsAdminStmt);
86 * @brief Synchronize addons:
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.
97 public static function loadAddons()
99 $installed_addons = [];
101 $r = DBA::select('addon', [], ['installed' => 1]);
102 if (DBA::isResult($r)) {
103 $installed_addons = DBA::toArray($r);
106 $addons = Config::get('system', 'addon');
110 $addons_arr = explode(',', str_replace(' ', '', $addons));
113 self::$addons = $addons_arr;
117 foreach ($installed_addons as $addon) {
118 if (!self::isEnabled($addon['name'])) {
119 self::uninstall($addon['name']);
121 $installed_arr[] = $addon['name'];
125 foreach (self::$addons as $p) {
126 if (!in_array($p, $installed_arr)) {
133 * @brief uninstalls an addon.
135 * @param string $addon name of the addon
139 public static function uninstall($addon)
141 $addon = Strings::sanitizeFilePathItem($addon);
143 Logger::notice("Addon {addon}: {action}", ['action' => 'uninstall', 'addon' => $addon]);
144 DBA::delete('addon', ['name' => $addon]);
146 @include_once('addon/' . $addon . '/' . $addon . '.php');
147 if (function_exists($addon . '_uninstall')) {
148 $func = $addon . '_uninstall';
152 unset(self::$addons[array_search($addon, self::$addons)]);
154 Addon::saveEnabledList();
158 * @brief installs an addon.
160 * @param string $addon name of the addon
164 public static function install($addon)
166 $addon = Strings::sanitizeFilePathItem($addon);
168 // silently fail if addon was removed of if $addon is funky
169 if (!file_exists('addon/' . $addon . '/' . $addon . '.php')) {
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());
180 $addon_admin = (function_exists($addon . "_addon_admin") ? 1 : 0);
182 DBA::insert('addon', ['name' => $addon, 'installed' => true,
183 'timestamp' => $t, 'plugin_admin' => $addon_admin]);
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.
189 if (file_exists('addon/' . $addon . '/.hidden')) {
190 DBA::update('addon', ['hidden' => true], ['name' => $addon]);
193 if (!self::isEnabled($addon)) {
194 self::$addons[] = $addon;
197 Addon::saveEnabledList();
201 Logger::error("Addon {addon}: {action} failed", ['action' => 'install', 'addon' => $addon]);
207 * reload all updated addons
209 public static function reload()
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);
220 $addon_list = explode(',', $addons);
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)) {
230 Logger::notice("Addon {addon}: {action}", ['action' => 'reload', 'addon' => $i['name']]);
231 @include_once($fname);
233 if (function_exists($addon . '_uninstall')) {
234 $func = $addon . '_uninstall';
235 $func(self::getApp());
237 if (function_exists($addon . '_install')) {
238 $func = $addon . '_install';
239 $func(self::getApp());
241 DBA::update('addon', ['timestamp' => $t], ['id' => $i['id']]);
250 * @brief Parse addon comment in search of addon infos.
255 * * Description: An addon which plugs in
257 * * Author: John <profile url>
258 * * Author: Jane <email>
259 * * Maintainer: Jess <email>
262 * @param string $addon the name of the addon
263 * @return array with the addon information
266 public static function getInfo($addon)
270 $addon = Strings::sanitizeFilePathItem($addon);
281 if (!is_file("addon/$addon/$addon.php")) {
285 $stamp1 = microtime(true);
286 $f = file_get_contents("addon/$addon/$addon.php");
287 $a->getProfiler()->saveTimestamp($stamp1, "file", System::callstack());
289 $r = preg_match("|/\*.*\*/|msU", $f, $m);
292 $ll = explode("\n", $m[0]);
293 foreach ($ll as $l) {
294 $l = trim($l, "\t\n\r */");
296 $addon_info = array_map("trim", explode(":", $l, 2));
297 if (count($addon_info) < 2) {
301 list($type, $v) = $addon_info;
302 $type = strtolower($type);
303 if ($type == "author" || $type == "maintainer") {
304 $r = preg_match("|([^<]+)<([^>]+)>|", $v, $m);
306 $info[$type][] = ['name' => $m[1], 'link' => $m[2]];
308 $info[$type][] = ['name' => $v];
311 if (array_key_exists($type, $info)) {
322 * Checks if the provided addon is enabled
324 * @param string $addon
327 public static function isEnabled($addon)
329 return in_array($addon, self::$addons);
333 * Returns a list of the enabled addon names
337 public static function getEnabledList()
339 return self::$addons;
343 * Saves the current enabled addon list in the system.addon config key
347 public static function saveEnabledList()
349 return Config::set('system', 'addon', implode(',', self::$addons));
353 * Returns the list of non-hidden enabled addon names
358 public static function getVisibleList()
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'];
368 return $visible_addons;
372 * Shim of Hook::register left for backward compatibility purpose.
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)
381 * @throws \Friendica\Network\HTTPException\InternalServerErrorException
383 public static function registerHook($hook, $file, $function, $priority = 0)
385 return Hook::register($hook, $file, $function, $priority);
389 * Shim of Hook::unregister left for backward compatibility purpose.
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
397 * @throws \Friendica\Network\HTTPException\InternalServerErrorException
399 public static function unregisterHook($hook, $file, $function)
401 return Hook::unregister($hook, $file, $function);
405 * Shim of Hook::callAll left for backward-compatibility purpose.
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
413 public static function callHooks($name, &$data = null)
415 Hook::callAll($name, $data);