diff --git a/UPGRADE.md b/UPGRADE.md index 895e05b..aab4132 100644 --- a/UPGRADE.md +++ b/UPGRADE.md @@ -8,6 +8,7 @@ Just click the "update" button or execute the migration command to finish the bu #### Update from Version 3.1.3 to Version 3.1.4 - **[ENHANCEMENT]**: Improving and adding additional Events for Restriction Changes on Entities ([#148](https://github.com/dachcom-digital/pimcore-members/issues/148)) +- **[ENHANCEMENT]**: Update Twig navigation to allow parameters ([@kjkooistra-youwe](https://github.com/dachcom-digital/pimcore-members/pull/147)) #### Update from Version 3.1.2 to Version 3.1.3 - **[ENHANCEMENT]**: Pimcore 6.6.5 ready diff --git a/docs/210_RestrictedNavigation.md b/docs/210_RestrictedNavigation.md index 797301a..c4ec234 100644 --- a/docs/210_RestrictedNavigation.md +++ b/docs/210_RestrictedNavigation.md @@ -2,12 +2,81 @@ Now - since you have restricted some documents to certain groups, you need to manipulate the pimcore navigation renderer. -**Navigation:** Do **not** use the default nav builder extension (`pimcore_build_nav`). Just use the `members_build_nav` to build secure menus. -Otherwise your restricted pages will show up. This twig extension will also handle your navigation cache strategy. +**Navigation:** Do **not** use the default nav builder extension (`pimcore_build_nav`). Just use the `members_build_nav` to build +secure menus. Otherwise your restricted pages will show up. This twig extension will also handle your navigation cache strategy. + +## Usage -### Usage - ```twig -{% set nav = members_build_nav(currentDoc, documentRootDoc, null, true) %} +{% set nav = members_build_nav({ + active: currentDoc, + root: documentRootDoc +}) %} + {{ pimcore_render_nav(nav, 'menu', 'renderMenu', { maxDepth: 2 }) }} ``` + +### Page callback parameter + +The `pageCallback` parameter is 'merged' with the Members bundle restriction access callback, allowing you to set custom data to +the navigation document. Note that the `ElementRestriction` argument is also passed on to the callback function. + +```twig +{% set nav = members_build_nav({ + active: document, + root: rootPage, + pageCallback: navigation_callback() +}) %} +``` + +As you cannot use closures directly within Twig templates, create a function to return one. + +```php +setCustomSetting('key', $page->getKey()); + } + }; + } +} +``` + +To retrieve the setting in the template: + +```twig +{{ page.getCustomSetting('key') }} +``` diff --git a/src/MembersBundle/Twig/Extension/NavigationExtension.php b/src/MembersBundle/Twig/Extension/NavigationExtension.php index f355684..7789013 100644 --- a/src/MembersBundle/Twig/Extension/NavigationExtension.php +++ b/src/MembersBundle/Twig/Extension/NavigationExtension.php @@ -6,6 +6,7 @@ use MembersBundle\Adapter\User\UserInterface; use MembersBundle\Manager\RestrictionManager; use MembersBundle\Manager\RestrictionManagerInterface; +use MembersBundle\Restriction\ElementRestriction; use Pimcore\Model\AbstractModel; use Pimcore\Model\Document; use Pimcore\Navigation\Container; @@ -14,6 +15,9 @@ use Twig\Extension\AbstractExtension; use Twig\TwigFunction; +/** + * @see \Pimcore\Twig\Extension\NavigationExtension + */ class NavigationExtension extends AbstractExtension { /** @@ -57,56 +61,122 @@ public function getFunctions(): array } /** - * @param Document $activeDocument + * @see \Pimcore\Twig\Extension\NavigationExtension::buildNavigation() + * @param array|Document $params config array or active document (legacy mode) * @param Document|null $navigationRootDocument - * @param string|null $htmlMenuPrefix - * @param bool $cache - * + * @param string|null $htmlMenuPrefix + * @param bool|string $cache * @return Container */ public function buildNavigation( - Document $activeDocument, + $params = null, Document $navigationRootDocument = null, string $htmlMenuPrefix = null, $cache = true ): Container { + if (is_array($params)) { + return $this->buildMembersNavigation($params); + } + + // using deprecated argument configuration ($params = navigation root document) + return $this->legacyBuildNavigation( + $params, + $navigationRootDocument, + $htmlMenuPrefix, + $cache + ); + } + + protected function buildMembersNavigation(array $params): Container + { + // Update cache key and page callback + $params['cache'] = $this->getCacheKey($params['cache'] ?? true); + $params['pageCallback'] = $this->getPageCallback($params['pageCallback'] ?? null); + + if (!method_exists($this->navigationHelper, 'build')) { + throw new \Exception( + 'Navigation::build() unavailable, update your Pimcore version to >= 6.5', + 1605864272 + ); + } + + return $this->navigationHelper->build($params); + } + + /** + * @param bool|string $cache + * @return bool|string + */ + protected function getCacheKey($cache) + { $cacheKey = $cache; $user = $this->tokenStorage->getToken() ? $this->tokenStorage->getToken()->getUser() : null; - if (!\Pimcore\Tool::isFrontendRequestByAdmin() && $cacheKey !== false) { - $mergedCacheKey = is_bool($cache) ? '' : $cache; + if (\Pimcore\Tool::isFrontendRequestByAdmin() || $cacheKey === false || !($user instanceof UserInterface)) { + return $cacheKey; + } + + $allowedGroups = $user->getGroups(); + $groupIds = []; + if (!empty($allowedGroups)) { + /** @var GroupInterface $group */ + foreach ($allowedGroups as $group) { + $groupIds[] = $group->getId(); + } + + if (!empty($groupIds)) { + $mergedCacheKey = is_bool($cache) ? '' : $cache; + $cacheKey = ltrim($mergedCacheKey . '-' . implode('-', $groupIds), '-'); + } + } - if ($user instanceof UserInterface) { - $allowedGroups = $user->getGroups(); + return $cacheKey; + } - $groupIds = []; - if (!empty($allowedGroups)) { - /** @var GroupInterface $group */ - foreach ($allowedGroups as $group) { - $groupIds[] = $group->getId(); - } + protected function getPageCallback(?\Closure $additionalClosure = null): \Closure + { + return function (\Pimcore\Navigation\Page\Document $document, AbstractModel $page) use ($additionalClosure) { + $restrictionElement = $this->applyPageRestrictions($document, $page); - if (!empty($groupIds)) { - $cacheKey = ltrim($mergedCacheKey . '-' . implode('-', $groupIds), '-'); - } - } + // Call additional closure if configured and also pass restriction element as additional argument + if ($additionalClosure !== null) { + $additionalClosure->call($this, $document, $page, $restrictionElement); } + + return $page; + }; + } + + protected function applyPageRestrictions(\Pimcore\Navigation\Page\Document $document, AbstractModel $page): ElementRestriction + { + $restrictionElement = $this->restrictionManager->getElementRestrictionStatus($page); + if ($restrictionElement->getSection() !== RestrictionManager::RESTRICTION_SECTION_ALLOWED) { + $document->setActive(false); + $document->setVisible(false); } + return $restrictionElement; + } + + /** + * @param Document $activeDocument + * @param Document|null $navigationRootDocument + * @param string|null $htmlMenuPrefix + * @param bool|string $cache + * @return Container + */ + protected function legacyBuildNavigation( + Document $activeDocument, + ?Document $navigationRootDocument = null, + ?string $htmlMenuPrefix = null, + $cache = true + ): Container { return $this->navigationHelper->buildNavigation( $activeDocument, $navigationRootDocument, $htmlMenuPrefix, - function (\Pimcore\Navigation\Page\Document $document, AbstractModel $page) { - $restrictionElement = $this->restrictionManager->getElementRestrictionStatus($page); - if ($restrictionElement->getSection() !== RestrictionManager::RESTRICTION_SECTION_ALLOWED) { - $document->setActive(false); - $document->setVisible(false); - } - - return $page; - }, - $cacheKey + $this->getPageCallback(), + $this->getCacheKey($cache) ); } } diff --git a/tests/_etc/config.yml b/tests/_etc/config.yml index 899882c..1c95b09 100644 --- a/tests/_etc/config.yml +++ b/tests/_etc/config.yml @@ -8,6 +8,7 @@ setup_files: - { path: app/views/default.html.twig, dest: ./app/Resources/views/Default/default.html.twig } - { path: app/views/snippet.html.twig, dest: ./app/Resources/views/Default/snippet.html.twig } - { path: app/views/staticRoute.html.twig, dest: ./app/Resources/views/Default/staticRoute.html.twig } + - { path: app/views/navigation.html.twig, dest: ./app/Resources/views/Default/navigation.html.twig } preload_files: - { path: Services/TestRestrictedStaticRouteListener.php } additional_composer_packages: diff --git a/tests/_etc/config/app/controller/DefaultController.php b/tests/_etc/config/app/controller/DefaultController.php index b863557..00b9ed3 100644 --- a/tests/_etc/config/app/controller/DefaultController.php +++ b/tests/_etc/config/app/controller/DefaultController.php @@ -24,4 +24,8 @@ public function snippetAction(Request $request) public function staticRouteAction(Request $request) { } + + public function navigationAction(Request $request) + { + } } diff --git a/tests/_etc/config/app/views/navigation.html.twig b/tests/_etc/config/app/views/navigation.html.twig new file mode 100644 index 0000000..3c55a8e --- /dev/null +++ b/tests/_etc/config/app/views/navigation.html.twig @@ -0,0 +1,29 @@ + + + + + Test Page for Members + + +
+ {% block content %} + + + +
+ {% set nav2 = members_build_nav(pimcore_document(1), pimcore_document(1), null, true) %} + {{ pimcore_render_nav(nav2, 'menu', 'renderMenu', { maxDepth: 2 }) }} +
+ + {% endblock %} +
+ + diff --git a/tests/_support/Helper/Members.php b/tests/_support/Helper/Members.php index b9e36d7..c709741 100644 --- a/tests/_support/Helper/Members.php +++ b/tests/_support/Helper/Members.php @@ -81,11 +81,12 @@ public function haveAFrontendUserGroup(string $name = 'Group 1') * * @param bool $confirmed * @param array $groups + * @param array $additionalParameter * * @return mixed * @throws ModuleException */ - public function haveARegisteredFrontEndUser(bool $confirmed = false, array $groups = []) + public function haveARegisteredFrontEndUser(bool $confirmed = false, array $groups = [], array $additionalParameter = []) { $configuration = $this->getContainer()->get(Configuration::class); $membersStoreObject = DataObject::getByPath($configuration->getConfig('storage_path')); @@ -99,6 +100,12 @@ public function haveARegisteredFrontEndUser(bool $confirmed = false, array $grou $userObject->setPlainPassword(MembersHelper::DEFAULT_FEU_PASSWORD); $userObject->setPublished(false); + if (count($additionalParameter) > 0) { + foreach ($additionalParameter as $additionalParam => $additionalParamValue) { + $userObject->setObjectVar($additionalParam, $additionalParamValue); + } + } + $user = $userManager->updateUser($userObject); if (count($groups) > 0) { diff --git a/tests/functional/Frontend/Navigation/RestrictedNavigationCest.php b/tests/functional/Frontend/Navigation/RestrictedNavigationCest.php new file mode 100644 index 0000000..c233075 --- /dev/null +++ b/tests/functional/Frontend/Navigation/RestrictedNavigationCest.php @@ -0,0 +1,115 @@ +haveAFrontendUserGroup('group-1'); + + $document1 = $I->haveAPageDocument('document-1', ['action' => 'navigation']); + $document2 = $I->haveAPageDocument('document-2', ['action' => 'navigation']); + + $I->addRestrictionToDocument($document2, [$group1->getId()], true, false); + + $I->amOnPage('/document-1'); + + $I->seeElement('div.nav a[title="document-1"]'); + $I->dontSeeElement('div.nav a[title="document-2"]'); + } + + /** + * @param FunctionalTester $I + */ + public function testNavigationWithLogin(FunctionalTester $I) + { + $group1 = $I->haveAFrontendUserGroup('group-1'); + $user = $I->haveARegisteredFrontEndUser(true, [$group1]); + + $document1 = $I->haveAPageDocument('document-1', ['action' => 'navigation']); + $document2 = $I->haveAPageDocument('document-2', ['action' => 'navigation']); + + $I->addRestrictionToDocument($document2, [$group1->getId()], true, false); + + $I->amLoggedInAsFrontendUser($user, 'members_fe'); + $I->amOnPage('/document-1'); + + $I->seeElement('div.nav a[title="document-1"]'); + $I->seeElement('div.nav a[title="document-2"]'); + } + + /** + * @param FunctionalTester $I + */ + public function testNavigationWithSwitchedUserLogin(FunctionalTester $I) + { + $group1 = $I->haveAFrontendUserGroup('group-1'); + $group2 = $I->haveAFrontendUserGroup('group-2'); + + $document1 = $I->haveAPageDocument('document-1', ['action' => 'navigation']); + $document2 = $I->haveAPageDocument('document-2', ['action' => 'navigation']); + + $user1 = $I->haveARegisteredFrontEndUser(true, [$group1]); + $user2 = $I->haveARegisteredFrontEndUser(true, [$group2], ['email' => 'second@universe.org', 'userName' => 'norris']); + + $I->addRestrictionToDocument($document1, [$group1->getId()], true, false); + $I->addRestrictionToDocument($document2, [$group2->getId()], true, false); + + $I->amLoggedInAsFrontendUser($user1, 'members_fe'); + $I->amOnPage('/document-1'); + + $I->seeElement('div.nav a[title="document-1"]'); + $I->dontSeeElement('div.nav a[title="document-2"]'); + + $I->amLoggedInAsFrontendUser($user2, 'members_fe'); + $I->amOnPage('/document-2'); + + $I->seeElement('div.nav a[title="document-2"]'); + $I->dontSeeElement('div.nav a[title="document-1"]'); + } + + /** + * @param FunctionalTester $I + */ + public function testLegacyNavigationWithoutLogin(FunctionalTester $I) + { + $group1 = $I->haveAFrontendUserGroup('group-1'); + + $document1 = $I->haveAPageDocument('document-1', ['action' => 'navigation']); + $document2 = $I->haveAPageDocument('document-2', ['action' => 'navigation']); + + $I->addRestrictionToDocument($document2, [$group1->getId()], true, false); + + $I->amOnPage('/document-1'); + + $I->seeElement('div.legacy-nav a[title="document-1"]'); + $I->dontSeeElement('div.legacy-nav a[title="document-2"]'); + } + + /** + * @param FunctionalTester $I + */ + public function testLegacyNavigationWithLogin(FunctionalTester $I) + { + $group1 = $I->haveAFrontendUserGroup('group-1'); + $user = $I->haveARegisteredFrontEndUser(true, [$group1]); + + $document1 = $I->haveAPageDocument('document-1', ['action' => 'navigation']); + $document2 = $I->haveAPageDocument('document-2', ['action' => 'navigation']); + + $I->addRestrictionToDocument($document2, [$group1->getId()], true, false); + + $I->amLoggedInAsFrontendUser($user, 'members_fe'); + $I->amOnPage('/document-1'); + + $I->seeElement('div.legacy-nav a[title="document-1"]'); + $I->seeElement('div.legacy-nav a[title="document-2"]'); + } + +} \ No newline at end of file