menu_attributes.module 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356
  1. <?php
  2. /**
  3. * @file
  4. * Alters the menu item form to allow the administrator to specify additional
  5. * attributes for the menu link
  6. */
  7. define('MENU_ATTRIBUTES_LINK', 'attributes');
  8. define('MENU_ATTRIBUTES_ITEM', 'item_attributes');
  9. /**
  10. * Implements hook_permission().
  11. */
  12. function menu_attributes_permission() {
  13. return array(
  14. 'administer menu attributes' => array(
  15. 'title' => t('Administer menu attributes'),
  16. 'description' => t('Administer menu attributes configuration.'),
  17. ),
  18. );
  19. }
  20. /**
  21. * Implements hook_menu_link_alter().
  22. */
  23. function menu_attributes_menu_link_alter(&$item, $menu) {
  24. if (isset($item['options']['attributes']) && is_array($item['options']['attributes'])) {
  25. // Filter out blank attributes.
  26. foreach ($item['options']['attributes'] as $key => $value) {
  27. if ((is_array($value) && empty($value)) || is_string($value) && !drupal_strlen($value)) {
  28. unset($item['options']['attributes'][$key]);
  29. }
  30. }
  31. // Convert classes to an array.
  32. if (isset($item['options']['attributes']['class']) && is_string($item['options']['attributes']['class'])) {
  33. $item['options']['attributes']['class'] = array_filter(explode(' ', $item['options']['attributes']['class']));
  34. }
  35. }
  36. }
  37. /**
  38. * Implements hook_menu_attribute_info().
  39. */
  40. function menu_attributes_menu_attribute_info() {
  41. $info['title'] = array(
  42. 'label' => t('Title'),
  43. 'description' => t('The description displayed when hovering over the link.'),
  44. 'form' => array(
  45. '#type' => 'textarea',
  46. '#rows' => 2,
  47. ),
  48. 'scope' => array(MENU_ATTRIBUTES_LINK),
  49. );
  50. $info['id'] = array(
  51. 'label' => t('ID'),
  52. 'description' => t('Specifies a unique ID for the link.'),
  53. 'scope' => array(MENU_ATTRIBUTES_LINK, MENU_ATTRIBUTES_ITEM),
  54. );
  55. $info['name'] = array(
  56. 'label' => t('Name'),
  57. 'scope' => array(MENU_ATTRIBUTES_LINK),
  58. );
  59. $info['rel'] = array(
  60. 'label' => t('Relationship'),
  61. 'description' => t("Specifies the relationship between the current page and the link. Enter 'nofollow' here to nofollow this link."),
  62. 'scope' => array(MENU_ATTRIBUTES_LINK),
  63. );
  64. $info['class'] = array(
  65. 'label' => t('Classes'),
  66. 'description' => t('Enter additional classes to be added to the link.'),
  67. 'scope' => array(MENU_ATTRIBUTES_LINK, MENU_ATTRIBUTES_ITEM),
  68. );
  69. $info['style'] = array(
  70. 'label' => t('Style'),
  71. 'description' => t('Enter additional styles to be applied to the link.'),
  72. 'scope' => array(MENU_ATTRIBUTES_LINK, MENU_ATTRIBUTES_ITEM),
  73. );
  74. $info['target'] = array(
  75. 'label' => t('Target'),
  76. 'description' => t('Specifies where to open the link. Using this attribute breaks XHTML validation.'),
  77. 'form' => array(
  78. '#type' => 'select',
  79. '#options' => array(
  80. '' => t('None (i.e. same window)'),
  81. '_blank' => t('New window (_blank)'),
  82. '_top' => t('Top window (_top)'),
  83. '_self' => t('Same window (_self)'),
  84. '_parent' => t('Parent window (_parent)'),
  85. ),
  86. ),
  87. 'scope' => array(MENU_ATTRIBUTES_LINK),
  88. );
  89. $info['accesskey'] = array(
  90. 'label' => t('Access Key'),
  91. 'description' => t('Specifies a <a href="@accesskey">keyboard shortcut</a> to access this link.', array('@accesskey' => url('http://en.wikipedia.org/wiki/Access_keys'))),
  92. 'form' => array(
  93. '#maxlength' => 1,
  94. '#size' => 1,
  95. ),
  96. 'scope' => array(MENU_ATTRIBUTES_LINK),
  97. );
  98. return $info;
  99. }
  100. /**
  101. * Fetch an array of menu attributes.
  102. */
  103. function menu_attributes_get_menu_attribute_info() {
  104. $attributes = module_invoke_all('menu_attribute_info');
  105. // Merge in default values.
  106. foreach ($attributes as $attribute => &$info) {
  107. $info += array(
  108. 'form' => array(),
  109. 'enabled' => variable_get("menu_attributes_{$attribute}_enable", 1),
  110. 'default' => '',
  111. );
  112. $info['form'] += array(
  113. '#type' => 'textfield',
  114. '#title' => $info['label'],
  115. '#description' => isset($info['description']) ? $info['description'] : '',
  116. '#default_value' => variable_get("menu_attributes_{$attribute}_default", $info['default']),
  117. );
  118. }
  119. drupal_alter('menu_attribute_info', $attributes);
  120. return $attributes;
  121. }
  122. /**
  123. * Implements hook_form_FORM_ID_alter().
  124. *
  125. * Adds menu attribute options to the edit menu item form.
  126. *
  127. * @see menu_edit_item()
  128. * @see _menu_attributes_form_alter()
  129. * @see menu_attributes_form_menu_edit_item_submit()
  130. */
  131. function menu_attributes_form_menu_edit_item_alter(&$form, $form_state) {
  132. $item = $form['original_item']['#value'];
  133. _menu_attributes_form_alter($form, $item, $form);
  134. }
  135. /**
  136. * Implements hook_form_FORM_ID_alter().
  137. *
  138. * Adds menu attribute options to the node's edit menu item form.
  139. *
  140. * @see _menu_attributes_form_alter()
  141. */
  142. function menu_attributes_form_node_form_alter(&$form, $form_state) {
  143. if (isset($form['menu']['link']) && isset($form['#node']->menu)) {
  144. $item = $form['#node']->menu;
  145. _menu_attributes_form_alter($form['menu']['link'], $item, $form);
  146. }
  147. }
  148. /**
  149. * Add the menu attributes to a menu item edit form.
  150. *
  151. * @param $form
  152. * The menu item edit form passed by reference.
  153. * @param $item
  154. * The optional existing menu item for context.
  155. */
  156. function _menu_attributes_form_alter(array &$form, array $item = array(), array &$complete_form) {
  157. $form['options']['#tree'] = TRUE;
  158. $form['options']['#weight'] = 50;
  159. // Unset the previous value so that the new values get saved.
  160. unset($form['options']['#value']['attributes']);
  161. unset($form['options']['#value']['item_attributes']);
  162. $form['options'][MENU_ATTRIBUTES_LINK] = array(
  163. '#type' => 'fieldset',
  164. '#title' => t('Menu link attributes'),
  165. '#collapsible' => TRUE,
  166. '#collapsed' => TRUE,
  167. '#tree' => TRUE,
  168. );
  169. $form['options'][MENU_ATTRIBUTES_ITEM] = array(
  170. '#type' => 'fieldset',
  171. '#title' => t('Menu item attributes'),
  172. '#collapsible' => TRUE,
  173. '#collapsed' => TRUE,
  174. '#tree' => TRUE,
  175. );
  176. $attributes = menu_attributes_get_menu_attribute_info();
  177. foreach ($attributes as $attribute => $info) {
  178. // If no scope is set, this attribute should be available to both link
  179. // and item.
  180. if (!isset($info['scope'])) {
  181. $info['scope'] = array(MENU_ATTRIBUTES_LINK, MENU_ATTRIBUTES_ITEM);
  182. }
  183. // Define fields for each scope.
  184. foreach ($info['scope'] as $group) {
  185. // Merge in the proper default value.
  186. if (isset($item['options'][$group][$attribute])) {
  187. // If the menu link already has this attribute, use it.
  188. $info['form']['#default_value'] = $item['options'][$group][$attribute];
  189. // Convert the classes array to a string for the form.
  190. if ($attribute == 'class' && is_array($info['form']['#default_value'])) {
  191. $info['form']['#default_value'] = implode(' ', $info['form']['#default_value']);
  192. }
  193. }
  194. elseif ($item['mlid']) {
  195. // If this is an existing link, use the raw default (usually empty).
  196. $info['form']['#default_value'] = $info['default'];
  197. }
  198. $form['options'][$group][$attribute] = $info['form'] + array(
  199. '#access' => $info['enabled'],
  200. );
  201. }
  202. }
  203. // Add form values for the reset of $item['options'] and
  204. // $item['options']['attributes'] so the values will carry over during save.
  205. foreach ($item['options'] as $key => $value) {
  206. if ($key !== 'attributes' && !isset($form['options'][$key])) {
  207. $form['options'][$key] = array(
  208. '#type' => 'value',
  209. '#value' => $value,
  210. );
  211. }
  212. }
  213. foreach (array(MENU_ATTRIBUTES_LINK, MENU_ATTRIBUTES_ITEM) as $group) {
  214. if (isset($item['options'][$group])) {
  215. foreach ($item['options'][$group] as $key => $value) {
  216. if (!isset($form['options'][$group][$key])) {
  217. $form['options'][$group][$key] = array(
  218. '#type' => 'value',
  219. '#value' => $value,
  220. );
  221. }
  222. }
  223. }
  224. }
  225. // Hide the 'description' field since we will be using our own 'title' field.
  226. if (isset($form['description'])) {
  227. $form['description']['#access'] = FALSE;
  228. // Because this form uses a special $form['description'] field which is
  229. // really the 'title' attribute, we need to add special pre-submit handling
  230. // to ensure our field gets saved as the title attribute.
  231. array_unshift($complete_form['#submit'], '_menu_attributes_form_submit');
  232. }
  233. // Restrict access to the new form elements.
  234. $has_visible_children = (bool) element_get_visible_children($form['options']['attributes']);
  235. $user_has_access = user_access('administer menu attributes');
  236. $form['options']['attributes']['#access'] = ($has_visible_children && $user_has_access);
  237. $has_visible_children = (bool) element_get_visible_children($form['options']['item_attributes']);
  238. $form['options']['item_attributes']['#access'] = ($has_visible_children && $user_has_access);
  239. }
  240. /**
  241. * Form submit handler for menu item links.
  242. *
  243. * Move the title attributes value into the 'description' value so that it
  244. * will get properly saved.
  245. */
  246. function _menu_attributes_form_submit($form, &$form_state) {
  247. if (isset($form_state['values']['menu']['options']['attributes']['title'])) {
  248. $form_state['values']['menu']['description'] = $form_state['values']['menu']['options']['attributes']['title'];
  249. }
  250. elseif (isset($form_state['values']['options']['attributes']['title'])) {
  251. $form_state['values']['description'] = $form_state['values']['options']['attributes']['title'];
  252. }
  253. }
  254. /**
  255. * Implements hook_form_FORM_ID_alter().
  256. *
  257. * Alters the menu settings form with our menu attribute settings.
  258. *
  259. * @see menu_configure_form()
  260. */
  261. function menu_attributes_form_menu_configure_alter(&$form, $form_state) {
  262. if (!user_access('administer menu attributes')) {
  263. return;
  264. }
  265. $form['attributes_title'] = array(
  266. '#type' => 'item',
  267. '#title' => t('Menu item attribute options'),
  268. );
  269. $form['attributes_vertical_tabs'] = array(
  270. '#type' => 'vertical_tabs',
  271. '#attached' => array(
  272. 'js' => array(drupal_get_path('module', 'menu_attributes') . '/menu_attributes.js'),
  273. ),
  274. );
  275. $attributes = menu_attributes_get_menu_attribute_info();
  276. foreach ($attributes as $attribute => $info) {
  277. $form['attributes'][$attribute] = array(
  278. '#type' => 'fieldset',
  279. '#title' => $info['label'],
  280. '#group' => 'attributes_vertical_tabs',
  281. '#description' => $info['form']['#description'],
  282. );
  283. $form['attributes'][$attribute]["menu_attributes_{$attribute}_enable"] = array(
  284. '#type' => 'checkbox',
  285. '#title' => t('Enable the @attribute attribute.', array('@attribute' => drupal_strtolower($info['label']))),
  286. '#default_value' => $info['enabled'],
  287. );
  288. $form['attributes'][$attribute]["menu_attributes_{$attribute}_default"] = array(
  289. '#title' => t('Default'),
  290. '#description' => '',
  291. '#states' => array(
  292. 'invisible' => array(
  293. 'input[name="menu_attributes_' . $attribute . '_enable"]' => array('checked' => FALSE),
  294. ),
  295. ),
  296. ) + $info['form'];
  297. }
  298. }
  299. /**
  300. * Implements MODULE_preprocess_HOOK().
  301. *
  302. * Adds appropriate attributes to the list item.
  303. *
  304. * @see theme_menu_link()
  305. */
  306. function menu_attributes_preprocess_menu_link(&$variables) {
  307. $options = &$variables['element']['#localized_options'];
  308. $attributes = &$variables['element']['#attributes'];
  309. if (isset($options['item_attributes'])) {
  310. foreach ($options['item_attributes'] as $attribute => $value) {
  311. if (!empty($value)) {
  312. // Class get's special treatment, as it's an array and it should not
  313. // replace existing values.
  314. if ($attribute == 'class') {
  315. $value = !is_array($value) ? explode(' ', $value) : $value;
  316. if (isset($attributes[$attribute])) {
  317. $value = array_merge($attributes[$attribute], $value);
  318. }
  319. }
  320. // Override the attribute.
  321. $attributes[$attribute] = $value;
  322. }
  323. }
  324. // Clean up, so we're not passing this to l().
  325. unset($options['item_attributes']);
  326. }
  327. }