: str_replace(): Passing null to parameter #2 ($replace) of type array|string is deprecated in
* @param string $parent_address [description]
* @param string $global_parent [description]
* @param string $global_parent_type [description]
* @return string The module's HTML output.
function _render( $attrs, $content = null, $render_slug, $parent_address = '', $global_parent = '', $global_parent_type = '', $parent_type = '' ) {
global $et_fb_processing_shortcode_object, $et_pb_current_parent_type, $et_pb_parent_section_type;
if ( $this->is_rendering ) {
// Every module instance is a singleton so the TB Post Content module
// can cause a section, row and/or column to call _render() multiple
// times - once for each respective shortcode found in the content
// rendered by the Post Content module.
// Since this _render() method changes object state this leads to
// props being messed up between renders so we have to clone the
// base instance every time we try to render while the base
// instance is still rendering.
$clone->is_rendering = false;
// @phpcs:ignore Generic.PHP.ForbiddenFunctions.Found
return call_user_func_array( array( $clone, '_render' ), func_get_args() );
$this->_maybe_rebuild_option_template();
$attrs = $this->_maybe_add_global_presets_settings( $attrs, $render_slug );
// Use the current layout or post ID for AB testing. This is not guaranteed to be the real
// current post ID if we are rendering a TB layout.
$post_interference = self::_should_respect_post_interference();
$post_id = apply_filters( 'et_is_ab_testing_active_post_id', self::get_layout_id() );
$is_main_post = $this->get_the_ID() === $post_id;
if ( ! $post_interference ) {
ET_Post_Stack::replace( ET_Post_Stack::get_main_post() );
$enabled_dynamic_attributes = $this->_get_enabled_dynamic_attributes( $attrs );
$attrs = $this->_encode_legacy_dynamic_content( $attrs, $enabled_dynamic_attributes );
$this->attrs_unprocessed = $attrs;
$attrs = $this->process_dynamic_attrs( $attrs );
$this->props = shortcode_atts( $this->resolve_conditional_defaults($attrs, $render_slug), $attrs );
$this->_decode_double_quotes( $enabled_dynamic_attributes, $et_fb_processing_shortcode_object );
$this->_maybe_remove_global_default_values_from_props();
// Some module items need to inherit value from its module parent
// This inheritance needs to be done before migration to make it compatible with migration process
$this->maybe_inherit_values();
$_address = $this->generate_element_address( $render_slug );
* @param array $props Array of processed props.
* @param array $attrs Array of original shortcode attrs
* @param string $slug Module slug
* @param string $_address Module Address
* @param string $content Module content
$this->props = apply_filters( 'et_pb_module_shortcode_attributes', $this->props, $attrs, $render_slug, $_address, $content );
$ab_testing_enabled = et_is_ab_testing_active( $post_id );
$hide_subject_module_cached = $hide_subject_module = false;
$global_module_id = $this->props['global_module'];
// If the section/row/module is disabled, hide it
if ( isset( $this->props['disabled'] ) && 'on' === $this->props['disabled'] && ! $et_fb_processing_shortcode_object ) {
if ( ! $post_interference ) {
ET_Post_Stack::restore();
// need to perform additional check and some modifications in case AB testing enabled
// skip for VB since it's handled on VB side.
if ( $ab_testing_enabled && ( ! $is_main_post || ! $et_fb_processing_shortcode_object ) ) {
// check if ab testing enabled for this module and if it shouldn't be displayed currently
$hide_subject_module = ( ! $is_main_post || ! $et_fb_processing_shortcode_object ) && ! $this->_is_display_module( $this->props ) && ! et_pb_detect_cache_plugins();
// add class to the AB testing subject if needed
if ( isset( $this->props['ab_subject_id'] ) && '' !== $this->props['ab_subject_id'] ) {
$subject_class = sprintf( ' et_pb_ab_subject et_pb_ab_subject_id-%1$s_%2$s',
esc_attr( $this->props['ab_subject_id'] )
$this->props['module_class'] = isset( $this->props['module_class'] ) && '' !== $this->props['module_class'] ? $this->props['module_class'] . $subject_class : $subject_class;
if ( et_pb_detect_cache_plugins() ) {
$hide_subject_module_cached = true;
// add class to the AB testing goal if needed
if ( isset( $this->props['ab_goal'] ) && 'on' === $this->props['ab_goal'] ) {
$goal_class = sprintf( ' et_pb_ab_goal et_pb_ab_goal_id-%1$s', esc_attr( $post_id ) );
$this->props['module_class'] = isset( $this->props['module_class'] ) && '' !== $this->props['module_class'] ? $this->props['module_class'] . $goal_class : $goal_class;
//override module attributes for global module. Skip that step while processing Frontend Builder object
if ( ! empty( $global_module_id ) && ! $et_fb_processing_shortcode_object ) {
// Update render_slug when rendering global rows inside Specialty sections.
$render_slug = 'et_pb_specialty_column' === $et_pb_current_parent_type && 'et_pb_row' === $render_slug ? 'et_pb_row_inner' : $render_slug;
$global_module_data = et_pb_load_global_module( $global_module_id, $render_slug );
if ( '' !== $global_module_data ) {
$unsynced_global_attributes = get_post_meta( $global_module_id, '_et_pb_excluded_global_options' );
$use_updated_global_sync_method = ! empty( $unsynced_global_attributes );
$unsynced_options = ! empty( $unsynced_global_attributes[0] ) ? json_decode( $unsynced_global_attributes[0], true ) : array();
$content_synced = $use_updated_global_sync_method && ! in_array( 'et_pb_content_field', $unsynced_options );
// support legacy selective sync system
if ( ! $use_updated_global_sync_method ) {
$content_synced = ! isset( $this->props['saved_tabs'] ) || false !== strpos( $this->props['saved_tabs'], 'general' ) || 'all' === $this->props['saved_tabs'];
// Set the flag showing if we load inner row
$load_inner_row = 'et_pb_row_inner' === $render_slug;
$global_content = et_pb_get_global_module_content( $global_module_data, $render_slug, $load_inner_row );
// cleanup the shortcode string to avoid the attributes messing with content
$global_content_processed = false !== $global_content ? str_replace( $global_content, '', $global_module_data ) : $global_module_data;
$global_atts = shortcode_parse_atts( et_pb_remove_shortcode_content( $global_content_processed, $this->slug ) );
$global_atts = $this->_encode_legacy_dynamic_content( $global_atts, $enabled_dynamic_attributes );
// Additional content processing required for Code Modules.
if ( in_array( $render_slug, array( 'et_pb_code', 'et_pb_fullwidth_code' ) ) ) {
$global_content_processed = _et_pb_code_module_prep_content( $global_content_processed );
// reset module addresses because global items will be processed once again and address will be incremented wrongly
if ( false !== strpos( $render_slug, '_section' ) ) {
self::_set_index( self::INDEX_SECTION, self::_get_index( self::INDEX_SECTION ) - 1 );
self::_set_index( self::INDEX_ROW, -1 );
self::_set_index( self::INDEX_ROW_INNER, -1 );
self::_set_index( self::INDEX_COLUMN, -1 );
self::_set_index( self::INDEX_COLUMN_INNER, -1 );
self::_set_index( self::INDEX_MODULE, -1 );
self::_set_index( self::INDEX_MODULE_ITEM, -1 );
} else if ( false !== strpos( $render_slug, '_row_inner' ) ) {
self::_set_index( self::INDEX_ROW, self::_get_index( self::INDEX_ROW ) - 1 );
self::_set_index( self::INDEX_COLUMN_INNER, -1 );
self::_set_index( self::INDEX_MODULE, -1 );
self::_set_index( self::INDEX_MODULE_ITEM, -1 );
} else if ( false !== strpos( $render_slug, '_row' ) ) {
self::_set_index( self::INDEX_ROW, self::_get_index( self::INDEX_ROW ) - 1 );
self::_set_index( self::INDEX_COLUMN, -1 );
self::_set_index( self::INDEX_MODULE, -1 );
self::_set_index( self::INDEX_MODULE_ITEM, -1 );
self::_set_index( self::INDEX_MODULE, self::_get_index( self::INDEX_MODULE ) - 1 );
self::_set_index( self::INDEX_MODULE_ITEM, -1 );
// Always unsync 'next_background_color' and 'prev_background_color' options for global sections
// They should be dynamic and reflect color of top and bottom sections
if ( $render_slug === 'et_pb_section' ) {
$unsynced_options = array_merge( $unsynced_options, array( 'next_background_color', 'prev_background_color' ) );
foreach( $this->props as $single_attr => $value ) {
if ( isset( $global_atts[$single_attr] ) && ! in_array( $single_attr, $unsynced_options ) ) {
// replace %22 with double quotes in options to make sure it's rendered correctly
$this->props[ $single_attr] = is_string( $global_atts[ $single_attr] ) && ! array_intersect( array( "et_pb_{$single_attr}", $single_attr ), $this->dbl_quote_exception_options ) ? str_replace( '%22', '"', $global_atts[ $single_attr] ) : $global_atts[ $single_attr];
$this->props = $this->process_dynamic_attrs( $this->props );
$this->_decode_double_quotes( array(), $et_fb_processing_shortcode_object );
self::set_order_class( $render_slug );
$this->content_unprocessed = $this->_encode_legacy_dynamic_content_value(
false !== $global_content ? $global_content : $content,
$enabled_dynamic_attributes
$content = $this->_resolve_value(
$this->content_unprocessed,
$enabled_dynamic_attributes,
$et_fb_processing_shortcode_object
// Process scroll effects earlier to preserve the modules hierarchy during processing.
$this->process_scroll_effects( $render_slug );
$content = apply_filters( 'et_pb_module_content', $content, $this->props, $attrs, $render_slug, $_address, $global_content );
// Set empty TinyMCE content '<br /><br />' as empty string.
if ( 'ltbrgtbr' === preg_replace( '/[^a-z]/', '', $content ) ) {
if ( $et_fb_processing_shortcode_object ) {
$this->content = et_pb_fix_shortcodes( $content, $this->use_raw_content );
// Line breaks should be converted before do_shortcode to avoid legit rendered shortcode
// line breaks being trimmed into one line and causing issue like broken javascript code
if ( $this->use_raw_content ) {
$content = et_builder_convert_line_breaks( et_builder_replace_code_content_entities( $content ) );
if( ! ( isset( $this->is_structure_element ) && $this->is_structure_element ) ) {
$content = et_pb_fix_shortcodes( $content, $this->use_raw_content );
$content = et_maybe_enable_embed_shortcode( $content, true );
$this->content = do_shortcode( $content );
$this->props['content'] = $this->content;
// Restart classname on shortcode callback. Module class is only called once, not on every
// shortcode module appearance. Thus classname construction need to be restarted on each
$this->classname = array();
if ( method_exists( $this, 'shortcode_atts' ) ) {
// Deprecated. Do not use this!
$this->process_additional_options( $render_slug );
$this->process_custom_css_fields( $render_slug );
// load inline fonts if needed
if ( isset( $this->props['inline_fonts'] ) ) {
$this->process_inline_fonts_option( $this->props['inline_fonts'] );
// Automatically add slug as classname for module that uses other module's shortcode callback
// This has to be added first because some classname is position-sensitive and used for
// JS-based calculation (i.e. .et_pb_column in column inner)
if ( $this->slug !== $render_slug ) {
$this->add_classname( $this->slug );
// Apply classnames added to the module that uses other module's shortcode callback
// (i.e. `process_additional_options` for the column inner)
$module = self::get_module( $render_slug, $this->get_post_type() );
$this->add_classname( $module->classname );
// Automatically add default classnames
$this->add_classname( array(
ET_Builder_Element::get_module_order_class( $render_slug ),
// Automatically added user-defined classname if there's any
if ( isset( $this->props['module_class'] ) && '' !== $this->props['module_class'] ) {
$this->add_classname( explode( ' ', $this->props['module_class'] ) );
$animation_style = isset( $this->props['animation_style'] ) && '' !== $this->props['animation_style'] ? $this->props['animation_style'] : false;
$animation_repeat = isset( $this->props['animation_repeat'] ) && '' !== $this->props['animation_repeat'] ? $this->props['animation_repeat'] : 'once';
$animation_direction = isset( $this->props['animation_direction'] ) && '' !== $this->props['animation_direction'] ? $this->props['animation_direction'] : 'center';
$animation_duration = isset( $this->props['animation_duration'] ) && '' !== $this->props['animation_duration'] ? $this->props['animation_duration'] : '500ms';
$animation_delay = isset( $this->props['animation_delay'] ) && '' !== $this->props['animation_delay'] ? $this->props['animation_delay'] : '0ms';
$animation_intensity = isset( $this->props["animation_intensity_{$animation_style }"] ) && '' !== $this->props["animation_intensity_{$animation_style }"] ? $this->props["animation_intensity_{$animation_style }"] : '50%';
$animation_starting_opacity = isset( $this->props['animation_starting_opacity'] ) && '' !== $this->props['animation_starting_opacity'] ? $this->props['animation_starting_opacity'] : '0%';
$animation_speed_curve = isset( $this->props['animation_speed_curve'] ) && '' !== $this->props['animation_speed_curve'] ? $this->props['animation_speed_curve'] : 'ease-in-out';
// Animation style and direction values for Tablet & Phone. Basically, style for tablet and
// phone are same with the desktop because we only edit responsive settings for the affected
// fields under animation style. Variable $animation_style_responsive need to be kept as
// unmodified variable because it will be used by animation intensity.
$animation_style_responsive = $animation_style;
$animation_style_tablet = $animation_style;
$animation_style_phone = $animation_style;
$animation_direction_tablet = et_pb_responsive_options()->get_any_value( $this->props, 'animation_direction_tablet' );
$animation_direction_phone = et_pb_responsive_options()->get_any_value( $this->props, 'animation_direction_phone' );
// Check if this is an AJAX request since this is how VB loads the initial module data
// et_core_is_fb_enabled() always returns `false` here
if ( $animation_style && 'none' !== $animation_style && ! wp_doing_ajax() ) {
$transformedAnimations = array(
// Fade doesn't have direction
if ( 'fade' === $animation_style ) {
$animation_direction_tablet = '';
$animation_direction_phone = '';
$directions_list = array( 'top', 'right', 'bottom', 'left' );
if ( in_array( $animation_direction, $directions_list ) ) {
$animation_style .= ucfirst( $animation_direction );
// avoid custom animation on button because animation is applied to the wrapper so transforms do not need to combine.
if ( 'et_pb_button' !== $render_slug ) {
foreach ( preg_grep( '/(transform_)/', array_keys( $this->props ) ) as $index => $key ) {
if ( strpos( $key, 'link' ) !== false || strpos( $key, 'hover' ) !== false || strpos( $key, 'last_edited' ) !== false ) {
if ( ! empty( $this->props[ $key ] ) ) {
if ( ! $transformedAnimations['desktop'] && strpos( $key, 'tablet' ) === false && strpos( $key, 'phone' ) === false ) {
$transformedAnimations['desktop'] = true;
$transformedAnimations['tablet'] = true;
$transformedAnimations['phone'] = true;
} else if ( ! $transformedAnimations['tablet'] && strpos( $key, 'tablet' ) !== false ) {
$transformedAnimations['tablet'] = true;
$transformedAnimations['phone'] = true;
} else if ( ! $transformedAnimations['phone'] && strpos( $key, 'phone' ) !== false ) {
$transformedAnimations['phone'] = true;
if ( $transformedAnimations['desktop'] && $transformedAnimations['tablet'] && $transformedAnimations['phone'] ) {
$module_class = ET_Builder_Element::get_module_order_class( $render_slug );
// Desktop animation data.
'class' => esc_attr( trim( $module_class ) ),
'style' => esc_html( $animation_style ),
'repeat' => esc_html( $animation_repeat ),
'duration' => esc_html( $animation_duration ),
'delay' => esc_html( $animation_delay ),
'intensity' => esc_html( $animation_intensity ),
'starting_opacity' => esc_html( $animation_starting_opacity ),
'speed_curve' => esc_html( $animation_speed_curve ),
// Being save to generate Tablet & Phone data attributes. As default, tablet
// default value will inherit desktop value and phone default value will inherit
// tablet value. Ensure to pass the value only if it's different compared to
// desktop value to avoid duplicate values.
$animation_attributes = array(
'repeat' => 'animation_repeat',
'duration' => 'animation_duration',
'delay' => 'animation_delay',
'intensity' => "animation_intensity_{$animation_style_responsive}",
'starting_opacity' => 'animation_starting_opacity',
'speed_curve' => 'animation_speed_curve',
foreach ( $animation_attributes as $animation_key => $animation_attribute ) {
$animation_attribute_tablet = '';
$animation_attribute_phone = '';
// Ensure responsive status for current attribute is activated.
if ( ! et_pb_responsive_options()->is_responsive_enabled( $this->props, $animation_attribute ) ) {
// Tablet animation value.
$animation_attribute_tablet = et_pb_responsive_options()->get_any_value( $this->props, "{$animation_attribute}_tablet", $animation_data[ $animation_key ] );
if ( ! empty( $animation_attribute_tablet ) ) {
$animation_data["{$animation_key}_tablet"] = $animation_attribute_tablet;
// Phone animation value.
$animation_attribute_phone = et_pb_responsive_options()->get_any_value( $this->props, "{$animation_attribute}_phone", $animation_data[ $animation_key ] );
if ( ! empty( $animation_attribute_phone ) ) {
$animation_data["{$animation_key}_phone"] = $animation_attribute_phone;
// Animation style is little bit different. We need to check the direction to get
// the correct style. We need to ensure the direction is valid, then add it as
// suffix for the animation style.
if ( et_pb_responsive_options()->is_responsive_enabled( $this->props, 'animation_direction' ) ) {
// Tablet animation style.
if ( ! empty( $animation_direction_tablet ) ) {
$animation_style_tablet_suffix = in_array( $animation_direction_tablet, $directions_list ) ? ucfirst( $animation_direction_tablet ) : '';
$animation_data['style_tablet'] = $animation_style_tablet . $animation_style_tablet_suffix;
// Phone animation style.
if ( ! empty( $animation_direction_phone ) ) {
$animation_style_phone_suffix = in_array( $animation_direction_phone, $directions_list ) ? ucfirst( $animation_direction_phone ) : '';
$animation_data['style_phone'] = $animation_style_phone . $animation_style_phone_suffix;
} else if ( ! empty( $animation_data['style_tablet'] ) ) {
$animation_data['style_phone'] = $animation_data['style_tablet'];
// overwrite animation name to match the custom animation generated on transforms options processing.
if ( $transformedAnimations['desktop'] ) {
$animation_data['style'] = 'transformAnim';
if ( $transformedAnimations['tablet'] ) {
$animation_data['style_tablet'] = 'transformAnim';
if ( $transformedAnimations['phone'] ) {
$animation_data['style_phone'] = 'transformAnim';
et_builder_handle_animation_data( $animation_data );
// Try to apply old method for plugins without vb support
if ( ! $et_fb_processing_shortcode_object && 'on' !== $this->vb_support ) {
add_filter( "{$render_slug}_shortcode_output", array( $this, 'add_et_animated_class' ), 10, 2 );
// Only print et_animated on front-end. Avoid adding it on computed callback of post slider(s)
// and modules because it'll cause the module to be visually hidden
if ( ! et_core_is_fb_enabled() ) {
$this->add_classname( 'et_animated' );
// Add "et_hover_enabled" class to elements that have at least one hover prop enabled
if ( et_has_hover_enabled( $this->props ) ) {
$this->add_classname( 'et_hover_enabled' );
$link_option_url = isset( $this->props['link_option_url'] ) ? $this->props['link_option_url'] : '';
$link_option_url_new_window = isset( $this->props['link_option_url_new_window'] ) ? $this->props['link_option_url_new_window'] : false;
if ( '' !== $link_option_url ) {
$module_class = ET_Builder_Element::get_module_order_class( $render_slug );
et_builder_handle_link_options_data( array(
'class' => trim( $module_class ),
'url' => esc_url_raw( $link_option_url ),
'target' => 'on' === $link_option_url_new_window ? '_blank' : '_self',
$this->add_classname( 'et_clickable' );
// Hide module on specific screens if needed
if ( isset( $this->props['disabled_on'] ) && '' !== $this->props['disabled_on'] ) {
$disabled_on_array = explode( '|', $this->props['disabled_on'] );
$current_media_query = 'max_width_767';
foreach( $disabled_on_array as $value ) {
// Added specific declaration to fix the problem when
// Video module is hidden for desktop the fullscreen
// won't work on mobile screen size.
$declaration = 'et_pb_video' === $render_slug ? 'height: 0; padding: 0; overflow: hidden;' : 'display: none !important;';
ET_Builder_Module::set_style( $render_slug, array(
'selector' => '%%order_class%%',
'declaration' => $declaration,
'media_query' => ET_Builder_Element::get_media_query( $current_media_query ),
$current_media_query = 1 === $i ? '768_980' : 'min_width_981';
if ( ! $et_fb_processing_shortcode_object ) {
if ( 'et_pb_section' === $render_slug ) {
$et_pb_current_parent_type = isset( $this->props['specialty'] ) && 'on' === $this->props['specialty'] ? 'et_pb_specialty_section' : 'et_pb_section';
$et_pb_parent_section_type = $et_pb_current_parent_type;
} else if ( 'et_pb_specialty_section' === $et_pb_current_parent_type && 'et_pb_column' === $render_slug ) {
$et_pb_current_parent_type = 'et_pb_specialty_column';
// Make sure content of Specialty Section is valid and has correct structure. Fix inner shortcode tags if needed.
if ( 'et_pb_specialty_section' === $et_pb_current_parent_type ) {
$content = $this->et_pb_maybe_fix_specialty_columns( $content );
$this->is_rendering = true;