PluginProbe
Customify / 2.5.1
Customify v2.5.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-gutenberg.php

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

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