PluginProbe
Elementor Website Builder – more than just a page builder / 3.14.0-beta4
Elementor Website Builder – more than just a page builder v3.14.0-beta4
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.14.0-beta4, at includes/managers/controls.php

1,178 lines 26.3 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 `$this->register()` 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 `$this->unregister()` 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 ];
750 }
751
752 /**
753 * Remove existing stack from the stacks cache
754 *
755 * Removes the stack of a passed instance from the Controls Manager's stacks cache.
756 *
757 * @param Controls_Stack $controls_stack
758 * @return void
759 */
760 public function delete_stack( Controls_Stack $controls_stack ) {
761 $stack_id = $controls_stack->get_unique_name();
762
763 unset( $this->stacks[ $stack_id ] );
764 }
765
766 /**
767 * Add control to stack.
768 *
769 * This method adds a new control to the stack.
770 *
771 * @since 1.0.0
772 * @access public
773 *
774 * @param Controls_Stack $element Element stack.
775 * @param string $control_id Control ID.
776 * @param array $control_data Control data.
777 * @param array $options Optional. Control additional options.
778 * Default is an empty array.
779 *
780 * @return bool True if control added, False otherwise.
781 */
782 public function add_control_to_stack( Controls_Stack $element, $control_id, $control_data, $options = [] ) {
783 $default_options = [
784 'overwrite' => false,
785 'index' => null,
786 ];
787
788 $options = array_merge( $default_options, $options );
789
790 $default_args = [
791 'type' => self::TEXT,
792 'tab' => self::TAB_CONTENT,
793 ];
794
795 $control_data['name'] = $control_id;
796
797 $control_data = array_merge( $default_args, $control_data );
798
799 $control_type_instance = $this->get_control( $control_data['type'] );
800
801 if ( ! $control_type_instance ) {
802 _doing_it_wrong( sprintf( '%1$s::%2$s', __CLASS__, __FUNCTION__ ), sprintf( 'Control type "%s" not found.', esc_html( $control_data['type'] ) ), '1.0.0' );
803 return false;
804 }
805
806 if ( $control_type_instance instanceof Base_Data_Control ) {
807 $control_default_value = $control_type_instance->get_default_value();
808
809 if ( is_array( $control_default_value ) ) {
810 $control_data['default'] = isset( $control_data['default'] ) ? array_merge( $control_default_value, $control_data['default'] ) : $control_default_value;
811 } else {
812 $control_data['default'] = isset( $control_data['default'] ) ? $control_data['default'] : $control_default_value;
813 }
814 }
815
816 $stack_id = $element->get_unique_name();
817
818 if ( ! $options['overwrite'] && isset( $this->stacks[ $stack_id ]['controls'][ $control_id ] ) ) {
819 _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' );
820
821 return false;
822 }
823
824 $tabs = self::get_tabs();
825
826 if ( ! isset( $tabs[ $control_data['tab'] ] ) ) {
827 $control_data['tab'] = $default_args['tab'];
828 }
829
830 $this->stacks[ $stack_id ]['tabs'][ $control_data['tab'] ] = $tabs[ $control_data['tab'] ];
831
832 $this->stacks[ $stack_id ]['controls'][ $control_id ] = $control_data;
833
834 if ( null !== $options['index'] ) {
835 $controls = $this->stacks[ $stack_id ]['controls'];
836
837 $controls_keys = array_keys( $controls );
838
839 array_splice( $controls_keys, $options['index'], 0, $control_id );
840
841 $this->stacks[ $stack_id ]['controls'] = array_merge( array_flip( $controls_keys ), $controls );
842 }
843
844 return true;
845 }
846
847 /**
848 * Remove control from stack.
849 *
850 * This method removes a control a the stack.
851 *
852 * @since 1.0.0
853 * @access public
854 *
855 * @param string $stack_id Stack ID.
856 * @param array|string $control_id The ID of the control to remove.
857 *
858 * @return bool|\WP_Error True if the stack was removed, False otherwise.
859 */
860 public function remove_control_from_stack( $stack_id, $control_id ) {
861 if ( is_array( $control_id ) ) {
862 foreach ( $control_id as $id ) {
863 $this->remove_control_from_stack( $stack_id, $id );
864 }
865
866 return true;
867 }
868
869 if ( empty( $this->stacks[ $stack_id ]['controls'][ $control_id ] ) ) {
870 return new \WP_Error( 'Cannot remove not-exists control.' );
871 }
872
873 unset( $this->stacks[ $stack_id ]['controls'][ $control_id ] );
874
875 return true;
876 }
877
878 /**
879 * Has Stacks Cache Been Cleared.
880 * @since 3.13.0
881 * @access public
882 * @return bool True if the CSS requires to clear the controls stack cache, False otherwise.
883 */
884 public function has_stacks_cache_been_cleared() {
885 return $this->has_stacks_cache_been_cleared;
886 }
887
888 /**
889 * Clear stack.
890 * This method clears the stack.
891 * @since 3.13.0
892 * @access public
893 */
894 public function clear_stack_cache() {
895 $this->stacks = [];
896 $this->has_stacks_cache_been_cleared = true;
897 }
898
899 /**
900 * Get control from stack.
901 *
902 * Retrieve a specific control for a given a specific stack.
903 *
904 * If the given control does not exist in the stack, or the stack does not
905 * exist, it will return `WP_Error`. Otherwise, it will retrieve the control
906 * from the stack.
907 *
908 * @since 1.1.0
909 * @access public
910 *
911 * @param string $stack_id Stack ID.
912 * @param string $control_id Control ID.
913 *
914 * @return array|\WP_Error The control, or an error.
915 */
916 public function get_control_from_stack( $stack_id, $control_id ) {
917 if ( empty( $this->stacks[ $stack_id ]['controls'][ $control_id ] ) ) {
918 return new \WP_Error( 'Cannot get a not-exists control.' );
919 }
920
921 return $this->stacks[ $stack_id ]['controls'][ $control_id ];
922 }
923
924 /**
925 * Update control in stack.
926 *
927 * This method updates the control data for a given stack.
928 *
929 * @since 1.1.0
930 * @access public
931 *
932 * @param Controls_Stack $element Element stack.
933 * @param string $control_id Control ID.
934 * @param array $control_data Control data.
935 * @param array $options Optional. Control additional options.
936 * Default is an empty array.
937 *
938 * @return bool True if control updated, False otherwise.
939 */
940 public function update_control_in_stack( Controls_Stack $element, $control_id, $control_data, array $options = [] ) {
941 $old_control_data = $this->get_control_from_stack( $element->get_unique_name(), $control_id );
942
943 if ( is_wp_error( $old_control_data ) ) {
944 return false;
945 }
946
947 if ( ! empty( $options['recursive'] ) ) {
948 $control_data = array_replace_recursive( $old_control_data, $control_data );
949 } else {
950 $control_data = array_merge( $old_control_data, $control_data );
951 }
952
953 return $this->add_control_to_stack( $element, $control_id, $control_data, [
954 'overwrite' => true,
955 ] );
956 }
957
958 /**
959 * Get stacks.
960 *
961 * Retrieve a specific stack for the list of stacks.
962 *
963 * If the given stack is wrong, it will return `null`. When the stack valid,
964 * it will return the the specific stack. When no stack was given, it will
965 * return all the stacks.
966 *
967 * @since 1.7.1
968 * @access public
969 *
970 * @param string $stack_id Optional. stack ID. Default is null.
971 *
972 * @return null|array A list of stacks.
973 */
974 public function get_stacks( $stack_id = null ) {
975 if ( $stack_id ) {
976 if ( isset( $this->stacks[ $stack_id ] ) ) {
977 return $this->stacks[ $stack_id ];
978 }
979
980 return null;
981 }
982
983 return $this->stacks;
984 }
985
986 /**
987 * Get element stack.
988 *
989 * Retrieve a specific stack for the list of stacks from the current instance.
990 *
991 * @since 1.0.0
992 * @access public
993 *
994 * @param Controls_Stack $controls_stack Controls stack.
995 *
996 * @return null|array Stack data if it exist, `null` otherwise.
997 */
998 public function get_element_stack( Controls_Stack $controls_stack ) {
999 $stack_id = $controls_stack->get_unique_name();
1000
1001 if ( ! isset( $this->stacks[ $stack_id ] ) ) {
1002 return null;
1003 }
1004
1005 return $this->stacks[ $stack_id ];
1006 }
1007
1008 /**
1009 * Add custom CSS controls.
1010 *
1011 * This method adds a new control for the "Custom CSS" feature. The free
1012 * version of elementor uses this method to display an upgrade message to
1013 * Elementor Pro.
1014 *
1015 * @since 1.0.0
1016 * @access public
1017 *
1018 * @param Controls_Stack $controls_stack .
1019 * @param string $tab
1020 * @param array $additional_messages
1021 *
1022 */
1023 public function add_custom_css_controls( Controls_Stack $controls_stack, $tab = self::TAB_ADVANCED, $additional_messages = [] ) {
1024 $controls_stack->start_controls_section(
1025 'section_custom_css_pro',
1026 [
1027 'label' => esc_html__( 'Custom CSS', 'elementor' ),
1028 'tab' => $tab,
1029 ]
1030 );
1031
1032 $messages = [
1033 esc_html__( 'Custom CSS lets you add CSS code to any widget, and see it render live right in the editor.', 'elementor' ),
1034 ];
1035
1036 if ( $additional_messages ) {
1037 $messages = array_merge( $messages, $additional_messages );
1038 }
1039
1040 $controls_stack->add_control(
1041 'custom_css_pro',
1042 [
1043 'type' => self::RAW_HTML,
1044 'raw' => $this->get_teaser_template( [
1045 'title' => esc_html__( 'Meet Our Custom CSS', 'elementor' ),
1046 'messages' => $messages,
1047 'link' => 'https://go.elementor.com/go-pro-custom-css/',
1048 ] ),
1049 ]
1050 );
1051
1052 $controls_stack->end_controls_section();
1053 }
1054
1055 /**
1056 * Add Page Transitions controls.
1057 *
1058 * This method adds a new control for the "Page Transitions" feature. The Core
1059 * version of elementor uses this method to display an upgrade message to
1060 * Elementor Pro.
1061 *
1062 * @param Controls_Stack $controls_stack .
1063 * @param string $tab
1064 * @param array $additional_messages
1065 *
1066 * @return void
1067 */
1068 public function add_page_transitions_controls( Controls_Stack $controls_stack, $tab = self::TAB_ADVANCED, $additional_messages = [] ) {
1069 $controls_stack->start_controls_section(
1070 'section_page_transitions_teaser',
1071 [
1072 'label' => esc_html__( 'Page Transitions', 'elementor' ),
1073 'tab' => $tab,
1074 ]
1075 );
1076
1077 $messages = [
1078 esc_html__( 'Page Transitions let you style entrance and exit animations between pages as well as display loader until your page assets load.', 'elementor' ),
1079 ];
1080
1081 if ( $additional_messages ) {
1082 $messages = array_merge( $messages, $additional_messages );
1083 }
1084
1085 $controls_stack->add_control(
1086 'page_transitions_teaser',
1087 [
1088 'type' => self::RAW_HTML,
1089 'raw' => $this->get_teaser_template( [
1090 'title' => esc_html__( 'Meet Page Transitions', 'elementor' ),
1091 'messages' => $messages,
1092 'link' => 'https://go.elementor.com/go-pro-page-transitions/',
1093 ] ),
1094 ]
1095 );
1096
1097 $controls_stack->end_controls_section();
1098 }
1099
1100 public function get_teaser_template( $texts ) {
1101 ob_start();
1102 ?>
1103 <div class="elementor-nerd-box">
1104 <img class="elementor-nerd-box-icon" src="<?php echo esc_url( ELEMENTOR_ASSETS_URL . 'images/go-pro.svg' ); ?>" loading="lazy" />
1105 <div class="elementor-nerd-box-title"><?php Utils::print_unescaped_internal_string( $texts['title'] ); ?></div>
1106 <?php foreach ( $texts['messages'] as $message ) { ?>
1107 <div class="elementor-nerd-box-message"><?php Utils::print_unescaped_internal_string( $message ); ?></div>
1108 <?php }
1109
1110 // Show the upgrade button only if the user doesn't have Pro.
1111 if ( $texts['link'] && ! Utils::has_pro() ) { ?>
1112 <a class="elementor-button go-pro" href="<?php echo esc_url( ( $texts['link'] ) ); ?>" target="_blank">
1113 <?php echo esc_html__( 'Upgrade Now', 'elementor' ); ?>
1114 </a>
1115 <?php } ?>
1116 </div>
1117 <?php
1118
1119 return ob_get_clean();
1120 }
1121
1122 /**
1123 * Get Responsive Control Device Suffix
1124 *
1125 * @param array $control
1126 * @return string $device suffix
1127 */
1128 public static function get_responsive_control_device_suffix( array $control ): string {
1129 if ( ! empty( $control['responsive']['max'] ) ) {
1130 $query_device = $control['responsive']['max'];
1131 } elseif ( ! empty( $control['responsive']['min'] ) ) {
1132 $query_device = $control['responsive']['min'];
1133 } else {
1134 return '';
1135 }
1136
1137 return 'desktop' === $query_device ? '' : '_' . $query_device;
1138 }
1139
1140 /**
1141 * Add custom attributes controls.
1142 *
1143 * This method adds a new control for the "Custom Attributes" feature. The free
1144 * version of elementor uses this method to display an upgrade message to
1145 * Elementor Pro.
1146 *
1147 * @since 2.8.3
1148 * @access public
1149 *
1150 * @param Controls_Stack $controls_stack.
1151 */
1152 public function add_custom_attributes_controls( Controls_Stack $controls_stack ) {
1153 $controls_stack->start_controls_section(
1154 'section_custom_attributes_pro',
1155 [
1156 'label' => esc_html__( 'Attributes', 'elementor' ),
1157 'tab' => self::TAB_ADVANCED,
1158 ]
1159 );
1160
1161 $controls_stack->add_control(
1162 'custom_attributes_pro',
1163 [
1164 'type' => self::RAW_HTML,
1165 'raw' => $this->get_teaser_template( [
1166 'title' => esc_html__( 'Meet Our Attributes', 'elementor' ),
1167 'messages' => [
1168 esc_html__( 'Attributes lets you add custom HTML attributes to any element.', 'elementor' ),
1169 ],
1170 'link' => 'https://go.elementor.com/go-pro-custom-attributes/',
1171 ] ),
1172 ]
1173 );
1174
1175 $controls_stack->end_controls_section();
1176 }
1177 }
1178