PluginProbe
Elementor Website Builder – more than just a page builder / 3.19.0-dev3
Elementor Website Builder – more than just a page builder v3.19.0-dev3
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.19.0-dev3, at includes/base/controls-stack.php

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