Changeset 32806
- Timestamp:
- 06/16/2015 10:07:08 PM (9 years ago)
- Location:
- trunk
- Files:
-
- 8 added
- 6 edited
Legend:
- Unmodified
- Added
- Removed
-
trunk/Gruntfile.js
r32698 r32806 142 142 cssmin: { 143 143 options: { 144 'wp-admin': ['wp-admin', 'color-picker', 'customize-controls', 'customize-widgets', ' ie', 'install', 'login', 'press-this', 'deprecated-*']144 'wp-admin': ['wp-admin', 'color-picker', 'customize-controls', 'customize-widgets', 'customize-nav-menus', 'ie', 'install', 'login', 'press-this', 'deprecated-*'] 145 145 }, 146 146 core: { -
trunk/src/wp-includes/class-wp-customize-control.php
r32657 r32806 1403 1403 } 1404 1404 } 1405 1406 /** 1407 * Customize Nav Menus Panel Class 1408 * 1409 * Needed to add screen options. 1410 * 1411 * @since 4.3.0 1412 */ 1413 class WP_Customize_Nav_Menus_Panel extends WP_Customize_Panel { 1414 1415 /** 1416 * Control type. 1417 * 1418 * @since 4.3.0 1419 * 1420 * @access public 1421 * @var string 1422 */ 1423 public $type = 'nav_menus'; 1424 1425 /** 1426 * Render screen options for Menus. 1427 * 1428 * @since 4.3.0 1429 */ 1430 public function render_screen_options() { 1431 // Essentially adds the screen options. 1432 add_filter( 'manage_nav-menus_columns', array( $this, 'wp_nav_menu_manage_columns' ) ); 1433 1434 // Display screen options. 1435 $screen = WP_Screen::get( 'nav-menus.php' ); 1436 $screen->render_screen_options(); 1437 } 1438 1439 /** 1440 * Returns the advanced options for the nav menus page. 1441 * 1442 * Link title attribute added as it's a relatively advanced concept for new users. 1443 * 1444 * @since 4.3.0 1445 * 1446 * @return array The advanced menu properties. 1447 */ 1448 function wp_nav_menu_manage_columns() { 1449 return array( 1450 '_title' => __( 'Show advanced menu properties' ), 1451 'cb' => '<input type="checkbox" />', 1452 'link-target' => __( 'Link Target' ), 1453 'attr-title' => __( 'Title Attribute' ), 1454 'css-classes' => __( 'CSS Classes' ), 1455 'xfn' => __( 'Link Relationship (XFN)' ), 1456 'description' => __( 'Description' ), 1457 ); 1458 } 1459 1460 /** 1461 * An Underscore (JS) template for this panel's content (but not its container). 1462 * 1463 * Class variables for this panel class are available in the `data` JS object; 1464 * export custom variables by overriding {@see WP_Customize_Panel::json()}. 1465 * 1466 * @since 4.3.0 1467 * 1468 * @see WP_Customize_Panel::print_template() 1469 * 1470 * @since 4.3.0 1471 */ 1472 protected function content_template() { 1473 ?> 1474 <li class="panel-meta customize-info accordion-section <# if ( ! data.description ) { #> cannot-expand<# } #>"> 1475 <button type="button" class="customize-panel-back" tabindex="-1"> 1476 <span class="screen-reader-text"><?php _e( 'Back' ); ?></span> 1477 </button> 1478 <div class="accordion-section-title"> 1479 <span class="preview-notice"> 1480 <?php 1481 /* translators: %s is the site/panel title in the Customizer */ 1482 printf( __( 'You are customizing %s' ), '<strong class="panel-title">{{ data.title }}</strong>' ); 1483 ?> 1484 </span> 1485 <button type="button" class="customize-screen-options-toggle" aria-expanded="false"> 1486 <span class="screen-reader-text"><?php _e( 'Menu Options' ); ?></span> 1487 </button> 1488 <button type="button" class="customize-help-toggle dashicons dashicons-editor-help" aria-expanded="false"> 1489 <span class="screen-reader-text"><?php _e( 'Help' ); ?></span> 1490 </button> 1491 </div> 1492 <# if ( data.description ) { #> 1493 <div class="description customize-panel-description">{{{ data.description }}}</div> 1494 <# } #> 1495 <?php $this->render_screen_options(); ?> 1496 </li> 1497 <?php 1498 } 1499 } 1500 1501 /** 1502 * Customize Nav Menu Control Class 1503 * 1504 * @since 4.3.0 1505 */ 1506 class WP_Customize_Nav_Menu_Control extends WP_Customize_Control { 1507 1508 /** 1509 * Control type. 1510 * 1511 * @since 4.3.0 1512 * 1513 * @access public 1514 * @var string 1515 */ 1516 public $type = 'nav_menu'; 1517 1518 /** 1519 * The nav menu setting. 1520 * 1521 * @since 4.3.0 1522 * 1523 * @var WP_Customize_Nav_Menu_Setting 1524 */ 1525 public $setting; 1526 1527 /** 1528 * Don't render the control's content - it uses a JS template instead. 1529 * 1530 * @since 4.3.0 1531 */ 1532 public function render_content() {} 1533 1534 /** 1535 * JS/Underscore template for the control UI. 1536 * 1537 * @since 4.3.0 1538 */ 1539 public function content_template() { 1540 ?> 1541 <button type="button" class="button-secondary add-new-menu-item"> 1542 <?php _e( 'Add Items' ); ?> 1543 </button> 1544 <button type="button" class="not-a-button reorder-toggle"> 1545 <span class="reorder"><?php _ex( 'Reorder', 'Reorder menu items in Customizer' ); ?></span> 1546 <span class="reorder-done"><?php _ex( 'Done', 'Cancel reordering menu items in Customizer' ); ?></span> 1547 </button> 1548 <span class="add-menu-item-loading spinner"></span> 1549 <span class="menu-delete-item"> 1550 <button type="button" class="not-a-button menu-delete"> 1551 <?php _e( 'Delete menu' ); ?> <span class="screen-reader-text">{{ data.menu_name }}</span> 1552 </button> 1553 </span> 1554 <?php if ( current_theme_supports( 'menus' ) ) : ?> 1555 <ul class="menu-settings"> 1556 <li class="customize-control"> 1557 <span class="customize-control-title"><?php _e( 'Menu locations' ); ?></span> 1558 </li> 1559 1560 <?php foreach ( get_registered_nav_menus() as $location => $description ) : ?> 1561 <li class="customize-control customize-control-checkbox assigned-menu-location"> 1562 <label> 1563 <input type="checkbox" data-menu-id="{{ data.menu_id }}" data-location-id="<?php echo esc_attr( $location ); ?>" class="menu-location" /> <?php echo $description; ?> 1564 <span class="theme-location-set"><?php printf( _x( '(Current: %s)', 'Current menu location' ), '<span class="current-menu-location-name-' . esc_attr( $location ) . '"></span>' ); ?></span> 1565 </label> 1566 </li> 1567 <?php endforeach; ?> 1568 1569 </ul> 1570 <?php endif; ?> 1571 <p> 1572 <label> 1573 <input type="checkbox" class="auto_add"> 1574 <?php _e( 'Automatically add new top-level pages to this menu.' ) ?> 1575 </label> 1576 </p> 1577 <?php 1578 } 1579 1580 /** 1581 * Return params for this control. 1582 * 1583 * @since 4.3.0 1584 * 1585 * @return array 1586 */ 1587 function json() { 1588 $exported = parent::json(); 1589 $exported['menu_id'] = $this->setting->term_id; 1590 1591 return $exported; 1592 } 1593 } 1594 1595 /** 1596 * Customize control to represent the name field for a given menu. 1597 * 1598 * @since 4.3.0 1599 */ 1600 class WP_Customize_Nav_Menu_Item_Control extends WP_Customize_Control { 1601 1602 /** 1603 * Control type. 1604 * 1605 * @since 4.3.0 1606 * 1607 * @access public 1608 * @var string 1609 */ 1610 public $type = 'nav_menu_item'; 1611 1612 /** 1613 * The nav menu item setting. 1614 * 1615 * @since 4.3.0 1616 * 1617 * @var WP_Customize_Nav_Menu_Item_Setting 1618 */ 1619 public $setting; 1620 1621 /** 1622 * Constructor. 1623 * 1624 * @since 4.3.0 1625 * 1626 * @uses WP_Customize_Control::__construct() 1627 * 1628 * @param WP_Customize_Manager $manager An instance of the WP_Customize_Manager class. 1629 * @param string $id The control ID. 1630 * @param array $args Optional. Overrides class property defaults. 1631 */ 1632 public function __construct( $manager, $id, $args = array() ) { 1633 parent::__construct( $manager, $id, $args ); 1634 } 1635 1636 /** 1637 * Don't render the control's content - it's rendered with a JS template. 1638 * 1639 * @since 4.3.0 1640 */ 1641 public function render_content() {} 1642 1643 /** 1644 * JS/Underscore template for the control UI. 1645 * 1646 * @since 4.3.0 1647 */ 1648 public function content_template() { 1649 ?> 1650 <dl class="menu-item-bar"> 1651 <dt class="menu-item-handle"> 1652 <span class="item-type">{{ data.item_type_label }}</span> 1653 <span class="item-title"> 1654 <span class="spinner"></span> 1655 <span class="menu-item-title">{{ data.title }}</span> 1656 </span> 1657 <span class="item-controls"> 1658 <button type="button" class="not-a-button item-edit"><span class="screen-reader-text"><?php _e( 'Edit Menu Item' ); ?></span></button> 1659 <button type="button" class="not-a-button item-delete submitdelete deletion"><span class="screen-reader-text"><?php _e( 'Remove Menu Item' ); ?></span></button> 1660 </span> 1661 </dt> 1662 </dl> 1663 1664 <div class="menu-item-settings" id="menu-item-settings-{{ data.menu_item_id }}"> 1665 <# if ( 'custom' === data.item_type ) { #> 1666 <p class="field-url description description-thin"> 1667 <label for="edit-menu-item-url-{{ data.menu_item_id }}"> 1668 <?php _e( 'URL' ); ?><br /> 1669 <input class="widefat code edit-menu-item-url" type="text" id="edit-menu-item-url-{{ data.menu_item_id }}" name="menu-item-url" /> 1670 </label> 1671 </p> 1672 <# } #> 1673 <p class="description description-thin"> 1674 <label for="edit-menu-item-title-{{ data.menu_item_id }}"> 1675 <?php _e( 'Navigation Label' ); ?><br /> 1676 <input type="text" id="edit-menu-item-title-{{ data.menu_item_id }}" class="widefat edit-menu-item-title" name="menu-item-title" /> 1677 </label> 1678 </p> 1679 <p class="field-link-target description description-thin"> 1680 <label for="edit-menu-item-target-{{ data.menu_item_id }}"> 1681 <input type="checkbox" id="edit-menu-item-target-{{ data.menu_item_id }}" class="edit-menu-item-target" value="_blank" name="menu-item-target" /> 1682 <?php _e( 'Open link in a new tab' ); ?> 1683 </label> 1684 </p> 1685 <p class="field-attr-title description description-thin"> 1686 <label for="edit-menu-item-attr-title-{{ data.menu_item_id }}"> 1687 <?php _e( 'Title Attribute' ); ?><br /> 1688 <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" /> 1689 </label> 1690 </p> 1691 <p class="field-css-classes description description-thin"> 1692 <label for="edit-menu-item-classes-{{ data.menu_item_id }}"> 1693 <?php _e( 'CSS Classes' ); ?><br /> 1694 <input type="text" id="edit-menu-item-classes-{{ data.menu_item_id }}" class="widefat code edit-menu-item-classes" name="menu-item-classes" /> 1695 </label> 1696 </p> 1697 <p class="field-xfn description description-thin"> 1698 <label for="edit-menu-item-xfn-{{ data.menu_item_id }}"> 1699 <?php _e( 'Link Relationship (XFN)' ); ?><br /> 1700 <input type="text" id="edit-menu-item-xfn-{{ data.menu_item_id }}" class="widefat code edit-menu-item-xfn" name="menu-item-xfn" /> 1701 </label> 1702 </p> 1703 <p class="field-description description description-thin"> 1704 <label for="edit-menu-item-description-{{ data.menu_item_id }}"> 1705 <?php _e( 'Description' ); ?><br /> 1706 <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> 1707 <span class="description"><?php _e( 'The description will be displayed in the menu if the current theme supports it.' ); ?></span> 1708 </label> 1709 </p> 1710 1711 <div class="menu-item-actions description-thin submitbox"> 1712 <# if ( 'custom' != data.item_type && '' != data.original_title ) { #> 1713 <p class="link-to-original"> 1714 <?php printf( __( 'Original: %s' ), '<a class="original-link" href="{{ data.url }}">{{{ data.original_title }}}</a>' ); ?> 1715 </p> 1716 <# } #> 1717 1718 <button type="button" class="not-a-button item-delete submitdelete deletion"><?php _e( 'Remove' ); ?></button> 1719 <span class="spinner"></span> 1720 </div> 1721 <input type="hidden" name="menu-item-db-id[{{ data.menu_item_id }}]" class="menu-item-data-db-id" value="{{ data.menu_item_id }}" /> 1722 <input type="hidden" name="menu-item-parent-id[{{ data.menu_item_id }}]" class="menu-item-data-parent-id" value="{{ data.parent }}" /> 1723 </div><!-- .menu-item-settings--> 1724 <ul class="menu-item-transport"></ul> 1725 <?php 1726 } 1727 1728 /** 1729 * Return params for this control. 1730 * 1731 * @since 4.3.0 1732 * 1733 * @return array 1734 */ 1735 function json() { 1736 $exported = parent::json(); 1737 $exported['menu_item_id'] = $this->setting->post_id; 1738 1739 return $exported; 1740 } 1741 } 1742 1743 /** 1744 * Customize Menu Location Control Class 1745 * 1746 * This custom control is only needed for JS. 1747 * 1748 * @since 4.3.0 1749 */ 1750 class WP_Customize_Nav_Menu_Location_Control extends WP_Customize_Control { 1751 1752 /** 1753 * Control type. 1754 * 1755 * @since 4.3.0 1756 * 1757 * @access public 1758 * @var string 1759 */ 1760 public $type = 'nav_menu_location'; 1761 1762 /** 1763 * Location ID. 1764 * 1765 * @since 4.3.0 1766 * 1767 * @access public 1768 * @var string 1769 */ 1770 public $location_id = ''; 1771 1772 /** 1773 * Refresh the parameters passed to JavaScript via JSON. 1774 * 1775 * @since 4.3.0 1776 * 1777 * @uses WP_Customize_Control::to_json() 1778 */ 1779 public function to_json() { 1780 parent::to_json(); 1781 $this->json['locationId'] = $this->location_id; 1782 } 1783 1784 /** 1785 * Render content just like a normal select control. 1786 * 1787 * @since 4.3.0 1788 */ 1789 public function render_content() { 1790 if ( empty( $this->choices ) ) { 1791 return; 1792 } 1793 ?> 1794 <label> 1795 <?php if ( ! empty( $this->label ) ) : ?> 1796 <span class="customize-control-title"><?php echo esc_html( $this->label ); ?></span> 1797 <?php endif; ?> 1798 1799 <?php if ( ! empty( $this->description ) ) : ?> 1800 <span class="description customize-control-description"><?php echo $this->description; ?></span> 1801 <?php endif; ?> 1802 1803 <select <?php $this->link(); ?>> 1804 <?php 1805 foreach ( $this->choices as $value => $label ) : 1806 echo '<option value="' . esc_attr( $value ) . '"' . selected( $this->value(), $value, false ) . '>' . $label . '</option>'; 1807 endforeach; 1808 ?> 1809 </select> 1810 </label> 1811 <?php 1812 } 1813 } 1814 1815 /** 1816 * Customize control to represent the name field for a given menu. 1817 * 1818 * @since 4.3.0 1819 */ 1820 class WP_Customize_Nav_Menu_Name_Control extends WP_Customize_Control { 1821 1822 /** 1823 * Type of control, used by JS. 1824 * 1825 * @since 4.3.0 1826 * 1827 * @var string 1828 */ 1829 public $type = 'nav_menu_name'; 1830 1831 /** 1832 * No-op since we're using JS template. 1833 * 1834 * @since 4.3.0 1835 */ 1836 protected function render_content() {} 1837 1838 /** 1839 * Render the Underscore template for this control. 1840 * 1841 * @since 4.3.0 1842 */ 1843 protected function content_template() { 1844 ?> 1845 <label> 1846 <input type="text" class="menu-name-field live-update-section-title" /> 1847 </label> 1848 <?php 1849 } 1850 } 1851 1852 /** 1853 * Customize control class for new menus. 1854 * 1855 * @since 4.3.0 1856 */ 1857 class WP_New_Menu_Customize_Control extends WP_Customize_Control { 1858 1859 /** 1860 * Control type. 1861 * 1862 * @since 4.3.0 1863 * 1864 * @access public 1865 * @var string 1866 */ 1867 public $type = 'new_menu'; 1868 1869 /** 1870 * Render the control's content. 1871 * 1872 * @since 4.3.0 1873 */ 1874 public function render_content() { 1875 ?> 1876 <button type="button" class="button button-primary" id="create-new-menu-submit"><?php _e( 'Create Menu' ); ?></button> 1877 <span class="spinner"></span> 1878 <?php 1879 } 1880 } -
trunk/src/wp-includes/class-wp-customize-manager.php
r32744 r32806 50 50 public $widgets; 51 51 52 /** 53 * Methods and properties deailing with managing nav menus in the Customizer. 54 * 55 * @var WP_Customize_Nav_Menus 56 */ 57 public $nav_menus; 58 52 59 protected $settings = array(); 53 60 protected $containers = array(); … … 105 112 require_once( ABSPATH . WPINC . '/class-wp-customize-control.php' ); 106 113 require_once( ABSPATH . WPINC . '/class-wp-customize-widgets.php' ); 114 require_once( ABSPATH . WPINC . '/class-wp-customize-nav-menus.php' ); 107 115 108 116 $this->widgets = new WP_Customize_Widgets( $this ); 117 $this->nav_menus = new WP_Customize_Nav_Menus( $this ); 109 118 110 119 add_filter( 'wp_die_handler', array( $this, 'wp_die_handler' ) ); … … 1482 1491 foreach ( array( 'color', 'image', 'position_x', 'repeat', 'attachment' ) as $prop ) { 1483 1492 $this->get_setting( 'background_' . $prop )->transport = 'postMessage'; 1484 }1485 }1486 1487 /* Nav Menus */1488 1489 $locations = get_registered_nav_menus();1490 $menus = wp_get_nav_menus();1491 $num_locations = count( array_keys( $locations ) );1492 1493 if ( 1 == $num_locations ) {1494 $description = __( 'Your theme supports one menu. Select which menu you would like to use.' );1495 } else {1496 $description = sprintf( _n( 'Your theme supports %s menu. Select which menu appears in each location.', 'Your theme supports %s menus. Select which menu appears in each location.', $num_locations ), number_format_i18n( $num_locations ) );1497 }1498 1499 $this->add_section( 'nav', array(1500 'title' => __( 'Navigation' ),1501 'theme_supports' => 'menus',1502 'priority' => 100,1503 'description' => $description . "\n\n" . __( 'You can edit your menu content on the Menus screen in the Appearance section.' ),1504 ) );1505 1506 if ( $menus ) {1507 $choices = array( '' => __( '— Select —' ) );1508 foreach ( $menus as $menu ) {1509 $choices[ $menu->term_id ] = wp_html_excerpt( $menu->name, 40, '…' );1510 }1511 1512 foreach ( $locations as $location => $description ) {1513 $menu_setting_id = "nav_menu_locations[{$location}]";1514 1515 $this->add_setting( $menu_setting_id, array(1516 'sanitize_callback' => 'absint',1517 'theme_supports' => 'menus',1518 ) );1519 1520 $this->add_control( $menu_setting_id, array(1521 'label' => $description,1522 'section' => 'nav',1523 'type' => 'select',1524 'choices' => $choices,1525 ) );1526 1493 } 1527 1494 } -
trunk/src/wp-includes/class-wp-customize-section.php
r32658 r32806 502 502 } 503 503 } 504 505 /** 506 * Customize Menu Section Class 507 * 508 * Custom section only needed in JS. 509 * 510 * @since 4.3.0 511 */ 512 class WP_Customize_Nav_Menu_Section extends WP_Customize_Section { 513 514 /** 515 * Control type. 516 * 517 * @since 4.3.0 518 * 519 * @access public 520 * @var string 521 */ 522 public $type = 'nav_menu'; 523 524 /** 525 * Get section params for JS. 526 * 527 * @since 4.3.0 528 * 529 * @return array 530 */ 531 function json() { 532 $exported = parent::json(); 533 $exported['menu_id'] = intval( preg_replace( '/^nav_menu\[(\d+)\]/', '$1', $this->id ) ); 534 535 return $exported; 536 } 537 } 538 539 /** 540 * Customize Menu Section Class 541 * 542 * Implements the new-menu-ui toggle button instead of a regular section. 543 * 544 * @since 4.3.0 545 */ 546 class WP_Customize_New_Menu_Section extends WP_Customize_Section { 547 548 /** 549 * Control type. 550 * 551 * @since 4.3.0 552 * 553 * @access public 554 * @var string 555 */ 556 public $type = 'new_menu'; 557 558 /** 559 * Render the section, and the controls that have been added to it. 560 * 561 * @since 4.3.0 562 */ 563 protected function render() { 564 ?> 565 <li id="accordion-section-<?php echo esc_attr( $this->id ); ?>" class="accordion-section-new-menu"> 566 <button type="button" class="button-secondary add-new-menu-item add-menu-toggle"> 567 <?php echo esc_html( $this->title ); ?> 568 <span class="screen-reader-text"><?php _e( 'Press return or enter to open' ); ?></span> 569 </button> 570 <ul class="new-menu-section-content"></ul> 571 </li> 572 <?php 573 } 574 } -
trunk/src/wp-includes/class-wp-customize-setting.php
r32767 r32806 631 631 } 632 632 } 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 */ 645 class 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 * @since 4.3.0 657 * 658 * @var string 659 */ 660 public $type = self::TYPE; 661 662 /** 663 * Default setting value. 664 * 665 * @since 4.3.0 666 * 667 * @see wp_setup_nav_menu_item() 668 * @var array 669 */ 670 public $default = array( 671 // The $menu_item_data for wp_update_nav_menu_item(). 672 'object_id' => 0, 673 'object' => '', // Taxonomy name. 674 'menu_item_parent' => 0, // A.K.A. menu-item-parent-id; note that post_parent is different, and not included. 675 'position' => 0, // A.K.A. menu_order. 676 'type' => 'custom', // Note that type_label is not included here. 677 'title' => '', 678 'url' => '', 679 'target' => '', 680 'attr_title' => '', 681 'description' => '', 682 'classes' => '', 683 'xfn' => '', 684 'status' => 'publish', 685 'original_title' => '', 686 'nav_menu_term_id' => 0, // This will be supplied as the $menu_id arg for wp_update_nav_menu_item(). 687 // @todo also expose invalid? 688 ); 689 690 /** 691 * Default transport. 692 * 693 * @since 4.3.0 694 * 695 * @var string 696 */ 697 public $transport = 'postMessage'; 698 699 /** 700 * The post ID represented by this setting instance. This is the db_id. 701 * 702 * A negative value represents a placeholder ID for a new menu not yet saved. 703 * 704 * @todo Should this be $db_id, and also use this for WP_Customize_Nav_Menu_Setting::$term_id 705 * 706 * @since 4.3.0 707 * 708 * @var int 709 */ 710 public $post_id; 711 712 /** 713 * Previous (placeholder) post ID used before creating a new menu item. 714 * 715 * This value will be exported to JS via the customize_save_response filter 716 * so that JavaScript can update the settings to refer to the newly-assigned 717 * post ID. This value is always negative to indicate it does not refer to 718 * a real post. 719 * 720 * @since 4.3.0 721 * 722 * @see WP_Customize_Nav_Menu_Item_Setting::update() 723 * @see WP_Customize_Nav_Menu_Item_Setting::amend_customize_save_response() 724 * 725 * @var int 726 */ 727 public $previous_post_id; 728 729 /** 730 * When previewing or updating a menu item, this stores the previous nav_menu_term_id 731 * which ensures that we can apply the proper filters. 732 * 733 * @since 4.3.0 734 * 735 * @var int 736 */ 737 public $original_nav_menu_term_id; 738 739 /** 740 * Whether or not preview() was called. 741 * 742 * @since 4.3.0 743 * 744 * @var bool 745 */ 746 protected $is_previewed = false; 747 748 /** 749 * Whether or not update() was called. 750 * 751 * @since 4.3.0 752 * 753 * @var bool 754 */ 755 protected $is_updated = false; 756 757 /** 758 * Status for calling the update method, used in customize_save_response filter. 759 * 760 * When status is inserted, the placeholder post ID is stored in $previous_post_id. 761 * When status is error, the error is stored in $update_error. 762 * 763 * @since 4.3.0 764 * 765 * @see WP_Customize_Nav_Menu_Item_Setting::update() 766 * @see WP_Customize_Nav_Menu_Item_Setting::amend_customize_save_response() 767 * 768 * @var string updated|inserted|deleted|error 769 */ 770 public $update_status; 771 772 /** 773 * Any error object returned by wp_update_nav_menu_item() when setting is updated. 774 * 775 * @since 4.3.0 776 * 777 * @see WP_Customize_Nav_Menu_Item_Setting::update() 778 * @see WP_Customize_Nav_Menu_Item_Setting::amend_customize_save_response() 779 * 780 * @var WP_Error 781 */ 782 public $update_error; 783 784 /** 785 * Constructor. 786 * 787 * Any supplied $args override class property defaults. 788 * 789 * @since 4.3.0 790 * 791 * @param WP_Customize_Manager $manager Manager instance. 792 * @param string $id An specific ID of the setting. Can be a 793 * theme mod or option name. 794 * @param array $args Optional. Setting arguments. 795 * @throws Exception If $id is not valid for this setting type. 796 */ 797 public function __construct( WP_Customize_Manager $manager, $id, array $args = array() ) { 798 if ( empty( $manager->nav_menus ) ) { 799 throw new Exception( 'Expected WP_Customize_Manager::$nav_menus to be set.' ); 800 } 801 802 if ( ! preg_match( self::ID_PATTERN, $id, $matches ) ) { 803 throw new Exception( "Illegal widget setting ID: $id" ); 804 } 805 806 $this->post_id = intval( $matches['id'] ); 807 808 $menu = $this->value(); 809 $this->original_nav_menu_term_id = $menu['nav_menu_term_id']; 810 811 parent::__construct( $manager, $id, $args ); 812 } 813 814 /** 815 * Get the instance data for a given widget setting. 816 * 817 * @since 4.3.0 818 * 819 * @see wp_setup_nav_menu_item() 820 * 821 * @return array 822 */ 823 public function value() { 824 if ( $this->is_previewed && $this->_previewed_blog_id === get_current_blog_id() ) { 825 $undefined = new stdClass(); // Symbol. 826 $post_value = $this->post_value( $undefined ); 827 828 if ( $undefined === $post_value ) { 829 $value = $this->_original_value; 830 } else { 831 $value = $post_value; 832 } 833 } else { 834 $value = false; 835 836 // Note that a ID of less than one indicates a nav_menu not yet inserted. 837 if ( $this->post_id > 0 ) { 838 $post = get_post( $this->post_id ); 839 if ( $post && self::POST_TYPE === $post->post_type ) { 840 $item = wp_setup_nav_menu_item( $post ); 841 $value = wp_array_slice_assoc( 842 (array) $item, 843 array_keys( $this->default ) 844 ); 845 $value['position'] = $item->menu_order; 846 $value['status'] = $item->post_status; 847 $value['original_title'] = ''; 848 849 $menus = wp_get_post_terms( $post->ID, WP_Customize_Nav_Menu_Setting::TAXONOMY, array( 850 'fields' => 'ids', 851 ) ); 852 853 if ( ! empty( $menus ) ) { 854 $value['nav_menu_term_id'] = array_shift( $menus ); 855 } else { 856 $value['nav_menu_term_id'] = 0; 857 } 858 859 if ( 'post_type' === $value['type'] ) { 860 $original_title = get_the_title( $value['object_id'] ); 861 } else if ( 'taxonomy' === $value['type'] ) { 862 $original_title = get_term_field( 'name', $value['object_id'], $value['object'], 'raw' ); 863 if ( is_wp_error( $original_title ) ) { 864 $original_title = ''; 865 } 866 } 867 868 if ( ! empty( $original_title ) ) { 869 $value['original_title'] = $original_title; 870 } 871 } 872 } 873 874 if ( ! is_array( $value ) ) { 875 $value = $this->default; 876 } 877 } 878 879 if ( is_array( $value ) ) { 880 foreach ( array( 'object_id', 'menu_item_parent', 'nav_menu_term_id' ) as $key ) { 881 $value[ $key ] = intval( $value[ $key ] ); 882 } 883 } 884 885 return $value; 886 } 887 888 /** 889 * Handle previewing the setting. 890 * 891 * @since 4.3.0 892 * 893 * @see WP_Customize_Manager::post_value() 894 */ 895 public function preview() { 896 if ( $this->is_previewed ) { 897 return; 898 } 899 900 $this->is_previewed = true; 901 $this->_original_value = $this->value(); 902 $this->original_nav_menu_term_id = $this->_original_value['nav_menu_term_id']; 903 $this->_previewed_blog_id = get_current_blog_id(); 904 905 add_filter( 'wp_get_nav_menu_items', array( $this, 'filter_wp_get_nav_menu_items' ), 10, 3 ); 906 907 $sort_callback = array( __CLASS__, 'sort_wp_get_nav_menu_items' ); 908 if ( ! has_filter( 'wp_get_nav_menu_items', $sort_callback ) ) { 909 add_filter( 'wp_get_nav_menu_items', array( __CLASS__, 'sort_wp_get_nav_menu_items' ), 1000, 3 ); 910 } 911 912 // @todo Add get_post_metadata filters for plugins to add their data. 913 } 914 915 /** 916 * Filter the wp_get_nav_menu_items() result to supply the previewed menu items. 917 * 918 * @since 4.3.0 919 * 920 * @see wp_get_nav_menu_items() 921 * 922 * @param array $items An array of menu item post objects. 923 * @param object $menu The menu object. 924 * @param array $args An array of arguments used to retrieve menu item objects. 925 * @return array Array of menu items, 926 */ 927 function filter_wp_get_nav_menu_items( $items, $menu, $args ) { 928 $this_item = $this->value(); 929 $current_nav_menu_term_id = $this_item['nav_menu_term_id']; 930 unset( $this_item['nav_menu_term_id'] ); 931 932 $should_filter = ( 933 $menu->term_id === $this->original_nav_menu_term_id 934 || 935 $menu->term_id === $current_nav_menu_term_id 936 ); 937 if ( ! $should_filter ) { 938 return $items; 939 } 940 941 // Handle deleted menu item, or menu item moved to another menu. 942 $should_remove = ( 943 false === $this_item 944 || 945 ( 946 $this->original_nav_menu_term_id === $menu->term_id 947 && 948 $current_nav_menu_term_id !== $this->original_nav_menu_term_id 949 ) 950 ); 951 if ( $should_remove ) { 952 $filtered_items = array(); 953 foreach ( $items as $item ) { 954 if ( $item->db_id !== $this->post_id ) { 955 $filtered_items[] = $item; 956 } 957 } 958 return $filtered_items; 959 } 960 961 $mutated = false; 962 $should_update = ( 963 is_array( $this_item ) 964 && 965 $current_nav_menu_term_id === $menu->term_id 966 ); 967 if ( $should_update ) { 968 foreach ( $items as $item ) { 969 if ( $item->db_id === $this->post_id ) { 970 foreach ( get_object_vars( $this->value_as_wp_post_nav_menu_item() ) as $key => $value ) { 971 $item->$key = $value; 972 } 973 $mutated = true; 974 } 975 } 976 977 // Not found so we have to append it.. 978 if ( ! $mutated ) { 979 $items[] = $this->value_as_wp_post_nav_menu_item(); 980 } 981 } 982 983 return $items; 984 } 985 986 /** 987 * Re-apply the tail logic also applied on $items by wp_get_nav_menu_items(). 988 * 989 * @since 4.3.0 990 * 991 * @see wp_get_nav_menu_items() 992 * 993 * @param array $items An array of menu item post objects. 994 * @param object $menu The menu object. 995 * @param array $args An array of arguments used to retrieve menu item objects. 996 * @return array Array of menu items, 997 */ 998 static function sort_wp_get_nav_menu_items( $items, $menu, $args ) { 999 // @todo We should probably re-apply some constraints imposed by $args. 1000 unset( $args['include'] ); 1001 1002 // Remove invalid items only in frontend. 1003 if ( ! is_admin() ) { 1004 $items = array_filter( $items, '_is_valid_nav_menu_item' ); 1005 } 1006 1007 if ( ARRAY_A === $args['output'] ) { 1008 $GLOBALS['_menu_item_sort_prop'] = $args['output_key']; 1009 usort( $items, '_sort_nav_menu_items' ); 1010 $i = 1; 1011 1012 foreach ( $items as $k => $item ) { 1013 $items[ $k ]->$args['output_key'] = $i++; 1014 } 1015 } 1016 1017 return $items; 1018 } 1019 1020 /** 1021 * Get the value emulated into a WP_Post and set up as a nav_menu_item. 1022 * 1023 * @since 4.3.0 1024 * 1025 * @return WP_Post With {@see wp_setup_nav_menu_item()} applied. 1026 */ 1027 public function value_as_wp_post_nav_menu_item() { 1028 $item = (object) $this->value(); 1029 unset( $item->nav_menu_term_id ); 1030 1031 $item->post_status = $item->status; 1032 unset( $item->status ); 1033 1034 $item->post_type = 'nav_menu_item'; 1035 $item->menu_order = $item->position; 1036 unset( $item->position ); 1037 1038 $item->post_author = get_current_user_id(); 1039 1040 if ( $item->title ) { 1041 $item->post_title = $item->title; 1042 } 1043 1044 $item->ID = $this->post_id; 1045 $post = new WP_Post( (object) $item ); 1046 $post = wp_setup_nav_menu_item( $post ); 1047 1048 return $post; 1049 } 1050 1051 /** 1052 * Sanitize an input. 1053 * 1054 * Note that parent::sanitize() erroneously does wp_unslash() on $value, but 1055 * we remove that in this override. 1056 * 1057 * @since 4.3.0 1058 * 1059 * @param array $menu_item_value The value to sanitize. 1060 * @return array|false|null Null if an input isn't valid. False if it is marked for deletion. Otherwise the sanitized value. 1061 */ 1062 public function sanitize( $menu_item_value ) { 1063 // Menu is marked for deletion. 1064 if ( false === $menu_item_value ) { 1065 return $menu_item_value; 1066 } 1067 1068 // Invalid. 1069 if ( ! is_array( $menu_item_value ) ) { 1070 return null; 1071 } 1072 1073 $default = array( 1074 'object_id' => 0, 1075 'object' => '', 1076 'menu_item_parent' => 0, 1077 'position' => 0, 1078 'type' => 'custom', 1079 'title' => '', 1080 'url' => '', 1081 'target' => '', 1082 'attr_title' => '', 1083 'description' => '', 1084 'classes' => '', 1085 'xfn' => '', 1086 'status' => 'publish', 1087 'original_title' => '', 1088 'nav_menu_term_id' => 0, 1089 ); 1090 $menu_item_value = array_merge( $default, $menu_item_value ); 1091 $menu_item_value = wp_array_slice_assoc( $menu_item_value, array_keys( $default ) ); 1092 $menu_item_value['position'] = max( 0, intval( $menu_item_value['position'] ) ); 1093 1094 foreach ( array( 'object_id', 'menu_item_parent', 'nav_menu_term_id' ) as $key ) { 1095 // Note we need to allow negative-integer IDs for previewed objects not inserted yet. 1096 $menu_item_value[ $key ] = intval( $menu_item_value[ $key ] ); 1097 } 1098 1099 foreach ( array( 'type', 'object', 'target' ) as $key ) { 1100 $menu_item_value[ $key ] = sanitize_key( $menu_item_value[ $key ] ); 1101 } 1102 1103 foreach ( array( 'xfn', 'classes' ) as $key ) { 1104 $value = $menu_item_value[ $key ]; 1105 if ( ! is_array( $value ) ) { 1106 $value = explode( ' ', $value ); 1107 } 1108 $menu_item_value[ $key ] = implode( ' ', array_map( 'sanitize_html_class', $value ) ); 1109 } 1110 1111 foreach ( array( 'title', 'attr_title', 'description', 'original_title' ) as $key ) { 1112 // @todo Should esc_attr() the attr_title as well? 1113 $menu_item_value[ $key ] = sanitize_text_field( $menu_item_value[ $key ] ); 1114 } 1115 1116 $menu_item_value['url'] = esc_url_raw( $menu_item_value['url'] ); 1117 if ( ! get_post_status_object( $menu_item_value['status'] ) ) { 1118 $menu_item_value['status'] = 'publish'; 1119 } 1120 1121 /** This filter is documented in wp-includes/class-wp-customize-setting.php */ 1122 return apply_filters( "customize_sanitize_{$this->id}", $menu_item_value, $this ); 1123 } 1124 1125 /** 1126 * Create/update the nav_menu_item post for this setting. 1127 * 1128 * Any created menu items will have their assigned post IDs exported to the client 1129 * via the customize_save_response filter. Likewise, any errors will be exported 1130 * to the client via the customize_save_response() filter. 1131 * 1132 * To delete a menu, the client can send false as the value. 1133 * 1134 * @since 4.3.0 1135 * 1136 * @see wp_update_nav_menu_item() 1137 * 1138 * @param array|false $value The menu item array to update. If false, then the menu item will be deleted entirely. 1139 * See {@see WP_Customize_Nav_Menu_Item_Setting::$default} for what the value should 1140 * consist of. 1141 * @return void 1142 */ 1143 protected function update( $value ) { 1144 if ( $this->is_updated ) { 1145 return; 1146 } 1147 1148 $this->is_updated = true; 1149 $is_placeholder = ( $this->post_id < 0 ); 1150 $is_delete = ( false === $value ); 1151 1152 add_filter( 'customize_save_response', array( $this, 'amend_customize_save_response' ) ); 1153 1154 if ( $is_delete ) { 1155 // If the current setting post is a placeholder, a delete request is a no-op. 1156 if ( $is_placeholder ) { 1157 $this->update_status = 'deleted'; 1158 } else { 1159 $r = wp_delete_post( $this->post_id, true ); 1160 1161 if ( false === $r ) { 1162 $this->update_error = new WP_Error( 'delete_failure' ); 1163 $this->update_status = 'error'; 1164 } else { 1165 $this->update_status = 'deleted'; 1166 } 1167 // @todo send back the IDs for all associated nav menu items deleted, so these settings (and controls) can be removed from Customizer? 1168 } 1169 } else { 1170 1171 // Handle saving menu items for menus that are being newly-created. 1172 if ( $value['nav_menu_term_id'] < 0 ) { 1173 $nav_menu_setting_id = sprintf( 'nav_menu[%s]', $value['nav_menu_term_id'] ); 1174 $nav_menu_setting = $this->manager->get_setting( $nav_menu_setting_id ); 1175 1176 if ( ! $nav_menu_setting || ! ( $nav_menu_setting instanceof WP_Customize_Nav_Menu_Setting ) ) { 1177 $this->update_status = 'error'; 1178 $this->update_error = new WP_Error( 'unexpected_nav_menu_setting' ); 1179 return; 1180 } 1181 1182 if ( false === $nav_menu_setting->save() ) { 1183 $this->update_status = 'error'; 1184 $this->update_error = new WP_Error( 'nav_menu_setting_failure' ); 1185 } 1186 1187 if ( $nav_menu_setting->previous_term_id !== intval( $value['nav_menu_term_id'] ) ) { 1188 $this->update_status = 'error'; 1189 $this->update_error = new WP_Error( 'unexpected_previous_term_id' ); 1190 return; 1191 } 1192 1193 $value['nav_menu_term_id'] = $nav_menu_setting->term_id; 1194 } 1195 1196 // Handle saving a nav menu item that is a child of a nav menu item being newly-created. 1197 if ( $value['menu_item_parent'] < 0 ) { 1198 $parent_nav_menu_item_setting_id = sprintf( 'nav_menu_item[%s]', $value['menu_item_parent'] ); 1199 $parent_nav_menu_item_setting = $this->manager->get_setting( $parent_nav_menu_item_setting_id ); 1200 1201 if ( ! $parent_nav_menu_item_setting || ! ( $parent_nav_menu_item_setting instanceof WP_Customize_Nav_Menu_Item_Setting ) ) { 1202 $this->update_status = 'error'; 1203 $this->update_error = new WP_Error( 'unexpected_nav_menu_item_setting' ); 1204 return; 1205 } 1206 1207 if ( false === $parent_nav_menu_item_setting->save() ) { 1208 $this->update_status = 'error'; 1209 $this->update_error = new WP_Error( 'nav_menu_item_setting_failure' ); 1210 } 1211 1212 if ( $parent_nav_menu_item_setting->previous_post_id !== intval( $value['menu_item_parent'] ) ) { 1213 $this->update_status = 'error'; 1214 $this->update_error = new WP_Error( 'unexpected_previous_post_id' ); 1215 return; 1216 } 1217 1218 $value['menu_item_parent'] = $parent_nav_menu_item_setting->post_id; 1219 } 1220 1221 // Insert or update menu. 1222 $menu_item_data = array( 1223 'menu-item-object-id' => $value['object_id'], 1224 'menu-item-object' => $value['object'], 1225 'menu-item-parent-id' => $value['menu_item_parent'], 1226 'menu-item-position' => $value['position'], 1227 'menu-item-type' => $value['type'], 1228 'menu-item-title' => $value['title'], 1229 'menu-item-url' => $value['url'], 1230 'menu-item-description' => $value['description'], 1231 'menu-item-attr-title' => $value['attr_title'], 1232 'menu-item-target' => $value['target'], 1233 'menu-item-classes' => $value['classes'], 1234 'menu-item-xfn' => $value['xfn'], 1235 'menu-item-status' => $value['status'], 1236 ); 1237 1238 $r = wp_update_nav_menu_item( 1239 $value['nav_menu_term_id'], 1240 $is_placeholder ? 0 : $this->post_id, 1241 $menu_item_data 1242 ); 1243 1244 if ( is_wp_error( $r ) ) { 1245 $this->update_status = 'error'; 1246 $this->update_error = $r; 1247 } else { 1248 if ( $is_placeholder ) { 1249 $this->previous_post_id = $this->post_id; 1250 $this->post_id = $r; 1251 $this->update_status = 'inserted'; 1252 } else { 1253 $this->update_status = 'updated'; 1254 } 1255 } 1256 } 1257 1258 } 1259 1260 /** 1261 * Export data for the JS client. 1262 * 1263 * @since 4.3.0 1264 * 1265 * @see WP_Customize_Nav_Menu_Item_Setting::update() 1266 * 1267 * @param array $data Additional information passed back to the 'saved' event on `wp.customize`. 1268 * @return array 1269 */ 1270 function amend_customize_save_response( $data ) { 1271 if ( ! isset( $data['nav_menu_item_updates'] ) ) { 1272 $data['nav_menu_item_updates'] = array(); 1273 } 1274 1275 $data['nav_menu_item_updates'][] = array( 1276 'post_id' => $this->post_id, 1277 'previous_post_id' => $this->previous_post_id, 1278 'error' => $this->update_error ? $this->update_error->get_error_code() : null, 1279 'status' => $this->update_status, 1280 ); 1281 1282 return $data; 1283 } 1284 } 1285 1286 /** 1287 * Customize Setting to represent a nav_menu. 1288 * 1289 * Subclass of WP_Customize_Setting to represent a nav_menu taxonomy term, and 1290 * the IDs for the nav_menu_items associated with the nav menu. 1291 * 1292 * @since 4.3.0 1293 * 1294 * @see wp_get_nav_menu_object() 1295 * @see WP_Customize_Setting 1296 */ 1297 class WP_Customize_Nav_Menu_Setting extends WP_Customize_Setting { 1298 1299 const ID_PATTERN = '/^nav_menu\[(?P<id>-?\d+)\]$/'; 1300 1301 const TAXONOMY = 'nav_menu'; 1302 1303 const TYPE = 'nav_menu'; 1304 1305 /** 1306 * Setting type. 1307 * 1308 * @since 4.3.0 1309 * 1310 * @var string 1311 */ 1312 public $type = self::TYPE; 1313 1314 /** 1315 * Default setting value. 1316 * 1317 * @since 4.3.0 1318 * 1319 * @see wp_get_nav_menu_object() 1320 * 1321 * @var array 1322 */ 1323 public $default = array( 1324 'name' => '', 1325 'description' => '', 1326 'parent' => 0, 1327 'auto_add' => false, 1328 ); 1329 1330 /** 1331 * Default transport. 1332 * 1333 * @since 4.3.0 1334 * 1335 * @var string 1336 */ 1337 public $transport = 'postMessage'; 1338 1339 /** 1340 * The term ID represented by this setting instance. 1341 * 1342 * A negative value represents a placeholder ID for a new menu not yet saved. 1343 * 1344 * @since 4.3.0 1345 * 1346 * @var int 1347 */ 1348 public $term_id; 1349 1350 /** 1351 * Previous (placeholder) term ID used before creating a new menu. 1352 * 1353 * This value will be exported to JS via the customize_save_response filter 1354 * so that JavaScript can update the settings to refer to the newly-assigned 1355 * term ID. This value is always negative to indicate it does not refer to 1356 * a real term. 1357 * 1358 * @since 4.3.0 1359 * 1360 * @see WP_Customize_Nav_Menu_Setting::update() 1361 * @see WP_Customize_Nav_Menu_Setting::amend_customize_save_response() 1362 * 1363 * @var int 1364 */ 1365 public $previous_term_id; 1366 1367 /** 1368 * Whether or not preview() was called. 1369 * 1370 * @since 4.3.0 1371 * 1372 * @var bool 1373 */ 1374 protected $is_previewed = false; 1375 1376 /** 1377 * Whether or not update() was called. 1378 * 1379 * @since 4.3.0 1380 * 1381 * @var bool 1382 */ 1383 protected $is_updated = false; 1384 1385 /** 1386 * Status for calling the update method, used in customize_save_response filter. 1387 * 1388 * When status is inserted, the placeholder term ID is stored in $previous_term_id. 1389 * When status is error, the error is stored in $update_error. 1390 * 1391 * @since 4.3.0 1392 * 1393 * @see WP_Customize_Nav_Menu_Setting::update() 1394 * @see WP_Customize_Nav_Menu_Setting::amend_customize_save_response() 1395 * 1396 * @var string updated|inserted|deleted|error 1397 */ 1398 public $update_status; 1399 1400 /** 1401 * Any error object returned by wp_update_nav_menu_object() when setting is updated. 1402 * 1403 * @since 4.3.0 1404 * 1405 * @see WP_Customize_Nav_Menu_Setting::update() 1406 * @see WP_Customize_Nav_Menu_Setting::amend_customize_save_response() 1407 * 1408 * @var WP_Error 1409 */ 1410 public $update_error; 1411 1412 /** 1413 * Constructor. 1414 * 1415 * Any supplied $args override class property defaults. 1416 * 1417 * @since 4.3.0 1418 * 1419 * @param WP_Customize_Manager $manager Manager instance. 1420 * @param string $id An specific ID of the setting. Can be a 1421 * theme mod or option name. 1422 * @param array $args Optional. Setting arguments. 1423 * @throws Exception If $id is not valid for this setting type. 1424 */ 1425 public function __construct( WP_Customize_Manager $manager, $id, array $args = array() ) { 1426 if ( empty( $manager->nav_menus ) ) { 1427 throw new Exception( 'Expected WP_Customize_Manager::$nav_menus to be set.' ); 1428 } 1429 1430 if ( ! preg_match( self::ID_PATTERN, $id, $matches ) ) { 1431 throw new Exception( "Illegal widget setting ID: $id" ); 1432 } 1433 1434 $this->term_id = intval( $matches['id'] ); 1435 1436 parent::__construct( $manager, $id, $args ); 1437 } 1438 1439 /** 1440 * Get the instance data for a given widget setting. 1441 * 1442 * @since 4.3.0 1443 * 1444 * @see wp_get_nav_menu_object() 1445 * 1446 * @return array 1447 */ 1448 public function value() { 1449 if ( $this->is_previewed && $this->_previewed_blog_id === get_current_blog_id() ) { 1450 $undefined = new stdClass(); // Symbol. 1451 $post_value = $this->post_value( $undefined ); 1452 1453 if ( $undefined === $post_value ) { 1454 $value = $this->_original_value; 1455 } else { 1456 $value = $post_value; 1457 } 1458 } else { 1459 $value = false; 1460 1461 // Note that a term_id of less than one indicates a nav_menu not yet inserted. 1462 if ( $this->term_id > 0 ) { 1463 $term = wp_get_nav_menu_object( $this->term_id ); 1464 1465 if ( $term ) { 1466 $value = wp_array_slice_assoc( (array) $term, array_keys( $this->default ) ); 1467 1468 $nav_menu_options = (array) get_option( 'nav_menu_options', array() ); 1469 $value['auto_add'] = false; 1470 1471 if ( isset( $nav_menu_options['auto_add'] ) && is_array( $nav_menu_options['auto_add'] ) ) { 1472 $value['auto_add'] = in_array( $term->term_id, $nav_menu_options['auto_add'] ); 1473 } 1474 } 1475 } 1476 1477 if ( ! is_array( $value ) ) { 1478 $value = $this->default; 1479 } 1480 } 1481 return $value; 1482 } 1483 1484 /** 1485 * Handle previewing the setting. 1486 * 1487 * @since 4.3.0 1488 * 1489 * @see WP_Customize_Manager::post_value() 1490 */ 1491 public function preview() { 1492 if ( $this->is_previewed ) { 1493 return; 1494 } 1495 1496 $this->is_previewed = true; 1497 $this->_original_value = $this->value(); 1498 $this->_previewed_blog_id = get_current_blog_id(); 1499 1500 add_filter( 'wp_get_nav_menu_object', array( $this, 'filter_wp_get_nav_menu_object' ), 10, 2 ); 1501 add_filter( 'default_option_nav_menu_options', array( $this, 'filter_nav_menu_options' ) ); 1502 add_filter( 'option_nav_menu_options', array( $this, 'filter_nav_menu_options' ) ); 1503 } 1504 1505 /** 1506 * Filter the wp_get_nav_menu_object() result to supply the previewed menu object. 1507 * 1508 * Requesting a nav_menu object by anything but ID is not supported. 1509 * 1510 * @since 4.3.0 1511 * 1512 * @see wp_get_nav_menu_object() 1513 * 1514 * @param object|null $menu_obj Object returned by wp_get_nav_menu_object(). 1515 * @param string $menu_id ID of the nav_menu term. Requests by slug or name will be ignored. 1516 * @return object|null 1517 */ 1518 function filter_wp_get_nav_menu_object( $menu_obj, $menu_id ) { 1519 $ok = ( 1520 get_current_blog_id() === $this->_previewed_blog_id 1521 && 1522 is_int( $menu_id ) 1523 && 1524 $menu_id === $this->term_id 1525 ); 1526 if ( ! $ok ) { 1527 return $menu_obj; 1528 } 1529 1530 $setting_value = $this->value(); 1531 1532 // Handle deleted menus. 1533 if ( false === $setting_value ) { 1534 return false; 1535 } 1536 1537 // Handle sanitization failure by preventing short-circuiting. 1538 if ( null === $setting_value ) { 1539 return $menu_obj; 1540 } 1541 1542 $menu_obj = (object) array_merge( array( 1543 'term_id' => $this->term_id, 1544 'term_taxonomy_id' => $this->term_id, 1545 'slug' => sanitize_title( $setting_value['name'] ), 1546 'count' => 0, 1547 'term_group' => 0, 1548 'taxonomy' => self::TAXONOMY, 1549 'filter' => 'raw', 1550 ), $setting_value ); 1551 1552 return $menu_obj; 1553 } 1554 1555 /** 1556 * Filter the nav_menu_options option to include this menu's auto_add preference. 1557 * 1558 * @since 4.3.0 1559 * 1560 * @param array $nav_menu_options Nav menu options including auto_add. 1561 * @return array 1562 */ 1563 function filter_nav_menu_options( $nav_menu_options ) { 1564 if ( $this->_previewed_blog_id !== get_current_blog_id() ) { 1565 return $nav_menu_options; 1566 } 1567 1568 $menu = $this->value(); 1569 $nav_menu_options = $this->filter_nav_menu_options_value( 1570 $nav_menu_options, 1571 $this->term_id, 1572 false === $menu ? false : $menu['auto_add'] 1573 ); 1574 1575 return $nav_menu_options; 1576 } 1577 1578 /** 1579 * Sanitize an input. 1580 * 1581 * Note that parent::sanitize() erroneously does wp_unslash() on $value, but 1582 * we remove that in this override. 1583 * 1584 * @since 4.3.0 1585 * 1586 * @param array $value The value to sanitize. 1587 * @return array|false|null Null if an input isn't valid. False if it is marked for deletion. Otherwise the sanitized value. 1588 */ 1589 public function sanitize( $value ) { 1590 // Menu is marked for deletion. 1591 if ( false === $value ) { 1592 return $value; 1593 } 1594 1595 // Invalid. 1596 if ( ! is_array( $value ) ) { 1597 return null; 1598 } 1599 1600 $default = array( 1601 'name' => '', 1602 'description' => '', 1603 'parent' => 0, 1604 'auto_add' => false, 1605 ); 1606 $value = array_merge( $default, $value ); 1607 $value = wp_array_slice_assoc( $value, array_keys( $default ) ); 1608 1609 $value['name'] = trim( esc_html( $value['name'] ) ); // This sanitization code is used in wp-admin/nav-menus.php. 1610 $value['description'] = sanitize_text_field( $value['description'] ); 1611 $value['parent'] = max( 0, intval( $value['parent'] ) ); 1612 $value['auto_add'] = ! empty( $value['auto_add'] ); 1613 1614 /** This filter is documented in wp-includes/class-wp-customize-setting.php */ 1615 return apply_filters( "customize_sanitize_{$this->id}", $value, $this ); 1616 } 1617 1618 /** 1619 * Create/update the nav_menu term for this setting. 1620 * 1621 * Any created menus will have their assigned term IDs exported to the client 1622 * via the customize_save_response filter. Likewise, any errors will be exported 1623 * to the client via the customize_save_response() filter. 1624 * 1625 * To delete a menu, the client can send false as the value. 1626 * 1627 * @since 4.3.0 1628 * 1629 * @see wp_update_nav_menu_object() 1630 * 1631 * @param array|false $value { 1632 * The value to update. Note that slug cannot be updated via wp_update_nav_menu_object(). 1633 * If false, then the menu will be deleted entirely. 1634 * 1635 * @type string $name The name of the menu to save. 1636 * @type string $description The term description. Default empty string. 1637 * @type int $parent The id of the parent term. Default 0. 1638 * @type bool $auto_add Whether pages will auto_add to this menu. Default false. 1639 * } 1640 * @return void 1641 */ 1642 protected function update( $value ) { 1643 if ( $this->is_updated ) { 1644 return; 1645 } 1646 1647 $this->is_updated = true; 1648 $is_placeholder = ( $this->term_id < 0 ); 1649 $is_delete = ( false === $value ); 1650 1651 add_filter( 'customize_save_response', array( $this, 'amend_customize_save_response' ) ); 1652 1653 $auto_add = null; 1654 if ( $is_delete ) { 1655 // If the current setting term is a placeholder, a delete request is a no-op. 1656 if ( $is_placeholder ) { 1657 $this->update_status = 'deleted'; 1658 } else { 1659 $r = wp_delete_nav_menu( $this->term_id ); 1660 1661 if ( is_wp_error( $r ) ) { 1662 $this->update_status = 'error'; 1663 $this->update_error = $r; 1664 } else { 1665 $this->update_status = 'deleted'; 1666 $auto_add = false; 1667 } 1668 } 1669 } else { 1670 // Insert or update menu. 1671 $menu_data = wp_array_slice_assoc( $value, array( 'description', 'parent' ) ); 1672 if ( isset( $value['name'] ) ) { 1673 $menu_data['menu-name'] = $value['name']; 1674 } 1675 1676 $r = wp_update_nav_menu_object( $is_placeholder ? 0 : $this->term_id, $menu_data ); 1677 if ( is_wp_error( $r ) ) { 1678 $this->update_status = 'error'; 1679 $this->update_error = $r; 1680 } else { 1681 if ( $is_placeholder ) { 1682 $this->previous_term_id = $this->term_id; 1683 $this->term_id = $r; 1684 $this->update_status = 'inserted'; 1685 } else { 1686 $this->update_status = 'updated'; 1687 } 1688 1689 $auto_add = $value['auto_add']; 1690 } 1691 } 1692 1693 if ( null !== $auto_add ) { 1694 $nav_menu_options = $this->filter_nav_menu_options_value( 1695 (array) get_option( 'nav_menu_options', array() ), 1696 $this->term_id, 1697 $auto_add 1698 ); 1699 update_option( 'nav_menu_options', $nav_menu_options ); 1700 } 1701 1702 // Make sure that new menus assigned to nav menu locations use their new IDs. 1703 if ( 'inserted' === $this->update_status ) { 1704 foreach ( $this->manager->settings() as $setting ) { 1705 if ( ! preg_match( '/^nav_menu_locations\[/', $setting->id ) ) { 1706 continue; 1707 } 1708 1709 $post_value = $setting->post_value( null ); 1710 if ( ! is_null( $post_value ) && $this->previous_term_id === intval( $post_value ) ) { 1711 $this->manager->set_post_value( $setting->id, $this->term_id ); 1712 $setting->save(); 1713 } 1714 } 1715 } 1716 } 1717 1718 /** 1719 * Update a nav_menu_options array. 1720 * 1721 * @since 4.3.0 1722 * 1723 * @see WP_Customize_Nav_Menu_Setting::filter_nav_menu_options() 1724 * @see WP_Customize_Nav_Menu_Setting::update() 1725 * 1726 * @param array $nav_menu_options Array as returned by get_option( 'nav_menu_options' ). 1727 * @param int $menu_id The term ID for the given menu. 1728 * @param bool $auto_add Whether to auto-add or not. 1729 * @return array 1730 */ 1731 protected function filter_nav_menu_options_value( $nav_menu_options, $menu_id, $auto_add ) { 1732 $nav_menu_options = (array) $nav_menu_options; 1733 if ( ! isset( $nav_menu_options['auto_add'] ) ) { 1734 $nav_menu_options['auto_add'] = array(); 1735 } 1736 1737 $i = array_search( $menu_id, $nav_menu_options['auto_add'] ); 1738 if ( $auto_add && false === $i ) { 1739 array_push( $nav_menu_options['auto_add'], $this->term_id ); 1740 } else if ( ! $auto_add && false !== $i ) { 1741 array_splice( $nav_menu_options['auto_add'], $i, 1 ); 1742 } 1743 1744 return $nav_menu_options; 1745 } 1746 1747 /** 1748 * Export data for the JS client. 1749 * 1750 * @since 4.3.0 1751 * 1752 * @see WP_Customize_Nav_Menu_Setting::update() 1753 * 1754 * @param array $data Additional information passed back to the 'saved' event on `wp.customize`. 1755 * @return array 1756 */ 1757 function amend_customize_save_response( $data ) { 1758 if ( ! isset( $data['nav_menu_updates'] ) ) { 1759 $data['nav_menu_updates'] = array(); 1760 } 1761 1762 $data['nav_menu_updates'][] = array( 1763 'term_id' => $this->term_id, 1764 'previous_term_id' => $this->previous_term_id, 1765 'error' => $this->update_error ? $this->update_error->get_error_code() : null, 1766 'status' => $this->update_status, 1767 ); 1768 1769 return $data; 1770 } 1771 } -
trunk/src/wp-includes/script-loader.php
r32779 r32806 407 407 $scripts->add( 'customize-preview-widgets', "/wp-includes/js/customize-preview-widgets$suffix.js", array( 'jquery', 'wp-util', 'customize-preview' ), false, 1 ); 408 408 409 $scripts->add( 'customize-nav-menus', "/wp-admin/js/customize-nav-menus$suffix.js", array( 'jquery', 'wp-backbone', 'customize-controls', 'accordion', 'nav-menu', 'wp-a11y' ), false, 1 ); 410 $scripts->add( 'customize-preview-nav-menus', "/wp-includes/js/customize-preview-nav-menus$suffix.js", array( 'jquery', 'wp-util', 'customize-preview' ), false, 1 ); 411 409 412 $scripts->add( 'accordion', "/wp-admin/js/accordion$suffix.js", array( 'jquery' ), false, 1 ); 410 413 … … 657 660 658 661 // 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' ) ); 666 667 $styles->add( 'ie', "/wp-admin/css/ie$suffix.css" ); 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( 'customize-nav-menus', "/wp-admin/css/customize-nav-menus$suffix.css", array( 'wp-admin', 'colors' ) ); 669 $styles->add( 'press-this', "/wp-admin/css/press-this$suffix.css", array( 'open-sans', 'buttons' ) ); 670 671 $styles->add( 'ie', "/wp-admin/css/ie$suffix.css" ); 668 672 $styles->add_data( 'ie', 'conditional', 'lte IE 7' ); 669 673 … … 674 678 675 679 // Includes CSS 676 $styles->add( 'admin-bar', "/wp-includes/css/admin-bar$suffix.css", array( 'open-sans', 'dashicons' ) ); 677 $styles->add( 'wp-auth-check', "/wp-includes/css/wp-auth-check$suffix.css", array( 'dashicons' ) ); 678 $styles->add( 'editor-buttons', "/wp-includes/css/editor$suffix.css", array( 'dashicons' ) ); 679 $styles->add( 'media-views', "/wp-includes/css/media-views$suffix.css", array( 'buttons', 'dashicons', 'wp-mediaelement' ) ); 680 $styles->add( 'wp-pointer', "/wp-includes/css/wp-pointer$suffix.css", array( 'dashicons' ) ); 680 $styles->add( 'admin-bar', "/wp-includes/css/admin-bar$suffix.css", array( 'open-sans', 'dashicons' ) ); 681 $styles->add( 'wp-auth-check', "/wp-includes/css/wp-auth-check$suffix.css", array( 'dashicons' ) ); 682 $styles->add( 'editor-buttons', "/wp-includes/css/editor$suffix.css", array( 'dashicons' ) ); 683 $styles->add( 'media-views', "/wp-includes/css/media-views$suffix.css", array( 'buttons', 'dashicons', 'wp-mediaelement' ) ); 684 $styles->add( 'wp-pointer', "/wp-includes/css/wp-pointer$suffix.css", array( 'dashicons' ) ); 685 $styles->add( 'customize-preview', "/wp-includes/css/customize-preview$suffix.css" ); 681 686 682 687 // External libraries and friends … … 696 701 $rtl_styles = array( 697 702 // wp-admin 698 'wp-admin', 'install', 'wp-color-picker', 'customize-controls', 'customize-widgets', ' ie', 'login', 'press-this',703 'wp-admin', 'install', 'wp-color-picker', 'customize-controls', 'customize-widgets', 'customize-nav-menus', 'ie', 'login', 'press-this', 699 704 // wp-includes 700 705 'buttons', 'admin-bar', 'wp-auth-check', 'editor-buttons', 'media-views', 'wp-pointer',
Note: See TracChangeset
for help on using the changeset viewer.