'3', 'path' => drupal_get_path('module', 'views_menu_reference'), ); } /** * Returns the <$link_path>'s parent menu hierarchy. It describes the mlids * in each depth that may be direct (0) or indirect (1..*) parent. * The hierarchy is indexed by the depth and may contain the tree items * from more than one menu item, because multiple menu items's may belong * to one <$link_path>. * The depth index consists of a numeric (0..1..2...) and a plus'sed key * (0+..1+..2+) which represent the subtree items. * * The function is the public variant which makes use of caching to speed things * up on a second request. * * @param string $link_path * @return array */ function views_menu_reference_get_link_path_parents_hierarchy($link_path) { if (empty($link_path)) { // Return empty array if no link given. return array(); } $cache_key = 'views_menu_reference_get_link_path_parents_hierarchy:' . $link_path; $parents_hierarchy = &drupal_static($cache_key); if (!isset($parents_hierarchy)) { if ($cache = cache_get($cache_key)) { $parents_hierarchy = $cache->data; } else { $parents_hierarchy = _views_menu_reference_get_link_path_parents_hierarchy($link_path); cache_set($cache_key, $parents_hierarchy, 'cache'); } } return $parents_hierarchy; } /** * Private variant of views_menu_reference_get_link_path_parents_hierarchy(). * Contains the true logic behind the caching. * * @param string $link_path * @return array */ function _views_menu_reference_get_link_path_parents_hierarchy($link_path) { // Use the alias and source pathes because a link can directly link both. $link_pathes = array($link_path => $link_path); $link_path_alias = drupal_lookup_path('alias', $link_path); if ($link_path_alias !== FALSE) { $link_pathes[$link_path_alias] = $link_path_alias; } $link_path_source = drupal_lookup_path('source', $link_path); if ($link_path_source !== FALSE) { $link_pathes[$link_path_source] = $link_path_source; } if (empty($link_pathes)) { // No matching results in the menu tree at all. return array(); } // Get the <$link_path> menu items and their properties we need. $query = db_select('menu_links', 'ml'); $query->fields('ml', array('mlid', 'depth', 'p1', 'p2', 'p3', 'p4', 'p5', 'p6', 'p7', 'p8', 'p9')); $query->condition('link_path', $link_pathes, 'IN'); $result = $query->execute(); $parents_hierarchy = array(); foreach ($result as $record) { // Process all pX Elements (not including sub menu entries!) $depth = $record->depth; $i = 0; while ($depth > 0) { $parent_field = 'p' . $depth; $parent_field_value = $record->$parent_field; if (!empty($parent_field_value)) { // We index the fields by their value so we ensure that no item is added twice with high performance. // This truely is nothing else then $parents_hierarchy[$i][] PLUS unique. $parents_hierarchy[$i][$parent_field_value] = $parent_field_value; } $depth--; $i++; } // Process all pX+ Elements (including sub menu entries!) $depth = $record->depth; $i = 1; while ($i <= $depth) { $parent_field = 'p' . $i; $parent_field_value = $record->$parent_field; if (!empty($parent_field_value)) { // We index the fields by their value so we ensure that no item is added twice with high performance. // This truely is nothing else then $parents_hierarchy[$i][] PLUS unique. $parents_hierarchy[$i . '+'][$parent_field_value] = $parent_field_value; } $i++; } } return $parents_hierarchy; } /** * Implements hook_menu_update(). */ function views_menu_reference_menu_update($menu) { // Clear cache on menu items updates. _views_menu_reference_clear_caches(); } /** * Implements hook_node_update(). */ function views_menu_reference_node_update($node) { // Clear cache on node updates. _views_menu_reference_clear_caches(); } /** * Helper function to clear the caches of views_menu_reference, when they should * be invalidated. */ function _views_menu_reference_clear_caches() { // Remove all entries starting with the key from this module. cache_clear_all('views_menu_reference_get_link_path_parents_hierarchy:', 'cache', TRUE); } /** * Implements hook_field_info(). * Provides the description of the field. */ function views_menu_reference_field_info() { return array( 'field_views_menu_reference' => array( 'label' => t('Views Menu Reference Field'), 'description' => t('Creates a field for views menu references.'), 'default_widget' => 'field_views_menu_reference', 'default_formatter' => 'field_views_menu_reference_default', ), ); } /** * Implements hook_field_widget_info(). */ function views_menu_reference_field_widget_info() { return array( 'field_views_menu_reference' => array( 'label' => t('Views Menu Reference Field'), 'field types' => array('field_views_menu_reference'), 'settings' => array( 'menus' => array(), ), ), ); } /** * Implements hook_field_widget_form(). */ function views_menu_reference_field_widget_form(&$form, &$form_state, $field, $instance, $langcode, $items, $delta, $element) { $menus = &$instance['widget']['settings']['menus']; $menu_names = menu_get_menus(); $filtered_menus = array(); foreach ($menus as $key => $value) { if ($value) { $filtered_menus[$key] = $menu_names[$key]; } } // Get the list of allowed menus and their menu items. $items_raw = menu_parent_options($filtered_menus, array('mlid' => 0)); // Create the menu list. $menu_list = array( '' => t('None'), ); $menu_counter = -4; // Loop through the list of menu items and process them. foreach ($items_raw as $key => $name) { $id = explode(':', $key); if ($id[1] == 0) { // Has no key: Is a menu parent item. $menu_list[$menu_counter] = $name; $menu_counter--; } else { $menu_list[$id[1]] = $name; } } // The mlid selector. $element['mlid'] = array( '#type' => 'select', '#options' => $menu_list, '#title' => t('Menu item'), '#default_value' => isset($items[$delta]['mlid']) ? $items[$delta]['mlid'] : '', ); // Create the select list. $depth_list = array('0' => t('0 (Only the menu entry itself)')); for ($i = 1; $i <= 9; $i++) { $depth_list[$i] = t($i . ' (Only the entry)'); $depth_list[$i . '+'] = t($i . '+ (Incl. subentries)'); } // The depth selector $element['depth'] = array( '#type' => 'select', '#options' => $depth_list, '#title' => t('Depth'), '#default_value' => isset($items[$delta]['depth']) ? $items[$delta]['depth'] : '0', '#description' => t('The depth is being calculated from the selected menu item on. This makes it possible to select a whole menu level X levels under the selected element including or excluding sub menu items.'), ); // To prevent an extra required indicator, disable the required flag on the // base element since all the sub-fields are already required if desired. $element['#required'] = FALSE; return $element; } /** * Implements hook_field_validate(). */ function views_menu_reference_field_validate($entity_type, $entity, $field, $instance, $langcode, $items, &$errors) { foreach ($items as $delta => $item) { if (!empty($item['mlid'])) { if ((int) $item['mlid'] < 0) { $errors[$field['field_name']][$langcode][$delta][] = array( 'error' => 'views_menu_reference_mlid_menuparent', 'message' => t('%name: The menu item you selected is a whole menu tree. This is not allowed. Please select a menu item.', array('%name' => $instance['label'])), ); return; } $mlid = $item['mlid']; if (menu_link_load($mlid) === FALSE) { $errors[$field['field_name']][$langcode][$delta][] = array( 'error' => 'views_menu_reference_wrong_mlid', 'message' => t('%name: The menu item you selected does not exist.', array('%name' => $instance['label'])), ); } } } } /** * Implements hook_field_is_empty(). */ function views_menu_reference_field_is_empty($item, $field) { // The field is empty if mlid equals an empty string or is less than zerso. return $item['mlid'] === '' OR (int) $item['mlid'] < 0; } /** * Implements hook_field_widget_settings_form(). */ function views_menu_reference_field_widget_settings_form($field, $instance) { $form = array(); // Create these variables by reference. No need to increase memory usage just // because we want to write variables in a readable way. $widget = &$instance['widget']; $settings = &$widget['settings']; if ($widget['type'] == 'field_views_menu_reference') { $form['menus'] = array( '#type' => 'checkboxes', '#title' => t('Selectable menus'), '#default_value' => $settings['menus'], '#options' => menu_get_menus(), '#description' => t('Select which menus should be possible to refer to.'), '#weight' => -1, '#required' => TRUE, ); } return $form; } /** * Implements hook_field_presave(). */ function views_menu_reference_field_presave($entity_type, $entity, $field, $instance, $langcode, &$items) { foreach ($items as $delta => $item) { if (isset($item['views_menu_reference']['mlid'])) { $items[$delta]['mlid'] = $item['views_menu_reference']['mlid']; $items[$delta]['depth'] = $item['views_menu_reference']['depth']; } } } /** * Implements hook_field_formatter_info(). */ function views_menu_reference_field_formatter_info() { return array( 'field_views_menu_reference_default' => array( 'label' => t('Default'), 'field types' => array('field_views_menu_reference'), ), ); } /** * Implements hook_field_formatter_view(). */ function views_menu_reference_field_formatter_view($entity_type, $entity, $field, $instance, $langcode, $items, $display) { $element = array(); switch ($display['type']) { case 'field_views_menu_reference_default' : foreach ($items as $delta => $item) { if (isset($item['mlid'])) { $mlid = $item['mlid']; $menu_link = menu_link_load($mlid); $element[$delta]['#markup'] = l($menu_link['link_title'], $menu_link['link_path']) . ' (' . check_plain($item['mlid']) . '), ' . t('Depth') . ': ' . check_plain($item['depth']); } } break; } return $element; }