: str_replace(): Passing null to parameter #2 ($replace) of type array|string is deprecated in
$stats_table_name = $wpdb->prefix . 'et_divi_ab_testing_stats';
$wpdb->et_divi_ab_testing_stats = $stats_table_name;
$client_subject_table_name = $wpdb->prefix . 'et_divi_ab_testing_clients';
$wpdb->et_divi_ab_testing_clients = $client_subject_table_name;
* We'll set the default character set and collation for this table.
* If we don't do this, some characters could end up being converted
* to just ?'s when saved in our table.
if ( ! empty( $wpdb->charset ) ) {
$charset_collate = sprintf(
'DEFAULT CHARACTER SET %1$s',
sanitize_text_field( $wpdb->charset )
if ( ! empty( $wpdb->collate ) ) {
$charset_collate .= sprintf(
sanitize_text_field( $wpdb->collate )
$ab_tables_queries = array();
// Remove client_id column from stats table
if ( 0 < $wpdb->query( "SHOW COLUMNS FROM `$wpdb->et_divi_ab_testing_stats` LIKE 'client_id'" ) ) {
$wpdb->query( "ALTER TABLE `$wpdb->et_divi_ab_testing_stats` DROP COLUMN client_id" );
// Remove client subject table
if ( 0 < $wpdb->query( $wpdb->prepare( "SHOW TABLES LIKE %s", $wpdb->et_divi_ab_testing_clients ) ) ) {
$wpdb->query( "DROP TABLE $wpdb->et_divi_ab_testing_clients" );
$ab_tables_queries[] = "CREATE TABLE $wpdb->et_divi_ab_testing_stats (
id mediumint(9) NOT NULL AUTO_INCREMENT,
test_id varchar(20) NOT NULL,
subject_id varchar(20) NOT NULL,
record_date datetime DEFAULT '0000-00-00 00:00:00' NOT NULL,
event varchar(10) NOT NULL,
require_once ABSPATH . 'wp-admin/includes/upgrade.php';
dbDelta( $ab_tables_queries );
'db_version' => ET_PB_AB_DB_VERSION,
update_option( 'et_pb_ab_test_settings', $db_settings );
// Register AB Testing cron
unset( $wpdb->et_divi_ab_testing_stats );
unset( $wpdb->et_divi_ab_testing_clients );
add_action( 'wp_ajax_et_pb_create_ab_tables', 'et_pb_create_ab_tables' );
* Handle adding the AB testing log record via ajax
function et_pb_update_stats_table() {
if ( ! isset( $_POST['et_ab_log_nonce'] ) || ! wp_verify_nonce( $_POST['et_ab_log_nonce'], 'et_ab_testing_log_nonce' ) ) {
$stats_data_json = str_replace( '\\', '', $_POST['stats_data_array'] );
$stats_data_array = json_decode( $stats_data_json, true );
et_pb_add_stats_record( $stats_data_array );
add_action( 'wp_ajax_et_pb_update_stats_table', 'et_pb_update_stats_table' );
add_action( 'wp_ajax_nopriv_et_pb_update_stats_table', 'et_pb_update_stats_table' );
* List of valid AB Testing refresh interval duration
function et_pb_ab_refresh_interval_durations() {
return apply_filters( 'et_pb_ab_refresh_interval_durations', array(
* Get refresh interval of particular AB Testing
* @param string default interval
* @return string interval used in particular AB Testing
function et_pb_ab_get_refresh_interval( $post_id, $default = 'hourly' ) {
$interval = get_post_meta( $post_id, '_et_pb_ab_stats_refresh_interval', true );
if ( in_array( $interval, array_keys( et_pb_ab_refresh_interval_durations() ) ) ) {
return apply_filters( 'et_pb_ab_get_refresh_interval', $interval, $post_id );
return apply_filters( 'et_pb_ab_default_refresh_interval', $default, $post_id );
* Get refresh interval duration of particular AB Testing
* @param string default interval duration
* @return string test's interval duration
function et_pb_ab_get_refresh_interval_duration( $post_id, $default = 'day' ) {
$durations = et_pb_ab_refresh_interval_durations();
$interval = et_pb_ab_get_refresh_interval( $post_id );
$interval_duration = isset( $durations[ $interval ] ) ? $durations[ $interval ] : $default;
return apply_filters( 'et_pb_ab_get_refresh_interval_duration', $interval_duration, $post_id );
* Get goal module slug of particular AB Testing
* @return string test's goal module slug
function et_pb_ab_get_goal_module( $post_id ) {
return get_post_meta( $post_id, '_et_pb_ab_goal_module', true );
* Register Divi's AB Testing cron
* There are 2 options - daily and hourly, so schedule 2 events
function et_pb_create_ab_cron() {
if ( ! wp_next_scheduled( 'et_pb_ab_cron', array( 'interval' => 'daily' ) ) ) {
wp_schedule_event( time(), 'daily', 'et_pb_ab_cron', array( 'interval' => 'daily' ) );
if ( ! wp_next_scheduled( 'et_pb_ab_cron', array( 'interval' => 'hourly' ) ) ) {
wp_schedule_event( time(), 'hourly', 'et_pb_ab_cron', array( 'interval' => 'hourly' ) );
* Perform Divi's AB Testing cron
function et_pb_ab_cron( $args ) {
$all_tests = et_pb_ab_get_all_tests();
$interval = isset( $args ) ? $args : 'hourly';
if ( empty( $all_tests ) ) {
// update cache for each test and for each duration
foreach ( $all_tests as $test ) {
$current_test_interval = et_pb_ab_get_refresh_interval( $test['test_id'] );
// determine whether or not we should update the stats for current test depending on interval parameter
if ( $current_test_interval !== $interval ) {
foreach ( et_pb_ab_get_stats_data_duration() as $duration ) {
et_pb_ab_get_stats_data( $test['test_id'], $duration, false, true, true );
add_action( 'et_pb_ab_cron', 'et_pb_ab_cron' );
function et_pb_ab_clear_cache_handler( $test_id ) {
foreach ( et_pb_ab_get_stats_data_duration() as $duration ) {
delete_transient( 'et_pb_ab_' . $test_id . '_stats_' . $duration );
function et_pb_ab_clear_cache() {
if ( ! isset( $_POST['et_pb_ab_nonce'] ) || ! wp_verify_nonce( $_POST['et_pb_ab_nonce'], 'ab_testing_builder_nonce' ) ) {
$test_id = ! empty( $_POST['et_pb_test_id'] ) ? intval( $_POST['et_pb_test_id'] ) : '';
// Verify user permission
if ( empty( $test_id ) || ! current_user_can( 'edit_post', $test_id ) || ! et_pb_is_allowed( 'ab_testing' ) ) {
et_pb_ab_clear_cache_handler( $test_id );
// VB ask to load data to save request
if ( isset( $_POST['et_pb_ab_load_data'] ) && isset( $_POST['et_pb_test_id'] ) && isset( $_POST['et_pb_ab_duration'] ) ) {
// Allowlist the duration value
$duration = in_array( $_POST['et_pb_ab_duration'], et_pb_ab_get_stats_data_duration() ) ? $_POST['et_pb_ab_duration'] : 'day';
$output = et_pb_ab_get_stats_data( intval( $_POST['et_pb_test_id'] ), $duration );
die( wp_json_encode( $output ) );
add_action( 'wp_ajax_et_pb_ab_clear_cache', 'et_pb_ab_clear_cache' );
function et_pb_ab_get_all_tests() {
$wpdb->et_divi_ab_testing_stats = $wpdb->prefix . 'et_divi_ab_testing_stats';
// do nothing if no stats table exists in current WP
if ( ! $wpdb->get_var( "SHOW TABLES LIKE '$wpdb->et_divi_ab_testing_stats'" ) ) {
// construct sql query to get all the test ID from db
$sql = "SELECT DISTINCT test_id FROM `$wpdb->et_divi_ab_testing_stats`";
// cache the data from conversions table
$all_tests = $wpdb->get_results( $sql, ARRAY_A ); // WPCS: unprepared SQL okay, value of $sql was prepared above.
unset( $wpdb->et_divi_ab_testing_stats );
function et_pb_ab_clear_stats() {
if ( ! isset( $_POST['et_pb_ab_nonce'] ) || ! wp_verify_nonce( $_POST['et_pb_ab_nonce'], 'ab_testing_builder_nonce' ) ) {
$test_id = ! empty( $_POST['et_pb_test_id'] ) ? intval( $_POST['et_pb_test_id'] ) : '';
// Verify user permission
if ( empty( $test_id ) || ! current_user_can( 'edit_post', $test_id ) || ! et_pb_is_allowed( 'ab_testing' ) ) {
et_pb_ab_remove_stats( $test_id );
et_pb_ab_clear_cache_handler( $test_id );
add_action( 'wp_ajax_et_pb_ab_clear_stats', 'et_pb_ab_clear_stats' );
* Remove AB Testing log and clear stats cache
function et_pb_ab_remove_stats( $test_id ) {
$test_id = intval( $test_id );
et_pb_ab_clear_cache_handler( $test_id );
$wpdb->et_divi_ab_testing_stats = $wpdb->prefix . 'et_divi_ab_testing_stats';
// do nothing if no stats table exists in current WP
if ( ! $wpdb->get_var( "SHOW TABLES LIKE '$wpdb->et_divi_ab_testing_stats'" ) ) {
// construct sql query to remove value from DB table
$sql = "DELETE FROM `$wpdb->et_divi_ab_testing_stats` WHERE test_id = %d";
$wpdb->query( $wpdb->prepare( $sql, $sql_args ) ); // WPCS: unprepared SQL okay, value of $sql was prepared above.
unset( $wpdb->et_divi_ab_testing_stats );
function et_pb_ab_shop_trigger() {
echo '<div class="et_pb_ab_shop_conversion"></div>';
add_action( 'woocommerce_thankyou', 'et_pb_ab_shop_trigger' );
function et_pb_split_track( $atts ) {
$settings = shortcode_atts( array(
$output = sprintf( '<div class="et_pb_ab_split_track" style="display:none;" data-test_id="%1$s"></div>',
esc_attr( $settings['id'] )
add_shortcode( 'et_pb_split_track', 'et_pb_split_track' );
* Get all posts loaded for the current request that have AB testing enabled.
* This includes TB layouts and the current post, if any.
function et_builder_ab_get_current_tests() {
$layouts = et_theme_builder_get_template_layouts();
foreach ( $layouts as $layout ) {
if ( is_array( $layout ) && $layout['override'] ) {
$posts[] = $layout['id'];
foreach ( $posts as $post_id ) {
if ( et_pb_is_pagebuilder_used( $post_id ) && et_is_ab_testing_active( $post_id ) ) {
'test_id' => get_post_meta( $post_id, '_et_pb_ab_testing_id', true ),
* Initialize AB Testing. Check whether the user has visited the page or not by checking its cookie
function et_pb_ab_init() {
$tests = et_builder_ab_get_current_tests();
foreach ( $tests as $test ) {
et_builder_ab_initialize_for_post( $test['post_id'] );
add_action( 'wp', 'et_pb_ab_init' );
* Initialize AB testing for the specified post.
* @param integer $post_id
function et_builder_ab_initialize_for_post( $post_id ) {
global $et_pb_ab_subject;
if ( ! is_array( $et_pb_ab_subject ) ) {
$et_pb_ab_subject = array();
$ab_subjects = et_pb_ab_get_subjects( $post_id );
$ab_hash_key = defined( 'NONCE_SALT' ) ? NONCE_SALT : 'default-divi-hash-key';
$hashed_subject_id = et_pb_ab_get_visitor_cookie( $post_id, 'view_page' );
if ( $hashed_subject_id ) {
// Compare subjects against hashed subject id found on cookie to verify whether cookie value is valid or not
foreach ( $ab_subjects as $ab_subject ) {
// Valid subject_id is found
if ( hash_hmac( 'md5', $ab_subject, $ab_hash_key ) === $hashed_subject_id ) {
$et_pb_ab_subject[ $post_id ] = $ab_subject;
// If no valid subject found, get the first one
if ( isset( $ab_subjects[0] ) && ! et_()->array_get( $et_pb_ab_subject, $post_id, '' ) ) {
$et_pb_ab_subject[ $post_id ] = $ab_subjects[0];
// First visit. Get next subject on queue
$next_subject_index = get_post_meta( $post_id, '_et_pb_ab_next_subject' , true );
// Get current subject index based on `_et_pb_ab_next_subject` post meta value
$subject_index = false !== $next_subject_index && isset( $ab_subjects[ $next_subject_index ] ) ? (int) $next_subject_index : 0;
// Get current subject index
$et_pb_ab_subject[ $post_id ] = $ab_subjects[ $subject_index ];
$hashed_subject_id = hash_hmac( 'md5', $et_pb_ab_subject[ $post_id ], $ab_hash_key );
// Set cookie for returning visit
et_pb_ab_set_visitor_cookie( $post_id, 'view_page', $hashed_subject_id );
// Bump subject index and save on post meta for next visitor
et_pb_ab_increment_current_ab_module_id( $post_id );
// log the view_page event right away
$is_et_fb_enabled = function_exists( 'et_fb_enabled' ) && et_fb_enabled();
if ( ! is_admin() && ! $is_et_fb_enabled ) {
et_pb_add_stats_record( array(
'subject_id' => $et_pb_ab_subject[ $post_id ],
'record_type' => 'view_page',