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

911 lines 23.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\Files\Base as Base_File;
9 use Elementor\Core\DynamicTags\Manager;
10 use Elementor\Core\DynamicTags\Tag;
11 use Elementor\Core\Kits\Documents\Tabs\Global_Typography;
12 use Elementor\Element_Base;
13 use Elementor\Plugin;
14 use Elementor\Core\Responsive\Responsive;
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 * Enqueue CSS.
182 *
183 * Either enqueue the CSS file in Elementor or add inline style.
184 *
185 * This method is also responsible for loading the fonts.
186 *
187 * @since 1.2.0
188 * @access public
189 */
190 public function enqueue() {
191 $handle_id = $this->get_file_handle_id();
192
193 if ( isset( self::$printed[ $handle_id ] ) ) {
194 return;
195 }
196
197 self::$printed[ $handle_id ] = true;
198
199 $meta = $this->get_meta();
200
201 if ( self::CSS_STATUS_EMPTY === $meta['status'] ) {
202 return;
203 }
204
205 // First time after clear cache and etc.
206 if ( '' === $meta['status'] || $this->is_update_required() ) {
207 $this->update();
208
209 $meta = $this->get_meta();
210 }
211
212 if ( self::CSS_STATUS_INLINE === $meta['status'] ) {
213 $dep = $this->get_inline_dependency();
214 // If the dependency has already been printed ( like a template in footer )
215 if ( wp_styles()->query( $dep, 'done' ) ) {
216 printf( '<style id="%1$s">%2$s</style>', $this->get_file_handle_id(), $meta['css'] ); // XSS ok.
217 } else {
218 wp_add_inline_style( $dep, $meta['css'] );
219 }
220 } elseif ( self::CSS_STATUS_FILE === $meta['status'] ) { // Re-check if it's not empty after CSS update.
221 wp_enqueue_style( $this->get_file_handle_id(), $this->get_url(), $this->get_enqueue_dependencies(), null ); // phpcs:ignore WordPress.WP.EnqueuedResourceParameters.MissingVersion
222 }
223
224 // Handle fonts.
225 if ( ! empty( $meta['fonts'] ) ) {
226 foreach ( $meta['fonts'] as $font ) {
227 Plugin::$instance->frontend->enqueue_font( $font );
228 }
229 }
230
231 if ( ! empty( $meta['icons'] ) ) {
232 $icons_types = Icons_Manager::get_icon_manager_tabs();
233 foreach ( $meta['icons'] as $icon_font ) {
234 if ( ! isset( $icons_types[ $icon_font ] ) ) {
235 continue;
236 }
237 Plugin::$instance->frontend->enqueue_font( $icon_font );
238 }
239 }
240
241 $name = $this->get_name();
242
243 /**
244 * Enqueue CSS file.
245 *
246 * Fires when CSS file is enqueued on Elementor.
247 *
248 * The dynamic portion of the hook name, `$name`, refers to the CSS file name.
249 *
250 * @since 2.0.0
251 *
252 * @param Base $this The current CSS file.
253 */
254 do_action( "elementor/css-file/{$name}/enqueue", $this );
255 }
256
257 /**
258 * Print CSS.
259 *
260 * Output the final CSS inside the `<style>` tags and all the frontend fonts in
261 * use.
262 *
263 * @since 1.9.4
264 * @access public
265 */
266 public function print_css() {
267 echo '<style>' . $this->get_content() . '</style>'; // XSS ok.
268 Plugin::$instance->frontend->print_fonts_links();
269 }
270
271 /**
272 * Add control rules.
273 *
274 * Parse the CSS for all the elements inside any given control.
275 *
276 * This method recursively renders the CSS for all the selectors in the control.
277 *
278 * @since 1.2.0
279 * @access public
280 *
281 * @param array $control The controls.
282 * @param array $controls_stack The controls stack.
283 * @param callable $value_callback Callback function for the value.
284 * @param array $placeholders Placeholders.
285 * @param array $replacements Replacements.
286 * @param array $values Global Values.
287 */
288 public function add_control_rules( array $control, array $controls_stack, callable $value_callback, array $placeholders, array $replacements, array $values = [] ) {
289 if ( empty( $control['selectors'] ) ) {
290 return;
291 }
292
293 $control_global_key = $control['name'];
294
295 if ( ! empty( $control['groupType'] ) ) {
296 $control_global_key = $control['groupPrefix'] . $control['groupType'];
297 }
298
299 $global_values = [];
300 $global_key = '';
301
302 if ( ! empty( $values['__globals__'] ) ) {
303 $global_values = $values['__globals__'];
304 }
305
306 if ( ! empty( $global_values[ $control_global_key ] ) ) {
307 $global_key = $global_values[ $control_global_key ];
308 }
309
310 if ( ! $global_key ) {
311 $value = call_user_func( $value_callback, $control );
312
313 if ( null === $value ) {
314 return;
315 }
316 }
317
318 $stylesheet = $this->get_stylesheet();
319
320 foreach ( $control['selectors'] as $selector => $css_property ) {
321 $output_css_property = '';
322
323 if ( $global_key ) {
324 $selector_global_value = $this->get_selector_global_value( $control, $global_key );
325
326 if ( $selector_global_value ) {
327 $output_css_property = preg_replace( '/(:)[^;]+(;?)/', '$1' . $selector_global_value . '$2', $css_property );
328 }
329 } else {
330 try {
331 $output_css_property = preg_replace_callback( '/{{(?:([^.}]+)\.)?([^}| ]*)(?: *\|\| *(?:([^.}]+)\.)?([^}| ]*) *)*}}/', function( $matches ) use ( $control, $value_callback, $controls_stack, $value, $css_property ) {
332 $external_control_missing = $matches[1] && ! isset( $controls_stack[ $matches[1] ] );
333
334 $parsed_value = '';
335
336 if ( ! $external_control_missing ) {
337 $parsed_value = $this->parse_property_placeholder( $control, $value, $controls_stack, $value_callback, $matches[2], $matches[1] );
338 }
339
340 if ( '' === $parsed_value ) {
341 if ( isset( $matches[4] ) ) {
342 $parsed_value = $matches[4];
343
344 $is_string_value = preg_match( '/^([\'"])(.*)\1$/', $parsed_value, $string_matches );
345
346 if ( $is_string_value ) {
347 $parsed_value = $string_matches[2];
348 } elseif ( ! is_numeric( $parsed_value ) ) {
349 if ( $matches[3] && ! isset( $controls_stack[ $matches[3] ] ) ) {
350 return '';
351 }
352
353 $parsed_value = $this->parse_property_placeholder( $control, $value, $controls_stack, $value_callback, $matches[4], $matches[3] );
354 }
355 }
356
357 if ( '' === $parsed_value ) {
358 if ( $external_control_missing ) {
359 return '';
360 }
361
362 throw new \Exception();
363 }
364 }
365
366 return $parsed_value;
367 }, $css_property );
368 } catch ( \Exception $e ) {
369 return;
370 }
371 }
372
373 if ( ! $output_css_property ) {
374 continue;
375 }
376
377 $device_pattern = '/^(?:\([^\)]+\)){1,2}/';
378
379 preg_match( $device_pattern, $selector, $device_rules );
380
381 $query = [];
382
383 if ( $device_rules ) {
384 $selector = preg_replace( $device_pattern, '', $selector );
385
386 preg_match_all( '/\(([^)]+)\)/', $device_rules[0], $pure_device_rules );
387
388 $pure_device_rules = $pure_device_rules[1];
389
390 foreach ( $pure_device_rules as $device_rule ) {
391 if ( Element_Base::RESPONSIVE_DESKTOP === $device_rule ) {
392 continue;
393 }
394
395 $device = preg_replace( '/\+$/', '', $device_rule );
396
397 $endpoint = $device === $device_rule ? 'max' : 'min';
398
399 $query[ $endpoint ] = $device;
400 }
401 }
402
403 $parsed_selector = str_replace( $placeholders, $replacements, $selector );
404
405 if ( ! $query && ! empty( $control['responsive'] ) ) {
406 $query = array_intersect_key( $control['responsive'], array_flip( [ 'min', 'max' ] ) );
407
408 if ( ! empty( $query['max'] ) && Element_Base::RESPONSIVE_DESKTOP === $query['max'] ) {
409 unset( $query['max'] );
410 }
411 }
412
413 $stylesheet->add_rules( $parsed_selector, $output_css_property, $query );
414 }
415 }
416
417 /**
418 * @param array $control
419 * @param mixed $value
420 * @param array $controls_stack
421 * @param callable $value_callback
422 * @param string $placeholder
423 * @param string $parser_control_name
424 *
425 * @return string
426 */
427 public function parse_property_placeholder( array $control, $value, array $controls_stack, $value_callback, $placeholder, $parser_control_name = null ) {
428 if ( $parser_control_name ) {
429 $control = $controls_stack[ $parser_control_name ];
430
431 $value = call_user_func( $value_callback, $control );
432 }
433
434 if ( Controls_Manager::FONT === $control['type'] ) {
435 $this->fonts[] = $value;
436 }
437
438 /** @var Base_Data_Control $control_obj */
439 $control_obj = Plugin::$instance->controls_manager->get_control( $control['type'] );
440
441 return (string) $control_obj->get_style_value( $placeholder, $value, $control );
442 }
443
444 /**
445 * Get the fonts.
446 *
447 * Retrieve the list of fonts.
448 *
449 * @since 1.9.0
450 * @access public
451 *
452 * @return array Fonts.
453 */
454 public function get_fonts() {
455 return $this->fonts;
456 }
457
458 /**
459 * Get stylesheet.
460 *
461 * Retrieve the CSS file stylesheet instance.
462 *
463 * @since 1.2.0
464 * @access public
465 *
466 * @return Stylesheet The stylesheet object.
467 */
468 public function get_stylesheet() {
469 if ( ! $this->stylesheet_obj ) {
470 $this->init_stylesheet();
471 }
472
473 return $this->stylesheet_obj;
474 }
475
476 /**
477 * Add controls stack style rules.
478 *
479 * Parse the CSS for all the elements inside any given controls stack.
480 *
481 * This method recursively renders the CSS for all the child elements in the stack.
482 *
483 * @since 1.6.0
484 * @access public
485 *
486 * @param Controls_Stack $controls_stack The controls stack.
487 * @param array $controls Controls array.
488 * @param array $values Values array.
489 * @param array $placeholders Placeholders.
490 * @param array $replacements Replacements.
491 * @param array $all_controls All controls.
492 */
493 public function add_controls_stack_style_rules( Controls_Stack $controls_stack, array $controls, array $values, array $placeholders, array $replacements, array $all_controls = null ) {
494 if ( ! $all_controls ) {
495 $all_controls = $controls_stack->get_controls();
496 }
497
498 $parsed_dynamic_settings = $controls_stack->parse_dynamic_settings( $values, $controls );
499
500 foreach ( $controls as $control ) {
501 if ( ! empty( $control['style_fields'] ) ) {
502 $this->add_repeater_control_style_rules( $controls_stack, $control, $values[ $control['name'] ], $placeholders, $replacements );
503 }
504
505 if ( ! empty( $control[ Manager::DYNAMIC_SETTING_KEY ][ $control['name'] ] ) ) {
506 $this->add_dynamic_control_style_rules( $control, $control[ Manager::DYNAMIC_SETTING_KEY ][ $control['name'] ] );
507 }
508
509 if ( Controls_Manager::ICONS === $control['type'] ) {
510 $this->icons_fonts[] = $values[ $control['name'] ]['library'];
511 }
512
513 if ( ! empty( $parsed_dynamic_settings[ Manager::DYNAMIC_SETTING_KEY ][ $control['name'] ] ) ) {
514 // Dynamic CSS should not be added to the CSS files.
515 // Instead it's handled by \Elementor\Core\DynamicTags\Dynamic_CSS
516 // and printed in a style tag.
517 unset( $parsed_dynamic_settings[ $control['name'] ] );
518
519 $this->dynamic_elements_ids[] = $controls_stack->get_id();
520
521 continue;
522 }
523
524 if ( empty( $control['selectors'] ) ) {
525 continue;
526 }
527
528 $this->add_control_style_rules( $control, $parsed_dynamic_settings, $all_controls, $placeholders, $replacements );
529 }
530 }
531
532 /**
533 * Get file handle ID.
534 *
535 * Retrieve the file handle ID.
536 *
537 * @since 1.2.0
538 * @access protected
539 * @abstract
540 *
541 * @return string CSS file handle ID.
542 */
543 abstract protected function get_file_handle_id();
544
545 /**
546 * Render CSS.
547 *
548 * Parse the CSS.
549 *
550 * @since 1.2.0
551 * @access protected
552 * @abstract
553 */
554 abstract protected function render_css();
555
556 protected function get_default_meta() {
557 return array_merge( parent::get_default_meta(), [
558 'fonts' => array_unique( $this->fonts ),
559 'icons' => array_unique( $this->icons_fonts ),
560 'dynamic_elements_ids' => [],
561 'status' => '',
562 ] );
563 }
564
565 /**
566 * Get enqueue dependencies.
567 *
568 * Retrieve the name of the stylesheet used by `wp_enqueue_style()`.
569 *
570 * @since 1.2.0
571 * @access protected
572 *
573 * @return array Name of the stylesheet.
574 */
575 protected function get_enqueue_dependencies() {
576 return [];
577 }
578
579 /**
580 * Get inline dependency.
581 *
582 * Retrieve the name of the stylesheet used by `wp_add_inline_style()`.
583 *
584 * @since 1.2.0
585 * @access protected
586 *
587 * @return string Name of the stylesheet.
588 */
589 protected function get_inline_dependency() {
590 return '';
591 }
592
593 /**
594 * Is update required.
595 *
596 * Whether the CSS requires an update. When there are new schemes or settings
597 * updates.
598 *
599 * @since 1.2.0
600 * @access protected
601 *
602 * @return bool True if the CSS requires an update, False otherwise.
603 */
604 protected function is_update_required() {
605 return false;
606 }
607
608 /**
609 * Parse CSS.
610 *
611 * Parsing the CSS file.
612 *
613 * @since 1.2.0
614 * @access protected
615 */
616 protected function parse_content() {
617 $this->render_css();
618
619 $name = $this->get_name();
620
621 /**
622 * Parse CSS file.
623 *
624 * Fires when CSS file is parsed on Elementor.
625 *
626 * The dynamic portion of the hook name, `$name`, refers to the CSS file name.
627 *
628 * @since 2.0.0
629 *
630 * @param Base $this The current CSS file.
631 */
632 do_action( "elementor/css-file/{$name}/parse", $this );
633
634 return $this->get_stylesheet()->__toString();
635 }
636
637 /**
638 * Add control style rules.
639 *
640 * Register new style rules for the control.
641 *
642 * @since 1.6.0
643 * @access private
644 *
645 * @param array $control The control.
646 * @param array $values Values array.
647 * @param array $controls The controls stack.
648 * @param array $placeholders Placeholders.
649 * @param array $replacements Replacements.
650 */
651 protected function add_control_style_rules( array $control, array $values, array $controls, array $placeholders, array $replacements ) {
652 $this->add_control_rules(
653 $control, $controls, function( $control ) use ( $values ) {
654 return $this->get_style_control_value( $control, $values );
655 }, $placeholders, $replacements, $values
656 );
657 }
658
659 /**
660 * Get style control value.
661 *
662 * Retrieve the value of the style control for any give control and values.
663 *
664 * It will retrieve the control name and return the style value.
665 *
666 * @since 1.6.0
667 * @access private
668 *
669 * @param array $control The control.
670 * @param array $values Values array.
671 *
672 * @return mixed Style control value.
673 */
674 private function get_style_control_value( array $control, array $values ) {
675 if ( ! empty( $values['__globals__'][ $control['name'] ] ) ) {
676 // When the control itself has no global value, but it refers to another control global value
677 return $this->get_selector_global_value( $control, $values['__globals__'][ $control['name'] ] );
678 }
679
680 $value = $values[ $control['name'] ];
681
682 if ( isset( $control['selectors_dictionary'][ $value ] ) ) {
683 $value = $control['selectors_dictionary'][ $value ];
684 }
685
686 if ( ! is_numeric( $value ) && ! is_float( $value ) && empty( $value ) ) {
687 return null;
688 }
689
690 return $value;
691 }
692
693 /**
694 * Init stylesheet.
695 *
696 * Initialize CSS file stylesheet by creating a new `Stylesheet` object and register new
697 * breakpoints for the stylesheet.
698 *
699 * @since 1.2.0
700 * @access private
701 */
702 private function init_stylesheet() {
703 $this->stylesheet_obj = new Stylesheet();
704
705 $breakpoints = Responsive::get_breakpoints();
706
707 $this->stylesheet_obj
708 ->add_device( 'mobile', 0 )
709 ->add_device( 'tablet', $breakpoints['md'] )
710 ->add_device( 'desktop', $breakpoints['lg'] );
711 }
712
713 /**
714 * Add repeater control style rules.
715 *
716 * Register new style rules for the repeater control.
717 *
718 * @since 2.0.0
719 * @access private
720 *
721 * @param Controls_Stack $controls_stack The control stack.
722 * @param array $repeater_control The repeater control.
723 * @param array $repeater_values Repeater values array.
724 * @param array $placeholders Placeholders.
725 * @param array $replacements Replacements.
726 */
727 protected function add_repeater_control_style_rules( Controls_Stack $controls_stack, array $repeater_control, array $repeater_values, array $placeholders, array $replacements ) {
728 $placeholders = array_merge( $placeholders, [ '{{CURRENT_ITEM}}' ] );
729
730 foreach ( $repeater_control['style_fields'] as $index => $item ) {
731 $this->add_controls_stack_style_rules(
732 $controls_stack,
733 $item,
734 $repeater_values[ $index ],
735 $placeholders,
736 array_merge( $replacements, [ '.elementor-repeater-item-' . $repeater_values[ $index ]['_id'] ] ),
737 $repeater_control['fields']
738 );
739 }
740 }
741
742 /**
743 * Add dynamic control style rules.
744 *
745 * Register new style rules for the dynamic control.
746 *
747 * @since 2.0.0
748 * @access private
749 *
750 * @param array $control The control.
751 * @param string $value The value.
752 */
753 protected function add_dynamic_control_style_rules( array $control, $value ) {
754 Plugin::$instance->dynamic_tags->parse_tags_text( $value, $control, function( $id, $name, $settings ) {
755 $tag = Plugin::$instance->dynamic_tags->create_tag( $id, $name, $settings );
756
757 if ( ! $tag instanceof Tag ) {
758 return;
759 }
760
761 $this->add_controls_stack_style_rules( $tag, $this->get_style_controls( $tag ), $tag->get_active_settings(), [ '{{WRAPPER}}' ], [ '#elementor-tag-' . $id ] );
762 } );
763 }
764
765 private function get_selector_global_value( $control, $global_key ) {
766 $data = Plugin::$instance->data_manager->run( $global_key );
767
768 if ( empty( $data['value'] ) ) {
769 return null;
770 }
771
772 $global_args = explode( '?id=', $global_key );
773
774 $id = $global_args[1];
775
776 if ( ! empty( $control['groupType'] ) ) {
777 $property_name = str_replace( [ $control['groupPrefix'], '_tablet', '_mobile' ], '', $control['name'] );
778
779 // TODO: This check won't retrieve the proper answer for array values (multiple controls).
780 if ( empty( $data['value'][ Global_Typography::TYPOGRAPHY_GROUP_PREFIX . $property_name ] ) ) {
781 return null;
782 }
783
784 $property_name = str_replace( '_', '-', $property_name );
785
786 $value = "var( --e-global-$control[groupType]-$id-$property_name )";
787
788 if ( $control['groupPrefix'] . 'font_family' === $control['name'] ) {
789 $default_generic_fonts = Plugin::$instance->kits_manager->get_current_settings( 'default_generic_fonts' );
790
791 if ( $default_generic_fonts ) {
792 $value .= ", $default_generic_fonts";
793 }
794 }
795 } else {
796 $value = "var( --e-global-$control[type]-$id )";
797 }
798
799 return $value;
800 }
801
802 final protected function get_active_controls( Controls_Stack $controls_stack, array $controls = null, array $settings = null ) {
803 if ( ! $controls ) {
804 $controls = $controls_stack->get_controls();
805 }
806
807 if ( ! $settings ) {
808 $settings = $controls_stack->get_controls_settings();
809 }
810
811 if ( $this->is_global_parsing_supported() ) {
812 $settings = $this->parse_global_settings( $settings, $controls );
813 }
814
815 $active_controls = array_reduce(
816 array_keys( $controls ), function( $active_controls, $control_key ) use ( $controls_stack, $controls, $settings ) {
817 $control = $controls[ $control_key ];
818
819 if ( $controls_stack->is_control_visible( $control, $settings ) ) {
820 $active_controls[ $control_key ] = $control;
821 }
822
823 return $active_controls;
824 }, []
825 );
826
827 return $active_controls;
828 }
829
830 final public function get_style_controls( Controls_Stack $controls_stack, array $controls = null, array $settings = null ) {
831 $controls = $this->get_active_controls( $controls_stack, $controls, $settings );
832
833 $style_controls = [];
834
835 foreach ( $controls as $control_name => $control ) {
836 $control_obj = Plugin::$instance->controls_manager->get_control( $control['type'] );
837
838 if ( ! $control_obj instanceof Base_Data_Control ) {
839 continue;
840 }
841
842 $control = array_merge( $control_obj->get_settings(), $control );
843
844 if ( $control_obj instanceof Control_Repeater ) {
845 $style_fields = [];
846
847 foreach ( $controls_stack->get_settings( $control_name ) as $item ) {
848 $style_fields[] = $this->get_style_controls( $controls_stack, $control['fields'], $item );
849 }
850
851 $control['style_fields'] = $style_fields;
852 }
853
854 if ( ! empty( $control['selectors'] ) || ! empty( $control['dynamic'] ) || $this->is_global_control( $controls_stack, $control_name, $controls ) || ! empty( $control['style_fields'] ) ) {
855 $style_controls[ $control_name ] = $control;
856 }
857 }
858
859 return $style_controls;
860 }
861
862 private function parse_global_settings( array $settings, array $controls ) {
863 foreach ( $controls as $control ) {
864 $control_name = $control['name'];
865 $control_obj = Plugin::$instance->controls_manager->get_control( $control['type'] );
866
867 if ( ! $control_obj instanceof Base_Data_Control ) {
868 continue;
869 }
870
871 if ( $control_obj instanceof Control_Repeater ) {
872 foreach ( $settings[ $control_name ] as & $field ) {
873 $field = $this->parse_global_settings( $field, $control['fields'] );
874 }
875
876 continue;
877 }
878
879 if ( empty( $control['global']['active'] ) ) {
880 continue;
881 }
882
883 if ( empty( $settings['__globals__'][ $control_name ] ) ) {
884 continue;
885 }
886
887 $settings[ $control_name ] = 'global';
888 }
889
890 return $settings;
891 }
892
893 private function is_global_control( Controls_Stack $controls_stack, $control_name, $controls ) {
894 $control = $controls[ $control_name ];
895
896 $control_global_key = $control_name;
897
898 if ( ! empty( $control['groupType'] ) ) {
899 $control_global_key = $control['groupPrefix'] . $control['groupType'];
900 }
901
902 if ( empty( $controls[ $control_global_key ]['global']['active'] ) ) {
903 return false;
904 }
905
906 $globals = $controls_stack->get_settings( '__globals__' );
907
908 return ! empty( $globals[ $control_global_key ] );
909 }
910 }
911