'Menu Trails', 'description' => 'Configure your menu trails.', 'page callback' => 'drupal_get_form', 'page arguments' => array('menutrails_settings_form'), 'access arguments' => array('administer site configuration'), 'type' => MENU_NORMAL_ITEM, ); return $items; } /** * Implementation of hook_init() * * Detect menutrails for non-node-view pages * * Currently supports og sub-pages only */ function menutrails_init() { // TODO: replace TRUE with settings check if (module_exists('og') && variable_get('menutrails_og_sub_pages', TRUE)) { if ($group = og_get_group_context()) { $item = menu_get_item(); if ($item['page_callback'] != 'node_page_view') { $item = menutrails_node_location($group); if ($item) { menu_set_item(NULL, $item); if (variable_get('menutrails_breadcrumbs', 1)) { $crumbs = menutrails_get_breadcrumbs(); $crumbs[] = l($group->title, 'node/'. $group->nid); drupal_set_breadcrumb($crumbs); } } } } } } /** * Implementation of hook_enable() * * Default menutrails to run after core/og modules for fuller control */ function menutrails_enable() { db_query("UPDATE {system} SET weight = 1 WHERE name = 'menutrails' AND type = 'module'"); } /*** * Implementation of hook_nodeapi * * This will evaluate individual nodes when being viewed and take the necessary * steps to set the active_trail for menus. * * This will retain menu state at the node/view level. For instance, forum nodes * would maintain an active trail to the forum menu item. * */ function menutrails_nodeapi(&$node, $op, $a3 = NULL, $page = FALSE) { if ($op == 'view' && $page == TRUE) { $item = menutrails_node_location($node); if ($item) { menu_set_item(NULL, $item); if (variable_get('menutrails_breadcrumbs', 1)) { drupal_set_breadcrumb(menutrails_get_breadcrumbs()); } } } } /** * Set Breadcrumbs based on active menu trail */ function menutrails_get_breadcrumbs() { $item = menu_get_item(); if (!$menu) { // give first priority to the selected menu $menu = variable_get('menutrails_menu', FALSE); if (!$menu) { $menu = db_result(db_query("SELECT menu_name FROM {menu_links} WHERE link_path = '%s' AND module = 'menu'", $item['href'])); } } $tree = menu_tree_page_data($menu); $crumbs = array(l(t('Home'), '')); _menutrails_recurse_crumbs($tree, $item, $crumbs); return $crumbs; } function _menutrails_recurse_crumbs($tree, $item, &$crumbs, $above = array()) { foreach ($tree as $menu_item) { if ($menu_item['link']['link_path'] == $item['href']) { foreach($above as $trail_item) { $crumbs[] = l($trail_item['link']['link_title'], $trail_item['link']['link_path']); } $crumbs[] = l($menu_item['link']['link_title'], $menu_item['link']['link_path']); break; } if (is_array($menu_item['below'])) { $above[] = $menu_item; _menutrails_recurse_crumbs($menu_item['below'], $item, $crumbs, $above); } } } // inspired by _menu_get_active_trail() function menutrails_node_location($node) { // this should only fire if the menu isn't already active $item = menu_get_item(); if (db_result(db_query("SELECT count(mlid) FROM {menu_links} WHERE link_path = '%s' AND module = 'menu'", $item['href'])) == 0) { $type_trails = variable_get('menutrails_node_types', array()); $href = $type_trails[$node->type] ? $type_trails[$node->type] : FALSE; $term_trails = variable_get('menutrails_terms', array()); foreach ($node->taxonomy as $term) { if ($term_trails[$term->tid]) { $href = $term_trails[$term->tid]; } } } else { return $item; // we may want to do some breadcrumbing } // organic groups support if (module_exists('og') && !empty($node->og_groups)) { $group = array_shift($node->og_groups); // we can only do one, so we take the first... if (variable_get('menutrails_og_group_menu', FALSE) != FALSE) { if (db_result(db_query("SELECT count(mlid) FROM {menu_links} WHERE link_path = '%s'", $item['href'])) == 0) { $href = 'node/'. $group; } } else { $group_trails = variable_get('menutrails_og_node', FALSE); if ($group_trails[$group] > 0) { $href = 'node/'. $group; } elseif(variable_get('menutrails_og_post_default', FALSE)) { $href = variable_get('menutrails_og_post_default', FALSE); } } } if ($href) { $item['href'] = $href; return $item; } return FALSE; } /*** * This implements the same functionality as the nodeapi, but for comment urls. * */ function menutrails_comment($comment, $op) { if ($op == 'form' && arg(0) == 'comment') { $node = node_load($comment['nid']['#value']); $item = menutrails_node_location($node); if ($item) { menu_set_item(NULL, $item); } } } /*** * Form function for settings * * This is where menutrails rules are set. The interface here could definitely * stand for some improvement. It's especially unhelpful for tagging * vocabularies with lots and lots of terms. */ function menutrails_settings_form() { $options = array('' => ''); $limit = _menu_parent_depth_limit($item); // Load up menutrail menu $menu = variable_get('menutrails_menu', 'primary-links'); $tree = menu_tree_all_data($menu, NULL); _menutrails_parents_recurse($tree, $menu, '--', $options, $item['mlid'], $limit); $form['description'] = array( '#type' => 'markup', '#weight' => '-10', '#value' => t('Use these settings to configure the "menu trails" for your nodes. This determines what menu items are activated when viewing an individual node. For instance, if you have a menu item for "Blog," you may want to have all blog posts fall under that menu.'), '#prefix' => '

', '#suffix' => '

', ); $form['menutrails_menu'] = array( '#type' => 'select', '#weight' => '-6', '#options' => menu_get_menus(), '#default_value' => variable_get('menutrails_menu', 'primary-links'), '#title' => t('Menutrails Menu'), '#description' => t('What menu are you most interested in assigning trails to? This menu will be used if there is ambiguity about what menu a node falls under.'), ); $form['menutrails_breadcrumbs'] = array( '#type' => 'checkbox', '#weight' => '-5', '#default_value' => variable_get('menutrails_breadcrumbs', 1), '#title' => t('Set breadcrumbs?'), '#description' => t('If checked, menutrails will also synchronize the default drupal breadcrumbs with the menu trails, again giving priority to the menu selected above.'), ); $form['order'] = array( '#type' => 'markup', '#weight' => '-1', '#value' => t('Menu trials are evaluated in the order they are shown below.'), '#prefix' => '

', '#suffix' => '

', ); $extra = module_invoke_all('menutrails_settings', $options); $form = array_merge($form, $extra); return system_settings_form($form); } /** * Implementation of menutrails_settings_api * * This api allows other modules to define their own menutrail behavior. * * @param $options * Options array to be used by other modules to define their own menutrails. * @return * A form element (or array) for the menutrails system settings form. * * Please define your input as a fieldset and do not assign a weight. This will * keep the groups of menutrails settings in order. */ function menutrails_menutrails_settings($options) { $form = array(); $node_types = node_get_types('names'); $node_trails = variable_get('menutrails_node_types', array()); $vocabs = taxonomy_get_vocabularies(); $term_trails = variable_get('menutrails_terms', array()); $form['menutrails_node_types'] = array( '#tree' => TRUE, '#type' => 'fieldset', '#collapsible' => TRUE, '#collapsed' => TRUE, '#title' => t('Node types'), ); foreach ($node_types as $key => $value) { $form['menutrails_node_types'][$key] = array('#type' => 'select', '#title' => t('Parent item for') ." $value", '#default_value' => $node_trails[$key], '#options' => $options, ); } foreach ($vocabs as $vocab) { if ($vocab->tags != 1) { // tagging gets out of hand too fast, so we disallow $form[$vocab->vid]['menutrails_terms'] = array( '#tree' => TRUE, '#type' => 'fieldset', '#collapsible' => TRUE, '#collapsed' => TRUE, '#title' => t('Categories:') ." $vocab->name", ); $terms = taxonomy_get_tree($vocab->vid); foreach ($terms as $term) { $form[$vocab->vid]['menutrails_terms'][$term->tid] = array('#type' => 'select', '#title' => t('Parent item for') ." $term->name", '#default_value' => $term_trails[$term->tid], '#options' => $options, ); } } } // organic groups support if (module_exists('og')) { $form['menutrails_og'] = array( '#type' => 'fieldset', '#collapsible' => TRUE, '#collapsed' => TRUE, '#title' => t('Organic groups'), '#description' => t('Settings for nodes withing Organic Groups: these override node and taxonomy settings'), ); $form['menutrails_og']['menutrails_og_sub_pages'] = array( '#type' => 'checkbox', '#title' => t('Use Group Node Menu Trail For Og Pages'), '#default_value' => variable_get('menutrails_og_sub_pages', TRUE), '#description' => t('Use menutrails present for an organic group node for OG sub pages (e.g. "manage subscription" or "members")'), ); $form['menutrails_og']['menutrails_og_post_default'] = array( '#type' => 'select', '#title' => t('Default menu trail for all nodes with group audience'), '#default_value' => variable_get('menutrails_og_post_default', 0), '#description' => t('Default menu trail for any posts in a group.'), '#options' => $options, ); $form['menutrails_og']['menutrails_og_node'] = array( '#type' => 'fieldset', '#collapsible' => TRUE, '#collapsed' => TRUE, '#tree' => TRUE, '#title' => t('Individual organic group trails'), '#description' => t('Specific trails for posts within specific groups'), ); $result = db_query('SELECT n.nid, n.title FROM {node} n INNER JOIN og ON n.nid = og.nid ORDER BY n.title'); $og_trails = variable_get('menutrails_og_node', array()); while ($node = db_fetch_object($result)) { $form['menutrails_og']['menutrails_og_node'][$node->nid] = array( '#type' => 'select', '#title' => 'Group '. $node->title, '#default_value' => $og_trails[$node->nid], '#options' => $options, ); } $form['menutrails_og']['menutrails_og_group_menu'] = array( '#type' => 'checkbox', '#title' => t('Use Group\'s Menu Item For Posts'), '#default_value' => variable_get('menutrails_og_group_menu', FALSE), '#description' => t('If a specific group node has an assigned menu item, use this as the trail for nodes which have that group as an audience. If present, this will override all other group settings.'), ); } return $form; } function menutrails_token_values($type, $object = NULL, $options = array()) { if ($type == 'node') { $node = $object; $mlid = db_result(db_query("SELECT mlid FROM {menu_links} WHERE link_path = '%s'", 'node/'. $node->nid)); if (!empty($mlid) || !empty($node->menu['mlid']) || !empty($node->menu['plid'])) { $menu_link = menu_link_load($mlid); $trail_raw = _menu_titles($menu_link, $node->nid); array_pop($trail_raw); // remove the node itself } $trail = array(); if (is_array($trail_raw)) { foreach ($trail_raw as $title) { $trail[] = check_plain($title); } $tokens['menu-trail-parents-raw'] = implode('/', $trail_raw); $tokens['menu-trail-parents'] = implode('/', $trail); } // return NULL in case there is no trail if (!isset($tokens['menu-trail-parents-raw'])) { $tokens['menu-trail-parents-raw'] = NULL; } if (!isset($tokens['menu-trail-parents'])) { $tokens['menu-trail-parents'] = NULL; } return $tokens; } } function menutrails_token_list($type = 'all') { if ($type == 'node' || $type == 'all') { $tokens['menutrails']['menu-trail-parents-raw'] = t("The menu trail leading up to but NOT including the node -- RAW"); $tokens['menutrails']['menu-trail-parents'] = t("The menu trail leading up to but NOT including the node"); return $tokens; } } /** * Inspired by _menu_parents_recurse() * * The same as above, except it delivers hrefs rather than coded ids */ function _menutrails_parents_recurse($tree, $menu_name, $indent, &$options, $exclude, $depth_limit) { foreach ($tree as $data) { if ($data['link']['depth'] > $depth_limit) { // Don't iterate through any links on this level. break; } if ($data['link']['mlid'] != $exclude && $data['link']['hidden'] >= 0) { $title = $indent .' '. truncate_utf8($data['link']['title'], 30, TRUE, FALSE); if (!$data['link']['hidden']) { $options[$data['link']['href']] = $title; } if ($data['below']) { _menutrails_parents_recurse($data['below'], $menu_name, $indent .'--', $options, $exclude, $depth_limit); } } } } /** * Recursion to find the top tree */ function _menutrails_recurse($tree, $href) { foreach ($tree as $link) { if ($link['link']['link_path'] == $href) { $found = $link; break; } if (is_array($link['below'])) { $found = _menutrails_recurse($link['below'], $href); } } return $found; } /*** * SUBSTITUTE THEME FUNCTION */ /*** * This is a substitute function for theme_links() * * The important difference is that we use the in_active_trail bit here to set * an "active" CSS class, which is what most themes (e.g. garland) use to * denote an active/open menu item. You should alter/override this as your * design needs dictate. * */ function phptemplate_links($links, $attributes = array('class' => 'links')) { $output = ''; if (count($links) > 0) { $output = ''; $num_links = count($links); $i = 1; foreach ($links as $key => $link) { $class = $key; // Add first, last and active classes to the list of links to help out themers. if ($i == 1) { $class .= ' first'; } if ($i == $num_links) { $class .= ' last'; } if (isset($link['href']) && ($link['href'] == $_GET['q'] || ($link['href'] == '' && drupal_is_front_page()))) { $class .= ' active'; } if (isset($link['href'])) { // add active class for containing
  • and if active-trail is set on the link itself if (strpos($link['attributes']['class'], 'active-trail') !== FALSE && strpos($class, 'active') === FALSE) { $class .= ' active'; $link['attributes']['class'] .= ' active'; } // Pass in $link as $options, they share the same keys. $link = l($link['title'], $link['href'], $link); } else if (!empty($link['title'])) { // Some links are actually not links, but we wrap these in for adding title and class attributes if (empty($link['html'])) { $link['title'] = check_plain($link['title']); } $span_attributes = ''; if (isset($link['attributes'])) { $span_attributes = drupal_attributes($link['attributes']); } $link = ''. $link['title'] .''; } $i++; $output .= ' $class)) .'>'; $output .= $link; $output .= "
  • \n"; } $output .= ''; } return $output; }