hs_menu.module 9.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326
  1. <?php
  2. /**
  3. * @file
  4. * Implementation of the Hierarchical Select API for the Menu module.
  5. */
  6. //----------------------------------------------------------------------------
  7. // Drupal core hooks.
  8. /**
  9. * Implements of hook_menu().
  10. */
  11. function hs_menu_menu() {
  12. $items['admin/config/content/hierarchical_select/menu'] = array(
  13. 'title' => 'Menu',
  14. 'description' => 'Hierarchical Select configuration for Menu',
  15. 'access arguments' => array('administer site configuration'),
  16. 'page callback' => 'drupal_get_form',
  17. 'page arguments' => array('hs_menu_admin_settings'),
  18. 'type' => MENU_LOCAL_TASK,
  19. );
  20. return $items;
  21. }
  22. /**
  23. * Implements hook_form_FORMID_alter().
  24. *
  25. * Alter the node form's menu form.
  26. */
  27. function hs_menu_form_node_form_alter(&$form, &$form_state) {
  28. $active_types = array_filter(variable_get('hs_menu_content_types', array()));
  29. $active = empty($active_types) || in_array($form_state['node']->type, $active_types);
  30. if ($active && isset($form['menu']['link']['parent']) && isset($form['menu']['#access']) && $form['menu']['#access']) {
  31. unset($form['menu']['link']['parent']['#options']);
  32. $form['menu']['link']['parent']['#type'] = 'hierarchical_select';
  33. // Get menu name, needed to exclude current node.
  34. $menu_name = explode(':', $form['menu']['link']['parent']['#default_value']);
  35. _hs_menu_apply_config($form['menu']['link']['parent'], array(
  36. 0 => $menu_name[0],
  37. 1 => $form['menu']['link']['mlid']['#value'],
  38. 'type' => $form['type']['#value'],
  39. ));
  40. // Set custom submit callback.
  41. array_unshift($form['#submit'], 'hs_menu_node_form_submit');
  42. // Change the loaded default value into an array so we can populate the
  43. // Hierarchical Select element.
  44. $form['menu']['link']['parent']['#default_value'] = array($form['menu']['link']['parent']['#default_value']);
  45. }
  46. }
  47. /**
  48. * Implements hook_form_BASE_FORMID_alter().
  49. *
  50. * Alter the widget type form; dynamically add the Hierarchical Select
  51. * Configuration form when it is needed.
  52. */
  53. function hs_menu_form_menu_edit_item_alter(&$form, &$form_state) {
  54. unset($form['parent']['#options']);
  55. $original_item = $form['original_item']['#value'];
  56. $form['parent']['#type'] = 'hierarchical_select';
  57. _hs_menu_apply_config($form['parent'], array('exclude' => array(
  58. $original_item['menu_name'],
  59. $original_item['mlid'],
  60. )));
  61. // Set custom submit callback.
  62. array_unshift($form['#submit'], 'hs_menu_menu_edit_item_form_submit');
  63. }
  64. //----------------------------------------------------------------------------
  65. // Form API callbacks.
  66. /**
  67. * Submit callback; menu edit item form.
  68. */
  69. function hs_menu_menu_edit_item_form_submit(&$form, &$form_state) {
  70. // Don't return an array, but a single item.
  71. $form_state['values']['parent'] = $form_state['values']['parent'][0];
  72. }
  73. /**
  74. * Submit callback; node edit form.
  75. */
  76. function hs_menu_node_form_submit(&$form, &$form_state) {
  77. // Don't return an array, but a single item.
  78. $form_state['values']['menu']['parent'] = $form_state['values']['menu']['parent'][0];
  79. }
  80. //----------------------------------------------------------------------------
  81. // Menu callbacks.
  82. /**
  83. * Form definition; admin settings.
  84. */
  85. function hs_menu_admin_settings() {
  86. $form['hs_menu_resizable'] = array(
  87. '#type' => 'radios',
  88. '#title' => t('Resizable'),
  89. '#description' => t(
  90. "When enabled, a handle appears below the Hierarchical Select to allow
  91. the user to dynamically resize it. Double clicking will toggle between
  92. the smallest and a sane 'big size'."
  93. ),
  94. '#options' => array(
  95. 0 => t('Disabled'),
  96. 1 => t('Enabled'),
  97. ),
  98. '#default_value' => variable_get('hs_menu_resizable', 1),
  99. );
  100. $form['hs_menu_content_types'] = array(
  101. '#type' => 'checkboxes',
  102. '#title' => t('Content types'),
  103. '#description' => t("Select the content types to use Hierarchical Select Menu on. If no content types are selected, then it will apply to all content types."),
  104. '#options' => node_type_get_names(),
  105. '#default_value' => variable_get('hs_menu_content_types', array()),
  106. );
  107. return system_settings_form($form);
  108. }
  109. //----------------------------------------------------------------------------
  110. // Hierarchical Select hooks.
  111. /**
  112. * Implements hook_hierarchical_select_params().
  113. */
  114. function hs_menu_hierarchical_select_params() {
  115. $params = array(
  116. 'exclude', // The menu_name and mlid (in an array) of a menu link that should be excluded from the hierarchy.
  117. );
  118. return $params;
  119. }
  120. /**
  121. * Implements hook_hierarchical_select_root_level().
  122. */
  123. function hs_menu_hierarchical_select_root_level($params) {
  124. $menus = array();
  125. $result = db_query("SELECT menu_name, title FROM {menu_custom} ORDER BY title");
  126. // If the type is set, respect the core menu options setting.
  127. if (isset($params['type'])) {
  128. $type_menus = variable_get('menu_options_' . $params['type'], array('main-menu' => 'main-menu'));
  129. while ($menu = $result->fetchObject()) {
  130. if (in_array($menu->menu_name, $type_menus)) {
  131. $menus[$menu->menu_name . ':0'] = $menu->title;
  132. }
  133. }
  134. }
  135. // Fall back to the legacy approach, show all menu's.
  136. else {
  137. while ($menu = $result->fetchObject()) {
  138. $menus[$menu->menu_name . ':0'] = $menu->title;
  139. }
  140. }
  141. return $menus;
  142. }
  143. /**
  144. * Implements hook_hierarchical_select_children().
  145. */
  146. function hs_menu_hierarchical_select_children($parent, $params) {
  147. $children = array();
  148. list($menu_name, $plid) = explode(':', $parent);
  149. $tree = menu_tree_all_data($menu_name, NULL);
  150. return _hs_menu_children($tree, $menu_name, $plid, $params['exclude']);
  151. }
  152. /**
  153. * Implements hook_hierarchical_select_lineage().
  154. */
  155. function hs_menu_hierarchical_select_lineage($item, $params) {
  156. $lineage = array($item);
  157. list($menu_name, $mlid) = explode(':', $item);
  158. // If the initial mlid is zero, then this is the root level, so we don't
  159. // have to get the lineage.
  160. if ($mlid > 0) {
  161. // Prepend each parent mlid (i.e. plid) to the lineage.
  162. do {
  163. $plid = db_query("SELECT plid FROM {menu_links} WHERE mlid = :mlid", array(':mlid' => $mlid))->fetchField();
  164. array_unshift($lineage, "$menu_name:$plid");
  165. if ($mlid == $plid) {
  166. // Somehow we have an infinite loop situation. Bail out of the loop.
  167. break;
  168. }
  169. $mlid = $plid;
  170. } while ($plid > 0);
  171. }
  172. return $lineage;
  173. }
  174. /**
  175. * Implements hook_hierarchical_select_valid_item().
  176. */
  177. function hs_menu_hierarchical_select_valid_item($item, $params) {
  178. $parts = explode(':', $item);
  179. $valid = TRUE;
  180. // Validate menu name.
  181. $valid = (array_key_exists($parts[0], menu_get_menus()));
  182. // Validate hierarchy of mlids.
  183. for ($i = 1; $valid && $i < count($parts); $i++) {
  184. $valid = $valid && is_numeric($parts[$i]);
  185. }
  186. // Ensure that this isn't the excluded menu link.
  187. $valid = $valid && $item != $params['exclude'][0] . $params['exclude'][1];
  188. return $valid;
  189. }
  190. /**
  191. * Implements hook_hierarchical_select_item_get_label().
  192. */
  193. function hs_menu_hierarchical_select_item_get_label($item, $params) {
  194. static $labels = array();
  195. $parts = explode(':', $item);
  196. if (count($parts) == 1) { // Get the menu name.
  197. $menu_name = $parts[0];
  198. $labels[$item] = db_query("SELECT title FROM {menu_custom} WHERE menu_name = :menu_name", array(':menu_name' => $menu_name))->fetchField();
  199. }
  200. else { // Get the menu link title.
  201. $mlid = end($parts);
  202. $menu_link = menu_link_load($mlid);
  203. $labels[$item] = $menu_link['title'];
  204. }
  205. return $labels[$item];
  206. }
  207. /**
  208. * Implements hook_hierarchical_select_implementation_info().
  209. */
  210. function hs_menu_hierarchical_select_implementation_info() {
  211. return array(
  212. 'hierarchy type' => t('Menu'),
  213. 'entity type' => t('N/A'),
  214. );
  215. }
  216. //----------------------------------------------------------------------------
  217. // Private functions.
  218. /**
  219. * Recursive helper function for hs_menu_hierarchical_select_children().
  220. */
  221. function _hs_menu_children($tree, $menu_name, $plid = 0, $exclude = FALSE) {
  222. $children = array();
  223. foreach ($tree as $data) {
  224. if ($data['link']['plid'] == $plid && $data['link']['hidden'] >= 0) {
  225. if ($exclude && $data['link']['menu_name'] === $exclude[0] && $data['link']['mlid'] == $exclude[1]) {
  226. continue;
  227. }
  228. $title = truncate_utf8($data['link']['title'], 30, TRUE, FALSE);
  229. if ($data['link']['hidden']) {
  230. $title .= ' (' . t('disabled') . ')';
  231. }
  232. $children[$menu_name . ':' . $data['link']['mlid']] = $title;
  233. if ($data['below']) {
  234. $children += _hs_menu_children($data['below'], $menu_name, $plid, $exclude);
  235. }
  236. }
  237. elseif ($data['below']) {
  238. $children += _hs_menu_children($data['below'], $menu_name, $plid, $exclude);
  239. }
  240. }
  241. return $children;
  242. }
  243. /**
  244. * Helper function to apply the HS config to a form item.
  245. */
  246. function _hs_menu_apply_config(&$form, $params) {
  247. // The following is to ensure via javascript self is not listed.
  248. if (!empty($params['exclude'])) {
  249. $params['exclude'] = $params['exclude'][0] .':'. $params['exclude'][1];
  250. drupal_add_js('jQuery(document).ready(function () {
  251. jQuery("[value*=\"' . $params['exclude'] . '\"]").hide();
  252. });', 'inline');
  253. }
  254. $form['#config'] = array(
  255. 'module' => 'hs_menu',
  256. 'params' => array(
  257. 'exclude' => isset($params['exclude']) ? $params['exclude'] : NULL,
  258. 'type' => isset($params['type']) ? $params['type'] : NULL,
  259. ),
  260. 'save_lineage' => 0,
  261. 'enforce_deepest' => 0,
  262. 'resizable' => variable_get('hs_menu_resizable', 1),
  263. 'level_labels' => array(
  264. 'status' => 0,
  265. ),
  266. 'dropbox' => array(
  267. 'status' => 0,
  268. ),
  269. 'editability' => array(
  270. 'status' => 0,
  271. ),
  272. 'entity_count' => array(
  273. 'enabled' => 0,
  274. 'require_entity' => 0,
  275. 'settings' => array(
  276. 'count_children' => 0,
  277. 'entity_types' => array(),
  278. ),
  279. ),
  280. 'render_flat_select' => 0,
  281. );
  282. }