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

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