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

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