You are here

menu_position.module in Menu Position 6

Same filename and directory in other branches
  1. 8 menu_position.module
  2. 7.2 menu_position.module
  3. 7 menu_position.module

Provides menu links for dynamic positioning of nodes based on configurable rules.

File

menu_position.module
View source
<?php

/**
 * @file
 * Provides menu links for dynamic positioning of nodes based on configurable rules.
 */

/**
 * Implements hook_page_delivery_callback_alter().
 *
 * This is the only hook that occurs after the page callback, but before
 * hook_page_build (when blocks are added). We're using this hook for its
 * timing, not its data.
 */
function menu_position_page_preprocess_callback() {
  $context = array();
  $context['path'] = $_GET['q'];
  if (arg(0) == 'node' && is_numeric(arg(1))) {
    $context['node'] = node_load(arg(1));
  }
  menu_position_evaluate_rules($context);
}

/**
 * Implements hook_perm().
 */
function menu_position_perm() {
  return array(
    'administer menu positions',
  );
}

/**
 * Implements hook_menu().
 */
function menu_position_menu() {
  module_load_include('inc', 'menu_position', 'menu_position.admin');
  return _menu_position_menu();
}

/**
 * Implements hook_theme().
 */
function menu_position_theme() {
  return array(
    'menu_position_rules_order' => array(
      'render element' => 'element',
      'file' => 'menu_position.admin.inc',
    ),
  );
}

/**
 * Implements hook_theme_registry_alter().
 */
function menu_position_theme_registry_alter(&$theme_registry) {

  // Allows rules to be run after the page callback, but before the rest of the page is built.
  array_unshift($theme_registry['page']['preprocess functions'], 'menu_position_page_preprocess_callback');
}

/**
 * Implements hook_menu_position_rule_plugins().
 */
function menu_position_menu_position_rule_plugins() {
  $plugins = array(
    'content_type' => array(
      'file' => 'plugins/menu_position.content_type.inc',
    ),
    'pages' => array(
      'file' => 'plugins/menu_position.pages.inc',
    ),
  );
  if (module_exists('taxonomy')) {
    $plugins['taxonomy'] = array(
      'file' => 'plugins/menu_position.taxonomy.inc',
    );
  }
  return $plugins;
}

/**
 * Implements hook_form_FORM_ID_alter().
 */
function menu_position_form_menu_overview_form_alter(&$form, &$form_state) {
  module_load_include('inc', 'menu_position', 'menu_position.admin');
  _menu_position_form_menu_overview_form_alter($form, $form_state);
}

/**
 * Implements hook_form_FORM_ID_alter().
 */
function menu_position_form_menu_edit_item_alter(&$form, &$form_state) {
  module_load_include('inc', 'menu_position', 'menu_position.admin');
  _menu_position_form_menu_edit_item_alter($form, $form_state);
}

/**
 * Implements hook_menu_link_alter().
 */
function menu_position_menu_link_alter(&$item, $menu) {
  if (isset($item['module']) && $item['module'] == 'menu_position') {
    module_load_include('inc', 'menu_position', 'menu_position.admin');
    _menu_position_menu_link_alter($item, $menu);
  }
}

/**
 * Evaluates all rules based on the given path.
 *
 * @param $path
 *   The path used to evaluate all rules.
 * @param $context
 *   The full node object if the path is a node page.
 */
