diff --git a/core/Controller/NavigationController.php b/core/Controller/NavigationController.php new file mode 100644 index 0000000000..3521fac3b4 --- /dev/null +++ b/core/Controller/NavigationController.php @@ -0,0 +1,92 @@ + + * + * @author Julius Härtl + * + * @license GNU AGPL version 3 or any later version + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + * + */ +namespace OC\Core\Controller; + +use OCP\AppFramework\Http\DataResponse; +use OCP\AppFramework\OCSController; +use OCP\INavigationManager; +use OCP\IRequest; +use OCP\IURLGenerator; + +class NavigationController extends OCSController { + + /** @var INavigationManager */ + private $navigationManager; + + /** @var IURLGenerator */ + private $urlGenerator; + + public function __construct(string $appName, IRequest $request, INavigationManager $navigationManager, IURLGenerator $urlGenerator) { + parent::__construct($appName, $request); + $this->navigationManager = $navigationManager; + $this->urlGenerator = $urlGenerator; + } + + /** + * @NoAdminRequired + * @NoCSRFRequired + * + * @param bool $absolute + * @return DataResponse + */ + public function getAppsNavigation(bool $absolute = false): DataResponse { + $navigation = $this->navigationManager->getAll(); + if ($absolute) { + $navigation = $this->rewriteToAbsoluteUrls($navigation); + } + return new DataResponse($navigation); + } + + /** + * @NoAdminRequired + * @NoCSRFRequired + * + * @param bool $absolute + * @return DataResponse + */ + public function getSettingsNavigation(bool $absolute = false): DataResponse { + $navigation = $this->navigationManager->getAll('settings'); + if ($absolute) { + $navigation = $this->rewriteToAbsoluteUrls($navigation); + } + return new DataResponse($navigation); + } + + /** + * Rewrite href attribute of navigation entries to an absolute URL + * + * @param array $navigation + * @return array + */ + private function rewriteToAbsoluteUrls(array $navigation): array { + foreach ($navigation as &$entry) { + if (0 !== strpos($entry['href'], $this->urlGenerator->getBaseUrl())) { + $entry['href'] = $this->urlGenerator->getAbsoluteURL($entry['href']); + } + if (0 !== strpos($entry['icon'], $this->urlGenerator->getBaseUrl())) { + $entry['icon'] = $this->urlGenerator->getAbsoluteURL($entry['icon']); + } + } + return $navigation; + } +} diff --git a/core/routes.php b/core/routes.php index 0eed8ed179..97a8621fc3 100644 --- a/core/routes.php +++ b/core/routes.php @@ -71,6 +71,8 @@ $application->registerRoutes($this, [ ['root' => '', 'name' => 'OCS#getConfig', 'url' => '/config', 'verb' => 'GET'], ['root' => '/person', 'name' => 'OCS#personCheck', 'url' => '/check', 'verb' => 'POST'], ['root' => '/identityproof', 'name' => 'OCS#getIdentityProof', 'url' => '/key/{cloudId}', 'verb' => 'GET'], + ['root' => '/core', 'name' => 'Navigation#getAppsNavigation', 'url' => '/navigation/apps', 'verb' => 'GET'], + ['root' => '/core', 'name' => 'Navigation#getSettingsNavigation', 'url' => '/navigation/settings', 'verb' => 'GET'], ], ]); diff --git a/lib/composer/composer/autoload_classmap.php b/lib/composer/composer/autoload_classmap.php index 3eb532be0c..06879c5179 100644 --- a/lib/composer/composer/autoload_classmap.php +++ b/lib/composer/composer/autoload_classmap.php @@ -521,6 +521,7 @@ return array( 'OC\\Core\\Controller\\JsController' => $baseDir . '/core/Controller/JsController.php', 'OC\\Core\\Controller\\LoginController' => $baseDir . '/core/Controller/LoginController.php', 'OC\\Core\\Controller\\LostController' => $baseDir . '/core/Controller/LostController.php', + 'OC\\Core\\Controller\\NavigationController' => $baseDir . '/core/Controller/NavigationController.php', 'OC\\Core\\Controller\\OCJSController' => $baseDir . '/core/Controller/OCJSController.php', 'OC\\Core\\Controller\\OCSController' => $baseDir . '/core/Controller/OCSController.php', 'OC\\Core\\Controller\\PreviewController' => $baseDir . '/core/Controller/PreviewController.php', diff --git a/lib/composer/composer/autoload_static.php b/lib/composer/composer/autoload_static.php index 2483b5689b..118094c30e 100644 --- a/lib/composer/composer/autoload_static.php +++ b/lib/composer/composer/autoload_static.php @@ -551,6 +551,7 @@ class ComposerStaticInit53792487c5a8370acc0b06b1a864ff4c 'OC\\Core\\Controller\\JsController' => __DIR__ . '/../../..' . '/core/Controller/JsController.php', 'OC\\Core\\Controller\\LoginController' => __DIR__ . '/../../..' . '/core/Controller/LoginController.php', 'OC\\Core\\Controller\\LostController' => __DIR__ . '/../../..' . '/core/Controller/LostController.php', + 'OC\\Core\\Controller\\NavigationController' => __DIR__ . '/../../..' . '/core/Controller/NavigationController.php', 'OC\\Core\\Controller\\OCJSController' => __DIR__ . '/../../..' . '/core/Controller/OCJSController.php', 'OC\\Core\\Controller\\OCSController' => __DIR__ . '/../../..' . '/core/Controller/OCSController.php', 'OC\\Core\\Controller\\PreviewController' => __DIR__ . '/../../..' . '/core/Controller/PreviewController.php', diff --git a/lib/private/NavigationManager.php b/lib/private/NavigationManager.php index 31d147a3b8..279c899c5f 100644 --- a/lib/private/NavigationManager.php +++ b/lib/private/NavigationManager.php @@ -104,26 +104,61 @@ class NavigationManager implements INavigationManager { } /** - * returns all the added Menu entries - * @param string $type - * @return array an array of the added entries + * Get a list of navigation entries + * + * @param string $type type of the navigation entries + * @return array */ - public function getAll($type = 'link') { + public function getAll(string $type = 'link'): array { $this->init(); foreach ($this->closureEntries as $c) { $this->add($c()); } $this->closureEntries = array(); - if ($type === 'all') { - return $this->entries; + $result = $this->entries; + if ($type !== 'all') { + $result = array_filter($this->entries, function($entry) use ($type) { + return $entry['type'] === $type; + }); } - return array_filter($this->entries, function($entry) use ($type) { - return $entry['type'] === $type; - }); + return $this->proceedNavigation($result); } + /** + * Sort navigation entries by order, name and set active flag + * + * @param array $list + * @return array + */ + private function proceedNavigation(array $list): array { + usort($list, function($a, $b) { + if (isset($a['order']) && isset($b['order'])) { + return ($a['order'] < $b['order']) ? -1 : 1; + } else if (isset($a['order']) || isset($b['order'])) { + return isset($a['order']) ? -1 : 1; + } else { + return ($a['name'] < $b['name']) ? -1 : 1; + } + }); + + $activeApp = $this->getActiveEntry(); + if ($activeApp !== null) { + foreach ($list as $index => &$navEntry) { + if ($navEntry['id'] == $activeApp) { + $navEntry['active'] = true; + } else { + $navEntry['active'] = false; + } + } + unset($navEntry); + } + + return $list; + } + + /** * removes all the entries */ diff --git a/lib/private/TemplateLayout.php b/lib/private/TemplateLayout.php index d37a8bbabb..8cc235bf81 100644 --- a/lib/private/TemplateLayout.php +++ b/lib/private/TemplateLayout.php @@ -80,9 +80,9 @@ class TemplateLayout extends \OC_Template { // Add navigation entry $this->assign( 'application', ''); $this->assign( 'appid', $appId ); - $navigation = \OC_App::getNavigation(); + $navigation = \OC::$server->getNavigationManager()->getAll(); $this->assign( 'navigation', $navigation); - $settingsNavigation = \OC_App::getSettingsNavigation(); + $settingsNavigation = \OC::$server->getNavigationManager()->getAll('settings'); $this->assign( 'settingsnavigation', $settingsNavigation); foreach($navigation as $entry) { if ($entry['active']) { diff --git a/lib/private/legacy/app.php b/lib/private/legacy/app.php index 9384582bac..f97b74ed24 100644 --- a/lib/private/legacy/app.php +++ b/lib/private/legacy/app.php @@ -445,31 +445,6 @@ class OC_App { $appManager->disableApp($app); } - // This is private as well. It simply works, so don't ask for more details - private static function proceedNavigation($list) { - usort($list, function($a, $b) { - if (isset($a['order']) && isset($b['order'])) { - return ($a['order'] < $b['order']) ? -1 : 1; - } else if (isset($a['order']) || isset($b['order'])) { - return isset($a['order']) ? -1 : 1; - } else { - return ($a['name'] < $b['name']) ? -1 : 1; - } - }); - - $activeApp = OC::$server->getNavigationManager()->getActiveEntry(); - foreach ($list as $index => &$navEntry) { - if ($navEntry['id'] == $activeApp) { - $navEntry['active'] = true; - } else { - $navEntry['active'] = false; - } - } - unset($navEntry); - - return $list; - } - /** * Get the path where to install apps * @@ -613,6 +588,7 @@ class OC_App { * Returns the navigation * * @return array + * @deprecated 14.0.0 use \OC::$server->getNavigationManager()->getAll() * * This function returns an array containing all entries added. The * entries are sorted by the key 'order' ascending. Additional to the keys @@ -620,21 +596,20 @@ class OC_App { * - active: boolean, signals if the user is on this navigation entry */ public static function getNavigation() { - $entries = OC::$server->getNavigationManager()->getAll(); - return self::proceedNavigation($entries); + return OC::$server->getNavigationManager()->getAll(); } /** * Returns the Settings Navigation * * @return string[] + * @deprecated 14.0.0 use \OC::$server->getNavigationManager()->getAll('settings') * * This function returns an array containing all settings pages added. The * entries are sorted by the key 'order' ascending. */ public static function getSettingsNavigation() { - $entries = OC::$server->getNavigationManager()->getAll('settings'); - return self::proceedNavigation($entries); + return OC::$server->getNavigationManager()->getAll('settings'); } /** diff --git a/lib/public/INavigationManager.php b/lib/public/INavigationManager.php index 216f8a897d..77b881b8b1 100644 --- a/lib/public/INavigationManager.php +++ b/lib/public/INavigationManager.php @@ -57,4 +57,13 @@ interface INavigationManager { * @since 6.0.0 */ public function setActiveEntry($appId); + + /** + * Get a list of navigation entries + * + * @param string $type type of the navigation entries + * @return array + * @since 14.0.0 + */ + public function getAll(string $type = 'link'): array; } diff --git a/settings/ajax/navigationdetect.php b/settings/ajax/navigationdetect.php index 167f819f91..043e10da55 100644 --- a/settings/ajax/navigationdetect.php +++ b/settings/ajax/navigationdetect.php @@ -23,6 +23,6 @@ OC_Util::checkAdminUser(); OCP\JSON::callCheck(); -$navigation = \OC_App::getNavigation(); +$navigation = \OC::$server->getNavigationManager()->getAll(); OCP\JSON::success(['nav_entries' => $navigation]); diff --git a/tests/Core/Controller/NavigationControllerTest.php b/tests/Core/Controller/NavigationControllerTest.php new file mode 100644 index 0000000000..1143ed003f --- /dev/null +++ b/tests/Core/Controller/NavigationControllerTest.php @@ -0,0 +1,129 @@ + + * + * @author Julius Härtl + * + * @license GNU AGPL version 3 or any later version + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + * + */ +namespace Tests\Core\Controller; + +use OC\Core\Controller\NavigationController; +use OCP\AppFramework\Http\DataResponse; +use OCP\INavigationManager; +use OCP\IRequest; +use OCP\IURLGenerator; +use Test\TestCase; + +class NavigationControllerTest extends TestCase { + + /** @var IRequest|\PHPUnit_Framework_MockObject_MockObject */ + private $request; + + /** @var INavigationManager|\PHPUnit_Framework_MockObject_MockObject */ + private $navigationManager; + + /** @var IURLGenerator|\PHPUnit_Framework_MockObject_MockObject */ + private $urlGenerator; + + /** @var NavigationController */ + private $controller; + + public function setUp() { + parent::setUp(); + + $this->request = $this->createMock(IRequest::class); + $this->navigationManager = $this->createMock(INavigationManager::class); + $this->urlGenerator = $this->createMock(IURLGenerator::class); + + $this->controller = new NavigationController( + 'core', + $this->request, + $this->navigationManager, + $this->urlGenerator + ); + } + + public function dataGetNavigation() { + return [ + [false], [true] + ]; + } + /** @dataProvider dataGetNavigation */ + public function testGetAppNavigation($absolute) { + $this->navigationManager->expects($this->once()) + ->method('getAll') + ->with('link') + ->willReturn([ ['id' => 'files', 'href' => '/index.php/apps/files', 'icon' => 'icon' ] ]); + if ($absolute) { + $this->urlGenerator->expects($this->any()) + ->method('getBaseURL') + ->willReturn('http://localhost/'); + $this->urlGenerator->expects($this->at(1)) + ->method('getAbsoluteURL') + ->with('/index.php/apps/files') + ->willReturn('http://localhost/index.php/apps/files'); + $this->urlGenerator->expects($this->at(3)) + ->method('getAbsoluteURL') + ->with('icon') + ->willReturn('http://localhost/icon'); + $actual = $this->controller->getAppsNavigation($absolute); + $this->assertInstanceOf(DataResponse::class, $actual); + $this->assertEquals('http://localhost/index.php/apps/files', $actual->getData()[0]['href']); + $this->assertEquals('http://localhost/icon', $actual->getData()[0]['icon']); + + + } else { + $actual = $this->controller->getAppsNavigation($absolute); + $this->assertInstanceOf(DataResponse::class, $actual); + $this->assertEquals('/index.php/apps/files', $actual->getData()[0]['href']); + $this->assertEquals('icon', $actual->getData()[0]['icon']); + + } + } + + /** @dataProvider dataGetNavigation */ + public function testGetSettingsNavigation($absolute) { + $this->navigationManager->expects($this->once()) + ->method('getAll') + ->with('settings') + ->willReturn([ ['id' => 'settings', 'href' => '/index.php/settings/user', 'icon' => '/core/img/settings.svg'] ]); + if ($absolute) { + $this->urlGenerator->expects($this->any()) + ->method('getBaseURL') + ->willReturn('http://localhost/'); + $this->urlGenerator->expects($this->at(1)) + ->method('getAbsoluteURL') + ->with('/index.php/settings/user') + ->willReturn('http://localhost/index.php/settings/user'); + $this->urlGenerator->expects($this->at(3)) + ->method('getAbsoluteURL') + ->with('/core/img/settings.svg') + ->willReturn('http://localhost/core/img/settings.svg'); + $actual = $this->controller->getSettingsNavigation($absolute); + $this->assertInstanceOf(DataResponse::class, $actual); + $this->assertEquals('http://localhost/index.php/settings/user', $actual->getData()[0]['href']); + $this->assertEquals('http://localhost/core/img/settings.svg', $actual->getData()[0]['icon']); + } else { + $actual = $this->controller->getSettingsNavigation($absolute); + $this->assertInstanceOf(DataResponse::class, $actual); + $this->assertEquals('/index.php/settings/user', $actual->getData()[0]['href']); + $this->assertEquals('/core/img/settings.svg', $actual->getData()[0]['icon']); + } + } + +} diff --git a/tests/lib/NavigationManagerTest.php b/tests/lib/NavigationManagerTest.php index 585161c887..b34ecca946 100644 --- a/tests/lib/NavigationManagerTest.php +++ b/tests/lib/NavigationManagerTest.php @@ -284,7 +284,7 @@ class NavigationManagerTest extends TestCase { ], ]; return [ - 'minimalistic' => [array_merge($defaults, [[ + 'minimalistic' => [array_merge([$defaults[0]], [[ 'id' => 'test', 'order' => 100, 'href' => '/apps/test/', @@ -293,8 +293,8 @@ class NavigationManagerTest extends TestCase { 'active' => false, 'type' => 'link', 'classes' => '', - ]]), ['navigations' => [['route' => 'test.page.index', 'name' => 'Test']]]], - 'minimalistic-settings' => [array_merge($defaults, [[ + ]], [$defaults[1]]), ['navigations' => [['route' => 'test.page.index', 'name' => 'Test']]]], + 'minimalistic-settings' => [array_merge([$defaults[0]], [[ 'id' => 'test', 'order' => 100, 'href' => '/apps/test/', @@ -303,8 +303,8 @@ class NavigationManagerTest extends TestCase { 'active' => false, 'type' => 'settings', 'classes' => '', - ]]), ['navigations' => [['route' => 'test.page.index', 'name' => 'Test', 'type' => 'settings']]]], - 'admin' => [array_merge($apps, $defaults, [[ + ]], [$defaults[1]]), ['navigations' => [['route' => 'test.page.index', 'name' => 'Test', 'type' => 'settings']]]], + 'admin' => [array_merge([$defaults[0]], $apps, [[ 'id' => 'test', 'order' => 100, 'href' => '/apps/test/', @@ -313,8 +313,8 @@ class NavigationManagerTest extends TestCase { 'active' => false, 'type' => 'link', 'classes' => '', - ]]), ['navigations' => [['@attributes' => ['role' => 'admin'], 'route' => 'test.page.index', 'name' => 'Test']]], true], - 'no name' => [array_merge($apps, $defaults), ['navigations' => [['@attributes' => ['role' => 'admin'], 'route' => 'test.page.index']]], true], + ]], [$defaults[1]]), ['navigations' => [['@attributes' => ['role' => 'admin'], 'route' => 'test.page.index', 'name' => 'Test']]], true], + 'no name' => [array_merge([$defaults[0]], $apps, [$defaults[1]]), ['navigations' => [['@attributes' => ['role' => 'admin'], 'route' => 'test.page.index']]], true], 'no admin' => [$defaults, ['navigations' => [['@attributes' => ['role' => 'admin'], 'route' => 'test.page.index', 'name' => 'Test']]]] ]; }