PluginProbe
Elementor Website Builder – more than just a page builder / 3.4.0-dev9
Elementor Website Builder – more than just a page builder v3.4.0-dev9
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 / base / controls-stack.php

controls-stack.php in Elementor Website Builder – more than just a page builder 3.4.0-dev9, at includes/base/controls-stack.php

2,143 lines 57.1 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\Base\Base_Object;
5 use Elementor\Core\DynamicTags\Manager;
6 use Elementor\Core\Schemes\Manager as Schemes_Manager;
7
8 if ( ! defined( 'ABSPATH' ) ) {
9 exit; // Exit if accessed directly.
10 }
11
12 /**
13 * Elementor controls stack.
14 *
15 * An abstract class that provides the needed properties and methods to
16 * manage and handle controls in the editor panel to inheriting classes.
17 *
18 * @since 1.4.0
19 * @abstract
20 */
21 abstract class Controls_Stack extends Base_Object {
22
23 /**
24 * Responsive 'desktop' device name.
25 */
26 const RESPONSIVE_DESKTOP = 'desktop';
27
28 /**
29 * Responsive 'tablet' device name.
30 */
31 const RESPONSIVE_TABLET = 'tablet';
32
33 /**
34 * Responsive 'mobile' device name.
35 */
36 const RESPONSIVE_MOBILE = 'mobile';
37
38 /**
39 * Generic ID.
40 *
41 * Holds the unique ID.
42 *
43 * @access private
44 *
45 * @var string
46 */
47 private $id;
48
49 private $active_settings;
50
51 private $parsed_active_settings;
52
53 /**
54 * Parsed Dynamic Settings.
55 *
56 * @access private
57 *
58 * @var null|array
59 */
60 private $parsed_dynamic_settings;
61
62 /**
63 * Raw Data.
64 *
65 * Holds all the raw data including the element type, the child elements,
66 * the user data.
67 *
68 * @access private
69 *
70 * @var null|array
71 */
72 private $data;
73
74 /**
75 * The configuration.
76 *
77 * Holds the configuration used to generate the Elementor editor. It includes
78 * the element name, icon, categories, etc.
79 *
80 * @access private
81 *
82 * @var null|array
83 */
84 private $config;
85
86 /**
87 * Current section.
88 *
89 * Holds the current section while inserting a set of controls sections.
90 *
91 * @access private
92 *
93 * @var null|array
94 */
95 private $current_section;
96
97 /**
98 * Current tab.
99 *
100 * Holds the current tab while inserting a set of controls tabs.
101 *
102 * @access private
103 *
104 * @var null|array
105 */
106 private $current_tab;
107
108 /**
109 * Current popover.
110 *
111 * Holds the current popover while inserting a set of controls.
112 *
113 * @access private
114 *
115 * @var null|array
116 */
117 private $current_popover;
118
119 /**
120 * Injection point.
121 *
122 * Holds the injection point in the stack where the control will be inserted.
123 *
124 * @access private
125 *
126 * @var null|array
127 */
128 private $injection_point;
129
130
131 /**
132 * Data sanitized.
133 *
134 * @access private
135 *
136 * @var bool
137 */
138 private $settings_sanitized = false;
139
140 /**
141 * Get element name.
142 *
143 * Retrieve the element name.
144 *
145 * @since 1.4.0
146 * @access public
147 * @abstract
148 *
149 * @return string The name.
150 */
151 abstract public function get_name();
152
153 /**
154 * Get unique name.
155 *
156 * Some classes need to use unique names, this method allows you to create
157 * them. By default it retrieves the regular name.
158 *
159 * @since 1.6.0
160 * @access public
161 *
162 * @return string Unique name.
163 */
164 public function get_unique_name() {
165 return $this->get_name();
166 }
167
168 /**
169 * Get element ID.
170 *
171 * Retrieve the element generic ID.
172 *
173 * @since 1.4.0
174 * @access public
175 *
176 * @return string The ID.
177 */
178 public function get_id() {
179 return $this->id;
180 }
181
182 /**
183 * Get element ID.
184 *
185 * Retrieve the element generic ID as integer.
186 *
187 * @since 1.8.0
188 * @access public
189 *
190 * @return string The converted ID.
191 */
192 public function get_id_int() {
193 /** We ignore possible notices, in order to support elements created prior to v1.8.0 and might include
194 * non-base 16 characters as part of their ID.
195 */
196 return @hexdec( $this->id );
197 }
198
199 /**
200 * Get the type.
201 *
202 * Retrieve the type, e.g. 'stack', 'section', 'widget' etc.
203 *
204 * @since 1.4.0
205 * @access public
206 * @static
207 *
208 * @return string The type.
209 */
210 public static function get_type() {
211 return 'stack';
212 }
213
214 /**
215 * @since 2.9.0
216 * @access public
217 *
218 * @return bool
219 */
220 public function is_editable() {
221 return true;
222 }
223
224 /**
225 * Get current section.
226 *
227 * When inserting new controls, this method will retrieve the current section.
228 *
229 * @since 1.7.1
230 * @access public
231 *
232 * @return null|array Current section.
233 */
234 public function get_current_section() {
235 return $this->current_section;
236 }
237
238 /**
239 * Get current tab.
240 *
241 * When inserting new controls, this method will retrieve the current tab.
242 *
243 * @since 1.7.1
244 * @access public
245 *
246 * @return null|array Current tab.
247 */
248 public function get_current_tab() {
249 return $this->current_tab;
250 }
251
252 /**
253 * Get controls.
254 *
255 * Retrieve all the controls or, when requested, a specific control.
256 *
257 * @since 1.4.0
258 * @access public
259 *
260 * @param string $control_id The ID of the requested control. Optional field,
261 * when set it will return a specific control.
262 * Default is null.
263 *
264 * @return mixed Controls list.
265 */
266 public function get_controls( $control_id = null ) {
267 return self::get_items( $this->get_stack()['controls'], $control_id );
268 }
269
270 /**
271 * Get active controls.
272 *
273 * Retrieve an array of active controls that meet the condition field.
274 *
275 * If specific controls was given as a parameter, retrieve active controls
276 * from that list, otherwise check for all the controls available.
277 *
278 * @since 1.4.0
279 * @since 2.0.9 Added the `controls` and the `settings` parameters.
280 * @access public
281 * @deprecated 3.0.0
282 *
283 * @param array $controls Optional. An array of controls. Default is null.
284 * @param array $settings Optional. Controls settings. Default is null.
285 *
286 * @return array Active controls.
287 */
288 public function get_active_controls( array $controls = null, array $settings = null ) {
289 // _deprecated_function( __METHOD__, '3.0.0' );
290
291 if ( ! $controls ) {
292 $controls = $this->get_controls();
293 }
294
295 if ( ! $settings ) {
296 $settings = $this->get_controls_settings();
297 }
298
299 $active_controls = array_reduce(
300 array_keys( $controls ), function( $active_controls, $control_key ) use ( $controls, $settings ) {
301 $control = $controls[ $control_key ];
302
303 if ( $this->is_control_visible( $control, $settings ) ) {
304 $active_controls[ $control_key ] = $control;
305 }
306
307 return $active_controls;
308 }, []
309 );
310
311 return $active_controls;
312 }
313
314 /**
315 * Get controls settings.
316 *
317 * Retrieve the settings for all the controls that represent them.
318 *
319 * @since 1.5.0
320 * @access public
321 *
322 * @return array Controls settings.
323 */
324 public function get_controls_settings() {
325 return array_intersect_key( $this->get_settings(), $this->get_controls() );
326 }
327
328 /**
329 * Add new control to stack.
330 *
331 * Register a single control to allow the user to set/update data.
332 *
333 * This method should be used inside `register_controls()`.
334 *
335 * @since 1.4.0
336 * @access public
337 *
338 * @param string $id Control ID.
339 * @param array $args Control arguments.
340 * @param array $options Optional. Control options. Default is an empty array.
341 *
342 * @return bool True if control added, False otherwise.
343 */
344 public function add_control( $id, array $args, $options = [] ) {
345 $default_options = [
346 'overwrite' => false,
347 'position' => null,
348 ];
349
350 if ( isset( $args['scheme'] ) ) {
351 $args['global'] = [
352 'default' => Plugin::$instance->kits_manager->convert_scheme_to_global( $args['scheme'] ),
353 ];
354
355 unset( $args['scheme'] );
356 }
357
358 $options = array_merge( $default_options, $options );
359
360 if ( $options['position'] ) {
361 $this->start_injection( $options['position'] );
362 }
363
364 if ( $this->injection_point ) {
365 $options['index'] = $this->injection_point['index']++;
366 }
367
368 if ( empty( $args['type'] ) || ! in_array( $args['type'], [ Controls_Manager::SECTION, Controls_Manager::WP_WIDGET ], true ) ) {
369 $args = $this->handle_control_position( $args, $id, $options['overwrite'] );
370 }
371
372 if ( $options['position'] ) {
373 $this->end_injection();
374 }
375
376 unset( $options['position'] );
377
378 if ( $this->current_popover && ! $this->current_popover['initialized'] ) {
379 $args['popover'] = [
380 'start' => true,
381 ];
382
383 $this->current_popover['initialized'] = true;
384 }
385
386 return Plugin::$instance->controls_manager->add_control_to_stack( $this, $id, $args, $options );
387 }
388
389 /**
390 * Remove control from stack.
391 *
392 * Unregister an existing control and remove it from the stack.
393 *
394 * @since 1.4.0
395 * @access public
396 *
397 * @param string $control_id Control ID.
398 *
399 * @return bool|\WP_Error
400 */
401 public function remove_control( $control_id ) {
402 return Plugin::$instance->controls_manager->remove_control_from_stack( $this->get_unique_name(), $control_id );
403 }
404
405 /**
406 * Update control in stack.
407 *
408 * Change the value of an existing control in the stack. When you add new
409 * control you set the `$args` parameter, this method allows you to update
410 * the arguments by passing new data.
411 *
412 * @since 1.4.0
413 * @since 1.8.1 New `$options` parameter added.
414 *
415 * @access public
416 *
417 * @param string $control_id Control ID.
418 * @param array $args Control arguments. Only the new fields you want
419 * to update.
420 * @param array $options Optional. Some additional options. Default is
421 * an empty array.
422 *
423 * @return bool
424 */
425 public function update_control( $control_id, array $args, array $options = [] ) {
426 $is_updated = Plugin::$instance->controls_manager->update_control_in_stack( $this, $control_id, $args, $options );
427
428 if ( ! $is_updated ) {
429 return false;
430 }
431
432 $control = $this->get_controls( $control_id );
433
434 if ( Controls_Manager::SECTION === $control['type'] ) {
435 $section_args = $this->get_section_args( $control_id );
436
437 $section_controls = $this->get_section_controls( $control_id );
438
439 foreach ( $section_controls as $section_control_id => $section_control ) {
440 $this->update_control( $section_control_id, $section_args, $options );
441 }
442 }
443
444 return true;
445 }
446
447 /**
448 * Get stack.
449 *
450 * Retrieve the stack of controls.
451 *
452 * @since 1.9.2
453 * @access public
454 *
455 * @return array Stack of controls.
456 */
457 public function get_stack() {
458 $stack = Plugin::$instance->controls_manager->get_element_stack( $this );
459
460 if ( null === $stack ) {
461 $this->init_controls();
462
463 return Plugin::$instance->controls_manager->get_element_stack( $this );
464 }
465
466 return $stack;
467 }
468
469 /**
470 * Get position information.
471 *
472 * Retrieve the position while injecting data, based on the element type.
473 *
474 * @since 1.7.0
475 * @access public
476 *
477 * @param array $position {
478 * The injection position.
479 *
480 * @type string $type Injection type, either `control` or `section`.
481 * Default is `control`.
482 * @type string $at Where to inject. If `$type` is `control` accepts
483 * `before` and `after`. If `$type` is `section`
484 * accepts `start` and `end`. Default values based on
485 * the `type`.
486 * @type string $of Control/Section ID.
487 * @type array $fallback Fallback injection position. When the position is
488 * not found it will try to fetch the fallback
489 * position.
490 * }
491 *
492 * @return bool|array Position info.
493 */
494 final public function get_position_info( array $position ) {
495 $default_position = [
496 'type' => 'control',
497 'at' => 'after',
498 ];
499
500 if ( ! empty( $position['type'] ) && 'section' === $position['type'] ) {
501 $default_position['at'] = 'end';
502 }
503
504 $position = array_merge( $default_position, $position );
505
506 if (
507 'control' === $position['type'] && in_array( $position['at'], [ 'start', 'end' ], true ) ||
508 'section' === $position['type'] && in_array( $position['at'], [ 'before', 'after' ], true )
509 ) {
510 _doing_it_wrong( sprintf( '%s::%s', get_called_class(), __FUNCTION__ ), 'Invalid position arguments. Use `before` / `after` for control or `start` / `end` for section.', '1.7.0' ); // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
511
512 return false;
513 }
514
515 $target_control_index = $this->get_control_index( $position['of'] );
516
517 if ( false === $target_control_index ) {
518 if ( ! empty( $position['fallback'] ) ) {
519 return $this->get_position_info( $position['fallback'] );
520 }
521
522 return false;
523 }
524
525 $target_section_index = $target_control_index;
526
527 $registered_controls = $this->get_controls();
528
529 $controls_keys = array_keys( $registered_controls );
530
531 while ( Controls_Manager::SECTION !== $registered_controls[ $controls_keys[ $target_section_index ] ]['type'] ) {
532 $target_section_index--;
533 }
534
535 if ( 'section' === $position['type'] ) {
536 $target_control_index++;
537
538 if ( 'end' === $position['at'] ) {
539 while ( Controls_Manager::SECTION !== $registered_controls[ $controls_keys[ $target_control_index ] ]['type'] ) {
540 if ( ++$target_control_index >= count( $registered_controls ) ) {
541 break;
542 }
543 }
544 }
545 }
546
547 $target_control = $registered_controls[ $controls_keys[ $target_control_index ] ];
548
549 if ( 'after' === $position['at'] ) {
550 $target_control_index++;
551 }
552
553 $section_id = $registered_controls[ $controls_keys[ $target_section_index ] ]['name'];
554
555 $position_info = [
556 'index' => $target_control_index,
557 'section' => $this->get_section_args( $section_id ),
558 ];
559
560 if ( ! empty( $target_control['tabs_wrapper'] ) ) {
561 $position_info['tab'] = [
562 'tabs_wrapper' => $target_control['tabs_wrapper'],
563 'inner_tab' => $target_control['inner_tab'],
564 ];
565 }
566
567 return $position_info;
568 }
569
570 /**
571 * Get control key.
572 *
573 * Retrieve the key of the control based on a given index of the control.
574 *
575 * @since 1.9.2
576 * @access public
577 *
578 * @param string $control_index Control index.
579 *
580 * @return int Control key.
581 */
582 final public function get_control_key( $control_index ) {
583 $registered_controls = $this->get_controls();
584
585 $controls_keys = array_keys( $registered_controls );
586
587 return $controls_keys[ $control_index ];
588 }
589
590 /**
591 * Get control index.
592 *
593 * Retrieve the index of the control based on a given key of the control.
594 *
595 * @since 1.7.6
596 * @access public
597 *
598 * @param string $control_key Control key.
599 *
600 * @return false|int Control index.
601 */
602 final public function get_control_index( $control_key ) {
603 $controls = $this->get_controls();
604
605 $controls_keys = array_keys( $controls );
606
607 return array_search( $control_key, $controls_keys );
608 }
609
610 /**
611 * Get section controls.
612 *
613 * Retrieve all controls under a specific section.
614 *
615 * @since 1.7.6
616 * @access public
617 *
618 * @param string $section_id Section ID.
619 *
620 * @return array Section controls
621 */
622 final public function get_section_controls( $section_id ) {
623 $section_index = $this->get_control_index( $section_id );
624
625 $section_controls = [];
626
627 $registered_controls = $this->get_controls();
628
629 $controls_keys = array_keys( $registered_controls );
630
631 while ( true ) {
632 $section_index++;
633
634 if ( ! isset( $controls_keys[ $section_index ] ) ) {
635 break;
636 }
637
638 $control_key = $controls_keys[ $section_index ];
639
640 if ( Controls_Manager::SECTION === $registered_controls[ $control_key ]['type'] ) {
641 break;
642 }
643
644 $section_controls[ $control_key ] = $registered_controls[ $control_key ];
645 };
646
647 return $section_controls;
648 }
649
650 /**
651 * Add new group control to stack.
652 *
653 * Register a set of related controls grouped together as a single unified
654 * control. For example grouping together like typography controls into a
655 * single, easy-to-use control.
656 *
657 * @since 1.4.0
658 * @access public
659 *
660 * @param string $group_name Group control name.
661 * @param array $args Group control arguments. Default is an empty array.
662 * @param array $options Optional. Group control options. Default is an
663 * empty array.
664 */
665 final public function add_group_control( $group_name, array $args = [], array $options = [] ) {
666 $group = Plugin::$instance->controls_manager->get_control_groups( $group_name );
667
668 if ( ! $group ) {
669 wp_die( sprintf( '%s::%s: Group "%s" not found.', get_called_class(), __FUNCTION__, $group_name ) ); // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
670 }
671
672 $group->add_controls( $this, $args, $options );
673 }
674
675 /**
676 * Get scheme controls.
677 *
678 * Retrieve all the controls that use schemes.
679 *
680 * @since 1.4.0
681 * @access public
682 * @deprecated 3.0.0
683 *
684 * @return array Scheme controls.
685 */
686 final public function get_scheme_controls() {
687 // _deprecated_function( __METHOD__, '3.0.0' );
688 $enabled_schemes = Schemes_Manager::get_enabled_schemes();
689
690 return array_filter(
691 $this->get_controls(), function( $control ) use ( $enabled_schemes ) {
692 return ( ! empty( $control['scheme'] ) && in_array( $control['scheme']['type'], $enabled_schemes ) );
693 }
694 );
695 }
696
697 /**
698 * Get style controls.
699 *
700 * Retrieve style controls for all active controls or, when requested, from
701 * a specific set of controls.
702 *
703 * @since 1.4.0
704 * @since 2.0.9 Added the `settings` parameter.
705 * @access public
706 * @deprecated 3.0.0
707 *
708 * @param array $controls Optional. Controls list. Default is null.
709 * @param array $settings Optional. Controls settings. Default is null.
710 *
711 * @return array Style controls.
712 */
713 final public function get_style_controls( array $controls = null, array $settings = null ) {
714 // _deprecated_function( __METHOD__, '3.0.0' );
715
716 $controls = $this->get_active_controls( $controls, $settings );
717
718 $style_controls = [];
719
720 foreach ( $controls as $control_name => $control ) {
721 $control_obj = Plugin::$instance->controls_manager->get_control( $control['type'] );
722
723 if ( ! $control_obj instanceof Base_Data_Control ) {
724 continue;
725 }
726
727 $control = array_merge( $control_obj->get_settings(), $control );
728
729 if ( $control_obj instanceof Control_Repeater ) {
730 $style_fields = [];
731
732 foreach ( $this->get_settings( $control_name ) as $item ) {
733 $style_fields[] = $this->get_style_controls( $control['fields'], $item );
734 }
735
736 $control['style_fields'] = $style_fields;
737 }
738
739 if ( ! empty( $control['selectors'] ) || ! empty( $control['dynamic'] ) || ! empty( $control['style_fields'] ) ) {
740 $style_controls[ $control_name ] = $control;
741 }
742 }
743
744 return $style_controls;
745 }
746
747 /**
748 * Get tabs controls.
749 *
750 * Retrieve all the tabs assigned to the control.
751 *
752 * @since 1.4.0
753 * @access public
754 *
755 * @return array Tabs controls.
756 */
757 final public function get_tabs_controls() {
758 return $this->get_stack()['tabs'];
759 }
760
761 /**
762 * Add new responsive control to stack.
763 *
764 * Register a set of controls to allow editing based on user screen size.
765 * This method registers one or more controls per screen size/device, depending on the current Responsive Control
766 * Duplication Mode. There are 3 control duplication modes:
767 * * 'off' - Only a single control is generated. In the Editor, this control is duplicated in JS.
768 * * 'on' - Multiple controls are generated, one control per enabled device/breakpoint + a default/desktop control.
769 * * 'dynamic' - If the control includes the `'dynamic' => 'active' => true` property - the control is duplicated,
770 * once for each device/breakpoint + default/desktop.
771 * If the control doesn't include the `'dynamic' => 'active' => true` property - the control is not duplicated.
772 *
773 * @since 1.4.0
774 * @access public
775 *
776 * @param string $id Responsive control ID.
777 * @param array $args Responsive control arguments.
778 * @param array $options Optional. Responsive control options. Default is
779 * an empty array.
780 */
781 final public function add_responsive_control( $id, array $args, $options = [] ) {
782 $args['responsive'] = [];
783
784 $active_breakpoints = Plugin::$instance->breakpoints->get_active_breakpoints();
785
786 $devices = array_keys( $active_breakpoints );
787
788 $devices[] = self::RESPONSIVE_DESKTOP;
789
790 if ( isset( $args['devices'] ) ) {
791 $devices = array_intersect( $devices, $args['devices'] );
792
793 $args['responsive']['devices'] = $devices;
794
795 unset( $args['devices'] );
796 }
797
798 $responsive_duplication_mode = Plugin::$instance->breakpoints->get_responsive_control_duplication_mode();
799 $additional_breakpoints_active = Plugin::$instance->experiments->is_feature_active( 'additional_custom_breakpoints' );
800 $control_is_dynamic = ! empty( $args['dynamic']['active'] );
801 $is_frontend_available = ! empty( $args['frontend_available'] );
802 $has_prefix_class = ! empty( $args['prefix_class'] );
803
804 // If the new responsive controls experiment is active, create only one control - duplicates per device will
805 // be created in JS in the Editor.
806 if (
807 $additional_breakpoints_active
808 && ( 'off' === $responsive_duplication_mode || ( 'dynamic' === $responsive_duplication_mode && ! $control_is_dynamic ) )
809 // Some responsive controls need responsive settings to be available to the widget handler, even when empty.
810 && ! $is_frontend_available
811 && ! $has_prefix_class
812 ) {
813 $args['is_responsive'] = true;
814
815 if ( ! empty( $options['overwrite'] ) ) {
816 $this->update_control( $id, $args, [
817 'recursive' => ! empty( $options['recursive'] ),
818 ] );
819 } else {
820 $this->add_control( $id, $args, $options );
821 }
822
823 return;
824 }
825
826 if ( isset( $args['default'] ) ) {
827 $args['desktop_default'] = $args['default'];
828
829 unset( $args['default'] );
830 }
831
832 foreach ( $devices as $device_name ) {
833 $control_args = $args;
834
835 if ( isset( $control_args['device_args'] ) ) {
836 if ( ! empty( $control_args['device_args'][ $device_name ] ) ) {
837 $control_args = array_merge( $control_args, $control_args['device_args'][ $device_name ] );
838 }
839
840 unset( $control_args['device_args'] );
841 }
842
843 if ( ! empty( $args['prefix_class'] ) ) {
844 $device_to_replace = self::RESPONSIVE_DESKTOP === $device_name ? '' : '-' . $device_name;
845
846 $control_args['prefix_class'] = sprintf( $args['prefix_class'], $device_to_replace );
847 }
848
849 $direction = 'max';
850
851 if ( self::RESPONSIVE_DESKTOP !== $device_name ) {
852 $direction = $active_breakpoints[ $device_name ]->get_direction();
853 }
854
855 $control_args['responsive'][ $direction ] = $device_name;
856
857 if ( isset( $control_args['min_affected_device'] ) ) {
858 if ( ! empty( $control_args['min_affected_device'][ $device_name ] ) ) {
859 $control_args['responsive']['min'] = $control_args['min_affected_device'][ $device_name ];
860 }
861
862 unset( $control_args['min_affected_device'] );
863 }
864
865 if ( isset( $control_args[ $device_name . '_default' ] ) ) {
866 $control_args['default'] = $control_args[ $device_name . '_default' ];
867 }
868
869 unset( $control_args['desktop_default'] );
870 unset( $control_args['tablet_default'] );
871 unset( $control_args['mobile_default'] );
872
873 $id_suffix = self::RESPONSIVE_DESKTOP === $device_name ? '' : '_' . $device_name;
874
875 if ( ! empty( $options['overwrite'] ) ) {
876 $this->update_control( $id . $id_suffix, $control_args, [
877 'recursive' => ! empty( $options['recursive'] ),
878 ] );
879 } else {
880 $this->add_control( $id . $id_suffix, $control_args, $options );
881 }
882 }
883 }
884
885 /**
886 * Update responsive control in stack.
887 *
888 * Change the value of an existing responsive control in the stack. When you
889 * add new control you set the `$args` parameter, this method allows you to
890 * update the arguments by passing new data.
891 *
892 * @since 1.4.0
893 * @access public
894 *
895 * @param string $id Responsive control ID.
896 * @param array $args Responsive control arguments.
897 * @param array $options Optional. Additional options.
898 */
899 final public function update_responsive_control( $id, array $args, array $options = [] ) {
900 $this->add_responsive_control( $id, $args, [
901 'overwrite' => true,
902 'recursive' => ! empty( $options['recursive'] ),
903 ] );
904 }
905
906 /**
907 * Remove responsive control from stack.
908 *
909 * Unregister an existing responsive control and remove it from the stack.
910 *
911 * @since 1.4.0
912 * @access public
913 *
914 * @param string $id Responsive control ID.
915 */
916 final public function remove_responsive_control( $id ) {
917 $devices = [
918 self::RESPONSIVE_DESKTOP,
919 self::RESPONSIVE_TABLET,
920 self::RESPONSIVE_MOBILE,
921 ];
922
923 foreach ( $devices as $device_name ) {
924 $id_suffix = self::RESPONSIVE_DESKTOP === $device_name ? '' : '_' . $device_name;
925
926 $this->remove_control( $id . $id_suffix );
927 }
928 }
929
930 /**
931 * Get class name.
932 *
933 * Retrieve the name of the current class.
934 *
935 * @since 1.4.0
936 * @access public
937 *
938 * @return string Class name.
939 */
940 final public function get_class_name() {
941 return get_called_class();
942 }
943
944 /**
945 * Get the config.
946 *
947 * Retrieve the config or, if non set, use the initial config.
948 *
949 * @since 1.4.0
950 * @access public
951 *
952 * @return array|null The config.
953 */
954 final public function get_config() {
955 if ( null === $this->config ) {
956 // TODO: This is for backwards compatibility starting from 2.9.0
957 // This if statement should be removed when the method is hard-deprecated
958 if ( $this->has_own_method( '_get_initial_config', self::class ) ) {
959 Plugin::$instance->modules_manager->get_modules( 'dev-tools' )->deprecation->deprecated_function( '_get_initial_config', '2.9.0', __CLASS__ . '::get_initial_config()' );
960
961 $this->config = $this->_get_initial_config();
962 } else {
963 $this->config = $this->get_initial_config();
964 }
965 }
966
967 return $this->config;
968 }
969
970 /**
971 * Get frontend settings keys.
972 *
973 * Retrieve settings keys for all frontend controls.
974 *
975 * @since 1.6.0
976 * @access public
977 *
978 * @return array Settings keys for each control.
979 */
980 final public function get_frontend_settings_keys() {
981 $controls = [];
982
983 foreach ( $this->get_controls() as $control ) {
984 if ( ! empty( $control['frontend_available'] ) ) {
985 $controls[] = $control['name'];
986 }
987 }
988
989 return $controls;
990 }
991
992 /**
993 * Get controls pointer index.
994 *
995 * Retrieve pointer index where the next control should be added.
996 *
997 * While using injection point, it will return the injection point index.
998 * Otherwise index of the last control plus one.
999 *
1000 * @since 1.9.2
1001 * @access public
1002 *
1003 * @return int Controls pointer index.
1004 */
1005 public function get_pointer_index() {
1006 if ( null !== $this->injection_point ) {
1007 return $this->injection_point['index'];
1008 }
1009
1010 return count( $this->get_controls() );
1011 }
1012
1013 /**
1014 * Get the raw data.
1015 *
1016 * Retrieve all the items or, when requested, a specific item.
1017 *
1018 * @since 1.4.0
1019 * @access public
1020 *
1021 * @param string $item Optional. The requested item. Default is null.
1022 *
1023 * @return mixed The raw data.
1024 */
1025 public function get_data( $item = null ) {
1026 if ( ! $this->settings_sanitized && ( ! $item || 'settings' === $item ) ) {
1027 $this->data['settings'] = $this->sanitize_settings( $this->data['settings'] );
1028
1029 $this->settings_sanitized = true;
1030 }
1031
1032 return self::get_items( $this->data, $item );
1033 }
1034
1035 /**
1036 * @since 2.0.14
1037 * @access public
1038 */
1039 public function get_parsed_dynamic_settings( $setting = null, $settings = null ) {
1040 if ( null === $settings ) {
1041 $settings = $this->get_settings();
1042 }
1043
1044 if ( null === $this->parsed_dynamic_settings ) {
1045 $this->parsed_dynamic_settings = $this->parse_dynamic_settings( $settings );
1046 }
1047
1048 return self::get_items( $this->parsed_dynamic_settings, $setting );
1049 }
1050
1051 /**
1052 * Get active settings.
1053 *
1054 * Retrieve the settings from all the active controls.
1055 *
1056 * @since 1.4.0
1057 * @since 2.1.0 Added the `controls` and the `settings` parameters.
1058 * @access public
1059 *
1060 * @param array $controls Optional. An array of controls. Default is null.
1061 * @param array $settings Optional. Controls settings. Default is null.
1062 *
1063 * @return array Active settings.
1064 */
1065 public function get_active_settings( $settings = null, $controls = null ) {
1066 $is_first_request = ! $settings && ! $this->active_settings;
1067
1068 if ( ! $settings ) {
1069 if ( $this->active_settings ) {
1070 return $this->active_settings;
1071 }
1072
1073 $settings = $this->get_controls_settings();
1074
1075 $controls = $this->get_controls();
1076 }
1077
1078 $active_settings = [];
1079
1080 foreach ( $settings as $setting_key => $setting ) {
1081 if ( ! isset( $controls[ $setting_key ] ) ) {
1082 $active_settings[ $setting_key ] = $setting;
1083
1084 continue;
1085 }
1086
1087 $control = $controls[ $setting_key ];
1088
1089 if ( $this->is_control_visible( $control, $settings ) ) {
1090 $control_obj = Plugin::$instance->controls_manager->get_control( $control['type'] );
1091
1092 if ( $control_obj instanceof Control_Repeater ) {
1093 foreach ( $setting as & $item ) {
1094 $item = $this->get_active_settings( $item, $control['fields'] );
1095 }
1096 }
1097
1098 $active_settings[ $setting_key ] = $setting;
1099 } else {
1100 $active_settings[ $setting_key ] = null;
1101 }
1102 }
1103
1104 if ( $is_first_request ) {
1105 $this->active_settings = $active_settings;
1106 }
1107
1108 return $active_settings;
1109 }
1110
1111 /**
1112 * Get settings for display.
1113 *
1114 * Retrieve all the settings or, when requested, a specific setting for display.
1115 *
1116 * Unlike `get_settings()` method, this method retrieves only active settings
1117 * that passed all the conditions, rendered all the shortcodes and all the dynamic
1118 * tags.
1119 *
1120 * @since 2.0.0
1121 * @access public
1122 *
1123 * @param string $setting_key Optional. The key of the requested setting.
1124 * Default is null.
1125 *
1126 * @return mixed The settings.
1127 */
1128 public function get_settings_for_display( $setting_key = null ) {
1129 if ( ! $this->parsed_active_settings ) {
1130 $this->parsed_active_settings = $this->get_active_settings( $this->get_parsed_dynamic_settings(), $this->get_controls() );
1131 }
1132
1133 return self::get_items( $this->parsed_active_settings, $setting_key );
1134 }
1135
1136 /**
1137 * Parse dynamic settings.
1138 *
1139 * Retrieve the settings with rendered dynamic tags.
1140 *
1141 * @since 2.0.0
1142 * @access public
1143 *
1144 * @param array $settings Optional. The requested setting. Default is null.
1145 * @param array $controls Optional. The controls array. Default is null.
1146 * @param array $all_settings Optional. All the settings. Default is null.
1147 *
1148 * @return array The settings with rendered dynamic tags.
1149 */
1150 public function parse_dynamic_settings( $settings, $controls = null, $all_settings = null ) {
1151 if ( null === $all_settings ) {
1152 $all_settings = $this->get_settings();
1153 }
1154
1155 if ( null === $controls ) {
1156 $controls = $this->get_controls();
1157 }
1158
1159 foreach ( $controls as $control ) {
1160 $control_name = $control['name'];
1161 $control_obj = Plugin::$instance->controls_manager->get_control( $control['type'] );
1162
1163 if ( ! $control_obj instanceof Base_Data_Control ) {
1164 continue;
1165 }
1166
1167 if ( $control_obj instanceof Control_Repeater ) {
1168 if ( ! isset( $settings[ $control_name ] ) ) {
1169 continue;
1170 }
1171
1172 foreach ( $settings[ $control_name ] as & $field ) {
1173 $field = $this->parse_dynamic_settings( $field, $control['fields'], $field );
1174 }
1175
1176 continue;
1177 }
1178
1179 $dynamic_settings = $control_obj->get_settings( 'dynamic' );
1180
1181 if ( ! $dynamic_settings ) {
1182 $dynamic_settings = [];
1183 }
1184
1185 if ( ! empty( $control['dynamic'] ) ) {
1186 $dynamic_settings = array_merge( $dynamic_settings, $control['dynamic'] );
1187 }
1188
1189 if ( empty( $dynamic_settings ) || ! isset( $all_settings[ Manager::DYNAMIC_SETTING_KEY ][ $control_name ] ) ) {
1190 continue;
1191 }
1192
1193 if ( ! empty( $dynamic_settings['active'] ) && ! empty( $all_settings[ Manager::DYNAMIC_SETTING_KEY ][ $control_name ] ) ) {
1194 $parsed_value = $control_obj->parse_tags( $all_settings[ Manager::DYNAMIC_SETTING_KEY ][ $control_name ], $dynamic_settings );
1195
1196 $dynamic_property = ! empty( $dynamic_settings['property'] ) ? $dynamic_settings['property'] : null;
1197
1198 if ( $dynamic_property ) {
1199 $settings[ $control_name ][ $dynamic_property ] = $parsed_value;
1200 } else {
1201 $settings[ $control_name ] = $parsed_value;
1202 }
1203 }
1204 }
1205
1206 return $settings;
1207 }
1208
1209 /**
1210 * Get frontend settings.
1211 *
1212 * Retrieve the settings for all frontend controls.
1213 *
1214 * @since 1.6.0
1215 * @access public
1216 *
1217 * @return array Frontend settings.
1218 */
1219 public function get_frontend_settings() {
1220 $frontend_settings = array_intersect_key( $this->get_settings_for_display(), array_flip( $this->get_frontend_settings_keys() ) );
1221
1222 foreach ( $frontend_settings as $key => $setting ) {
1223 if ( in_array( $setting, [ null, '' ], true ) ) {
1224 unset( $frontend_settings[ $key ] );
1225 }
1226 }
1227
1228 return $frontend_settings;
1229 }
1230
1231 /**
1232 * Filter controls settings.
1233 *
1234 * Receives controls, settings and a callback function to filter the settings by
1235 * and returns filtered settings.
1236 *
1237 * @since 1.5.0
1238 * @access public
1239 *
1240 * @param callable $callback The callback function.
1241 * @param array $settings Optional. Control settings. Default is an empty
1242 * array.
1243 * @param array $controls Optional. Controls list. Default is an empty
1244 * array.
1245 *
1246 * @return array Filtered settings.
1247 */
1248 public function filter_controls_settings( callable $callback, array $settings = [], array $controls = [] ) {
1249 if ( ! $settings ) {
1250 $settings = $this->get_settings();
1251 }
1252
1253 if ( ! $controls ) {
1254 $controls = $this->get_controls();
1255 }
1256
1257 return array_reduce(
1258 array_keys( $settings ), function( $filtered_settings, $setting_key ) use ( $controls, $settings, $callback ) {
1259 if ( isset( $controls[ $setting_key ] ) ) {
1260 $result = $callback( $settings[ $setting_key ], $controls[ $setting_key ] );
1261
1262 if ( null !== $result ) {
1263 $filtered_settings[ $setting_key ] = $result;
1264 }
1265 }
1266
1267 return $filtered_settings;
1268 }, []
1269 );
1270 }
1271
1272 /**
1273 * Whether the control is visible or not.
1274 *
1275 * Used to determine whether the control is visible or not.
1276 *
1277 * @since 1.4.0
1278 * @access public
1279 *
1280 * @param array $control The control.
1281 * @param array $values Optional. Condition values. Default is null.
1282 *
1283 * @return bool Whether the control is visible.
1284 */
1285 public function is_control_visible( $control, $values = null ) {
1286 if ( null === $values ) {
1287 $values = $this->get_settings();
1288 }
1289
1290 if ( ! empty( $control['conditions'] ) && ! Conditions::check( $control['conditions'], $values ) ) {
1291 return false;
1292 }
1293
1294 if ( empty( $control['condition'] ) ) {
1295 return true;
1296 }
1297
1298 foreach ( $control['condition'] as $condition_key => $condition_value ) {
1299 preg_match( '/([a-z_\-0-9]+)(?:\[([a-z_]+)])?(!?)$/i', $condition_key, $condition_key_parts );
1300
1301 $pure_condition_key = $condition_key_parts[1];
1302 $condition_sub_key = $condition_key_parts[2];
1303 $is_negative_condition = ! ! $condition_key_parts[3];
1304
1305 if ( ! isset( $values[ $pure_condition_key ] ) || null === $values[ $pure_condition_key ] ) {
1306 return false;
1307 }
1308
1309 $instance_value = $values[ $pure_condition_key ];
1310
1311 if ( $condition_sub_key && is_array( $instance_value ) ) {
1312 if ( ! isset( $instance_value[ $condition_sub_key ] ) ) {
1313 return false;
1314 }
1315
1316 $instance_value = $instance_value[ $condition_sub_key ];
1317 }
1318
1319 /**
1320 * If the $condition_value is a non empty array - check if the $condition_value contains the $instance_value,
1321 * If the $instance_value is a non empty array - check if the $instance_value contains the $condition_value
1322 * otherwise check if they are equal. ( and give the ability to check if the value is an empty array )
1323 */
1324 if ( is_array( $condition_value ) && ! empty( $condition_value ) ) {
1325 $is_contains = in_array( $instance_value, $condition_value, true );
1326 } elseif ( is_array( $instance_value ) && ! empty( $instance_value ) ) {
1327 $is_contains = in_array( $condition_value, $instance_value, true );
1328 } else {
1329 $is_contains = $instance_value === $condition_value;
1330 }
1331
1332 if ( $is_negative_condition && $is_contains || ! $is_negative_condition && ! $is_contains ) {
1333 return false;
1334 }
1335 }
1336
1337 return true;
1338 }
1339
1340 /**
1341 * Start controls section.
1342 *
1343 * Used to add a new section of controls. When you use this method, all the
1344 * registered controls from this point will be assigned to this section,
1345 * until you close the section using `end_controls_section()` method.
1346 *
1347 * This method should be used inside `register_controls()`.
1348 *
1349 * @since 1.4.0
1350 * @access public
1351 *
1352 * @param string $section_id Section ID.
1353 * @param array $args Section arguments Optional.
1354 */
1355 public function start_controls_section( $section_id, array $args = [] ) {
1356 $section_name = $this->get_name();
1357
1358 /**
1359 * Before section start.
1360 *
1361 * Fires before Elementor section starts in the editor panel.
1362 *
1363 * @since 1.4.0
1364 *
1365 * @param Controls_Stack $this The control.
1366 * @param string $section_id Section ID.
1367 * @param array $args Section arguments.
1368 */
1369 do_action( 'elementor/element/before_section_start', $this, $section_id, $args );
1370
1371 /**
1372 * Before section start.
1373 *
1374 * Fires before Elementor section starts in the editor panel.
1375 *
1376 * The dynamic portions of the hook name, `$section_name` and `$section_id`, refers to the section name and section ID, respectively.
1377 *
1378 * @since 1.4.0
1379 *
1380 * @param Controls_Stack $this The control.
1381 * @param array $args Section arguments.
1382 */
1383 do_action( "elementor/element/{$section_name}/{$section_id}/before_section_start", $this, $args );
1384
1385 $args['type'] = Controls_Manager::SECTION;
1386
1387 $this->add_control( $section_id, $args );
1388
1389 if ( null !== $this->current_section ) {
1390 wp_die( sprintf( 'Elementor: You can\'t start a section before the end of the previous section "%s".', $this->current_section['section'] ) ); // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
1391 }
1392
1393 $this->current_section = $this->get_section_args( $section_id );
1394
1395 if ( $this->injection_point ) {
1396 $this->injection_point['section'] = $this->current_section;
1397 }
1398
1399 /**
1400 * After section start.
1401 *
1402 * Fires after Elementor section starts in the editor panel.
1403 *
1404 * @since 1.4.0
1405 *
1406 * @param Controls_Stack $this The control.
1407 * @param string $section_id Section ID.
1408 * @param array $args Section arguments.
1409 */
1410 do_action( 'elementor/element/after_section_start', $this, $section_id, $args );
1411
1412 /**
1413 * After section start.
1414 *
1415 * Fires after Elementor section starts in the editor panel.
1416 *
1417 * The dynamic portions of the hook name, `$section_name` and `$section_id`, refers to the section name and section ID, respectively.
1418 *
1419 * @since 1.4.0
1420 *
1421 * @param Controls_Stack $this The control.
1422 * @param array $args Section arguments.
1423 */
1424 do_action( "elementor/element/{$section_name}/{$section_id}/after_section_start", $this, $args );
1425 }
1426
1427 /**
1428 * End controls section.
1429 *
1430 * Used to close an existing open controls section. When you use this method
1431 * it stops adding new controls to this section.
1432 *
1433 * This method should be used inside `register_controls()`.
1434 *
1435 * @since 1.4.0
1436 * @access public
1437 */
1438 public function end_controls_section() {
1439 $stack_name = $this->get_name();
1440
1441 // Save the current section for the action.
1442 $current_section = $this->current_section;
1443 $section_id = $current_section['section'];
1444 $args = [
1445 'tab' => $current_section['tab'],
1446 ];
1447
1448 /**
1449 * Before section end.
1450 *
1451 * Fires before Elementor section ends in the editor panel.
1452 *
1453 * @since 1.4.0
1454 *
1455 * @param Controls_Stack $this The control.
1456 * @param string $section_id Section ID.
1457 * @param array $args Section arguments.
1458 */
1459 do_action( 'elementor/element/before_section_end', $this, $section_id, $args );
1460
1461 /**
1462 * Before section end.
1463 *
1464 * Fires before Elementor section ends in the editor panel.
1465 *
1466 * The dynamic portions of the hook name, `$stack_name` and `$section_id`, refers to the stack name and section ID, respectively.
1467 *
1468 * @since 1.4.0
1469 *
1470 * @param Controls_Stack $this The control.
1471 * @param array $args Section arguments.
1472 */
1473 do_action( "elementor/element/{$stack_name}/{$section_id}/before_section_end", $this, $args );
1474
1475 $this->current_section = null;
1476
1477 /**
1478 * After section end.
1479 *
1480 * Fires after Elementor section ends in the editor panel.
1481 *
1482 * @since 1.4.0
1483 *
1484 * @param Controls_Stack $this The control.
1485 * @param string $section_id Section ID.
1486 * @param array $args Section arguments.
1487 */
1488 do_action( 'elementor/element/after_section_end', $this, $section_id, $args );
1489
1490 /**
1491 * After section end.
1492 *
1493 * Fires after Elementor section ends in the editor panel.
1494 *
1495 * The dynamic portions of the hook name, `$stack_name` and `$section_id`, refers to the section name and section ID, respectively.
1496 *
1497 * @since 1.4.0
1498 *
1499 * @param Controls_Stack $this The control.
1500 * @param array $args Section arguments.
1501 */
1502 do_action( "elementor/element/{$stack_name}/{$section_id}/after_section_end", $this, $args );
1503 }
1504
1505 /**
1506 * Start controls tabs.
1507 *
1508 * Used to add a new set of tabs inside a section. You should use this
1509 * method before adding new individual tabs using `start_controls_tab()`.
1510 * Each tab added after this point will be assigned to this group of tabs,
1511 * until you close it using `end_controls_tabs()` method.
1512 *
1513 * This method should be used inside `register_controls()`.
1514 *
1515 * @since 1.4.0
1516 * @access public
1517 *
1518 * @param string $tabs_id Tabs ID.
1519 * @param array $args Tabs arguments.
1520 */
1521 public function start_controls_tabs( $tabs_id, array $args = [] ) {
1522 if ( null !== $this->current_tab ) {
1523 wp_die( sprintf( 'Elementor: You can\'t start tabs before the end of the previous tabs "%s".', $this->current_tab['tabs_wrapper'] ) ); // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
1524 }
1525
1526 $args['type'] = Controls_Manager::TABS;
1527
1528 $this->add_control( $tabs_id, $args );
1529
1530 $this->current_tab = [
1531 'tabs_wrapper' => $tabs_id,
1532 ];
1533
1534 foreach ( [ 'condition', 'conditions' ] as $key ) {
1535 if ( ! empty( $args[ $key ] ) ) {
1536 $this->current_tab[ $key ] = $args[ $key ];
1537 }
1538 }
1539
1540 if ( $this->injection_point ) {
1541 $this->injection_point['tab'] = $this->current_tab;
1542 }
1543 }
1544
1545 /**
1546 * End controls tabs.
1547 *
1548 * Used to close an existing open controls tabs. When you use this method it
1549 * stops adding new controls to this tabs.
1550 *
1551 * This method should be used inside `register_controls()`.
1552 *
1553 * @since 1.4.0
1554 * @access public
1555 */
1556 public function end_controls_tabs() {
1557 $this->current_tab = null;
1558 }
1559
1560 /**
1561 * Start controls tab.
1562 *
1563 * Used to add a new tab inside a group of tabs. Use this method before
1564 * adding new individual tabs using `start_controls_tab()`.
1565 * Each tab added after this point will be assigned to this group of tabs,
1566 * until you close it using `end_controls_tab()` method.
1567 *
1568 * This method should be used inside `register_controls()`.
1569 *
1570 * @since 1.4.0
1571 * @access public
1572 *
1573 * @param string $tab_id Tab ID.
1574 * @param array $args Tab arguments.
1575 */
1576 public function start_controls_tab( $tab_id, $args ) {
1577 if ( ! empty( $this->current_tab['inner_tab'] ) ) {
1578 wp_die( sprintf( 'Elementor: You can\'t start a tab before the end of the previous tab "%s".', $this->current_tab['inner_tab'] ) ); // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
1579 }
1580
1581 $args['type'] = Controls_Manager::TAB;
1582 $args['tabs_wrapper'] = $this->current_tab['tabs_wrapper'];
1583
1584 $this->add_control( $tab_id, $args );
1585
1586 $this->current_tab['inner_tab'] = $tab_id;
1587
1588 if ( $this->injection_point ) {
1589 $this->injection_point['tab']['inner_tab'] = $this->current_tab['inner_tab'];
1590 }
1591 }
1592
1593 /**
1594 * End controls tab.
1595 *
1596 * Used to close an existing open controls tab. When you use this method it
1597 * stops adding new controls to this tab.
1598 *
1599 * This method should be used inside `register_controls()`.
1600 *
1601 * @since 1.4.0
1602 * @access public
1603 */
1604 public function end_controls_tab() {
1605 unset( $this->current_tab['inner_tab'] );
1606 }
1607
1608 /**
1609 * Start popover.
1610 *
1611 * Used to add a new set of controls in a popover. When you use this method,
1612 * all the registered controls from this point will be assigned to this
1613 * popover, until you close the popover using `end_popover()` method.
1614 *
1615 * This method should be used inside `register_controls()`.
1616 *
1617 * @since 1.9.0
1618 * @access public
1619 */
1620 final public function start_popover() {
1621 $this->current_popover = [
1622 'initialized' => false,
1623 ];
1624 }
1625
1626 /**
1627 * End popover.
1628 *
1629 * Used to close an existing open popover. When you use this method it stops
1630 * adding new controls to this popover.
1631 *
1632 * This method should be used inside `register_controls()`.
1633 *
1634 * @since 1.9.0
1635 * @access public
1636 */
1637 final public function end_popover() {
1638 $this->current_popover = null;
1639
1640 $last_control_key = $this->get_control_key( $this->get_pointer_index() - 1 );
1641
1642 $args = [
1643 'popover' => [
1644 'end' => true,
1645 ],
1646 ];
1647
1648 $options = [
1649 'recursive' => true,
1650 ];
1651
1652 $this->update_control( $last_control_key, $args, $options );
1653 }
1654
1655 /**
1656 * Print element template.
1657 *
1658 * Used to generate the element template on the editor.
1659 *
1660 * @since 2.0.0
1661 * @access public
1662 */
1663 public function print_template() {
1664 ob_start();
1665
1666 // TODO: This is for backwards compatibility starting from 2.9.0
1667 // This `if` statement should be removed when the method is removed
1668 if ( $this->has_own_method( '_content_template', self::class ) ) {
1669 Plugin::$instance->modules_manager->get_modules( 'dev-tools' )->deprecation->deprecated_function( '_content_template', '2.9.0', __CLASS__ . '::content_template()' );
1670
1671 $this->_content_template();
1672 } else {
1673 $this->content_template();
1674 }
1675
1676 $template_content = ob_get_clean();
1677
1678 $element_type = $this->get_type();
1679
1680 /**
1681 * Template content.
1682 *
1683 * Filters the controls stack template content before it's printed in the editor.
1684 *
1685 * The dynamic portion of the hook name, `$element_type`, refers to the element type.
1686 *
1687 * @since 1.0.0
1688 *
1689 * @param string $content_template The controls stack template in the editor.
1690 * @param Controls_Stack $this The controls stack.
1691 */
1692 $template_content = apply_filters( "elementor/{$element_type}/print_template", $template_content, $this );
1693
1694 if ( empty( $template_content ) ) {
1695 return;
1696 }
1697 ?>
1698 <script type="text/html" id="tmpl-elementor-<?php echo esc_attr( $this->get_name() ); ?>-content">
1699 <?php $this->print_template_content( $template_content ); ?>
1700 </script>
1701 <?php
1702 }
1703
1704 /**
1705 * Start injection.
1706 *
1707 * Used to inject controls and sections to a specific position in the stack.
1708 *
1709 * When you use this method, all the registered controls and sections will
1710 * be injected to a specific position in the stack, until you stop the
1711 * injection using `end_injection()` method.
1712 *
1713 * @since 1.7.1
1714 * @access public
1715 *
1716 * @param array $position {
1717 * The position where to start the injection.
1718 *
1719 * @type string $type Injection type, either `control` or `section`.
1720 * Default is `control`.
1721 * @type string $at Where to inject. If `$type` is `control` accepts
1722 * `before` and `after`. If `$type` is `section`
1723 * accepts `start` and `end`. Default values based on
1724 * the `type`.
1725 * @type string $of Control/Section ID.
1726 * }
1727 */
1728 final public function start_injection( array $position ) {
1729 if ( $this->injection_point ) {
1730 wp_die( 'A controls injection is already opened. Please close current injection before starting a new one (use `end_injection`).' );
1731 }
1732
1733 $this->injection_point = $this->get_position_info( $position );
1734 }
1735
1736 /**
1737 * End injection.
1738 *
1739 * Used to close an existing opened injection point.
1740 *
1741 * When you use this method it stops adding new controls and sections to
1742 * this point and continue to add controls to the regular position in the
1743 * stack.
1744 *
1745 * @since 1.7.1
1746 * @access public
1747 */
1748 final public function end_injection() {
1749 $this->injection_point = null;
1750 }
1751
1752 /**
1753 * Get injection point.
1754 *
1755 * Retrieve the injection point in the stack where new controls and sections
1756 * will be inserted.
1757 *
1758 * @since 1.9.2
1759 * @access public
1760 *
1761 * @return array|null An array when an injection point is defined, null
1762 * otherwise.
1763 */
1764 final public function get_injection_point() {
1765 return $this->injection_point;
1766 }
1767
1768 /**
1769 * Register controls.
1770 *
1771 * Used to add new controls to any element type. For example, external
1772 * developers use this method to register controls in a widget.
1773 *
1774 * Should be inherited and register new controls using `add_control()`,
1775 * `add_responsive_control()` and `add_group_control()`, inside control
1776 * wrappers like `start_controls_section()`, `start_controls_tabs()` and
1777 * `start_controls_tab()`.
1778 *
1779 * @since 1.4.0
1780 * @access protected
1781 * @deprecated 3.1.0 Use `Controls_Stack::register_controls()` instead
1782 */
1783 protected function _register_controls() {
1784 Plugin::$instance->modules_manager->get_modules( 'dev-tools' )->deprecation->deprecated_function( __METHOD__, '3.1.0', __CLASS__ . '::register_controls()' );
1785
1786 $this->register_controls();
1787 }
1788
1789 /**
1790 * Register controls.
1791 *
1792 * Used to add new controls to any element type. For example, external
1793 * developers use this method to register controls in a widget.
1794 *
1795 * Should be inherited and register new controls using `add_control()`,
1796 * `add_responsive_control()` and `add_group_control()`, inside control
1797 * wrappers like `start_controls_section()`, `start_controls_tabs()` and
1798 * `start_controls_tab()`.
1799 *
1800 * @since 3.1.0
1801 * @access protected
1802 */
1803 protected function register_controls() {}
1804
1805 /**
1806 * Get default data.
1807 *
1808 * Retrieve the default data. Used to reset the data on initialization.
1809 *
1810 * @since 1.4.0
1811 * @access protected
1812 *
1813 * @return array Default data.
1814 */
1815 protected function get_default_data() {
1816 return [
1817 'id' => 0,
1818 'settings' => [],
1819 ];
1820 }
1821
1822 /**
1823 * @since 2.3.0
1824 * @access protected
1825 */
1826 protected function get_init_settings() {
1827 $settings = $this->get_data( 'settings' );
1828
1829 foreach ( $this->get_controls() as $control ) {
1830 $control_obj = Plugin::$instance->controls_manager->get_control( $control['type'] );
1831
1832 if ( ! $control_obj instanceof Base_Data_Control ) {
1833 continue;
1834 }
1835
1836 $control = array_merge_recursive( $control_obj->get_settings(), $control );
1837
1838 $settings[ $control['name'] ] = $control_obj->get_value( $control, $settings );
1839 }
1840
1841 return $settings;
1842 }
1843
1844 /**
1845 * Get initial config.
1846 *
1847 * Retrieve the current element initial configuration - controls list and
1848 * the tabs assigned to the control.
1849 *
1850 * @since 2.9.0
1851 * @access protected
1852 *
1853 * @return array The initial config.
1854 */
1855 protected function get_initial_config() {
1856 return [
1857 'controls' => $this->get_controls(),
1858 ];
1859 }
1860
1861 /**
1862 * Get initial config.
1863 *
1864 * Retrieve the current element initial configuration - controls list and
1865 * the tabs assigned to the control.
1866 *
1867 * @since 1.4.0
1868 * @deprecated 2.9.0 use `get_initial_config()` instead
1869 * @access protected
1870 *
1871 * @return array The initial config.
1872 */
1873 protected function _get_initial_config() {
1874 Plugin::$instance->modules_manager->get_modules( 'dev-tools' )->deprecation->deprecated_function( __METHOD__, '2.9.0', __CLASS__ . '::get_initial_config()' );
1875
1876 return $this->get_initial_config();
1877 }
1878
1879 /**
1880 * Get section arguments.
1881 *
1882 * Retrieve the section arguments based on section ID.
1883 *
1884 * @since 1.4.0
1885 * @access protected
1886 *
1887 * @param string $section_id Section ID.
1888 *
1889 * @return array Section arguments.
1890 */
1891 protected function get_section_args( $section_id ) {
1892 $section_control = $this->get_controls( $section_id );
1893
1894 $section_args_keys = [ 'tab', 'condition' ];
1895
1896 $args = array_intersect_key( $section_control, array_flip( $section_args_keys ) );
1897
1898 $args['section'] = $section_id;
1899
1900 return $args;
1901 }
1902
1903 /**
1904 * Render element.
1905 *
1906 * Generates the final HTML on the frontend.
1907 *
1908 * @since 2.0.0
1909 * @access protected
1910 */
1911 protected function render() {}
1912
1913 /**
1914 * Render element in static mode.
1915 *
1916 * If not inherent will call the base render.
1917 */
1918 protected function render_static() {
1919 $this->render();
1920 }
1921
1922 /**
1923 * Determine the render logic.
1924 */
1925 protected function render_by_mode() {
1926 if ( Plugin::$instance->frontend->is_static_render_mode() ) {
1927 $this->render_static();
1928
1929 return;
1930 }
1931
1932 $this->render();
1933 }
1934
1935 /**
1936 * Print content template.
1937 *
1938 * Used to generate the content template on the editor, using a
1939 * Backbone JavaScript template.
1940 *
1941 * @access protected
1942 * @since 2.0.0
1943 *
1944 * @param string $template_content Template content.
1945 */
1946 protected function print_template_content( $template_content ) {
1947 echo $template_content; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
1948 }
1949
1950 /**
1951 * Render element output in the editor.
1952 *
1953 * Used to generate the live preview, using a Backbone JavaScript template.
1954 *
1955 * @since 2.9.0
1956 * @access protected
1957 */
1958 protected function content_template() {}
1959
1960 /**
1961 * Render element output in the editor.
1962 *
1963 * Used to generate the live preview, using a Backbone JavaScript template.
1964 *
1965 * @since 2.0.0
1966 * @deprecated 2.9.0 use `content_template()` instead
1967 * @access protected
1968 */
1969 protected function _content_template() {
1970 Plugin::$instance->modules_manager->get_modules( 'dev-tools' )->deprecation->deprecated_function( __METHOD__, '2.9.0', __CLASS__ . '::content_template()' );
1971
1972 $this->content_template();
1973 }
1974
1975 /**
1976 * Initialize controls.
1977 *
1978 * Register the all controls added by `register_controls()`.
1979 *
1980 * @since 2.0.0
1981 * @access protected
1982 */
1983 protected function init_controls() {
1984 Plugin::$instance->controls_manager->open_stack( $this );
1985
1986 // TODO: This is for backwards compatibility starting from 2.9.0
1987 // This `if` statement should be removed when the method is removed
1988 if ( $this->has_own_method( '_register_controls', self::class ) ) {
1989 Plugin::$instance->modules_manager->get_modules( 'dev-tools' )->deprecation->deprecated_function( '_register_controls', '3.1.0', __CLASS__ . '::register_controls()' );
1990
1991 $this->_register_controls();
1992 } else {
1993 $this->register_controls();
1994 }
1995 }
1996
1997 protected function handle_control_position( array $args, $control_id, $overwrite ) {
1998 if ( isset( $args['type'] ) && in_array( $args['type'], [ Controls_Manager::SECTION, Controls_Manager::WP_WIDGET ], true ) ) {
1999 return $args;
2000 }
2001
2002 $target_section_args = $this->current_section;
2003
2004 $target_tab = $this->current_tab;
2005
2006 if ( $this->injection_point ) {
2007 $target_section_args = $this->injection_point['section'];
2008
2009 if ( ! empty( $this->injection_point['tab'] ) ) {
2010 $target_tab = $this->injection_point['tab'];
2011 }
2012 }
2013
2014 if ( null !== $target_section_args ) {
2015 if ( ! empty( $args['section'] ) || ! empty( $args['tab'] ) ) {
2016 _doing_it_wrong( sprintf( '%s::%s', get_called_class(), __FUNCTION__ ), sprintf( 'Cannot redeclare control with `tab` or `section` args inside section "%s".', $control_id ), '1.0.0' ); // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
2017 }
2018
2019 $args = array_replace_recursive( $target_section_args, $args );
2020
2021 if ( null !== $target_tab ) {
2022 $args = array_replace_recursive( $target_tab, $args );
2023 }
2024 } elseif ( empty( $args['section'] ) && ( ! $overwrite || is_wp_error( Plugin::$instance->controls_manager->get_control_from_stack( $this->get_unique_name(), $control_id ) ) ) ) {
2025 wp_die( sprintf( '%s::%s: Cannot add a control outside of a section (use `start_controls_section`).', get_called_class(), __FUNCTION__ ) ); // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
2026 }
2027
2028 return $args;
2029 }
2030
2031 /**
2032 * Initialize the class.
2033 *
2034 * Set the raw data, the ID and the parsed settings.
2035 *
2036 * @since 2.9.0
2037 * @access protected
2038 *
2039 * @param array $data Initial data.
2040 */
2041 protected function init( $data ) {
2042 $this->data = array_merge( $this->get_default_data(), $data );
2043
2044 $this->id = $data['id'];
2045 }
2046
2047 /**
2048 * Initialize the class.
2049 *
2050 * Set the raw data, the ID and the parsed settings.
2051 *
2052 * @since 1.4.0
2053 * @deprecated 2.9.0 use `init()` instead
2054 * @access protected
2055 *
2056 * @param array $data Initial data.
2057 */
2058 protected function _init( $data ) {
2059 Plugin::$instance->modules_manager->get_modules( 'dev-tools' )->deprecation->deprecated_function( __METHOD__, '2.9.0', __CLASS__ . '::init()' );
2060
2061 $this->init( $data );
2062 }
2063
2064 /**
2065 * Sanitize initial data.
2066 *
2067 * Performs settings cleaning and sanitization.
2068 *
2069 * @since 2.1.5
2070 * @access private
2071 *
2072 * @param array $settings Settings to sanitize.
2073 * @param array $controls Optional. An array of controls. Default is an
2074 * empty array.
2075 *
2076 * @return array Sanitized settings.
2077 */
2078 private function sanitize_settings( array $settings, array $controls = [] ) {
2079 if ( ! $controls ) {
2080 $controls = $this->get_controls();
2081 }
2082
2083 foreach ( $controls as $control ) {
2084 $control_obj = Plugin::$instance->controls_manager->get_control( $control['type'] );
2085
2086 if ( $control_obj instanceof Control_Repeater ) {
2087 if ( empty( $settings[ $control['name'] ] ) ) {
2088 continue;
2089 }
2090
2091 foreach ( $settings[ $control['name'] ] as $index => $repeater_row_data ) {
2092 $sanitized_row_data = $this->sanitize_settings( $repeater_row_data, $control['fields'] );
2093
2094 $settings[ $control['name'] ][ $index ] = $sanitized_row_data;
2095 }
2096
2097 continue;
2098 }
2099
2100 $is_dynamic = isset( $settings[ Manager::DYNAMIC_SETTING_KEY ][ $control['name'] ] );
2101
2102 if ( ! $is_dynamic ) {
2103 continue;
2104 }
2105
2106 $value_to_check = $settings[ Manager::DYNAMIC_SETTING_KEY ][ $control['name'] ];
2107
2108 $tag_text_data = Plugin::$instance->dynamic_tags->tag_text_to_tag_data( $value_to_check );
2109
2110 if ( ! Plugin::$instance->dynamic_tags->get_tag_info( $tag_text_data['name'] ) ) {
2111 unset( $settings[ Manager::DYNAMIC_SETTING_KEY ][ $control['name'] ] );
2112 }
2113 }
2114
2115 return $settings;
2116 }
2117
2118 /**
2119 * Controls stack constructor.
2120 *
2121 * Initializing the control stack class using `$data`. The `$data` is required
2122 * for a normal instance. It is optional only for internal `type instance`.
2123 *
2124 * @since 1.4.0
2125 * @access public
2126 *
2127 * @param array $data Optional. Control stack data. Default is an empty array.
2128 */
2129 public function __construct( array $data = [] ) {
2130 if ( $data ) {
2131 // TODO: This is for backwards compatibility starting from 2.9.0
2132 // This if statement should be removed when the method is hard-deprecated
2133 if ( $this->has_own_method( '_init', self::class ) ) {
2134 Plugin::$instance->modules_manager->get_modules( 'dev-tools' )->deprecation->deprecated_function( '_init', '2.9.0', __CLASS__ . '::init()' );
2135
2136 $this->_init( $data );
2137 } else {
2138 $this->init( $data );
2139 }
2140 }
2141 }
2142 }
2143