function menu_position_evaluate_rules($context = array()) {
  $path = $context['path'];

  // Retrieve the rules from the database.
  $rules = db_query('SELECT * FROM {menu_position_rules} WHERE enabled = 1 ORDER BY weight, rid');

  // Retrieve the list of menus the path is already in.
  $result = db_query('SELECT menu_name FROM {menu_links} WHERE link_path = "%s"', $path);
  $menu_names = array();
  while ($menu_name = db_fetch_array($result)) {
    $menu_names[] = $menu_name['menu_name'];
  }

  // Retrieve the main and secondary menu names.
  $main_menu = theme_get_setting('toggle_primary_links') ? variable_get('menu_main_links_source', 'primary-links') : FALSE;
  $secondary_menu = theme_get_setting('toggle_secondary_links') ? variable_get('menu_secondary_links_source', 'secondary-links') : FALSE;

  // Retrieve the original router item.
  $original_router_item = menu_get_item();

  // Flag that we haven't set the breadcrumb yet.
  $breadcrumb_set = FALSE;

  // Examine each rule and check its conditions.
  while ($rule = db_fetch_object($rules)) {
    if (in_array($rule->menu_name, $menu_names)) {

      // If the node is already placed in the rule's menu, skip the rule.
      $rule_matches = FALSE;
      if ($rule->menu_name == $main_menu) {

        // Don't override the main menu links.
        $main_menu = FALSE;
      }
      if ($rule->menu_name == $secondary_menu) {
        $secondary_menu = FALSE;
      }
      $breadcrumb_set = TRUE;
    }
    else {

      // A rule with no conditions always matches.
      $rule_matches = TRUE;

      // Go through each condition, ANDing each result.
      $rule->conditions = unserialize($rule->conditions);
      foreach ($rule->conditions as $plugin => $variables) {

        // Add the current rule and node to the callback's variables.
        $variables['rule'] = $rule;
        $variables['context'] = $context;

        // Find the plugin's callback function.
        $callback = menu_position_get_condition_callback($plugin);
        if ($callback) {

          // Check if this condition matches.
          $rule_matches = $callback($variables);
        }
        else {

          // If the callback cannot be found, the condition has failed.
          $rule_matches = FALSE;
        }

        // No need to check other conditions if this condition failed.
        if (!$rule_matches) {
          break;
        }
      }
    }

    // We've found a matching rule.
    if ($rule_matches) {

      // Retrieve menu item specified in the rule.
      $menu_item = menu_link_load($rule->mlid);

      // Clone the original router item, but insert our menu_position path.
      $router_item = $original_router_item;
      $router_item['href'] = $menu_item['link_path'];

      // Even if we are denied access to the page, we still want to show the
      // navigational paths to the page.
      $router_item['access'] = TRUE;

      // Temporarily override the original router item.
      menu_set_item(NULL, $router_item);

      // Set the theme's secondary menu links if the rule matches the secondary menu.
      if ($rule->menu_name == $secondary_menu) {

        // Mark that we no longer need to set the secondary menu's tree.
        $secondary_menu = FALSE;
      }

      // Set the theme's main menu links if the rule matches the main menu.
      if ($rule->menu_name == $main_menu) {

        // Mark that we no longer need to set the main menu's tree.
        $main_menu = FALSE;
      }
      menu_position_precache_tree($router_item, $original_router_item, $menu_item['menu_name']);

      // Use our fake router item to influence the active trail.
      if (!$breadcrumb_set) {

        // Find the fake active trail.
        $active_trail = menu_position_get_active_trail($menu_item['menu_name']);

        // Replace the menu_position item with the original menu item.
        array_pop($active_trail);
        $active_trail[] = $original_router_item;
        menu_set_active_trail($active_trail);

        // Also override any trail set with drupal_set_breadcrumb().
        $breadcrumbs = menu_get_active_breadcrumb();
        array_pop($breadcrumbs);
        drupal_set_breadcrumb($breadcrumbs);
        $breadcrumb_set = TRUE;
      }

      // Restore the original router item.
      menu_set_item(NULL, $original_router_item);

      // Don't let other rules match against this rule's menu.
      $menu_names[] = $rule->menu_name;
    }
  }
}

/**
 * Retrieve the active trail for the specified menu.
 *
 * Copy of menu_set_active_trail() without the brain-dead static cache.
 */
function menu_position_get_active_trail($menu_name = NULL) {
  if (!isset($menu_name)) {
    $menu_name = menu_get_active_menu_name();
  }
  $trail = array();
  $trail[] = array(
    'title' => t('Home'),
    'href' => '<front>',
    'localized_options' => array(),
    'type' => 0,
  );
  $item = menu_get_item();

  // We know the current item isn't a local task, so we've removed some lines
  // from our copy of menu_set_active_trail().
  $tree = menu_tree_page_data($menu_name);
  list($key, $curr) = each($tree);
  while ($curr) {

    // Terminate the loop when we find the current path in the active trail.
    if ($curr['link']['href'] == $item['href']) {
      $trail[] = $curr['link'];
      $curr = FALSE;
    }
    else {

      // Add the link if it's in the active trail, then move to the link below.
      if ($curr['link']['in_active_trail']) {
        $trail[] = $curr['link'];
        $tree = $curr['below'] ? $curr['below'] : array();
      }
      list($key, $curr) = each($tree);
    }
  }

  // Make sure the current page is in the trail (needed for the page title),
  // but exclude tabs and the front page.
  $last = count($trail) - 1;
  if ($trail[$last]['href'] != $item['href'] && !(bool) ($item['type'] & MENU_IS_LOCAL_TASK) && !drupal_is_front_page()) {
    $trail[] = $item;
  }
  return $trail;
}

