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

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