// Prevent file from being loaded directly.
if ( ! defined( 'ABSPATH' ) ) {
if ( ! defined( 'ET_PB_AB_DB_VERSION' ) ) {
define( 'ET_PB_AB_DB_VERSION', '1.1' );
* AB Testing related data
* @param integer $post_id Post id.
* @return mixed|void {array} AB Testing related data
function et_builder_ab_options( $post_id ) {
'db_status' => true === et_pb_db_status_up_to_date() ? 'on' : 'off',
'has_report' => et_pb_ab_has_report( $post_id ),
'has_permission' => et_pb_is_allowed( 'ab_testing' ),
'refresh_interval_duration' => et_pb_ab_get_refresh_interval_duration( $post_id ),
'refresh_interval_durations' => et_pb_ab_refresh_interval_durations(),
'analysis_formula' => et_pb_ab_get_analysis_formulas(),
'have_conversions' => et_pb_ab_get_modules_have_conversions(),
'sales_title' => esc_html__( 'Sales', 'et_builder' ),
'total_title' => esc_html__( 'Total', 'et_builder' ),
'subjects_rank' => ( 'on' === get_post_meta( $post_id, '_et_pb_use_builder', true ) ) ? et_pb_ab_get_saved_subjects_ranks( $post_id ) : false,
'subjects_rank_color' => et_pb_ab_get_subject_rank_colors(),
return apply_filters( 'et_builder_ab_options', $ab_options );
* Filterable AB Testing labels
* @return {array} AB Testing labels
function et_builder_ab_labels() {
'alert_modal_defaults' => array(
'proceed_label' => esc_html__( 'Ok', 'et_builder' ),
'select_subject' => array(
'title' => esc_html__( 'Select Split Testing Subject', 'et_builder' ),
'desc' => esc_html__( 'You have activated the Divi Leads Split Testing System. Using split testing, you can create different element variations on your page to find out which variation most positively affects the conversion rate of your desired goal. After closing this window, please click on the section, row or module that you would like to split test.', 'et_builder' ),
'title' => esc_html__( 'Select Your Goal', 'et_builder' ),
'desc' => esc_html__( 'Congratulations, you have selected a split testing subject! Next you need to select your goal. After closing this window, please click the section, row or module that you want to use as your goal. Depending on the element you choose, Divi will track relevant conversion rates for clicks, reads or sales. For example, if you select a Call To Action module as your goal, then Divi will track how variations in your test subjects affect how often visitors read and click the button in your Call To Action module. The test subject itself can also be selected as your goal.', 'et_builder' ),
'proceed_label' => esc_html__( 'Ok', 'et_builder' ),
'configure_alternative' => array(
'title' => esc_html__( 'Configure Subject Variations', 'et_builder' ),
'desc' => esc_html__( 'Congratulations, your split test is ready to go! You will notice that your split testing subject has been duplicated. Each split testing variation will be displayed to your visitors and statistics will be collected to figure out which variation results in the highest goal conversion rate. Your test will begin when you save this page.', 'et_builder' ),
'proceed_label' => esc_html__( 'Ok', 'et_builder' ),
'select_winner_first' => array(
'title' => esc_html__( 'Select Split Testing Winner', 'et_builder' ),
'desc' => esc_html__( 'Before ending your split test, you must choose which split testing variation to keep. Please select your favorite or highest converting subject. Alternative split testing subjects will be removed and stats will be cleared.', 'et_builder' ),
'select_subject_first' => array(
'title' => esc_html__( 'Select Split Testing Subject', 'et_builder' ),
'desc' => esc_html__( 'You need to select a split testing subject first.', 'et_builder' ),
'select_goal_first' => array(
'title' => esc_html__( 'Select Split Testing Goal', 'et_builder' ),
'desc' => esc_html__( 'You need to select a split testing goal first. ', 'et_builder' ),
'cannot_select_subject_parent_as_goal' => array(
'title' => esc_html__( 'Select A Different Goal', 'et_builder' ),
'desc' => esc_html__( 'This element cannot be used as a your split testing goal. Please select a different module, or section.', 'et_builder' ),
'proceed_label' => esc_html__( 'Ok', 'et_builder' ),
'cannot_select_global_children_as_subject' => array(
'title' => esc_html__( 'Select a Different Subject', 'et_builder' ),
'desc' => esc_html__( 'This element cannot be used as split testing subject because it is part of global module. Please select different module, row, or section', 'et_builder' ),
'cannot_select_global_children_as_goal' => array(
'title' => esc_html__( 'Select a Different Goal', 'et_builder' ),
'desc' => esc_html__( 'This element cannot be used as split testing goal because it is part of global module. Please select different module, row, or section', 'et_builder' ),
'cannot_publish_finish_configuration_first' => array(
'title' => esc_html__( 'Setup Split Test First', 'et_builder' ),
'desc' => esc_html__( 'You cannot publish the layout right now because you have incomplete split test configuration. Please finish the split test configuration first, then try saving again.', 'et_builder' ),
'proceed_label' => esc_html__( 'Ok', 'et_builder' ),
'cannot_save_draft_finish_configuration_first' => array(
'title' => esc_html__( 'Setup Split Test First', 'et_builder' ),
'desc' => esc_html__( 'You cannot save the layout right now because you have incomplete split test configuration. Please finish the split test configuration first, then try save draft again.', 'et_builder' ),
'proceed_label' => esc_html__( 'Ok', 'et_builder' ),
'view_stats_thead_titles' => array(
esc_html__( 'ID', 'et_builder' ),
esc_html__( 'Subject', 'et_builder' ),
esc_html__( 'Impressions', 'et_builder' ),
esc_html__( 'Clicks', 'et_builder' ),
esc_html__( 'Clickthrough Rate', 'et_builder' ),
esc_html__( 'ID', 'et_builder' ),
esc_html__( 'Subject', 'et_builder' ),
esc_html__( 'Impressions', 'et_builder' ),
esc_html__( 'Reads', 'et_builder' ),
esc_html__( 'Reading Rate', 'et_builder' ),
esc_html__( 'ID', 'et_builder' ),
esc_html__( 'Subject', 'et_builder' ),
esc_html__( 'Impressions', 'et_builder' ),
esc_html__( 'Stays', 'et_builder' ),
esc_html__( 'Bounce Rate', 'et_builder' ),
esc_html__( 'ID', 'et_builder' ),
esc_html__( 'Subject', 'et_builder' ),
esc_html__( 'Goal Views', 'et_builder' ),
esc_html__( 'Goal Reads', 'et_builder' ),
esc_html__( 'Engagement Rate', 'et_builder' ),
esc_html__( 'ID', 'et_builder' ),
esc_html__( 'Subject', 'et_builder' ),
esc_html__( 'Impressions', 'et_builder' ),
esc_html__( 'Conversion Goals', 'et_builder' ),
esc_html__( 'Conversion Rate', 'et_builder' ),
'shortcode_conversions' => array(
esc_html__( 'ID', 'et_builder' ),
esc_html__( 'Subject', 'et_builder' ),
esc_html__( 'Impressions', 'et_builder' ),
esc_html__( 'Shortcode Conversions', 'et_builder' ),
esc_html__( 'Conversion Rate', 'et_builder' ),
'cannot_save_app_layout_has_ab_testing' => array(
'title' => esc_html__( 'Can\'t Save Layout', 'et_builder' ),
'desc' => esc_html__( 'You cannot save layout while a split test is running. Please end your split test and then try again.', 'et_builder' ),
'cannot_save_section_layout_has_ab_testing' => array(
'title' => esc_html__( 'Can\'t Save Section', 'et_builder' ),
'desc' => esc_html__( 'You cannot save this section while a split test is running. Please end your split test and then try again.', 'et_builder' ),
'cannot_save_row_layout_has_ab_testing' => array(
'title' => esc_html__( 'Can\'t Save Row', 'et_builder' ),
'desc' => esc_html__( 'You cannot save this row while a split test is running. Please end your split test and then try again.', 'et_builder' ),
'cannot_save_row_inner_layout_has_ab_testing' => array(
'title' => esc_html__( 'Can\'t Save Row', 'et_builder' ),
'desc' => esc_html__( 'You cannot save this row while a split test is running. Please end your split test and then try again.', 'et_builder' ),
'cannot_save_module_layout_has_ab_testing' => array(
'title' => esc_html__( 'Can\'t Save Module', 'et_builder' ),
'desc' => esc_html__( 'You cannot save this module while a split test is running. Please end your split test and then try again.', 'et_builder' ),
'cannot_load_layout_has_ab_testing' => array(
'title' => esc_html__( 'Can\'t Load Layout', 'et_builder' ),
'desc' => esc_html__( 'You cannot load a new layout while a split test is running. Please end your split test and then try again.', 'et_builder' ),
'cannot_clear_layout_has_ab_testing' => array(
'title' => esc_html__( 'Can\'t Clear Layout', 'et_builder' ),
'desc' => esc_html__( 'You cannot clear your layout while a split testing is running. Please end your split test before clearing your layout.', 'et_builder' ),
// Cannot Import / Export Layout (Portability).
'cannot_import_export_layout_has_ab_testing' => array(
'title' => esc_html__( 'Can\'t Import/Export Layout', 'et_builder' ),
'desc' => esc_html__( 'You cannot import or export a layout while a split test is running. Please end your split test and then try again.', 'et_builder' ),
// Moving Goal / Subject.
'cannot_move_module_goal_out_from_subject' => array(
'title' => esc_html__( 'Can\'t Move Goal', 'et_builder' ),
'desc' => esc_html__( 'Once set, a goal that has been placed inside a split testing subject cannot be moved outside the split testing subject. You can end your split test and start a new one if you would like to make this change.', 'et_builder' ),
'cannot_move_row_goal_out_from_subject' => array(
'title' => esc_html__( 'Can\'t Move Goal', 'et_builder' ),
'desc' => esc_html__( 'Once set, a goal that has been placed inside a split testing subject cannot be moved outside the split testing subject. You can end your split test and start a new one if you would like to make this change.', 'et_builder' ),
'cannot_move_goal_into_subject' => array(
'title' => esc_html__( 'Can\'t Move Goal', 'et_builder' ),
'desc' => esc_html__( 'A split testing goal cannot be moved inside of a split testing subject. To perform this action you must first end your split test.', 'et_builder' ),
'cannot_move_subject_into_goal' => array(
'title' => esc_html__( 'Can\'t Move Subject', 'et_builder' ),
'desc' => esc_html__( 'A split testing subject cannot be moved inside of a split testing goal. To perform this action you must first end your split test.', 'et_builder' ),
// Cannot Paste Goal / Subject.
'cannot_paste_goal' => array(
'title' => esc_html__( 'Can\'t Paste Goal', 'et_builder' ),
'desc' => esc_html__( 'A split testing goal cannot be copied, cut, and pasted. To perform this action you must first end your split test.', 'et_builder' ),
'cannot_paste_row_has_subject_into_goal' => array(
'title' => esc_html__( 'Can\'t Paste Row', 'et_builder' ),
'desc' => esc_html__( 'Row that has split testing subject cannot be pasted inside a split testing goal. To perform this action you must first end your split test.', 'et_builder' ),
'cannot_paste_subject_into_goal' => array(
'title' => esc_html__( 'Can\'t Paste Subject', 'et_builder' ),
'desc' => esc_html__( 'A split testing subject cannot be pasted inside a split testing goal. To perform this action you must first end your split test.', 'et_builder' ),
'cannot_remove_section_has_goal' => array(
'title' => esc_html__( 'Can\'t Remove Section', 'et_builder' ),
'desc' => esc_html__( 'This section cannot be removed because it contains a split testing goal. Goals cannot be deleted. You must first end your split test before performing this action.', 'et_builder' ),
'cannot_remove_row_has_goal' => array(
'title' => esc_html__( 'Can\'t Remove Row', 'et_builder' ),
'desc' => esc_html__( 'This row cannot be removed because it contains a split testing goal. Goals cannot be deleted. You must first end your split test before performing this action.', 'et_builder' ),
// Removing + Has Unremovable Subjects.
'cannot_remove_section_has_unremovable_subject' => array(
'title' => esc_html__( 'Can\'t Remove Section', 'et_builder' ),
'desc' => esc_html__( 'Split testing requires at least 2 subject variations. This variation cannot be removed until additional variations have been added.', 'et_builder' ),
'cannot_remove_row_has_unremovable_subject' => array(
'title' => esc_html__( 'Can\'t Remove Row', 'et_builder' ),
'desc' => esc_html__( 'Split testing requires at least 2 subject variations. This variation cannot be removed until additional variations have been added', 'et_builder' ),
'cannot_clone_section_has_goal' => array(
'title' => esc_html__( 'Can\'t Clone Section', 'et_builder' ),
'desc' => esc_html__( 'This section cannot be duplicated because it contains a split testing goal. Goals cannot be duplicated. You must first end your split test before performing this action.', 'et_builder' ),
'cannot_clone_row_has_goal' => array(
'title' => esc_html__( 'Can\'t Clone Row', 'et_builder' ),
'desc' => esc_html__( 'This row cannot be duplicated because it contains a split testing goal. Goals cannot be duplicated. You must first end your split test before performing this action.', 'et_builder' ),
'cannot_copy_section_has_goal' => array(
'title' => esc_html__( 'Can\'t Copy Section', 'et_builder' ),
'desc' => esc_html__( 'This section cannot be copied because it contains a split testing goal. Goals cannot be duplicated. You must first end your split test before performing this action.', 'et_builder' ),
'cannot_copy_row_has_goal' => array(
'title' => esc_html__( 'Can\'t Copy Row', 'et_builder' ),
'desc' => esc_html__( 'This row cannot be copied because it contains a split testing goal. Goals cannot be duplicated. You must first end your split test before performing this action.', 'et_builder' ),
'cannot_copy_goal' => array(
'title' => esc_html__( 'Can\'t Copy Goal', 'et_builder' ),
'desc' => esc_html__( 'Goal cannot be copied. You must first end your split test before performing this action.', 'et_builder' ),
// No AB Testing Permission.
'has_no_ab_permission' => array(
'title' => esc_html__( 'Can\'t Edit Split Test', 'default' ),
'desc' => esc_html__( 'You do not have permission to edit the module, row or section in this split test.', 'et_builder' ),
// No AB Testing Report Yet.
'title' => esc_html__( 'Statistics are being collected', 'et_builder' ),
'desc' => esc_html__( 'Stats will be displayed upon sufficient data collection', 'et_builder' ), // 10
// Set Global Winner Status.
'set_global_winner_status' => array(
'title' => esc_html__( 'Set Winner Status', 'et_builder' ),
'desc' => esc_html__( 'You were using global item as split testing winner. Consequently, you have to choose between:', 'et_builder' ),
'option_1' => esc_html__( 'Save winner as global item (selected subject will be synced and your global item will be updated in the Divi Library)', 'et_builder' ),
'option_2' => esc_html__( 'Save winner as non-global item (selected subject will no longer be a global item and your changes will not modify the global item)', 'et_builder' ),
'cancel' => esc_html__( 'Save as Global Item', 'et_builder' ),
'proceed' => esc_html__( 'Save', 'et_builder' ),
return apply_filters( 'et_builder_ab_settings', $ab_settings );
* AJAX endpoint for builder data
function et_pb_ab_builder_data() {
// phpcs:ignore ET.Sniffs.ValidatedSanitizedInput -- `wp_verify_nonce()` does not store or display the nonce value, therefor XSS safe.
if ( ! isset( $_POST['et_pb_ab_nonce'] ) || ! wp_verify_nonce( $_POST['et_pb_ab_nonce'], 'ab_testing_builder_nonce' ) ) {
'et_pb_ab_test_id' => '',
'et_pb_ab_duration' => 'week',
$_post = wp_parse_args( $_POST, $defaults );
$_post['et_pb_ab_test_id'] = ! empty( $_post['et_pb_ab_test_id'] ) ? intval( $_post['et_pb_ab_test_id'] ) : '';
// Verify user permission.
if ( empty( $_post['et_pb_ab_test_id'] ) || ! current_user_can( 'edit_post', $_post['et_pb_ab_test_id'] ) || ! et_pb_is_allowed( 'ab_testing' ) ) {
// Allowlist the duration value.
$duration = in_array( $_post['et_pb_ab_duration'], et_pb_ab_get_stats_data_duration(), true ) ? $_post['et_pb_ab_duration'] : $defaults['et_pb_ab_duration'];
$output = et_pb_ab_get_stats_data( intval( $_post['et_pb_ab_test_id'] ), $duration );
die( et_core_esc_previously( wp_json_encode( $output ) ) );
add_action( 'wp_ajax_et_pb_ab_builder_data', 'et_pb_ab_builder_data' );
* Get AB Testing subject ranking data
* @param integer $post_id Post id.
function et_pb_ab_get_saved_subjects_ranks( $post_id ) {
// Make sure that there are $post_id.
if ( ! isset( $post_id ) && isset( $post->ID ) ) {
$subject_list = get_post_meta( $post_id, '_et_pb_ab_subjects', true );
$subjects_ids = explode( ',', $subject_list );
$goal_slug = et_pb_ab_get_goal_module( $post_id );
$rank_metrics = in_array( $goal_slug, et_pb_ab_get_modules_have_conversions(), true ) ? 'conversions' : 'clicks';
if ( ! empty( $subjects_ids ) ) {
// Get conversion rate data.
$subjects_ranks = et_pb_ab_get_subjects_ranks( $post_id, $rank_metrics, 'all' );
// Sort from high to low and mantain key association.
arsort( $subjects_ranks );
// Loop saved subject ids.
foreach ( $subjects_ids as $subject_id ) {
$subject_key = 'subject_' . $subject_id;
$subject_rank = isset( $subjects_ranks[ $subject_key ] ) ? array_search( $subjects_ranks[ $subject_key ], array_values( $subjects_ranks ), true ) + 1 : false;
// Check whether current subject has saved conversion rate data or not.
$subjects[ $subject_key ] = array(
'percentage' => esc_html( $subjects_ranks[ $subject_key ] . '%' ),
'rank' => esc_attr( $subject_rank ),
* Define ranking-based subject color
function et_pb_ab_get_subject_rank_colors() {
$subject_rank_colors = array(
return array_map( 'et_sanitize_alpha_color', apply_filters( 'et_pb_ab_get_subject_rank_colors', $subject_rank_colors ) );
* Print AB Testing subject-ranking color scheme
* @return string inline CSS styling for subject rank
function et_pb_ab_get_subject_rank_colors_style() {
$colors = et_pb_ab_get_subject_rank_colors();
foreach ( $colors as $color ) {
'.et_pb_ab_subject.rank-%1$s .et_pb_module_block,
.et_pb_ab_subject.rank-%1$s.et_pb_section .et-pb-controls,
.et_pb_ab_subject.rank-%1$s.et_pb_row .et-pb-controls,
.et_pb_ab_subject.rank-%1$s.et_pb_module_block {
* @param int $post_id post ID.
* @param string $ranking_basis ranking basis. This can be any value on data's subjects_totals
* view_page|read_page|view_goal|read_goal|click_goal|con_goal|clicks|reads|bounces|engagements|conversions.
* @param string $duration duration of the data that is used.
* @return array key = `subject_` + subject_id as key and the value as value, sorted in ascending
function et_pb_ab_get_subjects_ranks( $post_id, $ranking_basis = 'engagements', $duration = 'week' ) {
$data = et_pb_ab_get_stats_data( $post_id, $duration );
$subjects = et_pb_ab_get_subjects( $post_id, 'array', 'subject_' );
if ( isset( $data['subjects_totals'] ) && ! empty( $data['subjects_totals'] ) && ! empty( $subjects ) ) {
$ranks = wp_list_pluck( $data['subjects_totals'], $ranking_basis );
// Remove inactive subjects from ranks.
foreach ( $ranks as $rank_key => $rank_value ) {
if ( ! in_array( $rank_key, $subjects, true ) ) {
unset( $ranks[ $rank_key ] );
* Get formatted stats data that is used by builder's AB Testing stats
* @param int $post_id post ID.
* @param string $duration day|week|month|all duration of stats.
* @param bool $time has to be in Y-m-d H:i:s format.
* @param bool $force_update Whether to force stats update.
* @param bool $is_cron_task Is it a cron task.
* @return array stats data
function et_pb_ab_get_stats_data( $post_id, $duration = 'week', $time = false, $force_update = false, $is_cron_task = false ) {
$post_id = intval( $post_id );
$goal_slug = et_pb_ab_get_goal_module( $post_id );
$rank_metrics = in_array( $goal_slug, et_pb_ab_get_modules_have_conversions(), true ) ? 'conversions' : 'clicks';
$subjects = et_pb_ab_get_subjects( $post_id, 'array', 'subject_', $is_cron_task );
$subjects_id = et_pb_ab_get_subjects( $post_id, 'array', false, $is_cron_task );
$cached_data = get_transient( 'et_pb_ab_' . $post_id . '_stats_' . $duration );
// Get rank coloring scheme.
$subject_rank_colors = et_pb_ab_get_subject_rank_colors();
// return cached logs if exist and if force_update == false.
if ( $cached_data && ! $force_update ) {
// Remove inactive subjects.
if ( isset( $cached_data['subjects_id'] ) && ! empty( $cached_data['subjects_id'] ) ) {
foreach ( $cached_data['subjects_id'] as $subject_id_key => $subject_id_value ) {
if ( ! in_array( $subject_id_value, $subjects_id, true ) ) {
unset( $cached_data['subjects_id'][ $subject_id_key ] );