PluginProbe
Elementor Website Builder – more than just a page builder / 3.22.0-dev4
Elementor Website Builder – more than just a page builder v3.22.0-dev4
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 / includes / managers / controls.php

controls.php in Elementor Website Builder – more than just a page builder 3.22.0-dev4, at includes/managers/controls.php

1,389 lines 32.1 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2 namespace Elementor;
3
4 use Elementor\Core\Frontend\Performance;
5
6 if ( ! defined( 'ABSPATH' ) ) {
7 exit; // Exit if accessed directly.
8 }
9
10 /**
11 * Elementor controls manager.
12 *
13 * Elementor controls manager handler class is responsible for registering and
14 * initializing all the supported controls, both regular controls and the group
15 * controls.
16 *
17 * @since 1.0.0
18 */
19 class Controls_Manager {
20
21 /**
22 * Content tab.
23 */
24 const TAB_CONTENT = 'content';
25
26 /**
27 * Style tab.
28 */
29 const TAB_STYLE = 'style';
30
31 /**
32 * Advanced tab.
33 */
34 const TAB_ADVANCED = 'advanced';
35
36 /**
37 * Responsive tab.
38 */
39 const TAB_RESPONSIVE = 'responsive';
40
41 /**
42 * Layout tab.
43 */
44 const TAB_LAYOUT = 'layout';
45
46 /**
47 * Settings tab.
48 */
49 const TAB_SETTINGS = 'settings';
50
51 /**
52 * Text control.
53 */
54 const TEXT = 'text';
55
56 /**
57 * Number control.
58 */
59 const NUMBER = 'number';
60
61 /**
62 * Textarea control.
63 */
64 const TEXTAREA = 'textarea';
65
66 /**
67 * Select control.
68 */
69 const SELECT = 'select';
70
71 /**
72 * Switcher control.
73 */
74 const SWITCHER = 'switcher';
75
76 /**
77 * Button control.
78 */
79 const BUTTON = 'button';
80
81 /**
82 * Hidden control.
83 */
84 const HIDDEN = 'hidden';
85
86 /**
87 * Heading control.
88 */
89 const HEADING = 'heading';
90
91 /**
92 * Raw HTML control.
93 */
94 const RAW_HTML = 'raw_html';
95
96 /**
97 * Notice control.
98 */
99 const NOTICE = 'notice';
100
101 /**
102 * Deprecated Notice control.
103 */
104 const DEPRECATED_NOTICE = 'deprecated_notice';
105
106 /**
107 * Alert control.
108 */
109 const ALERT = 'alert';
110
111 /**
112 * Popover Toggle control.
113 */
114 const POPOVER_TOGGLE = 'popover_toggle';
115
116 /**
117 * Section control.
118 */
119 const SECTION = 'section';
120
121 /**
122 * Tab control.
123 */
124 const TAB = 'tab';
125
126 /**
127 * Tabs control.
128 */
129 const TABS = 'tabs';
130
131 /**
132 * Divider control.
133 */
134 const DIVIDER = 'divider';
135
136 /**
137 * Color control.
138 */
139 const COLOR = 'color';
140
141 /**
142 * Media control.
143 */
144 const MEDIA = 'media';
145
146 /**
147 * Slider control.
148 */
149 const SLIDER = 'slider';
150
151 /**
152 * Dimensions control.
153 */
154 const DIMENSIONS = 'dimensions';
155
156 /**
157 * Choose control.
158 */
159 const CHOOSE = 'choose';
160
161 /**
162 * WYSIWYG control.
163 */
164 const WYSIWYG = 'wysiwyg';
165
166 /**
167 * Code control.
168 */
169 const CODE = 'code';
170
171 /**
172 * Font control.
173 */
174 const FONT = 'font';
175
176 /**
177 * Image dimensions control.
178 */
179 const IMAGE_DIMENSIONS = 'image_dimensions';
180
181 /**
182 * WordPress widget control.
183 */
184 const WP_WIDGET = 'wp_widget';
185
186 /**
187 * URL control.
188 */
189 const URL = 'url';
190
191 /**
192 * Repeater control.
193 */
194 const REPEATER = 'repeater';
195
196 /**
197 * Icon control.
198 */
199 const ICON = 'icon';
200
201 /**
202 * Icons control.
203 */
204 const ICONS = 'icons';
205
206 /**
207 * Gallery control.
208 */
209 const GALLERY = 'gallery';
210
211 /**
212 * Structure control.
213 */
214 const STRUCTURE = 'structure';
215
216 /**
217 * Select2 control.
218 */
219 const SELECT2 = 'select2';
220
221 /**
222 * Date/Time control.
223 */
224 const DATE_TIME = 'date_time';
225
226 /**
227 * Box shadow control.
228 */
229 const BOX_SHADOW = 'box_shadow';
230
231 /**
232 * Text shadow control.
233 */
234 const TEXT_SHADOW = 'text_shadow';
235
236 /**
237 * Entrance animation control.
238 */
239 const ANIMATION = 'animation';
240
241 /**
242 * Hover animation control.
243 */
244 const HOVER_ANIMATION = 'hover_animation';
245
246 /**
247 * Exit animation control.
248 */
249 const EXIT_ANIMATION = 'exit_animation';
250
251 /**
252 * Gaps control.
253 */
254 const GAPS = 'gaps';
255
256 /**
257 * Controls.
258 *
259 * Holds the list of all the controls. Default is `null`.
260 *
261 * @since 1.0.0
262 * @access private
263 *
264 * @var Base_Control[]
265 */
266 private $controls = null;
267
268 /**
269 * Control groups.
270 *
271 * Holds the list of all the control groups. Default is an empty array.
272 *
273 * @since 1.0.0
274 * @access private
275 *
276 * @var Group_Control_Base[]
277 */
278 private $control_groups = [];
279
280 /**
281 * Control stacks.
282 *
283 * Holds the list of all the control stacks. Default is an empty array.
284 *
285 * @since 1.0.0
286 * @access private
287 *
288 * @var array
289 */
290 private $stacks = [];
291
292 /**
293 * Tabs.
294 *
295 * Holds the list of all the tabs.
296 *
297 * @since 1.0.0
298 * @access private
299 * @static
300 *
301 * @var array
302 */
303 private static $tabs;
304
305 /**
306 * Has stacks cache been cleared.
307 *
308 * Boolean flag used to determine whether the controls manager stack cache has been cleared once during the current runtime.
309 *
310 * @since 3.13.0
311 * @access private
312 * @static
313 *
314 * @var array
315 */
316 private $has_stacks_cache_been_cleared = false;
317
318 /**
319 * Init tabs.
320 *
321 * Initialize control tabs.
322 *
323 * @since 1.6.0
324 * @access private
325 * @static
326 */
327 private static function init_tabs() {
328 self::$tabs = [
329 self::TAB_CONTENT => esc_html__( 'Content', 'elementor' ),
330 self::TAB_STYLE => esc_html__( 'Style', 'elementor' ),
331 self::TAB_ADVANCED => esc_html__( 'Advanced', 'elementor' ),
332 self::TAB_RESPONSIVE => esc_html__( 'Responsive', 'elementor' ),
333 self::TAB_LAYOUT => esc_html__( 'Layout', 'elementor' ),
334 self::TAB_SETTINGS => esc_html__( 'Settings', 'elementor' ),
335 ];
336 }
337
338 /**
339 * Get tabs.
340 *
341 * Retrieve the tabs of the current control.
342 *
343 * @since 1.6.0
344 * @access public
345 * @static
346 *
347 * @return array Control tabs.
348 */
349 public static function get_tabs() {
350 if ( ! self::$tabs ) {
351 self::init_tabs();
352 }
353
354 return self::$tabs;
355 }
356
357 /**
358 * Add tab.
359 *
360 * This method adds a new tab to the current control.
361 *
362 * @since 1.6.0
363 * @access public
364 * @static
365 *
366 * @param string $tab_name Tab name.
367 * @param string $tab_label Tab label.
368 */
369 public static function add_tab( $tab_name, $tab_label = '' ) {
370 if ( ! self::$tabs ) {
371 self::init_tabs();
372 }
373
374 if ( isset( self::$tabs[ $tab_name ] ) ) {
375 return;
376 }
377
378 self::$tabs[ $tab_name ] = $tab_label;
379 }
380
381 public static function get_groups_names() {
382 // Group name must use "-" instead of "_"
383 return [
384 'background',
385 'border',
386 'typography',
387 'image-size',
388 'box-shadow',
389 'css-filter',
390 'text-shadow',
391 'flex-container',
392 'grid-container',
393 'flex-item',
394 'text-stroke',
395 ];
396 }
397
398 public static function get_controls_names() {
399 return [
400 self::TEXT,
401 self::NUMBER,
402 self::TEXTAREA,
403 self::SELECT,
404 self::SWITCHER,
405
406 self::BUTTON,
407 self::HIDDEN,
408 self::HEADING,
409 self::RAW_HTML,
410 self::POPOVER_TOGGLE,
411 self::SECTION,
412 self::TAB,
413 self::TABS,
414 self::DIVIDER,
415 self::DEPRECATED_NOTICE,
416 self::ALERT,
417 self::NOTICE,
418
419 self::COLOR,
420 self::MEDIA,
421 self::SLIDER,
422 self::DIMENSIONS,
423 self::CHOOSE,
424 self::WYSIWYG,
425 self::CODE,
426 self::FONT,
427 self::IMAGE_DIMENSIONS,
428 self::GAPS,
429
430 self::WP_WIDGET,
431
432 self::URL,
433 self::REPEATER,
434 self::ICON,
435 self::ICONS,
436 self::GALLERY,
437 self::STRUCTURE,
438 self::SELECT2,
439 self::DATE_TIME,
440 self::BOX_SHADOW,
441 self::TEXT_SHADOW,
442 self::ANIMATION,
443 self::HOVER_ANIMATION,
444 self::EXIT_ANIMATION,
445 ];
446 }
447
448 /**
449 * Register controls.
450 *
451 * This method creates a list of all the supported controls by requiring the
452 * control files and initializing each one of them.
453 *
454 * The list of supported controls includes the regular controls and the group
455 * controls.
456 *
457 * External developers can register new controls by hooking to the
458 * `elementor/controls/controls_registered` action.
459 *
460 * @since 3.1.0
461 * @access private
462 */
463 private function register_controls() {
464 $this->controls = [];
465
466 foreach ( self::get_controls_names() as $control_id ) {
467 $control_class_id = str_replace( ' ', '_', ucwords( str_replace( '_', ' ', $control_id ) ) );
468 $class_name = __NAMESPACE__ . '\Control_' . $control_class_id;
469
470 $this->register( new $class_name() );
471 }
472
473 // Group Controls
474 foreach ( self::get_groups_names() as $group_name ) {
475 $group_class_id = str_replace( ' ', '_', ucwords( str_replace( '-', ' ', $group_name ) ) );
476 $class_name = __NAMESPACE__ . '\Group_Control_' . $group_class_id;
477
478 $this->control_groups[ $group_name ] = new $class_name();
479 }
480
481 /**
482 * After controls registered.
483 *
484 * Fires after Elementor controls are registered.
485 *
486 * @since 1.0.0
487 * @deprecated 3.5.0 Use `elementor/controls/register` hook instead.
488 *
489 * @param Controls_Manager $this The controls manager.
490 */
491 // TODO: Uncomment when Pro uses the new hook.
492 //Plugin::$instance->modules_manager->get_modules( 'dev-tools' )->deprecation->do_deprecated_action(
493 // 'elementor/controls/controls_registered',
494 // [ $this ],
495 // '3.5.0',
496 // 'elementor/controls/register'
497 //);
498
499 do_action( 'elementor/controls/controls_registered', $this );
500
501 /**
502 * After controls registered.
503 *
504 * Fires after Elementor controls are registered.
505 *
506 * @since 3.5.0
507 *
508 * @param Controls_Manager $this The controls manager.
509 */
510 do_action( 'elementor/controls/register', $this );
511 }
512
513 /**
514 * Register control.
515 *
516 * This method adds a new control to the controls list. It adds any given
517 * control to any given control instance.
518 *
519 * @since 1.0.0
520 * @access public
521 * @deprecated 3.5.0 Use `register()` method instead.
522 *
523 * @param string $control_id Control ID.
524 * @param Base_Control $control_instance Control instance, usually the
525 * current instance.
526 */
527 public function register_control( $control_id, Base_Control $control_instance ) {
528 // TODO: Uncomment when Pro uses the new hook.
529 //Plugin::$instance->modules_manager->get_modules( 'dev-tools' )->deprecation->deprecated_function(
530 // __METHOD__,
531 // '3.5.0',
532 // 'register()'
533 //);
534
535 $this->register( $control_instance, $control_id );
536 }
537
538 /**
539 * Register control.
540 *
541 * This method adds a new control to the controls list. It adds any given
542 * control to any given control instance.
543 *
544 * @since 3.5.0
545 * @access public
546 *
547 * @param Base_Control $control_instance Control instance, usually the current instance.
548 * @param string $control_id Control ID. Deprecated parameter.
549 *
550 * @return void
551 */
552 public function register( Base_Control $control_instance, $control_id = null ) {
553
554 // TODO: For BC. Remove in the future.
555 if ( $control_id ) {
556 Plugin::instance()->modules_manager->get_modules( 'dev-tools' )->deprecation->deprecated_argument(
557 '$control_id', '3.5.0'
558 );
559 } else {
560 $control_id = $control_instance->get_type();
561 }
562
563 $this->controls[ $control_id ] = $control_instance;
564 }
565
566 /**
567 * Unregister control.
568 *
569 * This method removes control from the controls list.
570 *
571 * @since 1.0.0
572 * @access public
573 * @deprecated 3.5.0 Use `unregister()` method instead.
574 *
575 * @param string $control_id Control ID.
576 *
577 * @return bool True if the control was removed, False otherwise.
578 */
579 public function unregister_control( $control_id ) {
580 Plugin::$instance->modules_manager->get_modules( 'dev-tools' )->deprecation->deprecated_function(
581 __METHOD__,
582 '3.5.0',
583 'unregister()'
584 );
585
586 return $this->unregister( $control_id );
587 }
588
589 /**
590 * Unregister control.
591 *
592 * This method removes control from the controls list.
593 *
594 * @since 3.5.0
595 * @access public
596 *
597 * @param string $control_id Control ID.
598 *
599 * @return bool Whether the controls has been unregistered.
600 */
601 public function unregister( $control_id ) {
602 if ( ! isset( $this->controls[ $control_id ] ) ) {
603 return false;
604 }
605
606 unset( $this->controls[ $control_id ] );
607
608 return true;
609 }
610
611 /**
612 * Get controls.
613 *
614 * Retrieve the controls list from the current instance.
615 *
616 * @since 1.0.0
617 * @access public
618 *
619 * @return Base_Control[] Controls list.
620 */
621 public function get_controls() {
622 if ( null === $this->controls ) {
623 $this->register_controls();
624 }
625
626 return $this->controls;
627 }
628
629 /**
630 * Get control.
631 *
632 * Retrieve a specific control from the current controls instance.
633 *
634 * @since 1.0.0
635 * @access public
636 *
637 * @param string $control_id Control ID.
638 *
639 * @return bool|Base_Control Control instance, or False otherwise.
640 */
641 public function get_control( $control_id ) {
642 $controls = $this->get_controls();
643
644 return isset( $controls[ $control_id ] ) ? $controls[ $control_id ] : false;
645 }
646
647 /**
648 * Get controls data.
649 *
650 * Retrieve all the registered controls and all the data for each control.
651 *
652 * @since 1.0.0
653 * @access public
654 *
655 * @return array {
656 * Control data.
657 *
658 * @type array $name Control data.
659 * }
660 */
661 public function get_controls_data() {
662 $controls_data = [];
663
664 foreach ( $this->get_controls() as $name => $control ) {
665 $controls_data[ $name ] = $control->get_settings();
666 }
667
668 return $controls_data;
669 }
670
671 /**
672 * Render controls.
673 *
674 * Generate the final HTML for all the registered controls using the element
675 * template.
676 *
677 * @since 1.0.0
678 * @access public
679 */
680 public function render_controls() {
681 foreach ( $this->get_controls() as $control ) {
682 $control->print_template();
683 }
684 }
685
686 /**
687 * Get control groups.
688 *
689 * Retrieve a specific group for a given ID, or a list of all the control
690 * groups.
691 *
692 * If the given group ID is wrong, it will return `null`. When the ID valid,
693 * it will return the group control instance. When no ID was given, it will
694 * return all the control groups.
695 *
696 * @since 1.0.10
697 * @access public
698 *
699 * @param string $id Optional. Group ID. Default is null.
700 *
701 * @return null|Group_Control_Base|Group_Control_Base[]
702 */
703 public function get_control_groups( $id = null ) {
704 if ( $id ) {
705 return isset( $this->control_groups[ $id ] ) ? $this->control_groups[ $id ] : null;
706 }
707
708 return $this->control_groups;
709 }
710
711 /**
712 * Add group control.
713 *
714 * This method adds a new group control to the control groups list. It adds
715 * any given group control to any given group control instance.
716 *
717 * @since 1.0.0
718 * @access public
719 *
720 * @param string $id Group control ID.
721 * @param Group_Control_Base $instance Group control instance, usually the
722 * current instance.
723 *
724 * @return Group_Control_Base Group control instance.
725 */
726 public function add_group_control( $id, $instance ) {
727 $this->control_groups[ $id ] = $instance;
728
729 return $instance;
730 }
731
732 /**
733 * Enqueue control scripts and styles.
734 *
735 * Used to register and enqueue custom scripts and styles used by the control.
736 *
737 * @since 1.0.0
738 * @access public
739 */
740 public function enqueue_control_scripts() {
741 foreach ( $this->get_controls() as $control ) {
742 $control->enqueue();
743 }
744 }
745
746 /**
747 * Open new stack.
748 *
749 * This method adds a new stack to the control stacks list. It adds any
750 * given stack to the current control instance.
751 *
752 * @since 1.0.0
753 * @access public
754 *
755 * @param Controls_Stack $controls_stack Controls stack.
756 */
757 public function open_stack( Controls_Stack $controls_stack ) {
758 $stack_id = $controls_stack->get_unique_name();
759
760 $this->stacks[ $stack_id ] = [
761 'tabs' => [],
762 'controls' => [],
763 'style_controls' => [],
764 'responsive_control_duplication_mode' => Plugin::$instance->breakpoints->get_responsive_control_duplication_mode(),
765 ];
766 }
767
768 /**
769 * Remove existing stack from the stacks cache
770 *
771 * Removes the stack of a passed instance from the Controls Manager's stacks cache.
772 *
773 * @param Controls_Stack $controls_stack
774 * @return void
775 */
776 public function delete_stack( Controls_Stack $controls_stack ) {
777 $stack_id = $controls_stack->get_unique_name();
778
779 unset( $this->stacks[ $stack_id ] );
780 }
781
782 /**
783 * Add control to stack.
784 *
785 * This method adds a new control to the stack.
786 *
787 * @since 1.0.0
788 * @access public
789 *
790 * @param Controls_Stack $element Element stack.
791 * @param string $control_id Control ID.
792 * @param array $control_data Control data.
793 * @param array $options Optional. Control additional options.
794 * Default is an empty array.
795 *
796 * @return bool True if control added, False otherwise.
797 */
798 public function add_control_to_stack( Controls_Stack $element, $control_id, $control_data, $options = [] ) {
799 $default_options = [
800 'overwrite' => false,
801 'index' => null,
802 ];
803
804 $control_type = 'controls';
805 if (
806 Performance::is_optimized_control_loading_feature_enabled()
807 && Performance::should_optimize_controls()
808 && $this->is_style_control( $control_data )
809 ) {
810 $control_type = 'style_controls';
811 }
812
813 $options = array_merge( $default_options, $options );
814
815 $default_args = [
816 'type' => self::TEXT,
817 'tab' => self::TAB_CONTENT,
818 ];
819
820 $control_data['name'] = $control_id;
821
822 $control_data = array_merge( $default_args, $control_data );
823
824 $control_type_instance = $this->get_control( $control_data['type'] );
825
826 if ( ! $control_type_instance ) {
827 _doing_it_wrong( sprintf( '%1$s::%2$s', __CLASS__, __FUNCTION__ ), sprintf( 'Control type "%s" not found.', esc_html( $control_data['type'] ) ), '1.0.0' );
828 return false;
829 }
830
831 if ( $control_type_instance instanceof Base_Data_Control ) {
832 $control_default_value = $control_type_instance->get_default_value();
833
834 if ( is_array( $control_default_value ) ) {
835 $control_data['default'] = isset( $control_data['default'] ) ? array_merge( $control_default_value, $control_data['default'] ) : $control_default_value;
836 } else {
837 $control_data['default'] = isset( $control_data['default'] ) ? $control_data['default'] : $control_default_value;
838 }
839 }
840
841 $stack_id = $element->get_unique_name();
842
843 if ( ! $options['overwrite'] && isset( $this->stacks[ $stack_id ][ $control_type ][ $control_id ] ) ) {
844 _doing_it_wrong( sprintf( '%1$s::%2$s', __CLASS__, __FUNCTION__ ), sprintf( 'Cannot redeclare control with same name "%s".', esc_html( $control_id ) ), '1.0.0' );
845
846 return false;
847 }
848
849 $tabs = self::get_tabs();
850
851 if ( ! isset( $tabs[ $control_data['tab'] ] ) ) {
852 $control_data['tab'] = $default_args['tab'];
853 }
854
855 $this->stacks[ $stack_id ]['tabs'][ $control_data['tab'] ] = $tabs[ $control_data['tab'] ];
856
857 $this->stacks[ $stack_id ][ $control_type ][ $control_id ] = $control_data;
858
859 if ( null !== $options['index'] ) {
860 $controls = $this->stacks[ $stack_id ][ $control_type ];
861
862 $controls_keys = array_keys( $controls );
863
864 array_splice( $controls_keys, $options['index'], 0, $control_id );
865
866 $this->stacks[ $stack_id ][ $control_type ] = array_merge( array_flip( $controls_keys ), $controls );
867 }
868
869 return true;
870 }
871
872 /**
873 * Remove control from stack.
874 *
875 * This method removes a control a the stack.
876 *
877 * @since 1.0.0
878 * @access public
879 *
880 * @param string $stack_id Stack ID.
881 * @param array|string $control_id The ID of the control to remove.
882 *
883 * @return bool|\WP_Error True if the stack was removed, False otherwise.
884 */
885 public function remove_control_from_stack( $stack_id, $control_id ) {
886 if ( is_array( $control_id ) ) {
887 foreach ( $control_id as $id ) {
888 $this->remove_control_from_stack( $stack_id, $id );
889 }
890
891 return true;
892 }
893
894 if ( empty( $this->stacks[ $stack_id ]['controls'][ $control_id ] ) ) {
895 return new \WP_Error( 'Cannot remove not-exists control.' );
896 }
897
898 unset( $this->stacks[ $stack_id ]['controls'][ $control_id ] );
899
900 return true;
901 }
902
903 /**
904 * Has Stacks Cache Been Cleared.
905 * @since 3.13.0
906 * @access public
907 * @return bool True if the CSS requires to clear the controls stack cache, False otherwise.
908 */
909 public function has_stacks_cache_been_cleared() {
910 return $this->has_stacks_cache_been_cleared;
911 }
912
913 /**
914 * Clear stack.
915 * This method clears the stack.
916 * @since 3.13.0
917 * @access public
918 */
919 public function clear_stack_cache() {
920 $this->stacks = [];
921 $this->has_stacks_cache_been_cleared = true;
922 }
923
924 /**
925 * Get control from stack.
926 *
927 * Retrieve a specific control for a given a specific stack.
928 *
929 * If the given control does not exist in the stack, or the stack does not
930 * exist, it will return `WP_Error`. Otherwise, it will retrieve the control
931 * from the stack.
932 *
933 * @since 1.1.0
934 * @access public
935 *
936 * @param string $stack_id Stack ID.
937 * @param string $control_id Control ID.
938 *
939 * @return array|\WP_Error The control, or an error.
940 */
941 public function get_control_from_stack( $stack_id, $control_id ) {
942 $stack_data = $this->get_stacks( $stack_id );
943
944 if ( ! empty( $stack_data['controls'][ $control_id ] ) ) {
945 return $stack_data['controls'][ $control_id ];
946 }
947
948 if ( ! empty( $stack_data['style_controls'][ $control_id ] ) ) {
949 return $stack_data['style_controls'][ $control_id ];
950 }
951
952 return new \WP_Error( 'Cannot get a not-exists control.' );
953 }
954
955 /**
956 * Update control in stack.
957 *
958 * This method updates the control data for a given stack.
959 *
960 * @since 1.1.0
961 * @access public
962 *
963 * @param Controls_Stack $element Element stack.
964 * @param string $control_id Control ID.
965 * @param array $control_data Control data.
966 * @param array $options Optional. Control additional options.
967 * Default is an empty array.
968 *
969 * @return bool True if control updated, False otherwise.
970 */
971 public function update_control_in_stack( Controls_Stack $element, $control_id, $control_data, array $options = [] ) {
972 $old_control_data = $this->get_control_from_stack( $element->get_unique_name(), $control_id );
973
974 if ( is_wp_error( $old_control_data ) ) {
975 return false;
976 }
977
978 if ( ! empty( $options['recursive'] ) ) {
979 $control_data = array_replace_recursive( $old_control_data, $control_data );
980 } else {
981 $control_data = array_merge( $old_control_data, $control_data );
982 }
983
984 return $this->add_control_to_stack( $element, $control_id, $control_data, [
985 'overwrite' => true,
986 ] );
987 }
988
989 /**
990 * Get stacks.
991 *
992 * Retrieve a specific stack for the list of stacks.
993 *
994 * If the given stack is wrong, it will return `null`. When the stack valid,
995 * it will return the the specific stack. When no stack was given, it will
996 * return all the stacks.
997 *
998 * @since 1.7.1
999 * @access public
1000 *
1001 * @param string $stack_id Optional. stack ID. Default is null.
1002 *
1003 * @return null|array A list of stacks.
1004 */
1005 public function get_stacks( $stack_id = null ) {
1006 if ( $stack_id ) {
1007 if ( isset( $this->stacks[ $stack_id ] ) ) {
1008 return $this->stacks[ $stack_id ];
1009 }
1010
1011 return null;
1012 }
1013
1014 return $this->stacks;
1015 }
1016
1017 /**
1018 * Get element stack.
1019 *
1020 * Retrieve a specific stack for the list of stacks from the current instance.
1021 *
1022 * @since 1.0.0
1023 * @access public
1024 *
1025 * @param Controls_Stack $controls_stack Controls stack.
1026 *
1027 * @return null|array Stack data if it exists, `null` otherwise.
1028 */
1029 public function get_element_stack( Controls_Stack $controls_stack ) {
1030 $stack_id = $controls_stack->get_unique_name();
1031
1032 if ( ! isset( $this->stacks[ $stack_id ] ) ) {
1033 return null;
1034 }
1035
1036 if ( $this->should_clean_stack( $this->stacks[ $stack_id ] ) ) {
1037 $this->delete_stack( $controls_stack );
1038 return null;
1039 }
1040
1041 return $this->stacks[ $stack_id ];
1042 }
1043
1044 /**
1045 * Add custom CSS controls.
1046 *
1047 * This method adds a new control for the "Custom CSS" feature. The free
1048 * version of elementor uses this method to display an upgrade message to
1049 * Elementor Pro.
1050 *
1051 * @since 1.0.0
1052 * @access public
1053 *
1054 * @param Controls_Stack $controls_stack .
1055 * @param string $tab
1056 * @param array $additional_messages
1057 *
1058 */
1059 public function add_custom_css_controls( Controls_Stack $controls_stack, $tab = self::TAB_ADVANCED, $additional_messages = [] ) {
1060 $controls_stack->start_controls_section(
1061 'section_custom_css_pro',
1062 [
1063 'label' => esc_html__( 'Custom CSS', 'elementor' ),
1064 'tab' => $tab,
1065 ]
1066 );
1067
1068 $messages = [
1069 esc_html__( 'Custom CSS lets you add CSS code to any widget, and see it render live right in the editor.', 'elementor' ),
1070 ];
1071
1072 if ( $additional_messages ) {
1073 $messages = array_merge( $messages, $additional_messages );
1074 }
1075
1076 $controls_stack->add_control(
1077 'custom_css_pro',
1078 [
1079 'type' => self::RAW_HTML,
1080 'raw' => $this->get_teaser_template( [
1081 'title' => esc_html__( 'Meet Our Custom CSS', 'elementor' ),
1082 'messages' => $messages,
1083 'link' => 'https://go.elementor.com/go-pro-custom-css/',
1084 ] ),
1085 ]
1086 );
1087
1088 $controls_stack->end_controls_section();
1089 }
1090
1091 /**
1092 * Add Page Transitions controls.
1093 *
1094 * This method adds a new control for the "Page Transitions" feature. The Core
1095 * version of elementor uses this method to display an upgrade message to
1096 * Elementor Pro.
1097 *
1098 * @param Controls_Stack $controls_stack .
1099 * @param string $tab
1100 * @param array $additional_messages
1101 *
1102 * @return void
1103 */
1104 public function add_page_transitions_controls( Controls_Stack $controls_stack, $tab = self::TAB_ADVANCED, $additional_messages = [] ) {
1105 $controls_stack->start_controls_section(
1106 'section_page_transitions_teaser',
1107 [
1108 'label' => esc_html__( 'Page Transitions', 'elementor' ),
1109 'tab' => $tab,
1110 ]
1111 );
1112
1113 $messages = [
1114 esc_html__( 'Page Transitions let you style entrance and exit animations between pages as well as display loader until your page assets load.', 'elementor' ),
1115 ];
1116
1117 if ( $additional_messages ) {
1118 $messages = array_merge( $messages, $additional_messages );
1119 }
1120
1121 $controls_stack->add_control(
1122 'page_transitions_teaser',
1123 [
1124 'type' => self::RAW_HTML,
1125 'raw' => $this->get_teaser_template( [
1126 'title' => esc_html__( 'Meet Page Transitions', 'elementor' ),
1127 'messages' => $messages,
1128 'link' => 'https://go.elementor.com/go-pro-page-transitions/',
1129 ] ),
1130 ]
1131 );
1132
1133 $controls_stack->end_controls_section();
1134 }
1135
1136 public function get_teaser_template( $texts ) {
1137 ob_start();
1138 ?>
1139 <div class="elementor-nerd-box">
1140 <img class="elementor-nerd-box-icon" src="<?php echo esc_url( ELEMENTOR_ASSETS_URL . 'images/go-pro.svg' ); ?>" loading="lazy" alt="<?php echo esc_attr__( 'Upgrade', 'elementor' ); ?>" />
1141 <div class="elementor-nerd-box-title"><?php Utils::print_unescaped_internal_string( $texts['title'] ); ?></div>
1142 <?php foreach ( $texts['messages'] as $message ) { ?>
1143 <div class="elementor-nerd-box-message"><?php Utils::print_unescaped_internal_string( $message ); ?></div>
1144 <?php }
1145
1146 // Show the upgrade button only if the user doesn't have Pro.
1147 if ( $texts['link'] && ! Utils::has_pro() ) { ?>
1148 <a class="elementor-button go-pro" href="<?php echo esc_url( ( $texts['link'] ) ); ?>" target="_blank">
1149 <?php echo esc_html__( 'Upgrade Now', 'elementor' ); ?>
1150 </a>
1151 <?php } ?>
1152 </div>
1153 <?php
1154
1155 return ob_get_clean();
1156 }
1157
1158 /**
1159 * Get Responsive Control Device Suffix
1160 *
1161 * @param array $control
1162 * @return string $device suffix
1163 */
1164 public static function get_responsive_control_device_suffix( array $control ): string {
1165 if ( ! empty( $control['responsive']['max'] ) ) {
1166 $query_device = $control['responsive']['max'];
1167 } elseif ( ! empty( $control['responsive']['min'] ) ) {
1168 $query_device = $control['responsive']['min'];
1169 } else {
1170 return '';
1171 }
1172
1173 return 'desktop' === $query_device ? '' : '_' . $query_device;
1174 }
1175
1176 /**
1177 * Add custom attributes controls.
1178 *
1179 * This method adds a new control for the "Custom Attributes" feature. The free
1180 * version of elementor uses this method to display an upgrade message to
1181 * Elementor Pro.
1182 *
1183 * @since 2.8.3
1184 * @access public
1185 *
1186 * @param Controls_Stack $controls_stack.
1187 */
1188 public function add_custom_attributes_controls( Controls_Stack $controls_stack, string $tab = self::TAB_ADVANCED ) {
1189 $controls_stack->start_controls_section(
1190 'section_custom_attributes_pro',
1191 [
1192 'label' => esc_html__( 'Attributes', 'elementor' ),
1193 'tab' => $tab,
1194 ]
1195 );
1196
1197 $controls_stack->add_control(
1198 'custom_attributes_pro',
1199 [
1200 'type' => self::RAW_HTML,
1201 'raw' => $this->get_teaser_template( [
1202 'title' => esc_html__( 'Meet Our Attributes', 'elementor' ),
1203 'messages' => [
1204 esc_html__( 'Attributes lets you add custom HTML attributes to any element.', 'elementor' ),
1205 ],
1206 'link' => 'https://go.elementor.com/go-pro-custom-attributes/',
1207 ] ),
1208 ]
1209 );
1210
1211 $controls_stack->end_controls_section();
1212 }
1213
1214 /**
1215 * Check if a stack should be cleaned by the current responsive control duplication mode.
1216 *
1217 * @param $stack
1218 * @return bool
1219 */
1220 private function should_clean_stack( $stack ): bool {
1221 if ( ! isset( $stack['responsive_control_duplication_mode'] ) ) {
1222 return false;
1223 }
1224
1225 $stack_duplication_mode = $stack['responsive_control_duplication_mode'];
1226
1227 // This array provides a convenient way to map human-readable mode names to numeric values for comparison.
1228 // If the current stack's mode is greater than or equal to the current mode, then we shouldn't clean the stack.
1229 $modes = [
1230 'off' => 1,
1231 'dynamic' => 2,
1232 'on' => 3,
1233 ];
1234
1235 if ( ! isset( $modes[ $stack_duplication_mode ] ) ) {
1236 return false;
1237 }
1238
1239 $current_duplication_mode = Plugin::$instance->breakpoints->get_responsive_control_duplication_mode();
1240
1241 if ( $modes[ $stack_duplication_mode ] >= $modes[ $current_duplication_mode ] ) {
1242 return false;
1243 }
1244
1245 return true;
1246 }
1247
1248 public function add_display_conditions_controls( Controls_Stack $controls_stack ) {
1249 if ( Utils::has_pro() ) {
1250 return;
1251 }
1252
1253 ob_start();
1254 ?>
1255 <div class="e-control-display-conditions-promotion__wrapper">
1256 <div class="e-control-display-conditions-promotion__description">
1257 <span class="e-control-display-conditions-promotion__text">
1258 <?php echo esc_html__( 'Display Conditions', 'elementor' ); ?>
1259 </span>
1260 <span class="e-control-display-conditions-promotion__lock-wrapper">
1261 <i class="eicon-lock e-control-display-conditions-promotion"></i>
1262 </span>
1263 </div>
1264 <i class="eicon-flow e-control-display-conditions-promotion"></i>
1265 </div>
1266 <?php
1267 $control_template = ob_get_clean();
1268
1269 $controls_stack->add_control(
1270 'display_conditions_pro',
1271 [
1272 'type' => self::RAW_HTML,
1273 'separator' => 'before',
1274 'raw' => $control_template,
1275 ]
1276 );
1277 }
1278
1279 public function add_motion_effects_promotion_control( Controls_Stack $controls_stack ) {
1280 if ( Utils::has_pro() ) {
1281 return;
1282 }
1283
1284 $controls_stack->add_control(
1285 'scrolling_effects_pro',
1286 [
1287 'type' => self::RAW_HTML,
1288 'separator' => 'before',
1289 'raw' => $this->promotion_switcher_control( esc_html__( 'Scrolling Effects', 'elementor' ), 'scrolling-effects' ),
1290 ]
1291 );
1292
1293 $controls_stack->add_control(
1294 'mouse_effects_pro',
1295 [
1296 'type' => self::RAW_HTML,
1297 'separator' => 'before',
1298 'raw' => $this->promotion_switcher_control( esc_html__( 'Mouse Effects', 'elementor' ), 'mouse-effects' ),
1299 ]
1300 );
1301
1302 $controls_stack->add_control(
1303 'sticky_pro',
1304 [
1305 'type' => self::RAW_HTML,
1306 'separator' => 'before',
1307 'raw' => $this->promotion_select_control( esc_html__( 'Sticky', 'elementor' ), 'sticky-effects' ),
1308 ]
1309 );
1310
1311 $controls_stack->add_control(
1312 'motion_effects_promotion_divider',
1313 [
1314 'type' => self::DIVIDER,
1315 ]
1316 );
1317 }
1318
1319 private function promotion_switcher_control( $title, $id ): string {
1320 return '<div class="elementor-control-type-switcher elementor-label-inline e-control-motion-effects-promotion__wrapper">
1321 <div class="elementor-control-content">
1322 <div class="elementor-control-field">
1323 <label>
1324 ' . $title . '
1325 </label>
1326 <span class="e-control-motion-effects-promotion__lock-wrapper">
1327 <i class="eicon-lock"></i>
1328 </span>
1329 <div class="elementor-control-input-wrapper">
1330 <label class="elementor-switch elementor-control-unit-2 e-control-' . $id . '-promotion">
1331 <input type="checkbox" class="elementor-switch-input" disabled>
1332 <span class="elementor-switch-label" data-off="Off"></span>
1333 <span class="elementor-switch-handle"></span>
1334 </label>
1335 </div>
1336 </div>
1337 </div>
1338 </div>';
1339 }
1340
1341 private function promotion_select_control( $title, $id ): string {
1342 return '<div class="elementor-control-type-select elementor-label-inline e-control-motion-effects-promotion__wrapper">
1343 <div class="elementor-control-content">
1344 <div class="elementor-control-field ">
1345 <label for="sticky-motion-effect-pro">
1346 ' . $title . '
1347 </label>
1348 <span class="e-control-motion-effects-promotion__lock-wrapper">
1349 <i class="eicon-lock"></i>
1350 </span>
1351 <div class="elementor-control-input-wrapper elementor-control-unit-5 e-control-' . $id . '-promotion">
1352 <div class="select-promotion elementor-control-unit-5">None</div>
1353 </div>
1354 </div>
1355 </div>
1356 </div>';
1357 }
1358
1359 private function is_style_control( $control_data ): bool {
1360 $frontend_available = $control_data['frontend_available'] ?? false;
1361 if ( $frontend_available ) {
1362 return false;
1363 }
1364
1365 if ( ! empty( $control_data['control_type'] ) && 'content' === $control_data['control_type'] ) {
1366 return false;
1367 }
1368
1369 if ( ! empty( $control_data['prefix_class'] ) ) {
1370 return false;
1371 }
1372
1373 $render_type = $control_data['render_type'] ?? '';
1374 if ( 'template' === $render_type ) {
1375 return false;
1376 }
1377
1378 if ( 'ui' === $render_type ) {
1379 return true;
1380 }
1381
1382 if ( ! empty( $control_data['selectors'] ) ) {
1383 return true;
1384 }
1385
1386 return false;
1387 }
1388 }
1389