6.x menu.inc menu_link_save(&$item)
7.x menu.inc menu_link_save(&$item, $existing_item = array(), $parent_candidates = array())

Save a menu link.

Parameters

$item: An array representing a menu link item. The only mandatory keys are link_path and link_title. Possible keys are:

  • menu_name: Default is navigation.
  • weight: Default is 0.
  • expanded: Whether the item is expanded.
  • options: An array of options, see l() for more.
  • mlid: Set to an existing value, or 0 or NULL to insert a new link.
  • plid: The mlid of the parent.
  • router_path: The path of the relevant router item.

Return value

The mlid of the saved menu link, or FALSE if the menu link could not be saved.

Related topics

13 calls to menu_link_save()
book_admin_edit_submit in modules/book/book.admin.inc
Handle submission of the book administrative page form.
book_update_6000 in modules/book/book.install
Drupal 5.x to 6.x update.
menu_edit_item_submit in modules/menu/menu.admin.inc
Process menu and menu item add/edit form submissions.
menu_edit_menu_submit in modules/menu/menu.admin.inc
Submit function for adding or editing a custom menu.
menu_enable in modules/menu/menu.module
Implementation of hook_enable()

... See full list

File

includes/menu.inc, line 1904
API for the Drupal menu system.

Code

function menu_link_save(&$item) {

  // Get the router if it's already in memory. $menu will be NULL, unless this
  // is during a menu rebuild
  $menu = _menu_router_cache();
  drupal_alter('menu_link', $item, $menu);

  // This is the easiest way to handle the unique internal path '<front>',
  // since a path marked as external does not need to match a router path.
  $item['_external'] = menu_path_is_external($item['link_path']) || $item['link_path'] == '<front>';
  // Load defaults.
  $item += array(
    'menu_name' => 'navigation',
    'weight' => 0,
    'link_title' => '',
    'hidden' => 0,
    'has_children' => 0,
    'expanded' => 0,
    'options' => array(),
    'module' => 'menu',
    'customized' => 0,
    'updated' => 0,
  );
  $existing_item = FALSE;
  if (isset($item['mlid'])) {
    $existing_item = db_fetch_array(db_query("SELECT * FROM {menu_links} WHERE mlid = %d", $item['mlid']));
  }

  if (isset($item['plid'])) {
    $parent = db_fetch_array(db_query("SELECT * FROM {menu_links} WHERE mlid = %d", $item['plid']));
  }
  else {
    // Find the parent - it must be unique.
    $parent_path = $item['link_path'];
    $where = "WHERE link_path = '%s'";
    // Only links derived from router items should have module == 'system', and
    // we want to find the parent even if it's in a different menu.
    if ($item['module'] == 'system') {
      $where .= " AND module = '%s'";
      $arg2 = 'system';
    }
    else {
      // If not derived from a router item, we respect the specified menu name.
      $where .= " AND menu_name = '%s'";
      $arg2 = $item['menu_name'];
    }
    do {
      $parent = FALSE;
      $parent_path = substr($parent_path, 0, strrpos($parent_path, '/'));
      $result = db_query("SELECT COUNT(*) FROM {menu_links} " . $where, $parent_path, $arg2);
      // Only valid if we get a unique result.
      if (db_result($result) == 1) {
        $parent = db_fetch_array(db_query("SELECT * FROM {menu_links} " . $where, $parent_path, $arg2));
      }
    } while ($parent === FALSE && $parent_path);
  }
  if ($parent !== FALSE) {
    $item['menu_name'] = $parent['menu_name'];
  }
  $menu_name = $item['menu_name'];
  // Menu callbacks need to be in the links table for breadcrumbs, but can't
  // be parents if they are generated directly from a router item.
  if (empty($parent['mlid']) || $parent['hidden'] < 0) {
    $item['plid'] = 0;
  }
  else {
    $item['plid'] = $parent['mlid'];
  }

  if (!$existing_item) {
    db_query("INSERT INTO {menu_links} (
       menu_name, plid, link_path,
      hidden, external, has_children,
      expanded, weight,
      module, link_title, options,
      customized, updated) VALUES (
      '%s', %d, '%s',
      %d, %d, %d,
      %d, %d,
      '%s', '%s', '%s', %d, %d)", 
    $item['menu_name'], $item['plid'], $item['link_path'], 
    $item['hidden'], $item['_external'], $item['has_children'], 
    $item['expanded'], $item['weight'], 
    $item['module'], $item['link_title'], serialize($item['options']), 
    $item['customized'], $item['updated']);
    $item['mlid'] = db_last_insert_id('menu_links', 'mlid');
  }

  if (!$item['plid']) {
    $item['p1'] = $item['mlid'];
    for ($i = 2; $i <= MENU_MAX_DEPTH; $i++) {
      $item["p$i"] = 0;
    }
    $item['depth'] = 1;
  }
  else {
    // Cannot add beyond the maximum depth.
    if ($item['has_children'] && $existing_item) {
      $limit = MENU_MAX_DEPTH - menu_link_children_relative_depth($existing_item) - 1;
    }
    else {
      $limit = MENU_MAX_DEPTH - 1;
    }
    if ($parent['depth'] > $limit) {
      return FALSE;
    }
    $item['depth'] = $parent['depth'] + 1;
    _menu_link_parents_set($item, $parent);
  }
  // Need to check both plid and menu_name, since plid can be 0 in any menu.
  if ($existing_item && ($item['plid'] != $existing_item['plid'] || $menu_name != $existing_item['menu_name'])) {
    _menu_link_move_children($item, $existing_item);
  }
  // Find the callback. During the menu update we store empty paths to be
  // fixed later, so we skip this.
  if (!isset($_SESSION['system_update_6021']) && (empty($item['router_path']) || !$existing_item || ($existing_item['link_path'] != $item['link_path']))) {
    if ($item['_external']) {
      $item['router_path'] = '';
    }
    else {
      // Find the router path which will serve this path.
      $item['parts'] = explode('/', $item['link_path'], MENU_MAX_PARTS);
      $item['router_path'] = _menu_find_router_path($item['link_path']);
    }
  }
  db_query("UPDATE {menu_links} SET menu_name = '%s', plid = %d, link_path = '%s',
    router_path = '%s', hidden = %d, external = %d, has_children = %d,
    expanded = %d, weight = %d, depth = %d,
    p1 = %d, p2 = %d, p3 = %d, p4 = %d, p5 = %d, p6 = %d, p7 = %d, p8 = %d, p9 = %d,
    module = '%s', link_title = '%s', options = '%s', customized = %d WHERE mlid = %d", 
  $item['menu_name'], $item['plid'], $item['link_path'], 
  $item['router_path'], $item['hidden'], $item['_external'], $item['has_children'], 
  $item['expanded'], $item['weight'], $item['depth'], 
  $item['p1'], $item['p2'], $item['p3'], $item['p4'], $item['p5'], $item['p6'], $item['p7'], $item['p8'], $item['p9'], 
  $item['module'], $item['link_title'], serialize($item['options']), $item['customized'], $item['mlid']);
  // Check the has_children status of the parent.
  _menu_update_parental_status($item);
  menu_cache_clear($menu_name);
  if ($existing_item && $menu_name != $existing_item['menu_name']) {
    menu_cache_clear($existing_item['menu_name']);
  }

  _menu_clear_page_cache();
  return $item['mlid'];
}

