Skip to content
This repository was archived by the owner on Sep 16, 2021. It is now read-only.

Commit dae5e53

Browse files
committed
Merge pull request #175 from symfony-cmf/menu-event
Have event in menu factory to be more flexible how menu nodes are converted to menu items
2 parents 0e74d9a + 247928e commit dae5e53

File tree

9 files changed

+319
-68
lines changed

9 files changed

+319
-68
lines changed

CHANGELOG.md

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,10 @@
11
Changelog
22
=========
33

4+
* **2014-04-04**: The menu factory now raises an event when a menu item is
5+
built from a menu node. The event can be used to change the behaviour or
6+
skip building the menu item altogether.
7+
48
* **2014-03-24**: setParent() and getParent() are now deprecated.
59
Use setParentDocument() and getParentDocument() instead.
610
Moreover, you should now enable the ChildExtension from the CoreBundle.
@@ -16,8 +20,8 @@ Changelog
1620
$ php app/console doctrine:phpcr:nodes:update \
1721
--query="SELECT * FROM [nt:base] WHERE [phpcr:class] = 'Symfony\\Cmf\\Bundle\\MenuBundle\\Doctrine\\Phpcr\\Menu' OR [phpcr:class] = 'Symfony\\Cmf\\Bundle\\MenuBundle\\Doctrine\\Phpcr\\MenuNode'" \
1822
--apply-closure="$node->addMixin('mix:referenceable');"
19-
20-
* **2013-11-25**: [PublishWorkflow] added a `MenuContentVoter`, this voter
23+
24+
* **2013-11-25**: [PublishWorkflow] added a `MenuContentVoter`, this voter
2125
decides that a menu node is not published if the content it is pointing to is
2226
not published.
2327

ContentAwareFactory.php

Lines changed: 43 additions & 41 deletions
Original file line numberDiff line numberDiff line change
@@ -9,22 +9,23 @@
99
* file that was distributed with this source code.
1010
*/
1111

12-
1312
namespace Symfony\Cmf\Bundle\MenuBundle;
1413

1514
use Knp\Menu\Silex\RouterAwareFactory;
1615
use Knp\Menu\ItemInterface;
1716
use Knp\Menu\NodeInterface;
1817
use Knp\Menu\MenuItem;
1918

19+
use Psr\Log\LoggerInterface;
20+
21+
use Symfony\Cmf\Bundle\MenuBundle\Event\Events;
22+
use Symfony\Cmf\Bundle\MenuBundle\Model\Menu;
2023
use Symfony\Component\Routing\Generator\UrlGeneratorInterface;
2124
use Symfony\Component\Routing\Exception\RouteNotFoundException;
22-
use Symfony\Component\Security\Core\SecurityContextInterface;
23-
use Symfony\Cmf\Bundle\CoreBundle\PublishWorkflow\PublishWorkflowChecker;
24-
25-
use Psr\Log\LoggerInterface;
2625

2726
use Symfony\Cmf\Bundle\MenuBundle\Voter\VoterInterface;
27+
use Symfony\Component\EventDispatcher\EventDispatcherInterface;
28+
use Symfony\Cmf\Bundle\MenuBundle\Event\CreateMenuItemFromNodeEvent;
2829

2930
/**
3031
* This factory builds menu items from the menu nodes and builds urls based on
@@ -60,18 +61,6 @@ class ContentAwareFactory extends RouterAwareFactory
6061
*/
6162
private $logger;
6263

63-
/**
64-
* @var SecurityContextInterface
65-
*/
66-
private $securityContext;
67-
68-
/**
69-
* The permission to check for when doing the publish workflow check.
70-
*
71-
* @var string
72-
*/
73-
private $publishWorkflowPermission = PublishWorkflowChecker::VIEW_ATTRIBUTE;
74-
7564
/**
7665
* Whether to return null or a MenuItem without any URL if no URL can be
7766
* found for a MenuNode.
@@ -81,25 +70,24 @@ class ContentAwareFactory extends RouterAwareFactory
8170
private $allowEmptyItems;
8271

8372
/**
84-
* @param UrlGeneratorInterface $generator for the parent class
85-
* @param UrlGeneratorInterface $contentRouter to generate routes when
73+
* @param UrlGeneratorInterface $generator for the parent class
74+
* @param UrlGeneratorInterface $contentRouter to generate routes when
8675
* content is set
87-
* @param SecurityContextInterface $securityContext the publish workflow
88-
* checker to check if menu items are published.
89-
* @param LoggerInterface $logger
76+
* @param EventDispatcherInterface $dispatcher to dispatch the CREATE_ITEM_FROM_NODE event.
77+
* @param LoggerInterface $logger
9078
*/
9179
public function __construct(
9280
UrlGeneratorInterface $generator,
9381
UrlGeneratorInterface $contentRouter,
94-
SecurityContextInterface $securityContext,
82+
EventDispatcherInterface $dispatcher,
9583
LoggerInterface $logger
9684
)
9785
{
9886
parent::__construct($generator);
9987
$this->contentRouter = $contentRouter;
100-
$this->securityContext = $securityContext;
101-
$this->logger = $logger;
10288
$this->linkTypes = array('route', 'uri', 'content');
89+
$this->dispatcher = $dispatcher;
90+
$this->logger = $logger;
10391
}
10492

