<?php
// $Id: nice_menus.module,v 1.62 2009/05/06 15:33:49 add1sun Exp $

/**
 * @file
 *  Module to enable CSS dropdown and flyout menus.
 *
 * Maintainer: Addison Berry (add1sun)
 * Originally written by Jake Gordon (jakeg)
 */

/**
 * Implementation of hook_help().
 */
function nice_menus_help($path, $arg) {
  $output = '';
  switch ($path) {
    case 'admin/settings/modules#description':
      $output .= t('Make drop down/flyout CSS menus for site and admin menus.');
      break;
    case 'admin/settings/nice_menus':
      $output .= t('<p>This is a simple module that enables the site to have drop down/flyout CSS menus for site and admin navigation.</p><p>Remember to activate and configure the menu blocks in !link</p>', array('!link' => l('admin/build/block', 'admin/build/block')));
      break;
  }
  return $output;
}

/**
 * Implementation of hook_form_alter().
 */
function nice_menus_form_alter(&$form, $form_state, $form_id) {
  switch ($form_id) {
    case 'system_theme_settings':

      // This is a global setting, so only insert the field
      // on the global settings page.
      if (arg(4)) {
        return;
      }

      // Have to add a custom submit handler since this form doesn't use
      // the standard system submit handler.
      $form['#submit'][] = 'nice_menus_system_theme_settings_submit';

      // Add global theme setting for a custom CSS file.
      $form['nice_menus_custom_css'] = array(
        '#type' => 'textfield',
        '#title' => t('Path to custom Nice menus CSS file'),
        '#description' => t('To override the default Nice menus CSS layout, enter the path to your custom CSS file.  It should be a relative path from the root of your Drupal install (e.g. sites/all/themes/example/mymenu.css).'),
        '#default_value' => variable_get('nice_menus_custom_css', ''),
        // Field appears below submit buttons without this -- yucky.
        '#weight' => 0,
      );
      break;
  }
}

/**
 * Records the Nice menu custom CSS file per theme.
 */
function nice_menus_system_theme_settings_submit($form, &$form_state) {
  variable_set('nice_menus_custom_css', $form_state['values']['nice_menus_custom_css']);
}

/**
 * Implementation of hook_menu().
 */
function nice_menus_menu() {
  $items['admin/settings/nice_menus'] = array(
    'title' => 'Nice menus',
    'description' => 'Configure Nice menus.',
    'page callback' => 'drupal_get_form',
    'page arguments' => array('nice_menus_admin_settings'),
    'access arguments' => array('administer site configuration'),
    'type' => MENU_NORMAL_ITEM,
  );

  return $items;
}


/**
 * Settings form as implemented by hook_menu
 */
function nice_menus_admin_settings() {
  $form['nice_menus_number'] = array(
    '#type' => 'textfield',
    '#description' => t('The total number of independent Nice menus blocks you want. Enter a number between 0 and 99. If you set this to 0, you will have no blocks created but you can still use the Nice menus theme functions directly in your theme.'),
    '#default_value' => variable_get('nice_menus_number', '2'),
    '#size' => 2,
  );
  $form['nice_menus_js'] = array(
    '#type' => 'checkbox',
    '#title' => t('Use JavaScript'),
    '#description' => t('This will add Superfish Jquery to Nice menus. This is required for Nice menus to work properly in Internet Explorer.'),
    '#default_value' => variable_get('nice_menus_js', 1),
  );
  $form['nice_menus_sf_options'] = array(
    '#type' => 'fieldset',
    '#title' => t('Advanced: Superfish options'),
    '#description' => t('You can change the default Superfish options by filling out the desired values here. These only take effect if the Use JavaScript box above is checked.'),
    '#collapsible' => TRUE,
    '#collapsed' => TRUE,
  );
  $form['nice_menus_sf_options']['nice_menus_sf_delay'] = array(
    '#type' => 'textfield',
    '#title' => t('Mouse delay'),
    '#description' => t('The delay in milliseconds that the mouse can remain outside a submenu without it closing.'),
    '#default_value' => variable_get('nice_menus_sf_delay', 800),
    '#size' => 5,
  );
  $form['nice_menus_sf_options']['nice_menus_sf_speed'] = array(
    '#type' => 'select',
    '#title' => t('Animation speed'),
    '#multiple' => FALSE,
    '#description' => t('Speed of the menu open/close animation.'),
    '#options' => array(t('slow'), t('normal'), t('fast')),
    '#default_value' => variable_get('nice_menus_sf_speed', 1),
     );

  // Custom validation to make sure the user is entering numbers.
  $form['#validate'][] = 'nice_menus_settings_validate';

  return system_settings_form($form);
}

