content_menu.module 19 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547
  1. <?php
  2. /**
  3. * @file
  4. * Main code for the content_menu.module.
  5. *
  6. * ToDo: Use view query alter hook to mandatorily filter existing-node-selection
  7. * by only those node types that can have a menu item in the resp. menu.
  8. *
  9. * ToDo: Mark menu items in item listing that are linked to unpublished content.
  10. * NOTE: menu links to unpublished nodes are suppressed by Drupal.
  11. * => Option 1: Alter Drupal's behavior to show unpublished links, too,
  12. * and then mark them as unpublished (new permission).
  13. * => Options 2: Leave everything as is.
  14. *
  15. * ToDo: Use menu.module's variable 'menu_override_parent_selector' to provide
  16. * custom more scalable menu parent selector.
  17. *
  18. * ToDo: Pre-placement of new menu items is sometimes slightly misplaced.
  19. */
  20. define('CONTENT_MENU_ADD_EXISTING_CONTENT_URL', 'admin/structure/menu/manage/add_existing_content');
  21. define('CONTENT_MENU_ADD_EXISTING_VIEW_URL', 'admin/structure/menu/manage/add_existing_view');
  22. define('CONTENT_MENU_ALTER_ALL_MENUS', FALSE);
  23. define('CONTENT_MENU_ADD_ITEM_WEIGHT', 50);
  24. /**
  25. * Implements hook_permission().
  26. */
  27. function content_menu_permission() {
  28. return array(
  29. // Introduce new permission for editing default system menus.
  30. 'administer system menus' => array(
  31. 'title' => t('Administer system menus'),
  32. 'description' => t('Administer system menus'),
  33. ),
  34. );
  35. }
  36. /**
  37. * Implements hook_menu().
  38. */
  39. function content_menu_menu() {
  40. // Add menu router item for use as target with dummy menu items.
  41. $items['menu-dummy'] = array(
  42. 'title' => 'Placeholder for menu item dummies.',
  43. 'page callback' => 'content_menu_menu_callback_menu_dummy',
  44. 'access arguments' => array('access content'),
  45. 'type' => MENU_CALLBACK,
  46. );
  47. $items[CONTENT_MENU_ADD_EXISTING_VIEW_URL] = array(
  48. 'title' => 'Add existing view.',
  49. 'page callback' => 'drupal_get_form',
  50. 'page arguments' => array('content_menu_add_exiting_view'),
  51. 'access arguments' => array('access content'),
  52. 'file' => 'content_menu.menu_admin.inc',
  53. );
  54. return $items;
  55. }
  56. /**
  57. * Menu router callback for placeholder menu items.
  58. */
  59. function content_menu_menu_callback_menu_dummy() {
  60. return t('This is an empty placeholder page for a menu item.<br /><br />You can replace it with a valid target page URL in the <a href="@url">menu administration</a>.', array('@url' => url('admin/structure/menu')));
  61. }
  62. /**
  63. * Implements hook_menu_alter().
  64. *
  65. * Rename some menu management tabs.
  66. */
  67. function content_menu_menu_alter(&$items) {
  68. $items['admin/structure/menu/manage/%menu']['title'] = $items['admin/structure/menu/manage/%menu/list']['title'] = t('Edit');
  69. $items['admin/structure/menu/manage/%menu/edit']['title'] = t('Configure');
  70. $items['admin/structure/menu']['page callback'] = 'content_menu_menu_overview_page_extended';
  71. unset($items['admin/structure/menu']['file']);
  72. }
  73. /**
  74. * Implements hook_init().
  75. *
  76. * Necessary to invoke for ajax callback to work correctly.
  77. * @todo Refactor as ajax is actually not used.
  78. */
  79. function content_menu_init() {
  80. if (arg(0) == 'admin' || ((arg(0) == 'system') && (arg(1) == 'ajax'))) {
  81. module_load_include('inc', 'content_menu', 'content_menu.menu_admin');
  82. }
  83. }
  84. /**
  85. * Callback for altered admin/structure/menu router item.
  86. */
  87. function content_menu_menu_overview_page_extended() {
  88. module_load_include('inc', 'content_menu', 'content_menu.menu_admin');
  89. return _content_menu_menu_overview_page_extended();
  90. }
  91. /**
  92. * Implements hook_theme().
  93. */
  94. function content_menu_theme() {
  95. return array(
  96. // Theming function for extended menu overview form rendering.
  97. 'menu_overview_form_extended' => array(
  98. 'file' => 'content_menu.menu_admin.inc',
  99. 'render element' => 'form',
  100. ),
  101. );
  102. }
  103. /**
  104. * Implements hook_views_api().
  105. */
  106. function content_menu_views_api() {
  107. return array('api' => 3.0);
  108. }
  109. /**
  110. * Implements hook_page_alter().
  111. *
  112. * Set consistent menu admin breadcrumbs on pages other than menu admin forms.
  113. */
  114. function content_menu_page_alter(&$page) {
  115. $menu_paths = array(
  116. 'admin/structure/menu/',
  117. 'admin/structure/menu-position/',
  118. content_menu_variable_get_add_existing_content_url(),
  119. );
  120. $path = current_path();
  121. foreach ($menu_paths as $menu_path) {
  122. if (strpos($path, $menu_path) === 0) {
  123. content_menu_set_menu_admin_breadcrumb();
  124. break;
  125. }
  126. }
  127. if (current_path() == content_menu_variable_get_add_existing_content_url()) {
  128. $item = content_menu_get_menu_item_from_querystring();
  129. if (isset($item['mlid']) && ($item['mlid'] != 0)) {
  130. drupal_set_message(t("You're about to select a new content target for menu item %title.", array('%title' => $item['title'])));
  131. drupal_set_message(t('Now you can select an existing content item for menu item %title.', array('%title' => $item['title'])));
  132. }
  133. }
  134. }
  135. /**
  136. * Set consistent menu admin breadcrumb.
  137. */
  138. function content_menu_set_menu_admin_breadcrumb($menu_name = NULL) {
  139. $breadcrumb = array();
  140. $breadcrumb[] = l(t('Home'), '<front>');
  141. $breadcrumb[] = l(t('Administration'), 'admin');
  142. $breadcrumb[] = l(t('Structure'), 'admin/structure');
  143. $breadcrumb[] = l(t('Menus'), 'admin/structure/menu');
  144. if ($menu_name == NULL) {
  145. $menu_item = content_menu_get_menu_item_from_querystring();
  146. if (isset($menu_item['name'])) {
  147. $menu_name = $menu_item['name'];
  148. }
  149. }
  150. if ($menu_name) {
  151. $menus = menu_get_menus();
  152. $menu_title = t($menus[$menu_name]);
  153. $breadcrumb[] = l(t($menu_title), 'admin/structure/menu/manage/' . $menu_name);
  154. drupal_set_title(t($menu_title));
  155. }
  156. drupal_set_breadcrumb($breadcrumb);
  157. }
  158. /**
  159. * Implements hook_menu_link_insert().
  160. */
  161. function content_menu_menu_link_insert($link) {
  162. content_menu_mark_link_updated($link);
  163. }
  164. /**
  165. * Implements hook_menu_link_update().
  166. */
  167. function content_menu_menu_link_update($link) {
  168. content_menu_mark_link_updated($link);
  169. }
  170. /**
  171. * Mark newly created menu items, to highlight them subsequently.
  172. *
  173. * @param $link Array defining the link.
  174. */
  175. function content_menu_mark_link_updated($link) {
  176. // Save menu links created on or right before menu admin pages to session var.
  177. if ((isset($_GET['destination']) && (strpos($_GET['destination'], 'admin/structure/menu/manage/') === 0))
  178. ||
  179. (strpos(current_path(), 'admin/structure/menu/manage/') === 0)
  180. ) {
  181. $link['created'] = time();
  182. $_SESSION['content_menu_inserted_links'][$link['mlid']] = $link;
  183. }
  184. }
  185. /**
  186. * Implements hook_form_FORM_ID_alter().
  187. *
  188. * Improve menu_edit_item form for better authoring experience.
  189. */
  190. function content_menu_form_menu_edit_item_alter(&$form, &$form_state, $form_id) {
  191. // Pre-populate menu item form elements with data from querystring.
  192. if (isset($_GET['menu_title'])) {
  193. $menu_item = content_menu_get_menu_item_from_querystring();
  194. // If even link_path is given, create item right away and go back.
  195. if (!empty($menu_item['link_path'])) {
  196. content_menu_link_save($menu_item);
  197. drupal_goto(check_plain($_GET['destination']));
  198. }
  199. $form['link_title']['#default_value'] = $menu_item['title'];
  200. $form['link_path']['#default_value'] = $menu_item['link_path'];
  201. $form['parent']['#default_value'] = $menu_item['name'] . ':' . $menu_item['plid'];
  202. $form['weight']['#default_value'] = $menu_item['weight'];
  203. $form['enabled']['#default_value'] = !($menu_item['hidden']);
  204. }
  205. // Extend breadcrumb.
  206. if ($form['mlid']['#value']) {
  207. $menu = explode(':', $form['parent']['#default_value']);
  208. content_menu_set_menu_admin_breadcrumb($menu[0]);
  209. }
  210. // Simplify the form, by putting all "advanced" fields in fieldset.
  211. $form['advanced'] = array(
  212. '#type' => 'fieldset',
  213. '#collapsible' => TRUE,
  214. '#collapsed' => TRUE,
  215. '#weight' => 100,
  216. '#title' => t('Advanced menu item settings'),
  217. );
  218. $advanced_elements = array('description', 'expanded', 'parent', 'weight');
  219. foreach ($advanced_elements as $el_key) {
  220. $form['advanced'][$el_key] = $form[$el_key];
  221. unset($form[$el_key]);
  222. }
  223. }
  224. /**
  225. * Implements hook_form_FORM_ID_alter().
  226. *
  227. * Improve menu_item_delete_form form for better authoring experience.
  228. */
  229. function content_menu_form_menu_item_delete_form_alter(&$form, &$form_state, $form_id) {
  230. if (!empty($form['#item']['mlid'])) {
  231. // Extend breadcrumb.
  232. $menu_name = $form['#item']['menu_name'];
  233. content_menu_set_menu_admin_breadcrumb($menu_name);
  234. // Add question text to form (instead of having it as the page title).
  235. $form['question'] = array(
  236. '#type' => 'markup',
  237. '#markup' => '<div class="question">' . t('Are you sure you want to delete the custom menu link %item?', array('%item' => t($form['#item']['link_title']))) . '</div><p></p>',
  238. '#weight' => -100,
  239. );
  240. // If menu item to delete links to a node, offer the choice to
  241. // delete the associated node as well, if user is permitted.
  242. if ($form['#item']['router_path'] == 'node/%') {
  243. $nid = explode('/', $form['#item']['link_path']);
  244. if (isset($nid[1]) && is_numeric($nid[1])) {
  245. $node = node_load($nid[1]);
  246. if (is_object($node) && node_access('delete', $node)) {
  247. $form['delete_node'] = array(
  248. '#type' => 'checkbox',
  249. // '#markup' => '<div class="question">' . t('Are you sure you want to delete the custom menu link %item?', array('%item' => t($form['#item']['link_title']))) . '</div><p></p>',
  250. '#title' => t('Delete associated %type <a href="@url">%title</a> as well.',
  251. array(
  252. '%type' => node_type_get_name($node),
  253. '@url' => url('node/' . $node->nid),
  254. '%title' => $node->title,
  255. )),
  256. '#default_value' => FALSE,
  257. '#weight' => -50,
  258. );
  259. $form['delete_node_nid'] = array('#type' => 'value', '#value' => $node->nid);
  260. $form['#submit'] = array('content_menu_item_delete_form_submit');
  261. }
  262. }
  263. }
  264. }
  265. }
  266. /**
  267. * Extended submit handler for menu_item_delete_form.
  268. */
  269. function content_menu_item_delete_form_submit($form, &$form_state) {
  270. // Call default submit handler to handle deletion of menu item itself.
  271. menu_item_delete_form_submit($form, $form_state);
  272. // Delete an associated node as well, if intended and user is permitted.
  273. if ($form_state['input']['delete_node'] == 1) {
  274. $nid = explode('/', $form['#item']['link_path']);
  275. if (isset($nid[1]) && is_numeric($nid[1])) {
  276. $node = node_load($nid[1]);
  277. if (is_object($node) && node_access('delete', $node) && ($node->nid == $form_state['values']['delete_node_nid'])) {
  278. node_delete($node->nid);
  279. watchdog('content', '@type: deleted %title.', array('@type' => $node->type, '%title' => $node->title));
  280. drupal_set_message(t('@type %title has been deleted.', array('@type' => node_type_get_name($node), '%title' => $node->title)));
  281. }
  282. }
  283. }
  284. }
  285. /**
  286. * Implements hook_form_FORM_ID_alter().
  287. *
  288. * Pre-populate input elements in menu_position_add_rule_form.
  289. */
  290. function content_menu_form_menu_position_add_rule_form_alter(&$form, &$form_state, $form_id) {
  291. // Pre-populate menu item form elements with data from querystring.
  292. if (isset($_GET['menu_title'])) {
  293. $menu_item = content_menu_get_menu_item_from_querystring();
  294. $form['admin_title']['#default_value'] = $menu_item['title'];
  295. $form['plid']['#default_value'] = $menu_item['name'] . ':' . $menu_item['plid'];
  296. }
  297. }
  298. /**
  299. * Implements hook_form_FORM_ID_alter().
  300. *
  301. * Overhaul the menu_overview_form to improve menu authoring experience.
  302. */
  303. function content_menu_form_menu_overview_form_alter(&$form, &$form_state, $form_id) {
  304. if (variable_get('content_menu_alter_all_menus', CONTENT_MENU_ALTER_ALL_MENUS) || content_menu_is_menu_considered($form['#menu']['menu_name'])) {
  305. module_load_include('inc', 'content_menu', 'content_menu.menu_admin');
  306. _content_menu_form_menu_overview_form_alter($form, $form_state, $form_id);
  307. }
  308. }
  309. /**
  310. * Implements hook_menu_local_tasks_alter().
  311. *
  312. * Remove the "Add link" link on menu listing page, since
  313. * we add a "New item" row and form element, which cares for adding new items.
  314. */
  315. function content_menu_menu_local_tasks_alter(&$data, $router_item, $root_path) {
  316. if (strpos(current_path(), 'admin/structure/menu/manage/') === 0) {
  317. if (variable_get('content_menu_alter_all_menus', CONTENT_MENU_ALTER_ALL_MENUS) || content_menu_is_menu_considered(arg(4))) {
  318. foreach ($data['actions']['output'] as $key => $action) {
  319. if ($action['#link']['path'] == 'admin/structure/menu/manage/%/add') {
  320. unset($data['actions']['output'][$key]);
  321. }
  322. }
  323. }
  324. }
  325. }
  326. /**
  327. * Implements template_preprocess_views_view_field().
  328. *
  329. * For views to select exiting content, rewrite the nid field ("select" link)
  330. * to link to the correct target url and pass through the querystring params.
  331. */
  332. function content_menu_preprocess_views_view_field(&$vars) {
  333. $view = &$vars['view'];
  334. $field = &$vars['field'];
  335. $selection_view = explode(':', variable_get('content_menu_add_existing_content_view', 'menu_existing_content_selection'));
  336. if (($field->field == 'nid') && ($view->name == $selection_view[0]) && ((!isset($selection_view[1]) || ($view->current_display == $selection_view[1])))) {
  337. module_load_include('inc', 'content_menu', 'content_menu.menu_admin');
  338. $item = content_menu_get_menu_item_from_querystring();
  339. if (!empty($item)) {
  340. $item['link_path'] = 'node/' . $field->original_value;
  341. $url = 'admin/structure/menu/manage/' . $item['name'] . '/add';
  342. $vars['output'] = l(t('select'), $url, array('query' => content_menu_assemble_query_string($item)));
  343. }
  344. }
  345. }
  346. /**
  347. * Implements hook_menu_item_target_types_alter().
  348. *
  349. * Extend the target types for a new menu item provided by default.
  350. * See content_menu.api.php for further documentation.
  351. */
  352. function content_menu_menu_item_target_types_alter(&$target_types, &$context) {
  353. module_load_include('inc', 'content_menu', 'content_menu.menu_admin');
  354. _content_menu_menu_item_target_types_alter($target_types, $context);
  355. }
  356. /**
  357. * Alter a menu item's form element in the menu item administration.
  358. *
  359. * @param $el Array The form element of the menu item to alter.
  360. */
  361. function content_menu_menu_item_element_alter(&$el) {
  362. module_load_include('inc', 'content_menu', 'content_menu.menu_admin');
  363. _content_menu_menu_item_element_alter($el);
  364. }
  365. /**
  366. * Implements hook_node_insert().
  367. *
  368. * After creating a new node, if it's being created to replace a menu-dummy,
  369. * perform the replacement.
  370. */
  371. function content_menu_node_insert($node) {
  372. // Ensure that this node is replacing a menu dummy.
  373. if (isset($_GET['menu_link_path']) && $_GET['menu_link_path'] == 'menu-dummy') {
  374. // Update the dummy that is being replaced by this node.
  375. $menu_item = content_menu_get_menu_item_from_querystring();
  376. $menu_item['link_path'] = "node/" . $node->nid;
  377. content_menu_link_save($menu_item);
  378. }
  379. }
  380. /**
  381. * Implements hook_form_alter().
  382. *
  383. * Improve menu item management for node edit forms.
  384. */
  385. function content_menu_form_alter(&$form, &$form_state, $form_id) {
  386. if (isset($form['#node_edit_form'])) {
  387. // Add link to menu overview / admin form to menu fieldset.
  388. if (user_access('administer menu')) {
  389. if ($form['#node_edit_form'] && isset($form['menu'])) {
  390. $menu = explode(':', $form['menu']['link']['parent']['#default_value']);
  391. $form['menu']['link']['menu_admin_link'] = array('#type' => 'markup', '#markup' => l(t('Manage menu structure') . ' …', 'admin/structure/menu/manage/' . $menu[0], array('attributes' => array('target' => '_blank'))));
  392. }
  393. }
  394. if (isset($form['menu'])) {
  395. if (isset($_GET['menu_link_path']) && $_GET['menu_link_path'] == 'menu-dummy') {
  396. // If this node is being created to replace a menu dummy, substitute
  397. // the usual menu form for a description of what will happen.
  398. $form['menu']['enabled']['#access'] = FALSE;
  399. $form['menu']['link']['#access'] = FALSE;
  400. $trail = content_menu_get_menu_trail($_GET['menu_mlid']);
  401. $form['menu']['menu_dummy_replace'] = array(
  402. '#markup' => t('A link to this node will replace the menu dummy at <br /> %trail', array('%trail' => implode(' » ', $trail))),
  403. );
  404. $form['title']['#default_value'] = $form['title_field']['und']['0']['value']['#default_value'] = $_GET['menu_title'];
  405. }
  406. else {
  407. // Hide description field from menu item fieldset.
  408. $form['menu']['link']['description']['#access'] = FALSE;
  409. // Pre-Populate menu item fields, if given via querystring.
  410. if (isset($_GET['menu_title'])) {
  411. $menu_item = content_menu_get_menu_item_from_querystring();
  412. // Set node form's menu item input field
  413. // according to query string input.
  414. $form['menu']['enabled']['#default_value'] = 1;
  415. $form['menu']['link']['hidden']['#value'] = $menu_item['hidden'];
  416. $form['menu']['link']['link_title']['#default_value'] = $menu_item['title'];
  417. $form['menu']['link']['parent']['#default_value'] = $menu_item['name'] . ':' . $menu_item['plid'];
  418. $form['menu']['link']['weight']['#default_value'] = $menu_item['weight'];
  419. $form['title']['#default_value'] = $form['title_field']['und']['0']['value']['#default_value'] = $menu_item['title'];
  420. }
  421. }
  422. }
  423. }
  424. }
  425. /**
  426. * Build the menu trail array that points to the given mlid.
  427. */
  428. function content_menu_get_menu_trail($leaf_mlid) {
  429. $menu_link = menu_link_load($leaf_mlid);
  430. $menu = menu_load($menu_link['menu_name']);
  431. $trail = array();
  432. while ($menu_link) {
  433. array_unshift($trail, $menu_link['link_title']);
  434. $menu_link = menu_link_load($menu_link['plid']);
  435. }
  436. array_unshift($trail, $menu['title']);
  437. return $trail;
  438. }
  439. /**
  440. * Build menu item data from querystring.
  441. */
  442. function content_menu_get_menu_item_from_querystring() {
  443. // Sanitize querystring input.
  444. $menu_item = array();
  445. foreach ($_GET as $get_key => $get_value) {
  446. if (substr($get_key, 0, 5) == 'menu_') {
  447. $menu_item[check_plain(substr($get_key, 5))] = strip_tags($get_value);
  448. }
  449. }
  450. return $menu_item;
  451. }
  452. /**
  453. * Check if a given menu (name) applies for being considered by this module.
  454. */
  455. function content_menu_is_menu_considered($menu_name) {
  456. $exclude_menus = variable_get('content_menu_special_menus',
  457. array('management', 'user-menu', 'navigation', 'devel'));
  458. return !in_array($menu_name, $exclude_menus);
  459. }
  460. /**
  461. * Wrapper for menu_link_save() to create new menu link item from array.
  462. */
  463. function content_menu_link_save($menu_item) {
  464. $menu_item['menu_name'] = $menu_item['name'];
  465. $menu_item['link_title'] = $menu_item['title'];
  466. $menu_item['customized'] = 1;
  467. return menu_link_save($menu_item);
  468. }
  469. /**
  470. * Get config variable 'content_menu_add_existing_content_url'.
  471. */
  472. function content_menu_variable_get_add_existing_content_url() {
  473. $url = variable_get('content_menu_add_existing_content_url', CONTENT_MENU_ADD_EXISTING_CONTENT_URL);
  474. // If using default view, ensure to only goto view page if views is enabled.
  475. if (($url == CONTENT_MENU_ADD_EXISTING_CONTENT_URL) && !module_exists('views')) {
  476. $url = NULL;
  477. }
  478. return $url;
  479. }