app = $app; } /** * Закрепляем необходимые данные за основным разделом * @deprecated * @param string $mainTitle название основного раздела * @param string $paramKey ключ * @param mixed $paramValue данные */ public function setMainTabParam($mainTitle, $paramKey, $paramValue) { foreach ($this->groups as $key => $group) { if ($group['title'] === $mainTitle) { $this->groups[$key][$paramKey] = $paramValue; break; } } } /** * Счетчики в шапке админ. панели * @deprecated * @param string $title название счетчика * @param string $counterKey ключ счетчика в настройках сайта или пользовательский ($options['userCounter'] = true) * @param string $module модуль * @param string $method метод * @param int $priority приоритет, определяет порядок счетчиков * @param string $icon иконка * @param array $options доп. параметры */ public function adminHeaderCounter($title, $counterKey, $module, $method, $priority, $icon = '', array $options = []) { $this ->header($title, $module, $method) ->counter((!empty($options['userCounter']) ? User::counter($counterKey) : (is_numeric($counterKey) ? $counterKey : config::get($counterKey, 0, TYPE_UINT)))) ->icon($icon) ->priority($priority); if (isset($options['params'])) { $this->params($options['params']); } if (isset($this->items[$this->current])) { if (! isset($options['counterKey'])) { $options['counterKey'] = $counterKey; } $this->items[$this->current]['o'] = $options; } } /** * Формирование счетчиков в шапке админ. панели * @deprecated * @return array */ public function adminHeaderCounters() { $result = []; foreach ($this->items as $item) { if (empty($item['header'])) { continue; } $item['t'] = $item['title']; $item['url'] = tplAdmin::adminLink($item['event'], $item['class']); $item['s'] = $item['class']; $item['ev'] = $item['event']; $item['evp'] = $item['eventParams']; $item['cnt'] = $this->makeCounter($item['id']); $item['num'] = $item['priority']; $item['i'] = $item['icon']; $item['danger'] = ! empty($item['o']['danger']); $result[] = $item; } func::sortByPriority($result); return $result; } /** * Добавление пункта меню * @deprecated * @param string $mainTitle название основного раздела меню * @param string $subTitle название подраздела меню * @param string $module название модуля, или ссылка (@example http://example.com/page1.html) * @param string $method название метода модуля * @param bool $isVisible показывать пункт меню по-умолчанию * @param int $priority приоритет, определяет порядок подразделов в пределах раздела * @param array $options дополнительные настройки: * 1. rlink => [title=>'+', event=>'', type=>static::TYPE_MODULE_METHOD, link=>''] * 2. counter => [] * 3. params => string дополнительные параметры ссылки * 4. access => ['module', 'method'] или 'method' - проверка прав доступа * @param int $type тип раздела/подраздела ( static::TYPE_ ) * @return static */ public function assign($mainTitle, $subTitle, $module, $method, $isVisible = true, $priority = 999, array $options = [], $type = 0) { $this ->group($mainTitle) ->add($subTitle, $module, $method, $priority) ->visible($isVisible) ->access(( ! empty($options['access']) ? $options['access'] : false)) ; if (! empty($options['params'])) { $this->params($options['params']); } if (! empty($options['rlink'])) { $rlink = $options['rlink']; if (!isset($rlink['title'])) { $rlink['title'] = '+'; } $rlink['url'] = (isset($rlink['type']) && $rlink['type'] == 1 ? $rlink['link'] : ($type == 1 ? '#' : tplAdmin::adminLink($rlink['event'], $module))); $this->plus(['rlink' => $rlink]); } if (! empty($options['counter'])) { if (isset($options['counterValue'])) { if (is_scalar($options['counterValue'])) { $cnt = $options['counterValue']; } elseif ($options['counterValue'] instanceof Closure) { $cnt = call_user_func($options['counterValue'], $options['counter'], [ 'options' => $options, 'module' => $module, 'method' => $method, ]); } } else { $cnt = (!empty($options['userCounter']) ? User::counter($options['counter']) : config::get($options['counter'], 0, TYPE_UINT)); } $this->counter($cnt); } return $this; } /** * Формирование admin-меню * @param \bff\base\Route|null $route * @param array $mainTabs список названий основных пунктов меню * @param array $opts доп. параметры: * string 'collectMethod' - название вызываемого метода для формирования пунктов меню * @return array */ public function build($route, array $mainTabs = [], array $opts = []): array { $opts = $this->defaults($opts, [ 'collectMethod' => 'declareAdminMenu', ]); $security = $this->app->security(); # основные пункты меню: $mainTabs = $this->app->filter('admin.menu.tabs', $mainTabs, $this); foreach ($mainTabs as $tab) { $title = ''; if (is_string($tab)) { $title = $tab; } elseif (is_array($tab) && array_key_exists('title', $tab)) { $title = $tab['title']; } if ($title) { $this->group($title, false, $tab['priority'] ?? false); } } # app modules $modulesPath = $this->app->modulesPath(); $modulesList = $this->app->getModulesList(); foreach ($modulesList as $module => $moduleParams) { $file = false; foreach (['admin.menu.php', "m.$module.class.php"] as $fileName) { $filePath = modification((!empty($moduleParams['path']) ? $moduleParams['path'] : $modulesPath . $module . DS) . $fileName); if (file_exists($filePath)) { $file = $filePath; break; } } if ($file === false) { continue; } require_once($file); # todo: remove M_ classes support if ( class_exists('M_' . $module, false) && method_exists('M_' . $module, $opts['collectMethod']) ) { if (!$this->userHasAccessTo($module) && !in_array($module, ['site'])) { continue; } call_user_func(['M_' . $module, $opts['collectMethod']], $this, $security); } } # extensions $this->app->hook('admin.menu.build', $this); $this->applyModify(); $this->checkPriorityGroups(); $this->checkPriorityItems(); $this->checkAccess(); $url = ''; $t = ''; return [ 'tabs' => $this->getTabs($route, $url), 'tabsBottom' => $this->getTabs($route, $url, ['bottom' => true]), 'url' => $url, 'header' => $this->getTabs($route, $t, ['header' => true, 'default' => true]), 'countersGroups' => $this->getTabs($route, $t, ['header' => true]), ]; } /** * Проверка доступа к пунктам меню у пользователя * @return void */ protected function checkAccess() { foreach ($this->items as $key => $item) { if (empty($item['access'])) { continue; } if ($item['access'] === true) { if ($this->userHasAccessTo($item['class'], $item['event'])) { continue; } } elseif ($item['access'] instanceof Closure) { if (call_user_func($item['access'])) { continue; } } elseif (is_string($item['access'])) { if (empty($item['class'])) { continue; } if ($this->userHasAccessTo($item['class'], $item['access'])) { continue; } } elseif (is_array($item['access']) && sizeof($item['access']) == 2) { if ($this->userHasAccessTo($item['access'][0], $item['access'][1])) { continue; } } elseif (is_callable($item['access'])) { if (call_user_func($item['access'])) { continue; } } unset($this->items[$key]); } } /** * Текущий авторизованный пользователь имеет доступ к указанному модулю/методу * @param mixed $module * @param mixed $scope * @return bool */ protected function userHasAccessTo($module, $scope = null) { return Security::haveAccessTo($module, $scope); } /** * Выборка групп меню с пунктами * @param \bff\base\Route|null $route * @param string $activeUrl * @param array $opts * @return array|mixed */ protected function getTabs($route, string &$activeUrl = '', array $opts = []) { $opts = $this->defaults($opts, [ 'bottom' => false, 'header' => false, 'default' => false, ]); $currentController = $route ? $route->getControllerName() : ''; $currentMethod = $route ? $route->getControllerMethod() : ''; $result = []; foreach ($this->groups as $group) { if ($group['bottom'] !== $opts['bottom']) { continue; } if ($group['header'] !== $opts['header']) { continue; } if ($group['header']) { if ($opts['default'] && $group['title'] !== '__default__') { continue; } if (! $opts['default'] && $group['title'] == '__default__') { continue; } } $group['active'] = false; $group['separator'] = false; $group['counter'] = 0; $sub = []; foreach ($this->items as $item) { if ($item['group'] != $group['id']) { continue; } $item['active'] = false; $item['separator'] = false; $item['url'] = $this->makeUrl($item['id']); if ($item['class'] === $currentController && $item['event'] === $currentMethod) { $group['active'] = $item['active'] = true; $activeUrl = $item['url']; Admin::pageSettings(['title' => $group['title'] . ' / ' . $item['title']], false); } if (! $item['active'] && !empty($item['aliases'])) { foreach ($item['aliases'] as $a) { if ($a['class'] == $currentController && $a['event'] == $currentMethod) { $group['active'] = $item['active'] = true; $activeUrl = $item['url']; Admin::pageSettings(['title' => $group['title'] . ' / ' . $item['title']], false); break; } } } if (! $item['visible']) { continue; } $item['counter'] = $this->makeCounter($item['id']); $item['title_orig'] = $item['title']; if ($item['counter']) { $item['title'] .= ' ' . $item['counter'] . ''; } $group['counter'] += (int)$item['counter']; $item['plus'] = $this->makePlus($item['id']); if (isset($item['plus']['rlink'])) { $item['rlink'] = $item['plus']['rlink']; unset($item['plus']['rlink']); } $sub[] = $item; } if (empty($sub)) { continue; } func::sortByPriority($sub); $group['subtabs'] = $sub; $result[] = $group; } func::sortByPriority($result); foreach ($result as &$v) { $first = reset($v['subtabs']); $v['url'] = $first['url']; $v['class'] = $first['class']; if (empty($activeUrl)) { $activeUrl = $v['url']; } if (count($v['subtabs']) == 1 && empty($first['rlink'])) { $v['subtabs'] = []; } } unset($v); if ($opts['default']) { $result = reset($result); } return $result; } /** * Применение модификаторов для изменения пунктов * @return void */ protected function applyModify() { if (empty($this->modify)) { return; } foreach ($this->modify as $modify) { if (empty($modify['class'])) { continue; } if (empty($modify['event'])) { continue; } if (! isset($modify['callable']) || ! is_callable($modify['callable'])) { continue; } $index = false; foreach ($this->items as $key => $item) { if ($item['class'] !== $modify['class']) { continue; } if ($item['event'] !== $modify['event']) { continue; } $index = $key; } if ($index === false) { # поиск среди алиасов foreach ($this->items as $key => $item) { if (empty($item['aliases'])) { continue; } foreach ($item['aliases'] as $alias) { if ($alias['class'] !== $modify['class']) { continue; } if ($alias['event'] !== $modify['event']) { continue; } $index = $key; } } } if ($index !== false && isset($this->items[$index])) { $this->current = $index; call_user_func($modify['callable'], $this, [ 'id' => $index, 'item' => & $this->items[$index] ]); } } } /** * Рассчет приоритета групп * @return void */ protected function checkPriorityGroups() { # добавим группы с неуказанным приоритетом в конец $max = 0; $isPositions = false; foreach ($this->groups as $group) { if (! empty($group['position'])) { $isPositions = true; } if ($group['priority'] === false) { continue; } if ($max < $group['priority']) { $max = $group['priority']; } } foreach ($this->groups as &$group) { if ($group['priority'] !== false) { continue; } $max += 10; $group['priority'] = $max; } unset($group); if (! $isPositions) { return; } # перенумеровка приоритета на уникальный $renum = function () { $priority = []; foreach ($this->groups as $key => $group) { $priority[$key] = $group['priority']; } asort($priority, SORT_NUMERIC); $counter = 10; foreach ($priority as $key => $p) { if (! isset($this->groups[$key])) { continue; } $this->groups[$key]['priority'] = $counter; $counter += 10; } }; $renum(); # изменим значения в зависимости от beforeGroup и afterGroup foreach ($this->groups as &$group) { if (empty($group['position'])) { continue; } if (empty($group['position']['title'])) { continue; } $title = $group['position']['title']; foreach ($this->groups as $group2) { if ($group2['title'] !== $title) { continue; } if ($group['position']['position'] === 'before') { $group['priority'] = $group2['priority'] - 1; } elseif ($group['position']['position'] === 'after') { $group['priority'] = $group2['priority'] + 1; } } } unset($group); $renum(); } /** * Рассчет приоритета пунктов * @return void */ protected function checkPriorityItems() { $get = function ($search, &$items) { if (empty($search['class'])) { return false; } if (empty($search['event'])) { return false; } foreach ($items as $key => $item) { if ( $item['class'] === $search['class'] && $item['event'] === $search['event'] && ($item['header'] ?? false) === ($search['header'] ?? false) ) { return $key; } } foreach ($items as $key => $item) { if (empty($item['aliases'])) { continue; } foreach ($item['aliases'] as $alias) { if ($alias['class'] === $search['class'] && $alias['event'] === $search['event']) { return $key; } } if ($item['class'] === $search['class'] && $item['event'] === $search['event']) { return $key; } } return false; }; # перенумеровка приоритета на уникальный $renum = function () { $priority = []; foreach ($this->items as $key => $item) { $priority[$key] = $item['priority']; } asort($priority, SORT_NUMERIC); $counter = 10; foreach ($priority as $key => $p) { if (! isset($this->items[$key])) { continue; } $this->items[$key]['priority'] = $counter; $counter += 10; } }; # для пунктов у которых задан target (after(), before()) установим поля group и header такими же как и у target $isPositions = false; foreach ($this->items as &$item) { if (empty($item['position'])) { continue; } $t = false; # найдем ближайший существующий 'position' (может быть несколько, extension after 'unknown' extension) foreach ($item['position'] as $pp) { $t = $get($pp, $this->items); if ($t === false) { continue; } if (! isset($this->items[$t])) { continue; } break; } if (! isset($this->items[$t])) { continue; } if ($pp) { # переопределим 'position' на найденный, для последующего изменения приоритета $item['position'] = $pp; } $target = $this->items[$t]; $isPositions = true; foreach (['group', 'header', 'priority'] as $f) { $item[$f] = $target[$f]; } } unset($item); # добавим пункты с неуказанным приоритетом в конец $max = 0; foreach ($this->items as $item) { if ($item['priority'] === false) { continue; } if ($max < $item['priority']) { $max = $item['priority']; } } foreach ($this->items as &$item) { if ($item['priority'] !== false) { continue; } $max += 10; $item['priority'] = $max; } unset($item); if (! $isPositions) { return; } $renum(); # изменим значения в зависимости от before и after foreach ($this->items as &$item) { if (empty($item['position'])) { continue; } $t = $get($item['position'], $this->items); if ($t === false) { continue; } if (! isset($this->items[$t])) { continue; } $target = $this->items[$t]; if ($item['position']['position'] == 'before') { $item['priority'] = $target['priority'] - 1; } elseif ($item['position']['position'] == 'after') { $item['priority'] = $target['priority'] + 1; } } unset($item); $renum(); } /** * Добавить основной пункт меню (группу подпунктов) * @param string $title заголовок * @param string|bool $icon иконка * @param int|bool $priority приоритет * @param array $opts ['bottom' - расположить внизу страницы] * @return static */ public function group(string $title, $icon = false, $priority = false, array $opts = []) { foreach ($this->groups as $key => $group) { # Group with $title already exists => update group data if ($group['title'] === $title) { $this->currentGroup = $group['id']; if ($icon) { $this->groups[$key]['icon'] = $icon; } if ($priority) { $this->groups[$key]['priority'] = $priority; } if (! empty($opts)) { $this->groups[$key] = $this->defaults($opts, $this->groups[$key]); } return $this; } } $id = sizeof($this->groups) + 1; $this->groups[$id] = $this->defaults($opts, [ 'id' => $id, 'title' => $title, 'priority' => $priority, 'icon' => $icon, 'style' => false, 'bottom' => false, 'header' => false, 'position' => [], # ->beforeGroup() ->afterGroup() ]); $this->currentGroup = $id; return $this; } /** * Разместить группу перед группой с заголовком $title * @param string $title заголовок * @return static */ public function beforeGroup(string $title) { return $this->_positionGroup($title, 'before'); } /** * Разместить группу после группы с заголовком $title * @param string $title заголовок * @return static */ public function afterGroup(string $title) { return $this->_positionGroup($title, 'after'); } /** * Изменить/установить позицию для группы * @param string $title * @param string $position * @return static */ protected function _positionGroup(string $title, string $position) { if (isset($this->groups[$this->currentGroup]) && in_array($position, ['before', 'after'])) { $this->groups[$this->currentGroup]['position'] = [ 'title' => $title, 'position' => $position, ]; } return $this; } /** * Добавить пункт меню в меню в заголовке * @param string $title * @param string $module * @param string $method * @param array $opts * @return static */ public function header(string $title, string $module, string $method, array $opts = []) { $opts['header'] = true; return $this->add($title, $module, $method, false, $opts); } /** * Добавить пункт меню в текущую группу * @param string $title * @param string $module * @param string $method * @param int|bool $priority приоритет * @param array $opts * @return static */ public function add(string $title, string $module, string $method, $priority = false, array $opts = []) { $module = mb_strtolower($module); $method = mb_strtolower($method); $currentGroup = $this->currentGroup; if (! empty($opts['header'])) { if ( ! isset($this->groups[$this->currentGroup]) || $this->groups[$this->currentGroup]['header'] !== $opts['header'] ) { $currentGroup = false; foreach ($this->groups as $key => $group) { if ($group['header'] && $group['title'] == '__default__') { $currentGroup = $key; break; } } if (! $currentGroup) { $t = $this->currentGroup; $this->group('__default__', false, false, ['header' => true]); $currentGroup = $this->currentGroup; $this->currentGroup = $t; } } } foreach ($this->items as $item) { if ( $item['group'] === $currentGroup && $item['class'] === $module && $item['event'] === $method && $item['title'] === $title ) { $this->current = $item['id']; return $this; } } $id = sizeof($this->items) + 1; $this->items[$id] = $this->defaults($opts, [ 'id' => $id, 'group' => $currentGroup, 'title' => $title, 'class' => $module, 'event' => $method, 'priority' => $priority, # ->priority() 'position' => [], # ->before() ->after() 'eventParams' => [], # ->params() 'url' => '', # ->url() 'counter' => [], # ->counter() 'visible' => true, # ->visible() 'right' => [], # ->plus() 'style' => false, # ->plus() 'access' => false, # ->access() 'icon' => false, # ->icon() 'aliases' => [], # ->alias() 'header' => false, ]); $this->current = $id; return $this; } /** * Выбрать пункт меню в текущей группе * @param string $title * @param array $opts * @throws Exception * @return static */ public function item(string $title, array $opts = []) { $currentGroup = (empty($opts['header']) ? $this->currentGroup : false); foreach ($this->items as $item) { if ( $item['group'] === $currentGroup && $item['title'] === $title ) { $this->current = $item['id']; return $this; } } throw new Exception('Menu item not found'); } /** * Установить/вернуть значение свойства для текущего пункта меню * @param string $name имя свойства * @param mixed $value значение, если null - вернуть значение свойства для текущего пункта меню * @return static|mixed */ protected function _prop($name, $value = null) { if (isset($this->items[$this->current])) { if (is_null($value)) { return $this->items[$this->current][$name] ?? null; } else { $this->items[$this->current][$name] = $value; } } return $this; } /** * Установить приоритет для текущего пункта меню * @param int|null $priority, если null - вернуть приоритет для текущего пункта меню * @return static|int */ public function priority($priority = null) { return $this->_prop('priority', $priority); } /** * Установить параметры метода для текущего пункта меню * @param array|null $params, если null - вернуть параметры события для текущего пункта меню * @return static|array */ public function params($params = null) { return $this->_prop('eventParams', $params); } /** * Установить параметр метода для текущего пункта меню * @param string $key название параметра * @param mixed|null $value значение параметра, если null - вернуть значение параметра метода для текущего пункта меню * @return static|mixed */ public function param($key, $value = null) { if (! empty($key) && isset($this->items[$this->current])) { if (is_null($value)) { if (isset($this->items[$this->current]['eventParams'][$key])) { return $this->items[$this->current]['eventParams'][$key]; } } $this->items[$this->current]['eventParams'][$key] = $value; } return $this; } /** * Установить url для текущего пункта меню * @param string|null $url, если null - вернуть url для текущего пункта меню * @return static|string */ public function url($url = null) { return $this->_prop('url', $url); } /** * Установить icon для текущего пункта меню * @param string|null $icon, если null - вернуть icon для текущего пункта меню * @return static|string */ public function icon($icon = null) { return $this->_prop('icon', $icon); } /** * Установить style для текущего пункта меню * @param string|null $style, если null - вернуть style для текущего пункта меню * @return static|string */ public function style($style = null) { return $this->_prop('style', $style); } /** * Установить счетчик для текущего пункта меню * @param mixed|null $counter, если null - вернуть счетчик для текущего пункта меню * @param array $opts доп параметры * @return static|mixed */ public function counter($counter = null, array $opts = []) { if (! is_null($counter)) { $opts['counter'] = $counter; return $this->_prop('counter', $counter); } return $this->_prop('counter'); } /** * Установить видимость для текущего пункта меню * @param bool|null $visible, если null - вернуть видимость для текущего пункта меню * @return static|bool */ public function visible($visible = null) { return $this->_prop('visible', $visible); } /** * Отметить пункт меню как скрытый * @return static|bool */ public function hidden() { return $this->visible(false); } /** * Установить параметры кнопки plus для текущего пункта меню * @param mixed|null $plus, если null - вернуть параметры кнопки plus для текущего пункта меню * @param array $opts доп. параметры * @return static|mixed */ public function plus($plus = null, array $opts = []) { if (! is_null($plus)) { $opts['right'] = $plus; return $this->_prop('right', $plus); } return $this->_prop('right'); } /** * Установить доступ для текущего пункта меню * @param string|array|null $module, если null - вернуть доступ для текущего пункта меню * @param string|null $scope * @return static|string|array */ public function access($module = null, ?string $scope = null) { if (is_null($module)) { $access = null; } else { # string => 'module' # array => ['module', 'scope']; # Closure => function () { return bool; } # bool(true) => menu item module & method $access = (is_string($module) ? [$module, $scope] : $module); } return $this->_prop('access', $access); } /** * Установить alias для пункта меню * Один пункт меню может содержать несколько сочетаний модуль/метод * @param string $module название модуля * @param string $method название метода * @return static|string */ public function alias(string $module, string $method) { if (isset($this->items[$this->current])) { $this->items[$this->current]['aliases'][] = [ 'class' => mb_strtolower($module), 'event' => mb_strtolower($method), ]; } return $this; } /** * Разместить пункт перед пунктом ($module, $method) в той-же группе * @param string $module название модуля * @param string $method название метода * @return static */ public function before(string $module, string $method) { return $this->_position($module, $method, 'before'); } /** * Разместить пункт после пункта ($module, $method) в той-же группе * @param string $module название модуля * @param string $method название метода * @return static */ public function after(string $module, string $method) { return $this->_position($module, $method, 'after'); } /** * Изменить/установить позицию для пункта меню * @param string $module * @param string $method * @param string $position * @return static */ protected function _position(string $module, string $method, string $position) { if (isset($this->items[$this->current]) && in_array($position, ['before', 'after'])) { $this->items[$this->current]['position'][] = [ 'class' => mb_strtolower($module), 'event' => mb_strtolower($method), 'position' => $position, ]; } return $this; } /** * Изменить параметры пункта ($module, $method) $this->current будет установлен на нужном элементе * @param string $module название модуля * @param string $method название метода * @param callable $callable функция для изменения параметров (Menu $menu, array ['id' => индекс пункта, 'item' => @ref указатель за пункт в массиве пунктов]) * @return static */ public function modify(string $module, string $method, callable $callable) { do { if (empty($module)) { break; } if (empty($method)) { break; } $this->modify[] = [ 'class' => mb_strtolower($module), 'event' => mb_strtolower($method), 'callable' => $callable, ]; } while (false); return $this; } /** * Сформировать url для пункта меню * @param int|null $id ID пункта, если null - для текущего пункта меню * @return string */ protected function makeUrl($id = null) { if (is_null($id)) { $id = $this->current; } if (! isset($this->items[$id])) { return ''; } $item = & $this->items[$id]; if (! empty($item['url'])) { return $item['url']; } if (! empty($item['class']) && ! empty($item['event'])) { $params = ''; if (! empty($item['eventParams'])) { if (is_array($item['eventParams'])) { $params = Url::query($item['eventParams'], [], '&'); } elseif (is_string($item['eventParams'])) { $params = '&' . $item['eventParams']; } } return tplAdmin::adminLink($item['event'], $item['class']) . $params; } return ''; } /** * Получить значение счетчика для пункта меню * @param int|null $id ID пункта, если null - для текущего пункта меню * @return string */ protected function makeCounter($id = null) { if (is_null($id)) { $id = $this->current; } if (! isset($this->items[$id])) { return 0; } $item = & $this->items[$id]; if (empty($item['counter'])) { return 0; } return tpl::counterValue($item['counter']); } /** * Получить параметры кнопки плюс для пункта меню * @param int|null $id ID пункта, если null - для текущего пункта меню * @return mixed */ protected function makePlus($id = null) { if (is_null($id)) { $id = $this->current; } if (! isset($this->items[$id])) { return ''; } $item = & $this->items[$id]; if (empty($item['right'])) { return ''; } if (isset($item['right']['rlink'])) { return $item['right']; } $right = $item['right']; if (! empty($right['event']) || ! empty($right['params'])) { $url = tplAdmin::adminLink( ( ! empty($right['event']) ? $right['event'] : $item['event']), $item['class'] ); if (! empty($right['params'])) { $url .= Url::query($right['params'], [], '&'); } return ['rlink' => ['url' => $url]]; } if (isset($right['callable'])) { $result = ''; Block::obCallable($result, $counter, function ($callable) { return call_user_func($callable); }); return $result; } return $right; } /** * Reset state */ public function reset() { $this->current = null; $this->currentGroup = null; } }