/**
 * Places a menu tree cache in place of the original router item's tree.
 */
function menu_position_precache_tree($router_item, $original_router_item, $menu_name) {

  // Create a menu tree using the menu_position path.
  menu_tree_page_data($menu_name);

  // Get the cache for the tree we just generated.
  $router_item_cid = 'links:' . $menu_name . ':page-cid:' . $router_item['href'] . ':' . (int) $router_item['access'];
  $cache = cache_get($router_item_cid, 'cache_menu');
  $tree_cid = $cache->data;

  // Copy the cache to the tree that contains the original router item.
  $original_router_item_cid = 'links:' . $menu_name . ':page-cid:' . $original_router_item['href'] . ':' . (int) $original_router_item['access'];
  cache_set($original_router_item_cid, $tree_cid, 'cache_menu');
}

/**
 * Retrieves a list of information about every rule plugin.
 */
function menu_position_get_plugins() {
  static $plugins = array();
  if (empty($plugins)) {
    foreach (module_implements('menu_position_rule_plugins') as $module) {
      $function = $module . '_menu_position_rule_plugins';
      if (function_exists($function)) {

        // Register each module's plugin while setting baseline defaults.
        foreach ($function() as $name => $plugin) {
          $plugins[$name] = $plugin + array(
            'module' => $module,
            'file' => '',
            'form_callback' => $module . '_menu_position_rule_' . $name . '_form',
            'condition_callback' => $module . '_menu_position_condition_' . $name,
          );
        }
      }
    }
  }
  return $plugins;
}

/**
 * Loads the include file containing a condition's callback function definition.
 *
 * @param $plugin
 *   The name of the plugin.
 * @return
 *   The name of the callback function, or FALSE if it could not be found.
 */
function menu_position_get_condition_callback($plugin) {
  $plugins = menu_position_get_plugins();
  $callback = !empty($plugins[$plugin]['condition_callback']) ? $plugins[$plugin]['condition_callback'] : FALSE;
  if ($callback && !function_exists($callback)) {

    // Load the specified include file.
    if (!empty($plugins[$plugin]['file'])) {
      $file = pathinfo($plugins[$plugin]['file']);

      // Allow plugins to be in a sub-directory.
      if ($file['dirname']) {
        $file['filename'] = $file['dirname'] . '/' . $file['filename'];
      }
      module_load_include($file['extension'], $plugins[$plugin]['module'], $file['filename']);
    }

    // Note if the callback still cannot be found.
    if (!function_exists($callback)) {
      $callback = FALSE;
    }
  }
  return $callback;
}

Functions

Namesort descending Description
menu_position_evaluate_rules Evaluates all rules based on the given path.
menu_position_form_menu_edit_item_alter Implements hook_form_FORM_ID_alter().
menu_position_form_menu_overview_form_alter Implements hook_form_FORM_ID_alter().
menu_position_get_active_trail Retrieve the active trail for the specified menu.
menu_position_get_condition_callback Loads the include file containing a condition's callback function definition.
menu_position_get_plugins Retrieves a list of information about every rule plugin.
menu_position_menu Implements hook_menu().
menu_position_menu_link_alter Implements hook_menu_link_alter().
menu_position_menu_position_rule_plugins Implements hook_menu_position_rule_plugins().
menu_position_page_preprocess_callback Implements hook_page_delivery_callback_alter().
menu_position_perm Implements hook_perm().
menu_position_precache_tree Places a menu tree cache in place of the original router item's tree.
menu_position_theme Implements hook_theme().
menu_position_theme_registry_alter Implements hook_theme_registry_alter().