/wp-includes/nav-menu.php

  1. <?php 
  2. /** 
  3. * Navigation Menu functions 
  4. * 
  5. * @package WordPress 
  6. * @subpackage Nav_Menus 
  7. * @since 3.0.0 
  8. */ 
  9.  
  10. /** 
  11. * Returns a navigation menu object. 
  12. * 
  13. * @since 3.0.0 
  14. * 
  15. * @param string $menu Menu ID, slug, or name - or the menu object. 
  16. * @return object|false False if $menu param isn't supplied or term does not exist, menu object if successful. 
  17. */ 
  18. function wp_get_nav_menu_object( $menu ) { 
  19. $menu_obj = false; 
  20.  
  21. if ( is_object( $menu ) ) { 
  22. $menu_obj = $menu; 
  23.  
  24. if ( $menu && ! $menu_obj ) { 
  25. $menu_obj = get_term( $menu, 'nav_menu' ); 
  26.  
  27. if ( ! $menu_obj ) { 
  28. $menu_obj = get_term_by( 'slug', $menu, 'nav_menu' ); 
  29.  
  30. if ( ! $menu_obj ) { 
  31. $menu_obj = get_term_by( 'name', $menu, 'nav_menu' ); 
  32.  
  33. if ( ! $menu_obj || is_wp_error( $menu_obj ) ) { 
  34. $menu_obj = false; 
  35.  
  36. /** 
  37. * Filters the nav_menu term retrieved for wp_get_nav_menu_object(). 
  38. * 
  39. * @since 4.3.0 
  40. * 
  41. * @param object|false $menu_obj Term from nav_menu taxonomy, or false if nothing had been found. 
  42. * @param string $menu The menu ID, slug, or name passed to wp_get_nav_menu_object(). 
  43. */ 
  44. return apply_filters( 'wp_get_nav_menu_object', $menu_obj, $menu ); 
  45.  
  46. /** 
  47. * Check if the given ID is a navigation menu. 
  48. * 
  49. * Returns true if it is; false otherwise. 
  50. * 
  51. * @since 3.0.0 
  52. * 
  53. * @param int|string $menu The menu to check (ID, slug, or name). 
  54. * @return bool Whether the menu exists. 
  55. */ 
  56. function is_nav_menu( $menu ) { 
  57. if ( ! $menu ) 
  58. return false; 
  59.  
  60. $menu_obj = wp_get_nav_menu_object( $menu ); 
  61.  
  62. if ( 
  63. $menu_obj && 
  64. ! is_wp_error( $menu_obj ) && 
  65. ! empty( $menu_obj->taxonomy ) && 
  66. 'nav_menu' == $menu_obj->taxonomy 
  67. return true; 
  68.  
  69. return false; 
  70.  
  71. /** 
  72. * Registers navigation menu locations for a theme. 
  73. * 
  74. * @since 3.0.0 
  75. * 
  76. * @global array $_wp_registered_nav_menus 
  77. * 
  78. * @param array $locations Associative array of menu location identifiers (like a slug) and descriptive text. 
  79. */ 
  80. function register_nav_menus( $locations = array() ) { 
  81. global $_wp_registered_nav_menus; 
  82.  
  83. add_theme_support( 'menus' ); 
  84.  
  85. $_wp_registered_nav_menus = array_merge( (array) $_wp_registered_nav_menus, $locations ); 
  86.  
  87. /** 
  88. * Unregisters a navigation menu location for a theme. 
  89. * 
  90. * @global array $_wp_registered_nav_menus 
  91. * 
  92. * @param string $location The menu location identifier. 
  93. * @return bool True on success, false on failure. 
  94. */ 
  95. function unregister_nav_menu( $location ) { 
  96. global $_wp_registered_nav_menus; 
  97.  
  98. if ( is_array( $_wp_registered_nav_menus ) && isset( $_wp_registered_nav_menus[$location] ) ) { 
  99. unset( $_wp_registered_nav_menus[$location] ); 
  100. if ( empty( $_wp_registered_nav_menus ) ) { 
  101. _remove_theme_support( 'menus' ); 
  102. return true; 
  103. return false; 
  104.  
  105. /** 
  106. * Registers a navigation menu location for a theme. 
  107. * 
  108. * @since 3.0.0 
  109. * 
  110. * @param string $location Menu location identifier, like a slug. 
  111. * @param string $description Menu location descriptive text. 
  112. */ 
  113. function register_nav_menu( $location, $description ) { 
  114. register_nav_menus( array( $location => $description ) ); 
  115. /** 
  116. * Retrieves all registered navigation menu locations in a theme. 
  117. * 
  118. * @since 3.0.0 
  119. * 
  120. * @global array $_wp_registered_nav_menus 
  121. * 
  122. * @return array Registered navigation menu locations. If none are registered, an empty array. 
  123. */ 
  124. function get_registered_nav_menus() { 
  125. global $_wp_registered_nav_menus; 
  126. if ( isset( $_wp_registered_nav_menus ) ) 
  127. return $_wp_registered_nav_menus; 
  128. return array(); 
  129.  
  130. /** 
  131. * Retrieves all registered navigation menu locations and the menus assigned to them. 
  132. * 
  133. * @since 3.0.0 
  134. * 
  135. * @return array Registered navigation menu locations and the menus assigned them. 
  136. * If none are registered, an empty array. 
  137. */ 
  138.  
  139. function get_nav_menu_locations() { 
  140. $locations = get_theme_mod( 'nav_menu_locations' ); 
  141. return ( is_array( $locations ) ) ? $locations : array(); 
  142.  
  143. /** 
  144. * Determines whether a registered nav menu location has a menu assigned to it. 
  145. * 
  146. * @since 3.0.0 
  147. * 
  148. * @param string $location Menu location identifier. 
  149. * @return bool Whether location has a menu. 
  150. */ 
  151. function has_nav_menu( $location ) { 
  152. $has_nav_menu = false; 
  153.  
  154. $registered_nav_menus = get_registered_nav_menus(); 
  155. if ( isset( $registered_nav_menus[ $location ] ) ) { 
  156. $locations = get_nav_menu_locations(); 
  157. $has_nav_menu = ! empty( $locations[ $location ] ); 
  158.  
  159. /** 
  160. * Filters whether a nav menu is assigned to the specified location. 
  161. * 
  162. * @since 4.3.0 
  163. * 
  164. * @param bool $has_nav_menu Whether there is a menu assigned to a location. 
  165. * @param string $location Menu location. 
  166. */ 
  167. return apply_filters( 'has_nav_menu', $has_nav_menu, $location ); 
  168.  
  169. /** 
  170. * Determines whether the given ID is a nav menu item. 
  171. * 
  172. * @since 3.0.0 
  173. * 
  174. * @param int $menu_item_id The ID of the potential nav menu item. 
  175. * @return bool Whether the given ID is that of a nav menu item. 
  176. */ 
  177. function is_nav_menu_item( $menu_item_id = 0 ) { 
  178. return ( ! is_wp_error( $menu_item_id ) && ( 'nav_menu_item' == get_post_type( $menu_item_id ) ) ); 
  179.  
  180. /** 
  181. * Creates a navigation menu. 
  182. * 
  183. * Note that `$menu_name` is expected to be pre-slashed. 
  184. * 
  185. * @since 3.0.0 
  186. * 
  187. * @param string $menu_name Menu name. 
  188. * @return int|WP_Error Menu ID on success, WP_Error object on failure. 
  189. */ 
  190. function wp_create_nav_menu( $menu_name ) { 
  191. // expected_slashed ($menu_name) 
  192. return wp_update_nav_menu_object( 0, array( 'menu-name' => $menu_name ) ); 
  193.  
  194. /** 
  195. * Delete a Navigation Menu. 
  196. * 
  197. * @since 3.0.0 
  198. * 
  199. * @param string $menu Menu ID, slug, or name. 
  200. * @return bool|WP_Error True on success, false or WP_Error object on failure. 
  201. */ 
  202. function wp_delete_nav_menu( $menu ) { 
  203. $menu = wp_get_nav_menu_object( $menu ); 
  204. if ( ! $menu ) 
  205. return false; 
  206.  
  207. $menu_objects = get_objects_in_term( $menu->term_id, 'nav_menu' ); 
  208. if ( ! empty( $menu_objects ) ) { 
  209. foreach ( $menu_objects as $item ) { 
  210. wp_delete_post( $item ); 
  211.  
  212. $result = wp_delete_term( $menu->term_id, 'nav_menu' ); 
  213.  
  214. // Remove this menu from any locations. 
  215. $locations = get_nav_menu_locations(); 
  216. foreach ( $locations as $location => $menu_id ) { 
  217. if ( $menu_id == $menu->term_id ) 
  218. $locations[ $location ] = 0; 
  219. set_theme_mod( 'nav_menu_locations', $locations ); 
  220.  
  221. if ( $result && !is_wp_error($result) ) 
  222.  
  223. /** 
  224. * Fires after a navigation menu has been successfully deleted. 
  225. * 
  226. * @since 3.0.0 
  227. * 
  228. * @param int $term_id ID of the deleted menu. 
  229. */ 
  230. do_action( 'wp_delete_nav_menu', $menu->term_id ); 
  231.  
  232. return $result; 
  233.  
  234. /** 
  235. * Save the properties of a menu or create a new menu with those properties. 
  236. * 
  237. * Note that `$menu_data` is expected to be pre-slashed. 
  238. * 
  239. * @since 3.0.0 
  240. * 
  241. * @param int $menu_id The ID of the menu or "0" to create a new menu. 
  242. * @param array $menu_data The array of menu data. 
  243. * @return int|WP_Error Menu ID on success, WP_Error object on failure. 
  244. */ 
  245. function wp_update_nav_menu_object( $menu_id = 0, $menu_data = array() ) { 
  246. // expected_slashed ($menu_data) 
  247. $menu_id = (int) $menu_id; 
  248.  
  249. $_menu = wp_get_nav_menu_object( $menu_id ); 
  250.  
  251. $args = array( 
  252. 'description' => ( isset( $menu_data['description'] ) ? $menu_data['description'] : '' ),  
  253. 'name' => ( isset( $menu_data['menu-name'] ) ? $menu_data['menu-name'] : '' ),  
  254. 'parent' => ( isset( $menu_data['parent'] ) ? (int) $menu_data['parent'] : 0 ),  
  255. 'slug' => null,  
  256. ); 
  257.  
  258. // double-check that we're not going to have one menu take the name of another 
  259. $_possible_existing = get_term_by( 'name', $menu_data['menu-name'], 'nav_menu' ); 
  260. if ( 
  261. $_possible_existing && 
  262. ! is_wp_error( $_possible_existing ) && 
  263. isset( $_possible_existing->term_id ) && 
  264. $_possible_existing->term_id != $menu_id 
  265. ) { 
  266. return new WP_Error( 'menu_exists',  
  267. /** translators: %s: menu name */ 
  268. sprintf( __( 'The menu name %s conflicts with another menu name. Please try another.' ),  
  269. '<strong>' . esc_html( $menu_data['menu-name'] ) . '</strong>' 
  270. ); 
  271.  
  272. // menu doesn't already exist, so create a new menu 
  273. if ( ! $_menu || is_wp_error( $_menu ) ) { 
  274. $menu_exists = get_term_by( 'name', $menu_data['menu-name'], 'nav_menu' ); 
  275.  
  276. if ( $menu_exists ) { 
  277. return new WP_Error( 'menu_exists',  
  278. /** translators: %s: menu name */ 
  279. sprintf( __( 'The menu name %s conflicts with another menu name. Please try another.' ),  
  280. '<strong>' . esc_html( $menu_data['menu-name'] ) . '</strong>' 
  281. ); 
  282.  
  283. $_menu = wp_insert_term( $menu_data['menu-name'], 'nav_menu', $args ); 
  284.  
  285. if ( is_wp_error( $_menu ) ) 
  286. return $_menu; 
  287.  
  288. /** 
  289. * Fires after a navigation menu is successfully created. 
  290. * 
  291. * @since 3.0.0 
  292. * 
  293. * @param int $term_id ID of the new menu. 
  294. * @param array $menu_data An array of menu data. 
  295. */ 
  296. do_action( 'wp_create_nav_menu', $_menu['term_id'], $menu_data ); 
  297.  
  298. return (int) $_menu['term_id']; 
  299.  
  300. if ( ! $_menu || ! isset( $_menu->term_id ) ) 
  301. return 0; 
  302.  
  303. $menu_id = (int) $_menu->term_id; 
  304.  
  305. $update_response = wp_update_term( $menu_id, 'nav_menu', $args ); 
  306.  
  307. if ( is_wp_error( $update_response ) ) 
  308. return $update_response; 
  309.  
  310. $menu_id = (int) $update_response['term_id']; 
  311.  
  312. /** 
  313. * Fires after a navigation menu has been successfully updated. 
  314. * 
  315. * @since 3.0.0 
  316. * 
  317. * @param int $menu_id ID of the updated menu. 
  318. * @param array $menu_data An array of menu data. 
  319. */ 
  320. do_action( 'wp_update_nav_menu', $menu_id, $menu_data ); 
  321. return $menu_id; 
  322.  
  323. /** 
  324. * Save the properties of a menu item or create a new one. 
  325. * 
  326. * The menu-item-title, menu-item-description, and menu-item-attr-title are expected 
  327. * to be pre-slashed since they are passed directly into `wp_insert_post()`. 
  328. * 
  329. * @since 3.0.0 
  330. * 
  331. * @param int $menu_id The ID of the menu. Required. If "0", makes the menu item a draft orphan. 
  332. * @param int $menu_item_db_id The ID of the menu item. If "0", creates a new menu item. 
  333. * @param array $menu_item_data The menu item's data. 
  334. * @return int|WP_Error The menu item's database ID or WP_Error object on failure. 
  335. */ 
  336. function wp_update_nav_menu_item( $menu_id = 0, $menu_item_db_id = 0, $menu_item_data = array() ) { 
  337. $menu_id = (int) $menu_id; 
  338. $menu_item_db_id = (int) $menu_item_db_id; 
  339.  
  340. // make sure that we don't convert non-nav_menu_item objects into nav_menu_item objects 
  341. if ( ! empty( $menu_item_db_id ) && ! is_nav_menu_item( $menu_item_db_id ) ) 
  342. return new WP_Error( 'update_nav_menu_item_failed', __( 'The given object ID is not that of a menu item.' ) ); 
  343.  
  344. $menu = wp_get_nav_menu_object( $menu_id ); 
  345.  
  346. if ( ! $menu && 0 !== $menu_id ) { 
  347. return new WP_Error( 'invalid_menu_id', __( 'Invalid menu ID.' ) ); 
  348.  
  349. if ( is_wp_error( $menu ) ) { 
  350. return $menu; 
  351.  
  352. $defaults = array( 
  353. 'menu-item-db-id' => $menu_item_db_id,  
  354. 'menu-item-object-id' => 0,  
  355. 'menu-item-object' => '',  
  356. 'menu-item-parent-id' => 0,  
  357. 'menu-item-position' => 0,  
  358. 'menu-item-type' => 'custom',  
  359. 'menu-item-title' => '',  
  360. 'menu-item-url' => '',  
  361. 'menu-item-description' => '',  
  362. 'menu-item-attr-title' => '',  
  363. 'menu-item-target' => '',  
  364. 'menu-item-classes' => '',  
  365. 'menu-item-xfn' => '',  
  366. 'menu-item-status' => '',  
  367. ); 
  368.  
  369. $args = wp_parse_args( $menu_item_data, $defaults ); 
  370.  
  371. if ( 0 == $menu_id ) { 
  372. $args['menu-item-position'] = 1; 
  373. } elseif ( 0 == (int) $args['menu-item-position'] ) { 
  374. $menu_items = 0 == $menu_id ? array() : (array) wp_get_nav_menu_items( $menu_id, array( 'post_status' => 'publish, draft' ) ); 
  375. $last_item = array_pop( $menu_items ); 
  376. $args['menu-item-position'] = ( $last_item && isset( $last_item->menu_order ) ) ? 1 + $last_item->menu_order : count( $menu_items ); 
  377.  
  378. $original_parent = 0 < $menu_item_db_id ? get_post_field( 'post_parent', $menu_item_db_id ) : 0; 
  379.  
  380. if ( 'custom' != $args['menu-item-type'] ) { 
  381. /** if non-custom menu item, then: 
  382. * use original object's URL 
  383. * blank default title to sync with original object's 
  384. */ 
  385.  
  386. $args['menu-item-url'] = ''; 
  387.  
  388. $original_title = ''; 
  389. if ( 'taxonomy' == $args['menu-item-type'] ) { 
  390. $original_parent = get_term_field( 'parent', $args['menu-item-object-id'], $args['menu-item-object'], 'raw' ); 
  391. $original_title = get_term_field( 'name', $args['menu-item-object-id'], $args['menu-item-object'], 'raw' ); 
  392. } elseif ( 'post_type' == $args['menu-item-type'] ) { 
  393.  
  394. $original_object = get_post( $args['menu-item-object-id'] ); 
  395. $original_parent = (int) $original_object->post_parent; 
  396. $original_title = $original_object->post_title; 
  397. } elseif ( 'post_type_archive' == $args['menu-item-type'] ) { 
  398. $original_object = get_post_type_object( $args['menu-item-object'] ); 
  399. if ( $original_object ) { 
  400. $original_title = $original_object->labels->archives; 
  401.  
  402. if ( $args['menu-item-title'] == $original_title ) 
  403. $args['menu-item-title'] = ''; 
  404.  
  405. // hack to get wp to create a post object when too many properties are empty 
  406. if ( '' == $args['menu-item-title'] && '' == $args['menu-item-description'] ) 
  407. $args['menu-item-description'] = ' '; 
  408.  
  409. // Populate the menu item object 
  410. $post = array( 
  411. 'menu_order' => $args['menu-item-position'],  
  412. 'ping_status' => 0,  
  413. 'post_content' => $args['menu-item-description'],  
  414. 'post_excerpt' => $args['menu-item-attr-title'],  
  415. 'post_parent' => $original_parent,  
  416. 'post_title' => $args['menu-item-title'],  
  417. 'post_type' => 'nav_menu_item',  
  418. ); 
  419.  
  420. $update = 0 != $menu_item_db_id; 
  421.  
  422. // New menu item. Default is draft status 
  423. if ( ! $update ) { 
  424. $post['ID'] = 0; 
  425. $post['post_status'] = 'publish' == $args['menu-item-status'] ? 'publish' : 'draft'; 
  426. $menu_item_db_id = wp_insert_post( $post ); 
  427. if ( ! $menu_item_db_id || is_wp_error( $menu_item_db_id ) ) 
  428. return $menu_item_db_id; 
  429.  
  430. /** 
  431. * Fires immediately after a new navigation menu item has been added. 
  432. * 
  433. * @since 4.4.0 
  434. * 
  435. * @see wp_update_nav_menu_item() 
  436. * 
  437. * @param int $menu_id ID of the updated menu. 
  438. * @param int $menu_item_db_id ID of the new menu item. 
  439. * @param array $args An array of arguments used to update/add the menu item. 
  440. */ 
  441. do_action( 'wp_add_nav_menu_item', $menu_id, $menu_item_db_id, $args ); 
  442.  
  443. // Associate the menu item with the menu term 
  444. // Only set the menu term if it isn't set to avoid unnecessary wp_get_object_terms() 
  445. if ( $menu_id && ( ! $update || ! is_object_in_term( $menu_item_db_id, 'nav_menu', (int) $menu->term_id ) ) ) { 
  446. wp_set_object_terms( $menu_item_db_id, array( $menu->term_id ), 'nav_menu' ); 
  447.  
  448. if ( 'custom' == $args['menu-item-type'] ) { 
  449. $args['menu-item-object-id'] = $menu_item_db_id; 
  450. $args['menu-item-object'] = 'custom'; 
  451.  
  452. $menu_item_db_id = (int) $menu_item_db_id; 
  453.  
  454. update_post_meta( $menu_item_db_id, '_menu_item_type', sanitize_key($args['menu-item-type']) ); 
  455. update_post_meta( $menu_item_db_id, '_menu_item_menu_item_parent', strval( (int) $args['menu-item-parent-id'] ) ); 
  456. update_post_meta( $menu_item_db_id, '_menu_item_object_id', strval( (int) $args['menu-item-object-id'] ) ); 
  457. update_post_meta( $menu_item_db_id, '_menu_item_object', sanitize_key($args['menu-item-object']) ); 
  458. update_post_meta( $menu_item_db_id, '_menu_item_target', sanitize_key($args['menu-item-target']) ); 
  459.  
  460. $args['menu-item-classes'] = array_map( 'sanitize_html_class', explode( ' ', $args['menu-item-classes'] ) ); 
  461. $args['menu-item-xfn'] = implode( ' ', array_map( 'sanitize_html_class', explode( ' ', $args['menu-item-xfn'] ) ) ); 
  462. update_post_meta( $menu_item_db_id, '_menu_item_classes', $args['menu-item-classes'] ); 
  463. update_post_meta( $menu_item_db_id, '_menu_item_xfn', $args['menu-item-xfn'] ); 
  464. update_post_meta( $menu_item_db_id, '_menu_item_url', esc_url_raw($args['menu-item-url']) ); 
  465.  
  466. if ( 0 == $menu_id ) 
  467. update_post_meta( $menu_item_db_id, '_menu_item_orphaned', (string) time() ); 
  468. elseif ( get_post_meta( $menu_item_db_id, '_menu_item_orphaned' ) ) 
  469. delete_post_meta( $menu_item_db_id, '_menu_item_orphaned' ); 
  470.  
  471. // Update existing menu item. Default is publish status 
  472. if ( $update ) { 
  473. $post['ID'] = $menu_item_db_id; 
  474. $post['post_status'] = 'draft' == $args['menu-item-status'] ? 'draft' : 'publish'; 
  475. wp_update_post( $post ); 
  476.  
  477. /** 
  478. * Fires after a navigation menu item has been updated. 
  479. * 
  480. * @since 3.0.0 
  481. * 
  482. * @see wp_update_nav_menu_item() 
  483. * 
  484. * @param int $menu_id ID of the updated menu. 
  485. * @param int $menu_item_db_id ID of the updated menu item. 
  486. * @param array $args An array of arguments used to update a menu item. 
  487. */ 
  488. do_action( 'wp_update_nav_menu_item', $menu_id, $menu_item_db_id, $args ); 
  489.  
  490. return $menu_item_db_id; 
  491.  
  492. /** 
  493. * Returns all navigation menu objects. 
  494. * 
  495. * @since 3.0.0 
  496. * @since 4.1.0 Default value of the 'orderby' argument was changed from 'none' 
  497. * to 'name'. 
  498. * 
  499. * @param array $args Optional. Array of arguments passed on to get_terms(). 
  500. * Default empty array. 
  501. * @return array Menu objects. 
  502. */ 
  503. function wp_get_nav_menus( $args = array() ) { 
  504. $defaults = array( 'hide_empty' => false, 'orderby' => 'name' ); 
  505. $args = wp_parse_args( $args, $defaults ); 
  506.  
  507. /** 
  508. * Filters the navigation menu objects being returned. 
  509. * 
  510. * @since 3.0.0 
  511. * 
  512. * @see get_terms() 
  513. * 
  514. * @param array $menus An array of menu objects. 
  515. * @param array $args An array of arguments used to retrieve menu objects. 
  516. */ 
  517. return apply_filters( 'wp_get_nav_menus', get_terms( 'nav_menu', $args), $args ); 
  518.  
  519. /** 
  520. * Sort menu items by the desired key. 
  521. * 
  522. * @since 3.0.0 
  523. * @access private 
  524. * 
  525. * @global string $_menu_item_sort_prop 
  526. * 
  527. * @param object $a The first object to compare 
  528. * @param object $b The second object to compare 
  529. * @return int -1, 0, or 1 if $a is considered to be respectively less than, equal to, or greater than $b. 
  530. */ 
  531. function _sort_nav_menu_items( $a, $b ) { 
  532. global $_menu_item_sort_prop; 
  533.  
  534. if ( empty( $_menu_item_sort_prop ) ) 
  535. return 0; 
  536.  
  537. if ( ! isset( $a->$_menu_item_sort_prop ) || ! isset( $b->$_menu_item_sort_prop ) ) 
  538. return 0; 
  539.  
  540. $_a = (int) $a->$_menu_item_sort_prop; 
  541. $_b = (int) $b->$_menu_item_sort_prop; 
  542.  
  543. if ( $a->$_menu_item_sort_prop == $b->$_menu_item_sort_prop ) 
  544. return 0; 
  545. elseif ( $_a == $a->$_menu_item_sort_prop && $_b == $b->$_menu_item_sort_prop ) 
  546. return $_a < $_b ? -1 : 1; 
  547. else 
  548. return strcmp( $a->$_menu_item_sort_prop, $b->$_menu_item_sort_prop ); 
  549.  
  550. /** 
  551. * Return if a menu item is valid. 
  552. * 
  553. * @link https://core.trac.wordpress.org/ticket/13958 
  554. * 
  555. * @since 3.2.0 
  556. * @access private 
  557. * 
  558. * @param object $item The menu item to check. 
  559. * @return bool False if invalid, otherwise true. 
  560. */ 
  561. function _is_valid_nav_menu_item( $item ) { 
  562. return empty( $item->_invalid ); 
  563.  
  564. /** 
  565. * Return all menu items of a navigation menu. 
  566. * 
  567. * @since 3.0.0 
  568. * 
  569. * @global string $_menu_item_sort_prop 
  570. * @staticvar array $fetched 
  571. * 
  572. * @param string $menu Menu name, ID, or slug. 
  573. * @param array $args Optional. Arguments to pass to get_posts(). 
  574. * @return false|array $items Array of menu items, otherwise false. 
  575. */ 
  576. function wp_get_nav_menu_items( $menu, $args = array() ) { 
  577. $menu = wp_get_nav_menu_object( $menu ); 
  578.  
  579. if ( ! $menu ) { 
  580. return false; 
  581.  
  582. static $fetched = array(); 
  583.  
  584. $items = get_objects_in_term( $menu->term_id, 'nav_menu' ); 
  585. if ( is_wp_error( $items ) ) { 
  586. return false; 
  587.  
  588. $defaults = array( 'order' => 'ASC', 'orderby' => 'menu_order', 'post_type' => 'nav_menu_item',  
  589. 'post_status' => 'publish', 'output' => ARRAY_A, 'output_key' => 'menu_order', 'nopaging' => true ); 
  590. $args = wp_parse_args( $args, $defaults ); 
  591. $args['include'] = $items; 
  592.  
  593. if ( ! empty( $items ) ) { 
  594. $items = get_posts( $args ); 
  595. } else { 
  596. $items = array(); 
  597.  
  598. // Get all posts and terms at once to prime the caches 
  599. if ( empty( $fetched[$menu->term_id] ) || wp_using_ext_object_cache() ) { 
  600. $fetched[$menu->term_id] = true; 
  601. $posts = array(); 
  602. $terms = array(); 
  603. foreach ( $items as $item ) { 
  604. $object_id = get_post_meta( $item->ID, '_menu_item_object_id', true ); 
  605. $object = get_post_meta( $item->ID, '_menu_item_object', true ); 
  606. $type = get_post_meta( $item->ID, '_menu_item_type', true ); 
  607.  
  608. if ( 'post_type' == $type ) 
  609. $posts[$object][] = $object_id; 
  610. elseif ( 'taxonomy' == $type) 
  611. $terms[$object][] = $object_id; 
  612.  
  613. if ( ! empty( $posts ) ) { 
  614. foreach ( array_keys($posts) as $post_type ) { 
  615. get_posts( array('post__in' => $posts[$post_type], 'post_type' => $post_type, 'nopaging' => true, 'update_post_term_cache' => false) ); 
  616. unset($posts); 
  617.  
  618. if ( ! empty( $terms ) ) { 
  619. foreach ( array_keys($terms) as $taxonomy ) { 
  620. get_terms( $taxonomy, array( 
  621. 'include' => $terms[ $taxonomy ],  
  622. 'hierarchical' => false,  
  623. ) ); 
  624. unset($terms); 
  625.  
  626. $items = array_map( 'wp_setup_nav_menu_item', $items ); 
  627.  
  628. if ( ! is_admin() ) { // Remove invalid items only in front end 
  629. $items = array_filter( $items, '_is_valid_nav_menu_item' ); 
  630.  
  631. if ( ARRAY_A == $args['output'] ) { 
  632. $GLOBALS['_menu_item_sort_prop'] = $args['output_key']; 
  633. usort($items, '_sort_nav_menu_items'); 
  634. $i = 1; 
  635. foreach ( $items as $k => $item ) { 
  636. $items[$k]->{$args['output_key']} = $i++; 
  637.  
  638. /** 
  639. * Filters the navigation menu items being returned. 
  640. * 
  641. * @since 3.0.0 
  642. * 
  643. * @param array $items An array of menu item post objects. 
  644. * @param object $menu The menu object. 
  645. * @param array $args An array of arguments used to retrieve menu item objects. 
  646. */ 
  647. return apply_filters( 'wp_get_nav_menu_items', $items, $menu, $args ); 
  648.  
  649. /** 
  650. * Decorates a menu item object with the shared navigation menu item properties. 
  651. * 
  652. * Properties: 
  653. * - ID: The term_id if the menu item represents a taxonomy term. 
  654. * - attr_title: The title attribute of the link element for this menu item. 
  655. * - classes: The array of class attribute values for the link element of this menu item. 
  656. * - db_id: The DB ID of this item as a nav_menu_item object, if it exists (0 if it doesn't exist). 
  657. * - description: The description of this menu item. 
  658. * - menu_item_parent: The DB ID of the nav_menu_item that is this item's menu parent, if any. 0 otherwise. 
  659. * - object: The type of object originally represented, such as "category, " "post", or "attachment." 
  660. * - object_id: The DB ID of the original object this menu item represents, e.g. ID for posts and term_id for categories. 
  661. * - post_parent: The DB ID of the original object's parent object, if any (0 otherwise). 
  662. * - post_title: A "no title" label if menu item represents a post that lacks a title. 
  663. * - target: The target attribute of the link element for this menu item. 
  664. * - title: The title of this menu item. 
  665. * - type: The family of objects originally represented, such as "post_type" or "taxonomy." 
  666. * - type_label: The singular label used to describe this type of menu item. 
  667. * - url: The URL to which this menu item points. 
  668. * - xfn: The XFN relationship expressed in the link of this menu item. 
  669. * - _invalid: Whether the menu item represents an object that no longer exists. 
  670. * 
  671. * @since 3.0.0 
  672. * 
  673. * @param object $menu_item The menu item to modify. 
  674. * @return object $menu_item The menu item with standard menu item properties. 
  675. */ 
  676. function wp_setup_nav_menu_item( $menu_item ) { 
  677. if ( isset( $menu_item->post_type ) ) { 
  678. if ( 'nav_menu_item' == $menu_item->post_type ) { 
  679. $menu_item->db_id = (int) $menu_item->ID; 
  680. $menu_item->menu_item_parent = ! isset( $menu_item->menu_item_parent ) ? get_post_meta( $menu_item->ID, '_menu_item_menu_item_parent', true ) : $menu_item->menu_item_parent; 
  681. $menu_item->object_id = ! isset( $menu_item->object_id ) ? get_post_meta( $menu_item->ID, '_menu_item_object_id', true ) : $menu_item->object_id; 
  682. $menu_item->object = ! isset( $menu_item->object ) ? get_post_meta( $menu_item->ID, '_menu_item_object', true ) : $menu_item->object; 
  683. $menu_item->type = ! isset( $menu_item->type ) ? get_post_meta( $menu_item->ID, '_menu_item_type', true ) : $menu_item->type; 
  684.  
  685. if ( 'post_type' == $menu_item->type ) { 
  686. $object = get_post_type_object( $menu_item->object ); 
  687. if ( $object ) { 
  688. $menu_item->type_label = $object->labels->singular_name; 
  689. } else { 
  690. $menu_item->type_label = $menu_item->object; 
  691. $menu_item->_invalid = true; 
  692.  
  693. $menu_item->url = get_permalink( $menu_item->object_id ); 
  694.  
  695. $original_object = get_post( $menu_item->object_id ); 
  696. /** This filter is documented in wp-includes/post-template.php */ 
  697. $original_title = apply_filters( 'the_title', $original_object->post_title, $original_object->ID ); 
  698.  
  699. if ( '' === $original_title ) { 
  700. /** translators: %d: ID of a post */ 
  701. $original_title = sprintf( __( '#%d (no title)' ), $original_object->ID ); 
  702.  
  703. $menu_item->title = '' == $menu_item->post_title ? $original_title : $menu_item->post_title; 
  704.  
  705. } elseif ( 'post_type_archive' == $menu_item->type ) { 
  706. $object = get_post_type_object( $menu_item->object ); 
  707. if ( $object ) { 
  708. $menu_item->title = '' == $menu_item->post_title ? $object->labels->archives : $menu_item->post_title; 
  709. $post_type_description = $object->description; 
  710. } else { 
  711. $menu_item->_invalid = true; 
  712. $post_type_description = ''; 
  713.  
  714. $menu_item->type_label = __( 'Post Type Archive' ); 
  715. $post_content = wp_trim_words( $menu_item->post_content, 200 ); 
  716. $post_type_description = '' == $post_content ? $post_type_description : $post_content;  
  717. $menu_item->url = get_post_type_archive_link( $menu_item->object ); 
  718. } elseif ( 'taxonomy' == $menu_item->type ) { 
  719. $object = get_taxonomy( $menu_item->object ); 
  720. if ( $object ) { 
  721. $menu_item->type_label = $object->labels->singular_name; 
  722. } else { 
  723. $menu_item->type_label = $menu_item->object; 
  724. $menu_item->_invalid = true; 
  725.  
  726. $term_url = get_term_link( (int) $menu_item->object_id, $menu_item->object ); 
  727. $menu_item->url = !is_wp_error( $term_url ) ? $term_url : ''; 
  728.  
  729. $original_title = get_term_field( 'name', $menu_item->object_id, $menu_item->object, 'raw' ); 
  730. if ( is_wp_error( $original_title ) ) 
  731. $original_title = false; 
  732. $menu_item->title = '' == $menu_item->post_title ? $original_title : $menu_item->post_title; 
  733.  
  734. } else { 
  735. $menu_item->type_label = __('Custom Link'); 
  736. $menu_item->title = $menu_item->post_title; 
  737. $menu_item->url = ! isset( $menu_item->url ) ? get_post_meta( $menu_item->ID, '_menu_item_url', true ) : $menu_item->url; 
  738.  
  739. $menu_item->target = ! isset( $menu_item->target ) ? get_post_meta( $menu_item->ID, '_menu_item_target', true ) : $menu_item->target; 
  740.  
  741. /** 
  742. * Filters a navigation menu item's title attribute. 
  743. * 
  744. * @since 3.0.0 
  745. * 
  746. * @param string $item_title The menu item title attribute. 
  747. */ 
  748. $menu_item->attr_title = ! isset( $menu_item->attr_title ) ? apply_filters( 'nav_menu_attr_title', $menu_item->post_excerpt ) : $menu_item->attr_title; 
  749.  
  750. if ( ! isset( $menu_item->description ) ) { 
  751. /** 
  752. * Filters a navigation menu item's description. 
  753. * 
  754. * @since 3.0.0 
  755. * 
  756. * @param string $description The menu item description. 
  757. */ 
  758. $menu_item->description = apply_filters( 'nav_menu_description', wp_trim_words( $menu_item->post_content, 200 ) ); 
  759.  
  760. $menu_item->classes = ! isset( $menu_item->classes ) ? (array) get_post_meta( $menu_item->ID, '_menu_item_classes', true ) : $menu_item->classes; 
  761. $menu_item->xfn = ! isset( $menu_item->xfn ) ? get_post_meta( $menu_item->ID, '_menu_item_xfn', true ) : $menu_item->xfn; 
  762. } else { 
  763. $menu_item->db_id = 0; 
  764. $menu_item->menu_item_parent = 0; 
  765. $menu_item->object_id = (int) $menu_item->ID; 
  766. $menu_item->type = 'post_type'; 
  767.  
  768. $object = get_post_type_object( $menu_item->post_type ); 
  769. $menu_item->object = $object->name; 
  770. $menu_item->type_label = $object->labels->singular_name; 
  771.  
  772. if ( '' === $menu_item->post_title ) { 
  773. /** translators: %d: ID of a post */ 
  774. $menu_item->post_title = sprintf( __( '#%d (no title)' ), $menu_item->ID ); 
  775.  
  776. $menu_item->title = $menu_item->post_title; 
  777. $menu_item->url = get_permalink( $menu_item->ID ); 
  778. $menu_item->target = ''; 
  779.  
  780. /** This filter is documented in wp-includes/nav-menu.php */ 
  781. $menu_item->attr_title = apply_filters( 'nav_menu_attr_title', '' ); 
  782.  
  783. /** This filter is documented in wp-includes/nav-menu.php */ 
  784. $menu_item->description = apply_filters( 'nav_menu_description', '' ); 
  785. $menu_item->classes = array(); 
  786. $menu_item->xfn = ''; 
  787. } elseif ( isset( $menu_item->taxonomy ) ) { 
  788. $menu_item->ID = $menu_item->term_id; 
  789. $menu_item->db_id = 0; 
  790. $menu_item->menu_item_parent = 0; 
  791. $menu_item->object_id = (int) $menu_item->term_id; 
  792. $menu_item->post_parent = (int) $menu_item->parent; 
  793. $menu_item->type = 'taxonomy'; 
  794.  
  795. $object = get_taxonomy( $menu_item->taxonomy ); 
  796. $menu_item->object = $object->name; 
  797. $menu_item->type_label = $object->labels->singular_name; 
  798.  
  799. $menu_item->title = $menu_item->name; 
  800. $menu_item->url = get_term_link( $menu_item, $menu_item->taxonomy ); 
  801. $menu_item->target = ''; 
  802. $menu_item->attr_title = ''; 
  803. $menu_item->description = get_term_field( 'description', $menu_item->term_id, $menu_item->taxonomy ); 
  804. $menu_item->classes = array(); 
  805. $menu_item->xfn = ''; 
  806.  
  807.  
  808. /** 
  809. * Filters a navigation menu item object. 
  810. * 
  811. * @since 3.0.0 
  812. * 
  813. * @param object $menu_item The menu item object. 
  814. */ 
  815. return apply_filters( 'wp_setup_nav_menu_item', $menu_item ); 
  816.  
  817. /** 
  818. * Get the menu items associated with a particular object. 
  819. * 
  820. * @since 3.0.0 
  821. * 
  822. * @param int $object_id The ID of the original object. 
  823. * @param string $object_type The type of object, such as "taxonomy" or "post_type." 
  824. * @param string $taxonomy If $object_type is "taxonomy", $taxonomy is the name of the tax that $object_id belongs to 
  825. * @return array The array of menu item IDs; empty array if none; 
  826. */ 
  827. function wp_get_associated_nav_menu_items( $object_id = 0, $object_type = 'post_type', $taxonomy = '' ) { 
  828. $object_id = (int) $object_id; 
  829. $menu_item_ids = array(); 
  830.  
  831. $query = new WP_Query; 
  832. $menu_items = $query->query( 
  833. array( 
  834. 'meta_key' => '_menu_item_object_id',  
  835. 'meta_value' => $object_id,  
  836. 'post_status' => 'any',  
  837. 'post_type' => 'nav_menu_item',  
  838. 'posts_per_page' => -1,  
  839. ); 
  840. foreach ( (array) $menu_items as $menu_item ) { 
  841. if ( isset( $menu_item->ID ) && is_nav_menu_item( $menu_item->ID ) ) { 
  842. $menu_item_type = get_post_meta( $menu_item->ID, '_menu_item_type', true ); 
  843. if ( 
  844. 'post_type' == $object_type && 
  845. 'post_type' == $menu_item_type 
  846. ) { 
  847. $menu_item_ids[] = (int) $menu_item->ID; 
  848. } elseif ( 
  849. 'taxonomy' == $object_type && 
  850. 'taxonomy' == $menu_item_type && 
  851. get_post_meta( $menu_item->ID, '_menu_item_object', true ) == $taxonomy 
  852. ) { 
  853. $menu_item_ids[] = (int) $menu_item->ID; 
  854.  
  855. return array_unique( $menu_item_ids ); 
  856.  
  857. /** 
  858. * Callback for handling a menu item when its original object is deleted. 
  859. * 
  860. * @since 3.0.0 
  861. * @access private 
  862. * 
  863. * @param int $object_id The ID of the original object being trashed. 
  864. * 
  865. */ 
  866. function _wp_delete_post_menu_item( $object_id = 0 ) { 
  867. $object_id = (int) $object_id; 
  868.  
  869. $menu_item_ids = wp_get_associated_nav_menu_items( $object_id, 'post_type' ); 
  870.  
  871. foreach ( (array) $menu_item_ids as $menu_item_id ) { 
  872. wp_delete_post( $menu_item_id, true ); 
  873.  
  874. /** 
  875. * Serves as a callback for handling a menu item when its original object is deleted. 
  876. * 
  877. * @since 3.0.0 
  878. * @access private 
  879. * 
  880. * @param int $object_id Optional. The ID of the original object being trashed. Default 0. 
  881. * @param int $tt_id Term taxonomy ID. Unused. 
  882. * @param string $taxonomy Taxonomy slug. 
  883. */ 
  884. function _wp_delete_tax_menu_item( $object_id = 0, $tt_id, $taxonomy ) { 
  885. $object_id = (int) $object_id; 
  886.  
  887. $menu_item_ids = wp_get_associated_nav_menu_items( $object_id, 'taxonomy', $taxonomy ); 
  888.  
  889. foreach ( (array) $menu_item_ids as $menu_item_id ) { 
  890. wp_delete_post( $menu_item_id, true ); 
  891.  
  892. /** 
  893. * Automatically add newly published page objects to menus with that as an option. 
  894. * 
  895. * @since 3.0.0 
  896. * @access private 
  897. * 
  898. * @param string $new_status The new status of the post object. 
  899. * @param string $old_status The old status of the post object. 
  900. * @param object $post The post object being transitioned from one status to another. 
  901. */ 
  902. function _wp_auto_add_pages_to_menu( $new_status, $old_status, $post ) { 
  903. if ( 'publish' != $new_status || 'publish' == $old_status || 'page' != $post->post_type ) 
  904. return; 
  905. if ( ! empty( $post->post_parent ) ) 
  906. return; 
  907. $auto_add = get_option( 'nav_menu_options' ); 
  908. if ( empty( $auto_add ) || ! is_array( $auto_add ) || ! isset( $auto_add['auto_add'] ) ) 
  909. return; 
  910. $auto_add = $auto_add['auto_add']; 
  911. if ( empty( $auto_add ) || ! is_array( $auto_add ) ) 
  912. return; 
  913.  
  914. $args = array( 
  915. 'menu-item-object-id' => $post->ID,  
  916. 'menu-item-object' => $post->post_type,  
  917. 'menu-item-type' => 'post_type',  
  918. 'menu-item-status' => 'publish',  
  919. ); 
  920.  
  921. foreach ( $auto_add as $menu_id ) { 
  922. $items = wp_get_nav_menu_items( $menu_id, array( 'post_status' => 'publish, draft' ) ); 
  923. if ( ! is_array( $items ) ) 
  924. continue; 
  925. foreach ( $items as $item ) { 
  926. if ( $post->ID == $item->object_id ) 
  927. continue 2; 
  928. wp_update_nav_menu_item( $menu_id, 0, $args ); 
.