Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
7.10% covered (danger)
7.10%
11 / 155
7.69% covered (danger)
7.69%
1 / 13
CRAP
0.00% covered (danger)
0.00%
0 / 1
GoogleTrait
6.49% covered (danger)
6.49%
10 / 154
7.69% covered (danger)
7.69%
1 / 13
765.81
0.00% covered (danger)
0.00%
0 / 1
 getClient
100.00% covered (success)
100.00%
10 / 10
100.00% covered (success)
100.00%
1 / 1
1
 getUserClient
0.00% covered (danger)
0.00%
0 / 10
0.00% covered (danger)
0.00%
0 / 1
6
 getAdsClient
0.00% covered (danger)
0.00%
0 / 18
0.00% covered (danger)
0.00%
0 / 1
6
 getGoogleAdsCustomers
0.00% covered (danger)
0.00%
0 / 44
0.00% covered (danger)
0.00%
0 / 1
56
 getGoogleAdsCampaigns
0.00% covered (danger)
0.00%
0 / 10
0.00% covered (danger)
0.00%
0 / 1
6
 getGoogleAdsCustomerFromCampaign
0.00% covered (danger)
0.00%
0 / 2
0.00% covered (danger)
0.00%
0 / 1
2
 getGoogleAdsConversionActions
0.00% covered (danger)
0.00%
0 / 19
0.00% covered (danger)
0.00%
0 / 1
6
 createGoogleAdsConversionAction
0.00% covered (danger)
0.00%
0 / 11
0.00% covered (danger)
0.00%
0 / 1
2
 getGoogleAdsCustomersDetails
0.00% covered (danger)
0.00%
0 / 10
0.00% covered (danger)
0.00%
0 / 1
6
 getGoogleAdsConversionActionCategories
0.00% covered (danger)
0.00%
0 / 6
0.00% covered (danger)
0.00%
0 / 1
12
 getGoogleAdsConversionActionTypes
0.00% covered (danger)
0.00%
0 / 6
0.00% covered (danger)
0.00%
0 / 1
12
 getGoogleAdsConversionActionStatuses
0.00% covered (danger)
0.00%
0 / 6
0.00% covered (danger)
0.00%
0 / 1
12
 parseConfigJson
