nav-menu-template.php 23 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645
  1. <?php
  2. /**
  3. * Nav Menu API: Template functions
  4. *
  5. * @package WordPress
  6. * @subpackage Nav_Menus
  7. * @since 3.0.0
  8. */
  9. /** Walker_Nav_Menu class */
  10. require_once ABSPATH . WPINC . '/class-walker-nav-menu.php';
  11. /**
  12. * Displays a navigation menu.
  13. *
  14. * @since 3.0.0
  15. * @since 4.7.0 Added the `item_spacing` argument.
  16. * @since 5.5.0 Added the `container_aria_label` argument.
  17. *
  18. * @param array $args {
  19. * Optional. Array of nav menu arguments.
  20. *
  21. * @type int|string|WP_Term $menu Desired menu. Accepts a menu ID, slug, name, or object.
  22. * Default empty.
  23. * @type string $menu_class CSS class to use for the ul element which forms the menu.
  24. * Default 'menu'.
  25. * @type string $menu_id The ID that is applied to the ul element which forms the menu.
  26. * Default is the menu slug, incremented.
  27. * @type string $container Whether to wrap the ul, and what to wrap it with.
  28. * Default 'div'.
  29. * @type string $container_class Class that is applied to the container.
  30. * Default 'menu-{menu slug}-container'.
  31. * @type string $container_id The ID that is applied to the container. Default empty.
  32. * @type string $container_aria_label The aria-label attribute that is applied to the container
  33. * when it's a nav element. Default empty.
  34. * @type callable|false $fallback_cb If the menu doesn't exist, a callback function will fire.
  35. * Default is 'wp_page_menu'. Set to false for no fallback.
  36. * @type string $before Text before the link markup. Default empty.
  37. * @type string $after Text after the link markup. Default empty.
  38. * @type string $link_before Text before the link text. Default empty.
  39. * @type string $link_after Text after the link text. Default empty.
  40. * @type bool $echo Whether to echo the menu or return it. Default true.
  41. * @type int $depth How many levels of the hierarchy are to be included.
  42. * 0 means all. Default 0.
  43. * Default 0.
  44. * @type object $walker Instance of a custom walker class. Default empty.
  45. * @type string $theme_location Theme location to be used. Must be registered with
  46. * register_nav_menu() in order to be selectable by the user.
  47. * @type string $items_wrap How the list items should be wrapped. Uses printf() format with
  48. * numbered placeholders. Default is a ul with an id and class.
  49. * @type string $item_spacing Whether to preserve whitespace within the menu's HTML.
  50. * Accepts 'preserve' or 'discard'. Default 'preserve'.
  51. * }
  52. * @return void|string|false Void if 'echo' argument is true, menu output if 'echo' is false.
  53. * False if there are no items or no menu was found.
  54. */
  55. function wp_nav_menu( $args = array() ) {
  56. static $menu_id_slugs = array();
  57. $defaults = array(
  58. 'menu' => '',
  59. 'container' => 'div',
  60. 'container_class' => '',
  61. 'container_id' => '',
  62. 'container_aria_label' => '',
  63. 'menu_class' => 'menu',
  64. 'menu_id' => '',
  65. 'echo' => true,
  66. 'fallback_cb' => 'wp_page_menu',
  67. 'before' => '',
  68. 'after' => '',
  69. 'link_before' => '',
  70. 'link_after' => '',
  71. 'items_wrap' => '<ul id="%1$s" class="%2$s">%3$s</ul>',
  72. 'item_spacing' => 'preserve',
  73. 'depth' => 0,
  74. 'walker' => '',
  75. 'theme_location' => '',
  76. );
  77. $args = wp_parse_args( $args, $defaults );
  78. if ( ! in_array( $args['item_spacing'], array( 'preserve', 'discard' ), true ) ) {
  79. // Invalid value, fall back to default.
  80. $args['item_spacing'] = $defaults['item_spacing'];
  81. }
  82. /**
  83. * Filters the arguments used to display a navigation menu.
  84. *
  85. * @since 3.0.0
  86. *
  87. * @see wp_nav_menu()
  88. *
  89. * @param array $args Array of wp_nav_menu() arguments.
  90. */
  91. $args = apply_filters( 'wp_nav_menu_args', $args );
  92. $args = (object) $args;
  93. /**
  94. * Filters whether to short-circuit the wp_nav_menu() output.
  95. *
  96. * Returning a non-null value from the filter will short-circuit wp_nav_menu(),
  97. * echoing that value if $args->echo is true, returning that value otherwise.
  98. *
  99. * @since 3.9.0
  100. *
  101. * @see wp_nav_menu()
  102. *
  103. * @param string|null $output Nav menu output to short-circuit with. Default null.
  104. * @param stdClass $args An object containing wp_nav_menu() arguments.
  105. */
  106. $nav_menu = apply_filters( 'pre_wp_nav_menu', null, $args );
  107. if ( null !== $nav_menu ) {
  108. if ( $args->echo ) {
  109. echo $nav_menu;
  110. return;
  111. }
  112. return $nav_menu;
  113. }
  114. // Get the nav menu based on the requested menu.
  115. $menu = wp_get_nav_menu_object( $args->menu );
  116. // Get the nav menu based on the theme_location.
  117. $locations = get_nav_menu_locations();
  118. if ( ! $menu && $args->theme_location && $locations && isset( $locations[ $args->theme_location ] ) ) {
  119. $menu = wp_get_nav_menu_object( $locations[ $args->theme_location ] );
  120. }
  121. // Get the first menu that has items if we still can't find a menu.
  122. if ( ! $menu && ! $args->theme_location ) {
  123. $menus = wp_get_nav_menus();
  124. foreach ( $menus as $menu_maybe ) {
  125. $menu_items = wp_get_nav_menu_items( $menu_maybe->term_id, array( 'update_post_term_cache' => false ) );
  126. if ( $menu_items ) {
  127. $menu = $menu_maybe;
  128. break;
  129. }
  130. }
  131. }
  132. if ( empty( $args->menu ) ) {
  133. $args->menu = $menu;
  134. }
  135. // If the menu exists, get its items.
  136. if ( $menu && ! is_wp_error( $menu ) && ! isset( $menu_items ) ) {
  137. $menu_items = wp_get_nav_menu_items( $menu->term_id, array( 'update_post_term_cache' => false ) );
  138. }
  139. /*
  140. * If no menu was found:
  141. * - Fall back (if one was specified), or bail.
  142. *
  143. * If no menu items were found:
  144. * - Fall back, but only if no theme location was specified.
  145. * - Otherwise, bail.
  146. */
  147. if ( ( ! $menu || is_wp_error( $menu ) || ( isset( $menu_items ) && empty( $menu_items ) && ! $args->theme_location ) )
  148. && isset( $args->fallback_cb ) && $args->fallback_cb && is_callable( $args->fallback_cb ) ) {
  149. return call_user_func( $args->fallback_cb, (array) $args );
  150. }
  151. if ( ! $menu || is_wp_error( $menu ) ) {
  152. return false;
  153. }
  154. $nav_menu = '';
  155. $items = '';
  156. $show_container = false;
  157. if ( $args->container ) {
  158. /**
  159. * Filters the list of HTML tags that are valid for use as menu containers.
  160. *
  161. * @since 3.0.0
  162. *
  163. * @param string[] $tags The acceptable HTML tags for use as menu containers.
  164. * Default is array containing 'div' and 'nav'.
  165. */
  166. $allowed_tags = apply_filters( 'wp_nav_menu_container_allowedtags', array( 'div', 'nav' ) );
  167. if ( is_string( $args->container ) && in_array( $args->container, $allowed_tags, true ) ) {
  168. $show_container = true;
  169. $class = $args->container_class ? ' class="' . esc_attr( $args->container_class ) . '"' : ' class="menu-' . $menu->slug . '-container"';
  170. $id = $args->container_id ? ' id="' . esc_attr( $args->container_id ) . '"' : '';
  171. $aria_label = ( 'nav' === $args->container && $args->container_aria_label ) ? ' aria-label="' . esc_attr( $args->container_aria_label ) . '"' : '';
  172. $nav_menu .= '<' . $args->container . $id . $class . $aria_label . '>';
  173. }
  174. }
  175. // Set up the $menu_item variables.
  176. _wp_menu_item_classes_by_context( $menu_items );
  177. $sorted_menu_items = array();
  178. $menu_items_tree = array();
  179. $menu_items_with_children = array();
  180. foreach ( (array) $menu_items as $menu_item ) {
  181. $sorted_menu_items[ $menu_item->menu_order ] = $menu_item;
  182. $menu_items_tree[ $menu_item->ID ] = $menu_item->menu_item_parent;
  183. if ( $menu_item->menu_item_parent ) {
  184. $menu_items_with_children[ $menu_item->menu_item_parent ] = 1;
  185. }
  186. }
  187. // Calculate the depth of each menu item with children.
  188. foreach ( $menu_items_with_children as $menu_item_key => &$menu_item_depth ) {
  189. $menu_item_parent = $menu_items_tree[ $menu_item_key ];
  190. while ( $menu_item_parent ) {
  191. $menu_item_depth = $menu_item_depth + 1;
  192. $menu_item_parent = $menu_items_tree[ $menu_item_parent ];
  193. }
  194. }
  195. // Add the menu-item-has-children class where applicable.
  196. if ( $menu_items_with_children ) {
  197. foreach ( $sorted_menu_items as &$menu_item ) {
  198. if (
  199. isset( $menu_items_with_children[ $menu_item->ID ] ) &&
  200. ( $args->depth <= 0 || $menu_items_with_children[ $menu_item->ID ] < $args->depth )
  201. ) {
  202. $menu_item->classes[] = 'menu-item-has-children';
  203. }
  204. }
  205. }
  206. unset( $menu_items_tree, $menu_items_with_children, $menu_items, $menu_item );
  207. /**
  208. * Filters the sorted list of menu item objects before generating the menu's HTML.
  209. *
  210. * @since 3.1.0
  211. *
  212. * @param array $sorted_menu_items The menu items, sorted by each menu item's menu order.
  213. * @param stdClass $args An object containing wp_nav_menu() arguments.
  214. */
  215. $sorted_menu_items = apply_filters( 'wp_nav_menu_objects', $sorted_menu_items, $args );
  216. $items .= walk_nav_menu_tree( $sorted_menu_items, $args->depth, $args );
  217. unset( $sorted_menu_items );
  218. // Attributes.
  219. if ( ! empty( $args->menu_id ) ) {
  220. $wrap_id = $args->menu_id;
  221. } else {
  222. $wrap_id = 'menu-' . $menu->slug;
  223. while ( in_array( $wrap_id, $menu_id_slugs, true ) ) {
  224. if ( preg_match( '#-(\d+)$#', $wrap_id, $matches ) ) {
  225. $wrap_id = preg_replace( '#-(\d+)$#', '-' . ++$matches[1], $wrap_id );
  226. } else {
  227. $wrap_id = $wrap_id . '-1';
  228. }
  229. }
  230. }
  231. $menu_id_slugs[] = $wrap_id;
  232. $wrap_class = $args->menu_class ? $args->menu_class : '';
  233. /**
  234. * Filters the HTML list content for navigation menus.
  235. *
  236. * @since 3.0.0
  237. *
  238. * @see wp_nav_menu()
  239. *
  240. * @param string $items The HTML list content for the menu items.
  241. * @param stdClass $args An object containing wp_nav_menu() arguments.
  242. */
  243. $items = apply_filters( 'wp_nav_menu_items', $items, $args );
  244. /**
  245. * Filters the HTML list content for a specific navigation menu.
  246. *
  247. * @since 3.0.0
  248. *
  249. * @see wp_nav_menu()
  250. *
  251. * @param string $items The HTML list content for the menu items.
  252. * @param stdClass $args An object containing wp_nav_menu() arguments.
  253. */
  254. $items = apply_filters( "wp_nav_menu_{$menu->slug}_items", $items, $args );
  255. // Don't print any markup if there are no items at this point.
  256. if ( empty( $items ) ) {
  257. return false;
  258. }
  259. $nav_menu .= sprintf( $args->items_wrap, esc_attr( $wrap_id ), esc_attr( $wrap_class ), $items );
  260. unset( $items );
  261. if ( $show_container ) {
  262. $nav_menu .= '</' . $args->container . '>';
  263. }
  264. /**
  265. * Filters the HTML content for navigation menus.
  266. *
  267. * @since 3.0.0
  268. *
  269. * @see wp_nav_menu()
  270. *
  271. * @param string $nav_menu The HTML content for the navigation menu.
  272. * @param stdClass $args An object containing wp_nav_menu() arguments.
  273. */
  274. $nav_menu = apply_filters( 'wp_nav_menu', $nav_menu, $args );
  275. if ( $args->echo ) {
  276. echo $nav_menu;
  277. } else {
  278. return $nav_menu;
  279. }
  280. }
  281. /**
  282. * Adds the class property classes for the current context, if applicable.
  283. *
  284. * @access private
  285. * @since 3.0.0
  286. *
  287. * @global WP_Query $wp_query WordPress Query object.
  288. * @global WP_Rewrite $wp_rewrite WordPress rewrite component.
  289. *
  290. * @param array $menu_items The current menu item objects to which to add the class property information.
  291. */
  292. function _wp_menu_item_classes_by_context( &$menu_items ) {
  293. global $wp_query, $wp_rewrite;
  294. $queried_object = $wp_query->get_queried_object();
  295. $queried_object_id = (int) $wp_query->queried_object_id;
  296. $active_object = '';
  297. $active_ancestor_item_ids = array();
  298. $active_parent_item_ids = array();
  299. $active_parent_object_ids = array();
  300. $possible_taxonomy_ancestors = array();
  301. $possible_object_parents = array();
  302. $home_page_id = (int) get_option( 'page_for_posts' );
  303. if ( $wp_query->is_singular && ! empty( $queried_object->post_type ) && ! is_post_type_hierarchical( $queried_object->post_type ) ) {
  304. foreach ( (array) get_object_taxonomies( $queried_object->post_type ) as $taxonomy ) {
  305. if ( is_taxonomy_hierarchical( $taxonomy ) ) {
  306. $term_hierarchy = _get_term_hierarchy( $taxonomy );
  307. $terms = wp_get_object_terms( $queried_object_id, $taxonomy, array( 'fields' => 'ids' ) );
  308. if ( is_array( $terms ) ) {
  309. $possible_object_parents = array_merge( $possible_object_parents, $terms );
  310. $term_to_ancestor = array();
  311. foreach ( (array) $term_hierarchy as $anc => $descs ) {
  312. foreach ( (array) $descs as $desc ) {
  313. $term_to_ancestor[ $desc ] = $anc;
  314. }
  315. }
  316. foreach ( $terms as $desc ) {
  317. do {
  318. $possible_taxonomy_ancestors[ $taxonomy ][] = $desc;
  319. if ( isset( $term_to_ancestor[ $desc ] ) ) {
  320. $_desc = $term_to_ancestor[ $desc ];
  321. unset( $term_to_ancestor[ $desc ] );
  322. $desc = $_desc;
  323. } else {
  324. $desc = 0;
  325. }
  326. } while ( ! empty( $desc ) );
  327. }
  328. }
  329. }
  330. }
  331. } elseif ( ! empty( $queried_object->taxonomy ) && is_taxonomy_hierarchical( $queried_object->taxonomy ) ) {
  332. $term_hierarchy = _get_term_hierarchy( $queried_object->taxonomy );
  333. $term_to_ancestor = array();
  334. foreach ( (array) $term_hierarchy as $anc => $descs ) {
  335. foreach ( (array) $descs as $desc ) {
  336. $term_to_ancestor[ $desc ] = $anc;
  337. }
  338. }
  339. $desc = $queried_object->term_id;
  340. do {
  341. $possible_taxonomy_ancestors[ $queried_object->taxonomy ][] = $desc;
  342. if ( isset( $term_to_ancestor[ $desc ] ) ) {
  343. $_desc = $term_to_ancestor[ $desc ];
  344. unset( $term_to_ancestor[ $desc ] );
  345. $desc = $_desc;
  346. } else {
  347. $desc = 0;
  348. }
  349. } while ( ! empty( $desc ) );
  350. }
  351. $possible_object_parents = array_filter( $possible_object_parents );
  352. $front_page_url = home_url();
  353. $front_page_id = (int) get_option( 'page_on_front' );
  354. $privacy_policy_page_id = (int) get_option( 'wp_page_for_privacy_policy' );
  355. foreach ( (array) $menu_items as $key => $menu_item ) {
  356. $menu_items[ $key ]->current = false;
  357. $classes = (array) $menu_item->classes;
  358. $classes[] = 'menu-item';
  359. $classes[] = 'menu-item-type-' . $menu_item->type;
  360. $classes[] = 'menu-item-object-' . $menu_item->object;
  361. // This menu item is set as the 'Front Page'.
  362. if ( 'post_type' === $menu_item->type && $front_page_id === (int) $menu_item->object_id ) {
  363. $classes[] = 'menu-item-home';
  364. }
  365. // This menu item is set as the 'Privacy Policy Page'.
  366. if ( 'post_type' === $menu_item->type && $privacy_policy_page_id === (int) $menu_item->object_id ) {
  367. $classes[] = 'menu-item-privacy-policy';
  368. }
  369. // If the menu item corresponds to a taxonomy term for the currently queried non-hierarchical post object.
  370. if ( $wp_query->is_singular && 'taxonomy' === $menu_item->type
  371. && in_array( (int) $menu_item->object_id, $possible_object_parents, true )
  372. ) {
  373. $active_parent_object_ids[] = (int) $menu_item->object_id;
  374. $active_parent_item_ids[] = (int) $menu_item->db_id;
  375. $active_object = $queried_object->post_type;
  376. // If the menu item corresponds to the currently queried post or taxonomy object.
  377. } elseif (
  378. $menu_item->object_id == $queried_object_id
  379. && (
  380. ( ! empty( $home_page_id ) && 'post_type' === $menu_item->type
  381. && $wp_query->is_home && $home_page_id == $menu_item->object_id )
  382. || ( 'post_type' === $menu_item->type && $wp_query->is_singular )
  383. || ( 'taxonomy' === $menu_item->type
  384. && ( $wp_query->is_category || $wp_query->is_tag || $wp_query->is_tax )
  385. && $queried_object->taxonomy == $menu_item->object )
  386. )
  387. ) {
  388. $classes[] = 'current-menu-item';
  389. $menu_items[ $key ]->current = true;
  390. $_anc_id = (int) $menu_item->db_id;
  391. while (
  392. ( $_anc_id = (int) get_post_meta( $_anc_id, '_menu_item_menu_item_parent', true ) )
  393. && ! in_array( $_anc_id, $active_ancestor_item_ids, true )
  394. ) {
  395. $active_ancestor_item_ids[] = $_anc_id;
  396. }
  397. if ( 'post_type' === $menu_item->type && 'page' === $menu_item->object ) {
  398. // Back compat classes for pages to match wp_page_menu().
  399. $classes[] = 'page_item';
  400. $classes[] = 'page-item-' . $menu_item->object_id;
  401. $classes[] = 'current_page_item';
  402. }
  403. $active_parent_item_ids[] = (int) $menu_item->menu_item_parent;
  404. $active_parent_object_ids[] = (int) $menu_item->post_parent;
  405. $active_object = $menu_item->object;
  406. // If the menu item corresponds to the currently queried post type archive.
  407. } elseif (
  408. 'post_type_archive' === $menu_item->type
  409. && is_post_type_archive( array( $menu_item->object ) )
  410. ) {
  411. $classes[] = 'current-menu-item';
  412. $menu_items[ $key ]->current = true;
  413. $_anc_id = (int) $menu_item->db_id;
  414. while (
  415. ( $_anc_id = (int) get_post_meta( $_anc_id, '_menu_item_menu_item_parent', true ) )
  416. && ! in_array( $_anc_id, $active_ancestor_item_ids, true )
  417. ) {
  418. $active_ancestor_item_ids[] = $_anc_id;
  419. }
  420. $active_parent_item_ids[] = (int) $menu_item->menu_item_parent;
  421. // If the menu item corresponds to the currently requested URL.
  422. } elseif ( 'custom' === $menu_item->object && isset( $_SERVER['HTTP_HOST'] ) ) {
  423. $_root_relative_current = untrailingslashit( $_SERVER['REQUEST_URI'] );
  424. // If it's the customize page then it will strip the query var off the URL before entering the comparison block.
  425. if ( is_customize_preview() ) {
  426. $_root_relative_current = strtok( untrailingslashit( $_SERVER['REQUEST_URI'] ), '?' );
  427. }
  428. $current_url = set_url_scheme( 'http://' . $_SERVER['HTTP_HOST'] . $_root_relative_current );
  429. $raw_item_url = strpos( $menu_item->url, '#' ) ? substr( $menu_item->url, 0, strpos( $menu_item->url, '#' ) ) : $menu_item->url;
  430. $item_url = set_url_scheme( untrailingslashit( $raw_item_url ) );
  431. $_indexless_current = untrailingslashit( preg_replace( '/' . preg_quote( $wp_rewrite->index, '/' ) . '$/', '', $current_url ) );
  432. $matches = array(
  433. $current_url,
  434. urldecode( $current_url ),
  435. $_indexless_current,
  436. urldecode( $_indexless_current ),
  437. $_root_relative_current,
  438. urldecode( $_root_relative_current ),
  439. );
  440. if ( $raw_item_url && in_array( $item_url, $matches, true ) ) {
  441. $classes[] = 'current-menu-item';
  442. $menu_items[ $key ]->current = true;
  443. $_anc_id = (int) $menu_item->db_id;
  444. while (
  445. ( $_anc_id = (int) get_post_meta( $_anc_id, '_menu_item_menu_item_parent', true ) )
  446. && ! in_array( $_anc_id, $active_ancestor_item_ids, true )
  447. ) {
  448. $active_ancestor_item_ids[] = $_anc_id;
  449. }
  450. if ( in_array( home_url(), array( untrailingslashit( $current_url ), untrailingslashit( $_indexless_current ) ), true ) ) {
  451. // Back compat for home link to match wp_page_menu().
  452. $classes[] = 'current_page_item';
  453. }
  454. $active_parent_item_ids[] = (int) $menu_item->menu_item_parent;
  455. $active_parent_object_ids[] = (int) $menu_item->post_parent;
  456. $active_object = $menu_item->object;
  457. // Give front page item the 'current-menu-item' class when extra query arguments are involved.
  458. } elseif ( $item_url == $front_page_url && is_front_page() ) {
  459. $classes[] = 'current-menu-item';
  460. }
  461. if ( untrailingslashit( $item_url ) == home_url() ) {
  462. $classes[] = 'menu-item-home';
  463. }
  464. }
  465. // Back-compat with wp_page_menu(): add "current_page_parent" to static home page link for any non-page query.
  466. if ( ! empty( $home_page_id ) && 'post_type' === $menu_item->type
  467. && empty( $wp_query->is_page ) && $home_page_id == $menu_item->object_id
  468. ) {
  469. $classes[] = 'current_page_parent';
  470. }
  471. $menu_items[ $key ]->classes = array_unique( $classes );
  472. }
  473. $active_ancestor_item_ids = array_filter( array_unique( $active_ancestor_item_ids ) );
  474. $active_parent_item_ids = array_filter( array_unique( $active_parent_item_ids ) );
  475. $active_parent_object_ids = array_filter( array_unique( $active_parent_object_ids ) );
  476. // Set parent's class.
  477. foreach ( (array) $menu_items as $key => $parent_item ) {
  478. $classes = (array) $parent_item->classes;
  479. $menu_items[ $key ]->current_item_ancestor = false;
  480. $menu_items[ $key ]->current_item_parent = false;
  481. if (
  482. isset( $parent_item->type )
  483. && (
  484. // Ancestral post object.
  485. (
  486. 'post_type' === $parent_item->type
  487. && ! empty( $queried_object->post_type )
  488. && is_post_type_hierarchical( $queried_object->post_type )
  489. && in_array( (int) $parent_item->object_id, $queried_object->ancestors, true )
  490. && $parent_item->object != $queried_object->ID
  491. ) ||
  492. // Ancestral term.
  493. (
  494. 'taxonomy' === $parent_item->type
  495. && isset( $possible_taxonomy_ancestors[ $parent_item->object ] )
  496. && in_array( (int) $parent_item->object_id, $possible_taxonomy_ancestors[ $parent_item->object ], true )
  497. && (
  498. ! isset( $queried_object->term_id ) ||
  499. $parent_item->object_id != $queried_object->term_id
  500. )
  501. )
  502. )
  503. ) {
  504. if ( ! empty( $queried_object->taxonomy ) ) {
  505. $classes[] = 'current-' . $queried_object->taxonomy . '-ancestor';
  506. } else {
  507. $classes[] = 'current-' . $queried_object->post_type . '-ancestor';
  508. }
  509. }
  510. if ( in_array( (int) $parent_item->db_id, $active_ancestor_item_ids, true ) ) {
  511. $classes[] = 'current-menu-ancestor';
  512. $menu_items[ $key ]->current_item_ancestor = true;
  513. }
  514. if ( in_array( (int) $parent_item->db_id, $active_parent_item_ids, true ) ) {
  515. $classes[] = 'current-menu-parent';
  516. $menu_items[ $key ]->current_item_parent = true;
  517. }
  518. if ( in_array( (int) $parent_item->object_id, $active_parent_object_ids, true ) ) {
  519. $classes[] = 'current-' . $active_object . '-parent';
  520. }
  521. if ( 'post_type' === $parent_item->type && 'page' === $parent_item->object ) {
  522. // Back compat classes for pages to match wp_page_menu().
  523. if ( in_array( 'current-menu-parent', $classes, true ) ) {
  524. $classes[] = 'current_page_parent';
  525. }
  526. if ( in_array( 'current-menu-ancestor', $classes, true ) ) {
  527. $classes[] = 'current_page_ancestor';
  528. }
  529. }
  530. $menu_items[ $key ]->classes = array_unique( $classes );
  531. }
  532. }
  533. /**
  534. * Retrieves the HTML list content for nav menu items.
  535. *
  536. * @uses Walker_Nav_Menu to create HTML list content.
  537. * @since 3.0.0
  538. *
  539. * @param array $items The menu items, sorted by each menu item's menu order.
  540. * @param int $depth Depth of the item in reference to parents.
  541. * @param stdClass $args An object containing wp_nav_menu() arguments.
  542. * @return string The HTML list content for the menu items.
  543. */
  544. function walk_nav_menu_tree( $items, $depth, $args ) {
  545. $walker = ( empty( $args->walker ) ) ? new Walker_Nav_Menu : $args->walker;
  546. return $walker->walk( $items, $depth, $args );
  547. }
  548. /**
  549. * Prevents a menu item ID from being used more than once.
  550. *
  551. * @since 3.0.1
  552. * @access private
  553. *
  554. * @param string $id
  555. * @param object $item
  556. * @return string
  557. */
  558. function _nav_menu_item_id_use_once( $id, $item ) {
  559. static $_used_ids = array();
  560. if ( in_array( $item->ID, $_used_ids, true ) ) {
  561. return '';
  562. }
  563. $_used_ids[] = $item->ID;
  564. return $id;
  565. }