home *** CD-ROM | disk | FTP | other *** search
/ HTML Examples / WP.iso / wordpress2 / wp-includes / customize / class-wp-customize-selective-refresh.php < prev    next >
Encoding:
PHP Script  |  2017-07-26  |  14.7 KB  |  457 lines

  1. <?php
  2. /**
  3.  * Customize API: WP_Customize_Selective_Refresh class
  4.  *
  5.  * @package WordPress
  6.  * @subpackage Customize
  7.  * @since 4.5.0
  8.  */
  9.  
  10. /**
  11.  * Core Customizer class for implementing selective refresh.
  12.  *
  13.  * @since 4.5.0
  14.  */
  15. final class WP_Customize_Selective_Refresh {
  16.  
  17.     /**
  18.      * Query var used in requests to render partials.
  19.      *
  20.      * @since 4.5.0
  21.      */
  22.     const RENDER_QUERY_VAR = 'wp_customize_render_partials';
  23.  
  24.     /**
  25.      * Customize manager.
  26.      *
  27.      * @since 4.5.0
  28.      * @var WP_Customize_Manager
  29.      */
  30.     public $manager;
  31.  
  32.     /**
  33.      * Registered instances of WP_Customize_Partial.
  34.      *
  35.      * @since 4.5.0
  36.      * @var WP_Customize_Partial[]
  37.      */
  38.     protected $partials = array();
  39.  
  40.     /**
  41.      * Log of errors triggered when partials are rendered.
  42.      *
  43.      * @since 4.5.0
  44.      * @var array
  45.      */
  46.     protected $triggered_errors = array();
  47.  
  48.     /**
  49.      * Keep track of the current partial being rendered.
  50.      *
  51.      * @since 4.5.0
  52.      * @var string
  53.      */
  54.     protected $current_partial_id;
  55.  
  56.     /**
  57.      * Plugin bootstrap for Partial Refresh functionality.
  58.      *
  59.      * @since 4.5.0
  60.      *
  61.      * @param WP_Customize_Manager $manager Manager instance.
  62.      */
  63.     public function __construct( WP_Customize_Manager $manager ) {
  64.         $this->manager = $manager;
  65.         require_once( ABSPATH . WPINC . '/customize/class-wp-customize-partial.php' );
  66.  
  67.         add_action( 'customize_preview_init', array( $this, 'init_preview' ) );
  68.     }
  69.  
  70.     /**
  71.      * Retrieves the registered partials.
  72.      *
  73.      * @since 4.5.0
  74.      *
  75.      * @return array Partials.
  76.      */
  77.     public function partials() {
  78.         return $this->partials;
  79.     }
  80.  
  81.     /**
  82.      * Adds a partial.
  83.      *
  84.      * @since 4.5.0
  85.      *
  86.      * @param WP_Customize_Partial|string $id   Customize Partial object, or Panel ID.
  87.      * @param array                       $args {
  88.      *  Optional. Array of properties for the new Partials object. Default empty array.
  89.      *
  90.      *  @type string   $type                  Type of the partial to be created.
  91.      *  @type string   $selector              The jQuery selector to find the container element for the partial, that is, a partial's placement.
  92.      *  @type array    $settings              IDs for settings tied to the partial.
  93.      *  @type string   $primary_setting       The ID for the setting that this partial is primarily responsible for
  94.      *                                        rendering. If not supplied, it will default to the ID of the first setting.
  95.      *  @type string   $capability            Capability required to edit this partial.
  96.      *                                        Normally this is empty and the capability is derived from the capabilities
  97.      *                                        of the associated `$settings`.
  98.      *  @type callable $render_callback       Render callback.
  99.      *                                        Callback is called with one argument, the instance of WP_Customize_Partial.
  100.      *                                        The callback can either echo the partial or return the partial as a string,
  101.      *                                        or return false if error.
  102.      *  @type bool     $container_inclusive   Whether the container element is included in the partial, or if only
  103.      *                                        the contents are rendered.
  104.      *  @type bool     $fallback_refresh      Whether to refresh the entire preview in case a partial cannot be refreshed.
  105.      *                                        A partial render is considered a failure if the render_callback returns
  106.      *                                        false.
  107.      * }
  108.      * @return WP_Customize_Partial             The instance of the panel that was added.
  109.      */
  110.     public function add_partial( $id, $args = array() ) {
  111.         if ( $id instanceof WP_Customize_Partial ) {
  112.             $partial = $id;
  113.         } else {
  114.             $class = 'WP_Customize_Partial';
  115.  
  116.             /** This filter is documented in wp-includes/customize/class-wp-customize-selective-refresh.php */
  117.             $args = apply_filters( 'customize_dynamic_partial_args', $args, $id );
  118.  
  119.             /** This filter is documented in wp-includes/customize/class-wp-customize-selective-refresh.php */
  120.             $class = apply_filters( 'customize_dynamic_partial_class', $class, $id, $args );
  121.  
  122.             $partial = new $class( $this, $id, $args );
  123.         }
  124.  
  125.         $this->partials[ $partial->id ] = $partial;
  126.         return $partial;
  127.     }
  128.  
  129.     /**
  130.      * Retrieves a partial.
  131.      *
  132.      * @since 4.5.0
  133.      *
  134.      * @param string $id Customize Partial ID.
  135.      * @return WP_Customize_Partial|null The partial, if set. Otherwise null.
  136.      */
  137.     public function get_partial( $id ) {
  138.         if ( isset( $this->partials[ $id ] ) ) {
  139.             return $this->partials[ $id ];
  140.         } else {
  141.             return null;
  142.         }
  143.     }
  144.  
  145.     /**
  146.      * Removes a partial.
  147.      *
  148.      * @since 4.5.0
  149.      *
  150.      * @param string $id Customize Partial ID.
  151.      */
  152.     public function remove_partial( $id ) {
  153.         unset( $this->partials[ $id ] );
  154.     }
  155.  
  156.     /**
  157.      * Initializes the Customizer preview.
  158.      *
  159.      * @since 4.5.0
  160.      */
  161.     public function init_preview() {
  162.         add_action( 'template_redirect', array( $this, 'handle_render_partials_request' ) );
  163.         add_action( 'wp_enqueue_scripts', array( $this, 'enqueue_preview_scripts' ) );
  164.     }
  165.  
  166.     /**
  167.      * Enqueues preview scripts.
  168.      *
  169.      * @since 4.5.0
  170.      */
  171.     public function enqueue_preview_scripts() {
  172.         wp_enqueue_script( 'customize-selective-refresh' );
  173.         add_action( 'wp_footer', array( $this, 'export_preview_data' ), 1000 );
  174.     }
  175.  
  176.     /**
  177.      * Exports data in preview after it has finished rendering so that partials can be added at runtime.
  178.      *
  179.      * @since 4.5.0
  180.      */
  181.     public function export_preview_data() {
  182.         $partials = array();
  183.  
  184.         foreach ( $this->partials() as $partial ) {
  185.             if ( $partial->check_capabilities() ) {
  186.                 $partials[ $partial->id ] = $partial->json();
  187.             }
  188.         }
  189.  
  190.         $switched_locale = switch_to_locale( get_user_locale() );
  191.         $l10n = array(
  192.             'shiftClickToEdit' => __( 'Shift-click to edit this element.' ),
  193.             'clickEditMenu' => __( 'Click to edit this menu.' ),
  194.             'clickEditWidget' => __( 'Click to edit this widget.' ),
  195.             'clickEditTitle' => __( 'Click to edit the site title.' ),
  196.             'clickEditMisc' => __( 'Click to edit this element.' ),
  197.             /* translators: %s: document.write() */
  198.             'badDocumentWrite' => sprintf( __( '%s is forbidden' ), 'document.write()' ),
  199.         );
  200.         if ( $switched_locale ) {
  201.             restore_previous_locale();
  202.         }
  203.  
  204.         $exports = array(
  205.             'partials'       => $partials,
  206.             'renderQueryVar' => self::RENDER_QUERY_VAR,
  207.             'l10n'           => $l10n,
  208.         );
  209.  
  210.         // Export data to JS.
  211.         echo sprintf( '<script>var _customizePartialRefreshExports = %s;</script>', wp_json_encode( $exports ) );
  212.     }
  213.  
  214.     /**
  215.      * Registers dynamically-created partials.
  216.      *
  217.      * @since 4.5.0
  218.      *
  219.      * @see WP_Customize_Manager::add_dynamic_settings()
  220.      *
  221.      * @param array $partial_ids The partial ID to add.
  222.      * @return array Added WP_Customize_Partial instances.
  223.      */
  224.     public function add_dynamic_partials( $partial_ids ) {
  225.         $new_partials = array();
  226.  
  227.         foreach ( $partial_ids as $partial_id ) {
  228.  
  229.             // Skip partials already created.
  230.             $partial = $this->get_partial( $partial_id );
  231.             if ( $partial ) {
  232.                 continue;
  233.             }
  234.  
  235.             $partial_args = false;
  236.             $partial_class = 'WP_Customize_Partial';
  237.  
  238.             /**
  239.              * Filters a dynamic partial's constructor arguments.
  240.              *
  241.              * For a dynamic partial to be registered, this filter must be employed
  242.              * to override the default false value with an array of args to pass to
  243.              * the WP_Customize_Partial constructor.
  244.              *
  245.              * @since 4.5.0
  246.              *
  247.              * @param false|array $partial_args The arguments to the WP_Customize_Partial constructor.
  248.              * @param string      $partial_id   ID for dynamic partial.
  249.              */
  250.             $partial_args = apply_filters( 'customize_dynamic_partial_args', $partial_args, $partial_id );
  251.             if ( false === $partial_args ) {
  252.                 continue;
  253.             }
  254.  
  255.             /**
  256.              * Filters the class used to construct partials.
  257.              *
  258.              * Allow non-statically created partials to be constructed with custom WP_Customize_Partial subclass.
  259.              *
  260.              * @since 4.5.0
  261.              *
  262.              * @param string $partial_class WP_Customize_Partial or a subclass.
  263.              * @param string $partial_id    ID for dynamic partial.
  264.              * @param array  $partial_args  The arguments to the WP_Customize_Partial constructor.
  265.              */
  266.             $partial_class = apply_filters( 'customize_dynamic_partial_class', $partial_class, $partial_id, $partial_args );
  267.  
  268.             $partial = new $partial_class( $this, $partial_id, $partial_args );
  269.  
  270.             $this->add_partial( $partial );
  271.             $new_partials[] = $partial;
  272.         }
  273.         return $new_partials;
  274.     }
  275.  
  276.     /**
  277.      * Checks whether the request is for rendering partials.
  278.      *
  279.      * Note that this will not consider whether the request is authorized or valid,
  280.      * just that essentially the route is a match.
  281.      *
  282.      * @since 4.5.0
  283.      *
  284.      * @return bool Whether the request is for rendering partials.
  285.      */
  286.     public function is_render_partials_request() {
  287.         return ! empty( $_POST[ self::RENDER_QUERY_VAR ] );
  288.     }
  289.  
  290.     /**
  291.      * Handles PHP errors triggered during rendering the partials.
  292.      *
  293.      * These errors will be relayed back to the client in the Ajax response.
  294.      *
  295.      * @since 4.5.0
  296.      *
  297.      * @param int    $errno   Error number.
  298.      * @param string $errstr  Error string.
  299.      * @param string $errfile Error file.
  300.      * @param string $errline Error line.
  301.      * @return true Always true.
  302.      */
  303.     public function handle_error( $errno, $errstr, $errfile = null, $errline = null ) {
  304.         $this->triggered_errors[] = array(
  305.             'partial'      => $this->current_partial_id,
  306.             'error_number' => $errno,
  307.             'error_string' => $errstr,
  308.             'error_file'   => $errfile,
  309.             'error_line'   => $errline,
  310.         );
  311.         return true;
  312.     }
  313.  
  314.     /**
  315.      * Handles the Ajax request to return the rendered partials for the requested placements.
  316.      *
  317.      * @since 4.5.0
  318.      */
  319.     public function handle_render_partials_request() {
  320.         if ( ! $this->is_render_partials_request() ) {
  321.             return;
  322.         }
  323.  
  324.         /*
  325.          * Note that is_customize_preview() returning true will entail that the
  326.          * user passed the 'customize' capability check and the nonce check, since
  327.          * WP_Customize_Manager::setup_theme() is where the previewing flag is set.
  328.          */
  329.         if ( ! is_customize_preview() ) {
  330.             wp_send_json_error( 'expected_customize_preview', 403 );
  331.         } elseif ( ! isset( $_POST['partials'] ) ) {
  332.             wp_send_json_error( 'missing_partials', 400 );
  333.         }
  334.  
  335.         // Ensure that doing selective refresh on 404 template doesn't result in fallback rendering behavior (full refreshes).
  336.         status_header( 200 );
  337.  
  338.         $partials = json_decode( wp_unslash( $_POST['partials'] ), true );
  339.  
  340.         if ( ! is_array( $partials ) ) {
  341.             wp_send_json_error( 'malformed_partials' );
  342.         }
  343.  
  344.         $this->add_dynamic_partials( array_keys( $partials ) );
  345.  
  346.         /**
  347.          * Fires immediately before partials are rendered.
  348.          *
  349.          * Plugins may do things like call wp_enqueue_scripts() and gather a list of the scripts
  350.          * and styles which may get enqueued in the response.
  351.          *
  352.          * @since 4.5.0
  353.          *
  354.          * @param WP_Customize_Selective_Refresh $this     Selective refresh component.
  355.          * @param array                          $partials Placements' context data for the partials rendered in the request.
  356.          *                                                 The array is keyed by partial ID, with each item being an array of
  357.          *                                                 the placements' context data.
  358.          */
  359.         do_action( 'customize_render_partials_before', $this, $partials );
  360.  
  361.         set_error_handler( array( $this, 'handle_error' ), error_reporting() );
  362.  
  363.         $contents = array();
  364.  
  365.         foreach ( $partials as $partial_id => $container_contexts ) {
  366.             $this->current_partial_id = $partial_id;
  367.  
  368.             if ( ! is_array( $container_contexts ) ) {
  369.                 wp_send_json_error( 'malformed_container_contexts' );
  370.             }
  371.  
  372.             $partial = $this->get_partial( $partial_id );
  373.  
  374.             if ( ! $partial || ! $partial->check_capabilities() ) {
  375.                 $contents[ $partial_id ] = null;
  376.                 continue;
  377.             }
  378.  
  379.             $contents[ $partial_id ] = array();
  380.  
  381.             // @todo The array should include not only the contents, but also whether the container is included?
  382.             if ( empty( $container_contexts ) ) {
  383.                 // Since there are no container contexts, render just once.
  384.                 $contents[ $partial_id ][] = $partial->render( null );
  385.             } else {
  386.                 foreach ( $container_contexts as $container_context ) {
  387.                     $contents[ $partial_id ][] = $partial->render( $container_context );
  388.                 }
  389.             }
  390.         }
  391.         $this->current_partial_id = null;
  392.  
  393.         restore_error_handler();
  394.  
  395.         /**
  396.          * Fires immediately after partials are rendered.
  397.          *
  398.          * Plugins may do things like call wp_footer() to scrape scripts output and return them
  399.          * via the {@see 'customize_render_partials_response'} filter.
  400.          *
  401.          * @since 4.5.0
  402.          *
  403.          * @param WP_Customize_Selective_Refresh $this     Selective refresh component.
  404.          * @param array                          $partials Placements' context data for the partials rendered in the request.
  405.          *                                                 The array is keyed by partial ID, with each item being an array of
  406.          *                                                 the placements' context data.
  407.          */
  408.         do_action( 'customize_render_partials_after', $this, $partials );
  409.  
  410.         $response = array(
  411.             'contents' => $contents,
  412.         );
  413.  
  414.         if ( defined( 'WP_DEBUG_DISPLAY' ) && WP_DEBUG_DISPLAY ) {
  415.             $response['errors'] = $this->triggered_errors;
  416.         }
  417.  
  418.         $setting_validities = $this->manager->validate_setting_values( $this->manager->unsanitized_post_values() );
  419.         $exported_setting_validities = array_map( array( $this->manager, 'prepare_setting_validity_for_js' ), $setting_validities );
  420.         $response['setting_validities'] = $exported_setting_validities;
  421.  
  422.         /**
  423.          * Filters the response from rendering the partials.
  424.          *
  425.          * Plugins may use this filter to inject `$scripts` and `$styles`, which are dependencies
  426.          * for the partials being rendered. The response data will be available to the client via
  427.          * the `render-partials-response` JS event, so the client can then inject the scripts and
  428.          * styles into the DOM if they have not already been enqueued there.
  429.          *
  430.          * If plugins do this, they'll need to take care for any scripts that do `document.write()`
  431.          * and make sure that these are not injected, or else to override the function to no-op,
  432.          * or else the page will be destroyed.
  433.          *
  434.          * Plugins should be aware that `$scripts` and `$styles` may eventually be included by
  435.          * default in the response.
  436.          *
  437.          * @since 4.5.0
  438.          *
  439.          * @param array $response {
  440.          *     Response.
  441.          *
  442.          *     @type array $contents Associative array mapping a partial ID its corresponding array of contents
  443.          *                           for the containers requested.
  444.          *     @type array $errors   List of errors triggered during rendering of partials, if `WP_DEBUG_DISPLAY`
  445.          *                           is enabled.
  446.          * }
  447.          * @param WP_Customize_Selective_Refresh $this     Selective refresh component.
  448.          * @param array                          $partials Placements' context data for the partials rendered in the request.
  449.          *                                                 The array is keyed by partial ID, with each item being an array of
  450.          *                                                 the placements' context data.
  451.          */
  452.         $response = apply_filters( 'customize_render_partials_response', $response, $this, $partials );
  453.  
  454.         wp_send_json_success( $response );
  455.     }
  456. }
  457.