Добавить максимальную эффективность для кампании с целями в поездках,Добавить максимальную производительность для кампании с целями в поездках

Джава

This example is not yet available in Java; you can take a look at the other languages.
    

С#

This example is not yet available in C#; you can take a look at the other languages.
    

PHP

<?php

/**
 * Copyright 2023 Google LLC
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     https://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

namespace Google\Ads\GoogleAds\Examples\Travel;

require __DIR__ . '/../../vendor/autoload.php';

use GetOpt\GetOpt;
use Google\Ads\GoogleAds\Examples\Utils\ArgumentNames;
use Google\Ads\GoogleAds\Examples\Utils\ArgumentParser;
use Google\Ads\GoogleAds\Examples\Utils\Helper;
use Google\Ads\GoogleAds\Lib\OAuth2TokenBuilder;
use Google\Ads\GoogleAds\Lib\V13\GoogleAdsClient;
use Google\Ads\GoogleAds\Lib\V13\GoogleAdsClientBuilder;
use Google\Ads\GoogleAds\Lib\V13\GoogleAdsException;
use Google\Ads\GoogleAds\Util\V13\ResourceNames;
use Google\Ads\GoogleAds\V13\Common\CallToActionAsset;
use Google\Ads\GoogleAds\V13\Common\HotelPropertyAsset;
use Google\Ads\GoogleAds\V13\Common\ImageAsset;
use Google\Ads\GoogleAds\V13\Common\MaximizeConversionValue;
use Google\Ads\GoogleAds\V13\Common\TextAsset;
use Google\Ads\GoogleAds\V13\Enums\AdvertisingChannelTypeEnum\AdvertisingChannelType;
use Google\Ads\GoogleAds\V13\Enums\AssetFieldTypeEnum\AssetFieldType;
use Google\Ads\GoogleAds\V13\Enums\AssetGroupStatusEnum\AssetGroupStatus;
use Google\Ads\GoogleAds\V13\Enums\AssetSetTypeEnum\AssetSetType;
use Google\Ads\GoogleAds\V13\Enums\BudgetDeliveryMethodEnum\BudgetDeliveryMethod;
use Google\Ads\GoogleAds\V13\Enums\CampaignStatusEnum\CampaignStatus;
use Google\Ads\GoogleAds\V13\Enums\HotelAssetSuggestionStatusEnum\HotelAssetSuggestionStatus;
use Google\Ads\GoogleAds\V13\Errors\GoogleAdsError;
use Google\Ads\GoogleAds\V13\Resources\Asset;
use Google\Ads\GoogleAds\V13\Resources\AssetGroup;
use Google\Ads\GoogleAds\V13\Resources\AssetGroupAsset;
use Google\Ads\GoogleAds\V13\Resources\AssetSet;
use Google\Ads\GoogleAds\V13\Resources\AssetSetAsset;
use Google\Ads\GoogleAds\V13\Resources\Campaign;
use Google\Ads\GoogleAds\V13\Resources\CampaignBudget;
use Google\Ads\GoogleAds\V13\Services\AssetGroupAssetOperation;
use Google\Ads\GoogleAds\V13\Services\AssetGroupOperation;
use Google\Ads\GoogleAds\V13\Services\AssetOperation;
use Google\Ads\GoogleAds\V13\Services\AssetSetAssetOperation;
use Google\Ads\GoogleAds\V13\Services\AssetSetOperation;
use Google\Ads\GoogleAds\V13\Services\CampaignBudgetOperation;
use Google\Ads\GoogleAds\V13\Services\CampaignOperation;
use Google\Ads\GoogleAds\V13\Services\HotelAssetSuggestion;
use Google\Ads\GoogleAds\V13\Services\HotelImageAsset;
use Google\Ads\GoogleAds\V13\Services\HotelTextAsset;
use Google\Ads\GoogleAds\V13\Services\MutateGoogleAdsResponse;
use Google\Ads\GoogleAds\V13\Services\MutateOperation;
use Google\Ads\GoogleAds\V13\Services\MutateOperationResponse;
use Google\ApiCore\ApiException;
use Google\ApiCore\Serializer;

/**
 * This example shows how to create a Performance Max for travel goals campaign. It also uses
 * TravelAssetSuggestionService to fetch suggested assets for creating an asset group. In case
 * there are not enough assets for the asset group (required by Performance Max), this example will
 * create more assets to fulfill the requirements.
 *
 * For more information about Performance Max campaigns, see
 * https://developers.google.com/google-ads/api/docs/performance-max/overview.
 *
 * Prerequisites:
 * - You must have at least one conversion action in the account. For more about conversion actions,
 * see
 * https://developers.google.com/google-ads/api/docs/conversions/overview#conversion_actions.
 *
 * Notes:
 * - This example uses the default customer conversion goals. For an example of setting
 *   campaign-specific conversion goals, see ShoppingAds/AddPerformanceMaxRetailCampaign.php.
 * - To learn how to create asset group signals, see
 *   AdvancedOperations/AddPerformanceMaxCampaign.php.
 */
class AddPerformanceMaxForTravelGoalsCampaign
{
    private const CUSTOMER_ID = 'INSERT_CUSTOMER_ID_HERE';
    // Sets a place ID that uniquely identifies a place in the Google Places database.
    // See https://developers.google.com/places/web-service/place-id to learn more.
    // The provided place ID must belong to a hotel property.
    private const PLACE_ID = 'INSERT_PLACE_ID_HERE';

    // Minimum requirements of assets required in a Performance Max asset group.
    // See https://developers.google.com/google-ads/api/docs/performance-max/assets for details.
    private const MIN_REQUIRED_TEXT_ASSET_COUNTS = [
        AssetFieldType::HEADLINE => 3,
        AssetFieldType::LONG_HEADLINE => 1,
        AssetFieldType::DESCRIPTION => 2,
        AssetFieldType::BUSINESS_NAME => 1
    ];
    private const MIN_REQUIRED_IMAGE_ASSET_COUNTS = [
        AssetFieldType::MARKETING_IMAGE => 1,
        AssetFieldType::SQUARE_MARKETING_IMAGE => 1,
        AssetFieldType::LOGO => 1
    ];
    // Texts and URLs used to create text and image assets when the TravelAssetSuggestionService
    // doesn't return enough assets required for creating an asset group.
    private const DEFAULT_TEXT_ASSETS_INFO = [
        AssetFieldType::HEADLINE => ['Hotel', 'Travel Reviews', 'Book travel'],
        AssetFieldType::LONG_HEADLINE => ['Travel the World'],
        AssetFieldType::DESCRIPTION => [
            'Great deal for your beloved hotel',
            'Best rate guaranteed'
        ],
        AssetFieldType::BUSINESS_NAME => ['Interplanetary Cruises']
    ];
    private const DEFAULT_IMAGE_ASSETS_INFO = [
        AssetFieldType::MARKETING_IMAGE => ['https://gaagl.page.link/Eit5'],
        AssetFieldType::SQUARE_MARKETING_IMAGE => ['https://gaagl.page.link/bjYi'],
        AssetFieldType::LOGO => ['https://gaagl.page.link/bjYi']
    ];

    // We specify temporary IDs that are specific to a single mutate request.
    // Temporary IDs are always negative and unique within one mutate request.
    //
    // See https://developers.google.com/google-ads/api/docs/mutating/best-practices
    // for further details.
    //
    // These temporary IDs are fixed because they are used in multiple places.
    private const ASSET_TEMPORARY_ID = -1;
    private const BUDGET_TEMPORARY_ID = -2;
    private const CAMPAIGN_TEMPORARY_ID = -3;
    private const ASSET_GROUP_TEMPORARY_ID = -4;

    // There are also entities that will be created in the same request but do not need to be fixed
    // temporary IDs because they are referenced only once.
    /** @var int the negative temporary ID used in bulk mutates. */
    private static $nextTempId = self::ASSET_GROUP_TEMPORARY_ID - 1;

    public static function main()
    {
        // Either pass the required parameters for this example on the command line, or insert them
        // into the constants above.
        $options = (new ArgumentParser())->parseCommandArguments([
            ArgumentNames::CUSTOMER_ID => GetOpt::REQUIRED_ARGUMENT,
            ArgumentNames::PLACE_ID => GetOpt::REQUIRED_ARGUMENT
        ]);

        // Generate a refreshable OAuth2 credential for authentication.
        $oAuth2Credential = (new OAuth2TokenBuilder())->fromFile()->build();

        // Construct a Google Ads client configured from a properties file and the
        // OAuth2 credentials above.
        $googleAdsClient = (new GoogleAdsClientBuilder())
            ->fromFile()
            ->withOAuth2Credential($oAuth2Credential)
            ->build();

        try {
            self::runExample(
                $googleAdsClient,
                $options[ArgumentNames::CUSTOMER_ID] ?: self::CUSTOMER_ID,
                $options[ArgumentNames::PLACE_ID] ?: self::PLACE_ID
            );
        } catch (GoogleAdsException $googleAdsException) {
            printf(
                "Request with ID '%s' has failed.%sGoogle Ads failure details:%s",
                $googleAdsException->getRequestId(),
                PHP_EOL,
                PHP_EOL
            );
            foreach ($googleAdsException->getGoogleAdsFailure()->getErrors() as $error) {
                /** @var GoogleAdsError $error */
                printf(
                    "\t%s: %s%s",
                    $error->getErrorCode()->getErrorCode(),
                    $error->getMessage(),
                    PHP_EOL
                );
            }
            exit(1);
        } catch (ApiException $apiException) {
            printf(
                "ApiException was thrown with message '%s'.%s",
                $apiException->getMessage(),
                PHP_EOL
            );
            exit(1);
        }
    }

    /**
     * Runs the example.
     *
     * @param GoogleAdsClient $googleAdsClient the Google Ads API client
     * @param int $customerId the customer ID
     * @param string $placeId the place ID for a hotel property asset
     */
    public static function runExample(
        GoogleAdsClient $googleAdsClient,
        int $customerId,
        string $placeId
    ) {
        // Gets hotel asset suggestion using the TravelAssetSuggestionService.
        $hotelAssetSuggestion =
            self::getHotelAssetSuggestion($googleAdsClient, $customerId, $placeId);

        // Performance Max campaigns require that repeated assets such as headlines
        // and descriptions be created before the campaign.
        // For the list of required assets for a Performance Max campaign, see
        // https://developers.google.com/google-ads/api/docs/performance-max/assets.
        //
        // This step is the same for any types of Performance Max campaigns.

        // Creates the headlines using the hotel asset suggestion.
        $headlineAssetResourceNames = self::createMultipleTextAssets(
            $googleAdsClient,
            $customerId,
            AssetFieldType::HEADLINE,
            $hotelAssetSuggestion
        );
        // Creates the descriptions using the hotel asset suggestion.
        $descriptionAssetResourceNames = self::createMultipleTextAssets(
            $googleAdsClient,
            $customerId,
            AssetFieldType::DESCRIPTION,
            $hotelAssetSuggestion
        );

        // Creates a hotel property asset set, which will be used later to link with a newly created
        // campaign.
        $hotelPropertyAssetSetResourceName =
            self::createHotelAssetSet($googleAdsClient, $customerId);
        // Creates a hotel property asset and link it with the previously created hotel property
        // asset set. This asset will also be linked to an asset group in the later steps.
        // In the real-world scenario, you'd need to create many assets for all your hotel
        // properties. We use one hotel property here for simplicity.
        // Both asset and asset set need to be created before creating a campaign, so we cannot
        // bundle them with other mutate operations below.
        $hotelPropertyAssetResourceName = self::createHotelAsset(
            $googleAdsClient,
            $customerId,
            $placeId,
            $hotelPropertyAssetSetResourceName
        );

        // It's important to create the below entities in this order because they depend on
        // each other.
        // The below methods create and return mutate operations that we later provide to the
        // GoogleAdsService.Mutate method in order to create the entities in a single request.
        // Since the entities for a Performance Max campaign are closely tied to one-another, it's
        // considered a best practice to create them in a single Mutate request so they all complete
        // successfully or fail entirely, leaving no orphaned entities. See:
        // https://developers.google.com/google-ads/api/docs/mutating/overview.
        $operations = [];
        $operations[] = self::createCampaignBudgetOperation($customerId);
        $operations[] =
            self::createCampaignOperation($customerId, $hotelPropertyAssetSetResourceName);
        $operations = array_merge($operations, self::createAssetGroupOperations(
            $customerId,
            $hotelPropertyAssetResourceName,
            $headlineAssetResourceNames,
            $descriptionAssetResourceNames,
            $hotelAssetSuggestion
        ));

        // Issues a mutate request to create everything and prints the results.
        $googleAdsServiceClient = $googleAdsClient->getGoogleAdsServiceClient();
        $response = $googleAdsServiceClient->mutate(
            $customerId,
            $operations
        );
        print "Created the following entities for a campaign budget, a campaign, and an asset group"
            . " for Performance Max for travel goals:" . PHP_EOL;
        self::printResponseDetails($response);
    }

