menu_link.module 9.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303
  1. <?php
  2. /**
  3. * @file
  4. * Defines a menu link field type.
  5. */
  6. require_once dirname(__FILE__) . '/menu_link.field.inc';
  7. /**
  8. * Name of the fixed Menu link field.
  9. *
  10. * This module provides one Menu link field by default. This field cannot be
  11. * deleted and its storage backend is restricted to field_sql_storage. This way
  12. * other modules can use its database table {field_data_menu_link}, to include
  13. * the {menu_links} table in entity queries.
  14. */
  15. define('MENU_LINK_DEFAULT_FIELD', 'menu_link');
  16. /**
  17. * Implements hook_help().
  18. */
  19. function menu_link_help($path, $arg) {
  20. switch ($path) {
  21. case 'admin/help#menu_link':
  22. $output = '';
  23. $output .= '<h3>' . t('About') . '</h3>';
  24. $output .= '<p>' . t("The Menu link module defines a menu link field type for the Field module. A menu link field may be used to place links into a menu that link to it's entity. See the <a href='@field-help'>Field module help page</a> for more information about fields.", array('@field-help' => url('admin/help/field'), '@filter-help' => url('admin/help/filter'))) . '</p>';
  25. return $output;
  26. }
  27. }
  28. /**
  29. * Load multiple menu links, access checked and link translated for rendering.
  30. *
  31. * This function should never be called from within node_load() or any other
  32. * function used as a menu object load function since an infinite recursion may
  33. * occur.
  34. *
  35. * @param $mlids array
  36. * An array of menu link IDs.
  37. * @param $conditions array
  38. * An associative array of conditions on the {menu_links}
  39. * table, where the keys are the database fields and the values are the
  40. * values those fields must have.
  41. *
  42. * @return
  43. * An array of menu links indexed by mlid.
  44. *
  45. * @see menu_link_load()
  46. *
  47. * @todo Remove this function when http://drupal.org/node/1034732 lands.
  48. */
  49. function menu_link_load_multiple(array $mlids, array $conditions = array()) {
  50. $query = db_select('menu_links', 'ml', array('fetch' => PDO::FETCH_ASSOC));
  51. $query->leftJoin('menu_router', 'm', 'm.path = ml.router_path');
  52. $query->fields('ml');
  53. // Weight should be taken from {menu_links}, not {menu_router}.
  54. $query->addField('ml', 'weight', 'link_weight');
  55. $query->fields('m');
  56. if (!empty($mlids)) {
  57. $query->condition('ml.mlid', $mlids, 'IN');
  58. }
  59. if (!empty($conditions)) {
  60. foreach ($conditions as $field => $value) {
  61. $query->condition('ml.' . $field, $value);
  62. }
  63. }
  64. $items = array();
  65. foreach ($query->execute() as $item) {
  66. $item['weight'] = $item['link_weight'];
  67. $items[$item['mlid']] = $item;
  68. _menu_link_translate($items[$item['mlid']]);
  69. }
  70. return $items;
  71. }
  72. /**
  73. * Delete multiple menu links.
  74. *
  75. * @param $mlids array
  76. * An array of menu link IDs.
  77. * @param $force boolean
  78. * Forces deletion. Internal use only, setting to TRUE is discouraged.
  79. *
  80. * @see menu_link_delete()
  81. *
  82. * @todo Remove this function when http://drupal.org/node/1034732 lands.
  83. */
  84. function menu_link_delete_multiple(array $mlids, $force = FALSE) {
  85. if (!empty($mlids)) {
  86. $query = db_select('menu_links')
  87. ->fields('menu_links')
  88. ->condition('mlid', $mlids, 'IN');
  89. if (!$force) {
  90. // Exclude links belonging to system module except if they are marked
  91. // updated (generated during update from Drupal 5).
  92. $query->condition(db_or()->condition('module', 'system', '<>')->condition('updated', 0, '<>'));
  93. }
  94. $links_to_delete = $query->execute()->fetchAllAssoc('mlid', PDO::FETCH_ASSOC);
  95. if (!empty($links_to_delete)) {
  96. $links_with_children = array();
  97. $parent_mlids = array();
  98. $affected_menus = array();
  99. foreach ($links_to_delete as $item) {
  100. if ($item['has_children']) {
  101. $links_with_children[$item['mlid']] = $item['mlid'];
  102. }
  103. $parent_mlids[$item['plid']] = $item['plid'];
  104. $affected_menus[$item['menu_name']] = $item['menu_name'];
  105. }
  106. $parent_mlids = array_diff_key($parent_mlids, array(0 => 0) + array_keys($links_to_delete));
  107. // Re-parent any children to it's closest parent that is not deleted.
  108. if (!empty($links_with_children)) {
  109. $children = menu_link_load_multiple(array(), array('plid' => $links_with_children));
  110. foreach ($children as $item) {
  111. while (isset($links_to_delete[$item['plid']])) {
  112. $item['plid'] = $links_to_delete[$item['plid']]['plid'];
  113. }
  114. menu_link_save($item);
  115. }
  116. }
  117. db_delete('menu_links')->condition('mlid', array_keys($links_to_delete), 'IN')->execute();
  118. foreach ($links_to_delete as $item) {
  119. // Notify modules we have deleted the item.
  120. module_invoke_all('menu_link_delete', $item);
  121. // Update the has_children status of the parent.
  122. _menu_update_parental_status($item);
  123. }
  124. // Update the has_children status of parents of deleted links.
  125. // @todo fix query und use this instead of _menu_update_parental_status($item);
  126. /*if (!empty($parent_mlids)) {
  127. $exists_query = db_select('menu_links', 'child')
  128. ->fields('child', array('mlid'))
  129. ->condition('child.hidden', 0)
  130. ->where('child.plid = parent.mlid')
  131. ->where('child.menu_name = parent.menu_name')
  132. ->range(0, 1);
  133. db_update('menu_links', 'parent')
  134. ->fields(array('has_children' => 0))
  135. ->condition('has_children', 1)
  136. ->condition('mlid', $parent_mlids, 'IN')
  137. ->notExists($exists_query)
  138. ->execute();
  139. }*/
  140. // Clear caches.
  141. foreach ($affected_menus as $menu_name) {
  142. menu_cache_clear($menu_name);
  143. }
  144. _menu_clear_page_cache();
  145. }
  146. }
  147. }
  148. /**
  149. * Implements hook_menu_delete().
  150. */
  151. function menu_link_menu_delete($menu) {
  152. // Menu should not be removed from settings of menu_link field instances; menus
  153. // have a machine name so they can be recreated. Non existant menus won't be
  154. // available in the field widgets.
  155. }
  156. /**
  157. * Implements hook_menu_link_alter().
  158. *
  159. * @see http://drupal.org/node/1087888
  160. * Add $prior_link to hook_menu_link_update().
  161. */
  162. function menu_link_menu_link_alter($item = NULL) {
  163. static $existing_item;
  164. if (isset($item)) {
  165. $existing_item = FALSE;
  166. if (isset($item['mlid'])) {
  167. if ($existing_item = db_query("SELECT * FROM {menu_links} WHERE mlid = :mlid", array(':mlid' => $item['mlid']))->fetchAssoc()) {
  168. $existing_item['options'] = unserialize($existing_item['options']);
  169. }
  170. }
  171. if ($existing_item['module'] == 'menu_link') {
  172. }
  173. }
  174. return $existing_item;
  175. }
  176. /**
  177. * Implements hook_menu_link_update().
  178. *
  179. * Synchronize menu_link fields with the updated menu link.
  180. */
  181. function menu_link_menu_link_update($item) {
  182. $prior_item = menu_link_menu_link_alter();
  183. if ($item['module'] == 'menu_link' && empty($item['menu_link_field_save'])) {
  184. static $entity_paths;
  185. if (empty($entity_paths)) {
  186. $entity_get_info = entity_get_info();
  187. foreach ($entity_get_info as $entity_type => $entity_info) {
  188. if (isset($entity_info['path'])) {
  189. $entity_path = str_replace('%' . $entity_type, '%', $entity_info['path']);
  190. $entity_paths[$entity_path] = $entity_type;
  191. }
  192. }
  193. }
  194. if (isset($entity_paths[$item['router_path']])) {
  195. // Get entity type
  196. $entity_type = $entity_paths[$item['router_path']];
  197. // Get path to entity without wildcard
  198. $router_path = str_replace('%','',$item['router_path']);
  199. // Get Entity ID
  200. $entity_id = str_replace($router_path, '', $item['link_path']);
  201. // Load entity
  202. $entity = array_shift(entity_load($entity_type, array($entity_id)));
  203. // Get bundle
  204. list( , , $bundle) = entity_extract_ids($entity_type, $entity);
  205. $save_entity = FALSE;
  206. foreach (field_info_instances($entity_type, $bundle) as $instance) {
  207. $field_name = $instance['field_name'];
  208. $field = field_info_field($field_name);
  209. if ($field['module'] == 'menu_link') {
  210. // Check if field exist on this entity
  211. if (isset($entity->{$field_name}) && !empty($entity->{$field_name})) {
  212. // Check if content is the same as current path for each language
  213. foreach ($entity->{$field_name} as $lang => $items) {
  214. // for each contents of fields
  215. foreach ($items as $i => $field_item) {
  216. // Check if it's current item
  217. if ($field_item['mlid'] == $item['mlid']) {
  218. // So give it updated options
  219. foreach ($entity->{$field_name}[$lang][$i] as $option_name => $option_value) {
  220. $entity->{$field_name}[$lang][$i][$option_name] = $item[$option_name];
  221. }
  222. $save_entity = TRUE;
  223. break; // We found item in field content so break to next field
  224. }
  225. }
  226. }
  227. }
  228. }
  229. }
  230. // Entity has been edited so save it
  231. if ($save_entity) {
  232. entity_save($entity_name, $entity);
  233. }
  234. }
  235. }
  236. }
  237. /**
  238. * Implements hook_menu_link_delete().
  239. *
  240. * Remove link from all menu_link fields. Note that this module disables the
  241. * possibility to delete menu links through the Admin > Structure > Menus
  242. * interface that are used in a menu_link field (by storing menu links under its
  243. * own module instead of system). So this hook may not be neccessary at all.
  244. */
  245. function menu_link_menu_link_delete($item) {
  246. if ($item['module'] == 'menu_link' && empty($item['menu_link_field_save'])) {
  247. // TODO
  248. }
  249. }
  250. /**
  251. * Implements hook_field_update_forbid().
  252. */
  253. function menu_link_field_update_forbid($field, $prior_field, $has_data) {
  254. if ($field['field_name'] == MENU_LINK_DEFAULT_FIELD) {
  255. if (!empty($field['settings']['link_path_field'])) {
  256. throw new FieldUpdateForbiddenException(t('The link path cannot not be exposed for the ":menu_link_field" field.', array(':menu_link_field' => MENU_LINK_DEFAULT_FIELD)));
  257. }
  258. }
  259. }
  260. /**
  261. * Implementation of hook_views_api().
  262. */
  263. function menu_link_views_api() {
  264. return array(
  265. 'api' => 3,
  266. 'path' => drupal_get_path('module', 'menu_link'),
  267. );
  268. }