nonce = $this->nonce( basename( __FILE__ ), 'reports' );
}
/**
* Create hooks.
*
* @return void
*/
public function hooks() {
add_action( 'admin_init', array( $this, 'admin_init' ) );
foreach ( $this->get_action_keys() as $key ) {
$key_underscore = self::_key( $key );
add_action( 'wp_ajax_' . $this->action_string( $key ), array( $this, 'action_' . $key_underscore ) );
}
}
/**
* Fires on admin_init. Parse input if available.
*
* @return void
*/
public function admin_init() {
$this->parse_input();
}
/**
* Enqueue scripts for the reports page.
*
* @return void
*/
public function enqueue() {
$this->build_report_data();
wp_enqueue_script( 'jquery' );
wp_enqueue_script( 'jquery-ui-core' );
wp_enqueue_script( 'jquery-ui-datepicker' );
wp_enqueue_script( 'jquery-ui-autocomplete' );
wp_enqueue_script( 'jquery-ui-tabs' );
$this->enqueue_jquery_ui_styles();
$handle = Util::ns( 'reports' );
wp_register_script(
$handle,
AdCommander::assets_url() . 'js/reports.js',
array(
'jquery',
'jquery-ui-core',
'jquery-ui-datepicker',
'jquery-ui-autocomplete',
'jquery-ui-tabs',
),
AdCommander::version(),
array( 'in_footer' => true )
);
wp_enqueue_script( $handle );
Util::enqueue_script_data(
$handle,
array(
'ajaxurl' => admin_url( 'admin-ajax.php' ),
'actions' => $this->get_ajax_actions(),
'reportData' => $this->current_data_bydate,
'adData' => $this->current_data_byad,
'groupData' => $this->current_data_bygroup,
'campaignData' => $this->current_data_bycampaign,
'advertiserData' => $this->current_data_byadvertiser,
'tableByDateCols' => $this->table_by_date_columns(),
'tableByAdCols' => $this->table_by_ad_columns(),
'tableByGroupCols' => $this->table_by_group_columns(),
'tableByCampaignCols' => $this->table_by_campaign_columns(),
'tableByAdvertiserCols' => $this->table_by_advertiser_columns(),
'chartType' => $this->get_chart_type(),
)
);
}
/**
* Get necessary action keys, which will be used to create wp_ajax hooks.
*
* @return array
*/
private function get_action_keys() {
return array(
'get-ads-for-filter',
'export-csv',
);
}
/**
* Creates an array of all of the necessary actions.
*
* @return array
*/
private function get_ajax_actions() {
$actions = array();
foreach ( $this->get_action_keys() as $key ) {
$actions[ self::_key( $key ) ] = array(
'action' => $this->action_string( $key ),
'security' => wp_create_nonce( $this->nonce_string( $key ) ),
);
}
return $actions;
}
/**
* Create HTML for the title of the page.
*
* @param string $title The title of the page.
*
* @return void
*/
private function title( $title ) {
?>
sf()->title( $title ); ?>
sf()->wo_forms->label( 'chart_type', __( 'Chart:', 'ad-commander' ) );
$this->sf()->wo_forms->inputgroup( 'chart_type', $this->allowed_chart_types(), $this->get_chart_type(), 'radio', array( 'id' => 'adcmdr_chart_type' ) );
?>
form_start();
$this->form();
$this->start_wrap();
$this->title( __( 'Reports', 'ad-commander' ) );
$this->chart();
$this->form_end();
$this->end_wrap();
$this->start_wrap();
$this->tabs_start();
$this->tab_nav();
$this->table_by_date();
$this->table_by_ad();
$this->table_by_group();
$this->table_by_campaign();
$this->table_by_advertiser();
$this->tabs_end();
$this->end_wrap();
}
private function tabs_start() {
?>
is_pro_loaded() ? ProBridge::instance()->pro_label( true ) : '';
?>
get_group_by() === 'week' ) : ?>
is_pro_loaded() ? ProBridge::instance()->pro_label( true ) : '';
?>
is_pro_loaded() ? ProBridge::instance()->pro_label( true ) : '';
?>
is_pro_loaded() ? ProBridge::instance()->pro_label( true ) : '';
?>
is_pro_loaded() ? ProBridge::instance()->pro_label( true ) : '';
?>
( $this->get_group_by() === 'hour' ) ? __( 'Hour', 'ad-commander' ) : __( 'Date', 'ad-commander' ),
'impressions' => __( 'Impressions', 'ad-commander' ),
'clicks' => __( 'Clicks', 'ad-commander' ),
'ctr' => __( 'CTR', 'ad-commander' ),
);
}
/**
* Create the columns for the by-ad table.
*/
private function table_by_ad_columns() {
return array(
'ad' => __( 'Ad', 'ad-commander' ),
'impressions' => __( 'Impressions', 'ad-commander' ),
'clicks' => __( 'Clicks', 'ad-commander' ),
'ctr' => __( 'CTR', 'ad-commander' ),
);
}
/**
* Create the columns for the by-group table.
*/
private function table_by_group_columns() {
return array(
'group' => __( 'Group', 'ad-commander' ),
'impressions' => __( 'Impressions', 'ad-commander' ),
'clicks' => __( 'Clicks', 'ad-commander' ),
'ctr' => __( 'CTR', 'ad-commander' ),
);
}
/**
* Create the columns for the by-campaign table.
*/
private function table_by_campaign_columns() {
return array(
'campaign' => __( 'Campaign', 'ad-commander' ),
'impressions' => __( 'Impressions', 'ad-commander' ),
'clicks' => __( 'Clicks', 'ad-commander' ),
'ctr' => __( 'CTR', 'ad-commander' ),
);
}
/**
* Create the columns for the by-advertiser table.
*/
private function table_by_advertiser_columns() {
return array(
'advertiser' => __( 'Advertiser', 'ad-commander' ),
'impressions' => __( 'Impressions', 'ad-commander' ),
'clicks' => __( 'Clicks', 'ad-commander' ),
'ctr' => __( 'CTR', 'ad-commander' ),
);
}
protected function sanitize_all_input( $source = array() ) {
$input = array();
foreach ( array(
'period',
'group_by',
'chart_type',
'start_date',
'end_date',
'filter_by_ad_ids',
) as $key ) {
if ( ! isset( $source[ $key ] ) ) {
continue;
}
$value = ( is_array( $source[ $key ] ) ) ? array_map( 'sanitize_text_field', wp_unslash( $source[ $key ] ) ) : sanitize_text_field( wp_unslash( $source[ $key ] ) );
switch ( $key ) {
case 'start_date':
case 'end_date':
$type = 'date';
break;
case 'filter_by_ad_ids':
$type = 'ints';
break;
default:
$type = 'str';
break;
}
$value = $this->sanitize_by_type( $value, $type );
$input[ $key ] = $value;
}
return $input;
}
/**
* Parse the input for later use.
*/
protected function parse_input() {
if ( ! isset( $_REQUEST[ $this->nonce['name'] ] ) ) {
return;
}
if ( ! wp_verify_nonce( sanitize_key( $_REQUEST[ $this->nonce['name'] ] ), $this->nonce['action'] ) ) {
die( esc_html( __( 'Security check', 'ad-commander' ) ) );
}
$this->input = $this->sanitize_all_input( $_REQUEST );
}
/**
* Start of the form on the Reports page.
*
* @return void
*/
private function form_start() {
?>
date_display_format ) {
if ( $this->get_group_by() !== 'hour' ) {
$this->date_display_format = get_option( 'date_format' );
} else {
$this->date_display_format = get_option( 'time_format' );
}
}
return $this->date_display_format;
}
/**
* Alternate display format for dates.
*
* @param string $format The date format to modify.
*/
private function date_display_format_alt( $format ) {
return str_replace( array( 'F ', ', Y' ), array( 'M ', ', \'y' ), $format );
}
/**
* The allowed time periods and their related info.
*
* @param bool $detailed Whether to return a detailed array or not.
*
* @return array
*/
public static function allowed_periods( $detailed = true ) {
$periods = array(
'today' => array(
'text' => __( 'Today', 'ad-commander' ),
'modifier' => '',
),
'yesterday' => array(
'text' => __( 'Yesterday', 'ad-commander' ),
'modifier' => '',
),
'this_week' => array(
'text' => __( 'This Week', 'ad-commander' ),
'modifier' => 'this week',
),
'this_month' => array(
'text' => __( 'This Month', 'ad-commander' ),
'modifier' => 'first day of this month',
),
'this_year' => array(
'text' => __( 'This Year', 'ad-commander' ),
'modifier' => 'first day of January',
),
'last_week' => array(
'text' => __( 'Last Week', 'ad-commander' ),
'modifier' => 'last week',
),
'last_month' => array(
'text' => __( 'Last Month', 'ad-commander' ),
'modifier' => 'last month',
),
'last_year' => array(
'text' => __( 'Last Year', 'ad-commander' ),
'modifier' => 'last year',
),
'past_7_days' => array(
'text' => __( 'Past 7 days', 'ad-commander' ),
'modifier' => '-6 days',
),
'past_14_days' => array(
'text' => __( 'Past 14 days', 'ad-commander' ),
'modifier' => '-13 days',
),
'past_30_days' => array(
'text' => __( 'Past 30 days', 'ad-commander' ),
'modifier' => '-29 days',
),
'past_365_days' => array(
'text' => __( 'Past 365 days', 'ad-commander' ),
'modifier' => '-364 days',
),
'custom' => array(
'text' => __( 'Custom', 'ad-commander' ),
'modifier' => '',
),
);
if ( ! $detailed ) {
$periods_simple = array();
foreach ( $periods as $key => $value ) {
$periods_simple[ $key ] = $value['text'];
}
return $periods_simple;
}
return $periods;
}
/**
* The allowed group_by keys and their related info.
*
* @param bool $detailed Whether to return a detailed array or not.
*
* @return array
*/
public static function allowed_group_by( $detailed = true ) {
$periods = array(
'hour' => array(
'text' => __( 'Hour', 'ad-commander' ),
'modifier' => '+1 hour',
),
'day' => array(
'text' => __( 'Day', 'ad-commander' ),
'modifier' => '+1 day',
),
'week' => array(
'text' => __( 'Week', 'ad-commander' ),
'modifier' => '+1 week',
),
'month' => array(
'text' => __( 'Month', 'ad-commander' ),
'modifier' => '+1 month',
),
);
if ( ! $detailed ) {
$periods_simple = array();
foreach ( $periods as $key => $value ) {
$periods_simple[ $key ] = $value['text'];
}
return $periods_simple;
}
return $periods;
}
/**
* The allowed chart types.
*
* @return array
*/
private function allowed_chart_types() {
return array(
'line' => 'Line',
'bar' => 'Bar',
);
}
/**
* Returns a modifier string for a time period if one exists.
*
* @param string $period The period to find the modifier for.
*
* @return null|string
*/
private function datetime_modifier_string( $period ) {
$periods = self::allowed_periods();
return isset( $periods[ $period ]['modifier'] ) ? $periods[ $period ]['modifier'] : null;
}
/**
* Returns the ad IDs that we are filtering by, if any.
*
* @return array
*/
private function get_filtered_ad_ids() {
if ( isset( $this->input['filter_by_ad_ids'] ) ) {
return Util::arrayify( $this->input['filter_by_ad_ids'] );
}
return array();
}
/**
* Gets an array of ads based on selected filters.
*
* @return array
*/
private function get_filtered_ads() {
$ads = $this->get_ads_for_filter();
$filtered_ads = array();
foreach ( $ads as $ad ) {
if ( in_array( $ad['id'], $this->get_filtered_ad_ids() ) ) {
$filtered_ads[] = $ad;
}
}
return $filtered_ads;
}
/**
* Gets the current filtered period.
*
* @return string
*/
private function get_period() {
if ( isset( $this->input['period'] ) && in_array( $this->input['period'], array_keys( self::allowed_periods() ) ) ) {
return $this->input['period'];
}
return 'past_7_days';
}
/**
* Gets the current filtered group_by.
*
* @return string
*/
private function get_group_by() {
if ( isset( $this->input['group_by'] ) && in_array( $this->input['group_by'], array_keys( self::allowed_group_by() ) ) ) {
return $this->input['group_by'];
}
return 'day';
}
/**
* Gets the current chart type.
*
* @return string
*/
private function get_chart_type() {
if ( isset( $this->input['chart_type'] ) && in_array( $this->input['chart_type'], array_keys( $this->allowed_chart_types() ) ) ) {
return $this->input['chart_type'];
}
return 'line';
}
/**
* Gets the current filtered start date.
*
* @return string|null
*/
private function get_start_date() {
if ( isset( $this->input['start_date'] ) && $this->get_period() === 'custom' ) {
return $this->input['start_date'];
}
return null;
}
/**
* Gets the current filtered end date.
*
* @return string|null
*/
private function get_end_date() {
if ( isset( $this->input['end_date'] ) && $this->get_period() === 'custom' ) {
return $this->input['end_date'];
}
return null;
}
/**
* Gets the modifier string for a group_by string.
*
* @param string $group_by The period we're grouping by.
*
* @return string
*/
private function get_group_by_modifier( $group_by ) {
switch ( $group_by ) {
case 'month':
$modifier = 'first day of next month';
break;
case 'week':
$modifier = 'next ' . Util::first_day_of_the_week();
break;
case 'hour':
$modifier = 'next hour';
break;
case 'day':
default:
$modifier = 'next day';
break;
}
return $modifier;
}
/**
* Creates an array of dates from a start and an end date.
*
* @param array $start_end_dates Array containing two DateTime objects.
* @param string $group_by Determines the frequency of the range of dates.
* @param bool $include_end_date Whether to include the end date or not.
*
* @return array
*/
private function range_of_start_dates( $start_end_dates, $group_by = 'day', $include_end_date = true ) {
$start_date = clone $start_end_dates['start_date'];
$end_date = clone $start_end_dates['end_date'];
if ( $include_end_date ) {
$end_date = $end_date->modify( '+1 day' ); // So that $end_date is included in our DatePeriod
}
if ( $group_by === 'hour' ) {
$now = Util::datetime_wp_timezone( current_time( 'Y-m-d H:00:00' ) );
$now->modify( 'next hour' )->modify( '-1 second' );
if ( $end_date->getTimestamp() > $now->getTimestamp() ) {
$end_date = clone $now;
}
}
$modifier = $this->get_group_by_modifier( $group_by );
$periods = array();
$date = clone $start_date;
while ( $date->getTimestamp() < $end_date->getTimestamp() ) {
$periods[] = clone $date;
$date = $date->modify( $modifier );
}
$dates = array();
$format = $this->date_display_format();
$format_alt = $this->date_display_format_alt( $format );
foreach ( $periods as $key => $value ) {
$timestamp = $value->getTimestamp();
$value_formatted = $value->format( $format );
$value_formatted_alt = $value->format( $format_alt );
$dates[] = array(
'display_format' => $value_formatted,
'display_format_alt' => $value_formatted_alt,
'timestamp' => $timestamp,
'datetime' => $value,
);
}
return $dates;
}
/**
* Creates start and end dates for a given period.
*
* @param string $period The period for which we are getting start and end dates.
*
* @return array
*/
private function start_end_dates( $period ) {
if ( $period === 'custom' ) {
$start_date_custom = $this->get_start_date();
$end_date_custom = $this->get_end_date();
if ( ! $start_date_custom || ! $end_date_custom ) {
return false;
}
$start_date = Util::datetime_wp_timezone( $start_date_custom )->modify( 'today' );
$end_date = Util::datetime_wp_timezone( $end_date_custom )->modify( 'today' );
} else {
$now = current_time( 'Y-m-d H:i:s' );
$now = Util::datetime_wp_timezone( $now )->modify( 'today' );
$end_date = clone $now;
if ( $period === 'yesterday' ) {
$end_date = $end_date->modify( '-1 day' );
}
$start_date = clone $end_date;
}
$modifier = $this->datetime_modifier_string( $period );
if ( $modifier ) {
if ( $modifier === 'this week' ) {
$start_week = Util::first_day_of_the_week();
$today = clone $now;
/**
* If the week starts today, end date and start date are the same.
*/
if ( $today->format( 'l' ) !== $start_week ) {
$start_date->modify( 'last ' . $start_week );
}
} elseif ( $modifier === 'last week' ) {
$start_week = Util::first_day_of_the_week();
if ( $end_date->format( 'l' ) === $start_week ) {
$end_date->modify( '-1 day' );
} else {
$end_date->modify( 'last ' . $start_week )->modify( '-1 day' );
}
$end_date->modify( 'today' );
$start_date = clone $end_date;
$start_date->modify( '-6 days' );
} elseif ( $modifier === 'last month' ) {
$end_date->modify( 'last day of last month' )->modify( 'today' );
$start_date = clone $end_date;
$start_date->modify( 'first day of this month' );
} elseif ( $modifier === 'last year' ) {
$end_date->modify( 'last year December 31' )->modify( 'today' );
$start_date = clone $end_date;
$start_date->modify( 'this year January 1' );
} else {
$start_date = $start_date->modify( $modifier );
}
}
$start_date->modify( 'today' );
/**
* Return set of dates
*/
return array(
'start_date' => $start_date,
'end_date' => $end_date,
);
}
/**
* The name of the transient that stores our filtered ads.
*
* @return string
*/
public static function filter_ads_transient() {
return Util::ns( 'report_filter_ads', '_' );
}
/**
* Store the available filter ads in a transient so we don't have to query them every time.
*
* @return array
*/
private function get_ads_for_filter() {
$ads = get_transient( self::filter_ads_transient() );
if ( $ads === false ) {
$available_ads = Query::ads( 'post_title', 'asc', Util::any_post_status(), array(), array() );
$ads = array();
if ( ! empty( $available_ads ) ) {
foreach ( $available_ads as $ad ) {
$ads[] = array(
'id' => $ad->ID,
'title' => $ad->post_title,
);
}
}
set_transient( self::filter_ads_transient(), $ads, MINUTE_IN_SECONDS );
}
return $ads;
}
/**
* Wp_ajax hook. Gets all of the ads for use in the filter.
*
* @return void
*/
public function action_get_ads_for_filter() {
$action = 'get-ads-for-filter';
check_ajax_referer( $this->nonce_string( $action ), 'security' );
if ( ! current_user_can( AdCommander::capability() ) ) {
wp_die();
}
wp_send_json_success(
array(
'action' => $action,
'ads' => $this->get_ads_for_filter(),
)
);
wp_die();
}
/**
* Wp_ajax hook. Gets all of the ads for use in the filter.
*
* @return void
*/
public function action_export_csv() {
$action = 'export-csv';
check_ajax_referer( $this->nonce_string( $action ), 'security' );
if ( ! current_user_can( AdCommander::capability() ) ) {
wp_die();
}
if ( ProBridge::instance()->pro_feature_check( 'exportreports' ) ) {
AdminExport::instance()->do_export();
}
wp_die();
}
/**
* Get the total stats for a given day or days.
*
* @param array $ad_ids The ad IDs to total stats for.
* @param string|int $days Either 'all' or the number of days to look back when totaling.
* @param string $type The type of stat to count.
*
* @return int
*/
public function ad_stats( $ad_ids, $days = 'all', $type = 'impressions' ) {
if ( $days === 'all' ) {
return TrackingLocal::total_stats( $ad_ids, $type, null, null );
}
$end_date = Util::datetime_wp_timezone( current_time( 'Y-m-d H:00:00' ) );
$end_date->modify( 'next hour' )->modify( '-1 second' );
$days = ( intval( $days ) - 1 );
if ( $days < 0 ) {
$days = 0;
}
$start_date = clone $end_date;
$start_date->modify( '-' . $days . ' days' )->modify( 'today' );
return TrackingLocal::total_stats( $ad_ids, $type, $start_date->getTimestamp(), $end_date->getTimestamp() );
}
/**
* Builds the data for display on the report page.
*
* @return void
*/
protected function build_report_data( $ignore_notices = false ) {
$this->current_data_bydate = array();
$this->current_data_byad = array();
$this->current_data_bygroup = array();
$this->current_data_bycampaign = array();
$this->current_data_byadvertiser = array();
$notice = null;
$period = $this->get_period();
$start_end_dates = $this->start_end_dates( $period );
if ( ! isset( $start_end_dates['start_date'] ) || ! isset( $start_end_dates['end_date'] ) || ! $start_end_dates['start_date'] || ! $start_end_dates['end_date'] ) {
$notice = __( 'There was an error processing your start and end dates.', 'ad-commander' );
}
if ( $start_end_dates['start_date'] > $start_end_dates['end_date'] && ! $ignore_notices ) {
$notice = __( 'The selected start date is before the end date.', 'ad-commander' );
}
if ( $notice && ! $ignore_notices ) {
add_action(
'admin_notices',
function () use ( $notice ) {
?>
get_group_by();
$start_date_range = $this->range_of_start_dates( $start_end_dates, $group_by );
$i = 0;
$parsed_dates = array();
foreach ( $start_date_range as $date ) {
$start_timestamp = $date['timestamp'];
if ( isset( $start_date_range[ $i + 1 ]['datetime'] ) ) {
$end_datetime = clone $start_date_range[ $i + 1 ]['datetime'];
$end_datetime->modify( '-1 second' );
} else {
$end_datetime = clone $start_end_dates['end_date'];
$end_datetime->modify( 'tomorrow' )->modify( '-1 second' );
}
$impressions = TrackingLocal::total_impressions( $this->get_filtered_ad_ids(), $start_timestamp, $end_datetime->getTimestamp(), false );
$clicks = TrackingLocal::total_clicks( $this->get_filtered_ad_ids(), $start_timestamp, $end_datetime->getTimestamp(), false );
$display_formatted = $date['display_format'];
if ( $group_by !== 'hour' && $date['datetime']->format( $this->date_display_format() ) !== $end_datetime->format( $this->date_display_format() ) ) {
$display_formatted = $date['datetime']->format( $this->date_display_format() ) . ' – ' . $end_datetime->format( $this->date_display_format() );
}
$parsed_dates[] = array(
'date' => $display_formatted, // . ' - ' . $start_timestamp . ' - ' . $end_datetime->getTimestamp(), // For testing
'date_alt' => $date['display_format_alt'],
'timestamp' => $date['timestamp'],
'impressions' => $impressions,
'clicks' => $clicks,
'ctr' => Util::ctr( $clicks, $impressions ),
);
++$i;
}
$this->current_data_bydate = $parsed_dates;
$parsed_ads = array();
$ad_groups = array();
$ad_campaigns = array();
$ad_advertisers = array();
$ad_ids = TrackingLocal::ads_with_stats( $start_date_range[0]['timestamp'], $end_datetime->getTimestamp(), $this->get_filtered_ad_ids() );
if ( ! empty( $ad_ids ) ) {
$ads = Query::ads( 'post_title', 'asc', Util::any_post_status(), array(), array(), $ad_ids );
foreach ( $ad_ids as $ad_id ) {
$impressions = TrackingLocal::total_impressions( $ad_id, $start_date_range[0]['timestamp'], $end_datetime->getTimestamp(), false );
$clicks = TrackingLocal::total_clicks( $ad_id, $start_date_range[0]['timestamp'], $end_datetime->getTimestamp(), false );
$this_ad = new \stdClass();
foreach ( $ads as $ad ) {
if ( $ad->ID === $ad_id ) {
$this_ad = $ad;
break;
}
}
$parsed_ads[] = array(
'ad_id' => $ad_id,
/* translators: %d: Ad id */
'ad' => isset( $this_ad->post_title ) ? $this_ad->post_title : sprintf( __( 'Ad not found (ID: %d)', 'ad-commander' ), $ad_id ),
'edit_link' => isset( $this_ad->post_title ) ? Admin::edit_ad_post_url( $this_ad->ID, $this_ad->post_status ) : null,
'impressions' => $impressions,
'clicks' => $clicks,
'ctr' => Util::ctr( $clicks, $impressions ),
);
if ( isset( $this_ad->ID ) && $this_ad->ID ) {
$groups = wp_get_post_terms( $this_ad->ID, AdCommander::tax_group(), array( 'fields' => 'all' ) );
if ( $groups && ! is_wp_error( $groups ) && ! empty( $groups ) ) {
$ad_groups[ $ad_id ] = $groups;
}
$campaigns = wp_get_post_terms( $this_ad->ID, AdCommander::tax_campaign(), array( 'fields' => 'all' ) );
if ( $campaigns && ! is_wp_error( $campaigns ) && ! empty( $campaigns ) ) {
$ad_campaigns[ $ad_id ] = $campaigns;
}
$advertisers = wp_get_post_terms( $this_ad->ID, AdCommander::tax_advertiser(), array( 'fields' => 'all' ) );
if ( $advertisers && ! is_wp_error( $advertisers ) && ! empty( $advertisers ) ) {
$ad_advertisers[ $ad_id ] = $advertisers;
}
}
}
}
if ( ! empty( $parsed_ads ) ) {
usort(
$parsed_ads,
function ( $a, $b ) {
if ( $a['edit_link'] === null ) {
return PHP_INT_MAX;
}
return $a['ad'] <=> $b['ad'];
}
);
}
$this->current_data_byad = $parsed_ads;
$parsed_groups = array();
if ( ! empty( $ad_groups ) ) {
foreach ( $ad_groups as $ad_id => $groups ) {
foreach ( $groups as $group ) {
if ( ! isset( $parsed_groups[ $group->term_id ] ) ) {
$parsed_groups[ $group->term_id ] = array(
'group_id' => $group->term_id,
'group' => $group->name,
'impressions' => 0,
'clicks' => 0,
'ctr' => 0,
'edit_link' => Admin::admin_group_term_url( $group->term_id ),
);
}
foreach ( $parsed_ads as $ad ) {
if ( $ad['ad_id'] === $ad_id ) {
$parsed_groups[ $group->term_id ]['impressions'] += $ad['impressions'];
$parsed_groups[ $group->term_id ]['clicks'] += $ad['clicks'];
break;
}
}
}
}
foreach ( $parsed_groups as $group_id => $group_data ) {
$parsed_groups[ $group_id ]['ctr'] = Util::ctr( $group_data['clicks'], $group_data['impressions'] );
}
}
$this->current_data_bygroup = array_values( $parsed_groups );
$parsed_campaigns = array();
if ( ! empty( $ad_campaigns ) ) {
foreach ( $ad_campaigns as $ad_id => $campaigns ) {
foreach ( $campaigns as $campaign ) {
if ( ! isset( $parsed_campaigns[ $campaign->term_id ] ) ) {
$parsed_campaigns[ $campaign->term_id ] = array(
'campaign_id' => $campaign->term_id,
'campaign' => $campaign->name,
'impressions' => 0,
'clicks' => 0,
'ctr' => 0,
'edit_link' => Admin::admin_campaign_term_url( $campaign->term_id ),
);
}
foreach ( $parsed_ads as $ad ) {
if ( $ad['ad_id'] === $ad_id ) {
$parsed_campaigns[ $campaign->term_id ]['impressions'] += $ad['impressions'];
$parsed_campaigns[ $campaign->term_id ]['clicks'] += $ad['clicks'];
break;
}
}
}
}
foreach ( $parsed_campaigns as $campaign_id => $campaign_data ) {
$parsed_campaigns[ $campaign_id ]['ctr'] = Util::ctr( $campaign_data['clicks'], $campaign_data['impressions'] );
}
}
$this->current_data_bycampaign = array_values( $parsed_campaigns );
$parsed_advertisers = array();
if ( ! empty( $ad_advertisers ) ) {
foreach ( $ad_advertisers as $ad_id => $advertisers ) {
foreach ( $advertisers as $advertiser ) {
if ( ! isset( $parsed_advertisers[ $advertiser->term_id ] ) ) {
$parsed_advertisers[ $advertiser->term_id ] = array(
'advertiser_id' => $advertiser->term_id,
'advertiser' => $advertiser->name,
'impressions' => 0,
'clicks' => 0,
'ctr' => 0,
'edit_link' => Admin::admin_advertiser_term_url( $advertiser->term_id ),
);
}
foreach ( $parsed_ads as $ad ) {
if ( $ad['ad_id'] === $ad_id ) {
$parsed_advertisers[ $advertiser->term_id ]['impressions'] += $ad['impressions'];
$parsed_advertisers[ $advertiser->term_id ]['clicks'] += $ad['clicks'];
break;
}
}
}
}
foreach ( $parsed_advertisers as $advertiser_id => $advertiser_data ) {
$parsed_advertisers[ $advertiser_id ]['ctr'] = Util::ctr( $advertiser_data['clicks'], $advertiser_data['impressions'] );
}
}
$this->current_data_byadvertiser = array_values( $parsed_advertisers );
}
/**
* Build report data from provided input and return the results.
* Intended for programmatic use (e.g., abilities API).
*
* @param array $input Associative array with keys: period, group_by, start_date, end_date, filter_by_ad_ids.
*
* @return array|WP_Error
*/
public function build_report( $input = array() ) {
// Convert Y-m-d dates to the F j, Y format expected by sanitize_all_input.
foreach ( array( 'start_date', 'end_date' ) as $date_key ) {
if ( isset( $input[ $date_key ] ) ) {
$date = \DateTime::createFromFormat( 'Y-m-d', $input[ $date_key ] );
if ( $date && $date->format( 'Y-m-d' ) === $input[ $date_key ] ) {
$input[ $date_key ] = $date->format( 'F j, Y' );
}
}
}
$this->input = $this->sanitize_all_input( $input );
$this->build_report_data( true );
$cast_ctr = function ( $item ) {
$item['ctr'] = (float) $item['ctr'];
return $item;
};
return array(
'by_date' => array_map( $cast_ctr, $this->current_data_bydate ),
'by_ad' => array_map( $cast_ctr, $this->current_data_byad ),
'by_group' => array_map( $cast_ctr, $this->current_data_bygroup ),
'by_campaign' => array_map( $cast_ctr, $this->current_data_bycampaign ),
'by_advertiser' => array_map( $cast_ctr, $this->current_data_byadvertiser ),
);
}
}