Same filename and directory in other branches
  1. 4.7.x modules/menu.module

Allows administrators to customize the site navigation menu.

File

modules/menu.module
View source
<?php

/**
 * @file
 * Allows administrators to customize the site navigation menu.
 */

/**
 * Implementation of hook_menu().
 */
function menu_menu($may_cache) {
  $items = array();
  if ($may_cache) {
    $items[] = array(
      'path' => 'admin/menu',
      'title' => t('menus'),
      'callback' => 'menu_overview',
      'access' => user_access('administer menu'),
    );
    $items[] = array(
      'path' => 'admin/menu/item/edit',
      'title' => t('edit menu item'),
      'callback' => 'menu_edit_item',
      'access' => user_access('administer menu'),
      'type' => MENU_CALLBACK,
    );
    $items[] = array(
      'path' => 'admin/menu/item/reset',
      'title' => t('reset menu item'),
      'callback' => 'menu_reset_item',
      'access' => user_access('administer menu'),
      'type' => MENU_CALLBACK,
    );
    $items[] = array(
      'path' => 'admin/menu/item/disable',
      'title' => t('disable menu item'),
      'callback' => 'menu_disable_item',
      'access' => user_access('administer menu'),
      'type' => MENU_CALLBACK,
    );
    $items[] = array(
      'path' => 'admin/menu/item/delete',
      'title' => t('delete menu item'),
      'callback' => 'menu_delete_item',
      'access' => user_access('administer menu'),
      'type' => MENU_CALLBACK,
    );
    $items[] = array(
      'path' => 'admin/menu/list',
      'title' => t('list'),
      'type' => MENU_DEFAULT_LOCAL_TASK,
      'weight' => -10,
    );
    $items[] = array(
      'path' => 'admin/menu/menu/add',
      'title' => t('add menu'),
      'callback' => 'menu_add_menu',
      'access' => user_access('administer menu'),
      'type' => MENU_LOCAL_TASK,
    );
    $items[] = array(
      'path' => 'admin/menu/item/add',
      'title' => t('add menu item'),
      'callback' => 'menu_edit_item',
      'access' => user_access('administer menu'),
      'type' => MENU_LOCAL_TASK,
    );
    $items[] = array(
      'path' => 'admin/menu/reset',
      'title' => t('reset menus'),
      'callback' => 'menu_reset',
      'access' => user_access('administer menu'),
      'type' => MENU_LOCAL_TASK,
    );
  }
  return $items;
}

/**
 * Implementation of hook_help().
 */
function menu_help($section) {
  switch ($section) {
    case 'admin/modules#description':
      return t('Allows administrators to customize the site navigation menu.');
    case 'admin/menu':
      return t('<p>Select an operation from the list to move, change, or delete a menu item.</p>');
    case 'admin/menu/menu/add':
      return t('<p>Enter the name for your new menu. Remember to enable the newly created block in the %blocks administration page.</p>', array(
        '%blocks' => l(t('blocks'), 'admin/block'),
      ));
    case 'admin/menu/item/add':
      return t('<p>Enter the title, path, position and the weight for your new menu item.</p>');
  }
}

/**
 * Implementation of hook_block().
 */
function menu_block($op = 'list', $delta = 0) {
  $menu = menu_get_menu();
  if ($op == 'list') {
    $blocks = array();
    foreach ($menu['items'][0]['children'] as $mid) {

      // Default "Navigation" block is handled by user.module.
      if ($mid != 1) {
        $blocks[$mid]['info'] = $menu['items'][$mid]['title'];
      }
    }
    return $blocks;
  }
  else {
    if ($op == 'view') {
      $data['subject'] = $menu['items'][$delta]['title'];
      $data['content'] = '<div class="menu">' . theme('menu_tree', $delta) . '</div>';
      return $data;
    }
  }
}

/**
 * Implementation of hook_perm().
 */
function menu_perm() {
  return array(
    'administer menu',
  );
}

/**
 * Menu callback; present the main menu management page.
 */
function menu_overview() {
  menu_rebuild();
  print theme('page', menu_overview_tree());
}

/**
 * Menu callback; clear the database, resetting the menu to factory defaults.
 */
