CHANGELOG.md 0000644 00000004710 14137221616 0006362 0 ustar 00 ## 3.1.0 (2020-11-29)
* Add support for php 8
## 3.0.0 (2019-12-01)
* [BC break] removed deprecated features
* [BC break] made final all classes that implement an interface
* Dropped support for KnpMenu 2
* Increased minimum PHP version to 7.2 (to be consistent with KnpMenu 3)
* Removed class parameters name from service definitions
## 2.3.0 (2019-09-19)
* Bumped minimum PHP version to 7.1
* Enforced some coding standards
* Deprecated some options
* Removed deprecations in tests
## 2.2.2 (2019-06-17)
New features:
* Symfony 3.3+ autowiring support for `Knp\Menu\Provider\MenuProviderInterface`
* Tested with PHP 7.3
Bugfixes:
* Do not use deprecated method with Symfony 4.2
## 2.2.1 (2017-12-24)
Bugfixes:
* Fixed registration of the KnpMenu templates when not using the Templating component.
## 2.2.0 (2017-11-29)
New features:
* Added support for Symfony 3.3+ autowiring for `Knp\Menu\FactoryInterface`, `Knp\Menu\Matcher\MatcherInterface` and `Knp\Menu\Util\MenuManipulator`
* Added support for autoconfiguring menu voters
* Added support for Symfony 4
* Added support for private services for menu builders and renderers
* Added lazy-loading for menu providers and voters when using Symfony DI 3.3+
Removed:
* Removed support for PHP 5.5 and older
## 2.1.3 (2016-10-03)
* Added support for `getCurrentItem` in the templating helper
## 2.1.2 (2016-06-21)
* Menu extensions now also work if you replace the knp_menu.factory service with an alias
* Menu items are translated in the default template
## 2.1.1 (2015-12-15)
* Support Symfony 3
* Documentation fixes
## 2.1.0 (2015-09-28)
* Added a priority to allow controlling the order of voters
* Added new templating features to the templating helper
* Added the necessary configuration for new Twig features of KnpMenu 2.1
* Added a menu provider registering builders as services
* Removed usage of deprecated API to run on Symfony 2.7 without warning
## 2.0.0 (2014-08-01)
* Updated to KnpMenu 2 stable
## 2.0.0 alpha 1 (2013-06-23)
* Updated the bundle for KnpMenu 2.0.0 alpha1
## 1.1.2 (2013-05-25)
* Updated the composer constraint to allow Symfony 2.3 and above
## 1.1.1 (2012-11-28)
* Made the bundle compatible with Symfony 2.2
## 1.1.0 (2012-05-17)
* Updated bundle for KnpMenu 1.1
* Added bundle inheritance support in the BundleAliasProvider
* Added parameters for the default options of the ListRenderer and TwigRenderer
## 1.0.0 (2012-05-03)
* Initial release of the new bundle based on KnpMenu
LICENSE 0000644 00000002064 14137221616 0005556 0 ustar 00 Copyright (c) 2011 KnpLabs - http://www.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.
README.md 0000644 00000003153 14137221616 0006030 0 ustar 00 KnpMenuBundle
=============
The `KnpMenuBundle` integrates the [KnpMenu](https://github.com/KnpLabs/KnpMenu)
PHP library with Symfony. This means easy-to-implement and feature-rich menus
in your Symfony application!
[![Build Status](https://github.com/KnpLabs/KnpMenuBundle/workflows/build/badge.svg)](https://github.com/KnpLabs/KnpMenuBundle/actions)
[![Latest Stable Version](https://poser.pugx.org/knplabs/knp-menu-bundle/v/stable.png)](https://packagist.org/packages/knplabs/knp-menu-bundle)
[![Latest Unstable Version](https://poser.pugx.org/knplabs/knp-menu-bundle/v/unstable.png)](https://packagist.org/packages/knplabs/knp-menu-bundle)
[![knpbundles.com](http://knpbundles.com/KnpLabs/KnpMenuBundle/badge-short)](http://knpbundles.com/KnpLabs/KnpMenuBundle)
### What now?
Documentation! The documentation for this bundle is available in the `docs`
directory of the bundle:
* Read the [KnpMenuBundle documentation](http://symfony.com/doc/master/bundles/KnpMenuBundle/index.html)
This bundle's job is to integrate a standalone PHP menu library called [KnpMenu](https://github.com/KnpLabs/KnpMenu).
You can learn a lot more about how this library works by reading that library's
documentation.
## Maintainers
Please read [this post](https://knplabs.com/en/blog/news-for-our-foss-projects-maintenance) first.
This library is maintained by the following people (alphabetically sorted) :
- @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](https://knplabs.com) and
the Symfony community.
composer.json 0000644 00000002320 14137221616 0007266 0 ustar 00 {
"name": "knplabs/knp-menu-bundle",
"description": "This bundle provides an integration of the KnpMenu library",
"keywords": ["menu"],
"type": "symfony-bundle",
"license": "MIT",
"authors": [
{
"name": "Knplabs",
"homepage": "http://knplabs.com"
},
{
"name": "Christophe Coevoet",
"email": "stof@notk.org"
},
{
"name": "Symfony Community",
"homepage": "https://github.com/KnpLabs/KnpMenuBundle/contributors"
}
],
"require": {
"php": "^7.2 || ^8.0",
"knplabs/knp-menu": "^3.1",
"symfony/framework-bundle": "^3.4 | ^4.4 | ^5.0 | ^6.0"
},
"require-dev": {
"phpunit/phpunit": "^8.5 | ^9.5",
"symfony/expression-language": "^3.4 | ^4.4 | ^5.0 | ^6.0",
"symfony/phpunit-bridge": "^5.2 | ^6.0",
"symfony/templating": "^3.4 | ^4.4 | ^5.0 | ^6.0"
},
"autoload": {
"psr-4": { "Knp\\Bundle\\MenuBundle\\": "src" }
},
"autoload-dev": {
"psr-4": { "Knp\\Bundle\\MenuBundle\\Tests\\": "tests" }
},
"extra": {
"branch-alias": {
"dev-master": "3.x-dev"
}
}
}
src/DependencyInjection/Compiler/AddExtensionsPass.php 0000644 00000003115 14137221616 0017141 0 ustar 00
*
* @internal
* @final
*/
final class AddExtensionsPass implements CompilerPassInterface
{
public function process(ContainerBuilder $container): void
{
if (!$container->has('knp_menu.factory')) {
return;
}
$taggedServiceIds = $container->findTaggedServiceIds('knp_menu.factory_extension');
if (0 === \count($taggedServiceIds)) {
return;
}
$definition = $container->findDefinition('knp_menu.factory');
if (!\method_exists($container->getParameterBag()->resolveValue($definition->getClass()), 'addExtension')) {
$msg = 'To use factory extensions, the service of class "%s" registered as knp_menu.factory must implement the "addExtension" method';
throw new InvalidConfigurationException(\sprintf($msg, $definition->getClass()));
}
foreach ($taggedServiceIds as $id => $tags) {
foreach ($tags as $tag) {
$priority = isset($tag['priority']) ? $tag['priority'] : 0;
$definition->addMethodCall('addExtension', [new Reference($id), $priority]);
}
}
}
}
src/DependencyInjection/Compiler/AddProvidersPass.php 0000644 00000003101 14137221616 0016752 0 ustar 00
*
* @internal
* @final
*/
final class AddProvidersPass implements CompilerPassInterface
{
public function process(ContainerBuilder $container): void
{
if (!$container->hasDefinition('knp_menu.menu_provider.chain')) {
return;
}
$providers = [];
foreach ($container->findTaggedServiceIds('knp_menu.provider') as $id => $tags) {
$providers[] = new Reference($id);
}
if (1 === \count($providers)) {
// Use an alias instead of wrapping it in the ChainProvider for performances
// when using only one (the default case as the bundle defines one provider)
$container->setAlias('knp_menu.menu_provider', (string) \reset($providers));
} else {
if (\class_exists(IteratorArgument::class)) {
$providers = new IteratorArgument($providers);
}
$definition = $container->getDefinition('knp_menu.menu_provider.chain');
$definition->replaceArgument(0, $providers);
$container->setAlias('knp_menu.menu_provider', 'knp_menu.menu_provider.chain');
}
}
}
src/DependencyInjection/Compiler/AddRenderersPass.php 0000644 00000002744 14137221616 0016742 0 ustar 00
*
* @internal
* @final
*/
final class AddRenderersPass implements CompilerPassInterface
{
public function process(ContainerBuilder $container): void
{
if (!$container->hasDefinition('knp_menu.renderer_provider')) {
return;
}
$rendererReferences = [];
foreach ($container->findTaggedServiceIds('knp_menu.renderer', true) as $id => $tags) {
foreach ($tags as $attributes) {
if (empty($attributes['alias'])) {
throw new \InvalidArgumentException(\sprintf('The alias is not defined in the "knp_menu.renderer" tag for the service "%s"', $id));
}
$rendererReferences[$attributes['alias']] = new Reference($id);
}
}
$locator = ServiceLocatorTagPass::register($container, $rendererReferences);
// Replace the service definition with a PsrProvider
$container->getDefinition('knp_menu.renderer_provider')->replaceArgument(0, $locator);
}
}
src/DependencyInjection/Compiler/AddVotersPass.php 0000644 00000003057 14137221616 0016271 0 ustar 00
*
* @internal
* @final
*/
final class AddVotersPass implements CompilerPassInterface
{
public function process(ContainerBuilder $container): void
{
if (!$container->hasDefinition('knp_menu.matcher')) {
return;
}
$definition = $container->getDefinition('knp_menu.matcher');
$voters = [];
foreach ($container->findTaggedServiceIds('knp_menu.voter') as $id => $tags) {
// Process only the first tag. Registering the same voter multiple time
// does not make any sense, and this allows user to overwrite the tag added
// by the autoconfiguration to change the priority (autoconfigured tags are
// always added at the end of the list).
$tag = $tags[0];
$priority = isset($tag['priority']) ? (int) $tag['priority'] : 0;
$voters[$priority][] = new Reference($id);
}
if (empty($voters)) {
return;
}
\krsort($voters);
$sortedVoters = \array_merge(...$voters);
$definition->replaceArgument(0, new IteratorArgument($sortedVoters));
}
}
src/DependencyInjection/Compiler/MenuBuilderPass.php 0000644 00000003671 14137221616 0016613 0 ustar 00
*
* @internal
* @final
*/
final class MenuBuilderPass implements CompilerPassInterface
{
public function process(ContainerBuilder $container): void
{
if (!$container->hasDefinition('knp_menu.menu_provider.builder_service')) {
return;
}
$definition = $container->getDefinition('knp_menu.menu_provider.builder_service');
$menuBuilders = [];
foreach ($container->findTaggedServiceIds('knp_menu.menu_builder') as $id => $tags) {
$builderDefinition = $container->getDefinition($id);
if (!$builderDefinition->isPublic()) {
throw new \InvalidArgumentException(\sprintf('Menu builder services must be public but "%s" is a private service.', $id));
}
if ($builderDefinition->isAbstract()) {
throw new \InvalidArgumentException(\sprintf('Abstract services cannot be registered as menu builders but "%s" is.', $id));
}
foreach ($tags as $attributes) {
if (empty($attributes['alias'])) {
throw new \InvalidArgumentException(\sprintf('The alias is not defined in the "knp_menu.menu_builder" tag for the service "%s"', $id));
}
if (empty($attributes['method'])) {
throw new \InvalidArgumentException(\sprintf('The method is not defined in the "knp_menu.menu_builder" tag for the service "%s"', $id));
}
$menuBuilders[$attributes['alias']] = [$id, $attributes['method']];
}
}
$definition->replaceArgument(1, $menuBuilders);
}
}
src/DependencyInjection/Compiler/RegisterMenusPass.php 0000644 00000004265 14137221616 0017174 0 ustar 00
*
* @internal
* @final
*/
final class RegisterMenusPass implements CompilerPassInterface
{
public function process(ContainerBuilder $container): void
{
if (!$container->hasDefinition('knp_menu.menu_provider.lazy')) {
return;
}
// Remove the old way of handling this feature.
$container->removeDefinition('knp_menu.menu_provider.builder_service');
$menuBuilders = [];
foreach ($container->findTaggedServiceIds('knp_menu.menu_builder', true) as $id => $tags) {
foreach ($tags as $attributes) {
if (empty($attributes['alias'])) {
throw new \InvalidArgumentException(\sprintf('The alias is not defined in the "knp_menu.menu_builder" tag for the service "%s"', $id));
}
if (empty($attributes['method'])) {
throw new \InvalidArgumentException(\sprintf('The method is not defined in the "knp_menu.menu_builder" tag for the service "%s"', $id));
}
$menuBuilders[$attributes['alias']] = [new ServiceClosureArgument(new Reference($id)), $attributes['method']];
}
}
foreach ($container->findTaggedServiceIds('knp_menu.menu', true) as $id => $tags) {
foreach ($tags as $attributes) {
if (empty($attributes['alias'])) {
throw new \InvalidArgumentException(\sprintf('The alias is not defined in the "knp_menu.menu" tag for the service "%s"', $id));
}
$menuBuilders[$attributes['alias']] = new ServiceClosureArgument(new Reference($id));
}
}
$container->getDefinition('knp_menu.menu_provider.lazy')->replaceArgument(0, $menuBuilders);
}
}
src/DependencyInjection/Configuration.php 0000644 00000003126 14137221616 0014601 0 ustar 00
*/
class Configuration implements ConfigurationInterface
{
/**
* Generates the configuration tree.
*
* @return TreeBuilder
*/
public function getConfigTreeBuilder()
{
$treeBuilder = new TreeBuilder('knp_menu');
// Keep compatibility with symfony/config < 4.2
if (\method_exists($treeBuilder, 'getRootNode')) {
$rootNode = $treeBuilder->getRootNode();
} else {
$rootNode = $treeBuilder->root('knp_menu');
}
$rootNode
->children()
->arrayNode('providers')
->addDefaultsIfNotSet()
->children()
->booleanNode('builder_alias')->defaultTrue()->end()
->end()
->end()
->arrayNode('twig')
->addDefaultsIfNotSet()
->canBeUnset()
->children()
->scalarNode('template')->defaultValue('@KnpMenu/menu.html.twig')->end()
->end()
->end()
->booleanNode('templating')->defaultFalse()->end()
->scalarNode('default_renderer')->cannotBeEmpty()->defaultValue('twig')->end()
->end();
return $treeBuilder;
}
}
src/DependencyInjection/KnpMenuExtension.php 0000644 00000004752 14137221616 0015252 0 ustar 00 load('menu.xml');
$configuration = new Configuration();
$config = $this->processConfiguration($configuration, $configs);
foreach ($config['providers'] as $builder => $enabled) {
if ($enabled) {
$container->getDefinition(\sprintf('knp_menu.menu_provider.%s', $builder))->addTag('knp_menu.provider');
}
}
if (isset($config['twig'])) {
$loader->load('twig.xml');
$container->setParameter('knp_menu.renderer.twig.template', $config['twig']['template']);
}
if ($config['templating']) {
$loader->load('templating.xml');
}
$container->setParameter('knp_menu.default_renderer', $config['default_renderer']);
// Register autoconfiguration rules for Symfony DI 3.3+
if (\method_exists($container, 'registerForAutoconfiguration')) {
$container->registerForAutoconfiguration(VoterInterface::class)
->addTag('knp_menu.voter');
}
}
/**
* {@inheritdoc}
*/
public function getNamespace(): string
{
return 'http://knplabs.com/schema/dic/menu';
}
/**
* {@inheritdoc}
*/
public function getXsdValidationBasePath(): string
{
return __DIR__.'/../Resources/config/schema';
}
public function prepend(ContainerBuilder $container): void
{
if (!$container->hasExtension('twig')) {
return;
}
$refl = new \ReflectionClass(ItemInterface::class);
$path = \dirname($refl->getFileName()).'/Resources/views';
$container->prependExtensionConfig('twig', ['paths' => [$path]]);
}
}
src/KnpMenuBundle.php 0000644 00000002120 14137221616 0010551 0 ustar 00 addCompilerPass(new RegisterMenusPass());
$container->addCompilerPass(new MenuBuilderPass());
$container->addCompilerPass(new AddExtensionsPass());
$container->addCompilerPass(new AddProvidersPass());
$container->addCompilerPass(new AddRenderersPass());
$container->addCompilerPass(new AddVotersPass());
}
}
src/Provider/BuilderAliasProvider.php 0000644 00000010645 14137221616 0013722 0 ustar 00
*/
final class BuilderAliasProvider implements MenuProviderInterface
{
private $kernel;
private $container;
private $menuFactory;
private $builders = [];
public function __construct(KernelInterface $kernel, ContainerInterface $container, FactoryInterface $menuFactory)
{
$this->kernel = $kernel;
$this->container = $container;
$this->menuFactory = $menuFactory;
}
/**
* Looks for a menu with the bundle:class:method format.
*
* For example, AcmeBundle:Builder:mainMenu would create and instantiate
* an Acme\DemoBundle\Menu\Builder class and call the mainMenu() method
* on it. The method is passed the menu factory.
*
* @param string $name The alias name of the menu
*
* @throws \InvalidArgumentException
*/
public function get(string $name, array $options = []): ItemInterface
{
if (!$this->has($name)) {
throw new \InvalidArgumentException(\sprintf('Invalid pattern passed to AliasProvider - expected "bundle:class:method", got "%s".', $name));
}
list($bundleName, $className, $methodName) = \explode(':', $name);
$builder = $this->getBuilder($bundleName, $className);
if (!\method_exists($builder, $methodName)) {
throw new \InvalidArgumentException(\sprintf('Method "%s" was not found on class "%s" when rendering the "%s" menu.', $methodName, $className, $name));
}
$menu = $builder->$methodName($this->menuFactory, $options);
if (!$menu instanceof ItemInterface) {
throw new \InvalidArgumentException(\sprintf('Method "%s" did not return an ItemInterface menu object for menu "%s"', $methodName, $name));
}
return $menu;
}
/**
* Verifies if the given name follows the bundle:class:method alias syntax.
*
* @param string $name The alias name of the menu
*/
public function has(string $name, array $options = []): bool
{
return 2 === \substr_count($name, ':');
}
/**
* Creates and returns the builder that lives in the given bundle.
*
* The convention is to look in the Menu namespace of the bundle for
* this class, to instantiate it with no arguments, and to inject the
* container if the class is ContainerAware.
*
* @param string $className The class name of the builder
*
* @throws \InvalidArgumentException If the class does not exist
*/
private function getBuilder(string $bundleName, string $className): object
{
$name = \sprintf('%s:%s', $bundleName, $className);
if (!isset($this->builders[$name])) {
$class = null;
$logs = [];
$bundles = [];
$allBundles = $this->kernel->getBundle($bundleName, false);
// In Symfony 4, bundle inheritance is gone, so there is no way to get an array anymore.
if (!\is_array($allBundles)) {
$allBundles = [$allBundles];
}
foreach ($allBundles as $bundle) {
$try = $bundle->getNamespace().'\\Menu\\'.$className;
if (\class_exists($try)) {
$class = $try;
break;
}
$logs[] = \sprintf('Class "%s" does not exist for menu builder "%s".', $try, $name);
$bundles[] = $bundle->getName();
}
if (null === $class) {
if (1 === \count($logs)) {
throw new \InvalidArgumentException($logs[0]);
}
throw new \InvalidArgumentException(\sprintf('Unable to find menu builder "%s" in bundles %s.', $name, \implode(', ', $bundles)));
}
$builder = new $class();
if ($builder instanceof ContainerAwareInterface) {
$builder->setContainer($this->container);
}
$this->builders[$name] = $builder;
}
return $this->builders[$name];
}
}
src/Resources/config/menu.xml 0000644 00000006514 14137221616 0012251 0 ustar 00
src/Resources/config/schema/menu-1.0.xsd 0000644 00000002201 14137221616 0013770 0 ustar 00
src/Resources/config/templating.xml 0000644 00000001473 14137221616 0013450 0 ustar 00
Knp\Bundle\MenuBundle\Templating\Helper\MenuHelper
src/Resources/config/twig.xml 0000644 00000002565 14137221616 0012261 0 ustar 00
Knp\Menu\Twig\MenuExtension
Knp\Menu\Renderer\TwigRenderer
src/Resources/views/menu.html.twig 0000644 00000000742 14137221616 0013253 0 ustar 00 {% extends 'knp_menu.html.twig' %}
{% block label %}
{%- set translation_domain = item.extra('translation_domain', 'messages') -%}
{%- set label = item.label -%}
{%- if translation_domain is not same as(false) -%}
{%- set label = label|trans(item.extra('translation_params', {}), translation_domain) -%}
{%- endif -%}
{%- if options.allow_safe_labels and item.extra('safe_label', false) %}{{ label|raw }}{% else %}{{ label }}{% endif -%}
{% endblock %}
src/Templating/Helper/MenuHelper.php 0000644 00000005517 14137221616 0013446 0 ustar 00 helper = $helper;
$this->matcher = $matcher;
$this->menuManipulator = $menuManipulator;
}
/**
* Retrieves an item following a path in the tree.
*
* @param \Knp\Menu\ItemInterface|string $menu
*
* @return \Knp\Menu\ItemInterface
*/
public function get($menu, array $path = [], array $options = [])
{
return $this->helper->get($menu, $path, $options);
}
/**
* Renders a menu with the specified renderer.
*
* @param \Knp\Menu\ItemInterface|string|array $menu
* @param string $renderer
*
* @return string
*/
public function render($menu, array $options = [], $renderer = null)
{
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)
{
return $this->helper->getBreadcrumbsArray($menu, $subItem);
}
/**
* A string representation of this menu item.
*
* e.g. Top Level 1 > Second Level > This menu
*
* @param string $separator
*
* @return string
*/
public function getPathAsString(ItemInterface $menu, $separator = ' > ')
{
return $this->menuManipulator->getPathAsString($menu, $separator);
}
/**
* Checks whether an item is current.
*
* @return bool
*/
public function isCurrent(ItemInterface $item)
{
return $this->matcher->isCurrent($item);
}
/**
* Checks whether an item is the ancestor of a current item.
*
* @param int $depth The max depth to look for the item
*
* @return bool
*/
public function isAncestor(ItemInterface $item, $depth = null)
{
return $this->matcher->isAncestor($item, $depth);
}
/**
* Returns the current item of a menu.
*
* @param ItemInterface|array|string $menu
*
* @return ItemInterface|null
*/
public function getCurrentItem($menu)
{
return $this->helper->getCurrentItem($menu);
}
/**
* @return string
*/
public function getName()
{
return 'knp_menu';
}
}