You are here

menu_node.module in Menu Node API 6

Same filename and directory in other branches
  1. 7 menu_node.module

Menu Node API Manages relationships between the {node} and {menu_links} table.

File

menu_node.module
View source
<?php

/**
 * @file
 * Menu Node API
 * Manages relationships between the {node} and {menu_links} table.
 */

/**
 * Implements hook_nodeapi().
 */
function menu_node_nodeapi(&$node, $op, $a3 = NULL, $a4 = NULL) {
  $watch = array(
    'insert',
    'update',
    'delete',
    'load',
  );

  // Do we care about this node?
  if (!in_array($op, $watch)) {
    return;
  }

  // On delete operations, the menu item may be deleted before this
  // runs, so ensure we have the data.
  if ($op == 'load') {

    // Ensure the menu object is loaded.
    $node->menu_node_items = menu_node_get_links($node->nid);
  }
  $mlid = isset($node->menu['mlid']) ? $node->menu['mlid'] : NULL;

  // If the node is being deleted, remove all records.
  if ($op == 'delete') {
    menu_node_delete($node);
  }
  else {

    // If we have a record to insert, then do so now.
    if (empty($node->menu['delete']) && !empty($mlid)) {
      menu_node_save($node->nid, $mlid);
    }
    else {
      if (!empty($mlid)) {
        menu_node_delete($node->nid, $mlid);
      }
    }
  }
}

/**
 * Get the relevant node object for a menu item.
 *
 * @param $mlid
 *   The menu link id.
 * @param $load
 *   Boolean value that indicates whether to return the node id or a full $node.
 * @return
 *   A node id, a complete node object or FALSE on failure.
 */
function menu_node_get_node($mlid, $load = TRUE) {
  $nid = db_result(db_query("SELECT n.nid FROM {node} n INNER JOIN {menu_node} mn ON n.nid = mn.nid WHERE mn.mlid = %d", $mlid));
  if (empty($nid)) {
    return FALSE;
  }
  if ($load) {
    return node_load($nid);
  }
  return $nid;
}

/**
 * Get the relevant menu links for a node.
 * @param $nid
 *   The node id.
 * @param $router
 *   Boolean flag indicating whether to attach the menu router item to the $item object.
 *   If set to TRUE, the router will be set as $item->menu_router.
 * @return
 *   An array of complete menu_link objects or an empy array on failure.
 */
function menu_node_get_links($nid, $router = FALSE) {
  $result = db_query("SELECT * FROM {menu_links} WHERE link_path = '%s'", 'node/' . $nid);
  $items = array();
  while ($data = db_fetch_object($result)) {
    if ($router) {
      $data->menu_router = menu_get_item('node/' . $nid);
    }
    $items[$data->mlid] = $data;
  }
  return $items;
}

/**
 * Get all menu links assigned to a specific menu.
 *
 * @param $menu_name
 *   The machine name of the menu, e.g. 'navigation'.
 * @return
 *   A simple array of menu link ids.
 */
function menu_node_get_links_by_menu($menu_name) {
  $links = array();
  $result = db_query("SELECT mlid FROM {menu_links} WHERE menu_name = '%s'", $menu_name);
  while ($data = db_fetch_object($result)) {
    $links[] = $data->mlid;
  }
  return $links;
}

/**
 * Get all nodes assigned to a specific menu.
 *
 * @param $menu_name
 *   The machine name of the menu, e.g. 'navigation'.
 * @param $load
 *   Boolean flag that indicates whether to load the node object or not.
 *   NOTE: This can be resource intensive!
 * @return
 *   A simple array of node ids.
 */
function menu_node_get_nodes_by_menu($menu_name, $load = FALSE) {
  $links = array();
  $result = db_query("SELECT mn.nid FROM {menu_node} mn INNER JOIN {menu_links} ml ON mn.mlid = ml.mlid WHERE ml.menu_name = '%s'", $menu_name);
  while ($data = db_fetch_object($result)) {
    if ($load) {
      $nodes[$data->nid] = node_load($data->nid);
    }
    else {
      $nodes[] = $data->nid;
    }
  }
  return $nodes;
}

