title ?? ''; } $this->title = $title; return $this; } /** * Add menu item * @param string $id * @param string|array $title * @param string|null $url * @return MenuItem */ public function add(string $id, $title, ?string $url = null) { $this->ordered = false; if (! array_key_exists($id, $this->blocks)) { $this->blocks[$id] = (new MenuItem())->setId($id)->setParent($this); } $item = $this->blocks[$id]; $item->title($title); if ($url) { $item->url($url); } return $item; } /** * Add menu divider item * @param string|null $id * @param string|null $title * @return MenuItem */ public function divider(?string $id = null, ?string $title = '') { return $this->add(($id ?: 'divider' . ($this->dividerIndex++)), $title) ->divider(true); } /** * Get menu item * @param string $id * @param Closure|null $callable * @return MenuItem|null */ public function get(string $id, ?Closure $callable = null) { $item = $this->blocks[$id] ?? null; if (! is_null($item) && $callable) { $callable($item); } return $item; } /** * Prepare render data * @return array|bool */ public function data() { $data = parent::data(); $data['items'] = $this->items(function (MenuItem $item) { return $item->visible(); }); return $data; } /** * Get menu items * @param Closure|null $filter * @return MenuItem[] */ public function items(?Closure $filter = null) { if ($filter) { return array_filter($this->blocks, $filter); } return $this->blocks; } /** * Is any item visible * @return bool */ public function visible() { foreach ($this->blocks as $item) { if ($item->visible()) { return true; } } return false; } /** * Iterate over visible items * @see IteratorAggregate::getIterator * @return ArrayIterator|MenuItem[] */ public function getIterator() { # reorder $this->order(); return new ArrayIterator($this->items(function (MenuItem $item) { return $item->visible(); })); } /** * Extend menu items * @param array|MenuItem[] $items */ public function extend(array $items) { # Add or Update foreach ($items as $id => $settings) { if (array_key_exists($id, $this->blocks)) { $this->blocks[$id]->override($settings); } elseif (is_array($settings)) { $this->blocks[$id] = (new MenuItem())->setId($id)->setParent($this)->override($settings); } elseif ($settings instanceof MenuItem) { $this->blocks[$settings->id()] = $settings->setParent($this); } } # reorder $this->order(); } /** * Order menu items by before/after * @return void */ public function order() { if ($this->ordered) { return; } $this->ordered = true; $first = 100; $last = 100000; foreach ($this->blocks as $item) { if ($item->priority > 0) { continue; } if (empty($item->order)) { continue; } if ($item->order > 0) { $item->priority = $first; $first += 100; } elseif ($item->order < 0) { $item->priority = $last; $last -= 100; } } foreach ($this->blocks as $item) { if ($item->priority > 0) { continue; } $exist = false; foreach ($item->before() as $id) { if (isset($this->blocks[$id])) { $exist = true; break; } } if (! $exist) { foreach ($item->after() as $id) { if (isset($this->blocks[$id])) { $exist = true; break; } } } if (! $exist) { $item->priority = $first; $first += 100; } } do { $changes = false; $step = 1; foreach ($this->blocks as $item) { if ($item->priority > 0) { continue; } $step++; } foreach ($this->blocks as $item) { if ($item->priority > 0) { continue; } foreach ($item->before() as $id) { if (isset($this->blocks[$id])) { if ($this->blocks[$id]->priority > 0) { $item->priority = $this->blocks[$id]->priority - $step; $changes = true; } continue 2; } } foreach ($item->after() as $id) { if (isset($this->blocks[$id])) { if ($this->blocks[$id]->priority > 0) { $item->priority = $this->blocks[$id]->priority + $step; $changes = true; } continue 2; } } } } while ($changes); foreach ($this->blocks as $item) { if (empty($item->priority)) { $item->priority = $first; $first += 100; } } uasort($this->blocks, function ($a, $b) { if ($a->priority == $b->priority) { return 0; } return $a->priority < $b->priority ? -1 : 1; }); } /** * Set/get menu editing availability * @param bool|null $editable * @return static|bool */ public function editable(?bool $editable = null) { if (is_null($editable)) { return $this->editable ?? false; } $this->editable = $editable; return $this; } /** * Set/get menu nested depth * @param int|null $nested * @return static|int */ public function nested(?int $nested = null) { if (is_null($nested)) { return $this->nested ?? 0; } $this->nested = $nested; return $this; } /** * Activate menu item by id (first) * @param string|bool $id item key or true (first) * @param bool $visible only * @param bool|mixed $active * @return MenuItem|null */ public function activate($id = true, $visible = true, $active = true) { if ($id === true) { foreach ($this->blocks as $item) { if (! $visible || $item->visible()) { return $item->active($active); } } } elseif (is_string($id)) { $item = $this->get($id); if ($item && (! $visible || $item->visible())) { return $item->active($active); } } return null; } /** * Get active menu item * @param string|bool $default item key or true (first) * @param bool $visible only * @return MenuItem|null */ public function active($default = true, $visible = true) { # First active foreach ($this->blocks as $item) { if ($item->active()) { if (! $visible || $item->visible()) { return $item; } } } # No active -> default if ($default) { # By Id if (is_string($default)) { $item = $this->get($default); if ($item && (! $visible || $item->visible())) { return $item; } $default = true; } # First if ($default === true) { foreach ($this->blocks as $item) { if (! $visible || $item->visible()) { return $item; } } } } return null; } /** * Get total items counter * @param bool $visible * @return int */ public function count($visible = true): int { $count = 0; foreach ($this->blocks as $item) { if (! $visible || $item->visible()) { $count++; } } return $count; } /** * Get menu items pages * @return array */ public function getPages(): array { $pages = []; foreach ($this->blocks as $item) { if ($page = $item->page()) { $pages[] = $page; } } return $pages; } /** * Forward request to menu item page * @param string|bool $id menu item id or true (first found) * @param array $params request params * @return Page|mixed|null */ public function forwardToPage($id, array $params = []) { $active = $this->activate($id ?: true); if ($active && ($page = $active->page())) { if (! is_object($page)) { $page = $this->app->make($page); } return $page(...$params); } return $this->errors->error404(); } }