    /**
     * Returns hotel asset suggestion obtained from TravelAssetsSuggestionService.
     *
     * @param GoogleAdsClient $googleAdsClient the Google Ads API client
     * @param int $customerId the customer ID
     * @param string $placeId the place ID of the hotel property you want to get its suggested
     *     assets
     * @return HotelAssetSuggestion a hotel asset suggestion
     */
    private static function getHotelAssetSuggestion(
        GoogleAdsClient $googleAdsClient,
        int $customerId,
        string $placeId
    ): HotelAssetSuggestion {
        // Send a request to suggest assets to be created as an asset group for the Performance Max
        // for travel goals campaign.
        $travelAssetSuggestionServiceClient =
            $googleAdsClient->getTravelAssetSuggestionServiceClient();
        $response = $travelAssetSuggestionServiceClient->suggestTravelAssets(
            $customerId,
            // Uses 'en-US' as an example. It can be any language specifications in BCP 47 format.
            'en-US',
            // The service accepts several place IDs. We use only one here for demonstration.
            ['placeId' => [$placeId]]
        );
        printf("Fetched a hotel asset suggestion for the place ID '%s'.%s", $placeId, PHP_EOL);
        return $response->getHotelAssetSuggestions()[0];
    }

    /**
     * Creates multiple text assets and returns the list of resource names. The hotel asset
     * suggestion is used to create a text asset first. If the number of created text assets is
     * still fewer than the minimum required number of assets of the specified asset field type,
     * adds more text assets to fulfill the requirement.
     *
     * @param GoogleAdsClient $googleAdsClient the Google Ads API client
     * @param int $customerId the customer ID
     * @param int $assetFieldType the asset field type that this text assets will be created for
     * @param HotelAssetSuggestion $hotelAssetSuggestion the hotel asset suggestion
     * @return string[] a list of asset resource names
     */
    private static function createMultipleTextAssets(
        GoogleAdsClient $googleAdsClient,
        int $customerId,
        int $assetFieldType,
        HotelAssetSuggestion $hotelAssetSuggestion
    ): array {
        // We use the GoogleAdService to create multiple text assets in a single request.
        // First, adds all the text assets of the specified asset field type.
        $operations = [];
        $numOperationsAdded = 0;
        if ($hotelAssetSuggestion->getStatus() === HotelAssetSuggestionStatus::SUCCESS) {
            foreach ($hotelAssetSuggestion->getTextAssets() as $textAsset) {
                /** @var HotelTextAsset $textAsset */
                if ($textAsset->getAssetFieldType() !== $assetFieldType) {
                    continue;
                }
                $operations[] = new MutateOperation([
                    'asset_operation' => new AssetOperation([
                        'create' => new Asset([
                            'text_asset' => new TextAsset(['text' => $textAsset->getText()])
                        ])
                    ])
                ]);
                $numOperationsAdded++;
            }
        }
        // If the added assets are still less than the minimum required assets for the asset field
        // type, add more text assets using the default texts.
        if ($numOperationsAdded < self::MIN_REQUIRED_TEXT_ASSET_COUNTS[$assetFieldType]) {
            for (
                $i = 0;
                $i < self::MIN_REQUIRED_TEXT_ASSET_COUNTS[$assetFieldType] - $numOperationsAdded;
                $i++
            ) {
                // Creates a mutate operation for a text asset.
                $operations[] = new MutateOperation([
                    'asset_operation' => new AssetOperation([
                        'create' => new Asset([
                            'text_asset' => new TextAsset([
                                'text' => self::DEFAULT_TEXT_ASSETS_INFO[$assetFieldType][$i]
                            ])
                        ])
                    ])
                ]);
            }
        }

        // Issues a mutate request to add all assets.
        $googleAdsService = $googleAdsClient->getGoogleAdsServiceClient();
        /** @var MutateGoogleAdsResponse $mutateGoogleAdsResponse */
        $mutateGoogleAdsResponse = $googleAdsService->mutate($customerId, $operations);

        $assetResourceNames = [];
        foreach ($mutateGoogleAdsResponse->getMutateOperationResponses() as $response) {
            /** @var MutateOperationResponse $response */
            $assetResourceNames[] = $response->getAssetResult()->getResourceName();
        }
        printf(
            "The following assets are created for the asset field type '%s':%s",
            AssetFieldType::name($assetFieldType),
            PHP_EOL
        );
        self::printResponseDetails($mutateGoogleAdsResponse);

        return $assetResourceNames;
    }

    /**
     * Creates a hotel property asset set.
     *
     * @param GoogleAdsClient $googleAdsClient the Google Ads API client
     * @param int $customerId the customer ID
     * @return string the created hotel property asset set resource name
     */
    private static function createHotelAssetSet(
        GoogleAdsClient $googleAdsClient,
        int $customerId
    ): string {
        // Creates an asset set operation for a hotel property asset set.
        $assetSetOperation = new AssetSetOperation([
            // Creates a hotel property asset set.
            'create' => new AssetSet([
                'name' => 'My Hotel propery asset set #' . Helper::getPrintableDatetime(),
                'type' => AssetSetType::HOTEL_PROPERTY
            ])
        ]);

        // Issues a mutate request to add a hotel asset set and prints its information.
        $assetSetServiceClient = $googleAdsClient->getAssetSetServiceClient();
        $response = $assetSetServiceClient->mutateAssetSets($customerId, [$assetSetOperation]);
        $assetSetResourceName = $response->getResults()[0]->getResourceName();
        printf("Created an asset set with resource name: '%s'.%s", $assetSetResourceName, PHP_EOL);
        return $assetSetResourceName;
    }

    /**
     * Creates a hotel property asset using the specified place ID. The place ID must belong to
     * a hotel property. Then, links it to the specified asset set.
     *
     * See https://developers.google.com/places/web-service/place-id to search for a hotel place ID.
     *
     * @param GoogleAdsClient $googleAdsClient the Google Ads API client
     * @param int $customerId the customer ID
     * @param string $placeId the place ID for a hotel
     * @param string $assetSetResourceName the asset set resource name
     * @return string the created hotel property asset resource name
     */
    private static function createHotelAsset(
        GoogleAdsClient $googleAdsClient,
        int $customerId,
        string $placeId,
        string $assetSetResourceName
    ): string {
        // We use the GoogleAdService to create an asset and asset set asset in a single
        // request.
        $operations = [];
        $assetResourceName =
            ResourceNames::forAsset($customerId, self::ASSET_TEMPORARY_ID);
        // Creates a mutate operation for a hotel property asset.
        $operations[] = new MutateOperation([
            'asset_operation' => new AssetOperation([
                // Creates a hotel property asset.
                'create' => new Asset([
                    'resource_name' => $assetResourceName,
                    // Creates a hotel property asset for the place ID.
                    'hotel_property_asset' => new HotelPropertyAsset(['place_id' => $placeId]),
                ])
            ])
        ]);
        // Creates a mutate operation for an asset set asset.
        $operations[] = new MutateOperation([
            'asset_set_asset_operation' => new AssetSetAssetOperation([
                // Creates an asset set asset.
                'create' => new AssetSetAsset([
                    'asset' => $assetResourceName,
                    'asset_set' => $assetSetResourceName
                ])
            ])
        ]);

        // Issues a mutate request to create all entities.
        $googleAdsService = $googleAdsClient->getGoogleAdsServiceClient();
        /** @var MutateGoogleAdsResponse $mutateGoogleAdsResponse */
        $mutateGoogleAdsResponse = $googleAdsService->mutate($customerId, $operations);
        print "Created the following entities for the hotel asset:" . PHP_EOL;
        self::printResponseDetails($mutateGoogleAdsResponse);

        // Returns the created asset resource name, which will be used later to create an asset
        // group. Other resource names are not used later.
        return $mutateGoogleAdsResponse->getMutateOperationResponses()[0]->getAssetResult()
            ->getResourceName();
    }

    /**
     * Creates a mutate operation that creates a new campaign budget.
     *
     * A temporary ID will be assigned to this campaign budget so that it can be
     * referenced by other objects being created in the same mutate request.
     *
     * @param int $customerId the customer ID
     * @return MutateOperation the mutate operation that creates a campaign budget
     */
    private static function createCampaignBudgetOperation(int $customerId): MutateOperation
    {
        // Creates a mutate operation that creates a campaign budget.
        return new MutateOperation([
            'campaign_budget_operation' => new CampaignBudgetOperation([
                'create' => new CampaignBudget([
                    // Sets a temporary ID in the budget's resource name so it can be referenced
                    // by the campaign in later steps.
                    'resource_name' => ResourceNames::forCampaignBudget(
                        $customerId,
                        self::BUDGET_TEMPORARY_ID
                    ),
                    'name' => 'Performance Max for travel goals campaign budget #'
                        . Helper::getPrintableDatetime(),
                    // The budget period already defaults to DAILY.
                    'amount_micros' => 50000000,
                    'delivery_method' => BudgetDeliveryMethod::STANDARD,
                    // A Performance Max campaign cannot use a shared campaign budget.
                    'explicitly_shared' => false
                ])
            ])
        ]);
    }

    /**
     * Creates a mutate operation that creates a new Performance Max campaign. Links the specified
     * hotel property asset set to this campaign.
     *
     * A temporary ID will be assigned to this campaign so that it can be referenced by other
     * objects being created in the same mutate request.
     *
     * @param int $customerId the customer ID
     * @param string $hotelPropertyAssetSetResourceName the asset set resource name
     * @return MutateOperation the mutate operation that creates the campaign
     */
    private static function createCampaignOperation(
        int $customerId,
        string $hotelPropertyAssetSetResourceName
    ): MutateOperation {
        // Creates a mutate operation that creates a campaign.
        return new MutateOperation([
            'campaign_operation' => new CampaignOperation([
                'create' => new Campaign([
                    'name' => 'Performance Max for travel goals campaign #'
                        . Helper::getPrintableDatetime(),
                    // Assigns the resource name with a temporary ID.
                    'resource_name' => ResourceNames::forCampaign(
                        $customerId,
                        self::CAMPAIGN_TEMPORARY_ID
                    ),
                    // Sets the budget using the given budget resource name.
                    'campaign_budget' => ResourceNames::forCampaignBudget(
                        $customerId,
                        self::BUDGET_TEMPORARY_ID
                    ),
                    // The campaign is the only entity in the mutate request that should have its
                    // status set.
                    // Recommendation: Set the campaign to PAUSED when creating it to prevent
                    // the ads from immediately serving.
                    'status' => CampaignStatus::PAUSED,
                    // Performance Max campaigns have an advertising_channel_type of
                    // PERFORMANCE_MAX. The advertising_channel_sub_type should not be set.
                    'advertising_channel_type' => AdvertisingChannelType::PERFORMANCE_MAX,

                    // To create a Performance Max for travel goals campaign, you need to set
                    // `hotel_property_asset_set`.
                    'hotel_property_asset_set' => $hotelPropertyAssetSetResourceName,

                    // Bidding strategy must be set directly on the campaign.
                    // Setting a portfolio bidding strategy by resource name is not supported.
                    // Max Conversion and Maximize Conversion Value are the only strategies
                    // supported for Performance Max campaigns.
                    // An optional ROAS (Return on Advertising Spend) can be set for
                    // maximize_conversion_value. The ROAS value must be specified as a ratio in
                    // the API. It is calculated by dividing "total value" by "total spend".
                    // For more information on Maximize Conversion Value, see the support
                    // article: https://support.google.com/google-ads/answer/7684216.
                    // A target_roas of 3.5 corresponds to a 350% return on ad spend.
                    'maximize_conversion_value' => new MaximizeConversionValue([
                        'target_roas' => 3.5
                    ])
                ])
            ])
        ]);
    }

