You are here

jump_menu.module in Better Jump Menus 7

Same filename and directory in other branches
  1. 8 jump_menu.module
  2. 6 jump_menu.module

Make use of the CTools jump menu and grabs from an existing menu. See: modules/ctools/includes/jump-menu.inc NOTE: Menu items must be checked as "expanded" for traversing to work.

File

jump_menu.module
View source
<?php

/**
 * @file
 * Make use of the CTools jump menu and grabs from an existing menu.
 * See: modules/ctools/includes/jump-menu.inc
 * NOTE: Menu items must be checked as "expanded" for traversing to work.
 */

// Constants.
define('JUMP_MENU_DEFAULT_CHOOSE', '-- Choose --');
define('JUMP_MENU_BLOCK_DEFAULTS_SHOW_CURRENT', 0);
define('JUMP_MENU_BLOCK_DEFAULTS_SHOW_BUTTON', 0);

/**
 * Output a core menu as a select jump menu.
 */
function jump_menu($menu, $parent, $btn = FALSE, $max_depth = 0, $choose = 'Select a destination', $current = FALSE) {
  ctools_include('jump-menu');

  // Load up the menu.
  $menu = menu_tree_all_data($menu);

  // Localize the tree.
  if (module_exists('i18n_menu')) {
    $menu = i18n_menu_localize_tree($menu);
  }

  // Trim to the needed portion, start at parent menuID.
  foreach ($menu as $m) {

    // The mlid is i18n tranlsation friendly.
    if ($m['link']['mlid'] == $parent) {
      $menu = $m['below'];
      break;
    }
  }

  // Initialize for building.
  $depths = array(
    'current' => 1,
    'max' => $max_depth,
  );
  $targets = array();

  // Build the jump options from the menu.
  _jump_menu_create_options($targets, $menu, $depths);

  // Output...
  if (count($targets) == 0) {
    return 'Jump menu contains no items!';
  }
  else {
    $options = array();

    // Handle button option.
    if ($btn) {
      $options['hide'] = FALSE;
      $options['button'] = $btn;
    }
    else {
      $options['hide'] = TRUE;
    }

    // Place initial select option value.
    $options['choose'] = t($choose);

    // Set current location if desired.
    if ($current) {
      $current_path = base_path() . request_path();
      if (!empty($current_path)) {
        $options['default_value'] = $current_path;
      }
    }

    // Other available options...
    // 'title' => The text to display for the #title attribute.
    // 'description': The text to display for the #description attribute.
    // 'image': If set, an image button will be used instead, and the image set to this.
    // 'inline': If set to TRUE (default) the display will be forced inline.
    return drupal_get_form('ctools_jump_menu', $targets, $options);
  }
}

/**
 * Recursive menu to select option building.
 */
function _jump_menu_create_options(&$t, &$m, &$d) {

  // Set the option.
  foreach ($m as $item) {

    // Kill non-viewable menu items.
    if ($item['link']['hidden'] == 0) {

      // Add depth indicators to titles.
      if ($d['current'] > 1) {
        $title = ' ' . str_repeat('-', $d['current'] - 1) . ' ' . $item['link']['title'];
      }
      else {
        $title = $item['link']['title'];
      }

      // Add option targets...
      // Active and depth classes (for aggressive theming).
      $classes = 'd-' . $d['current'];

      // @todo Break this off once dealing with language, since it's duplicated in local tasks.
      // @todo Add active trail, but beware: http://drupal.org/node/1854356
      $current_path = drupal_get_normal_path(request_path());
      if ($item['link']['href'] == $current_path) {
        $classes .= ' active';
      }
      $attributes = array(
        'class' => $classes,
      );

      // Add attribute for opening mode.
      if (isset($item['link']['options']['attributes']['target'])) {
        $attributes['target'] = $item['link']['options']['attributes']['target'];
      }

      // Allow for special menu item dummy items for grouping.
      if (module_exists('special_menu_items') && $item['link']['page_callback'] == 'drupal_not_found') {

        // Create a dummy option using optgroups.
        $t[] = array(
          'title' => t($title),
          '#attributes' => $attributes,
        );
      }
      else {

        // Create a normal option.
        $url_options = array();
        if (!empty($item['link']['localized_options']['query'])) {
          $url_options['query'] = $item['link']['localized_options']['query'];
        }
        if (!empty($item['link']['localized_options']['fragment'])) {
          $url_options['fragment'] = $item['link']['localized_options']['fragment'];
        }
        $t[] = array(
          'value' => url($item['link']['href'], $url_options),
          'title' => t($title),
          '#attributes' => $attributes,
        );
      }
    }

    // Loop deeper if there is no max or we haven't reached it.
    if ($item['below'] && ($d['max'] == 0 || $d['current'] < $d['max'])) {

      // Drop current depth.
      $d['current']++;
      _jump_menu_create_options($t, $item['below'], $d);
    }
  }

  // Raise current depth back up.
  $d['current']--;
}

