3 * @file src/Core/Addon.php
5 namespace Friendica\Core;
7 use Friendica\BaseObject;
8 use Friendica\Database\DBA;
9 use Friendica\Util\Strings;
12 * Some functions to handle addons
14 class Addon extends BaseObject
17 * The addon sub-directory
20 const DIRECTORY = 'addon';
23 * List of the names of enabled addons
27 private static $addons = [];
30 * @brief Synchronize addons:
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.
41 public static function loadAddons()
43 $installed_addons = [];
45 $r = DBA::select('addon', [], ['installed' => 1]);
46 if (DBA::isResult($r)) {
47 $installed_addons = DBA::toArray($r);
50 $addons = Config::get('system', 'addon');
54 $addons_arr = explode(',', str_replace(' ', '', $addons));
57 self::$addons = $addons_arr;
61 foreach ($installed_addons as $addon) {
62 if (!self::isEnabled($addon['name'])) {
63 self::uninstall($addon['name']);
65 $installed_arr[] = $addon['name'];
69 foreach (self::$addons as $p) {
70 if (!in_array($p, $installed_arr)) {
77 * @brief uninstalls an addon.
79 * @param string $addon name of the addon
83 public static function uninstall($addon)
85 $addon = Strings::sanitizeFilePathItem($addon);
87 Logger::notice("Addon {addon}: {action}", ['action' => 'uninstall', 'addon' => $addon]);
88 DBA::delete('addon', ['name' => $addon]);
90 @include_once('addon/' . $addon . '/' . $addon . '.php');
91 if (function_exists($addon . '_uninstall')) {
92 $func = $addon . '_uninstall';
96 unset(self::$addons[array_search($addon, self::$addons)]);
100 * @brief installs an addon.
102 * @param string $addon name of the addon
106 public static function install($addon)
108 $addon = Strings::sanitizeFilePathItem($addon);
110 // silently fail if addon was removed of if $addon is funky
111 if (!file_exists('addon/' . $addon . '/' . $addon . '.php')) {
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());
122 $addon_admin = (function_exists($addon . "_addon_admin") ? 1 : 0);
124 DBA::insert('addon', ['name' => $addon, 'installed' => true,
125 'timestamp' => $t, 'plugin_admin' => $addon_admin]);
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.
131 if (file_exists('addon/' . $addon . '/.hidden')) {
132 DBA::update('addon', ['hidden' => true], ['name' => $addon]);
135 if (!self::isEnabled($addon)) {
136 self::$addons[] = $addon;
141 Logger::error("Addon {addon}: {action} failed", ['action' => 'uninstall', 'addon' => $addon]);
147 * reload all updated addons
149 public static function reload()
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);
160 $addon_list = explode(',', $addons);
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)) {
170 Logger::notice("Addon {addon}: {action}", ['action' => 'reload', 'addon' => $i['name']]);
171 @include_once($fname);
173 if (function_exists($addon . '_uninstall')) {
174 $func = $addon . '_uninstall';
175 $func(self::getApp());
177 if (function_exists($addon . '_install')) {
178 $func = $addon . '_install';
179 $func(self::getApp());
181 DBA::update('addon', ['timestamp' => $t], ['id' => $i['id']]);
190 * @brief Parse addon comment in search of addon infos.
195 * * Description: An addon which plugs in
197 * * Author: John <profile url>
198 * * Author: Jane <email>
199 * * Maintainer: Jess <email>
202 * @param string $addon the name of the addon
203 * @return array with the addon information
206 public static function getInfo($addon)
210 $addon = Strings::sanitizeFilePathItem($addon);
221 if (!is_file("addon/$addon/$addon.php")) {
225 $stamp1 = microtime(true);
226 $f = file_get_contents("addon/$addon/$addon.php");
227 $a->getProfiler()->saveTimestamp($stamp1, "file", System::callstack());
229 $r = preg_match("|/\*.*\*/|msU", $f, $m);
232 $ll = explode("\n", $m[0]);
233 foreach ($ll as $l) {
234 $l = trim($l, "\t\n\r */");
236 $addon_info = array_map("trim", explode(":", $l, 2));
237 if (count($addon_info) < 2) {
241 list($type, $v) = $addon_info;
242 $type = strtolower($type);
243 if ($type == "author" || $type == "maintainer") {
244 $r = preg_match("|([^<]+)<([^>]+)>|", $v, $m);
246 $info[$type][] = ['name' => $m[1], 'link' => $m[2]];
248 $info[$type][] = ['name' => $v];
251 if (array_key_exists($type, $info)) {
262 * Checks if the provided addon is enabled
264 * @param string $addon
267 public static function isEnabled($addon)
269 return in_array($addon, self::$addons);
273 * Returns a list of the enabled addon names
277 public static function getEnabledList()
279 return self::$addons;
283 * Saves the current enabled addon list in the system.addon config key
286 * @throws \Friendica\Network\HTTPException\InternalServerErrorException
288 public static function saveEnabledList()
290 return Config::set("system", "addon", implode(", ", self::$addons));
294 * Returns the list of non-hidden enabled addon names
299 public static function getVisibleList()
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'];
309 return $visible_addons;
313 * Shim of Hook::register left for backward compatibility purpose.
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)
322 * @throws \Friendica\Network\HTTPException\InternalServerErrorException
324 public static function registerHook($hook, $file, $function, $priority = 0)
326 return Hook::register($hook, $file, $function, $priority);
330 * Shim of Hook::unregister left for backward compatibility purpose.
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
338 * @throws \Friendica\Network\HTTPException\InternalServerErrorException
340 public static function unregisterHook($hook, $file, $function)
342 return Hook::unregister($hook, $file, $function);
346 * Shim of Hook::callAll left for backward-compatibility purpose.
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
354 public static function callHooks($name, &$data = null)
356 Hook::callAll($name, $data);