function menu_reset() {
  $op = $_POST['op'];
  switch ($op) {
    case t('Reset all'):
      db_query('DELETE FROM {menu}');
      drupal_set_message(t('All menu items reset.'));
      drupal_goto('admin/menu');
      break;
    default:
      $output = theme('confirm', t('Are you sure you want to reset all menu items to their default settings?'), 'admin/menu', t('Any custom additions or changes to the menu will be lost.'), t('Reset all'));
      print theme('page', $output);
  }
}

/**
 * Menu callback; handle the adding of a new menu.
 */
function menu_add_menu() {
  $op = $_POST['op'];
  $edit = $_POST['edit'];
  $output = '';
  switch ($op) {
    case t('Submit'):
      menu_edit_item_validate($edit);
      if (!form_get_errors()) {
        menu_edit_item_save($edit);
        drupal_goto('admin/menu');
      }

    // Fall through.
    default:
      $edit['pid'] = 0;
      $edit['type'] = MENU_CUSTOM_MENU;
      $output .= menu_edit_item_form($edit);
  }
  print theme('page', $output);
}

/**
 * Menu callback; reset a single modified item.
 */
function menu_reset_item($mid) {
  $op = $_POST['op'];
  switch ($op) {
    case t('Reset'):
      db_query('DELETE FROM {menu} WHERE mid = %d', $mid);
      drupal_set_message(t('Menu item reset.'));
      drupal_goto('admin/menu');
      break;
    default:
      $title = db_result(db_query('SELECT title FROM {menu} WHERE mid = %d', $mid));
      $output = theme('confirm', t('Are you sure you want to reset the item %item to its default values?', array(
        '%item' => theme('placeholder', $title),
      )), 'admin/menu', t('Any customizations will be lost. This action cannot be undone.'), t('Reset'));
      print theme('page', $output);
  }
}

/**
 * Menu callback; delete a single custom item.
 */
function menu_delete_item($mid) {
  $op = $_POST['op'];
  $result = db_query('SELECT type, title FROM {menu} WHERE mid = %d', $mid);
  $menu = db_fetch_object($result);
  if (!$menu) {
    drupal_goto('admin/menu');
  }
  switch ($op) {
    case t('Delete'):
      db_query('DELETE FROM {menu} WHERE mid = %d', $mid);
      if ($menu->type & MENU_IS_ROOT) {
        drupal_set_message(t('Menu deleted.'));
      }
      else {
        drupal_set_message(t('Menu item deleted.'));
      }
      drupal_goto('admin/menu');
      break;
    default:
      if ($menu->type & MENU_IS_ROOT) {
        $message = t('Are you sure you want to delete the menu %item?', array(
          '%item' => theme('placeholder', $menu->title),
        ));
      }
      else {
        $message = t('Are you sure you want to delete the custom menu item %item?', array(
          '%item' => theme('placeholder', $menu->title),
        ));
      }
      $output = theme('confirm', $message, 'admin/menu', t('This action cannot be undone.'), t('Delete'));
      print theme('page', $output);
  }
}

/**
 * Menu callback; hide a menu item.
 */
function menu_disable_item($mid) {
  $op = $_POST['op'];
  $menu = menu_get_menu();
  switch ($op) {
    case t('Disable'):
      $type = $menu['items'][$mid]['type'];
      $type &= ~MENU_VISIBLE_IN_TREE;
      $type &= ~MENU_VISIBLE_IN_BREADCRUMB;
      $type |= MENU_MODIFIED_BY_ADMIN;
      db_query('UPDATE {menu} SET type = %d WHERE mid = %d', $type, $mid);
      drupal_set_message(t('Menu item disabled.'));
      drupal_goto('admin/menu');
      break;
    default:
      $output = theme('confirm', t('Are you sure you want disable %menu-item?', array(
        '%menu-item' => theme('placeholder', $menu['items'][$mid]['title']),
      )), 'admin/menu', ' ', t('Disable'));
      print theme('page', $output);
  }
}

/**
 * Menu callback; dispatch to the appropriate menu item edit function.
 */
