WordPress.org

Make WordPress Core


Ignore:
Timestamp:
02/19/2016 06:40:06 PM (6 years ago)
Author:
westonruter
Message:

Customize: Add selective refresh framework with implementation for widgets and re-implementation for nav menus.

See https://make.wordpress.org/core/2016/02/16/selective-refresh-in-the-customizer/.

Props westonruter, valendesigns, DrewAPicture, ocean90.
Fixes #27355.

File:
1 edited

Legend:

Unmodified
Added
Removed
  • trunk/src/wp-includes/class-wp-customize-widgets.php

    r36414 r36586  
    101101        add_filter( 'is_active_sidebar',                       array( $this, 'tally_sidebars_via_is_active_sidebar_calls' ), 10, 2 );
    102102        add_filter( 'dynamic_sidebar_has_widgets',             array( $this, 'tally_sidebars_via_dynamic_sidebar_calls' ), 10, 2 );
     103
     104        // Selective Refresh.
     105        add_filter( 'customize_dynamic_partial_args',          array( $this, 'customize_dynamic_partial_args' ), 10, 2 );
     106        add_action( 'customize_preview_init',                  array( $this, 'selective_refresh_init' ) );
    103107    }
    104108
     
    683687                'moveWidgetArea'   => $move_widget_area_tpl,
    684688            ),
     689            'selectiveRefresh'     => isset( $this->manager->selective_refresh ),
    685690        );
    686691
     
    763768            'type'       => 'option',
    764769            'capability' => 'edit_theme_options',
    765             'transport'  => 'refresh',
     770            'transport'  => isset( $this->manager->selective_refresh ) ? 'postMessage' : 'refresh',
    766771            'default'    => array(),
    767772        );
     
    885890                'is_disabled'  => $is_disabled,
    886891                'id_base'      => $id_base,
    887                 'transport'    => 'refresh',
     892                'transport'    => isset( $this->manager->selective_refresh ) ? 'postMessage' : 'refresh',
    888893                'width'        => $wp_registered_widget_controls[$widget['id']]['width'],
    889894                'height'       => $wp_registered_widget_controls[$widget['id']]['height'],
     
    10621067            'registeredWidgets'  => $wp_registered_widgets,
    10631068            'l10n'               => array(
    1064                 'widgetTooltip' => __( 'Shift-click to edit this widget.' ),
     1069                'widgetTooltip'  => __( 'Shift-click to edit this widget.' ),
    10651070            ),
     1071            'selectiveRefresh'   => isset( $this->manager->selective_refresh ),
    10661072        );
    10671073        foreach ( $settings['registeredWidgets'] as &$registered_widget ) {
     
    14601466    }
    14611467
    1462     /***************************************************************************
    1463      * Option Update Capturing
    1464      ***************************************************************************/
     1468    /*
     1469     * Selective Refresh Methods
     1470     */
     1471
     1472    /**
     1473     * Filter args for dynamic widget partials.
     1474     *
     1475     * @since 4.5.0
     1476     *
     1477     * @param array|false $partial_args Partial args.
     1478     * @param string      $partial_id  Partial ID.
     1479     * @return array Partial args
     1480     */
     1481    public function customize_dynamic_partial_args( $partial_args, $partial_id ) {
     1482
     1483        if ( preg_match( '/^widget\[.+\]$/', $partial_id ) ) {
     1484            if ( false === $partial_args ) {
     1485                $partial_args = array();
     1486            }
     1487            $partial_args = array_merge(
     1488                $partial_args,
     1489                array(
     1490                    'type' => 'widget',
     1491                    'render_callback' => array( $this, 'render_widget_partial' ),
     1492                    'container_inclusive' => true,
     1493                )
     1494            );
     1495        }
     1496
     1497        return $partial_args;
     1498    }
     1499
     1500    /**
     1501     * Add hooks for selective refresh.
     1502     *
     1503     * @since 4.5.0
     1504     * @access public
     1505     */
     1506    public function selective_refresh_init() {
     1507        if ( ! isset( $this->manager->selective_refresh ) ) {
     1508            return;
     1509        }
     1510
     1511        add_action( 'wp_enqueue_scripts', array( $this, 'customize_preview_enqueue_deps' ) );
     1512        add_filter( 'dynamic_sidebar_params', array( $this, 'filter_dynamic_sidebar_params' ) );
     1513        add_filter( 'wp_kses_allowed_html', array( $this, 'filter_wp_kses_allowed_data_attributes' ) );
     1514        add_action( 'dynamic_sidebar_before', array( $this, 'start_dynamic_sidebar' ) );
     1515        add_action( 'dynamic_sidebar_after', array( $this, 'end_dynamic_sidebar' ) );
     1516    }
     1517
     1518    /**
     1519     * Enqueue scripts for the Customizer preview.
     1520     *
     1521     * @since 4.5.0
     1522     * @access public
     1523     */
     1524    public function customize_preview_enqueue_deps() {
     1525        if ( isset( $this->manager->selective_refresh ) ) {
     1526            $script = wp_scripts()->registered['customize-preview-widgets'];
     1527            $script->deps[] = 'customize-selective-refresh';
     1528        }
     1529
     1530        wp_enqueue_script( 'customize-preview-widgets' );
     1531        wp_enqueue_style( 'customize-preview' );
     1532    }
     1533
     1534    /**
     1535     * Inject selective refresh data attributes into widget container elements.
     1536     *
     1537     * @param array $params {
     1538     *     Dynamic sidebar params.
     1539     *
     1540     *     @type array $args        Sidebar args.
     1541     *     @type array $widget_args Widget args.
     1542     * }
     1543     * @see WP_Customize_Nav_Menus_Partial_Refresh::filter_wp_nav_menu_args()
     1544     *
     1545     * @return array Params.
     1546     */
     1547    public function filter_dynamic_sidebar_params( $params ) {
     1548        $sidebar_args = array_merge(
     1549            array(
     1550                'before_widget' => '',
     1551                'after_widget' => '',
     1552            ),
     1553            $params[0]
     1554        );
     1555
     1556        // Skip widgets not in a registered sidebar or ones which lack a proper wrapper element to attach the data-* attributes to.
     1557        $matches = array();
     1558        $is_valid = (
     1559            isset( $sidebar_args['id'] )
     1560            &&
     1561            is_registered_sidebar( $sidebar_args['id'] )
     1562            &&
     1563            ( isset( $this->current_dynamic_sidebar_id_stack[0] ) && $this->current_dynamic_sidebar_id_stack[0] === $sidebar_args['id'] )
     1564            &&
     1565            preg_match( '#^<(?P<tag_name>\w+)#', $sidebar_args['before_widget'], $matches )
     1566        );
     1567        if ( ! $is_valid ) {
     1568            return $params;
     1569        }
     1570        $this->before_widget_tags_seen[ $matches['tag_name'] ] = true;
     1571
     1572        $context = array(
     1573            'sidebar_id' => $sidebar_args['id'],
     1574        );
     1575        if ( isset( $this->context_sidebar_instance_number ) ) {
     1576            $context['sidebar_instance_number'] = $this->context_sidebar_instance_number;
     1577        } else if ( isset( $sidebar_args['id'] ) && isset( $this->sidebar_instance_count[ $sidebar_args['id'] ] ) ) {
     1578            $context['sidebar_instance_number'] = $this->sidebar_instance_count[ $sidebar_args['id'] ];
     1579        }
     1580
     1581        $attributes = sprintf( ' data-customize-partial-id="%s"', esc_attr( 'widget[' . $sidebar_args['widget_id'] . ']' ) );
     1582        $attributes .= ' data-customize-partial-type="widget"';
     1583        $attributes .= sprintf( ' data-customize-partial-placement-context="%s"', esc_attr( wp_json_encode( $context ) ) );
     1584        $attributes .= sprintf( ' data-customize-widget-id="%s"', esc_attr( $sidebar_args['widget_id'] ) );
     1585        $sidebar_args['before_widget'] = preg_replace( '#^(<\w+)#', '$1 ' . $attributes, $sidebar_args['before_widget'] );
     1586
     1587        $params[0] = $sidebar_args;
     1588        return $params;
     1589    }
     1590
     1591    /**
     1592     * List of the tag names seen for before_widget strings.
     1593     *
     1594     * This is used in the filter_wp_kses_allowed_html filter to ensure that the
     1595     * data-* attributes can be whitelisted.
     1596     *
     1597     * @since 4.5.0
     1598     * @access private
     1599     * @var array
     1600     */
     1601    protected $before_widget_tags_seen = array();
     1602
     1603    /**
     1604     * Ensure that the HTML data-* attributes for selective refresh are allowed by kses.
     1605     *
     1606     * This is needed in case the $before_widget is run through wp_kses() when printed.
     1607     *
     1608     * @since 4.5.0
     1609     * @access public
     1610     *
     1611     * @param array $allowed_html Allowed HTML.
     1612     * @return array Allowed HTML.
     1613     */
     1614    public function filter_wp_kses_allowed_data_attributes( $allowed_html ) {
     1615        foreach ( array_keys( $this->before_widget_tags_seen ) as $tag_name ) {
     1616            if ( ! isset( $allowed_html[ $tag_name ] ) ) {
     1617                $allowed_html[ $tag_name ] = array();
     1618            }
     1619            $allowed_html[ $tag_name ] = array_merge(
     1620                $allowed_html[ $tag_name ],
     1621                array_fill_keys( array(
     1622                    'data-customize-partial-id',
     1623                    'data-customize-partial-type',
     1624                    'data-customize-partial-placement-context',
     1625                    'data-customize-partial-widget-id',
     1626                    'data-customize-partial-options',
     1627                ), true )
     1628            );
     1629        }
     1630        return $allowed_html;
     1631    }
     1632
     1633    /**
     1634     * Keep track of the number of times that dynamic_sidebar() was called for a given sidebar index.
     1635     *
     1636     * This helps facilitate the uncommon scenario where a single sidebar is rendered multiple times on a template.
     1637     *
     1638     * @since 4.5.0
     1639     * @access private
     1640     * @var array
     1641     */
     1642    protected $sidebar_instance_count = array();
     1643
     1644    /**
     1645     * The current request's sidebar_instance_number context.
     1646     *
     1647     * @since 4.5.0
     1648     * @access private
     1649     * @var int
     1650     */
     1651    protected $context_sidebar_instance_number;
     1652
     1653    /**
     1654     * Current sidebar ID being rendered.
     1655     *
     1656     * @since 4.5.0
     1657     * @access private
     1658     * @var array
     1659     */
     1660    protected $current_dynamic_sidebar_id_stack = array();
     1661
     1662    /**
     1663     * Start keeping track of the current sidebar being rendered.
     1664     *
     1665     * Insert marker before widgets are rendered in a dynamic sidebar.
     1666     *
     1667     * @since 4.5.0
     1668     *
     1669     * @param int|string $index Index, name, or ID of the dynamic sidebar.
     1670     */
     1671    public function start_dynamic_sidebar( $index ) {
     1672        array_unshift( $this->current_dynamic_sidebar_id_stack, $index );
     1673        if ( ! isset( $this->sidebar_instance_count[ $index ] ) ) {
     1674            $this->sidebar_instance_count[ $index ] = 0;
     1675        }
     1676        $this->sidebar_instance_count[ $index ] += 1;
     1677        if ( ! $this->manager->selective_refresh->is_render_partials_request() ) {
     1678            printf( "\n<!--dynamic_sidebar_before:%s:%d-->\n", esc_html( $index ), intval( $this->sidebar_instance_count[ $index ] ) );
     1679        }
     1680    }
     1681
     1682    /**
     1683     * Finish keeping track of the current sidebar being rendered.
     1684     *
     1685     * Insert marker after widgets are rendered in a dynamic sidebar.
     1686     *
     1687     * @since 4.5.0
     1688     *
     1689     * @param int|string $index Index, name, or ID of the dynamic sidebar.
     1690     */
     1691    public function end_dynamic_sidebar( $index ) {
     1692        if ( ! $this->manager->selective_refresh->is_render_partials_request() ) {
     1693            printf( "\n<!--dynamic_sidebar_after:%s:%d-->\n", esc_html( $index ), intval( $this->sidebar_instance_count[ $index ] ) );
     1694        }
     1695    }
     1696
     1697    /**
     1698     * Current sidebar being rendered.
     1699     *
     1700     * @since 4.5.0
     1701     * @access private
     1702     * @var string
     1703     */
     1704    protected $rendering_widget_id;
     1705
     1706    /**
     1707     * Current widget being rendered.
     1708     *
     1709     * @since 4.5.0
     1710     * @access private
     1711     * @var string
     1712     */
     1713    protected $rendering_sidebar_id;
     1714
     1715    /**
     1716     * Filter sidebars_widgets to ensure the currently-rendered widget is the only widget in the current sidebar.
     1717     *
     1718     * @since 4.5.0
     1719     * @access private
     1720     *
     1721     * @param array $sidebars_widgets Sidebars widgets.
     1722     * @return array Sidebars widgets.
     1723     */
     1724    public function filter_sidebars_widgets_for_rendering_widget( $sidebars_widgets ) {
     1725        $sidebars_widgets[ $this->rendering_sidebar_id ] = array( $this->rendering_widget_id );
     1726        return $sidebars_widgets;
     1727    }
     1728
     1729    /**
     1730     * Render a specific widget using the supplied sidebar arguments.
     1731     *
     1732     * @since 4.5.0
     1733     * @access public
     1734     *
     1735     * @see dynamic_sidebar()
     1736     *
     1737     * @param WP_Customize_Partial $partial      Partial.
     1738     * @param array                $context {
     1739     *     Sidebar args supplied as container context.
     1740     *
     1741     *     @type string $sidebar_id                ID for sidebar for widget to render into.
     1742     *     @type int    [$sidebar_instance_number] Disambiguating instance number.
     1743     * }
     1744     * @return string|false
     1745     */
     1746    public function render_widget_partial( $partial, $context ) {
     1747        $id_data   = $partial->id_data();
     1748        $widget_id = array_shift( $id_data['keys'] );
     1749
     1750        if ( ! is_array( $context )
     1751            || empty( $context['sidebar_id'] )
     1752            || ! is_registered_sidebar( $context['sidebar_id'] )
     1753        ) {
     1754            return false;
     1755        }
     1756
     1757        $this->rendering_sidebar_id = $context['sidebar_id'];
     1758
     1759        if ( isset( $context['sidebar_instance_number'] ) ) {
     1760            $this->context_sidebar_instance_number = intval( $context['sidebar_instance_number'] );
     1761        }
     1762
     1763        // Filter sidebars_widgets so that only the queried widget is in the sidebar.
     1764        $this->rendering_widget_id = $widget_id;
     1765
     1766        $filter_callback = array( $this, 'filter_sidebars_widgets_for_rendering_widget' );
     1767        add_filter( 'sidebars_widgets', $filter_callback, 1000 );
     1768
     1769        // Render the widget.
     1770        ob_start();
     1771        dynamic_sidebar( $this->rendering_sidebar_id = $context['sidebar_id'] );
     1772        $container = ob_get_clean();
     1773
     1774        // Reset variables for next partial render.
     1775        remove_filter( 'sidebars_widgets', $filter_callback, 1000 );
     1776
     1777        $this->context_sidebar_instance_number = null;
     1778        $this->rendering_sidebar_id = null;
     1779        $this->rendering_widget_id = null;
     1780
     1781        return $container;
     1782    }
     1783
     1784    //
     1785    // Option Update Capturing
     1786    //
    14651787
    14661788    /**
     
    16121934        }
    16131935
    1614         remove_filter( 'pre_update_option', array( $this, 'capture_filter_pre_update_option' ), 10, 3 );
     1936        remove_filter( 'pre_update_option', array( $this, 'capture_filter_pre_update_option' ), 10 );
    16151937
    16161938        foreach ( array_keys( $this->_captured_options ) as $option_name ) {
Note: See TracChangeset for help on using the changeset viewer.