You are here

menu_select.module in Menu Select 7

Expands the node menu select field functionality by adding filters and an expandable hierarchy.

File

menu_select.module
View source
<?php

/**
 * @file
 * Expands the node menu select field functionality by adding filters
 * and an expandable hierarchy.
 */

/**
 * Implements hook_menu().
 */
function menu_select_menu() {
  $items['admin/config/content/menu-select'] = array(
    'title' => 'Menu Select',
    'description' => 'Administer and configure settings for Menu Select such as enabling Search or modifying the auto-expand cut off point.',
    'access arguments' => array(
      'administer site configuration',
    ),
    'page callback' => 'drupal_get_form',
    'page arguments' => array(
      'menu_select_config_form',
    ),
  );
  $items['menu-select/autocomplete/%'] = array(
    'title' => 'Autocomplete for menu select',
    'page callback' => '_menu_select_autocomplete',
    'page arguments' => array(
      2,
    ),
    'access callback' => '_menu_select_access_autocomplete',
    'access arguments' => array(
      2,
    ),
    'type' => MENU_CALLBACK,
  );
  return $items;
}

/**
 * Implements hook_form_alter().
 */
function menu_select_form_alter(&$form, &$form_state) {

  // Target the Menu Position form and parent
  if (module_exists('menu_position') && ($form['#id'] == 'menu-position-add-rule-form' || $form['#id'] == 'menu-position-edit-rule-form')) {
    $menu_select_form =& $form;
    $menu_select_parent =& $form['plid'];
  }

  // Target the Menu Item form and parent
  if ($form['#id'] == 'menu-edit-item' && isset($form['parent'])) {
    $menu_select_form =& $form;
    $menu_select_parent =& $form['parent'];
  }

  // Target any form that contains menu, menu link, and menu link parent
  if (isset($form['menu']) && isset($form['menu']['link']) && isset($form['menu']['link']['parent'])) {
    $menu_select_form =& $form['menu']['link'];
    $menu_select_parent =& $form['menu']['link']['parent'];
  }
  if (isset($menu_select_form) && isset($menu_select_parent)) {
    $items =& $menu_select_parent;
    $menu = array();

    // The array of nested menu items
    $map = array();

    // An mkey to title lookup
    $depth = 0;

    // Array of pointers for accessing previous depths of the 2d array.
    // $pointers[0] will always point to $menu and the pointer at $pointer[1]
    // will always be a root item
    $pointers = array();
    $pointers[] =& $menu;

    // Iterate through the flat array and try to parse out a multi-dimensional structure
    foreach ($items['#options'] as $mkey => $label) {

      // Process root item labels
      if (strpos($label, '<') === 0) {
        $label = substr($label, 1, strlen($label) - 2);
      }

      // Ascertain depth by the number of dashes before the label.
      // None is root/1, two is first level/2, etc
      $parts = explode(' ', $label);
      if (count($parts) > 1 && $parts[0][1] === '-') {
        $new_depth = substr_count($parts[0], '-') / 2 + 1;
      }
      else {
        $new_depth = 1;
      }

      // Insert menu items into the $menu array using the appropriate pointer
      if ($new_depth > $depth) {

        // Inserting a new child item
        $pointers[$new_depth - 1][$mkey] = array();
        $pointers[] =& $pointers[$depth][$mkey];
      }
      elseif ($new_depth < $depth) {

        // Going up ($depth - $new_depth) level(s) and inserting a sibling
        $pointers = array_slice($pointers, 0, $new_depth);
        $pointers[$new_depth - 1][$mkey] = array();
        $pointers[] =& $pointers[$new_depth - 1][$mkey];
      }
      else {

        // Inserting a sibling
        $pointers[$new_depth - 1][$mkey] = array();

        // Change the pointer at this level so if the next item is a child it's inserted under this item
        $pointers[$depth] =& $pointers[$new_depth - 1][$mkey];
      }
      $map[$mkey] = trim(substr($label, ($new_depth - 1) * 2));
      $depth = $new_depth;
    }

    //_menu_select_debug_tree($menu, $map);
    $cut_off = variable_get('menu_select_cut_off', 30);
    $menu_options = array();
    foreach ($menu as $mkey => $children) {
      $menu_options[$mkey] = $map[$mkey];
    }

    // Set the default menu
    $default_menu = key($menu_options);

    // Change the default menu if the menu item belongs to a menu that isn't the default
    if (isset($items['#default_value']) && !empty($items['#default_value'])) {
      $default_value = explode(':', $items['#default_value']);
      $default_value = $default_value[0] . ':0';
      if (array_key_exists($default_value, $menu_options)) {
        $default_menu = $default_value;
      }
    }
    $menu_select_form['#attached']['js'][] = drupal_get_path('module', 'menu_select') . '/js/menu_select.js';
    $menu_select_form['#attached']['js'][] = array(
      'data' => array(
        'menu_select' => compact('menu', 'map', 'cut_off'),
      ),
      'type' => 'setting',
    );
    $menu_select_form['#attached']['css'][] = drupal_get_path('module', 'menu_select') . '/css/menu_select_icons.css';
    $menu_select_form['#attached']['css'][] = drupal_get_path('module', 'menu_select') . '/css/menu_select.css';

    // For debugging purposes

    /*
    $menu_select_form['#collapsed'] = false;
    $menu_select_form['#enabled']['#default_value'] = 1;
    $menu_select_form['#enabled']['#value'] = 1;
    $menu_select_form['#states']['invisible']['input[name="menu[enabled]"']['checked'] = true;
    */

    // Give everything a weight so we can insert our parent select widget in where Parent item should be
    $weight = 0;
    foreach ($menu_select_form as &$item) {
      if (is_array($item) && isset($item['#type'])) {
        $item['#weight'] = ++$weight;
      }
    }

    // Push actions to the bottom again
    $menu_select_form['actions']['#weight'] = $weight + 51;

    // Add a custom class to the existing menu parent select
    $menu_select_parent['#attributes']['class'][] = 'menu-select-menu-parent';

    // Build new form element
    $menu_select_form['menu_select'] = array(
      '#type' => 'fieldset',
      '#title' => t('Parent item'),
      '#collapsible' => FALSE,
      '#collapsed' => FALSE,
      '#weight' => $menu_select_parent['#weight'],
      '#attributes' => array(
        'class' => array(
          'element-invisible',
          'menu-select-menu-select',
        ),
      ),
      'preview' => array(
        '#type' => 'item',
        '#title' => t('Menu link position'),
        '#markup' => '<span class="menu-select-parent-menu-title" title="' . $default_menu . '">' . $map[$default_menu] . '</span> / <span class="menu-select-position-preview"></span> <span class="current-menu-item">' . t('Current menu item') . '</span>',
        '#description' => t('Preview of the menu item\'s hierarchy compared to its parent(s).'),
      ),
      'select_menu_link_position' => array(
        '#type' => 'fieldset',
        '#title' => t('Select menu link position'),
        '#collapsible' => FALSE,
        '#collapsed' => FALSE,
        '#attributes' => array(
          'class' => array(
            'menu-select-select-menu-link-position',
          ),
        ),
        'parent_menu' => array(
          '#type' => 'select',
          '#title' => t('Menu'),
          '#options' => $menu_options,
          '#description' => t('Select which menu you would like this item to belong to.'),
          '#default_value' => $default_menu,
          '#attributes' => array(
            'class' => array(
              'menu-select-parent-menu',
            ),
          ),
        ),
        'parent_menu_item' => array(
          '#type' => 'item',
          '#title' => t('Menu hierarchy'),
          '#markup' => '<div class="menu-select-menu-hierarchy"></div>',
          '#description' => t('Select the menu item\'s parent from the hierarchy above.'),
        ),
      ),
    );
    if (variable_get('menu_select_search_enabled', 0) === 1) {
      if (isset($form['type']['#value'])) {
        $type = $form['type']['#value'];
      }
      else {
        $type = '';
      }
      $menu_select_form['menu_select']['select_menu_link_position']['parent_menu_item_search'] = array(
        '#type' => 'textfield',
        '#title' => t('Search'),
        '#autocomplete_path' => 'menu-select/autocomplete/' . $type,
        '#description' => 'Alternatively, use this autocomplete search to find the menu item\'s parent and select it.',
        '#attributes' => array(
          'class' => array(
            'menu-select-parent-menu-item-search',
          ),
        ),
      );
    }
  }
}

