This commit is contained in:
Xes
2025-08-14 22:41:49 +02:00
parent 2de81ccc46
commit 8ce45119b6
39774 changed files with 4309466 additions and 0 deletions

View File

@@ -0,0 +1,90 @@
<?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();
}
/**
* Adds a voter in the matcher.
*
* If an iterator was used to provide voters in the constructor, it will be
* converted to array when using this method, breaking any potential lazy-loading.
*
* @deprecated since 2.3. Pass voters in the constructor instead.
*
* @param VoterInterface $voter
*/
public function addVoter(VoterInterface $voter)
{
@trigger_error(\sprintf('The %s() method is deprecated since version 2.3 and will be removed in 3.0. Pass voters in the constructor instead.', __METHOD__), E_USER_DEPRECATED);
if ($this->voters instanceof \Traversable) {
$this->voters = \iterator_to_array($this->voters);
}
$this->voters[] = $voter;
}
public function isCurrent(ItemInterface $item)
{
$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, $depth = null)
{
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()
{
$this->cache = new \SplObjectStorage();
}
}

View File

@@ -0,0 +1,35 @@
<?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);
/**
* 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, $depth = null);
/**
* Clears the state of the matcher.
*/
public function clear();
}

View File

@@ -0,0 +1,38 @@
<?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($regexp)
{
$this->regexp = $regexp;
}
public function matchItem(ItemInterface $item)
{
if (null === $this->regexp || null === $item->getUri()) {
return null;
}
if (\preg_match($this->regexp, $item->getUri())) {
return true;
}
return null;
}
}

View File

@@ -0,0 +1,123 @@
<?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|null
*/
private $requestStack;
/**
* @var Request|null
*/
private $request;
public function __construct($requestStack = null)
{
if ($requestStack instanceof RequestStack) {
$this->requestStack = $requestStack;
} elseif ($requestStack instanceof Request) {
@trigger_error(sprintf('Passing a Request as the first argument for "%s" constructor is deprecated since version 2.3 and won\'t be possible in 3.0. Pass a RequestStack instead.', __CLASS__), E_USER_DEPRECATED);
// BC layer for the old API of the class
$this->request = $requestStack;
} elseif (null !== $requestStack) {
throw new \InvalidArgumentException('The first argument of %s must be null, a RequestStack or a Request. %s given', __CLASS__, is_object($requestStack) ? get_class($requestStack) : gettype($requestStack));
} else {
@trigger_error(sprintf('Not passing a RequestStack as the first argument for "%s" constructor is deprecated since version 2.3 and won\'t be possible in 3.0.', __CLASS__), E_USER_DEPRECATED);
}
}
/**
* Sets the request against which the menu should be matched.
*
* This Request is ignored in case a RequestStack is passed in the constructor.
*
* @deprecated since version 2.3. Pass a RequestStack to the constructor instead.
*
* @param Request $request
*/
public function setRequest(Request $request)
{
@trigger_error(\sprintf('The %s() method is deprecated since version 2.3 and will be removed in 3.0. Pass a RequestStack in the constructor instead.', __METHOD__), E_USER_DEPRECATED);
$this->request = $request;
}
public function matchItem(ItemInterface $item)
{
if (null !== $this->requestStack) {
$request = $this->requestStack->getMasterRequest();
} else {
$request = $this->request;
}
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)
{
$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;
}
}

View File

@@ -0,0 +1,31 @@
<?php
namespace Knp\Menu\Matcher\Voter;
use Knp\Menu\ItemInterface;
/**
* Voter based on the uri
*/
class UriVoter implements VoterInterface
{
private $uri;
public function __construct($uri = null)
{
$this->uri = $uri;
}
public function matchItem(ItemInterface $item)
{
if (null === $this->uri || null === $item->getUri()) {
return null;
}
if ($item->getUri() === $this->uri) {
return true;
}
return null;
}
}

View File

@@ -0,0 +1,23 @@
<?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);
}