PluginProbe
Elementor Website Builder – more than just a page builder / 4.3.0-beta2
Elementor Website Builder – more than just a page builder v4.3.0-beta2
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.3.0-beta2, at core/files/css/base.php

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