CHANGELOG.md 0000644 00000016237 13715737100 0006372 0 ustar 00 ## 3.1 (2019-12-01) * Allowed Symfony 5 components * Removed support for unsupported Symfony versions (4.0 and 4.1) * Allowed Twig 3 ## 3.0 (2019-09-02) * Raised PHP requirements * [BC break] Enforced strong types on all interfaces and classes * [BC break] Removed deprecated features. Specifically, MenuFactory and MenuItem are not accepting a `null` name anymore ## 2.4 (2019-07-29) * Fixed Twig deprecations * Switched to namespaced Twig * Fixed sprintf use ## 2.3 (2017-11-18) * Deprecated the Silex 1 KnpMenuServiceProvider. Use the `knplabs/knp-menu-silex` package instead. * Fixed RouteVoter to also match on non-string request arguments like integers as long as both string representations are identical. * Add Symfony 4 support ## 2.2 (2016-09-22) * Added a new function to twig: `knp_menu_get_current_item` ## 2.1.1 (2016-01-08) * Made compatible with Symfony 3 ## 2.1.0 (2015-09-20) * Added a new function to twig: `knp_menu_get_breadcrumbs_array` * Added a new filter to twig: `knp_menu_as_string` * Added 2 new tests to twig: `knp_menu_current`, `knp_menu_ancestor` * Made the templates compatible with Twig 2 * Add menu and renderer providers supporting any ArrayAccess implementations. The Pimple-based providers (supporting only Pimple 1) are dperecated in favor of these new providers. ## 2.0.1 (2014-08-01) * Fixed voter conventions on RouteVoter ## 2.0.0 (2014-07-18) * [BC break] Clean code and removed the BC layer ## 2.0.0 beta 1 (2014-06-19) * [BC break] Added the new `Integration` namespace and removed the `Silex` one. * Added a new Voter based on regular expression: `Knp\Menu\Matcher\Voter\RegexVoter` ## 2.0.0 alpha 2 (2014-05-01) * [BC break] Changed the TwigRenderer to accept a menu template only as a string * [BC break] Refactored the way of rendering twig templates. Every template should extends the `knp_menu.html.twig` template. * Introduced extension points in the MenuFactory through `Knp\Menu\Factory\ExtensionInterface` * [BC break compared to 2.0 alpha 1] The inheritance extension points introduced in alpha1 are deprecated in favor of extensions and will be removed before the stable release. * `Knp\Menu\Silex\RouterAwareFactory` is deprecated in favor of `Knp\Menu\Silex\RoutingExtension`. * [BC break] Deprecated the methods `createFromArray` and `createFromNode` in the MenuFactory and removed them from `Knp\Menu\FactoryInterface`. Use `Knp\Menu\Loader\ArrayLoader` and `Knp\Menu\Loader\NodeLoader` instead. * [BC break] Deprecated the methods `moveToPosition`, `moveToFirstPosition`, `moveToLastPosition`, `moveChildToPosition`, `callRecursively`, `toArray`, `getPathAsString` and `getBreadcrumbsArray` in the MenuItem and removed them from `Knp\Menu\ItemInterface`. Use `Knp\Menu\Util\MenuManipulator` instead. * Made the RouterVoter compatible with SensioFrameworkExtraBundle param converters * Added the possibility to match routes using a regex on their name in the RouterVoter * [BC break compared to 2.0 alpha 1] Refactored the RouterVoter to make it more flexible The way to pass routes in the item extras has changed. Before: ```php 'extras' => array( 'routes' => array('foo', 'bar'), 'routeParameters' => array('foo' => array('id' => 4)), ) ``` After: ```php 'extras' => array( 'routes' => array( array('route' => 'foo', 'parameters' => array('id' => 4)), 'bar', ) ) ``` The old syntax is kept until the final release, but using it will trigger a E_USER_DEPRECATED error. ## 2.0.0 alpha 1 (2013-06-23) * Added protected methods `buildOptions` and `configureItem` in the MenuFactory as extension point by inheritance * [BC break] Refactored the way to mark items as current ``setCurrentUri``, ``getCurrentUri`` and ``getCurrentItem`` have been removed from the ItemInterface. Determining the current items is now delegated to a matcher, and the default implementation uses voters to apply the matching. Getting the current items can be done thanks to the CurrentItemFilterIterator. * [BC break] The signature of the CurrentItemFilterIterator constructor changed to accept the item matcher * [BC break] Changed the format of the breadcrumb array Instead of storing the elements with the label as key and the uri as value the array now stores an array of array elements with 3 keys: `label`, `uri` and `item`. ## 1.1.2 (2012-06-10) * Updated the Silex service provider for the change in the interface ## 1.1.1 (2012-05-17) * Added the children attributes and the extras in the array export ## 1.1.0 (2012-05-17) * Marked `Knp\Menu\ItemInterface::getCurrentItem` as deprecated * Added a recursive filter iterator keeping only displayed items * Added a filter iterator keeping only current items * Added a recursive iterator for the item * Fixed building an array of breadcrumbs when a label has only digits * Added a way to mark a label as safe * Refactored the ListRenderer to be consistent with the TwigRenderer and provide the same extension points * Added a way to attach extra data to an item * Removed unnecessary optimization in the TwigRenderer * Added some whitespace control in the Twig template to ensure an empty rendering is really empty * [BC break] Use the childrenAttributes for the root instead of the attributes * Made the default options configurable for the TwigRenderer * Added the support for menu registered as factory in PimpleProvider * Added a way to use the options in `knp_menu_get()` in Twig templates * Added an array of options for the MenuProviderInterface * Added a template to render an ordered list * Refactored the template a bit to make it easier to use an ordered list * Allow omitting the name of the child in `fromArray` (the key is used instead) ## 1.0.0 (2011-12-03) * Add composer.json file * Added more flexible list element blocks * Add support for attributes on the children collection. * Added a default renderer * Added a ChainProvider for the menus. * Added the Silex extension * Added a RouterAwareFactory * Added an helper to be able to reuse the logic more easily for other templating engines * Added a way to retrieve an item using a path in a menu tree * Changed the toArray method to use a depth instead of simply using a boolean flag * Refactored the export to array and the creation from an array * Added better support for encoding problems when escaping a string in the ListRenderer * Added a Twig renderer * Added missing escaping in the ListRenderer * Renamed some methods in the ItemInterface * Removed the configuration of the current item as link from the item * Refactored the ListRenderer to use options * Changed the interface of callRecursively * Refactored the NodeInterface to be consistent * Moved the creation of the item to the factory * Added a Twig extension to render the menu easily * Changed the menu provider interface with a pimple-based implementation * Added a renderer provider to get a renderer by name and a Pimple-based implementation * Removed the renderer from the menu * Removed the num in the item by refactoring isLast and isFirst * Changed the RendererInterface to accept an array of options to be more flexible * Added an ItemInterface * Initial import of KnpMenuBundle decoupled classes with a new namespace LICENSE 0000644 00000002071 13715737100 0005555 0 ustar 00 Copyright (c) 2011-present KnpLabs - https://knplabs.com Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. composer.json 0000644 00000002544 13715737100 0007277 0 ustar 00 { "name": "knplabs/knp-menu", "type": "library", "description": "An object oriented menu library", "keywords": ["menu", "tree"], "homepage": "https://knplabs.com", "license": "MIT", "authors": [ { "name": "KnpLabs", "homepage": "https://knplabs.com" }, { "name": "Christophe Coevoet", "email": "stof@notk.org" }, { "name": "The Community", "homepage": "https://github.com/KnpLabs/KnpMenu/contributors" } ], "require": { "php": "^7.2 || ^8.0" }, "conflict": { "twig/twig": "<1.40 || >=2,<2.9" }, "require-dev": { "phpspec/prophecy": "^1.8", "psr/container": "^1.0", "symfony/http-foundation": "^3.4 || ^4.2|| ^5.0", "symfony/phpunit-bridge": "^3.3 || ^4.2|| ^5.0", "symfony/routing": "^3.4 || ^4.2|| ^5.0", "twig/twig": "^1.40 || ^2.9 || ^3.0" }, "suggest": { "twig/twig": "for the TwigRenderer and the integration with your templates" }, "autoload": { "psr-4": { "Knp\\Menu\\": "src/Knp/Menu" } }, "autoload-dev": { "psr-4": { "Knp\\Menu\\Tests\\": "tests/Knp/Menu/Tests" } }, "extra": { "branch-alias": { "dev-master": "3.2-dev" } } } src/Knp/Menu/Factory/CoreExtension.php 0000644 00000004127 13715737100 0013704 0 ustar 00 null, 'label' => null, 'attributes' => [], 'linkAttributes' => [], 'childrenAttributes' => [], 'labelAttributes' => [], 'extras' => [], 'current' => null, 'display' => true, 'displayChildren' => true, ], $options ); } /** * Configures the newly created item with the passed options * * @param ItemInterface $item * @param array $options */ public function buildItem(ItemInterface $item, array $options): void { $item ->setUri($options['uri']) ->setLabel($options['label']) ->setAttributes($options['attributes']) ->setLinkAttributes($options['linkAttributes']) ->setChildrenAttributes($options['childrenAttributes']) ->setLabelAttributes($options['labelAttributes']) ->setCurrent($options['current']) ->setDisplay($options['display']) ->setDisplayChildren($options['displayChildren']) ; $this->buildExtras($item, $options); } /** * Configures the newly created item's extras * Extras are processed one by one in order not to reset values set by other extensions * * @param ItemInterface $item * @param array $options */ private function buildExtras(ItemInterface $item, array $options): void { if (!empty($options['extras'])) { foreach ($options['extras'] as $key => $value) { $item->setExtra($key, $value); } } } } src/Knp/Menu/Factory/ExtensionInterface.php 0000644 00000001070 13715737100 0014706 0 ustar 00 generator = $generator; } public function buildOptions(array $options = []): array { if (!empty($options['route'])) { $params = isset($options['routeParameters']) ? $options['routeParameters'] : []; $absolute = (isset($options['routeAbsolute']) && $options['routeAbsolute']) ? UrlGeneratorInterface::ABSOLUTE_URL : UrlGeneratorInterface::ABSOLUTE_PATH; $options['uri'] = $this->generator->generate($options['route'], $params, $absolute); // adding the item route to the extras under the 'routes' key (for the Silex RouteVoter) $options['extras']['routes'][] = [ 'route' => $options['route'], 'parameters' => $params, ]; } return $options; } public function buildItem(ItemInterface $item, array $options): void { } } src/Knp/Menu/ItemInterface.php 0000644 00000021553 13715737100 0012231 0 ustar 00 tag and is what you should interact with * most of the time by default. * Originally taken from ioMenuPlugin (http://github.com/weaverryan/ioMenuPlugin) */ interface ItemInterface extends \ArrayAccess, \Countable, \IteratorAggregate { public function setFactory(FactoryInterface $factory): self; public function getName(): string; /** * Renames the item. * * This method must also update the key in the parent. * * Provides a fluent interface * * @param string $name * * @return ItemInterface * * @throws \InvalidArgumentException if the name is already used by a sibling */ public function setName(string $name): self; /** * Get the uri for a menu item * * @return string|null */ public function getUri(): ?string; /** * Set the uri for a menu item * * Provides a fluent interface * * @param string|null $uri The uri to set on this menu item * * @return ItemInterface */ public function setUri(?string $uri): self; /** * Returns the label that will be used to render this menu item * * Defaults to the name of no label was specified * * @return string|null */ public function getLabel(): ?string; /** * Provides a fluent interface * * @param string|null $label The text to use when rendering this menu item * * @return ItemInterface */ public function setLabel(?string $label): self; public function getAttributes(): array; public function setAttributes(array $attributes): self; /** * @param string $name The name of the attribute to return * @param mixed $default The value to return if the attribute doesn't exist * * @return mixed */ public function getAttribute(string $name, $default = null); public function setAttribute(string $name, $value): self; public function getLinkAttributes(): array; public function setLinkAttributes(array $linkAttributes): self; /** * @param string $name The name of the attribute to return * @param mixed $default The value to return if the attribute doesn't exist * * @return mixed */ public function getLinkAttribute(string $name, $default = null); public function setLinkAttribute(string $name, $value): self; public function getChildrenAttributes(): array; public function setChildrenAttributes(array $childrenAttributes): self; /** * @param string $name The name of the attribute to return * @param mixed $default The value to return if the attribute doesn't exist * * @return mixed */ public function getChildrenAttribute(string $name, $default = null); public function setChildrenAttribute(string $name, $value); public function getLabelAttributes(): array; public function setLabelAttributes(array $labelAttributes): self; /** * @param string $name The name of the attribute to return * @param mixed $default The value to return if the attribute doesn't exist * * @return mixed */ public function getLabelAttribute(string $name, $default = null); public function setLabelAttribute(string $name, $value): self; public function getExtras(): array; public function setExtras(array $extras): self; /** * @param string $name The name of the extra to return * @param mixed $default The value to return if the extra doesn't exist * * @return mixed */ public function getExtra(string $name, $default = null); public function setExtra(string $name, $value): self; public function getDisplayChildren(): bool; /** * Set whether or not this menu item should show its children * * Provides a fluent interface * * @param bool $bool * * @return ItemInterface */ public function setDisplayChildren(bool $bool): self; /** * Whether or not to display this menu item * * @return bool */ public function isDisplayed(): bool; /** * Set whether or not this menu should be displayed * * Provides a fluent interface * * @param bool $bool * * @return ItemInterface */ public function setDisplay(bool $bool); /** * Add a child menu item to this menu * * Returns the child item * * @param ItemInterface|string $child An ItemInterface instance or the name of a new item to create * @param array $options If creating a new item, the options passed to the factory for the item * * @return ItemInterface * * @throws \InvalidArgumentException if the item is already in a tree */ public function addChild($child, array $options = []): self; /** * Returns the child menu identified by the given name * * @param string $name Then name of the child menu to return * * @return ItemInterface|null */ public function getChild(string $name): ?self; /** * Reorder children. * * Provides a fluent interface * * @param array $order new order of children * * @return ItemInterface */ public function reorderChildren(array $order): self; /** * Makes a deep copy of menu tree. Every item is copied as another object. * * @return ItemInterface */ public function copy(): self; /** * Returns the level of this menu item * * The root menu item is 0, followed by 1, 2, etc * * @return int */ public function getLevel(): int; /** * Returns the root ItemInterface of this menu tree * * @return ItemInterface */ public function getRoot(): self; /** * Returns whether or not this menu item is the root menu item * * @return bool */ public function isRoot(): bool; /** * @return ItemInterface|null */ public function getParent(): ?self; /** * Used internally when adding and removing children * * Provides a fluent interface * * @param ItemInterface|null $parent * * @return ItemInterface */ public function setParent(?self $parent = null): self; /** * Return the children as an array of ItemInterface objects * * @return ItemInterface[] */ public function getChildren(): array; /** * Provides a fluent interface * * @param array $children An array of ItemInterface objects * * @return ItemInterface */ public function setChildren(array $children): self; /** * Removes a child from this menu item * * Provides a fluent interface * * @param ItemInterface|string $name The name of ItemInterface instance or the ItemInterface to remove * * @return ItemInterface */ public function removeChild($name): self; /** * @return ItemInterface */ public function getFirstChild(): self; /** * @return ItemInterface */ public function getLastChild(): self; /** * Returns whether or not this menu items has viewable children * * This menu MAY have children, but this will return false if the current * user does not have access to view any of those items * * @return bool */ public function hasChildren(): bool; /** * Sets whether or not this menu item is "current". * * If the state is unknown, use null. * * Provides a fluent interface * * @param bool|null $bool Specify that this menu item is current * * @return ItemInterface */ public function setCurrent(?bool $bool): self; /** * Gets whether or not this menu item is "current". * * @return bool|null */ public function isCurrent(): ?bool; /** * Whether this menu item is last in its parent * * @return bool */ public function isLast(): bool; /** * Whether this menu item is first in its parent * * @return bool */ public function isFirst(): bool; /** * Whereas isFirst() returns if this is the first child of the parent * menu item, this function takes into consideration whether children are rendered or not. * * This returns true if this is the first child that would be rendered * for the current user * * @return bool */ public function actsLikeFirst(): bool; /** * Whereas isLast() returns if this is the last child of the parent * menu item, this function takes into consideration whether children are rendered or not. * * This returns true if this is the last child that would be rendered * for the current user * * @return bool */ public function actsLikeLast(): bool; } src/Knp/Menu/Iterator/CurrentItemFilterIterator.php 0000644 00000000744 13715737100 0016423 0 ustar 00 matcher = $matcher; parent::__construct($iterator); } public function accept() { return $this->matcher->isCurrent($this->current()); } } src/Knp/Menu/Iterator/DisplayedItemFilterIterator.php 0000644 00000000572 13715737100 0016716 0 ustar 00 current()->isDisplayed(); } public function hasChildren() { return $this->current()->getDisplayChildren() && parent::hasChildren(); } } src/Knp/Menu/Iterator/RecursiveItemIterator.php 0000644 00000000547 13715737100 0015603 0 ustar 00 current()); } public function getChildren() { return new static($this->current()); } } src/Knp/Menu/Loader/ArrayLoader.php 0000644 00000002772 13715737100 0013127 0 ustar 00 factory = $factory; } public function load($data): ItemInterface { if (!$this->supports($data)) { throw new \InvalidArgumentException(\sprintf('Unsupported data. Expected an array but got %s', \is_object($data) ? \get_class($data) : \gettype($data))); } return $this->fromArray($data); } public function supports($data): bool { return \is_array($data); } /** * @param array $data * @param string|null $name (the name of the item, used only if there is no name in the data themselves) * * @return ItemInterface */ private function fromArray(array $data, ?string $name = null): ItemInterface { $name = isset($data['name']) ? $data['name'] : $name; if (isset($data['children'])) { $children = $data['children']; unset($data['children']); } else { $children = []; } $item = $this->factory->createItem($name, $data); foreach ($children as $name => $child) { $item->addChild($this->fromArray($child, $name)); } return $item; } } src/Knp/Menu/Loader/LoaderInterface.php 0000644 00000000667 13715737100 0013752 0 ustar 00 factory = $factory; } public function load($data): ItemInterface { if (!$data instanceof NodeInterface) { throw new \InvalidArgumentException(\sprintf('Unsupported data. Expected Knp\Menu\NodeInterface but got %s', \is_object($data) ? \get_class($data) : \gettype($data))); } $item = $this->factory->createItem($data->getName(), $data->getOptions()); foreach ($data->getChildren() as $childNode) { $item->addChild($this->load($childNode)); } return $item; } public function supports($data): bool { return $data instanceof NodeInterface; } } src/Knp/Menu/Matcher/Matcher.php 0000644 00000003012 13715737100 0012446 0 ustar 00 voters = $voters; $this->cache = new \SplObjectStorage(); } public function isCurrent(ItemInterface $item): bool { $current = $item->isCurrent(); if (null !== $current) { return $current; } if ($this->cache->contains($item)) { return $this->cache[$item]; } foreach ($this->voters as $voter) { $current = $voter->matchItem($item); if (null !== $current) { break; } } $current = (bool) $current; $this->cache[$item] = $current; return $current; } public function isAncestor(ItemInterface $item, ?int $depth = null): bool { if (0 === $depth) { return false; } $childDepth = null === $depth ? null : $depth - 1; foreach ($item->getChildren() as $child) { if ($this->isCurrent($child) || $this->isAncestor($child, $childDepth)) { return true; } } return false; } public function clear(): void { $this->cache = new \SplObjectStorage(); } } src/Knp/Menu/Matcher/MatcherInterface.php 0000644 00000001324 13715737100 0014273 0 ustar 00 regexp = $regexp; } public function matchItem(ItemInterface $item): ?bool { if (null === $this->regexp || null === $item->getUri()) { return null; } if (\preg_match($this->regexp, $item->getUri())) { return true; } return null; } } src/Knp/Menu/Matcher/Voter/RouteVoter.php 0000644 00000004646 13715737100 0014316 0 ustar 00 requestStack = $requestStack; } public function matchItem(ItemInterface $item): ?bool { $request = $this->requestStack->getMasterRequest(); if (null === $request) { return null; } $route = $request->attributes->get('_route'); if (null === $route) { return null; } $routes = (array) $item->getExtra('routes', []); foreach ($routes as $testedRoute) { if (\is_string($testedRoute)) { $testedRoute = ['route' => $testedRoute]; } if (!\is_array($testedRoute)) { throw new \InvalidArgumentException('Routes extra items must be strings or arrays.'); } if ($this->isMatchingRoute($request, $testedRoute)) { return true; } } return null; } private function isMatchingRoute(Request $request, array $testedRoute): bool { $route = $request->attributes->get('_route'); if (isset($testedRoute['route'])) { if ($route !== $testedRoute['route']) { return false; } } elseif (!empty($testedRoute['pattern'])) { if (!\preg_match($testedRoute['pattern'], $route)) { return false; } } else { throw new \InvalidArgumentException('Routes extra items must have a "route" or "pattern" key.'); } if (!isset($testedRoute['parameters'])) { return true; } $routeParameters = $request->attributes->get('_route_params', []); foreach ($testedRoute['parameters'] as $name => $value) { // cast both to string so that we handle integer and other non-string parameters, but don't stumble on 0 == 'abc'. if (!isset($routeParameters[$name]) || (string) $routeParameters[$name] !== (string) $value) { return false; } } return true; } } src/Knp/Menu/Matcher/Voter/UriVoter.php 0000644 00000001026 13715737100 0013744 0 ustar 00 uri = $uri; } public function matchItem(ItemInterface $item): ?bool { if (null === $this->uri || null === $item->getUri()) { return null; } if ($item->getUri() === $this->uri) { return true; } return null; } } src/Knp/Menu/Matcher/Voter/VoterInterface.php 0000644 00000000725 13715737100 0015112 0 ustar 00 addExtension(new CoreExtension(), -10); } public function createItem(string $name, array $options = []): ItemInterface { foreach ($this->getExtensions() as $extension) { $options = $extension->buildOptions($options); } $item = new MenuItem($name, $this); foreach ($this->getExtensions() as $extension) { $extension->buildItem($item, $options); } return $item; } /** * Adds a factory extension * * @param ExtensionInterface $extension * @param int $priority */ public function addExtension(ExtensionInterface $extension, int $priority = 0): void { $this->extensions[$priority][] = $extension; $this->sorted = null; } /** * Sorts the internal list of extensions by priority. * * @return ExtensionInterface[]|null */ private function getExtensions(): ?array { if (null === $this->sorted) { \krsort($this->extensions); $this->sorted = !empty($this->extensions) ? \call_user_func_array('array_merge', $this->extensions) : []; } return $this->sorted; } } src/Knp/Menu/MenuItem.php 0000644 00000032476 13715737100 0011243 0 ustar 00 name = $name; $this->factory = $factory; } /** * setFactory * * @param FactoryInterface $factory * * @return ItemInterface */ public function setFactory(FactoryInterface $factory): ItemInterface { $this->factory = $factory; return $this; } public function getName(): string { return $this->name; } public function setName(string $name): ItemInterface { if ($this->name === $name) { return $this; } $parent = $this->getParent(); if (null !== $parent && isset($parent[$name])) { throw new \InvalidArgumentException('Cannot rename item, name is already used by sibling.'); } $oldName = $this->name; $this->name = $name; if (null !== $parent) { $names = \array_keys($parent->getChildren()); $items = \array_values($parent->getChildren()); $offset = \array_search($oldName, $names); $names[$offset] = $name; $parent->setChildren(\array_combine($names, $items)); } return $this; } public function getUri(): ?string { return $this->uri; } public function setUri(?string $uri): ItemInterface { $this->uri = $uri; return $this; } public function getLabel(): string { return (null !== $this->label) ? $this->label : $this->name; } public function setLabel(?string $label): ItemInterface { $this->label = $label; return $this; } public function getAttributes(): array { return $this->attributes; } public function setAttributes(array $attributes): ItemInterface { $this->attributes = $attributes; return $this; } public function getAttribute(string $name, $default = null) { if (isset($this->attributes[$name])) { return $this->attributes[$name]; } return $default; } public function setAttribute(string $name, $value): ItemInterface { $this->attributes[$name] = $value; return $this; } public function getLinkAttributes(): array { return $this->linkAttributes; } public function setLinkAttributes(array $linkAttributes): ItemInterface { $this->linkAttributes = $linkAttributes; return $this; } public function getLinkAttribute(string $name, $default = null) { if (isset($this->linkAttributes[$name])) { return $this->linkAttributes[$name]; } return $default; } public function setLinkAttribute(string $name, $value): ItemInterface { $this->linkAttributes[$name] = $value; return $this; } public function getChildrenAttributes(): array { return $this->childrenAttributes; } public function setChildrenAttributes(array $childrenAttributes): ItemInterface { $this->childrenAttributes = $childrenAttributes; return $this; } public function getChildrenAttribute(string $name, $default = null) { if (isset($this->childrenAttributes[$name])) { return $this->childrenAttributes[$name]; } return $default; } public function setChildrenAttribute(string $name, $value): ItemInterface { $this->childrenAttributes[$name] = $value; return $this; } public function getLabelAttributes(): array { return $this->labelAttributes; } public function setLabelAttributes(array $labelAttributes): ItemInterface { $this->labelAttributes = $labelAttributes; return $this; } public function getLabelAttribute(string $name, $default = null) { if (isset($this->labelAttributes[$name])) { return $this->labelAttributes[$name]; } return $default; } public function setLabelAttribute(string $name, $value): ItemInterface { $this->labelAttributes[$name] = $value; return $this; } public function getExtras(): array { return $this->extras; } public function setExtras(array $extras): ItemInterface { $this->extras = $extras; return $this; } public function getExtra(string $name, $default = null) { if (isset($this->extras[$name])) { return $this->extras[$name]; } return $default; } public function setExtra(string $name, $value): ItemInterface { $this->extras[$name] = $value; return $this; } public function getDisplayChildren(): bool { return $this->displayChildren; } public function setDisplayChildren(bool $bool): ItemInterface { $this->displayChildren = $bool; return $this; } public function isDisplayed(): bool { return $this->display; } public function setDisplay(bool $bool): ItemInterface { $this->display = $bool; return $this; } public function addChild($child, array $options = []): ItemInterface { if (!$child instanceof ItemInterface) { $child = $this->factory->createItem($child, $options); } elseif (null !== $child->getParent()) { throw new \InvalidArgumentException('Cannot add menu item as child, it already belongs to another menu (e.g. has a parent).'); } $child->setParent($this); $this->children[$child->getName()] = $child; return $child; } public function getChild(string $name): ?ItemInterface { return isset($this->children[$name]) ? $this->children[$name] : null; } public function reorderChildren(array $order): ItemInterface { if (\count($order) != $this->count()) { throw new \InvalidArgumentException('Cannot reorder children, order does not contain all children.'); } $newChildren = []; foreach ($order as $name) { if (!isset($this->children[$name])) { throw new \InvalidArgumentException('Cannot find children named '.$name); } $child = $this->children[$name]; $newChildren[$name] = $child; } $this->setChildren($newChildren); return $this; } public function copy(): ItemInterface { $newMenu = clone $this; $newMenu->setChildren([]); $newMenu->setParent(null); foreach ($this->getChildren() as $child) { $newMenu->addChild($child->copy()); } return $newMenu; } public function getLevel(): int { return $this->parent ? $this->parent->getLevel() + 1 : 0; } public function getRoot(): ItemInterface { $obj = $this; do { $found = $obj; } while ($obj = $obj->getParent()); return $found; } public function isRoot(): bool { return null === $this->parent; } public function getParent(): ?ItemInterface { return $this->parent; } public function setParent(?ItemInterface $parent = null): ItemInterface { if ($parent === $this) { throw new \InvalidArgumentException('Item cannot be a child of itself'); } $this->parent = $parent; return $this; } public function getChildren(): array { return $this->children; } public function setChildren(array $children): ItemInterface { $this->children = $children; return $this; } public function removeChild($name): ItemInterface { $name = $name instanceof ItemInterface ? $name->getName() : $name; if (isset($this->children[$name])) { // unset the child and reset it so it looks independent $this->children[$name]->setParent(null); unset($this->children[$name]); } return $this; } public function getFirstChild(): ItemInterface { return \reset($this->children); } public function getLastChild(): ItemInterface { return \end($this->children); } public function hasChildren(): bool { foreach ($this->children as $child) { if ($child->isDisplayed()) { return true; } } return false; } public function setCurrent(?bool $bool): ItemInterface { $this->isCurrent = $bool; return $this; } public function isCurrent(): ?bool { return $this->isCurrent; } public function isLast(): bool { // if this is root, then return false if ($this->isRoot()) { return false; } return $this->getParent()->getLastChild() === $this; } public function isFirst(): bool { // if this is root, then return false if ($this->isRoot()) { return false; } return $this->getParent()->getFirstChild() === $this; } public function actsLikeFirst(): bool { // root items are never "marked" as first if ($this->isRoot()) { return false; } // A menu acts like first only if it is displayed if (!$this->isDisplayed()) { return false; } // if we're first and visible, we're first, period. if ($this->isFirst()) { return true; } $children = $this->getParent()->getChildren(); foreach ($children as $child) { // loop until we find a visible menu. If its this menu, we're first if ($child->isDisplayed()) { return $child->getName() === $this->getName(); } } return false; } public function actsLikeLast(): bool { // root items are never "marked" as last if ($this->isRoot()) { return false; } // A menu acts like last only if it is displayed if (!$this->isDisplayed()) { return false; } // if we're last and visible, we're last, period. if ($this->isLast()) { return true; } $children = \array_reverse($this->getParent()->getChildren()); foreach ($children as $child) { // loop until we find a visible menu. If its this menu, we're first if ($child->isDisplayed()) { return $child->getName() === $this->getName(); } } return false; } /** * Implements Countable */ public function count(): int { return \count($this->children); } /** * Implements IteratorAggregate */ public function getIterator(): \Traversable { return new \ArrayIterator($this->children); } /** * Implements ArrayAccess */ public function offsetExists($name): bool { return isset($this->children[$name]); } /** * Implements ArrayAccess */ public function offsetGet($name) { return $this->getChild($name); } /** * Implements ArrayAccess */ public function offsetSet($name, $value) { return $this->addChild($name)->setLabel($value); } /** * Implements ArrayAccess */ public function offsetUnset($name): void { $this->removeChild($name); } } src/Knp/Menu/NodeInterface.php 0000644 00000001154 13715737100 0012213 0 ustar 00 registry = $registry; $this->menuIds = $menuIds; } public function get(string $name, array $options = []): ItemInterface { if (!isset($this->menuIds[$name])) { throw new \InvalidArgumentException(\sprintf('The menu "%s" is not defined.', $name)); } $menu = $this->registry[$this->menuIds[$name]]; if (\is_callable($menu)) { $menu = \call_user_func($menu, $options, $this->registry); } return $menu; } public function has(string $name, array $options = []): bool { return isset($this->menuIds[$name]); } } src/Knp/Menu/Provider/ChainProvider.php 0000644 00000001650 13715737100 0014035 0 ustar 00 providers = $providers; } public function get(string $name, array $options = []): ItemInterface { foreach ($this->providers as $provider) { if ($provider->has($name, $options)) { return $provider->get($name, $options); } } throw new \InvalidArgumentException(\sprintf('The menu "%s" is not defined.', $name)); } public function has(string $name, array $options = []): bool { foreach ($this->providers as $provider) { if ($provider->has($name, $options)) { return true; } } return false; } } src/Knp/Menu/Provider/LazyProvider.php 0000644 00000002412 13715737100 0013727 0 ustar 00 builders = $builders; } public function get(string $name, array $options = []): ItemInterface { if (!isset($this->builders[$name])) { throw new \InvalidArgumentException(\sprintf('The menu "%s" is not defined.', $name)); } $builder = $this->builders[$name]; if (\is_array($builder) && isset($builder[0]) && $builder[0] instanceof \Closure) { $builder[0] = $builder[0](); } if (!\is_callable($builder)) { throw new \LogicException(\sprintf('Invalid menu builder for "%s". A callable or a factory for an object callable are expected.', $name)); } return $builder($options); } public function has(string $name, array $options = []): bool { return isset($this->builders[$name]); } } src/Knp/Menu/Provider/MenuProviderInterface.php 0000644 00000001170 13715737100 0015535 0 ustar 00 container = $container; } public function get(string $name, array $options = []): ItemInterface { if (!$this->container->has($name)) { throw new \InvalidArgumentException(\sprintf('The menu "%s" is not defined.', $name)); } return $this->container->get($name); } public function has(string $name, array $options = []): bool { return $this->container->has($name); } } src/Knp/Menu/Renderer/ArrayAccessProvider.php 0000644 00000002312 13715737100 0015163 0 ustar 00 registry = $registry; $this->rendererIds = $rendererIds; $this->defaultRenderer = $defaultRenderer; } public function get(string $name = null): RendererInterface { if (null === $name) { $name = $this->defaultRenderer; } if (!isset($this->rendererIds[$name])) { throw new \InvalidArgumentException(\sprintf('The renderer "%s" is not defined.', $name)); } return $this->registry[$this->rendererIds[$name]]; } public function has($name): bool { return isset($this->rendererIds[$name]); } } src/Knp/Menu/Renderer/ListRenderer.php 0000644 00000020415 13715737100 0013656 0 ustar 00 matcher = $matcher; $this->defaultOptions = \array_merge([ 'depth' => null, 'matchingDepth' => null, 'currentAsLink' => true, 'currentClass' => 'current', 'ancestorClass' => 'current_ancestor', 'firstClass' => 'first', 'lastClass' => 'last', 'compressed' => false, 'allow_safe_labels' => false, 'clear_matcher' => true, 'leaf_class' => null, 'branch_class' => null, ], $defaultOptions); parent::__construct($charset); } public function render(ItemInterface $item, array $options = []): string { $options = \array_merge($this->defaultOptions, $options); $html = $this->renderList($item, $item->getChildrenAttributes(), $options); if ($options['clear_matcher']) { $this->matcher->clear(); } return $html; } protected function renderList(ItemInterface $item, array $attributes, array $options): string { /* * Return an empty string if any of the following are true: * a) The menu has no children eligible to be displayed * b) The depth is 0 * c) This menu item has been explicitly set to hide its children */ if (!$item->hasChildren() || 0 === $options['depth'] || !$item->getDisplayChildren()) { return ''; } $html = $this->format('