X-Git-Url: https://git.mxchange.org/?a=blobdiff_plain;f=src%2FCore%2FAddon.php;h=06f6a129675985e1d2feb433fa8292ac041f0e31;hb=dc9658f532a067adaf96d753a34f3ee99ff8587b;hp=aa3a403f909b55092dc0fc601f29361fc5d60628;hpb=d72473fd512a63c2831b943834fae3efba3896b8;p=friendica.git diff --git a/src/Core/Addon.php b/src/Core/Addon.php index aa3a403f90..06f6a12967 100644 --- a/src/Core/Addon.php +++ b/src/Core/Addon.php @@ -1,340 +1,216 @@ . + * */ -namespace Friendica\Core; -use Friendica\App; -use Friendica\BaseObject; -use Friendica\Database\DBA; +namespace Friendica\Core; -require_once 'include/dba.php'; +use Friendica\DI; +use Friendica\Model\Contact; +use Friendica\Util\Strings; /** * Some functions to handle addons */ -class Addon extends BaseObject +class Addon { /** - * @brief Synchronise addons: - * - * system.addon contains a comma-separated list of names - * of addons which are used on this system. - * Go through the database list of already installed addons, and if we have - * an entry, but it isn't in the config list, call the uninstall procedure - * and mark it uninstalled in the database (for now we'll remove it). - * Then go through the config list and if we have a addon that isn't installed, - * call the install procedure and add it to the database. - * + * The addon sub-directory + * @var string */ - public static function check() - { - $a = self::getApp(); - - $r = q("SELECT * FROM `addon` WHERE `installed` = 1"); - if (DBA::isResult($r)) { - $installed = $r; - } else { - $installed = []; - } - - $addons = Config::get('system', 'addon'); - $addons_arr = []; - - if ($addons) { - $addons_arr = explode(',', str_replace(' ', '', $addons)); - } - - $a->addons = $addons_arr; - - $installed_arr = []; - - if (count($installed)) { - foreach ($installed as $i) { - if (!in_array($i['name'], $addons_arr)) { - self::uninstall($i['name']); - } else { - $installed_arr[] = $i['name']; - } - } - } - - if (count($addons_arr)) { - foreach ($addons_arr as $p) { - if (!in_array($p, $installed_arr)) { - self::install($p); - } - } - } - - self::loadHooks(); - - return; - } + const DIRECTORY = 'addon'; /** - * @brief uninstalls an addon. + * List of the names of enabled addons * - * @param string $addon name of the addon - * @return boolean + * @var array */ - public static function uninstall($addon) - { - logger("Addons: uninstalling " . $addon); - DBA::delete('addon', ['name' => $addon]); - - @include_once('addon/' . $addon . '/' . $addon . '.php'); - if (function_exists($addon . '_uninstall')) { - $func = $addon . '_uninstall'; - $func(); - } - } + private static $addons = []; /** - * @brief installs an addon. + * Returns the list of available addons with their current status and info. + * This list is made from scanning the addon/ folder. + * Unsupported addons are excluded unless they already are enabled or system.show_unsupported_addon is set. * - * @param string $addon name of the addon - * @return bool + * @return array + * @throws \Exception */ - public static function install($addon) + public static function getAvailableList(): array { - // silently fail if addon was removed - - if (!file_exists('addon/' . $addon . '/' . $addon . '.php')) { - return false; - } - logger("Addons: installing " . $addon); - $t = @filemtime('addon/' . $addon . '/' . $addon . '.php'); - @include_once('addon/' . $addon . '/' . $addon . '.php'); - if (function_exists($addon . '_install')) { - $func = $addon . '_install'; - $func(); - - $addon_admin = (function_exists($addon."_addon_admin") ? 1 : 0); - - DBA::insert('addon', ['name' => $addon, 'installed' => true, - 'timestamp' => $t, 'plugin_admin' => $addon_admin]); - - // we can add the following with the previous SQL - // once most site tables have been updated. - // This way the system won't fall over dead during the update. - - if (file_exists('addon/' . $addon . '/.hidden')) { - DBA::update('addon', ['hidden' => true], ['name' => $addon]); + $addons = []; + $files = glob('addon/*/'); + if (is_array($files)) { + foreach ($files as $file) { + if (is_dir($file)) { + list($tmp, $addon) = array_map('trim', explode('/', $file)); + $info = self::getInfo($addon); + + if (DI::config()->get('system', 'show_unsupported_addons') + || strtolower($info['status']) != 'unsupported' + || self::isEnabled($addon) + ) { + $addons[] = [$addon, (self::isEnabled($addon) ? 'on' : 'off'), $info]; + } + } } - return true; - } else { - logger("Addons: FAILED installing " . $addon); - return false; } + + return $addons; } /** - * reload all updated addons + * Returns a list of addons that can be configured at the node level. + * The list is formatted for display in the admin panel aside. + * + * @return array + * @throws \Exception */ - public static function reload() + public static function getAdminList(): array { - $addons = Config::get('system', 'addon'); - if (strlen($addons)) { - $r = DBA::select('addon', [], ['installed' => 1]); - if (DBA::isResult($r)) { - $installed = DBA::toArray($r); - } else { - $installed = []; - } - - $addon_list = explode(',', $addons); - - if (count($addon_list)) { - foreach ($addon_list as $addon) { - $addon = trim($addon); - $fname = 'addon/' . $addon . '/' . $addon . '.php'; + $addons_admin = []; + $addons = array_filter(DI::config()->get('addons') ?? []); - if (file_exists($fname)) { - $t = @filemtime($fname); - foreach ($installed as $i) { - if (($i['name'] == $addon) && ($i['timestamp'] != $t)) { - logger('Reloading addon: ' . $i['name']); - @include_once($fname); - - if (function_exists($addon . '_uninstall')) { - $func = $addon . '_uninstall'; - $func(); - } - if (function_exists($addon . '_install')) { - $func = $addon . '_install'; - $func(); - } - DBA::update('addon', ['timestamp' => $t], ['id' => $i['id']]); - } - } - } - } + ksort($addons); + foreach ($addons as $name => $data) { + if (empty($data['admin'])) { + continue; } + + $addons_admin[$name] = [ + 'url' => 'admin/addons/' . $name, + 'name' => $name, + 'class' => 'addon' + ]; } + + return $addons_admin; } + /** - * @brief check if addon is enabled + * Synchronize addons: + * + * system.addon contains a comma-separated list of names + * of addons which are used on this system. + * Go through the database list of already installed addons, and if we have + * an entry, but it isn't in the config list, call the uninstall procedure + * and mark it uninstalled in the database (for now we'll remove it). + * Then go through the config list and if we have a addon that isn't installed, + * call the install procedure and add it to the database. * - * @param string $addon - * @return boolean */ - public static function isEnabled($addon) + public static function loadAddons() { - return DBA::exists('addon', ['installed' => true, 'name' => $addon]); + self::$addons = array_keys(array_filter(DI::config()->get('addons') ?? [])); } - /** - * @brief registers a hook. + * uninstalls an addon. * - * @param string $hook the name of the hook - * @param string $file the name of the file that hooks into - * @param string $function the name of the function that the hook will call - * @param int $priority A priority (defaults to 0) - * @return mixed|bool + * @param string $addon name of the addon + * @return void + * @throws \Exception */ - public static function registerHook($hook, $file, $function, $priority = 0) + public static function uninstall(string $addon) { - $file = str_replace(self::getApp()->get_basepath() . DIRECTORY_SEPARATOR, '', $file); + $addon = Strings::sanitizeFilePathItem($addon); + + Logger::debug("Addon {addon}: {action}", ['action' => 'uninstall', 'addon' => $addon]); + DI::config()->delete('addons', $addon); - $condition = ['hook' => $hook, 'file' => $file, 'function' => $function]; - $exists = DBA::exists('hook', $condition); - if ($exists) { - return true; + @include_once('addon/' . $addon . '/' . $addon . '.php'); + if (function_exists($addon . '_uninstall')) { + $func = $addon . '_uninstall'; + $func(); } - $r = DBA::insert('hook', ['hook' => $hook, 'file' => $file, 'function' => $function, 'priority' => $priority]); + Hook::delete(['file' => 'addon/' . $addon . '/' . $addon . '.php']); - return $r; + unset(self::$addons[array_search($addon, self::$addons)]); } /** - * @brief unregisters a hook. + * installs an addon. * - * @param string $hook the name of the hook - * @param string $file the name of the file that hooks into - * @param string $function the name of the function that the hook called - * @return array + * @param string $addon name of the addon + * @return bool + * @throws \Exception */ - public static function unregisterHook($hook, $file, $function) + public static function install(string $addon): bool { - $relative_file = str_replace(self::getApp()->get_basepath() . DIRECTORY_SEPARATOR, '', $file); + $addon = Strings::sanitizeFilePathItem($addon); - // This here is only needed for fixing a problem that existed on the develop branch - $condition = ['hook' => $hook, 'file' => $file, 'function' => $function]; - DBA::delete('hook', $condition); + $addon_file_path = 'addon/' . $addon . '/' . $addon . '.php'; - $condition = ['hook' => $hook, 'file' => $relative_file, 'function' => $function]; - $r = DBA::delete('hook', $condition); - return $r; - } - - /** - * Load hooks - */ - public static function loadHooks() - { - $a = self::getApp(); - $a->hooks = []; - $r = DBA::select('hook', ['hook', 'file', 'function'], [], ['order' => ['priority' => 'desc', 'file']]); + // silently fail if addon was removed of if $addon is funky + if (!file_exists($addon_file_path)) { + return false; + } - while ($rr = DBA::fetch($r)) { - if (! array_key_exists($rr['hook'], $a->hooks)) { - $a->hooks[$rr['hook']] = []; - } - $a->hooks[$rr['hook']][] = [$rr['file'],$rr['function']]; + Logger::debug("Addon {addon}: {action}", ['action' => 'install', 'addon' => $addon]); + $t = @filemtime($addon_file_path); + @include_once($addon_file_path); + if (function_exists($addon . '_install')) { + $func = $addon . '_install'; + $func(DI::app()); } - DBA::close($r); - } - /** - * @brief Forks a hook. - * - * Use this function when you want to fork a hook via the worker. - * - * @param string $name of the hook to call - * @param string|array $data to transmit to the callback handler - */ - public static function forkHooks($priority, $name, $data = null) - { - $a = self::getApp(); + DI::config()->set('addons', $addon, [ + 'last_update' => $t, + 'admin' => function_exists($addon . '_addon_admin'), + ]); - if (is_array($a->hooks) && array_key_exists($name, $a->hooks)) { - foreach ($a->hooks[$name] as $hook) { - Worker::add($priority, 'ForkHook', $name, $hook, $data); - } + if (!self::isEnabled($addon)) { + self::$addons[] = $addon; } + + return true; } /** - * @brief Calls a hook. + * reload all updated addons * - * Use this function when you want to be able to allow a hook to manipulate - * the provided data. + * @return void + * @throws \Exception * - * @param string $name of the hook to call - * @param string|array &$data to transmit to the callback handler */ - public static function callHooks($name, &$data = null) + public static function reload() { - $a = self::getApp(); - - if (is_array($a->hooks) && array_key_exists($name, $a->hooks)) { - foreach ($a->hooks[$name] as $hook) { - self::callSingleHook($a, $name, $hook, $data); + $addons = array_filter(DI::config()->get('addons') ?? []); + + foreach ($addons as $name => $data) { + $addonname = Strings::sanitizeFilePathItem(trim($name)); + $addon_file_path = 'addon/' . $addonname . '/' . $addonname . '.php'; + if (file_exists($addon_file_path) && $data['last_update'] == filemtime($addon_file_path)) { + // Addon unmodified, skipping + continue; } - } - } - - /** - * @brief Calls a single hook. - * - * @param App $a - * @param string $name of the hook to call - * @param array $hook Hook data - * @param string|array &$data to transmit to the callback handler - */ - public static function callSingleHook(App $a, $name, $hook, &$data = null) - { - // Don't run a theme's hook if the user isn't using the theme - if (strpos($hook[0], 'view/theme/') !== false && strpos($hook[0], 'view/theme/' . $a->getCurrentTheme()) === false) { - return; - } - - @include_once($hook[0]); - if (function_exists($hook[1])) { - $func = $hook[1]; - $func($a, $data); - } else { - // remove orphan hooks - $condition = ['hook' => $name, 'file' => $hook[0], 'function' => $hook[1]]; - DBA::delete('hook', $condition, ['cascade' => false]); - } - } - /** - * check if an app_menu hook exist for addon $name. - * Return true if the addon is an app - */ - public static function isApp($name) - { - $a = self::getApp(); + Logger::debug("Addon {addon}: {action}", ['action' => 'reload', 'addon' => $name]); - if (is_array($a->hooks) && (array_key_exists('app_menu', $a->hooks))) { - foreach ($a->hooks['app_menu'] as $hook) { - if ($hook[0] == 'addon/'.$name.'/'.$name.'.php') { - return true; - } - } + self::uninstall($name); + self::install($name); } - - return false; } /** - * @brief Parse addon comment in search of addon infos. + * Parse addon comment in search of addon infos. * * like * \code @@ -348,10 +224,11 @@ class Addon extends BaseObject * *\endcode * @param string $addon the name of the addon * @return array with the addon information + * @throws \Exception */ - public static function getInfo($addon) + public static function getInfo(string $addon): array { - $a = self::getApp(); + $addon = Strings::sanitizeFilePathItem($addon); $info = [ 'name' => $addon, @@ -366,9 +243,9 @@ class Addon extends BaseObject return $info; } - $stamp1 = microtime(true); + DI::profiler()->startRecording('file'); $f = file_get_contents("addon/$addon/$addon.php"); - $a->save_timestamp($stamp1, "file"); + DI::profiler()->stopRecording(); $r = preg_match("|/\*.*\*/|msU", $f, $m); @@ -387,6 +264,12 @@ class Addon extends BaseObject if ($type == "author" || $type == "maintainer") { $r = preg_match("|([^<]+)<([^>]+)>|", $v, $m); if ($r) { + if (!empty($m[2]) && empty(parse_url($m[2], PHP_URL_SCHEME))) { + $contact = Contact::getByURL($m[2], false); + if (!empty($contact['url'])) { + $m[2] = $contact['url']; + } + } $info[$type][] = ['name' => $m[1], 'link' => $m[2]]; } else { $info[$type][] = ['name' => $v]; @@ -401,4 +284,43 @@ class Addon extends BaseObject } return $info; } + + /** + * Checks if the provided addon is enabled + * + * @param string $addon + * @return boolean + */ + public static function isEnabled(string $addon): bool + { + return in_array($addon, self::$addons); + } + + /** + * Returns a list of the enabled addon names + * + * @return array + */ + public static function getEnabledList(): array + { + return self::$addons; + } + + /** + * Returns the list of non-hidden enabled addon names + * + * @return array + * @throws \Exception + */ + public static function getVisibleList(): array + { + $visible_addons = []; + $addons = array_filter(DI::config()->get('addons') ?? []); + + foreach ($addons as $name => $data) { + $visible_addons[] = $name; + } + + return $visible_addons; + } }