<?php /** * WordPress Customize Manager classes * * @package WordPress * @subpackage Customize * @since 3.4.0 */ /** * Customize Manager class. * * Bootstraps the Customize experience on the server-side. * * Sets up the theme-switching process if a theme other than the active one is * being previewed and customized. * * Serves as a factory for Customize Controls and Settings, and * instantiates default Customize Controls and Settings. * * @since 3.4.0 */ #[AllowDynamicProperties] final class WP_Customize_Manager { /** * An instance of the theme being previewed. * * @since 3.4.0 * @var WP_Theme */ protected $theme; /** * The directory name of the previously active theme (within the theme_root). * * @since 3.4.0 * @var string */ protected $original_stylesheet; /** * Whether this is a Customizer pageload. * * @since 3.4.0 * @var bool */ protected $previewing = false; /** * Methods and properties dealing with managing widgets in the Customizer. * * @since 3.9.0 * @var WP_Customize_Widgets */ public $widgets; /** * Methods and properties dealing with managing nav menus in the Customizer. * * @since 4.3.0 * @var WP_Customize_Nav_Menus */ public $nav_menus; /** * Methods and properties dealing with selective refresh in the Customizer preview. * * @since 4.5.0 * @var WP_Customize_Selective_Refresh */ public $selective_refresh; /** * Registered instances of WP_Customize_Setting. * * @since 3.4.0 * @var array */ protected $settings = array(); /** * Sorted top-level instances of WP_Customize_Panel and WP_Customize_Section. * * @since 4.0.0 * @var array */ protected $containers = array(); /** * Registered instances of WP_Customize_Panel. * * @since 4.0.0 * @var array */ protected $panels = array(); /** * List of core components. * * @since 4.5.0 * @var array */ protected $components = array( 'nav_menus' ); /** * Registered instances of WP_Customize_Section. * * @since 3.4.0 * @var array */ protected $sections = array(); /** * Registered instances of WP_Customize_Control. * * @since 3.4.0 * @var array */ protected $controls = array(); /** * Panel types that may be rendered from JS templates. * * @since 4.3.0 * @var array */ protected $registered_panel_types = array(); /** * Section types that may be rendered from JS templates. * * @since 4.3.0 * @var array */ protected $registered_section_types = array(); /** * Control types that may be rendered from JS templates. * * @since 4.1.0 * @var array */ protected $registered_control_types = array(); /** * Initial URL being previewed. * * @since 4.4.0 * @var string */ protected $preview_url; /** * URL to link the user to when closing the Customizer. * * @since 4.4.0 * @var string */ protected $return_url; /** * Mapping of 'panel', 'section', 'control' to the ID which should be autofocused. * * @since 4.4.0 * @var string[] */ protected $autofocus = array(); /** * Messenger channel. * * @since 4.7.0 * @var string */ protected $messenger_channel; /** * Whether the autosave revision of the changeset should be loaded. * * @since 4.9.0 * @var bool */ protected $autosaved = false; /** * Whether the changeset branching is allowed. * * @since 4.9.0 * @var bool */ protected $branching = true; /** * Whether settings should be previewed. * * @since 4.9.0 * @var bool */ protected $settings_previewed = true; /** * Whether a starter content changeset was saved. * * @since 4.9.0 * @var bool */ protected $saved_starter_content_changeset = false; /** * Unsanitized values for Customize Settings parsed from $_POST['customized']. * * @var array */ private $_post_values; /** * Changeset UUID. * * @since 4.7.0 * @var string */ private $_changeset_uuid; /** * Changeset post ID. * * @since 4.7.0 * @var int|false */ private $_changeset_post_id; /** * Changeset data loaded from a customize_changeset post. * * @since 4.7.0 * @var array|null */ private $_changeset_data; /** * Constructor. * * @since 3.4.0 * @since 4.7.0 Added `$args` parameter. * * @param array $args { * Args. * * @type null|string|false $changeset_uuid Changeset UUID, the `post_name` for the customize_changeset post containing the customized state. * Defaults to `null` resulting in a UUID to be immediately generated. If `false` is provided, then * then the changeset UUID will be determined during `after_setup_theme`: when the * `customize_changeset_branching` filter returns false, then the default UUID will be that * of the most recent `customize_changeset` post that has a status other than 'auto-draft', * 'publish', or 'trash'. Otherwise, if changeset branching is enabled, then a random UUID will be used. * @type string $theme Theme to be previewed (for theme switch). Defaults to customize_theme or theme query params. * @type string $messenger_channel Messenger channel. Defaults to customize_messenger_channel query param. * @type bool $settings_previewed If settings should be previewed. Defaults to true. * @type bool $branching If changeset branching is allowed; otherwise, changesets are linear. Defaults to true. * @type bool $autosaved If data from a changeset's autosaved revision should be loaded if it exists. Defaults to false. * } */ public function __construct( $args = array() ) { $args = array_merge( array_fill_keys( array( 'changeset_uuid', 'theme', 'messenger_channel', 'settings_previewed', 'autosaved', 'branching' ), null ), $args ); // Note that the UUID format will be validated in the setup_theme() method. if ( ! isset( $args['changeset_uuid'] ) ) { $args['changeset_uuid'] = wp_generate_uuid4(); } /* * The theme and messenger_channel should be supplied via $args, * but they are also looked at in the $_REQUEST global here for back-compat. */ if ( ! isset( $args['theme'] ) ) { if ( isset( $_REQUEST['customize_theme'] ) ) { $args['theme'] = wp_unslash( $_REQUEST['customize_theme'] ); } elseif ( isset( $_REQUEST['theme'] ) ) { // Deprecated. $args['theme'] = wp_unslash( $_REQUEST['theme'] ); } } if ( ! isset( $args['messenger_channel'] ) && isset( $_REQUEST['customize_messenger_channel'] ) ) { $args['messenger_channel'] = sanitize_key( wp_unslash( $_REQUEST['customize_messenger_channel'] ) ); } // Do not load 'widgets' component if a block theme is activated. if ( ! wp_is_block_theme() ) { $this->components[] = 'widgets'; } $this->original_stylesheet = get_stylesheet(); $this->theme = wp_get_theme( 0 === validate_file( $args['theme'] ) ? $args['theme'] : null ); $this->messenger_channel = $args['messenger_channel']; $this->_changeset_uuid = $args['changeset_uuid']; foreach ( array( 'settings_previewed', 'autosaved', 'branching' ) as $key ) { if ( isset( $args[ $key ] ) ) { $this->$key = (bool) $args[ $key ]; } } require_once ABSPATH . WPINC . '/class-wp-customize-setting.php'; require_once ABSPATH . WPINC . '/class-wp-customize-panel.php'; require_once ABSPATH . WPINC . '/class-wp-customize-section.php'; require_once ABSPATH . WPINC . '/class-wp-customize-control.php'; require_once ABSPATH . WPINC . '/customize/class-wp-customize-color-control.php'; require_once ABSPATH . WPINC . '/customize/class-wp-customize-media-control.php'; require_once ABSPATH . WPINC . '/customize/class-wp-customize-upload-control.php'; require_once ABSPATH . WPINC . '/customize/class-wp-customize-image-control.php'; require_once ABSPATH . WPINC . '/customize/class-wp-customize-background-image-control.php'; require_once ABSPATH . WPINC . '/customize/class-wp-customize-background-position-control.php'; require_once ABSPATH . WPINC . '/customize/class-wp-customize-cropped-image-control.php'; require_once ABSPATH . WPINC . '/customize/class-wp-customize-site-icon-control.php'; require_once ABSPATH . WPINC . '/customize/class-wp-customize-header-image-control.php'; require_once ABSPATH . WPINC . '/customize/class-wp-customize-theme-control.php'; require_once ABSPATH . WPINC . '/customize/class-wp-customize-code-editor-control.php'; require_once ABSPATH . WPINC . '/customize/class-wp-widget-area-customize-control.php'; require_once ABSPATH . WPINC . '/customize/class-wp-widget-form-customize-control.php'; require_once ABSPATH . WPINC . '/customize/class-wp-customize-nav-menu-control.php'; require_once ABSPATH . WPINC . '/customize/class-wp-customize-nav-menu-item-control.php'; require_once ABSPATH . WPINC . '/customize/class-wp-customize-nav-menu-location-control.php'; require_once ABSPATH . WPINC . '/customize/class-wp-customize-nav-menu-name-control.php'; require_once ABSPATH . WPINC . '/customize/class-wp-customize-nav-menu-locations-control.php'; require_once ABSPATH . WPINC . '/customize/class-wp-customize-nav-menu-auto-add-control.php'; require_once ABSPATH . WPINC . '/customize/class-wp-customize-nav-menus-panel.php'; require_once ABSPATH . WPINC . '/customize/class-wp-customize-themes-panel.php'; require_once ABSPATH . WPINC . '/customize/class-wp-customize-themes-section.php'; require_once ABSPATH . WPINC . '/customize/class-wp-customize-sidebar-section.php'; require_once ABSPATH . WPINC . '/customize/class-wp-customize-nav-menu-section.php'; require_once ABSPATH . WPINC . '/customize/class-wp-customize-custom-css-setting.php'; require_once ABSPATH . WPINC . '/customize/class-wp-customize-filter-setting.php'; require_once ABSPATH . WPINC . '/customize/class-wp-customize-header-image-setting.php'; require_once ABSPATH . WPINC . '/customize/class-wp-customize-background-image-setting.php'; require_once ABSPATH . WPINC . '/customize/class-wp-customize-nav-menu-item-setting.php'; require_once ABSPATH . WPINC . '/customize/class-wp-customize-nav-menu-setting.php'; /** * Filters the core Customizer components to load. * * This allows Core components to be excluded from being instantiated by * filtering them out of the array. Note that this filter generally runs * during the {@see 'plugins_loaded'} action, so it cannot be added * in a theme. * * @since 4.4.0 * * @see WP_Customize_Manager::__construct() * * @param string[] $components Array of core components to load. * @param WP_Customize_Manager $manager WP_Customize_Manager instance. */ $components = apply_filters( 'customize_loaded_components', $this->components, $this ); require_once ABSPATH . WPINC . '/customize/class-wp-customize-selective-refresh.php'; $this->selective_refresh = new WP_Customize_Selective_Refresh( $this ); if ( in_array( 'widgets', $components, true ) ) { require_once ABSPATH . WPINC . '/class-wp-customize-widgets.php'; $this->widgets = new WP_Customize_Widgets( $this ); } if ( in_array( 'nav_menus', $components, true ) ) { require_once ABSPATH . WPINC . '/class-wp-customize-nav-menus.php'; $this->nav_menus = new WP_Customize_Nav_Menus( $this ); } add_action( 'setup_theme', array( $this, 'setup_theme' ) ); add_action( 'wp_loaded', array( $this, 'wp_loaded' ) ); // Do not spawn cron (especially the alternate cron) while running the Customizer. remove_action( 'init', 'wp_cron' ); // Do not run update checks when rendering the controls. remove_action( 'admin_init', '_maybe_update_core' ); remove_action( 'admin_init', '_maybe_update_plugins' ); remove_action( 'admin_init', '_maybe_update_themes' ); add_action( 'wp_ajax_customize_save', array( $this, 'save' ) ); add_action( 'wp_ajax_customize_trash', array( $this, 'handle_changeset_trash_request' ) ); add_action( 'wp_ajax_customize_refresh_nonces', array( $this, 'refresh_nonces' ) ); add_action( 'wp_ajax_customize_load_themes', array( $this, 'handle_load_themes_request' ) ); add_filter( 'heartbeat_settings', array( $this, 'add_customize_screen_to_heartbeat_settings' ) ); add_filter( 'heartbeat_received', array( $this, 'check_changeset_lock_with_heartbeat' ), 10, 3 ); add_action( 'wp_ajax_customize_override_changeset_lock', array( $this, 'handle_override_changeset_lock_request' ) ); add_action( 'wp_ajax_customize_dismiss_autosave_or_lock', array( $this, 'handle_dismiss_autosave_or_lock_request' ) ); add_action( 'customize_register', array( $this, 'register_controls' ) ); add_action( 'customize_register', array( $this, 'register_dynamic_settings' ), 11 ); // Allow code to create settings first. add_action( 'customize_controls_init', array( $this, 'prepare_controls' ) ); add_action( 'customize_controls_enqueue_scripts', array( $this, 'enqueue_control_scripts' ) ); // Render Common, Panel, Section, and Control templates. add_action( 'customize_controls_print_footer_scripts', array( $this, 'render_panel_templates' ), 1 ); add_action( 'customize_controls_print_footer_scripts', array( $this, 'render_section_templates' ), 1 ); add_action( 'customize_controls_print_footer_scripts', array( $this, 'render_control_templates' ), 1 ); // Export header video settings with the partial response. add_filter( 'customize_render_partials_response', array( $this, 'export_header_video_settings' ), 10, 3 ); // Export the settings to JS via the _wpCustomizeSettings variable. add_action( 'customize_controls_print_footer_scripts', array( $this, 'customize_pane_settings' ), 1000 ); // Add theme update notices. if ( current_user_can( 'install_themes' ) || current_user_can( 'update_themes' ) ) { require_once ABSPATH . 'wp-admin/includes/update.php'; add_action( 'customize_controls_print_footer_scripts', 'wp_print_admin_notice_templates' ); } } /** * Returns true if it's an Ajax request. * * @since 3.4.0 * @since 4.2.0 Added `$action` param. * * @param string|null $action Whether the supplied Ajax action is being run. * @return bool True if it's an Ajax request, false otherwise. */ public function doing_ajax( $action = null ) { if ( ! wp_doing_ajax() ) { return false; } if ( ! $action ) { return true; } else { /* * Note: we can't just use doing_action( "wp_ajax_{$action}" ) because we need * to check before admin-ajax.php gets to that point. */ return isset( $_REQUEST['action'] ) && wp_unslash( $_REQUEST['action'] ) === $action; } } /** * Custom wp_die wrapper. Returns either the standard message for UI * or the Ajax message. * * @since 3.4.0 * * @param string|WP_Error $ajax_message Ajax return. * @param string $message Optional. UI message. */ protected function wp_die( $ajax_message, $message = null ) { if ( $this->doing_ajax() ) { wp_die( $ajax_message ); } if ( ! $message ) { $message = __( 'Something went wrong.' ); } if ( $this->messenger_channel ) { ob_start(); wp_enqueue_scripts(); wp_print_scripts( array( 'customize-base' ) ); $settings = array( 'messengerArgs' => array( 'channel' => $this->messenger_channel, 'url' => wp_customize_url(), ), 'error' => $ajax_message, ); $message .= ob_get_clean(); ob_start(); ?> <script> ( function( api, settings ) { var preview = new api.Messenger( settings.messengerArgs ); preview.send( 'iframe-loading-error', settings.error ); } )( wp.customize, <?php echo wp_json_encode( $settings ); ?> ); </script> <?php $message .= wp_get_inline_script_tag( wp_remove_surrounding_empty_script_tags( ob_get_clean() ) ); } wp_die( $message ); } /** * Returns the Ajax wp_die() handler if it's a customized request. * * @since 3.4.0 * @deprecated 4.7.0 * * @return callable Die handler. */ public function wp_die_handler() { _deprecated_function( __METHOD__, '4.7.0' ); if ( $this->doi