original_stylesheet = get_stylesheet(); $this->theme = wp_get_theme( 0 === validate_file( $args['theme'] ) ? $args['theme'] : null ); $this->messenger_channel = $args['messenger_channel']; $this->settings_previewed = ! empty( $args['settings_previewed'] ); $this->_changeset_uuid = $args['changeset_uuid']; 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-auto-add-control.php' ); require_once( ABSPATH . WPINC . '/customize/class-wp-customize-new-menu-control.php' ); require_once( ABSPATH . WPINC . '/customize/class-wp-customize-nav-menus-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-new-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 array $components List of core components to load. * @param WP_Customize_Manager $this 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_refresh_nonces', array( $this, 'refresh_nonces' ) ); 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 ); } /** * Return 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 mixed $ajax_message Ajax return * @param mixed $message UI message */ protected function wp_die( $ajax_message, $message = null ) { if ( $this->doing_ajax() ) { wp_die( $ajax_message ); } if ( ! $message ) { $message = __( 'Cheatin’ uh?' ); } 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, ); ?> doing_ajax() || isset( $_POST['customized'] ) ) { return '_ajax_wp_die_handler'; } return '_default_wp_die_handler'; } /** * Start preview and customize theme. * * Check if customize query variable exist. Init filters to filter the current theme. * * @since 3.4.0 * * @global string $pagenow */ public function setup_theme() { global $pagenow; // Check permissions for customize.php access since this method is called before customize.php can run any code, if ( 'customize.php' === $pagenow && ! current_user_can( 'customize' ) ) { if ( ! is_user_logged_in() ) { auth_redirect(); } else { wp_die( '
' . __( 'Sorry, you are not allowed to customize this site.' ) . '
', 403 ); } return; } if ( ! wp_is_uuid( $this->_changeset_uuid ) ) { $this->wp_die( -1, __( 'Invalid changeset UUID' ) ); } /* * Clear incoming post data if the user lacks a CSRF token (nonce). Note that the customizer * application will inject the customize_preview_nonce query parameter into all Ajax requests. * For similar behavior elsewhere in WordPress, see rest_cookie_check_errors() which logs out * a user when a valid nonce isn't present. */ $has_post_data_nonce = ( check_ajax_referer( 'preview-customize_' . $this->get_stylesheet(), 'nonce', false ) || check_ajax_referer( 'save-customize_' . $this->get_stylesheet(), 'nonce', false ) || check_ajax_referer( 'preview-customize_' . $this->get_stylesheet(), 'customize_preview_nonce', false ) ); if ( ! current_user_can( 'customize' ) || ! $has_post_data_nonce ) { unset( $_POST['customized'] ); unset( $_REQUEST['customized'] ); } /* * If unauthenticated then require a valid changeset UUID to load the preview. * In this way, the UUID serves as a secret key. If the messenger channel is present, * then send unauthenticated code to prompt re-auth. */ if ( ! current_user_can( 'customize' ) && ! $this->changeset_post_id() ) { $this->wp_die( $this->messenger_channel ? 0 : -1, __( 'Non-existent changeset UUID.' ) ); } if ( ! headers_sent() ) { send_origin_headers(); } // Hide the admin bar if we're embedded in the customizer iframe. if ( $this->messenger_channel ) { show_admin_bar( false ); } if ( $this->is_theme_active() ) { // Once the theme is loaded, we'll validate it. add_action( 'after_setup_theme', array( $this, 'after_setup_theme' ) ); } else { // If the requested theme is not the active theme and the user doesn't have the // switch_themes cap, bail. if ( ! current_user_can( 'switch_themes' ) ) { $this->wp_die( -1, __( 'Sorry, you are not allowed to edit theme options on this site.' ) ); } // If the theme has errors while loading, bail. if ( $this->theme()->errors() ) { $this->wp_die( -1, $this->theme()->errors()->get_error_message() ); } // If the theme isn't allowed per multisite settings, bail. if ( ! $this->theme()->is_allowed() ) { $this->wp_die( -1, __( 'The requested theme does not exist.' ) ); } } /* * Import theme starter content for fresh installations when landing in the customizer. * Import starter content at after_setup_theme:100 so that any * add_theme_support( 'starter-content' ) calls will have been made. */ if ( get_option( 'fresh_site' ) && 'customize.php' === $pagenow ) { add_action( 'after_setup_theme', array( $this, 'import_theme_starter_content' ), 100 ); } $this->start_previewing_theme(); } /** * Callback to validate a theme once it is loaded * * @since 3.4.0 */ public function after_setup_theme() { $doing_ajax_or_is_customized = ( $this->doing_ajax() || isset( $_POST['customized'] ) ); if ( ! $doing_ajax_or_is_customized && ! validate_current_theme() ) { wp_redirect( 'themes.php?broken=true' ); exit; } } /** * If the theme to be previewed isn't the active theme, add filter callbacks * to swap it out at runtime. * * @since 3.4.0 */ public function start_previewing_theme() { // Bail if we're already previewing. if ( $this->is_preview() ) { return; } $this->previewing = true; if ( ! $this->is_theme_active() ) { add_filter( 'template', array( $this, 'get_template' ) ); add_filter( 'stylesheet', array( $this, 'get_stylesheet' ) ); add_filter( 'pre_option_current_theme', array( $this, 'current_theme' ) ); // @link: https://core.trac.wordpress.org/ticket/20027 add_filter( 'pre_option_stylesheet', array( $this, 'get_stylesheet' ) ); add_filter( 'pre_option_template', array( $this, 'get_template' ) ); // Handle custom theme roots. add_filter( 'pre_option_stylesheet_root', array( $this, 'get_stylesheet_root' ) ); add_filter( 'pre_option_template_root', array( $this, 'get_template_root' ) ); } /** * Fires once the Customizer theme preview has started. * * @since 3.4.0 * * @param WP_Customize_Manager $this WP_Customize_Manager instance. */ do_action( 'start_previewing_theme', $this ); } /** * Stop previewing the selected theme. * * Removes filters to change the current theme. * * @since 3.4.0 */ public function stop_previewing_theme() { if ( ! $this->is_preview() ) { return; } $this->previewing = false; if ( ! $this->is_theme_active() ) { remove_filter( 'template', array( $this, 'get_template' ) ); remove_filter( 'stylesheet', array( $this, 'get_stylesheet' ) ); remove_filter( 'pre_option_current_theme', array( $this, 'current_theme' ) ); // @link: https://core.trac.wordpress.org/ticket/20027 remove_filter( 'pre_option_stylesheet', array( $this, 'get_stylesheet' ) ); remove_filter( 'pre_option_template', array( $this, 'get_template' ) ); // Handle custom theme roots. remove_filter( 'pre_option_stylesheet_root', array( $this, 'get_stylesheet_root' ) ); remove_filter( 'pre_option_template_root', array( $this, 'get_template_root' ) ); } /** * Fires once the Customizer theme preview has stopped. * * @since 3.4.0 * * @param WP_Customize_Manager $this WP_Customize_Manager instance. */ do_action( 'stop_previewing_theme', $this ); } /** * Gets whether settings are or will be previewed. * * @since 4.9.0 * @see WP_Customize_Setting::preview() * * @return bool */ public function settings_previewed() { return $this->settings_previewed; } /** * Get the changeset UUID. * * @since 4.7.0 * * @return string UUID. */ public function changeset_uuid() { return $this->_changeset_uuid; } /** * Get the theme being customized. * * @since 3.4.0 * * @return WP_Theme */ public function theme() { if ( ! $this->theme ) { $this->theme = wp_get_theme(); } return $this->theme; } /** * Get the registered settings. * * @since 3.4.0 * * @return array */ public function settings() { return $this->settings; } /** * Get the registered controls. * * @since 3.4.0 * * @return array */ public function controls() { return $this->controls; } /** * Get the registered containers. * * @since 4.0.0 * * @return array */ public function containers() { return $this->containers; } /** * Get the registered sections. * * @since 3.4.0 * * @return array */ public function sections() { return $this->sections; } /** * Get the registered panels. * * @since 4.0.0 * * @return array Panels. */ public function panels() { return $this->panels; } /** * Checks if the current theme is active. * * @since 3.4.0 * * @return bool */ public function is_theme_active() { return $this->get_stylesheet() == $this->original_stylesheet; } /** * Register styles/scripts and initialize the preview of each setting * * @since 3.4.0 */ public function wp_loaded() { /** * Fires once WordPress has loaded, allowing scripts and styles to be initialized. * * @since 3.4.0 * * @param WP_Customize_Manager $this WP_Customize_Manager instance. */ do_action( 'customize_register', $this ); if ( $this->settings_previewed() ) { foreach ( $this->settings as $setting ) { $setting->preview(); } } if ( $this->is_preview() && ! is_admin() ) { $this->customize_preview_init(); } } /** * Prevents Ajax requests from following redirects when previewing a theme * by issuing a 200 response instead of a 30x. * * Instead, the JS will sniff out the location header. * * @since 3.4.0 * @deprecated 4.7.0 * * @param int $status Status. * @return int */ public function wp_redirect_status( $status ) { _deprecated_function( __FUNCTION__, '4.7.0' ); if ( $this->is_preview() && ! is_admin() ) { return 200; } return $status; } /** * Find the changeset post ID for a given changeset UUID. * * @since 4.7.0 * * @param string $uuid Changeset UUID. * @return int|null Returns post ID on success and null on failure. */ public function find_changeset_post_id( $uuid ) { $cache_group = 'customize_changeset_post'; $changeset_post_id = wp_cache_get( $uuid, $cache_group ); if ( $changeset_post_id && 'customize_changeset' === get_post_type( $changeset_post_id ) ) { return $changeset_post_id; } $changeset_post_query = new WP_Query( array( 'post_type' => 'customize_changeset', 'post_status' => get_post_stati(), 'name' => $uuid, 'posts_per_page' => 1, 'no_found_rows' => true, 'cache_results' => true, 'update_post_meta_cache' => false, 'update_post_term_cache' => false, 'lazy_load_term_meta' => false, ) ); if ( ! empty( $changeset_post_query->posts ) ) { // Note: 'fields'=>'ids' is not being used in order to cache the post object as it will be needed. $changeset_post_id = $changeset_post_query->posts[0]->ID; wp_cache_set( $uuid, $changeset_post_id, $cache_group ); return $changeset_post_id; } return null; } /** * Get the changeset post id for the loaded changeset. * * @since 4.7.0 * * @return int|null Post ID on success or null if there is no post yet saved. */ public function changeset_post_id() { if ( ! isset( $this->_changeset_post_id ) ) { $post_id = $this->find_changeset_post_id( $this->_changeset_uuid ); if ( ! $post_id ) { $post_id = false; } $this->_changeset_post_id = $post_id; } if ( false === $this->_changeset_post_id ) { return null; } return $this->_changeset_post_id; } /** * Get the data stored in a changeset post. * * @since 4.7.0 * * @param int $post_id Changeset post ID. * @return array|WP_Error Changeset data or WP_Error on error. */ protected function get_changeset_post_data( $post_id ) { if ( ! $post_id ) { return new WP_Error( 'empty_post_id' ); } $changeset_post = get_post( $post_id ); if ( ! $changeset_post ) { return new WP_Error( 'missing_post' ); } if ( 'customize_changeset' !== $changeset_post->post_type ) { return new WP_Error( 'wrong_post_type' ); } $changeset_data = json_decode( $changeset_post->post_content, true ); if ( function_exists( 'json_last_error' ) && json_last_error() ) { return new WP_Error( 'json_parse_error', '', json_last_error() ); } if ( ! is_array( $changeset_data ) ) { return new WP_Error( 'expected_array' ); } return $changeset_data; } /** * Get changeset data. * * @since 4.7.0 * * @return array Changeset data. */ public function changeset_data() { if ( isset( $this->_changeset_data ) ) { return $this->_changeset_data; } $changeset_post_id = $this->changeset_post_id(); if ( ! $changeset_post_id ) { $this->_changeset_data = array(); } else { $data = $this->get_changeset_post_data( $changeset_post_id ); if ( ! is_wp_error( $data ) ) { $this->_changeset_data = $data; } else { $this->_changeset_data = array(); } } return $this->_changeset_data; } /** * Starter content setting IDs. * * @since 4.7.0 * @var array */ protected $pending_starter_content_settings_ids = array(); /** * Import theme starter content into the customized state. * * @since 4.7.0 * * @param array $starter_content Starter content. Defaults to `get_theme_starter_content()`. */ function import_theme_starter_content( $starter_content = array() ) { if ( empty( $starter_content ) ) { $starter_content = get_theme_starter_content(); } $changeset_data = array(); if ( $this->changeset_post_id() ) { $changeset_data = $this->get_changeset_post_data( $this->changeset_post_id() ); } $sidebars_widgets = isset( $starter_content['widgets'] ) && ! empty( $this->widgets ) ? $starter_content['widgets'] : array(); $attachments = isset( $starter_content['attachments'] ) && ! empty( $this->nav_menus ) ? $starter_content['attachments'] : array(); $posts = isset( $starter_content['posts'] ) && ! empty( $this->nav_menus ) ? $starter_content['posts'] : array(); $options = isset( $starter_content['options'] ) ? $starter_content['options'] : array(); $nav_menus = isset( $starter_content['nav_menus'] ) && ! empty( $this->nav_menus ) ? $starter_content['nav_menus'] : array(); $theme_mods = isset( $starter_content['theme_mods'] ) ? $starter_content['theme_mods'] : array(); // Widgets. $max_widget_numbers = array(); foreach ( $sidebars_widgets as $sidebar_id => $widgets ) { $sidebar_widget_ids = array(); foreach ( $widgets as $widget ) { list( $id_base, $instance ) = $widget; if ( ! isset( $max_widget_numbers[ $id_base ] ) ) { // When $settings is an array-like object, get an intrinsic array for use with array_keys(). $settings = get_option( "widget_{$id_base}", array() ); if ( $settings instanceof ArrayObject || $settings instanceof ArrayIterator ) { $settings = $settings->getArrayCopy(); } // Find the max widget number for this type. $widget_numbers = array_keys( $settings ); if ( count( $widget_numbers ) > 0 ) { $widget_numbers[] = 1; $max_widget_numbers[ $id_base ] = call_user_func_array( 'max', $widget_numbers ); } else { $max_widget_numbers[ $id_base ] = 1; } } $max_widget_numbers[ $id_base ] += 1; $widget_id = sprintf( '%s-%d', $id_base, $max_widget_numbers[ $id_base ] ); $setting_id = sprintf( 'widget_%s[%d]', $id_base, $max_widget_numbers[ $id_base ] ); $setting_value = $this->widgets->sanitize_widget_js_instance( $instance ); if ( empty( $changeset_data[ $setting_id ] ) || ! empty( $changeset_data[ $setting_id ]['starter_content'] ) ) { $this->set_post_value( $setting_id, $setting_value ); $this->pending_starter_content_settings_ids[] = $setting_id; } $sidebar_widget_ids[] = $widget_id; } $setting_id = sprintf( 'sidebars_widgets[%s]', $sidebar_id ); if ( empty( $changeset_data[ $setting_id ] ) || ! empty( $changeset_data[ $setting_id ]['starter_content'] ) ) { $this->set_post_value( $setting_id, $sidebar_widget_ids ); $this->pending_starter_content_settings_ids[] = $setting_id; } } $starter_content_auto_draft_post_ids = array(); if ( ! empty( $changeset_data['nav_menus_created_posts']['value'] ) ) { $starter_content_auto_draft_post_ids = array_merge( $starter_content_auto_draft_post_ids, $changeset_data['nav_menus_created_posts']['value'] ); } // Make an index of all the posts needed and what their slugs are. $needed_posts = array(); $attachments = $this->prepare_starter_content_attachments( $attachments ); foreach ( $attachments as $attachment ) { $key = 'attachment:' . $attachment['post_name']; $needed_posts[ $key ] = true; } foreach ( array_keys( $posts ) as $post_symbol ) { if ( empty( $posts[ $post_symbol ]['post_name'] ) && empty( $posts[ $post_symbol ]['post_title'] ) ) { unset( $posts[ $post_symbol ] ); continue; } if ( empty( $posts[ $post_symbol ]['post_name'] ) ) { $posts[ $post_symbol ]['post_name'] = sanitize_title( $posts[ $post_symbol ]['post_title'] ); } if ( empty( $posts[ $post_symbol ]['post_type'] ) ) { $posts[ $post_symbol ]['post_type'] = 'post'; } $needed_posts[ $posts[ $post_symbol ]['post_type'] . ':' . $posts[ $post_symbol ]['post_name'] ] = true; } $all_post_slugs = array_merge( wp_list_pluck( $attachments, 'post_name' ), wp_list_pluck( $posts, 'post_name' ) ); /* * Obtain all post types referenced in starter content to use in query. * This is needed because 'any' will not account for post types not yet registered. */ $post_types = array_filter( array_merge( array( 'attachment' ), wp_list_pluck( $posts, 'post_type' ) ) ); // Re-use auto-draft starter content posts referenced in the current customized state. $existing_starter_content_posts = array(); if ( ! empty( $starter_content_auto_draft_post_ids ) ) { $existing_posts_query = new WP_Query( array( 'post__in' => $starter_content_auto_draft_post_ids, 'post_status' => 'auto-draft', 'post_type' => $post_types, 'posts_per_page' => -1, ) ); foreach ( $existing_posts_query->posts as $existing_post ) { $post_name = $existing_post->post_name; if ( empty( $post_name ) ) { $post_name = get_post_meta( $existing_post->ID, '_customize_draft_post_name', true ); } $existing_starter_content_posts[ $existing_post->post_type . ':' . $post_name ] = $existing_post; } } // Re-use non-auto-draft posts. if ( ! empty( $all_post_slugs ) ) { $existing_posts_query = new WP_Query( array( 'post_name__in' => $all_post_slugs, 'post_status' => array_diff( get_post_stati(), array( 'auto-draft' ) ), 'post_type' => 'any', 'posts_per_page' => -1, ) ); foreach ( $existing_posts_query->posts as $existing_post ) { $key = $existing_post->post_type . ':' . $existing_post->post_name; if ( isset( $needed_posts[ $key ] ) && ! isset( $existing_starter_content_posts[ $key ] ) ) { $existing_starter_content_posts[ $key ] = $existing_post; } } } // Attachments are technically posts but handled differently. if ( ! empty( $attachments ) ) { $attachment_ids = array(); foreach ( $attachments as $symbol => $attachment ) { $file_array = array( 'name' => $attachment['file_name'], ); $file_path = $attachment['file_path']; $attachment_id = null; $attached_file = null; if ( isset( $existing_starter_content_posts[ 'attachment:' . $attachment['post_name'] ] ) ) { $attachment_post = $existing_starter_content_posts[ 'attachment:' . $attachment['post_name'] ]; $attachment_id = $attachment_post->ID; $attached_file = get_attached_file( $attachment_id ); if ( empty( $attached_file ) || ! file_exists( $attached_file ) ) { $attachment_id = null; $attached_file = null; } elseif ( $this->get_stylesheet() !== get_post_meta( $attachment_post->ID, '_starter_content_theme', true ) ) { // Re-generate attachment metadata since it was previously generated for a different theme. $metadata = wp_generate_attachment_metadata( $attachment_post->ID, $attached_file ); wp_update_attachment_metadata( $attachment_id, $metadata ); update_post_meta( $attachment_id, '_starter_content_theme', $this->get_stylesheet() ); } } // Insert the attachment auto-draft because it doesn't yet exist or the attached file is gone. if ( ! $attachment_id ) { // Copy file to temp location so that original file won't get deleted from theme after sideloading. $temp_file_name = wp_tempnam( basename( $file_path ) ); if ( $temp_file_name && copy( $file_path, $temp_file_name ) ) { $file_array['tmp_name'] = $temp_file_name; } if ( empty( $file_array['tmp_name'] ) ) { continue; } $attachment_post_data = array_merge( wp_array_slice_assoc( $attachment, array( 'post_title', 'post_content', 'post_excerpt' ) ), array( 'post_status' => 'auto-draft', // So attachment will be garbage collected in a week if changeset is never published. ) ); // In PHP < 5.6 filesize() returns 0 for the temp files unless we clear the file status cache. // Technically, PHP < 5.6.0 || < 5.5.13 || < 5.4.29 but no need to be so targeted. // See https://bugs.php.net/bug.php?id=65701 if ( version_compare( PHP_VERSION, '5.6', '<' ) ) { clearstatcache(); } $attachment_id = media_handle_sideload( $file_array, 0, null, $attachment_post_data ); if ( is_wp_error( $attachment_id ) ) { continue; } update_post_meta( $attachment_id, '_starter_content_theme', $this->get_stylesheet() ); update_post_meta( $attachment_id, '_customize_draft_post_name', $attachment['post_name'] ); } $attachment_ids[ $symbol ] = $attachment_id; } $starter_content_auto_draft_post_ids = array_merge( $starter_content_auto_draft_post_ids, array_values( $attachment_ids ) ); } // Posts & pages. if ( ! empty( $posts ) ) { foreach ( array_keys( $posts ) as $post_symbol ) { if ( empty( $posts[ $post_symbol ]['post_type'] ) || empty( $posts[ $post_symbol ]['post_name'] ) ) { continue; } $post_type = $posts[ $post_symbol ]['post_type']; if ( ! empty( $posts[ $post_symbol ]['post_name'] ) ) { $post_name = $posts[ $post_symbol ]['post_name']; } elseif ( ! empty( $posts[ $post_symbol ]['post_title'] ) ) { $post_name = sanitize_title( $posts[ $post_symbol ]['post_title'] ); } else { continue; } // Use existing auto-draft post if one already exists with the same type and name. if ( isset( $existing_starter_content_posts[ $post_type . ':' . $post_name ] ) ) { $posts[ $post_symbol ]['ID'] = $existing_starter_content_posts[ $post_type . ':' . $post_name ]->ID; continue; } // Translate the featured image symbol. if ( ! empty( $posts[ $post_symbol ]['thumbnail'] ) && preg_match( '/^{{(?Pcustomize_loaded_components
'
);
_doing_it_wrong( __METHOD__, $message, '4.5.0' );
}
unset( $this->panels[ $id ] );
}
/**
* Register a customize panel type.
*
* Registered types are eligible to be rendered via JS and created dynamically.
*
* @since 4.3.0
*
* @see WP_Customize_Panel
*
* @param string $panel Name of a custom panel which is a subclass of WP_Customize_Panel.
*/
public function register_panel_type( $panel ) {
$this->registered_panel_types[] = $panel;
}
/**
* Render JS templates for all registered panel types.
*
* @since 4.3.0
*/
public function render_panel_templates() {
foreach ( $this->registered_panel_types as $panel_type ) {
$panel = new $panel_type( $this, 'temp', array() );
$panel->print_template();
}
}
/**
* Add a customize section.
*
* @since 3.4.0
* @since 4.5.0 Return added WP_Customize_Section instance.
*
* @param WP_Customize_Section|string $id Customize Section object, or Section ID.
* @param array $args {
* Optional. Array of properties for the new Panel object. Default empty array.
* @type int $priority Priority of the panel, defining the display order of panels and sections.
* Default 160.
* @type string $panel Priority of the panel, defining the display order of panels and sections.
* @type string $capability Capability required for the panel. Default 'edit_theme_options'
* @type string|array $theme_supports Theme features required to support the panel.
* @type string $title Title of the panel to show in UI.
* @type string $description Description to show in the UI.
* @type string $type Type of the panel.
* @type callable $active_callback Active callback.
* @type bool $description_hidden Hide the description behind a help icon, instead of . Default false.
* }
* @return WP_Customize_Section The instance of the section that was added.
*/
public function add_section( $id, $args = array() ) {
if ( $id instanceof WP_Customize_Section ) {
$section = $id;
} else {
$section = new WP_Customize_Section( $this, $id, $args );
}
$this->sections[ $section->id ] = $section;
return $section;
}
/**
* Retrieve a customize section.
*
* @since 3.4.0
*
* @param string $id Section ID.
* @return WP_Customize_Section|void The section, if set.
*/
public function get_section( $id ) {
if ( isset( $this->sections[ $id ] ) )
return $this->sections[ $id ];
}
/**
* Remove a customize section.
*
* @since 3.4.0
*
* @param string $id Section ID.
*/
public function remove_section( $id ) {
unset( $this->sections[ $id ] );
}
/**
* Register a customize section type.
*
* Registered types are eligible to be rendered via JS and created dynamically.
*
* @since 4.3.0
*
* @see WP_Customize_Section
*
* @param string $section Name of a custom section which is a subclass of WP_Customize_Section.
*/
public function register_section_type( $section ) {
$this->registered_section_types[] = $section;
}
/**
* Render JS templates for all registered section types.
*
* @since 4.3.0
*/
public function render_section_templates() {
foreach ( $this->registered_section_types as $section_type ) {
$section = new $section_type( $this, 'temp', array() );
$section->print_template();
}
}
/**
* Add a customize control.
*
* @since 3.4.0
* @since 4.5.0 Return added WP_Customize_Control instance.
*
* @param WP_Customize_Control|string $id Customize Control object, or ID.
* @param array $args {
* Optional. Array of properties for the new Control object. Default empty array.
*
* @type array $settings All settings tied to the control. If undefined, defaults to `$setting`.
* IDs in the array correspond to the ID of a registered `WP_Customize_Setting`.
* @type string $setting The primary setting for the control (if there is one). Default is 'default'.
* @type string $capability Capability required to use this control. Normally derived from `$settings`.
* @type int $priority Order priority to load the control. Default 10.
* @type string $section The section this control belongs to. Default empty.
* @type string $label Label for the control. Default empty.
* @type string $description Description for the control. Default empty.
* @type array $choices List of choices for 'radio' or 'select' type controls, where values
* are the keys, and labels are the values. Default empty array.
* @type array $input_attrs List of custom input attributes for control output, where attribute
* names are the keys and values are the values. Default empty array.
* @type bool $allow_addition Show UI for adding new content, currently only used for the
* dropdown-pages control. Default false.
* @type string $type The type of the control. Default 'text'.
* @type callback $active_callback Active callback.
* }
* @return WP_Customize_Control The instance of the control that was added.
*/
public function add_control( $id, $args = array() ) {
if ( $id instanceof WP_Customize_Control ) {
$control = $id;
} else {
$control = new WP_Customize_Control( $this, $id, $args );
}
$this->controls[ $control->id ] = $control;
return $control;
}
/**
* Retrieve a customize control.
*
* @since 3.4.0
*
* @param string $id ID of the control.
* @return WP_Customize_Control|void The control object, if set.
*/
public function get_control( $id ) {
if ( isset( $this->controls[ $id ] ) )
return $this->controls[ $id ];
}
/**
* Remove a customize control.
*
* @since 3.4.0
*
* @param string $id ID of the control.
*/
public function remove_control( $id ) {
unset( $this->controls[ $id ] );
}
/**
* Register a customize control type.
*
* Registered types are eligible to be rendered via JS and created dynamically.
*
* @since 4.1.0
*
* @param string $control Name of a custom control which is a subclass of
* WP_Customize_Control.
*/
public function register_control_type( $control ) {
$this->registered_control_types[] = $control;
}
/**
* Render JS templates for all registered control types.
*
* @since 4.1.0
*/
public function render_control_templates() {
foreach ( $this->registered_control_types as $control_type ) {
$control = new $control_type( $this, 'temp', array(
'settings' => array(),
) );
$control->print_template();
}
?>
priority === $b->priority ) {
return $a->instance_number - $b->instance_number;
} else {
return $a->priority - $b->priority;
}
}
/**
* Prepare panels, sections, and controls.
*
* For each, check if required related components exist,
* whether the user has the necessary capabilities,
* and sort by priority.
*
* @since 3.4.0
*/
public function prepare_controls() {
$controls = array();
$this->controls = wp_list_sort( $this->controls, array(
'priority' => 'ASC',
'instance_number' => 'ASC',
), 'ASC', true );
foreach ( $this->controls as $id => $control ) {
if ( ! isset( $this->sections[ $control->section ] ) || ! $control->check_capabilities() ) {
continue;
}
$this->sections[ $control->section ]->controls[] = $control;
$controls[ $id ] = $control;
}
$this->controls = $controls;
// Prepare sections.
$this->sections = wp_list_sort( $this->sections, array(
'priority' => 'ASC',
'instance_number' => 'ASC',
), 'ASC', true );
$sections = array();
foreach ( $this->sections as $section ) {
if ( ! $section->check_capabilities() ) {
continue;
}
$section->controls = wp_list_sort( $section->controls, array(
'priority' => 'ASC',
'instance_number' => 'ASC',
) );
if ( ! $section->panel ) {
// Top-level section.
$sections[ $section->id ] = $section;
} else {
// This section belongs to a panel.
if ( isset( $this->panels [ $section->panel ] ) ) {
$this->panels[ $section->panel ]->sections[ $section->id ] = $section;
}
}
}
$this->sections = $sections;
// Prepare panels.
$this->panels = wp_list_sort( $this->panels, array(
'priority' => 'ASC',
'instance_number' => 'ASC',
), 'ASC', true );
$panels = array();
foreach ( $this->panels as $panel ) {
if ( ! $panel->check_capabilities() ) {
continue;
}
$panel->sections = wp_list_sort( $panel->sections, array(
'priority' => 'ASC',
'instance_number' => 'ASC',
), 'ASC', true );
$panels[ $panel->id ] = $panel;
}
$this->panels = $panels;
// Sort panels and top-level sections together.
$this->containers = array_merge( $this->panels, $this->sections );
$this->containers = wp_list_sort( $this->containers, array(
'priority' => 'ASC',
'instance_number' => 'ASC',
), 'ASC', true );
}
/**
* Enqueue scripts for customize controls.
*
* @since 3.4.0
*/
public function enqueue_control_scripts() {
foreach ( $this->controls as $control ) {
$control->enqueue();
}
}
/**
* Determine whether the user agent is iOS.
*
* @since 4.4.0
*
* @return bool Whether the user agent is iOS.
*/
public function is_ios() {
return wp_is_mobile() && preg_match( '/iPad|iPod|iPhone/', $_SERVER['HTTP_USER_AGENT'] );
}
/**
* Get the template string for the Customizer pane document title.
*
* @since 4.4.0
*
* @return string The template string for the document title.
*/
public function get_document_title_template() {
if ( $this->is_theme_active() ) {
/* translators: %s: document title from the preview */
$document_title_tmpl = __( 'Customize: %s' );
} else {
/* translators: %s: document title from the preview */
$document_title_tmpl = __( 'Live Preview: %s' );
}
$document_title_tmpl = html_entity_decode( $document_title_tmpl, ENT_QUOTES, 'UTF-8' ); // Because exported to JS and assigned to document.title.
return $document_title_tmpl;
}
/**
* Set the initial URL to be previewed.
*
* URL is validated.
*
* @since 4.4.0
*
* @param string $preview_url URL to be previewed.
*/
public function set_preview_url( $preview_url ) {
$preview_url = esc_url_raw( $preview_url );
$this->preview_url = wp_validate_redirect( $preview_url, home_url( '/' ) );
}
/**
* Get the initial URL to be previewed.
*
* @since 4.4.0
*
* @return string URL being previewed.
*/
public function get_preview_url() {
if ( empty( $this->preview_url ) ) {
$preview_url = home_url( '/' );
} else {
$preview_url = $this->preview_url;
}
return $preview_url;
}
/**
* Determines whether the admin and the frontend are on different domains.
*
* @since 4.7.0
*
* @return bool Whether cross-domain.
*/
public function is_cross_domain() {
$admin_origin = wp_parse_url( admin_url() );
$home_origin = wp_parse_url( home_url() );
$cross_domain = ( strtolower( $admin_origin['host'] ) !== strtolower( $home_origin['host'] ) );
return $cross_domain;
}
/**
* Get URLs allowed to be previewed.
*
* If the front end and the admin are served from the same domain, load the
* preview over ssl if the Customizer is being loaded over ssl. This avoids
* insecure content warnings. This is not attempted if the admin and front end
* are on different domains to avoid the case where the front end doesn't have
* ssl certs. Domain mapping plugins can allow other urls in these conditions
* using the customize_allowed_urls filter.
*
* @since 4.7.0
*
* @returns array Allowed URLs.
*/
public function get_allowed_urls() {
$allowed_urls = array( home_url( '/' ) );
if ( is_ssl() && ! $this->is_cross_domain() ) {
$allowed_urls[] = home_url( '/', 'https' );
}
/**
* Filters the list of URLs allowed to be clicked and followed in the Customizer preview.
*
* @since 3.4.0
*
* @param array $allowed_urls An array of allowed URLs.
*/
$allowed_urls = array_unique( apply_filters( 'customize_allowed_urls', $allowed_urls ) );
return $allowed_urls;
}
/**
* Get messenger channel.
*
* @since 4.7.0
*
* @return string Messenger channel.
*/
public function get_messenger_channel() {
return $this->messenger_channel;
}
/**
* Set URL to link the user to when closing the Customizer.
*
* URL is validated.
*
* @since 4.4.0
*
* @param string $return_url URL for return link.
*/
public function set_return_url( $return_url ) {
$return_url = esc_url_raw( $return_url );
$return_url = remove_query_arg( wp_removable_query_args(), $return_url );
$return_url = wp_validate_redirect( $return_url );
$this->return_url = $return_url;
}
/**
* Get URL to link the user to when closing the Customizer.
*
* @since 4.4.0
*
* @return string URL for link to close Customizer.
*/
public function get_return_url() {
$referer = wp_get_referer();
$excluded_referer_basenames = array( 'customize.php', 'wp-login.php' );
if ( $this->return_url ) {
$return_url = $this->return_url;
} else if ( $referer && ! in_array( basename( parse_url( $referer, PHP_URL_PATH ) ), $excluded_referer_basenames, true ) ) {
$return_url = $referer;
} else if ( $this->preview_url ) {
$return_url = $this->preview_url;
} else {
$return_url = home_url( '/' );
}
return $return_url;
}
/**
* Set the autofocused constructs.
*
* @since 4.4.0
*
* @param array $autofocus {
* Mapping of 'panel', 'section', 'control' to the ID which should be autofocused.
*
* @type string [$control] ID for control to be autofocused.
* @type string [$section] ID for section to be autofocused.
* @type string [$panel] ID for panel to be autofocused.
* }
*/
public function set_autofocus( $autofocus ) {
$this->autofocus = array_filter( wp_array_slice_assoc( $autofocus, array( 'panel', 'section', 'control' ) ), 'is_string' );
}
/**
* Get the autofocused constructs.
*
* @since 4.4.0
*
* @return array {
* Mapping of 'panel', 'section', 'control' to the ID which should be autofocused.
*
* @type string [$control] ID for control to be autofocused.
* @type string [$section] ID for section to be autofocused.
* @type string [$panel] ID for panel to be autofocused.
* }
*/
public function get_autofocus() {
return $this->autofocus;
}
/**
* Get nonces for the Customizer.
*
* @since 4.5.0
*
* @return array Nonces.
*/
public function get_nonces() {
$nonces = array(
'save' => wp_create_nonce( 'save-customize_' . $this->get_stylesheet() ),
'preview' => wp_create_nonce( 'preview-customize_' . $this->get_stylesheet() ),
);
/**
* Filters nonces for Customizer.
*
* @since 4.2.0
*
* @param array $nonces Array of refreshed nonces for save and
* preview actions.
* @param WP_Customize_Manager $this WP_Customize_Manager instance.
*/
$nonces = apply_filters( 'customize_refresh_nonces', $nonces, $this );
return $nonces;
}
/**
* Print JavaScript settings for parent window.
*
* @since 4.4.0
*/
public function customize_pane_settings() {
$login_url = add_query_arg( array(
'interim-login' => 1,
'customize-login' => 1,
), wp_login_url() );
// Ensure dirty flags are set for modified settings.
foreach ( array_keys( $this->unsanitized_post_values() ) as $setting_id ) {
$setting = $this->get_setting( $setting_id );
if ( $setting ) {
$setting->dirty = true;
}
}
// Prepare Customizer settings to pass to JavaScript.
$settings = array(
'changeset' => array(
'uuid' => $this->changeset_uuid(),
'status' => $this->changeset_post_id() ? get_post_status( $this->changeset_post_id() ) : '',
),
'timeouts' => array(
'windowRefresh' => 250,
'changesetAutoSave' => AUTOSAVE_INTERVAL * 1000,
'keepAliveCheck' => 2500,
'reflowPaneContents' => 100,
'previewFrameSensitivity' => 2000,
),
'theme' => array(
'stylesheet' => $this->get_stylesheet(),
'active' => $this->is_theme_active(),
),
'url' => array(
'preview' => esc_url_raw( $this->get_preview_url() ),
'parent' => esc_url_raw( admin_url() ),
'activated' => esc_url_raw( home_url( '/' ) ),
'ajax' => esc_url_raw( admin_url( 'admin-ajax.php', 'relative' ) ),
'allowed' => array_map( 'esc_url_raw', $this->get_allowed_urls() ),
'isCrossDomain' => $this->is_cross_domain(),
'home' => esc_url_raw( home_url( '/' ) ),
'login' => esc_url_raw( $login_url ),
),
'browser' => array(
'mobile' => wp_is_mobile(),
'ios' => $this->is_ios(),
),
'panels' => array(),
'sections' => array(),
'nonce' => $this->get_nonces(),
'autofocus' => $this->get_autofocus(),
'documentTitleTmpl' => $this->get_document_title_template(),
'previewableDevices' => $this->get_previewable_devices(),
);
// Prepare Customize Section objects to pass to JavaScript.
foreach ( $this->sections() as $id => $section ) {
if ( $section->check_capabilities() ) {
$settings['sections'][ $id ] = $section->json();
}
}
// Prepare Customize Panel objects to pass to JavaScript.
foreach ( $this->panels() as $panel_id => $panel ) {
if ( $panel->check_capabilities() ) {
$settings['panels'][ $panel_id ] = $panel->json();
foreach ( $panel->sections as $section_id => $section ) {
if ( $section->check_capabilities() ) {
$settings['sections'][ $section_id ] = $section->json();
}
}
}
}
?>
array(
'label' => __( 'Enter desktop preview mode' ),
'default' => true,
),
'tablet' => array(
'label' => __( 'Enter tablet preview mode' ),
),
'mobile' => array(
'label' => __( 'Enter mobile preview mode' ),
),
);
/**
* Filters the available devices to allow previewing in the Customizer.
*
* @since 4.5.0
*
* @see WP_Customize_Manager::get_previewable_devices()
*
* @param array $devices List of devices with labels and default setting.
*/
$devices = apply_filters( 'customize_previewable_devices', $devices );
return $devices;
}
/**
* Register some default controls.
*
* @since 3.4.0
*/
public function register_controls() {
/* Panel, Section, and Control Types */
$this->register_panel_type( 'WP_Customize_Panel' );
$this->register_section_type( 'WP_Customize_Section' );
$this->register_section_type( 'WP_Customize_Sidebar_Section' );
$this->register_control_type( 'WP_Customize_Color_Control' );
$this->register_control_type( 'WP_Customize_Media_Control' );
$this->register_control_type( 'WP_Customize_Upload_Control' );
$this->register_control_type( 'WP_Customize_Image_Control' );
$this->register_control_type( 'WP_Customize_Background_Image_Control' );
$this->register_control_type( 'WP_Customize_Background_Position_Control' );
$this->register_control_type( 'WP_Customize_Cropped_Image_Control' );
$this->register_control_type( 'WP_Customize_Site_Icon_Control' );
$this->register_control_type( 'WP_Customize_Theme_Control' );
$this->register_control_type( 'WP_Customize_Code_Editor_Control' );
/* Themes */
$this->add_section( new WP_Customize_Themes_Section( $this, 'themes', array(
'title' => $this->theme()->display( 'Name' ),
'capability' => 'switch_themes',
'priority' => 0,
) ) );
// Themes Setting (unused - the theme is considerably more fundamental to the Customizer experience).
$this->add_setting( new WP_Customize_Filter_Setting( $this, 'active_theme', array(
'capability' => 'switch_themes',
) ) );
require_once( ABSPATH . 'wp-admin/includes/theme.php' );
// Theme Controls.
// Add a control for the active/original theme.
if ( ! $this->is_theme_active() ) {
$themes = wp_prepare_themes_for_js( array( wp_get_theme( $this->original_stylesheet ) ) );
$active_theme = current( $themes );
$active_theme['isActiveTheme'] = true;
$this->add_control( new WP_Customize_Theme_Control( $this, $active_theme['id'], array(
'theme' => $active_theme,
'section' => 'themes',
'settings' => 'active_theme',
) ) );
}
$themes = wp_prepare_themes_for_js();
foreach ( $themes as $theme ) {
if ( $theme['active'] || $theme['id'] === $this->original_stylesheet ) {
continue;
}
$theme_id = 'theme_' . $theme['id'];
$theme['isActiveTheme'] = false;
$this->add_control( new WP_Customize_Theme_Control( $this, $theme_id, array(
'theme' => $theme,
'section' => 'themes',
'settings' => 'active_theme',
) ) );
}
/* Site Identity */
$this->add_section( 'title_tagline', array(
'title' => __( 'Site Identity' ),
'priority' => 20,
) );
$this->add_setting( 'blogname', array(
'default' => get_option( 'blogname' ),
'type' => 'option',
'capability' => 'manage_options',
) );
$this->add_control( 'blogname', array(
'label' => __( 'Site Title' ),
'section' => 'title_tagline',
) );
$this->add_setting( 'blogdescription', array(
'default' => get_option( 'blogdescription' ),
'type' => 'option',
'capability' => 'manage_options',
) );
$this->add_control( 'blogdescription', array(
'label' => __( 'Tagline' ),
'section' => 'title_tagline',
) );
// Add a setting to hide header text if the theme doesn't support custom headers.
if ( ! current_theme_supports( 'custom-header', 'header-text' ) ) {
$this->add_setting( 'header_text', array(
'theme_supports' => array( 'custom-logo', 'header-text' ),
'default' => 1,
'sanitize_callback' => 'absint',
) );
$this->add_control( 'header_text', array(
'label' => __( 'Display Site Title and Tagline' ),
'section' => 'title_tagline',
'settings' => 'header_text',
'type' => 'checkbox',
) );
}
$this->add_setting( 'site_icon', array(
'type' => 'option',
'capability' => 'manage_options',
'transport' => 'postMessage', // Previewed with JS in the Customizer controls window.
) );
$this->add_control( new WP_Customize_Site_Icon_Control( $this, 'site_icon', array(
'label' => __( 'Site Icon' ),
'description' => sprintf(
/* translators: %s: site icon size in pixels */
__( 'The Site Icon is used as a browser and app icon for your site. Icons must be square, and at least %s pixels wide and tall.' ),
'512'
),
'section' => 'title_tagline',
'priority' => 60,
'height' => 512,
'width' => 512,
) ) );
$this->add_setting( 'custom_logo', array(
'theme_supports' => array( 'custom-logo' ),
'transport' => 'postMessage',
) );
$custom_logo_args = get_theme_support( 'custom-logo' );
$this->add_control( new WP_Customize_Cropped_Image_Control( $this, 'custom_logo', array(
'label' => __( 'Logo' ),
'section' => 'title_tagline',
'priority' => 8,
'height' => $custom_logo_args[0]['height'],
'width' => $custom_logo_args[0]['width'],
'flex_height' => $custom_logo_args[0]['flex-height'],
'flex_width' => $custom_logo_args[0]['flex-width'],
'button_labels' => array(
'select' => __( 'Select logo' ),
'change' => __( 'Change logo' ),
'remove' => __( 'Remove' ),
'default' => __( 'Default' ),
'placeholder' => __( 'No logo selected' ),
'frame_title' => __( 'Select logo' ),
'frame_button' => __( 'Choose logo' ),
),
) ) );
$this->selective_refresh->add_partial( 'custom_logo', array(
'settings' => array( 'custom_logo' ),
'selector' => '.custom-logo-link',
'render_callback' => array( $this, '_render_custom_logo_partial' ),
'container_inclusive' => true,
) );
/* Colors */
$this->add_section( 'colors', array(
'title' => __( 'Colors' ),
'priority' => 40,
) );
$this->add_setting( 'header_textcolor', array(
'theme_supports' => array( 'custom-header', 'header-text' ),
'default' => get_theme_support( 'custom-header', 'default-text-color' ),
'sanitize_callback' => array( $this, '_sanitize_header_textcolor' ),
'sanitize_js_callback' => 'maybe_hash_hex_color',
) );
// Input type: checkbox
// With custom value
$this->add_control( 'display_header_text', array(
'settings' => 'header_textcolor',
'label' => __( 'Display Site Title and Tagline' ),
'section' => 'title_tagline',
'type' => 'checkbox',
'priority' => 40,
) );
$this->add_control( new WP_Customize_Color_Control( $this, 'header_textcolor', array(
'label' => __( 'Header Text Color' ),
'section' => 'colors',
) ) );
// Input type: Color
// With sanitize_callback
$this->add_setting( 'background_color', array(
'default' => get_theme_support( 'custom-background', 'default-color' ),
'theme_supports' => 'custom-background',
'sanitize_callback' => 'sanitize_hex_color_no_hash',
'sanitize_js_callback' => 'maybe_hash_hex_color',
) );
$this->add_control( new WP_Customize_Color_Control( $this, 'background_color', array(
'label' => __( 'Background Color' ),
'section' => 'colors',
) ) );
/* Custom Header */
if ( current_theme_supports( 'custom-header', 'video' ) ) {
$title = __( 'Header Media' );
$description = '' . __( 'If you add a video, the image will be used as a fallback while the video loads.' ) . '
'; $width = absint( get_theme_support( 'custom-header', 'width' ) ); $height = absint( get_theme_support( 'custom-header', 'height' ) ); if ( $width && $height ) { $control_description = sprintf( /* translators: 1: .mp4, 2: header size in pixels */ __( 'Upload your video in %1$s format and minimize its file size for best results. Your theme recommends dimensions of %2$s pixels.' ), '.mp4
',
sprintf( '%s × %s', $width, $height )
);
} elseif ( $width ) {
$control_description = sprintf(
/* translators: 1: .mp4, 2: header width in pixels */
__( 'Upload your video in %1$s format and minimize its file size for best results. Your theme recommends a width of %2$s pixels.' ),
'.mp4
',
sprintf( '%s', $width )
);
} else {
$control_description = sprintf(
/* translators: 1: .mp4, 2: header height in pixels */
__( 'Upload your video in %1$s format and minimize its file size for best results. Your theme recommends a height of %2$s pixels.' ),
'.mp4
',
sprintf( '%s', $height )
);
}
} else {
$title = __( 'Header Image' );
$description = '';
$control_description = '';
}
$this->add_section( 'header_image', array(
'title' => $title,
'description' => $description,
'theme_supports' => 'custom-header',
'priority' => 60,
) );
$this->add_setting( 'header_video', array(
'theme_supports' => array( 'custom-header', 'video' ),
'transport' => 'postMessage',
'sanitize_callback' => 'absint',
'validate_callback' => array( $this, '_validate_header_video' ),
) );
$this->add_setting( 'external_header_video', array(
'theme_supports' => array( 'custom-header', 'video' ),
'transport' => 'postMessage',
'sanitize_callback' => array( $this, '_sanitize_external_header_video' ),
'validate_callback' => array( $this, '_validate_external_header_video' ),
) );
$this->add_setting( new WP_Customize_Filter_Setting( $this, 'header_image', array(
'default' => sprintf( get_theme_support( 'custom-header', 'default-image' ), get_template_directory_uri(), get_stylesheet_directory_uri() ),
'theme_supports' => 'custom-header',
) ) );
$this->add_setting( new WP_Customize_Header_Image_Setting( $this, 'header_image_data', array(
'theme_supports' => 'custom-header',
) ) );
/*
* Switch image settings to postMessage when video support is enabled since
* it entails that the_custom_header_markup() will be used, and thus selective
* refresh can be utilized.
*/
if ( current_theme_supports( 'custom-header', 'video' ) ) {
$this->get_setting( 'header_image' )->transport = 'postMessage';
$this->get_setting( 'header_image_data' )->transport = 'postMessage';
}
$this->add_control( new WP_Customize_Media_Control( $this, 'header_video', array(
'theme_supports' => array( 'custom-header', 'video' ),
'label' => __( 'Header Video' ),
'description' => $control_description,
'section' => 'header_image',
'mime_type' => 'video',
'active_callback' => 'is_header_video_active',
) ) );
$this->add_control( 'external_header_video', array(
'theme_supports' => array( 'custom-header', 'video' ),
'type' => 'url',
'description' => __( 'Or, enter a YouTube URL:' ),
'section' => 'header_image',
'active_callback' => 'is_header_video_active',
) );
$this->add_control( new WP_Customize_Header_Image_Control( $this ) );
$this->selective_refresh->add_partial( 'custom_header', array(
'selector' => '#wp-custom-header',
'render_callback' => 'the_custom_header_markup',
'settings' => array( 'header_video', 'external_header_video', 'header_image' ), // The image is used as a video fallback here.
'container_inclusive' => true,
) );
/* Custom Background */
$this->add_section( 'background_image', array(
'title' => __( 'Background Image' ),
'theme_supports' => 'custom-background',
'priority' => 80,
) );
$this->add_setting( 'background_image', array(
'default' => get_theme_support( 'custom-background', 'default-image' ),
'theme_supports' => 'custom-background',
'sanitize_callback' => array( $this, '_sanitize_background_setting' ),
) );
$this->add_setting( new WP_Customize_Background_Image_Setting( $this, 'background_image_thumb', array(
'theme_supports' => 'custom-background',
'sanitize_callback' => array( $this, '_sanitize_background_setting' ),
) ) );
$this->add_control( new WP_Customize_Background_Image_Control( $this ) );
$this->add_setting( 'background_preset', array(
'default' => get_theme_support( 'custom-background', 'default-preset' ),
'theme_supports' => 'custom-background',
'sanitize_callback' => array( $this, '_sanitize_background_setting' ),
) );
$this->add_control( 'background_preset', array(
'label' => _x( 'Preset', 'Background Preset' ),
'section' => 'background_image',
'type' => 'select',
'choices' => array(
'default' => _x( 'Default', 'Default Preset' ),
'fill' => __( 'Fill Screen' ),
'fit' => __( 'Fit to Screen' ),
'repeat' => _x( 'Repeat', 'Repeat Image' ),
'custom' => _x( 'Custom', 'Custom Preset' ),
),
) );
$this->add_setting( 'background_position_x', array(
'default' => get_theme_support( 'custom-background', 'default-position-x' ),
'theme_supports' => 'custom-background',
'sanitize_callback' => array( $this, '_sanitize_background_setting' ),
) );
$this->add_setting( 'background_position_y', array(
'default' => get_theme_support( 'custom-background', 'default-position-y' ),
'theme_supports' => 'custom-background',
'sanitize_callback' => array( $this, '_sanitize_background_setting' ),
) );
$this->add_control( new WP_Customize_Background_Position_Control( $this, 'background_position', array(
'label' => __( 'Image Position' ),
'section' => 'background_image',
'settings' => array(
'x' => 'background_position_x',
'y' => 'background_position_y',
),
) ) );
$this->add_setting( 'background_size', array(
'default' => get_theme_support( 'custom-background', 'default-size' ),
'theme_supports' => 'custom-background',
'sanitize_callback' => array( $this, '_sanitize_background_setting' ),
) );
$this->add_control( 'background_size', array(
'label' => __( 'Image Size' ),
'section' => 'background_image',
'type' => 'select',
'choices' => array(
'auto' => __( 'Original' ),
'contain' => __( 'Fit to Screen' ),
'cover' => __( 'Fill Screen' ),
),
) );
$this->add_setting( 'background_repeat', array(
'default' => get_theme_support( 'custom-background', 'default-repeat' ),
'sanitize_callback' => array( $this, '_sanitize_background_setting' ),
'theme_supports' => 'custom-background',
) );
$this->add_control( 'background_repeat', array(
'label' => __( 'Repeat Background Image' ),
'section' => 'background_image',
'type' => 'checkbox',
) );
$this->add_setting( 'background_attachment', array(
'default' => get_theme_support( 'custom-background', 'default-attachment' ),
'sanitize_callback' => array( $this, '_sanitize_background_setting' ),
'theme_supports' => 'custom-background',
) );
$this->add_control( 'background_attachment', array(
'label' => __( 'Scroll with Page' ),
'section' => 'background_image',
'type' => 'checkbox',
) );
// If the theme is using the default background callback, we can update
// the background CSS using postMessage.
if ( get_theme_support( 'custom-background', 'wp-head-callback' ) === '_custom_background_cb' ) {
foreach ( array( 'color', 'image', 'preset', 'position_x', 'position_y', 'size', 'repeat', 'attachment' ) as $prop ) {
$this->get_setting( 'background_' . $prop )->transport = 'postMessage';
}
}
/*
* Static Front Page
* See also https://core.trac.wordpress.org/ticket/19627 which introduces the static-front-page theme_support.
* The following replicates behavior from options-reading.php.
*/
$this->add_section( 'static_front_page', array(
'title' => __( 'Homepage Settings' ),
'priority' => 120,
'description' => __( 'You can choose what’s displayed on the homepage of your site. It can be posts in reverse chronological order (classic blog), or a fixed/static page. To set a static homepage, you first need to create two Pages. One will become the homepage, and the other will be where your posts are displayed.' ),
'active_callback' => array( $this, 'has_published_pages' ),
) );
$this->add_setting( 'show_on_front', array(
'default' => get_option( 'show_on_front' ),
'capability' => 'manage_options',
'type' => 'option',
) );
$this->add_control( 'show_on_front', array(
'label' => __( 'Your homepage displays' ),
'section' => 'static_front_page',
'type' => 'radio',
'choices' => array(
'posts' => __( 'Your latest posts' ),
'page' => __( 'A static page' ),
),
) );
$this->add_setting( 'page_on_front', array(
'type' => 'option',
'capability' => 'manage_options',
) );
$this->add_control( 'page_on_front', array(
'label' => __( 'Homepage' ),
'section' => 'static_front_page',
'type' => 'dropdown-pages',
'allow_addition' => true,
) );
$this->add_setting( 'page_for_posts', array(
'type' => 'option',
'capability' => 'manage_options',
) );
$this->add_control( 'page_for_posts', array(
'label' => __( 'Posts page' ),
'section' => 'static_front_page',
'type' => 'dropdown-pages',
'allow_addition' => true,
) );
/* Custom CSS */
$section_description = ''; $section_description .= __( 'Add your own CSS code here to customize the appearance and layout of your site.' ); $section_description .= sprintf( ' %2$s%3$s', esc_url( __( 'https://codex.wordpress.org/CSS' ) ), __( 'Learn more about CSS' ), /* translators: accessibility text */ __( '(opens in a new window)' ) ); $section_description .= '
'; $section_description .= '' . __( 'When using a keyboard to navigate:' ) . '
'; $section_description .= ''; $section_description .= sprintf( /* translators: placeholder is link to user profile */ __( 'The edit field automatically highlights code syntax. You can disable this in your %s to work in plain text mode.' ), sprintf( ' %2$s%3$s', esc_url( get_edit_profile_url() ), __( 'user profile' ), /* translators: accessibility text */ __( '(opens in a new window)' ) ) ); $section_description .= '
'; } $section_description .= ' '; $this->add_section( 'custom_css', array( 'title' => __( 'Additional CSS' ), 'priority' => 200, 'description_hidden' => true, 'description' => $section_description, ) ); $custom_css_setting = new WP_Customize_Custom_CSS_Setting( $this, sprintf( 'custom_css[%s]', get_stylesheet() ), array( 'capability' => 'edit_css', 'default' => '', ) ); $this->add_setting( $custom_css_setting ); $this->add_control( new WP_Customize_Code_Editor_Control( $this, 'custom_css', array( 'label' => __( 'CSS code' ), 'section' => 'custom_css', 'settings' => array( 'default' => $custom_css_setting->id ), 'code_type' => 'text/css', 'input_attrs' => array( 'aria-describedby' => 'editor-keyboard-trap-help-1 editor-keyboard-trap-help-2 editor-keyboard-trap-help-3 editor-keyboard-trap-help-4', ), ) ) ); } /** * Return whether there are published pages. * * Used as active callback for static front page section and controls. * * @since 4.7.0 * * @returns bool Whether there are published (or to be published) pages. */ public function has_published_pages() { $setting = $this->get_setting( 'nav_menus_created_posts' ); if ( $setting ) { foreach ( $setting->value() as $post_id ) { if ( 'page' === get_post_type( $post_id ) ) { return true; } } } return 0 !== count( get_pages() ); } /** * Add settings from the POST data that were not added with code, e.g. dynamically-created settings for Widgets * * @since 4.2.0 * * @see add_dynamic_settings() */ public function register_dynamic_settings() { $setting_ids = array_keys( $this->unsanitized_post_values() ); $this->add_dynamic_settings( $setting_ids ); } /** * Callback for validating the header_textcolor value. * * Accepts 'blank', and otherwise uses sanitize_hex_color_no_hash(). * Returns default text color if hex color is empty. * * @since 3.4.0 * * @param string $color * @return mixed */ public function _sanitize_header_textcolor( $color ) { if ( 'blank' === $color ) return 'blank'; $color = sanitize_hex_color_no_hash( $color ); if ( empty( $color ) ) $color = get_theme_support( 'custom-header', 'default-text-color' ); return $color; } /** * Callback for validating a background setting value. * * @since 4.7.0 * * @param string $value Repeat value. * @param WP_Customize_Setting $setting Setting. * @return string|WP_Error Background value or validation error. */ public function _sanitize_background_setting( $value, $setting ) { if ( 'background_repeat' === $setting->id ) { if ( ! in_array( $value, array( 'repeat-x', 'repeat-y', 'repeat', 'no-repeat' ) ) ) { return new WP_Error( 'invalid_value', __( 'Invalid value for background repeat.' ) ); } } elseif ( 'background_attachment' === $setting->id ) { if ( ! in_array( $value, array( 'fixed', 'scroll' ) ) ) { return new WP_Error( 'invalid_value', __( 'Invalid value for background attachment.' ) ); } } elseif ( 'background_position_x' === $setting->id ) { if ( ! in_array( $value, array( 'left', 'center', 'right' ), true ) ) { return new WP_Error( 'invalid_value', __( 'Invalid value for background position X.' ) ); } } elseif ( 'background_position_y' === $setting->id ) { if ( ! in_array( $value, array( 'top', 'center', 'bottom' ), true ) ) { return new WP_Error( 'invalid_value', __( 'Invalid value for background position Y.' ) ); } } elseif ( 'background_size' === $setting->id ) { if ( ! in_array( $value, array( 'auto', 'contain', 'cover' ), true ) ) { return new WP_Error( 'invalid_value', __( 'Invalid value for background size.' ) ); } } elseif ( 'background_preset' === $setting->id ) { if ( ! in_array( $value, array( 'default', 'fill', 'fit', 'repeat', 'custom' ), true ) ) { return new WP_Error( 'invalid_value', __( 'Invalid value for background size.' ) ); } } elseif ( 'background_image' === $setting->id || 'background_image_thumb' === $setting->id ) { $value = empty( $value ) ? '' : esc_url_raw( $value ); } else { return new WP_Error( 'unrecognized_setting', __( 'Unrecognized background setting.' ) ); } return $value; } /** * Export header video settings to facilitate selective refresh. * * @since 4.7.0 * * @param array $response Response. * @param WP_Customize_Selective_Refresh $selective_refresh Selective refresh component. * @param array $partials Array of partials. * @return array */ public function export_header_video_settings( $response, $selective_refresh, $partials ) { if ( isset( $partials['custom_header'] ) ) { $response['custom_header_settings'] = get_header_video_settings(); } return $response; } /** * Callback for validating the header_video value. * * Ensures that the selected video is less than 8MB and provides an error message. * * @since 4.7.0 * * @param WP_Error $validity * @param mixed $value * @return mixed */ public function _validate_header_video( $validity, $value ) { $video = get_attached_file( absint( $value ) ); if ( $video ) { $size = filesize( $video ); if ( 8 < $size / pow( 1024, 2 ) ) { // Check whether the size is larger than 8MB. $validity->add( 'size_too_large', __( 'This video file is too large to use as a header video. Try a shorter video or optimize the compression settings and re-upload a file that is less than 8MB. Or, upload your video to YouTube and link it with the option below.' ) ); } if ( '.mp4' !== substr( $video, -4 ) && '.mov' !== substr( $video, -4 ) ) { // Check for .mp4 or .mov format, which (assuming h.264 encoding) are the only cross-browser-supported formats. $validity->add( 'invalid_file_type', sprintf( /* translators: 1: .mp4, 2: .mov */ __( 'Only %1$s or %2$s files may be used for header video. Please convert your video file and try again, or, upload your video to YouTube and link it with the option below.' ), '.mp4
',
'.mov
'
) );
}
}
return $validity;
}
/**
* Callback for validating the external_header_video value.
*
* Ensures that the provided URL is supported.
*
* @since 4.7.0
*
* @param WP_Error $validity
* @param mixed $value
* @return mixed
*/
public function _validate_external_header_video( $validity, $value ) {
$video = esc_url_raw( $value );
if ( $video ) {
if ( ! preg_match( '#^https?://(?:www\.)?(?:youtube\.com/watch|youtu\.be/)#', $video ) ) {
$validity->add( 'invalid_url', __( 'Please enter a valid YouTube URL.' ) );
}
}
return $validity;
}
/**
* Callback for sanitizing the external_header_video value.
*
* @since 4.7.1
*
* @param string $value URL.
* @return string Sanitized URL.
*/
public function _sanitize_external_header_video( $value ) {
return esc_url_raw( trim( $value ) );
}
/**
* Callback for rendering the custom logo, used in the custom_logo partial.
*
* This method exists because the partial object and context data are passed
* into a partial's render_callback so we cannot use get_custom_logo() as
* the render_callback directly since it expects a blog ID as the first
* argument. When WP no longer supports PHP 5.3, this method can be removed
* in favor of an anonymous function.
*
* @see WP_Customize_Manager::register_controls()
*
* @since 4.5.0
*
* @return string Custom logo.
*/
public function _render_custom_logo_partial() {
return get_custom_logo();
}
}