Bez popisu

nav-menu.php 41KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106110711081109111011111112111311141115111611171118111911201121112211231124112511261127112811291130113111321133113411351136113711381139114011411142114311441145114611471148114911501151115211531154115511561157115811591160116111621163116411651166116711681169117011711172117311741175117611771178117911801181118211831184118511861187118811891190119111921193119411951196119711981199120012011202120312041205120612071208120912101211121212131214121512161217121812191220122112221223122412251226122712281229123012311232123312341235123612371238123912401241124212431244124512461247124812491250125112521253125412551256125712581259126012611262126312641265126612671268126912701271127212731274
  1. <?php
  2. /**
  3. * Navigation Menu functions
  4. *
  5. * @package WordPress
  6. * @subpackage Nav_Menus
  7. * @since 3.0.0
  8. */
  9. /**
  10. * Returns a navigation menu object.
  11. *
  12. * @since 3.0.0
  13. *
  14. * @param int|string|WP_Term $menu Menu ID, slug, name, or object.
  15. * @return WP_Term|false Menu object on success, false if $menu param isn't supplied or term does not exist.
  16. */
  17. function wp_get_nav_menu_object( $menu ) {
  18. $menu_obj = false;
  19. if ( is_object( $menu ) ) {
  20. $menu_obj = $menu;
  21. }
  22. if ( $menu && ! $menu_obj ) {
  23. $menu_obj = get_term( $menu, 'nav_menu' );
  24. if ( ! $menu_obj ) {
  25. $menu_obj = get_term_by( 'slug', $menu, 'nav_menu' );
  26. }
  27. if ( ! $menu_obj ) {
  28. $menu_obj = get_term_by( 'name', $menu, 'nav_menu' );
  29. }
  30. }
  31. if ( ! $menu_obj || is_wp_error( $menu_obj ) ) {
  32. $menu_obj = false;
  33. }
  34. /**
  35. * Filters the nav_menu term retrieved for wp_get_nav_menu_object().
  36. *
  37. * @since 4.3.0
  38. *
  39. * @param WP_Term|false $menu_obj Term from nav_menu taxonomy, or false if nothing had been found.
  40. * @param int|string|WP_Term $menu The menu ID, slug, name, or object passed to wp_get_nav_menu_object().
  41. */
  42. return apply_filters( 'wp_get_nav_menu_object', $menu_obj, $menu );
  43. }
  44. /**
  45. * Check if the given ID is a navigation menu.
  46. *
  47. * Returns true if it is; false otherwise.
  48. *
  49. * @since 3.0.0
  50. *
  51. * @param int|string|WP_Term $menu Menu ID, slug, name, or object of menu to check.
  52. * @return bool Whether the menu exists.
  53. */
  54. function is_nav_menu( $menu ) {
  55. if ( ! $menu ) {
  56. return false;
  57. }
  58. $menu_obj = wp_get_nav_menu_object( $menu );
  59. if (
  60. $menu_obj &&
  61. ! is_wp_error( $menu_obj ) &&
  62. ! empty( $menu_obj->taxonomy ) &&
  63. 'nav_menu' === $menu_obj->taxonomy
  64. ) {
  65. return true;
  66. }
  67. return false;
  68. }
  69. /**
  70. * Registers navigation menu locations for a theme.
  71. *
  72. * @since 3.0.0
  73. *
  74. * @global array $_wp_registered_nav_menus
  75. *
  76. * @param string[] $locations Associative array of menu location identifiers (like a slug) and descriptive text.
  77. */
  78. function register_nav_menus( $locations = array() ) {
  79. global $_wp_registered_nav_menus;
  80. add_theme_support( 'menus' );
  81. foreach ( $locations as $key => $value ) {
  82. if ( is_int( $key ) ) {
  83. _doing_it_wrong( __FUNCTION__, __( 'Nav menu locations must be strings.' ), '5.3.0' );
  84. break;
  85. }
  86. }
  87. $_wp_registered_nav_menus = array_merge( (array) $_wp_registered_nav_menus, $locations );
  88. }
  89. /**
  90. * Unregisters a navigation menu location for a theme.
  91. *
  92. * @since 3.1.0
  93. *
  94. * @global array $_wp_registered_nav_menus
  95. *
  96. * @param string $location The menu location identifier.
  97. * @return bool True on success, false on failure.
  98. */
  99. function unregister_nav_menu( $location ) {
  100. global $_wp_registered_nav_menus;
  101. if ( is_array( $_wp_registered_nav_menus ) && isset( $_wp_registered_nav_menus[ $location ] ) ) {
  102. unset( $_wp_registered_nav_menus[ $location ] );
  103. if ( empty( $_wp_registered_nav_menus ) ) {
  104. _remove_theme_support( 'menus' );
  105. }
  106. return true;
  107. }
  108. return false;
  109. }
  110. /**
  111. * Registers a navigation menu location for a theme.
  112. *
  113. * @since 3.0.0
  114. *
  115. * @param string $location Menu location identifier, like a slug.
  116. * @param string $description Menu location descriptive text.
  117. */
  118. function register_nav_menu( $location, $description ) {
  119. register_nav_menus( array( $location => $description ) );
  120. }
  121. /**
  122. * Retrieves all registered navigation menu locations in a theme.
  123. *
  124. * @since 3.0.0
  125. *
  126. * @global array $_wp_registered_nav_menus
  127. *
  128. * @return string[] Associative array of egistered navigation menu descriptions keyed
  129. * by their location. If none are registered, an empty array.
  130. */
  131. function get_registered_nav_menus() {
  132. global $_wp_registered_nav_menus;
  133. if ( isset( $_wp_registered_nav_menus ) ) {
  134. return $_wp_registered_nav_menus;
  135. }
  136. return array();
  137. }
  138. /**
  139. * Retrieves all registered navigation menu locations and the menus assigned to them.
  140. *
  141. * @since 3.0.0
  142. *
  143. * @return int[] Associative array of egistered navigation menu IDs keyed by their
  144. * location name. If none are registered, an empty array.
  145. */
  146. function get_nav_menu_locations() {
  147. $locations = get_theme_mod( 'nav_menu_locations' );
  148. return ( is_array( $locations ) ) ? $locations : array();
  149. }
  150. /**
  151. * Determines whether a registered nav menu location has a menu assigned to it.
  152. *
  153. * @since 3.0.0
  154. *
  155. * @param string $location Menu location identifier.
  156. * @return bool Whether location has a menu.
  157. */
  158. function has_nav_menu( $location ) {
  159. $has_nav_menu = false;
  160. $registered_nav_menus = get_registered_nav_menus();
  161. if ( isset( $registered_nav_menus[ $location ] ) ) {
  162. $locations = get_nav_menu_locations();
  163. $has_nav_menu = ! empty( $locations[ $location ] );
  164. }
  165. /**
  166. * Filters whether a nav menu is assigned to the specified location.
  167. *
  168. * @since 4.3.0
  169. *
  170. * @param bool $has_nav_menu Whether there is a menu assigned to a location.
  171. * @param string $location Menu location.
  172. */
  173. return apply_filters( 'has_nav_menu', $has_nav_menu, $location );
  174. }
  175. /**
  176. * Returns the name of a navigation menu.
  177. *
  178. * @since 4.9.0
  179. *
  180. * @param string $location Menu location identifier.
  181. * @return string Menu name.
  182. */
  183. function wp_get_nav_menu_name( $location ) {
  184. $menu_name = '';
  185. $locations = get_nav_menu_locations();
  186. if ( isset( $locations[ $location ] ) ) {
  187. $menu = wp_get_nav_menu_object( $locations[ $location ] );
  188. if ( $menu && $menu->name ) {
  189. $menu_name = $menu->name;
  190. }
  191. }
  192. /**
  193. * Filters the navigation menu name being returned.
  194. *
  195. * @since 4.9.0
  196. *
  197. * @param string $menu_name Menu name.
  198. * @param string $location Menu location identifier.
  199. */
  200. return apply_filters( 'wp_get_nav_menu_name', $menu_name, $location );
  201. }
  202. /**
  203. * Determines whether the given ID is a nav menu item.
  204. *
  205. * @since 3.0.0
  206. *
  207. * @param int $menu_item_id The ID of the potential nav menu item.
  208. * @return bool Whether the given ID is that of a nav menu item.
  209. */
  210. function is_nav_menu_item( $menu_item_id = 0 ) {
  211. return ( ! is_wp_error( $menu_item_id ) && ( 'nav_menu_item' === get_post_type( $menu_item_id ) ) );
  212. }
  213. /**
  214. * Creates a navigation menu.
  215. *
  216. * Note that `$menu_name` is expected to be pre-slashed.
  217. *
  218. * @since 3.0.0
  219. *
  220. * @param string $menu_name Menu name.
  221. * @return int|WP_Error Menu ID on success, WP_Error object on failure.
  222. */
  223. function wp_create_nav_menu( $menu_name ) {
  224. // expected_slashed ($menu_name)
  225. return wp_update_nav_menu_object( 0, array( 'menu-name' => $menu_name ) );
  226. }
  227. /**
  228. * Delete a Navigation Menu.
  229. *
  230. * @since 3.0.0
  231. *
  232. * @param int|string|WP_Term $menu Menu ID, slug, name, or object.
  233. * @return bool|WP_Error True on success, false or WP_Error object on failure.
  234. */
  235. function wp_delete_nav_menu( $menu ) {
  236. $menu = wp_get_nav_menu_object( $menu );
  237. if ( ! $menu ) {
  238. return false;
  239. }
  240. $menu_objects = get_objects_in_term( $menu->term_id, 'nav_menu' );
  241. if ( ! empty( $menu_objects ) ) {
  242. foreach ( $menu_objects as $item ) {
  243. wp_delete_post( $item );
  244. }
  245. }
  246. $result = wp_delete_term( $menu->term_id, 'nav_menu' );
  247. // Remove this menu from any locations.
  248. $locations = get_nav_menu_locations();
  249. foreach ( $locations as $location => $menu_id ) {
  250. if ( $menu_id == $menu->term_id ) {
  251. $locations[ $location ] = 0;
  252. }
  253. }
  254. set_theme_mod( 'nav_menu_locations', $locations );
  255. if ( $result && ! is_wp_error( $result ) ) {
  256. /**
  257. * Fires after a navigation menu has been successfully deleted.
  258. *
  259. * @since 3.0.0
  260. *
  261. * @param int $term_id ID of the deleted menu.
  262. */
  263. do_action( 'wp_delete_nav_menu', $menu->term_id );
  264. }
  265. return $result;
  266. }
  267. /**
  268. * Save the properties of a menu or create a new menu with those properties.
  269. *
  270. * Note that `$menu_data` is expected to be pre-slashed.
  271. *
  272. * @since 3.0.0
  273. *
  274. * @param int $menu_id The ID of the menu or "0" to create a new menu.
  275. * @param array $menu_data The array of menu data.
  276. * @return int|WP_Error Menu ID on success, WP_Error object on failure.
  277. */
  278. function wp_update_nav_menu_object( $menu_id = 0, $menu_data = array() ) {
  279. // expected_slashed ($menu_data)
  280. $menu_id = (int) $menu_id;
  281. $_menu = wp_get_nav_menu_object( $menu_id );
  282. $args = array(
  283. 'description' => ( isset( $menu_data['description'] ) ? $menu_data['description'] : '' ),
  284. 'name' => ( isset( $menu_data['menu-name'] ) ? $menu_data['menu-name'] : '' ),
  285. 'parent' => ( isset( $menu_data['parent'] ) ? (int) $menu_data['parent'] : 0 ),
  286. 'slug' => null,
  287. );
  288. // Double-check that we're not going to have one menu take the name of another.
  289. $_possible_existing = get_term_by( 'name', $menu_data['menu-name'], 'nav_menu' );
  290. if (
  291. $_possible_existing &&
  292. ! is_wp_error( $_possible_existing ) &&
  293. isset( $_possible_existing->term_id ) &&
  294. $_possible_existing->term_id != $menu_id
  295. ) {
  296. return new WP_Error(
  297. 'menu_exists',
  298. sprintf(
  299. /* translators: %s: Menu name. */
  300. __( 'The menu name %s conflicts with another menu name. Please try another.' ),
  301. '<strong>' . esc_html( $menu_data['menu-name'] ) . '</strong>'
  302. )
  303. );
  304. }
  305. // Menu doesn't already exist, so create a new menu.
  306. if ( ! $_menu || is_wp_error( $_menu ) ) {
  307. $menu_exists = get_term_by( 'name', $menu_data['menu-name'], 'nav_menu' );
  308. if ( $menu_exists ) {
  309. return new WP_Error(
  310. 'menu_exists',
  311. sprintf(
  312. /* translators: %s: Menu name. */
  313. __( 'The menu name %s conflicts with another menu name. Please try another.' ),
  314. '<strong>' . esc_html( $menu_data['menu-name'] ) . '</strong>'
  315. )
  316. );
  317. }
  318. $_menu = wp_insert_term( $menu_data['menu-name'], 'nav_menu', $args );
  319. if ( is_wp_error( $_menu ) ) {
  320. return $_menu;
  321. }
  322. /**
  323. * Fires after a navigation menu is successfully created.
  324. *
  325. * @since 3.0.0
  326. *
  327. * @param int $term_id ID of the new menu.
  328. * @param array $menu_data An array of menu data.
  329. */
  330. do_action( 'wp_create_nav_menu', $_menu['term_id'], $menu_data );
  331. return (int) $_menu['term_id'];
  332. }
  333. if ( ! $_menu || ! isset( $_menu->term_id ) ) {
  334. return 0;
  335. }
  336. $menu_id = (int) $_menu->term_id;
  337. $update_response = wp_update_term( $menu_id, 'nav_menu', $args );
  338. if ( is_wp_error( $update_response ) ) {
  339. return $update_response;
  340. }
  341. $menu_id = (int) $update_response['term_id'];
  342. /**
  343. * Fires after a navigation menu has been successfully updated.
  344. *
  345. * @since 3.0.0
  346. *
  347. * @param int $menu_id ID of the updated menu.
  348. * @param array $menu_data An array of menu data.
  349. */
  350. do_action( 'wp_update_nav_menu', $menu_id, $menu_data );
  351. return $menu_id;
  352. }
  353. /**
  354. * Save the properties of a menu item or create a new one.
  355. *
  356. * The menu-item-title, menu-item-description, and menu-item-attr-title are expected
  357. * to be pre-slashed since they are passed directly into `wp_insert_post()`.
  358. *
  359. * @since 3.0.0
  360. *
  361. * @param int $menu_id The ID of the menu. Required. If "0", makes the menu item a draft orphan.
  362. * @param int $menu_item_db_id The ID of the menu item. If "0", creates a new menu item.
  363. * @param array $menu_item_data The menu item's data.
  364. * @return int|WP_Error The menu item's database ID or WP_Error object on failure.
  365. */
  366. function wp_update_nav_menu_item( $menu_id = 0, $menu_item_db_id = 0, $menu_item_data = array() ) {
  367. $menu_id = (int) $menu_id;
  368. $menu_item_db_id = (int) $menu_item_db_id;
  369. // Make sure that we don't convert non-nav_menu_item objects into nav_menu_item objects.
  370. if ( ! empty( $menu_item_db_id ) && ! is_nav_menu_item( $menu_item_db_id ) ) {
  371. return new WP_Error( 'update_nav_menu_item_failed', __( 'The given object ID is not that of a menu item.' ) );
  372. }
  373. $menu = wp_get_nav_menu_object( $menu_id );
  374. if ( ! $menu && 0 !== $menu_id ) {
  375. return new WP_Error( 'invalid_menu_id', __( 'Invalid menu ID.' ) );
  376. }
  377. if ( is_wp_error( $menu ) ) {
  378. return $menu;
  379. }
  380. $defaults = array(
  381. 'menu-item-db-id' => $menu_item_db_id,
  382. 'menu-item-object-id' => 0,
  383. 'menu-item-object' => '',
  384. 'menu-item-parent-id' => 0,
  385. 'menu-item-position' => 0,
  386. 'menu-item-type' => 'custom',
  387. 'menu-item-title' => '',
  388. 'menu-item-url' => '',
  389. 'menu-item-description' => '',
  390. 'menu-item-attr-title' => '',
  391. 'menu-item-target' => '',
  392. 'menu-item-classes' => '',
  393. 'menu-item-xfn' => '',
  394. 'menu-item-status' => '',
  395. 'menu-item-post-date' => '',
  396. 'menu-item-post-date-gmt' => '',
  397. );
  398. $args = wp_parse_args( $menu_item_data, $defaults );
  399. if ( 0 == $menu_id ) {
  400. $args['menu-item-position'] = 1;
  401. } elseif ( 0 == (int) $args['menu-item-position'] ) {
  402. $menu_items = 0 == $menu_id ? array() : (array) wp_get_nav_menu_items( $menu_id, array( 'post_status' => 'publish,draft' ) );
  403. $last_item = array_pop( $menu_items );
  404. $args['menu-item-position'] = ( $last_item && isset( $last_item->menu_order ) ) ? 1 + $last_item->menu_order : count( $menu_items );
  405. }
  406. $original_parent = 0 < $menu_item_db_id ? get_post_field( 'post_parent', $menu_item_db_id ) : 0;
  407. if ( 'custom' === $args['menu-item-type'] ) {
  408. // If custom menu item, trim the URL.
  409. $args['menu-item-url'] = trim( $args['menu-item-url'] );
  410. } else {
  411. /*
  412. * If non-custom menu item, then:
  413. * - use the original object's URL.
  414. * - blank default title to sync with the original object's title.
  415. */
  416. $args['menu-item-url'] = '';
  417. $original_title = '';
  418. if ( 'taxonomy' === $args['menu-item-type'] ) {
  419. $original_parent = get_term_field( 'parent', $args['menu-item-object-id'], $args['menu-item-object'], 'raw' );
  420. $original_title = get_term_field( 'name', $args['menu-item-object-id'], $args['menu-item-object'], 'raw' );
  421. } elseif ( 'post_type' === $args['menu-item-type'] ) {
  422. $original_object = get_post( $args['menu-item-object-id'] );
  423. $original_parent = (int) $original_object->post_parent;
  424. $original_title = $original_object->post_title;
  425. } elseif ( 'post_type_archive' === $args['menu-item-type'] ) {
  426. $original_object = get_post_type_object( $args['menu-item-object'] );
  427. if ( $original_object ) {
  428. $original_title = $original_object->labels->archives;
  429. }
  430. }
  431. if ( wp_unslash( $args['menu-item-title'] ) === wp_specialchars_decode( $original_title ) ) {
  432. $args['menu-item-title'] = '';
  433. }
  434. // Hack to get wp to create a post object when too many properties are empty.
  435. if ( '' === $args['menu-item-title'] && '' === $args['menu-item-description'] ) {
  436. $args['menu-item-description'] = ' ';
  437. }
  438. }
  439. // Populate the menu item object.
  440. $post = array(
  441. 'menu_order' => $args['menu-item-position'],
  442. 'ping_status' => 0,
  443. 'post_content' => $args['menu-item-description'],
  444. 'post_excerpt' => $args['menu-item-attr-title'],
  445. 'post_parent' => $original_parent,
  446. 'post_title' => $args['menu-item-title'],
  447. 'post_type' => 'nav_menu_item',
  448. );
  449. $post_date = wp_resolve_post_date( $args['menu-item-post-date'], $args['menu-item-post-date-gmt'] );
  450. if ( $post_date ) {
  451. $post['post_date'] = $post_date;
  452. }
  453. $update = 0 != $menu_item_db_id;
  454. // New menu item. Default is draft status.
  455. if ( ! $update ) {
  456. $post['ID'] = 0;
  457. $post['post_status'] = 'publish' === $args['menu-item-status'] ? 'publish' : 'draft';
  458. $menu_item_db_id = wp_insert_post( $post );
  459. if ( ! $menu_item_db_id || is_wp_error( $menu_item_db_id ) ) {
  460. return $menu_item_db_id;
  461. }
  462. /**
  463. * Fires immediately after a new navigation menu item has been added.
  464. *
  465. * @since 4.4.0
  466. *
  467. * @see wp_update_nav_menu_item()
  468. *
  469. * @param int $menu_id ID of the updated menu.
  470. * @param int $menu_item_db_id ID of the new menu item.
  471. * @param array $args An array of arguments used to update/add the menu item.
  472. */
  473. do_action( 'wp_add_nav_menu_item', $menu_id, $menu_item_db_id, $args );
  474. }
  475. // Associate the menu item with the menu term.
  476. // Only set the menu term if it isn't set to avoid unnecessary wp_get_object_terms().
  477. if ( $menu_id && ( ! $update || ! is_object_in_term( $menu_item_db_id, 'nav_menu', (int) $menu->term_id ) ) ) {
  478. wp_set_object_terms( $menu_item_db_id, array( $menu->term_id ), 'nav_menu' );
  479. }
  480. if ( 'custom' === $args['menu-item-type'] ) {
  481. $args['menu-item-object-id'] = $menu_item_db_id;
  482. $args['menu-item-object'] = 'custom';
  483. }
  484. $menu_item_db_id = (int) $menu_item_db_id;
  485. update_post_meta( $menu_item_db_id, '_menu_item_type', sanitize_key( $args['menu-item-type'] ) );
  486. update_post_meta( $menu_item_db_id, '_menu_item_menu_item_parent', (string) ( (int) $args['menu-item-parent-id'] ) );
  487. update_post_meta( $menu_item_db_id, '_menu_item_object_id', (string) ( (int) $args['menu-item-object-id'] ) );
  488. update_post_meta( $menu_item_db_id, '_menu_item_object', sanitize_key( $args['menu-item-object'] ) );
  489. update_post_meta( $menu_item_db_id, '_menu_item_target', sanitize_key( $args['menu-item-target'] ) );
  490. $args['menu-item-classes'] = array_map( 'sanitize_html_class', explode( ' ', $args['menu-item-classes'] ) );
  491. $args['menu-item-xfn'] = implode( ' ', array_map( 'sanitize_html_class', explode( ' ', $args['menu-item-xfn'] ) ) );
  492. update_post_meta( $menu_item_db_id, '_menu_item_classes', $args['menu-item-classes'] );
  493. update_post_meta( $menu_item_db_id, '_menu_item_xfn', $args['menu-item-xfn'] );
  494. update_post_meta( $menu_item_db_id, '_menu_item_url', esc_url_raw( $args['menu-item-url'] ) );
  495. if ( 0 == $menu_id ) {
  496. update_post_meta( $menu_item_db_id, '_menu_item_orphaned', (string) time() );
  497. } elseif ( get_post_meta( $menu_item_db_id, '_menu_item_orphaned' ) ) {
  498. delete_post_meta( $menu_item_db_id, '_menu_item_orphaned' );
  499. }
  500. // Update existing menu item. Default is publish status.
  501. if ( $update ) {
  502. $post['ID'] = $menu_item_db_id;
  503. $post['post_status'] = ( 'draft' === $args['menu-item-status'] ) ? 'draft' : 'publish';
  504. wp_update_post( $post );
  505. }
  506. /**
  507. * Fires after a navigation menu item has been updated.
  508. *
  509. * @since 3.0.0
  510. *
  511. * @see wp_update_nav_menu_item()
  512. *
  513. * @param int $menu_id ID of the updated menu.
  514. * @param int $menu_item_db_id ID of the updated menu item.
  515. * @param array $args An array of arguments used to update a menu item.
  516. */
  517. do_action( 'wp_update_nav_menu_item', $menu_id, $menu_item_db_id, $args );
  518. return $menu_item_db_id;
  519. }
  520. /**
  521. * Returns all navigation menu objects.
  522. *
  523. * @since 3.0.0
  524. * @since 4.1.0 Default value of the 'orderby' argument was changed from 'none'
  525. * to 'name'.
  526. *
  527. * @param array $args Optional. Array of arguments passed on to get_terms().
  528. * Default empty array.
  529. * @return WP_Term[] An array of menu objects.
  530. */
  531. function wp_get_nav_menus( $args = array() ) {
  532. $defaults = array(
  533. 'taxonomy' => 'nav_menu',
  534. 'hide_empty' => false,
  535. 'orderby' => 'name',
  536. );
  537. $args = wp_parse_args( $args, $defaults );
  538. /**
  539. * Filters the navigation menu objects being returned.
  540. *
  541. * @since 3.0.0
  542. *
  543. * @see get_terms()
  544. *
  545. * @param WP_Term[] $menus An array of menu objects.
  546. * @param array $args An array of arguments used to retrieve menu objects.
  547. */
  548. return apply_filters( 'wp_get_nav_menus', get_terms( $args ), $args );
  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. * Retrieves all menu items of a navigation menu.
  566. *
  567. * Note: Most arguments passed to the `$args` parameter – save for 'output_key' – are
  568. * specifically for retrieving nav_menu_item posts from get_posts() and may only
  569. * indirectly affect the ultimate ordering and content of the resulting nav menu
  570. * items that get returned from this function.
  571. *
  572. * @since 3.0.0
  573. *
  574. * @param int|string|WP_Term $menu Menu ID, slug, name, or object.
  575. * @param array $args {
  576. * Optional. Arguments to pass to get_posts().
  577. *
  578. * @type string $order How to order nav menu items as queried with get_posts(). Will be ignored
  579. * if 'output' is ARRAY_A. Default 'ASC'.
  580. * @type string $orderby Field to order menu items by as retrieved from get_posts(). Supply an orderby
  581. * field via 'output_key' to affect the output order of nav menu items.
  582. * Default 'menu_order'.
  583. * @type string $post_type Menu items post type. Default 'nav_menu_item'.
  584. * @type string $post_status Menu items post status. Default 'publish'.
  585. * @type string $output How to order outputted menu items. Default ARRAY_A.
  586. * @type string $output_key Key to use for ordering the actual menu items that get returned. Note that
  587. * that is not a get_posts() argument and will only affect output of menu items
  588. * processed in this function. Default 'menu_order'.
  589. * @type bool $nopaging Whether to retrieve all menu items (true) or paginate (false). Default true.
  590. * }
  591. * @return array|false Array of menu items, otherwise false.
  592. */
  593. function wp_get_nav_menu_items( $menu, $args = array() ) {
  594. $menu = wp_get_nav_menu_object( $menu );
  595. if ( ! $menu ) {
  596. return false;
  597. }
  598. static $fetched = array();
  599. $items = get_objects_in_term( $menu->term_id, 'nav_menu' );
  600. if ( is_wp_error( $items ) ) {
  601. return false;
  602. }
  603. $defaults = array(
  604. 'order' => 'ASC',
  605. 'orderby' => 'menu_order',
  606. 'post_type' => 'nav_menu_item',
  607. 'post_status' => 'publish',
  608. 'output' => ARRAY_A,
  609. 'output_key' => 'menu_order',
  610. 'nopaging' => true,
  611. );
  612. $args = wp_parse_args( $args, $defaults );
  613. $args['include'] = $items;
  614. if ( ! empty( $items ) ) {
  615. $items = get_posts( $args );
  616. } else {
  617. $items = array();
  618. }
  619. // Get all posts and terms at once to prime the caches.
  620. if ( empty( $fetched[ $menu->term_id ] ) && ! wp_using_ext_object_cache() ) {
  621. $fetched[ $menu->term_id ] = true;
  622. $posts = array();
  623. $terms = array();
  624. foreach ( $items as $item ) {
  625. $object_id = get_post_meta( $item->ID, '_menu_item_object_id', true );
  626. $object = get_post_meta( $item->ID, '_menu_item_object', true );
  627. $type = get_post_meta( $item->ID, '_menu_item_type', true );
  628. if ( 'post_type' === $type ) {
  629. $posts[ $object ][] = $object_id;
  630. } elseif ( 'taxonomy' === $type ) {
  631. $terms[ $object ][] = $object_id;
  632. }
  633. }
  634. if ( ! empty( $posts ) ) {
  635. foreach ( array_keys( $posts ) as $post_type ) {
  636. get_posts(
  637. array(
  638. 'post__in' => $posts[ $post_type ],
  639. 'post_type' => $post_type,
  640. 'nopaging' => true,
  641. 'update_post_term_cache' => false,
  642. )
  643. );
  644. }
  645. }
  646. unset( $posts );
  647. if ( ! empty( $terms ) ) {
  648. foreach ( array_keys( $terms ) as $taxonomy ) {
  649. get_terms(
  650. array(
  651. 'taxonomy' => $taxonomy,
  652. 'include' => $terms[ $taxonomy ],
  653. 'hierarchical' => false,
  654. )
  655. );
  656. }
  657. }
  658. unset( $terms );
  659. }
  660. $items = array_map( 'wp_setup_nav_menu_item', $items );
  661. if ( ! is_admin() ) { // Remove invalid items only on front end.
  662. $items = array_filter( $items, '_is_valid_nav_menu_item' );
  663. }
  664. if ( ARRAY_A === $args['output'] ) {
  665. $items = wp_list_sort(
  666. $items,
  667. array(
  668. $args['output_key'] => 'ASC',
  669. )
  670. );
  671. $i = 1;
  672. foreach ( $items as $k => $item ) {
  673. $items[ $k ]->{$args['output_key']} = $i++;
  674. }
  675. }
  676. /**
  677. * Filters the navigation menu items being returned.
  678. *
  679. * @since 3.0.0
  680. *
  681. * @param array $items An array of menu item post objects.
  682. * @param object $menu The menu object.
  683. * @param array $args An array of arguments used to retrieve menu item objects.
  684. */
  685. return apply_filters( 'wp_get_nav_menu_items', $items, $menu, $args );
  686. }
  687. /**
  688. * Decorates a menu item object with the shared navigation menu item properties.
  689. *
  690. * Properties:
  691. * - ID: The term_id if the menu item represents a taxonomy term.
  692. * - attr_title: The title attribute of the link element for this menu item.
  693. * - classes: The array of class attribute values for the link element of this menu item.
  694. * - db_id: The DB ID of this item as a nav_menu_item object, if it exists (0 if it doesn't exist).
  695. * - description: The description of this menu item.
  696. * - menu_item_parent: The DB ID of the nav_menu_item that is this item's menu parent, if any. 0 otherwise.
  697. * - object: The type of object originally represented, such as 'category', 'post', or 'attachment'.
  698. * - object_id: The DB ID of the original object this menu item represents, e.g. ID for posts and term_id for categories.
  699. * - post_parent: The DB ID of the original object's parent object, if any (0 otherwise).
  700. * - post_title: A "no title" label if menu item represents a post that lacks a title.
  701. * - target: The target attribute of the link element for this menu item.
  702. * - title: The title of this menu item.
  703. * - type: The family of objects originally represented, such as 'post_type' or 'taxonomy'.
  704. * - type_label: The singular label used to describe this type of menu item.
  705. * - url: The URL to which this menu item points.
  706. * - xfn: The XFN relationship expressed in the link of this menu item.
  707. * - _invalid: Whether the menu item represents an object that no longer exists.
  708. *
  709. * @since 3.0.0
  710. *
  711. * @param object $menu_item The menu item to modify.
  712. * @return object The menu item with standard menu item properties.
  713. */
  714. function wp_setup_nav_menu_item( $menu_item ) {
  715. if ( isset( $menu_item->post_type ) ) {
  716. if ( 'nav_menu_item' === $menu_item->post_type ) {
  717. $menu_item->db_id = (int) $menu_item->ID;
  718. $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;
  719. $menu_item->object_id = ! isset( $menu_item->object_id ) ? get_post_meta( $menu_item->ID, '_menu_item_object_id', true ) : $menu_item->object_id;
  720. $menu_item->object = ! isset( $menu_item->object ) ? get_post_meta( $menu_item->ID, '_menu_item_object', true ) : $menu_item->object;
  721. $menu_item->type = ! isset( $menu_item->type ) ? get_post_meta( $menu_item->ID, '_menu_item_type', true ) : $menu_item->type;
  722. if ( 'post_type' === $menu_item->type ) {
  723. $object = get_post_type_object( $menu_item->object );
  724. if ( $object ) {
  725. $menu_item->type_label = $object->labels->singular_name;
  726. // Denote post states for special pages (only in the admin).
  727. if ( function_exists( 'get_post_states' ) ) {
  728. $menu_post = get_post( $menu_item->object_id );
  729. $post_states = get_post_states( $menu_post );
  730. if ( $post_states ) {
  731. $menu_item->type_label = wp_strip_all_tags( implode( ', ', $post_states ) );
  732. }
  733. }
  734. } else {
  735. $menu_item->type_label = $menu_item->object;
  736. $menu_item->_invalid = true;
  737. }
  738. if ( 'trash' === get_post_status( $menu_item->object_id ) ) {
  739. $menu_item->_invalid = true;
  740. }
  741. $original_object = get_post( $menu_item->object_id );
  742. if ( $original_object ) {
  743. $menu_item->url = get_permalink( $original_object->ID );
  744. /** This filter is documented in wp-includes/post-template.php */
  745. $original_title = apply_filters( 'the_title', $original_object->post_title, $original_object->ID );
  746. } else {
  747. $menu_item->url = '';
  748. $original_title = '';
  749. $menu_item->_invalid = true;
  750. }
  751. if ( '' === $original_title ) {
  752. /* translators: %d: ID of a post. */
  753. $original_title = sprintf( __( '#%d (no title)' ), $menu_item->object_id );
  754. }
  755. $menu_item->title = ( '' === $menu_item->post_title ) ? $original_title : $menu_item->post_title;
  756. } elseif ( 'post_type_archive' === $menu_item->type ) {
  757. $object = get_post_type_object( $menu_item->object );
  758. if ( $object ) {
  759. $menu_item->title = ( '' === $menu_item->post_title ) ? $object->labels->archives : $menu_item->post_title;
  760. $post_type_description = $object->description;
  761. } else {
  762. $post_type_description = '';
  763. $menu_item->_invalid = true;
  764. }
  765. $menu_item->type_label = __( 'Post Type Archive' );
  766. $post_content = wp_trim_words( $menu_item->post_content, 200 );
  767. $post_type_description = ( '' === $post_content ) ? $post_type_description : $post_content;
  768. $menu_item->url = get_post_type_archive_link( $menu_item->object );
  769. } elseif ( 'taxonomy' === $menu_item->type ) {
  770. $object = get_taxonomy( $menu_item->object );
  771. if ( $object ) {
  772. $menu_item->type_label = $object->labels->singular_name;
  773. } else {
  774. $menu_item->type_label = $menu_item->object;
  775. $menu_item->_invalid = true;
  776. }
  777. $original_object = get_term( (int) $menu_item->object_id, $menu_item->object );
  778. if ( $original_object && ! is_wp_error( $original_object ) ) {
  779. $menu_item->url = get_term_link( (int) $menu_item->object_id, $menu_item->object );
  780. $original_title = $original_object->name;
  781. } else {
  782. $menu_item->url = '';
  783. $original_title = '';
  784. $menu_item->_invalid = true;
  785. }
  786. if ( '' === $original_title ) {
  787. /* translators: %d: ID of a term. */
  788. $original_title = sprintf( __( '#%d (no title)' ), $menu_item->object_id );
  789. }
  790. $menu_item->title = ( '' === $menu_item->post_title ) ? $original_title : $menu_item->post_title;
  791. } else {
  792. $menu_item->type_label = __( 'Custom Link' );
  793. $menu_item->title = $menu_item->post_title;
  794. $menu_item->url = ! isset( $menu_item->url ) ? get_post_meta( $menu_item->ID, '_menu_item_url', true ) : $menu_item->url;
  795. }
  796. $menu_item->target = ! isset( $menu_item->target ) ? get_post_meta( $menu_item->ID, '_menu_item_target', true ) : $menu_item->target;
  797. /**
  798. * Filters a navigation menu item's title attribute.
  799. *
  800. * @since 3.0.0
  801. *
  802. * @param string $item_title The menu item title attribute.
  803. */
  804. $menu_item->attr_title = ! isset( $menu_item->attr_title ) ? apply_filters( 'nav_menu_attr_title', $menu_item->post_excerpt ) : $menu_item->attr_title;
  805. if ( ! isset( $menu_item->description ) ) {
  806. /**
  807. * Filters a navigation menu item's description.
  808. *
  809. * @since 3.0.0
  810. *
  811. * @param string $description The menu item description.
  812. */
  813. $menu_item->description = apply_filters( 'nav_menu_description', wp_trim_words( $menu_item->post_content, 200 ) );
  814. }
  815. $menu_item->classes = ! isset( $menu_item->classes ) ? (array) get_post_meta( $menu_item->ID, '_menu_item_classes', true ) : $menu_item->classes;
  816. $menu_item->xfn = ! isset( $menu_item->xfn ) ? get_post_meta( $menu_item->ID, '_menu_item_xfn', true ) : $menu_item->xfn;
  817. } else {
  818. $menu_item->db_id = 0;
  819. $menu_item->menu_item_parent = 0;
  820. $menu_item->object_id = (int) $menu_item->ID;
  821. $menu_item->type = 'post_type';
  822. $object = get_post_type_object( $menu_item->post_type );
  823. $menu_item->object = $object->name;
  824. $menu_item->type_label = $object->labels->singular_name;
  825. if ( '' === $menu_item->post_title ) {
  826. /* translators: %d: ID of a post. */
  827. $menu_item->post_title = sprintf( __( '#%d (no title)' ), $menu_item->ID );
  828. }
  829. $menu_item->title = $menu_item->post_title;
  830. $menu_item->url = get_permalink( $menu_item->ID );
  831. $menu_item->target = '';
  832. /** This filter is documented in wp-includes/nav-menu.php */
  833. $menu_item->attr_title = apply_filters( 'nav_menu_attr_title', '' );
  834. /** This filter is documented in wp-includes/nav-menu.php */
  835. $menu_item->description = apply_filters( 'nav_menu_description', '' );
  836. $menu_item->classes = array();
  837. $menu_item->xfn = '';
  838. }
  839. } elseif ( isset( $menu_item->taxonomy ) ) {
  840. $menu_item->ID = $menu_item->term_id;
  841. $menu_item->db_id = 0;
  842. $menu_item->menu_item_parent = 0;
  843. $menu_item->object_id = (int) $menu_item->term_id;
  844. $menu_item->post_parent = (int) $menu_item->parent;
  845. $menu_item->type = 'taxonomy';
  846. $object = get_taxonomy( $menu_item->taxonomy );
  847. $menu_item->object = $object->name;
  848. $menu_item->type_label = $object->labels->singular_name;
  849. $menu_item->title = $menu_item->name;
  850. $menu_item->url = get_term_link( $menu_item, $menu_item->taxonomy );
  851. $menu_item->target = '';
  852. $menu_item->attr_title = '';
  853. $menu_item->description = get_term_field( 'description', $menu_item->term_id, $menu_item->taxonomy );
  854. $menu_item->classes = array();
  855. $menu_item->xfn = '';
  856. }
  857. /**
  858. * Filters a navigation menu item object.
  859. *
  860. * @since 3.0.0
  861. *
  862. * @param object $menu_item The menu item object.
  863. */
  864. return apply_filters( 'wp_setup_nav_menu_item', $menu_item );
  865. }
  866. /**
  867. * Get the menu items associated with a particular object.
  868. *
  869. * @since 3.0.0
  870. *
  871. * @param int $object_id Optional. The ID of the original object. Default 0.
  872. * @param string $object_type Optional. The type of object, such as 'post_type' or 'taxonomy'.
  873. * Default 'post_type'.
  874. * @param string $taxonomy Optional. If $object_type is 'taxonomy', $taxonomy is the name
  875. * of the tax that $object_id belongs to. Default empty.
  876. * @return int[] The array of menu item IDs; empty array if none.
  877. */
  878. function wp_get_associated_nav_menu_items( $object_id = 0, $object_type = 'post_type', $taxonomy = '' ) {
  879. $object_id = (int) $object_id;
  880. $menu_item_ids = array();
  881. $query = new WP_Query;
  882. $menu_items = $query->query(
  883. array(
  884. 'meta_key' => '_menu_item_object_id',
  885. 'meta_value' => $object_id,
  886. 'post_status' => 'any',
  887. 'post_type' => 'nav_menu_item',
  888. 'posts_per_page' => -1,
  889. )
  890. );
  891. foreach ( (array) $menu_items as $menu_item ) {
  892. if ( isset( $menu_item->ID ) && is_nav_menu_item( $menu_item->ID ) ) {
  893. $menu_item_type = get_post_meta( $menu_item->ID, '_menu_item_type', true );
  894. if (
  895. 'post_type' === $object_type &&
  896. 'post_type' === $menu_item_type
  897. ) {
  898. $menu_item_ids[] = (int) $menu_item->ID;
  899. } elseif (
  900. 'taxonomy' === $object_type &&
  901. 'taxonomy' === $menu_item_type &&
  902. get_post_meta( $menu_item->ID, '_menu_item_object', true ) == $taxonomy
  903. ) {
  904. $menu_item_ids[] = (int) $menu_item->ID;
  905. }
  906. }
  907. }
  908. return array_unique( $menu_item_ids );
  909. }
  910. /**
  911. * Callback for handling a menu item when its original object is deleted.
  912. *
  913. * @since 3.0.0
  914. * @access private
  915. *
  916. * @param int $object_id The ID of the original object being trashed.
  917. */
  918. function _wp_delete_post_menu_item( $object_id ) {
  919. $object_id = (int) $object_id;
  920. $menu_item_ids = wp_get_associated_nav_menu_items( $object_id, 'post_type' );
  921. foreach ( (array) $menu_item_ids as $menu_item_id ) {
  922. wp_delete_post( $menu_item_id, true );
  923. }
  924. }
  925. /**
  926. * Serves as a callback for handling a menu item when its original object is deleted.
  927. *
  928. * @since 3.0.0
  929. * @access private
  930. *
  931. * @param int $object_id The ID of the original object being trashed.
  932. * @param int $tt_id Term taxonomy ID. Unused.
  933. * @param string $taxonomy Taxonomy slug.
  934. */
  935. function _wp_delete_tax_menu_item( $object_id, $tt_id, $taxonomy ) {
  936. $object_id = (int) $object_id;
  937. $menu_item_ids = wp_get_associated_nav_menu_items( $object_id, 'taxonomy', $taxonomy );
  938. foreach ( (array) $menu_item_ids as $menu_item_id ) {
  939. wp_delete_post( $menu_item_id, true );
  940. }
  941. }
  942. /**
  943. * Automatically add newly published page objects to menus with that as an option.
  944. *
  945. * @since 3.0.0
  946. * @access private
  947. *
  948. * @param string $new_status The new status of the post object.
  949. * @param string $old_status The old status of the post object.
  950. * @param WP_Post $post The post object being transitioned from one status to another.
  951. */
  952. function _wp_auto_add_pages_to_menu( $new_status, $old_status, $post ) {
  953. if ( 'publish' !== $new_status || 'publish' === $old_status || 'page' !== $post->post_type ) {
  954. return;
  955. }
  956. if ( ! empty( $post->post_parent ) ) {
  957. return;
  958. }
  959. $auto_add = get_option( 'nav_menu_options' );
  960. if ( empty( $auto_add ) || ! is_array( $auto_add ) || ! isset( $auto_add['auto_add'] ) ) {
  961. return;
  962. }
  963. $auto_add = $auto_add['auto_add'];
  964. if ( empty( $auto_add ) || ! is_array( $auto_add ) ) {
  965. return;
  966. }
  967. $args = array(
  968. 'menu-item-object-id' => $post->ID,
  969. 'menu-item-object' => $post->post_type,
  970. 'menu-item-type' => 'post_type',
  971. 'menu-item-status' => 'publish',
  972. );
  973. foreach ( $auto_add as $menu_id ) {
  974. $items = wp_get_nav_menu_items( $menu_id, array( 'post_status' => 'publish,draft' ) );
  975. if ( ! is_array( $items ) ) {
  976. continue;
  977. }
  978. foreach ( $items as $item ) {
  979. if ( $post->ID == $item->object_id ) {
  980. continue 2;
  981. }
  982. }
  983. wp_update_nav_menu_item( $menu_id, 0, $args );
  984. }
  985. }
  986. /**
  987. * Delete auto-draft posts associated with the supplied changeset.
  988. *
  989. * @since 4.8.0
  990. * @access private
  991. *
  992. * @param int $post_id Post ID for the customize_changeset.
  993. */
  994. function _wp_delete_customize_changeset_dependent_auto_drafts( $post_id ) {
  995. $post = get_post( $post_id );
  996. if ( ! $post || 'customize_changeset' !== $post->post_type ) {
  997. return;
  998. }
  999. $data = json_decode( $post->post_content, true );
  1000. if ( empty( $data['nav_menus_created_posts']['value'] ) ) {
  1001. return;
  1002. }
  1003. remove_action( 'delete_post', '_wp_delete_customize_changeset_dependent_auto_drafts' );
  1004. foreach ( $data['nav_menus_created_posts']['value'] as $stub_post_id ) {
  1005. if ( empty( $stub_post_id ) ) {
  1006. continue;
  1007. }
  1008. if ( 'auto-draft' === get_post_status( $stub_post_id ) ) {
  1009. wp_delete_post( $stub_post_id, true );
  1010. } elseif ( 'draft' === get_post_status( $stub_post_id ) ) {
  1011. wp_trash_post( $stub_post_id );
  1012. delete_post_meta( $stub_post_id, '_customize_changeset_uuid' );
  1013. }
  1014. }
  1015. add_action( 'delete_post', '_wp_delete_customize_changeset_dependent_auto_drafts' );
  1016. }
  1017. /**
  1018. * Handle menu config after theme change.
  1019. *
  1020. * @access private
  1021. * @since 4.9.0
  1022. */
  1023. function _wp_menus_changed() {
  1024. $old_nav_menu_locations = get_option( 'theme_switch_menu_locations', array() );
  1025. $new_nav_menu_locations = get_nav_menu_locations();
  1026. $mapped_nav_menu_locations = wp_map_nav_menu_locations( $new_nav_menu_locations, $old_nav_menu_locations );
  1027. set_theme_mod( 'nav_menu_locations', $mapped_nav_menu_locations );
  1028. delete_option( 'theme_switch_menu_locations' );
  1029. }
  1030. /**
  1031. * Maps nav menu locations according to assignments in previously active theme.
  1032. *
  1033. * @since 4.9.0
  1034. *
  1035. * @param array $new_nav_menu_locations New nav menu locations assignments.
  1036. * @param array $old_nav_menu_locations Old nav menu locations assignments.
  1037. * @return array Nav menus mapped to new nav menu locations.
  1038. */
  1039. function wp_map_nav_menu_locations( $new_nav_menu_locations, $old_nav_menu_locations ) {
  1040. $registered_nav_menus = get_registered_nav_menus();
  1041. $new_nav_menu_locations = array_intersect_key( $new_nav_menu_locations, $registered_nav_menus );
  1042. // Short-circuit if there are no old nav menu location assignments to map.
  1043. if ( empty( $old_nav_menu_locations ) ) {
  1044. return $new_nav_menu_locations;
  1045. }
  1046. // If old and new theme have just one location, map it and we're done.
  1047. if ( 1 === count( $old_nav_menu_locations ) && 1 === count( $registered_nav_menus ) ) {
  1048. $new_nav_menu_locations[ key( $registered_nav_menus ) ] = array_pop( $old_nav_menu_locations );
  1049. return $new_nav_menu_locations;
  1050. }
  1051. $old_locations = array_keys( $old_nav_menu_locations );
  1052. // Map locations with the same slug.
  1053. foreach ( $registered_nav_menus as $location => $name ) {
  1054. if ( in_array( $location, $old_locations, true ) ) {
  1055. $new_nav_menu_locations[ $location ] = $old_nav_menu_locations[ $location ];
  1056. unset( $old_nav_menu_locations[ $location ] );
  1057. }
  1058. }
  1059. // If there are no old nav menu locations left, then we're done.
  1060. if ( empty( $old_nav_menu_locations ) ) {
  1061. return $new_nav_menu_locations;
  1062. }
  1063. /*
  1064. * If old and new theme both have locations that contain phrases
  1065. * from within the same group, make an educated guess and map it.
  1066. */
  1067. $common_slug_groups = array(
  1068. array( 'primary', 'menu-1', 'main', 'header', 'navigation', 'top' ),
  1069. array( 'secondary', 'menu-2', 'footer', 'subsidiary', 'bottom' ),
  1070. array( 'social' ),
  1071. );
  1072. // Go through each group...
  1073. foreach ( $common_slug_groups as $slug_group ) {
  1074. // ...and see if any of these slugs...
  1075. foreach ( $slug_group as $slug ) {
  1076. // ...and any of the new menu locations...
  1077. foreach ( $registered_nav_menus as $new_location => $name ) {
  1078. // ...actually match!
  1079. if ( is_string( $new_location ) && false === stripos( $new_location, $slug ) && false === stripos( $slug, $new_location ) ) {
  1080. continue;
  1081. } elseif ( is_numeric( $new_location ) && $new_location !== $slug ) {
  1082. continue;
  1083. }
  1084. // Then see if any of the old locations...
  1085. foreach ( $old_nav_menu_locations as $location => $menu_id ) {
  1086. // ...and any slug in the same group...
  1087. foreach ( $slug_group as $slug ) {
  1088. // ... have a match as well.
  1089. if ( is_string( $location ) && false === stripos( $location, $slug ) && false === stripos( $slug, $location ) ) {
  1090. continue;
  1091. } elseif ( is_numeric( $location ) && $location !== $slug ) {
  1092. continue;
  1093. }
  1094. // Make sure this location wasn't mapped and removed previously.
  1095. if ( ! empty( $old_nav_menu_locations[ $location ] ) ) {
  1096. // We have a match that can be mapped!
  1097. $new_nav_menu_locations[ $new_location ] = $old_nav_menu_locations[ $location ];
  1098. // Remove the mapped location so it can't be mapped again.
  1099. unset( $old_nav_menu_locations[ $location ] );
  1100. // Go back and check the next new menu location.
  1101. continue 3;
  1102. }
  1103. } // End foreach ( $slug_group as $slug ).
  1104. } // End foreach ( $old_nav_menu_locations as $location => $menu_id ).
  1105. } // End foreach foreach ( $registered_nav_menus as $new_location => $name ).
  1106. } // End foreach ( $slug_group as $slug ).
  1107. } // End foreach ( $common_slug_groups as $slug_group ).
  1108. return $new_nav_menu_locations;
  1109. }