PluginProbe
Customify / 2.2.0
Customify v2.2.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-gutenberg.php

class-customify-gutenberg.php in Customify 2.2.0, at includes/class-customify-gutenberg.php

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