10593
/**
@@ -124,17 +112,6 @@ public function setAllowEmptyItems($allowEmptyItems)
124112
$this->allowEmptyItems = $allowEmptyItems;
125113
}
126114

127-
/**
128-
* What attribute to use in the publish workflow check. This typically
129-
* is VIEW or VIEW_ANONYMOUS.
130-
*
131-
* @param string $attribute
132-
*/
133-
public function setPublishWorkflowPermission($attribute)
134-
{
135-
$this->publishWorkflowPermission = $attribute;
136-
}
137-
138115
/**
139116
* Add a voter to decide on current item.
140117
*
@@ -169,17 +146,42 @@ private function getVoters()
169146
*/
170147
public function createFromNode(NodeInterface $node)
171148
{
172-
$item = $this->createItem($node->getName(), $node->getOptions());
149+
$event = new CreateMenuItemFromNodeEvent($node, $this);
150+
$this->dispatcher->dispatch(Events::CREATE_ITEM_FROM_NODE, $event);
151+
152+
if ($event->isSkipNode()) {
153+
if ($node instanceof Menu) {
154+
// create an empty menu root to avoid the knp menu from failing.
155+
return $this->createItem('');
156+
}
157+
158+
return null;
159+
}
160+
161+
$item = $event->getItem() ?: $this->createItem($node->getName(), $node->getOptions());
173162

174163
if (empty($item)) {
175164
return null;
176165
}
177166

178-
foreach ($node->getChildren() as $childNode) {
179-
if (!$this->securityContext->isGranted($this->publishWorkflowPermission, $childNode)) {
180-
continue;
181-
}
167+
if ($event->isSkipChildren()) {
168+
return $item;
169+
}
170+
171+
return $this->addChildrenFromNode($node->getChildren(), $item);
172+
}
182173

174+
/**
175+
* Create menu items from a list of menu nodes and add them to $item.
176+
*
177+
* @param NodeInterface[] $node The menu nodes to create.
178+
* @param ItemInterface $item The menu item to add the children to.
179+
*
180+
* @return ItemInterface
181+
*/
182+
public function addChildrenFromNode($nodes, ItemInterface $item)
183+
{
184+
foreach ($nodes as $childNode) {
183185
if ($childNode instanceof NodeInterface) {
184186
$child = $this->createFromNode($childNode);
185187
if (!empty($child)) {
Lines changed: 169 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,169 @@
1+
<?php
2+
3+
/*
4+
* This file is part of the Symfony CMF package.
5+
*
6+
* (c) 2011-2014 Symfony CMF
7+
*
8+
* For the full copyright and license information, please view the LICENSE
9+
* file that was distributed with this source code.
10+
*/
11+
12+
namespace Symfony\Cmf\Bundle\MenuBundle\Event;
13+
14+
use Knp\Menu\ItemInterface;
15+
use Knp\Menu\NodeInterface;
16+
17+
use Symfony\Component\EventDispatcher\Event;
18+
19+
use Symfony\Cmf\Bundle\MenuBundle\ContentAwareFactory;
20+
21+
/**
22+
* This event is raised when a menu node is to be transformed into a menu item.
23+
*
24+
* The event allows to control whether the menu node should be handled or to
25+
* completely replace the default behaviour of converting a menu node to a menu
26+
* item.
27+
*
28+
* @author Ben Glassman <[email protected]>
29+
*/
30+
class CreateMenuItemFromNodeEvent extends Event
31+
{
32+
/**
33+
* @var NodeInterface
34+
*/
35+
private $node;
36+
37+
/**
38+
* @var ItemInterface
39+
*/
40+
private $item;
41+
42+
/**
43+
* @var ContentAwareFactory
44+
*/
45+
private $factory;
46+
47+
/**
48+
* Whether or not to skip processing of this node
49+
*
50+
* @var boolean
51+
*/
52+
private $skipNode = false;
53+
54+
/**
55+
* Whether or not to skip processing of child nodes
56+
*
57+
* @var boolean
58+
*/
59+
private $skipChildren = false;
60+
61+
/**
62+
* @param NodeInterface $node
63+
* @param ContentAwareFactory $factory
64+
*/
65+
public function __construct(
66+
NodeInterface $node,
67+
ContentAwareFactory $factory
68+
) {
69+
$this->node = $node;
70+
$this->factory = $factory;
71+
}
72+
73+
/**
74+
* Get the menu node that is about to be built.
75+
*
76+
* @return NodeInterface
77+
*/
78+
public function getNode()
79+
{
80+
return $this->node;
81+
}
82+
83+
/**
84+
* Get the menu item attached to this event.
85+
*
86+
* If this is non-null, it will be used instead of automatically converting
87+
* the NodeInterface into a MenuItem.
88+
*
89+
* @return ItemInterface
90+
*/
91+
public function getItem()
92+
{
93+
return $this->item;
94+
}
95+
96+
/**
97+
* Set the menu item that represents the menu node of this event.
98+
*
99+
* Unless you set the skip children option, the children from the menu node
100+
* will still be built and added after eventual children this menu item
101+
* has.
102+
*
103+
* @param ItemInterface $item Menu item to use.
104+
*/
105+
public function setItem(ItemInterface $item = null)
106+
{
107+
$this->item = $item;
108+
}
109+
110+
/**
111+
* Set whether the node associated with this event is to be skipped
112+
* entirely. This has precedence over an eventual menu item attached to the
113+
* event.
114+
*
115+
* This automatically skips the whole subtree, as the children have no
116+
* place where they could be attached to.
117+
*
118+
* @param bool $skipNode
119+
*/
120+
public function setSkipNode($skipNode)
121+
{
122+
$this->skipNode = (bool) $skipNode;
123+
}
124+
125+
/**
126+
* @return bool Whether the node associated to this event is to be skipped.
127+
*/
128+
public function isSkipNode()
129+
{
130+
return $this->skipNode;
131+
}
132+
133+
/**
134+
* Set whether the children of the *node* associated with this event should
135+
* be ignored.
136+
*
137+
* Use this for example when your event handler implements its own logic to
138+
* build children items for the node associated with this event.
139+
*
140+
* If this event has a menu *item*, those children won't be skipped.
141+
*
142+
* @param bool $skipChildren
143+
*/
144+
public function setSkipChildren($skipChildren)
145+
{
146+
$this->skipChildren = (bool) $skipChildren;
147+
}
148+
149+
/**
150+
* @return bool Whether the children of the node associated to this event
151+
* should be handled or ignored.
152+
*/
153+
public function isSkipChildren()
154+
{
155+
return $this->skipChildren;
156+
}
157+
158+
/**
159+
* Get the menu factory that raised this event.
160+
*
161+
* You can use the factory to build a custom menu item.
162+
*
163+
* @return ContentAwareFactory
164+
*/
165+
public function getFactory()
166+
{
167+
return $this->factory;
168+
}
169+
}

Event/Events.php

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
<?php
2+
3+
/*
4+
* This file is part of the Symfony CMF package.
5+
*
6+
* (c) 2011-2014 Symfony CMF
7+
*
8+
* For the full copyright and license information, please view the LICENSE
9+
* file that was distributed with this source code.
10+
*/
11+
12+
namespace Symfony\Cmf\Bundle\MenuBundle\Event;
13+
14+
final class Events
15+
{
16+
/**
17+
* Fired when a menu item is to be created from a node in ContentAwareFactory
18+
*
19+
* The event object is a CreateMenuItemFromNodeEvent.
20+
*/
21+
const CREATE_ITEM_FROM_NODE = 'cmf_menu.create_menu_item_from_node';
22+
}

Provider/PhpcrMenuProvider.php

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,6 @@
99
* file that was distributed with this source code.
1010
*/
1111

12-
1312
namespace Symfony\Cmf\Bundle\MenuBundle\Provider;
1413

1514
use Doctrine\Common\Persistence\ManagerRegistry;

0 commit comments

Comments
 (0)