/**
 * Register jump blocks for all menus.
 */
function jump_menu_block_info() {

  // Add all menus as blocks.
  $menus = menu_get_menus(TRUE);
  $blocks = array();
  foreach ($menus as $name => $title) {

    // Prefix length is 12 chars, leaves 20 for menu name according to 32 char db limit.
    $delta = 'jump_menu-m_' . substr($name, 0, 20);
    $blocks[$delta]['info'] = t('Jump Menu') . ' ' . t('menu') . ' - ' . check_plain($title);

    // Menu blocks can't be cached because each menu item can have a custom
    // access callback. menu.inc manages its own caching.
    $blocks[$delta]['cache'] = DRUPAL_NO_CACHE;
  }

  // Add local menu tasks block.
  $blocks['jump_menu-local-tasks']['info'] = t('Jump Menu') . ': ' . t('Local tasks');

  // Caching would need to be PER ROLE and PER PAGE.
  $blocks['jump_menu-local-tasks']['cache'] = DRUPAL_NO_CACHE;
  return $blocks;
}

/**
 * Display jump menu block.
 */
function jump_menu_block_view($delta = '') {

  // Default rendering.
  // The block_view_alter function re-renders if block settings are in place.
  return _jump_menu_render_block($delta);
}

/**
 * Make use of block settings on display.
 */
function jump_menu_block_view_alter(&$data, $block) {

  // Only bother if the title is set.
  if ($block->module == 'jump_menu' && $block->title) {
    $options = array();

    // Pass in the block settings.
    if ($block->title != '<none>') {
      $options['choose'] = $block->title != '<none>' ? $block->title : t(JUMP_MENU_DEFAULT_CHOOSE);
    }

    // Replace content with user set title as choice text.
    // Would be nice to avoid rendering the menu twice in the stack.
    $data_built = _jump_menu_render_block($block->delta, $options);
    if (isset($data) && $data_built) {
      $data['content'] = $data_built['content'];
    }
  }
}

/**
 * For killing off the title.
 */
function jump_menu_preprocess_block(&$vars, $hook) {

  // Kill off the title if set. It's being used as the choose value.
  if ($vars['block']->module == 'jump_menu' && $vars['block']->title) {
    $vars['block']->subject = '';
  }
}

/**
 * Abstract block rendering to be more flexible about when/how this happens.
 */
