ack_menu.module in Access Control Kit 7
The ACK menu module.
File
ack_menu/ack_menu.moduleView source
<?php
/**
* @file
* The ACK menu module.
*/
/**
* Implements hook_access_info().
*/
function ack_menu_access_info() {
// Declare support for controlling access to menu links.
$info['menu_link'] = array(
'label' => t('Menu link'),
);
return $info;
}
/**
* Implements hook_access_handler_info().
*/
function ack_menu_access_handler_info() {
// To keep URLs sane on management pages, only allow integer-based schemes.
// Integers also make link-to-realm mapping much easier and more searchable.
$integer_schemes = array();
foreach (access_scheme_info() as $scheme_info) {
if ($scheme_info['data_type'] == 'integer') {
$integer_schemes[] = $scheme_info['type'];
}
}
$info = array();
// Handler to map menu links to realms.
$info['AckMenuMap'] = array(
'label' => t('Administratively assigned'),
'scheme types' => $integer_schemes,
'object types' => array(
'menu_link',
),
);
// @todo Support access to a menu item based on the node it links to?
return $info;
}
/**
* Implements hook_permission().
*/
function ack_menu_permission() {
$menus = implode(', ', _ack_menu_managed_menus());
$schemes = implode(', ', access_object_schemes('menu_link', TRUE));
return array(
'administer ack_menu' => array(
'title' => t('Administer menus for all schemes'),
'description' => t('Manage the links in the following access scheme-controlled menus: %menus.', array(
'%menus' => $menus,
)),
),
'ack manage menu links' => array(
'title' => t('Manage menu links in assigned access realms'),
'description' => t('This permission only applies to the following access schemes: %schemes.', array(
'%schemes' => $schemes,
)),
),
);
}
/**
* Implements hook_menu().
*/
function ack_menu_menu() {
$items['ack_menu'] = array(
'title' => 'Manage menu links',
'description' => 'Add, edit, and rearrange links for menu trees that you are permitted to manage.',
'page callback' => 'ack_menu_overview_page',
'access callback' => 'ack_menu_access',
'menu_name' => 'navigation',
'file' => 'ack_menu.pages.inc',
);
$items['ack_menu/list'] = array(
'title' => 'List manageable links',
'type' => MENU_DEFAULT_LOCAL_TASK,
'weight' => -10,
);
$items['ack_menu/manage/%access_scheme_machine_name/%'] = array(
'title' => 'Manage realm menu links',
'title callback' => 'ack_menu_overview_title',
'title arguments' => array(
2,
3,
),
'page callback' => 'drupal_get_form',
'page arguments' => array(
'ack_menu_overview_form',
2,
3,
),
'access callback' => 'ack_menu_realm_access',
'access arguments' => array(
2,
3,
),
'file' => 'ack_menu.pages.inc',
);
$items['ack_menu/manage/%access_scheme_machine_name/%/list'] = array(
'title' => 'List links',
'type' => MENU_DEFAULT_LOCAL_TASK,
'weight' => -10,
);
$items['ack_menu/manage/%access_scheme_machine_name/%/add'] = array(
'title' => 'Add realm menu link',
'title callback' => 'ack_menu_link_add_title',
'title arguments' => array(
2,
3,
),
'page callback' => 'ack_menu_link_add',
'page arguments' => array(
2,
3,
),
'access callback' => 'ack_menu_realm_access',
'access arguments' => array(
2,
3,
),
'type' => MENU_LOCAL_ACTION,
'file' => 'ack_menu.pages.inc',
);
return $items;
}
/**
* Implements hook_menu_alter().
*/
function ack_menu_menu_alter(&$items) {
// Override the menu admin overview page.
$items['admin/structure/menu']['page callback'] = 'ack_menu_admin_page';
$items['admin/structure/menu']['access callback'] = 'ack_menu_admin_access';
$items['admin/structure/menu']['file'] = 'ack_menu.pages.inc';
$items['admin/structure/menu']['file path'] = drupal_get_path('module', 'ack_menu');
unset($items['admin/structure/menu']['access arguments']);
// Override access to the menu admin list and add pages.
$paths = array(
'admin/structure/menu/manage/%menu',
'admin/structure/menu/manage/%menu/add',
);
foreach ($paths as $path) {
$items[$path]['access callback'] = 'ack_menu_menu_access';
$items[$path]['access arguments'] = array(
4,
);
}
// Override access to the menu link edit and delete operations.
foreach (array(
'edit',
'delete',
) as $op) {
$items['admin/structure/menu/item/%menu_link/' . $op]['access callback'] = 'ack_menu_link_access';
$items['admin/structure/menu/item/%menu_link/' . $op]['access arguments'] = array(
4,
);
}
}
/**
* Implements hook_admin_paths().
*/
function ack_menu_admin_paths() {
$paths = array(
'ack_menu' => TRUE,
'ack_menu/*' => TRUE,
);
return $paths;
}
/**
* Menu title callback for the "manage realm links" page.
*
* @param object $scheme
* An access scheme.
* @param int $realm
* A realm value.
*
* @return string
* The translated page title.
*/
function ack_menu_overview_title($scheme, $realm) {
return t('Manage @realm menu links', array(
'@realm' => $scheme->realms[$realm],
));
}
/**
* Menu title callback for the "add realm link" page.
*
* @param object $scheme
* An access scheme.
* @param int $realm
* A realm value.
*
* @return string
* The translated page title.
*/
function ack_menu_link_add_title($scheme, $realm) {
return t('Add @realm menu link', array(
'@realm' => $scheme->realms[$realm],
));
}
/**
* Access callback for the realm menu manager.
*
* @param object $account
* (optional) A user account. Defaults to the currently logged-in user.
*
* @return bool
* TRUE if the user account has access to the realm menu manager.
*/
function ack_menu_access($account = NULL) {
return user_access('ack manage menu links', $account) || user_access('administer ack_menu', $account) || user_access('administer menu', $account);
}
/**
* Access callback for the menu admin pages.
*
* @param object $account
* (optional) A user account. Defaults to the currently logged-in user.
*
* @return bool
* TRUE if the user account has access to the menu admin pages.
*/
function ack_menu_admin_access($account = NULL) {
return user_access('administer menu', $account) || user_access('administer ack_menu', $account);
}
/**
* Access callback for managing a menu.
*
* @param array $menu
* An array defining a menu.
* @param object $account
* (optional) A user account. Defaults to the currently logged-in user.
*
* @return bool
* TRUE if the user account has access to manage the menu.
*/
function ack_menu_menu_access($menu, $account = NULL) {
// Global menu administrators have access to all menus.
if (user_access('administer menu', $account)) {
return TRUE;
}
// Scheme menu administrators have access to menus managed by ACK.
if (user_access('administer ack_menu', $account)) {
$menu_name = $menu['menu_name'];
$managed = _ack_menu_managed_menus();
return isset($managed[$menu_name]);
}
return FALSE;
}
/**
* Access callback for managing realm links.
*
* @param object $scheme
* An access scheme.
* @param int $realm
* A realm value.
* @param object $account
* (optional) A user account. Defaults to the currently logged-in user.
*
* @return bool
* TRUE if the user account has access to manage links in the given realm.
*/
function ack_menu_realm_access($scheme, $realm, $account = NULL) {
// Validate the scheme.
if (isset($scheme->handlers['menu_link'])) {
// Validate the realm value.
if (is_numeric($realm) && isset($scheme->realms[$realm])) {
// Admin access trumps any realm-level access.
if (ack_menu_admin_access($account)) {
return TRUE;
}
// Check realm access and that manageable links exist.
if (ack_menu_access($account)) {
$allowed_realms = access_user_permission_realms('ack manage menu links', $account, array(
$scheme,
));
if (in_array($realm, $allowed_realms[$scheme->machine_name])) {
$links = ack_menu_realm_links($scheme, $realm);
return !empty($links);
}
}
}
}
return FALSE;
}
/**
* Access callback for editing or deleting a menu link.
*
* @param array $menu_link
* The menu link.
* @param object $account
* (optional) A user account. Defaults to the currently logged-in user.
*
* @return bool
* TRUE if the user account has access to edit or delete the link.
*/
function ack_menu_link_access($menu_link, $account = NULL) {
// Check for overall access to the link's menu.
$menu = menu_load($menu_link['menu_name']);
if (ack_menu_menu_access($menu, $account)) {
return TRUE;
}
// Check access grants.
return access_user_object_access('ack manage menu links', 'menu_link', $menu_link, $account);
}
/**
* Implements hook_form_FORM_ID_alter().
*
* Alters the menu item add/edit form.
*/
function ack_menu_form_menu_edit_item_alter(&$form, &$form_state, $form_id) {
$item = $form['original_item']['#value'];
_ack_menu_form_alter($form, $form_state, $form_id, $item);
$form['#validate'][] = 'ack_menu_form_menu_edit_item_validate';
$form['#submit'][] = 'ack_menu_form_menu_edit_item_submit';
if (isset($form['actions']['delete'])) {
$form['actions']['delete']['#submit'] = array(
'ack_menu_item_delete_submit',
);
}
}
/**
* Validation handler for the altered menu item form.
*
* @see ack_menu_form_menu_edit_item_alter()
*/
function ack_menu_form_menu_edit_item_validate($form, &$form_state) {
$item = $form_state['values'];
foreach ($form_state['ack_menu']['schemes'] as $scheme) {
$scheme->handlers['menu_link']
->objectFormValidate('menu_link', $item, $form, $form_state);
}
}
/**
* Submit handler for the altered menu item form.
*
* @see ack_menu_form_menu_edit_item_alter()
*/
function ack_menu_form_menu_edit_item_submit($form, &$form_state) {
$item = $form_state['values'];
foreach ($form_state['ack_menu']['schemes'] as $scheme) {
$scheme->handlers['menu_link']
->objectFormSubmit('menu_link', $item, $form, $form_state);
}
if (isset($form_state['ack_menu']['destination'])) {
$form_state['redirect'] = $form_state['ack_menu']['destination'];
}
}
/**
* Submit handler for the delete button on the altered menu item form.
*
* @see ack_menu_form_menu_edit_item_alter()
* @see menu_item_delete_submit()
*/
function ack_menu_item_delete_submit($form, &$form_state) {
$destination = array();
if (isset($_GET['destination'])) {
$destination = drupal_get_destination();
unset($_GET['destination']);
}
$form_state['redirect'] = array(
'admin/structure/menu/item/' . $form_state['values']['mlid'] . '/delete',
array(
'query' => $destination,
),
);
}
/**
* Implements hook_form_BASE_FORM_ID_alter().
*
* Alters the menu options on the node form.
*/
function ack_menu_form_node_form_alter(&$form, &$form_state, $form_id) {
if (!empty($form['menu'])) {
$link = $form['#node']->menu;
$menus = variable_get('menu_options_' . $form['#node']->type, array());
$access =& $form['menu']['#access'];
// If the link exists, see if the user is allowed to manage it.
if (!empty($link['mlid'])) {
$access = ack_menu_link_access($link);
}
elseif (ack_menu_admin_access()) {
while (!$access && ($menu_name = array_pop($menus))) {
$menu = menu_load($menu_name);
if (!empty($menu)) {
$access = ack_menu_menu_access($menu);
}
}
}
elseif (ack_menu_access()) {
$realms = _ack_menu_user_realms();
while (!$access && (list($scheme_machine_name, $scheme_realms) = each($realms))) {
$scheme = access_scheme_machine_name_load($scheme_machine_name);
while (!$access && ($realm = array_pop($scheme_realms))) {
$realm_menus = ack_menu_realm_menus($scheme, $realm);
$allowed = array_intersect($menus, $realm_menus);
$access = !empty($allowed);
}
}
}
// If we granted access above, filter the parent link options.
if ($access) {
_ack_menu_form_alter($form['menu']['link'], $form_state, $form_id, $link);
}
}
}
/**
* Helper function for altering the menu item and node forms.
*
* @param array &$form
* A form structure representing a menu item. Must have 'parent' and 'weight'
* element children.
* @param array &$form_state
* The form state. If 'ack_menu' is already defined in the array, it must
* contain 'schemes' and 'realms' values, defining (respectively) the schemes
* that can manage the link and the realms that the user can assign to it.
* @param string $form_id
* The form identifier.
* @param array $link
* The menu link represented by the form.
*
* @see ack_menu_form_menu_edit_item_alter()
* @see ack_menu_form_node_form_alter()
*/
function _ack_menu_form_alter(&$form, &$form_state, $form_id, $link) {
// Place ack_menu values in the form state for use by the handlers.
if (!isset($form_state['ack_menu'])) {
$form_state['ack_menu'] = array(
'schemes' => access_object_schemes('menu_link'),
'realms' => _ack_menu_user_realms(),
);
}
$form_state['ack_menu']['admin'] = user_access('administer ack_menu');
$form_state['ack_menu']['global admin'] = user_access('administer menu');
$schemes = $form_state['ack_menu']['schemes'];
$realms = $form_state['ack_menu']['realms'];
// Apply AckMenuHandlerInterface::objectFormAlter() for all schemes.
foreach ($schemes as $scheme) {
// A new link can be added to any allowed realm, and admins can change the
// realm assignments of existing links.
if (empty($link['mlid']) || $form_state['ack_menu']['admin']) {
$scheme_realms = isset($realms[$scheme->machine_name]) ? $realms[$scheme->machine_name] : array();
}
else {
// For non-admins, an existing link must stay in its current realm.
$scheme_realms = NULL;
}
$scheme->handlers['menu_link']
->objectFormAlter('menu_link', $link, $form, $form_state, $form_id, $scheme_realms);
}
// If this link's current parent is not manageable by the user, then the user
// should not be able to change this link's position, since that would
// effectively be rearranging the parent's menu tree.
if (!empty($link['mlid']) && !empty($link['plid'])) {
$parent = menu_link_load($link['plid']);
if (!empty($parent)) {
$form['weight']['#disabled'] = !ack_menu_link_access($parent);
}
}
}
/**
* Implements hook_menu_link_delete().
*/
function ack_menu_menu_link_delete($link) {
db_delete('ack_menu_map')
->condition('mlid', $link['mlid'])
->execute();
}
/**
* Implements hook_access_scheme_delete().
*/
function ack_menu_access_scheme_delete($scheme) {
db_delete('ack_menu_map')
->condition('scheme', $scheme->machine_name)
->execute();
}
/**
* Loads the menu links for a realm.
*
* @param object $scheme
* An access scheme.
* @param int $realm
* A realm value.
* @param string $menu_name
* (optional) Limit the returned links to the given menu. If omitted, links
* from all menus will be included. Defaults to NULL.
* @param bool $reset
* (optional) Whether to reset the internal cache. Defaults to FALSE.
*
* @return array
* The realm's menu links, sorted by menu and depth. Each value is an array
* defining a link, similar to what would be returned by menu_link_load().
*/
function ack_menu_realm_links($scheme, $realm, $menu_name = NULL, $reset = FALSE) {
$links =& drupal_static(__FUNCTION__, array());
if (!isset($links[$scheme->sid][$realm]) || $reset) {
$realm_links = array();
// Validate the scheme.
if (isset($scheme->handlers['menu_link'])) {
// Validate the realm value.
if (is_numeric($realm) && isset($scheme->realms[$realm])) {
$managed = $scheme->handlers['menu_link']
->managedMenus();
ksort($managed);
foreach (array_keys($managed) as $menu) {
$realm_links[$menu] = $scheme->handlers['menu_link']
->realmLinks($realm, $menu);
}
}
}
$links[$scheme->sid][$realm] = $realm_links;
}
$list = array();
if (isset($menu_name)) {
if (isset($links[$scheme->sid][$realm][$menu_name])) {
$list = $links[$scheme->sid][$realm][$menu_name];
}
}
else {
foreach ($links[$scheme->sid][$realm] as $menu_links) {
if (!empty($menu_links)) {
$list = array_merge($list, $menu_links);
}
}
}
return $list;
}
/**
* Returns a list of menus that contain links for a realm.
*
* @param object $scheme
* An access scheme.
* @param int $realm
* A realm value.
* @param bool $reset
* (optional) Whether to reset the internal cache. Defaults to FALSE.
*
* @return array()
* An array of menu names.
*/
function ack_menu_realm_menus($scheme, $realm, $reset = FALSE) {
$menus =& drupal_static(__FUNCTION__, array());
if (!isset($menus[$scheme->sid][$realm]) || $reset) {
$realm_menus = array();
if (isset($scheme->handlers['menu_link'])) {
// If we're resetting the cache, clear the realm links cache once here, so
// we don't do it repeatedly in the foreach statement below.
if ($reset) {
ack_menu_realm_links($scheme, $realm, NULL, TRUE);
}
// Find all managed menus that contain links.
$managed = $scheme->handlers['menu_link']
->managedMenus();
foreach (array_keys($managed) as $menu_name) {
$menu_links = ack_menu_realm_links($scheme, $realm, $menu_name);
if (!empty($menu_links)) {
$realm_menus[] = $menu_name;
}
}
}
$menus[$scheme->sid][$realm] = $realm_menus;
}
return $menus[$scheme->sid][$realm];
}
/**
* Helper function to find the list of menus that are managed by access schemes.
*
* @return array
* An array with the machine-readable names as the keys, and human-readable
* titles as the values.
*/
function _ack_menu_managed_menus() {
$managed = array();
foreach (access_object_schemes('menu_link') as $scheme) {
$managed += $scheme->handlers['menu_link']
->managedMenus();
}
return $managed;
}
/**
* Helper function to find the realms where the user is allowed to manage links.
*
* @return array
* An array indexed by scheme machine name where the values are arrays of
* realm values manageable by the currently logged-in user.
*/
function _ack_menu_user_realms() {
$schemes = access_object_schemes('menu_link');
$realms = array();
// Admins can access all realm menus.
if (ack_menu_admin_access()) {
foreach ($schemes as $scheme) {
$realms[$scheme->machine_name] = array_keys($scheme->realms);
}
}
else {
// Otherwise, find the realms where the user has been granted access.
foreach ($schemes as $scheme) {
foreach (array_keys($scheme->realms) as $realm) {
if (ack_menu_realm_access($scheme, $realm)) {
$realms[$scheme->machine_name][] = $realm;
}
}
}
}
return $realms;
}
Functions
Name | Description |
---|---|
ack_menu_access | Access callback for the realm menu manager. |
ack_menu_access_handler_info | Implements hook_access_handler_info(). |
ack_menu_access_info | Implements hook_access_info(). |
ack_menu_access_scheme_delete | Implements hook_access_scheme_delete(). |
ack_menu_admin_access | Access callback for the menu admin pages. |
ack_menu_admin_paths | Implements hook_admin_paths(). |
ack_menu_form_menu_edit_item_alter | Implements hook_form_FORM_ID_alter(). |
ack_menu_form_menu_edit_item_submit | Submit handler for the altered menu item form. |
ack_menu_form_menu_edit_item_validate | Validation handler for the altered menu item form. |
ack_menu_form_node_form_alter | Implements hook_form_BASE_FORM_ID_alter(). |
ack_menu_item_delete_submit | Submit handler for the delete button on the altered menu item form. |
ack_menu_link_access | Access callback for editing or deleting a menu link. |
ack_menu_link_add_title | Menu title callback for the "add realm link" page. |
ack_menu_menu | Implements hook_menu(). |
ack_menu_menu_access | Access callback for managing a menu. |
ack_menu_menu_alter | Implements hook_menu_alter(). |
ack_menu_menu_link_delete | Implements hook_menu_link_delete(). |
ack_menu_overview_title | Menu title callback for the "manage realm links" page. |
ack_menu_permission | Implements hook_permission(). |
ack_menu_realm_access | Access callback for managing realm links. |
ack_menu_realm_links | Loads the menu links for a realm. |
ack_menu_realm_menus | Returns a list of menus that contain links for a realm. |
_ack_menu_form_alter | Helper function for altering the menu item and node forms. |
_ack_menu_managed_menus | Helper function to find the list of menus that are managed by access schemes. |
_ack_menu_user_realms | Helper function to find the realms where the user is allowed to manage links. |