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

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

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