diff --git a/src/Core/Addon/AddonManagerHelper.php b/src/Core/Addon/AddonManagerHelper.php index 6bf12285ac..d8ff19af93 100644 --- a/src/Core/Addon/AddonManagerHelper.php +++ b/src/Core/Addon/AddonManagerHelper.php @@ -9,7 +9,9 @@ declare(strict_types=1); namespace Friendica\Core\Addon; +use Friendica\Core\Cache\Capability\ICanCache; use Friendica\Core\Config\Capability\IManageConfigValues; +use Friendica\Database\Database; use Friendica\Util\Profiler; use Friendica\Util\Strings; use Psr\Log\LoggerInterface; @@ -23,8 +25,12 @@ final class AddonManagerHelper implements AddonHelper { private string $addonPath; + private Database $database; + private IManageConfigValues $config; + private ICanCache $cache; + private LoggerInterface $logger; private Profiler $profiler; @@ -37,12 +43,16 @@ final class AddonManagerHelper implements AddonHelper public function __construct( string $addonPath, + Database $database, IManageConfigValues $config, + ICanCache $cache, LoggerInterface $logger, Profiler $profiler ) { $this->addonPath = $addonPath; + $this->database = $database; $this->config = $config; + $this->cache = $cache; $this->logger = $logger; $this->profiler = $profiler; @@ -153,7 +163,31 @@ final class AddonManagerHelper implements AddonHelper */ public function uninstallAddon(string $addonId): void { - $this->proxy->uninstallAddon($addonId); + $addonId = Strings::sanitizeFilePathItem($addonId); + + $this->logger->debug("Addon {addon}: {action}", ['action' => 'uninstall', 'addon' => $addonId]); + $this->config->delete('addons', $addonId); + + $addon_file_path = $this->getAddonPath() . '/' . $addonId . '/' . $addonId . '.php'; + + @include_once($addon_file_path); + + if (function_exists($addonId . '_uninstall')) { + $func = $addonId . '_uninstall'; + $func(); + } + + // Remove registered hooks for the addon + // Handles both relative and absolute file paths + $condition = ['`file` LIKE ?', "%/$addonId/$addonId.php"]; + + $result = $this->database->delete('hook', $condition); + + if ($result) { + $this->cache->delete('routerDispatchData'); + } + + unset($this->addons[array_search($addonId, $this->addons)]); } /** diff --git a/tests/Unit/Core/Addon/AddonManagerHelperTest.php b/tests/Unit/Core/Addon/AddonManagerHelperTest.php index f7b4960fda..951f9655ac 100644 --- a/tests/Unit/Core/Addon/AddonManagerHelperTest.php +++ b/tests/Unit/Core/Addon/AddonManagerHelperTest.php @@ -12,7 +12,9 @@ namespace Friendica\Test\Unit\Core\Addon; use Exception; use Friendica\Core\Addon\AddonInfo; use Friendica\Core\Addon\AddonManagerHelper; +use Friendica\Core\Cache\Capability\ICanCache; use Friendica\Core\Config\Capability\IManageConfigValues; +use Friendica\Database\Database; use Friendica\Util\Profiler; use org\bovigo\vfs\vfsStream; use PHPUnit\Framework\TestCase; @@ -24,7 +26,9 @@ class AddonManagerHelperTest extends TestCase { $addonManagerHelper = new AddonManagerHelper( __DIR__ . '/../../../Util/addons', + $this->createStub(Database::class), $this->createStub(IManageConfigValues::class), + $this->createStub(ICanCache::class), $this->createStub(LoggerInterface::class), $this->createStub(Profiler::class) ); @@ -48,7 +52,9 @@ class AddonManagerHelperTest extends TestCase $addonManagerHelper = new AddonManagerHelper( __DIR__ . '/../../../Util/addons', + $this->createStub(Database::class), $config, + $this->createStub(ICanCache::class), $this->createStub(LoggerInterface::class), $this->createStub(Profiler::class) ); @@ -74,7 +80,9 @@ class AddonManagerHelperTest extends TestCase $addonManagerHelper = new AddonManagerHelper( __DIR__ . '/../../../Util/addons', + $this->createStub(Database::class), $config, + $this->createStub(ICanCache::class), $this->createStub(LoggerInterface::class), $this->createStub(Profiler::class) ); @@ -98,7 +106,9 @@ class AddonManagerHelperTest extends TestCase $addonManagerHelper = new AddonManagerHelper( __DIR__ . '/../../../Util/addons', + $this->createStub(Database::class), $config, + $this->createStub(ICanCache::class), $this->createStub(LoggerInterface::class), $this->createStub(Profiler::class) ); @@ -110,7 +120,9 @@ class AddonManagerHelperTest extends TestCase { $addonManagerHelper = new AddonManagerHelper( __DIR__ . '/../../../Util/addons', + $this->createStub(Database::class), $this->createStub(IManageConfigValues::class), + $this->createStub(ICanCache::class), $this->createStub(LoggerInterface::class), $this->createStub(Profiler::class) ); @@ -128,7 +140,9 @@ class AddonManagerHelperTest extends TestCase $addonManagerHelper = new AddonManagerHelper( $root->url(), + $this->createStub(Database::class), $this->createStub(IManageConfigValues::class), + $this->createStub(ICanCache::class), $this->createStub(LoggerInterface::class), $this->createStub(Profiler::class) ); @@ -159,7 +173,9 @@ class AddonManagerHelperTest extends TestCase $addonManagerHelper = new AddonManagerHelper( $root->url(), + $this->createStub(Database::class), $this->createStub(IManageConfigValues::class), + $this->createStub(ICanCache::class), $this->createStub(LoggerInterface::class), $this->createStub(Profiler::class) ); @@ -189,7 +205,9 @@ class AddonManagerHelperTest extends TestCase $addonManagerHelper = new AddonManagerHelper( $root->url(), + $this->createStub(Database::class), $config, + $this->createStub(ICanCache::class), $this->createStub(LoggerInterface::class), $this->createStub(Profiler::class) ); @@ -207,7 +225,9 @@ class AddonManagerHelperTest extends TestCase $addonManagerHelper = new AddonManagerHelper( $root->url(), + $this->createStub(Database::class), $this->createStub(IManageConfigValues::class), + $this->createStub(ICanCache::class), $this->createStub(LoggerInterface::class), $this->createStub(Profiler::class) ); @@ -218,4 +238,121 @@ class AddonManagerHelperTest extends TestCase $this->assertSame(['helloaddon'], $addonManagerHelper->getEnabledAddons()); } + public function testUninstallAddonIncludesAddonFile(): void + { + $root = vfsStream::setup(__FUNCTION__ . '_addons', 0777, [ + 'helloaddon' => [ + 'helloaddon.php' => 'url(), + $this->createStub(Database::class), + $this->createStub(IManageConfigValues::class), + $this->createStub(ICanCache::class), + $this->createStub(LoggerInterface::class), + $this->createStub(Profiler::class) + ); + + $this->expectException(Exception::class); + $this->expectExceptionMessage('Addon file loaded'); + + $addonManagerHelper->uninstallAddon('helloaddon'); + } + + public function testUninstallAddonCallsUninstallFunction(): void + { + // We need a unique name for the addon to avoid conflicts + // with other tests that may define the same install function. + $addonName = __FUNCTION__; + + $root = vfsStream::setup(__FUNCTION__ . '_addons', 0777, [ + $addonName => [ + $addonName . '.php' => <<url(), + $this->createStub(Database::class), + $this->createStub(IManageConfigValues::class), + $this->createStub(ICanCache::class), + $this->createStub(LoggerInterface::class), + $this->createStub(Profiler::class) + ); + + $this->expectException(Exception::class); + $this->expectExceptionMessage('Addon uninstalled'); + + $addonManagerHelper->uninstallAddon($addonName); + } + + public function testUninstallAddonRemovesHooksFromDatabase(): void + { + $root = vfsStream::setup(__FUNCTION__ . '_addons', 0777, [ + 'helloaddon' => [ + 'helloaddon.php' => 'createMock(Database::class); + $database->expects($this->once()) + ->method('delete') + ->with( + 'hook', + ['`file` LIKE ?', '%/helloaddon/helloaddon.php'] + ); + + $addonManagerHelper = new AddonManagerHelper( + $root->url(), + $database, + $this->createStub(IManageConfigValues::class), + $this->createStub(ICanCache::class), + $this->createStub(LoggerInterface::class), + $this->createStub(Profiler::class) + ); + + $addonManagerHelper->uninstallAddon('helloaddon'); + } + + public function testUninstallAddonDisablesAddon(): void + { + $root = vfsStream::setup(__FUNCTION__ . '_addons', 0777, [ + 'helloaddon' => [ + 'helloaddon.php' => 'createStub(IManageConfigValues::class); + $config->method('get')->willReturn([ + 'helloaddon' => [ + 'last_update' => 1234567890, + 'admin' => false, + ], + ]); + + $addonManagerHelper = new AddonManagerHelper( + $root->url(), + $this->createStub(Database::class), + $config, + $this->createStub(ICanCache::class), + $this->createStub(LoggerInterface::class), + $this->createStub(Profiler::class) + ); + + $addonManagerHelper->loadAddons(); + + $this->assertSame(['helloaddon'], $addonManagerHelper->getEnabledAddons()); + + $addonManagerHelper->uninstallAddon('helloaddon'); + + $this->assertSame([], $addonManagerHelper->getEnabledAddons()); + } }