'description' => sprintf(
// translators: %1$s: "Rows" - label.
__( 'Every section contains one or more %1$s of columns. You can choose between various column layouts for each row you add to your page. Click the highlighted three-column layout to add a new row to your section.', 'et_builder' ),
sprintf( '<span class="et_fb_tour_text_green">%1$s</span>', esc_html__( 'Rows' ) )
'title' => esc_html__( 'Add A Module To The Column', 'et_builder' ),
'description' => esc_html__( 'Within each column you can add one or more Modules. A module is basic content element. The Divi Builder comes with over 40 different content elements to choose from, such as Images, Videos, Text, and Buttons. Click the highlighted Blurb button to add a new Blurb module to the first column in your row.', 'et_builder' ),
'configureModule' => array(
'title' => esc_html__( 'Adjust Your Module Settings', 'et_builder' ),
'description' => esc_html__( 'Each Module comes with various settings. These settings are separated into three tabs: Content, Design and Advanced. Inside the content tab you can modify the module content elements, such as text and images. If you need more control over the appearance of your module, head over to the Design tab. For more advanced modifications, such as custom CSS and HTML attributes, explore the Advanced tab. Try adjusting the Title of your blurb by clicking into the highlighted field.', 'et_builder' ),
'title' => esc_html__( 'Accept Or Discard Your Changes', 'et_builder' ),
'description' => esc_html__( 'Whenever you make changes in the Divi Builder, these changes can be Undone, Redone, Discarded or Accepted. Now that you have adjusted your module’s title, you can click the red discard button to cancel these changes, or your can click the green button to accept them.', 'et_builder' ),
'duplicateModule' => array(
'title' => esc_html__( 'Hover To Access Action Buttons', 'et_builder' ),
'description' => esc_html__( 'Whenever you hover over a Section, Row or Module in the Divi Builder, action buttons will appear. These buttons can be used to move, modify, duplicate or delete your content. Click the highlighted “duplicate” icon to duplicate the blurb module that you just added to the page.', 'et_builder' ),
'title' => __( 'Drag & Drop Content', 'et_builder' ),
'description' => esc_html__( 'Every item on the page can be dragged and dropped to new locations. Using your mouse, click the highlighted move icon and hold down the mouse button. While holding down the mouse button, move your cursor over to the empty column and then release your mouse button to drop the module into the new column.', 'et_builder' ),
'rightClickCopy' => array(
'title' => esc_html__( 'Access Right Click Options', 'et_builder' ),
'description' => esc_html__( 'In addition to hover actions, additional options can be accessed by Right Clicking or Cmd + Clicking on any module, row or section. Using the right click menu shown, click the highlighted “Copy Module” button to copy the blurb module that you just moved.', 'et_builder' ),
'rightClickPaste' => array(
'title' => esc_html__( 'Paste Your Copied Module', 'et_builder' ),
'description' => esc_html__( 'Now that you have copied a module using the Right Click menu, you can Right Click in a new location to paste that module. Using the right click options shown, click the “Paste Module” button to paste the module you just copied into the empty column.', 'et_builder' ),
'title' => esc_html__( 'Access Your Row Options', 'et_builder' ),
'description' => esc_html__( 'Every Row and Section has its own set of options that can be used to adjust the item’s appearance. You can adjust its width, padding, background and more. To access a row’s settings, hover over the row and click the highlighted options button.', 'et_builder' ),
'title' => esc_html__( 'Adjust Your Row Setting', 'et_builder' ),
'description' => esc_html__( 'Just like Modules, Rows come with a lot of settings that are separated into the Content, Design and Advanced tabs. Click the highlighted button to add a new background color to your row.', 'et_builder' ),
'title' => esc_html__( 'Accept Your Changes', 'et_builder' ),
'description' => esc_html__( 'Click the highlighted green check mark button to accept your changes. ', 'et_builder' ),
'title' => esc_html__( 'Open Your Page Settings', 'et_builder' ),
'description' => esc_html__( 'While using the Divi Builder, you can access your page settings by toggling the page settings bar at the bottom of your screen. Click the highlighted button to reveal your page settings.', 'et_builder' ),
'tabletPreview' => array(
'title' => esc_html__( 'Preview Your Page On Mobile', 'et_builder' ),
'description' => esc_html__( 'While editing your page, it’s easy to see what your design will look like on mobile devices. You can also make adjustments to your module, row and section settings for each mobile breakpoint. Click the highlighted “Tablet” icon to enter Tablet preview mode. ', 'et_builder' ),
'desktopPreview' => array(
'title' => esc_html__( 'Switch Back To Desktop Mode', 'et_builder' ),
'description' => esc_html__( 'You can switch back and forth between each preview mode freely while editing your page. Now that we have previewed our page on Tablet, let’s switch back to Desktop preview mode by clicking the highlighted button.', 'et_builder' ),
'title' => esc_html__( 'Access Your Editing History', 'et_builder' ),
'description' => esc_html__( 'Every change you make while editing your page is saved in your editing history. You can navigate backwards and forwards through time to any point during your current editing session, as well as undo and redo recent changes. Click the highlighted History button to access your editing history. ', 'et_builder' ),
'title' => esc_html__( 'Undo, Redo And Restore', 'et_builder' ),
'description' => esc_html__( 'Here you can undo, redo or restore a saved history state. If you change your mind about recent changes, simply click back in time and start building again. You can also undo and redo recent changes. Click the undo and redo buttons and then accept your changes by clicking the green check mark.', 'et_builder' ),
'title' => esc_html__( 'Save Your Page', 'et_builder' ),
'description' => sprintf(
// translators: %1$s: "Save" or "Publish" - label.
esc_html__( 'When you are all done, you can save your changes by clicking the %1$s button inside of your page settings bar. You can also press Ctrl + S at any time to save your changes. Click the highlighted Save button to save your changes. Don’t worry, the page you were working on before starting this tour will not be lost!', 'et_builder' ),
in_array( $post_status, array( 'private', 'publish' ), true ) ? esc_html__( 'Save', 'et_builder' ) : esc_html__( 'Publish', 'et_builder' )
'title' => esc_html__( 'You’re Ready To Go!', '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' ),
'endButtonTextDefault' => esc_html__( 'End the Tour', 'et_builder' ),
'skipButtonTextDefault' => esc_html__( 'Skip This Step', 'et_builder' ),
return $product_tour_text;
* Process builder shortcode into object.
* The standard do_shortcode filter should be removed, and
* this function hooked instead.
* This function is very similar to `do_shortcode`,
* with the main differences being:
* - Its main design is to allow recursive array to be built out of wp shortcode
* - Allows shortcode callback to return an array rather than a string
* - It tracks the inner `index` / `_i` of each child shortcode to the passed content, which is used in the address creation as well
* - It uses and passes `$address` & `$parent_address`, which are used by FB app.
* @param string $content post content.
* @param string $parent_address parent shortcode address.
* @param string $global_parent ?? @todo Add param doc.
* @param string $global_parent_type ?? @todo Add param doc.
* @param string $parent_type ?? @todo Add param doc.
function et_fb_process_shortcode( $content, $parent_address = '', $global_parent = '', $global_parent_type = '', $parent_type = '' ) {
global $shortcode_tags, $fb_processing_counter;
if ( false === strpos( $content, '[' ) ) {
// Count started processes.
$fb_processing_counter = isset( $fb_processing_counter ) ? $fb_processing_counter + 1 : 1;
// Find all registered tag names in $content.
preg_match_all( '@\[([^<>&/\[\]\x00-\x20=]++)@', $content, $matches );
// Only need unique tag names.
$unique_matches = array_unique( $matches[1] );
$tagnames = array_intersect( array_keys( $shortcode_tags ), $unique_matches );
$pattern = get_shortcode_regex( $unique_matches );
$content = preg_match_all( "/$pattern/", $content, $matches, PREG_SET_ORDER );
foreach ( $matches as $match ) {
// reset global parent data to calculate it correctly for next modules.
if ( $global_parent_type === $tag && '' !== $global_parent ) {
$global_parent_type = '';
$attr = shortcode_parse_atts( $match[3] );
if ( ! is_array( $attr ) ) {
$address = isset( $parent_address ) && '' !== $parent_address ? (string) $parent_address . '.' . (string) $index : (string) $index;
// set global parent and global parent tag if current module is global and can be a parent.
$possible_global_parents = array( 'et_pb_section', 'et_pb_row', 'et_pb_row_inner' );
if ( '' === $global_parent && in_array( $tag, $possible_global_parents, true ) ) {
$global_parent = isset( $attr['global_module'] ) ? $attr['global_module'] : '';
$global_parent_type = $tag;
// As responsive content attributes value might be has been encoded before saving to database,
// so we need to decode it before passing back to builder.
$decoded_content_fields = array(
foreach ( $decoded_content_fields as $decoded_content_field ) {
if ( array_key_exists( $decoded_content_field, $attr ) ) {
$attr[ $decoded_content_field ] = str_replace( array( '%22', '%92', '%91', '%93' ), array( '"', '\\', '[', ']' ), $attr[ $decoded_content_field ] );
$attr['_address'] = $address;
// Builder shortcode which exist on page but not registered in WP i.e. 3rd party shortcode when 3rd party module disabled
// Add dummy object to render it in Divi Builder.
if ( ! in_array( $tag, $tagnames, true ) ) {
'component_path' => 'et-fb-removed-component',
// Flag that the shortcode object is being built.
$GLOBALS['et_fb_processing_shortcode_object'] = true;
if ( isset( $match[5] ) ) {
// phpcs:ignore Generic.PHP.ForbiddenFunctions.Found -- `call_user_func` calls the registered shortcode callback.
$output = call_user_func( $shortcode_tags[ $tag ], $attr, $match[5], $tag, $parent_address, $global_parent, $global_parent_type, $parent_type );
// phpcs:ignore Generic.PHP.ForbiddenFunctions.Found -- `call_user_func` calls the registered shortcode callback.
$output = call_user_func( $shortcode_tags[ $tag ], $attr, null, $tag );
$_matches[] = et_fb_add_additional_attrs( $attr, $output );
// Count finished processes.
$fb_processing_counter = $fb_processing_counter - 1; // phpcs:ignore Squiz.Operators.IncrementDecrementUsage -- This is more readable.
// Make sure ALL the processes finished to avoid wrong disabling of `et_fb_processing_shortcode_object` when several concurrent instances of `et_fb_process_shortcode` running.
if ( 0 === $fb_processing_counter ) {
// Turn off the flag since the shortcode object is done being built.
et_fb_reset_shortcode_object_processing();
* Allowlist any additional attributes.
* @param array $processed_attrs Shortcode's processed attributes.
* @param array $output Shortcode output.
function et_fb_add_additional_attrs( $processed_attrs, $output ) {
if ( empty( $output['attrs'] ) ) {
// A list of all the attributes that are already returned after the shortcode is processed.
$safe_attrs = array_keys( $output['attrs'] );
$allowlisted_attrs = array();
foreach ( $processed_attrs as $attr => $value ) {
if ( ! preg_match( '~_hover(_enabled)?$~', $attr ) ) {
$allowlisted_attrs[ $attr ] = $value;
if ( $allowlisted_attrs ) {
$output['attrs'] = array_merge( $output['attrs'], $allowlisted_attrs );
* Parse builder shortcode into an array.
* @param string $content Builder built post content.
* @return array Array representation of the builder shortcode.
function et_pb_parse_shortcode_to_array( $content ) {
if ( false === strpos( $content, '[' ) ) {
// Find all registered tag names in $content.
preg_match_all( '@\[([^<>&/\[\]\x00-\x20=]++)@', $content, $matches );
$tagnames = array_intersect( array_keys( $shortcode_tags ), $matches[1] );
$pattern = get_shortcode_regex( $matches[1] );
$content = preg_match_all( "/$pattern/", $content, $matches, PREG_SET_ORDER );
$shortcode_data = array();
foreach ( $matches as $match ) {
$attr = shortcode_parse_atts( $match[3] );
if ( ! is_array( $attr ) ) {
$_shortcode_data = array(
if ( ! empty( $match[5] ) ) {
$_shortcode_data['content'] = et_pb_parse_shortcode_to_array( $match[5] );
$shortcode_data[] = $_shortcode_data;
* Parse post content that includes builder shortcode to an array,
* then run it back through "some sanity check" and "some sanitization"
* and then form into a shortcode again.
* @see et_fb_process_to_shortcode() for exact sanitizations performed.
* @param string $content Builder built post content.
* @param bool $force_valid_builder_slugs Whether to force the shortcode to allow valid builder shortcode slugs only.
* @return string Sanitized builder built post content.
function et_pb_sanitize_shortcode( $content, $force_valid_builder_slugs = false ) {
if ( false === strpos( $content, '[' ) ) {
$content_array = et_pb_parse_shortcode_to_array( $content );
'force_valid_slugs' => $force_valid_builder_slugs,
$new_content = et_fb_process_to_shortcode( $content_array, $options );
* Use shortcode tag which renders the content to correctly display its properties.
* @param string $tag shortcode tag.
function et_fb_prepare_tag( $tag ) {
$aliases = apply_filters(
'et_fb_prepare_tag_aliases',
'et_pb_accordion_item' => 'et_pb_toggle',
return isset( $aliases[ $tag ] ) ? $aliases[ $tag ] : $tag;
if ( ! function_exists( 'et_strip_shortcodes' ) ) :
* Strip builder shortcodes only, leaving default WordPress shortcodes intact.
* @param string $content the content.
* @param string $truncate_post_based_shortcodes_only Optional. Whether trunct only post based shortcodes.
function et_strip_shortcodes( $content, $truncate_post_based_shortcodes_only = false ) {
$content = trim( $content );
$strip_content_shortcodes = array(
// list of post-based shortcodes.
if ( $truncate_post_based_shortcodes_only ) {
$strip_content_shortcodes = array(
'et_pb_fullwidth_post_slider',
foreach ( $strip_content_shortcodes as $shortcode_name ) {
'(\[%1$s[^\]]*\][^\[]*\[\/%1$s\]|\[%1$s[^\]]*\])',
esc_html( $shortcode_name )
$content = preg_replace( $regex, '', $content );
// do not proceed if we need to truncate post-based shortcodes only.
if ( $truncate_post_based_shortcodes_only ) {
$shortcode_tag_names = array();
foreach ( $shortcode_tags as $shortcode_tag_name => $shortcode_tag_cb ) {
if ( 0 !== strpos( $shortcode_tag_name, 'et_pb_' ) ) {
$shortcode_tag_names[] = $shortcode_tag_name;
$et_shortcodes = implode( '|', $shortcode_tag_names );
$regex_opening_shortcodes = sprintf( '(\[(%1$s)[^\]]+\])', esc_html( $et_shortcodes ) );
$regex_closing_shortcodes = sprintf( '(\[\/(%1$s)\])', esc_html( $et_shortcodes ) );
$content = preg_replace( $regex_opening_shortcodes, '', $content );
$content = preg_replace( $regex_closing_shortcodes, '', $content );
* Reset shortcode object processing.
function et_fb_reset_shortcode_object_processing() {
$GLOBALS['et_fb_processing_shortcode_object'] = false;
add_action( 'et_fb_enqueue_assets', 'et_fb_backend_helpers' );
if ( ! function_exists( 'et_builder_maybe_flush_rewrite_rules' ) ) :
* Flush rewrite rules if theme option saved value and passed $value are not same.
* @param string $setting_name The theme option.
* @param string $value The value to be compared.
function et_builder_maybe_flush_rewrite_rules( $setting_name, $value = 'done' ) {
$string_value = (string) $value;
$saved_value = et_get_option( $setting_name );
if ( $saved_value && $saved_value === $string_value ) {
et_update_option( $setting_name, $string_value );
* Flush rewrite rules to fix the issue Layouts, not being visible on front-end and visual builder,
* if pretty permalinks were enabled
function et_pb_maybe_flush_rewrite_rules_library() {
// Run flush rewrite only when et_pb_layout post type registered.
if ( post_type_exists( 'et_pb_layout' ) ) {
et_builder_maybe_flush_rewrite_rules( 'et_flush_rewrite_rules_library', ET_BUILDER_PRODUCT_VERSION );
add_action( 'init', 'et_pb_maybe_flush_rewrite_rules_library', 9 );
* Remove et_builder_maybe_flush_rewrite_rules flag if flush_rewrite_rules() is called while
* `et_pb_layout` post type hasn't been registered
* @param string|array $old_value old option value.
* @param string|array $value new option value.
* @param string $option option name.
function et_pb_maybe_remove_flush_rewrite_rules_library_flag( $old_value, $value, $option ) {
// rewrite rules for CPT that are rebuilt by flush_rewrite_rules() are based on
// get_post_types( array( '_builtin' => false ) ) value; Hence if flush_rewrite_rules() is
// executed while `et_pb_layout` CPT hasn't been registered (usually by third party plugin)
// et_pb_maybe_flush_rewrite_rules_library() flag has to be removed to trigger flush_rewrite_rules()
// via et_pb_maybe_flush_rewrite_rules_library() which contains `et_pb_layout` rewrite rules
// because et_pb_maybe_flush_rewrite_rules_library() checks for `et_pb_layout` first.
if ( '' === $value && ! post_type_exists( 'et_pb_layout' ) ) {
et_update_option( 'et_flush_rewrite_rules_library', '' );
add_action( 'update_option_rewrite_rules', 'et_pb_maybe_remove_flush_rewrite_rules_library_flag', 10, 3 );
if ( ! function_exists( 'et_builder_get_shortcuts' ) ) :
* Get list of shortcut available on BB and FB
* @param string $on (fb|bb) shortcut mode.
* @return array shortcut list
function et_builder_get_shortcuts( $on = 'fb' ) {