    /**
     * Creates a list of mutate operations that create a new asset group, composed of suggested
     * assets. In case the number of suggested assets is not enough for the requirements, it'll
     * create more assets to meet the requirement.
     *
     * For the list of required assets for a Performance Max campaign, see
     * https://developers.google.com/google-ads/api/docs/performance-max/assets.
     *
     * @param int $customerId the customer ID
     * @param string $hotelPropertyAssetResourceName the hotel property asset resource name that
     *     will be used to create an asset group
     * @param string[] $headlineAssetResourceNames a list of headline resource names
     * @param string[] $descriptionAssetResourceNames a list of description resource names
     * @param HotelAssetSuggestion $hotelAssetSuggestion the hotel asset suggestion
     * @return MutateOperation[] a list of mutate operations that create the asset group
     */
    private static function createAssetGroupOperations(
        int $customerId,
        string $hotelPropertyAssetResourceName,
        array $headlineAssetResourceNames,
        array $descriptionAssetResourceNames,
        HotelAssetSuggestion $hotelAssetSuggestion
    ): array {
        $operations = [];

        // Creates a new mutate operation that creates an asset group using suggested information
        // when available.
        $assetGroupName = $hotelAssetSuggestion->getStatus() === HotelAssetSuggestionStatus::SUCCESS
            ? $hotelAssetSuggestion->getHotelName()
            : 'Performance Max for travel goals asset group #' . Helper::getPrintableDatetime();
        $assetGroupFinalUrls =
            $hotelAssetSuggestion->getStatus() === HotelAssetSuggestionStatus::SUCCESS
                ? [$hotelAssetSuggestion->getFinalUrl()] : ['http://www.example.com'];
        $assetGroupResourceName =
            ResourceNames::forAssetGroup($customerId, self::ASSET_GROUP_TEMPORARY_ID);
        $operations[] = new MutateOperation([
            'asset_group_operation' => new AssetGroupOperation([
                'create' => new AssetGroup([
                    'resource_name' => $assetGroupResourceName,
                    'name' => $assetGroupName,
                    'campaign' => ResourceNames::forCampaign(
                        $customerId,
                        self::CAMPAIGN_TEMPORARY_ID
                    ),
                    'final_urls' => $assetGroupFinalUrls,
                    'status' => AssetGroupStatus::PAUSED
                ])
            ])
        ]);

        // An asset group is linked to an asset by creating a new asset group asset
        // and providing:
        // -  the resource name of the asset group
        // -  the resource name of the asset
        // -  the field_type of the asset in this asset group
        //
        // To learn more about asset groups, see
        // https://developers.google.com/google-ads/api/docs/performance-max/asset-groups.

        // Headline and description assets were created at the first step of this example. So, we
        // just need to link them with the created asset group.

        // Links the headline assets to the asset group.
        foreach ($headlineAssetResourceNames as $resourceName) {
            $operations[] = new MutateOperation([
                'asset_group_asset_operation' => new AssetGroupAssetOperation([
                    'create' => new AssetGroupAsset([
                        'asset' => $resourceName,
                        'asset_group' => $assetGroupResourceName,
                        'field_type' => AssetFieldType::HEADLINE
                    ])
                ])
            ]);
        }
        // Links the description assets to the asset group.
        foreach ($descriptionAssetResourceNames as $resourceName) {
            $operations[] = new MutateOperation([
                'asset_group_asset_operation' => new AssetGroupAssetOperation([
                    'create' => new AssetGroupAsset([
                        'asset' => $resourceName,
                        'asset_group' => $assetGroupResourceName,
                        'field_type' => AssetFieldType::DESCRIPTION
                    ])
                ])
            ]);
        }

        // Link the previously created hotel property asset to the asset group. In the real-world
        // scenario, you'd need to do this step several times for each hotel property asset.
        $operations[] = new MutateOperation([
            'asset_group_asset_operation' => new AssetGroupAssetOperation([
                'create' => new AssetGroupAsset([
                    'asset' => $hotelPropertyAssetResourceName,
                    'asset_group' => $assetGroupResourceName,
                    'field_type' => AssetFieldType::HOTEL_PROPERTY
                ])
            ])
        ]);

        // Creates the rest of required text assets and link them to the asset group.
        $operations = array_merge(
            $operations,
            self::createTextAssetsForAssetGroup($customerId, $hotelAssetSuggestion)
        );
        // Creates the image assets and link them to the asset group. Some optional image assets
        // suggested by the TravelAssetSuggestionService might be created too.
        $operations = array_merge(
            $operations,
            self::createImageAssetsForAssetGroup($customerId, $hotelAssetSuggestion)
        );

        if ($hotelAssetSuggestion->getStatus() === HotelAssetSuggestionStatus::SUCCESS) {
            // Creates a new mutate operation for a suggested call-to-action asset and link it
            // to the asset group.
            $operations[] = new MutateOperation([
                'asset_operation' => new AssetOperation([
                    'create' => new Asset([
                        'resource_name' => ResourceNames::forAsset($customerId, self::$nextTempId),
                        'name' => 'Suggested call-to-action asset #'
                            . Helper::getShortPrintableDatetime(),
                        'call_to_action_asset' => new CallToActionAsset([
                            'call_to_action' => $hotelAssetSuggestion->getCallToAction()
                        ])
                    ])
                ])
            ]);
            $operations[] = new MutateOperation([
                'asset_group_asset_operation' => new AssetGroupAssetOperation([
                    'create' => new AssetGroupAsset([
                        'asset' => ResourceNames::forAsset($customerId, self::$nextTempId),
                        'asset_group' => $assetGroupResourceName,
                        'field_type' => AssetFieldType::CALL_TO_ACTION_SELECTION
                    ])
                ])
            ]);
            self::$nextTempId--;
        }

        return $operations;
    }

    /**
     * Creates text assets required for an asset group using the suggested hotel text assets. It
     * adds more text assets to fulfill the requirements if the suggested hotel text assets are not
     * enough.
     *
     * @param int $customerId the customer ID
     * @param HotelAssetSuggestion $hotelAssetSuggestion the hotel asset suggestion
     * @return MutateOperation[] a list of mutate operations that create text assets
     */
    private static function createTextAssetsForAssetGroup(
        int $customerId,
        HotelAssetSuggestion $hotelAssetSuggestion
    ): array {
        $operations = [];
        // Creates mutate operations for the suggested text assets except for headlines and
        // descriptions, which were created previously.
        $requiredTextAssetCounts =
            array_fill_keys(array_keys(self::MIN_REQUIRED_TEXT_ASSET_COUNTS), 0);
        if ($hotelAssetSuggestion->getStatus() === HotelAssetSuggestionStatus::SUCCESS) {
            foreach ($hotelAssetSuggestion->getTextAssets() as $textAsset) {
                /** @var HotelTextAsset $textAsset */
                if (
                    $textAsset->getAssetFieldType() === AssetFieldType::HEADLINE
                    || $textAsset->getAssetFieldType() === AssetFieldType::DESCRIPTION
                ) {
                    // Headlines and descriptions were already created at the first step of this
                    // code example.
                    continue;
                }
                printf(
                    "A text asset with text '%s' is suggested for the asset field type '%s'.%s",
                    $textAsset->getText(),
                    AssetFieldType::name($textAsset->getAssetFieldType()),
                    PHP_EOL
                );
                $operations = array_merge(
                    $operations,
                    self::createTextAssetAndAssetGroupAssetOperations(
                        $customerId,
                        $textAsset->getText(),
                        $textAsset->getAssetFieldType()
                    )
                );
                $requiredTextAssetCounts[$textAsset->getAssetFieldType()]++;
            }
        }
        // Adds more text assets to fulfill the requirements.
        foreach (self::MIN_REQUIRED_TEXT_ASSET_COUNTS as $assetFieldType => $minCount) {
            if (
                $assetFieldType === AssetFieldType::HEADLINE
                || $assetFieldType === AssetFieldType::DESCRIPTION
            ) {
                // Headlines and descriptions were already created at the first step of this
                // code example.
                continue;
            }
            for ($i = 0; $i < $minCount - $requiredTextAssetCounts[$assetFieldType]; $i++) {
                printf(
                    "A default text '%s' is used to create a text asset for the asset"
                        . " field type '%s'.%s",
                    self::DEFAULT_TEXT_ASSETS_INFO[$assetFieldType][$i],
                    AssetFieldType::name($assetFieldType),
                    PHP_EOL
                );
                $operations = array_merge(
                    $operations,
                    self::createTextAssetAndAssetGroupAssetOperations(
                        $customerId,
                        self::DEFAULT_TEXT_ASSETS_INFO[$assetFieldType][$i],
                        $assetFieldType
                    )
                );
            }
        }

        return $operations;
    }

    /**
     * Creates image assets required for an asset group using the suggested hotel image assets. It
     * adds more image assets to fulfill the requirements if the suggested hotel image assets are
     * not enough.
     *
     * @param int $customerId the customer ID
     * @param HotelAssetSuggestion $hotelAssetSuggestion the hotel asset suggestion
     * @return MutateOperation[] a list of mutate operations that create image assets
     */
    private static function createImageAssetsForAssetGroup(
        int $customerId,
        HotelAssetSuggestion $hotelAssetSuggestion
    ): array {
        $operations = [];
        // Creates mutate operations for the suggested image assets.
        $requiredImageAssetCounts =
            array_fill_keys(array_keys(self::MIN_REQUIRED_IMAGE_ASSET_COUNTS), 0);
        foreach ($hotelAssetSuggestion->getImageAssets() as $imageAsset) {
            /** @var HotelImageAsset $imageAsset */
            printf(
                "An image asset with URL '%s' is suggested for the asset field type '%s'.%s",
                $imageAsset->getUri(),
                AssetFieldType::name($imageAsset->getAssetFieldType()),
                PHP_EOL
            );
            $operations = array_merge(
                $operations,
                self::createImageAssetAndAssetGroupAssetOperations(
                    $customerId,
                    $imageAsset->getUri(),
                    $imageAsset->getAssetFieldType(),
                    'Suggested image asset #' . Helper::getShortPrintableDatetime()
                )
            );
            // Keeps track of only required image assets. The service may sometimes suggest
            // optional image assets.
            if (array_key_exists($imageAsset->getAssetFieldType(), $requiredImageAssetCounts)) {
                $requiredImageAssetCounts[$imageAsset->getAssetFieldType()]++;
            }
        }
        // Adds more image assets to fulfill the requirements.
        foreach (self::MIN_REQUIRED_IMAGE_ASSET_COUNTS as $assetFieldType => $minCount) {
            for ($i = 0; $i < $minCount - $requiredImageAssetCounts[$assetFieldType]; $i++) {
                printf(
                    "A default image URL '%s' is used to create an image asset for the"
                    . " asset field type '%s'.%s",
                    self::DEFAULT_IMAGE_ASSETS_INFO[$assetFieldType][$i],
                    AssetFieldType::name($assetFieldType),
                    PHP_EOL
                );
                $operations = array_merge(
                    $operations,
                    self::createImageAssetAndAssetGroupAssetOperations(
                        $customerId,
                        self::DEFAULT_IMAGE_ASSETS_INFO[$assetFieldType][$i],
                        $assetFieldType,
                        strtolower(AssetFieldType::name($assetFieldType))
                        . Helper::getShortPrintableDatetime()
                    )
                );
            }
        }

        return $operations;
    }

    /**
     * Creates a list of mutate operations that create a new linked text asset.
     *
     * @param int $customerId the customer ID
     * @param string $text the text of the asset to be created
     * @param int $fieldType the field type of the new asset in the asset group asset
     * @return MutateOperation[] a list of mutate operations that create a new linked text asset
     */
    private static function createTextAssetAndAssetGroupAssetOperations(
        int $customerId,
        string $text,
        int $fieldType
    ): array {
        $operations = [];
        // Creates a new mutate operation that creates a text asset.
        $operations[] = new MutateOperation([
            'asset_operation' => new AssetOperation([
                'create' => new Asset([
                    'resource_name' => ResourceNames::forAsset($customerId, self::$nextTempId),
                    'text_asset' => new TextAsset(['text' => $text])
                ])
            ])
        ]);

        // Creates an asset group asset to link the asset to the asset group.
        $operations[] = new MutateOperation([
            'asset_group_asset_operation' => new AssetGroupAssetOperation([
                'create' => new AssetGroupAsset([
                    'asset' => ResourceNames::forAsset($customerId, self::$nextTempId),
                    'asset_group' => ResourceNames::forAssetGroup(
                        $customerId,
                        self::ASSET_GROUP_TEMPORARY_ID
                    ),
                    'field_type' => $fieldType
                ])
            ])
        ]);
        self::$nextTempId--;

        return $operations;
    }

    /**
     * Creates a list of mutate operations that create a new linked image asset.
     *
     * @param int $customerId the customer ID
     * @param string $url the URL of the image to be retrieved and put into an asset
     * @param int $fieldType the field type of the new asset in the asset group asset
     * @param string $assetName the asset name
     * @return MutateOperation[] a list of mutate operations that create a new linked image asset
     */
    private static function createImageAssetAndAssetGroupAssetOperations(
        int $customerId,
        string $url,
        int $fieldType,
        string $assetName
    ): array {
        $operations = [];
        // Creates a new mutate operation that creates an image asset.
        $operations[] = new MutateOperation([
            'asset_operation' => new AssetOperation([
                'create' => new Asset([
                    'resource_name' => ResourceNames::forAsset($customerId, self::$nextTempId),
                    // Provide a unique friendly name to identify your asset.
                    // When there is an existing image asset with the same content but a different
                    // name, the new name will be dropped silently.
                    'name' => $assetName,
                    'image_asset' => new ImageAsset(['data' => file_get_contents($url)])
                ])
            ])
        ]);

        // Creates an asset group asset to link the asset to the asset group.
        $operations[] = new MutateOperation([
            'asset_group_asset_operation' => new AssetGroupAssetOperation([
                'create' => new AssetGroupAsset([
                    'asset' => ResourceNames::forAsset($customerId, self::$nextTempId),
                    'asset_group' => ResourceNames::forAssetGroup(
                        $customerId,
                        self::ASSET_GROUP_TEMPORARY_ID
                    ),
                    'field_type' => $fieldType
                ])
            ])
        ]);
        self::$nextTempId--;

        return $operations;
    }

    /**
     * Prints the details of a MutateGoogleAdsResponse. Parses the "response" oneof field name and
     * uses it to extract the new entity's name and resource name.
     *
     * @param MutateGoogleAdsResponse $mutateGoogleAdsResponse the mutate Google Ads response
     */
    private static function printResponseDetails(
        MutateGoogleAdsResponse $mutateGoogleAdsResponse
    ): void {
        foreach ($mutateGoogleAdsResponse->getMutateOperationResponses() as $response) {
            /** @var MutateOperationResponse $response */
            $getter = Serializer::getGetter($response->getResponse());
            printf(
                "Created a(n) %s with '%s'.%s",
                preg_replace(
                    '/Result$/',
                    '',
                    ucfirst(Serializer::toCamelCase($response->getResponse()))
                ),
                $response->$getter()->getResourceName(),
                PHP_EOL
            );
        }
    }
}

AddPerformanceMaxForTravelGoalsCampaign::main();

      

питон

#!/usr/bin/env python
# Copyright 2023 Google LLC
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
#     https://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
"""This example shows how to create a Performance Max for travel goals campaign.

It also uses TravelAssetSuggestionService to fetch suggested assets for creating
an asset group. In case there are not enough assets for the asset group
(required by Performance Max), this example will create more assets to fulfill
the requirements.

For more information about Performance Max campaigns, see
https://developers.google.com/google-ads/api/docs/performance-max/overview.

Prerequisites:
- You must have at least one conversion action in the account. For more about
  conversion actions, see
  https://developers.google.com/google-ads/api/docs/conversions/overview#conversion_actions.

Notes:
- This example uses the default customer conversion goals. For an example of
  setting campaign-specific conversion goals, see
  shopping_ads/add_performance_max_retail_campaign.py.
- To learn how to create asset group signals, see
  advanced_operations/add_performance_max_campaign.py.
"""