function menu_edit_item($mid = 0) {
  $op = $_POST['op'];
  $edit = $_POST['edit'];
  $output = '';
  switch ($op) {
    case t('Submit'):
      menu_edit_item_validate($edit);
      if (!form_get_errors()) {
        menu_edit_item_save($edit);
        drupal_goto('admin/menu');
      }
      $output .= menu_edit_item_form($edit);
      break;
    default:
      if ($mid > 0) {
        $item = db_fetch_object(db_query('SELECT * FROM {menu} WHERE mid = %d', $mid));
        $edit['mid'] = $item->mid;
        $edit['pid'] = $item->pid;
        $edit['path'] = $item->path;
        $edit['title'] = $item->title;
        $edit['description'] = $item->description;
        $edit['weight'] = $item->weight;
        $edit['type'] = $item->type;
      }
      else {
        $edit['mid'] = 0;

        // In case a negative ID was passed in.
        $edit['pid'] = 1;

        // default to "Navigation" menu.
        $edit['type'] = MENU_CUSTOM_ITEM;
      }
      $output .= menu_edit_item_form($edit);
  }
  print theme('page', $output);
}

/**
 * Present the menu item editing form.
 */
function menu_edit_item_form($edit) {
  $menu = menu_get_menu();
  $form .= form_textfield(t('Title'), 'title', $edit['title'], 60, 128, t('The name to display for this link.'), NULL, TRUE);
  if ($edit['pid'] == 0) {

    // Display a limited set of fields for menus (not items).
    $form .= form_hidden('path', '');
    $form .= form_hidden('pid', 0);
    $form .= form_hidden('weight', 0);
  }
  else {
    $form .= form_textfield(t('Description'), 'description', $edit['description'], 60, 128, t('The description displayed when hovering over a menu item.'));
    $path_description = t('The Drupal path this menu item links to.');
    if (isset($edit['path']) && array_key_exists($edit['path'], $menu['path index']) && $menu['path index'][$edit['path']] != $edit['mid']) {
      $old_mid = $menu['path index'][$edit['path']];
      $old_item = $menu['items'][$old_mid];
      $path_description .= "\n" . t('Since a menu item "%old" already exists for "%path", this menu item is shortcut to that location.', array(
        '%old' => l($old_item['title'], 'admin/menu/item/edit/' . $old_mid),
        '%path' => $edit['path'],
      ));
    }
    if ($edit['type'] & MENU_CREATED_BY_ADMIN) {
      $form .= form_textfield(t('Path'), 'path', $edit['path'], 60, 128, $path_description, NULL, TRUE);
    }
    else {
      $form .= form_item(t('Path'), l($edit['path'], $edit['path']));
      $form .= form_hidden('path', $edit['path']);
    }
    $form .= form_checkbox(t('Expanded'), 'expanded', 1, $edit['type'] & MENU_EXPANDED, t('If selected and this menu item has children, the menu will always appear expanded.'));

    // Generate a list of possible parents (not including this item or descendants).
    $options = menu_parent_options($edit['mid']);
    $form .= form_select(t('Parent item'), 'pid', $edit['pid'], $options);
    $form .= form_weight(t('Weight'), 'weight', $edit['weight'], 10, t('Optional. In the menu, the heavier items will sink and the lighter items will be positioned nearer the top.'));
  }
  $form .= form_submit(t('Submit'));
  $form .= form_hidden('mid', $edit['mid']);

  // Always enable menu items (but not menus) when editing them.
  if (!($edit['type'] & MENU_IS_ROOT)) {
    $edit['type'] |= MENU_VISIBLE_IN_TREE | MENU_VISIBLE_IN_BREADCRUMB;
  }
  $form .= form_hidden('type', $edit['type']);
  return form($form);
}

/**
 * Confirm that an edited menu item has fields properly filled in.
 */
function menu_edit_item_validate($edit) {
  if (empty($edit['title'])) {
    form_set_error('title', t('You must specify a title.'));
  }
  if ($edit['pid'] != 0) {
    if (empty($edit['path'])) {
      form_set_error('path', t('You must specify a path.'));
    }
  }
}

/**
 * Save changes to a menu item into the database.
 */
