PluginProbe
Customify / 2.10.1
Customify v2.10.1
2.10.9 1.2.3 1.2.4 1.2.5 1.2.6 1.2.7 1.2.7.1 1.3.0 1.3.1 1.4.0 1.4.1 1.4.2 1.5.0 1.5.1 1.5.2 1.5.3 1.5.4 1.5.5 1.5.6 1.5.7 1.6.0 1.6.0.1 1.6.5 1.7.0 1.7.1 All 77 releases
customify / includes / class-customify-block-editor.php

class-customify-block-editor.php in Customify 2.10.1, at includes/class-customify-block-editor.php

563 lines 16.5 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2 /**
3 * This is the class that handles the overall logic for integration with the new Gutenberg Editor (WordPress 5.0+).
4 *
5 * @see https://pixelgrade.com
6 * @author Pixelgrade
7 * @since 2.2.0
8 */
9
10 if ( ! defined( 'ABSPATH' ) ) {
11 exit; // Exit if accessed directly
12 }
13
14 if ( ! class_exists( 'Customify_Block_Editor' ) ) {
15
16 class Customify_Block_Editor {
17
18 /**
19 * Holds the only instance of this class.
20 * @var null|Customify_Block_Editor
21 * @access protected
22 * @since 2.2.0
23 */
24 protected static $_instance = null;
25
26 /*
27 * Selectors that we will use to constrain CSS rules to certain scopes.
28 */
29 public static $editor_namespace_selector = '.edit-post-visual-editor.editor-styles-wrapper';
30 public static $title_namespace_selector = '.editor-styles-wrapper .editor-post-title__block';
31 public static $title_input_namespace_selector = '.editor-styles-wrapper .editor-post-title__block .editor-post-title__input';
32 public static function get_block_namespace_selector() {
33 global $wp_version;
34
35 $is_old_wp_version = version_compare($wp_version, '5.4', '<');
36
37 if( $is_old_wp_version ) {
38 return '.edit-post-visual-editor.editor-styles-wrapper .editor-block-list__block';
39 }
40
41 return '.edit-post-visual-editor.editor-styles-wrapper .block-editor-block-list__block';
42 }
43
44 /**
45 * Regexes
46 */
47 public static $gutenbergy_selector_regex = '/^(\.edit-post-visual-editor|\.editor-block-list__block).*$/';
48 public static $root_regex = '/^(body|html).*$/';
49 public static $title_regex = '/^(h1|h1\s+.*|\.single\s*\.entry-title.*|\.entry-title.*|\.page-title.*|\.article__?title.*)$/';
50 /* Regexes based on which we will ignore selectors = do not include them in the selector list for a certain rule. */
51 public static $excluded_selectors_regex = array(
52 // We don't want to mess with buttons as we have a high likelihood of messing with the Gutenberg toolbar.
53 '/^\s*button/',
54 '/^\s*\.button/',
55 '/^\s*input/',
56 '/^\s*select/',
57 '/^\s*#/', // ignore all ids
58 '/^\s*div#/', // ignore all ids
59
60 '/\.u-/',
61 '/\.c-/',
62 '/\.o-/',
63 '/\.site-/',
64 '/\.card/',
65
66 '/^\s*\.archive/',
67 '/^\s*\.search/',
68 '/^\s*\.no-results/',
69 '/^\s*\.home/',
70 '/^\s*\.blog/',
71 '/^\s*\.site-/',
72 '/\.search/',
73 '/\.page/',
74 '/\.mce-content-body/',
75 '/\.attachment/',
76 '/\.mobile/',
77
78 '/\.sticky/',
79 '/\.custom-logo-link/',
80
81 '/\.entry-meta/',
82 '/\.entry-footer/',
83 '/\.header-meta/',
84 '/\.nav/',
85 '/\.main-navigation/',
86 '/navbar/',
87 '/comment/',
88 '/\.dummy/',
89 '/\.back-to-top/',
90 '/\.page-numbers/',
91 '/\.featured/',
92 '/\.widget/',
93 '/\.edit-link/',
94 '/\.posted-on/',
95 '/\.cat-links/',
96 '/\.posted-by/',
97 '/\.more-link/',
98
99 '/jetpack/',
100 '/wpforms/',
101 '/contact-form/',
102 '/sharedaddy/',
103 );
104
105 /**
106 * Constructor.
107 *
108 * @since 2.2.0
109 */
110 protected function __construct() {
111 $this->init();
112 }
113
114 /**
115 * Initialize this module.
116 *
117 * @since 2.2.0
118 */
119 public function init() {
120
121 // Hook up.
122 $this->add_hooks();
123 }
124
125 /**
126 * Initiate our hooks
127 *
128 * @since 2.2.0
129 */
130 public function add_hooks() {
131
132 // Styles and scripts when editing.
133 add_action( 'enqueue_block_editor_assets', array( $this, 'dynamic_styles_scripts' ), 999 );
134
135 // Styles on the front end.
136 add_action( 'enqueue_block_assets', array( $this, 'frontend_styles' ), 999 );
137
138 add_action( 'admin_init', array( $this, 'editor_color_palettes' ), 20 );
139 }
140
141 /**
142 * Determine if Gutenberg is supported.
143 *
144 * @return bool
145 * @since 2.2.0
146 *
147 */
148 public function is_supported() {
149 $gutenberg = false;
150
151 // Determine if the block editor is active for the frontend.
152 if ( has_action( 'enqueue_block_assets' ) ) {
153 // Gutenberg is installed and activated.
154 $gutenberg = true;
155 }
156
157 // Determine if the block editor is being used in the WP admin.
158 $current_screen = get_current_screen();
159 if ( is_admin() && method_exists( $current_screen, 'is_block_editor' ) && get_current_screen()->is_block_editor() ) {
160 $gutenberg = true;
161 }
162
163 return apply_filters( 'customify_gutenberg_is_supported', $gutenberg );
164 }
165
166 public function get_editor_style_handle() {
167 global $wp_styles;
168 if ( ! ( $wp_styles instanceof WP_Styles ) ) {
169 return '';
170 }
171
172 // We need to look into the registered theme stylesheets and get the one most likely to be used for Gutenberg.
173 // Thus we can attach inline styles to it.
174 $theme_dir_uri = get_template_directory_uri();
175 $theme_slug = get_template();
176
177 $handle = 'wp-edit-post'; // this is better than nothing as it is the main editor style.
178 $reversed = array_reverse( $wp_styles->registered );
179 /** @var _WP_Dependency $style */
180 foreach ( $reversed as $style ) {
181 // This is the most precise.
182 if ( 0 === strpos( $style->src, $theme_dir_uri ) ) {
183 $handle = $style->handle;
184 break;
185 }
186
187 // If it is prefixed with the theme slug, it is good also.
188 if ( 0 === strpos( $style->handle, $theme_slug . '-' ) || 0 === strpos( $style->handle, $theme_slug . '_' ) ) {
189 $handle = $style->handle;
190 break;
191 }
192 }
193
194 return $handle;
195 }
196
197 public function get_frontend_style_handle() {
198 global $wp_styles;
199 if ( ! ( $wp_styles instanceof WP_Styles ) ) {
200 return '';
201 }
202
203 // We need to look into the registered theme stylesheets and get the one most likely to be used for Gutenberg.
204 // Thus we can attach inline styles to it.
205 $style_css_uri = get_template_directory_uri() . '/style.css';
206 $theme_slug = get_template();
207
208 $handle = 'wp-block-library'; // this is better than nothing as it is the main editor frontend style.
209 $reversed = array_reverse( $wp_styles->registered );
210 /** @var _WP_Dependency $style */
211 foreach ( $reversed as $style ) {
212 // This is the most precise.
213 if ( 0 === strpos( $style->src, $style_css_uri ) ) {
214 $handle = $style->handle;
215 break;
216 }
217
218 // If it is prefixed with the theme slug, it is good also.
219 if ( ( 0 === strpos( $style->handle, $theme_slug . '-' ) || 0 === strpos( $style->handle, $theme_slug . '_' ) )
220 && false !== strpos( $style->src, '.css' ) ) {
221 $handle = $style->handle;
222 break;
223 }
224 }
225
226 return $handle;
227 }
228
229 /**
230 * Output Customify's dynamic styles and scripts in the Gutenberg context.
231 *
232 * @since 2.2.0
233 */
234 public function dynamic_styles_scripts() {
235 if ( ! PixCustomifyPlugin()->settings->get_plugin_setting( 'enable_editor_style', true ) ) {
236 return;
237 }
238
239 require_once( PixCustomifyPlugin()->get_base_path() . 'includes/class-customify-fonts-global.php' );
240
241 $enqueue_parent_handle = $this->get_editor_style_handle();
242 if ( empty( $enqueue_parent_handle ) ) {
243 return;
244 }
245
246 wp_register_script( PixCustomifyPlugin()->get_slug() . '-web-font-loader',
247 plugins_url( 'js/vendor/webfontloader-1-6-28.min.js', PixCustomifyPlugin()->get_file() ), array('wp-editor'), null );
248
249 add_filter( 'customify_font_css_selector', array( $this, 'gutenbergify_font_css_selectors' ), 10, 2 );
250 Customify_Fonts_Global::instance()->enqueue_frontend_scripts_styles();
251 wp_add_inline_style( $enqueue_parent_handle, Customify_Fonts_Global::instance()->getFontsDynamicStyle() );
252 remove_filter( 'customify_font_css_selector', array( $this, 'gutenbergify_font_css_selectors' ), 10 );
253
254 add_filter( 'customify_css_selector', array( $this, 'gutenbergify_css_selectors' ), 10, 2 );
255 wp_add_inline_style( $enqueue_parent_handle, PixCustomifyPlugin()->customizer->get_dynamic_style() );
256 remove_filter( 'customify_css_selector', array( $this, 'gutenbergify_css_selectors' ), 10 );
257
258 // Add color palettes classes.
259 wp_add_inline_style( $enqueue_parent_handle, $this->editor_color_palettes_css_classes() );
260 }
261
262 public function frontend_styles() {
263 $enqueue_parent_handle = $this->get_frontend_style_handle();
264 if ( empty( $enqueue_parent_handle ) ) {
265 return;
266 }
267
268 // Add color palettes classes.
269 wp_add_inline_style( $enqueue_parent_handle, $this->editor_color_palettes_css_classes() );
270 }
271
272 /**
273 * @param string $selectors
274 * @param array $css_property
275 *
276 * @return string
277 */
278 public function gutenbergify_css_selectors( $selectors, $css_property ) {
279
280 // Treat the selector(s) as an array.
281 $selectors = $this->maybeExplodeSelectors( $selectors );
282
283 $new_selectors = array();
284 foreach ( $selectors as $selector ) {
285 // Clean up
286 $selector = trim( $selector );
287
288 // If the selector matches the excluded, skip it.
289 if ( $this->preg_match_any( self::$excluded_selectors_regex, $selector ) ) {
290 continue;
291 }
292
293 // If the selector is already Gutenbergy, we will not do anything to it
294 if ( preg_match( self::$gutenbergy_selector_regex, $selector ) ) {
295 $new_selectors[] = $selector;
296 continue;
297 }
298
299 // We will let :root selectors be
300 if ( ':root' === $selector ) {
301 $new_selectors[] = $selector;
302 continue;
303 }
304
305 // For root html elements, we will not prefix them, but replace them with the block and title namespace.
306 if ( preg_match( self::$root_regex, $selector ) ) {
307 // We will ignore pseudo-selectors
308 if ( preg_match( '/^(body|html)[\:\+]+.*$/', $selector ) ) {
309 continue;
310 }
311
312 // When it comes to background properties applied at the body level, we need to scope to the editor namespace
313 if ( isset( $css_property['property'] ) && 0 === strpos( $css_property['property'], 'background' ) ) {
314 $new_selectors[] = preg_replace( '/^(html body|body|html)/', self::$editor_namespace_selector, $selector );
315 } else {
316 $new_selectors[] = preg_replace( '/^(html body|body|html)/', self::get_block_namespace_selector(), $selector );
317 $new_selectors[] = preg_replace( '/^(html body|body|html)/', self::$title_namespace_selector, $selector );
318 }
319 continue;
320 }
321
322 // If we encounter selectors that seem that they could target the post title,
323 // we will add selectors for the Gutenberg title also.
324 if ( preg_match( self::$title_regex, $selector ) ) {
325 $new_selectors[] = preg_replace( self::$title_regex, self::$title_input_namespace_selector, $selector );
326 }
327
328 $new_selectors[] = self::get_block_namespace_selector() . ' ' . $selector;
329 }
330
331 return implode( ', ', $new_selectors );
332 }
333
334 /**
335 * @param array $selectors An array of standardized, cleaned selectors where the key is the selector and the value is possible details array.
336 *
337 * @return array
338 */
339 public function gutenbergify_font_css_selectors( $selectors ) {
340
341 $new_selectors = array();
342 foreach ( $selectors as $selector => $selector_details ) {
343 // If the selector matches the excluded, skip it.
344 if ( $this->preg_match_any( self::$excluded_selectors_regex, $selector ) ) {
345 continue;
346 }
347
348 // If the selector is already Gutenbergy, we will not do anything to it
349 if ( preg_match( self::$gutenbergy_selector_regex, $selector ) ) {
350 $new_selectors[ $selector ] = $selector_details;
351 continue;
352 }
353
354 // We will let :root selectors be
355 if ( ':root' === $selector ) {
356 $new_selectors[ $selector ] = $selector_details;
357 continue;
358 }
359
360 // For root html elements, we will not prefix them, but replace them with the block and title namespace.
361 if ( preg_match( self::$root_regex, $selector ) ) {
362 $new_selector = preg_replace( '/^(html body|body|html|)/', self::get_block_namespace_selector(), $selector );
363 $new_selectors[ $new_selector ] = $selector_details;
364 $new_selector = preg_replace( '/^(html body|body|html)/', self::$title_namespace_selector, $selector );
365 $new_selectors[ $new_selector ] = $selector_details;
366 continue;
367 }
368
369 // If we encounter selectors that seem that they could target the post title,
370 // we will add selectors for the Gutenberg title also.
371 if ( preg_match( self::$title_regex, $selector ) ) {
372 $new_selector = preg_replace( self::$title_regex, self::$title_input_namespace_selector, $selector );
373 $new_selectors[ $new_selector ] = $selector_details;
374 }
375
376 $selector = self::get_block_namespace_selector() . ' ' . $selector;
377 $new_selectors[ $selector ] = $selector_details;
378 }
379
380 return $new_selectors;
381 }
382
383 /**
384 * Preg_match a series of regex against a subject.
385 *
386 * @param string|array $regexes
387 * @param string $subject
388 *
389 * @return bool Returns true if at least one of the regex matches, false otherwise.
390 */
391 public function preg_match_any( $regexes, $subject ) {
392 if ( is_string( $regexes ) ) {
393 $regexes = array( $regexes );
394 }
395
396 if ( ! is_array( $regexes ) ) {
397 return false;
398 }
399
400 foreach ( $regexes as $regex ) {
401 if ( preg_match( $regex, $subject ) ) {
402 return true;
403 }
404 }
405
406 return false;
407 }
408
409 /**
410 * Attempt to split a string with selectors and return the parts as an array.
411 * If not a string or no comma present, just returns the value.
412 *
413 * @param mixed $value
414 *
415 * @return array|false|string[]
416 */
417 public function maybeExplodeSelectors( $value ) {
418 if ( ! is_string( $value ) ) {
419 return $value;
420 }
421
422 return preg_split( '#[\s]*,[\s]*#', $value, - 1, PREG_SPLIT_NO_EMPTY | PREG_SPLIT_DELIM_CAPTURE );
423 }
424
425 /**
426 * Add the SM Color Palettes to the editor sidebar.
427 */
428 public function editor_color_palettes() {
429 // Bail if Color Palettes are not supported
430 if ( ! Customify_Color_Palettes::instance()->is_supported() ) {
431 return;
432 }
433
434 $options_details = PixCustomifyPlugin()->get_options_configs();
435
436 $master_color_control_ids = Customify_Color_Palettes::instance()->get_all_master_color_controls_ids();
437 if ( empty( $master_color_control_ids ) ) {
438 return;
439 }
440
441 $editor_color_palettes = array();
442 foreach ( $master_color_control_ids as $control_id ) {
443 if ( empty( $options_details[ $control_id ] ) ) {
444 continue;
445 }
446
447 $value = get_option( $control_id . '_final' );
448 if ( empty( $value ) ) {
449 $value = $options_details[ $control_id ][ 'default' ];
450 }
451
452 if ( empty( $value ) ) {
453 continue;
454 }
455
456 $editor_color_palettes[] = array(
457 'name' => $options_details[ $control_id ]['label'],
458 'slug' => $control_id,
459 'color' => esc_html( $value ),
460 );
461 }
462
463 if ( ! empty( $editor_color_palettes ) ) {
464 /**
465 * Custom colors for use in the editor.
466 *
467 * @link https://wordpress.org/gutenberg/handbook/reference/theme-support/
468 */
469 add_theme_support(
470 'editor-color-palette',
471 $editor_color_palettes
472 );
473 }
474 }
475
476 /**
477 * Generate the special classes for our colors.
478 */
479 public function editor_color_palettes_css_classes() {
480 // Bail if Color Palettes are not supported
481 if ( ! Customify_Color_Palettes::instance()->is_supported() ) {
482 return '';
483 }
484
485 $options_details = PixCustomifyPlugin()->get_options_configs();
486
487 $master_color_control_ids = Customify_Color_Palettes::instance()->get_all_master_color_controls_ids();
488 if ( empty( $master_color_control_ids ) ) {
489 return '';
490 }
491
492 // Build styles.
493 $css = '';
494 foreach ( $master_color_control_ids as $control_id ) {
495 if ( empty( $options_details[ $control_id ] ) ) {
496 continue;
497 }
498
499 $value = get_option( $control_id . '_final' );
500 if ( empty( $value ) ) {
501 continue;
502 }
503
504 $editor_color_palettes[] = array(
505 'name' => $options_details[ $control_id ]['label'],
506 'slug' => $control_id,
507 'color' => esc_html( $value ),
508 );
509
510 $color_in_kebab_case = self::to_kebab_case( $control_id );
511
512 $css .= '.has-' . $color_in_kebab_case . '-color { color: ' . esc_attr( $value ) . ' !important; }';
513 $css .= '.has-' . $color_in_kebab_case . '-background-color { background-color: ' . esc_attr( $value ) . '; }';
514 }
515
516 return wp_strip_all_tags( $css );
517 }
518
519 public static function to_kebab_case( $string ) {
520 return implode( '-', array_map( '\strtolower', preg_split( "/[\n\r\t -_]+/", preg_replace( "/['\x{2019}]/u", '', $string ), - 1, PREG_SPLIT_NO_EMPTY ) ) );
521 }
522
523 /**
524 * Main Customify_Block_Editor Instance
525 *
526 * Ensures only one instance of Customify_Block_Editor is loaded or can be loaded.
527 *
528 * @return Customify_Block_Editor Main Customify_Block_Editor instance
529 * @since 2.2.0
530 * @static
531 *
532 */
533 public static function instance() {
534
535 if ( is_null( self::$_instance ) ) {
536 self::$_instance = new self();
537 }
538
539 return self::$_instance;
540 }
541
542 /**
543 * Cloning is forbidden.
544 *
545 * @since 2.2.0
546 */
547 public function __clone() {
548
549 _doing_it_wrong( __FUNCTION__, esc_html__( 'You should not do that!', 'customify' ), null );
550 }
551
552 /**
553 * Unserializing instances of this class is forbidden.
554 *
555 * @since 2.2.0
556 */
557 public function __wakeup() {
558
559 _doing_it_wrong( __FUNCTION__, esc_html__( 'You should not do that!', 'customify' ), null );
560 }
561 }
562 }
563