Code Coverage |
||||||||||
Lines |
Functions and Methods |
Classes and Traits |
||||||||
Total | |
7.10% |
11 / 155 |
|
7.69% |
1 / 13 |
CRAP | |
0.00% |
0 / 1 |
GoogleTrait | |
6.49% |
10 / 154 |
|
7.69% |
1 / 13 |
765.81 | |
0.00% |
0 / 1 |
getClient | |
100.00% |
10 / 10 |
|
100.00% |
1 / 1 |
1 | |||
getUserClient | |
0.00% |
0 / 10 |
|
0.00% |
0 / 1 |
6 | |||
getAdsClient | |
0.00% |
0 / 18 |
|
0.00% |
0 / 1 |
6 | |||
getGoogleAdsCustomers | |
0.00% |
0 / 44 |
|
0.00% |
0 / 1 |
56 | |||
getGoogleAdsCampaigns | |
0.00% |
0 / 10 |
|
0.00% |
0 / 1 |
6 | |||
getGoogleAdsCustomerFromCampaign | |
0.00% |
0 / 2 |
|
0.00% |
0 / 1 |
2 | |||
getGoogleAdsConversionActions | |
0.00% |
0 / 19 |
|
0.00% |
0 / 1 |
6 | |||
createGoogleAdsConversionAction | |
0.00% |
0 / 11 |
|
0.00% |
0 / 1 |
2 | |||
getGoogleAdsCustomersDetails | |
0.00% |
0 / 10 |
|
0.00% |
0 / 1 |
6 | |||
getGoogleAdsConversionActionCategories | |
0.00% |
0 / 6 |
|
0.00% |
0 / 1 |
12 | |||
getGoogleAdsConversionActionTypes | |
0.00% |
0 / 6 |
|
0.00% |
0 / 1 |
12 | |||
getGoogleAdsConversionActionStatuses | |
0.00% |
0 / 6 |
|
0.00% |
0 / 1 |
12 | |||
parseConfigJson | |
0.00% |
0 / 2 |
|
0.00% |
0 / 1 |
2 |
1 | <?php |
2 | namespace App\Traits; |
3 | |
4 | // google api package does not use namespaces, so this is necessary |
5 | require_once(__DIR__."/../../vendor/autoload.php"); |
6 | |
7 | use DB; |
8 | use Exception; |
9 | use Carbon\Carbon; |
10 | use Illuminate\Http\Request; |
11 | use Illuminate\Support\Facades\Storage; |
12 | use Illuminate\Support\Str; |
13 | |
14 | /** |
15 | * Models |
16 | */ |
17 | use App\Models\User; |
18 | use App\Models\GoogleAdsCustomer; |
19 | use App\Models\GoogleAdsCampaign; |
20 | |
21 | /** |
22 | * Google trait |
23 | * |
24 | * Trait for hangling interactions with google's api |
25 | * |
26 | * @author gbh |
27 | */ |
28 | trait 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 |