Банк данных ПРОДУКЦИЯ РОССИИ

Система меню с управляемым доступом

Озниев Н.К. ктн

Дата публикации: 08.01.2018 г.

Введение

Система меню Банка данных ПРОДУКЦИЯ РОССИИ (БДПР) содержит в себе механизм управления доступом к конкретным ее элементам, т.е. не все пользователи видят меню в полном объеме его пунктов, а только те, к которым они имеют доступ. Для регулирования доступа используются в том числе и разрешения системы разграничения доступа.

Сами элементы меню портала образуют дерево, которое хранится в специальной таблице базы данных. При инициализации главной страницы портала сначала обрабатываются пункты главного меню, которое для БДПР представлено горизонтальным набором его пунктов. Отображением этого меню занимается отдельная функция главного контроллера системы. Отображаются все пункты меню, за исключением одного – Регистрация КЛП. Этот пункт меню отображается только в том случае, если у пользователя в системе есть привязанный код ЦСМ, тем самым доступ к процессам регистрации КЛП закрыт для любого пользователя, кто не является сотрудником ЦСМ. Ясно, что если не отображается пункт главного меню, то все входящие в него подпункты автоматически становятся недоступными.

Кроме того у главного контроллера есть еще одна функция, которая регулирует доступ к меню администрирования – Admin. Этот пункт главного меню отображается только тем пользователям, которые имеют роль администратора системы.

На этом процесс формирования главного меню завершается.

В тех же процедурах, которые управляют главным меню, производится проверка пользователя на тот факт, что у него есть хотя бы одно из разрешений системы. Если таковое найдется – ему позволяется входить в окно редактирования своего профиля. Любой гость, посетивший страницу БДПР prodrf.gostinfo.ru, может зарегистрироваться на этом портале, определив для себя пару логин - пароль. После этого он может с этими данными войти в систему. Но по умолчанию зарегистрированный пользователь никакой роли не получает. Роли ему назначает администратор по отдельному запросу, например, по электронной почте или телефону. Когда он получит запрошенную роль, он уже получает возможность доступа к специальной ссылке Профиль, которая откроет веб-страницу с его персональными данными, и он их может редактировать.

Все остальные пункты меню являются подчиненными пунктам главного меню. Они и образуют указанное выше дерево меню.

Дерево меню

Дерево меню хранится в таблице БД menu_items, список полей и их назначение для которой имеют вид:

№№

Имя поля

Комментарии

1

idПервичный

Идентификатор

2

top_id

Идентификатор главного меню

3

parent_id

Идентификатор родительского меню

4

haschild

Наличие дочерних меню

5

menuindex

Индекс в списке равных

6

name

Наименование (для скриптов)

7

title

Заголовок (для пользователя)

8

tag

HTML-тэг меню

9

comment

Комментарий

10

role_id

Идентификатор роли (не используется)

11

page_url

URL веб-страницы, которую открывает данный пункт меню

Из этой таблицы видно, что фактически любой пункт дерева меню – это вход на какую-либо веб-страницу. Для отдельных функций внутри веб-страницы система имеет всплывающее меню, которое описывается в отдельной статье данного цикла.

Все элементы дерева меню отображаются в специально отведенном месте на портале. Для БДПР это – левая вертикальная боковая панель:

Здесь Реест ПРОДУКЦИЯ, Нормативные акты, ГОСТы, Классификаторы, ЦСМы – элементы меню из дерева меню системы.

Все элементы дерева меню особых пояснений не требуют, кроме двух – Индекс в списке равных и HTML-тэг меню.

Индекс в списке равных имеет простой смысл, который легко можно пояснить из приведенного рисунка. Все приведенные здесь пункты меню являются дочерними для пункта главного меню Информация. Все они равные между собой и порядок их следования задается индексом в списке равных. Если по какой-либо причине окажется, что попались одинаковые значения индексов, то порядок расположения таких пунктов будет регулироваться их взаимным расположением в таблице menu_items.

HTML-тег меню имеет немного более сложный смысл, который требует понимания структуры HTML-страницы, на которой дерево меню отображается, и служит для формирования вложенных меню. Если мы обратимся к содержанию таблицы menu_items для пункта меню Классификаторы, то увидим, что в поле tag находится значение <ul> - тег ненумерованного списка, а в поле haschild найдем значение 1, т.е. у этого пункта меню есть дочерние, и сам пункт меню открывает ненумерованный список HTML-элементов. Далее, если мы сделаем выборку из таблицы menu_items всех элементов, у которых parent_id = 6, т.е. идентификатору пункта Классификаторы, то увидим список,

id

menuindex

name

title

tag

page_url

7

1

okpd

ОКПД 2

 

