WordPress.org

Make WordPress Core

Ticket #32576: 32576.diff

File 32576.diff, 81.4 KB (added by obenland, 4 years ago)

Merging class-wp-*

  • src/wp-includes/class-wp-customize-control.php

     
    14021402                return $this->manager->widgets->is_widget_rendered( $this->widget_id );
    14031403        }
    14041404}
     1405
     1406/**
     1407 * Customize Menu Panel Class
     1408 *
     1409 * Needed to add screen options.
     1410 *
     1411 * @since 4.3.0
     1412 */
     1413class WP_Customize_Menus_Panel extends WP_Customize_Panel {
     1414
     1415        /**
     1416         * Control type.
     1417         *
     1418         * @access public
     1419         * @var string
     1420         */
     1421        public $type = 'menus';
     1422
     1423        /**
     1424         * Render screen options for Menus.
     1425         */
     1426        public function render_screen_options() {
     1427                // Essentially adds the screen options.
     1428                add_filter( 'manage_nav-menus_columns', array( $this, 'wp_nav_menu_manage_columns' ) );
     1429
     1430                // Display screen options.
     1431                $screen = WP_Screen::get( 'nav-menus.php' );
     1432                $screen->render_screen_options();
     1433        }
     1434
     1435        /**
     1436         * Returns the advanced options for the nav menus page.
     1437         *
     1438         * Link title attribute added as it's a relatively advanced concept for new users.
     1439         *
     1440         * @since 4.3.0
     1441         *
     1442         * @return array The advanced menu properties.
     1443         */
     1444        function wp_nav_menu_manage_columns() {
     1445                return array(
     1446                        '_title'      => __( 'Show advanced menu properties' ),
     1447                        'cb'          => '<input type="checkbox" />',
     1448                        'link-target' => __( 'Link Target' ),
     1449                        'attr-title'  => __( 'Title Attribute' ),
     1450                        'css-classes' => __( 'CSS Classes' ),
     1451                        'xfn'         => __( 'Link Relationship (XFN)' ),
     1452                        'description' => __( 'Description' ),
     1453                );
     1454        }
     1455
     1456        /**
     1457         * An Underscore (JS) template for this panel's content (but not its container).
     1458         *
     1459         * Class variables for this panel class are available in the `data` JS object;
     1460         * export custom variables by overriding {@see WP_Customize_Panel::json()}.
     1461         *
     1462         * @see WP_Customize_Panel::print_template()
     1463         *
     1464         * @since 4.3.0
     1465         */
     1466        protected function content_template() {
     1467                ?>
     1468                <li class="panel-meta customize-info accordion-section <# if ( ! data.description ) { #> cannot-expand<# } #>">
     1469                        <button type="button" class="customize-panel-back" tabindex="-1">
     1470                                <span class="screen-reader-text"><?php _e( 'Back' ); ?></span>
     1471                        </button>
     1472                        <div class="accordion-section-title">
     1473                                <span class="preview-notice">
     1474                                        <?php
     1475                                                /* translators: %s is the site/panel title in the Customizer */
     1476                                                printf( __( 'You are customizing %s' ), '<strong class="panel-title">{{ data.title }}</strong>' );
     1477                                        ?>
     1478                                </span>
     1479                                <button type="button" class="customize-screen-options-toggle" aria-expanded="false">
     1480                                        <span class="screen-reader-text"><?php _e( 'Menu Options' ); ?></span>
     1481                                </button>
     1482                                <button type="button" class="customize-help-toggle dashicons dashicons-editor-help" aria-expanded="false">
     1483                                        <span class="screen-reader-text"><?php _e( 'Help' ); ?></span>
     1484                                </button>
     1485                        </div>
     1486                        <# if ( data.description ) { #>
     1487                        <div class="description customize-panel-description">{{{ data.description }}}</div>
     1488                        <# } #>
     1489                        <?php $this->render_screen_options(); ?>
     1490                </li>
     1491                <?php
     1492        }
     1493}
     1494
     1495/**
     1496 * Customize Nav Menu Control Class
     1497 *
     1498 * @since 4.3.0
     1499 */
     1500class WP_Customize_Nav_Menu_Control extends WP_Customize_Control {
     1501
     1502        /**
     1503         * Control type
     1504         *
     1505         * @access public
     1506         * @var string
     1507         */
     1508        public $type = 'nav_menu';
     1509
     1510        /**
     1511         * The nav menu setting
     1512         *
     1513         * @var WP_Customize_Nav_Menu_Setting
     1514         */
     1515        public $setting;
     1516
     1517        /**
     1518         * Don't render the control's content - it uses a JS template instead.
     1519         */
     1520        public function render_content() {}
     1521
     1522        /**
     1523         * JS/Underscore template for the control UI.
     1524         */
     1525        public function content_template() {
     1526                ?>
     1527                <button type="button" class="button-secondary add-new-menu-item">
     1528                        <?php _e( 'Add Items' ); ?>
     1529                </button>
     1530                <button type="button" class="not-a-button reorder-toggle">
     1531                        <span class="reorder"><?php _ex( 'Reorder', 'Reorder menu items in Customizer' ); ?></span>
     1532                        <span class="reorder-done"><?php _ex( 'Done', 'Cancel reordering menu items in Customizer' ); ?></span>
     1533                </button>
     1534                <span class="add-menu-item-loading spinner"></span>
     1535                <span class="menu-delete-item">
     1536                        <button type="button" class="not-a-button menu-delete">
     1537                                <?php _e( 'Delete menu' ); ?> <span class="screen-reader-text">{{ data.menu_name }}</span>
     1538                        </button>
     1539                </span>
     1540                <?php if ( current_theme_supports( 'menus' ) ) : ?>
     1541                <ul class="menu-settings">
     1542                        <li class="customize-control">
     1543                                <span class="customize-control-title"><?php _e( 'Menu locations' ); ?></span>
     1544                        </li>
     1545
     1546                        <?php foreach ( get_registered_nav_menus() as $location => $description ) : ?>
     1547                        <li class="customize-control customize-control-checkbox assigned-menu-location">
     1548                                <label>
     1549                                        <input type="checkbox" data-menu-id="{{ data.menu_id }}" data-location-id="<?php echo esc_attr( $location ); ?>" class="menu-location" /> <?php echo $description; ?>
     1550                                        <span class="theme-location-set"><?php printf( _x( '(Current: %s)', 'Current menu location' ), '<span class="current-menu-location-name-' . esc_attr( $location ) . '"></span>' ); ?></span>
     1551                                </label>
     1552                        </li>
     1553                        <?php endforeach; ?>
     1554
     1555                </ul>
     1556                <?php endif; ?>
     1557                <p>
     1558                        <label>
     1559                                <input type="checkbox" class="auto_add">
     1560                                <?php _e( 'Automatically add new top-level pages to this menu.' ) ?>
     1561                        </label>
     1562                </p>
     1563                <?php
     1564        }
     1565
     1566        /**
     1567         * Return params for this control.
     1568         *
     1569         * @return array
     1570         */
     1571        function json() {
     1572                $exported = parent::json();
     1573                $exported['menu_id'] = $this->setting->term_id;
     1574                return $exported;
     1575        }
     1576}
     1577
     1578/**
     1579 * Customize control to represent the name field for a given menu.
     1580 *
     1581 * @since 4.3.0
     1582 */
     1583class WP_Customize_Nav_Menu_Item_Control extends WP_Customize_Control {
     1584
     1585        /**
     1586         * Control type
     1587         *
     1588         * @access public
     1589         * @var string
     1590         */
     1591        public $type = 'nav_menu_item';
     1592
     1593        /**
     1594         * The nav menu item setting
     1595         *
     1596         * @var WP_Customize_Nav_Menu_Item_Setting
     1597         */
     1598        public $setting;
     1599
     1600        /**
     1601         * Constructor.
     1602         *
     1603         * @uses WP_Customize_Control::__construct()
     1604         *
     1605         * @param WP_Customize_Manager $manager An instance of the WP_Customize_Manager class.
     1606         * @param string               $id      The control ID.
     1607         * @param array                $args    Optional. Overrides class property defaults.
     1608         */
     1609        public function __construct( $manager, $id, $args = array() ) {
     1610                parent::__construct( $manager, $id, $args );
     1611        }
     1612
     1613        /**
     1614         * Don't render the control's content - it's rendered with a JS template.
     1615         */
     1616        public function render_content() {}
     1617
     1618        /**
     1619         * JS/Underscore template for the control UI.
     1620         */
     1621        public function content_template() {
     1622                ?>
     1623                <dl class="menu-item-bar">
     1624                        <dt class="menu-item-handle">
     1625                                <span class="item-type">{{ data.item_type_label }}</span>
     1626                                <span class="item-title">
     1627                                        <span class="spinner"></span>
     1628                                        <span class="menu-item-title">{{ data.title }}</span>
     1629                                </span>
     1630                                <span class="item-controls">
     1631                                        <button type="button" class="not-a-button item-edit"><span class="screen-reader-text"><?php _e( 'Edit Menu Item' ); ?></span></button>
     1632                                        <button type="button" class="not-a-button item-delete submitdelete deletion"><span class="screen-reader-text"><?php _e( 'Remove Menu Item' ); ?></span></button>
     1633                                </span>
     1634                        </dt>
     1635                </dl>
     1636
     1637                <div class="menu-item-settings" id="menu-item-settings-{{ data.menu_item_id }}">
     1638                        <# if ( 'custom' === data.item_type ) { #>
     1639                        <p class="field-url description description-thin">
     1640                                <label for="edit-menu-item-url-{{ data.menu_item_id }}">
     1641                                        <?php _e( 'URL' ); ?><br />
     1642                                        <input class="widefat code edit-menu-item-url" type="text" id="edit-menu-item-url-{{ data.menu_item_id }}" name="menu-item-url" />
     1643                                </label>
     1644                        </p>
     1645                <# } #>
     1646                        <p class="description description-thin">
     1647                                <label for="edit-menu-item-title-{{ data.menu_item_id }}">
     1648                                        <?php _e( 'Navigation Label' ); ?><br />
     1649                                        <input type="text" id="edit-menu-item-title-{{ data.menu_item_id }}" class="widefat edit-menu-item-title" name="menu-item-title" />
     1650                                </label>
     1651                        </p>
     1652                        <p class="field-link-target description description-thin">
     1653                                <label for="edit-menu-item-target-{{ data.menu_item_id }}">
     1654                                        <input type="checkbox" id="edit-menu-item-target-{{ data.menu_item_id }}" class="edit-menu-item-target" value="_blank" name="menu-item-target" />
     1655                                        <?php _e( 'Open link in a new tab' ); ?>
     1656                                </label>
     1657                        </p>
     1658                        <p class="field-attr-title description description-thin">
     1659                                <label for="edit-menu-item-attr-title-{{ data.menu_item_id }}">
     1660                                        <?php _e( 'Title Attribute' ); ?><br />
     1661                                        <input type="text" id="edit-menu-item-attr-title-{{ data.menu_item_id }}" class="widefat edit-menu-item-attr-title" name="menu-item-attr-title" />
     1662                                </label>
     1663                        </p>
     1664                        <p class="field-css-classes description description-thin">
     1665                                <label for="edit-menu-item-classes-{{ data.menu_item_id }}">
     1666                                        <?php _e( 'CSS Classes' ); ?><br />
     1667                                        <input type="text" id="edit-menu-item-classes-{{ data.menu_item_id }}" class="widefat code edit-menu-item-classes" name="menu-item-classes" />
     1668                                </label>
     1669                        </p>
     1670                        <p class="field-xfn description description-thin">
     1671                                <label for="edit-menu-item-xfn-{{ data.menu_item_id }}">
     1672                                        <?php _e( 'Link Relationship (XFN)' ); ?><br />
     1673                                        <input type="text" id="edit-menu-item-xfn-{{ data.menu_item_id }}" class="widefat code edit-menu-item-xfn" name="menu-item-xfn" />
     1674                                </label>
     1675                        </p>
     1676                        <p class="field-description description description-thin">
     1677                                <label for="edit-menu-item-description-{{ data.menu_item_id }}">
     1678                                        <?php _e( 'Description' ); ?><br />
     1679                                        <textarea id="edit-menu-item-description-{{ data.menu_item_id }}" class="widefat edit-menu-item-description" rows="3" cols="20" name="menu-item-description">{{ data.description }}</textarea>
     1680                                        <span class="description"><?php _e( 'The description will be displayed in the menu if the current theme supports it.' ); ?></span>
     1681                                </label>
     1682                        </p>
     1683
     1684                        <div class="menu-item-actions description-thin submitbox">
     1685                                <# if ( 'custom' != data.item_type && '' != data.original_title ) { #>
     1686                                <p class="link-to-original">
     1687                                        <?php printf( __( 'Original: %s' ), '<a class="original-link" href="{{ data.url }}">{{{ data.original_title }}}</a>' ); ?>
     1688                                </p>
     1689                                <# } #>
     1690
     1691                                <button type="button" class="not-a-button item-delete submitdelete deletion"><?php _e( 'Remove' ); ?></button>
     1692                                <span class="spinner"></span>
     1693                        </div>
     1694                        <input type="hidden" name="menu-item-db-id[{{ data.menu_item_id }}]" class="menu-item-data-db-id" value="{{ data.menu_item_id }}" />
     1695                        <input type="hidden" name="menu-item-parent-id[{{ data.menu_item_id }}]" class="menu-item-data-parent-id" value="{{ data.parent }}" />
     1696                </div><!-- .menu-item-settings-->
     1697                <ul class="menu-item-transport"></ul>
     1698                <?php
     1699        }
     1700
     1701        /**
     1702         * Return params for this control.
     1703         *
     1704         * @return array
     1705         */
     1706        function json() {
     1707                $exported = parent::json();
     1708                $exported['menu_item_id'] = $this->setting->post_id;
     1709                return $exported;
     1710        }
     1711}
     1712
     1713/**
     1714 * Customize Menu Location Control Class
     1715 *
     1716 * This custom control is only needed for JS.
     1717 *
     1718 * @since 4.3.0
     1719 */
     1720class WP_Customize_Nav_Menu_Location_Control extends WP_Customize_Control {
     1721
     1722        /**
     1723         * Control type
     1724         *
     1725         * @access public
     1726         * @var string
     1727         */
     1728        public $type = 'nav_menu_location';
     1729
     1730        /**
     1731         * Location ID
     1732         *
     1733         * @access public
     1734         * @var string
     1735         */
     1736        public $location_id = '';
     1737
     1738        /**
     1739         * Refresh the parameters passed to JavaScript via JSON.
     1740         *
     1741         * @uses WP_Customize_Control::to_json()
     1742         */
     1743        public function to_json() {
     1744                parent::to_json();
     1745                $this->json['locationId'] = $this->location_id;
     1746        }
     1747
     1748        /**
     1749         * Render content just like a normal select control.
     1750         */
     1751        public function render_content() {
     1752                if ( empty( $this->choices ) ) {
     1753                        return;
     1754                }
     1755                ?>
     1756                <label>
     1757                        <?php if ( ! empty( $this->label ) ) : ?>
     1758                        <span class="customize-control-title"><?php echo esc_html( $this->label ); ?></span>
     1759                        <?php endif; ?>
     1760
     1761                        <?php if ( ! empty( $this->description ) ) : ?>
     1762                        <span class="description customize-control-description"><?php echo $this->description; ?></span>
     1763                        <?php endif; ?>
     1764
     1765                        <select <?php $this->link(); ?>>
     1766                                <?php
     1767                                foreach ( $this->choices as $value => $label ) :
     1768                                        echo '<option value="' . esc_attr( $value ) . '"' . selected( $this->value(), $value, false ) . '>' . $label . '</option>';
     1769                                endforeach;
     1770                                ?>
     1771                        </select>
     1772                </label>
     1773                <?php
     1774        }
     1775}
     1776
     1777/**
     1778 * Customize control to represent the name field for a given menu.
     1779 *
     1780 * @since 4.3.0
     1781 */
     1782class WP_Customize_Nav_Menu_Name_Control extends WP_Customize_Control {
     1783
     1784        /**
     1785         * Type of control, used by JS.
     1786         *
     1787         * @var string
     1788         */
     1789        public $type = 'nav_menu_name';
     1790
     1791        /**
     1792         * No-op since we're using JS template.
     1793         */
     1794        protected function render_content() {}
     1795
     1796        /**
     1797         * Render the Underscore template for this control.
     1798         */
     1799        protected function content_template() {
     1800                ?>
     1801                <label>
     1802                        <input type="text" class="menu-name-field live-update-section-title" />
     1803                </label>
     1804                <?php
     1805        }
     1806}
     1807
     1808/**
     1809 * Customize control class for new menus.
     1810 *
     1811 * @since 4.3.0
     1812 */
     1813class WP_New_Menu_Customize_Control extends WP_Customize_Control {
     1814
     1815        /**
     1816         * Control type.
     1817         *
     1818         * @access public
     1819         * @var string
     1820         */
     1821        public $type = 'new_menu';
     1822
     1823        /**
     1824         * Render the control's content.
     1825         */
     1826        public function render_content() {
     1827                ?>
     1828                <button type="button" class="button button-primary" id="create-new-menu-submit"><?php _e( 'Create Menu' ); ?></button>
     1829                <span class="spinner"></span>
     1830                <?php
     1831        }
     1832}
  • src/wp-includes/class-wp-customize-menus.php

     
     1<?php
     2/**
     3 * Base Customize Menus
     4 *
     5 * @package WordPress
     6 * @subpackage Customize
     7 */
     8
     9/**
     10 * Base Customize Menus class which implements menu management in the Customizer.
     11 *
     12 * @since 4.3.0
     13 */
     14class WP_Customize_Menus {
     15
     16        /**
     17         * WP_Customize_Manager instance.
     18         *
     19         * @access public
     20         * @var WP_Customize_Manager
     21         */
     22        public $manager;
     23
     24        /**
     25         * Previewed Menus.
     26         *
     27         * @access public
     28         * @var array
     29         */
     30        public $previewed_menus;
     31
     32        /**
     33         * Constructor
     34         *
     35         * @access public
     36         * @param object $manager An instance of the WP_Customize_Manager class.
     37         */
     38        public function __construct( $manager ) {
     39                $this->previewed_menus = array();
     40                $this->manager         = $manager;
     41
     42                add_action( 'wp_ajax_load-available-menu-items-customizer', array( $this, 'ajax_load_available_items' ) );
     43                add_action( 'wp_ajax_search-available-menu-items-customizer', array( $this, 'ajax_search_available_items' ) );
     44                add_action( 'customize_controls_enqueue_scripts', array( $this, 'enqueue_scripts' ) );
     45                add_action( 'customize_register', array( $this, 'customize_register' ), 11 ); // Needs to run after core Navigation section is set up.
     46                add_filter( 'customize_dynamic_setting_args', array( $this, 'filter_dynamic_setting_args' ), 10, 2 );
     47                add_filter( 'customize_dynamic_setting_class', array( $this, 'filter_dynamic_setting_class' ), 10, 3 );
     48                add_action( 'customize_controls_print_footer_scripts', array( $this, 'print_templates' ) );
     49                add_action( 'customize_controls_print_footer_scripts', array( $this, 'available_items_template' ) );
     50                add_action( 'customize_preview_init', array( $this, 'customize_preview_init' ) );
     51        }
     52
     53        /**
     54         * Ajax handler for loading available menu items.
     55         *
     56         * @access public
     57         */
     58        public function ajax_load_available_items() {
     59                check_ajax_referer( 'customize-menus', 'customize-menus-nonce' );
     60
     61                if ( ! current_user_can( 'edit_theme_options' ) ) {
     62                        wp_send_json_error( array( 'message' => __( 'Error: invalid user capabilities.' ) ) );
     63                }
     64                if ( empty( $_POST['obj_type'] ) || empty( $_POST['type'] ) ) {
     65                        wp_send_json_error( array( 'message' => __( 'Missing obj_type or type param.' ) ) );
     66                }
     67
     68                $obj_type = sanitize_key( $_POST['obj_type'] );
     69                if ( ! in_array( $obj_type, array( 'post_type', 'taxonomy' ) ) ) {
     70                        wp_send_json_error( array( 'message' => __( 'Invalid obj_type param: ' . $obj_type ) ) );
     71                }
     72                $taxonomy_or_post_type = sanitize_key( $_POST['type'] );
     73                $page = isset( $_POST['page'] ) ? absint( $_POST['page'] ) : 0;
     74                $items = array();
     75
     76                if ( 'post_type' === $obj_type ) {
     77                        if ( ! get_post_type_object( $taxonomy_or_post_type ) ) {
     78                                wp_send_json_error( array( 'message' => __( 'Unknown post type.' ) ) );
     79                        }
     80
     81                        if ( 0 === $page && 'page' === $taxonomy_or_post_type ) {
     82                                // Add "Home" link. Treat as a page, but switch to custom on add.
     83                                $items[] = array(
     84                                        'id'         => 'home',
     85                                        'title'      => _x( 'Home', 'nav menu home label' ),
     86                                        'type'       => 'custom',
     87                                        'type_label' => __( 'Custom Link' ),
     88                                        'object'     => '',
     89                                        'url'        => home_url(),
     90                                );
     91                        }
     92
     93                        $posts = get_posts( array(
     94                                'numberposts' => 10,
     95                                'offset'      => 10 * $page,
     96                                'orderby'     => 'date',
     97                                'order'       => 'DESC',
     98                                'post_type'   => $taxonomy_or_post_type,
     99                        ) );
     100                        foreach ( $posts as $post ) {
     101                                $items[] = array(
     102                                        'id'         => "post-{$post->ID}",
     103                                        'title'      => html_entity_decode( get_the_title( $post ), ENT_QUOTES, get_bloginfo( 'charset' ) ),
     104                                        'type'       => 'post_type',
     105                                        'type_label' => get_post_type_object( $post->post_type )->labels->singular_name,
     106                                        'object'     => $post->post_type,
     107                                        'object_id'  => (int) $post->ID,
     108                                );
     109                        }
     110                } else if ( 'taxonomy' === $obj_type ) {
     111                        $terms = get_terms( $taxonomy_or_post_type, array(
     112                                'child_of'     => 0,
     113                                'exclude'      => '',
     114                                'hide_empty'   => false,
     115                                'hierarchical' => 1,
     116                                'include'      => '',
     117                                'number'       => 10,
     118                                'offset'       => 10 * $page,
     119                                'order'        => 'DESC',
     120                                'orderby'      => 'count',
     121                                'pad_counts'   => false,
     122                        ) );
     123                        if ( is_wp_error( $terms ) ) {
     124                                wp_send_json_error( array( 'message' => wp_strip_all_tags( $terms->get_error_message(), true ) ) );
     125                        }
     126
     127                        foreach ( $terms as $term ) {
     128                                $items[] = array(
     129                                        'id'         => "term-{$term->term_id}",
     130                                        'title'      => html_entity_decode( $term->name, ENT_QUOTES, get_bloginfo( 'charset' ) ),
     131                                        'type'       => 'taxonomy',
     132                                        'type_label' => get_taxonomy( $term->taxonomy )->labels->singular_name,
     133                                        'object'     => $term->taxonomy,
     134                                        'object_id'  => $term->term_id,
     135                                );
     136                        }
     137                }
     138
     139                wp_send_json_success( array( 'items' => $items ) );
     140        }
     141
     142        /**
     143         * Ajax handler for searching available menu items.
     144         */
     145        public function ajax_search_available_items() {
     146                check_ajax_referer( 'customize-menus', 'customize-menus-nonce' );
     147
     148                if ( ! current_user_can( 'edit_theme_options' ) ) {
     149                        wp_send_json_error( array( 'message' => __( 'Error: invalid user capabilities.' ) ) );
     150                }
     151                if ( empty( $_POST['search'] ) ) {
     152                        wp_send_json_error( array( 'message' => __( 'Error: missing search parameter.' ) ) );
     153                }
     154
     155                $p = isset( $_POST['page'] ) ? absint( $_POST['page'] ) : 0;
     156                if ( $p < 1 ) {
     157                        $p = 1;
     158                }
     159
     160                $s = sanitize_text_field( wp_unslash( $_POST['search'] ) );
     161                $results = $this->search_available_items_query( array( 'pagenum' => $p, 's' => $s ) );
     162
     163                if ( empty( $results ) ) {
     164                        wp_send_json_error( array( 'message' => __( 'No results found.' ) ) );
     165                } else {
     166                        wp_send_json_success( array( 'items' => $results ) );
     167                }
     168        }
     169
     170        /**
     171         * Performs post queries for available-item searching.
     172         *
     173         * Based on WP_Editor::wp_link_query().
     174         *
     175         * @param array $args Optional. Accepts 'pagenum' and 's' (search) arguments.
     176         * @return array Results.
     177         */
     178        public function search_available_items_query( $args = array() ) {
     179                $results = array();
     180
     181                $post_type_objects = get_post_types( array( 'show_in_nav_menus' => true ), 'objects' );
     182                $query = array(
     183                        'post_type'              => array_keys( $post_type_objects ),
     184                        'suppress_filters'       => true,
     185                        'update_post_term_cache' => false,
     186                        'update_post_meta_cache' => false,
     187                        'post_status'            => 'publish',
     188                        'posts_per_page'         => 20,
     189                );
     190
     191                $args['pagenum'] = isset( $args['pagenum'] ) ? absint( $args['pagenum'] ) : 1;
     192                $query['offset'] = $args['pagenum'] > 1 ? $query['posts_per_page'] * ( $args['pagenum'] - 1 ) : 0;
     193
     194                if ( isset( $args['s'] ) ) {
     195                        $query['s'] = $args['s'];
     196                }
     197
     198                // Query posts.
     199                $get_posts = new WP_Query( $query );
     200
     201                // Check if any posts were found.
     202                if ( $get_posts->post_count ) {
     203                        foreach ( $get_posts->posts as $post ) {
     204                                $results[] = array(
     205                                        'id'         => 'post-' . $post->ID,
     206                                        'type'       => 'post_type',
     207                                        'type_label' => $post_type_objects[ $post->post_type ]->labels->singular_name,
     208                                        'object'     => $post->post_type,
     209                                        'object_id'  => intval( $post->ID ),
     210                                        'title'      => html_entity_decode( get_the_title( $post ), ENT_QUOTES, get_bloginfo( 'charset' ) ),
     211                                );
     212                        }
     213                }
     214
     215                // Query taxonomy terms.
     216                $taxonomies = get_taxonomies( array( 'show_in_nav_menus' => true ), 'names' );
     217                $terms = get_terms( $taxonomies, array(
     218                        'name__like' => $args['s'],
     219                        'number'     => 20,
     220                        'offset'     => 20 * ($args['pagenum'] - 1),
     221                ) );
     222
     223                // Check if any taxonomies were found.
     224                if ( ! empty( $terms ) ) {
     225                        foreach ( $terms as $term ) {
     226                                $results[] = array(
     227                                        'id'         => 'term-' . $term->term_id,
     228                                        'type'       => 'taxonomy',
     229                                        'type_label' => get_taxonomy( $term->taxonomy )->labels->singular_name,
     230                                        'object'     => $term->taxonomy,
     231                                        'object_id'  => intval( $term->term_id ),
     232                                        'title'      => html_entity_decode( $term->name, ENT_QUOTES, get_bloginfo( 'charset' ) ),
     233                                );
     234                        }
     235                }
     236
     237                return $results;
     238        }
     239
     240        /**
     241         * Enqueue scripts and styles for Customizer pane.
     242         *
     243         * @since Menu Customizer 0.0
     244         */
     245        public function enqueue_scripts() {
     246                wp_enqueue_style( 'menu-customizer' );
     247                wp_enqueue_script( 'menu-customizer' );
     248
     249                $temp_nav_menu_setting      = new WP_Customize_Nav_Menu_Setting( $this->manager, 'nav_menu[-1]' );
     250                $temp_nav_menu_item_setting = new WP_Customize_Nav_Menu_Item_Setting( $this->manager, 'nav_menu_item[-1]' );
     251
     252                // Pass data to JS.
     253                $settings = array(
     254                        'nonce'                => wp_create_nonce( 'customize-menus' ),
     255                        'allMenus'             => wp_get_nav_menus(),
     256                        'itemTypes'            => $this->available_item_types(),
     257                        'l10n'                 => array(
     258                                'untitled'          => _x( '(no label)', 'Missing menu item navigation label.' ),
     259                                'custom_label'      => _x( 'Custom', 'Custom menu item type label.' ),
     260                                'menuLocation'      => _x( '(Currently set to: %s)', 'Current menu location.' ),
     261                                'deleteWarn'        => __( 'You are about to permanently delete this menu. "Cancel" to stop, "OK" to delete.' ),
     262                                'itemAdded'         => __( 'Menu item added' ),
     263                                'itemDeleted'       => __( 'Menu item deleted' ),
     264                                'menuAdded'         => __( 'Menu created' ),
     265                                'menuDeleted'       => __( 'Menu deleted' ),
     266                                'movedUp'           => __( 'Menu item moved up' ),
     267                                'movedDown'         => __( 'Menu item moved down' ),
     268                                'movedLeft'         => __( 'Menu item moved out of submenu' ),
     269                                'movedRight'        => __( 'Menu item is now a sub-item' ),
     270                                'customizingMenus'  => _x( 'Customizing &#9656; Menus', '&#9656 is the unicode right-pointing triangle' ),
     271                                'invalidTitleTpl'   => __( '%s (Invalid)' ),
     272                                'pendingTitleTpl'   => __( '%s (Pending)' ),
     273                                'taxonomyTermLabel' => __( 'Taxonomy' ),
     274                                'postTypeLabel'     => __( 'Post Type' ),
     275                        ),
     276                        'menuItemTransport'    => 'postMessage',
     277                        'phpIntMax'            => PHP_INT_MAX,
     278                        'defaultSettingValues' => array(
     279                                'nav_menu'      => $temp_nav_menu_setting->default,
     280                                'nav_menu_item' => $temp_nav_menu_item_setting->default,
     281                        ),
     282                );
     283
     284                $data = sprintf( 'var _wpCustomizeMenusSettings = %s;', json_encode( $settings ) );
     285                wp_scripts()->add_data( 'menu-customizer', 'data', $data );
     286
     287                // This is copied from nav-menus.php, and it has an unfortunate object name of `menus`.
     288                $nav_menus_l10n = array(
     289                        'oneThemeLocationNoMenus' => null,
     290                        'moveUp'       => __( 'Move up one' ),
     291                        'moveDown'     => __( 'Move down one' ),
     292                        'moveToTop'    => __( 'Move to the top' ),
     293                        /* translators: %s: previous item name */
     294                        'moveUnder'    => __( 'Move under %s' ),
     295                        /* translators: %s: previous item name */
     296                        'moveOutFrom'  => __( 'Move out from under %s' ),
     297                        /* translators: %s: previous item name */
     298                        'under'        => __( 'Under %s' ),
     299                        /* translators: %s: previous item name */
     300                        'outFrom'      => __( 'Out from under %s' ),
     301                        /* translators: 1: item name, 2: item position, 3: total number of items */
     302                        'menuFocus'    => __( '%1$s. Menu item %2$d of %3$d.' ),
     303                        /* translators: 1: item name, 2: item position, 3: parent item name */
     304                        'subMenuFocus' => __( '%1$s. Sub item number %2$d under %3$s.' ),
     305                );
     306                wp_localize_script( 'nav-menu', 'menus', $nav_menus_l10n );
     307        }
     308
     309        /**
     310         * Filter a dynamic setting's constructor args.
     311         *
     312         * For a dynamic setting to be registered, this filter must be employed
     313         * to override the default false value with an array of args to pass to
     314         * the WP_Customize_Setting constructor.
     315         *
     316         * @param false|array $setting_args The arguments to the WP_Customize_Setting constructor.
     317         * @param string      $setting_id   ID for dynamic setting, usually coming from `$_POST['customized']`.
     318         * @return array|false
     319         */
     320        public function filter_dynamic_setting_args( $setting_args, $setting_id ) {
     321                if ( preg_match( WP_Customize_Nav_Menu_Setting::ID_PATTERN, $setting_id ) ) {
     322                        $setting_args = array(
     323                                'type' => WP_Customize_Nav_Menu_Setting::TYPE,
     324                        );
     325                } else if ( preg_match( WP_Customize_Nav_Menu_Item_Setting::ID_PATTERN, $setting_id ) ) {
     326                        $setting_args = array(
     327                                'type' => WP_Customize_Nav_Menu_Item_Setting::TYPE,
     328                        );
     329                }
     330                return $setting_args;
     331        }
     332
     333        /**
     334         * Allow non-statically created settings to be constructed with custom WP_Customize_Setting subclass.
     335         *
     336         * @param string $setting_class WP_Customize_Setting or a subclass.
     337         * @param string $setting_id    ID for dynamic setting, usually coming from `$_POST['customized']`.
     338         * @param array  $setting_args  WP_Customize_Setting or a subclass.
     339         * @return string
     340         */
     341        public function filter_dynamic_setting_class( $setting_class, $setting_id, $setting_args ) {
     342                unset( $setting_id );
     343
     344                if ( ! empty( $setting_args['type'] ) && WP_Customize_Nav_Menu_Setting::TYPE === $setting_args['type'] ) {
     345                        $setting_class = 'WP_Customize_Nav_Menu_Setting';
     346                } else if ( ! empty( $setting_args['type'] ) && WP_Customize_Nav_Menu_Item_Setting::TYPE === $setting_args['type'] ) {
     347                        $setting_class = 'WP_Customize_Nav_Menu_Item_Setting';
     348                }
     349                return $setting_class;
     350        }
     351
     352        /**
     353         * Add the customizer settings and controls.
     354         *
     355         * @since Menu Customizer 0.0
     356         */
     357        public function customize_register() {
     358
     359                // Require JS-rendered control types.
     360                $this->manager->register_panel_type( 'WP_Customize_Menus_Panel' );
     361                $this->manager->register_control_type( 'WP_Customize_Nav_Menu_Control' );
     362                $this->manager->register_control_type( 'WP_Customize_Nav_Menu_Name_Control' );
     363                $this->manager->register_control_type( 'WP_Customize_Nav_Menu_Item_Control' );
     364
     365                // Create a panel for Menus.
     366                $this->manager->add_panel( new WP_Customize_Menus_Panel( $this->manager, 'menus', array(
     367                        'title'       => __( 'Menus' ),
     368                        '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><p>' . __( 'Menus can be displayed in locations defined by your theme or in widget areas by adding a "Custom Menu" widget.' ) . '</p>',
     369                        'priority'    => 30,
     370                        // 'theme_supports' => 'menus|widgets', @todo allow multiple theme supports
     371                ) ) );
     372                $menus = wp_get_nav_menus();
     373
     374                // Menu loactions.
     375                $this->manager->remove_section( 'nav' ); // Remove old core section. @todo core merge remove corresponding code from WP_Customize_Manager::register_controls().
     376                $locations     = get_registered_nav_menus();
     377                $num_locations = count( array_keys( $locations ) );
     378                $description   = '<p>' . sprintf( _n( 'Your theme contains %s menu location. Select which menu you would like to use.', 'Your theme contains %s menu locations. Select which menu appears in each location.', $num_locations ), number_format_i18n( $num_locations ) );
     379                $description  .= '</p><p>' . __( 'You can also place menus in widget areas with the Custom Menu widget.' ) . '</p>';
     380
     381                $this->manager->add_section( 'menu_locations', array(
     382                        'title'       => __( 'Menu Locations' ),
     383                        'panel'       => 'menus',
     384                        'priority'    => 5,
     385                        'description' => $description,
     386                ) );
     387
     388                // @todo if ( ! $menus ) : make a "default" menu
     389                if ( $menus ) {
     390                        $choices = array( '0' => __( '&mdash; Select &mdash;' ) );
     391                        foreach ( $menus as $menu ) {
     392                                $choices[ $menu->term_id ] = wp_html_excerpt( $menu->name, 40, '&hellip;' );
     393                        }
     394
     395                        foreach ( $locations as $location => $description ) {
     396                                $setting_id = "nav_menu_locations[{$location}]";
     397
     398                                $setting = $this->manager->get_setting( $setting_id );
     399                                if ( $setting ) {
     400                                        $setting->transport = 'postMessage';
     401                                        remove_filter( "customize_sanitize_{$setting_id}", 'absint' );
     402                                        add_filter( "customize_sanitize_{$setting_id}", array( $this, 'intval_base10' ) );
     403                                } else {
     404                                        $this->manager->add_setting( $setting_id, array(
     405                                                'sanitize_callback' => array( $this, 'intval_base10' ),
     406                                                'theme_supports'    => 'menus',
     407                                                'type'              => 'theme_mod',
     408                                                'transport'         => 'postMessage',
     409                                        ) );
     410                                }
     411
     412                                $this->manager->add_control( new WP_Customize_Nav_Menu_Location_Control( $this->manager, $setting_id, array(
     413                                        'label'       => $description,
     414                                        'location_id' => $location,
     415                                        'section'     => 'menu_locations',
     416                                        'choices'     => $choices,
     417                                ) ) );
     418                        }
     419                }
     420
     421                // Register each menu as a Customizer section, and add each menu item to each menu.
     422                foreach ( $menus as $menu ) {
     423                        $menu_id = $menu->term_id;
     424
     425                        // Create a section for each menu.
     426                        $section_id = 'nav_menu[' . $menu_id . ']';
     427                        $this->manager->add_section( new WP_Customize_Nav_Menu_Section( $this->manager, $section_id, array(
     428                                'title'     => $menu->name,
     429                                'priority'  => 10,
     430                                'panel'     => 'menus',
     431                        ) ) );
     432
     433                        $nav_menu_setting_id = 'nav_menu[' . $menu_id . ']';
     434                        $this->manager->add_setting( new WP_Customize_Nav_Menu_Setting( $this->manager, $nav_menu_setting_id ) );
     435
     436                        // Add the menu contents.
     437                        $menu_items = (array) wp_get_nav_menu_items( $menu_id );
     438
     439                        foreach ( array_values( $menu_items ) as $i => $item ) {
     440
     441                                // Create a setting for each menu item (which doesn't actually manage data, currently).
     442                                $menu_item_setting_id = 'nav_menu_item[' . $item->ID . ']';
     443                                $this->manager->add_setting( new WP_Customize_Nav_Menu_Item_Setting( $this->manager, $menu_item_setting_id ) );
     444
     445                                // Create a control for each menu item.
     446                                $this->manager->add_control( new WP_Customize_Nav_Menu_Item_Control( $this->manager, $menu_item_setting_id, array(
     447                                        'label'    => $item->title,
     448                                        'section'  => $section_id,
     449                                        'priority' => 10 + $i,
     450                                ) ) );
     451                        }
     452
     453                        // Note: other controls inside of this section get added dynamically in JS via the MenuSection.ready() function.
     454                }
     455
     456                // Add the add-new-menu section and controls.
     457                $this->manager->add_section( new WP_Customize_New_Menu_Section( $this->manager, 'add_menu', array(
     458                        'title'    => __( 'Add a Menu' ),
     459                        'panel'    => 'menus',
     460                        'priority' => 999,
     461                ) ) );
     462
     463                $this->manager->add_setting( 'new_menu_name', array(
     464                        'type'      => 'new_menu',
     465                        'default'   => '',
     466                        'transport' => 'postMessage',
     467                ) );
     468
     469                $this->manager->add_control( 'new_menu_name', array(
     470                        'label'       => '',
     471                        'section'     => 'add_menu',
     472                        'type'        => 'text',
     473                        'input_attrs' => array(
     474                                'class'       => 'menu-name-field',
     475                                'placeholder' => __( 'New menu name' ),
     476                        ),
     477                ) );
     478
     479                $this->manager->add_setting( 'create_new_menu', array(
     480                        'type' => 'new_menu',
     481                ) );
     482
     483                $this->manager->add_control( new WP_New_Menu_Customize_Control( $this->manager, 'create_new_menu', array(
     484                        'section' => 'add_menu',
     485                ) ) );
     486        }
     487
     488        /**
     489         * Get the base10 intval.
     490         *
     491         * This is used as a setting's sanitize_callback; we can't use just plain
     492         * intval because the second argument is not what intval() expects.
     493         *
     494         * @param mixed $value Number to convert.
     495         *
     496         * @return int
     497         */
     498        function intval_base10( $value ) {
     499                return intval( $value, 10 );
     500        }
     501
     502        /**
     503         * Return an array of all the available item types.
     504         *
     505         * @since Menu Customizer 0.0
     506         */
     507        public function available_item_types() {
     508                $items = array(
     509                        'postTypes'  => array(),
     510                        'taxonomies' => array(),
     511                );
     512
     513                $post_types = get_post_types( array( 'show_in_nav_menus' => true ), 'objects' );
     514                foreach ( $post_types as $slug => $post_type ) {
     515                        $items['postTypes'][ $slug ] = array(
     516                                'label' => $post_type->labels->singular_name,
     517                        );
     518                }
     519
     520                $taxonomies = get_taxonomies( array( 'show_in_nav_menus' => true ), 'objects' );
     521                foreach ( $taxonomies as $slug => $taxonomy ) {
     522                        if ( 'post_format' === $taxonomy && ! current_theme_supports( 'post-formats' ) ) {
     523                                continue;
     524                        }
     525                        $items['taxonomies'][ $slug ] = array(
     526                                'label' => $taxonomy->labels->singular_name,
     527                        );
     528                }
     529                return $items;
     530        }
     531
     532        /**
     533         * Print the JavaScript templates used to render Menu Customizer components.
     534         *
     535         * Templates are imported into the JS use wp.template.
     536         *
     537         * @since Menu Customizer 0.0
     538         */
     539        public function print_templates() {
     540                ?>
     541                <script type="text/html" id="tmpl-available-menu-item">
     542                        <div id="menu-item-tpl-{{ data.id }}" class="menu-item-tpl" data-menu-item-id="{{ data.id }}">
     543                                <dl class="menu-item-bar">
     544                                        <dt class="menu-item-handle">
     545                                                <span class="item-type">{{ data.type_label }}</span>
     546                                                <span class="item-title">{{ data.title || wp.customize.Menus.data.l10n.untitled }}</span>
     547                                                <button type="button" class="not-a-button item-add"><span class="screen-reader-text"><?php _e( 'Add Menu Item' ) ?></span></button>
     548                                        </dt>
     549                                </dl>
     550                        </div>
     551                </script>
     552
     553                <script type="text/html" id="tmpl-available-menu-item-type">
     554                        <div id="available-menu-items-{{ data.type }}" class="accordion-section">
     555                                <h4 class="accordion-section-title">{{ data.type_label }}</h4>
     556                                <div class="accordion-section-content">
     557                                </div>
     558                        </div>
     559                </script>
     560
     561                <script type="text/html" id="tmpl-menu-item-reorder-nav">
     562                        <div class="menu-item-reorder-nav">
     563                                <?php
     564                                printf(
     565                                        '<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>',
     566                                        esc_html__( 'Move up' ),
     567                                        esc_html__( 'Move down' ),
     568                                        esc_html__( 'Move one level up' ),
     569                                        esc_html__( 'Move one level down' )
     570                                );
     571                                ?>
     572                        </div>
     573                </script>
     574        <?php
     575        }
     576
     577        /**
     578         * Print the html template used to render the add-menu-item frame.
     579         */
     580        public function available_items_template() {
     581                ?>
     582                <div id="available-menu-items" class="accordion-container">
     583                        <div class="customize-section-title">
     584                                <button type="button" class="customize-section-back" tabindex="-1">
     585                                        <span class="screen-reader-text"><?php _e( 'Back' ); ?></span>
     586                                </button>
     587                                <h3>
     588                                        <span class="customize-action">
     589                                                <?php
     590                                                        /* translators: &#9656; is the unicode right-pointing triangle, and %s is the section title in the Customizer */
     591                                                        printf( __( 'Customizing &#9656; %s' ), esc_html( $this->manager->get_panel( 'menus' )->title ) );
     592                                                ?>
     593                                        </span>
     594                                        <?php _e( 'Add Menu Items' ); ?>
     595                                </h3>
     596                        </div>
     597                        <div id="available-menu-items-search" class="accordion-section cannot-expand">
     598                                <div class="accordion-section-title">
     599                                        <label class="screen-reader-text" for="menu-items-search"><?php _e( 'Search Menu Items' ); ?></label>
     600                                        <input type="text" id="menu-items-search" placeholder="<?php esc_attr_e( 'Search menu items&hellip;' ) ?>" />
     601                                        <span class="spinner"></span>
     602                                </div>
     603                                <div class="accordion-section-content" data-type="search"></div>
     604                        </div>
     605                        <div id="new-custom-menu-item" class="accordion-section">
     606                                <h4 class="accordion-section-title"><?php _e( 'Links' ); ?><button type="button" class="not-a-button"><span class="screen-reader-text"><?php _e( 'Toggle' ); ?></span></button></h4>
     607                                <div class="accordion-section-content">
     608                                        <input type="hidden" value="custom" id="custom-menu-item-type" name="menu-item[-1][menu-item-type]" />
     609                                        <p id="menu-item-url-wrap">
     610                                                <label class="howto" for="custom-menu-item-url">
     611                                                        <span><?php _e( 'URL' ); ?></span>
     612                                                        <input id="custom-menu-item-url" name="menu-item[-1][menu-item-url]" type="text" class="code menu-item-textbox" value="http://">
     613                                                </label>
     614                                        </p>
     615                                        <p id="menu-item-name-wrap">
     616                                                <label class="howto" for="custom-menu-item-name">
     617                                                        <span><?php _e( 'Link Text' ); ?></span>
     618                                                        <input id="custom-menu-item-name" name="menu-item[-1][menu-item-title]" type="text" class="regular-text menu-item-textbox">
     619                                                </label>
     620                                        </p>
     621                                        <p class="button-controls">
     622                                                <span class="add-to-menu">
     623                                                        <input type="submit" class="button-secondary submit-add-to-menu right" value="<?php esc_attr_e( 'Add to Menu' ); ?>" name="add-custom-menu-item" id="custom-menu-item-submit">
     624                                                        <span class="spinner"></span>
     625                                                </span>
     626                                        </p>
     627                                </div>
     628                        </div>
     629                        <?php
     630
     631                        // @todo: consider using add_meta_box/do_accordion_section and making screen-optional?
     632                        // Containers for per-post-type item browsing; items added with JS.
     633                        $post_types = get_post_types( array( 'show_in_nav_menus' => true ), 'object' );
     634                        if ( $post_types ) :
     635                                foreach ( $post_types as $type ) :
     636                                        ?>
     637                                        <div id="available-menu-items-<?php echo esc_attr( $type->name ); ?>" class="accordion-section">
     638                                                <h4 class="accordion-section-title"><?php echo esc_html( $type->label ); ?> <span class="spinner"></span> <button type="button" class="not-a-button"><span class="screen-reader-text"><?php _e( 'Toggle' ); ?></span></button></h4>
     639                                                <div class="accordion-section-content" data-type="<?php echo esc_attr( $type->name ); ?>" data-obj_type="post_type"></div>
     640                                        </div>
     641                                <?php
     642                                endforeach;
     643                        endif;
     644
     645                        $taxonomies = get_taxonomies( array( 'show_in_nav_menus' => true ), 'object' );
     646                        if ( $taxonomies ) :
     647                                foreach ( $taxonomies as $tax ) :
     648                                        ?>
     649                                        <div id="available-menu-items-<?php echo esc_attr( $tax->name ); ?>" class="accordion-section">
     650                                                <h4 class="accordion-section-title"><?php echo esc_html( $tax->label ); ?> <span class="spinner"></span> <button type="button" class="not-a-button"><span class="screen-reader-text"><?php _e( 'Toggle' ); ?></span></button></h4>
     651                                                <div class="accordion-section-content" data-type="<?php echo esc_attr( $tax->name ); ?>" data-obj_type="taxonomy"></div>
     652                                        </div>
     653                                <?php
     654                                endforeach;
     655                        endif;
     656                        ?>
     657                </div><!-- #available-menu-items -->
     658        <?php
     659        }
     660
     661        // Start functionality specific to partial-refresh of menu changes in Customizer preview.
     662        const RENDER_AJAX_ACTION = 'customize_render_menu_partial';
     663        const RENDER_NONCE_POST_KEY = 'render-menu-nonce';
     664        const RENDER_QUERY_VAR = 'wp_customize_menu_render';
     665
     666        /**
     667         * The number of wp_nav_menu() calls which have happened in the preview.
     668         *
     669         * @var int
     670         */
     671        public $preview_nav_menu_instance_number = 0;
     672
     673        /**
     674         * Nav menu args used for each instance.
     675         *
     676         * @var array[]
     677         */
     678        public $preview_nav_menu_instance_args = array();
     679
     680        /**
     681         * Add hooks for the Customizer preview.
     682         */
     683        function customize_preview_init() {
     684                add_action( 'template_redirect', array( $this, 'render_menu' ) );
     685                add_action( 'wp_enqueue_scripts', array( $this, 'customize_preview_enqueue_deps' ) );
     686
     687                if ( ! isset( $_REQUEST[ self::RENDER_QUERY_VAR ] ) ) {
     688                        add_filter( 'wp_nav_menu_args', array( $this, 'filter_wp_nav_menu_args' ), 1000 );
     689                        add_filter( 'wp_nav_menu', array( $this, 'filter_wp_nav_menu' ), 10, 2 );
     690                }
     691        }
     692
     693        /**
     694         * Keep track of the arguments that are being passed to wp_nav_menu().
     695         *
     696         * @see wp_nav_menu()
     697         *
     698         * @param array $args  An array containing wp_nav_menu() arguments.
     699         * @return array
     700         */
     701        function filter_wp_nav_menu_args( $args ) {
     702                $this->preview_nav_menu_instance_number += 1;
     703                $args['instance_number'] = $this->preview_nav_menu_instance_number;
     704
     705                $can_partial_refresh = (
     706                        $args['echo']
     707                        &&
     708                        is_string( $args['fallback_cb'] )
     709                        &&
     710                        is_string( $args['walker'] )
     711                );
     712                $args['can_partial_refresh'] = $can_partial_refresh;
     713
     714                if ( ! $can_partial_refresh ) {
     715                        unset( $args['fallback_cb'] );
     716                        unset( $args['walker'] );
     717                }
     718
     719                ksort( $args );
     720                $args['args_hash'] = $this->hash_nav_menu_args( $args );
     721
     722                $this->preview_nav_menu_instance_args[ $this->preview_nav_menu_instance_number ] = $args;
     723                return $args;
     724        }
     725
     726        /**
     727         * Prepare wp_nav_menu() calls for partial refresh. Wraps output in container for refreshing.
     728         *
     729         * @see wp_nav_menu()
     730         *
     731         * @param string $nav_menu_content The HTML content for the navigation menu.
     732         * @param object $args             An object containing wp_nav_menu() arguments.
     733         * @return null
     734         */
     735        function filter_wp_nav_menu( $nav_menu_content, $args ) {
     736                if ( ! empty( $args->can_partial_refresh ) && ! empty( $args->instance_number ) ) {
     737                        $nav_menu_content = sprintf(
     738                                '<div id="partial-refresh-menu-container-%1$d" class="partial-refresh-menu-container" data-instance-number="%1$d">%2$s</div>',
     739                                $args->instance_number,
     740                                $nav_menu_content
     741                        );
     742                }
     743                return $nav_menu_content;
     744        }
     745
     746        /**
     747         * Hash (hmac) the arguments with the nonce and secret auth key to ensure they
     748         * are not tampered with when submitted in the Ajax request.
     749         *
     750         * @param array $args The arguments to hash.
     751         * @return string
     752         */
     753        function hash_nav_menu_args( $args ) {
     754                return wp_hash( wp_create_nonce( self::RENDER_AJAX_ACTION ) . serialize( $args ) );
     755        }
     756
     757        /**
     758         * Enqueue scripts for the Customizer preview.
     759         */
     760        function customize_preview_enqueue_deps() {
     761                wp_enqueue_script( 'customize-menus-preview' );
     762                wp_enqueue_style( 'customize-menus-preview' );
     763
     764                add_action( 'wp_print_footer_scripts', array( $this, 'export_preview_data' ) );
     765        }
     766
     767        /**
     768         * Export data from PHP to JS.
     769         */
     770        function export_preview_data() {
     771
     772                // Why not wp_localize_script? Because we're not localizing, and it forces values into strings.
     773                $exports = array(
     774                        'renderQueryVar'        => self::RENDER_QUERY_VAR,
     775                        'renderNonceValue'      => wp_create_nonce( self::RENDER_AJAX_ACTION ),
     776                        'renderNoncePostKey'    => self::RENDER_NONCE_POST_KEY,
     777                        'requestUri'            => '/',
     778                        'theme'                 => array(
     779                                'stylesheet' => $this->manager->get_stylesheet(),
     780                                'active'     => $this->manager->is_theme_active(),
     781                        ),
     782                        'previewCustomizeNonce' => wp_create_nonce( 'preview-customize_' . $this->manager->get_stylesheet() ),
     783                        'navMenuInstanceArgs'   => $this->preview_nav_menu_instance_args,
     784                );
     785
     786                if ( ! empty( $_SERVER['REQUEST_URI'] ) ) {
     787                        $exports['requestUri'] = esc_url_raw( home_url( wp_unslash( $_SERVER['REQUEST_URI'] ) ) );
     788                }
     789
     790                printf( '<script>var _wpCustomizePreviewMenusExports = %s;</script>', wp_json_encode( $exports ) );
     791        }
     792
     793        /**
     794         * Render a specific menu via wp_nav_menu() using the supplied arguments.
     795         *
     796         * @see wp_nav_menu()
     797         */
     798        function render_menu() {
     799                if ( empty( $_POST[ self::RENDER_QUERY_VAR ] ) ) {
     800                        return;
     801                }
     802
     803                $this->manager->remove_preview_signature();
     804
     805                if ( empty( $_POST[ self::RENDER_NONCE_POST_KEY ] ) ) {
     806                        wp_send_json_error( 'missing_nonce_param' );
     807                }
     808                if ( ! is_customize_preview() ) {
     809                        wp_send_json_error( 'expected_customize_preview' );
     810                }
     811                if ( ! check_ajax_referer( self::RENDER_AJAX_ACTION, self::RENDER_NONCE_POST_KEY, false ) ) {
     812                        wp_send_json_error( 'nonce_check_fail' );
     813                }
     814                if ( ! current_user_can( 'edit_theme_options' ) ) {
     815                        wp_send_json_error( 'unauthorized' );
     816                }
     817                if ( ! isset( $_POST['wp_nav_menu_args'] ) ) {
     818                        wp_send_json_error( 'missing_param' );
     819                }
     820                if ( ! isset( $_POST['wp_nav_menu_args_hash'] ) ) {
     821                        wp_send_json_error( 'missing_param' );
     822                }
     823
     824                $wp_nav_menu_args_hash = sanitize_text_field( wp_unslash( $_POST['wp_nav_menu_args_hash'] ) );
     825                $wp_nav_menu_args      = json_decode( wp_unslash( $_POST['wp_nav_menu_args'] ), true );
     826
     827                if ( json_last_error() ) {
     828                        wp_send_json_error( 'json_parse_error' );
     829                }
     830                if ( ! is_array( $wp_nav_menu_args ) ) {
     831                        wp_send_json_error( 'wp_nav_menu_args_not_array' );
     832                }
     833                if ( $this->hash_nav_menu_args( $wp_nav_menu_args ) !== $wp_nav_menu_args_hash ) {
     834                        wp_send_json_error( 'wp_nav_menu_args_hash_mismatch' );
     835                }
     836
     837                $wp_nav_menu_args['echo'] = false;
     838                wp_send_json_success( wp_nav_menu( $wp_nav_menu_args ) );
     839        }
     840}
  • src/wp-includes/class-wp-customize-section.php

     
    501501                return $this->manager->widgets->is_sidebar_rendered( $this->sidebar_id );
    502502        }
    503503}
     504
     505/**
     506 * Customize Menu Section Class
     507 *
     508 * Custom section only needed in JS.
     509 *
     510 * @since 4.3.0
     511 */
     512class WP_Customize_Nav_Menu_Section extends WP_Customize_Section {
     513
     514        /**
     515         * Control type
     516         *
     517         * @access public
     518         * @var string
     519         */
     520        public $type = 'nav_menu';
     521
     522        /**
     523         * Get section params for JS.
     524         *
     525         * @return array
     526         */
     527        function json() {
     528                $exported = parent::json();
     529                $exported['menu_id'] = intval( preg_replace( '/^nav_menu\[(\d+)\]/', '$1', $this->id ) );
     530
     531                return $exported;
     532        }
     533}
     534
     535/**
     536 * Customize Menu Section Class
     537 *
     538 * Implements the new-menu-ui toggle button instead of a regular section.
     539 *
     540 * @since 4.3.0
     541 */
     542class WP_Customize_New_Menu_Section extends WP_Customize_Section {
     543
     544        /**
     545         * Control type.
     546         *
     547         * @access public
     548         * @var string
     549         */
     550        public $type = 'new_menu';
     551
     552        /**
     553         * Render the section, and the controls that have been added to it.
     554         */
     555        protected function render() {
     556                ?>
     557                <li id="accordion-section-<?php echo esc_attr( $this->id ); ?>" class="accordion-section-new-menu">
     558                        <button type="button" class="button-secondary add-new-menu-item add-menu-toggle">
     559                                <?php echo esc_html( $this->title ); ?>
     560                                <span class="screen-reader-text"><?php _e( 'Press return or enter to open' ); ?></span>
     561                        </button>
     562                        <ul class="new-menu-section-content"></ul>
     563                </li>
     564                <?php
     565        }
     566}
  • src/wp-includes/class-wp-customize-setting.php

     
    630630                remove_theme_mod( 'background_image_thumb' );
    631631        }
    632632}
     633
     634/**
     635 * Customize Setting to represent a nav_menu.
     636 *
     637 * Subclass of WP_Customize_Setting to represent a nav_menu taxonomy term, and
     638 * the IDs for the nav_menu_items associated with the nav menu.
     639 *
     640 * @since 4.3.0
     641 *
     642 * @see wp_get_nav_menu_items()
     643 * @see WP_Customize_Setting
     644 */
     645class WP_Customize_Nav_Menu_Item_Setting extends WP_Customize_Setting {
     646
     647        const ID_PATTERN = '/^nav_menu_item\[(?P<id>-?\d+)\]$/';
     648
     649        const POST_TYPE = 'nav_menu_item';
     650
     651        const TYPE = 'nav_menu_item';
     652
     653        /**
     654         * Setting type.
     655         *
     656         * @var string
     657         */
     658        public $type = self::TYPE;
     659
     660        /**
     661         * Default setting value.
     662         *
     663         * @see wp_setup_nav_menu_item()
     664         * @var array
     665         */
     666        public $default = array(
     667                // The $menu_item_data for wp_update_nav_menu_item().
     668                'object_id'        => 0,
     669                'object'           => '', // Taxonomy name.
     670                'menu_item_parent' => 0, // A.K.A. menu-item-parent-id; note that post_parent is different, and not included.
     671                'position'         => 0, // A.K.A. menu_order.
     672                'type'             => 'custom', // Note that type_label is not included here.
     673                'title'            => '',
     674                'url'              => '',
     675                'target'           => '',
     676                'attr_title'       => '',
     677                'description'      => '',
     678                'classes'          => '',
     679                'xfn'              => '',
     680                'status'           => 'publish',
     681                'original_title'   => '',
     682                'nav_menu_term_id' => 0, // This will be supplied as the $menu_id arg for wp_update_nav_menu_item().
     683                // @todo also expose invalid?
     684        );
     685
     686        /**
     687         * Default transport.
     688         *
     689         * @var string
     690         */
     691        public $transport = 'postMessage';
     692
     693        /**
     694         * The post ID represented by this setting instance. This is the db_id.
     695         *
     696         * A negative value represents a placeholder ID for a new menu not yet saved.
     697         *
     698         * @todo Should this be $db_id, and also use this for WP_Customize_Nav_Menu_Setting::$term_id
     699         *
     700         * @var int
     701         */
     702        public $post_id;
     703
     704        /**
     705         * Previous (placeholder) post ID used before creating a new menu item.
     706         *
     707         * This value will be exported to JS via the customize_save_response filter
     708         * so that JavaScript can update the settings to refer to the newly-assigned
     709         * post ID. This value is always negative to indicate it does not refer to
     710         * a real post.
     711         *
     712         * @see WP_Customize_Nav_Menu_Item_Setting::update()
     713         * @see WP_Customize_Nav_Menu_Item_Setting::amend_customize_save_response()
     714         *
     715         * @var int
     716         */
     717        public $previous_post_id;
     718
     719        /**
     720         * When previewing or updating a menu item, this stores the previous nav_menu_term_id
     721         * which ensures that we can apply the proper filters.
     722         *
     723         * @var int
     724         */
     725        public $original_nav_menu_term_id;
     726
     727        /**
     728         * Whether or not preview() was called.
     729         *
     730         * @var bool
     731         */
     732        protected $is_previewed = false;
     733
     734        /**
     735         * Whether or not update() was called.
     736         *
     737         * @var bool
     738         */
     739        protected $is_updated = false;
     740
     741        /**
     742         * Status for calling the update method, used in customize_save_response filter.
     743         *
     744         * When status is inserted, the placeholder post ID is stored in $previous_post_id.
     745         * When status is error, the error is stored in $update_error.
     746         *
     747         * @see WP_Customize_Nav_Menu_Item_Setting::update()
     748         * @see WP_Customize_Nav_Menu_Item_Setting::amend_customize_save_response()
     749         *
     750         * @var string updated|inserted|deleted|error
     751         */
     752        public $update_status;
     753
     754        /**
     755         * Any error object returned by wp_update_nav_menu_item() when setting is updated.
     756         *
     757         * @see WP_Customize_Nav_Menu_Item_Setting::update()
     758         * @see WP_Customize_Nav_Menu_Item_Setting::amend_customize_save_response()
     759         *
     760         * @var WP_Error
     761         */
     762        public $update_error;
     763
     764        /**
     765         * Constructor.
     766         *
     767         * Any supplied $args override class property defaults.
     768         *
     769         * @param WP_Customize_Manager $manager Manager instance.
     770         * @param string               $id      An specific ID of the setting. Can be a
     771         *                                      theme mod or option name.
     772         * @param array                $args    Optional. Setting arguments.
     773         * @throws Exception If $id is not valid for this setting type.
     774         */
     775        public function __construct( WP_Customize_Manager $manager, $id, array $args = array() ) {
     776                if ( empty( $manager->menus ) ) {
     777                        throw new Exception( 'Expected WP_Customize_Manager::$menus to be set.' );
     778                }
     779
     780                if ( ! preg_match( self::ID_PATTERN, $id, $matches ) ) {
     781                        throw new Exception( "Illegal widget setting ID: $id" );
     782                }
     783
     784                $this->post_id = intval( $matches['id'] );
     785
     786                $menu = $this->value();
     787                $this->original_nav_menu_term_id = $menu['nav_menu_term_id'];
     788
     789                parent::__construct( $manager, $id, $args );
     790        }
     791
     792        /**
     793         * Get the instance data for a given widget setting.
     794         *
     795         * @see wp_setup_nav_menu_item()
     796         * @return array
     797         */
     798        public function value() {
     799                if ( $this->is_previewed && $this->_previewed_blog_id === get_current_blog_id() ) {
     800                        $undefined  = new stdClass(); // Symbol.
     801                        $post_value = $this->post_value( $undefined );
     802
     803                        if ( $undefined === $post_value ) {
     804                                $value = $this->_original_value;
     805                        } else {
     806                                $value = $post_value;
     807                        }
     808                } else {
     809                        $value = false;
     810
     811                        // Note that a ID of less than one indicates a nav_menu not yet inserted.
     812                        if ( $this->post_id > 0 ) {
     813                                $post = get_post( $this->post_id );
     814                                if ( $post && self::POST_TYPE === $post->post_type ) {
     815                                        $item  = wp_setup_nav_menu_item( $post );
     816                                        $value = wp_array_slice_assoc(
     817                                                (array) $item,
     818                                                array_keys( $this->default )
     819                                        );
     820                                        $value['position']       = $item->menu_order;
     821                                        $value['status']         = $item->post_status;
     822                                        $value['original_title'] = '';
     823
     824                                        $menus = wp_get_post_terms( $post->ID, WP_Customize_Nav_Menu_Setting::TAXONOMY, array(
     825                                                'fields' => 'ids',
     826                                        ) );
     827
     828                                        if ( ! empty( $menus ) ) {
     829                                                $value['nav_menu_term_id'] = array_shift( $menus );
     830                                        } else {
     831                                                $value['nav_menu_term_id'] = 0;
     832                                        }
     833
     834                                        if ( 'post_type' === $value['type'] ) {
     835                                                $original_title = get_the_title( $value['object_id'] );
     836                                        } else if ( 'taxonomy' === $value['type'] ) {
     837                                                $original_title = get_term_field( 'name', $value['object_id'], $value['object'], 'raw' );
     838                                                if ( is_wp_error( $original_title ) ) {
     839                                                        $original_title = '';
     840                                                }
     841                                        }
     842
     843                                        if ( ! empty( $original_title ) ) {
     844                                                $value['original_title'] = $original_title;
     845                                        }
     846                                }
     847                        }
     848
     849                        if ( ! is_array( $value ) ) {
     850                                $value = $this->default;
     851                        }
     852                }
     853
     854                if ( is_array( $value ) ) {
     855                        foreach ( array( 'object_id', 'menu_item_parent', 'nav_menu_term_id' ) as $key ) {
     856                                $value[ $key ] = intval( $value[ $key ] );
     857                        }
     858                }
     859
     860                return $value;
     861        }
     862
     863        /**
     864         * Handle previewing the setting.
     865         *
     866         * @see WP_Customize_Manager::post_value()
     867         */
     868        public function preview() {
     869                if ( $this->is_previewed ) {
     870                        return;
     871                }
     872
     873                $this->is_previewed              = true;
     874                $this->_original_value           = $this->value();
     875                $this->original_nav_menu_term_id = $this->_original_value['nav_menu_term_id'];
     876                $this->_previewed_blog_id        = get_current_blog_id();
     877
     878                add_filter( 'wp_get_nav_menu_items', array( $this, 'filter_wp_get_nav_menu_items' ), 10, 3 );
     879
     880                $sort_callback = array( __CLASS__, 'sort_wp_get_nav_menu_items' );
     881                if ( ! has_filter( 'wp_get_nav_menu_items', $sort_callback ) ) {
     882                        add_filter( 'wp_get_nav_menu_items', array( __CLASS__, 'sort_wp_get_nav_menu_items' ), 1000, 3 );
     883                }
     884
     885                // @todo Add get_post_metadata filters for plugins to add their data.
     886        }
     887
     888        /**
     889         * Filter the wp_get_nav_menu_items() result to supply the previewed menu items.
     890         *
     891         * @see wp_get_nav_menu_items()
     892         * @param array  $items An array of menu item post objects.
     893         * @param object $menu  The menu object.
     894         * @param array  $args  An array of arguments used to retrieve menu item objects.
     895         * @return array
     896         */
     897        function filter_wp_get_nav_menu_items( $items, $menu, $args ) {
     898                $this_item = $this->value();
     899                $current_nav_menu_term_id = $this_item['nav_menu_term_id'];
     900                unset( $this_item['nav_menu_term_id'] );
     901
     902                $should_filter = (
     903                        $menu->term_id === $this->original_nav_menu_term_id
     904                        ||
     905                        $menu->term_id === $current_nav_menu_term_id
     906                );
     907                if ( ! $should_filter ) {
     908                        return $items;
     909                }
     910
     911                // Handle deleted menu item, or menu item moved to another menu.
     912                $should_remove = (
     913                        false === $this_item
     914                        ||
     915                        (
     916                                $this->original_nav_menu_term_id === $menu->term_id
     917                                &&
     918                                $current_nav_menu_term_id !== $this->original_nav_menu_term_id
     919                        )
     920                );
     921                if ( $should_remove ) {
     922                        $filtered_items = array();
     923                        foreach ( $items as $item ) {
     924                                if ( $item->db_id !== $this->post_id ) {
     925                                        $filtered_items[] = $item;
     926                                }
     927                        }
     928                        return $filtered_items;
     929                }
     930
     931                $mutated = false;
     932                $should_update = (
     933                        is_array( $this_item )
     934                        &&
     935                        $current_nav_menu_term_id === $menu->term_id
     936                );
     937                if ( $should_update ) {
     938                        foreach ( $items as $item ) {
     939                                if ( $item->db_id === $this->post_id ) {
     940                                        foreach ( get_object_vars( $this->value_as_wp_post_nav_menu_item() ) as $key => $value ) {
     941                                                $item->$key = $value;
     942                                        }
     943                                        $mutated = true;
     944                                }
     945                        }
     946
     947                        // Not found so we have to append it..
     948                        if ( ! $mutated ) {
     949                                $items[] = $this->value_as_wp_post_nav_menu_item();
     950                        }
     951                }
     952
     953                return $items;
     954        }
     955
     956        /**
     957         * Re-apply the tail logic also applied on $items by wp_get_nav_menu_items().
     958         *
     959         * @see wp_get_nav_menu_items()
     960         *
     961         * @param array  $items An array of menu item post objects.
     962         * @param object $menu  The menu object.
     963         * @param array  $args  An array of arguments used to retrieve menu item objects.
     964         * @return array
     965         */
     966        static function sort_wp_get_nav_menu_items( $items, $menu, $args ) {
     967                // @todo We should probably re-apply some constraints imposed by $args.
     968                unset( $args['include'] );
     969
     970                // Remove invalid items only in frontend.
     971                if ( ! is_admin() ) {
     972                        $items = array_filter( $items, '_is_valid_nav_menu_item' );
     973                }
     974
     975                if ( ARRAY_A === $args['output'] ) {
     976                        $GLOBALS['_menu_item_sort_prop'] = $args['output_key'];
     977                        usort( $items, '_sort_nav_menu_items' );
     978                        $i = 1;
     979
     980                        foreach ( $items as $k => $item ) {
     981                                $items[ $k ]->$args['output_key'] = $i++;
     982                        }
     983                }
     984
     985                return $items;
     986        }
     987
     988        /**
     989         * Get the value emulated into a WP_Post and set up as a nav_menu_item.
     990         *
     991         * @return WP_Post With {@see wp_setup_nav_menu_item()} applied.
     992         */
     993        public function value_as_wp_post_nav_menu_item() {
     994                $item = (object) $this->value();
     995                unset( $item->nav_menu_term_id );
     996
     997                $item->post_status = $item->status;
     998                unset( $item->status );
     999
     1000                $item->post_type = 'nav_menu_item';
     1001                $item->menu_order = $item->position;
     1002                unset( $item->position );
     1003
     1004                $item->post_author = get_current_user_id();
     1005
     1006                if ( $item->title ) {
     1007                        $item->post_title = $item->title;
     1008                }
     1009
     1010                $item->ID = $this->post_id;
     1011                $post = new WP_Post( (object) $item );
     1012                $post = wp_setup_nav_menu_item( $post );
     1013
     1014                return $post;
     1015        }
     1016
     1017        /**
     1018         * Sanitize an input.
     1019         *
     1020         * Note that parent::sanitize() erroneously does wp_unslash() on $value, but
     1021         * we remove that in this override.
     1022         *
     1023         * @param array $menu_item_value The value to sanitize.
     1024         * @return array|false|null Null if an input isn't valid. False if it is marked for deletion. Otherwise the sanitized value.
     1025         */
     1026        public function sanitize( $menu_item_value ) {
     1027                // Menu is marked for deletion.
     1028                if ( false === $menu_item_value ) {
     1029                        return $menu_item_value;
     1030                }
     1031
     1032                // Invalid.
     1033                if ( ! is_array( $menu_item_value ) ) {
     1034                        return null;
     1035                }
     1036
     1037                $default = array(
     1038                        'object_id'        => 0,
     1039                        'object'           => '',
     1040                        'menu_item_parent' => 0,
     1041                        'position'         => 0,
     1042                        'type'             => 'custom',
     1043                        'title'            => '',
     1044                        'url'              => '',
     1045                        'target'           => '',
     1046                        'attr_title'       => '',
     1047                        'description'      => '',
     1048                        'classes'          => '',
     1049                        'xfn'              => '',
     1050                        'status'           => 'publish',
     1051                        'original_title'   => '',
     1052                        'nav_menu_term_id' => 0,
     1053                );
     1054                $menu_item_value = array_merge( $default, $menu_item_value );
     1055                $menu_item_value = wp_array_slice_assoc( $menu_item_value, array_keys( $default ) );
     1056                $menu_item_value['position'] = max( 0, intval( $menu_item_value['position'] ) );
     1057
     1058                foreach ( array( 'object_id', 'menu_item_parent', 'nav_menu_term_id' ) as $key ) {
     1059                        // Note we need to allow negative-integer IDs for previewed objects not inserted yet.
     1060                        $menu_item_value[ $key ] = intval( $menu_item_value[ $key ] );
     1061                }
     1062
     1063                foreach ( array( 'type', 'object', 'target' ) as $key ) {
     1064                        $menu_item_value[ $key ] = sanitize_key( $menu_item_value[ $key ] );
     1065                }
     1066
     1067                foreach ( array( 'xfn', 'classes' ) as $key ) {
     1068                        $value = $menu_item_value[ $key ];
     1069                        if ( ! is_array( $value ) ) {
     1070                                $value = explode( ' ', $value );
     1071                        }
     1072                        $menu_item_value[ $key ] = implode( ' ', array_map( 'sanitize_html_class', $value ) );
     1073                }
     1074
     1075                foreach ( array( 'title', 'attr_title', 'description', 'original_title' ) as $key ) {
     1076                        // @todo Should esc_attr() the attr_title as well?
     1077                        $menu_item_value[ $key ] = sanitize_text_field( $menu_item_value[ $key ] );
     1078                }
     1079
     1080                $menu_item_value['url'] = esc_url_raw( $menu_item_value['url'] );
     1081                if ( ! get_post_status_object( $menu_item_value['status'] ) ) {
     1082                        $menu_item_value['status'] = 'publish';
     1083                }
     1084
     1085                /** This filter is documented in wp-includes/class-wp-customize-setting.php */
     1086                return apply_filters( "customize_sanitize_{$this->id}", $menu_item_value, $this );
     1087        }
     1088
     1089        /**
     1090         * Create/update the nav_menu_item post for this setting.
     1091         *
     1092         * Any created menu items will have their assigned post IDs exported to the client
     1093         * via the customize_save_response filter. Likewise, any errors will be exported
     1094         * to the client via the customize_save_response() filter.
     1095         *
     1096         * To delete a menu, the client can send false as the value.
     1097         *
     1098         * @see wp_update_nav_menu_item()
     1099         *
     1100         * @param array|false $value The menu item array to update. If false, then the menu item will be deleted entirely.
     1101         *                           See {@see WP_Customize_Nav_Menu_Item_Setting::$default} for what the value should
     1102         *                           consist of.
     1103         * @return void
     1104         */
     1105        protected function update( $value ) {
     1106                if ( $this->is_updated ) {
     1107                        return;
     1108                }
     1109
     1110                $this->is_updated = true;
     1111                $is_placeholder   = ( $this->post_id < 0 );
     1112                $is_delete        = ( false === $value );
     1113
     1114                add_filter( 'customize_save_response', array( $this, 'amend_customize_save_response' ) );
     1115
     1116                if ( $is_delete ) {
     1117                        // If the current setting post is a placeholder, a delete request is a no-op.
     1118                        if ( $is_placeholder ) {
     1119                                $this->update_status = 'deleted';
     1120                        } else {
     1121                                $r = wp_delete_post( $this->post_id, true );
     1122
     1123                                if ( false === $r ) {
     1124                                        $this->update_error  = new WP_Error( 'delete_failure' );
     1125                                        $this->update_status = 'error';
     1126                                } else {
     1127                                        $this->update_status = 'deleted';
     1128                                }
     1129                                // @todo send back the IDs for all associated nav menu items deleted, so these settings (and controls) can be removed from Customizer?
     1130                        }
     1131                } else {
     1132
     1133                        // Handle saving menu items for menus that are being newly-created.
     1134                        if ( $value['nav_menu_term_id'] < 0 ) {
     1135                                $nav_menu_setting_id = sprintf( 'nav_menu[%s]', $value['nav_menu_term_id'] );
     1136                                $nav_menu_setting    = $this->manager->get_setting( $nav_menu_setting_id );
     1137
     1138                                if ( ! $nav_menu_setting || ! ( $nav_menu_setting instanceof WP_Customize_Nav_Menu_Setting ) ) {
     1139                                        $this->update_status = 'error';
     1140                                        $this->update_error  = new WP_Error( 'unexpected_nav_menu_setting' );
     1141                                        return;
     1142                                }
     1143
     1144                                if ( false === $nav_menu_setting->save() ) {
     1145                                        $this->update_status = 'error';
     1146                                        $this->update_error  = new WP_Error( 'nav_menu_setting_failure' );
     1147                                }
     1148
     1149                                if ( $nav_menu_setting->previous_term_id !== intval( $value['nav_menu_term_id'] ) ) {
     1150                                        $this->update_status = 'error';
     1151                                        $this->update_error  = new WP_Error( 'unexpected_previous_term_id' );
     1152                                        return;
     1153                                }
     1154
     1155                                $value['nav_menu_term_id'] = $nav_menu_setting->term_id;
     1156                        }
     1157
     1158                        // Handle saving a nav menu item that is a child of a nav menu item being newly-created.
     1159                        if ( $value['menu_item_parent'] < 0 ) {
     1160                                $parent_nav_menu_item_setting_id = sprintf( 'nav_menu_item[%s]', $value['menu_item_parent'] );
     1161                                $parent_nav_menu_item_setting    = $this->manager->get_setting( $parent_nav_menu_item_setting_id );
     1162
     1163                                if ( ! $parent_nav_menu_item_setting || ! ( $parent_nav_menu_item_setting instanceof WP_Customize_Nav_Menu_Item_Setting ) ) {
     1164                                        $this->update_status = 'error';
     1165                                        $this->update_error  = new WP_Error( 'unexpected_nav_menu_item_setting' );
     1166                                        return;
     1167                                }
     1168
     1169                                if ( false === $parent_nav_menu_item_setting->save() ) {
     1170                                        $this->update_status = 'error';
     1171                                        $this->update_error  = new WP_Error( 'nav_menu_item_setting_failure' );
     1172                                }
     1173
     1174                                if ( $parent_nav_menu_item_setting->previous_post_id !== intval( $value['menu_item_parent'] ) ) {
     1175                                        $this->update_status = 'error';
     1176                                        $this->update_error  = new WP_Error( 'unexpected_previous_post_id' );
     1177                                        return;
     1178                                }
     1179
     1180                                $value['menu_item_parent'] = $parent_nav_menu_item_setting->post_id;
     1181                        }
     1182
     1183                        // Insert or update menu.
     1184                        $menu_item_data = array(
     1185                                'menu-item-object-id'   => $value['object_id'],
     1186                                'menu-item-object'      => $value['object'],
     1187                                'menu-item-parent-id'   => $value['menu_item_parent'],
     1188                                'menu-item-position'    => $value['position'],
     1189                                'menu-item-type'        => $value['type'],
     1190                                'menu-item-title'       => $value['title'],
     1191                                'menu-item-url'         => $value['url'],
     1192                                'menu-item-description' => $value['description'],
     1193                                'menu-item-attr-title'  => $value['attr_title'],
     1194                                'menu-item-target'      => $value['target'],
     1195                                'menu-item-classes'     => $value['classes'],
     1196                                'menu-item-xfn'         => $value['xfn'],
     1197                                'menu-item-status'      => $value['status'],
     1198                        );
     1199
     1200                        $r = wp_update_nav_menu_item(
     1201                                $value['nav_menu_term_id'],
     1202                                $is_placeholder ? 0 : $this->post_id,
     1203                                $menu_item_data
     1204                        );
     1205
     1206                        if ( is_wp_error( $r ) ) {
     1207                                $this->update_status = 'error';
     1208                                $this->update_error = $r;
     1209                        } else {
     1210                                if ( $is_placeholder ) {
     1211                                        $this->previous_post_id = $this->post_id;
     1212                                        $this->post_id = $r;
     1213                                        $this->update_status = 'inserted';
     1214                                } else {
     1215                                        $this->update_status = 'updated';
     1216                                }
     1217                        }
     1218                }
     1219
     1220        }
     1221
     1222        /**
     1223         * Export data for the JS client.
     1224         *
     1225         * @see WP_Customize_Nav_Menu_Item_Setting::update()
     1226         *
     1227         * @param array $data Additional information passed back to the 'saved' event on `wp.customize`.
     1228         * @return array
     1229         */
     1230        function amend_customize_save_response( $data ) {
     1231                if ( ! isset( $data['nav_menu_item_updates'] ) ) {
     1232                        $data['nav_menu_item_updates'] = array();
     1233                }
     1234
     1235                $data['nav_menu_item_updates'][] = array(
     1236                        'post_id'          => $this->post_id,
     1237                        'previous_post_id' => $this->previous_post_id,
     1238                        'error'            => $this->update_error ? $this->update_error->get_error_code() : null,
     1239                        'status'           => $this->update_status,
     1240                );
     1241
     1242                return $data;
     1243        }
     1244}
     1245
     1246/**
     1247 * Customize Setting to represent a nav_menu.
     1248 *
     1249 * Subclass of WP_Customize_Setting to represent a nav_menu taxonomy term, and
     1250 * the IDs for the nav_menu_items associated with the nav menu.
     1251 *
     1252 * @since 4.3.0
     1253 *
     1254 * @see wp_get_nav_menu_object()
     1255 * @see WP_Customize_Setting
     1256 */
     1257class WP_Customize_Nav_Menu_Setting extends WP_Customize_Setting {
     1258
     1259        const ID_PATTERN = '/^nav_menu\[(?P<id>-?\d+)\]$/';
     1260
     1261        const TAXONOMY = 'nav_menu';
     1262
     1263        const TYPE = 'nav_menu';
     1264
     1265        /**
     1266         * Setting type.
     1267         *
     1268         * @var string
     1269         */
     1270        public $type = self::TYPE;
     1271
     1272        /**
     1273         * Default setting value;
     1274         *
     1275         * @see wp_get_nav_menu_object()
     1276         *
     1277         * @var array
     1278         */
     1279        public $default = array(
     1280                'name'        => '',
     1281                'description' => '',
     1282                'parent'      => 0,
     1283                'auto_add'    => false,
     1284        );
     1285
     1286        /**
     1287         * Default transport.
     1288         *
     1289         * @var string
     1290         */
     1291        public $transport = 'postMessage';
     1292
     1293        /**
     1294         * The term ID represented by this setting instance.
     1295         *
     1296         * A negative value represents a placeholder ID for a new menu not yet saved.
     1297         *
     1298         * @var int
     1299         */
     1300        public $term_id;
     1301
     1302        /**
     1303         * Previous (placeholder) term ID used before creating a new menu.
     1304         *
     1305         * This value will be exported to JS via the customize_save_response filter
     1306         * so that JavaScript can update the settings to refer to the newly-assigned
     1307         * term ID. This value is always negative to indicate it does not refer to
     1308         * a real term.
     1309         *
     1310         * @see WP_Customize_Nav_Menu_Setting::update()
     1311         * @see WP_Customize_Nav_Menu_Setting::amend_customize_save_response()
     1312         *
     1313         * @var int
     1314         */
     1315        public $previous_term_id;
     1316
     1317        /**
     1318         * Whether or not preview() was called.
     1319         *
     1320         * @var bool
     1321         */
     1322        protected $is_previewed = false;
     1323
     1324        /**
     1325         * Whether or not update() was called.
     1326         *
     1327         * @var bool
     1328         */
     1329        protected $is_updated = false;
     1330
     1331        /**
     1332         * Status for calling the update method, used in customize_save_response filter.
     1333         *
     1334         * When status is inserted, the placeholder term ID is stored in $previous_term_id.
     1335         * When status is error, the error is stored in $update_error.
     1336         *
     1337         * @see WP_Customize_Nav_Menu_Setting::update()
     1338         * @see WP_Customize_Nav_Menu_Setting::amend_customize_save_response()
     1339         *
     1340         * @var string updated|inserted|deleted|error
     1341         */
     1342        public $update_status;
     1343
     1344        /**
     1345         * Any error object returned by wp_update_nav_menu_object() when setting is updated.
     1346         *
     1347         * @see WP_Customize_Nav_Menu_Setting::update()
     1348         * @see WP_Customize_Nav_Menu_Setting::amend_customize_save_response()
     1349         *
     1350         * @var WP_Error
     1351         */
     1352        public $update_error;
     1353
     1354        /**
     1355         * Constructor.
     1356         *
     1357         * Any supplied $args override class property defaults.
     1358         *
     1359         * @param WP_Customize_Manager $manager Manager instance.
     1360         * @param string               $id      An specific ID of the setting. Can be a
     1361         *                                      theme mod or option name.
     1362         * @param array                $args    Optional. Setting arguments.
     1363         * @throws Exception If $id is not valid for this setting type.
     1364         */
     1365        public function __construct( WP_Customize_Manager $manager, $id, array $args = array() ) {
     1366                if ( empty( $manager->menus ) ) {
     1367                        throw new Exception( 'Expected WP_Customize_Manager::$menus to be set.' );
     1368                }
     1369
     1370                if ( ! preg_match( self::ID_PATTERN, $id, $matches ) ) {
     1371                        throw new Exception( "Illegal widget setting ID: $id" );
     1372                }
     1373
     1374                $this->term_id = intval( $matches['id'] );
     1375
     1376                parent::__construct( $manager, $id, $args );
     1377        }
     1378
     1379        /**
     1380         * Get the instance data for a given widget setting.
     1381         *
     1382         * @see wp_get_nav_menu_object()
     1383         * @return array
     1384         */
     1385        public function value() {
     1386                if ( $this->is_previewed && $this->_previewed_blog_id === get_current_blog_id() ) {
     1387                        $undefined  = new stdClass(); // Symbol.
     1388                        $post_value = $this->post_value( $undefined );
     1389
     1390                        if ( $undefined === $post_value ) {
     1391                                $value = $this->_original_value;
     1392                        } else {
     1393                                $value = $post_value;
     1394                        }
     1395                } else {
     1396                        $value = false;
     1397
     1398                        // Note that a term_id of less than one indicates a nav_menu not yet inserted.
     1399                        if ( $this->term_id > 0 ) {
     1400                                $term = wp_get_nav_menu_object( $this->term_id );
     1401
     1402                                if ( $term ) {
     1403                                        $value = wp_array_slice_assoc( (array) $term, array_keys( $this->default ) );
     1404
     1405                                        $nav_menu_options  = (array) get_option( 'nav_menu_options', array() );
     1406                                        $value['auto_add'] = false;
     1407
     1408                                        if ( isset( $nav_menu_options['auto_add'] ) && is_array( $nav_menu_options['auto_add'] ) ) {
     1409                                                $value['auto_add'] = in_array( $term->term_id, $nav_menu_options['auto_add'] );
     1410                                        }
     1411                                }
     1412                        }
     1413
     1414                        if ( ! is_array( $value ) ) {
     1415                                $value = $this->default;
     1416                        }
     1417                }
     1418                return $value;
     1419        }
     1420
     1421        /**
     1422         * Handle previewing the setting.
     1423         *
     1424         * @see WP_Customize_Manager::post_value()
     1425         */
     1426        public function preview() {
     1427                if ( $this->is_previewed ) {
     1428                        return;
     1429                }
     1430
     1431                $this->is_previewed       = true;
     1432                $this->_original_value    = $this->value();
     1433                $this->_previewed_blog_id = get_current_blog_id();
     1434
     1435                add_filter( 'wp_get_nav_menu_object', array( $this, 'filter_wp_get_nav_menu_object' ), 10, 2 );
     1436                add_filter( 'default_option_nav_menu_options', array( $this, 'filter_nav_menu_options' ) );
     1437                add_filter( 'option_nav_menu_options', array( $this, 'filter_nav_menu_options' ) );
     1438        }
     1439
     1440        /**
     1441         * Filter the wp_get_nav_menu_object() result to supply the previewed menu object.
     1442         *
     1443         * Requesting a nav_menu object by anything but ID is not supported.
     1444         *
     1445         * @see wp_get_nav_menu_object()
     1446         *
     1447         * @param object|null $menu_obj Object returned by wp_get_nav_menu_object().
     1448         * @param string      $menu_id  ID of the nav_menu term. Requests by slug or name will be ignored.
     1449         * @return object|null
     1450         */
     1451        function filter_wp_get_nav_menu_object( $menu_obj, $menu_id ) {
     1452                $ok = (
     1453                        get_current_blog_id() === $this->_previewed_blog_id
     1454                        &&
     1455                        is_int( $menu_id )
     1456                        &&
     1457                        $menu_id === $this->term_id
     1458                );
     1459                if ( ! $ok ) {
     1460                        return $menu_obj;
     1461                }
     1462
     1463                $setting_value = $this->value();
     1464
     1465                // Handle deleted menus.
     1466                if ( false === $setting_value ) {
     1467                        return false;
     1468                }
     1469
     1470                // Handle sanitization failure by preventing short-circuiting.
     1471                if ( null === $setting_value ) {
     1472                        return $menu_obj;
     1473                }
     1474
     1475                $menu_obj = (object) array_merge( array(
     1476                                'term_id'          => $this->term_id,
     1477                                'term_taxonomy_id' => $this->term_id,
     1478                                'slug'             => sanitize_title( $setting_value['name'] ),
     1479                                'count'            => 0,
     1480                                'term_group'       => 0,
     1481                                'taxonomy'         => self::TAXONOMY,
     1482                                'filter'           => 'raw',
     1483                        ), $setting_value );
     1484
     1485                return $menu_obj;
     1486        }
     1487
     1488        /**
     1489         * Filter the nav_menu_options option to include this menu's auto_add preference.
     1490         *
     1491         * @param array $nav_menu_options Nav menu options including auto_add.
     1492         * @return array
     1493         */
     1494        function filter_nav_menu_options( $nav_menu_options ) {
     1495                if ( $this->_previewed_blog_id !== get_current_blog_id() ) {
     1496                        return $nav_menu_options;
     1497                }
     1498
     1499                $menu = $this->value();
     1500                $nav_menu_options = $this->filter_nav_menu_options_value(
     1501                        $nav_menu_options,
     1502                        $this->term_id,
     1503                        false === $menu ? false : $menu['auto_add']
     1504                );
     1505
     1506                return $nav_menu_options;
     1507        }
     1508
     1509        /**
     1510         * Sanitize an input.
     1511         *
     1512         * Note that parent::sanitize() erroneously does wp_unslash() on $value, but
     1513         * we remove that in this override.
     1514         *
     1515         * @param array $value The value to sanitize.
     1516         * @return array|false|null Null if an input isn't valid. False if it is marked for deletion. Otherwise the sanitized value.
     1517         */
     1518        public function sanitize( $value ) {
     1519                // Menu is marked for deletion.
     1520                if ( false === $value ) {
     1521                        return $value;
     1522                }
     1523
     1524                // Invalid.
     1525                if ( ! is_array( $value ) ) {
     1526                        return null;
     1527                }
     1528
     1529                $default = array(
     1530                        'name'        => '',
     1531                        'description' => '',
     1532                        'parent'      => 0,
     1533                        'auto_add'    => false,
     1534                );
     1535                $value = array_merge( $default, $value );
     1536                $value = wp_array_slice_assoc( $value, array_keys( $default ) );
     1537
     1538                $value['name']        = trim( esc_html( $value['name'] ) ); // This sanitization code is used in wp-admin/nav-menus.php.
     1539                $value['description'] = sanitize_text_field( $value['description'] );
     1540                $value['parent']      = max( 0, intval( $value['parent'] ) );
     1541                $value['auto_add']    = ! empty( $value['auto_add'] );
     1542
     1543                /** This filter is documented in wp-includes/class-wp-customize-setting.php */
     1544                return apply_filters( "customize_sanitize_{$this->id}", $value, $this );
     1545        }
     1546
     1547        /**
     1548         * Create/update the nav_menu term for this setting.
     1549         *
     1550         * Any created menus will have their assigned term IDs exported to the client
     1551         * via the customize_save_response filter. Likewise, any errors will be exported
     1552         * to the client via the customize_save_response() filter.
     1553         *
     1554         * To delete a menu, the client can send false as the value.
     1555         *
     1556         * @see wp_update_nav_menu_object()
     1557         *
     1558         * @param array|false $value {
     1559         *     The value to update. Note that slug cannot be updated via wp_update_nav_menu_object().
     1560         *     If false, then the menu will be deleted entirely.
     1561         *
     1562         *     @type string $name        The name of the menu to save.
     1563         *     @type string $description The term description. Default empty string.
     1564         *     @type int    $parent      The id of the parent term. Default 0.
     1565         *     @type bool   $auto_add    Whether pages will auto_add to this menu. Default false.
     1566         * }
     1567         * @return void
     1568         */
     1569        protected function update( $value ) {
     1570                if ( $this->is_updated ) {
     1571                        return;
     1572                }
     1573
     1574                $this->is_updated = true;
     1575                $is_placeholder   = ( $this->term_id < 0 );
     1576                $is_delete        = ( false === $value );
     1577
     1578                add_filter( 'customize_save_response', array( $this, 'amend_customize_save_response' ) );
     1579
     1580                $auto_add = null;
     1581                if ( $is_delete ) {
     1582                        // If the current setting term is a placeholder, a delete request is a no-op.
     1583                        if ( $is_placeholder ) {
     1584                                $this->update_status = 'deleted';
     1585                        } else {
     1586                                $r = wp_delete_nav_menu( $this->term_id );
     1587
     1588                                if ( is_wp_error( $r ) ) {
     1589                                        $this->update_status = 'error';
     1590                                        $this->update_error  = $r;
     1591                                } else {
     1592                                        $this->update_status = 'deleted';
     1593                                        $auto_add = false;
     1594                                }
     1595                        }
     1596                } else {
     1597                        // Insert or update menu.
     1598                        $menu_data = wp_array_slice_assoc( $value, array( 'description', 'parent' ) );
     1599                        if ( isset( $value['name'] ) ) {
     1600                                $menu_data['menu-name'] = $value['name'];
     1601                        }
     1602
     1603                        $r = wp_update_nav_menu_object( $is_placeholder ? 0 : $this->term_id, $menu_data );
     1604                        if ( is_wp_error( $r ) ) {
     1605                                $this->update_status = 'error';
     1606                                $this->update_error  = $r;
     1607                        } else {
     1608                                if ( $is_placeholder ) {
     1609                                        $this->previous_term_id = $this->term_id;
     1610                                        $this->term_id          = $r;
     1611                                        $this->update_status    = 'inserted';
     1612                                } else {
     1613                                        $this->update_status = 'updated';
     1614                                }
     1615
     1616                                $auto_add = $value['auto_add'];
     1617                        }
     1618                }
     1619
     1620                if ( null !== $auto_add ) {
     1621                        $nav_menu_options = $this->filter_nav_menu_options_value(
     1622                                (array) get_option( 'nav_menu_options', array() ),
     1623                                $this->term_id,
     1624                                $auto_add
     1625                        );
     1626                        update_option( 'nav_menu_options', $nav_menu_options );
     1627                }
     1628
     1629                // Make sure that new menus assigned to nav menu locations use their new IDs.
     1630                if ( 'inserted' === $this->update_status ) {
     1631                        foreach ( $this->manager->settings() as $setting ) {
     1632                                if ( ! preg_match( '/^nav_menu_locations\[/', $setting->id ) ) {
     1633                                        continue;
     1634                                }
     1635
     1636                                $post_value = $setting->post_value( null );
     1637                                if ( ! is_null( $post_value ) && $this->previous_term_id === intval( $post_value ) ) {
     1638                                        $this->manager->set_post_value( $setting->id, $this->term_id );
     1639                                        $setting->save();
     1640                                }
     1641                        }
     1642                }
     1643        }
     1644
     1645        /**
     1646         * Update a nav_menu_options array.
     1647         *
     1648         * @see WP_Customize_Nav_Menu_Setting::filter_nav_menu_options()
     1649         * @see WP_Customize_Nav_Menu_Setting::update()
     1650         *
     1651         * @param array $nav_menu_options Array as returned by get_option( 'nav_menu_options' ).
     1652         * @param int   $menu_id          The term ID for the given menu.
     1653         * @param bool  $auto_add         Whether to auto-add or not.
     1654         * @return array
     1655         */
     1656        protected function filter_nav_menu_options_value( $nav_menu_options, $menu_id, $auto_add ) {
     1657                $nav_menu_options = (array) $nav_menu_options;
     1658                if ( ! isset( $nav_menu_options['auto_add'] ) ) {
     1659                        $nav_menu_options['auto_add'] = array();
     1660                }
     1661
     1662                $i = array_search( $menu_id, $nav_menu_options['auto_add'] );
     1663                if ( $auto_add && false === $i ) {
     1664                        array_push( $nav_menu_options['auto_add'], $this->term_id );
     1665                } else if ( ! $auto_add && false !== $i ) {
     1666                        array_splice( $nav_menu_options['auto_add'], $i, 1 );
     1667                }
     1668
     1669                return $nav_menu_options;
     1670        }
     1671
     1672        /**
     1673         * Export data for the JS client.
     1674         *
     1675         * @see WP_Customize_Nav_Menu_Setting::update()
     1676         *
     1677         * @param array $data Additional information passed back to the 'saved' event on `wp.customize`.
     1678         * @return array
     1679         */
     1680        function amend_customize_save_response( $data ) {
     1681                if ( ! isset( $data['nav_menu_updates'] ) ) {
     1682                        $data['nav_menu_updates'] = array();
     1683                }
     1684
     1685                $data['nav_menu_updates'][] = array(
     1686                        'term_id'          => $this->term_id,
     1687                        'previous_term_id' => $this->previous_term_id,
     1688                        'error'            => $this->update_error ? $this->update_error->get_error_code() : null,
     1689                        'status'           => $this->update_status,
     1690                );
     1691
     1692                return $data;
     1693        }
     1694}
  • src/wp-includes/script-loader.php

     
    406406        $scripts->add( 'customize-widgets', "/wp-admin/js/customize-widgets$suffix.js", array( 'jquery', 'jquery-ui-sortable', 'jquery-ui-droppable', 'wp-backbone', 'customize-controls' ), false, 1 );
    407407        $scripts->add( 'customize-preview-widgets', "/wp-includes/js/customize-preview-widgets$suffix.js", array( 'jquery', 'wp-util', 'customize-preview' ), false, 1 );
    408408
     409        $scripts->add( 'menu-customizer', "/wp-admin/js/menu-customizer$suffix.js", array( 'jquery', 'wp-backbone', 'customize-controls', 'accordion', 'nav-menu', 'wp-a11y' ) );
     410        $scripts->add( 'customize-menus-preview', "/wp-admin/js/customize-menus-preview$suffix.js", array( 'customize-preview', 'wp-util' ), false, 1 );
     411
    409412        $scripts->add( 'accordion', "/wp-admin/js/accordion$suffix.js", array( 'jquery' ), false, 1 );
    410413
    411414        $scripts->add( 'shortcode', "/wp-includes/js/shortcode$suffix.js", array( 'underscore' ), false, 1 );
     
    656659        $suffix = SCRIPT_DEBUG ? '' : '.min';
    657660
    658661        // Admin CSS
    659         $styles->add( 'wp-admin',           "/wp-admin/css/wp-admin$suffix.css", array( 'open-sans', 'dashicons' ) );
    660         $styles->add( 'login',              "/wp-admin/css/login$suffix.css", array( 'buttons', 'open-sans', 'dashicons' ) );
    661         $styles->add( 'install',            "/wp-admin/css/install$suffix.css", array( 'buttons', 'open-sans' ) );
    662         $styles->add( 'wp-color-picker',    "/wp-admin/css/color-picker$suffix.css" );
    663         $styles->add( 'customize-controls', "/wp-admin/css/customize-controls$suffix.css", array( 'wp-admin', 'colors', 'ie', 'imgareaselect' ) );
    664         $styles->add( 'customize-widgets',  "/wp-admin/css/customize-widgets$suffix.css", array( 'wp-admin', 'colors' ) );
    665         $styles->add( 'press-this',         "/wp-admin/css/press-this$suffix.css", array( 'open-sans', 'buttons' ) );
     662        $styles->add( 'wp-admin',                "/wp-admin/css/wp-admin$suffix.css", array( 'open-sans', 'dashicons' ) );
     663        $styles->add( 'login',                   "/wp-admin/css/login$suffix.css", array( 'buttons', 'open-sans', 'dashicons' ) );
     664        $styles->add( 'install',                 "/wp-admin/css/install$suffix.css", array( 'buttons', 'open-sans' ) );
     665        $styles->add( 'wp-color-picker',         "/wp-admin/css/color-picker$suffix.css" );
     666        $styles->add( 'customize-controls',      "/wp-admin/css/customize-controls$suffix.css", array( 'wp-admin', 'colors', 'ie', 'imgareaselect' ) );
     667        $styles->add( 'customize-widgets',       "/wp-admin/css/customize-widgets$suffix.css", array( 'wp-admin', 'colors' ) );
     668        $styles->add( 'menu-customizer',         "/wp-admin/css/menu-customizer$suffix.css" );
     669        $styles->add( 'customize-menus-preview', "/wp-admin/css/customize-menus-preview$suffix.css" );
     670        $styles->add( 'press-this',              "/wp-admin/css/press-this$suffix.css", array( 'open-sans', 'buttons' ) );
    666671
    667672        $styles->add( 'ie',                 "/wp-admin/css/ie$suffix.css" );
    668673        $styles->add_data( 'ie', 'conditional', 'lte IE 7' );