/**
 * Public function for generating a tree representation of nodes in a menu.
 *
 * This function is useful for showing the relationship between nodes within
 * a given menu tree. It can be used to build options lists for forms and other
 * user interface elements.
 *
 * @param $tree
 *   The parent menu tree, generated by menu_tree_all_data().
 * @param $menu
 *   The name of the menu for which to return data.
 * @param $filter
 *   An array of menu links ids that indicate the only children to return.
 *   That is, if this array is populated, only its members and their children will
 *   be returned by this function.
 * @param $options
 *   An array of processing options. The valid options are 'marker' and 'spacer'.
 *   -- 'marker' indicates a text mark to indicate menu depth for a menu link.
 *   -- 'spacer' indicates the text string to insert betwen a marker and its link title.
 * @return
 *   A nested array of menu data.
 */
function menu_node_tree($tree, $menu = NULL, $filter = array(), $options = array()) {
  $options += array(
    'marker' => '-',
    'spacer' => ' ',
  );
  $data = array();
  if (!empty($menu)) {
    _menu_node_tree($data, $menu, $value, $filter, $options['marker'], $options['spacer']);
    return $data[$menu];
  }
  else {
    foreach ($tree as $key => $value) {
      _menu_node_tree($data, $key, $value, $filter, $options['marker'], $options['spacer']);
    }
  }
  return $data;
}

/**
 * A private recursive sort function.
 *
 * Given a menu tree, return its child node items.
 *
 * @param $tree
 *   The recursive tree data.
 * @param $menu
 *   The menu that this data belongs to.
 * @param $data
 *   The tree data for this menu element.
 * @param $parents
 *   An array of menu link ids indicating the tree elements to return.
 * @param $marker
 *   A string (or NULL) to prepend to the menu link title, indicating relative depth.
 * @param $spacer
 *   A string (or NULL) to place between the marker and the title.
 * @return
 *   No return. Modify $tree by reference.
 */
function _menu_node_tree(&$tree, $menu, $data, $parents, $marker = NULL, $spacer = NULL) {
  if (empty($tree)) {
    $tree = array();
  }
  if (empty($parents)) {
    $parents = array();
  }
  if (in_array($data['link']['mlid'], $parents)) {
    $parent = menu_node_get_parent($data['link']);
    $tree[$parent][$data['link']['mlid']] = str_repeat($marker, $data['link']['depth']) . $spacer . $data['link']['title'] . ' ';
  }
  if (!empty($data['below'])) {

    // Recursive processing joy!
    foreach ($data['below'] as $value) {
      _menu_node_tree($tree, $menu, $value, $parents, $marker, $spacer);
    }
  }
}

/**
 * Return the parent item of a menu element.
 *
 * @param $item
 *   The menu item.
 * @param $return
 *   Indicates the value to return, options are:
 *   -- 'title' returns the name of the parent menu item.
 *   -- 'item' returns the entire parent, as loaded by menu_get_item().
 * @return
 *   A string, representing the parent name or a menu object.
 */
function menu_node_get_parent($item, $return = 'title') {
  if ($return == 'title') {
    return db_result(db_query("SELECT link_title FROM {menu_links} WHERE mlid = %d", $item['p1']));
  }
  $path = db_result(db_query("SELECT link_path FROM {menu_links} WHERE mlid = %d", $item['p1']));
  return menu_get_item($path);
}

/**
 * Implements hook_form_alter().
 *
 * React to the editing of custom menu items.
 */
function menu_node_form_menu_edit_item_alter(&$form, $form_state) {
  $form['#submit'][] = 'menu_node_edit_form_submit';
}

/**
 * Implements hook_form_alter().
 *
 * React to the deletion of custom menu items.
 */
function menu_node_form_menu_item_delete_form_alter(&$form, $form_state) {
  $form['mlid'] = array(
    '#type' => 'value',
    '#value' => $form['#item']['mlid'],
  );
  $form['#submit'][] = 'menu_node_delete_form_submit';

  // Our submit _must_ be run first.
  $form['#submit'] = array_reverse($form['#submit']);
}

/**
 * Implements hook_form_alter().
 *
 * React to the deletion of entire menus.
 */
function menu_node_form_menu_delete_menu_confirm_alter(&$form, $form_state) {
  $form['menu_name'] = array(
    '#type' => 'value',
    '#value' => $form['#menu']['menu_name'],
  );
  $form['#submit'][] = 'menu_node_delete_menu_form_submit';

  // Our submit _must_ be run first.
  $form['#submit'] = array_reverse($form['#submit']);
}

/**
 * Custom form handler to react to menu changes.
 */
