You are here

nodehierarchy_menu.module in Node Hierarchy 7.4

Create menu items for a node based on the Node Hierarchy.

File

nodehierarchy_menu/nodehierarchy_menu.module
View source
<?php

/**
 * @file
 * Create menu items for a node based on the Node Hierarchy.
 */

/**
 * Implements hook_menu_alter().
 */
function nodehierarchy_menu_alter(&$items) {

  // Override the menu overview form to handle the potentially large number of links created by node hierarchy.
  $items['admin/structure/menu/manage/%menu']['page arguments'] = array(
    'nodehierarchy_menu_overview_form',
    4,
    5,
  );
}

/**
 * Implements hook_theme().
 */
function nodehierarchy_menu_theme() {
  return array(
    'nodehierarchy_menu_overview_form' => array(
      'render element' => 'form',
    ),
  );
}

/**
 * Form for editing an entire menu tree at once.
 *
 * Shows for one menu the menu items accessible to the current user and
 * relevant operations. This is a clone of the menu.module function
 * menu_overview_form but with nodehierarchy items not loaded if they don't not
 * have a menu presence.
 */
function nodehierarchy_menu_overview_form($form, &$form_state, $menu, $plid = 0) {
  if (!module_exists('menu')) {
    return array();
  }
  global $menu_admin;
  $query = db_select('menu_links', 'ml');
  $query
    ->leftJoin('menu_router', 'm', 'm.path = ml.router_path');
  $query
    ->fields('m', array(
    'load_functions',
    'to_arg_functions',
    'access_callback',
    'access_arguments',
    'page_arguments',
    'delivery_callback',
    'title',
    'title_callback',
    'title_arguments',
    'type',
    'description',
  ))
    ->fields('ml')
    ->condition('ml.menu_name', $menu['menu_name']);
  $or = db_or()
    ->condition('ml.plid', $plid);

  // If we're in thea sub-menu, put together the rest of the tree.
  if ($plid) {
    $or
      ->condition('ml.plid', 0);
    $or
      ->condition('ml.mlid', $plid);

    // Get the grandparent mlid so we can go up one more level for back-navigation.
    $gplid = $plid;
    while ($gplid = db_select('menu_links', 'ml')
      ->fields('ml', array(
      'plid',
    ))
      ->condition('mlid', $gplid)
      ->execute()
      ->fetchField()) {
      $or
        ->condition('ml.mlid', $gplid);
      $or
        ->condition('ml.plid', $gplid);
    }
  }
  $query
    ->condition($or);
  for ($i = 1; $i <= 9; $i++) {
    $query
      ->orderBy('p' . $i, 'ASC');
  }
  $result = $query
    ->execute();
  $links = array();
  foreach ($result as $item) {
    $links[] = (array) $item;
  }
  $tree = menu_tree_data($links);
  $node_links = array();
  menu_tree_collect_node_links($tree, $node_links);

  // We indicate that a menu administrator is running the menu access check.
  $menu_admin = TRUE;
  menu_tree_check_access($tree, $node_links);
  $menu_admin = FALSE;
  $form = array_merge($form, _menu_overview_tree_form($tree));
  $form['#menu'] = $menu;

  // Add a breadcrumb
  // Replace the regular links with links to the sub form.
  foreach (element_children($form) as $id) {
    $form[$id]['title']['#markup'] = l($form[$id]['#item']['title'], 'admin/structure/menu/manage/' . $menu['menu_name'] . '/' . $form[$id]['#item']['mlid']);
  }
  if (element_children($form)) {
    $form['actions'] = array(
      '#type' => 'actions',
    );
    $form['actions']['submit'] = array(
      '#type' => 'submit',
      '#value' => t('Save configuration'),
    );
  }
  else {
    $form['#empty_text'] = t('There are no menu links yet. <a href="@link">Add link</a>.', array(
      '@link' => url('admin/structure/menu/manage/' . $form['#menu']['menu_name'] . '/add'),
    ));
  }

  // Set the form handlers so the behaviour is the same as the regular menu form.
  $form['#submit'][] = 'menu_overview_form_submit';
  $form['#submit'][] = 'nodehierarchy_menu_overview_form_submit';
  return $form;
}

/**
 * Theme the menu overview form into a table respecting the node hierarchy rules.
 *
 * @ingroup themeable
 */
function theme_nodehierarchy_menu_overview_form($variables) {

  // @TODO: add ajax expanding and limit parents by content type rules.
  return theme_menu_overview_form($variables);
}

/**
 * Save the new weight for a nodehierarchy item after a reorder.
 */
