PluginProbe
Customify / 2.7.0
Customify v2.7.0
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.7.0, at includes/class-customify-block-editor.php

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