import argparse
import sys
from timeit import default_timer

from examples.utils.example_helpers import get_printable_datetime
from google.ads.googleads.client import GoogleAdsClient
from google.ads.googleads.errors import GoogleAdsException

import requests

MIN_REQUIRED_TEXT_ASSET_COUNTS = {
    "HEADLINE": 3,
    "LONG_HEADLINE": 1,
    "DESCRIPTION": 2,
    "BUSINESS_NAME": 1,
}

MIN_REQUIRED_IMAGE_ASSET_COUNTS = {
    "MARKETING_IMAGE": 1,
    "SQUARE_MARKETING_IMAGE": 1,
    "LOGO": 1,
}

DEFAULT_TEXT_ASSETS_INFO = {
    "HEADLINE": ["Hotel", "Travel Reviews", "Book travel"],
    "LONG_HEADLINE": ["Travel the World"],
    "DESCRIPTION": [
        "Great deal for your beloved hotel",
        "Best rate guaranteed",
    ],
    "BUSINESS_NAME": ["Interplanetary Cruises"],
}

DEFAULT_IMAGE_ASSETS_INFO = {
    "MARKETING_IMAGE": ["https://gaagl.page.link/Eit5"],
    "SQUARE_MARKETING_IMAGE": ["https://gaagl.page.link/bjYi"],
    "LOGO": ["https://gaagl.page.link/bjYi"],
}

# We specify temporary IDs that are specific to a single mutate request.
# Temporary IDs are always negative and unique within one mutate request.

# For further details, see:
# https://developers.google.com/google-ads/api/docs/mutating/best-practices

# These temporary IDs are global because they are used throughout the module.
ASSET_TEMPORARY_ID = -1
BUDGET_TEMPORARY_ID = -2
CAMPAIGN_TEMPORARY_ID = -3
ASSET_GROUP_TEMPORARY_ID = -4

# There are also entities that will be created in the same request but do not
# need to be fixed temporary IDs because they are referenced only once.
next_temp_id = ASSET_GROUP_TEMPORARY_ID - 1


def main(client, customer_id, place_id):
    """The main method that creates all necessary entities for the example.

    Args:
        client: an initialized GoogleAdsClient instance.
        customer_id: a client customer ID.
        place_id: a place ID identifying a place in the Google Places database.
    """
    # Gets hotel asset suggestion using the TravelAssetSuggestionService.
    hotel_asset_suggestion = get_hotel_asset_suggestion(
        client, customer_id, place_id
    )

    # Performance Max campaigns require that repeated assets such as headlines
    # and descriptions be created before the campaign. For the list of required
    # assets for a Performance Max campaign, see:
    # https://developers.google.com/google-ads/api/docs/performance-max/assets.

    # This step is the same for all types of Performance Max campaigns.

    # Creates the headlines using the hotel asset suggestion.
    headline_asset_resource_names = create_multiple_text_assets(
        client,
        customer_id,
        client.enums.AssetFieldTypeEnum.HEADLINE,
        hotel_asset_suggestion,
    )

    # Creates the descriptions using the hotel asset suggestion.
    description_asset_resource_names = create_multiple_text_assets(
        client,
        customer_id,
        client.enums.AssetFieldTypeEnum.DESCRIPTION,
        hotel_asset_suggestion,
    )

    # Creates a hotel property asset set, which will be used later to link with
    # a newly created campaign.
    hotel_property_asset_set_resource_name = create_hotel_asset_set(
        client, customer_id
    )

    # Creates a hotel property asset and links it with the previously created
    # hotel property asset set. This asset will also be linked to an asset group
    # in the later steps. In a real-world scenario, you'd need to create assets
    # for each of your hotel properties. We use one hotel property here for
    # simplicity. Both asset and asset set need to be created before creating a
    # campaign, so we cannot bundle them with other mutate operations below.
    hotel_property_asset_resource_name = create_hotel_asset(
        client, customer_id, place_id, hotel_property_asset_set_resource_name
    )

    # It's important to create the below entities in this order because they
    # depend on each other.
    # The below methods create and return mutate operations that we later
    # provide to the GoogleAdsService.Mutate method in order to create the
    # entities in a single request. Since the entities for a Performance Max
    # campaign are closely tied to one-another, it's considered a best practice
    # to create them in a single Mutate request so they all complete
    # successfully or fail entirely, leaving no orphaned entities. See:
    # https://developers.google.com/google-ads/api/docs/mutating/overview.
    campaign_budget_operation = create_campaign_budget_operation(
        client, customer_id
    )
    campaign_operation = create_campaign_operation(
        client, customer_id, hotel_property_asset_set_resource_name
    )
    asset_group_operations = create_asset_group_operations(
        client,
        customer_id,
        hotel_property_asset_resource_name,
        headline_asset_resource_names,
        description_asset_resource_names,
        hotel_asset_suggestion,
    )

    # Issues a mutate request to create everything.
    googleads_service = client.get_service("GoogleAdsService")
    response = googleads_service.mutate(
        customer_id=customer_id,
        mutate_operations=[
            campaign_budget_operation,
            campaign_operation,
            *asset_group_operations,
        ],
    )

    print(
        "Created the following entities for a campaign budget, a campaign, and "
        "an asset group for Performance Max for travel goals:"
    )

    print_response_details(response)


def get_hotel_asset_suggestion(client, customer_id, place_id):
    """Returns hotel asset suggestion from TravelAssetsSuggestionService.

    Args:
        client: an initialized GoogleAdsClient instance.
        customer_id: a client customer ID.
        place_id: a place ID identifying a place in the Google Places database.
    """
    request = client.get_type("SuggestTravelAssetsRequest")
    request.customer_id = customer_id
    # Uses 'en-US' as an example. It can be any language specifications in
    # BCP 47 format.
    request.language_option = "en-US"
    # In this example we only use a single place ID for the purpose of
    # demonstration, but it's possible to append more than one here if needed.
    request.place_id.append(place_id)
    # Send a request to suggest assets to be created as an asset group for the
    # Performance Max for travel goals campaign.
    travel_asset_suggestion_service = client.get_service(
        "TravelAssetSuggestionService"
    )
    response = travel_asset_suggestion_service.suggest_travel_assets(
        request=request
    )
    print(f"Fetched a hotel asset suggestion for the place ID: '{place_id}'.")

    # Since we sent a single operation in the request, it's guaranteed that
    # there will only be a single item in the response.
    return response.hotel_asset_suggestions[0]


def create_multiple_text_assets(
    client, customer_id, asset_field_type, hotel_asset_suggestion
):
    """Creates multiple text assets and returns the list of resource names.

    The hotel asset suggestion is used to create a text asset first. If the
    number of created text assets is still fewer than the minimum required
    number of assets of the specified asset field type, adds more text assets to
    fulfill the requirement.

    Args:
        client: an initialized GoogleAdsClient instance.
        customer_id: a client customer ID.
        asset_field_type: the asset field type enum that the new assets will be
            created as.
        hotel_asset_suggestion: the hotel asset suggestion.

    Returns:
        a list of asset resource names.
    """
    # We use the GoogleAdService to create multiple text assets in a single
    # request. First, adds all the text assets of the specified asset field
    # type.
    operations = []
    success_status = client.enums.HotelAssetSuggestionStatusEnum.SUCCESS

    if hotel_asset_suggestion.status == success_status:
        for text_asset in hotel_asset_suggestion.text_assets:
            # If the suggested text asset is not of the type specified, then
            # we skip it and move on to the next text asset.
            if text_asset.asset_field_type != asset_field_type:
                continue

            # If the suggested text asset is of the type specified, then we
            # build a mutate operation that creates a new text asset using
            # the text from the suggestion.
            operation = client.get_type("MutateOperation")
            asset = operation.asset_operation.create
            asset.text_asset.text = text_asset.text
            operations.append(operation)

    # If the current number of operations is still less than the minimum
    # required assets for the asset field type, add more operations using the
    # default texts.
    minimum_required_text_asset_count = MIN_REQUIRED_TEXT_ASSET_COUNTS[
        asset_field_type.name
    ]

    if len(operations) < minimum_required_text_asset_count:
        # Calculate the number of additional operations that need to be created.
        difference = minimum_required_text_asset_count - len(operations)
        # Retrieve the list of default texts for the given asset type.
        default_texts = DEFAULT_TEXT_ASSETS_INFO[asset_field_type.name]
        for i in range(difference):
            operation = client.get_type("MutateOperation")
            asset = operation.asset_operation.create
            asset.text_asset.text = default_texts[i]
            operations.append(operation)

    # Issues a mutate request to add all assets.
    googleads_service = client.get_service("GoogleAdsService")
    response = googleads_service.mutate(
        customer_id=customer_id, mutate_operations=operations
    )

    print(
        "The following assets were created for the asset field type "
        f"'{asset_field_type.name}'"
    )
    print_response_details(response)

    return [
        result.asset_result.resource_name
        for result in response.mutate_operation_responses
    ]


def create_hotel_asset_set(client, customer_id):
    """Creates a hotel property asset set.

    Args:
        client: an initialized GoogleAdsClient instance.
        customer_id: a client customer ID.

    Returns:
        the created hotel property asset set's resource name.
    """
    # Creates an asset set operation for a hotel property asset set.
    operation = client.get_type("AssetSetOperation")
    # Creates a hotel property asset set.
    asset_set = operation.create
    asset_set.name = f"My hotel property asset set #{get_printable_datetime()}"
    asset_set.type_ = client.enums.AssetSetTypeEnum.HOTEL_PROPERTY

    # Issues a mutate request to add a hotel asset set.
    asset_set_service = client.get_service("AssetSetService")
    response = asset_set_service.mutate_asset_sets(
        customer_id=customer_id, operations=[operation]
    )
    resource_name = response.results[0].resource_name
    print(f"Created an asset set with resource name: '{resource_name}'")

    return resource_name


def create_hotel_asset(client, customer_id, place_id, asset_set_resource_name):
    """Creates a hotel property asset using the specified place ID.

    The place ID must belong to a hotel property. Then, links it to the
    specified asset set.

    Args:
        client: an initialized GoogleAdsClient instance.
        customer_id: a client customer ID.
        place_id: a place ID identifying a place in the Google Places database.
        asset_set_resource_name: an asset set resource name

    Returns:
        the created hotel property asset's resource name.
    """
    # We use the GoogleAdService to create an asset and asset set asset in a
    # single request.
    googleads_service = client.get_service("GoogleAdsService")
    asset_resource_name = googleads_service.asset_path(
        customer_id, ASSET_TEMPORARY_ID
    )

    # Creates a mutate operation for a hotel property asset.
    asset_mutate_operation = client.get_type("MutateOperation")
    # Creates a hotel property asset.
    asset = asset_mutate_operation.asset_operation.create
    asset.resource_name = asset_resource_name
    # Creates a hotel property asset for the place ID.
    asset.hotel_property_asset.place_id = place_id

    # Creates a mutate operation for an asset set asset.
    asset_set_asset_mutate_operation = client.get_type("MutateOperation")
    # Creates an asset set asset.
    asset_set_asset = (
        asset_set_asset_mutate_operation.asset_set_asset_operation.create
    )
    asset_set_asset.asset = asset_resource_name
    asset_set_asset.asset_set = asset_set_resource_name

    # Issues a mutate request to create all entities.
    response = googleads_service.mutate(
        customer_id=customer_id,
        mutate_operations=[
            asset_mutate_operation,
            asset_set_asset_mutate_operation,
        ],
    )
    print("Created the following entities for the hotel asset:")
    print_response_details(response)

    return response.mutate_operation_responses[0].asset_result.resource_name


def create_campaign_budget_operation(client, customer_id):
    """Creates a mutate operation that creates a new campaign budget.

    A temporary ID will be assigned to this campaign budget so that it can be
    referenced by other objects being created in the same mutate request.

    Args:
        client: an initialized GoogleAdsClient instance.
        customer_id: a client customer ID.

    Returns:
        a MutateOperation message that creates a new campaign budget
    """
    googleads_service = client.get_service("GoogleAdsService")
    # Creates a mutate operation that creates a campaign budget.
    operation = client.get_type("MutateOperation")
    budget = operation.campaign_budget_operation.create
    # Sets a temporary ID in the budget's resource name so it can be referenced
    # by the campaign in later steps.
    budget.resource_name = googleads_service.campaign_budget_path(
        customer_id, BUDGET_TEMPORARY_ID
    )
    budget.name = (
        "Performance Max for travel goals campaign budget "
        f"#{get_printable_datetime()}"
    )
    # The budget period already defaults to DAILY.
    budget.amount_micros = 50000000
    budget.delivery_method = client.enums.BudgetDeliveryMethodEnum.STANDARD
    # A Performance Max campaign cannot use a shared campaign budget.
    budget.explicitly_shared = False

    return operation


