oont-contents/plugins/google-site-kit/includes/Modules/Analytics_4/Report/Response.php
2025-02-08 15:10:23 +01:00

230 lines
7.7 KiB
PHP

<?php
/**
* Class Google\Site_Kit\Modules\Analytics_4\Report\Response
*
* @package Google\Site_Kit\Modules\Analytics_4\Report
* @copyright 2023 Google LLC
* @license https://www.apache.org/licenses/LICENSE-2.0 Apache License 2.0
* @link https://sitekit.withgoogle.com
*/
namespace Google\Site_Kit\Modules\Analytics_4\Report;
use Google\Site_Kit\Core\REST_API\Data_Request;
use Google\Site_Kit\Modules\Analytics_4\Report;
use Google\Site_Kit_Dependencies\Google\Service\AnalyticsData\DateRange as Google_Service_AnalyticsData_DateRange;
use Google\Site_Kit_Dependencies\Google\Service\AnalyticsData\Row as Google_Service_AnalyticsData_Row;
use Google\Site_Kit_Dependencies\Google\Service\AnalyticsData\RunReportResponse as Google_Service_AnalyticsData_RunReportResponse;
/**
* Class for Analytics 4 report responses.
*
* @since 1.99.0
* @access private
* @ignore
*/
class Response extends Report {
use Row_Trait;
/**
* Parses the report response, and pads the report data with zero-data rows where rows are missing. This only applies for reports which request a single `date` dimension.
*
* @since 1.99.0
*
* @param Data_Request $data Data request object.
* @param Google_Service_AnalyticsData_RunReportResponse $response Request response.
* @return mixed Parsed response data on success, or WP_Error on failure.
*/
public function parse_response( Data_Request $data, $response ) {
// Return early if the response is not of the expected type.
if ( ! $response instanceof Google_Service_AnalyticsData_RunReportResponse ) {
return $response;
}
// Get report dimensions and return early if there is either more than one dimension or
// the only dimension is not "date".
$dimensions = $this->parse_dimensions( $data );
if ( count( $dimensions ) !== 1 || $dimensions[0]->getName() !== 'date' ) {
return $response;
}
// Get date ranges and return early if there are no date ranges for this report.
$date_ranges = $this->get_sorted_dateranges( $data );
if ( empty( $date_ranges ) ) {
return $response;
}
// Get all available dates in the report.
$existing_rows = array();
foreach ( $response->getRows() as $row ) {
$dimension_values = $row->getDimensionValues();
$range = 'date_range_0';
if ( count( $dimension_values ) > 1 ) {
// Considering this code will only be run when we are requesting a single dimension, `date`,
// the implication is that the row will _only_ have an additional dimension when multiple
// date ranges are requested.
//
// In this scenario, the dimension at index 1 will have a value of `date_range_{i}`, where
// `i` is the zero-based index of the date range.
$range = $dimension_values[1]->getValue();
}
$range = str_replace( 'date_range_', '', $range );
$date = $dimension_values[0]->getValue();
$key = self::get_response_row_key( $date, is_numeric( $range ) ? $range : false );
$existing_rows[ $key ] = $row;
}
$metric_headers = $response->getMetricHeaders();
$ranges_count = count( $date_ranges );
$multiple_ranges = $ranges_count > 1;
$rows = array();
// Add rows for the current date for each date range.
self::iterate_date_ranges(
$date_ranges,
function ( $date ) use ( &$rows, $existing_rows, $ranges_count, $metric_headers, $multiple_ranges ) {
for ( $i = 0; $i < $ranges_count; $i++ ) {
// Copy the existing row if it is available, otherwise create a new zero-value row.
$key = self::get_response_row_key( $date, $i );
$rows[ $key ] = isset( $existing_rows[ $key ] )
? $existing_rows[ $key ]
: $this->create_report_row( $metric_headers, $date, $multiple_ranges ? $i : false );
}
}
);
// If we have the same number of rows as in the response at the moment, then
// we can return the response without setting the new rows back into the response.
$new_rows_count = count( $rows );
if ( $new_rows_count <= $response->getRowCount() ) {
return $response;
}
// If we have multiple date ranges, we need to sort rows to have them in
// the correct order.
if ( $multiple_ranges ) {
$rows = self::sort_response_rows( $rows, $date_ranges );
}
// Set updated rows back to the response object.
$response->setRows( array_values( $rows ) );
$response->setRowCount( $new_rows_count );
return $response;
}
/**
* Gets the response row key composed from the date and the date range index values.
*
* @since 1.99.0
*
* @param string $date The date of the row to return key for.
* @param int|bool $date_range_index The date range index, or FALSE if no index is available.
* @return string The row key.
*/
protected static function get_response_row_key( $date, $date_range_index ) {
return "{$date}_{$date_range_index}";
}
/**
* Returns sorted and filtered date ranges received in the request params. All corrupted date ranges
* are ignored and not included in the returning list.
*
* @since 1.99.0
*
* @param Data_Request $data Data request object.
* @return Google_Service_AnalyticsData_DateRange[] An array of AnalyticsData DateRange objects.
*/
protected function get_sorted_dateranges( Data_Request $data ) {
$date_ranges = $this->parse_dateranges( $data );
if ( empty( $date_ranges ) ) {
return $date_ranges;
}
// Filter out all corrupted date ranges.
$date_ranges = array_filter(
$date_ranges,
function ( $range ) {
$start = strtotime( $range->getStartDate() );
$end = strtotime( $range->getEndDate() );
return ! empty( $start ) && ! empty( $end );
}
);
// Sort date ranges preserving keys to have the oldest date range at the beginning and
// the latest date range at the end.
uasort(
$date_ranges,
function ( $a, $b ) {
$a_start = strtotime( $a->getStartDate() );
$b_start = strtotime( $b->getStartDate() );
return $a_start - $b_start;
}
);
return $date_ranges;
}
/**
* Sorts response rows using the algorithm similar to the one that Analytics 4 uses internally
* and returns sorted rows.
*
* @since 1.99.0
*
* @param Google_Service_AnalyticsData_Row[] $rows The current report rows.
* @param Google_Service_AnalyticsData_DateRange[] $date_ranges The report date ranges.
* @return Google_Service_AnalyticsData_Row[] Sorted rows.
*/
protected static function sort_response_rows( $rows, $date_ranges ) {
$sorted_rows = array();
$ranges_count = count( $date_ranges );
self::iterate_date_ranges(
$date_ranges,
function ( $date, $range_index ) use ( &$sorted_rows, $ranges_count, $rows ) {
// First take the main date range row.
$key = self::get_response_row_key( $date, $range_index );
$sorted_rows[ $key ] = $rows[ $key ];
// Then take all remaining rows.
for ( $i = 0; $i < $ranges_count; $i++ ) {
if ( $i !== $range_index ) {
$key = self::get_response_row_key( $date, $i );
$sorted_rows[ $key ] = $rows[ $key ];
}
}
}
);
return $sorted_rows;
}
/**
* Iterates over the date ranges and calls callback for each date in each range.
*
* @since 1.99.0
*
* @param Google_Service_AnalyticsData_DateRange[] $date_ranges The report date ranges.
* @param callable $callback The callback to execute for each date.
*/
protected static function iterate_date_ranges( $date_ranges, $callback ) {
foreach ( $date_ranges as $date_range_index => $date_range ) {
$now = strtotime( $date_range->getStartDate() );
$end = strtotime( $date_range->getEndDate() );
do {
call_user_func(
$callback,
gmdate( 'Ymd', $now ),
$date_range_index
);
$now += DAY_IN_SECONDS;
} while ( $now <= $end );
}
}
}