Переопределение прав доступа в Drupal 6

в 8:06, , рубрики: drupal, drupal 6, метки:

Хочу поделиться способом решения одной задачи для сборки Drupal Commons. По требованиям ТЗ нужно было ограничить доступ пользователей к нодам по определенным правилам:

  • Пользователи с определенной ролью могут видеть только определенный тип групп
  • Эти пользователи могут видеть только контент группы, где они состоят

Сразу я пытался реализовать это через hook_node_grants и hook_node_access_records, но пришел к выводу, что решение получается слишком громоздкое, глючное и замусоренное.

И пошел другим путем.

Вспомним про реестр меню. Массив с описанием элемента содержит в себе два параметра: access callback и access arguments. Попробуем переопределить их, но сохраним установленный access callback, как последний элемент в access arguments:

//Хук MYMODULE_menu_alter будет вызываться только при очистке кэша системы
function MYMODULE_menu_alter(&$items) {
  foreach ($items as $key => $item) {
    if (!isset($item['access arguments'])) {
      $items[$key]['access arguments'] = array();
    }

    if (isset($items[$key]['access callback'])) {
      //Сохраним стандартный callback, чтобы учитывать и его.
      $items[$key]['access arguments'][] = $items[$key]['access callback'];
    }
    else {
      //Если callback не установлен, но присутствуют аргументы, Drupal 6 
      //использует стандартный user_access()
      if (!empty($items[$key]['access arguments'])) {
        $items[$key]['access arguments'][] = 'user_access';
      }
      else {
        //Если параметры не установлены вообще - значит по-умолчанию доступ есть
        $items[$key]['access arguments'][] = TRUE;
      }
    }

    //Устанавливаем наш callback
    $items[$key]['access callback'] = 'MYMODULE_access_callback';
  }
}

Стоит отметить, что при установке модуля я выставляю его вес очень большим (т.е. мой модуль будет выполняться в последнюю очередь и никто больше не переопределит меню). Если у вас иная ситуация — лучше добавить свои параметры в массив, например:

$items[$key]['default access callback'] = $items[$key]['access callback'];

Но в этом случае, не будут учитываться более поздние альтеры. Т.е. все-таки лучше устанавливать вес модуля большим.

Теперь сам callback:

function MYMODULE_access_callback() {
  $args = func_get_args();

  //В этом случае здесь может быть только TRUE, FALSE или коллбэк без аргументов
  //Нас не интересует, оставляем как есть
  if (count($args) == 1) {
    if (is_bool($args[0])) {
      return $args[0];
    }

    return $args[0]();
  }

  //Вытащим callback по-умолчанию и его параметры
  $callback = $args[count($args) - 1];
  $callback_args = array_slice($args, 0, count($args) - 1);

  //Права доступа к нодам:
  if (is_object($args[1]) && isset($args[1]->nid)) {
    //Для начала проверим стандартные права
    //если доступа нет, то и наши проверять не имеет смысла
    if (!call_user_func_array($callback, $callback_args)) {
      return FALSE;
    }

    //Если 3-й аргумент так-же объект, то это аккаунт
    //т.к.    node_access($op, $node, $account = NULL)
    if (is_object($args[2])) {
      $account = $args[2];
    }
    else {
      global $user;
      $account = $user;
    }

    //Теперь проверяем права сами:
    if ($condition && some_rights()) {
      return TRUE;
    }
    else {
      return FALSE;
    }
  }

  //На всякий случай
  if (!function_exists($callback)) {
    $dump = print_r($callback_args, TRUE);
    $msg  = '
    <b>Access callback не существует!</b><br />
    Callback: "%callback"<br />
    Callback arguments:<hr>
    <pre>
    %args
    </pre>
    ';

    $vars = array(
      '%callback' => $callback, 
      '%args' => $callback_args
    );

    watchdog('php', $msg, $vars, WATCHDOG_ERROR);

    return TRUE;
  }

  //Просто используем стандартный коллбэк
  return call_user_func_array($callback, $callback_args);
}

Для меня это сработало и показалось достаточно удобным, возможно пригодится еще кому-либо.

Автор: ring


* - обязательные к заполнению поля


https://ajax.googleapis.com/ajax/libs/jquery/3.4.1/jquery.min.js