function menu_node_edit_form_submit($form, &$form_state) {
  $menu = $form_state['values']['menu'];

  // Is this a node item?
  if (count($menu['parts']) == 2 && ($menu['parts'][0] = 'node' && is_numeric($menu['parts'][1]))) {
    menu_node_save($menu['parts'][1], $menu['mlid']);
  }
}

/**
 * Save records to the {menu_node} table.
 *
 * After saving, we fire the appropriate menu_node hook,
 * either 'insert' or 'update'.
 *
 * @param $nid
 *   The node id.
 * @param $mlid
 *   The menu link id.
 */
function menu_node_save($nid, $mlid, $hook = 'update') {
  $new = menu_node_exists($mlid);
  $record = array(
    'nid' => $nid,
    'mlid' => $mlid,
  );

  // Save if the record does not exist, otherwise update the existing link
  if (empty($new)) {
    drupal_write_record('menu_node', $record);
    $hook = 'insert';
  }
  else {
    drupal_write_record('menu_node', $record, 'mlid');
  }
  _menu_node_invoke($nid, $mlid, $hook);
}

/**
 * Wrapper function for module hooks.
 *
 * @param $nid
 *   The node id.
 * @param $mlid
 *   The menu link id.
 * @param $hook
 *   The hook to invoke ('insert', 'update', or 'delete').
 */
function _menu_node_invoke($nid, $mlid, $hook) {

  // Use our internal lookup fuinctions.
  $node = menu_node_get_node($mlid);
  $items = menu_node_get_links($nid);
  module_invoke_all('menu_node_' . $hook, $items[$mlid], $node);
}

/**
 * Check to see if a specific mlid exists.
 *
 * @param $mlid
 *   The menu link id.
 * @return
 *   The count of matches (which should be 1 or 0).
 */
function menu_node_exists($mlid) {
  return db_result(db_query("SELECT COUNT(mlid) FROM {menu_node} WHERE mlid = %d", $mlid));
}

/**
 * Custom form handler to react to menu item changes.
 */
function menu_node_delete_form_submit($form, &$form_state) {
  $mlid = $form_state['values']['mlid'];
  $node = menu_node_get_node($mlid, TRUE);

  // Is this a node item?
  if (!empty($node)) {
    menu_node_delete($node);
  }
}

/**
 * Custom form handler to react to custom menu changes.
 */
function menu_node_delete_menu_form_submit($form, &$form_state) {
  $menu_name = $form_state['values']['menu_name'];
  $items = menu_node_get_links_by_menu($menu_name);

  // We pass these individually in case any hook implementations care.
  foreach ($items as $mlid) {
    $node = menu_node_get_node($mlid, TRUE);
    menu_node_delete($node);
  }
}

/**
 * Delete a record from {menu_node} and run hook_menu_node_delete().
 *
 * We deliberately run the hook before the delete, in case any module
 * wishes to run a JOIN on the {menu_node} table.
 *
 * @param $node
 *   The node being deleted.
 * @return
 *   No return. hook_menu_node_delete() is invoked.
 */
function menu_node_delete($node) {
  if (!empty($node->menu_node_items)) {
    foreach ($node->menu_node_items as $item) {
      module_invoke_all('menu_node_delete', $item, $node);
    }
  }
  db_query("DELETE FROM {menu_node} WHERE nid = %d", $node->nid);
}

Functions

Namesort descending Description
menu_node_delete Delete a record from {menu_node} and run hook_menu_node_delete().
menu_node_delete_form_submit Custom form handler to react to menu item changes.
menu_node_delete_menu_form_submit Custom form handler to react to custom menu changes.
menu_node_edit_form_submit Custom form handler to react to menu changes.
menu_node_exists Check to see if a specific mlid exists.
menu_node_form_menu_delete_menu_confirm_alter Implements hook_form_alter().
menu_node_form_menu_edit_item_alter Implements hook_form_alter().
menu_node_form_menu_item_delete_form_alter Implements hook_form_alter().
menu_node_get_links Get the relevant menu links for a node.
menu_node_get_links_by_menu Get all menu links assigned to a specific menu.
menu_node_get_node Get the relevant node object for a menu item.
menu_node_get_nodes_by_menu Get all nodes assigned to a specific menu.
menu_node_get_parent Return the parent item of a menu element.
menu_node_nodeapi Implements hook_nodeapi().
menu_node_save Save records to the {menu_node} table.
menu_node_tree Public function for generating a tree representation of nodes in a menu.
_menu_node_invoke Wrapper function for module hooks.
_menu_node_tree A private recursive sort function.