function menu_edit_item_save($edit) {
  $menu = menu_get_menu();
  if ($edit['expanded']) {
    $edit['type'] |= MENU_EXPANDED;
  }
  else {
    $edit['type'] &= ~MENU_EXPANDED;
  }
  if ($edit['mid']) {
    db_query("UPDATE {menu} SET pid = %d, path = '%s', title = '%s', description = '%s', weight = %d, type = %d WHERE mid = %d", $edit['pid'], $edit['path'], $edit['title'], $edit['description'], $edit['weight'], $edit['type'] | MENU_MODIFIED_BY_ADMIN, $edit['mid']);
    drupal_set_message(t('Updated menu item %title.', array(
      '%title' => theme('placeholder', $edit['title']),
    )));
  }
  else {
    $mid = db_next_id('{menu}_mid');
    db_query("INSERT INTO {menu} (mid, pid, path, title, description, weight, type) VALUES (%d, %d, '%s', '%s', '%s', %d, %d)", $mid, $edit['pid'], $edit['path'], $edit['title'], $edit['description'], $edit['weight'], $edit['type'] | MENU_MODIFIED_BY_ADMIN);
    drupal_set_message(t('Created new menu item %title.', array(
      '%title' => theme('placeholder', $edit['title']),
    )));
    if (array_key_exists($edit['path'], $menu['path index'])) {
      $old_mid = $menu['path index'][$edit['path']];
      $old_item = $menu['items'][$old_mid];
      drupal_set_message(t('Since a menu item %old already exists for %path, this new menu item was created as a shortcut to that location.', array(
        '%old' => l(theme('placeholder', $old_item['title']), 'admin/menu/item/edit/' . $old_mid, array(), NULL, NULL, FALSE, TRUE),
        '%path' => theme('placeholder', $edit['path']),
      )));
    }
  }
}

/**
 * Present the menu tree, rendered along with links to edit menu items.
 */
function menu_overview_tree() {
  $menu = menu_get_menu();
  $header = array(
    t('Menu item'),
    t('Expanded'),
    array(
      'data' => t('Operations'),
      'colspan' => '3',
    ),
  );
  $output = '';
  foreach ($menu['items'][0]['children'] as $mid) {
    $operations = array();
    if ($menu['items'][$mid]['type'] & MENU_MODIFIABLE_BY_ADMIN) {
      $operations[] = l(t('edit'), 'admin/menu/item/edit/' . $mid);
    }
    if ($menu['items'][$mid]['type'] & MENU_CREATED_BY_ADMIN) {
      $operations[] = l(t('delete'), 'admin/menu/item/delete/' . $mid);
    }
    $table = theme('item_list', $operations);
    $table .= theme('table', $header, menu_overview_tree_rows($mid));
    $output .= theme('box', $menu['items'][$mid]['title'], $table);
  }
  return $output;
}
function menu_overview_tree_rows($pid = 0, $depth = 0) {
  $menu = menu_get_menu();
  $rows = array();
  if (isset($menu['items'][$pid]) && $menu['items'][$pid]['children']) {
    usort($menu['items'][$pid]['children'], '_menu_sort');
    foreach ($menu['items'][$pid]['children'] as $mid) {

      // Populate the title field.
      $title = '';
      if ($pid == 0) {

        // Top-level items are menu names, and don't have an associated path.
        $title .= $menu['items'][$mid]['title'];
      }
      else {
        $title .= l($menu['items'][$mid]['title'], $menu['items'][$mid]['path']);
      }
      if ($depth > 0) {
        $title = '-&nbsp;' . $title;
      }
      for ($i = 1; $i < $depth; $i++) {
        $title = '&nbsp;&nbsp;' . $title;
      }

      // Populate the operations field.
      $operations = array();
      if (!($menu['items'][$mid]['type'] & MENU_MODIFIABLE_BY_ADMIN)) {
        $operations[] = array(
          'data' => t('locked'),
          'colspan' => '3',
          'align' => 'center',
        );
      }
      else {
        if ($menu['items'][$mid]['type'] & MENU_VISIBLE_IN_TREE) {
          $operations[] = array(
            'data' => l(t('edit'), 'admin/menu/item/edit/' . $mid),
          );
          if ($menu['items'][$mid]['type'] & MENU_IS_ROOT) {

            // Disabling entire menus is done from block admin page.
            $operations[] = array(
              'data' => '',
            );
          }
          else {
            $operations[] = array(
              'data' => l(t('disable'), 'admin/menu/item/disable/' . $mid),
            );
          }
        }
        else {
          $operations[] = array(
            'data' => '',
          );
          $operations[] = array(
            'data' => l(t('enable'), 'admin/menu/item/edit/' . $mid),
          );
        }
        if ($menu['items'][$mid]['type'] & MENU_CREATED_BY_ADMIN) {
          $operations[] = array(
            'data' => l(t('delete'), 'admin/menu/item/delete/' . $mid),
          );
        }
        else {
          if ($menu['items'][$mid]['type'] & MENU_MODIFIED_BY_ADMIN) {
            $operations[] = array(
              'data' => l(t('reset'), 'admin/menu/item/reset/' . $mid),
            );
          }
          else {
            $operations[] = array(
              'data' => '',
            );
          }
        }
      }

      // Call out disabled items.
      if ($menu['items'][$mid]['type'] & MENU_VISIBLE_IN_TREE) {
        $class = 'menu-enabled';
      }
      else {
        $title .= ' (' . t('disabled') . ')';
        $class = 'menu-disabled';
      }
      if ($menu['items'][$mid]['type'] & (MENU_MODIFIABLE_BY_ADMIN | MENU_VISIBLE_IN_TREE)) {
        $row = array(
          array(
            'data' => $title,
            'class' => $class,
          ),
          array(
            'data' => $menu['items'][$mid]['children'] ? $menu['items'][$mid]['type'] & MENU_EXPANDED ? t('Yes') : t('No') : '',
            'class' => $class,
          ),
        );
        foreach ($operations as $operation) {
          $operation['class'] = $class;
          $row[] = $operation;
        }
        $rows[] = $row;
        $rows = array_merge($rows, menu_overview_tree_rows($mid, $depth + 1));
      }
      else {

        // Skip items that are hidden and locked; admins will never care about them.
        $rows = array_merge($rows, menu_overview_tree_rows($mid, $depth));
      }
    }
  }
  return $rows;
}

