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

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