imagecms/ImageCMS

View on GitHub
application/modules/menu/menu.php

Summary

Maintainability
F
6 days
Test Coverage
<?php

if (!defined('BASEPATH')) {
    exit('No direct script access allowed');
}

/**
 * Image CMS
 *
 * Menu module
 */
class Menu extends MY_Controller
{

    /*     * ***** Это можно редактировать *********** */
    private $tpl_folder_prefix = 'level_';

    /**
     *
     * @var array
     */
    private $tpl_file_names = [
                               'container'           => 'container',
                               'item_default'        => 'item_default',
                               'item_default_active' => 'item_default_active',
                               'item_first'          => 'item_first',
                               'item_first_active'   => 'item_first_active',
                               'item_last'           => 'item_last',
                               'item_last_active'    => 'item_last_active',
                               'item_one'            => 'item_one',
                               'item_one_active'     => 'item_one_active',
                              ];

    /*     * ***** То что ниже редактируйте осторожно, вдумчиво :) *********** */
    private $current_uri = '';

    private $tpl_folder = '';

    private $stack = [];

    private $errors = [];

    public $menu_array = []; // the root of the menu tree

    public $sub_menu_array = []; // the list of menu items

    public $select_hidden = FALSE;

    public $arranged_menu_array = [];

    public $activate_by_sub_urls = TRUE;

    private $expand = []; // items id to expand

    private $cache_key = '';

    public $menu_template_vars = [];

    private $cur_level = 0;

    public static $IS_ADMIN_PART = FALSE;

    public function __construct() {
        parent::__construct();
        $this->load->module('core');
        $this->cache_key = 'menu_data_';
        $this->cache_key = $this->cache_key . $this->dx_auth->get_role_id();
        $lang = new MY_Lang();
        $lang->load('menu');
        $this->setAdminPart();
        $this->load->helper('string');
    }

    public function index() {
        redirect();
    }

    public function autoload() {
        $this->load->helper('menu');

        $this->prepare_current_uri();
        //$this->current_uri = site_url( $this->uri->uri_string() );
    }

    /**
     * Prepare and set $this->current_uri value
     *
     * @access private
     */
    private function prepare_current_uri() {
        $lang_id = $this->config->item('cur_lang');
        $this->db->select('identif');
        $query = $this->db->get_where('languages', ['id' => $lang_id])->result();

        if ($query) {
            if ($this->uri->segment(1) == $query[0]->identif) {
                $segment_array = $this->uri->segment_array();
                array_shift($segment_array); // убираем первый элемент ( идентификатор языка ) массива сегментов uri
                $this->current_uri = site_url($segment_array);
            } else {
                $this->current_uri = site_url($this->uri->uri_string());
            }
        } else {
            // что-то не в порядке с таблицей languages или функцией $this->config->item
            return false;
        }
        return true;
    }

    /**
     * Prepare and display menu
     *
     * @param string $menu menu name
     * @access public
     */
    public function render($menu) {
        $this->clear();

        $this->prepare_current_uri(); // правильно определяет текущий uri, фикс бага многоязычности

        $this->prepare_menu_array($menu);

        $array_keys = array_keys($this->menu_array);
        $start_index = $array_keys[0];
        $this->tpl_folder = $this->menu_array[$start_index]['tpl'];

        $this->prepare_menu_recursion(); // Инициализируем первый элемент для начала итераций

        $this->display_menu($this->menu_array);

        if ($this->errors) {
            $data = [
                     'menu'       => $menu,
                     'errors'     => array_unique($this->errors),
                     'tpl_folder' => $this->tpl_folder,
                    ];
            $this->display_tpl('error', $data);
        } else {
            echo $this->arranged_menu_array[-1]['html'];
        }
    }

    private function prepare_menu_recursion() {
        array_push($this->stack, -1);
        $this->arranged_menu_array[-1]['html'] = FALSE;
        $this->arranged_menu_array[-1]['level'] = -1;
    }

