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

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

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