0.00% covered (danger)
0.00%
0 / 2
0.00% covered (danger)
0.00%
0 / 1
2
1<?php
2namespace App\Traits;
3
4// google api package does not use namespaces, so this is necessary
5require_once(__DIR__."/../../vendor/autoload.php");
6
7use DB;
8use Exception;
9use Carbon\Carbon;
10use Illuminate\Http\Request;
11use Illuminate\Support\Facades\Storage;
12use Illuminate\Support\Str;
13
14/**
15 * Models
16 */
17use App\Models\User;
18use App\Models\GoogleAdsCustomer;
19use App\Models\GoogleAdsCampaign;
20
21/**
22 * Google trait
23 *
24 * Trait for hangling interactions with google's api
25 *
26 * @author gbh
27 */
28trait GoogleTrait
29{
30    /**
31     * File containing the google 'config.json'
32     */
33    private String $configJsonFile = "google-test.json";
34
35    /**
36     * Hardcoded developer token from google for accessing google ads
37     */
38    private String $developerToken = "RTlW6it4bYRHt2n9sLRNWg";
39
40    /**
41     * Gets a google client
42     *
43     * @return \Google\Client
44     */
45    private function getClient():\Google\Client
46    {
47        //$configJson = base_path().'/google-test.json';
48        $configJson = base_path().'/'.$this->configJsonFile;
49        $applicationName = 'app.google.application_name';
50
51
52        $scopes = [
53                "https://www.googleapis.com/auth/adwords",
54                \Google\Service\Oauth2::USERINFO_PROFILE,
55                \Google\Service\Oauth2::USERINFO_EMAIL,
56                \Google\Service\Oauth2::OPENID,
57                \Google\Service\Adsense::ADSENSE,
58                \Google\Service\Adsense::ADSENSE_READONLY,
59        ];
60
61        $client = new \Google_Client();
62        $client->setApplicationName($applicationName);
63        $client->setAuthConfig($configJson);
64        $client->setAccessType('offline'); // necessary for getting the refresh token
65        $client->setApprovalPrompt('force'); // necessary for getting the refresh token
66        $client->setScopes(
67            $scopes
68        );
69        $client->setIncludeGrantedScopes(true);
70        return $client;
71    } // getClient
72
73
74    /**
75     * Returns a google client that is logged into the current user
76     *
77     * @return \Google_Client
78     */
79    public function getUserClient():\Google\Client
80    {
81        /**
82         * Logged in user
83         */
84        $user = User::whereMe()->first();
85
86        /**
87         * Strip slashes on json
88         * if you don't strip mysql's escaping, everything will seem to work
89         * but you will not get a new access token from your refresh token
90         */
91        $accessTokenJson = stripslashes($user->google_access_token_json);
92
93        /**
94         * Get client and set access token
95         */
96        $client = $this->getClient();
97        $client->setAccessToken($accessTokenJson);
98
99        /**
100         * Handle refresh
101         */
102        if ($client->isAccessTokenExpired()) {
103            // fetch new access token
104            $client->fetchAccessTokenWithRefreshToken($client->getRefreshToken());
105            $client->setAccessToken($client->getAccessToken());
106
107            // save new access token
108            $user->google_access_token_json = json_encode($client->getAccessToken());
109            $user->save();
110        }
111
112        return $client;
113    } // getUserClient
114
115
116    /**
117     * Creates a base client for the google 'ads' sdk.
118     * This is different than the other google api client, for 'reasons'.
119     * You can only create this client if you have a session user that has
120     * a google access token in the db.
121     *
122     * @return  \Google\Ads\GoogleAds\Lib\V10\GoogleAdsClient
123     * @see     $this->developerToken
124     */
125    public function getAdsClient(?String $customerId = null):\Google\Ads\GoogleAds\Lib\V10\GoogleAdsClient
126    {
127        /**
128         * Get access token for session user
129         */
130        $user = User::whereMe()->first();
131        $accessTokenJson = stripslashes($user->google_access_token_json);
132        $accessTokenObj = json_decode($accessTokenJson);
133
134        /**
135         * Get config values, ie client_id and client_secret
136         */
137        $config = $this->parseConfigJson();
138        $clientId = $config->client_id;
139        $clientSecret = $config->client_secret;
140
141        /**
142         * Create the oauth token that the 'ads' sdk needs
143         */
144        $oauthToken = (new \Google\Ads\GoogleAds\Lib\OAuth2TokenBuilder())
145            ->withClientId($clientId)
146            ->withClientSecret($clientSecret)
147            ->withRefreshToken($accessTokenObj->refresh_token)
148            ->build();
149
150
151        /**
152         * Create the client
153         * NB: developerToken
154         */
155        $googleAdsClient = (new \Google\Ads\GoogleAds\Lib\V10\GoogleAdsClientBuilder())
156            ->withOAuth2Credential($oauthToken)
157            ->withDeveloperToken($this->developerToken);
158
159        if ($customerId) {
160            $googleAdsClient = $googleAdsClient->withLoginCustomerId((Int)$customerId);
161        }
162
163        $googleAdsClient = $googleAdsClient->build();
164
165        return $googleAdsClient;
166    } // getAdsClient
167
168
169    /**
170     * Gets array of all customers for session user's google ads account
171     *
172     * @return  Array
173     */
174    public function getGoogleAdsCustomers():array
175    {
176        $max_cache_in_seconds = 86400;
177        //$max_cache_in_seconds = 1;
178
179        /**
180         * Return cached customer_ids if they are less than max_cache_in_seconds old
181         */
182
183        // select cached customer_ids
184        $gacs = GoogleAdsCustomer::whereMine()->get()->toArray();
185
186        // if we have cached customer_ids and all of them are less than max_cache_in_seconds, return those
187        if ($gacs) {
188            $cacheChecks = array_map(fn ($gac) => (bool)(time() - date_create_from_format("Y-m-d\TH:i:s", substr($gac['created_at'], 0, 19))->getTimestamp() < $max_cache_in_seconds), $gacs);
189            if (in_array(true, $cacheChecks)) {
190                return array_map(fn ($gac) => [
191                    'customer_id' => $gac['customer_id'],
192                    'customer_descriptive_name' => $gac['customer_descriptive_name'],
193                    'customer_accessible' => (bool)$gac['customer_accessible'],
194                    'customer_test_account' => (bool)$gac['customer_test_account'],
195                ], $gacs);
196            }
197        }
198
199        /**
200         * Get api client for ads
201         */
202        $googleAdsClient = $this->getAdsClient();
203
204        /**
205         * All accessible customers
206         */
207        $customerServiceClient = $googleAdsClient->getCustomerServiceClient();
208        $accessibleCustomers = $customerServiceClient->listAccessibleCustomers();
209
210        /**
211         * Delete cached customer ids
212         */
213        $gac = GoogleAdsCustomer::whereMine()->delete();
214
215        /**
216         * Get details of each customer and cache
217         */
218        $customers = [];
219        foreach ($accessibleCustomers->getResourceNames() as $resourceName) {
220            $customerId = preg_replace('/customers\//', '', $resourceName);
221            $userId = auth()->guard('api')->user()->id;
222            $customerDescriptiveName = null;
223            $customerTestAccount = false;
224
225            /**
226             * Try to get the descriptive name
227             */
228            try {
229                $customer = $this->getGoogleAdsCustomersDetails($customerId);
230                $customerDescriptiveName = $customer['customer_descriptive_name'];
231                $customerTestAccount = $customer['customer_test_account'];
232                $customerAccessible = true;
233            } catch (\Exception $e) {
234                $customerAccessible = false;
235            }
236
237            /**
238             * Add to the return array
239             */
240            $customers[] = [
241                'customer_id' => $customerId,
242                'customer_descriptive_name' => $customerDescriptiveName,
243                'customer_accessible' => $customerAccessible,
244                'customer_test_account' => $customerTestAccount,
245            ];
246
247            /**
248             * Cache customer
249             */
250            $gac = new GoogleAdsCustomer();
251            $gac->user_id = $userId;
252            $gac->customer_id = (String)$customerId;
253            $gac->customer_descriptive_name = $customerDescriptiveName;
254            $gac->customer_test_account = $customerTestAccount;
255            $gac->customer_accessible = $customerAccessible;
256            $gac->save();
257
258            /**
259             * Cache campaigns for customer
260             */
261            try {
262                $campaigns = $this->getGoogleAdsCampaigns($customerId);
263                GoogleAdsCampaign::where('customer_id', '=', $customerId)->delete();
264                foreach ($campaigns as $c) {
265                    $gac = new GoogleAdsCampaign();
266                    $gac->customer_id = (String)$customerId;
267                    $gac->campaign_id = (String)$c['id'];
268                    $gac->campaign_name = (String)$c['name'];
269                    $gac->save();
270                }
271            } catch (\Exception $e) {
272            }
273        }
274
275        return $customers;
276    } // getGoogleAdsCustomers
277
278
279    /**
280     * Get all campaigns for a customer
281     *
282     * @param  String $customerId The id of the customer from getGoogleAdsCustomerIds()
283     * @return Array
284     */
285    public function getGoogleAdsCampaigns(String $customerId):array
286    {
287        /**
288         * Get client for ads
289         */
290        $googleAdsClient = $this->getAdsClient();
291        $googleAdsServiceClient = $googleAdsClient->getGoogleAdsServiceClient();
292
293        /**
294         * Query campaigns
295         */
296        $query = 'SELECT campaign.id, campaign.name FROM campaign ORDER BY campaign.id';
297        $stream =$googleAdsServiceClient->search($customerId, $query);
298
299        /**
300         * Create returnable array
301         */
302        $campaigns = [];
303        foreach ($stream->iterateAllElements() as $googleAdsRow) {
304            $campaigns[] = [
305                'id' => $googleAdsRow->getCampaign()->getId(),
306                'name' => $googleAdsRow->getCampaign()->getName(),
307            ];
308        }
309
310        return $campaigns;
311    } // getGoogleAdsCampaigns
312
313
314    public function getGoogleAdsCustomerFromCampaign(String $campaignId):String
315    {
316        $gcus = GoogleAdsCampaign::where('campaign_id', '=', $campaignId)->pluck('customer_id')->first();
317        //$gcus = GoogleAdsCampaign::where('campaign_id', '=', $campaignId)->first()->customer_id;
318        return strval($gcus);
319    }
320
321    /**
322     * Get all conversion actions for a customer
323     *
324     * @param  String $customerId The id of the customer from getGoogleAdsCustomerIds()
325     * @return Array
326     */
327    public function getGoogleAdsConversionActions(String $customerId):array
328    {
329        /**
330         * Get client for ads
331         */
332        $googleAdsClient = $this->getAdsClient();
333        $googleAdsServiceClient = $googleAdsClient->getGoogleAdsServiceClient();
334
335        $typesLookup = $this->getGoogleAdsConversionActionTypes();
336        $categoriesLookup = $this->getGoogleAdsConversionActionCategories();
337        $statusesLookup = $this->getGoogleAdsConversionActionStatuses();
338
339        /**
340         * Query conversion actions
341         */
342        $query = 'SELECT    conversion_action.id,
343                            conversion_action.name,
344                            conversion_action.category,
345                            conversion_action.type,
346                            conversion_action.status
347                  FROM      conversion_action';
348        $stream =$googleAdsServiceClient->search($customerId, $query);
349
350        /**
351         * Create returnable array
352         */
353        $conversionActions = [];
354        foreach ($stream->iterateAllElements() as $googleAdsRow) {
355
356            // lookup up category name
357            $categoryId =  (int)$googleAdsRow->getConversionAction()->getCategory();
358            $categoryName = @array_values(array_filter($categoriesLookup, fn ($n) => $n->id == $categoryId))[0]->name;
359
360            // lookup up type name
361            $typeId =  (int)$googleAdsRow->getConversionAction()->getType();
362            $typeName = @array_values(array_filter($typesLookup, fn ($n) => $n->id == $typeId))[0]->name;
363            
364            // lookup up status name
365            $statusId =  (int)$googleAdsRow->getConversionAction()->getStatus();
366            $statusName = @array_values(array_filter($statusesLookup, fn ($n) => $n->id == $statusId))[0]->name;
367
368            $conversionActions[] = [
369                'id' => $googleAdsRow->getConversionAction()->getId(),
370                'name' => $googleAdsRow->getConversionAction()->getName(),
371                'category_id' => $categoryId,
372                'category_name' => $categoryName,
373                'type_id' => $typeId,
374                'type_name' => $typeName,
375                'status_id' => $statusId,
376                'status_name' => $statusName,
377            ];
378        }
379
380        return $conversionActions;
381    } // getGoogleAdsConversionActions
382
383
384    /**
385     * Creates a Conversion Action for the customer
386     *
387     * @param  String $customerId
388     * @param  String $name Free text name
389     * @param  Int    $category see GoogleController.getConversionActionCateogries
390     * @param  Int    $type see GoogleController.getConversionActionTypes
391     * @param  Int    $status see GoogleController.getConversionActionStatuses
392     * @param  float  $defaultValue Default dollar values of the conversion, ie 32.50
393     * @param  Int    $viewThroughLookbackWindowDays  The maximum number of days which may elapse between an impression and a conversion without an interaction.
394     * @return void
395     */
396    public function createGoogleAdsConversionAction(String $customerId, String $name, Int $category, Int $type, Int $status, float $defaultValue, Int $viewThroughLookbackWindowDays):void
397    {
398        /**
399         * Set arguments for the Conversion Action
400         */
401        $valueSettings = new \Google\Ads\GoogleAds\V10\Resources\ConversionAction\ValueSettings(
402            [
403                'default_value' => $defaultValue,
404                'always_use_default_value' => false,
405            ]
406        );
407
408        $conversionAction = new \Google\Ads\GoogleAds\V10\Resources\ConversionAction(
409            [
410                'name' => $name,
411                'category' => $category,
412                'type' => $type,
413                'status' => $status,
414                'view_through_lookback_window_days' => $viewThroughLookbackWindowDays,
415                'value_settings' => $valueSettings,
416            ]
417        );
418        
419        /**
420         * Create the operation wiht the configs in conversionAction.
421         * The operation is the request we send with 'mutateConversionActions'
422         */
423        $conversionActionOperation = new \Google\Ads\GoogleAds\V10\Services\ConversionActionOperation();
424        $conversionActionOperation->setCreate($conversionAction);
425
426        /**
427         * Call 'mutate' with the conversionActionOperation to created
428         */
429        $googleAdsClient = $this->getAdsClient();
430        $conversionActionServiceClient = $googleAdsClient->getConversionActionServiceClient();
431        $response = $conversionActionServiceClient->mutateConversionActions(
432            $customerId,
433            [$conversionActionOperation]
434        );
435
436        $results = $response->getResults(); // unused atm
437    } // createGoogleAdsConversionAction
438
439
440    /**
441     * Returns details on a given customer identified by $customerId
442     *
443     * @param  String $customerId
444     * @return Array
445     */
446    public function getGoogleAdsCustomersDetails(String $customerId):array
447    {
448        /**
449         * Get client for ads
450         */
451        $googleAdsClient = $this->getAdsClient();
452        $googleAdsServiceClient = $googleAdsClient->getGoogleAdsServiceClient();
453
454        /**
455         * Query campaigns
456         */
457        $query = 'select customer.id, customer.descriptive_name, customer.test_account from customer';
458
459        $stream =$googleAdsServiceClient->search($customerId, $query);
460
461        /**
462         * Create returnable array
463         */
464        $customer = [];
465        foreach ($stream->iterateAllElements() as $googleAdsRow) {
466            $customer['customer_id'] = $googleAdsRow->getCustomer()->getId();
467            $customer['customer_descriptive_name'] = $googleAdsRow->getCustomer()->getDescriptiveName();
468            $customer['customer_test_account'] = (bool)$googleAdsRow->getCustomer()->getTestAccount();
469        }
470
471        return $customer;
472    } // getGoogleAdsCustomersDetails
473
474
475    /**
476     * Get all Conversion Action Categories for google ads, ie for menu or lookup
477     *
478     * @return Array
479     */
480    public function getGoogleAdsConversionActionCategories():array
481    {
482        /**
483         * Get up to fifty conversion action categories form the googleads sdk
484         */
485        $returnArray = [];
486        foreach (range(1, 50) as $id) {
487            try {
488                $returnArray[] = (object)[
489                    'id' => $id,
490                    'name' => \Google\Ads\GoogleAds\V10\Enums\ConversionActionCategoryEnum\ConversionActionCategory::name($id)
491                ];
492            } catch (\Exception $e) {
493            }
494        }
495        return $returnArray;
496    } // getGoogleAdsConversionActionCategories
497
498
499    /**
500     * Get all Conversion Action Types for google ads, ie for menu or lookup
501     *
502     * @return Array
503     */
504    public function getGoogleAdsConversionActionTypes():array
505    {
506        /**
507         * Get up to fifty conversion action typess form the googleads sdk
508         */
509        $returnArray = [];
510        foreach (range(1, 50) as $id) {
511            try {
512                $returnArray[] = (object)[
513                    'id' => $id,
514                    'name' => \Google\Ads\GoogleAds\V10\Enums\ConversionActionTypeEnum\ConversionActionType::name($id),
515                ];
516            } catch (\Exception $e) {
517            }
518        }
519        return $returnArray;
520    }
521
522    /**
523     * Get all Conversion Action Statuses for google ads, ie for menu or lookup
524     *
525     * @return Array
526     */
527    public function getGoogleAdsConversionActionStatuses():array
528    {
529        /**
530         * Get up to fifty conversion action statuses form the googleads sdk
531         */
532        $returnArray = [];
533        foreach (range(1, 50) as $id) {
534            try {
535                $returnArray[] = (object)[
536                    'id' => $id,
537                    'name' => \Google\Ads\GoogleAds\V10\Enums\ConversionActionStatusEnum\ConversionActionStatus::name($id),
538                ];
539            } catch (\Exception $e) {
540            }
541        }
542        return $returnArray;
543    }
544
545    /**
546     * Parse the 'config' file for google access into an object
547     *
548     * @return  object
549     * @see     $this->configJsonFile
550     */
551    private function parseConfigJson():object
552    {
553        $configJson = base_path().'/'.$this->configJsonFile;
554        return json_decode(file_get_contents($configJson))->web;
555    } // parseConfigJson
556} // GoogleTrait