function _jump_menu_render_block($delta, $options = array()) {

  // Strip off jump_menu.
  $block_name = str_replace('jump_menu-', '', $delta);

  // Cache menu list.
  static $menus;
  if (!isset($menus)) {
    $menus = menu_get_menus(TRUE);
  }

  // Options are always available.
  $options['hide'] = isset($options['hide']) ? $options['hide'] : TRUE;

  // Allow setting active item.
  $settings = _jump_menu_get_settings($delta);

  // Allow showing a button via block UI.
  if ($settings['show_button']) {
    $options['hide'] = FALSE;
    $options['button'] = 'Go';
  }
  else {
    $options['hide'] = isset($options['hide']) ? $options['hide'] : TRUE;
    $options['button'] = FALSE;
  }

  // If a menu block.
  if (substr($block_name, 0, 2) == 'm_') {
    foreach ($menus as $k => $v) {

      // Block delta prefix length is 12 chars, leaves 20 for menu name.
      // Compare as much fits against the menu portion of the delta.
      if (substr($k, 0, 20) == substr($block_name, 2)) {

        // Set the title.
        $data['subject'] = check_plain($menus[$k]);

        // Set default 'choose' text to menu name.
        $options['choose'] = isset($options['choose']) ? $options['choose'] : check_plain($menus[$k]);
        $data['content'] = jump_menu($k, 0, $options['button'], 0, $options['choose'], $settings['show_current']);
      }
    }
  }
  elseif (substr($block_name, 0, 11) == 'local-tasks') {

    // Collect the local tasks.
    $links = menu_local_tasks(0);
    $links_secondary = menu_local_tasks(1);

    // Are there any real secondary tasks?
    $secondary = count($links_secondary['tabs']['output']) != 0 ? TRUE : FALSE;
    if ($links['tabs']['count'] > 0) {

      // Add plugin.
      ctools_include('jump-menu');
      $targets = array();

      // Create select list targets.
      foreach ($links['tabs']['output'] as $l) {
        if ($l['#link']['access'] == TRUE) {

          // Set active.
          $classes = isset($l['#active']) ? 'active' : '';
          $targets[] = array(
            'value' => url($l['#link']['href']),
            'title' => t($l['#link']['title']),
            '#attributes' => array(
              'class' => $classes,
            ),
          );

          // Do secondary tabs fit with this item?
          if ($secondary && $links_secondary['tabs']['output'][0]['#link']['tab_parent_href'] == $l['#link']['href']) {
            foreach ($links_secondary['tabs']['output'] as $sl) {

              // Set active.
              $classes = $l['#active'] ? 'active' : '';
              $targets[] = array(
                'value' => url($sl['#link']['href']),
                'title' => '- ' . t($sl['#link']['title']),
                '#attributes' => array(
                  'class' => $classes,
                ),
              );
            }
          }
        }
      }

      // Take options and place defaults.
      $options['choose'] = isset($options['choose']) ? $options['choose'] : t(JUMP_MENU_DEFAULT_CHOOSE);

      // Process setting active item.
      if ($settings['show_current']) {
        $current_path = base_path() . request_path();
        if (!empty($current_path)) {
          $options['default_value'] = $current_path;
        }
      }

      // Populate block.
      $data['subject'] = t('Local Tasks');
      $data['content'] = drupal_get_form('ctools_jump_menu', $targets, $options);
    }
    else {
      $data = FALSE;
    }
  }
  else {
    $notice = t('Something is wrong with the Jump Menu module, please report.');
    if (variable_get('error_level') > 0) {
      drupal_set_message($notice);
    }
    else {
      $message = 'Unable to render Jump Menu block. Likely due to a bad menu delta in the database.';
      watchdog('jump_menu', $message, array(), WATCHDOG_ERROR);
      drupal_set_message($notice);
    }
    $data = FALSE;
  }
  return $data;
}

/**
 * Utility.
 */
function _trim_name(&$value) {
  $value = $value + 1;
}

/**
 * Alter theme registration to allow overriding select theme function,
 * to allow for extra attributes within options through form API.
 * This is to add depth classes.
 */
function jump_menu_theme_registry_alter(&$theme_registry) {
  $theme_registry['select']['function'] = 'jump_menu_select';
}

/**
 * Override select theme function (points select theming to following function).
 */
