diff --git a/src/Core/Addon/AddonHelper.php b/src/Core/Addon/AddonHelper.php index 84b0c5f89d..03a21232e5 100644 --- a/src/Core/Addon/AddonHelper.php +++ b/src/Core/Addon/AddonHelper.php @@ -55,7 +55,7 @@ interface AddonHelper public function loadAddons(): void; /** - * Reload (uninstall and install) all updated addons. + * Reload (uninstall and install) all installed and modified addons. */ public function reloadAddons(): void; diff --git a/src/Core/Addon/AddonManagerHelper.php b/src/Core/Addon/AddonManagerHelper.php index d8ff19af93..f2ff78d896 100644 --- a/src/Core/Addon/AddonManagerHelper.php +++ b/src/Core/Addon/AddonManagerHelper.php @@ -201,11 +201,31 @@ final class AddonManagerHelper implements AddonHelper } /** - * Reload (uninstall and install) all updated addons. + * Reload (uninstall and install) all installed and modified addons. */ public function reloadAddons(): void { - $this->proxy->reloadAddons(); + $addons = array_filter($this->config->get('addons') ?? []); + + foreach ($addons as $addonName => $data) { + $addonId = Strings::sanitizeFilePathItem(trim($addonName)); + + $addon_file_path = $this->getAddonPath() . '/' . $addonId . '/' . $addonId . '.php'; + + if (!file_exists($addon_file_path)) { + continue; + } + + if (array_key_exists('last_update', $data) && intval($data['last_update']) === filemtime($addon_file_path)) { + // Addon unmodified, skipping + continue; + } + + $this->logger->debug("Addon {addon}: {action}", ['action' => 'reload', 'addon' => $addonId]); + + $this->uninstallAddon($addonId); + $this->installAddon($addonId); + } } /** diff --git a/tests/Unit/Core/Addon/AddonManagerHelperTest.php b/tests/Unit/Core/Addon/AddonManagerHelperTest.php index 951f9655ac..41320361dd 100644 --- a/tests/Unit/Core/Addon/AddonManagerHelperTest.php +++ b/tests/Unit/Core/Addon/AddonManagerHelperTest.php @@ -162,12 +162,12 @@ class AddonManagerHelperTest extends TestCase $root = vfsStream::setup(__FUNCTION__ . '_addons', 0777, [ $addonName => [ $addonName . '.php' => << [ $addonName . '.php' => <<assertSame([], $addonManagerHelper->getEnabledAddons()); } + + public function testReloadAddonsInstallsAddon(): 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' => <<getChild($addonName . '/' . $addonName . '.php')->lastModified(1234567890); + + $config = $this->createStub(IManageConfigValues::class); + $config->method('get')->willReturn([ + $addonName => [ + 'last_update' => 0, + '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([$addonName], $addonManagerHelper->getEnabledAddons()); + + $this->expectException(Exception::class); + $this->expectExceptionMessage('Addon reinstalled'); + + $addonManagerHelper->reloadAddons(); + } }