delete_post_meta( $post_id, '_et_pb_ab_subjects' );
if ( isset( $_POST['et_pb_ab_goal_module'] ) && '' !== $_POST['et_pb_ab_goal_module'] ) {
update_post_meta( $post_id, '_et_pb_ab_goal_module', sanitize_text_field( $_POST['et_pb_ab_goal_module'] ) );
delete_post_meta( $post_id, '_et_pb_ab_goal_module' );
if ( isset( $_POST['et_pb_ab_stats_refresh_interval'] ) && '' !== $_POST['et_pb_ab_stats_refresh_interval'] ) {
update_post_meta( $post_id, '_et_pb_ab_stats_refresh_interval', sanitize_text_field( $_POST['et_pb_ab_stats_refresh_interval'] ) );
delete_post_meta( $post_id, '_et_pb_ab_stats_refresh_interval' );
if ( isset( $_POST['et_pb_old_content'] ) ) {
update_post_meta( $post_id, '_et_pb_old_content', $_POST['et_pb_old_content'] );
* Fires after the `_et_pb_old_content` post meta is updated.
* In case you want to over-ride `_et_pb_old_content` content, this is the hook you should use.
* @see et_builder_wc_long_description_metabox_save()
* @param int $post_id Post ID.
* $param WP_Post $post The Post.
* $param array $_POST Request variables. This could be used for Nonce verification, etc.
do_action( 'et_pb_old_content_updated', $post_id, $post, $_POST );
delete_post_meta( $post_id, '_et_pb_old_content' );
et_builder_update_settings( null, $post_id );
if ( isset( $_POST['et_pb_unsynced_global_attrs'] ) ) {
$unsynced_options_array = stripslashes( sanitize_text_field( $_POST['et_pb_unsynced_global_attrs'] ) );
update_post_meta( $post_id, '_et_pb_excluded_global_options', $unsynced_options_array );
add_action( 'save_post', 'et_pb_metabox_settings_save_details', 10, 2 );
* Set et-saved-post-* cookie and delete et-saving-post-* cookie after post save.
* @param int $post_id Post id.
* @param WP_Post $post Object.
function et_pb_set_et_saved_cookie( $post_id, $post ) {
if ( 'post.php' !== $pagenow ) {
if ( defined( 'DOING_AUTOSAVE' ) && DOING_AUTOSAVE ) {
$post_type = get_post_type_object( $post->post_type );
if ( ! current_user_can( $post_type->cap->edit_post, $post_id ) ) {
if ( ! isset( $_POST['et_pb_settings_nonce'] ) || ! wp_verify_nonce( $_POST['et_pb_settings_nonce'], basename( __FILE__ ) ) ) { // phpcs:ignore ET.Sniffs.ValidatedSanitizedInput -- The nonce value is used only for comparision in the `wp_verify_nonce`.
setcookie( 'et-saving-post-' . $post_id . '-bb', 'bb', time() - DAY_IN_SECONDS, SITECOOKIEPATH, false, is_ssl() );
setcookie( 'et-saved-post-' . $post_id . '-bb', 'bb', time() + MINUTE_IN_SECONDS * 5, SITECOOKIEPATH, false, is_ssl() );
add_action( 'save_post', 'et_pb_set_et_saved_cookie', 10, 2 );
* Handling title-less & content-less switching from backend builder to normal editor
* @param int $maybe_empty whether the wp_insert_post content is empty or not.
* @param array $postarr all $_POST data that is being passed to wp_insert_post().
* @return int whether wp_insert_post content should be considered empty or not
function et_pb_ensure_builder_activation_switching( $maybe_empty, $postarr ) {
// Consider wp_insert_post() content is not empty if incoming et_pb_use_builder is `off` while currently saved _et_pb_use_builder value is `on`.
if ( isset( $postarr['et_pb_use_builder'] ) && 'off' === $postarr['et_pb_use_builder'] && isset( $postarr['post_ID'] ) && et_pb_is_pagebuilder_used( $postarr['post_ID'] ) ) {
add_filter( 'wp_insert_post_empty_content', 'et_pb_ensure_builder_activation_switching', 10, 2 );
* Display buttons before main editor in BFB.
* @param WP_Post $post object.
function et_pb_before_main_editor( $post ) {
if ( ! et_builder_enabled_for_post( $post->ID ) ) {
$_et_builder_use_builder = get_post_meta( $post->ID, '_et_pb_use_builder', true );
$is_builder_used = 'on' === $_et_builder_use_builder;
$last_builder_version_used = get_post_meta( $post->ID, '_et_builder_version', true ); // Examples: 'BB|Divi|3.0.30' 'VB|Divi|3.0.30'.
$_et_builder_use_ab_testing = et_builder_bfb_enabled() ? false : get_post_meta( $post->ID, '_et_pb_use_ab_testing', true );
$_et_builder_ab_stats_refresh_interval = et_builder_bfb_enabled() ? false : et_pb_ab_get_refresh_interval( $post->ID );
$_et_builder_ab_subjects = et_builder_bfb_enabled() ? false : get_post_meta( $post->ID, '_et_pb_ab_subjects', true );
$_et_builder_ab_goal_module = et_builder_bfb_enabled() ? false : et_pb_ab_get_goal_module( $post->ID );
$builder_always_enabled = apply_filters( 'et_builder_always_enabled', false, $post->post_type, $post );
if ( 'et_pb_layout' === $post->post_type ) {
// No matter what, in Divi Library we always want the builder.
$builder_always_enabled = true;
if ( $builder_always_enabled ) {
$_et_builder_use_builder = 'on';
// TODO, need to change the output of these buttons if BFB.
// Add button only if current user is allowed to use it otherwise display placeholder with all required data.
if ( et_pb_is_allowed( 'divi_builder_control' ) ) {
'<a href="#" id="et_pb_toggle_builder" data-builder="%2$s" data-editor="%3$s" class="button button-primary button-large%4$s%5$s">%1$s</a>',
( $is_builder_used ? esc_html__( 'Return To Standard Editor', 'et_builder' ) : esc_html__( 'Use The Divi Builder', 'et_builder' ) ),
esc_html__( 'Use The Divi Builder', 'et_builder' ),
esc_html__( 'Return To Standard Editor', 'et_builder' ),
( $is_builder_used ? ' et_pb_builder_is_used' : '' ),
( $builder_always_enabled ? ' et_pb_hidden' : '' )
// add in the visual builder button only on appropriate post types
// also, don't add the button on page if it set as static posts page.
if ( et_builder_fb_enabled_for_post( $post->ID ) && et_pb_is_allowed( 'use_visual_builder' ) && ! et_is_extra_library_layout( $post->ID ) && get_option( 'page_for_posts' ) !== $post->ID ) {
'<a href="%1$s" id="et_pb_fb_cta" class="button button-primary button-large%3$s%4$s">%2$s</a>',
esc_url( et_fb_get_vb_url() ),
esc_html__( 'Build On The Front End', 'et_builder' ),
( $builder_always_enabled ? ' et-first-child' : '' ),
( et_pb_is_pagebuilder_used( $post->ID ) ? ' et_pb_ready' : '' )
'<div class="et_pb_toggle_builder_wrapper%1$s"%4$s>%2$s</div><div id="et_pb_main_editor_wrap"%3$s>',
( $is_builder_used ? ' et_pb_builder_is_used' : '' ),
et_core_esc_previously( $buttons ),
( $is_builder_used ? ' class="et_pb_post_body_hidden"' : '' ),
( et_builder_bfb_enabled() ? ' style="opacity: 0;"' : '' )
'<div class="et_pb_toggle_builder_wrapper%2$s"%3$s></div><div id="et_pb_main_editor_wrap"%1$s>',
( $is_builder_used ? ' class="et_pb_post_body_hidden"' : '' ),
( $is_builder_used ? ' et_pb_builder_is_used' : '' ),
( et_builder_bfb_enabled() ? ' style="opacity: 0;"' : '' )
if ( ! et_builder_bfb_enabled() ) {
$module_fields_dependencies = wp_json_encode( ET_Builder_Element::get_field_dependencies( $post->post_type ) );
echo et_core_esc_previously(
window.et_pb_module_field_dependencies = JSON.parse( '{$module_fields_dependencies}' );
<p class="et_pb_page_settings" style="display: none;">
<?php wp_nonce_field( basename( __FILE__ ), 'et_pb_settings_nonce' ); ?>
<input type="hidden" id="et_pb_last_post_modified" name="et_pb_last_post_modified" value="<?php echo esc_attr( $post->post_modified ); ?>" />
<input type="hidden" id="et_pb_use_builder" name="et_pb_use_builder" value="<?php echo esc_attr( $_et_builder_use_builder ); ?>" />
<input type="hidden" id="et_builder_version" name="et_builder_version" value="<?php echo esc_attr( $last_builder_version_used ); ?>" />
<input type="hidden" autocomplete="off" id="et_pb_use_ab_testing" name="et_pb_use_ab_testing" value="<?php echo esc_attr( $_et_builder_use_ab_testing ); ?>">
<input type="hidden" autocomplete="off" id="_et_pb_ab_stats_refresh_interval" name="et_pb_ab_stats_refresh_interval" value="<?php echo esc_attr( $_et_builder_ab_stats_refresh_interval ); ?>">
<input type="hidden" autocomplete="off" id="et_pb_ab_subjects" name="et_pb_ab_subjects" value="<?php echo esc_attr( $_et_builder_ab_subjects ); ?>">
<input type="hidden" autocomplete="off" id="et_pb_ab_goal_module" name="et_pb_ab_goal_module" value="<?php echo esc_attr( $_et_builder_ab_goal_module ); ?>">
<?php et_pb_builder_settings_hidden_inputs( $post->ID ); ?>
<?php et_pb_builder_global_library_inputs( $post->ID ); ?>
<textarea id="et_pb_old_content" name="et_pb_old_content"><?php echo esc_attr( get_post_meta( $post->ID, '_et_pb_old_content', true ) ); ?></textarea>
* Add #et_pb_main_editor_wrap closing div.
* @param WP_Post $post object.
function et_pb_after_main_editor( $post ) {
if ( ! et_builder_enabled_for_post( $post->ID ) ) {
echo '</div> <!-- #et_pb_main_editor_wrap -->';
* Setup Divi Builder in BFB.
function et_pb_setup_main_editor() {
if ( ! et_core_is_gutenberg_enabled() ) {
add_action( 'edit_form_after_title', 'et_pb_before_main_editor' );
add_action( 'edit_form_after_editor', 'et_pb_after_main_editor' );
add_action( 'add_meta_boxes', 'et_pb_setup_main_editor', 11 );
* Load scripts and styles in admin.
* @param string $hook The current admin page.
function et_pb_admin_scripts_styles( $hook ) {
global $typenow, $pagenow;
// load css file for the Divi menu.
wp_enqueue_style( 'library-menu-styles', ET_BUILDER_URI . '/styles/library_menu.css', array(), ET_BUILDER_VERSION );
if ( 'widgets.php' === $hook ) {
wp_enqueue_script( 'et_pb_widgets_js', ET_BUILDER_URI . '/scripts/ext/widgets.js', array( 'jquery' ), ET_BUILDER_VERSION, true );
$et_pb_options_admin = array(
'ajaxurl' => admin_url( 'admin-ajax.php' ),
'et_admin_load_nonce' => wp_create_nonce( 'et_admin_load_nonce' ),
'widget_info' => sprintf(
'<div id="et_pb_widget_area_create"><p>%1$s.</p><p>%2$s.</p><p><label>%3$s <input id="et_pb_new_widget_area_name" value="" /></label><button class="button button-primary et_pb_create_widget_area">%4$s</button></p><p class="et_pb_widget_area_result"></p></div>',
esc_html__( 'Here you can create new widget areas for use in the Sidebar module', 'et_builder' ),
esc_html__( 'Note: Naming your widget area "sidebar 1", "sidebar 2", "sidebar 3", "sidebar 4" or "sidebar 5" will cause conflicts with this theme', 'et_builder' ),
esc_html__( 'Widget Name', 'et_builder' ),
esc_html__( 'Create', 'et_builder' )
'delete_string' => esc_html__( 'Delete', 'et_builder' ),
wp_localize_script( 'et_pb_widgets_js', 'et_pb_options', apply_filters( 'et_pb_options_admin', $et_pb_options_admin ) );
wp_enqueue_style( 'et_pb_widgets_css', ET_BUILDER_URI . '/styles/widgets.css', array(), ET_BUILDER_VERSION );
// Do not enqueue BB assets if GB is active on this page.
if ( et_core_is_gutenberg_enabled() ) {
if ( ! in_array( $hook, array( 'post-new.php', 'post.php' ), true ) ) {
* Load the builder javascript and css files for custom post types
* custom post types can be added using et_builder_post_types filter
$post_types = et_builder_get_builder_post_types();
$on_enabled_post_type = isset( $typenow ) && in_array( $typenow, $post_types, true );
$on_enabled_post = isset( $pagenow ) && 'post.php' === $pagenow && isset( $_GET['post'] ) && et_builder_enabled_for_post( intval( $_GET['post'] ) ); // phpcs:ignore WordPress.Security.NonceVerification -- This function does not change any state, and is therefore not susceptible to CSRF.
if ( $on_enabled_post_type || $on_enabled_post ) {
wp_enqueue_style( 'et_bb_bfb_common', ET_BUILDER_URI . '/styles/bb_bfb_common.css', array(), ET_BUILDER_VERSION );
// Boot one builders assets or the other.
if ( et_builder_bfb_enabled() ) {
et_bfb_enqueue_scripts();
// do not load BFB if builder is disabled on page.
if ( ! et_pb_is_pagebuilder_used( get_the_ID() ) ) {
// BFB loads builder modal outside the iframe using react portal. external scripts
// that is used on modal needs to be enqueued.
et_builder_enqueue_assets_main();
et_builder_enqueue_open_sans();
$secondary_css_bundles = glob( ET_BUILDER_DIR . 'frontend-builder/build/bundle.*.css' );
if ( $secondary_css_bundles ) {
$bundles = array( 'et-frontend-builder' );
foreach ( $secondary_css_bundles as $css_bundle ) {
$slug = basename( $css_bundle, '.css' );
$parts = explode( '.', $slug, -1 );
// Drop "bundle" from array.
$slug = implode( '-', $parts );
et_fb_enqueue_bundle( "et-fb-{$slug}", basename( $css_bundle ), $bundles, null );
// Hooks for theme/plugin specific styling which complements visual builder.
do_action( 'et_bfb_boot' );
et_pb_add_builder_page_js_css();
add_action( 'admin_enqueue_scripts', 'et_pb_admin_scripts_styles', 10, 1 );
* Disable emoji detection script on edit page which has Backend Builder on it.
* WordPress automatically replaces emoji with plain image for backward compatibility
* on older browsers. This causes issue when emoji is used on header or other input
* text field because (when the modal is saved, shortcode is generated, and emoji
* is being replaced with plain image) it creates incorrect attribute markup
* such as `title="I <img class="emoji" src="../heart.png" /> WP"` and causes
* the whole input text value to be disappeared
function et_pb_remove_emoji_detection_script() {
// phpcs:disable WordPress.Security.NonceVerification -- This function does not change any state, and is therefore not susceptible to CSRF.
$disable_emoji_detection = false;
// Disable emoji detection script on editing page which has Backend Builder
// global $post isn't available at admin_init, so retrieve $post data manually.
if ( 'post.php' === $pagenow && isset( $_GET['post'] ) ) {
$post_id = (int) $_GET['post'];
$post = get_post( $post_id );
if ( et_builder_enabled_for_post( $post->ID ) ) {
$disable_emoji_detection = true;
// Disable emoji detection script on post new page which has Backend Builder.
$has_post_type_query = isset( $_GET['post_type'] );
if ( 'post-new.php' === $pagenow && ( ! $has_post_type_query || ( $has_post_type_query && in_array( $_GET['post_type'], et_builder_get_builder_post_types(), true ) ) ) ) {
$disable_emoji_detection = true;
if ( $disable_emoji_detection ) {
remove_action( 'admin_print_scripts', 'print_emoji_detection_script' );
add_action( 'admin_init', 'et_pb_remove_emoji_detection_script' );
* Disable emoji detection script on visual builder
* WordPress automatically replaces emoji with plain image for backward compatibility
* on older browsers. This causes issue when emoji is used on header or other input
* text field because the staticize emoji creates HTML markup which appears to be
* invalid on input[type="text"] field such as `title="I <img class="emoji"
* src="../heart.png" /> WP"` and causes the input text value to be escaped and
function et_fb_remove_emoji_detection_script() {
// Disable emoji detection script on visual builder. React's auto escaping will
// remove all staticized emoji when being opened on modal's input field.
if ( isset( $post->ID ) && et_fb_is_enabled( $post->ID ) ) {
remove_action( 'wp_head', 'print_emoji_detection_script', 7 );
add_action( 'wp', 'et_fb_remove_emoji_detection_script' );
* If the builder is used for the page, get rid of random p tags.
* @param string $content content.
* @return string|string[]|null
function et_pb_fix_builder_shortcodes( $content ) {
// ET_Builder_Element is not loaded in the administration and some plugins call
// the_content there (e.g. WP File Manager).
$is_theme_builder = ET_Builder_Element::is_theme_builder_layout();
$is_singular = is_singular() && 'on' === get_post_meta( get_the_ID(), '_et_pb_use_builder', true );
// if the builder is used for the page, get rid of random p tags.
if ( $is_theme_builder || $is_singular ) {
$content = et_pb_fix_shortcodes( $content );
add_filter( 'the_content', 'et_pb_fix_builder_shortcodes' );
add_filter( 'et_builder_render_layout', 'et_pb_fix_builder_shortcodes' );
* Prepare code module for wpautop.
* @param string $content content.
* @return string|string[]|null
function et_pb_the_content_prep_code_module_for_wpautop( $content ) {
if ( 'on' === get_post_meta( get_the_ID(), '_et_pb_use_builder', true ) ) {
$content = et_pb_prep_code_module_for_wpautop( $content );
add_filter( 'the_content', 'et_pb_the_content_prep_code_module_for_wpautop', 0 );
add_filter( 'et_builder_render_layout', 'et_pb_the_content_prep_code_module_for_wpautop', 0 );
if ( ! function_exists( 'et_pb_generate_new_layout_modal' ) ) {
* Generate the html for "Add new template" Modal in Library.
function et_pb_generate_new_layout_modal() {
$template_type_option_output = '';
$template_module_tabs_option_output = '';
$template_global_option_output = '';
$layout_cat_option_output = '';
$new_layout_template_types = array(
'module' => esc_html__( 'Module', 'et_builder' ),
'fullwidth_module' => esc_html__( 'Fullwidth Module', 'et_builder' ),
'row' => esc_html__( 'Row', 'et_builder' ),
'section' => esc_html__( 'Section', 'et_builder' ),
'fullwidth_section' => esc_html__( 'Fullwidth Section', 'et_builder' ),
'specialty_section' => esc_html__( 'Specialty Section', 'et_builder' ),
'layout' => et_builder_i18n( 'Layout' ),
$template_type_options = apply_filters( 'et_pb_new_layout_template_types', $new_layout_template_types );
// construct output for the template type option.
if ( ! empty( $template_type_options ) ) {
$template_type_option_output = sprintf(
'<br><label>%1$s:</label>
<select id="new_template_type">',
esc_html__( 'Layout Type', 'et_builder' )
foreach ( $template_type_options as $option_id => $option_name ) {
$template_type_option_output .= sprintf(
'<option value="%1$s">%2$s</option>',
$template_type_option_output .= '</select>';
$template_global_option_output = apply_filters(
'et_pb_new_layout_global_option',
'<br><label>%1$s<input type="checkbox" value="global" id="et_pb_template_global"></label>',
esc_html__( 'Global', 'et_builder' )
// construct output for the layout category option.
$layout_cat_option_output .= sprintf(
'<br><label>%1$s</label>',
esc_html__( 'Add To Categories', 'et_builder' )
$layout_categories = apply_filters( 'et_pb_new_layout_cats_array', get_terms( 'layout_category', array( 'hide_empty' => false ) ) );
if ( is_array( $layout_categories ) && ! empty( $layout_categories ) ) {
$layout_cat_option_output .= '<div class="layout_cats_container">';
foreach ( $layout_categories as $category ) {
$layout_cat_option_output .= sprintf(
'<label>%1$s<input type="checkbox" value="%2$s"/></label>',
esc_html( $category->name ),
esc_attr( $category->term_id )
$layout_cat_option_output .= '</div>';