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

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