def create_campaign_operation(
    client, customer_id, hotel_property_asset_set_resource_name
):
    """Creates a mutate operation that creates a new Performance Max campaign.

    Links the specified hotel property asset set to this campaign.

    A temporary ID will be assigned to this campaign budget so that it can be
    referenced by other objects being created in the same mutate request.

    Args:
        client: an initialized GoogleAdsClient instance.
        customer_id: a client customer ID.
        hotel_property_asset_set_resource_name: the resource name for a hotel
            property asset set.

    Returns:
        a MutateOperation message that creates a new Performance Max campaign.
    """
    googleads_service = client.get_service("GoogleAdsService")
    # Creates a mutate operation that creates a campaign.
    operation = client.get_type("MutateOperation")
    campaign = operation.campaign_operation.create
    campaign.name = (
        f"Performance Max for travel goals campaign #{get_printable_datetime}"
    )
    # Assigns the resource name with a temporary ID.
    campaign.resource_name = googleads_service.campaign_path(
        customer_id, CAMPAIGN_TEMPORARY_ID
    )
    # Sets the budget using the given budget resource name.
    campaign.campaign_budget = googleads_service.campaign_budget_path(
        customer_id, BUDGET_TEMPORARY_ID
    )
    # The campaign is the only entity in the mutate request that should have its
    # status set.
    # Recommendation: Set the campaign to PAUSED when creating it to prevent
    # the ads from immediately serving.
    campaign.status = client.enums.CampaignStatusEnum.PAUSED
    # Performance Max campaigns have an advertising_channel_type of
    # PERFORMANCE_MAX. The advertising_channel_sub_type should not be set.
    campaign.advertising_channel_type = (
        client.enums.AdvertisingChannelTypeEnum.PERFORMANCE_MAX
    )
    # To create a Performance Max for travel goals campaign, you need to set
    # the `hotel_property_asset_set` field.
    campaign.hotel_property_asset_set = hotel_property_asset_set_resource_name
    # Bidding strategy must be set directly on the campaign.
    # Setting a portfolio bidding strategy by resource name is not supported.
    # Max Conversion and Maximize Conversion Value are the only strategies
    # supported for Performance Max campaigns.
    # An optional ROAS (Return on Advertising Spend) can be set for
    # maximize_conversion_value. The ROAS value must be specified as a ratio in
    # the API. It is calculated by dividing "total value" by "total spend".
    # For more information on Maximize Conversion Value, see the support
    # article: https://support.google.com/google-ads/answer/7684216.
    # A target_roas of 3.5 corresponds to a 350% return on ad spend.
    campaign.maximize_conversion_value.target_roas = 3.5

    return operation


def create_asset_group_operations(
    client,
    customer_id,
    hotel_property_asset_resource_name,
    headline_asset_resource_names,
    description_asset_resource_names,
    hotel_asset_suggestion,
):
    """Creates a list of mutate operations that create a new asset group.

    The asset group is composed of suggested assets. In case the number of
    suggested assets is not enough for the requirements, it will create more
    assets to meet the requirement.

    For the list of required assets for a Performance Max campaign, see
    https://developers.google.com/google-ads/api/docs/performance-max/assets.

    Args:
        client: an initialized GoogleAdsClient instance.
        customer_id: a client customer ID.
        hotel_property_asset_resource_name: the hotel property asset resource
            name that will be used to create an asset group.
        headline_asset_resource_names: a list of headline asset resource names.
        description_asset_resource_names: a list of description asset resource
            names.
        hotel_asset_suggestion: the hotel asset suggestion.

    Returns:
        a list of mutate operations that create the asset group.
    """
    global next_temp_id
    googleads_service = client.get_service("GoogleAdsService")
    operations = []

    # Creates a new mutate operation that creates an asset group using suggested
    # information when available.
    success_status = client.enums.HotelAssetSuggestionStatusEnum.SUCCESS
    if hotel_asset_suggestion.status == success_status:
        asset_group_name = hotel_asset_suggestion.hotel_name
        asset_group_final_urls = [hotel_asset_suggestion.final_url]
    else:
        asset_group_name = (
            "Performance Max for travel goals asset group "
            f"#{get_printable_datetime()}"
        )
        asset_group_final_urls = ["http://www.example.com"]

    asset_group_resource_name = googleads_service.asset_group_path(
        customer_id, ASSET_GROUP_TEMPORARY_ID
    )
    asset_group_mutate_operation = client.get_type("MutateOperation")
    asset_group = asset_group_mutate_operation.asset_group_operation.create
    asset_group.resource_name = asset_group_resource_name
    asset_group.name = asset_group_name
    asset_group.campaign = googleads_service.campaign_path(
        customer_id, CAMPAIGN_TEMPORARY_ID
    )
    asset_group.final_urls = asset_group_final_urls
    asset_group.status = client.enums.AssetGroupStatusEnum.PAUSED
    # Append the asset group operation to the list of operations.
    operations.append(asset_group_mutate_operation)

    # An asset group is linked to an asset by creating a new asset group asset
    # and providing:
    # -  the resource name of the asset group
    # -  the resource name of the asset
    # -  the field_type of the asset in this asset group

    # To learn more about asset groups, see
    # https://developers.google.com/google-ads/api/docs/performance-max/asset-groups.

    # Headline and description assets were created at the first step of this
    # example. So, we just need to link them with the created asset group.

    # Links the headline assets to the asset group.
    for resource_name in headline_asset_resource_names:
        headline_operation = client.get_type("MutateOperation")
        asset_group_asset = (
            headline_operation.asset_group_asset_operation.create
        )
        asset_group_asset.asset = resource_name
        asset_group_asset.asset_group = asset_group_resource_name
        asset_group_asset.field_type = client.enums.AssetFieldTypeEnum.HEADLINE
        operations.append(headline_operation)

    # Links the description assets to the asset group.
    for resource_name in description_asset_resource_names:
        description_operation = client.get_type("MutateOperation")
        asset_group_asset = (
            description_operation.asset_group_asset_operation.create
        )
        asset_group_asset.asset = resource_name
        asset_group_asset.asset_group = asset_group_resource_name
        asset_group_asset.field_type = (
            client.enums.AssetFieldTypeEnum.DESCRIPTION
        )
        operations.append(description_operation)

    # Link the previously created hotel property asset to the asset group. If
    # there are multiple assets, these steps to create a new operation need to
    # be performed for each asset.
    asset_group_asset_mutate_operation = client.get_type("MutateOperation")
    asset_group_asset = (
        asset_group_asset_mutate_operation.asset_group_asset_operation.create
    )
    asset_group_asset.asset = hotel_property_asset_resource_name
    asset_group_asset.asset_group = asset_group_resource_name
    asset_group_asset.field_type = (
        client.enums.AssetFieldTypeEnum.HOTEL_PROPERTY
    )
    operations.append(asset_group_asset_mutate_operation)

    # Creates the rest of required text assets and link them to the asset group.
    operations.extend(
        create_text_assets_for_asset_group(
            client, customer_id, hotel_asset_suggestion
        )
    )

    # Creates the image assets and link them to the asset group. Some optional
    # image assets suggested by the TravelAssetSuggestionService might be
    # created too.
    operations.extend(
        create_image_assets_for_asset_group(
            client, customer_id, hotel_asset_suggestion
        )
    )

    if hotel_asset_suggestion.status == success_status:
        # Creates a new mutate operation for a suggested call-to-action asset
        # and link it to the asset group.
        asset_mutate_operation = client.get_type("MutateOperation")
        asset = asset_mutate_operation.asset_operation.create
        asset.resource_name = googleads_service.asset_path(customer_id, next_temp_id)
        asset.name = (
            f"Suggested call-to-action asset #{get_printable_datetime()}"
        )
        asset.call_to_action_asset.call_to_action = (
            hotel_asset_suggestion.call_to_action
        )

        # Creates a new mutate operation for a call-to-action asset group.
        asset_group_asset_mutate_operation = client.get_type("MutateOperation")
        asset_group_asset = (
            asset_group_asset_mutate_operation.asset_group_asset_operation.create
        )
        asset_group_asset.asset = googleads_service.asset_path(customer_id, next_temp_id)
        asset_group_asset.asset_group = asset_group_resource_name
        asset_group_asset.field_type = (
            client.enums.AssetFieldTypeEnum.CALL_TO_ACTION_SELECTION
        )

        next_temp_id -= 1

    return operations


def create_text_assets_for_asset_group(
    client, customer_id, hotel_asset_suggestion
):
    """Creates text assets for an asset group using the given hotel text assets.

    It adds more text assets to fulfill the requirements if the suggested hotel
    text assets are not enough.

    Args:
        client: an initialized GoogleAdsClient instance.
        customer_id: a client customer ID.
        hotel_asset_suggestion: the hotel asset suggestion.

    Returns:
        a list of mutate operations that create text assets.
    """
    operations = []

    # Creates mutate operations for the suggested text assets except for
    # headlines and descriptions, which were created previously.
    required_text_asset_counts = {
        key: 0 for key in MIN_REQUIRED_TEXT_ASSET_COUNTS.keys()
    }
    success_status = client.enums.HotelAssetSuggestionStatusEnum.SUCCESS
    if hotel_asset_suggestion.status == success_status:
        for text_asset in hotel_asset_suggestion.text_assets:
            text = text_asset.text
            asset_field_type = text_asset.asset_field_type

            if asset_field_type.name in ("HEADLINE", "DESCRIPTION"):
                # Headlines and descriptions were already created at the first
                # step of this code example
                continue

            print(
                f"A test asset with text {text} is suggested for the asset "
                f"field type `{asset_field_type.name}`"
            )

            operations.extend(
                create_text_asset_and_asset_group_asset_operations(
                    client, customer_id, text, asset_field_type
                )
            )

            required_text_asset_counts[asset_field_type.name] += 1

    # Adds more text assets to fulfill the requirements.
    for field_type_name, min_count in MIN_REQUIRED_TEXT_ASSET_COUNTS.items():
        if field_type_name in ("HEADLINE", "DESCRIPTION"):
            # Headlines and descriptions were already created at the first step
            # of this code example.
            continue

        difference = min_count - required_text_asset_counts[field_type_name]
        if difference > 0:
            for i in range(difference):
                default_text = DEFAULT_TEXT_ASSETS_INFO[field_type_name][i]
                field_type_enum = client.enums.AssetFieldTypeEnum[
                    field_type_name
                ]

                print(
                    f"A default text {default_text} is used to create a "
                    f"text asset for the asset field type {field_type_name}"
                )

                operations.extend(
                    create_text_asset_and_asset_group_asset_operations(
                        client, customer_id, default_text, field_type_enum
                    )
                )

    return operations


def create_text_asset_and_asset_group_asset_operations(
    client, customer_id, text, field_type_enum
):
    """Creates a list of mutate operations that create a new linked text asset.

    Args:
        client: an initialized GoogleAdsClient instance.
        customer_id: a client customer ID.
        text: the text of an asset to be created.
        field_type_enum: the field type enum of a new asset in the asset group
            asset.

    Returns:
        a list of mutate operations that create a new linked text asset.
    """
    global next_temp_id
    googleads_service = client.get_service("GoogleAdsService")
    operations = []

    # Creates a new mutate operation that creates a text asset.
    asset_mutate_operation = client.get_type("MutateOperation")
    asset = asset_mutate_operation.asset_operation.create
    asset.resource_name = googleads_service.asset_path(
        customer_id, next_temp_id
    )
    asset.text_asset.text = text
    operations.append(asset_mutate_operation)

    # Creates an asset group asset operation to link the asset to the asset
    # group.
    asset_group_asset_mutate_operation = client.get_type("MutateOperation")
    asset_group_asset = (
        asset_group_asset_mutate_operation.asset_group_asset_operation.create
    )
    asset_group_asset.asset = googleads_service.asset_path(
        customer_id, next_temp_id
    )
    asset_group_asset.asset_group = googleads_service.asset_group_path(
        customer_id, ASSET_GROUP_TEMPORARY_ID
    )
    asset_group_asset.field_type = field_type_enum
    operations.append(asset_group_asset_mutate_operation)

    next_temp_id -= 1

    return operations


def create_image_assets_for_asset_group(
    client, customer_id, hotel_asset_suggestion
):
    """Creates image assets for an asset group with the given hotel suggestions.

    It adds more image assets to fulfill the requirements if the suggested hotel
    image assets are not enough.

    Args:
        client: an initialized GoogleAdsClient instance.
        customer_id: a client customer ID.
        hotel_asset_suggestion: the hotel asset suggestion.

    Returns:
        a list of mutate operations that create image assets.
    """
    operations = []

    # Creates mutate operations for the suggested image assets.
    required_image_asset_counts = {
        key: 0 for key in MIN_REQUIRED_IMAGE_ASSET_COUNTS.keys()
    }
    for image_asset in hotel_asset_suggestion.image_assets:
        url = image_asset.uri
        field_type_enum = image_asset.asset_field_type
        name = f"Suggested image asset #{get_printable_datetime()}"

        print(
            f"An image asset with URL '{url}' is suggested for the asset field "
            f"type '{field_type_enum.name}'"
        )

        operations.extend(
            create_image_asset_and_image_asset_group_asset_operations(
                client, customer_id, url, field_type_enum, name
            )
        )

        # Keeps track of only required image assets. The
        # TravelAssetSuggestionService may sometimes suggest optional image
        # assets.
        if field_type_enum.name in required_image_asset_counts:
            required_image_asset_counts[field_type_enum.name] += 1


    # Adds more image assets to fulfill the requirements.
    for field_type_name, min_count in MIN_REQUIRED_IMAGE_ASSET_COUNTS.items():
        difference = min_count - required_image_asset_counts[field_type_name]
        if difference > 0:
            for i in range(difference):
                default_url = DEFAULT_IMAGE_ASSETS_INFO[field_type_name][i]
                name = f"{field_type_name.lower()} {get_printable_datetime()}"
                field_type_enum = client.enums.AssetFieldTypeEnum[
                    field_type_name
                ]

                print(
                    f"A default image URL {default_url} is used to create an "
                    f"image asset for the asset field type {field_type_name}"
                )

                operations.extend(
                    create_image_asset_and_image_asset_group_asset_operations(
                        client, customer_id, default_url, field_type_enum, name
                    )
                )

    return operations


