Actualización

This commit is contained in:
Xes
2025-04-10 12:24:57 +02:00
parent 8969cc929d
commit 45420b6f0d
39760 changed files with 4303286 additions and 0 deletions

View File

@@ -0,0 +1,43 @@
<?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($name = null)
{
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)
{
return isset($this->rendererIds[$name]);
}
}

View File

@@ -0,0 +1,250 @@
<?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 = [], $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 = [])
{
$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)
{
/**
* 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)
{
// 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)
{
// 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 = [])
{
if ($item->getUri() && (!$item->isCurrent() || $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)
{
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)
{
return \sprintf('<span%s>%s</span>', $this->renderHtmlAttributes($item->getLabelAttributes()), $this->renderLabel($item, $options));
}
protected function renderLabel(ItemInterface $item, array $options)
{
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($html, $type, $level, array $options)
{
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";
}
}

View File

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

View File

@@ -0,0 +1,47 @@
<?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($name = null)
{
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($name)
{
return $this->container->has($name);
}
}

View File

@@ -0,0 +1,110 @@
<?php
namespace Knp\Menu\Renderer;
abstract class Renderer
{
protected $charset = 'UTF-8';
/**
* @param string|null $charset
*/
public function __construct($charset = null)
{
if (null !== $charset) {
$this->charset = (string) $charset;
}
}
/**
* Renders a HTML attribute
*
* @param string $name
* @param string|bool $value
*
* @return string
*/
protected function renderHtmlAttribute($name, $value)
{
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)
{
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($name, $value)
{
if (false === $value || null === $value) {
return '';
}
return ' '.$this->renderHtmlAttribute($name, $value);
}
/**
* Escapes an HTML value
*
* @param string $value
*
* @return string
*/
protected function escape($value)
{
return $this->fixDoubleEscape(\htmlspecialchars((string) $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($escaped)
{
return \preg_replace('/&amp;([a-z]+|(#\d+)|(#x[\da-f]+));/i', '&$1;', $escaped);
}
/**
* Get the HTML charset
*
* @return string
*/
public function getCharset()
{
return $this->charset;
}
/**
* Set the HTML charset
*
* @param string $charset
*/
public function setCharset($charset)
{
$this->charset = (string) $charset;
}
}

View File

@@ -0,0 +1,29 @@
<?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 = []);
}

View File

@@ -0,0 +1,28 @@
<?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($name = null);
/**
* Checks whether a renderer exists
*
* @param string $name
*
* @return bool
*/
public function has($name);
}

View File

@@ -0,0 +1,57 @@
<?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 = [])
{
$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;
}
}