function menu_link_save

Saves a menu link.

After calling this function, rebuild the menu cache using menu_cache_clear_all().

Parameters

$item: An associative array representing a menu link item, with elements:

  • link_path: (required) The path of the menu item, which should be normalized first by calling drupal_get_normal_path() on it.
  • link_title: (required) Title to appear in menu for the link.
  • menu_name: (optional) The machine name of the menu for the link. Defaults to 'navigation'.
  • weight: (optional) Integer to determine position in menu. Default is 0.
  • expanded: (optional) Boolean that determines if the item is expanded.
  • options: (optional) An array of options, see l() for more.
  • mlid: (optional) Menu link identifier, the primary integer key for each menu link. Can be set to an existing value, or to 0 or NULL to insert a new link.
  • plid: (optional) The mlid of the parent.
  • router_path: (optional) The path of the relevant router item.

$existing_item: Optional, the current record from the {menu_links} table as an array.

$parent_candidates: Optional array of menu links keyed by mlid. Used by _menu_navigation_links_rebuild() only.

Return value

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

Related topics

24 calls to menu_link_save()
book_admin_edit_submit in modules/book/book.admin.inc
Form submission handler for book_admin_edit().
MenuDataIntegrityTestCase::testNullMenuWeight in modules/simpletest/tests/menu.test
Tests for null/casting weight parameter.
MenuLinksUnitTestCase::assertMenuLinkParents in modules/simpletest/tests/menu.test
Assert that at set of links is properly parented.
MenuLinksUnitTestCase::createLinkHierarchy in modules/simpletest/tests/menu.test
Create a simple hierarchy of links.
MenuLinksUnitTestCase::testMenuLinkReparenting in modules/simpletest/tests/menu.test
Test automatic reparenting of menu links.

... See full list

File

includes/menu.inc, line 3153

Code

function menu_link_save(&$item, $existing_item = array(), $parent_candidates = array()) {
    drupal_alter('menu_link', $item);
    // 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'] = url_is_external($item['link_path']) || $item['link_path'] == '<front>' ? 1 : 0;
    // 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,
    );
    if (isset($item['mlid'])) {
        if (!$existing_item) {
            $existing_item = db_query('SELECT * FROM {menu_links} WHERE mlid = :mlid', array(
                'mlid' => $item['mlid'],
            ))->fetchAssoc();
        }
        if ($existing_item) {
            $existing_item['options'] = unserialize($existing_item['options']);
        }
    }
    else {
        $existing_item = FALSE;
    }
    // Try to find a parent link. If found, assign it and derive its menu.
    $parent = _menu_link_find_parent($item, $parent_candidates);
    if (!empty($parent['mlid'])) {
        $item['plid'] = $parent['mlid'];
        $item['menu_name'] = $parent['menu_name'];
    }
    else {
        $item['plid'] = 0;
    }
    $menu_name = $item['menu_name'];
    if (!$existing_item) {
        $item['mlid'] = db_insert('menu_links')->fields(array(
            'menu_name' => $item['menu_name'],
            'plid' => $item['plid'],
            'link_path' => $item['link_path'],
            'hidden' => $item['hidden'],
            'external' => $item['external'],
            'has_children' => $item['has_children'],
            'expanded' => $item['expanded'],
            'weight' => (int) $item['weight'],
            'module' => $item['module'],
            'link_title' => $item['link_title'],
            'options' => serialize($item['options']),
            'customized' => $item['customized'],
            'updated' => $item['updated'],
        ))
            ->execute();
    }
    // Directly fill parents for top-level links.
    if ($item['plid'] == 0) {
        $item['p1'] = $item['mlid'];
        for ($i = 2; $i <= MENU_MAX_DEPTH; $i++) {
            $item["p{$i}"] = 0;
        }
        $item['depth'] = 1;
    }
    else {
        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 router_path.
    if (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']);
        }
    }
    // If every value in $existing_item is the same in the $item, there is no
    // reason to run the update queries or clear the caches. We use
    // array_intersect_key() with the $item as the first parameter because
    // $item may have additional keys left over from building a router entry.
    // The intersect removes the extra keys, allowing a meaningful comparison.
    if (!$existing_item || array_intersect_key($item, $existing_item) != $existing_item) {
        db_update('menu_links')->fields(array(
            'menu_name' => $item['menu_name'],
            'plid' => $item['plid'],
            'link_path' => $item['link_path'],
            'router_path' => $item['router_path'],
            'hidden' => $item['hidden'],
            'external' => $item['external'],
            'has_children' => $item['has_children'],
            'expanded' => $item['expanded'],
            'weight' => (int) $item['weight'],
            'depth' => $item['depth'],
            'p1' => $item['p1'],
            'p2' => $item['p2'],
            'p3' => $item['p3'],
            'p4' => $item['p4'],
            'p5' => $item['p5'],
            'p6' => $item['p6'],
            'p7' => $item['p7'],
            'p8' => $item['p8'],
            'p9' => $item['p9'],
            'module' => $item['module'],
            'link_title' => $item['link_title'],
            'options' => serialize($item['options']),
            'customized' => $item['customized'],
        ))
            ->condition('mlid', $item['mlid'])
            ->execute();
        // 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']);
        }
        // Notify modules we have acted on a menu item.
        $hook = 'menu_link_insert';
        if ($existing_item) {
            $hook = 'menu_link_update';
        }
        module_invoke_all($hook, $item);
        // Now clear the cache.
        _menu_clear_page_cache();
    }
    return $item['mlid'];
}

Buggy or inaccurate documentation? Please file an issue. Need support? Need help programming? Connect with the Drupal community.