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

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