plugins/sys/classes/yf_core_menu.class.php
<?php
/**
* Menu API methods.
*
* @author YFix Team <yfix.dev@gmail.com>
* @version 1.0
*/
class yf_core_menu
{
/** @conf_skip */
public $USE_DYNAMIC_ATTS = true;
/**
* Catch missing method call.
* @param mixed $name
* @param mixed $args
*/
public function __call($name, $args)
{
return main()->extend_call($this, $name, $args);
}
/**
* Show menu (alias for the '_show_menu').
* @param mixed $params
*/
public function show($params)
{
return $this->_show_menu($params);
}
/**
* Show menu (alias for the '_show_menu').
* @param mixed $params
*/
public function show_menu($params)
{
return $this->_show_menu($params);
}
/**
* Show menu
* $_item_types = array(
* 1 => 'Internal link',
* 2 => 'External link',
* 3 => 'Spacer',
* 4 => 'Divider',
* );.
* @param mixed $params
*/
public function _show_menu($params = [])
{
$return_array = isset($params['return_array']) ? $params['return_array'] : false;
$force_stpl_name = isset($params['force_stpl_name']) ? $params['force_stpl_name'] : false;
$menu_name = $params['name'];
if (empty($menu_name)) {
trigger_error(__CLASS__ . ': Given empty menu name to display', E_USER_WARNING);
return false;
}
$cur_menu_info = $this->_get_cur_menu_info($menu_name);
if ( ! $cur_menu_info['active']) {
return false;
}
_class('core_events')->fire('core.before_menu', [
'name' => $menu_name,
'info' => $cur_menu_info,
'params' => $params,
]);
if ($force_stpl_name) {
$cur_menu_info['stpl_name'] = $force_stpl_name;
}
$stpl_item = ! empty($cur_menu_info['stpl_name']) ? $cur_menu_info['stpl_name'] . '_item' : 'system/menu_item';
$stpl_main = ! empty($cur_menu_info['stpl_name']) ? $cur_menu_info['stpl_name'] : 'system/menu_main';
$stpl_pad = ! empty($cur_menu_info['stpl_name']) ? $cur_menu_info['stpl_name'] . '_pad' : 'system/menu_pad';
$level_pad_text = tpl()->parse($stpl_pad);
$menu_items = $this->_get_menu_items($cur_menu_info);
if (empty($menu_items)) {
return false;
}
$num_menu_items = count((array) $menu_items);
$_prev_level = 0;
$_next_level = 0;
$item_counter = 0;
$in_output_cache = main()->_IN_OUTPUT_CACHE;
$ICONS_DIR = _class('graphics')->ICONS_PATH;
$MEDIA_PATH = _class('graphics')->MEDIA_PATH;
$item_links = [];
$cur_page_id = null;
foreach ((array) $menu_items as $i => $item) {
$is_cur_page = false;
$item_link = '';
if (substr($item['location'], 0, 3) == './?') {
$item['location'] = substr($item['location'], 3);
}
// Internal link
if ($item['type_id'] == 1 && strlen($item['location']) > 0) {
$is_cur_page = $this->_is_current_page($item);
$item_link = './?' . $item['location'];
} elseif ($item['type_id'] == 2) {
$item_link = $item['location'];
}
$id = $item['id'];
if ($is_cur_page) {
$cur_page_id = $id;
}
$item_links[$id] = $item_link;
}
foreach ((array) $menu_items as $i => $item) {
$id = $item['id'];
$item_counter++;
$_next_info = isset($menu_items[$i + 1]) ? $menu_items[$i + 1] : [];
$_next_level = isset($_next_info['level']) ? (int) $_next_info['level'] : 0;
$icon = trim($item['icon']);
$icon_path = '';
$icon_class = '';
if ($icon) {
// Icon class from bootstrap icon class names
if (preg_match('/^icon\-[a-z0-9_-]+$/i', $icon) || (strpos($icon, '.') === false)) {
$icon_class = $icon;
} else {
$_icon_fs_path = PROJECT_PATH . $ICONS_DIR . $icon;
if (file_exists($_icon_fs_path)) {
$icon_path = $MEDIA_PATH . $ICONS_DIR . $icon;
}
}
}
$items[$item['id']] = [
'item_id' => (int) ($item['id']),
'parent_id' => (int) ($item['parent_id']),
'bg_class' => ! ($item_counter % 2) ? 'bg1' : 'bg2',
'link' => $item_links[$id],
'name' => _prepare_html(t($item['name'])),
'level_pad' => str_repeat($level_pad_text, $item['level']),
'level_num' => (int) ($item['level']),
'prev_level' => (int) $_prev_level,
'next_level' => (int) $_next_level,
'type_id' => $item['type_id'],
'icon_path' => $icon_path,
'icon_class' => $icon_class,
'is_first_item' => (int) ($item_counter == 1),
'is_last_item' => (int) ($item_counter == $num_menu_items),
'is_cur_page' => (int) ($id === $cur_page_id),
'have_children' => (int) ((bool) $item['have_children']),
'next_level_diff' => (int) (abs($item['level'] - $_next_level)),
];
// Save current level for the next iteration
$_prev_level = $item['level'];
}
_class('core_events')->fire('core.after_menu', [
'name' => $menu_name,
'items' => $items,
'params' => $params,
]);
if ($return_array) {
return $items;
}
foreach ((array) $items as $id => $item) {
$items[$id] = tpl()->parse($stpl_item, $item);
}
return tpl()->parse($stpl_main, [
'items' => implode(PHP_EOL, (array) $items),
]);
}
/**
* @param mixed $cur_menu_info
*/
public function _get_menu_items($cur_menu_info)
{
$menu_id = $cur_menu_info['id'];
$menu_name = $cur_menu_info['name'];
if ( ! isset($this->_menu_items)) {
$this->_menu_items = main()->get_data('menu_items');
}
// Do not show menu if there is no items in it
if (empty($this->_menu_items[$menu_id])) {
return false;
}
// Check if we need to call special menu handler
$special_class_name = '';
$special_method_name = '';
if (false !== strpos($cur_menu_info['method_name'], '.')) {
list($special_class_name, $special_method_name) = explode('.', $cur_menu_info['method_name']);
}
$special_params = [
'menu_name' => $menu_name,
'menu_id' => $menu_id,
];
$menu_items = [];
if ( ! empty($special_class_name) && ! empty($special_method_name)) {
$menu_items = _class($special_class_name, $special_path)->$special_method_name($special_params);
} else {
$menu_items = $this->_recursive_get_menu_items($menu_id);
}
// Support for custom fields, that will be available as menu_items array keys
$menu_items = $this->_apply_custom_fields($cur_menu_info, $menu_items);
$menu_items = $this->_cleanup_menu_items($menu_items);
return $menu_items;
}
/**
* @param mixed $menu_name
*/
public function _get_cur_menu_info($menu_name)
{
if ( ! isset($this->_menus_infos)) {
$this->_menus_infos = main()->get_data('menus');
}
if (empty($this->_menus_infos)) {
if ( ! $this->_error_no_menus_raised) {
trigger_error(__CLASS__ . ': Menus info not loaded', E_USER_WARNING);
$this->_error_no_menus_raised = true;
}
return false;
}
$menu_id = 0;
foreach ((array) $this->_menus_infos as $menu_info) {
if ($menu_info['type'] != MAIN_TYPE) {
continue;
}
if ($menu_info['name'] == $menu_name) {
$menu_id = $menu_info['id'];
break;
}
}
if ( ! $menu_id) {
trigger_error(__CLASS__ . ': Menu name "' . _prepare_html($menu_name) . '" not found in menus list', E_USER_WARNING);
return false;
}
return $this->_menus_infos[$menu_id];
}
/**
* @param mixed $cur_menu_info
* @param mixed $menu_items
*/
public function _apply_custom_fields($cur_menu_info, $menu_items)
{
$custom_fields = [];
if ($cur_menu_info['custom_fields']) {
foreach (explode(',', str_replace(';', ',', trim($cur_menu_info['custom_fields']))) as $f) {
$f = trim($f);
if ($f) {
$custom_fields[$f] = $f;
}
}
}
if ($this->USE_DYNAMIC_ATTS && $custom_fields) {
foreach ((array) $menu_items as $item_id => $item_info) {
$custom_attrs = [];
if ( ! strlen($item_info['other_info'])) {
continue;
}
$custom_attrs = (array) _attrs_string2array($item_info['other_info']);
foreach ((array) $custom_fields as $f) {
$menu_items[$item_id][$f] = (string) ($custom_attrs[$f]);
}
}
}
return $menu_items;
}
/**
* @param mixed $menu_items
*/
public function _cleanup_menu_items($menu_items = [])
{
$center_block_id = _class('graphics')->_get_center_block_id();
$out = [];
foreach ((array) $menu_items as $item_id => $item) {
if (empty($item)) {
continue;
}
// Check PHP conditional code for display
if ( ! empty($item['cond_code'])) {
$cond_result = (bool) eval('return (' . $item['cond_code'] . ');');
if ( ! $cond_result) {
continue;
}
}
if (substr($item['location'], 0, 3) == './?') {
$item['location'] = substr($item['location'], 3);
}
// Internal link
if ($item['type_id'] == 1 && strlen($item['location']) > 0) {
parse_str($item['location'], $_item_parts);
if (_class('graphics')->MENU_HIDE_INACTIVE_MODULES) {
if ( ! isset($this->_active_modules)) {
$cl_name = MAIN_TYPE_USER ? 'user_modules' : 'admin_modules';
$this->_active_modules = _class($cl_name, 'admin_modules/')->_get_modules();
}
if ($_item_parts['object'] && ! isset($this->_active_modules[$_item_parts['object']])) {
continue;
}
if ($center_block_id && ! _class('graphics')->_check_block_rights($center_block_id, $_item_parts['object'], $_item_parts['action']) && $_item_parts['task'] != 'logout') {
continue;
}
}
}
$out[] = $item;
}
// Check for empty blocks starts with spacers
if (_class('graphics')->MENU_HIDE_INACTIVE_MODULES) {
foreach ((array) $out as $i => $item) {
if ($item['level_num'] == 0 && $item['type_id'] == 3) {
$next_item = $out[$i + 1];
if ( ! $next_item || ($next_item['level_num'] == 0 && $next_item['type_id'] == 3)) {
unset($out[$i]);
}
}
}
}
return $out;
}
/**
* @param mixed $item
*/
public function _is_current_page($item)
{
$object = $_GET['object'];
$action = $_GET['action'];
$id = $_GET['id'];
$is_cur_page = false;
parse_str($item['location'], $u);
// Check if we are on the current page
if (isset($u['object']) && $u['object'] && $u['object'] == $object) {
if (isset($u['action']) && $u['action']) {
if ($u['action'] == $action) {
// Needed for static pages
if ($u['id']) {
if ($u['id'] == $id) {
$is_cur_page = true;
}
} else {
$is_cur_page = true;
}
}
} else {
$is_cur_page = true;
}
}
return $is_cur_page;
}
/**
* Template for the custom class method for menu block (useful to inherit).
* @param mixed $params
*/
public function _custom_menu_items($params = [])
{
// Example what passes by params
$params = [
'menu_name' => $menu_name,
'menu_id' => $menu_id,
];
return false;
}
/**
* Get menu items ordered array (recursively).
* @param mixed $menu_id
* @param mixed $skip_item_id
*/
public function _recursive_get_menu_items($menu_id = 0, $skip_item_id = 0)
{
if (empty($menu_id) || empty($this->_menu_items[$menu_id])) {
return false;
}
if (MAIN_TYPE_ADMIN) {
$CUR_USER_GROUP = (int) main()->ADMIN_GROUP;
} elseif (MAIN_TYPE_USER) {
$CUR_USER_GROUP = (int) main()->USER_GROUP;
if (empty($CUR_USER_GROUP)) {
$CUR_USER_GROUP = 1;
}
}
$CUR_SITE = (int) conf('SITE_ID');
$CUR_SERVER = (int) conf('SERVER_ID');
$items_ids = [];
$items_array = [];
foreach ((array) $this->_menu_items[$menu_id] as $item) {
if ($skip_item_id == $item['id']) {
continue;
}
if ( ! empty($item['user_groups'])) {
$user_groups = [];
foreach (explode(',', $item['user_groups']) as $v) {
if ( ! empty($v)) {
$user_groups[$v] = $v;
}
}
if ( ! empty($user_groups) && ! isset($user_groups[$CUR_USER_GROUP])) {
continue;
}
}
if ( ! empty($item['site_ids'])) {
$site_ids = [];
foreach (explode(',', $item['site_ids']) as $v) {
if ( ! empty($v)) {
$site_ids[$v] = $v;
}
}
if ( ! empty($site_ids) && ! isset($site_ids[$CUR_SITE])) {
continue;
}
}
if ( ! empty($item['server_ids'])) {
$server_ids = [];
foreach (explode(',', $item['server_ids']) as $v) {
if ( ! empty($v)) {
$server_ids[$v] = $v;
}
}
if ( ! empty($server_ids) && ! isset($server_ids[$CUR_SERVER])) {
continue;
}
}
$items_array[$item['id']] = $item;
}
return $this->_recursive_sort_items($items_array, $skip_item_id);
}
/**
* Get and sort items ordered array (recursively).
* @param mixed $items
* @param mixed $skip_item_id
*/
public function _recursive_sort_items($items = [], $skip_item_id = 0)
{
$children = [];
foreach ((array) $items as $id => $info) {
$parent_id = $info['parent_id'];
if ($skip_item_id == $id) {
continue;
}
$children[$parent_id][$id] = $id;
}
$ids = $this->_count_levels(0, $children);
$new_items = [];
foreach ((array) $ids as $id => $level) {
$new_items[$id] = $items[$id] + ['level' => $level];
}
return $new_items;
}
/**
* @param mixed $start_id
* @param mixed $level
*/
public function _count_levels($start_id = 0, &$children, $level = 0)
{
$ids = [];
foreach ((array) $children[$start_id] as $id => $_tmp) {
$ids[$id] = $level;
if (isset($children[$id])) {
foreach ((array) $this->_count_levels($id, $children, $level + 1) as $_id => $_level) {
$ids[$_id] = $_level;
}
}
}
return $ids;
}
}