    private function clear() {
        $this->current_uri = '';
        $this->tpl_folder = '';
        $this->menu_array = [];
        $this->errors = [];
        $this->sub_menu_array = [];
        $this->select_hidden = FALSE;
        $this->activate_by_sub_urls = TRUE;
        $this->expand = [];
        $this->arranged_menu_array = [];
        $this->stack = [];
        $this->menu_template_vars = [];
        $this->cur_level = 0;
    }

    /**
     * Recursive function to display menu ul list
     * TODO: Rewrite this part of code to display valid html list
     *
     * @param array $menu_array
     * @access public
     */
    public function display_menu($menu_array) {

        $array_keys = array_keys($menu_array);
        $start_index = $array_keys[0];
        $end_index = $array_keys[count($array_keys) - 1];

        foreach ($menu_array as $item) {
            if (!$item['hidden']) {

                $arranged_items_count = count($this->arranged_menu_array);
                $this->arranged_menu_array[$arranged_items_count]['level'] = $this->cur_level;
                $this->arranged_menu_array[$arranged_items_count]['tpl'] = $item['tpl'];

                // Translate title
                if (isset($item['lang_title_' . $this->config->item('cur_lang')])) {
                    $item['title'] = $item['lang_title_' . $this->config->item('cur_lang')];
                }

                if ($item['item_type'] != 'url') {
                    $site_url = site_url($item['link']);
                } else {
                    $site_url = $item['link'];
                }

                if ($this->activate_by_sub_urls === TRUE) {
                    $exp = explode('/', trim_slashes($this->uri->uri_string()));
                    $exp2 = explode('/', trim_slashes($item['link']));

                    $matches = 0;
                    foreach ($exp2 as $k => $v) {
                        if ($v == $exp[$k]) {
                            $matches++;
                        }
                    }

                    if ($matches == count($exp2)) {
                        $active_cur = TRUE;
                    } else {
                        $active_cur = FALSE;
                    }
                }

                if ($this->cur_level < ($item['expand_level'])) {
                    $this->expand[$item['id']] = TRUE; // to expand tree
                }
                if ($site_url == $this->current_uri OR $active_cur === TRUE) {
                    $this->expand[$item['id']] = TRUE;
                    $is_active = TRUE;
                    $this->arranged_menu_array[$arranged_items_count]['is_active'] = TRUE;
                } else {
                    // Make link active if link is / and no segments in url
                    // if ($item['link'] == '/' AND $this->uri->total_segments() == 0) {
                    if ($item['link'] == '/' AND get_main_lang('identif') == $this->uri->segment(1) AND $this->uri->total_segments() == 1 or $item['link'] == '/' AND get_main_lang('identif') != $this->uri->segment(1) AND $this->uri->total_segments() == 0 or $item['item_type'] == 'url' AND $item['link'] != '/' AND strstr($this->input->server('REQUEST_URI'), $item['link'])) {
                        $is_active = TRUE;
                        $this->arranged_menu_array[$arranged_items_count]['is_active'] = TRUE;
                    } else {
                        $is_active = FALSE;
                        $this->arranged_menu_array[$arranged_items_count]['is_active'] = FALSE;
                    }
                }

                //$item['item_type'] == 'url' ? $href = $item['link'] : $href = site_url($item['link']);
                //echo $item['item_type'];
                if ($item['item_type'] == 'url' && strstr($item['link'], 'http://') or $item['item_type'] == 'url' && strstr($item['link'], 'www')) {
                    $href = $item['link'];
                } else {
                    $href = rtrim(site_url($item['link']), '/');
                }

                $this->arranged_menu_array[$arranged_items_count]['link'] = $href;
                $this->arranged_menu_array[$arranged_items_count]['id'] = $item['id'];
                $this->arranged_menu_array[$arranged_items_count]['title'] = $item['title'];
                $this->arranged_menu_array[$arranged_items_count]['image'] = $item['image'];
                if (!is_array($item['add_data'])) {
                    $item['add_data'] = unserialize($item['add_data']);
                    $item['add_data']['newpage'] == '1' ? $this->arranged_menu_array[$arranged_items_count]['target'] = 'target="_blank"' : $this->arranged_menu_array[$arranged_items_count]['target'] = 'target="_self"';
                } else {
                    $item['add_data']['newpage'] == '1' ? $this->arranged_menu_array[$arranged_items_count]['target'] = 'target="_blank"' : $this->arranged_menu_array[$arranged_items_count]['target'] = 'target="_self"';
                }

                if (($menu_array[$start_index]['position'] != $item['position']) AND ($menu_array[$end_index]['position'] != $item['position'])) {
                    $this->arranged_menu_array[$arranged_items_count]['edge'] = 'default';
                }

                if (($menu_array[$start_index]['position'] == $item['position']) AND ($menu_array[$end_index]['position'] != $item['position'])) {
                    $this->arranged_menu_array[$arranged_items_count]['edge'] = 'first';
                }

                if (($menu_array[$start_index]['position'] != $item['position']) AND ($menu_array[$end_index]['position'] == $item['position'])) {
                    $this->arranged_menu_array[$arranged_items_count]['edge'] = 'last';
                }

                if (($menu_array[$start_index]['position'] == $item['position']) AND ($menu_array[$end_index]['position'] == $item['position'])) {
                    $this->arranged_menu_array[$arranged_items_count]['edge'] = 'one';
                }

                $sub_menus = $this->_get_sub_menus($item['id']);

                $this->arranged_menu_array[$arranged_items_count]['has_childs'] = count($sub_menus) > 0 ? 1 : 0;

                if (isset($this->expand[$item['id']]) AND $this->expand[$item['id']] == TRUE AND count($sub_menus) > 0) {
                    $this->cur_level++;
                    array_push($this->stack, $arranged_items_count);

                    $this->display_menu($sub_menus);
                } else {
                    $this->_prepare_item_tpl($arranged_items_count);
                }
            }
        }

        $wrapper = '';
        $stack_item = array_pop($this->stack);
        for ($i = $stack_item + 1; $i <= $arranged_items_count; $i++) {
            if ($this->arranged_menu_array[$i]['level'] <= $this->arranged_menu_array[$stack_item]['level'] + 1) {
                $wrapper .= $this->arranged_menu_array[$i]['html'] . "\n";
            }
        }

        $this->_prepare_item_tpl($stack_item, $wrapper);

        $this->cur_level--;
    }