function jump_menu_select($variables) {
  $element = $variables['element'];
  element_set_attributes($element, array(
    'id',
    'name',
    'size',
  ));
  _form_set_class($element, array(
    'form-select',
  ));

  // Provide alternate rendering path for jump menus.
  // To be careful, only altering jump menu selects, http://drupal.org/node/1512550
  $output = '<select' . drupal_attributes($element['#attributes']) . '>';

  // @todo This is a weak thing to test.
  if ($element['#attributes']['class'][0] == 'ctools-jump-menu-select') {
    $output .= jump_menu_form_select_options($element);
  }
  else {
    $output .= form_select_options($element);
  }
  $output .= '</select>';
  return $output;
}

/**
 * Provide alternate select options builder.
 * Taken from form_select_options() within includes/form.inc
 */
function jump_menu_form_select_options($element, $choices = NULL) {
  if (!isset($choices)) {
    $choices = $element['#options'];
  }

  // Using array_key_exists() accommodates the rare event where $element['#value'] is NULL.
  // Note that isset() fails in this situation.
  $value_valid = isset($element['#value']) || array_key_exists('#value', $element);
  $value_is_array = $value_valid && is_array($element['#value']);
  $options = '';
  foreach ($choices as $key => $choice) {

    // Allow overloading options with an array.
    if (is_array($choice)) {
      if (isset($choice['value'])) {

        // Overloaded option array.
        $opt_value = (string) $choice['value'];
      }
      else {
        if (isset($choice['title'])) {

          // Optgroup for special menu items.
          $options .= '<optgroup label="' . $choice['title'] . '"></optgroup>';
        }
        else {

          // Normal optgroups.
          $options .= '<optgroup label="' . $key . '">';
          $options .= form_select_options($element, $choice);
          $options .= '</optgroup>';
        }
      }
    }
    else {

      // Simple options.
      $opt_value = $key;
      $choice = array(
        'title' => $choice,
      );
    }

    // Create the HTML output.
    if (isset($opt_value)) {
      if (!isset($choice['#attributes'])) {
        $choice['#attributes'] = array();
      }

      // Note this makes the first item no longer selected, but that doesn't matter.
      if ($value_valid && (!$value_is_array && (string) $element['#value'] === $opt_value || $value_is_array && in_array($opt_value, $element['#value']))) {
        $selected = ' selected="selected"';
      }
      else {
        $selected = '';
      }
      $options .= '<option value="' . check_plain($opt_value) . '"' . $selected . drupal_attributes($choice['#attributes']) . '>' . check_plain($choice['title']) . '</option>';
    }
    unset($opt_value);
  }
  return $options;
}

/**
 * Implements hook_form_FORM_ID_alter().
 * Add custom options to block editing forms.
 */
function jump_menu_form_block_admin_configure_alter(&$form, &$form_state, $form_id) {
  if ($form['module']['#value'] == 'jump_menu') {
    $delta = $form['delta']['#value'];
    $settings = variable_get('jump_menu_block_settings', array());

    // Add jump menu fieldset with options.
    $form['jump_menu_options'] = array(
      '#type' => 'fieldset',
      '#title' => t('Jump Menu'),
      '#weight' => 1,
      // Show current page selected.
      'jump_menu_show_current' => array(
        '#type' => 'checkbox',
        '#title' => t('Display Curent Location'),
        '#description' => t('Set the menu item to the currently active page location.'),
        '#default_value' => isset($settings[$delta]['show_current']) ? $settings[$delta]['show_current'] : JUMP_MENU_BLOCK_DEFAULTS_SHOW_CURRENT,
      ),
      // Show Go button.
      'jump_menu_show_button' => array(
        '#type' => 'checkbox',
        '#title' => t('Display Go Button'),
        '#description' => t('Show button on jump menu.'),
        '#default_value' => isset($settings[$delta]['show_button']) ? $settings[$delta]['show_button'] : JUMP_MENU_BLOCK_DEFAULTS_SHOW_CURRENT,
      ),
    );

    // Attach a submit handler to save our preferences.
    $form['#submit'][] = 'jump_menu_block_settings_submit';
  }
}

