', et_core_esc_previously( $output ) );
return $output;
}
endif;
if ( ! function_exists( 'et_pb_get_font_down_icon_list_items' ) ) :
/**
* Return font down icon list items.
*/
function et_pb_get_font_down_icon_list_items() {
$output = '';
$symbols = et_pb_get_font_down_icon_symbols();
foreach ( $symbols as $symbol ) {
$output .= sprintf( '', esc_attr( $symbol ) );
}
return $output;
}
endif;
if ( ! function_exists( 'et_pb_font_down_icon_list' ) ) :
/**
* Return icon to display for the scroll down button.
*/
function et_pb_font_down_icon_list() {
echo et_core_esc_previously( et_pb_get_font_down_icon_list() );
}
endif;
if ( ! function_exists( 'et_pb_process_font_icon' ) ) :
/**
* Processes font icon value for use on front-end
*
* @param string $font_icon Font Icon ( exact value or in %%index_number%% format ).
* @param string $symbols_function Optional. Name of the function that gets an array of font icon values.
* et_pb_get_font_icon_symbols function is used by default.
* @return string $font_icon Font Icon value
*/
function et_pb_process_font_icon( $font_icon, $symbols_function = 'default' ) {
// Do it if $font_icon is an extended icon.
if ( et_pb_maybe_extended_icon( $font_icon ) ) {
return et_pb_get_extended_font_icon_value( $font_icon );
}
// the exact font icon value is saved.
if ( 1 !== preg_match( '/^%%/', trim( $font_icon ) ) ) {
return $font_icon;
}
// the font icon value is saved in the following format: %%index_number%%.
$icon_index = (int) str_replace( '%', '', $font_icon );
$icon_symbols = 'default' === $symbols_function ? et_pb_get_font_icon_symbols() : call_user_func( $symbols_function );
$font_icon = isset( $icon_symbols[ $icon_index ] ) ? $icon_symbols[ $icon_index ] : '';
return $font_icon;
}
endif;
if ( ! function_exists( 'et_builder_accent_color' ) ) :
/**
* Return an accent color.
*
* @param string $default_color Default color.
*/
function et_builder_accent_color( $default_color = '#7EBEC5' ) {
// Accent color option exists in Divi theme only. Use default color in plugin.
$accent_color = ! et_is_builder_plugin_active() ? et_get_option( 'accent_color', $default_color ) : $default_color;
return apply_filters( 'et_builder_accent_color', $accent_color );
}
endif;
if ( ! function_exists( 'et_pb_process_header_level' ) ) :
/**
* Process header level.
*
* @param string $new_level Header level.
* @param string $default Default header level.
*
* @return string
*/
function et_pb_process_header_level( $new_level, $default ) {
$valid_header_levels = array( 'h1', 'h2', 'h3', 'h4', 'h5', 'h6' );
// return the new header level if exists in the list of valid header levels.
if ( in_array( $new_level, $valid_header_levels, true ) ) {
return $new_level;
}
// return default if defined. Fallback to h2 otherwise.
return isset( $default ) ? $default : 'h2';
}
endif;
if ( ! function_exists( 'et_pb_get_alignment' ) ) {
/**
* Return an alignment value from alignment key.
*
* @param string $key Alignment key.
*
* @return string
*/
function et_pb_get_alignment( $key ) {
if ( is_rtl() && 'left' === $key ) {
$key = 'right';
}
switch ( $key ) {
case 'force_left':
return 'left';
case 'justified':
return 'justify';
default:
return $key;
}
}
}
if ( ! function_exists( 'et_builder_get_text_orientation_options' ) ) :
/**
* Return text orientation options to use in dropdown.
*
* @param array $exclude_options Options to be excluded.
* @param array $include_options Options to be included.
*/
function et_builder_get_text_orientation_options( $exclude_options = array(), $include_options = array() ) {
$text_orientation_options = array(
'left' => et_builder_i18n( 'Left' ),
'center' => et_builder_i18n( 'Center' ),
'right' => et_builder_i18n( 'Right' ),
'justified' => et_builder_i18n( 'Justified' ),
);
if ( is_rtl() ) {
$text_orientation_options = array(
'right' => et_builder_i18n( 'Right' ),
'center' => et_builder_i18n( 'Center' ),
'force_left' => et_builder_i18n( 'Left' ),
);
}
// Exclude some options if needed.
if ( ! empty( $exclude_options ) ) {
foreach ( $exclude_options as $exclude ) {
unset( $text_orientation_options[ $exclude ] );
}
}
// Include some options if needed.
if ( ! empty( $include_options ) ) {
$text_orientation_options = wp_parse_args( $include_options, $text_orientation_options );
}
return apply_filters( 'et_builder_text_orientation_options', $text_orientation_options );
}
endif;
if ( ! function_exists( 'et_builder_get_gallery_settings' ) ) :
/**
* Return gallery button.
*/
function et_builder_get_gallery_settings() {
$output = sprintf(
'',
esc_attr__( 'Update Gallery', 'et_builder' )
);
return $output;
}
endif;
if ( ! function_exists( 'et_builder_get_nav_menus_options' ) ) :
/**
* Return navigation menus options.
*/
function et_builder_get_nav_menus_options() {
$nav_menus_options = array( 'none' => esc_html__( 'Select a menu', 'et_builder' ) );
$nav_menus = wp_get_nav_menus( array( 'orderby' => 'name' ) );
foreach ( (array) $nav_menus as $_nav_menu ) {
$nav_menus_options[ $_nav_menu->term_id ] = $_nav_menu->name;
}
return apply_filters( 'et_builder_nav_menus_options', $nav_menus_options );
}
endif;
if ( ! function_exists( 'et_builder_generate_center_map_setting' ) ) :
/**
* Return center map div.
*/
function et_builder_generate_center_map_setting() {
return '';
}
endif;
if ( ! function_exists( 'et_builder_generate_pin_zoom_level_input' ) ) :
/**
* Return map's pin zoom level input element.
*/
function et_builder_generate_pin_zoom_level_input() {
return '';
}
endif;
/**
* Define conditional tags needed for component's backend parser. This is used for FB's public facing update
* mechanism to pass conditional tag to admin-ajax.php for component which relies to backend parsing. Backend
* uses this conditional tags' key as well for sanitization
*
* @return array
*/
function et_fb_conditional_tag_params() {
global $post;
$post_type = isset( $post->post_type ) ? $post->post_type : false;
$conditional_tags = array(
'is_limited_mode' => et_builder_is_limited_mode(),
'is_bfb' => et_builder_bfb_enabled(),
'is_bfb_activated' => et_builder_bfb_activated(),
'is_tb' => et_builder_tb_enabled(),
'is_front_page' => is_front_page(),
'is_home_page' => is_home() || is_front_page(),
'is_search' => is_search(),
'is_single' => is_single(),
'is_singular' => is_singular(),
'is_singular_project' => is_singular( 'project' ),
'is_rtl' => is_rtl(),
'is_no_rtl' => 'on' === et_get_option( 'divi_disable_translations', 'off' ),
'et_is_builder_plugin_active' => et_is_builder_plugin_active(),
'is_user_logged_in' => is_user_logged_in(),
'et_is_ab_testing_active' => et_is_ab_testing_active() ? 'yes' : 'no',
'is_wrapped_styles' => et_builder_has_limitation( 'use_wrapped_styles' ),
'is_gutenberg' => et_core_is_gutenberg_active(),
'is_custom_post_type' => et_builder_is_post_type_custom( $post_type ),
'is_layout_post_type' => et_theme_builder_is_layout_post_type( $post_type ),
'is_rich_editor' => 'true' === apply_filters( 'user_can_richedit', get_user_option( 'rich_editing' ) ) ? 'yes' : 'no',
// Pass falsey as empty string so it remains falsey when conditionalTags is fetched and
// passed string as AJAX payload (on AJAX string, false bool becomes 'false' string).
'is_layout_block' => ET_GB_Block_Layout::is_layout_block_preview() ? true : '',
);
return apply_filters( 'et_fb_conditional_tag_params', $conditional_tags );
}
if ( ! function_exists( 'et_builder_page_creation_options' ) ) :
/**
* Get Page Creation flow options
*
* @since 4.2
*
* @return array
*/
function et_builder_page_creation_options() {
return array(
'build_from_scratch' => array(
'className' => 'accent-blue',
'imgSrc' => 'scratch.png',
'imgSrcHover' => 'scratch.gif',
'titleText' => esc_html__( 'Build From Scratch', 'et_builder' ),
'descriptionText' => esc_html__( 'Build your page from the ground up. Don’t worry, you can access our premade layouts at any time.', 'et_builder' ),
'buttonText' => esc_html__( 'Start Building', 'et_builder' ),
'permission' => array( 'add_module' ),
'setting' => array(
'value_index' => 1,
),
),
'use_existing_content' => array(
'className' => 'accent-blue',
'imgSrc' => 'existing.png',
'imgSrcHover' => 'existing.gif',
'titleText' => esc_html__( 'Use Existing Content', 'et_builder' ),
'descriptionText' => esc_html__( 'Use the Divi Builder while retaining your existing page content.', 'et_builder' ),
'buttonText' => esc_html__( 'Start Building', 'et_builder' ),
'permission' => array( 'edit_module' ),
'setting' => false,
),
'choose_premade_layout' => array(
'className' => 'accent-purple',
'imgSrc' => 'premade.png',
'imgSrcHover' => 'premade.gif',
'titleText' => esc_html__( 'Choose a premade Layout', 'et_builder' ),
'descriptionText' => esc_html__( 'Choose from hundreds of premade layouts, start from any of your saved layouts, or clone an existing page.', 'et_builder' ),
'buttonText' => esc_html__( 'Browse Layouts', 'et_builder' ),
'permission' => array( 'load_layout', 'divi_library' ),
'setting' => array(
'label' => esc_html__( 'Load Premade Layout', 'et_builder' ),
'value_index' => 2,
),
),
'build_with_ai' => array(
'className' => 'accent-dark-blue',
'imgSrc' => 'layout-insert-build-with-ai.svg',
'imgSrcHover' => 'layout-insert-build-with-ai.svg',
'titleText' => esc_html__( 'Build With AI', 'et_builder' ),
'bannerText' => esc_html__( 'Brand New', 'et_builder' ),
'descriptionText' => esc_html__( 'Simply describe your page content, sit back, relax, and let Divi AI build your page with the click of a button.', 'et_builder' ),
'buttonText' => esc_html__( 'Generate Layout', 'et_builder' ),
'permission' => array( 'divi_ai' ),
'setting' => array(
'value_index' => 3,
),
),
);
}
endif;
if ( ! function_exists( 'et_builder_page_creation_settings' ) ) :
/**
* Get Page Creation flow setting options
*
* @since 4.2
*
* @param bool $value_as_index Flag to set the options value as numeric index.
*
* @return array
*/
function et_builder_page_creation_settings( $value_as_index = false ) {
$default_label = esc_html__( 'Give Me A Choice', 'et_builder' );
if ( $value_as_index ) {
$settings = array(
$default_label,
);
} else {
$settings = array(
'default' => $default_label,
);
}
foreach ( et_builder_page_creation_options() as $key => $option ) {
if ( ! et_()->array_get( $option, 'setting' ) ) {
continue;
}
if ( isset( $option['permission'] ) ) {
$capabilities = is_array( $option['permission'] ) ? $option['permission'] : explode( ',', $option['permission'] );
$allowed = array_filter( $capabilities, 'et_pb_is_allowed' );
if ( ! $allowed || count( $capabilities ) !== count( $allowed ) ) {
continue;
}
}
$value = $value_as_index ? $option['setting']['value_index'] : $key;
$label = et_()->array_get( $option, 'setting.label', $option['titleText'] );
$settings[ $value ] = $label;
}
return $settings;
}
endif;
/**
* Return an app preferences.
*
* @return mixed|void
*/
function et_fb_app_preferences_settings() {
$app_preferences = array(
'settings_bar_location' => array(
'type' => 'string',
'default' => 'bottom',
'options' => array(
'top-left',
'top',
'top-right',
'right',
'bottom-right',
'bottom',
'bottom-left',
'left',
),
),
'builder_animation' => array(
'type' => 'bool',
'default' => true,
),
'builder_display_modal_settings' => array(
'type' => 'bool',
'default' => false,
),
'builder_enable_dummy_content' => array(
'type' => 'bool',
'default' => true,
),
'builder_enable_visual_theme_builder' => array(
'type' => 'bool',
'default' => true,
),
'event_mode' => array(
'type' => 'string',
'default' => 'hover',
'options' => array(
'hover' => esc_html__( 'Hover Mode', 'et_builder' ),
'click' => esc_html__( 'Click Mode', 'et_builder' ),
'grid' => esc_html__( 'Grid Mode', 'et_builder' ),
),
),
'view_mode' => array(
'type' => 'string',
'default' => et_builder_bfb_enabled() ? 'wireframe' : 'desktop',
'options' => array(
'desktop' => esc_html__( 'Desktop View', 'et_builder' ),
'tablet' => esc_html__( 'Tablet View', 'et_builder' ),
'phone' => esc_html__( 'Phone View', 'et_builder' ),
'wireframe' => esc_html__( 'Wireframe View', 'et_builder' ),
),
),
'hide_disabled_modules' => array(
'type' => 'bool',
'default' => false,
),
'history_intervals' => array(
'type' => 'int',
'default' => 1,
'options' => array(
'1' => esc_html__( 'After Every Action', 'et_builder' ),
'10' => esc_html__( 'After Every 10th Action', 'et_builder' ),
'20' => esc_html__( 'After Every 20th Action', 'et_builder' ),
'30' => esc_html__( 'After Every 30th Action', 'et_builder' ),
'40' => esc_html__( 'After Every 40th Action', 'et_builder' ),
),
),
'page_creation_flow' => array(
'type' => 'string',
'default' => 'default',
'options' => et_builder_page_creation_settings(),
),
'quick_actions_always_start_with' => array(
'type' => 'string',
'default' => 'nothing',
),
'quick_actions_show_recent_queries' => array(
'type' => 'string',
'default' => 'off',
),
'quick_actions_recent_queries' => array(
'type' => 'string',
'default' => '',
'max_length' => 100,
),
'quick_actions_recent_category' => array(
'type' => 'string',
'default' => '',
'max_length' => 100,
),
'modal_preference' => array(
'type' => 'string',
'default' => 'default',
'options' => array(
'default' => esc_html__( 'Last Used Position', 'et_builder' ),
'minimum' => esc_html__( 'Floating Minimum Size', 'et_builder' ),
'fullscreen' => esc_html__( 'Fullscreen', 'et_builder' ),
'left' => esc_html__( 'Fixed Left Sidebar', 'et_builder' ),
'right' => esc_html__( 'Fixed Right Sidebar', 'et_builder' ),
'bottom' => esc_html__( 'Fixed Bottom Panel', 'et_builder' ),
// TODO, disabled until further notice (Issue #3930 & #5859)
// 'top' => esc_html__( 'Fixed Top Panel', 'et_builder' ),.
),
),
'modal_snap_location' => array(
'type' => 'string',
'default' => '',
),
'modal_snap' => array(
'type' => 'bool',
'default' => false,
),
'modal_fullscreen' => array(
'type' => 'bool',
'default' => false,
),
'modal_dimension_width' => array(
'type' => 'int',
'default' => 400,
),
'modal_dimension_height' => array(
'type' => 'int',
'default' => 400,
),
'modal_position_x' => array(
'type' => 'int',
'default' => 30,
),
'modal_position_y' => array(
'type' => 'int',
'default' => 50,
),
'toolbar_click' => array(
'type' => 'bool',
'default' => false,
),
'toolbar_desktop' => array(
'type' => 'bool',
'default' => true,
),
'toolbar_grid' => array(
'type' => 'bool',
'default' => false,
),
'toolbar_hover' => array(
'type' => 'bool',
'default' => false,
),
'toolbar_phone' => array(
'type' => 'bool',
'default' => true,
),
'toolbar_tablet' => array(
'type' => 'bool',
'default' => true,
),
'toolbar_wireframe' => array(
'type' => 'bool',
'default' => true,
),
'toolbar_zoom' => array(
'type' => 'bool',
'default' => true,
),
'lv_modal_dimension_height' => array(
'type' => 'int',
'default' => 0,
),
'lv_modal_dimension_width' => array(
'type' => 'int',
'default' => 0,
),
'lv_modal_position_x' => array(
'type' => 'int',
'default' => 0,
),
'lv_modal_position_y' => array(
'type' => 'int',
'default' => 0,
),
// Re: "width/height": responsive dimensions presume portrait orientation.
'responsive_tablet_width' => array(
'type' => 'int',
'default' => 768,
),
'responsive_tablet_height' => array(
'type' => 'int',
'default' => 0,
),
'responsive_phone_width' => array(
'type' => 'int',
'default' => 400,
),
'responsive_phone_height' => array(
'type' => 'int',
'default' => 0,
),
'responsive_minimum_width' => array(
'type' => 'int',
'default' => 320,
),
'responsive_maximum_width' => array(
'type' => 'int',
'default' => 980,
),
);
return apply_filters( 'et_fb_app_preferences_defaults', $app_preferences );
}
/**
* Return the preferences list which should not be synced between Visual Builder and Backend Visual Builder.
*
* @return mixed|void
*/
function et_fb_unsynced_preferences() {
/**
* Filters the preferences list which should not be synced between Visual Builder and Backend Visual Builder.
*
* @since 3.18
*
* @param array
*/
return apply_filters( 'et_fb_app_preferences_unsynced', array( 'view_mode', 'toolbar_click', 'toolbar_desktop', 'toolbar_grid', 'toolbar_hover', 'toolbar_phone', 'toolbar_tablet', 'toolbar_wireframe', 'toolbar_zoom', 'modal_preference' ) );
}
/**
* Return app preferences.
*
* @return mixed|void
*/
function et_fb_app_preferences() {
$app_preferences = et_fb_app_preferences_settings();
if ( et_is_builder_plugin_active() ) {
// Since Divi Builder Plugin is always 'limited', need to use a different
// condition to prefix the options when BFB is used.
$limited_prefix = et_builder_bfb_enabled() ? 'limited_' : '';
} else {
$limited_prefix = et_builder_is_limited_mode() ? 'limited_' : '';
}
foreach ( $app_preferences as $preference_key => $preference ) {
$option_name = 'et_fb_pref_' . $preference_key;
// Some preferences should not be synced between VB and Limited VB.
if ( in_array( $preference_key, et_fb_unsynced_preferences(), true ) ) {
$option_name = 'et_fb_pref_' . $limited_prefix . $preference_key;
}
$option_value = et_get_option( $option_name, $preference['default'], '', true );
// If options available, verify returned value against valid options. Return default if fails.
if ( isset( $preference['options'] ) ) {
$options = $preference['options'];
$valid_options = isset( $options[0] ) ? $options : array_keys( $options );
// phpcs:ignore WordPress.PHP.StrictInArray -- $valid_options array has strings and numbers values.
if ( ! in_array( (string) $option_value, $valid_options ) ) {
$option_value = $preference['default'];
}
}
// Exceptional preference. Snap left is not supported in Limited mode, so replace it with default.
if ( '' !== $limited_prefix && 'modal_snap_location' === $preference_key && 'left' === $option_value ) {
$option_value = $preference['default'];
}
$app_preferences[ $preference_key ]['value'] = $option_value;
}
return apply_filters( 'et_fb_app_preferences', $app_preferences );
}
/**
* Woocommerce Components for visual builder
*
* @since 4.0.1
*
* @return array
*/
function et_fb_current_page_woocommerce_components() {
$is_product_cpt = 'product' === get_post_type();
$is_tb = et_builder_tb_enabled();
$cpt_has_wc_components = $is_product_cpt || $is_tb;
$has_wc_components = et_is_woocommerce_plugin_active() && $cpt_has_wc_components;
if ( $has_wc_components && $is_tb ) {
// Set upsells ID for upsell module in TB.
ET_Theme_Builder_Woocommerce_Product_Variable_Placeholder::set_tb_upsells_ids();
// Force set product's class to ET_Theme_Builder_Woocommerce_Product_Variable_Placeholder in TB.
add_filter( 'woocommerce_product_class', 'et_theme_builder_wc_product_class' );
// Set product categories and tags in TB.
add_filter( 'get_the_terms', 'et_theme_builder_wc_terms', 10, 3 );
// Use Divi's image placeholder in TB.
add_filter( 'woocommerce_single_product_image_thumbnail_html', 'et_builder_wc_placeholder_img' );
}
$woocommerce_components = ! $has_wc_components ? array() : array(
'et_pb_wc_add_to_cart' => ET_Builder_Module_Woocommerce_Add_To_Cart::get_add_to_cart(),
'et_pb_wc_additional_info' => ET_Builder_Module_Woocommerce_Additional_Info::get_additional_info(),
'et_pb_wc_breadcrumb' => ET_Builder_Module_Woocommerce_Breadcrumb::get_breadcrumb(),
'et_pb_wc_cart_notice' => ET_Builder_Module_Woocommerce_Cart_Notice::get_cart_notice(),
'et_pb_wc_description' => ET_Builder_Module_Woocommerce_Description::get_description(),
'et_pb_wc_images' => ET_Builder_Module_Woocommerce_Images::get_images(),
'et_pb_wc_meta' => ET_Builder_Module_Woocommerce_Meta::get_meta(),
'et_pb_wc_price' => ET_Builder_Module_Woocommerce_Price::get_price(),
'et_pb_wc_rating' => ET_Builder_Module_Woocommerce_Rating::get_rating(),
'et_pb_wc_reviews' => ET_Builder_Module_Woocommerce_Reviews::get_reviews_html(),
'et_pb_wc_stock' => ET_Builder_Module_Woocommerce_Stock::get_stock(),
'et_pb_wc_tabs' => ET_Builder_Module_Woocommerce_Tabs::get_tabs(),
'et_pb_wc_title' => ET_Builder_Module_Woocommerce_Title::get_title(),
'et_pb_wc_related_products' => ET_Builder_Module_Woocommerce_Related_Products::get_related_products(),
'et_pb_wc_upsells' => ET_Builder_Module_Woocommerce_Upsells::get_upsells(),
);
return $woocommerce_components;
}
/**
* Before & after components for builder.
*
* This method should not be used for anything other than to determine whether a module
* has before & after components on builder load.
*
* @since 4.14.5
*
* @return array Components (HTML).
*/
function et_fb_current_page_before_after_components() {
$modules_components = array();
// Bail early if current request comes from any Ajax request.
if ( wp_doing_ajax() ) {
return $modules_components;
}
// Bail early if Module Shortcode Manager class doesn't exist.
if ( ! class_exists( 'ET_Builder_Module_Shortcode_Manager' ) ) {
return $modules_components;
}
/**
* Filters modules list.
*
* The modules list comes from Shortcode Manager only contains built-in modules
* intentionally. 3rd-party modules need to include their module slug and class name
* via `et_fb_fetch_before_after_modules_map` filter.
*
* @param array Modules list.
*/
$modules_map = apply_filters( 'et_fb_fetch_before_after_modules_map', ET_Builder_Module_Shortcode_Manager::get_modules_map() );
// Bail early if components map is empty.
if ( empty( $modules_map ) ) {
return $modules_components;
}
foreach ( $modules_map as $module_slug => $module_data ) {
$module_class = et_()->array_get( $module_data, 'classname' );
// Skip if module class name is not found.
if ( empty( $module_class ) || ! class_exists( $module_class ) ) {
continue;
}
$module_components = $module_class::get_component_before_after_module( $module_slug, array() );
// Skip if there is no before & after components.
$has_components = et_()->array_get( $module_components, 'has_components' );
if ( true !== $has_components ) {
continue;
}
$modules_components[ $module_slug ] = $module_components;
}
return $modules_components;
}
/**
* Array of WooCommerce Tabs.
*
* @since 4.4.2 Fixed fatal error @link https://github.com/elegantthemes/Divi/issues/19404
* @since 4.4.2 Added Custom Tabs support.
*
* @used-by et_fb_current_page_params()
*
* @return array
*/
function et_fb_woocommerce_tabs() {
global $product, $post;
$old_product = $product;
$old_post = $post;
$is_product = isset( $product ) && is_a( $product, 'WC_Product' );
if ( ! $is_product && et_is_woocommerce_plugin_active() ) {
$product = ET_Builder_Module_Helper_Woocommerce_Modules::get_product( 'latest' );
if ( $product ) {
$post = get_post( $product->get_id() ); // phpcs:ignore WordPress.WP.GlobalVariablesOverride -- Overriding global post is safe as original $post has been restored at the end.
} else {
$product = $old_product;
return ET_Builder_Module_Helper_Woocommerce_Modules::get_default_tab_options();
}
}
// On non-product post types, the filter will cause fatal error
// unless we have global $product set.
$tabs = apply_filters( 'woocommerce_product_tabs', array() );
$options = array();
foreach ( $tabs as $name => $tab ) {
$options[ $name ] = array(
'value' => $name,
'label' => $tab['title'],
);
}
// Reset global $product.
$product = $old_product;
$post = $old_post; // phpcs:ignore WordPress.WP.GlobalVariablesOverride -- Restoring original global $post data.
return $options;
}
/**
* Get the category taxonomy associated with a given post type.
*
* @since 4.0.6
*
* @param string $post_type Post type.
*
* @return string|bool
*/
function et_builder_get_category_taxonomy( $post_type ) {
static $cache = array();
// Address common cases.
switch ( $post_type ) {
case 'page':
return false;
case 'post':
return 'category';
case 'project':
return 'project_category';
case 'product':
return 'product_cat';
}
if ( isset( $cache[ $post_type ] ) ) {
// Use cached value.
return $cache[ $post_type ];
}
// Unknown post_type, guess the taxonomy.
$taxonomies = get_object_taxonomies( $post_type, 'names' );
foreach ( array( 'category', 'cat' ) as $pattern ) {
$matches = preg_grep( '/' . $pattern . '$/', $taxonomies );
if ( ! empty( $matches ) ) {
$cache[ $post_type ] = reset( $matches );
return $cache[ $post_type ];
}
}
// Tough luck.
$cache[ $post_type ] = false;
return $cache[ $post_type ];
}
/**
* Retrieve a post's category terms as a list with specified format.
*
* @since 4.0.6
*
* @param string $separator Optional. Separate items using this.
*
* @return string|false|WP_Error A list of terms on success, false if there are no terms, WP_Error on failure.
*/
function et_builder_get_the_term_list( $separator = '' ) {
$id = get_the_ID();
$taxonomy = et_builder_get_category_taxonomy( get_post_type( $id ) );
return $taxonomy ? get_the_term_list( $id, $taxonomy, $before = '', $separator ) : false;
}
/**
* Define current-page related data that are needed by frontend builder. Backend parser also uses this
* to sanitize updated value for computed data
*
* @return array
*/
function et_fb_current_page_params() {
global $post, $authordata, $paged;
// Get current page url.
$current_url = ( isset( $_SERVER['HTTP_HOST'] ) && isset( $_SERVER['REQUEST_URI'] ) ) ? ( is_ssl() ? 'https://' : 'http://' ) . sanitize_text_field( $_SERVER['HTTP_HOST'] ) . sanitize_text_field( $_SERVER['REQUEST_URI'] ) : '';
// Fallback for preview.
if ( empty( $authordata ) && isset( $post->post_author ) ) {
// phpcs:ignore WordPress.WP.GlobalVariablesOverride.Prohibited -- A fallback to set global $authordata.
$authordata = get_userdata( $post->post_author );
}
// Get comment count.
$comment_count = isset( $post->ID ) ? get_comments_number( $post->ID ) : 0;
// WordPress' _n() only supports singular n plural, thus we do comment count to text manually.
if ( 0 === $comment_count ) {
$comment_count_text = __( 'No Comments', 'et_builder' );
} elseif ( 1 === $comment_count ) {
$comment_count_text = __( '1 Comment', 'et_builder' );
} else {
// translators: comments count.
$comment_count_text = sprintf( __( '%d Comments', 'et_builder' ), $comment_count );
}
// Get current page paginated data.
$et_paged = is_front_page() ? get_query_var( 'page' ) : get_query_var( 'paged' );
// Get thumbnail size.
$thumbnail_size = isset( $post->ID ) && 'post' === get_post_type( $post->ID ) && 'et_full_width_page' === get_post_meta( $post->ID, '_et_pb_page_layout', true ) ? 'et-pb-post-main-image-fullwidth-large' : 'large';
$post_id = isset( $post->ID ) ? $post->ID : (int) et_()->array_get( $_POST, 'current_page.id' ); // phpcs:ignore WordPress.Security.NonceVerification -- This function does not change any state, and is therefore not susceptible to CSRF.
$exclude_woo = wp_doing_ajax() || ! et_is_woocommerce_plugin_active() || 'latest' === ET_Builder_Module_Helper_Woocommerce_Modules::get_product_default();
$default_categories = array( get_term_by( 'name', 'Uncategorized', 'category' ) );
$categories = et_pb_get_post_categories( $post_id, $default_categories );
// phpcs:ignore
$block_id = et_()->array_get( $_GET, 'blockId', '' );
$current_page = array(
'url' => esc_url( $current_url ),
'permalink' => esc_url( remove_query_arg( 'et_fb', $current_url ) ),
'backendBuilderUrl' => esc_url( sprintf( admin_url( '/post.php?post=%d&action=edit' ), get_the_ID() ) ),
'id' => isset( $post->ID ) ? $post->ID : false,
'title' => esc_html( get_the_title() ),
'thumbnailUrl' => isset( $post->ID ) ? esc_url( get_the_post_thumbnail_url( $post->ID, $thumbnail_size ) ) : '',
'thumbnailId' => isset( $post->ID ) ? get_post_thumbnail_id( $post->ID ) : '',
'authorName' => esc_html( get_the_author() ),
'authorUrl' => isset( $authordata->ID ) && isset( $authordata->user_nicename ) ? esc_html( get_author_posts_url( $authordata->ID, $authordata->user_nicename ) ) : false,
// translators: post author name.
'authorUrlTitle' => sprintf( esc_html__( 'Posts by %s', 'et_builder' ), get_the_author() ),
'date' => intval( get_the_time( 'U' ) ),
'categories' => $categories,
'commentsPopup' => esc_html( $comment_count_text ),
'commentsCount' => esc_html( $comment_count ),
'comments_popup_tb' => esc_html__( '12 Comments', 'et_builder' ),
'paged' => is_front_page() ? $et_paged : $paged,
'post_modified' => isset( $post->ID ) ? esc_attr( $post->post_modified ) : '',
'lang' => get_locale(),
'blockId' => ET_GB_Block_Layout::is_layout_block_preview() ? sanitize_title( et_()->array_get( $_GET, 'blockId', '' ) ) : '', // phpcs:ignore WordPress.Security.NonceVerification -- This function does not change any state, and is therefore not susceptible to CSRF.
'langCode' => get_locale(),
'page_layout' => $post_id ? get_post_meta( $post_id, '_et_pb_page_layout', true ) : '',
'woocommerceComponents' => $exclude_woo ? array() : et_fb_current_page_woocommerce_components(),
'woocommerceTabs' => et_builder_tb_enabled() && et_is_woocommerce_plugin_active() ?
ET_Builder_Module_Helper_Woocommerce_Modules::get_default_tab_options() : et_fb_woocommerce_tabs(),
'woocommerce' => array(
'inactive_module_notice' => esc_html__(
'WooCommerce must be active for this module to appear',
'et_builder'
),
),
'beforeAfterComponents' => et_fb_current_page_before_after_components(),
);
return apply_filters( 'et_fb_current_page_params', $current_page );
}
/**
* Ajax Callback :: Process computed property.
*/
function et_pb_process_computed_property() {
if ( ! isset( $_POST['et_pb_process_computed_property_nonce'] ) || ! wp_verify_nonce( sanitize_text_field( $_POST['et_pb_process_computed_property_nonce'] ), 'et_pb_process_computed_property_nonce' ) ) {
die( -1 );
}
if ( ! current_user_can( 'edit_posts' ) ) {
die( -1 );
}
if ( ! isset( $_POST['depends_on'], $_POST['conditional_tags'], $_POST['current_page'] ) ) {
// Shouldn't even be a possibility, but...
// Since computing `__page` can exit here too, we need to json_encode the reponse.
// This is needed in case jQuery migrate is disabled (eg via plugin) otherwise the AJAX success callback
// won't be executed (because json is malformed).
die( wp_json_encode( null ) );
}
$utils = ET_Core_Data_Utils::instance();
// phpcs:disable ET.Sniffs.ValidatedSanitizedInput -- Sanitization of following arrays is done at the time of accessing an array values.
$depends_on = isset( $_POST['depends_on'] ) ? $_POST['depends_on'] : array();
$conditional_tags = isset( $_POST['conditional_tags'] ) ? $_POST['conditional_tags'] : array();
$current_page = isset( $_POST['current_page'] ) ? $_POST['current_page'] : array();
// phpcs:enable
// allowlist keys.
$conditional_tags = array_intersect_key( $conditional_tags, et_fb_conditional_tag_params() );
$current_page = array_intersect_key( $current_page, et_fb_current_page_params() );
// sanitize values.
$conditional_tags = $utils->sanitize_text_fields( $conditional_tags );
$current_page = $utils->sanitize_text_fields( $current_page );
$module_slug = isset( $_POST['module_type'] ) ? sanitize_text_field( $_POST['module_type'] ) : '';
$request_type = isset( $_POST['request_type'] ) ? sanitize_text_field( $_POST['request_type'] ) : '';
if ( in_array( $request_type, array( '404', 'archive', 'home' ), true ) ) {
// On non-singular page, we do not have $current_page id, so we will check if user has theme_builder capability.
if ( ! et_pb_is_allowed( 'theme_builder' ) ) {
die( -1 );
}
} else {
// For other pages, we will check if user can edit specific post.
if ( ! current_user_can( 'edit_post', $current_page['id'] ) ) {
die( -1 );
}
}
// Check if there is page id.
if ( empty( $current_page['id'] ) && '404' !== $request_type ) {
die( -1 );
}
// $_POST['depends_on'] is a single dimensional assoc array created by jQuery.ajax data param, sanitize each key and value, they will both be strings
foreach ( $depends_on as $key => $value ) {
if ( et_()->includes( $value, '%' ) ) {
// `sanitize_text_fields` removes octets `%[a-f0-9]{2}` and would zap icon values / `%date`
// so we prefix octets with `_` to protected them and remove the prefix after sanitization.
$prepared_value = preg_replace( '/%([a-f0-9]{2})/', '%_$1', $value );
$sanitized_value = preg_replace( '/%_([a-f0-9]{2})/', '%$1', sanitize_text_field( $prepared_value ) );
} else {
$sanitized_value = sanitize_text_field( $value );
}
$depends_on[ sanitize_text_field( $key ) ] = $sanitized_value;
}
// Since VB performance, it is introduced single ajax request for several property
// in that case, computed_property posted data can be as an array
// hence we get the raw post data value, then sanitize it afterward either as array or string.
// @phpcs:ignore ET.Sniffs.ValidatedSanitizedInput.InputNotSanitized -- Will be sanitized conditionally as string or array afterward.
$computed_property = isset( $_POST['computed_property'] ) ? $_POST['computed_property'] : '';
$computed_property = is_array( $computed_property ) ? array_map( 'sanitize_text_field', $computed_property ) : sanitize_text_field( $computed_property );
// get all fields for module.
$fields = ET_Builder_Element::get_module_fields( $request_type, $module_slug );
// make sure only valid fields are being passed through.
$depends_on = array_intersect_key( $depends_on, $fields );
if ( is_array( $computed_property ) ) {
$results = array();
foreach ( $computed_property as $property ) {
if ( ! isset( $fields[ $property ], $fields[ $property ]['computed_callback'] ) ) {
continue;
}
$callback = $fields[ $property ]['computed_callback'];
if ( is_callable( $callback ) ) {
// @phpcs:ignore Generic.PHP.ForbiddenFunctions.Found -- The callback is hard-coded in module fields configuration.
$results[ $property ] = call_user_func( $callback, $depends_on, $conditional_tags, $current_page );
}
}
if ( empty( $results ) ) {
die( -1 );
}
die( wp_json_encode( $results ) );
}
// computed property field.
$field = $fields[ $computed_property ];
$callback = $field['computed_callback'];
if ( is_callable( $callback ) ) {
// @phpcs:ignore Generic.PHP.ForbiddenFunctions.Found -- The callback is hard-coded in module fields configuration.
die( wp_json_encode( call_user_func( $callback, $depends_on, $conditional_tags, $current_page ) ) );
} else {
die( -1 );
}
}
add_action( 'wp_ajax_et_pb_process_computed_property', 'et_pb_process_computed_property' );
/**
* Fetch before or after components.
*
* @since 4.14.5
*
* @return string Components outputs.
*/
function et_fb_fetch_before_after_components() {
// Bail early if the nonce is incorrect or current user can't edit posts.
$nonce = ! empty( $_POST['et_fb_fetch_before_after_components_nonce'] ) ? sanitize_text_field( $_POST['et_fb_fetch_before_after_components_nonce'] ) : '';
if ( empty( $nonce ) || ! wp_verify_nonce( $nonce, 'et_fb_fetch_before_after_components_nonce' ) ) {
wp_send_json_error();
}
if ( ! current_user_can( 'edit_posts' ) ) {
wp_send_json_error();
}
// Bail early if Module Shortcode Manager class doesn't exist.
if ( ! class_exists( 'ET_Builder_Module_Shortcode_Manager' ) ) {
wp_send_json_error();
}
/**
* Filters modules list.
*
* The modules list comes from Shortcode Manager only contains built-in modules
* intentionally. 3rd-party modules need to include their module slug and class name
* via `et_fb_fetch_before_after_modules_map` filter.
*
* @param array Modules list.
*/
$modules_map = apply_filters( 'et_fb_fetch_before_after_modules_map', ET_Builder_Module_Shortcode_Manager::get_modules_map() );
// Bail early if components map is empty.
if ( empty( $modules_map ) ) {
return $modules_components;
}
$module_type = ! empty( $_POST['module_type'] ) ? sanitize_text_field( $_POST['module_type'] ) : '';
$module_class = et_()->array_get( $modules_map, array( $module_type, 'classname' ) );
// Bail early if module class name is not found.
if ( empty( $module_class ) || ! class_exists( $module_class ) ) {
wp_send_json_error();
}
// phpcs:disable ET.Sniffs.ValidatedSanitizedInput -- Sanitization of following arrays is done on Sanitize values section below.
$conditional_tags = isset( $_POST['conditional_tags'] ) ? $_POST['conditional_tags'] : array();
$current_page = isset( $_POST['current_page'] ) ? $_POST['current_page'] : array();
$module_attrs = isset( $_POST['module_attrs'] ) ? $_POST['module_attrs'] : array();
// phpcs:enable
// Allow list keys.
$conditional_tags = array_intersect_key( $conditional_tags, et_fb_conditional_tag_params() );
$current_page = array_intersect_key( $current_page, et_fb_current_page_params() );
// Sanitize values.
$conditional_tags = et_()->sanitize_text_fields( $conditional_tags );
$current_page = et_()->sanitize_text_fields( $current_page );
$module_attrs = et_()->sanitize_text_fields( $module_attrs );
$render_mode = isset( $_POST['render_mode'] ) ? sanitize_text_field( $_POST['render_mode'] ) : '';
$post_type = isset( $_POST['post_type'] ) ? sanitize_text_field( $_POST['post_type'] ) : '';
$action = isset( $_POST['action'] ) ? sanitize_text_field( $_POST['action'] ) : '';
// Bail early if current user can't edit this post.
if ( empty( $current_page['id'] ) || ! current_user_can( 'edit_post', $current_page['id'] ) ) {
wp_send_json_error();
}
// Setup module data.
$module_data = array(
'conditional_tags' => $conditional_tags,
'current_page' => $current_page,
'module_attrs' => $module_attrs,
'render_mode' => $render_mode,
'post_type' => $post_type,
'action' => $action,
);
/**
* Fires before processing before & after components.
*
* @since 4.14.5
*
* @param string $module_type Module slug.
* @param array $module_data Module data passed from the request.
*/
do_action( 'et_fb_pre_fetch_before_after_components', $module_type, $module_data );
$output = $module_class::get_component_before_after_module( $module_type, $module_data );
wp_send_json_success( $output );
}
add_action( 'wp_ajax_et_fb_fetch_before_after_components', 'et_fb_fetch_before_after_components' );
/**
* Process shortcode json.
*
* @since 4.11.4 Added $inject_responsive_hover param.
*
* @param array $object Shortcodes object.
* @param array $options Options.
* @param string $library_item_type Library item type.
* @param bool $escape_content_slashes Whether escape content slashes.
* @param bool $inject_responsive_hover Flag to inject missing responsive and hover mode attributes.
*
* @return string
*/
function et_fb_process_to_shortcode( $object, $options = array(), $library_item_type = '', $escape_content_slashes = true, $inject_responsive_hover = false ) {
$output = '';
$_object = array();
$default_options = array(
'force_valid_slugs' => false,
'post_type' => false,
'apply_global_presets' => false,
);
$options = wp_parse_args( $options, $default_options );
$global_presets_manager = ET_Builder_Global_Presets_Settings::instance();
// do not proceed if $object is empty.
if ( empty( $object ) ) {
return '';
}
$font_icon_fields = ! empty( $options['post_type'] ) ? ET_Builder_Element::get_font_icon_fields( $options['post_type'] ) : false;
$structure_types = ET_Builder_Element::get_structure_module_slugs();
if ( in_array( $library_item_type, array( 'module', 'row' ), true ) ) {
$excluded_elements = array();
switch ( $library_item_type ) {
case 'module':
$excluded_elements = array( 'et_pb_section', 'et_pb_row', 'et_pb_column' );
break;
case 'row':
$excluded_elements = array( 'et_pb_section' );
break;
}
foreach ( $object as $item ) {
// do not proceed if $item is empty.
if ( empty( $item ) ) {
continue;
}
while ( in_array( $item['type'], $excluded_elements, true ) ) {
$item = $item['content'][0];
}
$_object[] = $item;
}
} else {
$_object = $object;
}
if ( $options['force_valid_slugs'] ) {
// we need to supply a reasonable default post type to get a simple list of slugs,
// otherwise the function will return an array of arrays of slugs for every possible post_type.
$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.
if ( empty( $item ) ) {
continue;
}
$attributes = '';
$content = '';
$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, true ) ) {
continue;
}
}
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'] );
}
// Inject responsive/hover attribute value to inherit from desktop
// when the responsive/hover setting is "on" but the responsive/hover attribute is not exist.
// This can happen when responsive/hover mode values reset in the builder.
if ( $inject_responsive_hover ) {
foreach ( $item['attrs'] as $attribute => $value ) {
// Inject responsive mode attribute value.
if ( '_last_edited' === substr( $attribute, -12 ) && 0 === strpos( $value, 'on' ) ) {
$attr_key_base = str_replace( '_last_edited', '', $attribute );
$attr_key_tablet = $attr_key_base . '_tablet';
$attr_key_phone = $attr_key_base . '_phone';
$attr_is_content = 'content' === $attr_key_base && isset( $item['content'] ) && is_string( $item['content'] );
// Inject tablet mode attribute value.
if ( ! isset( $item['attrs'][ $attr_key_tablet ] ) ) {
if ( $attr_is_content ) {
$item['attrs'][ $attr_key_tablet ] = $item['content'];
} elseif ( isset( $item['attrs'][ $attr_key_base ] ) ) {
$item['attrs'][ $attr_key_tablet ] = $item['attrs'][ $attr_key_base ];
}
}
// Inject phone mode attribute value.
if ( ! isset( $item['attrs'][ $attr_key_phone ] ) ) {
if ( isset( $item['attrs'][ $attr_key_tablet ] ) ) {
$item['attrs'][ $attr_key_phone ] = $item['attrs'][ $attr_key_tablet ];
} else {
if ( $attr_is_content ) {
$item['attrs'][ $attr_key_phone ] = $content;
} elseif ( isset( $values[ $attr_key_base ] ) ) {
$item['attrs'][ $attr_key_phone ] = $values[ $attr_key_base ];
}
}
}
}
// Inject hover mode attribute value.
if ( '__hover_enabled' === substr( $attribute, -15 ) && 0 === strpos( $value, 'on' ) ) {
$attr_key_base = str_replace( '__hover_enabled', '', $attribute );
$attr_key_hover = $attr_key_base . '__hover';
if ( ! isset( $item['attrs'][ $attr_key_hover ] ) ) {
$attr_is_content = 'content' === $attr_key_base && isset( $item['content'] ) && is_string( $item['content'] );
if ( $attr_is_content ) {
$item['attrs'][ $attr_key_hover ] = $item['content'];
} elseif ( isset( $item['attrs'][ $attr_key_base ] ) ) {
$item['attrs'][ $attr_key_hover ] = $item['attrs'][ $attr_key_base ];
}
}
}
}
}
foreach ( $item['attrs'] as $attribute => $value ) {
// ignore computed fields.
if ( '__' === substr( $attribute, 0, 2 ) ) {
continue;
}
// Ignore post_content_module_attrs. They are needed only during editing.
if ( 'post_content_module_attrs' === $attribute ) {
continue;
}
// Sanitize attribute.
$attribute = sanitize_text_field( $attribute );
// Sanitize input properly.
if ( isset( $font_icon_fields[ $item['type'] ][ $attribute ] ) ) {
$value = esc_attr( $value );
}
// handle content.
if ( in_array( $attribute, array( 'content', 'raw_content' ), true ) ) {
// do not override the content if item has raw_child_content.
if ( empty( $item['raw_child_content'] ) ) {
$content = $value;
$content = trim( $content );
if ( ! empty( $content ) && 'content' === $attribute ) {
$content = "\n\n" . $content . "\n\n";
}
}
} else {
// 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;
if ( '' === $value
&& et_pb_hover_options()->get_field_base_name( $attribute ) !== $attribute
&& et_pb_hover_options()->is_enabled( et_pb_hover_options()->get_field_base_name( $attribute ), $item['attrs'] ) ) {
$is_include_attr = true;
}
if ( '' === $value
&& et_pb_responsive_options()->get_field_base_name( $attribute ) !== $attribute
&& et_pb_responsive_options()->is_enabled( et_pb_responsive_options()->get_field_base_name( $attribute ), $item['attrs'] ) ) {
$is_include_attr = true;
}
if ( '' !== $value ) {
$is_include_attr = true;
}
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', 'conditional_logic_rules' );
if ( 0 === strpos( $attribute, 'custom_css_' ) || in_array( $attribute, $json_attributes, true ) ) {
$value = str_ireplace( '\\', '%92', $value );
} elseif ( et_builder_parse_dynamic_content( $value )->is_dynamic() ) {
$value = str_replace( '\\', '%92', $value );
}
// Encode backslash for custom date format attributes.
$modules_and_attr_with_custom_date = array(
'et_pb_blog' => 'meta_date',
'et_pb_fullwidth_post_title' => 'date_format',
'et_pb_post_title' => 'date_format',
);
if ( ! empty( $modules_and_attr_with_custom_date[ $type ] ) && $modules_and_attr_with_custom_date[ $type ] === $attribute ) {
$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;
}
// build shortcode
// start the opening tag.
$output .= '[' . $type . $attributes;
// close the opening tag, depending on self closing.
if ( empty( $content ) && ! isset( $item['content'] ) && ! in_array( $type, $structure_types, true ) ) {
$open_tag_only = true;
$output .= ' /]';
} else {
$open_tag_only = false;
$output .= ']';
}
// 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]';
} elseif ( isset( $item['content'] ) && is_array( $item['content'] ) ) {
$output .= et_fb_process_to_shortcode( $item['content'], $options, '', $escape_content_slashes, $inject_responsive_hover );
} else {
if ( ! empty( $content ) ) {
if ( et_is_builder_plugin_active() && in_array( $type, ET_Builder_Element::get_has_content_modules(), true ) ) {
// 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.
/**
* Filter whether to apply wpautop to content.
*
* This filter allows customization of whether the wpautop filter should be applied to
* the content of a shortcode. It helps in wrapping content in
tags automatically when necessary.
*
* @since 4.11.4
*
* @param bool $should_wpautop Whether to apply wpautop. Default true.
* @param string $type The shortcode or module type.
* @param string $content The content to be filtered.
* @param array $item The current shortcode being processed.
*/
$should_wpautop = apply_filters( 'et_fb_should_apply_wpautop', true, $type, $content, $item );
$content = $should_wpautop ? wpautop( $content ) : $content;
}
$output .= $content;
} else {
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(), true ) ) {
// 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.
/**
* Filter whether to apply wpautop to content.
*
* This filter allows customization of whether the wpautop filter should be applied to
* the content of a shortcode. It helps in wrapping content in
tags automatically when necessary.
*
* @since 4.11.4
*
* @param bool $should_wpautop Whether to apply wpautop. Default true.
* @param string $type The shortcode or module type.
* @param string $content The content to be filtered.
* @param array $item The current shortcode being processed.
*/
$should_wpautop = apply_filters( 'et_fb_should_apply_wpautop', true, $type, $_content, $item );
$_content = $should_wpautop ? wpautop( $_content ) : $_content;
}
$output .= $_content;
} else {
$output .= '';
}
}
}
// add the closing tag.
$output .= '[/' . $type . ']';
}
}
return $output;
}
/**
* Ajax Callback :: Render shortcode output.
*/
function et_fb_ajax_render_shortcode() {
if ( ! isset( $_POST['et_pb_render_shortcode_nonce'] ) || ! wp_verify_nonce( sanitize_text_field( $_POST['et_pb_render_shortcode_nonce'] ), 'et_pb_render_shortcode_nonce' ) ) {
wp_send_json_error();
}
if ( ! current_user_can( 'edit_posts' ) ) {
wp_send_json_error();
}
$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(); // phpcs:ignore ET.Sniffs.ValidatedSanitizedInput -- sanitize_text_fields sanitize the options.
// 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;
// phpcs:ignore ET.Sniffs.ValidatedSanitizedInput -- $_POST['object'] will not be stored in db.
$object = isset( $_POST['object'] ) ? $_POST['object'] : array();
// convert shortcode array to shortcode string.
$shortcode = et_fb_process_to_shortcode( $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 ) ) {
$output .= sprintf(
'',
$styles
);
}
wp_send_json_success( $output );
}
add_action( 'wp_ajax_et_fb_ajax_render_shortcode', 'et_fb_ajax_render_shortcode' );
/**
* Determine current user can save the post.
*
* @param int $post_id Post id.
* @param string $status Post status.
*
* @return bool
*/
function et_fb_current_user_can_save( $post_id, $status = '' ) {
if ( 'page' === get_post_type( $post_id ) ) {
if ( ! current_user_can( 'edit_pages' ) ) {
return false;
}
if ( ! current_user_can( 'publish_pages' ) && 'publish' === $status ) {
return false;
}
if ( ! current_user_can( 'edit_published_pages' ) && 'publish' === get_post_status( $post_id ) ) {
return false;
}
if ( ! current_user_can( 'edit_others_pages' ) && ! current_user_can( 'edit_page', $post_id ) ) {
return false;
}
} else {
if ( ! current_user_can( 'edit_posts' ) ) {
return false;
}
if ( ! current_user_can( 'publish_posts' ) && 'publish' === $status ) {
return false;
}
if ( ! current_user_can( 'edit_published_posts' ) && 'publish' === get_post_status( $post_id ) ) {
return false;
}
if ( ! current_user_can( 'edit_others_posts' ) && ! current_user_can( 'edit_post', $post_id ) ) {
return false;
}
}
// If this is a theme builder layout post type, check divi roles for that capability.
if ( in_array( get_post_type( $post_id ), et_theme_builder_get_layout_post_types(), true ) && ! et_pb_is_allowed( 'theme_builder' ) ) {
return false;
}
return true;
}
/**
* Ajax Callback :: Drop backup/autosave depending on exit type.
*/
function et_fb_ajax_drop_autosave() {
if ( ! isset( $_POST['et_fb_drop_autosave_nonce'] ) || ! wp_verify_nonce( sanitize_text_field( $_POST['et_fb_drop_autosave_nonce'] ), 'et_fb_drop_autosave_nonce' ) ) {
wp_send_json_error();
}
$post_id = isset( $_POST['post_id'] ) ? absint( $_POST['post_id'] ) : 0;
if ( ! et_fb_current_user_can_save( $post_id ) ) {
wp_send_json_error();
}
$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;
}
} else {
$autosave_deleted = true;
}
if ( $autosave_deleted ) {
wp_send_json_success();
} else {
wp_send_json_error();
}
}
add_action( 'wp_ajax_et_fb_ajax_drop_autosave', 'et_fb_ajax_drop_autosave' );
/**
* Ajax Callback :: Save layout.
*/
function et_fb_ajax_save() {
if ( ! isset( $_POST['et_fb_save_nonce'] ) || ! wp_verify_nonce( sanitize_text_field( $_POST['et_fb_save_nonce'] ), 'et_fb_save_nonce' ) ) {
wp_send_json_error();
}
$utils = ET_Core_Data_Utils::instance();
$post_id = isset( $_POST['post_id'] ) ? absint( $_POST['post_id'] ) : 0;
$options = isset( $_POST['options'] ) ? $_POST['options'] : array(); // phpcs:ignore ET.Sniffs.ValidatedSanitizedInput -- $_POST['options'] is an array, it's value sanitization is done at the time of accessing value.
$layout_type = isset( $_POST['layout_type'] ) ? sanitize_text_field( $_POST['layout_type'] ) : '';
$is_theme_builder_layout = in_array( get_post_type( $post_id ), et_theme_builder_get_layout_post_types(), true );
// For post content check if user can save post.
if ( ! et_fb_current_user_can_save( $post_id, $utils->array_get_sanitized( $options, 'status' ) ) ) {
wp_send_json_error();
}
$update = false;
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 = isset( $_POST['modules'] ) ? json_decode( stripslashes( $_POST['modules'] ), true ) : array(); // phpcs:ignore ET.Sniffs.ValidatedSanitizedInput -- modules string will be sanitized at the time of saving in the db.
// 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.
if ( ! $block_id ) {
$block_id = false;
}
// 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;
}
$built_for_type = get_post_meta( $post_id, '_et_pb_built_for_post_type', true );
if ( ! $built_for_type && ! $is_layout_block_preview ) {
update_post_meta( $post_id, '_et_pb_built_for_post_type', 'page' );
}
// If Default Editor is used for Post Content, and Post Content is not edited,
// handleAjaxSave will pass return_to_default_editor,
// and in that case we need reactivate the default editor for the post.
// phpcs:ignore ET.Sniffs.ValidatedSanitizedInput.InputNotSanitized -- input is inside isset() function so its value is not used
if ( isset( $_POST['return_to_default_editor'] ) && rest_sanitize_boolean( $_POST['return_to_default_editor'] ) ) {
update_post_meta( $post_id, '_et_pb_use_builder', 'off' );
update_post_meta( $post_id, '_et_pb_show_page_creation', 'on' );
// Get old content and if we should return to the Default Editor.
$post_content = get_post_meta( $post_id, '_et_pb_old_content', true );
} else {
if ( ! $is_layout_block_preview ) {
update_post_meta( $post_id, '_et_pb_use_builder', 'on' );
}
$post_content = et_fb_process_to_shortcode( $shortcode_data, $options, $layout_type, true, true );
}
// 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,
)
);
wp_die();
}
$update = update_post_meta( $post_id, $layout_preview_meta_key, $post_content );
if ( $update ) {
wp_send_json_success(
array(
'save_verification' => true,
)
);
} else {
wp_send_json_error();
}
wp_die();
}
$update = wp_update_post(
array(
'ID' => $post_id,
'post_content' => $post_content,
'post_status' => $utils->array_get_sanitized( $options, 'status' ),
)
);
}
// update Global modules with selective sync.
if ( 'module' === $layout_type && isset( $_POST['unsyncedGlobalSettings'] ) && 'none' !== $_POST['unsyncedGlobalSettings'] ) {
// phpcs:ignore ET.Sniffs.ValidatedSanitizedInput -- $_POST['unsyncedGlobalSettings'] will be sanitized before storing in db.
$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 ); // phpcs:ignore ET.Sniffs.ValidatedSanitizedInput -- $_POST['settings'] is an array, it's value sanitization is done inside function at the time of accessing value.
}
if ( isset( $_POST['preferences'] ) && is_array( $_POST['preferences'] ) && ! $is_theme_builder_layout ) {
$app_preferences = et_fb_app_preferences_settings();
// phpcs:ignore ET.Sniffs.ValidatedSanitizedInput -- $_POST['et_builder_mode'] value used in the comparision.
$limited_prefix = ! empty( $_POST['et_builder_mode'] ) && 'limited' === $_POST['et_builder_mode'] ? 'limited_' : '';
foreach ( $app_preferences as $preference_key => $preference_data ) {
// phpcs:ignore ET.Sniffs.ValidatedSanitizedInput -- $preference_value will be sanitized before saving in db.
$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'] ) {
case 'int':
$preference_value = absint( $preference_value );
break;
case 'bool':
$preference_value = 'true' === $preference_value ? 'true' : 'false';
break;
default:
$preference_value = sanitize_text_field( $preference_value );
break;
}
$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(), true ) ) {
$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 ( $update ) {
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/.
global $wpdb;
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' );
} else {
delete_post_meta( $post_id, '_et_pb_show_page_creation' );
}
}
/**
* Hook triggered when the Post is updated.
*
* @param int $post_id Post ID.
*
* @since 3.29
*/
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,
)
);
} elseif ( isset( $_POST['skip_post_update'] ) ) {
wp_send_json_success();
} else {
wp_send_json_error();
}
}
add_action( 'wp_ajax_et_fb_ajax_save', 'et_fb_ajax_save' );
/**
* Ajax Callback :: Convert fb object into shortcode.
*/
function et_fb_get_shortcode_from_fb_object() {
if ( ! et_core_security_check( 'edit_posts', 'et_fb_convert_to_shortcode_nonce', 'et_fb_convert_to_shortcode_nonce', '_POST', false ) ) {
wp_send_json_error();
}
// phpcs:ignore ET.Sniffs.ValidatedSanitizedInput -- $_POST['modules'] will not be stored in db.
$shortcode_data = isset( $_POST['modules'] ) ? json_decode( stripslashes( $_POST['modules'] ), true ) : array();
$layout_type = isset( $_POST['layout_type'] ) ? sanitize_text_field( $_POST['layout_type'] ) : '';
$post_content = et_fb_process_to_shortcode( $shortcode_data, array(), $layout_type );
// Get rendered post content only if it's needed.
$utils = ET_Core_Data_Utils::instance();
$return_rendered_content = sanitize_text_field( $utils->array_get( $_POST, 'options.return_rendered_content', 'false' ) );
$rendered_post_content = 'true' === $return_rendered_content ? do_shortcode( $post_content ) : '';
wp_send_json_success(
array(
'processed_content' => $post_content,
'rendered_content' => $rendered_post_content,
)
);
}
add_action( 'wp_ajax_et_fb_get_shortcode_from_fb_object', 'et_fb_get_shortcode_from_fb_object' );
/**
* Ajax Callback :: Convert shortcode into HTML.
*/
function et_fb_get_html_from_shortcode() {
if ( ! et_core_security_check( 'edit_posts', 'et_fb_shortcode_to_html_nonce', 'et_fb_shortcode_to_html_nonce', '_POST', false ) ) {
wp_send_json_error();
}
// phpcs:ignore ET.Sniffs.ValidatedSanitizedInput -- $_POST['modules'] will not be stored in db.
$post_content = isset( $_POST['content'] ) ? stripslashes( $_POST['content'] ) : '';
// Get rendered post content by shortcode.
$rendered_post_content = do_shortcode( $post_content );
wp_send_json_success(
array(
'rendered_content' => $rendered_post_content,
)
);
}
add_action( 'wp_ajax_et_fb_get_html_from_shortcode', 'et_fb_get_html_from_shortcode' );
/**
* Ajax Callback :: Save library modules.
*/
function et_fb_save_layout() {
if ( ! isset( $_POST['et_fb_save_library_modules_nonce'] ) || ! wp_verify_nonce( sanitize_text_field( $_POST['et_fb_save_library_modules_nonce'] ), 'et_fb_save_library_modules_nonce' ) ) {
die( -1 );
}
if ( ! current_user_can( 'edit_posts' ) ) {
die( -1 );
}
if ( empty( $_POST['et_layout_name'] ) ) {
die( -1 );
}
$post_type = sanitize_text_field( et_()->array_get( $_POST, 'et_post_type', 'page' ) );
if ( et_theme_builder_is_layout_post_type( $post_type ) ) {
// Treat TB layouts as normal posts when storing layouts from the library.
$post_type = 'page';
}
$args = array(
'layout_type' => isset( $_POST['et_layout_type'] ) ? sanitize_text_field( $_POST['et_layout_type'] ) : 'layout',
'layout_selected_cats' => isset( $_POST['et_layout_cats'] ) ? sanitize_text_field( $_POST['et_layout_cats'] ) : '',
'layout_selected_tags' => isset( $_POST['et_layout_tags'] ) ? sanitize_text_field( $_POST['et_layout_tags'] ) : '',
'built_for_post_type' => $post_type,
'layout_new_cat' => isset( $_POST['et_layout_new_cat'] ) ? sanitize_text_field( $_POST['et_layout_new_cat'] ) : '',
'layout_new_tag' => isset( $_POST['et_layout_new_tag'] ) ? sanitize_text_field( $_POST['et_layout_new_tag'] ) : '',
'columns_layout' => isset( $_POST['et_columns_layout'] ) ? sanitize_text_field( $_POST['et_columns_layout'] ) : '0',
'module_type' => isset( $_POST['et_module_type'] ) ? sanitize_text_field( $_POST['et_module_type'] ) : 'et_pb_unknown',
'layout_scope' => isset( $_POST['et_layout_scope'] ) ? sanitize_text_field( $_POST['et_layout_scope'] ) : 'not_global',
'module_width' => isset( $_POST['et_module_width'] ) ? sanitize_text_field( $_POST['et_module_width'] ) : 'regular',
'layout_content' => isset( $_POST['et_layout_content'] ) ? et_fb_process_to_shortcode( json_decode( stripslashes( $_POST['et_layout_content'] ), true ) ) : '', // phpcs:ignore ET.Sniffs.ValidatedSanitizedInput -- The `$_POST['et_layout_content']` will be sanitized before saving into db.
'layout_name' => isset( $_POST['et_layout_name'] ) ? sanitize_text_field( $_POST['et_layout_name'] ) : '',
);
$new_layout_meta = et_pb_submit_layout( $args );
$updated_terms = array();
foreach ( [ 'layout_category', 'layout_tag' ] as $taxonomy ) {
$raw_terms_array = apply_filters( 'et_pb_new_layout_cats_array', get_terms( $taxonomy, array( 'hide_empty' => false ) ) );
$clean_terms_array = array();
if ( is_array( $raw_terms_array ) && ! empty( $raw_terms_array ) ) {
foreach ( $raw_terms_array as $term ) {
$clean_terms_array[] = array(
'name' => html_entity_decode( $term->name ),
'id' => $term->term_id,
'slug' => $term->slug,
);
}
}
$updated_terms[ $taxonomy ] = $clean_terms_array;
}
$data = array(
'layout_data' => json_decode( $new_layout_meta, true ),
'updated_terms' => $updated_terms,
);
die( wp_json_encode( et_core_esc_previously( $data ) ) );
}
add_action( 'wp_ajax_et_fb_save_layout', 'et_fb_save_layout' );
/**
* Ajax Callback :: Process shortcode to exported layout object.
*/
function et_fb_get_cloud_item_content() {
if ( ! isset( $_POST['et_fb_save_cloud_item_nonce'] ) || ! wp_verify_nonce( sanitize_text_field( $_POST['et_fb_save_cloud_item_nonce'] ), 'et_fb_save_cloud_item_nonce' ) ) {
die( -1 );
}
if ( ! current_user_can( 'edit_posts' ) ) {
die( -1 );
}
$layout_type = isset( $_POST['et_layout_type'] ) ? sanitize_text_field( $_POST['et_layout_type'] ) : '';
$layout_content = isset( $_POST['et_layout_content'] ) ? et_fb_process_to_shortcode( json_decode( stripslashes( $_POST['et_layout_content'] ), true ), array(), $layout_type ) : ''; // phpcs:ignore ET.Sniffs.ValidatedSanitizedInput -- The `$_POST['et_layout_content']` will be sanitized before saving into db.
$exported_shortcode = get_exported_content( $layout_content );
die( wp_json_encode( array( 'shortcode' => $exported_shortcode ) ) );
}
add_action( 'wp_ajax_et_fb_get_cloud_item_content', 'et_fb_get_cloud_item_content' );
/**
* Prepare shortcode for exporting.
*
* @since 4.17.0
*
* @param string $shortcode Shortcode to process.
*
* @return array
*/
function get_exported_content( $shortcode ) {
// Set faux $_POST value that is required by portability.
$_POST['post'] = '1';
$_POST['content'] = $shortcode;
// Remove page value if it is equal to `false`, avoiding paginated images not accidentally triggered.
if ( isset( $_POST['page'] ) && false === $_POST['page'] ) { // phpcs:ignore WordPress.Security.NonceVerification -- This function does not change any state, and is therefore not susceptible to CSRF.
unset( $_POST['page'] ); // phpcs:ignore WordPress.Security.NonceVerification -- This function does not change any state, and is therefore not susceptible to CSRF.
}
$portability = et_core_portability_load( 'et_builder' );
// Export the content.
return $portability->export( true, true );
}
/**
* Ajax Callback :: Process shortcode.
*/
function et_fb_prepare_shortcode() {
if ( ! isset( $_POST['et_fb_prepare_shortcode_nonce'] ) || ! wp_verify_nonce( sanitize_text_field( $_POST['et_fb_prepare_shortcode_nonce'] ), 'et_fb_prepare_shortcode_nonce' ) ) {
wp_send_json_error();
}
if ( ! current_user_can( 'edit_posts' ) ) {
die( -1 );
}
// phpcs:disable ET.Sniffs.ValidatedSanitizedInput -- The `$_POST['et_page_content']` and `$_POST['apply_global_presets']` will not be stored on db.
$content = isset( $_POST['et_page_content'] ) ? json_decode( stripslashes( $_POST['et_page_content'] ), true ) : '';
$apply_global_presets = isset( $_POST['apply_global_presets'] ) ? wp_validate_boolean( $_POST['apply_global_presets'] ) : false;
// phpcs:enable
$options = array(
'apply_global_presets' => $apply_global_presets,
);
$result = $content ? et_fb_process_to_shortcode( $content, $options, '', false ) : '';
die( wp_json_encode( array( 'shortcode' => $result ) ) );
}
add_action( 'wp_ajax_et_fb_prepare_shortcode', 'et_fb_prepare_shortcode' );
/**
* Ajax Callback :: Save library module.
*/
function et_fb_update_layout() {
if ( ! isset( $_POST['et_fb_save_library_modules_nonce'] ) || ! wp_verify_nonce( $_POST['et_fb_save_library_modules_nonce'], 'et_fb_save_library_modules_nonce' ) ) { // phpcs:ignore ET.Sniffs.ValidatedSanitizedInput -- The nonce value is used only for comparision in the `wp_verify_nonce`.
die( -1 );
}
if ( ! current_user_can( 'edit_posts' ) ) {
die( -1 );
}
$post_id = isset( $_POST['et_template_post_id'] ) ? absint( $_POST['et_template_post_id'] ) : '';
if ( ! current_user_can( 'edit_post', $post_id ) ) {
die( -1 );
}
// phpcs:disable ET.Sniffs.ValidatedSanitizedInput -- Following $_POST values will be sanitized before storing in db.
$post_content = isset( $_POST['et_layout_content'] ) ? json_decode( stripslashes( $_POST['et_layout_content'] ), true ) : array();
$new_content = isset( $_POST['et_layout_content'] ) ? et_fb_process_to_shortcode( $post_content ) : '';
$excluded_global_options = isset( $_POST['et_excluded_global_options'] ) ? stripslashes( $_POST['et_excluded_global_options'] ) : array();
$is_saving_global_module = isset( $_POST['et_saving_global_module'] ) ? sanitize_text_field( $_POST['et_saving_global_module'] ) : '';
// phpcs:enable
if ( '' !== $post_id ) {
$update = array(
'ID' => $post_id,
'post_content' => $new_content,
);
$result = wp_update_post( $update );
if ( ! $result || is_wp_error( $result ) ) {
wp_send_json_error();
}
ET_Core_PageResource::remove_static_resources( 'all', 'all' );
// update list of unsynced options for global module.
if ( 'true' === $is_saving_global_module ) {
update_post_meta( $post_id, '_et_pb_excluded_global_options', sanitize_text_field( $excluded_global_options ) );
}
}
die();
}
add_action( 'wp_ajax_et_fb_update_layout', 'et_fb_update_layout' );
/**
* Ajax Callback :: Return requested attachments from media library.
*/
function et_fb_fetch_attachments() {
et_core_security_check( 'edit_posts', 'et_fb_fetch_attachments', 'et_fb_fetch_attachments' );
$ids = ET_Core_Data_Utils::instance()->array_get( $_POST, 'ids' );
if ( empty( $ids ) ) {
wp_send_json( null );
} else {
$attachments = get_posts(
array(
'posts_per_page' => - 1,
'include' => $ids,
'post_type' => 'attachment',
)
);
foreach ( $attachments as $index => $attachment ) {
$metadata = array();
foreach ( get_intermediate_image_sizes() as $size ) {
$metadata[ $size ] = wp_get_attachment_image_src( $attachment->ID, $size );
}
$attachments[ $index ] = array_merge(
get_object_vars( $attachment ),
array(
'metadata' => $metadata,
)
);
}
wp_send_json( $attachments );
}
die();
}
add_action( 'wp_ajax_et_fb_fetch_attachments', 'et_fb_fetch_attachments' );
if ( ! function_exists( 'et_fb_disable_product_tour' ) ) :
/**
* Saving User Specific Tour status.
*/
function et_fb_disable_product_tour() {
do_action( 'et_fb_disable_product_tour' );
if ( ! et_core_security_check_passed( 'edit_posts' ) ) {
ET_Core_Logger::debug( 'Unable to disable product tour. Security check failed!' );
return;
}
$user_id = (int) get_current_user_id();
$product_tour_status = et_get_option( 'product_tour_status', [] );
$all_product_settings = is_array( $product_tour_status ) ? $product_tour_status : [];
$all_product_settings[ $user_id ] = 'off';
et_update_option( 'product_tour_status', $all_product_settings );
}
endif;
if ( ! function_exists( 'et_builder_include_categories_option' ) ) :
/**
* Generate output string for `include_categories` option used in backbone template.
*
* @param array $args Arguments to get project categories.
* @param string $default_category @todo Add parameter doc.
* @return string
*/
function et_builder_include_categories_option( $args = array(), $default_category = '' ) {
$custom_items = array();
if ( ! empty( $args['custom_items'] ) ) {
$custom_items = $args['custom_items'];
unset( $args['custom_items'] );
}
$defaults = array(
'use_terms' => true,
'term_name' => 'project_category',
'field_name' => 'et_pb_include_categories',
);
$defaults = apply_filters( 'et_builder_include_categories_defaults', $defaults );
$args = wp_parse_args( $args, $defaults );
$args['field_name'] = esc_attr( $args['field_name'] );
$term_args = apply_filters( 'et_builder_include_categories_option_args', array( 'hide_empty' => false ) );
$output = "\t<% var {$args['field_name']}_temp = typeof data !== 'undefined' && typeof data.{$args['field_name']} !== 'undefined' ? data.{$args['field_name']}.split( ',' ) : ['" . esc_html( $default_category ) . "']; {$args['field_name']}_temp = typeof data === 'undefined' && typeof {$args['field_name']} !== 'undefined' ? {$args['field_name']}.split( ',' ) : {$args['field_name']}_temp; %>\n";
if ( $args['use_terms'] ) {
$cats_array = get_terms( $args['term_name'], $term_args );
} else {
$cats_array = get_categories( apply_filters( 'et_builder_get_categories_args', 'hide_empty=0' ) );
}
$cats_array = array_merge( $custom_items, $cats_array );
if ( empty( $cats_array ) ) {
$taxonomy_type = $args['use_terms'] ? $args['term_name'] : 'category';
$taxonomy = get_taxonomy( $taxonomy_type );
$labels = get_taxonomy_labels( $taxonomy );
$output = sprintf( '
`, `] ` `]\n` with `]`
// Make sure to remove any closing `` tags or line breaks or new lines after shortcode tag.
$pattern_1 = sprintf( '/%1$s%2$s%3$s/', $shortcode_pattern, $space_pattern, $closing_pattern );
// Replace `
[`, ` [` `\n[` with `[`
// Make sure to remove any opening `
` tags or line breaks or new lines before shortcode tag.
$pattern_2 = sprintf( '/%1$s%2$s%3$s/', $opening_pattern, $space_pattern, $shortcode_pattern );
$content = preg_replace( $pattern_1, '$1', $content );
$content = preg_replace( $pattern_2, '$2', $content );
return $content;
}
}
if ( ! function_exists( 'et_pb_load_global_module' ) ) {
/**
* Return gloval module content.
*
* @param integer $global_id layout id.
* @param string $row_type row type.
* @param string $prev_bg Previous background color.
* @param string $next_bg next background color.
*
* @return string|string[]|null
*/
function et_pb_load_global_module( $global_id, $row_type = '', $prev_bg = '', $next_bg = '' ) {
$global_shortcode = '';
if ( '' !== $global_id ) {
$query = new WP_Query(
array(
'p' => (int) $global_id,
'post_type' => array(
ET_BUILDER_LAYOUT_POST_TYPE,
ET_THEME_BUILDER_HEADER_LAYOUT_POST_TYPE,
ET_THEME_BUILDER_BODY_LAYOUT_POST_TYPE,
ET_THEME_BUILDER_FOOTER_LAYOUT_POST_TYPE,
),
)
);
if ( ! empty( $query->post ) ) {
// Call the_post() to properly configure post data. Make sure to call the_post() and
// wp_reset_postdata() only if the posts result exist to avoid unexpected issues.
$query->the_post();
wp_reset_postdata();
$global_shortcode = $query->post->post_content;
if ( '' !== $row_type && 'et_pb_row_inner' === $row_type ) {
$global_shortcode = str_replace( 'et_pb_row', 'et_pb_row_inner', $global_shortcode );
$global_shortcode = str_replace( 'et_pb_column', 'et_pb_column_inner', $global_shortcode );
}
}
}
// Set provided prev_background_color.
if ( ! empty( $prev_bg ) ) {
$global_shortcode = preg_replace( '/prev_background_color="(.*?)"/', 'prev_background_color="' . $prev_bg . '"', $global_shortcode, 1 );
}
// Set provided next_background_color.
if ( ! empty( $next_bg ) ) {
$global_shortcode = preg_replace( '/next_background_color="(.*?)"/', 'next_background_color="' . $next_bg . '"', $global_shortcode, 1 );
}
return $global_shortcode;
}
}
if ( ! function_exists( 'et_pb_extract_shortcode_content' ) ) {
/**
* Return the shortcode content.
*
* @param string $content content.
* @param string $shortcode_name shortcode name.
*
* @return bool|false|string
*/
function et_pb_extract_shortcode_content( $content, $shortcode_name ) {
$start = strpos( $content, ']' ) + 1;
$end = strrpos( $content, '[/' . $shortcode_name );
if ( false !== $end ) {
$content = substr( $content, $start, $end - $start );
} else {
$content = (bool) false;
}
return $content;
}
}
if ( ! function_exists( 'et_pb_remove_shortcode_content' ) ) {
/**
* Remove the content part of the shortcode.
*
* @param string $content content.
* @param string $shortcode_name shortcode name.
*
* @return string|string[]
*/
function et_pb_remove_shortcode_content( $content, $shortcode_name ) {
$shortcode_content = et_pb_extract_shortcode_content( $content, $shortcode_name );
if ( $shortcode_content ) {
// Anchor to the ][ brackets around the content so content that appears in
// attributes does not get removed as well.
return str_replace( ']' . $shortcode_content . '[', '][', $content );
}
return $content;
}
}
if ( ! function_exists( 'et_pb_get_global_module_content' ) ) {
/**
* Return global module content.
*
* @param string $content content.
* @param string $shortcode_name shortcode slug.
* @param bool $for_inner_row whether we getting module content for inner row.
*
* @return bool|false|string|string[]|null
*/
function et_pb_get_global_module_content( $content, $shortcode_name, $for_inner_row = false ) {
/**
* Filter list of modules where we don't need to apply autop to the global module content.
*
* @param array Module slugs list.
*/
$custom_autop_ignored_modules = apply_filters( 'et_builder_global_modules_ignore_autop', array() );
$custom_autop_ignored_modules = is_array( $custom_autop_ignored_modules ) ? $custom_autop_ignored_modules : array();
$default_autop_ignored_modules = array_merge( array( 'et_pb_code', 'et_pb_fullwidth_code' ), $custom_autop_ignored_modules );
// Do not apply autop to code modules.
if ( in_array( $shortcode_name, $default_autop_ignored_modules, true ) ) {
return et_pb_extract_shortcode_content( $content, $shortcode_name );
}
$original_code_modules = array();
$shortcode_content = et_pb_extract_shortcode_content( $content, $shortcode_name );
// Getting content for Global row when it's turned to inner row in specialty section
// Need to make sure it wrapped in et_pb_column_inner, not et_pb_column.
if ( $for_inner_row && false === strpos( $shortcode_content, '[et_pb_column_inner' ) ) {
$shortcode_content = str_replace( 'et_pb_column', 'et_pb_column_inner', $shortcode_content );
}
// Get all the code and fullwidth code modules from content.
preg_match_all( '/(\[et_pb(_fullwidth_code|_code).+?\[\/et_pb(_fullwidth_code|_code)\])/s', $shortcode_content, $original_code_modules );
$global_content = et_pb_fix_shortcodes( wpautop( $shortcode_content ) );
// Replace content modified by wpautop for code and fullwidth code modules with original content.
if ( ! empty( $original_code_modules ) ) {
global $et_pb_global_code_replacements;
$et_pb_global_code_replacements = $original_code_modules[0];
$global_content = preg_replace_callback( '/(\[et_pb(_fullwidth_code|_code).+?\[\/et_pb(_fullwidth_code|_code)\])/s', 'et_builder_get_global_code_replacement', $global_content );
}
return $global_content;
}
}
if ( ! function_exists( 'et_builder_get_global_code_replacement' ) ) {
/**
* Retrieve the global code original instance to replace the modified in global code shortcode.
*
* @param array $matches found matches.
*
* @return mixed
*/
function et_builder_get_global_code_replacement( $matches ) {
global $et_pb_global_code_replacements;
return array_shift( $et_pb_global_code_replacements );
}
}
if ( ! function_exists( 'et_builder_activate_bfb_auto_draft' ) ) {
/**
* Force activate post_id which has auto-draft status
*/
function et_builder_activate_bfb_auto_draft() {
et_core_security_check( 'edit_posts', 'et_enable_bfb_nonce' );
$post_id = ! empty( $_POST['et_post_id'] ) ? absint( $_POST['et_post_id'] ) : 0;
if ( 0 === $post_id || ! current_user_can( 'edit_post', $post_id ) ) {
die();
}
// et_builder_activate_bfb_auto_draft() is executed when post title and content empty which means post_status is still lik. ely
// to be "auto-draft". "auto-draft" status returns 404 page; thus post status needs to be updated to "draft".
wp_update_post(
array(
'ID' => $post_id,
'post_status' => 'draft',
)
);
update_post_meta( $post_id, '_et_pb_use_builder', 'on' );
die();
}
}
add_action( 'wp_ajax_et_builder_activate_bfb_auto_draft', 'et_builder_activate_bfb_auto_draft' );
if ( ! function_exists( 'et_builder_ajax_toggle_bfb' ) ) {
/**
* Ajax Callback :: Switch To The New Divi Builder.
*/
function et_builder_ajax_toggle_bfb() {
et_core_security_check( 'manage_options', 'et_builder_toggle_bfb', 'nonce', '_GET' );
$enable = isset( $_GET['enable'] ) && '1' === $_GET['enable'];
$redirect = isset( $_GET['redirect'] ) ? esc_url_raw( $_GET['redirect'] ) : '';
if ( empty( $redirect ) && isset( $_SERVER['HTTP_REFERER'] ) ) {
$redirect = esc_url_raw( $_SERVER['HTTP_REFERER'] );
}
if ( empty( $redirect ) ) {
$redirect = esc_url_raw( admin_url( '/' ) );
}
et_builder_toggle_bfb( $enable );
set_transient( 'et_builder_show_bfb_welcome_modal', true, 0 );
wp_safe_redirect( $redirect );
exit;
}
}
add_action( 'wp_ajax_et_builder_toggle_bfb', 'et_builder_ajax_toggle_bfb' );
/**
* Return font weight select input element html.
*
* @return string
*/
function et_generate_font_weight_select_output() {
$all_weights = et_builder_get_font_weight_list();
$output = '';
foreach ( $all_weights as $number => $name ) {
$output .= sprintf(
'',
esc_attr( $number ),
esc_html( $name ),
esc_html( $number )
);
}
return $output;
}
/**
* Return regular and specialty layouts.
*
* @return mixed|void
*/
function et_builder_get_columns() {
$columns = array(
'specialty' => array(
'1_2,1_2' => array(
'position' => '1,0',
'columns' => '3',
),
'1_2,1_2' => array(
'position' => '0,1',
'columns' => '3',
),
'1_4,3_4' => array(
'position' => '0,1',
'columns' => '3',
),
'3_4,1_4' => array(
'position' => '1,0',
'columns' => '3',
),
'1_4,1_4,1_2' => array(
'position' => '0,0,1',
'columns' => '3',
),
'1_2,1_4,1_4' => array(
'position' => '1,0,0',
'columns' => '3',
),
'1_4,1_2,1_4' => array(
'position' => '0,1,0',
'columns' => '3',
),
'1_3,2_3' => array(
'position' => '0,1',
'columns' => '4',
),
'2_3,1_3' => array(
'position' => '1,0',
'columns' => '4',
),
),
'regular' => array(
'4_4',
'1_2,1_2',
'1_3,1_3,1_3',
'1_4,1_4,1_4,1_4',
'1_5,1_5,1_5,1_5,1_5',
'1_6,1_6,1_6,1_6,1_6,1_6',
'2_5,3_5',
'3_5,2_5',
'1_3,2_3',
'2_3,1_3',
'1_4,3_4',
'3_4,1_4',
'1_4,1_2,1_4',
'1_5,3_5,1_5',
'1_4,1_4,1_2',
'1_2,1_4,1_4',
'1_5,1_5,3_5',
'3_5,1_5,1_5',
'1_6,1_6,1_6,1_2',
'1_2,1_6,1_6,1_6',
),
);
return apply_filters( 'et_builder_get_columns', $columns );
}
/**
* Return columns layout.
*
* @return mixed|void
*/
function et_builder_get_columns_layout() {
$layout_columns =
'<% if ( typeof et_pb_specialty !== \'undefined\' && et_pb_specialty === \'on\' ) { %>
<% if ( view.model.attributes.layout === "2_3" ) { %>
<% } else { %>
<% } %>
<% } else { %>
<%
}
%>';
return apply_filters( 'et_builder_layout_columns', $layout_columns );
}
/**
* Display meta box in admin screen.
*/
function et_pb_pagebuilder_meta_box() {
global $typenow, $post;
do_action( 'et_pb_before_page_builder' );
if ( et_builder_bfb_enabled() ) {
$new_page_url = false;
$is_new_page = false;
$edit_page_id = get_the_ID();
$no_rtl_class = is_rtl() && 'on' === et_get_option( 'divi_disable_translations', 'off' ) ? 'et-fb-no-rtl' : '';
// Polylang creates copy of page and BFB should be loaded on page which is not saved yet and cannot be loaded on FE
// Therefore load the homepage and replace the content for BFB to make it load with content from other post.
if ( 'add' === get_current_screen()->action || (int) get_option( 'page_for_posts' ) === $edit_page_id ) {
$new_page_url = get_home_url();
$is_new_page = true;
}
$bfb_url = et_core_intentionally_unescaped( et_fb_get_bfb_url( $new_page_url, $is_new_page, $edit_page_id ), 'fixed_string' );
// If Admin is SSL but FE is not, we need to fix VB url or it won't work
// because trying to load insecure resource.
$bfb_url = set_url_scheme( $bfb_url, is_ssl() ? 'https' : 'http' );
// phpcs:disable WordPress.Security.EscapeOutput -- XSS safe.
echo "
";
// phpcs:enable
return;
}
$new_builder_url_args = array(
'action' => 'et_builder_toggle_bfb',
'enable' => '1',
'nonce' => wp_create_nonce( 'et_builder_toggle_bfb' ),
);
$new_builder_url = add_query_arg( $new_builder_url_args, admin_url( 'admin-ajax.php' ) );
// Disable BFB notification on Extra category builder. BFB support for Extra category builder will be added post inital launch
// This option available for admins only.
if ( apply_filters( 'et_pb_display_bfb_notification_under_bb', true ) && current_user_can( 'manage_options' ) && et_pb_is_allowed( 'use_visual_builder' ) && et_pb_is_allowed( 'divi_builder_control' ) ) {
echo '