<?php
declare( strict_types=1 );
namespace Automattic\WooCommerce\GoogleListingsAndAds\API\Google;
use Automattic\WooCommerce\GoogleListingsAndAds\API\Google\Query\AdsCampaignReportQuery;
use Automattic\WooCommerce\GoogleListingsAndAds\API\Google\Query\MerchantFreeListingReportQuery;
use Automattic\WooCommerce\GoogleListingsAndAds\Google\Ads\GoogleAdsClient;
use Automattic\WooCommerce\GoogleListingsAndAds\Options\OptionsAwareInterface;
use Automattic\WooCommerce\GoogleListingsAndAds\Options\OptionsAwareTrait;
use Automattic\WooCommerce\GoogleListingsAndAds\Options\TransientsInterface;
use Automattic\WooCommerce\GoogleListingsAndAds\Proxies\WP;
use DateTime;
use Exception;
use Google\Ads\GoogleAds\V11\Services\GoogleAdsRow;
use Google\ApiCore\PagedListResponse;
use Google\Service\ShoppingContent;
use Google\Service\ShoppingContent\SearchResponse;
/**
* Class MerchantMetrics
*
* @since 1.7.0
*
* @package Automattic\WooCommerce\GoogleListingsAndAds\API\Google
*/
class MerchantMetrics implements OptionsAwareInterface {
use OptionsAwareTrait;
/**
* The Google shopping client.
*
* @var ShoppingContent
*/
protected $shopping_client;
/**
* The Google ads client.
*
* @var GoogleAdsClient
*/
protected $ads_client;
/**
* @var WP
*/
protected $wp;
/**
* @var TransientsInterface
*/
protected $transients;
protected const MAX_QUERY_START_DATE = '2020-01-01';
/**
* MerchantMetrics constructor.
*
* @param ShoppingContent $shopping_client
* @param GoogleAdsClient $ads_client
* @param WP $wp
* @param TransientsInterface $transients
*/
public function __construct( ShoppingContent $shopping_client, GoogleAdsClient $ads_client, WP $wp, TransientsInterface $transients ) {
$this->shopping_client = $shopping_client;
$this->ads_client = $ads_client;
$this->wp = $wp;
$this->transients = $transients;
}
/**
* Get free listing metrics.
*
* @return array Of metrics or empty if no metrics were available.
* @type int $clicks Number of free clicks.
* @type int $impressions NUmber of free impressions.
*
* @throws Exception When unable to get clicks data.
*/
public function get_free_listing_metrics(): array {
if ( ! $this->options->get_merchant_id() ) {
// Merchant account not set up
return [];
}
// Google API requires a date clause to be set but there doesn't seem to be any limits on how wide the range
$query = ( new MerchantFreeListingReportQuery( [] ) )
->set_client( $this->shopping_client, $this->options->get_merchant_id() )
->where_date_between( self::MAX_QUERY_START_DATE, $this->get_tomorrow() )
->fields( [ 'clicks', 'impressions' ] );
/** @var SearchResponse $response */
$response = $query->get_results();
if ( empty( $response ) || empty( $response->getResults() ) ) {
return [];
}
$report_row = $response->getResults()[0];
return [
'clicks' => (int) $report_row->getMetrics()->getClicks(),
'impressions' => (int) $report_row->getMetrics()->getImpressions(),
];
}
/**
* Get free listing metrics but cached for 12 hours.
*
* PLEASE NOTE: These metrics will not be 100% accurate since there is no invalidation apart from the 12 hour refresh.
*
* @return array Of metrics or empty if no metrics were available.
* @type int $clicks Number of free clicks.
* @type int $impressions NUmber of free impressions.
*
* @throws Exception When unable to get data.
*/
public function get_cached_free_listing_metrics(): array {
$value = $this->transients->get( TransientsInterface::FREE_LISTING_METRICS );
if ( $value === null ) {
$value = $this->get_free_listing_metrics();
$this->transients->set( TransientsInterface::FREE_LISTING_METRICS, $value, HOUR_IN_SECONDS * 12 );
}
return $value;
}
/**
* Get ads metrics across all campaigns.
*
* @return array Of metrics or empty if no metrics were available.
*
* @throws Exception When unable to get data.
*/
public function get_ads_metrics(): array {
if ( ! $this->options->get_ads_id() ) {
// Ads account not set up
return [];
}
// Google API requires a date clause to be set but there doesn't seem to be any limits on how wide the range
$query = ( new AdsCampaignReportQuery( [] ) )
->set_client( $this->ads_client, $this->options->get_ads_id() )
->where_date_between( self::MAX_QUERY_START_DATE, $this->get_tomorrow() )
->fields( [ 'clicks', 'conversions', 'impressions' ] );
/** @var PagedListResponse $response */
$response = $query->get_results();
$page = $response->getPage();
if ( $page && $page->getIterator()->current() ) {
/** @var GoogleAdsRow $row */
$row = $page->getIterator()->current();
$metrics = $row->getMetrics();
if ( $metrics ) {
return [
'clicks' => $metrics->getClicks(),
'conversions' => (int) $metrics->getConversions(),
'impressions' => $metrics->getImpressions(),
];
}
}
return [];
}
/**
* Get ads metrics across all campaigns but cached for 12 hours.
*
* PLEASE NOTE: These metrics will not be 100% accurate since there is no invalidation apart from the 12 hour refresh.
*
* @return array Of metrics or empty if no metrics were available.
*
* @throws Exception When unable to get data.
*/
public function get_cached_ads_metrics(): array {
$value = $this->transients->get( TransientsInterface::ADS_METRICS );
if ( $value === null ) {
$value = $this->get_ads_metrics();
$this->transients->set( TransientsInterface::ADS_METRICS, $value, HOUR_IN_SECONDS * 12 );
}
return $value;
}
/**
* Get tomorrow's date to ensure we include any metrics from the current day.
*
* @return string
*/
protected function get_tomorrow(): string {
return ( new DateTime( 'tomorrow', $this->wp->wp_timezone() ) )->format( 'Y-m-d' );
}
}