/okpd

8

2

okp

ОКП

 

/okp

10

3

oks

ОКС

 

/oks

9

4

kgs

КГС

</ul>

/kgs

из которого следует, что рассмотренный пункт меню Классификаторы имеет 4 дочерних пункта меню, и последний из них, с menuindex = 4, имеет HTML-тег меню, равный </u>, т.е. он закрывает список дочерних пунктов меню. Таким образом построены вложенные меню. В данном случае вложенность на одну ступень. Но их может быть и много, если внутри списка найдется пункт меню с дочерними, у которого может открываться новый список.

Функции обработки меню

Функции обработки меню, как уже было выше сказано, находятся в модуле главного контроллера портала modules.php.

// Установка главного меню для заданного пользователя

00. public function setTopMenu($user) {

01. // Незарегистрированные пользователи и те, у кого нет заданных ролей

02. // не видят страницу для доступа к операциям с КЛП

03. if ($user->roles) {

04.   $this->template->set("link_clp", $this->url->clp());

05.   $csmCode = $user->getCSMCode();

06.   if ($csmCode == "") {

07.     $this->template->set("link_clp_title", "");

08.     $this->template->set("link_clp", "");

09.   } else

10.     $this->template->set("link_clp_title", "Регистрация КЛП");

11. } else {

12.   $this->template->set("link_clp", "");

13.   $this->template->set("link_clp_title", "");

14. }

15. }

В первом же комментарии внутри тела функции в строке 03 стоит условие

if ($user->roles)

Это значит, что при отсутствии ролей у пользователя данная функция не будет работать и не сформирует пункт главного меню.

В строке 04 в шаблон страницы передается ссылка на главную страницу, которая вызывается функцией clp() модуля url.php. Затем в строке 05 определяется код привязанного ЦСМ. Если пользователь не имеет привязанный код ЦСМ, то пункт меню Регистрация КЛП в шаблоне веб-страницы просто не отображается. Настройки для этого случая делаются в строках 07, 08. Аналогично настраивается шаблон и при отсутствии ролей у пользователя в строках 12,13.

Если у пользователя роль найдена (любая), то в строке 10 в шаблон веб-страницы передается заголовок пункта меню Регистрация КЛП. Если бы управляемых пунктов меню было больше, то такая же участь была бы для всех, у кого был бы код привязанного ЦСМ.

Как видим, в данном случае выполняется очень простая операция.

Обратим внимание на то, что для работы данной функции требуется параметр $user – ссылка на объект системы, описывающий пользователя. Если быть совершенно строгим, то в приведенной функции не хватает проверки существования ссылки $user, т.к. при вызове данной функции с нулевой ссылкой будет сбой в системе. Спасает то, что этого не происходит по той причине, что вызов данной функции без создания объекта пользователя просто невозможен по архитектуре системы.

// Определение прав пользователя и настройка формы авторизации

00. protected function setInfologin($topmenu_id) {

01.  $this->user->getRolesAndPermissions();

02.  $this->template->set("logged_user", $this->user->username);

    

     // Формирование бокового меню в левой панели веб-страницы

     // для заданного массива кодов разрешений пользователя

03.  $menu = $this->block_menu->getWebpageBlockMenu($topmenu_id,

      $this->user->permcodes, $this->user->username);

     // Передача меню на веб-страницу

04.  $this->template->set("top_menu", $menu);

 

05.  $msg = $this->message();

06.  $this->template->set("message", $msg);

 

07.  if (isset($_SESSION["logged_user"])) {

08.   $this->template->set("user_login", "");

09.   $this->template->set("user_logout", "Выход");

 

10.   if (isset($_SESSION["user_user_id"])) {

11.     $user_user_id = $_SESSION["user_user_id"];

        // Ссылку "Профиль" показывать только тем, кто имеет роль

13.     if ($this->user->hasAnyPermittion($user_user_id))

14.        $this->template->set("user_profile", "Профиль");

      }

     } else {

15.   $this->template->set("user_login", "Вход");

16.   $this->template->set("user_logout", "");

17.   $this->template->set("user_profile", "");

18.   $this->template->set("user_register", "Регистрация");

     }

 

     // Установка главного меню для заданного пользователя

19.  $this->setTopMenu($this->user) ;

 

20.  if (isset($_SESSION["logged_user"])) {

21.   if (isset($_SESSION["username"])) {

        // Админку показывать только администраторам

22.     if ($this->user->isAdmin()) {

23.        $this->template->set("link_admin", $this->url->admin());

24.        $this->template->set("link_admin_title", "Admin");

        } else {

25.        $this->template->set("link_admin", "");

26.        $this->template->set("link_admin_title", "");

        }

      }

     } else {

27.   $this->template->set("link_admin", "");

28.   $this->template->set("link_admin_title", "");

     }

}