def create_image_asset_and_image_asset_group_asset_operations(
    client, customer_id, url, field_type_enum, asset_name
):
    """Creates a list of mutate operations that create a new linked image asset.

    Args:
        client: an initialized GoogleAdsClient instance.
        customer_id: a client customer ID.
        url: the URL of the image to be retrieved and put into an asset.
        field_type_enum: the field type enum of the new asset in the asset group
            asset.
        asset_name: the asset name.

    Returns:
        a list of mutate operations that create a new linked image asset.
    """
    global next_temp_id
    googleads_service = client.get_service("GoogleAdsService")
    operations = []

    # Creates a new mutate operation that creates an image asset.
    asset_mutate_operation = client.get_type("MutateOperation")
    asset = asset_mutate_operation.asset_operation.create
    asset.resource_name = googleads_service.asset_path(
        customer_id, next_temp_id
    )
    # Provide a unique friendly name to identify your asset. When there is an
    # existing image asset with the same content but a different name, the new
    # name will be dropped silently.
    asset.name = asset_name
    asset.image_asset.data = requests.get(url).content
    operations.append(asset_mutate_operation)

    # Creates an asset group asset operation to link the asset to the asset
    # group.
    asset_group_asset_mutate_operation = client.get_type("MutateOperation")
    asset_group_asset = (
        asset_group_asset_mutate_operation.asset_group_asset_operation.create
    )
    asset_group_asset.asset = googleads_service.asset_path(
        customer_id, next_temp_id
    )
    asset_group_asset.asset_group = googleads_service.asset_group_path(
        customer_id, ASSET_GROUP_TEMPORARY_ID
    )
    asset_group_asset.field_type = field_type_enum
    operations.append(asset_group_asset_mutate_operation)

    next_temp_id -= 1

    return operations


def print_response_details(mutate_response):
    """Prints the details of a MutateGoogleAdsResponse message.

    Parses the "response" oneof field name and uses it to extract the new
    entity's name and resource name.

    Args:
        mutate_response: a MutateGoogleAdsResponse message.
    """
    for result in mutate_response.mutate_operation_responses:
        resource_type = "unrecognized"
        resource_name = "not found"

        if "asset_result" in result:
            resource_type = "Asset"
            resource_name = result.asset_result.resource_name
        elif "asset_set_asset_result" in result:
            resource_type = "AssetSetAsset"
            resource_name = result.asset_set_asset_result.resource_name
        elif "campaign_budget_result" in result:
            resource_type = "CampaignBudget"
            resource_name = result.campaign_budget_result.resource_name
        elif "campaign_result" in result:
            resource_type = "Campaign"
            resource_name = result.campaign_result.resource_name
        elif "asset_group_result" in result:
            resource_type = "AssetGroup"
            resource_name = result.asset_group_result.resource_name
        elif "asset_group_asset_result" in result:
            resource_type = "AssetGroupAsset"
            resource_name = result.asset_group_asset_result.resource_name

        print(
            f"Created a(n) {resource_type} with "
            f"resource_name: '{resource_name}'."
        )


if __name__ == "__main__":
    # GoogleAdsClient will read the google-ads.yaml configuration file in the
    # home directory if none is specified.
    googleads_client = GoogleAdsClient.load_from_storage(version="v13")

    parser = argparse.ArgumentParser(
        description=("Creates a Performance Max for travel goals campaign.")
    )
    # The following argument(s) should be provided to run the example.
    parser.add_argument(
        "-c",
        "--customer_id",
        type=str,
        required=True,
        help="The Google Ads customer ID.",
    )
    parser.add_argument(
        "-p",
        "--place_id",
        type=str,
        required=True,
        help=(
            "Sets a place ID that uniquely identifies a place in the Google "
            "Places database. The provided place ID must belong to a hotel "
            "property. To learn more, see: "
            "https://developers.google.com/places/web-service/place-id "
        ),
    )

    args = parser.parse_args()

    try:
        main(googleads_client, args.customer_id, args.place_id)
    except GoogleAdsException as ex:
        print(
            f'Request with ID "{ex.request_id}" failed with status '
            f'"{ex.error.code().name}" and includes the following errors:'
        )
        for error in ex.failure.errors:
            print(f'Error with message "{error.message}".')
            if error.location:
                for field_path_element in error.location.field_path_elements:
                    print(f"\t\tOn field: {field_path_element.field_name}")
        sys.exit(1)

      

Рубин

This example is not yet available in Ruby; you can take a look at the other languages.
    

Перл

#!/usr/bin/perl -w
#
# Copyright 2023, Google LLC
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
#     http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
#
# This example shows how to create a Performance Max for travel goals campaign. It also uses
# TravelAssetSuggestionService to fetch suggested assets for creating an asset group. In case
# there are not enough assets for the asset group (required by Performance Max), this example will
# create more assets to fulfill the requirements.
#
# For more information about Performance Max campaigns, see
# https://developers.google.com/google-ads/api/docs/performance-max/overview.
#
# Prerequisites:
# - You must have at least one conversion action in the account. For more about conversion actions,
# see https://developers.google.com/google-ads/api/docs/conversions/overview#conversion_actions.
#
# Notes:
# - This example uses the default customer conversion goals. For an example of setting
#   campaign-specific conversion goals, see shopping_ads/add_performance_max_retail_campaign.pl.
# - To learn how to create asset group signals, see
#   advanced_operations/add_performance_max_campaign.pl.

use strict;
use warnings;
use utf8;

use FindBin qw($Bin);
use lib "$Bin/../../lib";
use Google::Ads::GoogleAds::Client;
use Google::Ads::GoogleAds::Utils::GoogleAdsHelper;
use Google::Ads::GoogleAds::Utils::MediaUtils;
use Google::Ads::GoogleAds::V13::Resources::CampaignBudget;
use Google::Ads::GoogleAds::V13::Resources::Campaign;
use Google::Ads::GoogleAds::V13::Resources::Asset;
use Google::Ads::GoogleAds::V13::Resources::AssetGroup;
use Google::Ads::GoogleAds::V13::Resources::AssetGroupAsset;
use Google::Ads::GoogleAds::V13::Resources::AssetSet;
use Google::Ads::GoogleAds::V13::Resources::AssetSetAsset;
use Google::Ads::GoogleAds::V13::Common::CallToActionAsset;
use Google::Ads::GoogleAds::V13::Common::MaximizeConversionValue;
use Google::Ads::GoogleAds::V13::Common::TextAsset;
use Google::Ads::GoogleAds::V13::Common::HotelPropertyAsset;
use Google::Ads::GoogleAds::V13::Common::ImageAsset;
use Google::Ads::GoogleAds::V13::Enums::BudgetDeliveryMethodEnum qw(STANDARD);
use Google::Ads::GoogleAds::V13::Enums::CampaignStatusEnum;
use Google::Ads::GoogleAds::V13::Enums::AdvertisingChannelTypeEnum
  qw(PERFORMANCE_MAX);
use Google::Ads::GoogleAds::V13::Enums::AssetGroupStatusEnum;
use Google::Ads::GoogleAds::V13::Enums::AssetFieldTypeEnum
  qw(HEADLINE DESCRIPTION LONG_HEADLINE BUSINESS_NAME LOGO MARKETING_IMAGE SQUARE_MARKETING_IMAGE HOTEL_PROPERTY CALL_TO_ACTION_SELECTION);
use Google::Ads::GoogleAds::V13::Enums::HotelAssetSuggestionStatusEnum
  qw(SUCCESS);
use Google::Ads::GoogleAds::V13::Services::GoogleAdsService::MutateOperation;
use
  Google::Ads::GoogleAds::V13::Services::CampaignBudgetService::CampaignBudgetOperation;
use Google::Ads::GoogleAds::V13::Services::CampaignService::CampaignOperation;
use Google::Ads::GoogleAds::V13::Services::AssetService::AssetOperation;
use
  Google::Ads::GoogleAds::V13::Services::AssetGroupService::AssetGroupOperation;
use
  Google::Ads::GoogleAds::V13::Services::AssetGroupAssetService::AssetGroupAssetOperation;
use Google::Ads::GoogleAds::V13::Services::AssetSetService::AssetSetOperation;
use
  Google::Ads::GoogleAds::V13::Services::AssetSetAssetService::AssetSetAssetOperation;
use Google::Ads::GoogleAds::V13::Utils::ResourceNames;

use Getopt::Long qw(:config auto_help);
use Pod::Usage;
use Cwd          qw(abs_path);
use Data::Uniqid qw(uniqid);

# Minimum requirements of assets required in a Performance Max asset group.
# See https://developers.google.com/google-ads/api/docs/performance-max/assets for details.
my $min_required_text_asset_counts = {
  HEADLINE      => 3,
  LONG_HEADLINE => 1,
  DESCRIPTION   => 2,
  BUSINESS_NAME => 1,
};

my $min_required_image_asset_counts = {
  MARKETING_IMAGE        => 1,
  SQUARE_MARKETING_IMAGE => 1,
  LOGO                   => 1,
};

# Texts and URLs used to create text and image assets when the TravelAssetSuggestionService
# doesn't return enough assets required for creating an asset group.
my $default_text_assets_info = {
  HEADLINE      => ['Hotel', 'Travel Reviews', 'Book travel'],
  LONG_HEADLINE => ['Travel the World'],
  DESCRIPTION => ['Great deal for your beloved hotel', 'Best rate guaranteed',],
  BUSINESS_NAME => ['Interplanetary Cruises'],
};

my $default_image_assets_info = {
  MARKETING_IMAGE        => ['https://gaagl.page.link/Eit5'],
  SQUARE_MARKETING_IMAGE => ['https://gaagl.page.link/bjYi'],
  LOGO                   => ['https://gaagl.page.link/bjYi'],
};

# We specify temporary IDs that are specific to a single mutate request.
# Temporary IDs are always negative and unique within one mutate request.
#
# See https://developers.google.com/google-ads/api/docs/mutating/best-practices
# for further details.
#
# These temporary IDs are fixed because they are used in multiple places.
use constant ASSET_TEMPORARY_ID       => -1;
use constant BUDGET_TEMPORARY_ID      => -2;
use constant CAMPAIGN_TEMPORARY_ID    => -3;
use constant ASSET_GROUP_TEMPORARY_ID => -4;

# There are also entities that will be created in the same request but do not need to be fixed
# temporary IDs because they are referenced only once.
our $next_temp_id = ASSET_GROUP_TEMPORARY_ID - 1;

sub add_performance_max_for_travel_goals_campaign {
  my ($api_client, $customer_id, $place_id) = @_;

  my $hotel_asset_suggestion =
    get_hotel_asset_suggestion($api_client, $customer_id, $place_id);

  # Performance Max campaigns require that repeated assets such as headlines
  # and descriptions be created before the campaign.
  # For the list of required assets for a Performance Max campaign, see
  # https://developers.google.com/google-ads/api/docs/performance-max/assets.
  #
  # This step is the same for any types of Performance Max campaigns.

  # Create the headlines using the hotel asset suggestion.
  my $headline_asset_resource_names =
    create_multiple_text_assets($api_client, $customer_id, HEADLINE,
    $hotel_asset_suggestion);

  my $description_asset_resource_names =
    create_multiple_text_assets($api_client, $customer_id, DESCRIPTION,
    $hotel_asset_suggestion);

  # Create a hotel property asset set, which will be used later to link with a newly created
  # campaign.
  my $hotel_property_asset_set_resource_name =
    create_hotel_asset_set($api_client, $customer_id);

  # Create a hotel property asset and link it with the previously created hotel property
  # asset set. This asset will also be linked to an asset group in the later steps.
  # In the real-world scenario, you'd need to create many assets for all your hotel
  # properties. We use one hotel property here for simplicity.
  # Both asset and asset set need to be created before creating a campaign, so we cannot
  # bundle them with other mutate operations below.
  my $hotel_property_asset_resource_name =
    create_hotel_asset($api_client, $customer_id, $place_id,
    $hotel_property_asset_set_resource_name);

  # It's important to create the below entities in this order because they depend on
  # each other.
  # The below methods create and return mutate operations that we later provide to the
  # GoogleAdsService.Mutate method in order to create the entities in a single request.
  # Since the entities for a Performance Max campaign are closely tied to one-another, it's
  # considered a best practice to create them in a single Mutate request so they all complete
  # successfully or fail entirely, leaving no orphaned entities. See:
  # https://developers.google.com/google-ads/api/docs/mutating/overview.
  my $operations = [];
  push @$operations, create_campaign_budget_operation($customer_id);
  push @$operations,
    create_campaign_operation($customer_id,
    $hotel_property_asset_set_resource_name);
  push @$operations,
    @{
    create_asset_group_operations(
      $customer_id,                   $hotel_property_asset_resource_name,
      $headline_asset_resource_names, $description_asset_resource_names,
      $hotel_asset_suggestion
    )};

  # Issue a mutate request to create everything and print its information.
  my $mutate_google_ads_response = $api_client->GoogleAdsService()->mutate({
    customerId       => $customer_id,
    mutateOperations => $operations
  });
  printf
"Created the following entities for a campaign budget, a campaign, and an asset group"
    . " for Performance Max for travel goals:\n";
  print_response_details($mutate_google_ads_response);
}