function nodehierarchy_menu_overview_form_submit($form, &$form_state) {
  module_load_include('inc', 'nodehierarchy', 'nodehierarchy.admin');
  $fields = array(
    'weight',
    'plid',
  );
  foreach (element_children($form) as $mlid) {
    if (isset($form[$mlid]['#item'])) {
      $element = $form[$mlid];

      // If the weight has changed.
      if ($element['weight']['#value'] != $element['weight']['#default_value']) {
        if ($nid = _nodehierarchy_menu_get_mlid_nid($element['mlid']['#value'])) {
          if ($parent = nodehierarchy_get_node_parent_primary($nid)) {
            $parent->cweight = $element['weight']['#value'];
            _nodehierarchy_record_save($parent);
          }
        }
      }
    }
  }
}

/**
 * Implements hook_form_menu_edit_item_alter().
 *
 * Alter the menu edit screen to limit the available parents according to the rules of node hierachy.
 */
function nodehierarchy_menu_form_menu_edit_item_alter(&$form, &$form_state) {

  // Replace the parent pulldown with the node hierarchy parent selector.
  if ($form['module']['#value'] == 'nodehierarchy') {

    // Remove the weight and parent.
    $form['parent']['#access'] = $form['weight']['#access'] = FALSE;
  }
}

/**
 * Implements hook_menu_menu_overview_form_alter().
 */
function nodehierarchy_menu_form_menu_overview_form_alter(&$form, &$form_state) {
  $max_delta = 0;

  // Increase the delta to switch from pulldowns to textfields.
  foreach (element_children($form) as $child) {
    if (isset($form[$child]['weight'])) {
      $form[$child]['weight']['#delta'] = variable_get('drupal_weight_select_max', DRUPAL_WEIGHT_SELECT_MAX) + 1;
    }
  }
}

/**
 * Implements hook_node_view().
 */
function nodehierarchy_menu_node_view($node, $view_mode = 'full') {
  if ($view_mode == 'full') {
    nodehierarchy_menu_set_menu_active_tail($node);
  }
}

/**
 * Implements hook_node_prepare().
 */
function nodehierarchy_menu_node_prepare($node) {
  if (!empty($node->nodehierarchy_parents[0])) {

    // Only the first item can have a menu item.
    if (empty($node->nodehierarchy_parents[0]->menu_link)) {
      if (isset($node->nid) && ($link = _nodehierarchy_menu_get_node_record_menu_links($node->nodehierarchy_parents[0]))) {
        $node->nodehierarchy_parents[0]->menu_link = $link;
      }
      else {
        $node->nodehierarchy_parents[0]->menu_link = _nodehierarchy_menu_default_menu_link(isset($node->nid) ? $node->nid : NULL);
      }
    }
  }
}

/**
 * Implements hook_node_update().
 */
function nodehierarchy_menu_node_update($node) {
  nodehierarchy_menu_node_save($node);
}

/**
 * Implements hook_node_insert().
 */
function nodehierarchy_menu_node_insert($node) {
  nodehierarchy_menu_node_save($node);
}

/**
 * Do the actual insertion or update.
 */
function nodehierarchy_menu_node_save($node) {
  if (isset($node->nodehierarchy_parents)) {
    foreach ($node->nodehierarchy_parents as $parent) {
      if (isset($parent->menu_link)) {
        _nodehierarcny_menu_save_node_menu_link($parent->menu_link, $node, $parent);
      }
    }
  }
}

/**
 * Implements hook_nodehierarchy_reorder_children().
 */
function nodehierarchy_menu_nodehierarchy_reorder_children($items) {
  $updated = FALSE;
  foreach ($items as $item) {
    if ($menu_link = _nodehierarchy_menu_get_node_record_menu_links($item)) {
      if ($menu_link['weight'] != $item->cweight) {
        $menu_link['weight'] = $item->cweight;
        menu_link_save($menu_link);
        $updated = TRUE;
      }
    }
  }
  if ($updated) {
    menu_cache_clear_all();
  }
}

/**
 * Implements hook_menu_link_delete().
 */
function nodehierarchy_menu_menu_link_delete($menu_link) {
  db_delete('nodehierarchy_menu_links')
    ->condition('mlid', $menu_link['mlid'])
    ->execute();
}

/**
 * Save a single Node Hierarchy menu item.
 */
