menu_editor.admin.inc 24 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680
  1. <?php
  2. /**
  3. * Form for editing an entire menu tree at once.
  4. *
  5. * Shows for one menu the menu items accessible to the current user and
  6. * relevant operations.
  7. *
  8. * @param array $form
  9. * @param array $form_state
  10. * @param array $menu
  11. *
  12. * @return array
  13. *
  14. * @see menu_overview_form()
  15. * @see menu_edit_item()
  16. * @see menu_editor_overview_form_validate()
  17. * @see menu_editor_overview_form_submit()
  18. */
  19. function menu_editor_overview_form(array $form, array &$form_state, array $menu) {
  20. $form['#attached']['css'] = array(drupal_get_path('module', 'menu') . '/menu.css');
  21. $sql = "
  22. SELECT m.load_functions, m.to_arg_functions, m.access_callback, m.access_arguments, m.page_callback, m.page_arguments, m.delivery_callback, m.title, m.title_callback, m.title_arguments, m.type, m.description, ml.*
  23. FROM {menu_links} ml LEFT JOIN {menu_router} m ON m.path = ml.router_path
  24. WHERE ml.menu_name = :menu
  25. ORDER BY p1 ASC, p2 ASC, p3 ASC, p4 ASC, p5 ASC, p6 ASC, p7 ASC, p8 ASC, p9 ASC";
  26. $result = db_query($sql, array(':menu' => $menu['menu_name']), array('fetch' => PDO::FETCH_ASSOC));
  27. $links = array();
  28. foreach ($result as $item) {
  29. $links[] = $item;
  30. }
  31. $tree = menu_tree_data($links);
  32. $node_links = array();
  33. menu_tree_collect_node_links($tree, $node_links);
  34. // We indicate that a menu administrator is running the menu access check.
  35. $GLOBALS['menu_admin'] = TRUE;
  36. menu_tree_check_access($tree, $node_links);
  37. $GLOBALS['menu_admin'] = FALSE;
  38. $form = array('#tree' => TRUE);
  39. $form['#multilingual_menu'] = FALSE;
  40. $form['#translatable_nodes'] = FALSE;
  41. $max_root_weight = _menu_editor_overview_tree_form($form, $form_state, $tree);
  42. // default form values for all new menu items..
  43. $default_values = array(
  44. 'link_title' => '',
  45. 'link_path' => '<front>',
  46. 'description' => '',
  47. 'enabled' => true,
  48. 'expanded' => false,
  49. 'weight' => 0,
  50. // 'mlid' => NULL, // this is different for every single one
  51. 'plid' => 0,
  52. 'language' => LANGUAGE_NONE,
  53. );
  54. foreach (menu_editor_get_placeholders() as $placeholder_code => $placeholder_path) {
  55. // take the first placeholder as default link path instead of <front>
  56. $default_values['link_path'] = $placeholder_code;
  57. break;
  58. }
  59. for ($i = 0; $i < 8; ++$i) {
  60. // new menu item
  61. $default_values['mlid'] = 'new' . $i;
  62. $item_key = 'mlid-new' . $i;
  63. $form[$item_key] = _menu_editor_overview_tree_form_item('new' . $i, $default_values, $form['#multilingual_menu']);
  64. $form[$item_key]['weight']['#default_value'] = $max_root_weight + $i + 1;
  65. $form[$item_key]['#item'] = array(
  66. 'module' => 'menu',
  67. 'hidden' => 0,
  68. 'menu_name' => $menu['menu_name'],
  69. );
  70. $form[$item_key]['#attributes'] = array('class' => 'menu-new');
  71. $form[$item_key]['drag']['#markup'] = t('New menu item');
  72. }
  73. $form['#menu'] = $menu;
  74. if (element_children($form)) {
  75. $form['submit'] = array(
  76. '#type' => 'submit',
  77. '#value' => t('Save configuration'),
  78. );
  79. }
  80. else {
  81. $form['empty_menu'] = array('#value' => t('There are no menu items yet.'));
  82. }
  83. return $form;
  84. }
  85. /**
  86. * Recursive helper function for menu_overview_form().
  87. *
  88. * @param array $form
  89. * @param array $form_state
  90. * @param array $tree
  91. *
  92. * @return int
  93. * The max root weight (whatever this means)
  94. */
  95. function _menu_editor_overview_tree_form(array &$form, array &$form_state, array $tree) {
  96. $max_root_weight = 0;
  97. foreach ($tree as $data) {
  98. $title = '';
  99. $item = $data['link'];
  100. // Don't show callbacks; these have $item['hidden'] < 0.
  101. if ($item && $item['hidden'] >= 0) {
  102. $item_key = 'mlid-' . $item['mlid'];
  103. // @todo This is bogus. The value might be in $form_state['input'][$item_key], but never in $form_state[$item_key].
  104. $weight = isset($form_state[$item_key]['weight']) ? $form_state[$item_key]['weight'] : $item['weight'];
  105. $plid = isset($form_state[$item_key]['plid']) ? $form_state[$item_key]['plid'] : $item['plid'];
  106. if (!$plid && $weight > $max_root_weight) {
  107. // this is a root level item
  108. $max_root_weight = $weight;
  109. }
  110. $path = $item['link_path'];
  111. if (!empty($item['options']['query'])) {
  112. if (is_array($item['options']['query'])) {
  113. $path .= '?' . drupal_http_build_query($item['options']['query']);
  114. }
  115. else {
  116. // Query could be a string, due to a malfunction in previous versions of this module.
  117. $path .= '?' . $item['options']['query'];
  118. }
  119. }
  120. if (isset($item['options']['fragment'])) {
  121. $path .= '#' . $item['options']['fragment'];
  122. }
  123. /* @see menu_firstchild_form_alter() */
  124. if (isset($item['href']) && '<firstchild>' === $item['href']) {
  125. $item['hidden'] = !empty($item['options']['unaltered_hidden']);
  126. }
  127. $default_values = array(
  128. 'link_title' => $item['link_title'],
  129. 'link_path' => $path,
  130. 'description' => isset($item['options']['attributes']['title']) ? $item['options']['attributes']['title'] : '',
  131. 'enabled' => !$item['hidden'],
  132. 'expanded' => $item['expanded'],
  133. 'weight' => $weight,
  134. 'plid' => $plid,
  135. 'depth' => 1,
  136. );
  137. $form[$item_key] = _menu_editor_overview_tree_form_item($item['mlid'], $default_values, $form['#multilingual_menu']);
  138. $form[$item_key]['#item'] = $item;
  139. $form[$item_key]['#attributes'] = $item['hidden'] ? array('class' => 'menu-disabled') : array('class' => 'menu-enabled');
  140. $form[$item_key]['drag']['#markup'] = l($item['title'], $item['href'], $item['localized_options']) . ($item['hidden'] ? ' ('. t('disabled') .')' : '');
  141. // Include i18n support.
  142. if (module_exists('i18n_menu')) {
  143. $item['language'] = i18n_menu_item_get_language($item);
  144. $node_item = ($node = i18n_menu_item_get_node($item)) && i18n_menu_node_supported_type($node->type);
  145. if (!$node_item && i18n_menu_mode($item['menu_name'], I18N_MODE_TRANSLATE)) {
  146. $form[$item_key]['language'] = array('#title' => NULL) + i18n_element_language_select($item);
  147. $form['#multilingual_menu'] = TRUE;
  148. // If the term to be added will be a translation of a source term,
  149. // set the default value of the option list to the target language and
  150. // create a form element for storing the translation set of the source term.
  151. if (isset($_GET['translation']) && isset($_GET['target']) && ($source_item = menu_link_load($_GET['translation']))) {
  152. if (!empty($source_item['i18n_tsid'])) {
  153. $translation_set = i18n_translation_set_load($source_item['i18n_tsid']);
  154. }
  155. else {
  156. // Create object and stick the source information in the translation set.
  157. $translation_set = i18n_translation_set_build('menu_link')
  158. ->add_item($source_item);
  159. }
  160. $form[$item_key]['link_path']['#default_value'] = $source_item['link_path'];
  161. // Maybe we should disable the 'link_path' and 'parent' form elements?
  162. // $form['link_path']['#disabled'] = TRUE;
  163. // $form['parent']['#disabled'] = TRUE;
  164. $form[$item_key]['language']['#default_value'] = $_GET['target'];
  165. $form[$item_key]['language']['#disabled'] = TRUE;
  166. }
  167. }
  168. else {
  169. $form[$item_key]['language'] = array(
  170. '#type' => 'value',
  171. '#value' => $item['language'],
  172. );
  173. }
  174. // Aside from the usual conditions from i18n_menu_form_menu_edit_item_alter(), also
  175. // check if the return of the language name isn't "Undefined". This is caused when
  176. // choosing "Translate mode" to be "No multilingual options for menu items. Only the
  177. // menu will be translatable." but one of the menu items is a node which
  178. // is translatable.
  179. if ($node_item && i18n_langcode($item['language'])) {
  180. $form[$item_key]['lang_message'] = array(
  181. '#type' => 'markup',
  182. '#title' => t('Language'),
  183. '#markup' => i18n_language_name($item['language']),
  184. );
  185. $form['#translatable_nodes'] = TRUE;
  186. }
  187. }
  188. // Only items created by the menu module can be deleted.
  189. if ($item['module'] === 'menu' || (string)$item['updated'] === '1') {
  190. $form[$item_key]['delete'] = array(
  191. '#type' => 'checkbox',
  192. '#title' => t('delete'),
  193. '#default_value' => false,
  194. );
  195. }
  196. }
  197. // process child elements
  198. if ($data['below']) {
  199. _menu_editor_overview_tree_form($form, $form_state, $data['below']);
  200. }
  201. }
  202. return $max_root_weight;
  203. }
  204. /**
  205. * @param int|string $item_mlid
  206. * @param array $default_values
  207. * @param bool $multilingual_menu
  208. *
  209. * @return array
  210. */
  211. function _menu_editor_overview_tree_form_item($item_mlid, $default_values, $multilingual_menu = FALSE) {
  212. foreach (menu_editor_get_placeholders() as $code => $path) {
  213. if (str_replace('@mlid', $item_mlid, $path) === $default_values['link_path']) {
  214. $default_values['link_path'] = $code;
  215. }
  216. }
  217. $element = array();
  218. $element['link_title'] = array(
  219. '#type' => 'textfield',
  220. '#size' => 25,
  221. '#maxlength' => 128,
  222. );
  223. $element['link_path'] = array(
  224. '#type' => 'textfield',
  225. '#size' => 25,
  226. '#maxlength' => 255,
  227. );
  228. $element['description'] = array(
  229. '#type' => 'textarea',
  230. '#cols' => 8,
  231. '#rows' => 1,
  232. '#resizable' => FALSE,
  233. );
  234. $element['enabled'] = array(
  235. '#type' => 'checkbox',
  236. );
  237. $element['expanded'] = array(
  238. '#type' => 'checkbox',
  239. );
  240. $element['weight'] = array(
  241. // The original form uses a select box for the weight.
  242. // We use a textfield instead, to save memory on server side
  243. // and to reduce page size.
  244. '#type' => 'textfield',
  245. '#size' => 4,
  246. '#element_validate' => array('_menu_editor_valid_weight'),
  247. );
  248. $element['mlid'] = array(
  249. '#type' => 'hidden',
  250. '#value' => $item_mlid,
  251. );
  252. $element['plid'] = array(
  253. '#type' => 'textfield',
  254. '#size' => 6,
  255. );
  256. // If this is a new menu item, simply provide i18n_menu select options.
  257. if (strpos($item_mlid, 'new') === 0 && $multilingual_menu) {
  258. $element['language'] = array('#title' => NULL) + i18n_element_language_select();
  259. }
  260. foreach ($default_values as $key => $value) {
  261. if (isset($element[$key])) {
  262. $element[$key]['#default_value'] = $value;
  263. }
  264. }
  265. return $element;
  266. }
  267. /**
  268. * Weight textfield validation function
  269. * Stolen from http://drupal.org/project/tiny_menu_editor
  270. * Big thanks to Dmitriy.trt
  271. *
  272. * @param array $element
  273. * @param array $form_state
  274. */
  275. function _menu_editor_valid_weight(array $element, array &$form_state) {
  276. if ((isset($element['#value']) && $element['#value'] !== '') || !empty($element['#required'])) {
  277. if (!preg_match('/^\-?\d+$/', $element['#value'])) {
  278. form_error($element, t('Weight has to be an integer value.'));
  279. }
  280. }
  281. }
  282. /**
  283. * Form validation callback for menu_editor_overview_form.
  284. *
  285. * @param array $form
  286. * @param array $form_state
  287. *
  288. * @see menu_editor_overview_form()
  289. */
  290. function menu_editor_overview_form_validate(array $form, array &$form_state) {
  291. $form_values = &$form_state['values'];
  292. // Check existing items.
  293. foreach (element_children($form) as $item_key) {
  294. // Check if these are menu items.
  295. if (strpos($item_key, 'mlid-') !== FALSE) {
  296. $element = &$form[$item_key];
  297. if (isset($element['link_path'])) {
  298. menu_editor_validate_item($element, $element['link_path']['#value'], $item_key . '][');
  299. }
  300. }
  301. }
  302. // allow new items to be dynamically added via javascript,
  303. // that have not been in the form originally.
  304. foreach ($form_state['input'] as $item_key => $item) {
  305. if (preg_match('/^mlid-new\d+$/', $item_key)) {
  306. if (menu_editor_overview_form_validate_new_item($item)) {
  307. $form_values[$item_key] = $item;
  308. }
  309. }
  310. }
  311. }
  312. /**
  313. * Validate form values for a menu link being added or edited.
  314. *
  315. * @param array $element
  316. * @param string $link_path
  317. * @param string $error_key_prefix
  318. */
  319. function menu_editor_validate_item(array &$element, $link_path, $error_key_prefix) {
  320. $placeholders = menu_editor_get_placeholders();
  321. if (isset($placeholders[$link_path])) {
  322. // it would be hard to check access,
  323. // because we don't necessarily know the mlid.
  324. // Thus, we simply grant access for all placeholders.
  325. return;
  326. }
  327. if ($element['link_path']['#default_value'] === $link_path) {
  328. // link_path is the only field that we check,
  329. // and we don't complain about existing link paths.
  330. return;
  331. }
  332. $item = $element['#item'];
  333. $normal_path = drupal_get_normal_path($link_path);
  334. $item['link_path'] = $normal_path;
  335. if (!url_is_external($normal_path)) {
  336. $parsed_link = parse_url($normal_path);
  337. if (isset($parsed_link['query'])) {
  338. $item['options']['query'] = drupal_get_query_array($parsed_link['query']);
  339. }
  340. if (isset($parsed_link['fragment'])) {
  341. $item['options']['fragment'] = $parsed_link['fragment'];
  342. }
  343. $item['link_path'] = $parsed_link['path'];
  344. }
  345. if (!trim($item['link_path']) || !drupal_valid_path($item['link_path'])) {
  346. form_set_error($error_key_prefix . 'link_path', t("The path '@link_path' is either invalid or you do not have access to it.", array('@link_path' => $item['link_path'])));
  347. }
  348. }
  349. /**
  350. * Validates a menu item that was dynamically added through javascript.
  351. *
  352. * @param array|mixed $item
  353. *
  354. * @return bool
  355. */
  356. function menu_editor_overview_form_validate_new_item($item) {
  357. // TODO: add some sanity checks
  358. return true;
  359. }
  360. /**
  361. * Submit handler for the menu overview form.
  362. *
  363. * This function takes great care in saving parent items first, then items
  364. * underneath them. Saving items in the incorrect order can break the menu tree.
  365. *
  366. * @param array $form
  367. * @param array $form_state
  368. *
  369. * @see menu_editor_overview_form()
  370. * @see menu_overview_form_submit()
  371. * @see menu_edit_item_submit()
  372. */
  373. function menu_editor_overview_form_submit(array $form, array &$form_state) {
  374. // When dealing with saving menu items, the order in which these items are
  375. // saved is critical. If a changed child item is saved before its parent,
  376. // the child item could be saved with an invalid path past its immediate
  377. // parent. To prevent this, save items in the form in the same order they
  378. // are sent by $_POST, ensuring parents are saved first, then their children.
  379. // See http://drupal.org/node/181126#comment-632270
  380. $item_keys = array_flip(array_keys($form_state['input'])); // Get the $_POST order.
  381. $form_values = array_merge($item_keys, $form_state['values']); // Update our original form with the new order.
  382. $menu_name = $form['#menu']['menu_name'];
  383. $updated_items = array();
  384. $fields = array('expanded', 'weight', 'plid', 'link_title', 'link_path', 'description');
  385. foreach ($form_values as $item_key => $v) {
  386. if (isset($form[$item_key]['#item'])) {
  387. $element = $form[$item_key];
  388. $original_item = $item = $element['#item'];
  389. if (!isset($item['mlid'])) {
  390. // add new item
  391. unset($v['mlid']);
  392. if (!is_string($v['link_title']) || empty($v['link_title'])) {
  393. continue;
  394. }
  395. if (!is_string($v['link_path']) || empty($v['link_path'])) {
  396. continue;
  397. }
  398. $item['menu_name'] = $menu_name;
  399. // Set all fields in this menu item.
  400. foreach ($fields as $field) {
  401. // Check if field is set since some fields could not be NULL when
  402. // doing menu_link_save().
  403. if (isset($v[$field])) {
  404. $item[$field] = $v[$field];
  405. }
  406. }
  407. $updated_items[$item_key] = $item;
  408. }
  409. elseif (!empty($v['delete'])) {
  410. // delete existing item
  411. if (is_numeric($item['mlid'])) {
  412. menu_link_delete($item['mlid']);
  413. }
  414. continue;
  415. }
  416. else {
  417. // update existing item
  418. // Update any fields that have changed in this menu item.
  419. foreach ($fields as $field) {
  420. // Using != instead of !== to support '5' == 5.
  421. /** @noinspection TypeUnsafeComparisonInspection */
  422. if ($v[$field] != $element[$field]['#default_value']) {
  423. $item[$field] = $v[$field];
  424. $updated_items[$item_key] = $item;
  425. }
  426. }
  427. }
  428. // Hidden is a special case, the value needs to be inverted.
  429. // Besides, while we operate with boolean, the database wants numeric 0/1.
  430. if ($v['enabled'] xor $element['enabled']['#default_value']) {
  431. // Checkbox for 'enabled' was changed.
  432. $element['#item']['hidden'] = empty($v['enabled']) ? 1 : 0;
  433. $updated_items[$item_key] = $element['#item'];
  434. }
  435. else {
  436. // Checkbox for 'enabled' was not changed.
  437. }
  438. // langcode is a special case as well
  439. if (!empty($form['#multilingual_menu'])) {
  440. $langcode = isset($element['language']['#default_value']) ? $element['language']['#default_value'] : $element['language']['#value'];
  441. if ($v['language'] !== $langcode) {
  442. $element['#item']['language'] = $v['language'];
  443. $updated_items[$item_key] = $element['#item'];
  444. }
  445. }
  446. // description is a special case
  447. if (isset($updated_items[$item_key]['description'])) {
  448. $updated_items[$item_key]['options']['attributes']['title'] = $updated_items[$item_key]['description'];
  449. }
  450. }
  451. }
  452. // placeholders to change the link path
  453. $placeholders = menu_editor_get_placeholders();
  454. // Save all our changed items to the database.
  455. $errors = array();
  456. $mlids = array();
  457. foreach ($updated_items as $item_key => $item) {
  458. $item['customized'] = 1;
  459. // check the link path
  460. $link_path = &$item['link_path'];
  461. $link_path_placeholder = NULL;
  462. // placeholders
  463. if (isset($placeholders[$link_path])) {
  464. if (isset($item['mlid']) && is_numeric($item['mlid'])) {
  465. $link_path = str_replace('@mlid', $item['mlid'], $placeholders[$link_path]);
  466. }
  467. else {
  468. $link_path_placeholder = $placeholders[$link_path];
  469. // use a dummy link path,
  470. // until we know the correct mlid.
  471. $link_path = '<front>';
  472. }
  473. }
  474. // clean the link path
  475. if (isset($link_path)) {
  476. $link_path = drupal_get_normal_path($link_path);
  477. if (!url_is_external($link_path)) {
  478. $parsed_link = parse_url($link_path);
  479. if (isset($parsed_link['query'])) {
  480. $item['options']['query'] = drupal_get_query_array($parsed_link['query']);
  481. }
  482. else {
  483. unset($item['options']['query']);
  484. }
  485. if (isset($parsed_link['fragment'])) {
  486. $item['options']['fragment'] = $parsed_link['fragment'];
  487. }
  488. else {
  489. unset($item['options']['fragment']);
  490. }
  491. if ($link_path !== $parsed_link['path']) {
  492. $link_path = $parsed_link['path'];
  493. }
  494. }
  495. if (!trim($link_path) || !drupal_valid_path($link_path)) {
  496. // invalid link path, discard this item
  497. continue;
  498. }
  499. }
  500. // drupal_set_message('<pre>' . print_r($item, true) . '</pre>');
  501. if (!empty($item['plid']) && !is_numeric($item['plid'])) {
  502. if (isset($mlids["mlid-$item[plid]"])) {
  503. $item['plid'] = $mlids["mlid-$item[plid]"];
  504. }
  505. else {
  506. unset($item['plid']);
  507. }
  508. }
  509. // @see menu_firstchild_menu_link_alter() expects $link['module'] to be set.
  510. $item += array(
  511. 'module' => 'menu',
  512. 'hidden' => 0,
  513. );
  514. $mlid = menu_link_save($item);
  515. if (is_numeric($mlid)) {
  516. // remember as a plid for child items
  517. $mlids[$item_key] = $mlid;
  518. if (isset($link_path_placeholder)) {
  519. // overwrite the dummy link path
  520. $link_path = str_replace('@mlid', $item['mlid'], $link_path_placeholder);
  521. menu_link_save($item);
  522. }
  523. }
  524. else {
  525. $errors[] = $item_key;
  526. }
  527. }
  528. if (!empty($errors)) {
  529. drupal_set_message(t('There were errors saving the following menu links:<br/>' . implode('<br/>', $errors)), 'error');
  530. }
  531. }
  532. /**
  533. * Theme the menu overview form into a table.
  534. *
  535. * @param array $variables
  536. *
  537. * @return string
  538. *
  539. * @ingroup themeable
  540. */
  541. function theme_menu_editor_overview_form(array $variables) {
  542. $form = $variables['form'];
  543. drupal_add_css(drupal_get_path('module', 'menu_editor') . '/menu_editor.css');
  544. drupal_add_js(drupal_get_path('module', 'menu_editor') . '/menu_editor.js');
  545. global $language;
  546. $i18n_menu = $form['#multilingual_menu'] || $form['#translatable_nodes'];
  547. drupal_add_tabledrag('menu-overview', 'match', 'parent', 'menu-plid', 'menu-plid', 'menu-mlid', TRUE, MENU_MAX_DEPTH - 1);
  548. drupal_add_tabledrag('menu-overview', 'order', 'sibling', 'menu-weight');
  549. $header = array();
  550. $header[] = t('Menu item');
  551. $header[] = t('Title');
  552. $header[] = t('Path');
  553. $header[] = array('data' => t('Descr.', array(), array('context' => 'Abbr: "Description"')), 'class' => 'description');
  554. $header[] = array('data' => t('En.', array(), array('context' => 'Abbr: "Enabled"')), 'class' => 'checkbox');
  555. $header[] = array('data' => t('Exp.', array(), array('context' => 'Abbr: "Expanded"')), 'class' => 'checkbox');
  556. $header[] = t('Weight');
  557. if ($i18n_menu) {
  558. $header[] = t('Language');
  559. }
  560. $header[] = array('data' => t('Delete'), 'class' => 'delete-checkbox');
  561. $rows = array();
  562. $items = array();
  563. foreach (element_children($form) as $item_key) {
  564. if (isset($form[$item_key]['enabled'])) {
  565. $element = &$form[$item_key];
  566. // Add special classes to be used for tabledrag.js.
  567. $element['plid']['#attributes']['class'] = array('menu-plid');
  568. $element['mlid']['#attributes']['class'] = array('menu-mlid');
  569. $element['weight']['#attributes']['class'] = array('menu-weight');
  570. // Change the parent field to a hidden. This allows any value but hides the field.
  571. $element['plid']['#type'] = 'hidden';
  572. // Adjust tab index to allow vertical tabbing
  573. foreach (array('link_title', 'link_path', 'description', 'enabled', 'expanded') as $i => $key) {
  574. $element[$key]['#attributes']['tabindex'] = $i+2;
  575. }
  576. $element['link_path']['#attributes']['tabindex'] = 2;
  577. $cells = array();
  578. $size = isset($element['#item']['depth']) ? $element['#item']['depth'] : 1;
  579. $cells['drag'] = array(
  580. 'data' => theme('indentation', array('size' => $size - 1)) . drupal_render($element['drag']),
  581. 'class' => 'drag',
  582. );
  583. $cells['link_title'] = array('data' => drupal_render($element['link_title']), 'class' => 'title-edit');
  584. $cells['link_path'] = array('data' => drupal_render($element['link_path']), 'class' => 'path-edit');
  585. $cells['description'] = array('data' => drupal_render($element['description']), 'class' => 'description');
  586. $cells['enabled'] = array('data' => drupal_render($element['enabled']), 'class' => 'checkbox');
  587. $cells['expanded'] = array('data' => drupal_render($element['expanded']), 'class' => 'checkbox');
  588. $cells['mlid'] = drupal_render($element['weight']) . drupal_render($element['plid']) . drupal_render($element['mlid']);
  589. if ($i18n_menu) {
  590. if (isset($element['lang_message'])) {
  591. $cells['language'] = array('data' => drupal_render($element['lang_message']));
  592. }
  593. else {
  594. $cells['language'] = array('data' => drupal_render($element['language']), 'class' => 'select');
  595. }
  596. }
  597. $cells['delete'] = array('data' => drupal_render($element['delete']), 'class' => 'delete-checkbox');
  598. $row = array_merge(array('data' => $cells), $element['#attributes']);
  599. $row['class'] = array('draggable');
  600. if ($i18n_menu) {
  601. $langcode = isset($element['language']['#default_value']) ? $element['language']['#default_value'] : $element['language']['#value'];
  602. $row['class'][] = ($langcode !== LANGUAGE_NONE) ? 'langcode-'.$langcode : 'all-languages';
  603. $row['class'][]= ($langcode === $language->language) ? 'active-language' : '';
  604. }
  605. $rows[$item_key] = $row;
  606. }
  607. }
  608. $output = '';
  609. // allow other modules to change the table data.
  610. drupal_alter('menu_editor_overview_table', $header, $rows);
  611. $output .= theme('table', array('header' => $header, 'rows' => $rows, 'attributes' => array('id' => 'menu-overview')));
  612. $output .= drupal_render_children($form);
  613. return $output;
  614. }