balmet.com

Unnamed repository; edit this file 'description' to name the repository.
Log | Files | Refs

controls-stack.php (64600B)


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