/**
 * Custom validation for the settings form.
 */
function nice_menus_settings_validate($form, &$form_state) {
  $number = $form_state['values']['nice_menus_number'];
  // Check to make sure it is a number and that is a maximum of 2 digits.
  if (!is_numeric($number) || strlen($number) > 2) {
      form_set_error('nice_menus_number', t('You must enter a number from 0 to 99.'));
  }
}

/**
 * Implementation of hook_block().
 */
function nice_menus_block($op = 'list', $delta = 0, $edit = array()) {
  global $user;

  switch ($op) {
    case 'list':
      for ($i = 1; $i <= variable_get('nice_menus_number', '2'); $i++) {
        $blocks[$i]['info'] = variable_get('nice_menus_name_'. $i, 'Nice menu '. $i) .' (Nice menu)';
        // We have too many things changing per user, per page to cache.
        $blocks[$i]['cache'] = BLOCK_NO_CACHE;
      }
      return $blocks;
    break;

    case 'configure':
      $form['nice_menus_name_'. $delta] = array(
        '#type' => 'textfield',
        '#title' => t('Menu Name'),
        '#default_value' => variable_get('nice_menus_name_'. $delta, 'Nice menu '. $delta),
      );
      $form['nice_menus_menu_'. $delta] = array(
        '#type' => 'select',
        '#title' => t('Menu Parent'),
        '#description' => t('The menu parent from which to show a Nice menu.'),
        '#default_value' => variable_get('nice_menus_menu_'. $delta, 'navigation:0'),
        '#options' => menu_parent_options(menu_get_menus(), 0),
      );
      $form['nice_menus_depth_'. $delta] = array(
       '#type' => 'select',
       '#title' => t('Menu Depth'),
       '#description' => t('The depth of the menu, i.e. the number of child levels starting with the parent selected above. Leave set to -1 to display all children and use 0 to display no children.'),
       '#default_value' => variable_get('nice_menus_depth_'. $delta, -1),
       '#options' => drupal_map_assoc(range(-1, 5))
      );
      $form['nice_menus_type_'. $delta] = array(
        '#type' => 'select',
        '#title' => t('Menu Style'),
        '#description' => t('right: menu items are listed on top of each other and expand to the right') .'<br />'. t('left: menu items are listed on top of each other and expand to the left') .'<br />'. t('down: menu items are listed side by side and expand down'),
        '#default_value' => variable_get('nice_menus_type_'. $delta, 'right'),
        '#options' => drupal_map_assoc(array('right', 'left', 'down')),
      );
      return $form;
    break;

    case 'save':
      variable_set('nice_menus_name_'. $delta, $edit['nice_menus_name_'. $delta]);
      variable_set('nice_menus_menu_'. $delta, $edit['nice_menus_menu_'. $delta]);
      variable_set('nice_menus_depth_'. $delta, $edit['nice_menus_depth_'. $delta]);
      variable_set('nice_menus_type_'. $delta, $edit['nice_menus_type_'. $delta]);
    break;

    case 'view':
      // Build the Nice menu for the block.
      list($menu_name, $mlid) = explode(':', variable_get('nice_menus_menu_'. $delta, 'navigation:0'));
      $direction = variable_get('nice_menus_type_'. $delta, 'right');
      $depth = variable_get('nice_menus_depth_'. $delta, '-1');
      if ($output = theme('nice_menus', $delta, $menu_name, $mlid, $direction, $depth)) {
        $block['content'] = $output['content'];
        if (variable_get('nice_menus_type_'. $delta, 'right') == 'down') {
          $class = 'nice-menu-hide-title';
        }
        else {
          $class = 'nice-menu-show-title';
        }
        // If we're building the navigation block
        // use the same block title logic as menu module.
        if ($output['subject'] == t('Navigation') && $user->uid) {
          $subject = $user->name;
        }
        else {
          $subject = $output['subject'];
        }
        $block['subject'] = '<span class="'. $class .'">'. check_plain($subject) .'</span>';
      }
      else {
        $block['content'] = FALSE;
      }
      return $block;
    break;
  }
}

