: str_replace(): Passing null to parameter #2 ($replace) of type array|string is deprecated in
$slug_post_type = ! empty( $options['post_type'] ) ? $options['post_type'] : 'page';
$valid_slugs = ET_Builder_Element::get_module_slugs_by_post_type( $slug_post_type );
foreach ( $_object as $item ) {
// do not proceed if $item is empty
$type = sanitize_text_field( $item['type'] );
$type = esc_attr( $type );
// if option enabled, reject invalid slugs
if ( $options['force_valid_slugs'] ) {
if ( ! in_array( $type, $valid_slugs ) ) {
if ( ! empty( $item['raw_child_content'] ) ) {
$content = stripslashes( $item['raw_child_content'] );
if ( $options['apply_global_presets'] ) {
$module_type = $global_presets_manager->maybe_convert_module_type( $type, $item['attrs'] );
$module_global_presets = $global_presets_manager->get_module_presets_settings( $module_type, $item['attrs'] );
$item['attrs'] = array_merge( $module_global_presets, $item['attrs'] );
foreach ( $item['attrs'] as $attribute => $value ) {
// ignore computed fields
if ( '__' === substr( $attribute, 0, 2 ) ) {
$attribute = sanitize_text_field( $attribute );
// Sanitize input properly
if ( isset( $font_icon_fields[ $item['type'] ][ $attribute ] ) ) {
$value = esc_attr( $value );
if ( in_array( $attribute, array('content', 'raw_content') ) ) {
// do not override the content if item has raw_child_content
if ( empty( $item['raw_child_content'] ) ) {
$content = trim( $content );
if ( ! empty( $content ) && 'content' === $attribute ) {
$content = "\n\n" . $content . "\n\n";
// Since WordPress version 5.1, any links in the content that
// has "target" attribute will be automatically added
// rel="noreferrer noopener" attribute. This attribute added
// after the shortcode processed in et_fb_process_to_shortcode
// function. This become an issue for the builder while parsing the shortcode attributes
// because the double quote that wrapping the "rel" attribute value is not encoded.
// So we need to manipulate "target" attribute here before storing the content by renaming
// is as "data-et-target-link". Later in "et_pb_fix_shortcodes" function
// we will turn it back as "target"
$value = str_replace( ' target=', ' data-et-target-link=', $value );
$is_include_attr = false;
&& $attribute !== et_pb_hover_options()->get_field_base_name( $attribute )
&& et_pb_hover_options()->is_enabled( et_pb_hover_options()->get_field_base_name( $attribute ), $item['attrs'] ) ) {
&& $attribute !== et_pb_responsive_options()->get_field_base_name( $attribute )
&& et_pb_responsive_options()->is_enabled( et_pb_responsive_options()->get_field_base_name( $attribute ), $item['attrs'] ) ) {
if ( $is_include_attr ) {
// TODO, should we check for and handle default here? probably done in FB alredy...
// Make sure double quotes are encoded, before adding values to shortcode
$value = str_ireplace('"', '%22', $value);
// Make sure single backslash is encoded, before adding values to Shortcode.
if ( 'breadcrumb_separator' === $attribute ) {
$value = str_ireplace( '\\', '%5c', $value );
// Encode backslash for custom CSS-related and json attributes.
$json_attributes = array( 'checkbox_options', 'radio_options', 'select_options' );
if ( 0 === strpos( $attribute, 'custom_css_' ) || in_array( $attribute, $json_attributes ) ) {
$value = str_ireplace('\\', '%92', $value);
} else if ( et_builder_parse_dynamic_content( $value )->is_dynamic() ) {
$value = str_replace( '\\', '%92', $value );
$attributes .= ' ' . esc_attr( $attribute ) . '="' . et_core_esc_previously( $value ) . '"';
$attributes = str_replace( array( '[', ']' ), array( '%91', '%93' ), $attributes );
// prefix sections with a fb_built attr flag
if ( 'et_pb_section' === $type ) {
$attributes = ' fb_built="1"' . $attributes;
$output .= '[' . $type . $attributes;
// close the opening tag, depending on self closing
if ( empty( $content ) && ! isset( $item['content'] ) && ! in_array( $type, $structure_types ) ) {
// if applicable, add inner content and close tag
if ( ! $open_tag_only ) {
if ( 'et_pb_section' === $type && isset( $item['attrs'] ) && isset( $item['attrs']['fullwidth'] ) && 'on' !== $item['attrs']['fullwidth'] && isset( $item['attrs']['specialty'] ) && 'on' !== $item['attrs']['specialty'] && ( ! isset( $item['content'] ) || ! is_array( $item['content'] ) ) ) {
// insert empty row if saving empty Regular section to make it work correctly in BB
$output .= '[et_pb_row admin_label="Row"][/et_pb_row]';
} else if ( isset( $item['content'] ) && is_array( $item['content'] ) ) {
$output .= et_fb_process_to_shortcode( $item['content'], $options, '', $escape_content_slashes );
if ( !empty( $content ) ) {
if ( et_is_builder_plugin_active() && in_array( $type, ET_Builder_Element::get_has_content_modules() ) ) {
// Wrap content in autop to avoid tagless content on FE due to content is edited on html editor and only
// have one-line without newline wrap which prevent `the_content`'s wpautop filter to properly wrap it
$content = wpautop( $content );
if ( isset( $item['content'] ) ) {
$_content = $item['content'];
if ( $escape_content_slashes ) {
$_content = str_replace( '\\', '\\\\', $_content );
if ( et_is_builder_plugin_active() && in_array( $type, ET_Builder_Element::get_has_content_modules() ) ) {
// Wrap content in autop to avoid tagless content on FE due to content is edited on html editor and only
// have one-line without newline wrap which prevent `the_content`'s wpautop filter to properly wrap it
$_content = wpautop( $_content );
$output .= '[/' . $type . ']';
function et_fb_ajax_render_shortcode() {
if ( !isset( $_POST['et_pb_render_shortcode_nonce'] ) || !wp_verify_nonce( $_POST['et_pb_render_shortcode_nonce'], 'et_pb_render_shortcode_nonce' ) ) {
if ( ! current_user_can( 'edit_posts' ) ) {
$utils = ET_Core_Data_Utils::instance();
global $et_pb_predefined_module_index;
$et_pb_predefined_module_index = isset( $_POST['et_fb_module_index'] ) && 'default' !== $_POST['et_fb_module_index'] ? sanitize_text_field( $_POST['et_fb_module_index'] ) : false;
$options = isset( $_POST['options'] ) ? $utils->sanitize_text_fields( $_POST['options'] ) : array();
// enforce valid module slugs only
// shortcode slugs need to be allowlisted so as to prevent malicious shortcodes from being generated and run through do_shortcode().
$options['force_valid_slugs'] = true;
// convert shortcode array to shortcode string.
$shortcode = et_fb_process_to_shortcode( $_POST['object'], $options );
// take shortcode string and ensure it's properly sanitized for the purposes of this function.
$shortcode = et_pb_enforce_builder_shortcode( $shortcode );
$output = do_shortcode( $shortcode );
$styles = ET_Builder_Element::get_style();
if ( ! empty( $styles ) ) {
'<style type="text/css" class="et-builder-advanced-style">
wp_send_json_success( $output );
add_action( 'wp_ajax_et_fb_ajax_render_shortcode', 'et_fb_ajax_render_shortcode' );
function et_fb_current_user_can_save( $post_id, $status = '' ) {
if ( is_page( $post_id ) ) {
if ( ! current_user_can( 'edit_pages' ) ) {
if ( ! current_user_can( 'publish_pages' ) && 'publish' === $status ) {
if ( ! current_user_can( 'edit_published_pages' ) && 'publish' === get_post_status( $post_id ) ) {
if ( ! current_user_can( 'edit_others_pages' ) && ! current_user_can( 'edit_page', $post_id ) ) {
if ( ! current_user_can( 'edit_posts' ) ) {
if ( ! current_user_can( 'publish_posts' ) && 'publish' === $status ) {
if ( ! current_user_can( 'edit_published_posts' ) && 'publish' === get_post_status( $post_id ) ) {
if ( ! current_user_can( 'edit_others_posts' ) && ! current_user_can( 'edit_post', $post_id ) ) {
function et_fb_ajax_drop_autosave() {
if ( !isset( $_POST['et_fb_drop_autosave_nonce'] ) || !wp_verify_nonce( $_POST['et_fb_drop_autosave_nonce'], 'et_fb_drop_autosave_nonce' ) ) {
$post_id = absint( $_POST['post_id'] );
if ( ! et_fb_current_user_can_save( $post_id ) ) {
$post_author = get_current_user_id();
$autosave = wp_get_post_autosave( $post_id, $post_author );
$autosave_deleted = false;
// delete builder settings autosave
delete_post_meta( $post_id, "_et_builder_settings_autosave_{$post_author}" );
if ( !empty( $autosave ) ) {
wp_delete_post_revision( $autosave->ID );
$autosave = wp_get_post_autosave( $post_id, $post_author );
if ( empty( $autosave ) ) {
$autosave_deleted = true;
$autosave_deleted = true;
if ( $autosave_deleted ) {
add_action( 'wp_ajax_et_fb_ajax_drop_autosave', 'et_fb_ajax_drop_autosave' );
function et_fb_ajax_save() {
if ( !isset( $_POST['et_fb_save_nonce'] ) || !wp_verify_nonce( $_POST['et_fb_save_nonce'], 'et_fb_save_nonce' ) ) {
$post_id = absint( $_POST['post_id'] );
if ( ! et_fb_current_user_can_save( $post_id, $_POST['options']['status'] ) ) {
$utils = ET_Core_Data_Utils::instance();
$layout_type = isset( $_POST['layout_type'] ) ? sanitize_text_field( $_POST['layout_type'] ) : '';
if ( ! isset( $_POST['skip_post_update'] ) ) {
$is_layout_block_preview = sanitize_text_field( $utils->array_get( $_POST, 'options.conditional_tags.is_layout_block', '' ) );
$block_id = sanitize_title( $utils->array_get( $_POST, 'options.current_page.blockId', '' ) );
$shortcode_data = json_decode( stripslashes( $_POST['modules'] ), true );
// Cast as bool if falsey; blockId is retrieved from ajax request, and
// already return empty string (falsey) if no value found. Nevertheless let's be more safe.
// Cast as bool if falsey; is_layout_block_preview is retrieved from ajax request, and
// already return empty string (falsey) if no value found. Nevertheless let's be more safe.
if ( ! $is_layout_block_preview ) {
$is_layout_block_preview = false;
if ( ! $built_for_type = get_post_meta( $post_id, '_et_pb_built_for_post_type', true ) && ! $is_layout_block_preview ) {
update_post_meta( $post_id, '_et_pb_built_for_post_type', 'page' );
$post_content = et_fb_process_to_shortcode( $shortcode_data, $_POST['options'], $layout_type );
// Store a copy of the sanitized post content in case wpkses alters it since that
// would cause our check at the end of this function to fail.
$sanitized_content = sanitize_post_field( 'post_content', $post_content, $post_id, 'db' );
// Exit early for layout block update; builder should not actually save post content in this scenario
// Update post meta and let it is being used to update layoutContent on editor
if ( $is_layout_block_preview && $block_id ) {
$layout_preview_meta_key = "_et_block_layout_preview_{$block_id}";
$saved_layout = get_post_meta( $post_id, $layout_preview_meta_key, true );
// If saved layout is identical to the the layout sent via AJAX, return send json success;
// this is needed because update_post_meta() returns false if the saved layout is identical
// to the the one given as param
if ( ! empty( $saved_layout ) && $saved_layout === $post_content ) {
wp_send_json_success( array(
'save_verification' => true,
$update = update_post_meta( $post_id, $layout_preview_meta_key, $post_content );
wp_send_json_success( array(
'save_verification' => true,
$update = wp_update_post( array(
'post_content' => $post_content,
'post_status' => sanitize_text_field( $_POST['options']['status'] ),
// update Global modules with selective sync
if ( 'module' === $layout_type && isset( $_POST['unsyncedGlobalSettings'] ) && 'none' !== $_POST['unsyncedGlobalSettings'] ) {
$unsynced_options = stripslashes( $_POST['unsyncedGlobalSettings'] );
update_post_meta( $post_id, '_et_pb_excluded_global_options', sanitize_text_field( $unsynced_options ) );
// check if there is an autosave that is newer
$post_author = get_current_user_id();
// Store one autosave per author. If there is already an autosave, overwrite it.
$autosave = wp_get_post_autosave( $post_id, $post_author );
if ( !empty( $autosave ) ) {
wp_delete_post_revision( $autosave->ID );
if ( isset($_POST['settings'] ) && is_array( $_POST['settings'] ) ) {
et_builder_update_settings( $_POST['settings'], $post_id );
if ( isset($_POST['preferences'] ) && is_array( $_POST['preferences'] ) ) {
$app_preferences = et_fb_app_preferences_settings();
$limited_prefix = ! empty( $_POST['et_builder_mode'] ) && 'limited' === $_POST['et_builder_mode'] ? 'limited_' : '';
foreach( $app_preferences as $preference_key => $preference_data ) {
$preference_value = isset( $_POST['preferences'][ $preference_key ] ) && isset( $_POST['preferences'][ $preference_key ]['value'] ) ? $_POST['preferences'][ $preference_key ]['value'] : $preference_data['default'];
// sanitize based on type
switch ( $preference_data['type'] ) {
$preference_value = absint( $preference_value );
$preference_value = $preference_value === 'true' ? 'true' : 'false';
$preference_value = sanitize_text_field( $preference_value );
$preference_value_max_length = et_()->array_get( $preference_data, 'max_length', 0 );
if ( $preference_value && is_numeric( $preference_value_max_length ) && $preference_value_max_length > 0 ) {
$preference_value = substr( $preference_value, 0, $preference_value_max_length );
$option_name = 'et_fb_pref_' . $preference_key;
if ( in_array( $preference_key, et_fb_unsynced_preferences() ) ) {
$option_name = 'et_fb_pref_' . $limited_prefix . $preference_key;
et_update_option( $option_name, $preference_value );
// Clear AB Testing stats & transient data
if ( isset( $_POST['ab_testing'] ) && isset( $_POST['ab_testing']['is_clear_stats'] ) && 'true' === $_POST['ab_testing']['is_clear_stats'] && et_pb_is_allowed( 'ab_testing' ) ) {
et_pb_ab_remove_stats( $post_id );
et_pb_ab_clear_cache_handler( $post_id );
do_action( 'et_save_post', $post_id );
if ( ! empty( $_POST['et_builder_version'] ) ) {
update_post_meta( $post_id, '_et_builder_version', sanitize_text_field( $_POST['et_builder_version'] ) );
// Get saved post, verify its content against the one that is being sent
$saved_post = get_post( $update );
$saved_post_content = $saved_post->post_content;
$builder_post_content = stripslashes( $sanitized_content );
// Get rendered post content only if it's needed.
$return_rendered_content = sanitize_text_field( $utils->array_get( $_POST, 'options.return_rendered_content', 'false' ) );
$rendered_post_content = 'true' === $return_rendered_content ? do_shortcode( $saved_post_content ) : '';
// If `post_content` column on wp_posts table doesn't use `utf8mb4` charset, the saved post
// content's emoji will be encoded which means the check of saved post_content vs
// builder's post_content will be false; Thus check the charset of `post_content` column
// first then encode the builder's post_content if needed
// @see https://make.wordpress.org/core/2015/04/02/omg-emoji-%f0%9f%98%8e/
// @see https://make.wordpress.org/core/2015/04/02/the-utf8mb4-upgrade/
if ( 'utf8' === $wpdb->get_col_charset( $wpdb->posts, 'post_content' ) ) {
$builder_post_content = wp_encode_emoji( $builder_post_content );
$saved_verification = $saved_post_content === $builder_post_content;
if ( $saved_verification ) {
// Strip non-printable characters to ensure preg_match_all operation work properly.
$post_content_cleaned = preg_replace('/[\x00-\x1F\x7F]/u', '', $saved_post->post_content);
preg_match_all( '/\[et_pb_section(.*?)?\]\[et_pb_row(.*?)?\]\[et_pb_column(.*?)?\](.+?)\[\/et_pb_column\]\[\/et_pb_row\]\[\/et_pb_section\]/m', $post_content_cleaned, $matches );
if ( isset( $matches[4] ) && ! empty( $matches[4] ) ) {
// Set page creation flow to off.
update_post_meta( $post_id, '_et_pb_show_page_creation', 'off' );
delete_post_meta( $post_id, '_et_pb_show_page_creation' );
* Hook triggered when the Post is updated.
* @param int $post_id Post ID.
do_action( 'et_update_post', $post_id );
wp_send_json_success( array(
'status' => get_post_status( $update ),
'save_verification' => apply_filters( 'et_fb_ajax_save_verification_result', $saved_verification ),
'rendered_content' => $rendered_post_content,
} else if( isset( $_POST['skip_post_update'] ) ) {