Initial commit
This commit is contained in:
commit
6bead2fc57
9 changed files with 490 additions and 0 deletions
13
Annotation/Menu.php
Normal file
13
Annotation/Menu.php
Normal file
|
|
@ -0,0 +1,13 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
namespace Catalyst\MenuBundle\Annotation;
|
||||||
|
|
||||||
|
use Annotation;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @Annotation
|
||||||
|
*/
|
||||||
|
class Menu
|
||||||
|
{
|
||||||
|
public $selected;
|
||||||
|
}
|
||||||
10
CatalystMenuBundle.php
Normal file
10
CatalystMenuBundle.php
Normal file
|
|
@ -0,0 +1,10 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
namespace Catalyst\MenuBundle;
|
||||||
|
|
||||||
|
use Symfony\Component\HttpKernel\Bundle\Bundle;
|
||||||
|
|
||||||
|
class CatalystMenuBundle extends Bundle
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
30
DependencyInjection/CatalystMenuExtension.php
Normal file
30
DependencyInjection/CatalystMenuExtension.php
Normal file
|
|
@ -0,0 +1,30 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
namespace Catalyst\MenuBundle\DependencyInjection;
|
||||||
|
|
||||||
|
use Symfony\Component\DependencyInjection\ContainerBuilder;
|
||||||
|
use Symfony\Component\DependencyInjection\Extension\Extension;
|
||||||
|
use Symfony\Component\DependencyInjection\Loader\YamlFileLoader;
|
||||||
|
use Symfony\Component\Config\FileLocator;
|
||||||
|
|
||||||
|
class CatalystMenuExtension extends Extension
|
||||||
|
{
|
||||||
|
public function load(array $configs, ContainerBuilder $container)
|
||||||
|
{
|
||||||
|
$loader = new YamlFileLoader(
|
||||||
|
$container,
|
||||||
|
new FileLocator(__DIR__ . '/../Resources/config')
|
||||||
|
);
|
||||||
|
$loader->load('services.yaml');
|
||||||
|
|
||||||
|
// NOTE: cannot use processConfiguration here since having the key as an identifier
|
||||||
|
// will break across multiple configuration files.
|
||||||
|
// Issue can be found here: https://github.com/symfony/symfony/issues/29817
|
||||||
|
|
||||||
|
// $data = $this->processConfigs($configs);
|
||||||
|
|
||||||
|
// set acl data for main acl generator
|
||||||
|
$def = $container->getDefinition('catalyst_menu.generator');
|
||||||
|
$def->replaceArgument('$menu_data', $configs);
|
||||||
|
}
|
||||||
|
}
|
||||||
76
EventListener/MenuAnnotationListener.php
Normal file
76
EventListener/MenuAnnotationListener.php
Normal file
|
|
@ -0,0 +1,76 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
namespace Catalyst\MenuBundle\EventListener;
|
||||||
|
|
||||||
|
use Doctrine\Common\Annotations\Reader;
|
||||||
|
use ReflectionClass;
|
||||||
|
use ReflectionException;
|
||||||
|
use RuntimeException;
|
||||||
|
use Symfony\Component\HttpKernel\Event\ControllerEvent;
|
||||||
|
use Twig\Environment as TwigEnvironment;
|
||||||
|
|
||||||
|
use Catalyst\MenuBundle\Annotation\Menu as MenuAnnotation;
|
||||||
|
use Catalyst\MenuBundle\Service\Generator as MenuGenerator;
|
||||||
|
|
||||||
|
class MenuAnnotationListener
|
||||||
|
{
|
||||||
|
protected $annot_reader;
|
||||||
|
protected $menu_gen;
|
||||||
|
protected $twig;
|
||||||
|
protected $menu_id;
|
||||||
|
|
||||||
|
public function __construct(Reader $annot_reader, MenuGenerator $menu_gen, TwigEnvironment $twig, $menu_id)
|
||||||
|
{
|
||||||
|
$this->annot_reader = $annot_reader;
|
||||||
|
$this->menu_gen = $menu_gen;
|
||||||
|
$this->twig = $twig;
|
||||||
|
$this->menu_id = $menu_id;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function onKernelController(ControllerEvent $event)
|
||||||
|
{
|
||||||
|
if (!$event->isMasterRequest())
|
||||||
|
return;
|
||||||
|
|
||||||
|
// get controller
|
||||||
|
$event_controller = $event->getController();
|
||||||
|
if (!is_array($event_controller))
|
||||||
|
return;
|
||||||
|
|
||||||
|
list($controller, $method_name) = $event_controller;
|
||||||
|
|
||||||
|
// get reflection class
|
||||||
|
try
|
||||||
|
{
|
||||||
|
$ref_controller = new ReflectionClass($controller);
|
||||||
|
}
|
||||||
|
catch (ReflectionException $e)
|
||||||
|
{
|
||||||
|
throw new RuntimeException('Cannot read menu annotation.');
|
||||||
|
}
|
||||||
|
|
||||||
|
// get method annotations
|
||||||
|
$ref_method = $ref_controller->getMethod($method_name);
|
||||||
|
$annotation = $this->annot_reader->getMethodAnnotation($ref_method, MenuAnnotation::class);
|
||||||
|
|
||||||
|
// check if we get anything
|
||||||
|
if ($annotation == null)
|
||||||
|
return;
|
||||||
|
|
||||||
|
$this->selectMenu($annotation->selected);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected function selectMenu($selected)
|
||||||
|
{
|
||||||
|
// get menu
|
||||||
|
$menu = $this->menu_gen->getMenu($this->menu_id);
|
||||||
|
|
||||||
|
// set menu selected
|
||||||
|
$sel = $menu['index']->get($selected);
|
||||||
|
if ($sel != null)
|
||||||
|
$sel->setSelected();
|
||||||
|
|
||||||
|
// create twig global variable
|
||||||
|
$this->twig->addGlobal('menu_' . $this->menu_id, $menu);
|
||||||
|
}
|
||||||
|
}
|
||||||
71
Menu/Collection.php
Normal file
71
Menu/Collection.php
Normal file
|
|
@ -0,0 +1,71 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
namespace Catalyst\MenuBundle\Menu;
|
||||||
|
|
||||||
|
use Iterator;
|
||||||
|
|
||||||
|
// iterable collection of menu items
|
||||||
|
class Collection implements Iterator
|
||||||
|
{
|
||||||
|
protected $position = 0;
|
||||||
|
protected $array;
|
||||||
|
protected $index_array;
|
||||||
|
|
||||||
|
public function __construct()
|
||||||
|
{
|
||||||
|
$this->position = 0;
|
||||||
|
$this->array = array();
|
||||||
|
$this->index_array = array();
|
||||||
|
}
|
||||||
|
|
||||||
|
// iterator stuff
|
||||||
|
public function rewind()
|
||||||
|
{
|
||||||
|
$this->position = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function current()
|
||||||
|
{
|
||||||
|
return $this->array[$this->index_array[$this->position]];
|
||||||
|
}
|
||||||
|
|
||||||
|
public function key()
|
||||||
|
{
|
||||||
|
return $this->position;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function next()
|
||||||
|
{
|
||||||
|
return ++$this->position;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function valid()
|
||||||
|
{
|
||||||
|
return isset($this->index_array[$this->position]);
|
||||||
|
}
|
||||||
|
// end of iterator stuff
|
||||||
|
|
||||||
|
public function add(Item $mi)
|
||||||
|
{
|
||||||
|
$id = $mi->getID();
|
||||||
|
$this->array[$id] = $mi;
|
||||||
|
$this->index_array[] = $id;
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function get($id)
|
||||||
|
{
|
||||||
|
if (isset($this->array[$id]))
|
||||||
|
return $this->array[$id];
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function unselectAll()
|
||||||
|
{
|
||||||
|
foreach ($this->array as $mi)
|
||||||
|
$mi->setSelected(false, false);
|
||||||
|
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
|
}
|
||||||
138
Menu/Item.php
Normal file
138
Menu/Item.php
Normal file
|
|
@ -0,0 +1,138 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
namespace Catalyst\MenuBundle\Menu;
|
||||||
|
|
||||||
|
class Item
|
||||||
|
{
|
||||||
|
protected $id;
|
||||||
|
protected $icon;
|
||||||
|
protected $link;
|
||||||
|
protected $label;
|
||||||
|
protected $children;
|
||||||
|
protected $selected;
|
||||||
|
protected $parent;
|
||||||
|
protected $acl_key;
|
||||||
|
|
||||||
|
public function __construct()
|
||||||
|
{
|
||||||
|
$this->id = '';
|
||||||
|
$this->icon = null;
|
||||||
|
$this->link = null;
|
||||||
|
$this->label = '';
|
||||||
|
$this->children = [];
|
||||||
|
$this->selected = false;
|
||||||
|
$this->parent = null;
|
||||||
|
$this->acl_key = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
// setters
|
||||||
|
public function setID($id)
|
||||||
|
{
|
||||||
|
$this->id = $id;
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function setIcon($icon)
|
||||||
|
{
|
||||||
|
$this->icon = $icon;
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function setLink($link)
|
||||||
|
{
|
||||||
|
$this->link = $link;
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function setLabel($label)
|
||||||
|
{
|
||||||
|
$this->label = $label;
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function setParent(self $parent)
|
||||||
|
{
|
||||||
|
$this->parent = $parent;
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function addChild(self $child)
|
||||||
|
{
|
||||||
|
$child->setParent($this);
|
||||||
|
|
||||||
|
// check if selected
|
||||||
|
if ($child->isSelected())
|
||||||
|
$this->setSelected();
|
||||||
|
|
||||||
|
$this->children[] = $child;
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function setSelected($sel = true, $to_bubble = true)
|
||||||
|
{
|
||||||
|
if ($sel)
|
||||||
|
{
|
||||||
|
$this->selected = true;
|
||||||
|
// bubble up to parents
|
||||||
|
if ($this->parent != null && $to_bubble)
|
||||||
|
$this->parent->setSelected(true, true);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
$this->selected = false;
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function setACLKey($key)
|
||||||
|
{
|
||||||
|
$this->acl_key = $key;
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
|
|
||||||
|
// getters
|
||||||
|
public function getID()
|
||||||
|
{
|
||||||
|
return $this->id;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getIcon()
|
||||||
|
{
|
||||||
|
return $this->icon;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getLink()
|
||||||
|
{
|
||||||
|
return $this->link;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getLabel()
|
||||||
|
{
|
||||||
|
return $this->label;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getChildren()
|
||||||
|
{
|
||||||
|
return $this->children;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function hasChildren()
|
||||||
|
{
|
||||||
|
if (count($this->children) > 0)
|
||||||
|
return true;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function isSelected()
|
||||||
|
{
|
||||||
|
return $this->selected;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getParent()
|
||||||
|
{
|
||||||
|
return $this->parent;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getACLKey()
|
||||||
|
{
|
||||||
|
return $this->acl_key;
|
||||||
|
}
|
||||||
|
}
|
||||||
18
Resources/config/services.yaml
Normal file
18
Resources/config/services.yaml
Normal file
|
|
@ -0,0 +1,18 @@
|
||||||
|
services:
|
||||||
|
_defaults:
|
||||||
|
autowire: true
|
||||||
|
autoconfigure: true
|
||||||
|
|
||||||
|
catalyst_menu.generator:
|
||||||
|
class: Catalyst\MenuBundle\Service\Generator
|
||||||
|
autowire: true
|
||||||
|
arguments:
|
||||||
|
$router: "@router.default"
|
||||||
|
$menu_data: []
|
||||||
|
|
||||||
|
Catalyst\MenuBundle\EventListener\MenuAnnotationListener:
|
||||||
|
arguments:
|
||||||
|
$menu_gen: "@catalyst_menu.generator"
|
||||||
|
$menu_id: "main"
|
||||||
|
tags:
|
||||||
|
- { name: kernel.event_listener, event: kernel.controller, method: onKernelController }
|
||||||
120
Service/Generator.php
Normal file
120
Service/Generator.php
Normal file
|
|
@ -0,0 +1,120 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
namespace Catalyst\MenuBundle\Service;
|
||||||
|
|
||||||
|
use Symfony\Component\Routing\Exception\RouteNotFoundException;
|
||||||
|
use Symfony\Component\Yaml\Parser as YamlParser;
|
||||||
|
use Symfony\Component\Config\ConfigCache;
|
||||||
|
use Symfony\Component\Config\Resource\FileResource;
|
||||||
|
use Symfony\Component\Routing\RouterInterface;
|
||||||
|
|
||||||
|
use Catalyst\MenuBundle\Menu\Collection;
|
||||||
|
use Catalyst\MenuBundle\Menu\Item;
|
||||||
|
|
||||||
|
|
||||||
|
class Generator
|
||||||
|
{
|
||||||
|
protected $index;
|
||||||
|
protected $menu;
|
||||||
|
|
||||||
|
protected $router;
|
||||||
|
protected $menu_data;
|
||||||
|
|
||||||
|
public function __construct(RouterInterface $router, $menu_data)
|
||||||
|
{
|
||||||
|
$this->index = new Collection();
|
||||||
|
$this->menu = new Collection();
|
||||||
|
$this->router = $router;
|
||||||
|
$this->menu_data = $this->processConfigs($menu_data);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getMenu($menu_key)
|
||||||
|
{
|
||||||
|
if (!isset($this->menu_data[$menu_key]))
|
||||||
|
return null;
|
||||||
|
|
||||||
|
return $this->menu_data[$menu_key];
|
||||||
|
}
|
||||||
|
|
||||||
|
protected function processConfigs($data)
|
||||||
|
{
|
||||||
|
$pdata = [];
|
||||||
|
|
||||||
|
error_log(print_r($data, true));
|
||||||
|
|
||||||
|
// first layer contains all the instances in config
|
||||||
|
foreach ($data as $instance_data)
|
||||||
|
{
|
||||||
|
// 2nd layer are the groups and the parents
|
||||||
|
foreach ($instance_data as $group => $group_data)
|
||||||
|
{
|
||||||
|
// initialize group data
|
||||||
|
if (!isset($pdata[$group]))
|
||||||
|
{
|
||||||
|
$pdata[$group] = [
|
||||||
|
'menu' => new Collection(),
|
||||||
|
'index' => new Collection(),
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
$index = $pdata[$group]['index'];
|
||||||
|
$menu = $pdata[$group]['menu'];
|
||||||
|
|
||||||
|
foreach ($group_data as $mi_data)
|
||||||
|
{
|
||||||
|
// check params
|
||||||
|
if (!isset($mi_data['icon']))
|
||||||
|
$mi_data['icon'] = null;
|
||||||
|
|
||||||
|
// instantiate
|
||||||
|
$mi = $this->newItem($index, $mi_data['id'], $mi_data['label'], $mi_data['icon']);
|
||||||
|
|
||||||
|
// acl
|
||||||
|
if (isset($mi_data['acl']))
|
||||||
|
$mi->setACLKey($mi_data['acl']);
|
||||||
|
|
||||||
|
// check parent
|
||||||
|
if (isset($mi_data['parent']) && $mi_data['parent'] != null)
|
||||||
|
{
|
||||||
|
$parent = $index->get($mi_data['parent']);
|
||||||
|
if ($parent == null)
|
||||||
|
continue;
|
||||||
|
|
||||||
|
$parent->addChild($mi);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
$menu->add($mi);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
error_log(print_r($pdata, true));
|
||||||
|
|
||||||
|
return $pdata;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected function newItem($index, $id, $label, $icon = null)
|
||||||
|
{
|
||||||
|
$mi = new Item();
|
||||||
|
$mi->setID($id)
|
||||||
|
->setLabel($label);
|
||||||
|
|
||||||
|
// TODO: have a way to set manual links or specify route parameters
|
||||||
|
try
|
||||||
|
{
|
||||||
|
$mi->setLink($this->router->generate($id));
|
||||||
|
}
|
||||||
|
catch (RouteNotFoundException $e)
|
||||||
|
{
|
||||||
|
// no route, set to #
|
||||||
|
$mi->setLink('javascript:;');
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($icon != null)
|
||||||
|
$mi->setIcon($icon);
|
||||||
|
|
||||||
|
$index->add($mi);
|
||||||
|
|
||||||
|
return $mi;
|
||||||
|
}
|
||||||
|
}
|
||||||
14
composer.json
Normal file
14
composer.json
Normal file
|
|
@ -0,0 +1,14 @@
|
||||||
|
{
|
||||||
|
"name": "jankstudio/catalyst-menu-bundle",
|
||||||
|
"type": "symfony-bundle",
|
||||||
|
"license": "Apache-2.0",
|
||||||
|
"authors": [
|
||||||
|
{
|
||||||
|
"name": "Kendrick Chan",
|
||||||
|
"email": "kc@jankstudio.com"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"autoload": {
|
||||||
|
"psr-4": { "Catalyst\\MenuBundle\\": "" }
|
||||||
|
}
|
||||||
|
}
|
||||||
Loading…
Reference in a new issue