Same name and namespace in other branches
  1. 7.x includes/menu.inc \_menu_router_build()

Helper function to build the router table based on the data from hook_menu.

Related topics

1 call to _menu_router_build()
menu_router_build in includes/menu.inc
Collect, alter and store the menu definitions.

File

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

Code

function _menu_router_build($callbacks) {

  // First pass: separate callbacks from paths, making paths ready for
  // matching. Calculate fitness, and fill some default values.
  $menu = array();
  foreach ($callbacks as $path => $item) {
    $load_functions = array();
    $to_arg_functions = array();
    $fit = 0;
    $move = FALSE;
    $parts = explode('/', $path, MENU_MAX_PARTS);
    $number_parts = count($parts);

    // We store the highest index of parts here to save some work in the fit
    // calculation loop.
    $slashes = $number_parts - 1;

    // Extract load and to_arg functions.
    foreach ($parts as $k => $part) {
      $match = FALSE;

      // Look for wildcards in the form allowed to be used in PHP functions,
      // because we are using these to construct the load function names.
      // See http://php.net/manual/en/language.functions.php for reference.
      if (preg_match('/^%(|[a-zA-Z_\\x7f-\\xff][a-zA-Z0-9_\\x7f-\\xff]*)$/', $part, $matches)) {
        if (empty($matches[1])) {
          $match = TRUE;
          $load_functions[$k] = NULL;
        }
        else {
          if (function_exists($matches[1] . '_to_arg')) {
            $to_arg_functions[$k] = $matches[1] . '_to_arg';
            $load_functions[$k] = NULL;
            $match = TRUE;
          }
          if (function_exists($matches[1] . '_load')) {
            $function = $matches[1] . '_load';

            // Create an array of arguments that will be passed to the _load
            // function when this menu path is checked, if 'load arguments'
            // exists.
            $load_functions[$k] = isset($item['load arguments']) ? array(
              $function => $item['load arguments'],
            ) : $function;
            $match = TRUE;
          }
        }
      }
      if ($match) {
        $parts[$k] = '%';
      }
      else {
        $fit |= 1 << $slashes - $k;
      }
    }
    if ($fit) {
      $move = TRUE;
    }
    else {

      // If there is no %, it fits maximally.
      $fit = (1 << $number_parts) - 1;
    }
    $masks[$fit] = 1;
    $item['load_functions'] = empty($load_functions) ? '' : serialize($load_functions);
    $item['to_arg_functions'] = empty($to_arg_functions) ? '' : serialize($to_arg_functions);
    $item += array(
      'title' => '',
      'weight' => 0,
      'type' => MENU_NORMAL_ITEM,
      '_number_parts' => $number_parts,
      '_parts' => $parts,
      '_fit' => $fit,
    );
    $item += array(
      '_visible' => (bool) ($item['type'] & MENU_VISIBLE_IN_BREADCRUMB),
      '_tab' => (bool) ($item['type'] & MENU_IS_LOCAL_TASK),
    );
    if ($move) {
      $new_path = implode('/', $item['_parts']);
      $menu[$new_path] = $item;
      $sort[$new_path] = $number_parts;
    }
    else {
      $menu[$path] = $item;
      $sort[$path] = $number_parts;
    }
  }
  array_multisort($sort, SORT_NUMERIC, $menu);
  if (!$menu) {

    // We must have a serious error - there is no data to save.
    watchdog('php', 'Menu router rebuild failed - some paths may not work correctly.', array(), WATCHDOG_ERROR);
    return array();
  }

  // Delete the existing router since we have some data to replace it.
  db_query('DELETE FROM {menu_router}');

  // Apply inheritance rules.
  foreach ($menu as $path => $v) {
    $item =& $menu[$path];
    if (!$item['_tab']) {

      // Non-tab items.
      $item['tab_parent'] = '';
      $item['tab_root'] = $path;
    }
    for ($i = $item['_number_parts'] - 1; $i; $i--) {
      $parent_path = implode('/', array_slice($item['_parts'], 0, $i));
      if (isset($menu[$parent_path])) {
        $parent = $menu[$parent_path];
        if (!isset($item['tab_parent'])) {

          // Parent stores the parent of the path.
          $item['tab_parent'] = $parent_path;
        }
        if (!isset($item['tab_root']) && !$parent['_tab']) {
          $item['tab_root'] = $parent_path;
        }

        // If an access callback is not found for a default local task we use
        // the callback from the parent, since we expect them to be identical.
        // In all other cases, the access parameters must be specified.
        if ($item['type'] == MENU_DEFAULT_LOCAL_TASK && !isset($item['access callback']) && isset($parent['access callback'])) {
          $item['access callback'] = $parent['access callback'];
          if (!isset($item['access arguments']) && isset($parent['access arguments'])) {
            $item['access arguments'] = $parent['access arguments'];
          }
        }

        // Same for page callbacks.
        if (!isset($item['page callback']) && isset($parent['page callback'])) {
          $item['page callback'] = $parent['page callback'];
          if (!isset($item['page arguments']) && isset($parent['page arguments'])) {
            $item['page arguments'] = $parent['page arguments'];
          }
          if (!isset($item['file']) && isset($parent['file'])) {
            $item['file'] = $parent['file'];
          }
          if (!isset($item['file path']) && isset($parent['file path'])) {
            $item['file path'] = $parent['file path'];
          }
        }
      }
    }
    if (!isset($item['access callback']) && isset($item['access arguments'])) {

      // Default callback.
      $item['access callback'] = 'user_access';
    }
    if (!isset($item['access callback']) || empty($item['page callback'])) {
      $item['access callback'] = 0;
    }
    if (is_bool($item['access callback'])) {
      $item['access callback'] = intval($item['access callback']);
    }
    $item += array(
      'access arguments' => array(),
      'access callback' => '',
      'page arguments' => array(),
      'page callback' => '',
      'block callback' => '',
      'title arguments' => array(),
      'title callback' => 't',
      'description' => '',
      'position' => '',
      'tab_parent' => '',
      'tab_root' => $path,
      'path' => $path,
      'file' => '',
      'file path' => '',
      'include file' => '',
      'module' => '',
    );

    // Calculate out the file to be included for each callback, if any.
    if ($item['file']) {
      $file_path = $item['file path'] ? $item['file path'] : drupal_get_path('module', $item['module']);
      $item['include file'] = $file_path . '/' . $item['file'];
    }
    $title_arguments = $item['title arguments'] ? serialize($item['title arguments']) : '';
    db_query("INSERT INTO {menu_router}\n      (path, load_functions, to_arg_functions, access_callback,\n      access_arguments, page_callback, page_arguments, fit,\n      number_parts, tab_parent, tab_root,\n      title, title_callback, title_arguments,\n      type, block_callback, description, position, weight, file)\n      VALUES ('%s', '%s', '%s', '%s',\n      '%s', '%s', '%s', %d,\n      %d, '%s', '%s',\n      '%s', '%s', '%s',\n      %d, '%s', '%s', '%s', %d, '%s')", $path, $item['load_functions'], $item['to_arg_functions'], $item['access callback'], serialize($item['access arguments']), $item['page callback'], serialize($item['page arguments']), $item['_fit'], $item['_number_parts'], $item['tab_parent'], $item['tab_root'], $item['title'], $item['title callback'], $title_arguments, $item['type'], $item['block callback'], $item['description'], $item['position'], $item['weight'], $item['include file']);
  }

  // Sort the masks so they are in order of descending fit, and store them.
  $masks = array_keys($masks);
  rsort($masks);
  variable_set('menu_masks', $masks);
  return $menu;
}