From 13b234d279a7de89c718c446a85238bff7d873ca Mon Sep 17 00:00:00 2001
From: Philipp <admin@philipp.info>
Date: Tue, 3 Jan 2023 20:24:48 +0100
Subject: [PATCH] Use addons config entries instead of the addon table

---
 src/Core/Addon.php                            |  52 ++++---
 .../Config/Capability/IManageConfigValues.php |   6 +-
 src/Core/Config/Model/Config.php              |   2 +-
 src/Core/L10n.php                             |  11 +-
 static/settings.config.php                    |   4 -
 tests/src/Core/Config/Cache/CacheTest.php     | 129 ++++++++++++++++++
 tests/src/Core/Config/ConfigTest.php          | 129 ++++++++++++++++++
 7 files changed, 294 insertions(+), 39 deletions(-)

diff --git a/src/Core/Addon.php b/src/Core/Addon.php
index cea814f320..be3e70a540 100644
--- a/src/Core/Addon.php
+++ b/src/Core/Addon.php
@@ -21,7 +21,6 @@
 
 namespace Friendica\Core;
 
-use Friendica\Database\DBA;
 use Friendica\DI;
 use Friendica\Model\Contact;
 use Friendica\Util\Strings;
@@ -85,15 +84,18 @@ class Addon
 	public static function getAdminList()
 	{
 		$addons_admin = [];
-		$addonsAdminStmt = DBA::select('addon', ['name'], ['plugin_admin' => 1], ['order' => ['name']]);
-		while ($addon = DBA::fetch($addonsAdminStmt)) {
-			$addons_admin[$addon['name']] = [
-				'url' => 'admin/addons/' . $addon['name'],
-				'name' => $addon['name'],
+		$addons = DI::config()->get('addons');
+		foreach ($addons as $name => $data) {
+			if (empty($data['admin'])) {
+				continue;
+			}
+
+			$addons_admin[$name] = [
+				'url' => 'admin/addons/' . $name,
+				'name' => $name,
 				'class' => 'addon'
 			];
 		}
-		DBA::close($addonsAdminStmt);
 
 		return $addons_admin;
 	}
@@ -113,8 +115,7 @@ class Addon
 	 */
 	public static function loadAddons()
 	{
-		$installed_addons = DBA::selectToArray('addon', ['name'], ['installed' => true]);
-		self::$addons = array_column($installed_addons, 'name');
+		self::$addons = array_keys(DI::config()->get('addons') ?? []);
 	}
 
 	/**
@@ -129,7 +130,7 @@ class Addon
 		$addon = Strings::sanitizeFilePathItem($addon);
 
 		Logger::debug("Addon {addon}: {action}", ['action' => 'uninstall', 'addon' => $addon]);
-		DBA::delete('addon', ['name' => $addon]);
+		DI::config()->delete('addons', $addon);
 
 		@include_once('addon/' . $addon . '/' . $addon . '.php');
 		if (function_exists($addon . '_uninstall')) {
@@ -168,12 +169,9 @@ class Addon
 			$func(DI::app());
 		}
 
-		DBA::insert('addon', [
-			'name' => $addon,
-			'installed' => true,
-			'timestamp' => $t,
-			'plugin_admin' => function_exists($addon . '_addon_admin'),
-			'hidden' => file_exists('addon/' . $addon . '/.hidden')
+		DI::config()->set('addons', $addon,[
+			'last_update' => $t,
+			'admin' => function_exists($addon . '_addon_admin'),
 		]);
 
 		if (!self::isEnabled($addon)) {
@@ -190,20 +188,20 @@ class Addon
 	 */
 	public static function reload()
 	{
-		$addons = DBA::selectToArray('addon', [], ['installed' => true]);
+		$addons = DI::config()->get('addons');
 
-		foreach ($addons as $addon) {
-			$addonname = Strings::sanitizeFilePathItem(trim($addon['name']));
+		foreach ($addons as $name => $data) {
+			$addonname = Strings::sanitizeFilePathItem(trim($name));
 			$addon_file_path = 'addon/' . $addonname . '/' . $addonname . '.php';
-			if (file_exists($addon_file_path) && $addon['timestamp'] == filemtime($addon_file_path)) {
+			if (file_exists($addon_file_path) && $data['last_update'] == filemtime($addon_file_path)) {
 				// Addon unmodified, skipping
 				continue;
 			}
 
-			Logger::debug("Addon {addon}: {action}", ['action' => 'reload', 'addon' => $addon['name']]);
+			Logger::debug("Addon {addon}: {action}", ['action' => 'reload', 'addon' => $name]);
 
-			self::uninstall($addon['name']);
-			self::install($addon['name']);
+			self::uninstall($name);
+			self::install($name);
 		}
 	}
 
@@ -313,11 +311,9 @@ class Addon
 	public static function getVisibleList(): array
 	{
 		$visible_addons = [];
-		$stmt = DBA::select('addon', ['name'], ['hidden' => false, 'installed' => true]);
-		if (DBA::isResult($stmt)) {
-			foreach (DBA::toArray($stmt) as $addon) {
-				$visible_addons[] = $addon['name'];
-			}
+		$addons = DI::config()->get('addons');
+		foreach ($addons as $name => $data) {
+			$visible_addons[] = $name;
 		}
 
 		return $visible_addons;
diff --git a/src/Core/Config/Capability/IManageConfigValues.php b/src/Core/Config/Capability/IManageConfigValues.php
index 715887ddf3..09e2cd9144 100644
--- a/src/Core/Config/Capability/IManageConfigValues.php
+++ b/src/Core/Config/Capability/IManageConfigValues.php
@@ -47,8 +47,8 @@ interface IManageConfigValues
 	 *
 	 * Get a particular config value from the given category ($cat)
 	 *
-	 * @param string  $cat        The category of the configuration value
-	 * @param string  $key           The configuration key to query
+	 * @param string  $cat           The category of the configuration value
+	 * @param ?string $key           The configuration key to query (if null, the whole array at the category will get returned)
 	 * @param mixed   $default_value Deprecated, use `Config->get($cat, $key, null, $refresh) ?? $default_value` instead
 	 *
 	 * @return mixed Stored value or null if it does not exist
@@ -56,7 +56,7 @@ interface IManageConfigValues
 	 * @throws ConfigPersistenceException In case the persistence layer throws errors
 	 *
 	 */
-	public function get(string $cat, string $key, $default_value = null);
+	public function get(string $cat, string $key = null, $default_value = null);
 
 	/**
 	 * Load all configuration values from a given cache and saves it back in the configuration node store
diff --git a/src/Core/Config/Model/Config.php b/src/Core/Config/Model/Config.php
index 29ea6b12d3..593ab04070 100644
--- a/src/Core/Config/Model/Config.php
+++ b/src/Core/Config/Model/Config.php
@@ -102,7 +102,7 @@ class Config implements IManageConfigValues
 	}
 
 	/** {@inheritDoc} */
-	public function get(string $cat, string $key, $default_value = null)
+	public function get(string $cat, string $key = null, $default_value = null)
 	{
 		return $this->configCache->get($cat, $key) ?? $default_value;
 	}
diff --git a/src/Core/L10n.php b/src/Core/L10n.php
index 0a91677b5d..3f74a40849 100644
--- a/src/Core/L10n.php
+++ b/src/Core/L10n.php
@@ -85,10 +85,15 @@ class L10n
 	 * @var Database
 	 */
 	private $dba;
+	/**
+	 * @var IManageConfigValues
+	 */
+	private $config;
 
 	public function __construct(IManageConfigValues $config, Database $dba, IHandleSessions $session, array $server, array $get)
 	{
 		$this->dba    = $dba;
+		$this->config = $config;
 
 		$this->loadTranslationTable(L10n::detectLanguage($server, $get, $config->get('system', 'language', self::DEFAULT)));
 		$this->setSessionVariable($session);
@@ -157,9 +162,9 @@ class L10n
 		$a->strings = [];
 
 		// load enabled addons strings
-		$addons = $this->dba->select('addon', ['name'], ['installed' => true]);
-		while ($p = $this->dba->fetch($addons)) {
-			$name = Strings::sanitizeFilePathItem($p['name']);
+		$addons = array_keys($this->config->get('addons') ?? []);
+		foreach ($addons as $addon) {
+			$name = Strings::sanitizeFilePathItem($addon);
 			if (file_exists(__DIR__ . "/../../addon/$name/lang/$lang/strings.php")) {
 				include __DIR__ . "/../../addon/$name/lang/$lang/strings.php";
 			}
diff --git a/static/settings.config.php b/static/settings.config.php
index c7d4b6a9c2..04994bc73e 100644
--- a/static/settings.config.php
+++ b/static/settings.config.php
@@ -52,10 +52,6 @@ return [
 		// Enter 0 for no time limit.
 		'account_abandon_days' => 0,
 
-		// addon (Comma-separated list)
-		// Manual list of addons which are enabled on this system.
-		'addon' => '',
-
 		// add_missing_posts (boolean)
 		// Checks for missing entries in "post", "post-thread" or "post-thread-user" and creates them
 		'add_missing_posts' => false,
diff --git a/tests/src/Core/Config/Cache/CacheTest.php b/tests/src/Core/Config/Cache/CacheTest.php
index 2db6196b7b..024c9c7e3e 100644
--- a/tests/src/Core/Config/Cache/CacheTest.php
+++ b/tests/src/Core/Config/Cache/CacheTest.php
@@ -410,4 +410,133 @@ class CacheTest extends MockedTest
 		// the newCache entry wasn't set, because we Diff
 		self::assertNull($mergedCache->get('system', 'test_3'));
 	}
+
+	public function dataTestCat()
+	{
+		return [
+			'test_with_hashmap'     => [
+				'data'      => [
+					'test_with_hashmap' => [
+						'notifyall' => [
+							'last_update' => 1671051565,
+							'admin'       => true,
+						],
+						'blockbot'  => [
+							'last_update' => 1658952852,
+							'admin'       => true,
+						],
+					],
+					'config'            => [
+						'register_policy' => 2,
+						'register_text'   => '',
+						'sitename'        => 'Friendica Social Network23',
+						'hostname'        => 'friendica.local',
+						'private_addons'  => false,
+					],
+					'system'            => [
+						'dbclean_expire_conversation' => 90,
+					],
+				],
+				'cat'       => 'test_with_hashmap',
+				'assertion' => [
+					'notifyall' => [
+						'last_update' => 1671051565,
+						'admin'       => true,
+					],
+					'blockbot'  => [
+						'last_update' => 1658952852,
+						'admin'       => true,
+					],
+				],
+			],
+			'test_with_keys'        => [
+				'data'      => [
+					'test_with_keys' => [
+						[
+							'last_update' => 1671051565,
+							'admin'       => true,
+						],
+						[
+							'last_update' => 1658952852,
+							'admin'       => true,
+						],
+					],
+					'config'            => [
+						'register_policy' => 2,
+						'register_text'   => '',
+						'sitename'        => 'Friendica Social Network23',
+						'hostname'        => 'friendica.local',
+						'private_addons'  => false,
+					],
+					'system'            => [
+						'dbclean_expire_conversation' => 90,
+					],
+				],
+				'cat'       => 'test_with_keys',
+				'assertion' => [
+					[
+						'last_update' => 1671051565,
+						'admin'       => true,
+					],
+					[
+						'last_update' => 1658952852,
+						'admin'       => true,
+					],
+				],
+			],
+			'test_with_inner_array' => [
+				'data'      => [
+					'test_with_inner_array' => [
+						'notifyall' => [
+							'last_update' => 1671051565,
+							'admin'       => [
+								'yes' => true,
+								'no'  => 1.5,
+							],
+						],
+						'blogbot'   => [
+							'last_update' => 1658952852,
+							'admin'       => true,
+						],
+					],
+					'config'                => [
+						'register_policy' => 2,
+						'register_text'   => '',
+						'sitename'        => 'Friendica Social Network23',
+						'hostname'        => 'friendica.local',
+						'private_addons'  => false,
+					],
+					'system'                => [
+						'dbclean_expire_conversation' => 90,
+					],
+				],
+				'cat'       => 'test_with_inner_array',
+				'assertion' => [
+					'notifyall' => [
+						'last_update' => 1671051565,
+						'admin'       => [
+							'yes' => true,
+							'no'  => 1.5,
+						],
+					],
+					'blogbot'   => [
+						'last_update' => 1658952852,
+						'admin'       => true,
+					],
+				],
+			],
+		];
+	}
+
+	/**
+	 * Tests that the Cache can return a whole category at once
+	 *
+	 * @dataProvider dataTestCat
+	 */
+	public function testGetCategory(array $data, string $category, array $assertion)
+	{
+		$cache = new Cache($data);
+
+		self::assertEquals($assertion, $cache->get($category));
+	}
 }
diff --git a/tests/src/Core/Config/ConfigTest.php b/tests/src/Core/Config/ConfigTest.php
index c85cf3f708..876a0b05b3 100644
--- a/tests/src/Core/Config/ConfigTest.php
+++ b/tests/src/Core/Config/ConfigTest.php
@@ -403,4 +403,133 @@ class ConfigTest extends MockedTest
 		self::assertFalse($this->testedConfig->set('config', 'test', '123'));
 		self::assertEquals('prio', $this->testedConfig->get('config', 'test', '', true));
 	}
+
+
+	public function dataTestCat()
+	{
+		return [
+			'test_with_hashmap'     => [
+				'data'      => [
+					'test_with_hashmap' => [
+						'notifyall' => [
+							'last_update' => 1671051565,
+							'admin'       => true,
+						],
+						'blockbot'  => [
+							'last_update' => 1658952852,
+							'admin'       => true,
+						],
+					],
+					'config'            => [
+						'register_policy' => 2,
+						'register_text'   => '',
+						'sitename'        => 'Friendica Social Network23',
+						'hostname'        => 'friendica.local',
+						'private_addons'  => false,
+					],
+					'system'            => [
+						'dbclean_expire_conversation' => 90,
+					],
+				],
+				'cat'       => 'test_with_hashmap',
+				'assertion' => [
+					'notifyall' => [
+						'last_update' => 1671051565,
+						'admin'       => true,
+					],
+					'blockbot'  => [
+						'last_update' => 1658952852,
+						'admin'       => true,
+					],
+				],
+			],
+			'test_with_keys'        => [
+				'data'      => [
+					'test_with_keys' => [
+						[
+							'last_update' => 1671051565,
+							'admin'       => true,
+						],
+						[
+							'last_update' => 1658952852,
+							'admin'       => true,
+						],
+					],
+					'config'            => [
+						'register_policy' => 2,
+						'register_text'   => '',
+						'sitename'        => 'Friendica Social Network23',
+						'hostname'        => 'friendica.local',
+						'private_addons'  => false,
+					],
+					'system'            => [
+						'dbclean_expire_conversation' => 90,
+					],
+				],
+				'cat'       => 'test_with_keys',
+				'assertion' => [
+					[
+						'last_update' => 1671051565,
+						'admin'       => true,
+					],
+					[
+						'last_update' => 1658952852,
+						'admin'       => true,
+					],
+				],
+			],
+			'test_with_inner_array' => [
+				'data'      => [
+					'test_with_inner_array' => [
+						'notifyall' => [
+							'last_update' => 1671051565,
+							'admin'       => [
+								'yes' => true,
+								'no'  => 1.5,
+							],
+						],
+						'blogbot'   => [
+							'last_update' => 1658952852,
+							'admin'       => true,
+						],
+					],
+					'config'                => [
+						'register_policy' => 2,
+						'register_text'   => '',
+						'sitename'        => 'Friendica Social Network23',
+						'hostname'        => 'friendica.local',
+						'private_addons'  => false,
+					],
+					'system'                => [
+						'dbclean_expire_conversation' => 90,
+					],
+				],
+				'cat'       => 'test_with_inner_array',
+				'assertion' => [
+					'notifyall' => [
+						'last_update' => 1671051565,
+						'admin'       => [
+							'yes' => true,
+							'no'  => 1.5,
+						],
+					],
+					'blogbot'   => [
+						'last_update' => 1658952852,
+						'admin'       => true,
+					],
+				],
+			],
+		];
+	}
+
+	/**
+	 * @dataProvider dataTestCat
+	 */
+	public function testGetCategory(array $data, string $category, array $assertion)
+	{
+		$this->configCache = new Cache($data);
+		$config = new Config($this->configFileManager, $this->configCache);
+
+		self::assertEquals($assertion, $config->get($category));
+	}
 }
-- 
2.39.5