    /**
     * Натягивает шаблон на данные и запихивает всю эту красоту в this->arranged_menu_array[$arranged_items_count]['html']. версия для элемента списка
     *
     * @param integer $index номер элемента для натягивания шаблона
     * @param string $wrapper натянутые шаблоны на всех всех наследников
     * @access private
     * @return string|false
     */
    private function _prepare_container_tpl($index = 0, $wrapper = FALSE) {
        $data = ['wrapper' => $wrapper];

        $tpl_path = $this->_get_real_tpl($index, 'container');
        if ($tpl_path) {
            return $this->fetch_tpl($tpl_path, $data);
        } else {
            return FALSE;
        }
    }

    /**
     * Натягивает данные на шаблон и запихивает всю эту красоту в this->arranged_menu_array[$arranged_items_count]['html']. версия для элемента списка
     *
     * @param integer $index номер элемента для натягивания шаблона
     * @param string $wrapper натянутые шаблоны на всех всех наследников
     * @access private
     * @return TRUE
     */
    private function _prepare_item_tpl($index = 0, $wrapper = FALSE) {
        if ($wrapper == TRUE) {
            $wrapper = $this->_prepare_container_tpl($index, $wrapper);
        }

        $is_active_hard = $this->arranged_menu_array[$index]['link'] == $this->current_uri ? 1 : 0;

        $data = [
                 'id'             => $this->arranged_menu_array[$index]['id'],
                 'title'          => $this->arranged_menu_array[$index]['title'],
                 'link'           => $this->arranged_menu_array[$index]['link'],
                 'image'          => $this->arranged_menu_array[$index]['image'],
                 'wrapper'        => $wrapper,
                 'target'         => $this->arranged_menu_array[$index]['target'],
                 'has_childs'     => $this->arranged_menu_array[$index]['has_childs'],
                 'is_active_hard' => $is_active_hard,
                ];

        if ($index == -1) {
            $this->arranged_menu_array[$index]['html'] = $wrapper;
        } else {
            $tpl_path = $this->_get_real_tpl($index);
            if ($tpl_path) {
                $this->arranged_menu_array[$index]['html'] = $this->fetch_tpl($tpl_path, $data);
            }
        }

        return TRUE;
    }

