3 * @copyright Copyright (C) 2010-2023, the Friendica project
5 * @license GNU AGPL version 3 or any later version
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.
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.
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/>.
22 namespace Friendica\Core;
25 use Friendica\Model\Contact;
26 use Friendica\Util\Strings;
29 * Some functions to handle addons
34 * The addon sub-directory
37 const DIRECTORY = 'addon';
40 * List of the names of enabled addons
44 private static $addons = [];
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.
54 public static function getAvailableList(): array
57 $files = glob('addon/*/');
58 if (is_array($files)) {
59 foreach ($files as $file) {
61 list($tmp, $addon) = array_map('trim', explode('/', $file));
62 $info = self::getInfo($addon);
64 if (DI::config()->get('system', 'show_unsupported_addons')
65 || strtolower($info['status']) != 'unsupported'
66 || self::isEnabled($addon)
68 $addons[] = [$addon, (self::isEnabled($addon) ? 'on' : 'off'), $info];
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.
84 public static function getAdminList(): array
87 $addons = array_filter(DI::config()->get('addons') ?? []);
90 foreach ($addons as $name => $data) {
91 if (empty($data['admin'])) {
95 $addons_admin[$name] = [
96 'url' => 'admin/addons/' . $name,
102 return $addons_admin;
107 * Synchronize addons:
109 * system.addon contains a comma-separated list of names
110 * of addons which are used on this system.
111 * Go through the database list of already installed addons, and if we have
112 * an entry, but it isn't in the config list, call the uninstall procedure
113 * and mark it uninstalled in the database (for now we'll remove it).
114 * Then go through the config list and if we have a addon that isn't installed,
115 * call the install procedure and add it to the database.
118 public static function loadAddons()
120 self::$addons = array_keys(array_filter(DI::config()->get('addons') ?? []));
124 * uninstalls an addon.
126 * @param string $addon name of the addon
130 public static function uninstall(string $addon)
132 $addon = Strings::sanitizeFilePathItem($addon);
134 Logger::debug("Addon {addon}: {action}", ['action' => 'uninstall', 'addon' => $addon]);
135 DI::config()->delete('addons', $addon);
137 @include_once('addon/' . $addon . '/' . $addon . '.php');
138 if (function_exists($addon . '_uninstall')) {
139 $func = $addon . '_uninstall';
143 Hook::delete(['file' => 'addon/' . $addon . '/' . $addon . '.php']);
145 unset(self::$addons[array_search($addon, self::$addons)]);
151 * @param string $addon name of the addon
155 public static function install(string $addon): bool
157 $addon = Strings::sanitizeFilePathItem($addon);
159 $addon_file_path = 'addon/' . $addon . '/' . $addon . '.php';
161 // silently fail if addon was removed of if $addon is funky
162 if (!file_exists($addon_file_path)) {
166 Logger::debug("Addon {addon}: {action}", ['action' => 'install', 'addon' => $addon]);
167 $t = @filemtime($addon_file_path);
168 @include_once($addon_file_path);
169 if (function_exists($addon . '_install')) {
170 $func = $addon . '_install';
174 DI::config()->set('addons', $addon, [
176 'admin' => function_exists($addon . '_addon_admin'),
179 if (!self::isEnabled($addon)) {
180 self::$addons[] = $addon;
187 * reload all updated addons
193 public static function reload()
195 $addons = array_filter(DI::config()->get('addons') ?? []);
197 foreach ($addons as $name => $data) {
198 $addonname = Strings::sanitizeFilePathItem(trim($name));
199 $addon_file_path = 'addon/' . $addonname . '/' . $addonname . '.php';
200 if (file_exists($addon_file_path) && $data['last_update'] == filemtime($addon_file_path)) {
201 // Addon unmodified, skipping
205 Logger::debug("Addon {addon}: {action}", ['action' => 'reload', 'addon' => $name]);
207 self::uninstall($name);
208 self::install($name);
213 * Parse addon comment in search of addon infos.
218 * * Description: An addon which plugs in
220 * * Author: John <profile url>
221 * * Author: Jane <email>
222 * * Maintainer: Jess <email>
225 * @param string $addon the name of the addon
226 * @return array with the addon information
229 public static function getInfo(string $addon): array
231 $addon = Strings::sanitizeFilePathItem($addon);
242 if (!is_file("addon/$addon/$addon.php")) {
246 DI::profiler()->startRecording('file');
247 $f = file_get_contents("addon/$addon/$addon.php");
248 DI::profiler()->stopRecording();
250 $r = preg_match("|/\*.*\*/|msU", $f, $m);
253 $ll = explode("\n", $m[0]);
254 foreach ($ll as $l) {
255 $l = trim($l, "\t\n\r */");
257 $addon_info = array_map("trim", explode(":", $l, 2));
258 if (count($addon_info) < 2) {
262 list($type, $v) = $addon_info;
263 $type = strtolower($type);
264 if ($type == "author" || $type == "maintainer") {
265 $r = preg_match("|([^<]+)<([^>]+)>|", $v, $m);
267 if (!empty($m[2]) && empty(parse_url($m[2], PHP_URL_SCHEME))) {
268 $contact = Contact::getByURL($m[2], false);
269 if (!empty($contact['url'])) {
270 $m[2] = $contact['url'];
273 $info[$type][] = ['name' => $m[1], 'link' => $m[2]];
275 $info[$type][] = ['name' => $v];
278 if (array_key_exists($type, $info)) {
289 * Checks if the provided addon is enabled
291 * @param string $addon
294 public static function isEnabled(string $addon): bool
296 return in_array($addon, self::$addons);
300 * Returns a list of the enabled addon names
304 public static function getEnabledList(): array
306 return self::$addons;
310 * Returns the list of non-hidden enabled addon names
315 public static function getVisibleList(): array
317 $visible_addons = [];
318 $addons = array_filter(DI::config()->get('addons') ?? []);
320 foreach ($addons as $name => $data) {
321 $visible_addons[] = $name;
324 return $visible_addons;