Migrate the menu items from the old menu system to the new menu_links table.

Related topics

File

modules/system/system.install, line 1788

Code

function system_update_6021() {
  $ret = array(
    '#finished' => 0,
  );
  $menus = array(
    'navigation' => array(
      'menu_name' => 'navigation',
      'title' => 'Navigation',
      'description' => 'The navigation menu is provided by Drupal and is the main interactive menu for any site. It is usually the only menu that contains personalized links for authenticated users, and is often not even visible to anonymous users.',
    ),
    'primary-links' => array(
      'menu_name' => 'primary-links',
      'title' => 'Primary links',
      'description' => 'Primary links are often used at the theme layer to show the major sections of a site. A typical representation for primary links would be tabs along the top.',
    ),
    'secondary-links' => array(
      'menu_name' => 'secondary-links',
      'title' => 'Secondary links',
      'description' => 'Secondary links are often used for pages like legal notices, contact details, and other secondary navigation items that play a lesser role than primary links.',
    ),
  );

  // Multi-part update
  if (!isset($_SESSION['system_update_6021'])) {
    db_add_field($ret, 'menu', 'converted', array(
      'type' => 'int',
      'unsigned' => TRUE,
      'not null' => TRUE,
      'default' => 0,
      'size' => 'tiny',
    ));
    $_SESSION['system_update_6021_max'] = db_result(db_query('SELECT COUNT(*) FROM {menu}'));
    $_SESSION['menu_menu_map'] = array(
      1 => 'navigation',
    );

    // 0 => FALSE is for new menus, 1 => FALSE is for the navigation.
    $_SESSION['menu_item_map'] = array(
      0 => FALSE,
      1 => FALSE,
    );
    $table = array(
      'fields' => array(
        'menu_name' => array(
          'type' => 'varchar',
          'length' => 32,
          'not null' => TRUE,
          'default' => '',
        ),
        'title' => array(
          'type' => 'varchar',
          'length' => 255,
          'not null' => TRUE,
          'default' => '',
        ),
        'description' => array(
          'type' => 'text',
          'not null' => FALSE,
        ),
      ),
      'primary key' => array(
        'menu_name',
      ),
    );
    db_create_table($ret, 'menu_custom', $table);
    db_query("INSERT INTO {menu_custom} (menu_name, title, description) VALUES ('%s', '%s', '%s')", $menus['navigation']);
    $_SESSION['system_update_6021'] = 0;
  }
  $limit = 50;
  while ($limit-- && ($item = db_fetch_array(db_query_range('SELECT * FROM {menu} WHERE converted = 0', 0, 1)))) {

    // If it's not a menu...
    if ($item['pid']) {

      // Let's climb up until we find an item with a converted parent.
      $item_original = $item;
      while ($item && !isset($_SESSION['menu_item_map'][$item['pid']])) {
        $item = db_fetch_array(db_query('SELECT * FROM {menu} WHERE mid = %d', $item['pid']));
      }

      // This can only occur if the menu entry is a leftover in the menu table.
      // These do not appear in Drupal 5 anyways, so we skip them.
      if (!$item) {
        db_query('UPDATE {menu} SET converted = %d WHERE mid = %d', 1, $item_original['mid']);
        $_SESSION['system_update_6021']++;
        continue;
      }
    }

    // We need to recheck because item might have changed.
    if ($item['pid']) {

      // Fill the new fields.
      $item['link_title'] = $item['title'];
      $item['link_path'] = drupal_get_normal_path($item['path']);

      // We know the parent is already set. If it's not FALSE then it's an item.
      if ($_SESSION['menu_item_map'][$item['pid']]) {

        // The new menu system parent link id.
        $item['plid'] = $_SESSION['menu_item_map'][$item['pid']]['mlid'];

        // The new menu system menu name.
        $item['menu_name'] = $_SESSION['menu_item_map'][$item['pid']]['menu_name'];
      }
      else {

        // This a top level element.
        $item['plid'] = 0;

        // The menu name is stored among the menus.
        $item['menu_name'] = $_SESSION['menu_menu_map'][$item['pid']];
      }

      // Is the element visible in the menu block?
      $item['hidden'] = !($item['type'] & MENU_VISIBLE_IN_TREE);

      // Is it a custom(ized) element?
      if ($item['type'] & (MENU_CREATED_BY_ADMIN | MENU_MODIFIED_BY_ADMIN)) {
        $item['customized'] = TRUE;
      }

      // Items created via the menu module need to be assigned to it.
      if ($item['type'] & MENU_CREATED_BY_ADMIN) {
        $item['module'] = 'menu';
        $item['router_path'] = '';
        $item['updated'] = TRUE;
      }
      else {
        $item['module'] = 'system';
        $item['router_path'] = $item['path'];
        $item['updated'] = FALSE;
      }
      if ($item['description']) {
        $item['options']['attributes']['title'] = $item['description'];
      }

      // Save the link.
      menu_link_save($item);
      $_SESSION['menu_item_map'][$item['mid']] = array(
        'mlid' => $item['mlid'],
        'menu_name' => $item['menu_name'],
      );
    }
    elseif (!isset($_SESSION['menu_menu_map'][$item['mid']])) {
      $item['menu_name'] = 'menu-' . preg_replace('/[^a-zA-Z0-9]/', '-', strtolower($item['title']));
      $item['menu_name'] = substr($item['menu_name'], 0, 20);
      $original_menu_name = $item['menu_name'];
      $i = 0;
      while (db_result(db_query("SELECT menu_name FROM {menu_custom} WHERE menu_name = '%s'", $item['menu_name']))) {
        $item['menu_name'] = $original_menu_name . $i++;
      }
      if ($item['path']) {

        // Another bunch of bogus entries. Apparently, these are leftovers
        // from Drupal 4.7 .
        $_SESSION['menu_bogus_menus'][] = $item['menu_name'];
      }
      else {

        // Add this menu to the list of custom menus.
        db_query("INSERT INTO {menu_custom} (menu_name, title, description) VALUES ('%s', '%s', '')", $item['menu_name'], $item['title']);
      }
      $_SESSION['menu_menu_map'][$item['mid']] = $item['menu_name'];
      $_SESSION['menu_item_map'][$item['mid']] = FALSE;
    }
    db_query('UPDATE {menu} SET converted = %d WHERE mid = %d', 1, $item['mid']);
    $_SESSION['system_update_6021']++;
  }
  if ($_SESSION['system_update_6021'] >= $_SESSION['system_update_6021_max']) {
    if (!empty($_SESSION['menu_bogus_menus'])) {

      // Remove entries in bogus menus. This is secure because we deleted
      // every non-alpanumeric character from the menu name.
      $ret[] = update_sql("DELETE FROM {menu_links} WHERE menu_name IN ('" . implode("', '", $_SESSION['menu_bogus_menus']) . "')");
    }
    $menu_primary_menu = variable_get('menu_primary_menu', 0);

    // Ensure that we wind up with a system menu named 'primary-links'.
    if (isset($_SESSION['menu_menu_map'][2])) {

      // The primary links menu that ships with Drupal 5 has mid = 2.  If this
      // menu hasn't been deleted by the site admin, we use that.
      $updated_primary_links_menu = 2;
    }
    elseif (isset($_SESSION['menu_menu_map'][$menu_primary_menu]) && $menu_primary_menu > 1) {

      // Otherwise, we use the menu that is currently assigned to the primary
      // links region of the theme, as long as it exists and isn't the
      // Navigation menu.
      $updated_primary_links_menu = $menu_primary_menu;
    }
    else {

      // As a last resort, create 'primary-links' as a new menu.
      $updated_primary_links_menu = 0;
      db_query("INSERT INTO {menu_custom} (menu_name, title, description) VALUES ('%s', '%s', '%s')", $menus['primary-links']);
    }
    if ($updated_primary_links_menu) {

      // Change the existing menu name to 'primary-links'.
      $replace = array(
        '%new_name' => 'primary-links',
        '%desc' => $menus['primary-links']['description'],
        '%old_name' => $_SESSION['menu_menu_map'][$updated_primary_links_menu],
      );
      $ret[] = update_sql(strtr("UPDATE {menu_custom} SET menu_name = '%new_name', description = '%desc' WHERE menu_name = '%old_name'", $replace));
      $ret[] = update_sql("UPDATE {menu_links} SET menu_name = 'primary-links' WHERE menu_name = '" . $_SESSION['menu_menu_map'][$updated_primary_links_menu] . "'");
      $_SESSION['menu_menu_map'][$updated_primary_links_menu] = 'primary-links';
    }
    $menu_secondary_menu = variable_get('menu_secondary_menu', 0);

    // Ensure that we wind up with a system menu named 'secondary-links'.
    if (isset($_SESSION['menu_menu_map'][$menu_secondary_menu]) && $menu_secondary_menu > 1 && $menu_secondary_menu != $updated_primary_links_menu) {

      // We use the menu that is currently assigned to the secondary links
      // region of the theme, as long as (a) it exists, (b) it isn't the
      // Navigation menu, (c) it isn't the same menu we assigned as the
      // system 'primary-links' menu above, and (d) it isn't the same menu
      // assigned to the primary links region of the theme.
      $updated_secondary_links_menu = $menu_secondary_menu;
    }
    else {

      // Otherwise, create 'secondary-links' as a new menu.
      $updated_secondary_links_menu = 0;
      db_query("INSERT INTO {menu_custom} (menu_name, title, description) VALUES ('%s', '%s', '%s')", $menus['secondary-links']);
    }
    if ($updated_secondary_links_menu) {

      // Change the existing menu name to 'secondary-links'.
      $replace = array(
        '%new_name' => 'secondary-links',
        '%desc' => $menus['secondary-links']['description'],
        '%old_name' => $_SESSION['menu_menu_map'][$updated_secondary_links_menu],
      );
      $ret[] = update_sql(strtr("UPDATE {menu_custom} SET menu_name = '%new_name', description = '%desc' WHERE menu_name = '%old_name'", $replace));
      $ret[] = update_sql("UPDATE {menu_links} SET menu_name = 'secondary-links' WHERE menu_name = '" . $_SESSION['menu_menu_map'][$updated_secondary_links_menu] . "'");
      $_SESSION['menu_menu_map'][$updated_secondary_links_menu] = 'secondary-links';
    }

    // Update menu OTF preferences.
    $mid = variable_get('menu_parent_items', 0);
    $menu_name = $mid && isset($_SESSION['menu_menu_map'][$mid]) ? $_SESSION['menu_menu_map'][$mid] : 'navigation';
    variable_set('menu_default_node_menu', $menu_name);
    variable_del('menu_parent_items');

    // Update the source of the primary and secondary links.
    $menu_name = $menu_primary_menu && isset($_SESSION['menu_menu_map'][$menu_primary_menu]) ? $_SESSION['menu_menu_map'][$menu_primary_menu] : '';
    variable_set('menu_primary_links_source', $menu_name);
    variable_del('menu_primary_menu');
    $menu_name = $menu_secondary_menu && isset($_SESSION['menu_menu_map'][$menu_secondary_menu]) ? $_SESSION['menu_menu_map'][$menu_secondary_menu] : '';
    variable_set('menu_secondary_links_source', $menu_name);
    variable_del('menu_secondary_menu');

    // Skip the navigation menu - it is handled by the user module.
    unset($_SESSION['menu_menu_map'][1]);

    // Update the deltas for all menu module blocks.
    foreach ($_SESSION['menu_menu_map'] as $mid => $menu_name) {

      // This is again secure because we deleted every non-alpanumeric
      // character from the menu name.
      $ret[] = update_sql("UPDATE {blocks} SET delta = '" . $menu_name . "' WHERE module = 'menu' AND delta = '" . $mid . "'");
      $ret[] = update_sql("UPDATE {blocks_roles} SET delta = '" . $menu_name . "' WHERE module = 'menu' AND delta = '" . $mid . "'");
    }
    $ret[] = array(
      'success' => TRUE,
      'query' => 'Relocated ' . $_SESSION['system_update_6021'] . ' existing items to the new menu system.',
    );
    $ret[] = update_sql("DROP TABLE {menu}");
    unset($_SESSION['system_update_6021'], $_SESSION['system_update_6021_max'], $_SESSION['menu_menu_map'], $_SESSION['menu_item_map'], $_SESSION['menu_bogus_menus']);

    // Create the menu overview links - also calls menu_rebuild(). If menu is
    // disabled, then just call menu_rebuild.
    if (function_exists('menu_enable')) {
      menu_enable();
    }
    else {
      menu_rebuild();
    }
    $ret['#finished'] = 1;
  }
  else {
    $ret['#finished'] = $_SESSION['system_update_6021'] / $_SESSION['system_update_6021_max'];
  }
  return $ret;
}