    /**
     * Find sub menus
     *
     * @param integer $id
     * @access public
     * @return mixed
     */
    private function _get_real_tpl($index = 0, $mode = 'item') {
        if ($mode == 'item') {
            $is_active = $this->arranged_menu_array[$index]['is_active'];
            $edge = $this->arranged_menu_array[$index]['edge'];

            switch ($edge) {
                case 'first':
                    $item_active_tpl = $this->tpl_file_names['item_first_active'];
                    $item_tpl = $this->tpl_file_names['item_first'];
                    break;
                case 'last':
                    $item_active_tpl = $this->tpl_file_names['item_last_active'];
                    $item_tpl = $this->tpl_file_names['item_last'];
                    break;
                case 'one':
                    $item_active_tpl = $this->tpl_file_names['item_one_active'];
                    $item_tpl = $this->tpl_file_names['item_one'];
                    break;
                default:
                    // ничего вроде не надо...
                    break;
            }

            /*             * *** дефолтный шаблон ***** */
            $default_item_active_tpl = $this->tpl_file_names['item_default_active'];
            $default_item_tpl = $this->tpl_file_names['item_default'];
            $is_good = FALSE;

            for ($level = $this->arranged_menu_array[$index]['level']; $level >= 0; $level--) {
                if ($is_active) {
                    if ($item_active_tpl) {
                        $tpl = $this->tpl_folder_prefix . $level . '/' . $this->tpl_file_names[$item_active_tpl];
                        if ($this->test_tpl($tpl)) {
                            $is_good = TRUE;
                            break;
                        }
                    }
                    if ($item_tpl) {
                        $tpl = $this->tpl_folder_prefix . $level . '/' . $this->tpl_file_names[$item_tpl];
                        if ($this->test_tpl($tpl)) {
                            $is_good = TRUE;
                            break;
                        }
                    }

                    $tpl = $this->tpl_folder_prefix . $level . '/' . $this->tpl_file_names[$default_item_active_tpl];
                    if ($this->test_tpl($tpl)) {
                        $is_good = TRUE;
                        break;
                    }

                    $tpl = $this->tpl_folder_prefix . $level . '/' . $this->tpl_file_names[$default_item_tpl];
                    if ($this->test_tpl($tpl)) {
                        $is_good = TRUE;
                        break;
                    }
                } else {
                    if ($item_tpl) {
                        $tpl = $this->tpl_folder_prefix . $level . '/' . $this->tpl_file_names[$item_tpl];
                        if ($this->test_tpl($tpl)) {
                            $is_good = TRUE;
                            break;
                        }
                    }
                    $tpl = $this->tpl_folder_prefix . $level . '/' . $this->tpl_file_names[$default_item_tpl];
                    if ($this->test_tpl($tpl)) {
                        $is_good = TRUE;
                        break;
                    }
                }
            }
        } else {
            for ($level = $this->arranged_menu_array[$index]['level'] + 1; $level >= 0; $level--) {
                $tpl = $this->tpl_folder_prefix . $level . '/' . $this->tpl_file_names['container'];
                if ($this->test_tpl($tpl)) {
                    $is_good = TRUE;
                    break;
                }
            }
        }

        if ($is_good) {
            return $tpl;
        } else {
            //передача управления сюда означает что один из необходимых файлов шаблона меню не найден в указанной папке

            if ($this->tpl_folder) {
                $err_data = ['user_template' => $this->tpl_folder . '/' . $tpl . '.tpl'];
            } else {
                $err_data = ['system_template' => './application/' . getModContDirName('menu') . '/menu/templates/public/' . $tpl . '.tpl'];
            }

            $this->errors[] = $err_data;
            return FALSE;
        }
    }