Comments

mikeytown2’s picture

module_load_include('inc', 'menu', 'menu.admin');

$form_state = array();
$form_state['values'] = array(
  'menu_name'     => 'test-menu',
  'title'         => 'Test Menu Title',
  'description'   => 'Test Menu Description',
);
drupal_execute('menu_edit_menu', $form_state, NULL);

$form_state = array();
$form_state['values']['menu'] = array(
  'link_path'     => '',
  'link_title'    => 'test a',
  'description'   => 'test b',
  'enabled'       => 1,
  'expanded'      => 0,
  'parent'        => 'menu-test-menu:0',
  'weight'        => 0,
  'customized'    => 1,
);
drupal_execute('menu_edit_item', $form_state, 'add', NULL, array('menu_name' => 'menu-test-menu'));

$form_state = array();
$form_state['values']['menu'] = array(
  'link_path'     => '',
  'link_title'    => 'test y',
  'description'   => 'test z',
  'enabled'       => 1,
  'expanded'      => 0,
  'parent'        => 'menu-test-menu:0',
  'weight'        => -1,
  'customized'    => 1,
);
drupal_execute('menu_edit_item', $form_state, 'add', NULL, array('menu_name' => 'menu-test-menu'));

nvahalik’s picture

Make sure you specify to run the code as uid = 1. It will not work if you do not since drush runs as anonymous.

drush -u 1 scr create_menu_code.php

DerekAhmedzai’s picture

If your newly created links aren't showing up, call menu_rebuild().

ZechariahStover’s picture

Another Parameter that I've found helpful is

  • module: Set to the name of the module you are calling this function from.

This is very helpful if you are running this function in hook_install. You can then remove these menu links in hook_uninstall like this:
db_query("DELETE FROM {menu_links} WHERE module = 'mymodule'");

akalam’s picture

There is an error in the documentation, with the params...
where it says router_path, it must say link_path, almost in drupal 6.22
Also note that you can set the link_title as array key in order to set the title of the menu item

Marvine’s picture

Remember to use menu_link_save() with the internal Drupal path. If the url alias is already used, you can run the following code to update your menu :

$result = db_queryd("SELECT mlid FROM {menu_links} WHERE menu_name = '%s'", 'menu-to-update');
	while ($m = db_fetch_array($result)) {
		$link = menu_link_load($m['mlid']);
		$link['link_path'] = drupal_get_normal_path($link['link_path']);
		menu_link_save($link);
	}

Helpful for me.

sudishth’s picture

$item = array(
'link_path' => 'node/' . $node->nid,
'link_title' => $node->title,
'menu_name' => 'primary-links', // Menu machine name, for example: main-menu
'weight' => 0,
'language' => $node->language,
'plid' => 0, // Parent menu item, 0 if menu item is on top level
'module' => 'menu',
);
$newmid = menu_link_save($item);