Хочу поделиться способом решения одной задачи для сборки 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