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

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