Initial commit

This commit is contained in:
Kendrick Chan 2020-03-17 08:30:54 +08:00
commit 6bead2fc57
9 changed files with 490 additions and 0 deletions

13
Annotation/Menu.php Normal file
View file

@ -0,0 +1,13 @@
<?php
namespace Catalyst\MenuBundle\Annotation;
use Annotation;
/**
* @Annotation
*/
class Menu
{
public $selected;
}

10
CatalystMenuBundle.php Normal file
View file

@ -0,0 +1,10 @@
<?php
namespace Catalyst\MenuBundle;
use Symfony\Component\HttpKernel\Bundle\Bundle;
class CatalystMenuBundle extends Bundle
{
}

View 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);
}
}

View 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
View 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
View 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;
}
}

View 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
View 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
View 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\\": "" }
}
}