├── README.md ├── composer.json └── src ├── Builder.php ├── Collection.php ├── Facades └── Menu.php ├── Item.php ├── Link.php ├── Menu.php └── MenusServiceProvider.php /README.md: -------------------------------------------------------------------------------- 1 | Caffeinated Menus 2 | ================= 3 | [![Source](http://img.shields.io/badge/source-caffeinated/menus-blue.svg?style=flat-square)](https://github.com/caffeinated/menus) 4 | [![License](http://img.shields.io/badge/license-MIT-brightgreen.svg?style=flat-square)](https://tldrlegal.com/license/mit-license) 5 | 6 | --- 7 | 8 | Easily create dynamic menus from within your Laravel 5 application. Originally developed for [FusionCMS](https://github.com/fusioncms/fusioncms), an open source content management system. 9 | 10 | Caffeinated Menus was based off of Lavary's [Laravel Menu](https://github.com/lavary/laravel-menu) package with support for the Caffeinated Shinobi package. 11 | 12 | The package follows the FIG standards PSR-1, PSR-2, and PSR-4 to ensure a high level of interoperability between shared PHP code. At the moment the package is not unit tested, but is planned to be covered later down the road. 13 | 14 | Documentation 15 | ------------- 16 | You will find user friendly and updated documentation in the wiki here: [Caffeinated Menus Wiki](https://github.com/caffeinated/menus/wiki) 17 | 18 | Quick Installation 19 | ------------------ 20 | Begin by installing the package through Composer. 21 | 22 | ``` 23 | composer require caffeinated/menus 24 | ``` 25 | 26 | Once this operation is complete, simply add the service provider class and facade alias to your project's `config/app.php` file: 27 | 28 | #### Service Provider 29 | ```php 30 | Caffeinated\Menus\MenusServiceProvider::class, 31 | ``` 32 | 33 | #### Facade 34 | ```php 35 | 'Menu' => Caffeinated\Menus\Facades\Menu::class, 36 | ``` 37 | 38 | And that's it! With your coffee in reach, start building out some awesome menus! 39 | -------------------------------------------------------------------------------- /composer.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "caffeinated/menus", 3 | "description": "Laravel Menus", 4 | "keywords": ["menu", "navigation", "laravel", "caffeinated"], 5 | "license": "MIT", 6 | "authors": [ 7 | { 8 | "name": "Shea Lewis", 9 | "email": "shea.lewis89@gmail.com" 10 | } 11 | ], 12 | "require": { 13 | "php": "^7.2|^8.0", 14 | "illuminate/support": "^6.0|^7.0|^8.0" 15 | }, 16 | "autoload": { 17 | "psr-4": { 18 | "Caffeinated\\Menus\\": "src/" 19 | } 20 | }, 21 | "extra": { 22 | "laravel": { 23 | "providers": [ 24 | "Caffeinated\\Menus\\MenusServiceProvider" 25 | ], 26 | "aliases": { 27 | "Menu": "Caffeinated\\Menus\\Facades\\Menu" 28 | } 29 | } 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /src/Builder.php: -------------------------------------------------------------------------------- 1 | name = $name; 49 | $this->config = $config; 50 | $this->items = new Collection; 51 | } 52 | 53 | /** 54 | * Add an item to the defined menu. 55 | * 56 | * @param string $title 57 | * @param array|string $options 58 | * 59 | * @return Item 60 | */ 61 | public function add($title, $options = '') 62 | { 63 | $item = new Item($this, $this->id(), $title, $options); 64 | 65 | $this->items->push($item); 66 | 67 | $this->lastId = $item->id; 68 | 69 | return $item; 70 | } 71 | 72 | /** 73 | * Generate a unique ID for every item added to the menu. 74 | * 75 | * @return int 76 | */ 77 | protected function id() 78 | { 79 | return $this->lastId + 1; 80 | } 81 | 82 | /** 83 | * Extract the valid attributes from the passed options. 84 | * 85 | * @param array $options 86 | * 87 | * @return array 88 | */ 89 | public function extractAttributes($options = array()) 90 | { 91 | if (is_array($options)) { 92 | if (count($this->groupStack) > 0) { 93 | $options = $this->mergeWithLastGroup($options); 94 | } 95 | 96 | return Arr::except($options, $this->reserved); 97 | } 98 | 99 | return array(); 100 | } 101 | 102 | /** 103 | * Insert a divider after the item. 104 | * 105 | * @param array $attributes 106 | * 107 | * @return void 108 | */ 109 | public function divide($attributes = array()) 110 | { 111 | $attributes['class'] = self::formatGroupClass(['class' => 'divider'], $attributes); 112 | 113 | $this->items->last()->divider = $attributes; 114 | } 115 | 116 | /** 117 | * Return the configuration value by key. 118 | * 119 | * @param string $key 120 | * 121 | * @return string 122 | */ 123 | public function config($key) 124 | { 125 | return $this->config[$key]; 126 | } 127 | 128 | /** 129 | * Get the prefix from the last group of the stack. 130 | * 131 | * @return mixed 132 | */ 133 | public function getLastGroupPrefix() 134 | { 135 | if (count($this->groupStack) > 0) { 136 | return Arr::get(last($this->groupStack), 'prefix', ''); 137 | } 138 | 139 | return null; 140 | } 141 | 142 | /** 143 | * Format the groups class. 144 | * 145 | * @return mixed 146 | */ 147 | public static function formatGroupClass($new, $old) 148 | { 149 | if (isset($new['class'])) { 150 | $classes = trim(trim(Arr::get($old, 'class')).' '.trim(Arr::get($new, 'class'))); 151 | 152 | return implode(' ', array_unique(explode(' ', $classes))); 153 | } 154 | 155 | return Arr::get($old, 'class'); 156 | } 157 | 158 | /* 159 | |-------------------------------------------------------------------------- 160 | | Fetching Methods 161 | |-------------------------------------------------------------------------- 162 | | 163 | */ 164 | 165 | /** 166 | * Fetches and returns all menu items. 167 | * 168 | * @return Collection 169 | */ 170 | public function all() 171 | { 172 | return $this->items; 173 | } 174 | 175 | /** 176 | * Returns all items with no parents. 177 | * 178 | * @return Collection 179 | */ 180 | public function roots() 181 | { 182 | return $this->whereParent(); 183 | } 184 | 185 | /** 186 | * Fetches and returns a menu item by it's slug. 187 | * 188 | * @param string $slug 189 | * 190 | * @return Item 191 | */ 192 | public function get($slug) 193 | { 194 | return $this->whereSlug($slug)->first(); 195 | } 196 | 197 | /** 198 | * Facade method for the get() method. 199 | * 200 | * @param string $slug 201 | * 202 | * @return Item 203 | */ 204 | public function item($slug) 205 | { 206 | return $this->get($slug); 207 | } 208 | 209 | /** 210 | * Fetches and returns a menu item by it's ID. 211 | * 212 | * @param integer $id 213 | * 214 | * @return Item 215 | */ 216 | public function find($id) 217 | { 218 | return $this->whereId($id)->first(); 219 | } 220 | 221 | /** 222 | * Fetches and returns the first menu item. 223 | * 224 | * @return Item 225 | */ 226 | public function first() 227 | { 228 | return $this->items->first(); 229 | } 230 | 231 | /** 232 | * Fetches and returns the last menu item. 233 | * 234 | * @return Item 235 | */ 236 | public function last() 237 | { 238 | return $this->items->last(); 239 | } 240 | 241 | /** 242 | * Fetches and returns all active state menu items. 243 | * 244 | * @return Collection 245 | */ 246 | public function active() 247 | { 248 | $activeItems = array(); 249 | 250 | foreach ($this->items as $item) { 251 | if ($item->data('active')) { 252 | $activeItems[] = $item; 253 | } 254 | } 255 | 256 | return $activeItems; 257 | } 258 | 259 | /* 260 | |-------------------------------------------------------------------------- 261 | | Dispatch Methods 262 | |-------------------------------------------------------------------------- 263 | | 264 | */ 265 | 266 | /** 267 | * Get the action type from the options. 268 | * 269 | * @param array $options 270 | * 271 | * @return string 272 | */ 273 | public function dispatch($options) 274 | { 275 | if (isset($options['url'])) { 276 | return $this->getUrl($options); 277 | } elseif (isset($options['route'])) { 278 | return $this->getRoute($options['route']); 279 | } elseif (isset($options['action'])) { 280 | return $this->getAction($options['action']); 281 | } 282 | 283 | return null; 284 | } 285 | 286 | /** 287 | * Get the action for a "url" option. 288 | * 289 | * @param array|string $options 290 | * 291 | * @return string 292 | */ 293 | protected function getUrl($options) 294 | { 295 | foreach ($options as $key => $value) { 296 | $$key = $value; 297 | } 298 | 299 | $secure = (isset($options['secure']) and $options['secure'] === true) ? true : false; 300 | 301 | if ($prefix) { 302 | $prefix = $prefix.'/'; 303 | } 304 | 305 | if (is_array($url)) { 306 | if (self::isAbsolute($url[0])) { 307 | return $url[0]; 308 | } 309 | 310 | return url()->to($prefix.$url[0], array_slice($url, 1), $secure); 311 | } 312 | 313 | if (self::isAbsolute($url)) { 314 | return $url; 315 | } 316 | 317 | return url()->to($prefix.$url, array(), $secure); 318 | } 319 | 320 | /** 321 | * Get the route action for a "route" option. 322 | * 323 | * @param array|string $route 324 | * 325 | * @return string 326 | */ 327 | protected function getRoute($route) 328 | { 329 | if (is_array($route)) { 330 | return url()->route($route[0], array_slice($route, 1)); 331 | } 332 | 333 | return url()->route($route); 334 | } 335 | 336 | /** 337 | * Get the controller action for a "action" option. 338 | * 339 | * @param array|string $action 340 | * 341 | * @return string 342 | */ 343 | protected function getAction($action) 344 | { 345 | if (is_array($action)) { 346 | return url()->action($action[0], array_slice($action, 1)); 347 | } 348 | 349 | return url()->action($action); 350 | } 351 | 352 | /** 353 | * Determines if the given URL is absolute. 354 | * 355 | * @param string $url 356 | * 357 | * @return bool 358 | */ 359 | public static function isAbsolute($url) 360 | { 361 | return parse_url($url, PHP_URL_SCHEME) or false; 362 | } 363 | 364 | /* 365 | |-------------------------------------------------------------------------- 366 | | Filter Methods 367 | |-------------------------------------------------------------------------- 368 | | 369 | */ 370 | 371 | /** 372 | * Filter menu items through a callback. 373 | * 374 | * Since menu items are stored as a collection, this will 375 | * simply forward the callback to the Laravel Collection 376 | * filter() method and return the results. 377 | * 378 | * @param callable $callback 379 | * 380 | * @return Builder 381 | */ 382 | public function filter($callback) 383 | { 384 | if (is_callable($callback)) { 385 | $this->items = $this->items->filter($callback); 386 | } 387 | 388 | return $this; 389 | } 390 | 391 | /** 392 | * Filter menu items recursively. 393 | * 394 | * @param string $attribute 395 | * @param mixed $value 396 | * 397 | * @return Collection 398 | */ 399 | public function filterRecursively($attribute, $value) 400 | { 401 | $collection = new Collection; 402 | 403 | $this->items->each(function ($item) use ($attribute, $value, &$collection) { 404 | if (! property_exists($item, $attribute)) { 405 | return false; 406 | } 407 | 408 | if ($item->$attribute == $value) { 409 | $collection->push($item); 410 | 411 | if ($item->hasChildren()) { 412 | $collection = $collection->merge($this->filterRecursively($attribute, $item->id)); 413 | } 414 | } 415 | }); 416 | 417 | return $collection; 418 | } 419 | 420 | /** 421 | * Sorts the menu based on key given in ascending order. 422 | * 423 | * @param string $key 424 | * 425 | * @return Builder 426 | */ 427 | public function sortBy($key) 428 | { 429 | $this->items = $this->items->sortBy(function ($item) use ($key) { 430 | return $item->$key; 431 | }); 432 | 433 | return $this; 434 | } 435 | 436 | /** 437 | * Sorts the menu based on key given in descending order. 438 | * 439 | * @param string $key 440 | * 441 | * @return Builder 442 | */ 443 | public function sortByDesc($key) 444 | { 445 | $this->items = $this->items->sortByDesc(function ($item) use ($key) { 446 | return $item->$key; 447 | }); 448 | 449 | return $this; 450 | } 451 | 452 | /** 453 | * Filter menu items based on Shinobi permissions. 454 | * 455 | * @return Builder 456 | */ 457 | public function guard() 458 | { 459 | if (class_exists('Caffeinated\Shinobi\Shinobi')) { 460 | $this->filter(function ($item) { 461 | if (! $item->data('can') and ! $item->data('canatleast')) { 462 | return true; 463 | } elseif ($item->data('canatleast')) { 464 | return \Shinobi::canAtLeast($item->data('canatleast')); 465 | } else { 466 | return \Shinobi::can($item->data('can')); 467 | } 468 | }); 469 | } 470 | 471 | return $this; 472 | } 473 | 474 | /** 475 | * Dynamic search method against a menu attribute. 476 | * 477 | * @param string $method 478 | * @param array $args 479 | * 480 | * @return Item|bool 481 | */ 482 | public function __call($method, $args) 483 | { 484 | preg_match('/^[W|w]here([a-zA-Z0-9_]+)$/', $method, $matches); 485 | 486 | if ($matches) { 487 | $attribute = Str::lower($matches[1]); 488 | } else { 489 | throw new BadMethodCallException('Call to undefined method '.$method); 490 | } 491 | 492 | $value = $args ? $args[0] : null; 493 | $recursive = isset($args[1]) ? $args[1] : false; 494 | 495 | if ($recursive) { 496 | return $this->filterRecursively($attribute, $value); 497 | } 498 | 499 | return $this->items->filter(function ($item) use ($attribute, $value) { 500 | if (isset($item->data[$attribute]) && $item->data[$attribute] == $value) { 501 | return true; 502 | } 503 | 504 | if (! property_exists($item, $attribute)) { 505 | return false; 506 | } 507 | 508 | if ($item->$attribute == $value) { 509 | return true; 510 | } 511 | 512 | return false; 513 | })->values(); 514 | } 515 | 516 | /** 517 | * Returns menu item by name. 518 | * 519 | * @param string $property 520 | * 521 | * @return Item 522 | */ 523 | public function __get($property) 524 | { 525 | if (property_exists($this, $property)) { 526 | return $this->$property; 527 | } 528 | 529 | return $this->whereSlug($property)->first(); 530 | } 531 | } 532 | -------------------------------------------------------------------------------- /src/Collection.php: -------------------------------------------------------------------------------- 1 | each(function($item) use ($args) { 19 | if (count($args) >= 2) { 20 | $item->attr($args[0], $args[1]); 21 | } else { 22 | $item->attr($args[0]); 23 | } 24 | }); 25 | 26 | return $this; 27 | } 28 | 29 | /** 30 | * Add metadata to the collection of items. 31 | * 32 | * @param mixed 33 | * @return \Caffeinated\Menus\Collection 34 | */ 35 | public function data() 36 | { 37 | $args = func_get_args(); 38 | 39 | $this->each(function($item) use ($args) { 40 | if (count($args) >= 2) { 41 | $item->data($args[0], $args[1]); 42 | } else { 43 | $item->data($args[0]); 44 | } 45 | }); 46 | 47 | return $this; 48 | } 49 | 50 | /** 51 | * Appends text or HTML to the collection of items. 52 | * 53 | * @param string $html 54 | * @return \Caffeinated\Menus\Collection 55 | */ 56 | public function append($html) 57 | { 58 | $this->each(function($item) use ($html) { 59 | $item->title .= $html; 60 | }); 61 | 62 | return $this; 63 | } 64 | 65 | /** 66 | * Prepends text or HTML to the collection of items. 67 | * 68 | * @param string $html 69 | * @param mixed $key 70 | * @return \Caffeinated\Menus\Collection 71 | */ 72 | public function prepend($html, $key = null) 73 | { 74 | $this->each(function($item) use ($html) { 75 | $item->title = $html.$item->title; 76 | }); 77 | 78 | return $this; 79 | } 80 | } 81 | -------------------------------------------------------------------------------- /src/Facades/Menu.php: -------------------------------------------------------------------------------- 1 | builder = $builder; 61 | $this->id = $id; 62 | $this->title = $title; 63 | $this->slug = Str::camel(Str::slug($title, ' ')); 64 | $this->attributes = $this->builder->extractAttributes($options); 65 | $this->parent = (is_array($options) and isset($options['parent'])) ? $options['parent'] : null; 66 | 67 | $this->configureLink($options); 68 | } 69 | 70 | public function builder() 71 | { 72 | return $this->builder; 73 | } 74 | 75 | /** 76 | * Configures the link for the menu item. 77 | * 78 | * @param array|string $options 79 | * @return null 80 | */ 81 | public function configureLink($options) 82 | { 83 | if (! is_array($options)) { 84 | $path = ['url' => $options]; 85 | } elseif (isset($options['raw']) and $options['raw'] == true) { 86 | $path = null; 87 | } else { 88 | $path = Arr::only($options, ['url', 'route', 'action', 'secure']); 89 | } 90 | 91 | if (! is_null($path)) { 92 | $path['prefix'] = $this->builder->getLastGroupPrefix(); 93 | } 94 | 95 | $this->link = isset($path) ? new Link($path) : null; 96 | 97 | $this->checkActiveStatus(); 98 | } 99 | 100 | /** 101 | * Adds a sub item to the menu. 102 | * 103 | * @param string $title 104 | * @param array|string $options 105 | * @return \Caffeinated\Menus\Item 106 | */ 107 | public function add($title, $options = '') 108 | { 109 | if (! is_array($options)) { 110 | $url = $options; 111 | $options = array(); 112 | $options['url'] = $url; 113 | } 114 | 115 | $options['parent'] = $this->id; 116 | 117 | return $this->builder->add($title, $options); 118 | } 119 | 120 | /** 121 | * Get all attributes. 122 | * 123 | * @return array 124 | */ 125 | public function getAttributes() 126 | { 127 | return $this->attributes; 128 | } 129 | 130 | /** 131 | * Assign or fetch the desired attribute. 132 | * 133 | * @param array|string $attribute 134 | * @param string $value 135 | * @return mixed 136 | */ 137 | public function attribute($attribute, $value = null) 138 | { 139 | if (isset($attribute) and is_array($attribute)) { 140 | if (array_key_exists('class', $attribute)) { 141 | $this->attributes['class'] = $this->builder->formatGroupClass(['class' => $attribute['class']], $this->attributes); 142 | unset($attribute['class']); 143 | } 144 | 145 | $this->attributes = array_merge($this->attributes, $attribute); 146 | 147 | return $this; 148 | } elseif (isset($attribute) and isset($value)) { 149 | if ($attribute == 'class') { 150 | $this->attributes['class'] = $this->builder->formatGroupClass(['class' => $value], $this->attributes); 151 | } else { 152 | $this->attributes[$attribute] = $value; 153 | } 154 | 155 | return $this; 156 | } 157 | 158 | return isset($this->attributes[$attribute]) ? $this->attributes[$attribute] : null; 159 | } 160 | 161 | /** 162 | * Generates a valid URL for the menu item. 163 | * 164 | * @return string 165 | */ 166 | public function url() 167 | { 168 | if (! is_null($this->link)) { 169 | if ($this->link->href) { 170 | return $this->link->href; 171 | } 172 | 173 | return $this->builder->dispatch($this->link->path); 174 | } 175 | } 176 | 177 | /** 178 | * Prepends HTML to the item. 179 | * 180 | * @param string $html 181 | * @return \Caffeinated\Menus\Item 182 | */ 183 | public function prepend($html) 184 | { 185 | $this->title = $html.' '.$this->title; 186 | 187 | return $this; 188 | } 189 | 190 | /** 191 | * Appends HTML to the item. 192 | * 193 | * @param string $html 194 | * @return \Caffeinated\Menus\Item 195 | */ 196 | public function append($html) 197 | { 198 | $this->title = $this->title.' '.$html; 199 | 200 | return $this; 201 | } 202 | 203 | /** 204 | * Appends the specified icon to the item. 205 | * 206 | * @param string $icon 207 | * @param string $type Can be either "fontawesome" or "glyphicon" 208 | * @return \Caffeinated\Menus\Item 209 | */ 210 | public function icon($icon, $type = 'fontawesome') 211 | { 212 | switch ($type) { 213 | case 'fontawesome': 214 | $html = ''; 215 | break; 216 | 217 | case 'glyphicon': 218 | $html = ''; 219 | break; 220 | 221 | case 'entypo': 222 | $html = ''; 223 | break; 224 | 225 | default: 226 | $html = ''; 227 | break; 228 | } 229 | 230 | return $this->data('icon', $html); 231 | } 232 | 233 | /** 234 | * Return the title with the icon prepended automatically. 235 | * 236 | * @return string 237 | */ 238 | public function prependIcon() 239 | { 240 | return $this->prepend($this->data('icon')); 241 | } 242 | 243 | /** 244 | * Return the title with the icon appended automatically. 245 | * 246 | * @return string 247 | */ 248 | public function appendIcon() 249 | { 250 | return $this->append($this->data('icon')); 251 | } 252 | 253 | /** 254 | * Insert a divider after the item. 255 | * 256 | * @param array $attributes 257 | * @return void 258 | */ 259 | public function divide($attributes = array()) 260 | { 261 | $attributes['class'] = $this->builder->formatGroupClass($attributes, ['class' => 'divider']); 262 | 263 | $this->divider = $attributes; 264 | 265 | return $this; 266 | } 267 | 268 | /** 269 | * Determines if the menu item has children. 270 | * 271 | * @return bool 272 | */ 273 | public function hasChildren() 274 | { 275 | return count($this->builder->whereParent($this->id)) or false; 276 | } 277 | 278 | /** 279 | * Returns all children underneath the menu item. 280 | * 281 | * @return \Caffeinated\Menus\Collection 282 | */ 283 | public function children() 284 | { 285 | return $this->builder->whereParent($this->id); 286 | } 287 | 288 | /** 289 | * Set or get an item's metadata. 290 | * 291 | * @param mixed 292 | * @return string|\Caffeinated\Menus\Item 293 | */ 294 | public function data() 295 | { 296 | $args = func_get_args(); 297 | 298 | if (isset($args[0]) and is_array($args[0])) { 299 | $this->data = array_merge($this->data, array_change_key_case($args[0])); 300 | 301 | return $this; 302 | } elseif (isset($args[0]) and isset($args[1])) { 303 | $this->data[strtolower($args[0])] = $args[1]; 304 | 305 | return $this; 306 | } elseif (isset($args[0])) { 307 | return isset($this->data[$args[0]]) ? $this->data[$args[0]] : null; 308 | } 309 | 310 | return $this->data; 311 | } 312 | 313 | /** 314 | * Decide if the item should be active. 315 | * 316 | * @return null 317 | */ 318 | public function checkActiveStatus() 319 | { 320 | $path = ltrim(parse_url($this->url(), PHP_URL_PATH), '/'); 321 | $requestPath = Request::path(); 322 | 323 | if (isset($this->builder->config) && $this->builder->config['rest_base']) { 324 | $base = (is_array($this->builder->config['rest_base'])) ? implode('|', $this->builder->config['rest_base']) : $this->builder->conf['rest_base']; 325 | 326 | list($path, $requestPath) = preg_replace('@^('.$base.')/@', '', [$path, $requestPath], 1); 327 | } 328 | 329 | if ($this->url() == Request::url() || $this->url() == \URL::secure(Request::path())) { 330 | $this->activate(); 331 | } 332 | } 333 | 334 | public function activate(Item $item = null) 335 | { 336 | $item = (is_null($item)) ? $this : $item; 337 | 338 | $item->active(); 339 | 340 | $item->data('active', true); 341 | 342 | if ($item->parent) { 343 | $parent = $this->builder->whereId($item->parent)->first(); 344 | $parent->attributes['class'] = $parent->builder->formatGroupClass(['class' => 'opened'], $parent->attributes); 345 | $this->activate($parent); 346 | } 347 | } 348 | 349 | public function active($pattern = null) 350 | { 351 | if (! is_null($pattern)) { 352 | $pattern = ltrim(preg_replace('/\/\*/', '(/.*)?', $pattern), '/'); 353 | 354 | if (preg_match("@{$pattern}\z@", Request::path())) { 355 | $this->activate(); 356 | } 357 | 358 | return $this; 359 | } 360 | 361 | $this->attributes['class'] = $this->builder->formatGroupClass(['class' => 'active'], $this->attributes); 362 | 363 | return $this; 364 | } 365 | 366 | /** 367 | * Returns bool value if item is active or not. 368 | * 369 | * @return bool 370 | */ 371 | public function isActive() 372 | { 373 | return $this->data('active'); 374 | } 375 | 376 | public function can($permissions) 377 | { 378 | return $this->data('can', $permissions); 379 | } 380 | 381 | public function canAtLeast($permissions) 382 | { 383 | return $this->data('canatleast', $permissions); 384 | } 385 | 386 | /** 387 | * Return either a property or attribute item value. 388 | * 389 | * @param string $property 390 | * @return string 391 | */ 392 | public function __get($property) 393 | { 394 | if (property_exists($this, $property)) { 395 | return $this->$property; 396 | } 397 | 398 | return $this->data($property); 399 | } 400 | } 401 | -------------------------------------------------------------------------------- /src/Link.php: -------------------------------------------------------------------------------- 1 | path = $path; 29 | } 30 | 31 | /** 32 | * Set the link's href property. 33 | * 34 | * @param string $href 35 | * @return \Caffeinated\Menus\Link 36 | */ 37 | public function href($href) 38 | { 39 | $this->href = $href; 40 | 41 | return $this; 42 | } 43 | 44 | /** 45 | * Make the link active. 46 | * 47 | * @return \Caffeinated\Menus\Link 48 | */ 49 | public function active() 50 | { 51 | $this->attributes['class'] = Builder::formatGroupClass(['class' => 'active'], $this->attributes); 52 | 53 | return $this; 54 | } 55 | 56 | /** 57 | * Add attributes to the link. 58 | * 59 | * @param mixed 60 | * @return \Caffeinated\Menus\Link|string 61 | */ 62 | public function attr() 63 | { 64 | $args = func_get_args(); 65 | 66 | if (isset($args[0]) and is_array($args[0])) { 67 | $this->attributes = array_merge($this->attributes, $args[0]); 68 | 69 | return $this; 70 | } elseif (isset($args[0]) and isset($args[1])) { 71 | $this->attributes[$args[0]] = $args[1]; 72 | 73 | return $this; 74 | } elseif (isset($args[0])) { 75 | return isset($this->attributes[$args[0]]) ? $this->attributes[$args[0]] : null; 76 | } 77 | 78 | return $this->attributes; 79 | } 80 | 81 | /** 82 | * Dynamically retrieve property value. 83 | * 84 | * @param string $property 85 | * @return mixed 86 | */ 87 | public function __get($property) 88 | { 89 | if (property_exists($this, $property)) { 90 | return $this->$property; 91 | } 92 | 93 | return $this->attr($property); 94 | } 95 | } 96 | -------------------------------------------------------------------------------- /src/Menu.php: -------------------------------------------------------------------------------- 1 | config = $config; 26 | $this->collection = new Collection; 27 | } 28 | 29 | /** 30 | * Create a new menu instance. 31 | * 32 | * @param string $name 33 | * @param callable $callback 34 | * @return \Caffeinated\Menus\Builder 35 | */ 36 | public function make($name, $callback) 37 | { 38 | if (is_callable($callback)) { 39 | $menu = new Builder($name, $this->loadConfig($name)); 40 | 41 | call_user_func($callback, $menu); 42 | 43 | $this->collection->put($name, $menu); 44 | 45 | view()->share('menu_'.$name, $menu); 46 | 47 | return $menu; 48 | } 49 | } 50 | 51 | /** 52 | * Loads and merges configuration data. 53 | * 54 | * @param string $name 55 | * @return array 56 | */ 57 | public function loadConfig($name) 58 | { 59 | $options = $this->config->get('menu.settings'); 60 | $name = strtolower($name); 61 | 62 | if (isset($options[$name]) and is_array($options[$name])) { 63 | return array_merge($options['default'], $options[$name]); 64 | } 65 | 66 | return $options['default'] ?? null; 67 | } 68 | 69 | /** 70 | * Find and return the given menu collection. 71 | * 72 | * @param string $key 73 | * @return \Caffeinated\Menus\Collection 74 | */ 75 | public function get($key) 76 | { 77 | return $this->collection->get($key); 78 | } 79 | 80 | /** 81 | * Returns all menu instances. 82 | * 83 | * @return \Caffeinated\Menus\Collection 84 | */ 85 | public function all() 86 | { 87 | return $this->collection; 88 | } 89 | } 90 | -------------------------------------------------------------------------------- /src/MenusServiceProvider.php: -------------------------------------------------------------------------------- 1 | registerServices(); 23 | } 24 | 25 | /** 26 | * Get the services provided by the provider. 27 | * 28 | * @return array 29 | */ 30 | public function provides() 31 | { 32 | return ['menu']; 33 | } 34 | 35 | /** 36 | * Register the package services. 37 | * 38 | * @return void 39 | */ 40 | protected function registerServices() 41 | { 42 | // Bind our Menu class to the IoC container 43 | $this->app->singleton('menu', function($app) { 44 | return new Menu($app['config']); 45 | }); 46 | } 47 | } 48 | --------------------------------------------------------------------------------