ORIENT/plugins/rainlab/pages/classes/Menu.php

302 lines
9.9 KiB
PHP

<?php namespace RainLab\Pages\Classes;
use Url;
use Event;
use Request;
use SystemException;
use Cms\Classes\Meta;
use October\Rain\Support\Str;
/**
* Represents a front-end menu.
*
* @package rainlab\pages
* @author Alexey Bobkov, Samuel Georges
*/
class Menu extends Meta
{
/**
* @var string The container name associated with the model, eg: pages.
*/
protected $dirName = 'meta/menus';
/**
* @var array The attributes that are mass assignable.
*/
protected $fillable = [
'content',
'code',
'name',
'itemData',
];
/**
* @var array List of attribute names which are not considered "settings".
*/
protected $purgeable = [
'code',
];
/**
* @var array The rules to be applied to the data.
*/
public $rules = [
'code' => 'required|regex:/^[0-9a-z\-\_]+$/i',
];
/**
* @var array The array of custom error messages.
*/
public $customMessages = [
'required' => 'rainlab.pages::lang.menu.code_required',
'regex' => 'rainlab.pages::lang.menu.invalid_code',
];
/**
* Returns the menu code.
* @return string
*/
public function getCodeAttribute()
{
return $this->getBaseFileName();
}
/**
* Sets the menu code.
* @param string $code Specifies the file code.
* @return \Cms\Classes\CmsObject Returns the object instance.
*/
public function setCodeAttribute($code)
{
$code = trim($code);
if (strlen($code)) {
$this->fileName = $code.'.yaml';
$this->attributes = array_merge($this->attributes, ['code' => $code]);
}
return $this;
}
/**
* Returns a default value for items attribute.
* Items are objects of the \RainLab\Pages\Classes\MenuItem class.
* @return array
*/
public function getItemsAttribute()
{
$items = [];
if (!empty($this->attributes['items'])) {
$items = MenuItem::initFromArray($this->attributes['items']);
}
return $items;
}
/**
* Store the itemData in the items attribute
*
* @param array $data
* @return void
*/
public function setItemDataAttribute($data)
{
$this->items = $data;
return $this;
}
/**
* Processes the content attribute to an array of menu data.
* @return array|null
*/
protected function parseContent()
{
$parsedData = parent::parseContent();
if (!array_key_exists('name', $parsedData)) {
throw new SystemException(sprintf('The content of the %s file is invalid: the name element is not found.', $this->fileName));
}
return $parsedData;
}
/**
* Initializes a cache item.
* @param array &$item The cached item array.
*/
public static function initCacheItem(&$item)
{
$obj = new static($item);
$item['name'] = $obj->name;
$item['items'] = $obj->items;
}
/**
* Returns the menu item references.
* This function is used on the front-end.
* @param Cms\Classes\Page $page The current page object.
* @return array Returns an array of the \RainLab\Pages\Classes\MenuItemReference objects.
*/
public function generateReferences($page)
{
$currentUrl = Request::path();
if (!strlen($currentUrl)) {
$currentUrl = '/';
}
$currentUrl = Str::lower(Url::to($currentUrl));
$activeMenuItem = $page->activeMenuItem ?: false;
$iterator = function($items) use ($currentUrl, &$iterator, $activeMenuItem) {
$result = [];
foreach ($items as $item) {
$parentReference = new MenuItemReference;
$parentReference->title = $item->title;
$parentReference->code = $item->code;
$parentReference->viewBag = $item->viewBag;
/*
* If the item type is URL, assign the reference the item's URL and compare the current URL with the item URL
* to determine whether the item is active.
*/
if ($item->type == 'url') {
$parentReference->url = $item->url;
$parentReference->isActive = $currentUrl == Str::lower(Url::to($item->url)) || $activeMenuItem === $item->code;
}
else {
/*
* If the item type is not URL, use the API to request the item type's provider to
* return the item URL, subitems and determine whether the item is active.
*/
$apiResult = Event::fire('pages.menuitem.resolveItem', [$item->type, $item, $currentUrl, $this->theme]);
if (is_array($apiResult)) {
foreach ($apiResult as $itemInfo) {
if (!is_array($itemInfo)) {
continue;
}
if (!$item->replace && isset($itemInfo['url'])) {
$parentReference->url = $itemInfo['url'];
$parentReference->isActive = $itemInfo['isActive'] || $activeMenuItem === $item->code;
}
if (isset($itemInfo['items'])) {
$itemIterator = function($items) use (&$itemIterator, $parentReference) {
$result = [];
foreach ($items as $item) {
$reference = new MenuItemReference;
$reference->title = isset($item['title']) ? $item['title'] : '--no title--';
$reference->url = isset($item['url']) ? $item['url'] : '#';
$reference->isActive = isset($item['isActive']) ? $item['isActive'] : false;
$reference->viewBag = isset($item['viewBag']) ? $item['viewBag'] : [];
$reference->code = isset($item['code']) ? $item['code'] : null;
if (!strlen($parentReference->url)) {
$parentReference->url = $reference->url;
$parentReference->isActive = $reference->isActive;
}
if (isset($item['items'])) {
$reference->items = $itemIterator($item['items']);
}
$result[] = $reference;
}
return $result;
};
$parentReference->items = $itemIterator($itemInfo['items']);
}
}
}
}
if ($item->items) {
$parentReference->items = $iterator($item->items);
}
if (!$item->replace) {
$result[] = $parentReference;
}
else {
foreach ($parentReference->items as $subItem) {
$result[] = $subItem;
}
}
}
return $result;
};
$items = $iterator($this->items);
/*
* Populate the isChildActive property
*/
$hasActiveChild = function($items) use (&$hasActiveChild) {
foreach ($items as $item) {
if ($item->isActive) {
return true;
}
$result = $hasActiveChild($item->items);
if ($result) {
return $result;
}
}
};
$iterator = function($items) use (&$iterator, &$hasActiveChild) {
foreach ($items as $item) {
$item->isChildActive = $hasActiveChild($item->items);
$iterator($item->items);
}
};
$iterator($items);
/*
* @event pages.menu.referencesGenerated
* Provides opportunity to dynamically change menu entries right after reference generation.
*
* For example you can use it to filter menu entries for user groups from RainLab.User
* Before doing so you have to add custom field 'group' to menu viewBag using backend.form.extendFields event
* where the group can be selected by the user. See how to do this here:
* https://octobercms.com/docs/backend/forms#extend-form-fields
*
* Parameter provided is `$items` - a collection of the MenuItemReference objects passed by reference
*
* For example to hide entries where group is not 'registered' you can use the following code. It can
* be used to show different menus for different user groups.
*
* Event::listen('pages.menu.referencesGenerated', function (&$items) {
* $iterator = function ($menuItems) use (&$iterator, $clusterRepository) {
* $result = [];
* foreach ($menuItems as $item) {
* if (isset($item->viewBag['group']) && $item->viewBag['group'] !== "registered") {
* $item->viewBag['isHidden'] = "1";
* }
* if ($item->items) {
* $item->items = $iterator($item->items);
* }
* $result[] = $item;
* }
* return $result;
* };
* $items = $iterator($items);
* });
*/
Event::fire('pages.menu.referencesGenerated', [&$items]);
return $items;
}
}