PluginProbe
Elementor Website Builder – more than just a page builder / 4.2.0
Elementor Website Builder – more than just a page builder v4.2.0
4.3.0-beta2 4.3.0-beta1 4.2.4 4.2.3 4.2.2 4.2.1 4.2.0 4.1.5 4.2.0-beta2 4.2.0-dev2 4.2.0-beta1 4.1.4 4.1.3 4.1.2 4.1.1 4.1.0 4.1.0-beta3 4.1.0-dev3 4.0.9 4.1.0-beta2 4.1.0-dev2 4.0.8 4.1.0-beta1 4.1.0-dev1 4.0.7 All 451 releases
elementor / core / files / css / base.php

base.php in Elementor Website Builder – more than just a page builder 4.2.0, at core/files/css/base.php

1,036 lines 27.7 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2 namespace Elementor\Core\Files\CSS;
3
4 use Elementor\Base_Data_Control;
5 use Elementor\Control_Repeater;
6 use Elementor\Controls_Manager;
7 use Elementor\Controls_Stack;
8 use Elementor\Core\Breakpoints\Manager as Breakpoints_Manager;
9 use Elementor\Core\Files\Base as Base_File;
10 use Elementor\Core\DynamicTags\Manager;
11 use Elementor\Core\DynamicTags\Tag;
12 use Elementor\Core\Frontend\Performance;
13 use Elementor\Core\Kits\Documents\Tabs\Global_Typography;
14 use Elementor\Plugin;
15 use Elementor\Stylesheet;
16 use Elementor\Icons_Manager;
17
18 if ( ! defined( 'ABSPATH' ) ) {
19 exit; // Exit if accessed directly.
20 }
21
22 /**
23 * Elementor CSS file.
24 *
25 * Elementor CSS file handler class is responsible for generating CSS files.
26 *
27 * @since 1.2.0
28 * @abstract
29 */
30 abstract class Base extends Base_File {
31
32 /**
33 * Elementor CSS file generated status.
34 *
35 * The parsing result after generating CSS file.
36 */
37 const CSS_STATUS_FILE = 'file';
38
39 /**
40 * Elementor inline CSS status.
41 *
42 * The parsing result after generating inline CSS.
43 */
44 const CSS_STATUS_INLINE = 'inline';
45
46 /**
47 * Elementor CSS empty status.
48 *
49 * The parsing result when an empty CSS returned.
50 */
51 const CSS_STATUS_EMPTY = 'empty';
52
53 /**
54 * Fonts.
55 *
56 * Holds the list of fonts.
57 *
58 * @access private
59 *
60 * @var array
61 */
62 private $fonts = [];
63
64 private $icons_fonts = [];
65
66 private $dynamic_elements_ids = [];
67
68 private $preserved_dynamic_style_values = [];
69
70 /**
71 * Stylesheet object.
72 *
73 * Holds the CSS file stylesheet instance.
74 *
75 * @access protected
76 *
77 * @var Stylesheet
78 */
79 protected $stylesheet_obj;
80
81 /**
82 * Printed.
83 *
84 * Holds the list of printed files.
85 *
86 * @access protected
87 *
88 * @var array
89 */
90 private static $printed = [];
91
92 /**
93 * Get CSS file name.
94 *
95 * Retrieve the CSS file name.
96 *
97 * @since 1.6.0
98 * @access public
99 * @abstract
100 */
101 abstract public function get_name();
102
103 protected function is_global_parsing_supported() {
104 return false;
105 }
106
107 /**
108 * Use external file.
109 *
110 * Whether to use external CSS file of not. When there are new schemes or settings
111 * updates.
112 *
113 * @since 1.9.0
114 * @access protected
115 *
116 * @return bool True if the CSS requires an update, False otherwise.
117 */
118 protected function use_external_file() {
119 return 'internal' !== get_option( 'elementor_css_print_method' );
120 }
121
122 /**
123 * Update the CSS file.
124 *
125 * Delete old CSS, parse the CSS, save the new file and update the database.
126 *
127 * This method also sets the CSS status to be used later on in the render posses.
128 *
129 * @since 1.2.0
130 * @access public
131 */
132 public function update() {
133 $this->update_file();
134
135 $meta = $this->get_meta();
136
137 $meta['time'] = time();
138
139 $content = $this->get_content();
140
141 if ( empty( $content ) ) {
142 $meta['status'] = self::CSS_STATUS_EMPTY;
143 $meta['css'] = '';
144 } else {
145 $use_external_file = $this->use_external_file();
146
147 if ( $use_external_file ) {
148 $meta['status'] = self::CSS_STATUS_FILE;
149 } else {
150 $meta['status'] = self::CSS_STATUS_INLINE;
151 $meta['css'] = $content;
152 }
153 }
154
155 $meta['dynamic_elements_ids'] = $this->dynamic_elements_ids;
156
157 $this->update_meta( $meta );
158 }
159
160 /**
161 * @since 2.1.0
162 * @access public
163 */
164 public function write() {
165 if ( $this->use_external_file() ) {
166 parent::write();
167 }
168 }
169
170 /**
171 * @since 3.0.0
172 * @access public
173 */
174 public function delete() {
175 if ( $this->use_external_file() ) {
176 parent::delete();
177 } else {
178 $this->delete_meta();
179 }
180 }
181
182 /**
183 * Get Responsive Control Duplication Mode
184 *
185 * @since 3.4.0
186 *
187 * @return string
188 */
189 protected function get_responsive_control_duplication_mode() {
190 return 'on';
191 }
192
193 /**
194 * Enqueue CSS.
195 *
196 * Either enqueue the CSS file in Elementor or add inline style.
197 *
198 * This method is also responsible for loading the fonts.
199 *
200 * @since 1.2.0
201 * @access public
202 */
203 public function enqueue() {
204 $handle_id = $this->get_file_handle_id();
205
206 if ( isset( self::$printed[ $handle_id ] ) ) {
207 return;
208 }
209
210 self::$printed[ $handle_id ] = true;
211
212 $meta = $this->get_meta();
213
214 if ( self::CSS_STATUS_EMPTY === $meta['status'] ) {
215 return;
216 }
217
218 /**
219 * Enqueue CSS file.
220 *
221 * Fires before enqueuing a CSS file.
222 *
223 * @param Base $this The current CSS file.
224 */
225 do_action( 'elementor/css-file/before_enqueue', $this );
226
227 // First time after clear cache and etc.
228 if ( '' === $meta['status'] || $this->is_update_required() ) {
229 $this->update();
230
231 $meta = $this->get_meta();
232 }
233
234 if ( self::CSS_STATUS_INLINE === $meta['status'] ) {
235 $dep = $this->get_inline_dependency();
236 // If the dependency has already been printed ( like a template in footer )
237 if ( wp_styles()->query( $dep, 'done' ) ) {
238 printf( '<style id="%1$s">%2$s</style>', $this->get_file_handle_id(), $meta['css'] ); // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
239 } else {
240 wp_add_inline_style( $dep, $meta['css'] );
241 }
242 } elseif ( self::CSS_STATUS_FILE === $meta['status'] ) { // Re-check if it's not empty after CSS update.
243 wp_enqueue_style( $this->get_file_handle_id(), $this->get_url(), $this->get_enqueue_dependencies(), null );
244 }
245
246 // Handle fonts.
247 if ( ! empty( $meta['fonts'] ) ) {
248 foreach ( $meta['fonts'] as $font ) {
249 Plugin::$instance->frontend->enqueue_font( $font );
250 }
251 }
252
253 if ( ! empty( $meta['icons'] ) ) {
254 $icons_types = Icons_Manager::get_icon_manager_tabs();
255 foreach ( $meta['icons'] as $icon_font ) {
256 if ( ! isset( $icons_types[ $icon_font ] ) ) {
257 continue;
258 }
259 Plugin::$instance->frontend->enqueue_font( $icon_font );
260 }
261 }
262
263 $name = $this->get_name();
264
265 /**
266 * Enqueue CSS file.
267 *
268 * Fires when CSS file is enqueued on Elementor.
269 *
270 * The dynamic portion of the hook name, `$name`, refers to the CSS file name.
271 *
272 * @since 2.0.0
273 *
274 * @param Base $this The current CSS file.
275 */
276 do_action( "elementor/css-file/{$name}/enqueue", $this );
277
278 /**
279 * Enqueue CSS file.
280 *
281 * Fires after enqueuing a CSS file.
282 *
283 * @param Base $this The current CSS file.
284 */
285 do_action( 'elementor/css-file/after_enqueue', $this );
286 }
287
288 /**
289 * Print CSS.
290 *
291 * Output the final CSS inside the `<style>` tags and all the frontend fonts in
292 * use.
293 *
294 * @since 1.9.4
295 * @access public
296 */
297 public function print_css() {
298 echo '<style>' . $this->get_content() . '</style>'; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
299 Plugin::$instance->frontend->print_fonts_links();
300 }
301
302 /**
303 * Add control rules.
304 *
305 * Parse the CSS for all the elements inside any given control.
306 *
307 * This method recursively renders the CSS for all the selectors in the control.
308 *
309 * @since 1.2.0
310 * @access public
311 *
312 * @param array $control The controls.
313 * @param array $controls_stack The controls stack.
314 * @param callable $value_callback Callback function for the value.
315 * @param array $placeholders Placeholders.
316 * @param array $replacements Replacements.
317 * @param array $values Global Values.
318 */
319 public function add_control_rules( array $control, array $controls_stack, callable $value_callback, array $placeholders, array $replacements, array $values = [] ) {
320 if ( empty( $control['selectors'] ) ) {
321 return;
322 }
323
324 $control_global_key = $control['name'];
325
326 if ( ! empty( $control['groupType'] ) ) {
327 $control_global_key = $control['groupPrefix'] . $control['groupType'];
328 }
329
330 $global_values = [];
331 $global_key = '';
332
333 if ( ! empty( $values['__globals__'] ) ) {
334 $global_values = $values['__globals__'];
335 }
336
337 if ( ! empty( $global_values[ $control_global_key ] ) ) {
338 $global_key = $global_values[ $control_global_key ];
339 }
340
341 if ( ! $global_key ) {
342 $value = call_user_func( $value_callback, $control );
343
344 if ( null === $value ) {
345 return;
346 }
347 }
348
349 $stylesheet = $this->get_stylesheet();
350
351 $control = apply_filters( 'elementor/files/css/selectors', $control, $value ?? [], $this );
352
353 foreach ( $control['selectors'] as $selector => $css_property ) {
354 $output_css_property = '';
355
356 if ( $global_key ) {
357 $selector_global_value = $this->get_selector_global_value( $control, $global_key );
358
359 if ( $selector_global_value ) {
360 $output_css_property = preg_replace( '/(:)[^;]+(;?)/', '$1' . $selector_global_value . '$2', $css_property );
361 }
362 } else {
363 try {
364 if ( $this->unit_has_custom_selector( $control, $value ) ) {
365 $css_property = $control['unit_selectors_dictionary'][ $value['unit'] ];
366 }
367
368 $output_css_property = preg_replace_callback( '/{{(?:([^.}]+)\.)?([^}| ]*)(?: *\|\| *(?:([^.}]+)\.)?([^}| ]*) *)*}}/', function( $matches ) use ( $control, $value_callback, $controls_stack, $value, $css_property ) {
369 $external_control_missing = $matches[1] && ! isset( $controls_stack[ $matches[1] ] );
370
371 $parsed_value = '';
372
373 $value = apply_filters( 'elementor/files/css/property', $value, $css_property, $matches, $control );
374
375 if ( ! $external_control_missing ) {
376 $parsed_value = $this->parse_property_placeholder( $control, $value, $controls_stack, $value_callback, $matches[2], $matches[1] );
377 }
378
379 if ( '' === $parsed_value ) {
380 if ( isset( $matches[4] ) ) {
381 $parsed_value = $matches[4];
382
383 $is_string_value = preg_match( '/^([\'"])(.*)\1$/', $parsed_value, $string_matches );
384
385 if ( $is_string_value ) {
386 $parsed_value = $string_matches[2];
387 } elseif ( ! is_numeric( $parsed_value ) ) {
388 if ( $matches[3] && ! isset( $controls_stack[ $matches[3] ] ) ) {
389 return '';
390 }
391
392 $parsed_value = $this->parse_property_placeholder( $control, $value, $controls_stack, $value_callback, $matches[4], $matches[3] );
393 }
394 }
395
396 if ( '' === $parsed_value ) {
397 if ( $external_control_missing ) {
398 return '';
399 }
400
401 throw new \Exception();
402 }
403 }
404
405 if ( '__EMPTY__' === $parsed_value ) {
406 $parsed_value = '';
407 }
408
409 return $parsed_value;
410 }, $css_property );
411 } catch ( \Exception $e ) {
412 return;
413 }
414 }
415
416 if ( ! $output_css_property ) {
417 continue;
418 }
419
420 $device_pattern = '/^(?:\([^\)]+\)){1,2}/';
421
422 preg_match( $device_pattern, $selector, $device_rules );
423
424 $query = [];
425
426 if ( $device_rules ) {
427 $selector = preg_replace( $device_pattern, '', $selector );
428
429 preg_match_all( '/\(([^)]+)\)/', $device_rules[0], $pure_device_rules );
430
431 $pure_device_rules = $pure_device_rules[1];
432
433 foreach ( $pure_device_rules as $device_rule ) {
434 if ( Breakpoints_Manager::BREAKPOINT_KEY_DESKTOP === $device_rule ) {
435 continue;
436 }
437
438 $device = preg_replace( '/\+$/', '', $device_rule );
439
440 $endpoint = $device === $device_rule ? 'max' : 'min';
441
442 $query[ $endpoint ] = $device;
443 }
444 }
445
446 $parsed_selector = str_replace( $placeholders, $replacements, $selector );
447
448 if ( ! $query && ! empty( $control['responsive'] ) ) {
449 $query = array_intersect_key( $control['responsive'], array_flip( [ 'min', 'max' ] ) );
450
451 if ( ! empty( $query['max'] ) && Breakpoints_Manager::BREAKPOINT_KEY_DESKTOP === $query['max'] ) {
452 unset( $query['max'] );
453 }
454 }
455
456 $stylesheet->add_rules( $parsed_selector, $output_css_property, $query );
457 }
458 }
459
460 protected function unit_has_custom_selector( $control, $value ) {
461 return isset( $control['unit_selectors_dictionary'] ) && isset( $control['unit_selectors_dictionary'][ $value['unit'] ] );
462 }
463
464 /**
465 * @param array $control
466 * @param mixed $value
467 * @param array $controls_stack
468 * @param callable $value_callback
469 * @param string $placeholder
470 * @param string $parser_control_name
471 *
472 * @return string
473 */
474 public function parse_property_placeholder( array $control, $value, array $controls_stack, $value_callback, $placeholder, $parser_control_name = null ) {
475 if ( $parser_control_name ) {
476 // If both the processed control and the control name found in the placeholder are responsive
477 if ( ! empty( $control['responsive'] ) && ! empty( $controls_stack[ $parser_control_name ]['responsive'] ) ) {
478 $device_suffix = Controls_Manager::get_responsive_control_device_suffix( $control );
479
480 $control = $controls_stack[ $parser_control_name . $device_suffix ] ?? $controls_stack[ $parser_control_name ];
481 } else {
482 $control = $controls_stack[ $parser_control_name ];
483 }
484
485 $value = call_user_func( $value_callback, $control );
486 }
487
488 // If the control value is empty, check for global default. `0` (integer, string) are falsy but are valid values.
489 if ( empty( $value ) && '0' !== $value && 0 !== $value ) {
490 $value = $this->get_control_global_default_value( $control );
491 }
492
493 if ( Controls_Manager::FONT === $control['type'] ) {
494 $this->add_font( $value );
495 }
496
497 /** @var Base_Data_Control $control_obj */
498 $control_obj = Plugin::$instance->controls_manager->get_control( $control['type'] );
499
500 return (string) $control_obj->get_style_value( $placeholder, $value, $control );
501 }
502
503 /**
504 * Get the fonts.
505 *
506 * Retrieve the list of fonts.
507 *
508 * @since 1.9.0
509 * @access public
510 *
511 * @return array Fonts.
512 */
513 public function get_fonts() {
514 return $this->fonts;
515 }
516
517 /**
518 * Get stylesheet.
519 *
520 * Retrieve the CSS file stylesheet instance.
521 *
522 * @since 1.2.0
523 * @access public
524 *
525 * @return Stylesheet The stylesheet object.
526 */
527 public function get_stylesheet() {
528 if ( ! $this->stylesheet_obj ) {
529 $this->init_stylesheet();
530 }
531
532 return $this->stylesheet_obj;
533 }
534
535 /**
536 * Add controls stack style rules.
537 *
538 * Parse the CSS for all the elements inside any given controls stack.
539 *
540 * This method recursively renders the CSS for all the child elements in the stack.
541 *
542 * @since 1.6.0
543 * @access public
544 *
545 * @param Controls_Stack $controls_stack The controls stack.
546 * @param array $controls Controls array.
547 * @param array $values Values array.
548 * @param array $placeholders Placeholders.
549 * @param array $replacements Replacements.
550 * @param array $all_controls All controls.
551 */
552 public function add_controls_stack_style_rules( Controls_Stack $controls_stack, array $controls, array $values, array $placeholders, array $replacements, ?array $all_controls = null ) {
553 if ( ! $all_controls ) {
554 $all_controls = $controls_stack->get_controls();
555 }
556
557 $parsed_dynamic_settings = $controls_stack->parse_dynamic_settings( $values, $controls );
558
559 foreach ( $controls as $control ) {
560 if ( ! empty( $control['style_fields'] ) ) {
561 $this->add_repeater_control_style_rules( $controls_stack, $control, $values[ $control['name'] ], $placeholders, $replacements );
562 }
563
564 if ( ! empty( $control[ Manager::DYNAMIC_SETTING_KEY ][ $control['name'] ] ) ) {
565 $this->add_dynamic_control_style_rules( $control, $control[ Manager::DYNAMIC_SETTING_KEY ][ $control['name'] ] );
566 }
567
568 if ( Controls_Manager::ICONS === $control['type'] ) {
569 $this->icons_fonts[] = $values[ $control['name'] ]['library'];
570 }
571
572 if ( ! empty( $parsed_dynamic_settings[ Manager::DYNAMIC_SETTING_KEY ][ $control['name'] ] ) ) {
573 // Dynamic CSS should not be added to the CSS files.
574 // Instead it's handled by \Elementor\Core\DynamicTags\Dynamic_CSS
575 // and printed in a style tag.
576 $should_preserve_value = isset( $control['control_type'] ) && 'content' === $control['control_type'];
577 if ( $should_preserve_value ) {
578 $this->preserved_dynamic_style_values[ $control['name'] ] = $parsed_dynamic_settings[ $control['name'] ];
579 }
580
581 unset( $parsed_dynamic_settings[ $control['name'] ] );
582
583 $this->dynamic_elements_ids[] = $controls_stack->get_id();
584
585 continue;
586 }
587
588 if ( empty( $control['selectors'] ) ) {
589 continue;
590 }
591
592 $this->add_control_style_rules( $control, $parsed_dynamic_settings, $all_controls, $placeholders, $replacements );
593 }
594 }
595
596 /**
597 * Get file handle ID.
598 *
599 * Retrieve the file handle ID.
600 *
601 * @since 1.2.0
602 * @access protected
603 * @abstract
604 *
605 * @return string CSS file handle ID.
606 */
607 abstract protected function get_file_handle_id();
608
609 /**
610 * Render CSS.
611 *
612 * Parse the CSS.
613 *
614 * @since 1.2.0
615 * @access protected
616 * @abstract
617 */
618 abstract protected function render_css();
619
620 protected function get_default_meta() {
621 return array_merge( parent::get_default_meta(), [
622 'fonts' => array_unique( $this->fonts ),
623 'icons' => array_unique( $this->icons_fonts ),
624 'dynamic_elements_ids' => [],
625 'status' => '',
626 ] );
627 }
628
629 /**
630 * Get enqueue dependencies.
631 *
632 * Retrieve the name of the stylesheet used by `wp_enqueue_style()`.
633 *
634 * @since 1.2.0
635 * @access protected
636 *
637 * @return array Name of the stylesheet.
638 */
639 protected function get_enqueue_dependencies() {
640 return [];
641 }
642
643 /**
644 * Get inline dependency.
645 *
646 * Retrieve the name of the stylesheet used by `wp_add_inline_style()`.
647 *
648 * @since 1.2.0
649 * @access protected
650 *
651 * @return string Name of the stylesheet.
652 */
653 protected function get_inline_dependency() {
654 return '';
655 }
656
657 /**
658 * Is update required.
659 *
660 * Whether the CSS requires an update. When there are new schemes or settings
661 * updates.
662 *
663 * @since 1.2.0
664 * @access protected
665 *
666 * @return bool True if the CSS requires an update, False otherwise.
667 */
668 protected function is_update_required() {
669 return false;
670 }
671
672 /**
673 * Parse CSS.
674 *
675 * Parsing the CSS file.
676 *
677 * @since 1.2.0
678 * @access protected
679 */
680 protected function parse_content() {
681 Performance::set_use_style_controls( true );
682
683 $initial_responsive_controls_duplication_mode = Plugin::$instance->breakpoints->get_responsive_control_duplication_mode();
684
685 Plugin::$instance->breakpoints->set_responsive_control_duplication_mode( $this->get_responsive_control_duplication_mode() );
686
687 $this->render_css();
688
689 $name = $this->get_name();
690
691 /**
692 * Parse CSS file.
693 *
694 * Fires when CSS file is parsed on Elementor.
695 *
696 * The dynamic portion of the hook name, `$name`, refers to the CSS file name.
697 *
698 * @since 2.0.0
699 *
700 * @param Base $this The current CSS file.
701 */
702 do_action( "elementor/css-file/{$name}/parse", $this );
703
704 Plugin::$instance->breakpoints->set_responsive_control_duplication_mode( $initial_responsive_controls_duplication_mode );
705
706 Performance::set_use_style_controls( false );
707
708 return $this->get_stylesheet()->__toString();
709 }
710
711 /**
712 * Add control style rules.
713 *
714 * Register new style rules for the control.
715 *
716 * @since 1.6.0
717 * @access private
718 *
719 * @param array $control The control.
720 * @param array $values Values array.
721 * @param array $controls The controls stack.
722 * @param array $placeholders Placeholders.
723 * @param array $replacements Replacements.
724 */
725 protected function add_control_style_rules( array $control, array $values, array $controls, array $placeholders, array $replacements ) {
726 $this->add_control_rules(
727 $control, $controls, function( $control ) use ( $values ) {
728 return $this->get_style_control_value( $control, $values );
729 }, $placeholders, $replacements, $values
730 );
731 }
732
733 /**
734 * Get Control Global Default Value
735 *
736 * If the control has a global default value, and the corresponding global default setting is enabled, this method
737 * fetches and returns the global default value. Otherwise, it returns null.
738 *
739 * @since 3.7.0
740 * @access private
741 *
742 * @param $control
743 * @return string|null
744 */
745 private function get_control_global_default_value( $control ) {
746 if ( empty( $control['global']['default'] ) ) {
747 return null;
748 }
749
750 // If the control value is empty, and the control has a global default set, fetch the global value and use it.
751 $global_enabled = false;
752
753 if ( 'color' === $control['type'] ) {
754 $global_enabled = Plugin::$instance->kits_manager->is_custom_colors_enabled();
755 } elseif ( isset( $control['groupType'] ) && 'typography' === $control['groupType'] ) {
756 $global_enabled = Plugin::$instance->kits_manager->is_custom_typography_enabled();
757 }
758
759 $value = null;
760
761 // Only apply the global default if Global Colors are enabled.
762 if ( $global_enabled ) {
763 $value = $this->get_selector_global_value( $control, $control['global']['default'] );
764 }
765
766 return $value;
767 }
768
769 /**
770 * Get style control value.
771 *
772 * Retrieve the value of the style control for any give control and values.
773 *
774 * It will retrieve the control name and return the style value.
775 *
776 * @since 1.6.0
777 * @access private
778 *
779 * @param array $control The control.
780 * @param array $values Values array.
781 *
782 * @return mixed Style control value.
783 */
784 private function get_style_control_value( array $control, array $values ) {
785 if ( ! empty( $values['__globals__'][ $control['name'] ] ) ) {
786 // When the control itself has no global value, but it refers to another control global value
787 return $this->get_selector_global_value( $control, $values['__globals__'][ $control['name'] ] );
788 }
789
790 $value = isset( $values[ $control['name'] ] )
791 ? $values[ $control['name'] ]
792 : $this->preserved_dynamic_style_values[ $control['name'] ] ?? null;
793
794 if ( isset( $control['selectors_dictionary'][ $value ] ) ) {
795 $value = $control['selectors_dictionary'][ $value ];
796 }
797
798 if ( ! is_numeric( $value ) && ! is_float( $value ) && empty( $value ) ) {
799 return null;
800 }
801
802 return $value;
803 }
804
805 /**
806 * Init stylesheet.
807 *
808 * Initialize CSS file stylesheet by creating a new `Stylesheet` object and register new
809 * breakpoints for the stylesheet.
810 *
811 * @since 1.2.0
812 * @access private
813 */
814 private function init_stylesheet() {
815 $this->stylesheet_obj = new Stylesheet();
816
817 $active_breakpoints = Plugin::$instance->breakpoints->get_active_breakpoints();
818
819 foreach ( $active_breakpoints as $breakpoint_name => $breakpoint ) {
820 $this->stylesheet_obj->add_device( $breakpoint_name, $breakpoint->get_value() );
821 }
822 }
823
824 /**
825 * Add repeater control style rules.
826 *
827 * Register new style rules for the repeater control.
828 *
829 * @since 2.0.0
830 * @access private
831 *
832 * @param Controls_Stack $controls_stack The control stack.
833 * @param array $repeater_control The repeater control.
834 * @param array $repeater_values Repeater values array.
835 * @param array $placeholders Placeholders.
836 * @param array $replacements Replacements.
837 */
838 protected function add_repeater_control_style_rules( Controls_Stack $controls_stack, array $repeater_control, array $repeater_values, array $placeholders, array $replacements ) {
839 $placeholders = array_merge( $placeholders, [ '{{CURRENT_ITEM}}' ] );
840
841 foreach ( $repeater_control['style_fields'] as $index => $item ) {
842 $this->add_controls_stack_style_rules(
843 $controls_stack,
844 $item,
845 $repeater_values[ $index ],
846 $placeholders,
847 array_merge( $replacements, [ '.elementor-repeater-item-' . $repeater_values[ $index ]['_id'] ] ),
848 $repeater_control['fields']
849 );
850 }
851 }
852
853 /**
854 * Add dynamic control style rules.
855 *
856 * Register new style rules for the dynamic control.
857 *
858 * @since 2.0.0
859 * @access private
860 *
861 * @param array $control The control.
862 * @param string $value The value.
863 */
864 protected function add_dynamic_control_style_rules( array $control, $value ) {
865 Plugin::$instance->dynamic_tags->parse_tags_text( $value, $control, function( $id, $name, $settings ) {
866 $tag = Plugin::$instance->dynamic_tags->create_tag( $id, $name, $settings );
867
868 if ( ! $tag instanceof Tag ) {
869 return;
870 }
871
872 $this->add_controls_stack_style_rules( $tag, $this->get_style_controls( $tag ), $tag->get_active_settings(), [ '{{WRAPPER}}' ], [ '#elementor-tag-' . $id ] );
873 } );
874 }
875
876 private function get_selector_global_value( $control, $global_key ) {
877 $data = Plugin::$instance->data_manager_v2->run( $global_key );
878
879 if ( empty( $data['value'] ) ) {
880 return null;
881 }
882
883 $global_args = explode( '?id=', $global_key );
884
885 $id = $global_args[1];
886
887 if ( ! empty( $control['groupType'] ) ) {
888 $strings_to_replace = [ $control['groupPrefix'] ];
889
890 $active_breakpoint_keys = array_keys( Plugin::$instance->breakpoints->get_active_breakpoints() );
891
892 foreach ( $active_breakpoint_keys as $breakpoint ) {
893 $strings_to_replace[] = '_' . $breakpoint;
894 }
895
896 $property_name = str_replace( $strings_to_replace, '', $control['name'] );
897
898 // TODO: This check won't retrieve the proper answer for array values (multiple controls).
899 if ( empty( $data['value'][ Global_Typography::TYPOGRAPHY_GROUP_PREFIX . $property_name ] ) ) {
900 return null;
901 }
902
903 $property_name = str_replace( '_', '-', $property_name );
904
905 $value = "var( --e-global-$control[groupType]-$id-$property_name )";
906
907 if ( $control['groupPrefix'] . 'font_family' === $control['name'] ) {
908 $default_generic_fonts = Plugin::$instance->kits_manager->get_current_settings( 'default_generic_fonts' );
909
910 if ( $default_generic_fonts ) {
911 $value .= ", $default_generic_fonts";
912 }
913 }
914 } else {
915 $value = "var( --e-global-$control[type]-$id )";
916 }
917
918 return $value;
919 }
920
921 final protected function get_active_controls( Controls_Stack $controls_stack, ?array $controls = null, ?array $settings = null ) {
922 if ( ! $controls ) {
923 $controls = $controls_stack->get_controls();
924 }
925
926 if ( ! $settings ) {
927 $settings = $controls_stack->get_controls_settings();
928 }
929
930 if ( $this->is_global_parsing_supported() ) {
931 $settings = $this->parse_global_settings( $settings, $controls );
932 }
933
934 $active_controls = array_reduce(
935 array_keys( $controls ), function( $active_controls, $control_key ) use ( $controls_stack, $controls, $settings ) {
936 $control = $controls[ $control_key ];
937
938 if ( $controls_stack->is_control_visible( $control, $settings, $controls ) ) {
939 $active_controls[ $control_key ] = $control;
940 }
941
942 return $active_controls;
943 }, []
944 );
945
946 return $active_controls;
947 }
948
949 final public function get_style_controls( Controls_Stack $controls_stack, ?array $controls = null, ?array $settings = null ) {
950 $controls = $this->get_active_controls( $controls_stack, $controls, $settings );
951
952 $style_controls = [];
953
954 foreach ( $controls as $control_name => $control ) {
955 $control_obj = Plugin::$instance->controls_manager->get_control( $control['type'] );
956
957 if ( ! $control_obj instanceof Base_Data_Control ) {
958 continue;
959 }
960
961 $control = array_merge( $control_obj->get_settings(), $control );
962
963 if ( $control_obj instanceof Control_Repeater ) {
964 $style_fields = [];
965
966 foreach ( $controls_stack->get_settings( $control_name ) as $item ) {
967 $style_fields[] = $this->get_style_controls( $controls_stack, $control['fields'], $item );
968 }
969
970 $control['style_fields'] = $style_fields;
971 }
972
973 if ( ! empty( $control['selectors'] ) || ! empty( $control['dynamic'] ) || $this->is_global_control( $controls_stack, $control_name, $controls ) || ! empty( $control['style_fields'] ) ) {
974 $style_controls[ $control_name ] = $control;
975 }
976 }
977
978 return $style_controls;
979 }
980
981 private function parse_global_settings( array $settings, array $controls ) {
982 foreach ( $controls as $control ) {
983 $control_name = $control['name'];
984 $control_obj = Plugin::$instance->controls_manager->get_control( $control['type'] );
985
986 if ( ! $control_obj instanceof Base_Data_Control ) {
987 continue;
988 }
989
990 if ( $control_obj instanceof Control_Repeater ) {
991 foreach ( $settings[ $control_name ] as & $field ) {
992 $field = $this->parse_global_settings( $field, $control['fields'] );
993 }
994
995 continue;
996 }
997
998 if ( empty( $control['global']['active'] ) ) {
999 continue;
1000 }
1001
1002 if ( empty( $settings['__globals__'][ $control_name ] ) ) {
1003 continue;
1004 }
1005
1006 $settings[ $control_name ] = 'global';
1007 }
1008
1009 return $settings;
1010 }
1011
1012 private function is_global_control( Controls_Stack $controls_stack, $control_name, $controls ) {
1013 $control = $controls[ $control_name ];
1014
1015 $control_global_key = $control_name;
1016
1017 if ( ! empty( $control['groupType'] ) ) {
1018 $control_global_key = $control['groupPrefix'] . $control['groupType'];
1019 }
1020
1021 if ( empty( $controls[ $control_global_key ]['global']['active'] ) ) {
1022 return false;
1023 }
1024
1025 $globals = $controls_stack->get_settings( '__globals__' );
1026
1027 return ! empty( $globals[ $control_global_key ] );
1028 }
1029
1030 public function add_font( $font ) {
1031 if ( ! in_array( $font, $this->fonts, true ) ) {
1032 $this->fonts[] = $font;
1033 }
1034 }
1035 }
1036