function _nodehierarcny_menu_save_node_menu_link(&$menu_link, $node, $parent) {

  // Match the weight to the child weight (If there is a nodehierarchy record. Not always true at the top level).
  if ($parent->nhid) {
    $menu_link['weight'] = $parent->cweight;
  }
  $menu_link['hidden'] = $menu_link['enabled'] ? 0 : 1;

  // Update the paths (needed for new nodes).
  $menu_link['nid'] = $node->nid;
  $menu_link['link_path'] = 'node/' . $node->nid;
  if (empty($menu_link['customized'])) {
    $menu_link['link_title'] = $node->title;
  }
  if (isset($menu_link['description'])) {
    $menu_link['options']['attributes']['title'] = $menu_link['description'];
  }

  // Only save the menu if it exists already or is enabled.
  // @TODO: remove the menu link if it's not necessary (menu hidden and no children menus)
  if (!empty($menu_link['mlid']) || !$menu_link['hidden']) {

    // Get the plid from the parent node id or create if needed.
    $menu_link['plid'] = _nodehierarchy_menu_get_node_mlid($parent->pnid, TRUE);
    _nodehierarchy_menu_save_menu_link($menu_link);
  }
}

/**
 * Get the main menu link for the given parent record.
 */
function _nodehierarchy_menu_get_node_record_menu_links($parent) {
  $out = _nodehierarchy_menu_get_node_menu_links($parent->cnid, 1);
  return array_pop($out);
}

/**
 * Get all the menu links for the given node.
 */
function _nodehierarchy_menu_get_node_menu_links($nid, $limit = NULL) {
  $query = db_select('nodehierarchy_menu_links', 'mhml')
    ->fields('mhml', array(
    'mlid',
  ))
    ->condition('mhml.nid', $nid);
  if ($limit) {
    $query
      ->range(0, $limit);
  }
  $out = array();
  $result = $query
    ->execute()
    ->fetchAll();
  foreach ($result as $item) {
    $out[] = menu_link_load($item->mlid);
  }
  return $out;
}

/**
 * Save a menu link with changes if needed.
 */
function _nodehierarchy_menu_save_menu_link(&$menu_link) {

  // Save the parent
  menu_link_save($menu_link);

  // Create the link reference.
  _nodehierarchy_menu_create_nodehierarchy_menu_link_reference($menu_link);
}

/**
 * Create a link from the node to its menu item.
 *
 * This pivot table can be used for more efficiently joining to the menu links table for views integration.
 */
function _nodehierarchy_menu_create_nodehierarchy_menu_link_reference($menu_link) {
  if (!db_query("SELECT mlid FROM {nodehierarchy_menu_links} WHERE mlid = :mlid", array(
    ':mlid' => $menu_link['mlid'],
  ))
    ->fetchField()) {
    drupal_write_record('nodehierarchy_menu_links', $menu_link);
  }
}

/**
 * Get the primary menu link id for the given node. Optionally create one if needed.
 */
function _nodehierarchy_menu_get_node_mlid($nid, $create = FALSE) {
  $out = NULL;
  if ($nid) {
    $out = db_query("SELECT mlid FROM {menu_links} WHERE module = :module AND link_path = :link_path ORDER BY mlid LIMIT 1", array(
      ':module' => 'nodehierarchy',
      ':link_path' => 'node/' . $nid,
    ))
      ->fetchField();

    // Create a new menu item if needed.
    if ($create && !$out) {
      $menu_link = _nodehierarchy_menu_create_node_menu_link($nid);
      $out = $menu_link['mlid'];
    }
  }
  return $out;
}

/**
 * Get the primary menu link id for the given node. Optionally create one if needed.
 */
function _nodehierarchy_menu_get_mlid_nid($mlid) {
  $out = NULL;
  if ($mlid) {
    $out = db_query("SELECT nid FROM {nodehierarchy_menu_links} WHERE mlid = :mlid ORDER BY nid LIMIT 1", array(
      ':mlid' => $mlid,
    ))
      ->fetchField();
  }
  return $out;
}

/**
 * Get the menu link for the given node.
 */
function _nodehierarchy_menu_create_node_menu_link($nid) {
  $node = node_load($nid);
  $menu_link = _nodehierarchy_menu_default_menu_link($node->nid, NULL, FALSE);
  $menu_link['link_title'] = $node->title;

  // Retrieve or create the parent menu link.
  if ($parent = nodehierarchy_get_node_parent_primary($node)) {
    $parent_menu = _nodehierarchy_menu_get_node_mlid($parent->pnid, TRUE);
    $menu_link['plid'] = $parent_menu['mlid'];
    $menu_link['weight'] = $parent->cweight;
  }
  _nodehierarchy_menu_save_menu_link($menu_link);
  return $menu_link;
}