/**
 * Return a list of menu items that are valid possible parents for the
 * given menu item.
 */
function menu_parent_options($mid, $pid = 0, $depth = 0) {
  $menu = menu_get_menu();
  $options = array();
  if (isset($menu['items'][$pid]) && $menu['items'][$pid]['children']) {
    usort($menu['items'][$pid]['children'], '_menu_sort');
    foreach ($menu['items'][$pid]['children'] as $child) {
      if ($child != $mid) {
        if ($child > 0 && $menu['items'][$child]['type'] & (MENU_MODIFIABLE_BY_ADMIN | MENU_IS_ROOT)) {
          $title = ' ' . $menu['items'][$child]['title'];
          for ($i = 0; $i < $depth; $i++) {
            $title = '--' . $title;
          }
          if (!($menu['items'][$child]['type'] & MENU_VISIBLE_IN_TREE)) {
            $title .= ' (' . t('disabled') . ')';
          }
          $options[$child] = $title;
        }
        $options += menu_parent_options($mid, $child, $depth + 1);
      }
    }
  }
  return $options;
}

Functions

Namesort descending Description
menu_add_menu Menu callback; handle the adding of a new menu.
menu_block Implementation of hook_block().
menu_delete_item Menu callback; delete a single custom item.
menu_disable_item Menu callback; hide a menu item.
menu_edit_item Menu callback; dispatch to the appropriate menu item edit function.
menu_edit_item_form Present the menu item editing form.
menu_edit_item_save Save changes to a menu item into the database.
menu_edit_item_validate Confirm that an edited menu item has fields properly filled in.
menu_help Implementation of hook_help().
menu_menu Implementation of hook_menu().
menu_overview Menu callback; present the main menu management page.
menu_overview_tree Present the menu tree, rendered along with links to edit menu items.
menu_overview_tree_rows
menu_parent_options Return a list of menu items that are valid possible parents for the given menu item.
menu_perm Implementation of hook_perm().
menu_reset Menu callback; clear the database, resetting the menu to factory defaults.
menu_reset_item Menu callback; reset a single modified item.