В этом модуле вызывается функция формирования бокового меню getWebpageBlockMenu, которая расположена в модуле class.block_mehu.php. Приведем полный код данного модуля, в котором содержится описание специального класса Block_Menu, предназначенного для манипуляций с боковым меню системы портала.

  class Block_Menu extends GlobalClass {

    public $out_menu = Array();

   

    public function __construct() {

      parent::__construct("menu_items");

    }

   

    // Вывод меню для заданного $id пункта главного меню

    public function getMenuTree($top_id) {

      // Очистка выходного массива

      unset($out_menu);

     

      if (!$top_id)

        return false;

     

      // Запрос списка корневых пунктов бокового меню

      $query = "SELECT `id`,`top_id`,`parent_id`,`haschild`,".

"`menuindex`,`name`,`title`, ".

      "`tag`,`comment`,`role_id`,`page_url` " .

      "FROM `menu_items` WHERE `top_id` = " .

      $this->config->sym_query . " ORDER BY `menuindex`";

     

      $menus = $this->db->select($query, array($top_id));

      if (!$menus)

        return false;

     

      // Раскрутка подменю

      foreach($menus as $menu) {

        // Заносим пункт меню в выходной массив

        $this->out_menu[] = $menu;

       

        // Идем раскручивать, если будут подменю

        $rs = $this->getSubMenus($menu);

        if ($rs) {

           // Если они нашлись - заносим в выходной масссив

           foreach ($rs as $r)

             $this->out_menu[] = $r;

        };

      }

    }

       

    // Получить комплект подменю - рекурсия

    private function getSubMenus($menu) {

      if (!$menu)

        return false;

      $haschild = $menu["haschild"];

      if (!$haschild)

        return false;

      // Запрос списка подменю для меню $menu

      $query = "SELECT `id`,`top_id`,`parent_id`,".

"`haschild`,`menuindex`,`name`,`title`, " .

      "`tag`,`comment`,`role_id`,`page_url` " .

      "FROM `menu_items` WHERE `parent_id` = " . $menu["id"] .

" ORDER BY `menuindex`";

      $submenus = $this->db->select($query);

      if ($submenus)

        foreach ($submenus as $submenu) {

           // Заносим подменю в выходной список

           $this->out_menu[] = $submenu;

           // Обращаемся к рекурсии

           $this->getSubMenus($submenu);

        }

    }

   

    // Выводим меню на заданный объект

    public function getWebpageBlockMenu($top_id, $user_permcodes, $username) {

      // Получаем набор меню для $top_id

      $tree = $this->getMenuTree($top_id);

     

      // Рисуем

      $menu = "\n";

      foreach($this->out_menu as $item) {

        // Разрешения для данного пункта меню

        if (!$this->allowedMenuItem($item["id"], $user_permcodes))

           continue;

          

        // Меню уровня 0

        if ($item["tag"] == "<ul>") {

           // Функция openMenu просто открывает или закрывает

// пункт меню через стили,

           // находится в модуле rusprodfuncs.js

           $menu .= "   <li><a href='#' onclick='openMenu(this);return false;'><span>" . $item["title"] . "</span></a>\n";

           $menu .= "   <ul>\n";

        } else

           $menu .= "   <li><a href='" . $item["page_url"] .

 "'><span>" . $item["title"] . "</span></a></li>\n";

        if ($item["tag"] == "</ul>")

           $menu .= "   </ul></li>\n";

      }

      return $menu;

    }

   

    // Разрешения для данного пункта меню по отношению

// к набору кодов разрешения пользователя

    private function allowedMenuItem($item_id, $user_permcodes) {

      $menu_item_permcodes = Array();

      $menu_item_permcodes [] = $this->getMenuObjPermissions($item_id);

 

      if (!$menu_item_permcodes[0])

        return true;

     

      foreach ($menu_item_permcodes[0] as $menu_item_permcode) {

        if (in_array($menu_item_permcode, $user_permcodes))

           return true;

      }

      return false;

    }

   

    // Получить список разрешений для объекта меню

    private function getMenuObjPermissions($id) {

      if (!$id)

        return false;

      $query = "SELECT p.`perm_code` FROM `menu_items` m ".

      "INNER JOIN `menu_items_perm` mp ON mp.`menu_item_id` = m.`id` ".

      "INNER JOIN `permissions` p ON p.`id` = mp.`perm_id` ".

      "WHERE m.`id` = ".$this->config->sym_query;

      $res = $this->db->select($query, array($id));

      if (!$res)

        return false;

      return $res;

    }

  }