add_action( 'admin_bar_menu', 'et_fb_add_admin_bar_link', 999 );
* Retrieve and process saved Layouts.
* It different than the function which retrieves saved Sections, Rows and Modules from library because layouts require different processing
function et_fb_get_saved_layouts() {
if ( ! isset( $_POST['et_fb_retrieve_library_modules_nonce'] ) || ! wp_verify_nonce( $_POST['et_fb_retrieve_library_modules_nonce'], 'et_fb_retrieve_library_modules_nonce' ) ) { // phpcs:ignore ET.Sniffs.ValidatedSanitizedInput -- The nonce value is used only for comparision in the `wp_verify_nonce`.
if ( ! current_user_can( 'edit_posts' ) ) {
// Reduce number of results per page if we're hosted on wpengine to avoid 500 error due to memory allocation.
// This is caused by one of their custom mu-plugins doing additional stuff but we have no control over there.
$page_size = function_exists( 'is_wpe' ) || function_exists( 'is_wpe_snapshot' ) ? 25 : 50;
$post_type = ! empty( $_POST['et_post_type'] ) ? sanitize_text_field( $_POST['et_post_type'] ) : 'post';
$layouts_type = ! empty( $_POST['et_load_layouts_type'] ) ? sanitize_text_field( $_POST['et_load_layouts_type'] ) : 'all';
$start_from = ! empty( $_POST['et_templates_start_page'] ) ? sanitize_text_field( $_POST['et_templates_start_page'] ) : 0;
$post_type = apply_filters( 'et_pb_show_all_layouts_built_for_post_type', $post_type, $layouts_type );
$all_layouts_data = et_pb_retrieve_templates( 'layout', '', 'false', '0', $post_type, $layouts_type, array( $start_from, $page_size ) );
$all_layouts_data_processed = $all_layouts_data;
if ( 0 !== $start_from && empty( $all_layouts_data ) ) {
$all_layouts_data_processed = array();
if ( empty( $all_layouts_data ) ) {
$all_layouts_data_processed = array( 'error' => esc_html__( 'You have not saved any items to your Divi Library yet. Once an item has been saved to your library, it will appear here for easy use.', 'et_builder' ) );
foreach ( $all_layouts_data as $index => $data ) {
$all_layouts_data_processed[ $index ]['shortcode'] = et_fb_process_shortcode( $data['shortcode'] );
$next_page = $start_from + $page_size;
$json_templates = wp_json_encode(
'templates_data' => $all_layouts_data_processed,
'next_page' => $next_page,
die( et_core_intentionally_unescaped( $json_templates, 'html' ) );
add_action( 'wp_ajax_et_fb_get_saved_layouts', 'et_fb_get_saved_layouts' );
* Ajax Callback: Process imported content.
function et_fb_process_imported_content() {
if ( ! isset( $_POST['et_fb_process_imported_data_nonce'] ) || ! wp_verify_nonce( $_POST['et_fb_process_imported_data_nonce'], 'et_fb_process_imported_data_nonce' ) ) { // phpcs:ignore ET.Sniffs.ValidatedSanitizedInput -- The nonce value is used only for comparision in the `wp_verify_nonce`.
if ( ! current_user_can( 'edit_posts' ) ) {
// phpcs:ignore ET.Sniffs.ValidatedSanitizedInput -- Value in `$_POST['et_raw_shortcode']` is processed by `et_fb_process_shortcode` and being returned in ajax response.
$processed_shortcode = isset( $_POST['et_raw_shortcode'] ) ? et_fb_process_shortcode( stripslashes( $_POST['et_raw_shortcode'] ) ) : '';
die( wp_json_encode( $processed_shortcode ) );
add_action( 'wp_ajax_et_fb_process_imported_content', 'et_fb_process_imported_content' );
* Builder initial content.
* @param string $content post content.
* @param integer $post_id post id.
function et_fb_maybe_get_bfb_initial_content( $content, $post_id ) {
$from_post = filter_input( INPUT_GET, 'from_post', FILTER_SANITIZE_STRING );
if ( ! empty( $from_post ) && 'empty' !== $from_post ) {
$copy_content_from = get_post( $from_post );
$existing_content = $copy_content_from->post_content;
if ( '' !== $existing_content && has_shortcode( $existing_content, 'et_pb_section' ) ) {
return $existing_content;
// process the content only for BFB.
if ( ! et_builder_bfb_enabled() ) {
// If content already has a section, it means builder is active and activation has to be
// skipped to avoid nested and unwanted builder structure.
if ( has_shortcode( $content, 'et_pb_section' ) ) {
$saved_old_content = get_post_meta( $post_id, '_et_pb_old_content', true );
$save_old_content = false;
$post = get_post( $post_id );
$save_old_content = update_post_meta( $post_id, '_et_pb_old_content', $content );
* Filters the flag that sets default Content during Builder activation.
* @used-by et_builder_wc_init()
if ( apply_filters( 'et_builder_skip_content_activation', false, $post ) ) {
if ( true !== $save_old_content && $saved_old_content !== $content && '' !== $content ) {
$text_module = '' !== $content ? '[et_pb_text admin_label="Text"]' . $content . '[/et_pb_text]' : '';
'[et_pb_section admin_label="section"]
[et_pb_row admin_label="row"]
[et_pb_column type="4_4"]' . $text_module . '[/et_pb_column]
* Called via async AJAX call after the builder rendered. It will regenerate both helper/definitions files.
* If their content changed, the builder will trigger a page reload to use the updated cached files.
function et_fb_update_builder_assets() {
if ( ! isset( $_POST['et_fb_helper_nonce'] ) || ! wp_verify_nonce( $_POST['et_fb_helper_nonce'], 'et_fb_update_helper_assets_nonce' ) ) { // phpcs:ignore ET.Sniffs.ValidatedSanitizedInput -- The nonce value is used only for comparision in the `wp_verify_nonce`.
$post_id = ! empty( $_POST['et_post_id'] ) ? sanitize_text_field( $_POST['et_post_id'] ) : '';
if ( ! current_user_can( 'edit_post', $post_id ) ) {
// Set current post as global $post.
$post = get_post( $post_id ); // phpcs:ignore WordPress.Variables.GlobalVariables.OverrideProhibited
$post_type = ! empty( $_POST['et_post_type'] ) ? sanitize_text_field( $_POST['et_post_type'] ) : 'post';
// Update helpers cached js file.
$helpers = et_fb_get_dynamic_asset( 'helpers', $post_type, true );
// Update definitions cached js file.
$definitions = et_fb_get_dynamic_asset( 'definitions', $post_type, true );
// When either definitions or helpers needs an update, also clear modules cache.
if ( $definitions['updated'] || $helpers['updated'] ) {
$modules_cache = ET_Builder_Element::get_cache_filename( $post_type );
if ( file_exists( $modules_cache ) ) {
@unlink( $modules_cache ); // phpcs:ignore WordPress.PHP.NoSilencedErrors.Discouraged -- `unlink` may fail with the permissions denied error.
'definitions' => $definitions,
add_action( 'wp_ajax_et_fb_update_builder_assets', 'et_fb_update_builder_assets' );
* Returns builder definitions.
* @param string $post_type post type.
function et_fb_get_builder_definitions( $post_type ) {
// force render builder data when retrieving builder definition to ensure definitions retrieved via ajax call
// equal to definitions retrieved on wp_footer when no dynamic asset cache found.
add_filter( 'et_builder_module_force_render', '__return_true' );
$fields_data['custom_css'] = ET_Builder_Element::get_custom_css_fields( $post_type );
$fields_data['advanced_fields'] = ET_Builder_Element::get_advanced_fields( $post_type );
$fields_data['general_fields'] = ET_Builder_Element::get_general_fields( $post_type );
$fields_data['childModuleTitles'] = ET_Builder_Element::get_child_module_titles( $post_type );
$fields_data['optionsToggles'] = ET_Builder_Element::get_toggles( $post_type );
$fields_data['customTabs'] = ET_Builder_Element::get_tabs( $post_type );
$fields_data['customTabsFields'] = ET_Builder_Element::get_settings_modal_tabs_fields( $post_type );
$fields_data['customLayoutsTabs'] = ET_Builder_Library::builder_library_modal_custom_tabs( $post_type );
$fields_data['moduleItemsConfig'] = ET_Builder_Element::get_module_items_configs( $post_type );
$fields_data['moduleTransitions'] = ET_Builder_Element::get_modules_transitions( $post_type );
$fields_data['contact_form_input_defaults'] = et_fb_process_shortcode(
'[et_pb_contact_field field_title="%1$s" field_type="input" field_id="Name" required_mark="on" fullwidth_field="off" /][et_pb_contact_field field_title="%2$s" field_type="email" field_id="Email" required_mark="on" fullwidth_field="off" /][et_pb_contact_field field_title="%3$s" field_type="text" field_id="Message" required_mark="on" fullwidth_field="on" /]',
esc_attr__( 'Name', 'et_builder' ),
esc_attr__( 'Email Address', 'et_builder' ),
esc_attr__( 'Message', 'et_builder' )
// Remove duplicates from field definitions.
$unique_fields = array();
foreach ( array( 'custom_css', 'general_fields', 'advanced_fields' ) as $source ) {
$definitions = &$fields_data[ $source ];
$module_names = array_keys( $definitions );
foreach ( $module_names as $module_name ) {
$module = &$definitions[ $module_name ];
$setting_names = array_keys( $module );
foreach ( $setting_names as $setting_name ) {
$setting = &$module[ $setting_name ];
if ( 'advanced_defaults' === $setting_name ) {
// advanced_defaults are just duplicated data, we can rebuilt them later.
$key = wp_json_encode( $setting );
if ( ! isset( $map[ $key ] ) ) {
// Found a duplicate here.
$unique_fields[] = $setting;
$map[ $key ] = $unique_count++;
// Remove force builder data render.
remove_filter( 'et_builder_module_force_render', '__return_true' );
// Include the unique fields in the AJAX payload.
$fields_data['unique_fields'] = $unique_fields;
* Returns builder shortcode object.
* @param string $post_type the post type.
* @param integer $post_id the post id.
* @param string $layout_type layout type.
function et_fb_get_builder_shortcode_object( $post_type, $post_id, $layout_type ) {
// We need to store the current post when this function is executed in a wp-admin page
// to prevent post based modules included in the shortcode from altering the loop.
add_filter( 'et_builder_module_force_render', '__return_true' );
$post_data = get_post( $post_id );
$post_data_post_modified = gmdate( 'U', strtotime( $post_data->post_modified ) );
$post_content = $post_data->post_content;
// if autosave exists here, return it with the real content, autosave.js and getServerSavedPostData() will look for it.
$current_user_id = get_current_user_id();
// Store one autosave per author. If there is already an autosave, overwrite it.
$autosave = wp_get_post_autosave( $post_id, $current_user_id );
if ( ! empty( $autosave ) ) {
$autosave_post_modified = gmdate( 'U', strtotime( $autosave->post_modified ) );
if ( $autosave_post_modified > $post_data_post_modified ) {
$fields_data['autosave_shortcode_object'] = et_fb_process_shortcode( $autosave->post_content );
$fields_data['has_newer_autosave'] = true;
$fields_data['has_newer_autosave'] = false;
// Delete the autosave, becuase we will present the option to use the autosave to the user, and they will use it or not
// we need to delete the db copy now.
wp_delete_post_revision( $autosave->ID );
switch ( $layout_type ) {
$use_fullwidth_section = false !== strpos( $post_content, '[et_pb_fullwidth_' ) ? true : false;
// Remove module placeholders.
$post_content = false !== strpos( $post_content, 'et_pb_fullwidth_module_placeholder' ) || false !== strpos( $post_content, 'et_pb_module_placeholder' ) ? '' : $post_content;
if ( ! $use_fullwidth_section ) {
$post_content = sprintf( '[et_pb_row][et_pb_column type="4_4"]%1$s[/et_pb_column][/et_pb_row]', $post_content );
'[et_pb_section%2$s]%1$s[/et_pb_section]',
$use_fullwidth_section ? ' fullwidth="on"' : ''
$post_content = '[et_pb_section]' . $post_content . '[/et_pb_section]';
$post_content = et_fb_maybe_get_bfb_initial_content( $post_content, $post_id );
* Filters the raw post content when the Builder is loaded.
* @param string $post_content The raw/unprocessed post content.
* @param int $post_id Post ID.
$post_content = apply_filters( 'et_fb_load_raw_post_content', $post_content, $post_id );
$fields_data['shortcode_object'] = et_fb_process_shortcode( $post_content );
remove_filter( 'et_builder_module_force_render', '__return_true' );
$post = $backup; // phpcs:ignore WordPress.WP.GlobalVariablesOverride -- This is legit way of setting global $post.
* Ajax Callback: Retrieve builder data on frontend app load.
function et_fb_retrieve_builder_data() {
if ( ! isset( $_POST['et_fb_helper_nonce'] ) || ! wp_verify_nonce( $_POST['et_fb_helper_nonce'], 'et_fb_load_helper_assets_nonce' ) ) { // phpcs:ignore ET.Sniffs.ValidatedSanitizedInput -- The nonce value is used only for comparision in the `wp_verify_nonce`.
$post_id = ! empty( $_POST['et_post_id'] ) ? sanitize_text_field( $_POST['et_post_id'] ) : '';
if ( ! current_user_can( 'edit_posts' ) || ! current_user_can( 'edit_post', $post_id ) ) {
$post_type = ! empty( $_POST['et_post_type'] ) ? sanitize_text_field( $_POST['et_post_type'] ) : 'post';
$layout_type = ! empty( $_POST['et_layout_type'] ) ? sanitize_text_field( $_POST['et_layout_type'] ) : '';
$fields_data = array_merge(
et_fb_get_builder_definitions( $post_type ),
et_fb_get_builder_shortcode_object( $post_type, $post_id, $layout_type )
// Enable zlib compression.
et_builder_enable_zlib_compression();
die( wp_json_encode( $fields_data ) );
add_action( 'wp_ajax_et_fb_retrieve_builder_data', 'et_fb_retrieve_builder_data' );
* Replaces site_url in a json string with its protocol-less version.
* @param string $json The json string that contain site url.
function et_fb_remove_site_url_protocol( $json ) {
$no_proto = str_replace( '/', '\/', preg_replace( '#^\w+:#', '', get_site_url() ) );
"https:$no_proto" => $no_proto,
"http:$no_proto" => $no_proto,
return strtr( $json, $from );
* Used to update the content of the cached definitions js file.
* @param string $content content? @todo Add param description.
* @param string $post_type Post type? @todo Add param description.
function et_fb_get_asset_definitions( $content, $post_type ) {
$definitions = et_fb_get_builder_definitions( $post_type );
'window.ETBuilderBackend=jQuery.extend(true,%s,window.ETBuilderBackend)',
et_fb_remove_site_url_protocol( wp_json_encode( $definitions, ET_BUILDER_JSON_ENCODE_OPTIONS ) )
add_filter( 'et_fb_get_asset_definitions', 'et_fb_get_asset_definitions', 10, 2 );
* Return Divi options setting page link.
* @return mixed|string|void
function et_pb_get_options_page_link() {
// Builder plugin has different path to options page.
if ( et_is_builder_plugin_active() ) {
return admin_url( 'admin.php?page=et_divi_options#tab_et_dashboard_tab_content_api_main' );
return apply_filters( 'et_pb_theme_options_link', admin_url( 'admin.php?page=et_divi_options' ) );
* Localization: Product tour text.
* @param integer $post_id The post id to determine the Save/Publish button text from post status.
function et_fb_get_product_tour_text( $post_id ) {
$post_status = get_post_status( $post_id );
$product_tour_text = array(
'title' => esc_html__( 'Welcome To The Divi Builder', 'et_builder' ),
'description' => sprintf(
// translators: %10$s: Tour video overlay, %1$s: "Section" - label, %2$s: Add icon markup, %3$s: "Row" - label, %4$s: Add icon markup, %5$s: "Modules" - label, %6$s: Add icon markup, %7$s: Settings gear icon markup, %9$s: Documentation link markup.
__( '%10$sBuilding beautiful pages is a breeze using the Visual Builder. To get started, add a new %1$s to your page by pressing the %2$s button. Next, add a %3$s of columns inside your section by pressing the %4$s button. Finally, start adding some content %5$s inside your columns by pressing the %6$s button. You can customize the design and content of any element on the page by pressing the %7$s button. If you ever need help, visit our %9$s page for a full list of tutorials.', 'et_builder' ),
sprintf( '<span class="et_fb_tour_text et_fb_tour_text_blue">%1$s</span>', esc_html__( 'Section' ) ),
'<span class="et_fb_tour_icon et_fb_tour_icon_blue"><svg viewBox="0 0 28 28" preserveAspectRatio="xMidYMid meet" shapeRendering="geometricPrecision"><g><path d="M18 13h-3v-3a1 1 0 0 0-2 0v3h-3a1 1 0 0 0 0 2h3v3a1 1 0 0 0 2 0v-3h3a1 1 0 0 0 0-2z" fillRule="evenodd" /></g></svg></span>',
sprintf( '<span class="et_fb_tour_text et_fb_tour_text_green">%1$s</span>', esc_html__( 'Row' ) ),
'<span class="et_fb_tour_icon et_fb_tour_icon_green"><svg viewBox="0 0 28 28" preserveAspectRatio="xMidYMid meet" shapeRendering="geometricPrecision"><g><path d="M18 13h-3v-3a1 1 0 0 0-2 0v3h-3a1 1 0 0 0 0 2h3v3a1 1 0 0 0 2 0v-3h3a1 1 0 0 0 0-2z" fillRule="evenodd" /></g></svg></span>',
sprintf( '<span class="et_fb_tour_text et_fb_tour_text_black">%1$s</span>', esc_html__( 'Modules' ) ),
'<span class="et_fb_tour_icon"><svg viewBox="0 0 28 28" preserveAspectRatio="xMidYMid meet" shapeRendering="geometricPrecision"><g><path d="M18 13h-3v-3a1 1 0 0 0-2 0v3h-3a1 1 0 0 0 0 2h3v3a1 1 0 0 0 2 0v-3h3a1 1 0 0 0 0-2z" fillRule="evenodd" /></g></svg></span>',
'<span class="et_fb_tour_icon"><svg viewBox="0 0 28 28" preserveAspectRatio="xMidYMid meet" shapeRendering="geometricPrecision"><g><path d="M20.426 13.088l-1.383-.362a.874.874 0 0 1-.589-.514l-.043-.107a.871.871 0 0 1 .053-.779l.721-1.234a.766.766 0 0 0-.116-.917 6.682 6.682 0 0 0-.252-.253.768.768 0 0 0-.917-.116l-1.234.722a.877.877 0 0 1-.779.053l-.107-.044a.87.87 0 0 1-.513-.587l-.362-1.383a.767.767 0 0 0-.73-.567h-.358a.768.768 0 0 0-.73.567l-.362 1.383a.878.878 0 0 1-.513.589l-.107.044a.875.875 0 0 1-.778-.054l-1.234-.722a.769.769 0 0 0-.918.117c-.086.082-.17.166-.253.253a.766.766 0 0 0-.115.916l.721 1.234a.87.87 0 0 1 .053.779l-.043.106a.874.874 0 0 1-.589.514l-1.382.362a.766.766 0 0 0-.567.731v.357a.766.766 0 0 0 .567.731l1.383.362c.266.07.483.26.588.513l.043.107a.87.87 0 0 1-.053.779l-.721 1.233a.767.767 0 0 0 .115.917c.083.087.167.171.253.253a.77.77 0 0 0 .918.116l1.234-.721a.87.87 0 0 1 .779-.054l.107.044a.878.878 0 0 1 .513.589l.362 1.383a.77.77 0 0 0 .731.567h.356a.766.766 0 0 0 .73-.567l.362-1.383a.878.878 0 0 1 .515-.589l.107-.044a.875.875 0 0 1 .778.054l1.234.721c.297.17.672.123.917-.117.087-.082.171-.166.253-.253a.766.766 0 0 0 .116-.917l-.721-1.234a.874.874 0 0 1-.054-.779l.044-.107a.88.88 0 0 1 .589-.513l1.383-.362a.77.77 0 0 0 .567-.731v-.357a.772.772 0 0 0-.569-.724v-.005zm-6.43 3.9a2.986 2.986 0 1 1 2.985-2.986 3 3 0 0 1-2.985 2.987v-.001z" fillRule="evenodd" /></g></svg></span>',
'<span class="et_fb_tour_text et_fb_tour_text_black">?</span>',
sprintf( '<a target="_blank" href="https://www.elegantthemes.com/documentation/divi/" class="et_fb_tour_text et_fb_tour_text_black">%1$s</a>', esc_html__( 'Documentation' ) ),
'<div class="et-fb-tour-video-overlay" data-video="https://www.youtube.com/embed/JXZIGZqr9OE?rel=0&autoplay=1">
<div class="et-fb-play-overlay"></div>
esc_url( ET_BUILDER_URI . '/frontend-builder/assets/img/product-tour-intro.jpg' )
'endButtonText' => esc_html__( 'Start Building', 'et_builder' ),
'skipButtonText' => esc_html__( 'Take the Tour', 'et_builder' ),
'title' => esc_html__( 'Load A New Layout', 'et_builder' ),
'description' => esc_html__( 'Loading pre-made layouts is a great way to jump-start your new page. The Divi Builder comes with dozens of layouts to choose from, and you can find lots of great free layouts online too. You can save your favorite layouts to the Divi Library and load them on new pages or share them with the community. Click the highlighted button to open the layouts menu and select a pre-made layout.', 'et_builder' ),
'selectLayoutPack' => array(
'title' => esc_html__( 'Choose A Layout Pack', 'et_builder' ),
'description' => esc_html__( 'Here you can see a list of pre-made layout packs that ship with the Divi Builder. You can also access layouts that you have saved to your Divi Library. Choose the “Divi Builder Demo” layout pack to see the layouts it includes.', 'et_builder' ),
'loadLayoutItem' => array(
'title' => esc_html__( 'Choose A Layout To Start With', 'et_builder' ),
'description' => esc_html__( 'Now you can see more details about the layout pack as well as a list of the layouts it includes. Click “Use Layout” to apply the layout to your page.', 'et_builder' ),
'title' => esc_html__( 'Add A New Section', 'et_builder' ),
'description' => sprintf(
// translators: %1$s: "Sections" - label, %2$s: "Rows" - label.
__( 'Now that your pre-made layout has been loaded, we can start adding new content to the page. The Divi Builder organizes content using %1$s, %2$s and Modules. Sections are the largest organizational element. Click the highlighted button to add a new section to the page.', 'et_builder' ),
sprintf( '<span class="et_fb_tour_text_blue">%1$s</span>', esc_html__( 'Sections' ) ),
sprintf( '<span class="et_fb_tour_text_green">%1$s</span>', esc_html__( 'Rows' ) )
'selectSectionType' => array(
'title' => esc_html__( 'Choose A Section Type', 'et_builder' ),
'description' => sprintf(
// translators: %1$s: "Regular" - label text, %2$s: "Specialty" - label tex, %3$s: "Fullwidth" - label tex.
__( 'The Divi Builder has three different section types. %1$s sections conform to the standard width of your page layout. %2$s Sections can be used to create advanced sidebar layouts. %3$s sections extend the full width of your page and can be used with fullwidth modules. Click the “Regular” section button to add a new section to your page.', 'et_builder' ),
sprintf( '<span class="et_fb_tour_text_blue">%1$s</span>', esc_html__( 'Regular' ) ),
sprintf( '<span class="et_fb_tour_text_red">%1$s</span>', esc_html__( 'Specialty' ) ),
sprintf( '<span class="et_fb_tour_text_purple">%1$s</span>', esc_html__( 'Fullwidth' ) )
'title' => esc_html__( 'Add A New Row Of Columns', 'et_builder' ),