    /**
     * Find sub menus
     *
     * @param integer $id
     * @access public
     * @return array|false
     */
    public function _get_sub_menus($id) {
        $sub_menus = [];

        foreach ($this->sub_menu_array as $sub_menu) {
            if ($sub_menu['parent_id'] == $id) {
                array_push($sub_menus, $sub_menu);

                if (site_url($item['link']) == $this->current_uri) {
                    $this->expand[$item['id']] == TRUE;
                }
            }
        }

        if (count($sub_menus > 0)) {
            return $sub_menus;
        } else {
            return FALSE;
        }
    }

    /**
     * Select menu data from DB and separate menus is two arrays: root and submenus.
     *
     * @param string $menu - menu name
     * @access public
     */
    public function prepare_menu_array($menu) {
        $menu_cache_key = $this->cache_key . $menu . self::getCurrentLocale();
        if (($menu_data = $this->cache->fetch($menu_cache_key, 'menus')) !== FALSE) {
            $this->menu_array = $menu_data['menu_array'];
            $this->sub_menu_array = $menu_data['sub_menu_array'];
        } else {
            $this->db->select('menus.name', FALSE);
            $this->db->select('menus.tpl AS tpl', FALSE);
            $this->db->select('menus.expand_level AS expand_level', FALSE);
            //$this->db->select('menus.main_title', FALSE);
            $this->db->select('menus_data.id AS id', FALSE);
            $this->db->select('menus_data.menu_id AS menu_id', FALSE);
            $this->db->select('menus_data.item_type AS item_type', FALSE);
            $this->db->select('menus_data.item_id AS item_id', FALSE);
            $this->db->select('menus_data.title AS title', FALSE);
            $this->db->select('menus_data.hidden AS hidden', FALSE);
            $this->db->select('menus_data.position AS position', FALSE);
            $this->db->select('menus_data.roles AS roles', FALSE);
            $this->db->select('menus_data.parent_id AS parent_id', FALSE);
            $this->db->select('menus_data.description AS description', FALSE);
            $this->db->select('menus_data.add_data AS add_data', FALSE);
            $this->db->select('menus_data.item_image AS image', FALSE);

            $this->db->join('menus_data', 'menus_data.menu_id = menus.id');
            $this->db->where('menus.name', $menu);
            $this->db->where('menus_data.hidden', 0);
            $this->db->order_by('position', 'asc');

            // Select hidden items
            if ($this->select_hidden == TRUE) {
                $this->db->or_where('menus_data.hidden', 1);
                $this->db->where('menus.name', $menu);
            }

            $menus = $this->db->get('menus');

            if ($menus->num_rows() == 0) {
                //echo 'Ошибка загрузки меню '.$menu;
                return FALSE;
            } else {
                $menus = $menus->result_array();
            }

            // detect roles
            $cnt = count($menus);
            for ($i = 0; $i < $cnt; $i++) {
                if ($menus[$i]['roles'] != FALSE) {
                    $access = $this->_check_roles(unserialize($menus[$i]['roles']));
                    if ($access == FALSE) {
                        unset($menus[$i]);
                    }
                }
            }

            $this->cur_menu_id = $menus[0]['menu_id'];
            $this->load->model('menu_model', 'item');

            $menus = array_values($menus);

            $langs = $this->db->get('languages');

            if ($langs->num_rows() > 0) {
                $langs = $langs->result_array();
            } else {
                $langs = FALSE;
            }

            // Create links
            $cnt = count($menus);
            for ($i = 0; $i < $cnt; $i++) {
                switch ($menus[$i]['item_type']) {
                    case 'page':

                        $url = $this->item->get_page_url($menus[$i]['item_id']);
                        $menus[$i]['link'] = $url['cat_url'] . '/' . $url['url'];
                        break;

                    case 'category':
                        $category = $this->lib_category->get_category($menus[$i]['item_id']);
                        $menus[$i]['link'] = $category['path_url'];
                        break;

                    case 'module':
                        $mod_info = unserialize($menus[$i]['add_data']);
                        $mod_url = $this->item->get_module_link($mod_info['mod_name']);

                        if ($menus[$i]['add_data'] != NULL) {
                            $method = $mod_info['method'];

                            if (substr($method, 0, 1) == '/') {
                                $url = $mod_url . $method;
                            } else {
                                $url = $mod_url . '/' . $method;
                            }
                        }

                        $menus[$i]['link'] = $url;
                        break;

                    case 'url':
                        $menus[$i]['add_data'] = unserialize($menus[$i]['add_data']);
                        $menus[$i]['link'] = $menus[$i]['add_data']['url'];
                        break;
                }

                if ($langs != FALSE) {
                    foreach ($langs as $lang) {

                        if (self::getCurrentLocale($lang)) {
                            $this->db->where('item_id', $menus[$i]['id']);
                            $this->db->where('lang_id', $lang['id']);
                            $t_query = $this->db->get('menu_translate');

                            if ($t_query->num_rows() == 1) {
                                $n_title = $t_query->row_array();
                                if ($n_title['title']) {
                                    $menus[$i]['title'] = $n_title['title'];
                                }
                            }
                        }
                    }
                }

                if ($menus[$i]['parent_id'] == 0) {
                    $this->menu_array[$menus[$i]['id']] = $menus[$i];
                } else {
                    $this->sub_menu_array[$menus[$i]['id']] = $menus[$i];
                }
            }

            $data = [
                     'menu_array'     => $this->menu_array,
                     'sub_menu_array' => $this->sub_menu_array,
                    ];

            $this->cache->store($menu_cache_key, $data, FALSE, 'menus');
        }
    }

