Server IP : 213.176.29.180  /  Your IP : 3.135.195.229
Web Server : Apache
System : Linux 213.176.29.180.hostiran.name 4.18.0-553.22.1.el8_10.x86_64 #1 SMP Tue Sep 24 05:16:59 EDT 2024 x86_64
User : webtaragh ( 1001)
PHP Version : 7.4.33
Disable Function : NONE
MySQL : OFF  |  cURL : ON  |  WGET : ON  |  Perl : ON  |  Python : ON
Directory (0750) :  /home/webtaragh/public_html/webtaragh.ir/../../php/../public_html/

[  Home  ][  C0mmand  ][  Upload File  ]

Current File : /home/webtaragh/public_html/webtaragh.ir/../../php/../public_html/knplabs.tar
knp-menu/README.markdown000064400000005535147361032510011011 0ustar00KnpMenu
=======

The KnpMenu library provides object oriented menus for PHP 7.
It is used by the [KnpMenuBundle](https://github.com/KnpLabs/KnpMenuBundle) for Symfony
but can now be used stand-alone.

[![Build Status](https://secure.travis-ci.org/KnpLabs/KnpMenu.svg)](http://travis-ci.org/KnpLabs/KnpMenu)
[![Latest Stable Version](https://poser.pugx.org/knplabs/knp-menu/v/stable.svg)](https://packagist.org/packages/knplabs/knp-menu)
[![Latest Unstable Version](https://poser.pugx.org/knplabs/knp-menu/v/unstable.svg)](https://packagist.org/packages/knplabs/knp-menu)
[![Gitter chat](https://badges.gitter.im/KnpLabs/KnpMenu.svg)](https://gitter.im/KnpLabs/KnpMenu)

## Installation

KnpMenu uses Composer, please checkout the [composer website](http://getcomposer.org) for more information.

The simple following command will install `knp-menu` into your project. It also add a new
entry in your `composer.json` and update the `composer.lock` as well.

```bash
$ composer require knplabs/knp-menu
```

> KnpMenu follows the PSR-4 convention names for its classes, which means you can easily integrate `knp-menu` classes loading in your own autoloader.

## Getting Started

```php
<?php

// Include dependencies installed with composer
require 'vendor/autoload.php';

use Knp\Menu\MenuFactory;
use Knp\Menu\Renderer\ListRenderer;

$factory = new MenuFactory();
$menu = $factory->createItem('My menu');
$menu->addChild('Home', ['uri' => '/']);
$menu->addChild('Comments', ['uri' => '#comments']);
$menu->addChild('Symfony', ['uri' => 'http://symfony.com/']);
$menu->addChild('Happy Awesome Developers');

$renderer = new ListRenderer(new \Knp\Menu\Matcher\Matcher());
echo $renderer->render($menu);
```

The above menu would render the following HTML:

```html
<ul>
  <li class="first">
    <a href="/">Home</a>
  </li>
  <li class="current">
    <a href="#comments">Comments</a>
  </li>
  <li>
    <a href="http://symfony.com/">Symfony</a>
  </li>
  <li class="last">
    <span>Happy Awesome Developers</span>
  </li>
</ul>
```

This way you can finally avoid writing an ugly template to show the selected item,
the first and last items, submenus, ...

> The bulk of the documentation can be found in the `doc` directory.

## What now?

Follow the tutorial in [`doc/01-Basic-Menus.md`][0] and [`doc/02-Twig-Integration.md`][1]
to discover how KnpMenu will rock your world!

Find all available documentation at [`doc/`][2].

## Maintainers

This library is maintained by the following people (alphabetically sorted) :

- @derrabus
- @garak
- @stof

## Credits

This bundle was originally ported from [ioMenuPlugin](http://github.com/weaverryan/ioMenuPlugin),
a menu plugin for symfony1. It has since been developed by [KnpLabs](http://www.knplabs.com) and
the [Symfony community](https://github.com/KnpLabs/KnpMenu/graphs/contributors).

[0]: doc/01-Basic-Menus.md
[1]: doc/02-Twig-Integration.md
[2]: doc/
knp-menu/CODE_OF_CONDUCT.md000064400000005210147361032510011075 0ustar00# Contributor Covenant Code of Conduct

## Our Pledge

In the interest of fostering an open and welcoming environment, we as
contributors and maintainers pledge to make participation in our project and
our community a harassment-free experience for everyone, regardless of age, body
size, disability, ethnicity, sex characteristics, gender identity and expression,
level of experience, education, socio-economic status, nationality, personal
appearance, race, religion, or sexual identity and orientation.

## Our Standards

Examples of behavior that contributes to creating a positive environment
include:

* Using welcoming and inclusive language
* Being respectful of differing viewpoints and experiences
* Gracefully accepting constructive criticism
* Focusing on what is best for the community
* Showing empathy towards other community members

Examples of unacceptable behavior by participants include:

* The use of sexualized language or imagery and unwelcome sexual attention or
  advances
* Trolling, insulting/derogatory comments, and personal or political attacks
* Public or private harassment
* Publishing others' private information, such as a physical or electronic
  address, without explicit permission
* Other conduct which could reasonably be considered inappropriate in a
  professional setting

## Our Responsibilities

Project maintainers are responsible for clarifying the standards of acceptable
behavior and are expected to take appropriate and fair corrective action in
response to any instances of unacceptable behavior.

Project maintainers have the right and responsibility to remove, edit, or
reject comments, commits, code, wiki edits, issues, and other contributions
that are not aligned to this Code of Conduct, or to ban temporarily or
permanently any contributor for other behaviors that they deem inappropriate,
threatening, offensive, or harmful.

## Scope

This Code of Conduct applies within all project spaces, and it also applies when
an individual is representing the project or its community in public spaces.
Examples of representing a project or community include using an official
project e-mail address, posting via an official social media account, or acting
as an appointed representative at an online or offline event. Representation of
a project may be further defined and clarified by project maintainers.

## Attribution

This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 1.4,
available at https://www.contributor-covenant.org/version/1/4/code-of-conduct.html

[homepage]: https://www.contributor-covenant.org

## Contact
If you have any questions or feedback, [please ping us](https://twitter.com/KNPLabs) !
knp-menu/src/Knp/Menu/Matcher/Matcher.php000064400000003012147361032510014176 0ustar00<?php

namespace Knp\Menu\Matcher;

use Knp\Menu\ItemInterface;
use Knp\Menu\Matcher\Voter\VoterInterface;

/**
 * A MatcherInterface implementation using a voter system
 */
class Matcher implements MatcherInterface
{
    private $cache;
    private $voters;

    /**
     * @param VoterInterface[]|iterable $voters
     */
    public function __construct($voters = [])
    {
        $this->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();
    }
}
knp-menu/src/Knp/Menu/Matcher/MatcherInterface.php000064400000001324147361032510016023 0ustar00<?php

namespace Knp\Menu\Matcher;

use Knp\Menu\ItemInterface;

/**
 * Interface implemented by the item matcher
 */
interface MatcherInterface
{
    /**
     * Checks whether an item is current.
     *
     * @param ItemInterface $item
     *
     * @return bool
     */
    public function isCurrent(ItemInterface $item): bool;

    /**
     * Checks whether an item is the ancestor of a current item.
     *
     * @param ItemInterface $item
     * @param int|null      $depth The max depth to look for the item
     *
     * @return bool
     */
    public function isAncestor(ItemInterface $item, ?int $depth = null): bool;

    /**
     * Clears the state of the matcher.
     */
    public function clear(): void;
}
knp-menu/src/Knp/Menu/Matcher/Voter/RegexVoter.php000064400000001441147361032510016010 0ustar00<?php

namespace Knp\Menu\Matcher\Voter;

use Knp\Menu\ItemInterface;

/**
 * Implements the VoterInterface which can be used as voter for "current" class
 * `matchItem` will return true if the pattern you're searching for is found in the URI of the item
 */
class RegexVoter implements VoterInterface
{
    /**
     * @var string|null
     */
    private $regexp;

    /**
     * @param string|null $regexp
     */
    public function __construct(?string $regexp)
    {
        $this->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;
    }
}
knp-menu/src/Knp/Menu/Matcher/Voter/RouteVoter.php000064400000004646147361032510016046 0ustar00<?php

namespace Knp\Menu\Matcher\Voter;

use Knp\Menu\ItemInterface;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\RequestStack;

/**
 * Voter based on the route
 */
class RouteVoter implements VoterInterface
{
    /**
     * @var RequestStack
     */
    private $requestStack;

    /**
     * @var Request|null
     */
    private $request;

    public function __construct(RequestStack $requestStack)
    {
        $this->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;
    }
}
knp-menu/src/Knp/Menu/Matcher/Voter/UriVoter.php000064400000001026147361032510015474 0ustar00<?php

namespace Knp\Menu\Matcher\Voter;

use Knp\Menu\ItemInterface;

/**
 * Voter based on the uri
 */
class UriVoter implements VoterInterface
{
    private $uri;

    public function __construct(?string $uri = null)
    {
        $this->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;
    }
}
knp-menu/src/Knp/Menu/Matcher/Voter/VoterInterface.php000064400000000725147361032510016642 0ustar00<?php

namespace Knp\Menu\Matcher\Voter;

use Knp\Menu\ItemInterface;

/**
 * Interface implemented by the matching voters
 */
interface VoterInterface
{
    /**
     * Checks whether an item is current.
     *
     * If the voter is not able to determine a result,
     * it should return null to let other voters do the job.
     *
     * @param ItemInterface $item
     *
     * @return bool|null
     */
    public function matchItem(ItemInterface $item): ?bool;
}
knp-menu/src/Knp/Menu/Factory/ExtensionInterface.php000064400000001070147361032510016436 0ustar00<?php

namespace Knp\Menu\Factory;

use Knp\Menu\ItemInterface;

interface ExtensionInterface
{
    /**
     * Builds the full option array used to configure the item.
     *
     * @param array $options The options processed by the previous extensions
     *
     * @return array
     */
    public function buildOptions(array $options): array;

    /**
     * Configures the item with the passed options
     *
     * @param ItemInterface $item
     * @param array         $options
     */
    public function buildItem(ItemInterface $item, array $options): void;
}
knp-menu/src/Knp/Menu/Factory/CoreExtension.php000064400000004127147361032510015434 0ustar00<?php

namespace Knp\Menu\Factory;

use Knp\Menu\ItemInterface;

/**
 * core factory extension with the main logic
 */
class CoreExtension implements ExtensionInterface
{
    /**
     * Builds the full option array used to configure the item.
     *
     * @param array $options
     *
     * @return array
     */
    public function buildOptions(array $options): array
    {
        return \array_merge(
            [
                'uri' => 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);
            }
        }
    }
}
knp-menu/src/Knp/Menu/Iterator/CurrentItemFilterIterator.php000064400000000744147361032510020153 0ustar00<?php

namespace Knp\Menu\Iterator;

use Knp\Menu\Matcher\MatcherInterface;

/**
 * Filter iterator keeping only current items
 */
class CurrentItemFilterIterator extends \FilterIterator
{
    private $matcher;

    public function __construct(\Iterator $iterator, MatcherInterface $matcher)
    {
        $this->matcher = $matcher;

        parent::__construct($iterator);
    }

    public function accept()
    {
        return $this->matcher->isCurrent($this->current());
    }
}
knp-menu/src/Knp/Menu/Iterator/RecursiveItemIterator.php000064400000000547147361032510017333 0ustar00<?php

namespace Knp\Menu\Iterator;

/**
 * Recursive iterator iterating on an item
 */
class RecursiveItemIterator extends \IteratorIterator implements \RecursiveIterator
{
    public function hasChildren()
    {
        return 0 < \count($this->current());
    }

    public function getChildren()
    {
        return new static($this->current());
    }
}
knp-menu/src/Knp/Menu/Iterator/DisplayedItemFilterIterator.php000064400000000572147361032510020446 0ustar00<?php

namespace Knp\Menu\Iterator;

/**
 * Filter iterator keeping only current items
 */
class DisplayedItemFilterIterator extends \RecursiveFilterIterator
{
    public function accept()
    {
        return $this->current()->isDisplayed();
    }

    public function hasChildren()
    {
        return $this->current()->getDisplayChildren() && parent::hasChildren();
    }
}
knp-menu/src/Knp/Menu/ItemInterface.php000064400000021554147361032510013762 0ustar00<?php

namespace Knp\Menu;

/**
 * Interface implemented by a menu item.
 *
 * It roughly represents a single <li> 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;
}
knp-menu/src/Knp/Menu/Loader/LoaderInterface.php000064400000000667147361032510015502 0ustar00<?php

namespace Knp\Menu\Loader;

use Knp\Menu\ItemInterface;

interface LoaderInterface
{
    /**
     * Loads the data into a menu item
     *
     * @param mixed $data
     *
     * @return ItemInterface
     */
    public function load($data): ItemInterface;

    /**
     * Checks whether the loader can load these data
     *
     * @param mixed $data
     *
     * @return bool
     */
    public function supports($data): bool;
}
knp-menu/src/Knp/Menu/Loader/ArrayLoader.php000064400000002772147361032510014657 0ustar00<?php

namespace Knp\Menu\Loader;

use Knp\Menu\FactoryInterface;
use Knp\Menu\ItemInterface;

/**
 * Loader importing a menu tree from an array.
 *
 * The array should match the output of MenuManipulator::toArray
 */
class ArrayLoader implements LoaderInterface
{
    private $factory;

    public function __construct(FactoryInterface $factory)
    {
        $this->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;
    }
}
knp-menu/src/Knp/Menu/Loader/NodeLoader.php000064400000001636147361032510014464 0ustar00<?php

namespace Knp\Menu\Loader;

use Knp\Menu\FactoryInterface;
use Knp\Menu\ItemInterface;
use Knp\Menu\NodeInterface;

class NodeLoader implements LoaderInterface
{
    private $factory;

    public function __construct(FactoryInterface $factory)
    {
        $this->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;
    }
}
knp-menu/src/Knp/Menu/MenuItem.php000064400000032500147361032510012757 0ustar00<?php

namespace Knp\Menu;

/**
 * Default implementation of the ItemInterface
 */
class MenuItem implements ItemInterface
{
    /**
     * Name of this menu item (used for id by parent menu)
     *
     * @var string
     */
    protected $name;

    /**
     * Label to output, name is used by default
     *
     * @var string|null
     */
    protected $label;

    /**
     * Attributes for the item link
     *
     * @var array
     */
    protected $linkAttributes = [];

    /**
     * Attributes for the children list
     *
     * @var array
     */
    protected $childrenAttributes = [];

    /**
     * Attributes for the item text
     *
     * @var array
     */
    protected $labelAttributes = [];

    /**
     * Uri to use in the anchor tag
     *
     * @var string|null
     */
    protected $uri;

    /**
     * Attributes for the item
     *
     * @var array
     */
    protected $attributes = [];

    /**
     * Extra stuff associated to the item
     *
     * @var array
     */
    protected $extras = [];

    /**
     * Whether the item is displayed
     *
     * @var bool
     */
    protected $display = true;

    /**
     * Whether the children of the item are displayed
     *
     * @var bool
     */
    protected $displayChildren = true;

    /**
     * Child items
     *
     * @var ItemInterface[]
     */
    protected $children = [];

    /**
     * Parent item
     *
     * @var ItemInterface|null
     */
    protected $parent;

    /**
     * whether the item is current. null means unknown
     *
     * @var bool|null
     */
    protected $isCurrent;

    /**
     * @var FactoryInterface
     */
    protected $factory;

    /**
     * Class constructor
     *
     * @param string           $name    The name of this menu, which is how its parent will
     *                                  reference it. Also used as label if label not specified
     * @param FactoryInterface $factory
     */
    public function __construct(string $name, FactoryInterface $factory)
    {
        $this->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);
    }
}
knp-menu/src/Knp/Menu/Util/MenuManipulator.php000064400000022633147361032510015277 0ustar00<?php

namespace Knp\Menu\Util;

use Knp\Menu\ItemInterface;

class MenuManipulator
{
    /**
     * Moves item to specified position. Rearrange siblings accordingly.
     *
     * @param ItemInterface $item
     * @param int           $position Position to move child to.
     */
    public function moveToPosition(ItemInterface $item, $position): void
    {
        $this->moveChildToPosition($item->getParent(), $item, $position);
    }

    /**
     * Moves child to specified position. Rearrange other children accordingly.
     *
     * @param ItemInterface $item
     * @param ItemInterface $child    Child to move
     * @param int           $position Position to move child to
     */
    public function moveChildToPosition(ItemInterface $item, ItemInterface $child, $position): void
    {
        $name = $child->getName();
        $order = \array_keys($item->getChildren());

        $oldPosition = \array_search($name, $order);
        unset($order[$oldPosition]);

        $order = \array_values($order);

        \array_splice($order, $position, 0, $name);
        $item->reorderChildren($order);
    }

    /**
     * Moves item to first position. Rearrange siblings accordingly.
     *
     * @param ItemInterface $item
     */
    public function moveToFirstPosition(ItemInterface $item): void
    {
        $this->moveToPosition($item, 0);
    }

    /**
     * Moves item to last position. Rearrange siblings accordingly.
     *
     * @param ItemInterface $item
     */
    public function moveToLastPosition(ItemInterface $item): void
    {
        $this->moveToPosition($item, $item->getParent()->count());
    }

    /**
     * Get slice of menu as another menu.
     *
     * If offset and/or length are numeric, it works like in array_slice function:
     *
     *   If offset is non-negative, slice will start at the offset.
     *   If offset is negative, slice will start that far from the end.
     *
     *   If length is null, slice will have all elements.
     *   If length is positive, slice will have that many elements.
     *   If length is negative, slice will stop that far from the end.
     *
     * It's possible to mix names/object/numeric, for example:
     *   slice("child1", 2);
     *   slice(3, $child5);
     * Note: when using a child as limit, it will not be included in the returned menu.
     * the slice is done before this menu.
     *
     * @param ItemInterface $item
     * @param mixed         $offset Name of child, child object, or numeric offset.
     * @param mixed         $length Name of child, child object, or numeric length.
     *
     * @return ItemInterface
     */
    public function slice(ItemInterface $item, $offset, $length = null)
    {
        $names = \array_keys($item->getChildren());
        if ($offset instanceof ItemInterface) {
            $offset = $offset->getName();
        }
        if (!\is_numeric($offset)) {
            $offset = \array_search($offset, $names);
        }

        if (null !== $length) {
            if ($length instanceof ItemInterface) {
                $length = $length->getName();
            }
            if (!\is_numeric($length)) {
                $index = \array_search($length, $names);
                $length = ($index < $offset) ? 0 : $index - $offset;
            }
        }

        $slicedItem = $item->copy();
        $children = \array_slice($slicedItem->getChildren(), $offset, $length);
        $slicedItem->setChildren($children);

        return $slicedItem;
    }

    /**
     * Split menu into two distinct menus.
     *
     * @param ItemInterface $item
     * @param mixed         $length Name of child, child object, or numeric length.
     *
     * @return array Array with two menus, with "primary" and "secondary" key
     */
    public function split(ItemInterface $item, $length)
    {
        return [
            'primary' => $this->slice($item, 0, $length),
            'secondary' => $this->slice($item, $length),
        ];
    }

    /**
     * Calls a method recursively on all of the children of this item
     *
     * @example
     * $menu->callRecursively('setShowChildren', array(false));
     *
     * @param ItemInterface $item
     * @param string        $method
     * @param array         $arguments
     */
    public function callRecursively(ItemInterface $item, $method, $arguments = []): void
    {
        $item->$method(...$arguments);

        foreach ($item->getChildren() as $child) {
            $this->callRecursively($child, $method, $arguments);
        }
    }

    /**
     * A string representation of this menu item
     *
     * e.g. Top Level > Second Level > This menu
     *
     * @param ItemInterface $item
     * @param string        $separator
     *
     * @return string
     */
    public function getPathAsString(ItemInterface $item, $separator = ' > ')
    {
        $children = [];
        $obj = $item;

        do {
            $children[] = $obj->getLabel();
        } while ($obj = $obj->getParent());

        return \implode($separator, \array_reverse($children));
    }

    /**
     * @param ItemInterface $item
     * @param int|null      $depth the depth until which children should be exported (null means unlimited)
     *
     * @return array
     */
    public function toArray(ItemInterface $item, $depth = null)
    {
        $array = [
            'name' => $item->getName(),
            'label' => $item->getLabel(),
            'uri' => $item->getUri(),
            'attributes' => $item->getAttributes(),
            'labelAttributes' => $item->getLabelAttributes(),
            'linkAttributes' => $item->getLinkAttributes(),
            'childrenAttributes' => $item->getChildrenAttributes(),
            'extras' => $item->getExtras(),
            'display' => $item->isDisplayed(),
            'displayChildren' => $item->getDisplayChildren(),
            'current' => $item->isCurrent(),
        ];

        // export the children as well, unless explicitly disabled
        if (0 !== $depth) {
            $childDepth = null === $depth ? null : $depth - 1;
            $array['children'] = [];
            foreach ($item->getChildren() as $key => $child) {
                $array['children'][$key] = $this->toArray($child, $childDepth);
            }
        }

        return $array;
    }

    /**
     * Renders an array ready to be used for breadcrumbs.
     *
     * Each element in the array will be an array with 3 keys:
     * - `label` containing the label of the item
     * - `url` containing the url of the item (may be `null`)
     * - `item` containing the original item (may be `null` for the extra items)
     *
     * The subItem can be one of the following forms
     *   * 'subItem'
     *   * ItemInterface object
     *   * array('subItem' => '@homepage')
     *   * array('subItem1', 'subItem2')
     *   * array(array('label' => 'subItem1', 'url' => '@homepage'), array('label' => 'subItem2'))
     *
     * @param ItemInterface $item
     * @param mixed         $subItem A string or array to append onto the end of the array
     *
     * @return array
     *
     * @throws \InvalidArgumentException if an element of the subItem is invalid
     */
    public function getBreadcrumbsArray(ItemInterface $item, $subItem = null)
    {
        $breadcrumbs = $this->buildBreadcrumbsArray($item);

        if (null === $subItem) {
            return $breadcrumbs;
        }

        if ($subItem instanceof ItemInterface) {
            $breadcrumbs[] = $this->getBreadcrumbsItem($subItem);

            return $breadcrumbs;
        }

        if (!\is_array($subItem) && !$subItem instanceof \Traversable) {
            $subItem = [$subItem];
        }

        foreach ($subItem as $key => $value) {
            switch (true) {
                case $value instanceof ItemInterface:
                    $value = $this->getBreadcrumbsItem($value);
                    break;

                case \is_array($value):
                    // Assume we already have the appropriate array format for the element
                    break;

                case \is_int($key) && \is_string($value):
                    $value = [
                        'label' => (string) $value,
                        'uri' => null,
                        'item' => null,
                    ];
                    break;

                case \is_scalar($value):
                    $value = [
                        'label' => (string) $key,
                        'uri' => (string) $value,
                        'item' => null,
                    ];
                    break;

                case null === $value:
                    $value = [
                        'label' => (string) $key,
                        'uri' => null,
                        'item' => null,
                    ];
                    break;

                default:
                    throw new \InvalidArgumentException(\sprintf('Invalid value supplied for the key "%s". It should be an item, an array or a scalar', $key));
            }

            $breadcrumbs[] = $value;
        }

        return $breadcrumbs;
    }

    private function buildBreadcrumbsArray(ItemInterface $item)
    {
        $breadcrumb = [];

        do {
            $breadcrumb[] = $this->getBreadcrumbsItem($item);
        } while ($item = $item->getParent());

        return \array_reverse($breadcrumb);
    }

    private function getBreadcrumbsItem(ItemInterface $item)
    {
        return [
            'label' => $item->getLabel(),
            'uri' => $item->getUri(),
            'item' => $item,
        ];
    }
}
knp-menu/src/Knp/Menu/Provider/PimpleProvider.php000064400000001044147361032510015766 0ustar00<?php

namespace Knp\Menu\Provider;

@trigger_error('The '.__NAMESPACE__.'\PimpleProvider class is deprecated since version 2.1 and will be removed in 3.0. Use the '.__NAMESPACE__.'\ArrayAccessProvider class instead.', E_USER_DEPRECATED);

/**
 * Menu provider getting menus from a Pimple 1 container
 *
 * @deprecated use the ArrayAccessProvider instead.
 */
class PimpleProvider extends ArrayAccessProvider
{
    public function __construct(\Pimple $pimple, array $menuIds = array())
    {
        parent::__construct($pimple, $menuIds);
    }
}
knp-menu/src/Knp/Menu/Provider/ArrayAccessProvider.php000064400000002503147361032510016741 0ustar00<?php

namespace Knp\Menu\Provider;

use Knp\Menu\ItemInterface;

/**
 * A menu provider getting the menus from a class implementing ArrayAccess.
 *
 * In case the value stored in the registry is a callable rather than an ItemInterface,
 * it will be called with the options as first argument and the registry as second argument
 * and is expected to return a menu item.
 */
class ArrayAccessProvider implements MenuProviderInterface
{
    private $registry;
    private $menuIds;

    /**
     * @param \ArrayAccess $registry
     * @param array        $menuIds  The map between menu identifiers and registry keys
     */
    public function __construct(\ArrayAccess $registry, array $menuIds = [])
    {
        $this->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]);
    }
}
knp-menu/src/Knp/Menu/Provider/PsrProvider.php000064400000001711147361032510015305 0ustar00<?php

namespace Knp\Menu\Provider;

use Knp\Menu\ItemInterface;
use Psr\Container\ContainerInterface;

/**
 * A menu provider getting the menus from a PSR-11 container.
 *
 * This menu provider does not support using options, as it cannot pass them to the container
 * to alter the menu building. Use a different provider in case you need support for options.
 */
class PsrProvider implements MenuProviderInterface
{
    private $container;

    public function __construct(ContainerInterface $container)
    {
        $this->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);
    }
}
knp-menu/src/Knp/Menu/Provider/LazyProvider.php000064400000002412147361032510015457 0ustar00<?php

namespace Knp\Menu\Provider;

use Knp\Menu\ItemInterface;

/**
 * A menu provider building menus lazily thanks to builder callables.
 *
 * Builders can either be callables or a factory for an object callable
 * represented as array(Closure, method), where the Closure gets called
 * to instantiate the object.
 */
class LazyProvider implements MenuProviderInterface
{
    private $builders;

    public function __construct(array $builders)
    {
        $this->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]);
    }
}
knp-menu/src/Knp/Menu/Provider/MenuProviderInterface.php000064400000001170147361032510017265 0ustar00<?php

namespace Knp\Menu\Provider;

use Knp\Menu\ItemInterface;

interface MenuProviderInterface
{
    /**
     * Retrieves a menu by its name
     *
     * @param string $name
     * @param array  $options
     *
     * @return ItemInterface
     *
     * @throws \InvalidArgumentException if the menu does not exists
     */
    public function get(string $name, array $options = []): ItemInterface;

    /**
     * Checks whether a menu exists in this provider
     *
     * @param string $name
     * @param array  $options
     *
     * @return bool
     */
    public function has(string $name, array $options = []): bool;
}
knp-menu/src/Knp/Menu/Provider/ChainProvider.php000064400000001650147361032510015565 0ustar00<?php

namespace Knp\Menu\Provider;

use Knp\Menu\ItemInterface;

class ChainProvider implements MenuProviderInterface
{
    private $providers;

    /**
     * @param MenuProviderInterface[]|iterable $providers
     */
    public function __construct($providers)
    {
        $this->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;
    }
}
knp-menu/src/Knp/Menu/Twig/Helper.php000064400000013512147361032510013367 0ustar00<?php

namespace Knp\Menu\Twig;

use Knp\Menu\ItemInterface;
use Knp\Menu\Matcher\MatcherInterface;
use Knp\Menu\Provider\MenuProviderInterface;
use Knp\Menu\Renderer\RendererProviderInterface;
use Knp\Menu\Util\MenuManipulator;

/**
 * Helper class containing logic to retrieve and render menus from templating engines
 */
class Helper
{
    private $rendererProvider;
    private $menuProvider;
    private $menuManipulator;
    private $matcher;

    /**
     * @param RendererProviderInterface  $rendererProvider
     * @param MenuProviderInterface|null $menuProvider
     * @param MenuManipulator|null       $menuManipulator
     * @param MatcherInterface|null      $matcher
     */
    public function __construct(RendererProviderInterface $rendererProvider, MenuProviderInterface $menuProvider = null, MenuManipulator $menuManipulator = null, MatcherInterface $matcher = null)
    {
        $this->rendererProvider = $rendererProvider;
        $this->menuProvider = $menuProvider;
        $this->menuManipulator = $menuManipulator;
        $this->matcher = $matcher;
    }

    /**
     * Retrieves item in the menu, eventually using the menu provider.
     *
     * @param ItemInterface|string $menu
     * @param array                $path
     * @param array                $options
     *
     * @return ItemInterface
     *
     * @throws \BadMethodCallException   when there is no menu provider and the menu is given by name
     * @throws \LogicException
     * @throws \InvalidArgumentException when the path is invalid
     */
    public function get($menu, array $path = [], array $options = [])
    {
        if (!$menu instanceof ItemInterface) {
            if (null === $this->menuProvider) {
                throw new \BadMethodCallException('A menu provider must be set to retrieve a menu');
            }

            $menuName = $menu;
            $menu = $this->menuProvider->get($menuName, $options);

            if (!$menu instanceof ItemInterface) {
                throw new \LogicException(\sprintf('The menu "%s" exists, but is not a valid menu item object. Check where you created the menu to be sure it returns an ItemInterface object.', $menuName));
            }
        }

        foreach ($path as $child) {
            $menu = $menu->getChild($child);
            if (null === $menu) {
                throw new \InvalidArgumentException(\sprintf('The menu has no child named "%s"', $child));
            }
        }

        return $menu;
    }

    /**
     * Renders a menu with the specified renderer.
     *
     * If the argument is an array, it will follow the path in the tree to
     * get the needed item. The first element of the array is the whole menu.
     * If the menu is a string instead of an ItemInterface, the provider
     * will be used.
     *
     * @param ItemInterface|string|array $menu
     * @param array                      $options
     * @param string                     $renderer
     *
     * @return string
     *
     * @throws \InvalidArgumentException
     */
    public function render($menu, array $options = [], $renderer = null)
    {
        $menu = $this->castMenu($menu);

        return $this->rendererProvider->get($renderer)->render($menu, $options);
    }

    /**
     * Renders an array ready to be used for breadcrumbs.
     *
     * Each element in the array will be an array with 3 keys:
     * - `label` containing the label of the item
     * - `url` containing the url of the item (may be `null`)
     * - `item` containing the original item (may be `null` for the extra items)
     *
     * The subItem can be one of the following forms
     *   * 'subItem'
     *   * ItemInterface object
     *   * ['subItem' => '@homepage']
     *   * ['subItem1', 'subItem2']
     *   * [['label' => 'subItem1', 'url' => '@homepage'], ['label' => 'subItem2']]
     *
     * @param mixed $menu
     * @param mixed $subItem A string or array to append onto the end of the array
     *
     * @return array
     */
    public function getBreadcrumbsArray($menu, $subItem = null)
    {
        if (null === $this->menuManipulator) {
            throw new \BadMethodCallException('The menu manipulator must be set to get the breadcrumbs array');
        }

        $menu = $this->castMenu($menu);

        return $this->menuManipulator->getBreadcrumbsArray($menu, $subItem);
    }

    /**
     * Returns the current item of a menu.
     *
     * @param ItemInterface|array|string $menu
     *
     * @return ItemInterface|null
     */
    public function getCurrentItem($menu)
    {
        if (null === $this->matcher) {
            throw new \BadMethodCallException('The matcher must be set to get the current item of a menu');
        }

        $menu = $this->castMenu($menu);

        return $this->retrieveCurrentItem($menu);
    }

    /**
     * @param ItemInterface|array|string $menu
     *
     * @return ItemInterface
     */
    private function castMenu($menu)
    {
        if (!$menu instanceof ItemInterface) {
            $path = [];
            if (\is_array($menu)) {
                if (empty($menu)) {
                    throw new \InvalidArgumentException('The array cannot be empty');
                }
                $path = $menu;
                $menu = \array_shift($path);
            }

            return $this->get($menu, $path);
        }

        return $menu;
    }

    /**
     * @param ItemInterface $item
     *
     * @return ItemInterface|null
     */
    private function retrieveCurrentItem(ItemInterface $item)
    {
        if ($this->matcher->isCurrent($item)) {
            return $item;
        }

        if ($this->matcher->isAncestor($item)) {
            foreach ($item->getChildren() as $child) {
                $currentItem = $this->retrieveCurrentItem($child);

                if (null !== $currentItem) {
                    return $currentItem;
                }
            }
        }

        return null;
    }
}
knp-menu/src/Knp/Menu/Twig/MenuExtension.php000064400000010625147361032510014753 0ustar00<?php

namespace Knp\Menu\Twig;

use Knp\Menu\ItemInterface;
use Knp\Menu\Matcher\MatcherInterface;
use Knp\Menu\Util\MenuManipulator;
use Twig\Extension\AbstractExtension;
use Twig\TwigFilter;
use Twig\TwigFunction;
use Twig\TwigTest;

class MenuExtension extends AbstractExtension
{
    private $helper;
    private $matcher;
    private $menuManipulator;

    public function __construct(Helper $helper, MatcherInterface $matcher = null, MenuManipulator $menuManipulator = null)
    {
        $this->helper = $helper;
        $this->matcher = $matcher;
        $this->menuManipulator = $menuManipulator;
    }

    public function getFunctions(): array
    {
        return [
             new TwigFunction('knp_menu_get', [$this, 'get']),
             new TwigFunction('knp_menu_render', [$this, 'render'], ['is_safe' => ['html']]),
             new TwigFunction('knp_menu_get_breadcrumbs_array', [$this, 'getBreadcrumbsArray']),
             new TwigFunction('knp_menu_get_current_item', [$this, 'getCurrentItem']),
        ];
    }

    public function getFilters(): array
    {
        return [
            new TwigFilter('knp_menu_as_string', [$this, 'pathAsString']),
        ];
    }

    public function getTests(): array
    {
        return [
            new TwigTest('knp_menu_current', [$this, 'isCurrent']),
            new TwigTest('knp_menu_ancestor', [$this, 'isAncestor']),
        ];
    }

    /**
     * Retrieves an item following a path in the tree.
     *
     * @param ItemInterface|string $menu
     * @param array                $path
     * @param array                $options
     *
     * @return ItemInterface
     */
    public function get($menu, array $path = [], array $options = []): ItemInterface
    {
        return $this->helper->get($menu, $path, $options);
    }

    /**
     * Renders a menu with the specified renderer.
     *
     * @param ItemInterface|string|array $menu
     * @param array                      $options
     * @param string                     $renderer
     *
     * @return string
     */
    public function render($menu, array $options = [], $renderer = null): string
    {
        return $this->helper->render($menu, $options, $renderer);
    }

    /**
     * Returns an array ready to be used for breadcrumbs.
     *
     * @param ItemInterface|array|string $menu
     * @param string|array|null          $subItem
     *
     * @return array
     */
    public function getBreadcrumbsArray($menu, $subItem = null): array
    {
        return $this->helper->getBreadcrumbsArray($menu, $subItem);
    }

    /**
     * Returns the current item of a menu.
     *
     * @param ItemInterface|string $menu
     *
     * @return ItemInterface
     */
    public function getCurrentItem($menu): ItemInterface
    {
        $rootItem = $this->get($menu);

        $currentItem = $this->helper->getCurrentItem($rootItem);

        if (null === $currentItem) {
            $currentItem = $rootItem;
        }

        return $currentItem;
    }

    /**
     * A string representation of this menu item
     *
     * e.g. Top Level > Second Level > This menu
     *
     * @param ItemInterface $menu
     * @param string        $separator
     *
     * @return string
     */
    public function pathAsString(ItemInterface $menu, $separator = ' > '): string
    {
        if (null === $this->menuManipulator) {
            throw new \BadMethodCallException('The menu manipulator must be set to get the breadcrumbs array');
        }

        return $this->menuManipulator->getPathAsString($menu, $separator);
    }

    /**
     * Checks whether an item is current.
     *
     * @param ItemInterface $item
     *
     * @return bool
     */
    public function isCurrent(ItemInterface $item): bool
    {
        if (null === $this->matcher) {
            throw new \BadMethodCallException('The matcher must be set to get the breadcrumbs array');
        }

        return $this->matcher->isCurrent($item);
    }

    /**
     * Checks whether an item is the ancestor of a current item.
     *
     * @param ItemInterface $item
     * @param int|null      $depth The max depth to look for the item
     *
     * @return bool
     */
    public function isAncestor(ItemInterface $item, ?int $depth = null): bool
    {
        if (null === $this->matcher) {
            throw new \BadMethodCallException('The matcher must be set to get the breadcrumbs array');
        }

        return $this->matcher->isAncestor($item, $depth);
    }
}
knp-menu/src/Knp/Menu/Integration/Silex/KnpMenuServiceProvider.php000064400000007422147361032510021221 0ustar00<?php

namespace Knp\Menu\Integration\Silex;

use Knp\Menu\Integration\Symfony\RoutingExtension;
use Silex\Application;
use Silex\ServiceProviderInterface;
use Knp\Menu\Matcher\Matcher;
use Knp\Menu\MenuFactory;
use Knp\Menu\Renderer\ListRenderer;
use Knp\Menu\Renderer\TwigRenderer;
use Knp\Menu\Provider\ArrayAccessProvider as PimpleMenuProvider;
use Knp\Menu\Renderer\ArrayAccessProvider as PimpleRendererProvider;
use Knp\Menu\Twig\Helper;
use Knp\Menu\Twig\MenuExtension;
use Knp\Menu\Util\MenuManipulator;

class KnpMenuServiceProvider implements ServiceProviderInterface
{
    public function register(Application $app)
    {
        $app['knp_menu.factory'] = $app->share(function () use ($app) {
            $factory = new MenuFactory();

            if (isset($app['url_generator'])) {
                $factory->addExtension(new RoutingExtension($app['url_generator']));
            }

            return $factory;
        });

        $app['knp_menu.matcher'] = $app->share(function () use ($app) {
            $matcher = new Matcher();

            if (isset($app['knp_menu.matcher.configure'])) {
                $app['knp_menu.matcher.configure']($matcher);
            }

            return $matcher;
        });

        $app['knp_menu.renderer.list'] = $app->share(function () use ($app) {
            return new ListRenderer($app['knp_menu.matcher'], array(), $app['charset']);
        });

        $app['knp_menu.menu_provider'] = $app->share(function () use ($app) {
            return new PimpleMenuProvider($app, $app['knp_menu.menus']);
        });

        if (!isset($app['knp_menu.menus'])) {
            $app['knp_menu.menus'] = array();
        }

        $app['knp_menu.renderer_provider'] = $app->share(function () use ($app) {
            $app['knp_menu.renderers'] = array_merge(
                array('list' => 'knp_menu.renderer.list'),
                isset($app['knp_menu.renderer.twig']) ? array('twig' => 'knp_menu.renderer.twig') : array(),
                isset($app['knp_menu.renderers']) ? $app['knp_menu.renderers'] : array()
            );

            return new PimpleRendererProvider($app, $app['knp_menu.default_renderer'], $app['knp_menu.renderers']);
        });

        $app['knp_menu.menu_manipulator'] = $app->share(function () use ($app) {
            return new MenuManipulator();
        });

        if (!isset($app['knp_menu.default_renderer'])) {
            $app['knp_menu.default_renderer'] = 'list';
        }

        $app['knp_menu.helper'] = $app->share(function () use ($app) {
            return new Helper($app['knp_menu.renderer_provider'], $app['knp_menu.menu_provider'], $app['knp_menu.menu_manipulator']);
        });

        if (isset($app['twig'])) {
            $app['knp_menu.twig_extension'] = $app->share(function () use ($app) {
                return new MenuExtension($app['knp_menu.helper'], $app['knp_menu.matcher'], $app['knp_menu.menu_manipulator']);
            });

            $app['knp_menu.renderer.twig'] = $app->share(function () use ($app) {
                return new TwigRenderer($app['twig'], $app['knp_menu.template'], $app['knp_menu.matcher']);
            });

            if (!isset($app['knp_menu.template'])) {
                $app['knp_menu.template'] = 'knp_menu.html.twig';
            }

            $app['twig'] = $app->share($app->extend('twig', function (\Twig_Environment $twig) use ($app) {
                $twig->addExtension($app['knp_menu.twig_extension']);

                return $twig;
            }));

            $app['twig.loader.filesystem'] = $app->share($app->extend('twig.loader.filesystem', function (\Twig_Loader_Filesystem $loader) use ($app) {
                $loader->addPath(__DIR__.'/../../Resources/views');

                return $loader;
            }));
        }
    }

    public function boot(Application $app) {}
}
knp-menu/src/Knp/Menu/Integration/Symfony/RoutingExtension.php000064400000002426147361032510020513 0ustar00<?php

namespace Knp\Menu\Integration\Symfony;

use Knp\Menu\Factory\ExtensionInterface;
use Knp\Menu\ItemInterface;
use Symfony\Component\Routing\Generator\UrlGeneratorInterface;

/**
 * Factory able to use the Symfony Routing component to build the url
 */
class RoutingExtension implements ExtensionInterface
{
    private $generator;

    public function __construct(UrlGeneratorInterface $generator)
    {
        $this->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
    {
    }
}
knp-menu/src/Knp/Menu/Resources/views/knp_menu_base.html.twig000064400000000142147361032510020274 0ustar00{% if options.compressed %}{{ block('compressed_root') }}{% else %}{{ block('root') }}{% endif %}
knp-menu/src/Knp/Menu/Resources/views/knp_menu_ordered.html.twig000064400000000456147361032510021016 0ustar00{% extends 'knp_menu.html.twig' %}

{% block list %}
{% import 'knp_menu.html.twig' as macros %}

{% if item.hasChildren and options.depth is not same as(0) and item.displayChildren %}
    <ol{{ macros.attributes(listAttributes) }}>
        {{ block('children') }}
    </ol>
{% endif %}
{% endblock %}
knp-menu/src/Knp/Menu/Resources/views/knp_menu.html.twig000064400000007555147361032510017321 0ustar00{% extends 'knp_menu_base.html.twig' %}

{% macro attributes(attributes) %}
{% for name, value in attributes %}
    {%- if value is not none and value is not same as(false) -%}
        {{- ' %s="%s"'|format(name, value is same as(true) ? name|e : value|e)|raw -}}
    {%- endif -%}
{%- endfor -%}
{% endmacro %}

{% block compressed_root %}
{% apply spaceless %}
{{ block('root') }}
{% endapply %}
{% endblock %}

{% block root %}
{% set listAttributes = item.childrenAttributes %}
{{ block('list') -}}
{% endblock %}

{% block list %}
{% if item.hasChildren and options.depth is not same as(0) and item.displayChildren %}
    {% import _self as knp_menu %}
    <ul{{ knp_menu.attributes(listAttributes) }}>
        {{ block('children') }}
    </ul>
{% endif %}
{% endblock %}

{% block children %}
{# save current variables #}
{% set currentOptions = options %}
{% set currentItem = item %}
{# update the depth for children #}
{% if options.depth is not none %}
{% set options = options|merge({'depth': currentOptions.depth - 1}) %}
{% endif %}
{# update the matchingDepth for children #}
{% if options.matchingDepth is not none and options.matchingDepth > 0 %}
{% set options = options|merge({'matchingDepth': currentOptions.matchingDepth - 1}) %}
{% endif %}
{% for item in currentItem.children %}
    {{ block('item') }}
{% endfor %}
{# restore current variables #}
{% set item = currentItem %}
{% set options = currentOptions %}
{% endblock %}

{% block item %}
{% if item.displayed %}
{# building the class of the item #}
    {%- set classes = item.attribute('class') is not empty ? [item.attribute('class')] : [] %}
    {%- if matcher.isCurrent(item) %}
        {%- set classes = classes|merge([options.currentClass]) %}
    {%- elseif matcher.isAncestor(item, options.matchingDepth) %}
        {%- set classes = classes|merge([options.ancestorClass]) %}
    {%- endif %}
    {%- if item.actsLikeFirst %}
        {%- set classes = classes|merge([options.firstClass]) %}
    {%- endif %}
    {%- if item.actsLikeLast %}
        {%- set classes = classes|merge([options.lastClass]) %}
    {%- endif %}

    {# Mark item as "leaf" (no children) or as "branch" (has children that are displayed) #}
    {% if item.hasChildren and options.depth is not same as(0) %}
        {% if options.branch_class is not empty and item.displayChildren %}
            {%- set classes = classes|merge([options.branch_class]) %}
        {% endif %}
    {% elseif options.leaf_class is not empty %}
        {%- set classes = classes|merge([options.leaf_class]) %}
    {%- endif %}

    {%- set attributes = item.attributes %}
    {%- if classes is not empty %}
        {%- set attributes = attributes|merge({'class': classes|join(' ')}) %}
    {%- endif %}
{# displaying the item #}
    {% import _self as knp_menu %}
    <li{{ knp_menu.attributes(attributes) }}>
        {%- if item.uri is not empty and (not matcher.isCurrent(item) or options.currentAsLink) %}
        {{ block('linkElement') }}
        {%- else %}
        {{ block('spanElement') }}
        {%- endif %}
{# render the list of children#}
        {%- set childrenClasses = item.childrenAttribute('class') is not empty ? [item.childrenAttribute('class')] : [] %}
        {%- set childrenClasses = childrenClasses|merge(['menu_level_' ~ item.level]) %}
        {%- set listAttributes = item.childrenAttributes|merge({'class': childrenClasses|join(' ') }) %}
        {{ block('list') }}
    </li>
{% endif %}
{% endblock %}

{% block linkElement %}{% import _self as knp_menu %}<a href="{{ item.uri }}"{{ knp_menu.attributes(item.linkAttributes) }}>{{ block('label') }}</a>{% endblock %}

{% block spanElement %}{% import _self as knp_menu %}<span{{ knp_menu.attributes(item.labelAttributes) }}>{{ block('label') }}</span>{% endblock %}

{% block label %}{% if options.allow_safe_labels and item.getExtra('safe_label', false) %}{{ item.label|raw }}{% else %}{{ item.label }}{% endif %}{% endblock %}
knp-menu/src/Knp/Menu/NodeInterface.php000064400000001154147361032510013743 0ustar00<?php

namespace Knp\Menu;

/**
 * Interface implemented by a node to construct a menu from a tree.
 */
interface NodeInterface
{
    /**
     * Get the name of the node
     *
     * Each child of a node must have a unique name
     *
     * @return string
     */
    public function getName(): string;

    /**
     * Get the options for the factory to create the item for this node
     *
     * @return array
     */
    public function getOptions(): array;

    /**
     * Get the child nodes implementing NodeInterface
     *
     * @return \Traversable
     */
    public function getChildren(): \Traversable;
}
knp-menu/src/Knp/Menu/Renderer/PimpleProvider.php000064400000001116147361032510015742 0ustar00<?php

namespace Knp\Menu\Renderer;

@trigger_error('The '.__NAMESPACE__.'\PimpleProvider class is deprecated since version 2.1 and will be removed in 3.0. Use the '.__NAMESPACE__.'\ArrayAccessProvider class instead.', E_USER_DEPRECATED);

/**
 * Renderer provider getting renderers from a Pimple 1 container
 *
 * @deprecated use the ArrayAccessProvider instead.
 */
class PimpleProvider extends ArrayAccessProvider
{
    public function __construct(\Pimple $pimple, $defaultRenderer, array $rendererIds)
    {
        parent::__construct($pimple, $defaultRenderer, $rendererIds);
    }
}
knp-menu/src/Knp/Menu/Renderer/ArrayAccessProvider.php000064400000002312147361032510016713 0ustar00<?php

namespace Knp\Menu\Renderer;

/**
 * A renderer provider getting the renderers from a class implementing ArrayAccess.
 */
class ArrayAccessProvider implements RendererProviderInterface
{
    private $registry;
    private $rendererIds;
    private $defaultRenderer;

    /**
     * @param \ArrayAccess $registry
     * @param string       $defaultRenderer The name of the renderer used by default
     * @param array        $rendererIds     The map between renderer names and regstry keys
     */
    public function __construct(\ArrayAccess $registry, $defaultRenderer, array $rendererIds)
    {
        $this->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]);
    }
}
knp-menu/src/Knp/Menu/Renderer/PsrProvider.php000064400000002501147361032510015257 0ustar00<?php

namespace Knp\Menu\Renderer;

use Psr\Container\ContainerInterface;

/**
 * A renderer provider getting the renderer from a PSR-11 container.
 *
 * This menu provider does not support using options, as it cannot pass them to the container
 * to alter the menu building. Use a different provider in case you need support for options.
 */
class PsrProvider implements RendererProviderInterface
{
    private $container;
    private $defaultRenderer;

    /**
     * PsrProvider constructor.
     *
     * @param ContainerInterface $container
     * @param string             $defaultRenderer id of the default renderer (it should exist in the container to avoid weird failures)
     */
    public function __construct(ContainerInterface $container, $defaultRenderer)
    {
        $this->container = $container;
        $this->defaultRenderer = $defaultRenderer;
    }

    public function get(?string $name = null): RendererInterface
    {
        if (null === $name) {
            $name = $this->defaultRenderer;
        }

        if (!$this->container->has($name)) {
            throw new \InvalidArgumentException(\sprintf('The renderer "%s" is not defined.', $name));
        }

        return $this->container->get($name);
    }

    public function has(string $name): bool
    {
        return $this->container->has($name);
    }
}
knp-menu/src/Knp/Menu/Renderer/Renderer.php000064400000005001147361032510014544 0ustar00<?php

namespace Knp\Menu\Renderer;

abstract class Renderer
{
    protected $charset = 'UTF-8';

    /**
     * @param string|null $charset
     */
    public function __construct(?string $charset = null)
    {
        if (null !== $charset) {
            $this->charset = $charset;
        }
    }

    /**
     * Renders a HTML attribute
     *
     * @param string      $name
     * @param string|bool $value
     *
     * @return string
     */
    protected function renderHtmlAttribute(string $name, $value): string
    {
        if (true === $value) {
            return \sprintf('%s="%s"', $name, $this->escape($name));
        }

        return \sprintf('%s="%s"', $name, $this->escape($value));
    }

    /**
     * Renders HTML attributes
     *
     * @param array $attributes
     *
     * @return string
     */
    protected function renderHtmlAttributes(array $attributes): string
    {
        return \implode('', \array_map([$this, 'htmlAttributesCallback'], \array_keys($attributes), \array_values($attributes)));
    }

    /**
     * Prepares an attribute key and value for HTML representation.
     *
     * It removes empty attributes.
     *
     * @param string           $name  The attribute name
     * @param string|bool|null $value The attribute value
     *
     * @return string The HTML representation of the HTML key attribute pair.
     */
    private function htmlAttributesCallback(string $name, $value): string
    {
        if (false === $value || null === $value) {
            return '';
        }

        return ' '.$this->renderHtmlAttribute($name, $value);
    }

    /**
     * Escapes an HTML value
     *
     * @param string $value
     *
     * @return string
     */
    protected function escape(string $value): string
    {
        return $this->fixDoubleEscape(\htmlspecialchars($value, ENT_QUOTES | ENT_SUBSTITUTE, $this->charset));
    }

    /**
     * Fixes double escaped strings.
     *
     * @param string $escaped string to fix
     *
     * @return string A single escaped string
     */
    protected function fixDoubleEscape(string $escaped): string
    {
        return \preg_replace('/&amp;([a-z]+|(#\d+)|(#x[\da-f]+));/i', '&$1;', $escaped);
    }

    /**
     * Get the HTML charset
     *
     * @return string
     */
    public function getCharset(): string
    {
        return $this->charset;
    }

    /**
     * Set the HTML charset
     *
     * @param string $charset
     */
    public function setCharset(string $charset): void
    {
        $this->charset = $charset;
    }
}
knp-menu/src/Knp/Menu/Renderer/ListRenderer.php000064400000020417147361032510015410 0ustar00<?php

namespace Knp\Menu\Renderer;

use Knp\Menu\ItemInterface;
use Knp\Menu\Matcher\MatcherInterface;

/**
 * Renders MenuItem tree as unordered list
 */
class ListRenderer extends Renderer implements RendererInterface
{
    protected $matcher;
    protected $defaultOptions;

    /**
     * @param MatcherInterface $matcher
     * @param array            $defaultOptions
     * @param string|null      $charset
     */
    public function __construct(MatcherInterface $matcher, array $defaultOptions = [], ?string $charset = null)
    {
        $this->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('<ul'.$this->renderHtmlAttributes($attributes).'>', 'ul', $item->getLevel(), $options);
        $html .= $this->renderChildren($item, $options);
        $html .= $this->format('</ul>', 'ul', $item->getLevel(), $options);

        return $html;
    }

    /**
     * Renders all of the children of this menu.
     *
     * This calls ->renderItem() on each menu item, which instructs each
     * menu item to render themselves as an <li> tag (with nested ul if it
     * has children).
     * This method updates the depth for the children.
     *
     * @param ItemInterface $item
     * @param array         $options The options to render the item.
     *
     * @return string
     */
    protected function renderChildren(ItemInterface $item, array $options): string
    {
        // render children with a depth - 1
        if (null !== $options['depth']) {
            $options['depth'] = $options['depth'] - 1;
        }

        if (null !== $options['matchingDepth'] && $options['matchingDepth'] > 0) {
            $options['matchingDepth'] = $options['matchingDepth'] - 1;
        }

        $html = '';
        foreach ($item->getChildren() as $child) {
            $html .= $this->renderItem($child, $options);
        }

        return $html;
    }

    /**
     * Called by the parent menu item to render this menu.
     *
     * This renders the li tag to fit into the parent ul as well as its
     * own nested ul tag if this menu item has children
     *
     * @param ItemInterface $item
     * @param array         $options The options to render the item
     *
     * @return string
     */
    protected function renderItem(ItemInterface $item, array $options): string
    {
        // if we don't have access or this item is marked to not be shown
        if (!$item->isDisplayed()) {
            return '';
        }

        // create an array than can be imploded as a class list
        $class = (array) $item->getAttribute('class');

        if ($this->matcher->isCurrent($item)) {
            $class[] = $options['currentClass'];
        } elseif ($this->matcher->isAncestor($item, $options['matchingDepth'])) {
            $class[] = $options['ancestorClass'];
        }

        if ($item->actsLikeFirst()) {
            $class[] = $options['firstClass'];
        }
        if ($item->actsLikeLast()) {
            $class[] = $options['lastClass'];
        }

        if ($item->hasChildren() && 0 !== $options['depth']) {
            if (null !== $options['branch_class'] && $item->getDisplayChildren()) {
                $class[] = $options['branch_class'];
            }
        } elseif (null !== $options['leaf_class']) {
            $class[] = $options['leaf_class'];
        }

        // retrieve the attributes and put the final class string back on it
        $attributes = $item->getAttributes();
        if (!empty($class)) {
            $attributes['class'] = \implode(' ', $class);
        }

        // opening li tag
        $html = $this->format('<li'.$this->renderHtmlAttributes($attributes).'>', 'li', $item->getLevel(), $options);

        // render the text/link inside the li tag
        //$html .= $this->format($item->getUri() ? $item->renderLink() : $item->renderLabel(), 'link', $item->getLevel());
        $html .= $this->renderLink($item, $options);

        // renders the embedded ul
        $childrenClass = (array) $item->getChildrenAttribute('class');
        $childrenClass[] = 'menu_level_'.$item->getLevel();

        $childrenAttributes = $item->getChildrenAttributes();
        $childrenAttributes['class'] = \implode(' ', $childrenClass);

        $html .= $this->renderList($item, $childrenAttributes, $options);

        // closing li tag
        $html .= $this->format('</li>', 'li', $item->getLevel(), $options);

        return $html;
    }

    /**
     * Renders the link in a a tag with link attributes or
     * the label in a span tag with label attributes
     *
     * Tests if item has a an uri and if not tests if it's
     * the current item and if the text has to be rendered
     * as a link or not.
     *
     * @param ItemInterface $item    The item to render the link or label for
     * @param array         $options The options to render the item
     *
     * @return string
     */
    protected function renderLink(ItemInterface $item, array $options = []): string
    {
        if ($item->getUri() && (!$this->matcher->isCurrent($item) || $options['currentAsLink'])) {
            $text = $this->renderLinkElement($item, $options);
        } else {
            $text = $this->renderSpanElement($item, $options);
        }

        return $this->format($text, 'link', $item->getLevel(), $options);
    }

    protected function renderLinkElement(ItemInterface $item, array $options): string
    {
        return \sprintf('<a href="%s"%s>%s</a>', $this->escape($item->getUri()), $this->renderHtmlAttributes($item->getLinkAttributes()), $this->renderLabel($item, $options));
    }

    protected function renderSpanElement(ItemInterface $item, array $options): string
    {
        return \sprintf('<span%s>%s</span>', $this->renderHtmlAttributes($item->getLabelAttributes()), $this->renderLabel($item, $options));
    }

    protected function renderLabel(ItemInterface $item, array $options): string
    {
        if ($options['allow_safe_labels'] && $item->getExtra('safe_label', false)) {
            return $item->getLabel();
        }

        return $this->escape($item->getLabel());
    }

    /**
     * If $this->renderCompressed is on, this will apply the necessary
     * spacing and line-breaking so that the particular thing being rendered
     * makes up its part in a fully-rendered and spaced menu.
     *
     * @param string $html    The html to render in an (un)formatted way
     * @param string $type    The type [ul,link,li] of thing being rendered
     * @param int    $level
     * @param array  $options
     *
     * @return string
     */
    protected function format(string $html, string $type, int $level, array $options): string
    {
        if ($options['compressed']) {
            return $html;
        }

        switch ($type) {
            case 'ul':
            case 'link':
                $spacing = $level * 4;
                break;

            case 'li':
                $spacing = $level * 4 - 2;
                break;
        }

        return \str_repeat(' ', $spacing).$html."\n";
    }
}
knp-menu/src/Knp/Menu/Renderer/TwigRenderer.php000064400000003205147361032510015403 0ustar00<?php

namespace Knp\Menu\Renderer;

use Knp\Menu\ItemInterface;
use Knp\Menu\Matcher\MatcherInterface;
use Twig\Environment;

class TwigRenderer implements RendererInterface
{
    /**
     * @var Environment
     */
    private $environment;
    private $matcher;
    private $defaultOptions;

    /**
     * @param Environment      $environment
     * @param string           $template
     * @param MatcherInterface $matcher
     * @param array            $defaultOptions
     */
    public function __construct(Environment $environment, $template, MatcherInterface $matcher, array $defaultOptions = [])
    {
        $this->environment = $environment;
        $this->matcher = $matcher;
        $this->defaultOptions = \array_merge([
            'depth' => null,
            'matchingDepth' => null,
            'currentAsLink' => true,
            'currentClass' => 'current',
            'ancestorClass' => 'current_ancestor',
            'firstClass' => 'first',
            'lastClass' => 'last',
            'template' => $template,
            'compressed' => false,
            'allow_safe_labels' => false,
            'clear_matcher' => true,
            'leaf_class' => null,
            'branch_class' => null,
        ], $defaultOptions);
    }

    public function render(ItemInterface $item, array $options = []): string
    {
        $options = \array_merge($this->defaultOptions, $options);

        $html = $this->environment->render($options['template'], ['item' => $item, 'options' => $options, 'matcher' => $this->matcher]);

        if ($options['clear_matcher']) {
            $this->matcher->clear();
        }

        return $html;
    }
}
knp-menu/src/Knp/Menu/Renderer/RendererInterface.php000064400000001553147361032510016375 0ustar00<?php

namespace Knp\Menu\Renderer;

use Knp\Menu\ItemInterface;

interface RendererInterface
{
    /**
     * Renders menu tree.
     *
     * Common options:
     *      - depth: The depth at which the item is rendered
     *          null: no limit
     *          0: no children
     *          1: only direct children
     *      - currentAsLink: whether the current item should be a link
     *      - currentClass: class added to the current item
     *      - ancestorClass: class added to the ancestors of the current item
     *      - firstClass: class added to the first child
     *      - lastClass: class added to the last child
     *
     * @param ItemInterface $item    Menu item
     * @param array         $options some rendering options
     *
     * @return string
     */
    public function render(ItemInterface $item, array $options = []): string;
}
knp-menu/src/Knp/Menu/Renderer/RendererProviderInterface.php000064400000001123147361032510020101 0ustar00<?php

namespace Knp\Menu\Renderer;

interface RendererProviderInterface
{
    /**
     * Retrieves a renderer by its name
     *
     * If null is given, a renderer marked as default is returned.
     *
     * @param string|null $name
     *
     * @return RendererInterface
     *
     * @throws \InvalidArgumentException if the renderer does not exists
     */
    public function get(?string $name = null): RendererInterface;

    /**
     * Checks whether a renderer exists
     *
     * @param string $name
     *
     * @return bool
     */
    public function has(string $name): bool;
}
knp-menu/src/Knp/Menu/MenuFactory.php000064400000003100147361032510013462 0ustar00<?php

namespace Knp\Menu;

use Knp\Menu\Factory\CoreExtension;
use Knp\Menu\Factory\ExtensionInterface;

/**
 * Factory to create a menu from a tree
 */
class MenuFactory implements FactoryInterface
{
    /**
     * @var array[]
     */
    private $extensions = [];

    /**
     * @var ExtensionInterface[]|null
     */
    private $sorted;

    public function __construct()
    {
        $this->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;
    }
}
knp-menu/src/Knp/Menu/FactoryInterface.php000064400000000534147361032510014466 0ustar00<?php

namespace Knp\Menu;

/**
 * Interface implemented by the factory to create items
 */
interface FactoryInterface
{
    /**
     * Creates a menu item
     *
     * @param string $name
     * @param array  $options
     *
     * @return ItemInterface
     */
    public function createItem(string $name, array $options = []): ItemInterface;
}
knp-menu/CHANGELOG.md000064400000016237147361032510010122 0ustar00## 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
knp-menu/LICENSE000064400000002071147361032510007305 0ustar00Copyright (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.