<?php
declare( strict_types=1 );
namespace Automattic\WooCommerce\GoogleListingsAndAds\Ads;
use Automattic\WooCommerce\GoogleListingsAndAds\Google\Ads\GoogleAdsClient;
use Automattic\WooCommerce\GoogleListingsAndAds\Infrastructure\Service;
use Automattic\WooCommerce\GoogleListingsAndAds\Options\OptionsAwareInterface;
use Automattic\WooCommerce\GoogleListingsAndAds\Options\OptionsAwareTrait;
use Automattic\WooCommerce\GoogleListingsAndAds\PluginHelper;
use Automattic\WooCommerce\GoogleListingsAndAds\API\Google\AssetFieldType;
use Automattic\WooCommerce\GoogleListingsAndAds\API\Google\ExceptionTrait;
use Automattic\WooCommerce\GoogleListingsAndAds\Exception\ExceptionWithResponseData;
use Google\Ads\GoogleAds\V23\Services\GenerateTextRequest;
use Google\Ads\GoogleAds\V23\Services\GenerateImagesRequest;
use Google\Ads\GoogleAds\V23\Services\FinalUrlImageGenerationInput;
use Google\Ads\GoogleAds\V23\Enums\AdvertisingChannelTypeEnum\AdvertisingChannelType;
use Google\ApiCore\ApiException;
use Exception;
defined( 'ABSPATH' ) || exit;
/**
* Class AdsAssetGenerationService
*
* Encapsulates all calls to the Google Ads API v22 AssetGenerationService.
*
* @package Automattic\WooCommerce\GoogleListingsAndAds\Ads
*/
class AdsAssetGenerationService implements OptionsAwareInterface, Service {
use OptionsAwareTrait;
use PluginHelper;
use ExceptionTrait;
/**
* The Asset Generation Service Client.
*
* @var \Google\Ads\GoogleAds\V23\Services\Client\AssetGenerationServiceClient
*/
protected $client;
/**
* The Google Ads Client.
*
* @var GoogleAdsClient
*/
protected $google_ads_client;
/**
* Valid text asset field types.
*
* @var array
*/
public const VALID_TEXT_TYPES = [
AssetFieldType::HEADLINE,
AssetFieldType::LONG_HEADLINE,
AssetFieldType::DESCRIPTION,
];
/**
* Valid image asset field types.
*
* @var array
*/
public const VALID_IMAGE_TYPES = [
AssetFieldType::MARKETING_IMAGE,
AssetFieldType::SQUARE_MARKETING_IMAGE,
AssetFieldType::PORTRAIT_MARKETING_IMAGE,
];
/**
* AdsAssetGenerationService constructor.
*
* @param GoogleAdsClient $client The Google Ads client.
*/
public function __construct( GoogleAdsClient $client ) {
$this->google_ads_client = $client;
$this->client = $client->getAssetGenerationServiceClient();
}
/**
* Generate text assets using Google's AI.
*
* @param array $args {
* Optional. Arguments for generating text assets.
*
* @type string $final_url The final URL - defaults to the Site URL.
* @type array $asset_field_types Can be one or more of: headline, long_headline, description.
* }
* @return array Array of generated text objects with 'text' and 'type' keys.
* @throws Exception If the text assets can't be generated.
*/
public function generate_text( array $args = [] ): array {
$customer_id = $this->options->get_ads_id();
if ( empty( $customer_id ) ) {
throw new Exception( __( 'Ads account ID is required.', 'google-listings-and-ads' ) );
}
$final_url = $args['final_url'] ?? $this->get_site_url();
// Default to all text types if not specified.
if ( empty( $args['asset_field_types'] ) ) {
$args['asset_field_types'] = [ 'headline', 'long_headline', 'description' ];
}
// Convert asset field types from lowercase strings to enum numbers.
$asset_field_types = $this->convert_types_to_enums( $args['asset_field_types'], self::VALID_TEXT_TYPES );
$request = new GenerateTextRequest(
[
'customer_id' => $customer_id,
'final_url' => $final_url,
'advertising_channel_type' => AdvertisingChannelType::PERFORMANCE_MAX,
'asset_field_types' => $asset_field_types,
]
);
try {
$response = $this->client->generateText( $request );
$results = [];
foreach ( $response->getGeneratedText() as $text_asset ) {
$asset_field_type_number = $text_asset->getAssetFieldType();
$asset_field_type_label = AssetFieldType::label( $asset_field_type_number );
$results[] = [
'text' => $text_asset->getText(),
'type' => $asset_field_type_label,
];
}
return $results;
} catch ( ApiException $e ) {
do_action( 'woocommerce_gla_ads_client_exception', $e, __METHOD__ );
$errors = $this->get_exception_errors( $e );
throw new ExceptionWithResponseData(
/* translators: %s Error message */
sprintf( __( 'Unable to generate text assets: %s', 'google-listings-and-ads' ), reset( $errors ) ),
$this->map_grpc_code_to_http_status_code( $e ),
$e,
[ 'errors' => $errors ]
);
}
}
/**
* Generate image assets using Google's AI.
*
* @param array $args {
* Optional. Arguments for generating image assets.
*
* @type string $final_url The final URL - defaults to the Site URL.
* @type array $asset_field_types Can be one or more of: marketing_image, square_marketing_image, portrait_marketing_image.
* }
* @return array Array of generated image objects with 'temporary_image_url' and 'type' keys.
* @throws Exception If the image assets can't be generated.
*/
public function generate_images( array $args = [] ): array {
$customer_id = $this->options->get_ads_id();
if ( empty( $customer_id ) ) {
throw new Exception( __( 'Ads account ID is required.', 'google-listings-and-ads' ) );
}
$final_url = $args['final_url'] ?? $this->get_site_url();
// Default to all valid image types if not specified.
if ( empty( $args['asset_field_types'] ) ) {
$args['asset_field_types'] = self::VALID_IMAGE_TYPES;
}
$asset_field_types = $this->convert_types_to_enums( $args['asset_field_types'], self::VALID_IMAGE_TYPES );
$request_data = [
'customer_id' => $customer_id,
'advertising_channel_type' => AdvertisingChannelType::PERFORMANCE_MAX,
'final_url_generation' => new FinalUrlImageGenerationInput(
[
'final_url' => $final_url,
]
),
'asset_field_types' => $asset_field_types,
];
$request = new GenerateImagesRequest( $request_data );
try {
$response = $this->client->generateImages( $request );
$results = [];
foreach ( $response->getGeneratedImages() as $image_asset ) {
$asset_field_type_number = $image_asset->getAssetFieldType();
$asset_field_type_label = AssetFieldType::label( $asset_field_type_number );
$results[] = [
'temporary_image_url' => $image_asset->getImageTemporaryUrl(),
'type' => $asset_field_type_label,
];
}
return $results;
} catch ( ApiException $e ) {
do_action( 'woocommerce_gla_ads_client_exception', $e, __METHOD__ );
$errors = $this->get_exception_errors( $e );
throw new ExceptionWithResponseData(
/* translators: %s Error message */
sprintf( __( 'Unable to generate image assets: %s', 'google-listings-and-ads' ), reset( $errors ) ),
$this->map_grpc_code_to_http_status_code( $e ),
$e,
[ 'errors' => $errors ]
);
}
}
/**
* Convert asset field types from lowercase strings to enum numbers.
*
* @param array $types Array of lowercase type strings.
* @param array $allowed_types Optional. Array of AssetFieldType constants to filter by.
* @return array Array of enum numbers.
*/
protected function convert_types_to_enums( array $types, array $allowed_types = [] ): array {
$enums = [];
foreach ( $types as $type ) {
// Filter by allowed types if specified.
if ( ! empty( $allowed_types ) && ! in_array( $type, $allowed_types, true ) ) {
continue;
}
$enums[] = AssetFieldType::number( $type );
}
return $enums;
}
}