: str_replace(): Passing null to parameter #2 ($replace) of type array|string is deprecated in
unset( $cached_data['subjects_totals'][ $subject_total_id ] );
$cached_subjects_ranks = wp_list_pluck( $cached_data['subjects_totals'], $rank_metrics );
$cached_subjects_ranks_index = 0;
// Sort from high to low, mantain keys
arsort( $cached_subjects_ranks );
foreach ( $cached_subjects_ranks as $subject_rank_id => $subject_rank_value ) {
$is_empty_rank_value = 0 === $subject_rank_value;
$has_subject_rank_color = isset( $subject_rank_colors[ $cached_subjects_ranks_index ] );
// If the rank value (derived from engagement) is empty, display default subject color
if ( $is_empty_rank_value ) {
$cached_data['subjects_totals'][ $subject_rank_id ]['color'] = '#F3CB57';
$cached_data['subjects_totals'][ $subject_rank_id ]['color'] = $has_subject_rank_color ? $subject_rank_colors[ $cached_subjects_ranks_index ] : '#7E0000';
$cached_subjects_ranks_index++;
$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'" ) ) {
$event_types = et_pb_ab_get_event_types();
$analysis_types = et_pb_ab_get_analysis_types();
$analysis_formulas = et_pb_ab_get_analysis_formulas();
$time = $time ? $time : date( 'Y-m-d H:i:s', current_time( 'timestamp' ) );
'subjects_id' => $subjects_id,
'subjects_logs' => array(),
'subjects_analysis' => array(),
'subjects_totals' => array(),
'events_totals' => array(),
$date_range_interval = 'week';
"SELECT subject_id, event, YEARWEEK(record_date) AS 'date', COUNT(id) AS 'count' FROM `{$wpdb->et_divi_ab_testing_stats}` WHERE test_id = %d GROUP BY subject_id, YEARWEEK(record_date), event",
$date_range_interval = 'day';
"SELECT subject_id, event, DATE(record_date) AS 'date', COUNT(id) AS 'count' FROM `{$wpdb->et_divi_ab_testing_stats}` WHERE test_id = %d AND record_date <= %s AND record_date > DATE_SUB( %s, INTERVAL 1 MONTH ) GROUP BY subject_id, DAYOFMONTH(record_date), event",
$date_range_interval = 'hour';
"SELECT subject_id, event, DATE_FORMAT(record_date, %s) AS 'date', COUNT(id) AS 'count' FROM `{$wpdb->et_divi_ab_testing_stats}` WHERE test_id = %d AND record_date <= %s AND record_date > DATE_SUB( %s, INTERVAL 1 DAY ) GROUP BY subject_id, HOUR(record_date), event",
$date_range_interval = 'day';
"SELECT subject_id, event, DATE(record_date) AS 'date', COUNT(id) AS 'count' FROM `{$wpdb->et_divi_ab_testing_stats}` WHERE test_id = %d AND record_date <= %s AND record_date > DATE_SUB( %s, INTERVAL 1 WEEK ) GROUP BY subject_id, DAYOFMONTH(record_date), event",
$results = $wpdb->get_results( $query ); // phpcs:ignore WordPress.DB.PreparedSQL.NotPrepared -- value of $query was prepared in above switch statement.
unset( $wpdb->et_divi_ab_testing_stats );
if ( ! empty( $results ) ) {
// Get min and max timestamp based on query result
$min_max_date = et_pb_ab_get_min_max_timestamp( $results, $date_range_interval );
$date_list = et_pb_ab_get_date_range( $min_max_date['min'], $min_max_date['max'], $date_range_interval );
// Insert date list to main placeholder
$stats['dates'] = $date_list;
// Format YYYYWW format on all-time stats into human-readable format (M jS)
foreach ( $stats['dates'] as $date_key => $date_time ) {
if ( 'all' === $duration ) {
$week_in_seconds = 60 * 60 * 24 * 7;
$current_time = current_time( 'timestamp' );
$week_start_time = strtotime( substr( $date_time, 0, 4 ) . 'W' . substr( $date_time, 4, 2 ) );
$week_end_time = $week_start_time + $week_in_seconds;
// Don't let the end time pass current time
if ( $week_end_time > $current_time ) {
$week_end_time = $current_time;
// Simplify the label by removing the end month when the start and end month are identical
if ( date( 'M', $week_start_time ) === date( 'M', $week_end_time ) ) {
$stats['dates'][ $date_key ] = date( 'M jS', $week_start_time ) . ' - ' . date( 'jS', $week_end_time );
$stats['dates'][ $date_key ] = date( 'M jS', $week_start_time ) . ' - ' . date( 'M jS', $week_end_time );
} else if ( 'day' === $duration ) {
$stats['dates'][ $date_key ] = date( 'H:i', strtotime( $date_time ) );
$stats['dates'][ $date_key ] = date( 'M jS', strtotime( $date_time ) );
// Fill subject logs placeholder with proper default
$stats['subjects_logs'] = array_fill_keys(
// Loop query result and place into placeholder
foreach ( $results as $log ) {
if ( ! in_array( $log->subject_id, $subjects_id ) ) {
// Format year-week to ensure the given date is equal to the expected format. MySQl YEARWEEK() seems to output
// the date in ISO-8601 format (first week of the year becomes 201753 instead of the expected 201801)
if ( 'all' === $duration ) {
$log_date = date( 'YW', strtotime( substr( $log_date, 0, 4 ) . 'W' . substr( $log_date, 4, 2 ) ) );
$stats['subjects_logs'][ "subject_{$log->subject_id}" ][ $log->event ][ $log_date ] = $log->count;
// Determine logs' totals and run analysis
foreach ( $stats['subjects_logs'] as $subject_log_id => $subject_log ) {
foreach ( $subject_log as $log_type => $logs ) {
$stats['subjects_totals'][ $subject_log_id ][ $log_type ] = array_sum( $logs );
// Run analysis for stats' total data
foreach ( $analysis_types as $analysis_type ) {
$numerator_event = $analysis_formulas[ $analysis_type ]['numerator'];
$denominator_event = $analysis_formulas[ $analysis_type ]['denominator'];
$numerator = isset( $stats['subjects_totals'][ $subject_log_id ][ $numerator_event ] ) ? $stats['subjects_totals'][ $subject_log_id ][ $numerator_event ] : 0;
$denominator = isset( $stats['subjects_totals'][ $subject_log_id ][ $denominator_event ] ) ? $stats['subjects_totals'][ $subject_log_id ][ $denominator_event ] : 0;
$analysis = $denominator === 0 ? 0 : floatval( number_format( ( $numerator / $denominator ) * 100, 2 ) );
if ( $analysis_formulas[ $analysis_type ]['inverse'] && 0 !== $numerator && 0 !== $denominator_event ) {
$analysis = 100 - $analysis;
$stats['subjects_totals'][ $subject_log_id ][ $analysis_type ] = $analysis;
// Run analysis for each log date
foreach ( $date_list as $log_date ) {
// Run analysis per analysis type
foreach ( $analysis_types as $analysis_type ) {
$numerator_event = $analysis_formulas[ $analysis_type ]['numerator'];
$denominator_event = $analysis_formulas[ $analysis_type ]['denominator'];
$numerator = isset( $stats['subjects_logs'][ $subject_log_id ][ $numerator_event ][ $log_date ] ) ? intval( $stats['subjects_logs'][ $subject_log_id ][ $numerator_event ][ $log_date ] ) : 0;
$denominator = isset( $stats['subjects_logs'][ $subject_log_id ][ $denominator_event ][ $log_date ] ) ? intval( $stats['subjects_logs'][ $subject_log_id ][ $denominator_event ][ $log_date ] ) : 0;
$analysis = $denominator === 0 ? 0 : floatval( number_format( ( $numerator / $denominator ) * 100, 2 ) );
if ( $analysis_formulas[ $analysis_type ]['inverse'] ) {
$analysis = 100 - $analysis;
$stats['subjects_analysis'][ $subject_log_id ][ $analysis_type ][ $log_date ] = $analysis;
// Push total events data
foreach ( $event_types as $event_type ) {
$stats['events_totals'][ $event_type ] = array_sum( wp_list_pluck( $stats['subjects_totals'], $event_type ) );
foreach ( $analysis_types as $analysis_type ) {
$analysis_data = wp_list_pluck( $stats['subjects_totals'], $analysis_type );
$analysis_count = count( $analysis_data );
$stats['events_totals'][ $analysis_type ] = floatval( number_format( array_sum( $analysis_data ) / $analysis_count, 2 ) );
$subjects_ranks = wp_list_pluck( $stats['subjects_totals'], $rank_metrics );
$subjects_ranks_index = 0;
// Sort from high to low, mantain keys
arsort( $subjects_ranks );
foreach ( $subjects_ranks as $subject_rank_id => $subject_rank_value ) {
$is_empty_rank_value = 0 === $subject_rank_value;
$has_subject_rank_color = isset( $subject_rank_colors[ $subjects_ranks_index ] );
// If the rank value (derived from engagement) is empty, display default subject color
if ( $is_empty_rank_value ) {
$stats['subjects_totals'][ $subject_rank_id ]['color'] = '#F3CB57';
$stats['subjects_totals'][ $subject_rank_id ]['color'] = $has_subject_rank_color ? $subject_rank_colors[ $subjects_ranks_index ] : '#7E0000';
set_transient( 'et_pb_ab_' . $post_id . '_stats_' . $duration, $stats, DAY_IN_SECONDS );
// remove the cache if no logs found
delete_transient( 'et_pb_ab_' . $post_id . '_stats_' . $duration );
* Outputs get data stats duration
function et_pb_ab_get_stats_data_duration() {
return apply_filters( 'et_pb_ab_get_stats_data_duration', array(
* Get list of AB Testing event type
* @return array of event types
function et_pb_ab_get_event_types() {
return apply_filters( 'et_pb_ab_get_event_types', array(
* Get min and max timestamp from returned MySQL query
* @param array MySQL returned value. Expected to be array( array ( 'date' => 'YYYY-MM-DD' ) ) format
* @return array using min and max key
function et_pb_ab_get_min_max_timestamp( $query_result, $interval = 'day' ) {
// Get all available dates from logs
$dates = array_unique( wp_list_pluck( $query_result, 'date' ) );
// Sort low-to-high and reset array keys
// Get min and max dates from logs
$max_date = $dates[ ( count( $dates ) - 1 ) ];
$output['min'] = strtotime( substr( $min_date, 0, 4 ) . 'W' . substr( $min_date, 4, 2 ) );
$output['max'] = strtotime( substr( $max_date, 0, 4 ) . 'W' . substr( $max_date, 4, 2 ) );
$output['min'] = strtotime( $min_date );
$output['max'] = strtotime( $max_date );
* Get all days between min and max dates from logs
* @param int start date timestamp
* @param int end date timestamp
* @param string day|week interval of rage
function et_pb_ab_get_date_range( $min_date_timestamp, $max_date_timestamp, $interval = 'day' ) {
$day_timestamp = $min_date_timestamp;
$time_interval = '+1 week';
$date_format = 'Y-m-d H:i';
$time_interval = '+1 hour';
$time_interval = '+1 day';
while ( $day_timestamp <= $max_date_timestamp ) {
$full_dates[] = date( $date_format, $day_timestamp );
$day_timestamp = strtotime( $time_interval, $day_timestamp );
* Get list of Split analysis types
* @return array analysis types
function et_pb_ab_get_analysis_types() {
return apply_filters( 'et_pb_ab_get_analysis_types', array(
* Get numerator and denominator of various stats types
* @return array stats' data type formula
function et_pb_ab_get_analysis_formulas() {
return apply_filters( 'et_pb_ab_get_analysis_formulas', array(
'numerator' => 'click_goal',
'denominator' => 'view_page',
'numerator' => 'read_goal',
'denominator' => 'view_page',
'numerator' => 'read_page',
'denominator' => 'view_page',
'numerator' => 'read_goal',
'denominator' => 'view_goal',
'numerator' => 'con_goal',
'denominator' => 'view_page',
'shortcode_conversions' => array(
'numerator' => 'con_short',
'denominator' => 'view_page',
* List modules' slug which has conversions support
* @return array slugs of modules which have conversions support
function et_pb_ab_get_modules_have_conversions() {
return apply_filters( 'et_pb_ab_get_modules_have_conversions', array(
* Check whether AB Testing active on current page
* @since 4.0 Added the $post_id parameter.
* @param integer $post_id
function et_is_ab_testing_active( $post_id = 0 ) {
$post_id = $post_id > 0 ? $post_id : get_the_ID();
$post_id = apply_filters( 'et_is_ab_testing_active_post_id', $post_id );
$ab_testing_status = 'on' === get_post_meta( $post_id, '_et_pb_use_ab_testing', true );
$fb_enabled = function_exists( 'et_fb_enabled' ) ? et_fb_enabled() : false;
if ( ! $ab_testing_status && $fb_enabled && 'publish' !== get_post_status() ) {
$ab_testing_status = 'on' === get_post_meta( $post_id, '_et_pb_use_ab_testing_draft', true );
return $ab_testing_status;
* Check whether AB Testing has report
function et_pb_ab_has_report( $post_id ) {
if ( ! et_is_ab_testing_active() ) {
$wpdb->et_divi_ab_testing_stats = $wpdb->prefix . 'et_divi_ab_testing_stats';
$result = $wpdb->get_row( $wpdb->prepare(
"SELECT * FROM `{$wpdb->et_divi_ab_testing_stats}` WHERE test_id = %d",
unset( $wpdb->et_divi_ab_testing_stats );
return apply_filters( 'et_pb_ab_has_report', $result, $post_id );
* Check the status of the ab db version
function et_pb_db_status_up_to_date() {
return ( $ab_db_settings = get_option( 'et_pb_ab_test_settings' ) ) && version_compare( $ab_db_settings['db_version'], ET_PB_AB_DB_VERSION, '>=' );
* Create AB Testing table needed for AB Testing feature
function et_pb_create_ab_tables() {
if ( isset( $_POST['et_pb_ab_nonce'] ) && ! wp_verify_nonce( $_POST['et_pb_ab_nonce'], 'ab_testing_builder_nonce' ) ) {
// Verify user permission
if ( ! current_user_can( 'edit_posts' ) || ! et_pb_is_allowed( 'ab_testing' ) ) {
// Verify update is needed
if ( et_pb_db_status_up_to_date() ) {