/**
 * Implementation of hook_theme().
 */
function nice_menus_theme() {
  return array(
    'nice_menus_tree' => array(
      'arguments' => array('menu_name' => NULL, 'mlid' => NULL, 'depth' => -1, 'menu' => NULL),
    ),
    'nice_menus_build' => array(
      'arguments' => array('menu' => NULL,  'depth' => -1, 'trail' => NULL),
    ),
    'nice_menus' => array(
      'arguments' => array('id' => NULL, 'menu_name' => NULL, 'mlid' => NULL, 'direction' => 'right', 'depth' => -1, 'menu' => NULL),
    ),
    'nice_menus_primary_links' => array(
      'arguments' => array('direction' => 'down', 'depth' => -1, 'menu' => NULL),
    ),
    'nice_menus_secondary_links' => array(
      'arguments' => array('direction' => 'down', 'depth' => -1, 'menu' => NULL),
    ),
    // Deprecated theme functions that will be removed in 7.x.
    'nice_menu_tree' => array(
      'arguments' => array('menu_name' => NULL, 'mlid' => NULL, 'depth' => -1, 'menu' => NULL),
    ),
    'nice_menu_build' => array(
      'arguments' => array('menu' => NULL,  'depth' => -1, 'trail' => NULL),
    ),
    'nice_menu' => array(
      'arguments' => array('id' => NULL, 'menu_name' => NULL, 'mlid' => NULL, 'direction' => 'right', 'depth' => -1, 'menu' => NULL),
    ),
    'nice_menu_primary_links' => array(
      'arguments' => array('direction' => 'down', 'depth' => -1, 'menu' => NULL),
    ),
  );
}

/**
 * Implementation of hook_init().
 *
 * We are adding the JavaScript and CSS here rather than theme_nice_menu
 * because when block caching is enabled none of it would get fired
 * and the menus are unstyled.
 */
function nice_menus_init() {
  // Add Superfish JavaScript, if enabled.
  if (variable_get('nice_menus_js', 1) == 1) {
    // The script, from http://users.tpg.com.au/j_birch/plugins/superfish.
    drupal_add_js(drupal_get_path('module', 'nice_menus') .'/superfish/js/superfish.js');
    // Add the Superfish options variables.
    drupal_add_js(array(
      'nice_menus_options' => array(
        'delay' => variable_get('nice_menus_sf_delay', 800),
        'speed' => variable_get('nice_menus_sf_speed', 1),
        )),'setting');
    // Add the bgIframe plugin.
    drupal_add_js(drupal_get_path('module', 'nice_menus') .'/superfish/js/jquery.bgiframe.min.js');
    // The Nice menus implementation.
    drupal_add_js(drupal_get_path('module', 'nice_menus') .'/nice_menus.js');
  }

  // Add main CSS functionality.
  drupal_add_css(drupal_get_path('module', 'nice_menus') .'/nice_menus.css');
  // Add custom CSS layout if specified.
  if ($custom = variable_get('nice_menus_custom_css', '')) {
    drupal_add_css($custom);
  }
  // Fall back to default layout.
  else {
    drupal_add_css(drupal_get_path('module', 'nice_menus') .'/nice_menus_default.css');
  }
}

/**
 * Builds the active trail from the page's menu data.
 *
 * @param $page_menu
 *   The menu data for a page.
 * @return
 *   An array of parent menu item ids.
 */
function nice_menus_build_page_trail($page_menu) {
  $trail = array();
 foreach ($page_menu as $item) {
    if ($item['link']['in_active_trail']) {
      $trail[] = $item['link']['mlid'];
    }
    if ($item['below']) {
      $trail = array_merge($trail, nice_menus_build_page_trail($item['below']));
    }
  }
  return $trail;
}

/**
 * Builds the final Nice menu.
 *
 * @param $menu_name
 *   The top-level menu name that contains the menu to use (e.g. navigation
 *   or primary-links) for Drupal menus. For custom $menus this is just the
 *   name for menu display.
 * @param $mlid
 *   The menu ID from which to start building the items, i.e. the parent
 *   of the displayed menu.
 * @param $depth
 *   The number of children levels to display. Use -1 to display all children
 *   and use 0 to display no children.
 * @param $menu
 *   Optional. A custom menu array to use for theming -- it should have
 *   the same structure as that returned by menu_tree_all_data().
 * @return
 *   An HTML string of properly nested Nice menu lists.
 */
