init(); } /** * Initialize this module. * * @since 2.2.0 */ public function init() { // Hook up. $this->add_hooks(); } /** * Initiate our hooks * * @since 2.2.0 */ public function add_hooks() { // Styles and scripts when editing. add_action( 'enqueue_block_assets', array( $this, 'dynamic_styles_scripts' ), 999 ); // Styles on the front end. add_action( 'enqueue_block_assets', array( $this, 'frontend_styles' ), 999 ); add_action( 'admin_init', array( $this, 'editor_color_palettes' ), 20 ); } /** * Determine if Gutenberg is supported. * * @return bool * @since 2.2.0 * */ public function is_supported() { $gutenberg = false; // Determine if the block editor is active for the frontend. if ( has_action( 'enqueue_block_assets' ) ) { // Gutenberg is installed and activated. $gutenberg = true; } // Determine if the block editor is being used in the WP admin. $current_screen = get_current_screen(); if ( is_admin() && method_exists( $current_screen, 'is_block_editor' ) && get_current_screen()->is_block_editor() ) { $gutenberg = true; } return apply_filters( 'customify_gutenberg_is_supported', $gutenberg ); } public function get_editor_style_handle() { global $wp_styles; if ( ! ( $wp_styles instanceof WP_Styles ) ) { return ''; } // We need to look into the registered theme stylesheets and get the one most likely to be used for Gutenberg. // Thus we can attach inline styles to it. $theme_dir_uri = get_template_directory_uri(); $theme_slug = get_template(); $handle = 'wp-edit-post'; // this is better than nothing as it is the main editor style. $reversed = array_reverse( $wp_styles->registered ); /** @var _WP_Dependency $style */ foreach ( $reversed as $style ) { // This is the most precise. if ( 0 === strpos( $style->src, $theme_dir_uri ) ) { $handle = $style->handle; break; } // If it is prefixed with the theme slug, it is good also. if ( 0 === strpos( $style->handle, $theme_slug . '-' ) || 0 === strpos( $style->handle, $theme_slug . '_' ) ) { $handle = $style->handle; break; } } return $handle; } public function get_frontend_style_handle() { global $wp_styles; if ( ! ( $wp_styles instanceof WP_Styles ) ) { return ''; } // We need to look into the registered theme stylesheets and get the one most likely to be used for Gutenberg. // Thus we can attach inline styles to it. $style_css_uri = get_template_directory_uri() . '/style.css'; $theme_slug = get_template(); $handle = 'wp-block-library'; // this is better than nothing as it is the main editor frontend style. $reversed = array_reverse( $wp_styles->registered ); /** @var _WP_Dependency $style */ foreach ( $reversed as $style ) { // This is the most precise. if ( 0 === strpos( $style->src, $style_css_uri ) ) { $handle = $style->handle; break; } // If it is prefixed with the theme slug, it is good also. if ( ( 0 === strpos( $style->handle, $theme_slug . '-' ) || 0 === strpos( $style->handle, $theme_slug . '_' ) ) && false !== strpos( $style->src, '.css' ) ) { $handle = $style->handle; break; } } return $handle; } /** * Output Customify's dynamic styles and scripts in the Gutenberg context. * * @since 2.2.0 */ public function dynamic_styles_scripts() { if ( ! $this->is_admin_block_editor_screen() ) { return; } if ( ! PixCustomifyPlugin()->settings->get_plugin_setting( 'enable_editor_style', true ) ) { return; } require_once( PixCustomifyPlugin()->get_base_path() . 'includes/class-customify-fonts-global.php' ); $enqueue_parent_handle = PixCustomifyPlugin()->get_slug() . '-editor-dynamic'; wp_register_style( $enqueue_parent_handle, false, array(), PixCustomifyPlugin()->get_version() ); wp_enqueue_style( $enqueue_parent_handle ); wp_register_script( PixCustomifyPlugin()->get_slug() . '-web-font-loader', plugins_url( 'js/vendor/webfontloader-1-6-28.min.js', PixCustomifyPlugin()->get_file() ), array( 'wp-block-editor' ), PixCustomifyPlugin()->get_version(), false ); add_filter( 'customify_font_css_selector', array( $this, 'gutenbergify_font_css_selectors' ), 10, 2 ); Customify_Fonts_Global::instance()->enqueue_frontend_scripts_styles(); wp_add_inline_style( $enqueue_parent_handle, Customify_Fonts_Global::instance()->getFontsDynamicStyle() ); remove_filter( 'customify_font_css_selector', array( $this, 'gutenbergify_font_css_selectors' ), 10 ); add_filter( 'customify_css_selector', array( $this, 'gutenbergify_css_selectors' ), 10, 2 ); wp_add_inline_style( $enqueue_parent_handle, PixCustomifyPlugin()->customizer->get_dynamic_style() ); remove_filter( 'customify_css_selector', array( $this, 'gutenbergify_css_selectors' ), 10 ); // Add color palettes classes. wp_add_inline_style( $enqueue_parent_handle, $this->editor_color_palettes_css_classes() ); } /** * Determine whether the current request is for an admin block editor canvas. * * @return bool */ protected function is_admin_block_editor_screen() { if ( ! is_admin() || ! function_exists( 'get_current_screen' ) ) { return false; } $current_screen = get_current_screen(); if ( ! $current_screen ) { return false; } if ( method_exists( $current_screen, 'is_block_editor' ) && $current_screen->is_block_editor() ) { return true; } return in_array( $current_screen->id, array( 'site-editor', 'site-editor-v2' ), true ); } public function frontend_styles() { $enqueue_parent_handle = $this->get_frontend_style_handle(); if ( empty( $enqueue_parent_handle ) ) { return; } // Add color palettes classes. wp_add_inline_style( $enqueue_parent_handle, $this->editor_color_palettes_css_classes() ); } /** * @param string $selectors * @param array $css_property * * @return string */ public function gutenbergify_css_selectors( $selectors, $css_property ) { // Treat the selector(s) as an array. $selectors = $this->maybeExplodeSelectors( $selectors ); $new_selectors = array(); foreach ( $selectors as $selector ) { // Clean up $selector = trim( $selector ); // If the selector matches the excluded, skip it. if ( $this->preg_match_any( self::$excluded_selectors_regex, $selector ) ) { continue; } // If the selector is already Gutenbergy, we will not do anything to it if ( preg_match( self::$gutenbergy_selector_regex, $selector ) ) { $new_selectors[] = $selector; continue; } // We will let :root selectors be if ( ':root' === $selector ) { $new_selectors[] = $selector; continue; } // For root html elements, we will not prefix them, but replace them with the block and title namespace. if ( preg_match( self::$root_regex, $selector ) ) { // We will ignore pseudo-selectors if ( preg_match( '/^(body|html)[\:\+]+.*$/', $selector ) ) { continue; } // When it comes to background properties applied at the body level, we need to scope to the editor namespace if ( isset( $css_property['property'] ) && 0 === strpos( $css_property['property'], 'background' ) ) { $new_selectors[] = preg_replace( '/^(html body|body|html)/', self::$editor_namespace_selector, $selector ); } else { $new_selectors[] = preg_replace( '/^(html body|body|html)/', self::get_block_namespace_selector(), $selector ); $new_selectors[] = preg_replace( '/^(html body|body|html)/', self::$title_namespace_selector, $selector ); } continue; } // If we encounter selectors that seem that they could target the post title, // we will add selectors for the Gutenberg title also. if ( preg_match( self::$title_regex, $selector ) ) { $new_selectors[] = preg_replace( self::$title_regex, self::$title_input_namespace_selector, $selector ); } $new_selectors[] = self::get_block_namespace_selector() . ' ' . $selector; } return implode( ', ', $new_selectors ); } /** * @param array $selectors An array of standardized, cleaned selectors where the key is the selector and the value is possible details array. * * @return array */ public function gutenbergify_font_css_selectors( $selectors ) { $new_selectors = array(); foreach ( $selectors as $selector => $selector_details ) { // If the selector matches the excluded, skip it. if ( $this->preg_match_any( self::$excluded_selectors_regex, $selector ) ) { continue; } // If the selector is already Gutenbergy, we will not do anything to it if ( preg_match( self::$gutenbergy_selector_regex, $selector ) ) { $new_selectors[ $selector ] = $selector_details; continue; } // We will let :root selectors be if ( ':root' === $selector ) { $new_selectors[ $selector ] = $selector_details; continue; } // For root html elements, we will not prefix them, but replace them with the block and title namespace. if ( preg_match( self::$root_regex, $selector ) ) { $new_selector = preg_replace( '/^(html body|body|html|)/', self::get_block_namespace_selector(), $selector ); $new_selectors[ $new_selector ] = $selector_details; $new_selector = preg_replace( '/^(html body|body|html)/', self::$title_namespace_selector, $selector ); $new_selectors[ $new_selector ] = $selector_details; continue; } // If we encounter selectors that seem that they could target the post title, // we will add selectors for the Gutenberg title also. if ( preg_match( self::$title_regex, $selector ) ) { $new_selector = preg_replace( self::$title_regex, self::$title_input_namespace_selector, $selector ); $new_selectors[ $new_selector ] = $selector_details; } $selector = self::get_block_namespace_selector() . ' ' . $selector; $new_selectors[ $selector ] = $selector_details; } return $new_selectors; } /** * Preg_match a series of regex against a subject. * * @param string|array $regexes * @param string $subject * * @return bool Returns true if at least one of the regex matches, false otherwise. */ public function preg_match_any( $regexes, $subject ) { if ( is_string( $regexes ) ) { $regexes = array( $regexes ); } if ( ! is_array( $regexes ) ) { return false; } foreach ( $regexes as $regex ) { if ( preg_match( $regex, $subject ) ) { return true; } } return false; } /** * Attempt to split a string with selectors and return the parts as an array. * If not a string or no comma present, just returns the value. * * @param mixed $value * * @return array|false|string[] */ public function maybeExplodeSelectors( $value ) { if ( ! is_string( $value ) ) { return $value; } return preg_split( '#[\s]*,[\s]*#', $value, - 1, PREG_SPLIT_NO_EMPTY | PREG_SPLIT_DELIM_CAPTURE ); } /** * Add the SM Color Palettes to the editor sidebar. */ public function editor_color_palettes() { // Bail if Color Palettes are not supported if ( ! Customify_Color_Palettes::instance()->is_supported() ) { return; } $options_details = PixCustomifyPlugin()->get_options_configs(); $master_color_control_ids = Customify_Color_Palettes::instance()->get_all_master_color_controls_ids(); if ( empty( $master_color_control_ids ) ) { return; } $editor_color_palettes = array(); foreach ( $master_color_control_ids as $control_id ) { if ( empty( $options_details[ $control_id ] ) ) { continue; } $value = get_option( $control_id . '_final' ); if ( empty( $value ) ) { $value = $options_details[ $control_id ][ 'default' ]; } if ( empty( $value ) ) { continue; } $editor_color_palettes[] = array( 'name' => $options_details[ $control_id ]['label'], 'slug' => $control_id, 'color' => esc_html( $value ), ); } if ( ! empty( $editor_color_palettes ) ) { /** * Custom colors for use in the editor. * * @link https://wordpress.org/gutenberg/handbook/reference/theme-support/ */ add_theme_support( 'editor-color-palette', $editor_color_palettes ); } } /** * Generate the special classes for our colors. */ public function editor_color_palettes_css_classes() { // Bail if Color Palettes are not supported if ( ! Customify_Color_Palettes::instance()->is_supported() ) { return ''; } $options_details = PixCustomifyPlugin()->get_options_configs(); $master_color_control_ids = Customify_Color_Palettes::instance()->get_all_master_color_controls_ids(); if ( empty( $master_color_control_ids ) ) { return ''; } // Build styles. $css = ''; foreach ( $master_color_control_ids as $control_id ) { if ( empty( $options_details[ $control_id ] ) ) { continue; } $value = get_option( $control_id . '_final' ); if ( empty( $value ) ) { continue; } $editor_color_palettes[] = array( 'name' => $options_details[ $control_id ]['label'], 'slug' => $control_id, 'color' => esc_html( $value ), ); $color_in_kebab_case = self::to_kebab_case( $control_id ); $css .= '.has-' . $color_in_kebab_case . '-color { color: ' . esc_attr( $value ) . ' !important; }'; $css .= '.has-' . $color_in_kebab_case . '-background-color { background-color: ' . esc_attr( $value ) . '; }'; } return wp_strip_all_tags( $css ); } public static function to_kebab_case( $string ) { $parts = preg_split( "/[\n\r\t -_]+/", preg_replace( "/['\x{2019}]/u", '', $string ), - 1, PREG_SPLIT_NO_EMPTY ); if ( ! is_array( $parts ) ) { return ''; } return implode( '-', array_map( '\strtolower', $parts ) ); } /** * Main Customify_Block_Editor Instance * * Ensures only one instance of Customify_Block_Editor is loaded or can be loaded. * * @return Customify_Block_Editor Main Customify_Block_Editor instance * @since 2.2.0 * @static * */ public static function instance() { if ( is_null( self::$_instance ) ) { self::$_instance = new self(); } return self::$_instance; } /** * Cloning is forbidden. * * @since 2.2.0 */ public function __clone() { _doing_it_wrong( __FUNCTION__, esc_html__( 'You should not do that!', 'customify' ), null ); } /** * Unserializing instances of this class is forbidden. * * @since 2.2.0 */ public function __wakeup() { _doing_it_wrong( __FUNCTION__, esc_html__( 'You should not do that!', 'customify' ), null ); } } }