: str_replace(): Passing null to parameter #2 ($replace) of type array|string is deprecated in
// Format FB component path
// TODO, move this to class method and property, and allow both to be overridden
$component_path = str_replace( 'et_pb_' , '', $function_name_processed );
$component_path = str_replace( '_', '-', $component_path );
$_i = isset( $atts['_i'] ) ? $atts['_i'] : 0;
$address = isset( $atts['_address'] ) ? $atts['_address'] : '0';
// set the global parent if exists
if ( ( ! isset( $attrs['global_module'] ) || '' === $attrs['global_module'] ) && '' !== $global_parent ) {
$attrs['global_parent'] = $global_parent;
if ( isset( $this->is_structure_element ) && $this->is_structure_element ) {
$this->vb_support = 'on';
$processed_content = false !== $global_content ? $global_content : $this->content;
// Determine the parent type to send it down the tree while processing shortcode
// Main purpose is to know when we rendering Specialty Section content.
if ( 'et_pb_section' === $render_slug ) {
$parent_type = isset( $attrs['specialty'] ) && 'on' === $attrs['specialty'] ? 'et_pb_specialty_section' : 'et_pb_section';
} else if ( 'et_pb_specialty_section' === $parent_type && 'et_pb_column' === $render_slug ) {
$parent_type = 'et_pb_specialty_column';
$parent_type = $render_slug;
// Make sure content of Specialty Section is valid and has correct structure. Fix inner shortcode tags if needed.
if ( 'et_pb_specialty_section' === $parent_type ) {
$processed_content = $this->et_pb_maybe_fix_specialty_columns( $processed_content );
$content = array_key_exists( 'content', $this->fields_unprocessed ) || 'et_pb_code' === $function_name_processed || 'et_pb_fullwidth_code' === $function_name_processed ? $processed_content : et_fb_process_shortcode( $processed_content, $address, $global_parent, $global_parent_type, $parent_type );
// Global Code module content should be decoded before passing to VB.
$is_global_code = in_array( $function_name_processed, array( 'et_pb_code', 'et_pb_fullwidth_code' ) );
$prepared_content = $content;
if ( ( ! is_array( $content ) && $this->vb_support !== 'on' && ! $this->has_line_breaks( $content ) ) || $is_global_code ) {
$prepared_content = html_entity_decode( $content, ENT_COMPAT, 'UTF-8' );
// Visual Builder expects $attrs to be an object.
// Associative array converted to an object by wp_json_encode correctly, but empty array is not and it causes issues.
$is_child_module = in_array( $render_slug, self::get_child_slugs( $this->get_post_type() ) ) && false === strpos( $render_slug, '_column_inner' ) && false === strpos( $render_slug, '_column' );
$module_type = $this->type;
$render_count = $is_child_module ? self::_get_index( self::INDEX_MODULE_ITEM ) : self::_get_index( array( self::INDEX_MODULE_ORDER, $function_name_processed ) );
$child_title_var = isset( $this->child_title_var ) ? $this->child_title_var : '';
$child_title_fallback_var = isset( $this->child_title_fallback_var ) ? $this->child_title_fallback_var : '';
$advanced_setting_title_text = isset( $this->advanced_setting_title_text ) ? $this->advanced_setting_title_text : '';
// If this is a shop module use the Shop module render count
// Shop module creates a new class instance which resets the $_render_count value
// ( see get_shop_html() method of ET_Builder_Module_Shop class in main-modules.php )
// so we use a static property to track its proper render count
if ( 'et_pb_shop' === $render_slug ) {
$render_count = self::$_shop_render_count;
self::$_shop_render_count++;
// Ensuring that module which uses another module's template (i.e. accordion item uses toggle's
// component) has correct values for class properties where it makes a difference. This is covered on front-end, but it causes inheriting
// module uses its template's value on render_as_builder_data()
if ( isset( $rendering_module, $rendering_module->type ) ) {
$module_type = $rendering_module->type;
$child_title_var = isset( $rendering_module->child_title_var ) ? $rendering_module->child_title_var : $child_title_var;
$child_title_fallback_var = isset( $rendering_module->child_title_fallback_var ) ? $rendering_module->child_title_fallback_var : $child_title_fallback_var;
$advanced_setting_title_text = isset( $rendering_module->advanced_setting_title_text ) ? $rendering_module->advanced_setting_title_text : $advanced_setting_title_text;
// TODO make address be _address, its conflicting with 'address' prop in map module... (not sure how though, they are in diffent places...)
'child_slug' => $this->child_slug,
'parent_slug' => $real_parent_type,
'vb_support' => $this->vb_support,
'parent_address' => $parent_address,
'shortcode_index' => $render_count,
'type' => $output_render_slug,
'theme_builder_suffix' => self::_get_theme_builder_order_class_suffix(),
'component_path' => $component_path,
'main_css_element' => $this->main_css_element,
'content' => $prepared_content,
'is_module_child' => 'child' === $module_type,
'is_structure_element' => !empty($this->is_structure_element),
'is_specialty_placeholder' => $is_specialty_placeholder,
'is_official_module' => $this->_is_official_module,
'child_title_var' => $child_title_var,
'child_title_fallback_var' => $child_title_fallback_var,
'advanced_setting_title_text' => $advanced_setting_title_text,
'wrapper_settings' => $this->get_wrapper_settings( $render_slug ),
if ( ! empty( $unsynced_global_attributes ) ) {
$object['unsyncedGlobalSettings'] = $unsynced_global_attributes[0];
if ( $is_global_template ) {
$object['libraryModuleScope'] = 'global';
if ( isset( $this->module_items_config ) ) {
$object['module_items_config'] = $this->module_items_config;
* Determine if provided string contain line-breaks (`\r\n`)
* @param string $content String to check
function has_line_breaks( $content ) {
return count( preg_split('/\r\n*\n/', trim( $content ), -1, PREG_SPLIT_NO_EMPTY ) ) > 1;
// intended to be overridden as needed
function additional_render( $attrs, $content = null, $render_slug ) {
if ( method_exists( $this, 'additional_shortcode_callback' ) ) {
// Backwards compatibility
$this->__call( 'additional_shortcode_callback', array( $attrs, $content, $render_slug ) );
// intended to be overridden as needed
function predefined_child_modules(){}
* Generate global setting name
* @param string $option_slug Option slug
* @return string Global setting name in the following format: "module_slug-option_slug"
public function get_global_setting_name( $option_slug ) {
$global_setting_name = sprintf(
isset( $this->global_settings_slug ) ? $this->global_settings_slug : $this->slug,
return $global_setting_name;
* Add global default values to all fields, if they don't have defaults set
protected function _maybe_add_global_defaults() {
// Don't add default settings to "child" modules
if ( 'child' === $this->type ) {
$fields = $this->fields_unprocessed;
// Font color settings have custom_color set to true, so add them to ignored keys array
if ( isset( $this->advanced_fields['fonts'] ) && is_array( $this->advanced_fields['fonts'] ) ) {
foreach ( $this->advanced_fields['fonts'] as $font_key => $font_settings ) {
$ignored_keys[] = sprintf( '%1$s_text_color', $font_key );
$ignored_keys = apply_filters( 'et_builder_add_defaults_ignored_keys', $ignored_keys );
foreach ( $fields as $field_key => $field_settings ) {
if ( in_array( $field_key, $ignored_keys ) ) {
$global_setting_name = $this->get_global_setting_name( $field_key );
$global_setting_value = ET_Global_Settings::get_value( $global_setting_name );
if ( ! isset( $field_settings['default'] ) && $global_setting_value ) {
$fields[ $field_key ]['default'] = $global_setting_value;
// Mark this default as global so VB won't print it to replicate FE behaviour
$fields[ $field_key ]['is_global_default'] = true;
$this->fields_unprocessed = $fields;
* Rebuild option template in $this->fields_unprocessed property into actual field if needed.
protected function _maybe_rebuild_option_template() {
// Once module's option template inside $this->fields_unprocessed is rebuilt, the next
// module's `_render()` won't need it. Thus, skip this to speed up performance.
if ( in_array( $this->slug, self::$_has_rebuilt_option_template ) ) {
foreach( $this->fields_unprocessed as $field_name => $field ) {
// If first two field name matches template prefix, it is safely assume that current
// unprocessed fields is reference to option template.
if ( self::$option_template->is_enabled() && self::$option_template->is_option_template_field( $field_name ) ) {
$rebuilt_fields = self::$option_template->rebuild_field_template( $field_name );
// Assign rebuilt fields to module's unprocessed fields
foreach( $rebuilt_fields as $rebuilt_field_name => $rebuilt_field ) {
$this->fields_unprocessed[ $rebuilt_field_name ] = $rebuilt_field;
// Remove option template field
unset( $this->fields_unprocessed[ $field_name ] );
self::$_has_rebuilt_option_template[] = $this->slug;
* Adds Global Presets settings.
* @param array $attrs The list of a module attributes
* @param string $render_slug The real slug from the shortcode
protected function _maybe_add_global_presets_settings( $attrs, $render_slug ) {
if ( ( et_fb_is_enabled() || et_builder_bfb_enabled() ) && ! self::is_theme_builder_layout() ) {
$render_slug = self::$global_presets_manager->maybe_convert_module_type( $render_slug, $attrs );
$module_preset_settings = self::$global_presets_manager->get_module_presets_settings( $render_slug, $attrs );
if ( is_array( $attrs ) ) {
// We need a special handler for social media child items module background color setting
// as it has different default background color for each network.
if ( 'et_pb_social_media_follow_network' === $render_slug
&& ! empty( $attrs['social_network'] )
&& ! empty( $attrs['background_color'] )
&& ! empty( $module_preset_settings['background_color'] )
&& $attrs['background_color'] !== $module_preset_settings['background_color']
$background_color_definition = self::$_->array_get( $this->get_fields(), "social_network.options.{$attrs['social_network']}.data.color" );
if ( $background_color_definition === $attrs['background_color'] ) {
// Unset the background color attrs if it was default based on selected network.
unset( $attrs['background_color'] );
return array_merge( $module_preset_settings, $attrs );
return $module_preset_settings;
* Add additional option fields.
* @since 3.23 Introduce form field options set. Also, add codes to generate responsive options
* set with suffix automatically. It also supports mobile_options on composite, bg
* field, and computed fields as well.
protected function _add_additional_fields() {
// Setup has_advanced_fields property to adjust advanced options visibility on
// module that has no VB support to avoid sudden advanced options appearances
$this->has_advanced_fields = isset( $this->advanced_fields );
// Advanced options are added by default unless module explicitly disabled it
$this->advanced_fields = $this->has_advanced_fields ? $this->advanced_fields : array();
// Advanced options have to be array
if ( ! is_array( $this->advanced_fields ) ) {
// Add form field options set to modules that use form as main part.
$this->_add_form_field_fields();
$this->_add_font_fields();
$this->_add_background_fields();
$this->_add_borders_fields();
$this->_add_button_fields();
$this->_add_box_shadow_fields();
$this->_add_transforms_fields();
$this->_add_position_fields();
$this->_add_text_fields();
$this->_add_sizing_fields();
$this->_add_overflow_fields();
$this->_add_margin_padding_fields();
// Add filter fields to modules
$this->_add_filter_fields();
// Add divider fields to section modules.
$this->_add_divider_fields();
// Add animation fields to all modules
$this->_add_animation_fields();
$this->_add_additional_transition_fields();
// Add text shadow fields to all modules
$this->_add_text_shadow_fields();
// Add link options to all modules
$this->_add_link_options_fields();
$this->_add_scroll_effects_fields();
if ( ! isset( $this->_additional_fields_options ) ) {
$additional_options = $this->_additional_fields_options;
$this->_additional_fields_options = array();
$is_column_module = in_array( $this->slug, array( 'et_pb_column', 'et_pb_column_inner' ) );
// delete second level advanced options default values
if ( isset( $this->type ) && 'child' === $this->type && ! $is_column_module && apply_filters( 'et_pb_remove_child_module_defaults', true ) ) {
foreach ( $additional_options as $name => $settings ) {
if ( isset( $additional_options[ $name ]['default'] ) && ! isset( $additional_options[ $name ]['default_on_child'] ) ) {
$additional_options[ $name ]['default'] = '';
if ( function_exists( 'et_builder_definition_sort' ) ) {
et_builder_definition_sort( $additional_options );
$additional_options = et_pb_responsive_options()->create( $additional_options );
$this->_set_fields_unprocessed( $additional_options );
* Set i18n used by font fields.
protected function set_i18n_font() {
// Cache results so that translation/escaping only happens once.
if ( ! isset( $i18n['font'] ) ) {
// phpcs:disable WordPress.WP.I18n.MissingTranslatorsComment
'letter_spacing' => array(
'label' => esc_html__( '%1$s Letter Spacing', 'et_builder' ),
'description' => esc_html__( 'Letter spacing adjusts the distance between each letter in the %1$s.', 'et_builder' ),
'label' => esc_html__( '%1$s Text Size', 'et_builder' ),
'description' => esc_html__( 'Increase or decrease the size of the %1$s text.', 'et_builder' ),
'label' => esc_html__( '%1$s Font', 'et_builder' ),
'description' => esc_html__( 'Choose a custom font to use for the %1$s. All Google web fonts are available, or you can upload your own custom font files.', 'et_builder' ),
'label' => esc_html__( '%1$s Text Color', 'et_builder' ),
'description' => esc_html__( 'Pick a color to be used for the %1$s text.', 'et_builder' ),
'label' => esc_html__( '%1$s Line Height', 'et_builder' ),
'description' => esc_html__( 'Line height adjusts the distance between each line of the %1$s text. This becomes noticeable if the %1$s is long and wraps onto multiple lines.', 'et_builder' ),
'label' => esc_html__( '%1$s Text Alignment', 'et_builder' ),
'description' => esc_html__( 'Align the %1$s to the left, right, center or justify.', 'et_builder' ),
* Add font option fields.
* @since 3.23 Introduce block elements sub options group. Add responsive settings for font set,
* text color, text alignment, and text-shadow options set.
protected function _add_font_fields() {
// Font fields are added by default if module has partial or full VB support
if ( $this->has_vb_support() ) {
$this->advanced_fields['fonts'] = self::$_->array_get( $this->advanced_fields, 'fonts', array(
'label' => esc_html__( 'Module', 'custom_module' ),
'default' => floatval( et_get_option( 'body_font_height', '1.7' ) ) . 'em',
'default' => absint( et_get_option( 'body_font_size', '14' ) ) . 'px',
} else if ( ! $this->has_advanced_fields ) {
// Disable if module doesn't set advanced_fields property and has no VB support
// Font settings have to be array
if ( ! is_array( self::$_->array_get( $this->advanced_fields, 'fonts' ) ) ) {
$advanced_font_options = $this->advanced_fields['fonts'];
$additional_options = array();
foreach ( $advanced_font_options as $option_name => $option_settings ) {
$advanced_font_options[ $option_name ]['defaults'] = $defaults;
// Continue if toggle is disabled.
$toggle_disabled = isset( $option_settings['disable_toggle'] ) && $option_settings['disable_toggle'];
if ( $toggle_disabled ) {
// Block Elements - 1. Add block elements settings to fonts.
// We need to add link, ul, ol, and quote settings to fonts. We also need to convert
// current font setting with block_elements to be sub toggle of P.
if ( isset( $option_settings['block_elements'] ) && is_array( $option_settings['block_elements'] ) ) {
// Ensure target font option is exist.
if ( ! isset( $advanced_font_options[ $option_name ] ) ) {
// Get current block elements selector.
$block_default_selector = isset( $option_settings['css']['main'] ) ? $option_settings['css']['main'] : '';
$block_elements_css = isset( $option_settings['block_elements']['css'] ) ? $option_settings['block_elements']['css'] : array();
$block_elements_selector = isset( $block_elements_css['main'] ) ? $block_elements_css['main'] : $block_default_selector;
// Ensure block elements selector exist and not empty.
if ( empty( $block_elements_selector ) ) {
// Don't forget to disable block elements, so no sub toggles will be added.
$advanced_font_options[ $option_name ]['block_elements'] = false;
// Block element default settings will be used by the following sub toggles. Special
// for P sub toggle, we have to use existing font settings.
$existing_text_settings = $advanced_font_options[ $option_name ];
$block_elements_default_settings = array(
'toggle_slug' => $option_name,
// Check if current module is child or not. Then append default_on_child argument.
$is_child_module = isset( $this->type ) && 'child' === $this->type;
if ( $is_child_module ) {
// Tell font options to set default_on_child. Use fields prefix to avoid confusion.