# Return hotel asset suggestion obtained from TravelAssetsSuggestionService.
sub get_hotel_asset_suggestion {
  my ($api_client, $customer_id, $place_id) = @_;

  # Send a request to suggest assets to be created as an asset group for the Performance Max
  # for travel goals campaign.
  my $suggest_travel_assets_response =
    $api_client->TravelAssetSuggestionService()->suggest_travel_assets({
      customerId => $customer_id,
      # Uses 'en-US' as an example. It can be any language specifications in BCP 47 format.
      languageOption => 'en-US',
      # The service accepts several place IDs. We use only one here for demonstration.
      placeId => [$place_id],
    });

  printf "Fetched a hotel asset suggestion for the place ID '%s'.\n", $place_id;
  return $suggest_travel_assets_response->{hotelAssetSuggestions}[0];
}

# Create multiple text assets and returns the list of resource names. The hotel asset
# suggestion is used to create a text asset first. If the number of created text assets is
# still fewer than the minimum required number of assets of the specified asset field type,
# adds more text assets to fulfill the requirement.
sub create_multiple_text_assets {
  my ($api_client, $customer_id, $asset_field_type, $hotel_asset_suggestion) =
    @_;
  # We use the GoogleAdService to create multiple text assets in a single request.
  # First, add all the text assets of the specified asset field type.
  my $operations = [];

  if ($hotel_asset_suggestion->{status} eq SUCCESS) {
    foreach my $text_asset (@{$hotel_asset_suggestion->{textAssets}}) {
      if ($text_asset->{assetFieldType} ne $asset_field_type) {
        next;
      }
      push @$operations,
        Google::Ads::GoogleAds::V13::Services::GoogleAdsService::MutateOperation
        ->new({
          assetOperation =>
            Google::Ads::GoogleAds::V13::Services::AssetService::AssetOperation
            ->new({
              create => Google::Ads::GoogleAds::V13::Resources::Asset->new({
                  textAsset =>
                    Google::Ads::GoogleAds::V13::Common::TextAsset->new({
                      text => $text_asset->{text}})})})});
    }
  }

  # If the added assets are still less than the minimum required assets for the asset field
  # type, add more text assets using the default texts.
  my $min_count = $min_required_text_asset_counts->{$asset_field_type};
  my $num_operations_added = scalar @$operations;
  for (my $i = 0 ; $i < $min_count - $num_operations_added ; $i++) {
    my $text = $default_text_assets_info->{$asset_field_type}[$i++];
    # Creates a mutate operation for a text asset, using the default text.
    push @$operations,
      Google::Ads::GoogleAds::V13::Services::GoogleAdsService::MutateOperation
      ->new({
        assetOperation =>
          Google::Ads::GoogleAds::V13::Services::AssetService::AssetOperation->
          new({
            create => Google::Ads::GoogleAds::V13::Resources::Asset->new({
                textAsset =>
                  Google::Ads::GoogleAds::V13::Common::TextAsset->new({
                    text => $text
                  })})})});
  }

  # Issue a mutate request to add all assets.
  my $mutate_google_ads_response = $api_client->GoogleAdsService()->mutate({
    customerId       => $customer_id,
    mutateOperations => $operations
  });

  my $asset_resource_names = [];
  foreach
    my $response (@{$mutate_google_ads_response->{mutateOperationResponses}})
  {
    push @$asset_resource_names, $response->{assetResult}{resourceName};
  }
  print_response_details($mutate_google_ads_response);

  return $asset_resource_names;
}

# Create a hotel property asset set.
sub create_hotel_asset_set {
  my ($api_client, $customer_id) = @_;

  my $asset_set_operation =
    Google::Ads::GoogleAds::V13::Services::AssetSetService::AssetSetOperation->
    new({
      # Creates a hotel property asset set.
      create => Google::Ads::GoogleAds::V13::Resources::AssetSet->new({
          name => 'My Hotel propery asset set #' . uniqid(),
          type => HOTEL_PROPERTY
        })});
  # Issues a mutate request to add a hotel asset set and prints its information.
  my $response = $api_client->AssetSetService()->mutate({
      customerId => $customer_id,
      operations => [$asset_set_operation]});

  my $asset_set_resource_name = $response->{results}[0]{resourceName};
  printf "Created an asset set with resource name: '%s'.\n",
    $asset_set_resource_name;

  return $asset_set_resource_name;
}

# Create a hotel property asset using the specified place ID. The place ID must belong to
# a hotel property. Then, links it to the specified asset set.
#
# See https://developers.google.com/places/web-service/place-id to search for a hotel place ID.
sub create_hotel_asset {
  my ($api_client, $customer_id, $place_id, $asset_set_resource_name) = @_;

  # We use the GoogleAdService to create an asset and asset set asset in a single request.
  my $operations = [];
  my $asset_resource_name =
    Google::Ads::GoogleAds::V13::Utils::ResourceNames::asset($customer_id,
    ASSET_TEMPORARY_ID);

  # Create a mutate operation for a hotel property asset.
  push @$operations,
    Google::Ads::GoogleAds::V13::Services::GoogleAdsService::MutateOperation->
    new({
      assetOperation =>
        Google::Ads::GoogleAds::V13::Services::AssetService::AssetOperation->
        new({
          create => Google::Ads::GoogleAds::V13::Resources::Asset->new({
              resourceName       => $asset_resource_name,
              hotelPropertyAsset =>
                Google::Ads::GoogleAds::V13::Common::HotelPropertyAsset->new({
                  placeId => $place_id
                })})})});

  # Create a mutate operation for an asset set asset.
  push @$operations,
    Google::Ads::GoogleAds::V13::Services::GoogleAdsService::MutateOperation->
    new({
      assetSetAssetOperation =>
        Google::Ads::GoogleAds::V13::Services::AssetSetAssetService::AssetSetAssetOperation
        ->new({
          create => Google::Ads::GoogleAds::V13::Resources::AssetSetAsset->new({
              asset    => $asset_resource_name,
              assetSet => $asset_set_resource_name
            })})});

  # Issue a mutate request to create all entities.
  my $mutate_google_ads_response = $api_client->GoogleAdsService()->mutate({
    customerId       => $customer_id,
    mutateOperations => $operations
  });

  printf "Created the following entities for the hotel asset:\n";
  print_response_details($mutate_google_ads_response);

  # Return the created asset resource name, which will be used later to create an asset
  # group. Other resource names are not used later.
  return $mutate_google_ads_response->{mutateOperationResponses}[0]
    {assetResult}{resourceName};
}

# Create a mutate operation that creates a new campaign budget.
#
# A temporary ID will be assigned to this campaign budget so that it can be
# referenced by other objects being created in the same mutate request.
sub create_campaign_budget_operation {
  my ($customer_id) = @_;

  return
    Google::Ads::GoogleAds::V13::Services::GoogleAdsService::MutateOperation->
    new({
      campaignBudgetOperation =>
        Google::Ads::GoogleAds::V13::Services::CampaignBudgetService::CampaignBudgetOperation
        ->new({
          create => Google::Ads::GoogleAds::V13::Resources::CampaignBudget->new(
            {
              # Set a temporary ID in the budget's resource name so it can be
              # referenced by the campaign in later steps.
              resourceName =>
                Google::Ads::GoogleAds::V13::Utils::ResourceNames::campaign_budget(
                $customer_id, BUDGET_TEMPORARY_ID
                ),
              name => "Performance Max for travel goals campaign budget #" .
                uniqid(),
              # The budget period already defaults to DAILY.
              amountMicros   => 50000000,
              deliveryMethod => STANDARD,
              # A Performance Max campaign cannot use a shared campaign budget.
              explicitlyShared => "false",
            })})});
}

# Create a mutate operation that creates a new Performance Max campaign. Links the specified
# hotel property asset set to this campaign.
#
# A temporary ID will be assigned to this campaign so that it can be referenced by other
# objects being created in the same mutate request.
sub create_campaign_operation {
  my ($customer_id, $hotel_property_asset_set_resource_name) = @_;

  # Create a mutate operation that creates a campaign operation.
  return
    Google::Ads::GoogleAds::V13::Services::GoogleAdsService::MutateOperation->
    new({
      campaignOperation =>
        Google::Ads::GoogleAds::V13::Services::CampaignService::CampaignOperation
        ->new({
          create => Google::Ads::GoogleAds::V13::Resources::Campaign->new({
              # Assign the resource name with a temporary ID.
              resourceName =>
                Google::Ads::GoogleAds::V13::Utils::ResourceNames::campaign(
                $customer_id, CAMPAIGN_TEMPORARY_ID
                ),
              name => "Performance Max for travel goals campaign #'" . uniqid(),
              # Set the budget using the given budget resource name.
              campaignBudget =>
                Google::Ads::GoogleAds::V13::Utils::ResourceNames::campaign_budget(
                $customer_id, BUDGET_TEMPORARY_ID
                ),
              # Set the campaign status as PAUSED. The campaign is the only entity in
              # the mutate request that should have its status set.
              status =>
                Google::Ads::GoogleAds::V13::Enums::CampaignStatusEnum::PAUSED,
              # All Performance Max campaigns have an advertisingChannelType of
              # PERFORMANCE_MAX. The advertisingChannelSubType should not be set.
              advertisingChannelType => PERFORMANCE_MAX,

              # To create a Performance Max for travel goals campaign, you need to set
              # `hotelPropertyAssetSet`.
              hotelPropertyAssetSet => $hotel_property_asset_set_resource_name,

              # Bidding strategy must be set directly on the campaign.
              # Setting a portfolio bidding strategy by resource name is not supported.
              # Max Conversion and Max Conversion Value are the only strategies
              # supported for Performance Max campaigns.
              # An optional ROAS (Return on Advertising Spend) can be set for
              # maximizeConversionValue. The ROAS value must be specified as a ratio in
              # the API. It is calculated by dividing "total value" by "total spend".
              # For more information on Max Conversion Value, see the support article:
              # http://support.google.com/google-ads/answer/7684216.
              # A targetRoas of 3.5 corresponds to a 350% return on ad spend.
              maximizeConversionValue =>
                Google::Ads::GoogleAds::V13::Common::MaximizeConversionValue->
                new({
                  targetRoas => 3.5
                })})})});
}

# Create a list of mutate operations that create a new asset group, composed of suggested
# assets. In case the number of suggested assets is not enough for the requirements, it'll
# create more assets to meet the requirement.
#
# For the list of required assets for a Performance Max campaign, see
# https://developers.google.com/google-ads/api/docs/performance-max/assets.
sub create_asset_group_operations {
  my (
    $customer_id,
    $hotel_property_asset_resource_name,
    $headline_asset_resource_names,
    $description_asset_resource_names,
    $hotel_asset_suggestion
  ) = @_;
  my $operations = [];

  # Create a new mutate operation that creates an asset group using suggested information
  # when available.
  my $asset_group_name =
      $hotel_asset_suggestion->{status} eq SUCCESS
    ? $hotel_asset_suggestion->{hotelName}
    : 'Performance Max for travel goals asset group #' . uniqid();
  my $asset_group_final_urls =
    $hotel_asset_suggestion->{status} eq SUCCESS
    ? [$hotel_asset_suggestion->{finalUrl}]
    : ['http://www.example.com'];
  my $asset_group_resource_name =
    Google::Ads::GoogleAds::V13::Utils::ResourceNames::asset_group($customer_id,
    ASSET_GROUP_TEMPORARY_ID);
  push @$operations,
    Google::Ads::GoogleAds::V13::Services::GoogleAdsService::MutateOperation->
    new({
      assetGroupOperation =>
        Google::Ads::GoogleAds::V13::Services::AssetGroupService::AssetGroupOperation
        ->new({
          create => Google::Ads::GoogleAds::V13::Resources::AssetGroup->new({
              resourceName => $asset_group_resource_name,
              name         => $asset_group_name,
              campaign     =>
                Google::Ads::GoogleAds::V13::Utils::ResourceNames::campaign(
                $customer_id, CAMPAIGN_TEMPORARY_ID
                ),
              finalUrls => $asset_group_final_urls,
              status    =>
                Google::Ads::GoogleAds::V13::Enums::AssetGroupStatusEnum::PAUSED
            })})});

  # An asset group is linked to an asset by creating a new asset group asset and providing:
  # -  the resource name of the asset group
  # -  the resource name of the asset
  # -  the field_type of the asset in this asset group
  #
  # To learn more about asset groups, see
  # https://developers.google.com/google-ads/api/docs/performance-max/asset-groups.
  #
  # Headline and description assets were created at the first step of this example. So, we
  # just need to link them with the created asset group.
  #
  # Link the headline assets to the asset group.
  foreach my $resource_name (@$headline_asset_resource_names) {
    push @$operations,
      Google::Ads::GoogleAds::V13::Services::GoogleAdsService::MutateOperation
      ->new({
        assetGroupAssetOperation =>
          Google::Ads::GoogleAds::V13::Services::AssetGroupAssetService::AssetGroupAssetOperation
          ->new({
            create =>
              Google::Ads::GoogleAds::V13::Resources::AssetGroupAsset->new({
                asset      => $resource_name,
                assetGroup =>
                  Google::Ads::GoogleAds::V13::Utils::ResourceNames::asset_group(
                  $customer_id, ASSET_GROUP_TEMPORARY_ID
                  ),
                fieldType => HEADLINE
              })})});
  }

  # Link the description assets.
  foreach my $resource_name (@$description_asset_resource_names) {
    push @$operations,
      Google::Ads::GoogleAds::V13::Services::GoogleAdsService::MutateOperation
      ->new({
        assetGroupAssetOperation =>
          Google::Ads::GoogleAds::V13::Services::AssetGroupAssetService::AssetGroupAssetOperation
          ->new({
            create =>
              Google::Ads::GoogleAds::V13::Resources::AssetGroupAsset->new({
                asset      => $resource_name,
                assetGroup =>
                  Google::Ads::GoogleAds::V13::Utils::ResourceNames::asset_group(
                  $customer_id, ASSET_GROUP_TEMPORARY_ID
                  ),
                fieldType => DESCRIPTION
              })})});
  }

  # Link the previously created hotel property asset to the asset group. In the real-world
  # scenario, you'd need to do this step several times for each hotel property asset.
  push @$operations,
    Google::Ads::GoogleAds::V13::Services::GoogleAdsService::MutateOperation->
    new({
      assetGroupAssetOperation =>
        Google::Ads::GoogleAds::V13::Services::AssetGroupAssetService::AssetGroupAssetOperation
        ->new({
          create =>
            Google::Ads::GoogleAds::V13::Resources::AssetGroupAsset->new({
              asset      => $hotel_property_asset_resource_name,
              assetGroup => $asset_group_resource_name,
              fieldType  => HOTEL_PROPERTY
            })})});

  # Create the rest of required text assets and link them to the asset group.
  push @$operations,
    @{create_text_assets_for_asset_group($customer_id, $hotel_asset_suggestion)
    };

  # Create the image assets and link them to the asset group. Some optional image assets
  # suggested by the TravelAssetSuggestionService might be created too.
  push @$operations,
    @{create_image_assets_for_asset_group($customer_id, $hotel_asset_suggestion)
    };

  if ($hotel_asset_suggestion->{status} eq SUCCESS) {
    # Create a new mutate operation for a suggested call-to-action asset and link it
    # to the asset group.
    push @$operations,
      Google::Ads::GoogleAds::V13::Services::GoogleAdsService::MutateOperation
      ->new({
        assetOperation =>
          Google::Ads::GoogleAds::V13::Services::AssetService::AssetOperation->
          new({
            create => Google::Ads::GoogleAds::V13::Resources::Asset->new({
                resourceName =>
                  Google::Ads::GoogleAds::V13::Utils::ResourceNames::asset(
                  $customer_id, $next_temp_id
                  ),
                name => 'Suggested call-to-action asset #' . uniqid(),
                callToActionAsset =>
                  Google::Ads::GoogleAds::V13::Common::CallToActionAsset->new({
                    callToAction => $hotel_asset_suggestion->{callToAction}})})}
          )});
    push @$operations,
      Google::Ads::GoogleAds::V13::Services::GoogleAdsService::MutateOperation
      ->new({
        assetGroupAssetOperation =>
          Google::Ads::GoogleAds::V13::Services::AssetGroupAssetService::AssetGroupAssetOperation
          ->new({
            create =>
              Google::Ads::GoogleAds::V13::Resources::AssetGroupAsset->new({
                asset =>
                  Google::Ads::GoogleAds::V13::Utils::ResourceNames::asset(
                  $customer_id, $next_temp_id
                  ),
                assetGroup => $asset_group_resource_name,
                fieldType  => CALL_TO_ACTION_SELECTION
              })})});
    $next_temp_id--;
  }

  return $operations;
}

