diff --git a/IONOS b/IONOS index 3dc76d7819e43..3d870c64bb27b 160000 --- a/IONOS +++ b/IONOS @@ -1 +1 @@ -Subproject commit 3dc76d7819e4385a51e948d9468b1bac340c48b7 +Subproject commit 3d870c64bb27b778439108ddc3ddb524432f1be2 diff --git a/apps/settings/lib/Controller/AppSettingsController.php b/apps/settings/lib/Controller/AppSettingsController.php index c0ba0b556291b..d76408010da79 100644 --- a/apps/settings/lib/Controller/AppSettingsController.php +++ b/apps/settings/lib/Controller/AppSettingsController.php @@ -35,6 +35,7 @@ use OCP\Files\SimpleFS\ISimpleFile; use OCP\Files\SimpleFS\ISimpleFolder; use OCP\Http\Client\IClientService; +use OCP\IAppConfig; use OCP\IConfig; use OCP\IGroup; use OCP\IL10N; @@ -74,6 +75,7 @@ public function __construct( private IInitialState $initialState, private AppDiscoverFetcher $discoverFetcher, private IClientService $clientService, + private IAppConfig $appConfig, ) { parent::__construct($appName, $request); $this->appData = $appDataFactory->get('appstore'); @@ -90,7 +92,12 @@ public function viewApps(): TemplateResponse { $this->initialState->provideInitialState('appstoreEnabled', $this->config->getSystemValueBool('appstoreenabled', true)); $this->initialState->provideInitialState('appstoreBundles', $this->getBundles()); - $this->initialState->provideInitialState('appstoreDeveloperDocs', $this->urlGenerator->linkToDocs('developer-manual')); + + // Conditionally set developer docs link based on configuration + $displayDocumentationLink = $this->appConfig->getValueBool('settings', 'display_documentation_link', true); + $developerDocsUrl = $displayDocumentationLink ? $this->urlGenerator->linkToDocs('developer-manual') : ''; + $this->initialState->provideInitialState('appstoreDeveloperDocs', $developerDocsUrl); + $this->initialState->provideInitialState('appstoreUpdateCount', count($this->getAppsWithUpdates())); if ($this->appManager->isInstalled('app_api')) { diff --git a/apps/settings/src/views/AppStoreNavigation.vue b/apps/settings/src/views/AppStoreNavigation.vue index a35cd94da95fb..3507006c47d5b 100644 --- a/apps/settings/src/views/AppStoreNavigation.vue +++ b/apps/settings/src/views/AppStoreNavigation.vue @@ -91,7 +91,8 @@ - diff --git a/apps/settings/tests/Controller/AppSettingsControllerTest.php b/apps/settings/tests/Controller/AppSettingsControllerTest.php index f72bd45a3d234..3c0df4532d79f 100644 --- a/apps/settings/tests/Controller/AppSettingsControllerTest.php +++ b/apps/settings/tests/Controller/AppSettingsControllerTest.php @@ -19,6 +19,7 @@ use OCP\AppFramework\Services\IInitialState; use OCP\Files\AppData\IAppDataFactory; use OCP\Http\Client\IClientService; +use OCP\IAppConfig; use OCP\IConfig; use OCP\IL10N; use OCP\INavigationManager; @@ -45,6 +46,8 @@ class AppSettingsControllerTest extends TestCase { private $l10n; /** @var IConfig|MockObject */ private $config; + /** @var IAppConfig|MockObject */ + private $appConfig; /** @var INavigationManager|MockObject */ private $navigationManager; private AppManager&MockObject $appManager; @@ -81,6 +84,7 @@ protected function setUp(): void { ->method('t') ->willReturnArgument(0); $this->config = $this->createMock(IConfig::class); + $this->appConfig = $this->createMock(IAppConfig::class); $this->navigationManager = $this->createMock(INavigationManager::class); $this->appManager = $this->createMock(AppManager::class); $this->categoryFetcher = $this->createMock(CategoryFetcher::class); @@ -112,6 +116,7 @@ protected function setUp(): void { $this->initialState, $this->discoverFetcher, $this->clientService, + $this->appConfig, ); } @@ -180,14 +185,31 @@ public function testViewApps(): void { ->method('getSystemValueBool') ->with('appstoreenabled', true) ->willReturn(true); + $this->appConfig + ->expects($this->once()) + ->method('getValueBool') + ->with('settings', 'display_documentation_link', true) + ->willReturn(true); $this->navigationManager ->expects($this->once()) ->method('setActiveEntry') ->with('core_apps'); + // Test that developer docs link is generated correctly + $this->urlGenerator + ->expects($this->once()) + ->method('linkToDocs') + ->with('developer-manual') + ->willReturn('https://docs.nextcloud.com/server/latest/developer_manual/'); + $this->initialState ->expects($this->exactly(4)) - ->method('provideInitialState'); + ->method('provideInitialState') + ->willReturnCallback(function ($key, $value) { + if ($key === 'appstoreDeveloperDocs') { + $this->assertEquals('https://docs.nextcloud.com/server/latest/developer_manual/', $value); + } + }); $policy = new ContentSecurityPolicy(); $policy->addAllowedImageDomain('https://usercontent.apps.nextcloud.com'); @@ -213,14 +235,31 @@ public function testViewAppsAppstoreNotEnabled(): void { ->method('getSystemValueBool') ->with('appstoreenabled', true) ->willReturn(false); + $this->appConfig + ->expects($this->once()) + ->method('getValueBool') + ->with('settings', 'display_documentation_link', true) + ->willReturn(true); $this->navigationManager ->expects($this->once()) ->method('setActiveEntry') ->with('core_apps'); + // Test that developer docs link is still generated even when appstore is disabled + $this->urlGenerator + ->expects($this->once()) + ->method('linkToDocs') + ->with('developer-manual') + ->willReturn('https://docs.nextcloud.com/server/latest/developer_manual/'); + $this->initialState ->expects($this->exactly(4)) - ->method('provideInitialState'); + ->method('provideInitialState') + ->willReturnCallback(function ($key, $value) { + if ($key === 'appstoreDeveloperDocs') { + $this->assertEquals('https://docs.nextcloud.com/server/latest/developer_manual/', $value); + } + }); $policy = new ContentSecurityPolicy(); $policy->addAllowedImageDomain('https://usercontent.apps.nextcloud.com'); @@ -235,4 +274,88 @@ public function testViewAppsAppstoreNotEnabled(): void { $this->assertEquals($expected, $this->appSettingsController->viewApps()); } + + public function testDeveloperDocumentationLinkHiddenWhenConfigured(): void { + $this->installer->expects($this->any()) + ->method('isUpdateAvailable') + ->willReturn(false); + $this->bundleFetcher->expects($this->once())->method('getBundles')->willReturn([]); + $this->config + ->expects($this->once()) + ->method('getSystemValueBool') + ->with('appstoreenabled', true) + ->willReturn(true); + $this->appConfig + ->expects($this->once()) + ->method('getValueBool') + ->with('settings', 'display_documentation_link', true) + ->willReturn(false); + $this->navigationManager + ->expects($this->once()) + ->method('setActiveEntry') + ->with('core_apps'); + + // When display_documentation_link is false, linkToDocs should not be called + $this->urlGenerator + ->expects($this->never()) + ->method('linkToDocs'); + + $providedStates = []; + $this->initialState + ->expects($this->exactly(4)) + ->method('provideInitialState') + ->willReturnCallback(function ($key, $value) use (&$providedStates) { + $providedStates[$key] = $value; + }); + + $this->appSettingsController->viewApps(); + + // Assert that the developer docs state was provided with an empty string + $this->assertArrayHasKey('appstoreDeveloperDocs', $providedStates); + $this->assertEquals('', $providedStates['appstoreDeveloperDocs']); + } + + public function testDeveloperDocumentationLinkShownByDefault(): void { + $this->installer->expects($this->any()) + ->method('isUpdateAvailable') + ->willReturn(false); + $this->bundleFetcher->expects($this->once())->method('getBundles')->willReturn([]); + $this->config + ->expects($this->once()) + ->method('getSystemValueBool') + ->with('appstoreenabled', true) + ->willReturn(true); + $this->appConfig + ->expects($this->once()) + ->method('getValueBool') + ->with('settings', 'display_documentation_link', true) + ->willReturn(true); + $this->navigationManager + ->expects($this->once()) + ->method('setActiveEntry') + ->with('core_apps'); + + $developerDocsUrl = 'https://docs.nextcloud.com/server/latest/developer_manual/'; + + // When display_documentation_link is true (default), linkToDocs should be called + $this->urlGenerator + ->expects($this->once()) + ->method('linkToDocs') + ->with('developer-manual') + ->willReturn($developerDocsUrl); + + $providedStates = []; + $this->initialState + ->expects($this->exactly(4)) + ->method('provideInitialState') + ->willReturnCallback(function ($key, $value) use (&$providedStates) { + $providedStates[$key] = $value; + }); + + $this->appSettingsController->viewApps(); + + // Assert that the developer docs state was provided with the correct URL + $this->assertArrayHasKey('appstoreDeveloperDocs', $providedStates); + $this->assertEquals($developerDocsUrl, $providedStates['appstoreDeveloperDocs']); + } }