/**
 * Submit handler to save block-specific jump menu settings.
 * Keeps the variable clean and tidy!
 */
function jump_menu_block_settings_submit($form, &$form_state) {
  $delta = $form_state['values']['delta'];
  $settings = variable_get('jump_menu_block_settings', array());
  $update_needed = FALSE;
  $system_defaults = array(
    'show_current' => JUMP_MENU_BLOCK_DEFAULTS_SHOW_CURRENT,
    'show_button' => JUMP_MENU_BLOCK_DEFAULTS_SHOW_BUTTON,
  );

  // Check all settings to clear out array if just default.
  foreach ($system_defaults as $config => $val) {

    // NOTE: Separated routes due to PHP notices with empty comparisons.
    // Also, every save shouldn't trigger a variable_set().
    // Config IS empty (new save).
    if (!isset($settings[$delta][$config])) {

      // Submission IS NOT default.
      if ($form_state['values']['jump_menu_' . $config] != $val) {
        $settings[$delta][$config] = $form_state['values']['jump_menu_' . $config];
        $update_needed = TRUE;
      }
      else {

        // Submission IS default, do nothing.
      }
    }
    else {

      // Submission DOES NOT match what was set previously.
      if ($form_state['values']['jump_menu_' . $config] != $settings[$delta][$config]) {
        $update_needed = TRUE;

        // Submission matches system default (set back to default).
        if ($form_state['values']['jump_menu_' . $config] == $val) {

          // Delete it to remove setting.
          unset($settings[$delta][$config]);
        }
        else {

          // Collect the new configuration.
          $settings[$delta][$config] = $form_state['values']['jump_menu_' . $config];
        }
      }
    }

    // No there is meaningful data for this block, but it's stored.
    if (isset($settings[$delta]) && count($settings[$delta]) == 0) {
      unset($settings[$delta]);
      $update_needed = TRUE;
    }
  }

  // Update the block settings variable if necessary.
  if ($update_needed) {
    variable_set('jump_menu_block_settings', $settings);
  }
}

/**
 * Utility: Grab settings or defaults, for display or settings form.
 */
function _jump_menu_get_settings($delta) {
  $settings = variable_get('jump_menu_block_settings', array());
  return array(
    'show_current' => isset($settings[$delta]['show_current']) ? $settings[$delta]['show_current'] : JUMP_MENU_BLOCK_DEFAULTS_SHOW_CURRENT,
    'show_button' => isset($settings[$delta]['show_button']) ? $settings[$delta]['show_button'] : JUMP_MENU_BLOCK_DEFAULTS_SHOW_BUTTON,
  );
}

Functions

Namesort descending Description
jump_menu Output a core menu as a select jump menu.
jump_menu_block_info Register jump blocks for all menus.
jump_menu_block_settings_submit Submit handler to save block-specific jump menu settings. Keeps the variable clean and tidy!
jump_menu_block_view Display jump menu block.
jump_menu_block_view_alter Make use of block settings on display.
jump_menu_form_block_admin_configure_alter Implements hook_form_FORM_ID_alter(). Add custom options to block editing forms.
jump_menu_form_select_options Provide alternate select options builder. Taken from form_select_options() within includes/form.inc
jump_menu_preprocess_block For killing off the title.
jump_menu_select Override select theme function (points select theming to following function).
jump_menu_theme_registry_alter Alter theme registration to allow overriding select theme function, to allow for extra attributes within options through form API. This is to add depth classes.
_jump_menu_create_options Recursive menu to select option building.
_jump_menu_get_settings Utility: Grab settings or defaults, for display or settings form.
_jump_menu_render_block Abstract block rendering to be more flexible about when/how this happens.
_trim_name Utility.

Constants