/**
 * Config form for 'admin/config/content/menu-select'.
 *
 * @see menu_select_menu().
 */
function menu_select_config_form($form, $form_state) {
  $form['menu_select_cut_off'] = array(
    '#title' => t('Auto-expansion cut off'),
    '#description' => t('The minimum number of menu items in a tree required to have the tree collapsed by default. Set to -1 to disable.'),
    '#type' => 'textfield',
    '#element_validate' => array(
      'element_validate_number',
    ),
    '#default_value' => variable_get('menu_select_cut_off', 30),
    '#size' => 4,
  );
  $form['menu_select_search_enabled'] = array(
    '#title' => t('Enable autocomplete search'),
    '#description' => t('By default the search field is disabled because a core patch is required. If you wish to use the search field visit <a href="http://drupal.org/node/365241#comment-6314864">http://drupal.org/node/365241#comment-6314864</a> and apply the core patch. Once done, check this box and submit this form.'),
    '#type' => 'checkbox',
    '#default_value' => variable_get('menu_select_search_enabled', 0),
  );
  return system_settings_form($form);
}

/**
 * Permission to access Menu Select autocomplete config.
 *
 * @see menu_select_menu().
 */
function _menu_select_access_autocomplete($type) {
  return user_access('create ' . $type . ' content');
}

/**
 * Menu Select autocomplete config page.
 *
 * @see menu_select_menu().
 */
function _menu_select_autocomplete($type = '', $partial = '') {
  $matches = array();
  $partial = strtolower($partial);
  $options = menu_parent_options(menu_get_menus(), $type, $type);
  foreach ($options as $mkey => $option) {
    if (strpos(strtolower($option), $partial) !== FALSE) {
      $option = preg_replace('/^\\-+\\s/', '', $option);
      $option = str_replace(array(
        '<',
        '>',
      ), '', $option);
      $matches[$mkey] = $option . ' (' . $mkey . ')';
    }
  }
  drupal_json_output($matches);
}

/**
 * Helper for a recursive function.
 */
function _menu_select_hierarchy($menu_level, &$map) {
  $output = '';
  foreach ($menu_level as $key => $children) {
    $output .= '<li>' . $map[$key] . '</li>';
  }
}

/**
 * Debug function for testing the menu tree.
 */
function _menu_select_debug_tree($level, &$map, $depth = 0) {
  foreach ($level as $key => $children) {
    if (!empty($children)) {
      _menu_select_debug_tree($children, $map, $depth + 1);
    }
  }
}

Functions

Namesort descending Description
menu_select_config_form Config form for 'admin/config/content/menu-select'.
menu_select_form_alter Implements hook_form_alter().
menu_select_menu Implements hook_menu().
_menu_select_access_autocomplete Permission to access Menu Select autocomplete config.
_menu_select_autocomplete Menu Select autocomplete config page.
_menu_select_debug_tree Debug function for testing the menu tree.
_menu_select_hierarchy Helper for a recursive function.