function theme_nice_menus_tree($menu_name, $mlid = NULL, $depth = -1, $menu = NULL) {
  // Load the full menu array.
  $menu = isset($menu) ? $menu : menu_tree_all_data($menu_name);

  if (isset($menu)) {
    $page_menu = menu_tree_page_data($menu_name);
    $trail = nice_menus_build_page_trail($page_menu);
    unset($page_menu);
  }

  // Allow i18n module to translate strings where available.
  if(module_exists('i18nmenu')) {
    i18nmenu_localize_tree($menu);
  }

  // For custom $menus and menus built all the way from the top-level we
  // don't need to "create" the specific sub-menu and we need to get the title
  // from the $menu_name since there is no "parent item" array.

  // Create the specific menu if we have a mlid.
  if (!empty($mlid)) {
    // Load the parent menu item.
    $item = menu_link_load($mlid);
    $title = check_plain($item['title']);
    // The depth for our parent item.
    $parent_depth = $item['depth'];

    // Narrow down the full menu to the specific sub-tree we need.
    for ($p = 1; $p < 10; $p++) {
      if ($sub_mlid = $item["p$p"]) {
        $subitem = menu_link_load($sub_mlid);
        // Menu sets these ghetto-ass keys in _menu_tree_check_access().
        $menu = $menu[(50000 + $subitem['weight']) .' '. $subitem['title'] .' '. $subitem['mlid']]['below'];
      }
    }
  }
  // Otherwise just set a title and move on.
  else {
    // Get the title from the DB since we don't have it in the $menu.
    $result = db_result(db_query("SELECT title FROM {menu_custom} WHERE menu_name = '%s'", $menu_name));
    $title = check_plain($result);
  }

  $output['content'] = '';
  $output['subject'] = $title;

  if ($menu) {
    // Set the total menu depth counting from this parent if we need it.
    $depth =  ($depth > 0) ? ($parent_depth + $depth) : $depth;
    $output['content'] .= theme('nice_menus_build', $menu, $depth, $trail);
  }

  return $output;
}

/**
 * Helper function that builds the nested lists of a Nice menu.
 *
 * @param $menu
 *   Menu array from which to build the nested lists.
 * @param $depth
 *   The number of children levels to display. Use -1 to display all children
 *   and use 0 to display no children.
 * @param $trail
 *   An array of parent menu items.
 */
function theme_nice_menus_build($menu, $depth = -1, $trail = NULL) {
  $output = '';
  foreach ($menu as $menu_item) {
    $mlid = $menu_item['link']['mlid'];
    // Check to see if it is a visible menu item.
    if ($menu_item['link']['hidden'] == 0) {
      // Build class name based on menu path
      // e.g. to give each menu item individual style.
      // Strip funny symbols.
      $clean_path = str_replace(array('http://', 'www', '<', '>', '&', '=', '?', ':'), '', $menu_item['link']['href']);
      // Convert slashes to dashes.
      $clean_path = str_replace('/', '-', $clean_path);
      $class = 'menu-path-'. $clean_path;
      if ($trail && in_array($mlid, $trail)) {
        $class .= ' active-trail';
      }
      // If it has children build a nice little tree under it.
      if ((!empty($menu_item['link']['has_children'])) && (!empty($menu_item['below'])) && $depth != 0) {
        // Keep passing children into the function 'til we get them all.
        $children = theme('nice_menus_build', $menu_item['below'], $depth, $trail);
        // Set the class to parent only of children are displayed.
        $parent_class = $children ? 'menuparent ' : '';
        $output .= '<li id="menu-'. $mlid .'" class="'. $parent_class . $class .'">'. theme('menu_item_link', $menu_item['link']);
        // Check our depth parameters.
        if ($menu_item['link']['depth'] <= $depth || $depth == -1) {
          // Build the child UL only if children are displayed for the user.
          if ($children) {
            $output .= '<ul>';
            $output .= $children;
            $output .= "</ul>\n";
          }
        }
        $output .= "</li>\n";
      }
      else {
        $output .= '<li id="menu-'. $mlid .'" class="'. $class .'">'. theme('menu_item_link', $menu_item['link']) .'</li>'."\n";
      }
    }
  }
  return $output;
}

