Skip to content

Commit

Permalink
refactor: update Twig navigation to allow parameters (#147)
Browse files Browse the repository at this point in the history
* refactor: update Twig navigation to allow parameters

Navigation was still using the deprecated buildNavigation method.
In addition, it was impossible to configure custom parameters, which is
possible when you use the Pimcore build_nav Twig function.

The logic was refactored to work similar to the Pimcore build_nav. The old
deprecated setup still works, but parameters can (and should) be used now.
In addition, when configuring the pageCallback parameter, it is now called
within the members callback, which also forwards the restriction element,
allowing you to override the restriction settings and apply other logic.

* [test] add restricted navigation tests
  • Loading branch information
kjkooistra-youwe authored Jan 15, 2021
1 parent 6e9081c commit f3ede63
Show file tree
Hide file tree
Showing 8 changed files with 331 additions and 35 deletions.
1 change: 1 addition & 0 deletions UPGRADE.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
79 changes: 74 additions & 5 deletions docs/210_RestrictedNavigation.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
<?php

declare(strict_types=1);

namespace AppBundle\Twig\Extension;

use MembersBundle\Restriction\ElementRestriction;
use Pimcore\Model\AbstractModel;
use Pimcore\Model\Document;
use Twig\Extension\AbstractExtension;
use Twig\TwigFunction;

class NavigationExtension extends AbstractExtension
{
/**
* {@inheritdoc}
* @return TwigFunction[]
*/
public function getFunctions(): array
{
return [
new TwigFunction(
'navigation_callback',
[$this, 'getNavigationCallback']
),
];
}

public function getNavigationCallback(): \Closure
{
return function (
\Pimcore\Navigation\Page\Document $document,
AbstractModel $page,
ElementRestriction $elementRestriction
): void {
if ($page instanceof Document) {
$document->setCustomSetting('key', $page->getKey());
}
};
}
}
```

To retrieve the setting in the template:

```twig
{{ page.getCustomSetting('key') }}
```
128 changes: 99 additions & 29 deletions src/MembersBundle/Twig/Extension/NavigationExtension.php
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -14,6 +15,9 @@
use Twig\Extension\AbstractExtension;
use Twig\TwigFunction;

/**
* @see \Pimcore\Twig\Extension\NavigationExtension
*/
class NavigationExtension extends AbstractExtension
{
/**
Expand Down Expand Up @@ -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)
);
}
}
1 change: 1 addition & 0 deletions tests/_etc/config.yml
Original file line number Diff line number Diff line change
Expand Up @@ -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:
Expand Down
4 changes: 4 additions & 0 deletions tests/_etc/config/app/controller/DefaultController.php
Original file line number Diff line number Diff line change
Expand Up @@ -24,4 +24,8 @@ public function snippetAction(Request $request)
public function staticRouteAction(Request $request)
{
}

public function navigationAction(Request $request)
{
}
}
29 changes: 29 additions & 0 deletions tests/_etc/config/app/views/navigation.html.twig
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Test Page for Members</title>
</head>
<body>
<div id="site">
{% block content %}

<div class="nav">

{% set nav = members_build_nav({
active: pimcore_document(1),
root: pimcore_document(1)
}) %}

{{ pimcore_render_nav(nav, 'menu', 'renderMenu', { maxDepth: 2 }) }}
</div>

<div class="legacy-nav">
{% set nav2 = members_build_nav(pimcore_document(1), pimcore_document(1), null, true) %}
{{ pimcore_render_nav(nav2, 'menu', 'renderMenu', { maxDepth: 2 }) }}
</div>

{% endblock %}
</div>
</body>
</html>
9 changes: 8 additions & 1 deletion tests/_support/Helper/Members.php
Original file line number Diff line number Diff line change
Expand Up @@ -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'));
Expand All @@ -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) {
Expand Down
Loading

0 comments on commit f3ede63

Please sign in to comment.