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

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