/**
 * Theme function to allow any menu tree to be themed as a Nice menu.
 *
 * @param $id
 *   The Nice menu ID.
 * @param $menu_name
 *   The top parent menu name from which to build the full menu.
 * @param $mlid
 *   The menu ID from which to build the displayed menu.
 * @param $direction
 *   Optional. The direction the menu expands. Default is 'right'.
 * @param $depth
 *   The number of children levels to display. Use -1 to display all children
 *   and use 0 to display no children.
 * @param $menu
 *   Optional. A custom menu array to use for theming --
 *   it should have the same structure as that returned
 *  by menu_tree_all_data(). Default is the standard menu tree.
 * @return
 *   An HTML string of Nice menu links.
 */
function theme_nice_menus($id, $menu_name, $mlid, $direction = 'right', $depth = -1, $menu = NULL) {
  $output = array();

  if ($menu_tree = theme('nice_menus_tree', $menu_name, $mlid, $depth, $menu)) {
    if ($menu_tree['content']) {
      $output['content'] = '<ul class="nice-menu nice-menu-'. $direction .'" id="nice-menu-'. $id .'">'. $menu_tree['content'] .'</ul>'."\n";
      $output['subject'] = $menu_tree['subject'];
    }
  }
  return $output;
}

/**
 * Theme primary links as Nice menus.
 *
 * @param $direction
 *   Optional. The direction the menu expands. Default is 'down'.
 * @param $depth
 *   The number of children levels to display. Use -1 to display all children
 *   and use 0 to display no children.
 * @param $menu
 *   Optional. A custom menu array to use for theming --
 *   it should have the same structure as that returned
 *   by menu_tree_all_data(). Default is the standard menu tree.
 * @return
 *   An HTML string of Nice menu primary links.
 */
function theme_nice_menus_primary_links($direction = 'down', $depth = -1, $menu = NULL) {
  $menu_name = variable_get('menu_primary_links_source', 'primary-links');
  $output = theme('nice_menus', 0, $menu_name, 0, $direction, $depth, $menu);
  return $output['content'];
}

/**
 * Theme secondary links as nice menus.
 *
 * @param $direction
 *   Optional. The direction the menu expands. Default is 'down'.
 * @param $depth
 *   The number of children levels to display. Use -1 to display all children
 *   and use 0 to display no children.
 * @param $menu
 *   Optional. A custom menu array to use for theming --
 *   it should have the same structure as that returned
 *   by menu_tree_all_data(). Default is the standard menu tree.
 * @return
 *   An HTML string of nice menu secondary links.
 */
function theme_nice_menus_secondary_links($direction = 'down', $depth = -1, $menu = NULL) {
  $menu_name = variable_get('menu_secondary_links_source', 'secondary-links');
  $output = theme('nice_menus', 0, $menu_name, 0, $direction, $depth, $menu);
  return $output['content'];
}

/*********** Legacy Theme Functions that will be removed in 7.x ************/

/**
 * DEPRECATED theme function. Please switch to theme_nice_menus_tree().
 */
function theme_nice_menu_tree($menu_name, $mlid = NULL, $depth = -1, $menu = NULL) {
  return theme('nice_menus_tree', $menu_name, $mlid = NULL, $depth = -1, $menu = NULL);
}

/**
 * DEPRECATED theme function. Please switch to theme_nice_menus_build().
 */
function theme_nice_menu_build($menu, $depth = -1, $trail = NULL) {
  return theme('nice_menus_build', $menu, $depth = -1, $trail = NULL);
}

/**
 * DEPRECATED theme function. Please switch to theme_nice_menus().
 */
function theme_nice_menu($id, $menu_name, $mlid, $direction = 'right', $depth = -1, $menu = NULL) {
  return theme('nice_menus', $id, $menu_name, $mlid, $direction = 'right', $depth = -1, $menu = NULL);
}

/**
 * DEPRECATED theme function. Please switch to
 * theme_nice_menus_primary_links().
 */
function theme_nice_menu_primary_links($direction = 'down', $depth = -1, $menu = NULL) {
  return theme('nice_menus_primary_links', $direction = 'down', $depth = -1, $menu = NULL);
}
