nav-menu.php 41 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137
  1. <?php
  2. /**
  3. * Core Navigation Menu API
  4. *
  5. * @package WordPress
  6. * @subpackage Nav_Menus
  7. * @since 3.0.0
  8. */
  9. /** Walker_Nav_Menu_Edit class */
  10. require_once( ABSPATH . 'wp-admin/includes/class-walker-nav-menu-edit.php' );
  11. /** Walker_Nav_Menu_Checklist class */
  12. require_once( ABSPATH . 'wp-admin/includes/class-walker-nav-menu-checklist.php' );
  13. /**
  14. * Prints the appropriate response to a menu quick search.
  15. *
  16. * @since 3.0.0
  17. *
  18. * @param array $request The unsanitized request values.
  19. */
  20. function _wp_ajax_menu_quick_search( $request = array() ) {
  21. $args = array();
  22. $type = isset( $request['type'] ) ? $request['type'] : '';
  23. $object_type = isset( $request['object_type'] ) ? $request['object_type'] : '';
  24. $query = isset( $request['q'] ) ? $request['q'] : '';
  25. $response_format = isset( $request['response-format'] ) && in_array( $request['response-format'], array( 'json', 'markup' ) ) ? $request['response-format'] : 'json';
  26. if ( 'markup' == $response_format ) {
  27. $args['walker'] = new Walker_Nav_Menu_Checklist;
  28. }
  29. if ( 'get-post-item' == $type ) {
  30. if ( post_type_exists( $object_type ) ) {
  31. if ( isset( $request['ID'] ) ) {
  32. $object_id = (int) $request['ID'];
  33. if ( 'markup' == $response_format ) {
  34. echo walk_nav_menu_tree( array_map('wp_setup_nav_menu_item', array( get_post( $object_id ) ) ), 0, (object) $args );
  35. } elseif ( 'json' == $response_format ) {
  36. echo wp_json_encode(
  37. array(
  38. 'ID' => $object_id,
  39. 'post_title' => get_the_title( $object_id ),
  40. 'post_type' => get_post_type( $object_id ),
  41. )
  42. );
  43. echo "\n";
  44. }
  45. }
  46. } elseif ( taxonomy_exists( $object_type ) ) {
  47. if ( isset( $request['ID'] ) ) {
  48. $object_id = (int) $request['ID'];
  49. if ( 'markup' == $response_format ) {
  50. echo walk_nav_menu_tree( array_map('wp_setup_nav_menu_item', array( get_term( $object_id, $object_type ) ) ), 0, (object) $args );
  51. } elseif ( 'json' == $response_format ) {
  52. $post_obj = get_term( $object_id, $object_type );
  53. echo wp_json_encode(
  54. array(
  55. 'ID' => $object_id,
  56. 'post_title' => $post_obj->name,
  57. 'post_type' => $object_type,
  58. )
  59. );
  60. echo "\n";
  61. }
  62. }
  63. }
  64. } elseif ( preg_match('/quick-search-(posttype|taxonomy)-([a-zA-Z_-]*\b)/', $type, $matches) ) {
  65. if ( 'posttype' == $matches[1] && get_post_type_object( $matches[2] ) ) {
  66. $post_type_obj = _wp_nav_menu_meta_box_object( get_post_type_object( $matches[2] ) );
  67. $args = array_merge(
  68. $args,
  69. array(
  70. 'no_found_rows' => true,
  71. 'update_post_meta_cache' => false,
  72. 'update_post_term_cache' => false,
  73. 'posts_per_page' => 10,
  74. 'post_type' => $matches[2],
  75. 's' => $query,
  76. )
  77. );
  78. if ( isset( $post_type_obj->_default_query ) ) {
  79. $args = array_merge( $args, (array) $post_type_obj->_default_query );
  80. }
  81. $search_results_query = new WP_Query( $args );
  82. if ( ! $search_results_query->have_posts() ) {
  83. return;
  84. }
  85. while ( $search_results_query->have_posts() ) {
  86. $post = $search_results_query->next_post();
  87. if ( 'markup' == $response_format ) {
  88. $var_by_ref = $post->ID;
  89. echo walk_nav_menu_tree( array_map('wp_setup_nav_menu_item', array( get_post( $var_by_ref ) ) ), 0, (object) $args );
  90. } elseif ( 'json' == $response_format ) {
  91. echo wp_json_encode(
  92. array(
  93. 'ID' => $post->ID,
  94. 'post_title' => get_the_title( $post->ID ),
  95. 'post_type' => $matches[2],
  96. )
  97. );
  98. echo "\n";
  99. }
  100. }
  101. } elseif ( 'taxonomy' == $matches[1] ) {
  102. $terms = get_terms( $matches[2], array(
  103. 'name__like' => $query,
  104. 'number' => 10,
  105. ));
  106. if ( empty( $terms ) || is_wp_error( $terms ) )
  107. return;
  108. foreach ( (array) $terms as $term ) {
  109. if ( 'markup' == $response_format ) {
  110. echo walk_nav_menu_tree( array_map('wp_setup_nav_menu_item', array( $term ) ), 0, (object) $args );
  111. } elseif ( 'json' == $response_format ) {
  112. echo wp_json_encode(
  113. array(
  114. 'ID' => $term->term_id,
  115. 'post_title' => $term->name,
  116. 'post_type' => $matches[2],
  117. )
  118. );
  119. echo "\n";
  120. }
  121. }
  122. }
  123. }
  124. }
  125. /**
  126. * Register nav menu meta boxes and advanced menu items.
  127. *
  128. * @since 3.0.0
  129. **/
  130. function wp_nav_menu_setup() {
  131. // Register meta boxes
  132. wp_nav_menu_post_type_meta_boxes();
  133. add_meta_box( 'add-custom-links', __( 'Custom Links' ), 'wp_nav_menu_item_link_meta_box', 'nav-menus', 'side', 'default' );
  134. wp_nav_menu_taxonomy_meta_boxes();
  135. // Register advanced menu items (columns)
  136. add_filter( 'manage_nav-menus_columns', 'wp_nav_menu_manage_columns' );
  137. // If first time editing, disable advanced items by default.
  138. if ( false === get_user_option( 'managenav-menuscolumnshidden' ) ) {
  139. $user = wp_get_current_user();
  140. update_user_option($user->ID, 'managenav-menuscolumnshidden',
  141. array( 0 => 'link-target', 1 => 'css-classes', 2 => 'xfn', 3 => 'description', 4 => 'title-attribute', ),
  142. true);
  143. }
  144. }
  145. /**
  146. * Limit the amount of meta boxes to pages, posts, links, and categories for first time users.
  147. *
  148. * @since 3.0.0
  149. *
  150. * @global array $wp_meta_boxes
  151. **/
  152. function wp_initial_nav_menu_meta_boxes() {
  153. global $wp_meta_boxes;
  154. if ( get_user_option( 'metaboxhidden_nav-menus' ) !== false || ! is_array($wp_meta_boxes) )
  155. return;
  156. $initial_meta_boxes = array( 'add-post-type-page', 'add-post-type-post', 'add-custom-links', 'add-category' );
  157. $hidden_meta_boxes = array();
  158. foreach ( array_keys($wp_meta_boxes['nav-menus']) as $context ) {
  159. foreach ( array_keys($wp_meta_boxes['nav-menus'][$context]) as $priority ) {
  160. foreach ( $wp_meta_boxes['nav-menus'][$context][$priority] as $box ) {
  161. if ( in_array( $box['id'], $initial_meta_boxes ) ) {
  162. unset( $box['id'] );
  163. } else {
  164. $hidden_meta_boxes[] = $box['id'];
  165. }
  166. }
  167. }
  168. }
  169. $user = wp_get_current_user();
  170. update_user_option( $user->ID, 'metaboxhidden_nav-menus', $hidden_meta_boxes, true );
  171. }
  172. /**
  173. * Creates meta boxes for any post type menu item..
  174. *
  175. * @since 3.0.0
  176. */
  177. function wp_nav_menu_post_type_meta_boxes() {
  178. $post_types = get_post_types( array( 'show_in_nav_menus' => true ), 'object' );
  179. if ( ! $post_types )
  180. return;
  181. foreach ( $post_types as $post_type ) {
  182. /**
  183. * Filters whether a menu items meta box will be added for the current
  184. * object type.
  185. *
  186. * If a falsey value is returned instead of an object, the menu items
  187. * meta box for the current meta box object will not be added.
  188. *
  189. * @since 3.0.0
  190. *
  191. * @param object $meta_box_object The current object to add a menu items
  192. * meta box for.
  193. */
  194. $post_type = apply_filters( 'nav_menu_meta_box_object', $post_type );
  195. if ( $post_type ) {
  196. $id = $post_type->name;
  197. // Give pages a higher priority.
  198. $priority = ( 'page' == $post_type->name ? 'core' : 'default' );
  199. add_meta_box( "add-post-type-{$id}", $post_type->labels->name, 'wp_nav_menu_item_post_type_meta_box', 'nav-menus', 'side', $priority, $post_type );
  200. }
  201. }
  202. }
  203. /**
  204. * Creates meta boxes for any taxonomy menu item.
  205. *
  206. * @since 3.0.0
  207. */
  208. function wp_nav_menu_taxonomy_meta_boxes() {
  209. $taxonomies = get_taxonomies( array( 'show_in_nav_menus' => true ), 'object' );
  210. if ( !$taxonomies )
  211. return;
  212. foreach ( $taxonomies as $tax ) {
  213. /** This filter is documented in wp-admin/includes/nav-menu.php */
  214. $tax = apply_filters( 'nav_menu_meta_box_object', $tax );
  215. if ( $tax ) {
  216. $id = $tax->name;
  217. add_meta_box( "add-{$id}", $tax->labels->name, 'wp_nav_menu_item_taxonomy_meta_box', 'nav-menus', 'side', 'default', $tax );
  218. }
  219. }
  220. }
  221. /**
  222. * Check whether to disable the Menu Locations meta box submit button
  223. *
  224. * @since 3.6.0
  225. *
  226. * @global bool $one_theme_location_no_menus to determine if no menus exist
  227. *
  228. * @param int|string $nav_menu_selected_id (id, name or slug) of the currently-selected menu
  229. * @return string Disabled attribute if at least one menu exists, false if not
  230. */
  231. function wp_nav_menu_disabled_check( $nav_menu_selected_id ) {
  232. global $one_theme_location_no_menus;
  233. if ( $one_theme_location_no_menus )
  234. return false;
  235. return disabled( $nav_menu_selected_id, 0 );
  236. }
  237. /**
  238. * Displays a meta box for the custom links menu item.
  239. *
  240. * @since 3.0.0
  241. *
  242. * @global int $_nav_menu_placeholder
  243. * @global int|string $nav_menu_selected_id
  244. */
  245. function wp_nav_menu_item_link_meta_box() {
  246. global $_nav_menu_placeholder, $nav_menu_selected_id;
  247. $_nav_menu_placeholder = 0 > $_nav_menu_placeholder ? $_nav_menu_placeholder - 1 : -1;
  248. ?>
  249. <div class="customlinkdiv" id="customlinkdiv">
  250. <input type="hidden" value="custom" name="menu-item[<?php echo $_nav_menu_placeholder; ?>][menu-item-type]" />
  251. <p id="menu-item-url-wrap" class="wp-clearfix">
  252. <label class="howto" for="custom-menu-item-url"><?php _e( 'URL' ); ?></label>
  253. <input id="custom-menu-item-url" name="menu-item[<?php echo $_nav_menu_placeholder; ?>][menu-item-url]" type="text" class="code menu-item-textbox" value="http://" />
  254. </p>
  255. <p id="menu-item-name-wrap" class="wp-clearfix">
  256. <label class="howto" for="custom-menu-item-name"><?php _e( 'Link Text' ); ?></label>
  257. <input id="custom-menu-item-name" name="menu-item[<?php echo $_nav_menu_placeholder; ?>][menu-item-title]" type="text" class="regular-text menu-item-textbox" />
  258. </p>
  259. <p class="button-controls wp-clearfix">
  260. <span class="add-to-menu">
  261. <input type="submit"<?php wp_nav_menu_disabled_check( $nav_menu_selected_id ); ?> class="button submit-add-to-menu right" value="<?php esc_attr_e('Add to Menu'); ?>" name="add-custom-menu-item" id="submit-customlinkdiv" />
  262. <span class="spinner"></span>
  263. </span>
  264. </p>
  265. </div><!-- /.customlinkdiv -->
  266. <?php
  267. }
  268. /**
  269. * Displays a meta box for a post type menu item.
  270. *
  271. * @since 3.0.0
  272. *
  273. * @global int $_nav_menu_placeholder
  274. * @global int|string $nav_menu_selected_id
  275. *
  276. * @param string $object Not used.
  277. * @param array $box {
  278. * Post type menu item meta box arguments.
  279. *
  280. * @type string $id Meta box 'id' attribute.
  281. * @type string $title Meta box title.
  282. * @type string $callback Meta box display callback.
  283. * @type WP_Post_Type $args Extra meta box arguments (the post type object for this meta box).
  284. * }
  285. */
  286. function wp_nav_menu_item_post_type_meta_box( $object, $box ) {
  287. global $_nav_menu_placeholder, $nav_menu_selected_id;
  288. $post_type_name = $box['args']->name;
  289. // Paginate browsing for large numbers of post objects.
  290. $per_page = 50;
  291. $pagenum = isset( $_REQUEST[$post_type_name . '-tab'] ) && isset( $_REQUEST['paged'] ) ? absint( $_REQUEST['paged'] ) : 1;
  292. $offset = 0 < $pagenum ? $per_page * ( $pagenum - 1 ) : 0;
  293. $args = array(
  294. 'offset' => $offset,
  295. 'order' => 'ASC',
  296. 'orderby' => 'title',
  297. 'posts_per_page' => $per_page,
  298. 'post_type' => $post_type_name,
  299. 'suppress_filters' => true,
  300. 'update_post_term_cache' => false,
  301. 'update_post_meta_cache' => false
  302. );
  303. if ( isset( $box['args']->_default_query ) )
  304. $args = array_merge($args, (array) $box['args']->_default_query );
  305. // @todo transient caching of these results with proper invalidation on updating of a post of this type
  306. $get_posts = new WP_Query;
  307. $posts = $get_posts->query( $args );
  308. if ( ! $get_posts->post_count ) {
  309. echo '<p>' . __( 'No items.' ) . '</p>';
  310. return;
  311. }
  312. $num_pages = $get_posts->max_num_pages;
  313. $page_links = paginate_links( array(
  314. 'base' => add_query_arg(
  315. array(
  316. $post_type_name . '-tab' => 'all',
  317. 'paged' => '%#%',
  318. 'item-type' => 'post_type',
  319. 'item-object' => $post_type_name,
  320. )
  321. ),
  322. 'format' => '',
  323. 'prev_text' => '<span aria-label="' . esc_attr__( 'Previous page' ) . '">' . __( '&laquo;' ) . '</span>',
  324. 'next_text' => '<span aria-label="' . esc_attr__( 'Next page' ) . '">' . __( '&raquo;' ) . '</span>',
  325. 'before_page_number' => '<span class="screen-reader-text">' . __( 'Page' ) . '</span> ',
  326. 'total' => $num_pages,
  327. 'current' => $pagenum
  328. ));
  329. $db_fields = false;
  330. if ( is_post_type_hierarchical( $post_type_name ) ) {
  331. $db_fields = array( 'parent' => 'post_parent', 'id' => 'ID' );
  332. }
  333. $walker = new Walker_Nav_Menu_Checklist( $db_fields );
  334. $current_tab = 'most-recent';
  335. if ( isset( $_REQUEST[$post_type_name . '-tab'] ) && in_array( $_REQUEST[$post_type_name . '-tab'], array('all', 'search') ) ) {
  336. $current_tab = $_REQUEST[$post_type_name . '-tab'];
  337. }
  338. if ( ! empty( $_REQUEST['quick-search-posttype-' . $post_type_name] ) ) {
  339. $current_tab = 'search';
  340. }
  341. $removed_args = array(
  342. 'action',
  343. 'customlink-tab',
  344. 'edit-menu-item',
  345. 'menu-item',
  346. 'page-tab',
  347. '_wpnonce',
  348. );
  349. ?>
  350. <div id="posttype-<?php echo $post_type_name; ?>" class="posttypediv">
  351. <ul id="posttype-<?php echo $post_type_name; ?>-tabs" class="posttype-tabs add-menu-item-tabs">
  352. <li <?php echo ( 'most-recent' == $current_tab ? ' class="tabs"' : '' ); ?>>
  353. <a class="nav-tab-link" data-type="tabs-panel-posttype-<?php echo esc_attr( $post_type_name ); ?>-most-recent" href="<?php if ( $nav_menu_selected_id ) echo esc_url(add_query_arg($post_type_name . '-tab', 'most-recent', remove_query_arg($removed_args))); ?>#tabs-panel-posttype-<?php echo $post_type_name; ?>-most-recent">
  354. <?php _e( 'Most Recent' ); ?>
  355. </a>
  356. </li>
  357. <li <?php echo ( 'all' == $current_tab ? ' class="tabs"' : '' ); ?>>
  358. <a class="nav-tab-link" data-type="<?php echo esc_attr( $post_type_name ); ?>-all" href="<?php if ( $nav_menu_selected_id ) echo esc_url(add_query_arg($post_type_name . '-tab', 'all', remove_query_arg($removed_args))); ?>#<?php echo $post_type_name; ?>-all">
  359. <?php _e( 'View All' ); ?>
  360. </a>
  361. </li>
  362. <li <?php echo ( 'search' == $current_tab ? ' class="tabs"' : '' ); ?>>
  363. <a class="nav-tab-link" data-type="tabs-panel-posttype-<?php echo esc_attr( $post_type_name ); ?>-search" href="<?php if ( $nav_menu_selected_id ) echo esc_url(add_query_arg($post_type_name . '-tab', 'search', remove_query_arg($removed_args))); ?>#tabs-panel-posttype-<?php echo $post_type_name; ?>-search">
  364. <?php _e( 'Search'); ?>
  365. </a>
  366. </li>
  367. </ul><!-- .posttype-tabs -->
  368. <div id="tabs-panel-posttype-<?php echo $post_type_name; ?>-most-recent" class="tabs-panel <?php
  369. echo ( 'most-recent' == $current_tab ? 'tabs-panel-active' : 'tabs-panel-inactive' );
  370. ?>">
  371. <ul id="<?php echo $post_type_name; ?>checklist-most-recent" class="categorychecklist form-no-clear">
  372. <?php
  373. $recent_args = array_merge( $args, array( 'orderby' => 'post_date', 'order' => 'DESC', 'posts_per_page' => 15 ) );
  374. $most_recent = $get_posts->query( $recent_args );
  375. $args['walker'] = $walker;
  376. /**
  377. * Filters the posts displayed in the 'Most Recent' tab of the current
  378. * post type's menu items meta box.
  379. *
  380. * The dynamic portion of the hook name, `$post_type_name`, refers to the post type name.
  381. *
  382. * @since 4.3.0
  383. * @since 4.9.0 Added the `$recent_args` parameter.
  384. *
  385. * @param array $most_recent An array of post objects being listed.
  386. * @param array $args An array of WP_Query arguments for the meta box.
  387. * @param array $box Arguments passed to wp_nav_menu_item_post_type_meta_box().
  388. * @param array $recent_args An array of WP_Query arguments for 'Most Recent' tab.
  389. */
  390. $most_recent = apply_filters( "nav_menu_items_{$post_type_name}_recent", $most_recent, $args, $box, $recent_args );
  391. echo walk_nav_menu_tree( array_map( 'wp_setup_nav_menu_item', $most_recent ), 0, (object) $args );
  392. ?>
  393. </ul>
  394. </div><!-- /.tabs-panel -->
  395. <div class="tabs-panel <?php
  396. echo ( 'search' == $current_tab ? 'tabs-panel-active' : 'tabs-panel-inactive' );
  397. ?>" id="tabs-panel-posttype-<?php echo $post_type_name; ?>-search">
  398. <?php
  399. if ( isset( $_REQUEST['quick-search-posttype-' . $post_type_name] ) ) {
  400. $searched = esc_attr( $_REQUEST['quick-search-posttype-' . $post_type_name] );
  401. $search_results = get_posts( array( 's' => $searched, 'post_type' => $post_type_name, 'fields' => 'all', 'order' => 'DESC', ) );
  402. } else {
  403. $searched = '';
  404. $search_results = array();
  405. }
  406. ?>
  407. <p class="quick-search-wrap">
  408. <label for="quick-search-posttype-<?php echo $post_type_name; ?>" class="screen-reader-text"><?php _e( 'Search' ); ?></label>
  409. <input type="search" class="quick-search" value="<?php echo $searched; ?>" name="quick-search-posttype-<?php echo $post_type_name; ?>" id="quick-search-posttype-<?php echo $post_type_name; ?>" />
  410. <span class="spinner"></span>
  411. <?php submit_button( __( 'Search' ), 'small quick-search-submit hide-if-js', 'submit', false, array( 'id' => 'submit-quick-search-posttype-' . $post_type_name ) ); ?>
  412. </p>
  413. <ul id="<?php echo $post_type_name; ?>-search-checklist" data-wp-lists="list:<?php echo $post_type_name?>" class="categorychecklist form-no-clear">
  414. <?php if ( ! empty( $search_results ) && ! is_wp_error( $search_results ) ) : ?>
  415. <?php
  416. $args['walker'] = $walker;
  417. echo walk_nav_menu_tree( array_map('wp_setup_nav_menu_item', $search_results), 0, (object) $args );
  418. ?>
  419. <?php elseif ( is_wp_error( $search_results ) ) : ?>
  420. <li><?php echo $search_results->get_error_message(); ?></li>
  421. <?php elseif ( ! empty( $searched ) ) : ?>
  422. <li><?php _e('No results found.'); ?></li>
  423. <?php endif; ?>
  424. </ul>
  425. </div><!-- /.tabs-panel -->
  426. <div id="<?php echo $post_type_name; ?>-all" class="tabs-panel tabs-panel-view-all <?php
  427. echo ( 'all' == $current_tab ? 'tabs-panel-active' : 'tabs-panel-inactive' );
  428. ?>">
  429. <?php if ( ! empty( $page_links ) ) : ?>
  430. <div class="add-menu-item-pagelinks">
  431. <?php echo $page_links; ?>
  432. </div>
  433. <?php endif; ?>
  434. <ul id="<?php echo $post_type_name; ?>checklist" data-wp-lists="list:<?php echo $post_type_name?>" class="categorychecklist form-no-clear">
  435. <?php
  436. $args['walker'] = $walker;
  437. /*
  438. * If we're dealing with pages, let's put a checkbox for the front
  439. * page at the top of the list.
  440. */
  441. if ( 'page' == $post_type_name ) {
  442. $front_page = 'page' == get_option('show_on_front') ? (int) get_option( 'page_on_front' ) : 0;
  443. if ( ! empty( $front_page ) ) {
  444. $front_page_obj = get_post( $front_page );
  445. $front_page_obj->front_or_home = true;
  446. array_unshift( $posts, $front_page_obj );
  447. } else {
  448. $_nav_menu_placeholder = ( 0 > $_nav_menu_placeholder ) ? intval($_nav_menu_placeholder) - 1 : -1;
  449. array_unshift( $posts, (object) array(
  450. 'front_or_home' => true,
  451. 'ID' => 0,
  452. 'object_id' => $_nav_menu_placeholder,
  453. 'post_content' => '',
  454. 'post_excerpt' => '',
  455. 'post_parent' => '',
  456. 'post_title' => _x('Home', 'nav menu home label'),
  457. 'post_type' => 'nav_menu_item',
  458. 'type' => 'custom',
  459. 'url' => home_url('/'),
  460. ) );
  461. }
  462. }
  463. $post_type = get_post_type_object( $post_type_name );
  464. if ( $post_type->has_archive ) {
  465. $_nav_menu_placeholder = ( 0 > $_nav_menu_placeholder ) ? intval($_nav_menu_placeholder) - 1 : -1;
  466. array_unshift( $posts, (object) array(
  467. 'ID' => 0,
  468. 'object_id' => $_nav_menu_placeholder,
  469. 'object' => $post_type_name,
  470. 'post_content' => '',
  471. 'post_excerpt' => '',
  472. 'post_title' => $post_type->labels->archives,
  473. 'post_type' => 'nav_menu_item',
  474. 'type' => 'post_type_archive',
  475. 'url' => get_post_type_archive_link( $post_type_name ),
  476. ) );
  477. }
  478. /**
  479. * Filters the posts displayed in the 'View All' tab of the current
  480. * post type's menu items meta box.
  481. *
  482. * The dynamic portion of the hook name, `$post_type_name`, refers
  483. * to the slug of the current post type.
  484. *
  485. * @since 3.2.0
  486. * @since 4.6.0 Converted the `$post_type` parameter to accept a WP_Post_Type object.
  487. *
  488. * @see WP_Query::query()
  489. *
  490. * @param array $posts The posts for the current post type.
  491. * @param array $args An array of WP_Query arguments.
  492. * @param WP_Post_Type $post_type The current post type object for this menu item meta box.
  493. */
  494. $posts = apply_filters( "nav_menu_items_{$post_type_name}", $posts, $args, $post_type );
  495. $checkbox_items = walk_nav_menu_tree( array_map('wp_setup_nav_menu_item', $posts), 0, (object) $args );
  496. if ( 'all' == $current_tab && ! empty( $_REQUEST['selectall'] ) ) {
  497. $checkbox_items = preg_replace('/(type=(.)checkbox(\2))/', '$1 checked=$2checked$2', $checkbox_items);
  498. }
  499. echo $checkbox_items;
  500. ?>
  501. </ul>
  502. <?php if ( ! empty( $page_links ) ) : ?>
  503. <div class="add-menu-item-pagelinks">
  504. <?php echo $page_links; ?>
  505. </div>
  506. <?php endif; ?>
  507. </div><!-- /.tabs-panel -->
  508. <p class="button-controls wp-clearfix">
  509. <span class="list-controls">
  510. <a href="<?php
  511. echo esc_url( add_query_arg(
  512. array(
  513. $post_type_name . '-tab' => 'all',
  514. 'selectall' => 1,
  515. ),
  516. remove_query_arg( $removed_args )
  517. ));
  518. ?>#posttype-<?php echo $post_type_name; ?>" class="select-all aria-button-if-js"><?php _e( 'Select All' ); ?></a>
  519. </span>
  520. <span class="add-to-menu">
  521. <input type="submit"<?php wp_nav_menu_disabled_check( $nav_menu_selected_id ); ?> class="button submit-add-to-menu right" value="<?php esc_attr_e( 'Add to Menu' ); ?>" name="add-post-type-menu-item" id="<?php echo esc_attr( 'submit-posttype-' . $post_type_name ); ?>" />
  522. <span class="spinner"></span>
  523. </span>
  524. </p>
  525. </div><!-- /.posttypediv -->
  526. <?php
  527. }
  528. /**
  529. * Displays a meta box for a taxonomy menu item.
  530. *
  531. * @since 3.0.0
  532. *
  533. * @global int|string $nav_menu_selected_id
  534. *
  535. * @param string $object Not used.
  536. * @param array $box {
  537. * Taxonomy menu item meta box arguments.
  538. *
  539. * @type string $id Meta box 'id' attribute.
  540. * @type string $title Meta box title.
  541. * @type string $callback Meta box display callback.
  542. * @type object $args Extra meta box arguments (the taxonomy object for this meta box).
  543. * }
  544. */
  545. function wp_nav_menu_item_taxonomy_meta_box( $object, $box ) {
  546. global $nav_menu_selected_id;
  547. $taxonomy_name = $box['args']->name;
  548. $taxonomy = get_taxonomy( $taxonomy_name );
  549. // Paginate browsing for large numbers of objects.
  550. $per_page = 50;
  551. $pagenum = isset( $_REQUEST[$taxonomy_name . '-tab'] ) && isset( $_REQUEST['paged'] ) ? absint( $_REQUEST['paged'] ) : 1;
  552. $offset = 0 < $pagenum ? $per_page * ( $pagenum - 1 ) : 0;
  553. $args = array(
  554. 'child_of' => 0,
  555. 'exclude' => '',
  556. 'hide_empty' => false,
  557. 'hierarchical' => 1,
  558. 'include' => '',
  559. 'number' => $per_page,
  560. 'offset' => $offset,
  561. 'order' => 'ASC',
  562. 'orderby' => 'name',
  563. 'pad_counts' => false,
  564. );
  565. $terms = get_terms( $taxonomy_name, $args );
  566. if ( ! $terms || is_wp_error($terms) ) {
  567. echo '<p>' . __( 'No items.' ) . '</p>';
  568. return;
  569. }
  570. $num_pages = ceil( wp_count_terms( $taxonomy_name , array_merge( $args, array('number' => '', 'offset' => '') ) ) / $per_page );
  571. $page_links = paginate_links( array(
  572. 'base' => add_query_arg(
  573. array(
  574. $taxonomy_name . '-tab' => 'all',
  575. 'paged' => '%#%',
  576. 'item-type' => 'taxonomy',
  577. 'item-object' => $taxonomy_name,
  578. )
  579. ),
  580. 'format' => '',
  581. 'prev_text' => '<span aria-label="' . esc_attr__( 'Previous page' ) . '">' . __( '&laquo;' ) . '</span>',
  582. 'next_text' => '<span aria-label="' . esc_attr__( 'Next page' ) . '">' . __( '&raquo;' ) . '</span>',
  583. 'before_page_number' => '<span class="screen-reader-text">' . __( 'Page' ) . '</span> ',
  584. 'total' => $num_pages,
  585. 'current' => $pagenum
  586. ));
  587. $db_fields = false;
  588. if ( is_taxonomy_hierarchical( $taxonomy_name ) ) {
  589. $db_fields = array( 'parent' => 'parent', 'id' => 'term_id' );
  590. }
  591. $walker = new Walker_Nav_Menu_Checklist( $db_fields );
  592. $current_tab = 'most-used';
  593. if ( isset( $_REQUEST[$taxonomy_name . '-tab'] ) && in_array( $_REQUEST[$taxonomy_name . '-tab'], array('all', 'most-used', 'search') ) ) {
  594. $current_tab = $_REQUEST[$taxonomy_name . '-tab'];
  595. }
  596. if ( ! empty( $_REQUEST['quick-search-taxonomy-' . $taxonomy_name] ) ) {
  597. $current_tab = 'search';
  598. }
  599. $removed_args = array(
  600. 'action',
  601. 'customlink-tab',
  602. 'edit-menu-item',
  603. 'menu-item',
  604. 'page-tab',
  605. '_wpnonce',
  606. );
  607. ?>
  608. <div id="taxonomy-<?php echo $taxonomy_name; ?>" class="taxonomydiv">
  609. <ul id="taxonomy-<?php echo $taxonomy_name; ?>-tabs" class="taxonomy-tabs add-menu-item-tabs">
  610. <li <?php echo ( 'most-used' == $current_tab ? ' class="tabs"' : '' ); ?>>
  611. <a class="nav-tab-link" data-type="tabs-panel-<?php echo esc_attr( $taxonomy_name ); ?>-pop" href="<?php if ( $nav_menu_selected_id ) echo esc_url(add_query_arg($taxonomy_name . '-tab', 'most-used', remove_query_arg($removed_args))); ?>#tabs-panel-<?php echo $taxonomy_name; ?>-pop">
  612. <?php echo esc_html( $taxonomy->labels->most_used ); ?>
  613. </a>
  614. </li>
  615. <li <?php echo ( 'all' == $current_tab ? ' class="tabs"' : '' ); ?>>
  616. <a class="nav-tab-link" data-type="tabs-panel-<?php echo esc_attr( $taxonomy_name ); ?>-all" href="<?php if ( $nav_menu_selected_id ) echo esc_url(add_query_arg($taxonomy_name . '-tab', 'all', remove_query_arg($removed_args))); ?>#tabs-panel-<?php echo $taxonomy_name; ?>-all">
  617. <?php _e( 'View All' ); ?>
  618. </a>
  619. </li>
  620. <li <?php echo ( 'search' == $current_tab ? ' class="tabs"' : '' ); ?>>
  621. <a class="nav-tab-link" data-type="tabs-panel-search-taxonomy-<?php echo esc_attr( $taxonomy_name ); ?>" href="<?php if ( $nav_menu_selected_id ) echo esc_url(add_query_arg($taxonomy_name . '-tab', 'search', remove_query_arg($removed_args))); ?>#tabs-panel-search-taxonomy-<?php echo $taxonomy_name; ?>">
  622. <?php _e( 'Search' ); ?>
  623. </a>
  624. </li>
  625. </ul><!-- .taxonomy-tabs -->
  626. <div id="tabs-panel-<?php echo $taxonomy_name; ?>-pop" class="tabs-panel <?php
  627. echo ( 'most-used' == $current_tab ? 'tabs-panel-active' : 'tabs-panel-inactive' );
  628. ?>">
  629. <ul id="<?php echo $taxonomy_name; ?>checklist-pop" class="categorychecklist form-no-clear" >
  630. <?php
  631. $popular_terms = get_terms( $taxonomy_name, array( 'orderby' => 'count', 'order' => 'DESC', 'number' => 10, 'hierarchical' => false ) );
  632. $args['walker'] = $walker;
  633. echo walk_nav_menu_tree( array_map('wp_setup_nav_menu_item', $popular_terms), 0, (object) $args );
  634. ?>
  635. </ul>
  636. </div><!-- /.tabs-panel -->
  637. <div id="tabs-panel-<?php echo $taxonomy_name; ?>-all" class="tabs-panel tabs-panel-view-all <?php
  638. echo ( 'all' == $current_tab ? 'tabs-panel-active' : 'tabs-panel-inactive' );
  639. ?>">
  640. <?php if ( ! empty( $page_links ) ) : ?>
  641. <div class="add-menu-item-pagelinks">
  642. <?php echo $page_links; ?>
  643. </div>
  644. <?php endif; ?>
  645. <ul id="<?php echo $taxonomy_name; ?>checklist" data-wp-lists="list:<?php echo $taxonomy_name?>" class="categorychecklist form-no-clear">
  646. <?php
  647. $args['walker'] = $walker;
  648. echo walk_nav_menu_tree( array_map('wp_setup_nav_menu_item', $terms), 0, (object) $args );
  649. ?>
  650. </ul>
  651. <?php if ( ! empty( $page_links ) ) : ?>
  652. <div class="add-menu-item-pagelinks">
  653. <?php echo $page_links; ?>
  654. </div>
  655. <?php endif; ?>
  656. </div><!-- /.tabs-panel -->
  657. <div class="tabs-panel <?php
  658. echo ( 'search' == $current_tab ? 'tabs-panel-active' : 'tabs-panel-inactive' );
  659. ?>" id="tabs-panel-search-taxonomy-<?php echo $taxonomy_name; ?>">
  660. <?php
  661. if ( isset( $_REQUEST['quick-search-taxonomy-' . $taxonomy_name] ) ) {
  662. $searched = esc_attr( $_REQUEST['quick-search-taxonomy-' . $taxonomy_name] );
  663. $search_results = get_terms( $taxonomy_name, array( 'name__like' => $searched, 'fields' => 'all', 'orderby' => 'count', 'order' => 'DESC', 'hierarchical' => false ) );
  664. } else {
  665. $searched = '';
  666. $search_results = array();
  667. }
  668. ?>
  669. <p class="quick-search-wrap">
  670. <label for="quick-search-taxonomy-<?php echo $taxonomy_name; ?>" class="screen-reader-text"><?php _e( 'Search' ); ?></label>
  671. <input type="search" class="quick-search" value="<?php echo $searched; ?>" name="quick-search-taxonomy-<?php echo $taxonomy_name; ?>" id="quick-search-taxonomy-<?php echo $taxonomy_name; ?>" />
  672. <span class="spinner"></span>
  673. <?php submit_button( __( 'Search' ), 'small quick-search-submit hide-if-js', 'submit', false, array( 'id' => 'submit-quick-search-taxonomy-' . $taxonomy_name ) ); ?>
  674. </p>
  675. <ul id="<?php echo $taxonomy_name; ?>-search-checklist" data-wp-lists="list:<?php echo $taxonomy_name?>" class="categorychecklist form-no-clear">
  676. <?php if ( ! empty( $search_results ) && ! is_wp_error( $search_results ) ) : ?>
  677. <?php
  678. $args['walker'] = $walker;
  679. echo walk_nav_menu_tree( array_map('wp_setup_nav_menu_item', $search_results), 0, (object) $args );
  680. ?>
  681. <?php elseif ( is_wp_error( $search_results ) ) : ?>
  682. <li><?php echo $search_results->get_error_message(); ?></li>
  683. <?php elseif ( ! empty( $searched ) ) : ?>
  684. <li><?php _e('No results found.'); ?></li>
  685. <?php endif; ?>
  686. </ul>
  687. </div><!-- /.tabs-panel -->
  688. <p class="button-controls wp-clearfix">
  689. <span class="list-controls">
  690. <a href="<?php
  691. echo esc_url(add_query_arg(
  692. array(
  693. $taxonomy_name . '-tab' => 'all',
  694. 'selectall' => 1,
  695. ),
  696. remove_query_arg($removed_args)
  697. ));
  698. ?>#taxonomy-<?php echo $taxonomy_name; ?>" class="select-all aria-button-if-js"><?php _e( 'Select All' ); ?></a>
  699. </span>
  700. <span class="add-to-menu">
  701. <input type="submit"<?php wp_nav_menu_disabled_check( $nav_menu_selected_id ); ?> class="button submit-add-to-menu right" value="<?php esc_attr_e( 'Add to Menu' ); ?>" name="add-taxonomy-menu-item" id="<?php echo esc_attr( 'submit-taxonomy-' . $taxonomy_name ); ?>" />
  702. <span class="spinner"></span>
  703. </span>
  704. </p>
  705. </div><!-- /.taxonomydiv -->
  706. <?php
  707. }
  708. /**
  709. * Save posted nav menu item data.
  710. *
  711. * @since 3.0.0
  712. *
  713. * @param int $menu_id The menu ID for which to save this item. $menu_id of 0 makes a draft, orphaned menu item.
  714. * @param array $menu_data The unsanitized posted menu item data.
  715. * @return array The database IDs of the items saved
  716. */
  717. function wp_save_nav_menu_items( $menu_id = 0, $menu_data = array() ) {
  718. $menu_id = (int) $menu_id;
  719. $items_saved = array();
  720. if ( 0 == $menu_id || is_nav_menu( $menu_id ) ) {
  721. // Loop through all the menu items' POST values.
  722. foreach ( (array) $menu_data as $_possible_db_id => $_item_object_data ) {
  723. if (
  724. // Checkbox is not checked.
  725. empty( $_item_object_data['menu-item-object-id'] ) &&
  726. (
  727. // And item type either isn't set.
  728. ! isset( $_item_object_data['menu-item-type'] ) ||
  729. // Or URL is the default.
  730. in_array( $_item_object_data['menu-item-url'], array( 'http://', '' ) ) ||
  731. ! ( 'custom' == $_item_object_data['menu-item-type'] && ! isset( $_item_object_data['menu-item-db-id'] ) ) || // or it's not a custom menu item (but not the custom home page)
  732. // Or it *is* a custom menu item that already exists.
  733. ! empty( $_item_object_data['menu-item-db-id'] )
  734. )
  735. ) {
  736. // Then this potential menu item is not getting added to this menu.
  737. continue;
  738. }
  739. // If this possible menu item doesn't actually have a menu database ID yet.
  740. if (
  741. empty( $_item_object_data['menu-item-db-id'] ) ||
  742. ( 0 > $_possible_db_id ) ||
  743. $_possible_db_id != $_item_object_data['menu-item-db-id']
  744. ) {
  745. $_actual_db_id = 0;
  746. } else {
  747. $_actual_db_id = (int) $_item_object_data['menu-item-db-id'];
  748. }
  749. $args = array(
  750. 'menu-item-db-id' => ( isset( $_item_object_data['menu-item-db-id'] ) ? $_item_object_data['menu-item-db-id'] : '' ),
  751. 'menu-item-object-id' => ( isset( $_item_object_data['menu-item-object-id'] ) ? $_item_object_data['menu-item-object-id'] : '' ),
  752. 'menu-item-object' => ( isset( $_item_object_data['menu-item-object'] ) ? $_item_object_data['menu-item-object'] : '' ),
  753. 'menu-item-parent-id' => ( isset( $_item_object_data['menu-item-parent-id'] ) ? $_item_object_data['menu-item-parent-id'] : '' ),
  754. 'menu-item-position' => ( isset( $_item_object_data['menu-item-position'] ) ? $_item_object_data['menu-item-position'] : '' ),
  755. 'menu-item-type' => ( isset( $_item_object_data['menu-item-type'] ) ? $_item_object_data['menu-item-type'] : '' ),
  756. 'menu-item-title' => ( isset( $_item_object_data['menu-item-title'] ) ? $_item_object_data['menu-item-title'] : '' ),
  757. 'menu-item-url' => ( isset( $_item_object_data['menu-item-url'] ) ? $_item_object_data['menu-item-url'] : '' ),
  758. 'menu-item-description' => ( isset( $_item_object_data['menu-item-description'] ) ? $_item_object_data['menu-item-description'] : '' ),
  759. 'menu-item-attr-title' => ( isset( $_item_object_data['menu-item-attr-title'] ) ? $_item_object_data['menu-item-attr-title'] : '' ),
  760. 'menu-item-target' => ( isset( $_item_object_data['menu-item-target'] ) ? $_item_object_data['menu-item-target'] : '' ),
  761. 'menu-item-classes' => ( isset( $_item_object_data['menu-item-classes'] ) ? $_item_object_data['menu-item-classes'] : '' ),
  762. 'menu-item-xfn' => ( isset( $_item_object_data['menu-item-xfn'] ) ? $_item_object_data['menu-item-xfn'] : '' ),
  763. );
  764. $items_saved[] = wp_update_nav_menu_item( $menu_id, $_actual_db_id, $args );
  765. }
  766. }
  767. return $items_saved;
  768. }
  769. /**
  770. * Adds custom arguments to some of the meta box object types.
  771. *
  772. * @since 3.0.0
  773. *
  774. * @access private
  775. *
  776. * @param object $object The post type or taxonomy meta-object.
  777. * @return object The post type of taxonomy object.
  778. */
  779. function _wp_nav_menu_meta_box_object( $object = null ) {
  780. if ( isset( $object->name ) ) {
  781. if ( 'page' == $object->name ) {
  782. $object->_default_query = array(
  783. 'orderby' => 'menu_order title',
  784. 'post_status' => 'publish',
  785. );
  786. // Posts should show only published items.
  787. } elseif ( 'post' == $object->name ) {
  788. $object->_default_query = array(
  789. 'post_status' => 'publish',
  790. );
  791. // Categories should be in reverse chronological order.
  792. } elseif ( 'category' == $object->name ) {
  793. $object->_default_query = array(
  794. 'orderby' => 'id',
  795. 'order' => 'DESC',
  796. );
  797. // Custom post types should show only published items.
  798. } else {
  799. $object->_default_query = array(
  800. 'post_status' => 'publish',
  801. );
  802. }
  803. }
  804. return $object;
  805. }
  806. /**
  807. * Returns the menu formatted to edit.
  808. *
  809. * @since 3.0.0
  810. *
  811. * @param int $menu_id Optional. The ID of the menu to format. Default 0.
  812. * @return string|WP_Error $output The menu formatted to edit or error object on failure.
  813. */
  814. function wp_get_nav_menu_to_edit( $menu_id = 0 ) {
  815. $menu = wp_get_nav_menu_object( $menu_id );
  816. // If the menu exists, get its items.
  817. if ( is_nav_menu( $menu ) ) {
  818. $menu_items = wp_get_nav_menu_items( $menu->term_id, array('post_status' => 'any') );
  819. $result = '<div id="menu-instructions" class="post-body-plain';
  820. $result .= ( ! empty($menu_items) ) ? ' menu-instructions-inactive">' : '">';
  821. $result .= '<p>' . __( 'Add menu items from the column on the left.' ) . '</p>';
  822. $result .= '</div>';
  823. if ( empty($menu_items) )
  824. return $result . ' <ul class="menu" id="menu-to-edit"> </ul>';
  825. /**
  826. * Filters the Walker class used when adding nav menu items.
  827. *
  828. * @since 3.0.0
  829. *
  830. * @param string $class The walker class to use. Default 'Walker_Nav_Menu_Edit'.
  831. * @param int $menu_id ID of the menu being rendered.
  832. */
  833. $walker_class_name = apply_filters( 'wp_edit_nav_menu_walker', 'Walker_Nav_Menu_Edit', $menu_id );
  834. if ( class_exists( $walker_class_name ) ) {
  835. $walker = new $walker_class_name;
  836. } else {
  837. return new WP_Error( 'menu_walker_not_exist',
  838. /* translators: %s: walker class name */
  839. sprintf( __( 'The Walker class named %s does not exist.' ),
  840. '<strong>' . $walker_class_name . '</strong>'
  841. )
  842. );
  843. }
  844. $some_pending_menu_items = $some_invalid_menu_items = false;
  845. foreach ( (array) $menu_items as $menu_item ) {
  846. if ( isset( $menu_item->post_status ) && 'draft' == $menu_item->post_status )
  847. $some_pending_menu_items = true;
  848. if ( ! empty( $menu_item->_invalid ) )
  849. $some_invalid_menu_items = true;
  850. }
  851. if ( $some_pending_menu_items ) {
  852. $result .= '<div class="notice notice-info notice-alt inline"><p>' . __( 'Click Save Menu to make pending menu items public.' ) . '</p></div>';
  853. }
  854. if ( $some_invalid_menu_items ) {
  855. $result .= '<div class="notice notice-error notice-alt inline"><p>' . __( 'There are some invalid menu items. Please check or delete them.' ) . '</p></div>';
  856. }
  857. $result .= '<ul class="menu" id="menu-to-edit"> ';
  858. $result .= walk_nav_menu_tree( array_map('wp_setup_nav_menu_item', $menu_items), 0, (object) array('walker' => $walker ) );
  859. $result .= ' </ul> ';
  860. return $result;
  861. } elseif ( is_wp_error( $menu ) ) {
  862. return $menu;
  863. }
  864. }
  865. /**
  866. * Returns the columns for the nav menus page.
  867. *
  868. * @since 3.0.0
  869. *
  870. * @return array Columns.
  871. */
  872. function wp_nav_menu_manage_columns() {
  873. return array(
  874. '_title' => __( 'Show advanced menu properties' ),
  875. 'cb' => '<input type="checkbox" />',
  876. 'link-target' => __( 'Link Target' ),
  877. 'title-attribute' => __( 'Title Attribute' ),
  878. 'css-classes' => __( 'CSS Classes' ),
  879. 'xfn' => __( 'Link Relationship (XFN)' ),
  880. 'description' => __( 'Description' ),
  881. );
  882. }
  883. /**
  884. * Deletes orphaned draft menu items
  885. *
  886. * @access private
  887. * @since 3.0.0
  888. *
  889. * @global wpdb $wpdb WordPress database abstraction object.
  890. */
  891. function _wp_delete_orphaned_draft_menu_items() {
  892. global $wpdb;
  893. $delete_timestamp = time() - ( DAY_IN_SECONDS * EMPTY_TRASH_DAYS );
  894. // Delete orphaned draft menu items.
  895. $menu_items_to_delete = $wpdb->get_col($wpdb->prepare("SELECT ID FROM $wpdb->posts AS p LEFT JOIN $wpdb->postmeta AS m ON p.ID = m.post_id WHERE post_type = 'nav_menu_item' AND post_status = 'draft' AND meta_key = '_menu_item_orphaned' AND meta_value < %d", $delete_timestamp ) );
  896. foreach ( (array) $menu_items_to_delete as $menu_item_id )
  897. wp_delete_post( $menu_item_id, true );
  898. }
  899. /**
  900. * Saves nav menu items
  901. *
  902. * @since 3.6.0
  903. *
  904. * @param int|string $nav_menu_selected_id (id, slug, or name ) of the currently-selected menu
  905. * @param string $nav_menu_selected_title Title of the currently-selected menu
  906. * @return array $messages The menu updated message
  907. */
  908. function wp_nav_menu_update_menu_items ( $nav_menu_selected_id, $nav_menu_selected_title ) {
  909. $unsorted_menu_items = wp_get_nav_menu_items( $nav_menu_selected_id, array( 'orderby' => 'ID', 'output' => ARRAY_A, 'output_key' => 'ID', 'post_status' => 'draft,publish' ) );
  910. $messages = array();
  911. $menu_items = array();
  912. // Index menu items by db ID
  913. foreach ( $unsorted_menu_items as $_item )
  914. $menu_items[$_item->db_id] = $_item;
  915. $post_fields = array(
  916. 'menu-item-db-id', 'menu-item-object-id', 'menu-item-object',
  917. 'menu-item-parent-id', 'menu-item-position', 'menu-item-type',
  918. 'menu-item-title', 'menu-item-url', 'menu-item-description',
  919. 'menu-item-attr-title', 'menu-item-target', 'menu-item-classes', 'menu-item-xfn'
  920. );
  921. wp_defer_term_counting( true );
  922. // Loop through all the menu items' POST variables
  923. if ( ! empty( $_POST['menu-item-db-id'] ) ) {
  924. foreach ( (array) $_POST['menu-item-db-id'] as $_key => $k ) {
  925. // Menu item title can't be blank
  926. if ( ! isset( $_POST['menu-item-title'][ $_key ] ) || '' == $_POST['menu-item-title'][ $_key ] )
  927. continue;
  928. $args = array();
  929. foreach ( $post_fields as $field )
  930. $args[$field] = isset( $_POST[$field][$_key] ) ? $_POST[$field][$_key] : '';
  931. $menu_item_db_id = wp_update_nav_menu_item( $nav_menu_selected_id, ( $_POST['menu-item-db-id'][$_key] != $_key ? 0 : $_key ), $args );
  932. if ( is_wp_error( $menu_item_db_id ) ) {
  933. $messages[] = '<div id="message" class="error"><p>' . $menu_item_db_id->get_error_message() . '</p></div>';
  934. } else {
  935. unset( $menu_items[ $menu_item_db_id ] );
  936. }
  937. }
  938. }
  939. // Remove menu items from the menu that weren't in $_POST
  940. if ( ! empty( $menu_items ) ) {
  941. foreach ( array_keys( $menu_items ) as $menu_item_id ) {
  942. if ( is_nav_menu_item( $menu_item_id ) ) {
  943. wp_delete_post( $menu_item_id );
  944. }
  945. }
  946. }
  947. // Store 'auto-add' pages.
  948. $auto_add = ! empty( $_POST['auto-add-pages'] );
  949. $nav_menu_option = (array) get_option( 'nav_menu_options' );
  950. if ( ! isset( $nav_menu_option['auto_add'] ) )
  951. $nav_menu_option['auto_add'] = array();
  952. if ( $auto_add ) {
  953. if ( ! in_array( $nav_menu_selected_id, $nav_menu_option['auto_add'] ) )
  954. $nav_menu_option['auto_add'][] = $nav_menu_selected_id;
  955. } else {
  956. if ( false !== ( $key = array_search( $nav_menu_selected_id, $nav_menu_option['auto_add'] ) ) )
  957. unset( $nav_menu_option['auto_add'][$key] );
  958. }
  959. // Remove nonexistent/deleted menus
  960. $nav_menu_option['auto_add'] = array_intersect( $nav_menu_option['auto_add'], wp_get_nav_menus( array( 'fields' => 'ids' ) ) );
  961. update_option( 'nav_menu_options', $nav_menu_option );
  962. wp_defer_term_counting( false );
  963. /** This action is documented in wp-includes/nav-menu.php */
  964. do_action( 'wp_update_nav_menu', $nav_menu_selected_id );
  965. $messages[] = '<div id="message" class="updated notice is-dismissible"><p>' .
  966. /* translators: %s: nav menu title */
  967. sprintf( __( '%s has been updated.' ),
  968. '<strong>' . $nav_menu_selected_title . '</strong>'
  969. ) . '</p></div>';
  970. unset( $menu_items, $unsorted_menu_items );
  971. return $messages;
  972. }
  973. /**
  974. * If a JSON blob of navigation menu data is in POST data, expand it and inject
  975. * it into `$_POST` to avoid PHP `max_input_vars` limitations. See #14134.
  976. *
  977. * @ignore
  978. * @since 4.5.3
  979. * @access private
  980. */
  981. function _wp_expand_nav_menu_post_data() {
  982. if ( ! isset( $_POST['nav-menu-data'] ) ) {
  983. return;
  984. }
  985. $data = json_decode( stripslashes( $_POST['nav-menu-data'] ) );
  986. if ( ! is_null( $data ) && $data ) {
  987. foreach ( $data as $post_input_data ) {
  988. // For input names that are arrays (e.g. `menu-item-db-id[3][4][5]`),
  989. // derive the array path keys via regex and set the value in $_POST.
  990. preg_match( '#([^\[]*)(\[(.+)\])?#', $post_input_data->name, $matches );
  991. $array_bits = array( $matches[1] );
  992. if ( isset( $matches[3] ) ) {
  993. $array_bits = array_merge( $array_bits, explode( '][', $matches[3] ) );
  994. }
  995. $new_post_data = array();
  996. // Build the new array value from leaf to trunk.
  997. for ( $i = count( $array_bits ) - 1; $i >= 0; $i -- ) {
  998. if ( $i == count( $array_bits ) - 1 ) {
  999. $new_post_data[ $array_bits[ $i ] ] = wp_slash( $post_input_data->value );
  1000. } else {
  1001. $new_post_data = array( $array_bits[ $i ] => $new_post_data );
  1002. }
  1003. }
  1004. $_POST = array_replace_recursive( $_POST, $new_post_data );
  1005. }
  1006. }
  1007. }