if ( '' !== $instance_value ) {
$parent = isset( $controls[ $parent ]['parent'] ) ? $controls[ $parent ]['parent'] : false;
* If the $condition_value is a non empty array - check if the $condition_value contains the $instance_value,
* If the $instance_value is a non empty array - check if the $instance_value contains the $condition_value
* otherwise check if they are equal. ( and give the ability to check if the value is an empty array )
if ( is_array( $condition_value ) && ! empty( $condition_value ) ) {
$is_contains = in_array( $instance_value, $condition_value, true );
} elseif ( is_array( $instance_value ) && ! empty( $instance_value ) ) {
$is_contains = in_array( $condition_value, $instance_value, true );
$is_contains = $instance_value === $condition_value;
( $is_negative_condition && $is_contains ) ||
( ! $is_negative_condition && ! $is_contains )
* Start controls section.
* Used to add a new section of controls. When you use this method, all the
* registered controls from this point will be assigned to this section,
* until you close the section using `end_controls_section()` method.
* This method should be used inside `register_controls()`.
* @param string $section_id Section ID.
* @param array $args Section arguments Optional.
public function start_controls_section( $section_id, array $args = [] ) {
$stack_name = $this->get_name();
* Fires before Elementor section starts in the editor panel.
* @param Controls_Stack $this The control.
* @param string $section_id Section ID.
* @param array $args Section arguments.
do_action( 'elementor/element/before_section_start', $this, $section_id, $args );
* Fires before Elementor section starts in the editor panel.
* The dynamic portions of the hook name, `$stack_name` and `$section_id`, refers to the stack name and section ID, respectively.
* @param Controls_Stack $this The control.
* @param array $args Section arguments.
do_action( "elementor/element/{$stack_name}/{$section_id}/before_section_start", $this, $args );
if ( $this->should_manually_trigger_common_action( $stack_name ) ) {
do_action( "elementor/element/common/{$section_id}/before_section_start", $this, $args );
$args['type'] = Controls_Manager::SECTION;
$this->add_control( $section_id, $args );
if ( null !== $this->current_section ) {
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
$this->current_section = $this->get_section_args( $section_id );
if ( $this->injection_point ) {
$this->injection_point['section'] = $this->current_section;
* Fires after Elementor section starts in the editor panel.
* @param Controls_Stack $this The control.
* @param string $section_id Section ID.
* @param array $args Section arguments.
do_action( 'elementor/element/after_section_start', $this, $section_id, $args );
* Fires after Elementor section starts in the editor panel.
* The dynamic portions of the hook name, `$stack_name` and `$section_id`, refers to the stack name and section ID, respectively.
* @param Controls_Stack $this The control.
* @param array $args Section arguments.
do_action( "elementor/element/{$stack_name}/{$section_id}/after_section_start", $this, $args );
if ( $this->should_manually_trigger_common_action( $stack_name ) ) {
do_action( "elementor/element/common/{$section_id}/after_section_start", $this, $args );
* Used to close an existing open controls section. When you use this method
* it stops adding new controls to this section.
* This method should be used inside `register_controls()`.
public function end_controls_section() {
$stack_name = $this->get_name();
// Save the current section for the action.
$current_section = $this->current_section;
$section_id = $current_section['section'];
'tab' => $current_section['tab'],
* Fires before Elementor section ends in the editor panel.
* @param Controls_Stack $this The control.
* @param string $section_id Section ID.
* @param array $args Section arguments.
do_action( 'elementor/element/before_section_end', $this, $section_id, $args );
* Fires before Elementor section ends in the editor panel.
* The dynamic portions of the hook name, `$stack_name` and `$section_id`, refers to the stack name and section ID, respectively.
* @param Controls_Stack $this The control.
* @param array $args Section arguments.
do_action( "elementor/element/{$stack_name}/{$section_id}/before_section_end", $this, $args );
if ( $this->should_manually_trigger_common_action( $stack_name ) ) {
do_action( "elementor/element/common/{$section_id}/before_section_end", $this, $args );
$this->current_section = null;
* Fires after Elementor section ends in the editor panel.
* @param Controls_Stack $this The control.
* @param string $section_id Section ID.
* @param array $args Section arguments.
do_action( 'elementor/element/after_section_end', $this, $section_id, $args );
* Fires after Elementor section ends in the editor panel.
* The dynamic portions of the hook name, `$stack_name` and `$section_id`, refers to the stack name and section ID, respectively.
* @param Controls_Stack $this The control.
* @param array $args Section arguments.
do_action( "elementor/element/{$stack_name}/{$section_id}/after_section_end", $this, $args );
if ( $this->should_manually_trigger_common_action( $stack_name ) ) {
do_action( "elementor/element/common/{$section_id}/after_section_end", $this, $args );
* Should manually trigger common action.
* With the Optimized Markup experiment, the Advanced Tab has been split to maintain backward compatibility:
* - 'common' refers to the existing Advanced Tab.
* - 'common-optimized' refers to the new Advanced Tab for optimized widgets.
* Third-party developers may have used hooks like 'elementor/element/common/_section_background/before_section_end'
* to add controls to the Advanced Tab. However, this hook will now only work on widgets that are not optimized.
* This method checks whether the 'elementor/element/common/...' hooks should be manually executed
* to prevent third parties from needing to add equivalent hooks for 'elementor/element/common-optimized/...'.
* @todo Remove this method and the manual execution of 'common' hooks when the feature is merged.
* @param string $stack_name Stack name.
private function should_manually_trigger_common_action( $stack_name ): bool {
return 'common-optimized' === $stack_name && Plugin::$instance->experiments->is_feature_active( 'e_optimized_markup' );
* Used to add a new set of tabs inside a section. You should use this
* method before adding new individual tabs using `start_controls_tab()`.
* Each tab added after this point will be assigned to this group of tabs,
* until you close it using `end_controls_tabs()` method.
* This method should be used inside `register_controls()`.
* @param string $tabs_id Tabs ID.
* @param array $args Tabs arguments.
public function start_controls_tabs( $tabs_id, array $args = [] ) {
if ( null !== $this->current_tab ) {
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
$args['type'] = Controls_Manager::TABS;
$this->add_control( $tabs_id, $args );
'tabs_wrapper' => $tabs_id,
foreach ( [ 'condition', 'conditions' ] as $key ) {
if ( ! empty( $args[ $key ] ) ) {
$this->current_tab[ $key ] = $args[ $key ];
if ( $this->injection_point ) {
$this->injection_point['tab'] = $this->current_tab;
* Used to close an existing open controls tabs. When you use this method it
* stops adding new controls to this tabs.
* This method should be used inside `register_controls()`.
public function end_controls_tabs() {
$this->current_tab = null;
* Used to add a new tab inside a group of tabs. Use this method before
* adding new individual tabs using `start_controls_tab()`.
* Each tab added after this point will be assigned to this group of tabs,
* until you close it using `end_controls_tab()` method.
* This method should be used inside `register_controls()`.
* @param string $tab_id Tab ID.
* @param array $args Tab arguments.
public function start_controls_tab( $tab_id, $args ) {
if ( ! empty( $this->current_tab['inner_tab'] ) ) {
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
$args['type'] = Controls_Manager::TAB;
$args['tabs_wrapper'] = $this->current_tab['tabs_wrapper'];
$this->add_control( $tab_id, $args );
$this->current_tab['inner_tab'] = $tab_id;
if ( $this->injection_point ) {
$this->injection_point['tab']['inner_tab'] = $this->current_tab['inner_tab'];
* Used to close an existing open controls tab. When you use this method it
* stops adding new controls to this tab.
* This method should be used inside `register_controls()`.
public function end_controls_tab() {
unset( $this->current_tab['inner_tab'] );
* Used to add a new set of controls in a popover. When you use this method,
* all the registered controls from this point will be assigned to this
* popover, until you close the popover using `end_popover()` method.
* This method should be used inside `register_controls()`.
final public function start_popover() {
$this->current_popover = [
* Used to close an existing open popover. When you use this method it stops
* adding new controls to this popover.
* This method should be used inside `register_controls()`.
final public function end_popover() {
$this->current_popover = null;
$last_control_key = $this->get_control_key( $this->get_pointer_index() - 1 );
$this->update_control( $last_control_key, $args, $options );
* Used to add attributes to a specific HTML element.
* The HTML tag is represented by the element parameter, then you need to
* define the attribute key and the attribute key. The final result will be:
* `<element attribute_key="attribute_value">`.
* `$this->add_render_attribute( 'wrapper', 'class', 'custom-widget-wrapper-class' );`
* `$this->add_render_attribute( 'widget', 'id', 'custom-widget-id' );`
* `$this->add_render_attribute( 'button', [ 'class' => 'custom-button-class', 'id' => 'custom-button-id' ] );`
* @param array|string $element The HTML element.
* @param array|string $key Optional. Attribute key. Default is null.
* @param array|string $value Optional. Attribute value. Default is null.
* @param bool $overwrite Optional. Whether to overwrite existing
* attribute. Default is false, not to overwrite.
* @return self Current instance of the element.
public function add_render_attribute( $element, $key = null, $value = null, $overwrite = false ) {
if ( is_array( $element ) ) {
foreach ( $element as $element_key => $attributes ) {
$this->add_render_attribute( $element_key, $attributes, null, $overwrite );
if ( is_array( $key ) ) {
foreach ( $key as $attribute_key => $attributes ) {
$this->add_render_attribute( $element, $attribute_key, $attributes, $overwrite );
if ( empty( $this->render_attributes[ $element ][ $key ] ) ) {
$this->render_attributes[ $element ][ $key ] = [];
settype( $value, 'array' );
$this->render_attributes[ $element ][ $key ] = $value;
$this->render_attributes[ $element ][ $key ] = array_merge( $this->render_attributes[ $element ][ $key ], $value );
* Used to retrieve render attribute.
* The returned array is either all elements and their attributes if no `$element` is specified, an array of all
* attributes of a specific element or a specific attribute properties if `$key` is specified.
* Returns null if one of the requested parameters isn't set.
public function get_render_attributes( $element = '', $key = '' ) {
$attributes = $this->render_attributes;
if ( ! isset( $attributes[ $element ] ) ) {
$attributes = $attributes[ $element ];
if ( ! isset( $attributes[ $key ] ) ) {
$attributes = $attributes[ $key ];
* Used to set the value of the HTML element render attribute or to update
* an existing render attribute.
* @param array|string $element The HTML element.
* @param array|string $key Optional. Attribute key. Default is null.
* @param array|string $value Optional. Attribute value. Default is null.