/**
 * Get the menu link for the nearest ancestor
 */
function _nodehierarchy_menu_get_nearest_ancestor_menu_link($nid) {
  $ancestors = nodehierarchy_get_node_primary_ancestor_nids($nid);
  $ancestors = array_reverse($ancestors);
  foreach ($ancestors as $ancestor) {
    if ($mlid = _nodehierarchy_menu_get_node_mlid($ancestor)) {
      return menu_link_load($mlid);
    }
  }
  return NULL;
}

/**
 * Delete a single menu_link from a node.
 */
function nodehierarchy_menu_delete_node_nodehierarchy_menu_link($mlid) {
  menu_link_delete($mlid);
  db_delete('nodehierarchy_menu_links')
    ->condition('mlid', $mlid)
    ->execute();
}

/**
 * Get the default menu link values for a new nodehierarchy menu link.
 */
function _nodehierarchy_menu_default_menu_link($nid = NULL, $plid = 0, $enabled = FALSE) {
  return array(
    'mlid' => NULL,
    'module' => 'nodehierarchy',
    'menu_name' => variable_get('nodehierarchy_default_menu_name', 'navigation'),
    'router_path' => 'node/%',
    'link_path' => !empty($nid) ? 'node/' . $nid : '',
    'hidden' => !$enabled,
    'enabled' => $enabled,
    'plid' => $plid,
    'weight' => 0,
    'nid' => !empty($nid) ? $nid : NULL,
    'customized' => 0,
  );
}

/**
 * Implementation of hook_nodehierarchy_node_parent_form_items_alter().
 */
function nodehierarchy_menu_nodehierarchy_node_parent_form_items_alter(&$form, $node, $parent) {
  if (!empty($parent->menu_link)) {
    $menu_link = $parent->menu_link;
    $create_menu = variable_get('nh_createmenu_' . $node->type, 'optional_no');
    if ((user_access('administer menus') || user_access('customize nodehierarchy menus')) && $create_menu !== 'never') {

      // Add the js to hide/show the menu selector.
      drupal_add_js(drupal_get_path("module", "nodehierarchy_menu") . '/nodehierarchy_menu.js');
      $form['menu_link'] = array(
        '#prefix' => '<div class="nodehierarchy-menu-link">',
        '#suffix' => '</div>',
        '#tree' => TRUE,
      );
      if ($create_menu == 'optional_yes' || $create_menu == 'optional_no') {
        $form['menu_link']['enabled'] = array(
          '#type' => 'checkbox',
          '#title' => 'Show in menu',
          '#attributes' => array(
            'class' => array(
              'nodehierarchy-menu-enable',
            ),
          ),
          '#default_value' => !$menu_link['hidden'],
          '#description' => t('All of this node\'s ancestors must have this option selected as well for this item to show in the menu.'),
        );
      }
      $form['menu_link']['menu_name'] = array(
        '#type' => 'select',
        '#title' => 'Menu',
        '#prefix' => '<div class="nodehierarchy-menu-settings"><div class="nodehierarchy-menu-name">',
        '#suffix' => '</div>',
        '#options' => menu_get_menus(),
        '#default_value' => $menu_link['menu_name'],
        '#description' => t('If you do not pick a parent for this node its menu item will appear at the top level of this menu.'),
      );
      $form['menu_link']['customized'] = array(
        '#type' => 'checkbox',
        '#attributes' => array(
          'class' => array(
            'nodehierarchy-menu-customize',
          ),
        ),
        '#title' => 'Customize menu title',
        '#default_value' => $menu_link['customized'],
        '#return_value' => 1,
        '#description' => t('Specify a name for this node\'s menu item that is something other than the node\'s title. Leave unchecked to use the node\'s title.'),
      );
      $form['menu_link']['link_title'] = array(
        '#type' => 'textfield',
        '#prefix' => '<div class="nodehierarchy-menu-title">',
        '#suffix' => '</div>',
        '#title' => t('Menu link title'),
        '#default_value' => empty($menu_link['link_title']) ? '' : $menu_link['link_title'],
        '#description' => t('The link text corresponding to this item that should appear in the menu.'),
      );
      $form['menu_link']['expanded'] = array(
        '#type' => 'checkbox',
        '#title' => t('Expand Menu Item'),
        '#default_value' => empty($menu_link['expanded']) ? '' : $menu_link['expanded'],
        '#description' => t('If selected and this menu item has children, the menu will always appear expanded.'),
      );
      $form['menu_link']['description'] = array(
        '#type' => 'textarea',
        '#title' => t('Menu Item Description'),
        '#default_value' => isset($menu_link['options']['attributes']['title']) ? $menu_link['options']['attributes']['title'] : '',
        '#rows' => 1,
        '#description' => t('The description displayed when hovering over a menu item. Hold your mouse over <a href="#" title="This is where the description will appear.">this link</a> for a demonstration.'),
        '#suffix' => '</div>',
      );

      // Populate the element with the link data.
      foreach (array(
        'mlid',
        'module',
        'weight',
      ) as $key) {
        $form['menu_link'][$key] = array(
          '#type' => 'value',
          '#value' => $menu_link[$key],
        );
      }
    }
  }
}