# Create text assets required for an asset group using the suggested hotel text assets. It adds
# more text assets to fulfill the requirements if the suggested hotel text assets are not enough.
sub create_text_assets_for_asset_group {
  my ($customer_id, $hotel_asset_suggestion) = @_;

  # Create mutate operations for the suggested text assets except for headlines and
  # descriptions, which were created previously.
  my $operations = [];

  # Create a map of asset field type to number of text values.
  my $required_text_asset_counts = {};
  foreach my $field_type (keys %$min_required_text_asset_counts) {
    $required_text_asset_counts->{$field_type} = 0;
  }

  if ($hotel_asset_suggestion->{status} eq SUCCESS) {
    # Add text values of suggested text assets.
    foreach my $hotel_text_asset (@{$hotel_asset_suggestion->{textAssetsList}})
    {
      my $asset_field_type = $hotel_text_asset->{assetFieldType};
      if ($asset_field_type eq HEADLINE or $asset_field_type eq DESCRIPTION) {
        # Headlines and descriptions were already created at the first step of this code example.
        next;
      }
      printf
"A text asset with text '%s' is suggested for the asset field type '%s'.\n",
        $hotel_text_asset->{text}, $asset_field_type;

      push @$operations,
        @{
        create_text_asset_and_asset_group_asset_operations(
          $customer_id, $hotel_text_asset->{text},
          $hotel_text_asset->{assetFieldType})};
      $required_text_asset_counts->{$asset_field_type}++;
    }
  }

  # Add more text values by field type to fulfill the requirements.
  foreach my $asset_field_type (keys %$min_required_text_asset_counts) {
    if ($asset_field_type eq HEADLINE or $asset_field_type eq DESCRIPTION) {
      # Headlines and descriptions were already created at the first step of this code example.
      next;
    }

    my $min_count = $min_required_text_asset_counts->{$asset_field_type};
    for (
      my $i = 0 ;
      $i < $min_count - $required_text_asset_counts->{$asset_field_type} ;
      $i++
      )
    {
      my $text_from_defaults =
        $default_text_assets_info->{$asset_field_type}[$i++];
      printf
"A default text '%s' is used to create a text asset for the asset field type '%s'.\n",
        $text_from_defaults, $asset_field_type;
      push @$operations,
        @{
        create_text_asset_and_asset_group_asset_operations($customer_id,
          $text_from_defaults, $asset_field_type)};

    }
  }

  return $operations;
}

# Create image assets required for an asset group using the suggested hotel image assets. It
# adds more image assets to fulfill the requirements if the suggested hotel image assets are
# not enough.
sub create_image_assets_for_asset_group {
  my ($customer_id, $hotel_asset_suggestion) = @_;

  my $operations = [];
  # Create mutate operations for the suggested image assets.
  # Create a map of asset field type to number of text values.
  my $required_image_asset_counts = {};
  foreach my $field_type (keys %$min_required_image_asset_counts) {
    $required_image_asset_counts->{$field_type} = 0;
  }
  foreach my $hotel_image_asset (@{$hotel_asset_suggestion->{imageAssets}}) {
    printf
"An image asset with url '%s' is suggested for the asset field type '%s'.\n",
      $hotel_image_asset->{uri}, $hotel_image_asset->{assetFieldType};
    push @$operations,
      @{
      create_image_asset_and_asset_group_asset_operations(
        $customer_id,
        $hotel_image_asset->{uri},
        $hotel_image_asset->{assetFieldType},
        'Suggested image asset #' . uniqid())};
    # Keeps track of only required image assets. The service may sometimes suggest optional
    # image assets.
    if (
      exists $required_image_asset_counts->
      {$hotel_image_asset->{assetFieldType}})
    {
      $required_image_asset_counts->{$hotel_image_asset->{assetFieldType}}++;
    }
  }

  # Add more image assets to fulfill the requirements.
  foreach my $asset_field_type (keys %$min_required_image_asset_counts) {
    my $min_count = $min_required_image_asset_counts->{$asset_field_type};
    for (
      my $i = 0 ;
      $i < $min_count - $required_image_asset_counts->{$asset_field_type} ;
      $i++
      )
    {
      my $image_from_defaults =
        $default_image_assets_info->{$asset_field_type}[$i++];
      printf
"A default image URL '%s' is used to create an image asset for the asset field type '%s'.\n",
        $image_from_defaults, $asset_field_type;
      push @$operations,
        @{
        create_image_asset_and_asset_group_asset_operations(
          $customer_id,      $image_from_defaults,
          $asset_field_type, lc $asset_field_type . uniqid())};
    }
  }

  return $operations;
}

# Create a list of mutate operations that create a new linked text asset.
sub create_text_asset_and_asset_group_asset_operations {
  my ($customer_id, $text, $field_type) = @_;

  my $operations = [];
  # Create a new mutate operation that creates a text asset.
  push @$operations,
    Google::Ads::GoogleAds::V13::Services::GoogleAdsService::MutateOperation->
    new({
      assetOperation =>
        Google::Ads::GoogleAds::V13::Services::AssetService::AssetOperation->
        new({
          create => Google::Ads::GoogleAds::V13::Resources::Asset->new({
              resourceName =>
                Google::Ads::GoogleAds::V13::Utils::ResourceNames::asset(
                $customer_id, $next_temp_id
                ),
              textAsset => Google::Ads::GoogleAds::V13::Common::TextAsset->new({
                  text => $text
                })})})});

  # Create an asset group asset to link the asset to the asset group.
  push @$operations,
    Google::Ads::GoogleAds::V13::Services::GoogleAdsService::MutateOperation->
    new({
      assetGroupAssetOperation =>
        Google::Ads::GoogleAds::V13::Services::AssetGroupAssetService::AssetGroupAssetOperation
        ->new({
          create =>
            Google::Ads::GoogleAds::V13::Resources::AssetGroupAsset->new({
              asset => Google::Ads::GoogleAds::V13::Utils::ResourceNames::asset(
                $customer_id, $next_temp_id
              ),
              assetGroup =>
                Google::Ads::GoogleAds::V13::Utils::ResourceNames::asset_group(
                $customer_id, ASSET_GROUP_TEMPORARY_ID
                ),
              fieldType => $field_type
            })})});

  $next_temp_id--;

  return $operations;
}

# Create a list of mutate operations that create a new linked image asset.
sub create_image_asset_and_asset_group_asset_operations {
  my ($customer_id, $url, $field_type, $asset_name) = @_;

  my $operations = [];
  # Create a new mutate operation that creates an image asset.
  # Create a new mutate operation that creates a text asset.
  push @$operations,
    Google::Ads::GoogleAds::V13::Services::GoogleAdsService::MutateOperation->
    new({
      assetOperation =>
        Google::Ads::GoogleAds::V13::Services::AssetService::AssetOperation->
        new({
          create => Google::Ads::GoogleAds::V13::Resources::Asset->new({
              resourceName =>
                Google::Ads::GoogleAds::V13::Utils::ResourceNames::asset(
                $customer_id, $next_temp_id
                ),
              # Provide a unique friendly name to identify your asset.
              # When there is an existing image asset with the same content but a different
              # name, the new name will be dropped silently.
              name       => $asset_name,
              imageAsset =>
                Google::Ads::GoogleAds::V13::Common::ImageAsset->new({
                  data => get_base64_data_from_url($url)})})})});

  # Create an asset group asset to link the asset to the asset group.
  push @$operations,
    Google::Ads::GoogleAds::V13::Services::GoogleAdsService::MutateOperation->
    new({
      assetGroupAssetOperation =>
        Google::Ads::GoogleAds::V13::Services::AssetGroupAssetService::AssetGroupAssetOperation
        ->new({
          create =>
            Google::Ads::GoogleAds::V13::Resources::AssetGroupAsset->new({
              asset => Google::Ads::GoogleAds::V13::Utils::ResourceNames::asset(
                $customer_id, $next_temp_id
              ),
              assetGroup =>
                Google::Ads::GoogleAds::V13::Utils::ResourceNames::asset_group(
                $customer_id, ASSET_GROUP_TEMPORARY_ID
                ),
              fieldType => $field_type
            })})});

  $next_temp_id--;

  return $operations;
}

# Prints the details of a MutateGoogleAdsResponse.
# Parses the "response" oneof field name and uses it to extract the new entity's
# name and resource name.
sub print_response_details {
  my ($mutate_google_ads_response) = @_;

  foreach
    my $response (@{$mutate_google_ads_response->{mutateOperationResponses}})
  {
    my $result_type = [keys %$response]->[0];

    printf "Created a(n) %s with '%s'.\n",
      ucfirst $result_type =~ s/Result$//r,
      $response->{$result_type}{resourceName};
  }
}

# Don't run the example if the file is being included.
if (abs_path($0) ne abs_path(__FILE__)) {
  return 1;
}

# Get Google Ads Client, credentials will be read from ~/googleads.properties.
my $api_client = Google::Ads::GoogleAds::Client->new();

# By default examples are set to die on any server returned fault.
$api_client->set_die_on_faults(1);

my $customer_id = undef;
my $place_id    = undef;

# Parameters passed on the command line will override any parameters set in code.
GetOptions(
  "customer_id=s" => \$customer_id,
  "place_id=s"    => \$place_id,
);

# Print the help message if the parameters are not initialized in the code nor
# in the command line.
pod2usage(2)
  if not check_params($customer_id, $place_id);

# Call the example.
add_performance_max_for_travel_goals_campaign($api_client,
  $customer_id =~ s/-//gr, $place_id);

=pod

=head1 NAME

add_performance_max_for_travel_goals_campaign

=head1 DESCRIPTION

This example shows how to create a Performance Max for travel goals campaign. It also uses
TravelAssetSuggestionService to fetch suggested assets for creating an asset group. In case
there are not enough assets for the asset group (required by Performance Max), this example will
create more assets to fulfill the requirements.

For more information about Performance Max campaigns, see
https://developers.google.com/google-ads/api/docs/performance-max/overview.

Prerequisites:
- You must have at least one conversion action in the account. For more about conversion actions,
see https://developers.google.com/google-ads/api/docs/conversions/overview#conversion_actions.

Notes:
- This example uses the default customer conversion goals. For an example of setting
  campaign-specific conversion goals, see shopping_ads/add_performance_max_retail_campaign.pl.
- To learn how to create asset group signals, see
  advanced_operations/add_performance_max_campaign.pl.

=head1 SYNOPSIS

add_performance_max_for_travel_goals_campaign.pl [options]

    -help                         Show the help message.
    -customer_id                  The Google Ads customer ID.
    -place_id 					  The place ID of a hotel property. A place ID uniquely identifies a place in the Google Places database. See https://developers.google.com/places/web-service/place-id to learn more.

=cut