    /**
     * Get current locale for menu module
     * @param $language
     * @return string|boolean
     */
    public static function getCurrentLocale($language = NULL) {
        if ($language == null) {
            return self::$IS_ADMIN_PART ? MY_Controller::defaultLocale() : MY_Controller::getCurrentLocale();
        }

        if (self::$IS_ADMIN_PART && $language['identif'] == MY_Controller::defaultLocale()) {
            return TRUE;
        }

        if (!self::$IS_ADMIN_PART && $language['identif'] == MY_Controller::getCurrentLocale()) {
            return TRUE;
        }

        return FALSE;
    }

    private function setAdminPart() {
        return $this->uri->segment(1) === 'admin' ? TRUE : FALSE;
    }

    /**
     *
     * @param array $roles
     * @return boolean
     */
    private function _check_roles($roles = []) {
        $access = FALSE;

        foreach ($roles as $role_id) {
            $my_role = $this->dx_auth->get_role_id();

            // admin
            if ($this->dx_auth->is_admin() === TRUE) {
                $access = TRUE;
            }

            // all users
            if ($role_id == 0) {
                $access = TRUE;
            }

            if ($role_id == $my_role) {
                $access = TRUE;
            }
        }

        return $access;
    }

    public function get_all_menus() {
        $this->db->select('name, main_title');
        $query = $this->db->get('menus');

        if ($query->num_rows() > 0) {
            return $query->result_array();
        } else {
            return FALSE;
        }
    }

    /**
     * Display template file
     */
    private function display_tpl($file = '', $data = []) {
        if (count($data) > 0) {
            $this->template->add_array($data);
        }

        $file = realpath(__DIR__) . '/templates/public/' . $file . '.tpl';

        $this->template->display('file:' . $file);
    }

    /**
     * Fetch template file
     */
    private function fetch_tpl($file = '', $data = []) {
        if (count($data) > 0) {
            $this->template->add_array($data);
        }

        if ($this->tpl_folder) {
            $file = $this->template->template_dir . $this->tpl_folder . '/' . $file . '.tpl';
        } else {
            $file = realpath(__DIR__) . '/templates/public/' . $file . '.tpl';
        }

        return $this->template->fetch('file:' . $file);
    }

    private function test_tpl($file = '') {
        if ($this->tpl_folder) {
            $file = $this->template->template_dir . $this->tpl_folder . '/' . $file . '.tpl';
        } else {
            $file = realpath(__DIR__) . '/templates/public/' . $file . '.tpl';
        }

        if (file_exists($file)) {
            return TRUE;
        } else {
            return FALSE;
        }
    }

    public function _install() {
        $this->db->where('name', 'menu');
        $this->db->update('components', ['autoload' => 1]);
    }

}