/**
 * Get the nodehierarchy setting form for a particular node type.
 */
function nodehierarchy_menu_nodehierarchy_node_type_settings_form($key) {
  $form = array();
  $form['nh_createmenu'] = array(
    '#type' => 'radios',
    '#title' => t('Show item in menu'),
    '#default_value' => variable_get('nh_createmenu_' . $key, 'optional_no'),
    '#options' => array(
      'never' => t('Never'),
      'optional_no' => t('Optional - default to no'),
      'optional_yes' => t('Optional - default to yes'),
      'always' => t('Always'),
    ),
    '#description' => t("Users must have the 'administer menu' or 'customize nodehierarchy menus' permission to override default options."),
  );
  return $form;
}

/**
 * Set the active menu to the nearest visible parent.
 */
function nodehierarchy_menu_set_menu_active_tail($node) {

  // Check if the current item has an active menu item.
  $trail = menu_get_active_trail();
  if ($last = array_pop($trail)) {
    if ($last['link_path'] != $_GET['q']) {

      // Set the menu posution to the nearest ancestor which has a menu.
      if ($menu_link = _nodehierarchy_menu_get_nearest_ancestor_menu_link($node->nid)) {
        menu_tree_set_path($menu_link['menu_name'], $menu_link['link_path']);
      }
    }
  }
}

Functions

Namesort descending Description
nodehierarchy_menu_alter Implements hook_menu_alter().
nodehierarchy_menu_delete_node_nodehierarchy_menu_link Delete a single menu_link from a node.
nodehierarchy_menu_form_menu_edit_item_alter Implements hook_form_menu_edit_item_alter().
nodehierarchy_menu_form_menu_overview_form_alter Implements hook_menu_menu_overview_form_alter().
nodehierarchy_menu_menu_link_delete Implements hook_menu_link_delete().
nodehierarchy_menu_nodehierarchy_node_parent_form_items_alter Implementation of hook_nodehierarchy_node_parent_form_items_alter().
nodehierarchy_menu_nodehierarchy_node_type_settings_form Get the nodehierarchy setting form for a particular node type.
nodehierarchy_menu_nodehierarchy_reorder_children Implements hook_nodehierarchy_reorder_children().
nodehierarchy_menu_node_insert Implements hook_node_insert().
nodehierarchy_menu_node_prepare Implements hook_node_prepare().
nodehierarchy_menu_node_save Do the actual insertion or update.
nodehierarchy_menu_node_update Implements hook_node_update().
nodehierarchy_menu_node_view Implements hook_node_view().
nodehierarchy_menu_overview_form Form for editing an entire menu tree at once.
nodehierarchy_menu_overview_form_submit Save the new weight for a nodehierarchy item after a reorder.
nodehierarchy_menu_set_menu_active_tail Set the active menu to the nearest visible parent.
nodehierarchy_menu_theme Implements hook_theme().
theme_nodehierarchy_menu_overview_form Theme the menu overview form into a table respecting the node hierarchy rules.
_nodehierarchy_menu_create_nodehierarchy_menu_link_reference Create a link from the node to its menu item.
_nodehierarchy_menu_create_node_menu_link Get the menu link for the given node.
_nodehierarchy_menu_default_menu_link Get the default menu link values for a new nodehierarchy menu link.
_nodehierarchy_menu_get_mlid_nid Get the primary menu link id for the given node. Optionally create one if needed.
_nodehierarchy_menu_get_nearest_ancestor_menu_link Get the menu link for the nearest ancestor
_nodehierarchy_menu_get_node_menu_links Get all the menu links for the given node.
_nodehierarchy_menu_get_node_mlid Get the primary menu link id for the given node. Optionally create one if needed.
_nodehierarchy_menu_get_node_record_menu_links Get the main menu link for the given parent record.
_nodehierarchy_menu_save_menu_link Save a menu link with changes if needed.
_nodehierarcny_menu_save_node_menu_link Save a single Node Hierarchy menu item.