1: <?php
2:
3: namespace Charcoal\Ui\Menu;
4:
5: use \InvalidArgumentException;
6:
7: // Intra-module (`charcoal-ui`) dependencies
8: use \Charcoal\Ui\AbstractUiItem;
9: use \Charcoal\Ui\Menu\MenuInterface;
10: use \Charcoal\Ui\MenuItem\MenuItemInterface;
11: use \Charcoal\Ui\MenuItem\MenuItemBuilder;
12:
13: /**
14: * A Basic Menu
15: *
16: * Abstract implementation of {@see \Charcoal\Ui\Menu\MenuInterface}.
17: */
18: abstract class AbstractMenu extends AbstractUiItem implements
19: MenuInterface
20: {
21: /**
22: * A collection menu items.
23: *
24: * @var MenuItemInterface[]
25: */
26: private $items = [];
27:
28: /**
29: * A callback applied to each menu item output by {@see self::items()}.
30: *
31: * @var callable
32: */
33: private $itemCallback;
34:
35: /**
36: * Store a menu builder instance.
37: *
38: * @var MenuItemBuilder $menuItemBuilder
39: */
40: private $menuItemBuilder;
41:
42: /**
43: * Return a new menu.
44: *
45: * @param array|\ArrayAccess $data Class dependencies.
46: */
47: public function __construct($data)
48: {
49: $this->setMenuItemBuilder($data['menu_item_builder']);
50: }
51:
52: /**
53: * @param MenuItemBuilder $menuItemBuilder The Menu Item Builder that will be used to create new items.
54: * @return AsbtractMenu Chainable
55: */
56: public function setMenuItemBuilder(MenuItemBuilder $menuItemBuilder)
57: {
58: $this->menuItemBuilder = $menuItemBuilder;
59: return $this;
60: }
61:
62: /**
63: * @param callable $cb The item callback.
64: * @return AbstractMenu Chainable
65: */
66: public function setItemCallback(callable $cb)
67: {
68: $this->itemCallback = $cb;
69: return $this;
70: }
71:
72: /**
73: * @param array $items The menu items.
74: * @return AbstractMenu Chainable
75: */
76: public function setItems(array $items)
77: {
78: $this->items = [];
79: foreach ($items as $item) {
80: $this->addItem($item);
81: }
82: return $this;
83: }
84:
85: /**
86: * @param array|MenuItemInterface $item A menu item structure or object.
87: * @throws InvalidArgumentException If the item argument is not a structure or object.
88: * @return MenuItem Chainable
89: */
90: public function addItem($item)
91: {
92: if (is_array($item)) {
93: $item['menu'] = $this;
94: $i = $this->menuItemBuilder->build($item);
95: $item = $i;
96: } elseif ($item instanceof MenuItemInterface) {
97: $item->setMenu($this);
98: } else {
99: throw new InvalidArgumentException(
100: 'Item must be an array of menu item options or a MenuItem object'
101: );
102: }
103: $this->items[] = $item;
104: return $this;
105: }
106:
107: /**
108: * Menu Item generator.
109: *
110: * @param callable $itemCallback Optional. Item callback.
111: * @return MenuItemInterface[]
112: */
113: public function items(callable $itemCallback = null)
114: {
115: $items = $this->items;
116: uasort($items, ['self', 'sortItemsByPriority']);
117:
118: $itemCallback = isset($itemCallback) ? $itemCallback : $this->itemCallback;
119: foreach ($items as $item) {
120: if ($itemCallback) {
121: $itemCallback($item);
122: }
123: $GLOBALS['widget_template'] = $item->template();
124: yield $item->ident() => $item;
125: $GLOBALS['widget_template'] = '';
126: }
127: }
128:
129: /**
130: * @return boolean
131: */
132: public function hasItems()
133: {
134: return (count($this->items) > 0);
135: }
136:
137: /**
138: * @return integer
139: */
140: public function numItems()
141: {
142: return count($this->items);
143: }
144:
145: /**
146: * Static comparison function used by {@see uasort()}.
147: *
148: * @param MenuItemInterface $a Menu A.
149: * @param MenuItemInterface $b Menu B.
150: * @return integer Sorting value: -1, 0, or 1
151: */
152: protected static function sortItemsByPriority(
153: MenuItemInterface $a,
154: MenuItemInterface $b
155: ) {
156: $a = $a->priority();
157: $b = $b->priority();
158:
159: if ($a == $b) {
160: return 0;
161: }
162:
163: return ($a < $b) ? (-1) : 1;
164: }
165: }
166: