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

1,379 lines 31.9 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 (
806 Performance::is_optimized_control_loading_feature_enabled()
807 && Performance::should_optimize_controls()
808 && $this->is_style_control( $control_data )
809 ) {
810 $control_type = 'style_controls';
811 }
812
813 $options = array_merge( $default_options, $options );
814
815 $default_args = [
816 'type' => self::TEXT,
817 'tab' => self::TAB_CONTENT,
818 ];
819
820 $control_data['name'] = $control_id;
821
822 $control_data = array_merge( $default_args, $control_data );
823
824 $control_type_instance = $this->get_control( $control_data['type'] );
825
826 if ( ! $control_type_instance ) {
827 _doing_it_wrong( sprintf( '%1$s::%2$s', __CLASS__, __FUNCTION__ ), sprintf( 'Control type "%s" not found.', esc_html( $control_data['type'] ) ), '1.0.0' );
828 return false;
829 }
830
831 if ( $control_type_instance instanceof Base_Data_Control ) {
832 $control_default_value = $control_type_instance->get_default_value();
833
834 if ( is_array( $control_default_value ) ) {
835 $control_data['default'] = isset( $control_data['default'] ) ? array_merge( $control_default_value, $control_data['default'] ) : $control_default_value;
836 } else {
837 $control_data['default'] = isset( $control_data['default'] ) ? $control_data['default'] : $control_default_value;
838 }
839 }
840
841 $stack_id = $element->get_unique_name();
842
843 if ( ! $options['overwrite'] && isset( $this->stacks[ $stack_id ][ $control_type ][ $control_id ] ) ) {
844 _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' );
845
846 return false;
847 }
848
849 $tabs = self::get_tabs();
850
851 if ( ! isset( $tabs[ $control_data['tab'] ] ) ) {
852 $control_data['tab'] = $default_args['tab'];
853 }
854
855 $this->stacks[ $stack_id ]['tabs'][ $control_data['tab'] ] = $tabs[ $control_data['tab'] ];
856
857 $this->stacks[ $stack_id ][ $control_type ][ $control_id ] = $control_data;
858
859 if ( null !== $options['index'] ) {
860 $controls = $this->stacks[ $stack_id ][ $control_type ];
861
862 $controls_keys = array_keys( $controls );
863
864 array_splice( $controls_keys, $options['index'], 0, $control_id );
865
866 $this->stacks[ $stack_id ][ $control_type ] = array_merge( array_flip( $controls_keys ), $controls );
867 }
868
869 return true;
870 }
871
872 /**
873 * Remove control from stack.
874 *
875 * This method removes a control a the stack.
876 *
877 * @since 1.0.0
878 * @access public
879 *
880 * @param string $stack_id Stack ID.
881 * @param array|string $control_id The ID of the control to remove.
882 *
883 * @return bool|\WP_Error True if the stack was removed, False otherwise.
884 */
885 public function remove_control_from_stack( $stack_id, $control_id ) {
886 if ( is_array( $control_id ) ) {
887 foreach ( $control_id as $id ) {
888 $this->remove_control_from_stack( $stack_id, $id );
889 }
890
891 return true;
892 }
893
894 if ( empty( $this->stacks[ $stack_id ]['controls'][ $control_id ] ) ) {
895 return new \WP_Error( 'Cannot remove not-exists control.' );
896 }
897
898 unset( $this->stacks[ $stack_id ]['controls'][ $control_id ] );
899
900 return true;
901 }
902
903 /**
904 * Has Stacks Cache Been Cleared.
905 * @since 3.13.0
906 * @access public
907 * @return bool True if the CSS requires to clear the controls stack cache, False otherwise.
908 */
909 public function has_stacks_cache_been_cleared() {
910 return $this->has_stacks_cache_been_cleared;
911 }
912
913 /**
914 * Clear stack.
915 * This method clears the stack.
916 * @since 3.13.0
917 * @access public
918 */
919 public function clear_stack_cache() {
920 $this->stacks = [];
921 $this->has_stacks_cache_been_cleared = true;
922 }
923
924 /**
925 * Get control from stack.
926 *
927 * Retrieve a specific control for a given a specific stack.
928 *
929 * If the given control does not exist in the stack, or the stack does not
930 * exist, it will return `WP_Error`. Otherwise, it will retrieve the control
931 * from the stack.
932 *
933 * @since 1.1.0
934 * @access public
935 *
936 * @param string $stack_id Stack ID.
937 * @param string $control_id Control ID.
938 *
939 * @return array|\WP_Error The control, or an error.
940 */
941 public function get_control_from_stack( $stack_id, $control_id ) {
942 if ( empty( $this->stacks[ $stack_id ]['controls'][ $control_id ] ) ) {
943 return new \WP_Error( 'Cannot get a not-exists control.' );
944 }
945
946 return $this->stacks[ $stack_id ]['controls'][ $control_id ];
947 }
948
949 /**
950 * Update control in stack.
951 *
952 * This method updates the control data for a given stack.
953 *
954 * @since 1.1.0
955 * @access public
956 *
957 * @param Controls_Stack $element Element stack.
958 * @param string $control_id Control ID.
959 * @param array $control_data Control data.
960 * @param array $options Optional. Control additional options.
961 * Default is an empty array.
962 *
963 * @return bool True if control updated, False otherwise.
964 */
965 public function update_control_in_stack( Controls_Stack $element, $control_id, $control_data, array $options = [] ) {
966 $old_control_data = $this->get_control_from_stack( $element->get_unique_name(), $control_id );
967
968 if ( is_wp_error( $old_control_data ) ) {
969 return false;
970 }
971
972 if ( ! empty( $options['recursive'] ) ) {
973 $control_data = array_replace_recursive( $old_control_data, $control_data );
974 } else {
975 $control_data = array_merge( $old_control_data, $control_data );
976 }
977
978 return $this->add_control_to_stack( $element, $control_id, $control_data, [
979 'overwrite' => true,
980 ] );
981 }
982
983 /**
984 * Get stacks.
985 *
986 * Retrieve a specific stack for the list of stacks.
987 *
988 * If the given stack is wrong, it will return `null`. When the stack valid,
989 * it will return the the specific stack. When no stack was given, it will
990 * return all the stacks.
991 *
992 * @since 1.7.1
993 * @access public
994 *
995 * @param string $stack_id Optional. stack ID. Default is null.
996 *
997 * @return null|array A list of stacks.
998 */
999 public function get_stacks( $stack_id = null ) {
1000 if ( $stack_id ) {
1001 if ( isset( $this->stacks[ $stack_id ] ) ) {
1002 return $this->stacks[ $stack_id ];
1003 }
1004
1005 return null;
1006 }
1007
1008 return $this->stacks;
1009 }
1010
1011 /**
1012 * Get element stack.
1013 *
1014 * Retrieve a specific stack for the list of stacks from the current instance.
1015 *
1016 * @since 1.0.0
1017 * @access public
1018 *
1019 * @param Controls_Stack $controls_stack Controls stack.
1020 *
1021 * @return null|array Stack data if it exists, `null` otherwise.
1022 */
1023 public function get_element_stack( Controls_Stack $controls_stack ) {
1024 $stack_id = $controls_stack->get_unique_name();
1025
1026 if ( ! isset( $this->stacks[ $stack_id ] ) ) {
1027 return null;
1028 }
1029
1030 if ( $this->should_clean_stack( $this->stacks[ $stack_id ] ) ) {
1031 $this->delete_stack( $controls_stack );
1032 return null;
1033 }
1034
1035 return $this->stacks[ $stack_id ];
1036 }
1037
1038 /**
1039 * Add custom CSS controls.
1040 *
1041 * This method adds a new control for the "Custom CSS" feature. The free
1042 * version of elementor uses this method to display an upgrade message to
1043 * Elementor Pro.
1044 *
1045 * @since 1.0.0
1046 * @access public
1047 *
1048 * @param Controls_Stack $controls_stack .
1049 * @param string $tab
1050 * @param array $additional_messages
1051 *
1052 */
1053 public function add_custom_css_controls( Controls_Stack $controls_stack, $tab = self::TAB_ADVANCED, $additional_messages = [] ) {
1054 $controls_stack->start_controls_section(
1055 'section_custom_css_pro',
1056 [
1057 'label' => esc_html__( 'Custom CSS', 'elementor' ),
1058 'tab' => $tab,
1059 ]
1060 );
1061
1062 $messages = [
1063 esc_html__( 'Custom CSS lets you add CSS code to any widget, and see it render live right in the editor.', 'elementor' ),
1064 ];
1065
1066 if ( $additional_messages ) {
1067 $messages = array_merge( $messages, $additional_messages );
1068 }
1069
1070 $controls_stack->add_control(
1071 'custom_css_pro',
1072 [
1073 'type' => self::RAW_HTML,
1074 'raw' => $this->get_teaser_template( [
1075 'title' => esc_html__( 'Meet Our Custom CSS', 'elementor' ),
1076 'messages' => $messages,
1077 'link' => 'https://go.elementor.com/go-pro-custom-css/',
1078 ] ),
1079 ]
1080 );
1081
1082 $controls_stack->end_controls_section();
1083 }
1084
1085 /**
1086 * Add Page Transitions controls.
1087 *
1088 * This method adds a new control for the "Page Transitions" feature. The Core
1089 * version of elementor uses this method to display an upgrade message to
1090 * Elementor Pro.
1091 *
1092 * @param Controls_Stack $controls_stack .
1093 * @param string $tab
1094 * @param array $additional_messages
1095 *
1096 * @return void
1097 */
1098 public function add_page_transitions_controls( Controls_Stack $controls_stack, $tab = self::TAB_ADVANCED, $additional_messages = [] ) {
1099 $controls_stack->start_controls_section(
1100 'section_page_transitions_teaser',
1101 [
1102 'label' => esc_html__( 'Page Transitions', 'elementor' ),
1103 'tab' => $tab,
1104 ]
1105 );
1106
1107 $messages = [
1108 esc_html__( 'Page Transitions let you style entrance and exit animations between pages as well as display loader until your page assets load.', 'elementor' ),
1109 ];
1110
1111 if ( $additional_messages ) {
1112 $messages = array_merge( $messages, $additional_messages );
1113 }
1114
1115 $controls_stack->add_control(
1116 'page_transitions_teaser',
1117 [
1118 'type' => self::RAW_HTML,
1119 'raw' => $this->get_teaser_template( [
1120 'title' => esc_html__( 'Meet Page Transitions', 'elementor' ),
1121 'messages' => $messages,
1122 'link' => 'https://go.elementor.com/go-pro-page-transitions/',
1123 ] ),
1124 ]
1125 );
1126
1127 $controls_stack->end_controls_section();
1128 }
1129
1130 public function get_teaser_template( $texts ) {
1131 ob_start();
1132 ?>
1133 <div class="elementor-nerd-box">
1134 <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' ); ?>" />
1135 <div class="elementor-nerd-box-title"><?php Utils::print_unescaped_internal_string( $texts['title'] ); ?></div>
1136 <?php foreach ( $texts['messages'] as $message ) { ?>
1137 <div class="elementor-nerd-box-message"><?php Utils::print_unescaped_internal_string( $message ); ?></div>
1138 <?php }
1139
1140 // Show the upgrade button only if the user doesn't have Pro.
1141 if ( $texts['link'] && ! Utils::has_pro() ) { ?>
1142 <a class="elementor-button go-pro" href="<?php echo esc_url( ( $texts['link'] ) ); ?>" target="_blank">
1143 <?php echo esc_html__( 'Upgrade Now', 'elementor' ); ?>
1144 </a>
1145 <?php } ?>
1146 </div>
1147 <?php
1148
1149 return ob_get_clean();
1150 }
1151
1152 /**
1153 * Get Responsive Control Device Suffix
1154 *
1155 * @param array $control
1156 * @return string $device suffix
1157 */
1158 public static function get_responsive_control_device_suffix( array $control ): string {
1159 if ( ! empty( $control['responsive']['max'] ) ) {
1160 $query_device = $control['responsive']['max'];
1161 } elseif ( ! empty( $control['responsive']['min'] ) ) {
1162 $query_device = $control['responsive']['min'];
1163 } else {
1164 return '';
1165 }
1166
1167 return 'desktop' === $query_device ? '' : '_' . $query_device;
1168 }
1169
1170 /**
1171 * Add custom attributes controls.
1172 *
1173 * This method adds a new control for the "Custom Attributes" feature. The free
1174 * version of elementor uses this method to display an upgrade message to
1175 * Elementor Pro.
1176 *
1177 * @since 2.8.3
1178 * @access public
1179 *
1180 * @param Controls_Stack $controls_stack.
1181 */
1182 public function add_custom_attributes_controls( Controls_Stack $controls_stack ) {
1183 $controls_stack->start_controls_section(
1184 'section_custom_attributes_pro',
1185 [
1186 'label' => esc_html__( 'Attributes', 'elementor' ),
1187 'tab' => self::TAB_ADVANCED,
1188 ]
1189 );
1190
1191 $controls_stack->add_control(
1192 'custom_attributes_pro',
1193 [
1194 'type' => self::RAW_HTML,
1195 'raw' => $this->get_teaser_template( [
1196 'title' => esc_html__( 'Meet Our Attributes', 'elementor' ),
1197 'messages' => [
1198 esc_html__( 'Attributes lets you add custom HTML attributes to any element.', 'elementor' ),
1199 ],
1200 'link' => 'https://go.elementor.com/go-pro-custom-attributes/',
1201 ] ),
1202 ]
1203 );
1204
1205 $controls_stack->end_controls_section();
1206 }
1207
1208 /**
1209 * Check if a stack should be cleaned by the current responsive control duplication mode.
1210 *
1211 * @param $stack
1212 * @return bool
1213 */
1214 private function should_clean_stack( $stack ): bool {
1215 if ( ! isset( $stack['responsive_control_duplication_mode'] ) ) {
1216 return false;
1217 }
1218
1219 $stack_duplication_mode = $stack['responsive_control_duplication_mode'];
1220
1221 // This array provides a convenient way to map human-readable mode names to numeric values for comparison.
1222 // If the current stack's mode is greater than or equal to the current mode, then we shouldn't clean the stack.
1223 $modes = [
1224 'off' => 1,
1225 'dynamic' => 2,
1226 'on' => 3,
1227 ];
1228
1229 if ( ! isset( $modes[ $stack_duplication_mode ] ) ) {
1230 return false;
1231 }
1232
1233 $current_duplication_mode = Plugin::$instance->breakpoints->get_responsive_control_duplication_mode();
1234
1235 if ( $modes[ $stack_duplication_mode ] >= $modes[ $current_duplication_mode ] ) {
1236 return false;
1237 }
1238
1239 return true;
1240 }
1241
1242 public function add_display_conditions_controls( Controls_Stack $controls_stack ) {
1243 if ( Utils::has_pro() ) {
1244 return;
1245 }
1246
1247 ob_start();
1248 ?>
1249 <div class="e-control-display-conditions-promotion__wrapper">
1250 <div class="e-control-display-conditions-promotion__description">
1251 <span class="e-control-display-conditions-promotion__text">
1252 <?php echo esc_html__( 'Display Conditions', 'elementor' ); ?>
1253 </span>
1254 <span class="e-control-display-conditions-promotion__lock-wrapper">
1255 <i class="eicon-lock e-control-display-conditions-promotion"></i>
1256 </span>
1257 </div>
1258 <i class="eicon-flow e-control-display-conditions-promotion"></i>
1259 </div>
1260 <?php
1261 $control_template = ob_get_clean();
1262
1263 $controls_stack->add_control(
1264 'display_conditions_pro',
1265 [
1266 'type' => self::RAW_HTML,
1267 'separator' => 'before',
1268 'raw' => $control_template,
1269 ]
1270 );
1271 }
1272
1273 public function add_motion_effects_promotion_control( Controls_Stack $controls_stack ) {
1274 if ( Utils::has_pro() ) {
1275 return;
1276 }
1277
1278 $controls_stack->add_control(
1279 'scrolling_effects_pro',
1280 [
1281 'type' => self::RAW_HTML,
1282 'separator' => 'before',
1283 'raw' => $this->promotion_switcher_control( esc_html__( 'Scrolling Effects', 'elementor' ), 'scrolling-effects' ),
1284 ]
1285 );
1286
1287 $controls_stack->add_control(
1288 'mouse_effects_pro',
1289 [
1290 'type' => self::RAW_HTML,
1291 'separator' => 'before',
1292 'raw' => $this->promotion_switcher_control( esc_html__( 'Mouse Effects', 'elementor' ), 'mouse-effects' ),
1293 ]
1294 );
1295
1296 $controls_stack->add_control(
1297 'sticky_pro',
1298 [
1299 'type' => self::RAW_HTML,
1300 'separator' => 'before',
1301 'raw' => $this->promotion_select_control( esc_html__( 'Sticky', 'elementor' ), 'sticky-effects' ),
1302 ]
1303 );
1304
1305 $controls_stack->add_control(
1306 'motion_effects_promotion_divider',
1307 [
1308 'type' => self::DIVIDER,
1309 ]
1310 );
1311 }
1312
1313 private function promotion_switcher_control( $title, $id ): string {
1314 return '<div class="elementor-control-type-switcher elementor-label-inline e-control-motion-effects-promotion__wrapper">
1315 <div class="elementor-control-content">
1316 <div class="elementor-control-field">
1317 <label>
1318 ' . $title . '
1319 </label>
1320 <span class="e-control-motion-effects-promotion__lock-wrapper">
1321 <i class="eicon-lock"></i>
1322 </span>
1323 <div class="elementor-control-input-wrapper">
1324 <label class="elementor-switch elementor-control-unit-2 e-control-' . $id . '-promotion">
1325 <input type="checkbox" class="elementor-switch-input" disabled>
1326 <span class="elementor-switch-label" data-off="Off"></span>
1327 <span class="elementor-switch-handle"></span>
1328 </label>
1329 </div>
1330 </div>
1331 </div>
1332 </div>';
1333 }
1334
1335 private function promotion_select_control( $title, $id ): string {
1336 return '<div class="elementor-control-type-select elementor-label-inline e-control-motion-effects-promotion__wrapper">
1337 <div class="elementor-control-content">
1338 <div class="elementor-control-field ">
1339 <label for="sticky-motion-effect-pro">
1340 ' . $title . '
1341 </label>
1342 <span class="e-control-motion-effects-promotion__lock-wrapper">
1343 <i class="eicon-lock"></i>
1344 </span>
1345 <div class="elementor-control-input-wrapper elementor-control-unit-5 e-control-' . $id . '-promotion">
1346 <div class="select-promotion elementor-control-unit-5">None</div>
1347 </div>
1348 </div>
1349 </div>
1350 </div>';
1351 }
1352
1353 private function is_style_control( $control_data ): bool {
1354 $frontend_available = $control_data['frontend_available'] ?? false;
1355 if ( $frontend_available ) {
1356 return false;
1357 }
1358
1359 if ( ! empty( $control_data['prefix_class'] ) ) {
1360 return false;
1361 }
1362
1363 $render_type = $control_data['render_type'] ?? '';
1364 if ( 'template' === $render_type ) {
1365 return false;
1366 }
1367
1368 if ( 'ui' === $render_type ) {
1369 return true;
1370 }
1371
1372 if ( ! empty( $control_data['selectors'] ) ) {
1373 return true;
1374 }
1375
1376 return false;
1377 }
1378 }
1379