Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
97.95% covered (success)
97.95%
239 / 244
78.57% covered (warning)
78.57%
11 / 14
CRAP
0.00% covered (danger)
0.00%
0 / 1
ConversionController
97.95% covered (success)
97.95%
239 / 244
78.57% covered (warning)
78.57%
11 / 14
81
0.00% covered (danger)
0.00%
0 / 1
 __construct
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 postConversion
100.00% covered (success)
100.00%
47 / 47
100.00% covered (success)
100.00%
1 / 1
36
 getConversionsDigest
100.00% covered (success)
100.00%
58 / 58
100.00% covered (success)
100.00%
1 / 1
18
 getConversion
100.00% covered (success)
100.00%
8 / 8
100.00% covered (success)
100.00%
1 / 1
2
 getConversionTransactions
100.00% covered (success)
100.00%
11 / 11
100.00% covered (success)
100.00%
1 / 1
2
 postConversionApprove
94.74% covered (success)
94.74%
36 / 38
0.00% covered (danger)
0.00%
0 / 1
5.00
 postConversionReject
100.00% covered (success)
100.00%
18 / 18
100.00% covered (success)
100.00%
1 / 1
2
 postConversionFlag
100.00% covered (success)
100.00%
18 / 18
100.00% covered (success)
100.00%
1 / 1
2
 postConversionNote
100.00% covered (success)
100.00%
23 / 23
100.00% covered (success)
100.00%
1 / 1
3
 __validateNoteRequest
100.00% covered (success)
100.00%
4 / 4
100.00% covered (success)
100.00%
1 / 1
2
 __validateConversionRequest
100.00% covered (success)
100.00%
4 / 4
100.00% covered (success)
100.00%
1 / 1
2
 __validateYYYYMMDD
75.00% covered (warning)
75.00%
3 / 4
0.00% covered (danger)
0.00%
0 / 1
3.14
 __validateConversionApprovalRequest
100.00% covered (success)
100.00%
4 / 4
100.00% covered (success)
100.00%
1 / 1
2
 getGoogleCampaignIds
66.67% covered (warning)
66.67%
4 / 6
0.00% covered (danger)
0.00%
0 / 1
1.04
1<?php
2
3namespace App\Http\Controllers\api;
4
5use DB;
6use App\Http\Controllers\Controller;
7use Illuminate\Http\Request;
8use Illuminate\Http\JsonResponse;
9use Illuminate\Database\Eloquent\Builder;
10
11use App\Models\Role;
12use App\Models\Conversion;
13use App\Models\ConversionNote;
14use App\Models\ConversionDigest;
15use App\Models\ConversionStatus;
16use App\Models\GoogleAdsCampaign;
17use App\Models\GoogleAdsCustomer;
18use App\Models\ConversionCurrency;
19use App\Models\ConversionTransaction;
20
21use App\Libs\GoogleAds;
22use App\Libs\SqlFormatter;
23
24use Carbon\Carbon;
25
26use App\Traits\GoogleTrait;
27
28class ConversionController extends BaseController
29{
30    use GoogleTrait;
31
32    protected GoogleAds $googleAds;
33
34    /**
35     * Constructore
36     */
37    public function __construct(\App\Libs\GoogleAds $ga) {
38        $this->googleAds = $ga;
39    }
40
41    /**
42     * Creates one conversion record
43     *
44     * @author gbh
45     * @see http://swagger-humanverify.fruitbat.systems/#post-/api/conversions
46     */
47    public function postConversion(Request $request):JsonResponse
48    {
49        //$this->uploadOfflineConversion();
50        //return $this->sendResponse(201, "woozozo");
51
52        /**
53         * Validate request body
54         * HTTP 400
55         */
56        $validationTest = $this->__validateConversionRequest($request);
57        if (is_object($validationTest)) {
58            return $validationTest;
59        }
60
61        /**
62         * Get status id
63         */
64        $statusId = ConversionStatus::where('name', '=', 'new')->first()->id;
65
66        /**
67         * Figure out the conversion currency
68         */
69        $conversionCurrencyId = 1;
70        if (ConversionCurrency::where('name', '=', $request->conversion_currency)->count() > 0) {
71            $conversionCurrencyId = ConversionCurrency::where('name', '=', $request->conversion_currency)->first()->id;
72        }
73
74        /**
75         * Create the conversion
76         */
77        $conversion = new Conversion();
78
79        // mandatory and defined fields
80        $conversion->website_url = $request->website_url;
81        $conversion->status_id = $statusId;
82        $conversion->mobile = isset($request->mobile) ? (bool)$request->mobile : false;
83        $conversion->conversion_currency = $conversionCurrencyId;
84        $conversion->conversion_time = date("Y-m-d H:i:s");
85
86        // optional strings fields
87        $conversion->conversion_value = isset($request->conversion_value) ? (string)$request->conversion_value : null;
88        $conversion->gclid = isset($request->gclid) ? (string)$request->gclid : null;
89        $conversion->fbclid = isset($request->fbclid) ? (string)$request->fbclid : null;
90        $conversion->event_category = isset($request->event_category) ? (string)$request->event_category : null;
91        $conversion->event_action = isset($request->event_action) ? (string)$request->event_action : null;
92        $conversion->event_label = isset($request->event_label) ? (string)$request->event_label : null;
93        $conversion->utm_campaign_id = isset($request->utm_campaign_id) ? (string)$request->utm_campaign_id : null;
94        $conversion->utm_campaign_source = isset($request->utm_campaign_source) ? (string)$request->utm_campaign_source : null;
95        $conversion->utm_campaign_medium = isset($request->utm_campaign_medium) ? (string)$request->utm_campaign_medium : null;
96        $conversion->utm_campaign_name = isset($request->utm_campaign_name) ? (string)$request->utm_campaign_name : null;
97        $conversion->utm_campaign_term = isset($request->utm_campaign_term) ? (string)$request->utm_campaign_term : null;
98        $conversion->utm_campaign_content = isset($request->utm_campaign_content) ? (string)$request->utm_campaign_content : null;
99        $conversion->conversion_name = isset($request->conversion_name) ? (string)$request->conversion_name : null;
100        $conversion->nw = isset($request->nw) ? (string)$request->nw : null;
101        $conversion->cmpid = isset($request->cmpid) ? (string)$request->cmpid : null;
102        $conversion->agid = isset($request->agid) ? (string)$request->agid : null;
103        $conversion->fiid = isset($request->fiid) ? (string)$request->fiid : null;
104        $conversion->tgid = isset($request->tgid) ? (string)$request->tgid : null;
105        $conversion->loc = isset($request->loc) ? (string)$request->loc : null;
106        $conversion->ploc = isset($request->ploc) ? (string)$request->ploc : null;
107        $conversion->match = isset($request->match) ? (string)$request->match : null;
108        $conversion->dev = isset($request->dev) ? (string)$request->dev : null;
109        $conversion->devm = isset($request->devm) ? (string)$request->devm : null;
110        $conversion->cmpt = isset($request->cmpt) ? (string)$request->cmpt : null;
111        $conversion->ad = isset($request->ad) ? (string)$request->ad : null;
112        $conversion->pl = isset($request->pl) ? (string)$request->pl : null;
113        $conversion->pltar = isset($request->pltar) ? (string)$request->pltar : null;
114        $conversion->pr1 = isset($request->pr1) ? (string)$request->pr1 : null;
115        $conversion->pr2 = isset($request->pr2) ? (string)$request->pr2 : null;
116        $conversion->url = isset($request->url) ? (string)$request->url : null;
117        $conversion->adpos = isset($request->adpos) ? (string)$request->adpos : null;
118        $conversion->form_data = isset($request->form_data) ? (string)$request->form_data : null;
119        $conversion->save();
120
121        /**
122         * HTTP 201
123         */
124        return $this->sendResponse(201, null);
125    } // postConversion
126
127
128    /**
129     * Return one page of conversions
130     *
131     * query string 'status' value is one of:
132     *  - new
133     *  - approved
134     *  - rejected
135     *  - all *default
136     *
137     * query string 'order' value is one of:
138     *  - customer (sorts by name of customer)
139     *  - campaign (sorts by name of campaign)
140     *  - gclid
141     *  - fbclid
142     *  - *default created_at
143     *
144     *  query string args:
145     *      - customer_id
146     *      - campaign_id
147     *      - status (the name of the status)
148     *      - start
149     *      - end
150     *      - order
151     *      - sort
152     *      - page
153     *      - size
154     *
155     * @author gbh
156     * @see http://swagger.humanverify.test/#get-/api/conversions/digest
157     */
158    public function getConversionsDigest(Request $request):JsonResponse
159    {
160        /**
161         * Get pagination data from request
162         */
163        $paginationDataResult = $this->__validateAndExtractPaginationData($request);
164        $size = $paginationDataResult['size'];
165
166        /**
167         * Get filters, order and sort data from request
168         */
169        $order = in_array($request->query('order'), ['campaign', 'customer', 'gclid', 'fbclid']) ? $request->query('order') : 'created_at';
170        $sort = in_array(strtolower($request->query('sort')), ['asc', 'desc']) ? $request->query('sort') : 'desc';
171        $status = $request->query('status', null);
172        $filter_campaign_id = $request->query('campaign_id', null);
173        $filter_customer_id = $request->query('customer_id', null);
174
175        /**
176         * Function to yield start and end dates
177         *
178         * @param  Request  $request
179         * @return Generator
180         */
181        $getStartEndDates = function (Request $request) {
182            // default start and end dates
183            $startDate = (string)Carbon::createFromFormat('Y-m-d', '1980-01-01')->format('Y-m-d').' 00:00:01';
184            $endDate = (string)Carbon::now()->addDay()->format('Y-m-d').' 23:59:59';
185
186            // use provided dates if present and valid, otherwise use defaults
187            $start = $request->query('start', null);
188            $end = $request->query('end', null);
189
190            // validate and assign start date if non null
191            if (!is_null($start) && $this->__validateYYYYMMDD($start)) {
192                $startDate = (string)Carbon::createFromFormat('Y-m-d', $start)->format('Y-m-d').' 00:00:01';
193            }
194
195            // validate and assign end date if non null
196            if (!is_null($end) && $this->__validateYYYYMMDD($end)) {
197                $endDate = (string)Carbon::createFromFormat('Y-m-d', $end)->format('Y-m-d').' 23:59:59';
198            }
199
200            yield $startDate;
201            yield $endDate;
202        }; // getStartEndDates
203
204        /**
205         * Get start and end dates
206         */
207        $startEndDate =  $getStartEndDates($request);
208        $startDate = $startEndDate->current();
209        $startEndDate->next();
210        $endDate = $startEndDate->current();
211
212        /**
213         * Function to optionally filter query by status id from status name
214         *
215         * @param  Builder $conversionsPage The builder to add the status filter to
216         * @param  Request $request
217         * @return Builder
218         */
219        $filterByStatus = function (Builder $conversionsPage, Request $request) {
220            try {
221                $statusId = ConversionStatus::where('name', '=', $request->query('status'))->first()->id;
222                return $statusId ? $conversionsPage->where('status_id', '=', $statusId) : $conversionsPage;
223            } catch (\Exception $e) {
224                return $conversionsPage;
225            }
226        }; //filterByStatus
227
228
229        $googleAdsCustomers = $this->googleAds->getGoogleAdsCustomers();
230        $googleAdsCampaignIds = self::getGoogleCampaignIds($googleAdsCustomers, $request);
231        #return $this->sendResponse(200, $googleAdsCampaignIds);
232
233        /**
234         * Base query
235         */
236        $conversionsPage = ConversionDigest::leftJoin('google_ads_campaigns', 'google_ads_campaigns.campaign_id', '=', 'cmpid')
237            ->select(
238                'conversions.id',
239                'conversions.gclid',
240                'conversions.fbclid',
241                'conversions.website_url',
242                'conversions.cmpid',
243                'conversions.created_at',
244                'conversions.status_id',
245                'google_ads_customers.customer_id',
246                'google_ads_customers.customer_descriptive_name',
247                'google_ads_campaigns.campaign_id',
248                'google_ads_campaigns.campaign_name'
249            )
250            ->leftJoin('google_ads_customers', 'google_ads_customers.customer_id', '=', 'google_ads_campaigns.customer_id')
251            ->whereIn('cmpid', $googleAdsCampaignIds) // only return campaigns of the session user
252            ->where('conversions.created_at', '>', $startDate)
253            ->where('conversions.created_at', '<', $endDate);
254
255
256        /**
257         * Optional filters
258         */
259        $conversionsPage = $filter_customer_id ? $conversionsPage->where('google_ads_customers.customer_id', '=', $filter_customer_id) : $conversionsPage;
260        $conversionsPage = $filter_campaign_id ? $conversionsPage->where('google_ads_campaigns.campaign_id', '=', $filter_campaign_id) : $conversionsPage;
261        $conversionsPage = $request->query('gclid') ? $conversionsPage->where('gclid', '=', $request->query('gclid')) : $conversionsPage;
262        $conversionsPage = $request->query('fbclid') ? $conversionsPage->where('fbclid', '=', $request->query('fbclid')) : $conversionsPage;
263
264        /**
265         * Optional filter by status name
266         */
267        $conversionsPage = $filterByStatus($conversionsPage, $request);
268
269        /**
270         * Order and sort
271         */
272        switch ($order) {
273            // sort by campaign name
274            case 'campaign':
275                $conversionsPage->orderBy('google_ads_campaigns.campaign_name', $sort);
276                break;
277
278            // sort by customer name
279            case 'customer':
280                $conversionsPage->orderBy('google_ads_customers.customer_descriptive_name', $sort);
281                break;
282
283            // sort by click ids
284            case 'gclid':
285            case 'fbclid':
286                $conversionsPage->orderBy($order, $sort);
287                break;
288
289            case 'created_at':
290                $conversionsPage->orderBy('conversions.created_at', $sort);
291                break;
292        }
293
294        /**
295         * Page and items
296         */
297        $conversionsPage = $conversionsPage->paginate($size);
298        $conversionsItems = $conversionsPage->items();
299
300        /**
301         * Build pagination hateoas
302         */
303        $paginationHateoas = $this->createPaginationFromOrm($conversionsPage);
304
305        /**
306         * HTTP 200
307         */
308        return $this->sendResponse(200, $conversionsItems, $paginationHateoas);
309    } // getConversionsDigest
310
311
312    /**
313     * Return one conversion
314     *
315     * @author gbh
316     * @see http://swagger-humanverify.fruitbat.systems/#get-/api/conversions/-id-
317     */
318    public function getConversion(Request $request, Int $id):JsonResponse
319    {
320        $googleAdsCustomers = $this->googleAds->getGoogleAdsCustomers();
321        $googleAdsCampaignIds = self::getGoogleCampaignIds($googleAdsCustomers, $request);
322
323        /**
324         * Get conversion
325         */
326        $conversion =  Conversion::where('id', '=', $id)
327                        ->whereIn('cmpid', $googleAdsCampaignIds) // only return campaigns of the session user
328                        ->first();
329
330        /**
331         * No record
332         * HTTP 404
333         */
334        if (is_null($conversion)) {
335            return $this->sendError(404, null, null);
336        }
337
338        /**
339         * HTTP 200
340         */
341        return $this->sendResponse(200, $conversion);
342    } // getConversion
343
344
345    /**
346     * Return transactions for one conversion
347     *
348     * @author gbh
349     * @see http://swagger-humanverify.fruitbat.systems/#get-/api/conversions/-id-/transactions
350     */
351    public function getConversionTransactions(Request $request, Int $id):JsonResponse
352    {
353        $googleAdsCustomers = $this->googleAds->getGoogleAdsCustomers();
354        $googleAdsCampaignIds = self::getGoogleCampaignIds($googleAdsCustomers, $request);
355
356        /**
357         * Get conversion
358         */
359        $conversion =  Conversion::where('id', '=', $id)
360                        ->whereIn('cmpid', $googleAdsCampaignIds) // only return campaigns of the session user
361                        ->first();
362
363        /**
364         * No record
365         * HTTP 404
366         */
367        if (is_null($conversion)) {
368            return $this->sendError(404, null, null);
369        }
370
371        $transactions = ConversionTransaction::where('conversion_id', '=', $id)
372            ->orderBy('created_at', 'desc')
373            ->get();
374
375        /**
376         * HTTP 200
377         */
378        return $this->sendResponse(200, $transactions);
379    }
380
381
382    /*
383    public function postConversionApproveTest(Request $request, Int $id):JsonResponse
384    {
385         * attempting to get the 'conversion acctions' attached to a 'customer' here,
386         * see: https://groups.google.com/g/adwords-api/c/8f50BJxpaQI
387         * https://groups.google.com/g/adwords-api/c/8f50BJxpaQI
388
389        $googleCustomerIds = $this->getGoogleAdsCustomerIds();
390        $gcid = $googleCustomerIds[2];
391
392        $t = $this->getGoogleAdsCampaigns($gcid);
393        return $this->sendResponse(200, $t);
394
395
396        //$googleAdsClient = $this->getAdsClient($gcid);
397        $googleAdsClient = $this->getAdsClient();
398
399        $googleAdsServiceClient = $googleAdsClient->getGoogleAdsServiceClient();
400        $query = 'SELECT conversion_action.id, conversion_action.name FROM conversion_action';
401        $query = 'SELECT campaign.id, campaign.name FROM campaign ORDER BY campaign.id';
402        $stream =$googleAdsServiceClient->search($gcid, $query);
403
404        foreach ($stream->iterateAllElements() as $googleAdsRow) {
405            //print_r($googleAdsRow);
406            print ".";
407            print_r($googleAdsRow->getCampaign()->getName());
408            print_r($googleAdsRow->getCampaign()->getId());
409        }
410
411
412        return $this->sendResponse(200, "END postConversionApproveTest test");
413        // end
414
415         * Get conversion
416        $conversion = Conversion::find($id);
417        print json_encode($conversion);
418
419        $googleAdsClient = $this->getAdsClient();
420
421        $conversionArgs = [
422            'name' => 'Earth to Mars Cruises Conversion #',
423            'category' => \Google\Ads\GoogleAds\V10\Enums\ConversionActionCategoryEnum\ConversionActionCategory::PBDEFAULT,
424            'type' => \Google\Ads\GoogleAds\V10\Enums\ConversionActionTypeEnum\ConversionActionType::WEBPAGE,
425            'status' => \Google\Ads\GoogleAds\V10\Enums\ConversionActionStatusEnum\ConversionActionStatus::ENABLED,
426            'view_through_lookback_window_days' => 15,
427            'value_settings' => new \Google\Ads\GoogleAds\V10\Resources\ConversionAction\ValueSettings([
428                'default_value' => 23.41,
429                'always_use_default_value' => true
430            ])
431            ];
432
433        $ca = new \Google\Ads\GoogleAds\V10\Resources\ConversionAction();
434        return $this->sendResponse(200, "postConversionApproveTest test");
435    }
436     */
437
438    /**
439     * Set on conversion as approved
440     *
441     * @note conversion_action_id comes from GET /api/googleads/customers/{id}/conversionaction
442     * @author gbh
443     * @see http://swagger-humanverify.fruitbat.systems/#post-/api/conversions/-id-/approved
444     */
445    public function postConversionApprove(Request $request, Int $id):JsonResponse
446    {
447        $googleAdsCustomers = $this->googleAds->getGoogleAdsCustomers();
448        $googleAdsCampaignIds = self::getGoogleCampaignIds($googleAdsCustomers, $request);
449
450        /**
451         * Validate request body
452         * HTTP 400
453         */
454        $validationTest = $this->__validateConversionApprovalRequest($request);
455        if (is_object($validationTest)) {
456            return $validationTest;
457        }
458
459        /**
460         * Get conversion
461         */
462        $conversion =  Conversion::where('id', '=', $id)
463                        ->whereIn('cmpid', $googleAdsCampaignIds) // only return campaigns of the session user
464                        ->first();
465
466        /**
467         * No record
468         * HTTP 404
469         */
470        if (is_null($conversion)) {
471            return $this->sendError(404, null, null);
472        }
473
474        /**
475         * Get status id for 'approved'
476         */
477        $statusId = ConversionStatus::where('name', '=', 'approved')->first()->id;
478
479        /**
480         * Get the currency code
481         */
482        $currencyCode = ConversionCurrency::where('id', '=', $conversion->conversion_currency)->first()->name;
483        
484        /**
485         * BOOKMARK
486         * Approve in google
487         */
488
489        /**
490         * Build the click conversion
491         * @see https://developers.google.com/google-ads/api/docs/samples/upload-conversion-with-identifiers
492         */
493        $campaignId = $conversion->cmpid;
494        $conversionId = $conversion->id;
495        $gclid = $conversion->gclid;
496        $currencyCode = $conversion->conversion_currency;
497        $conversionActionId = $request->conversion_action_id;
498        $conversionValue = $request->conversion_value;
499        $conversionDateTime = date("Y-m-d H:i:sP");
500        $orderId = $request->order_id ?? null;
501
502        // get the customer id from the campaign id via cache
503        $customerId = $this->googleAds->getGoogleAdsCustomerFromCampaign($campaignId);
504
505        // the conversion action here is just an endpoint, but we get it the canonical way anyway.
506        $conversionAction = \Google\Ads\GoogleAds\Util\V10\ResourceNames::forConversionAction($customerId, $conversionActionId);
507
508        // the click conversion object
509        $clickConversion = new \Google\Ads\GoogleAds\V10\Services\ClickConversion([
510            'conversion_action' => $conversionAction,
511            'conversion_date_time' => $conversionDateTime,
512            'conversion_value' => $conversionValue,
513            'currency_code' => $currencyCode
514        ]);
515
516        // optional order id from body of request
517        if ($orderId) {
518            $clickConversion->setOrderId($orderId);
519        }
520
521        // BOOKMARK SET USER IDENTIFIER
522        // see https://developers.google.com/google-ads/api/docs/samples/upload-conversion-with-identifiers
523
524        /**
525         * Submit conversion to google
526         */
527        try {
528            $this->googleAds->submitConversionToGoogle($customerId, $clickConversion);
529        }
530        catch (\Exception $e) {
531            return $this->sendError(500, $e->getMessage(), null);
532        }
533
534        /**
535         * Approve locally
536         */
537
538        /**
539         * Update status id
540         */
541        $conversion->status_id = $statusId;
542        $conversion->status_user_id = $this->getLoggedInUserId();
543        $conversion->save();
544
545        /**
546         * Write transaction
547         */
548        $conversionTransaction = new ConversionTransaction();
549        $conversionTransaction->conversion_id = $id;
550        $conversionTransaction->user_id = $this->getLoggedInUserId();
551        $conversionTransaction->status_id = $statusId;
552        $conversionTransaction->event = "approved";
553        $conversionTransaction->save();
554
555        /**
556         * HTTP 201
557         */
558        return $this->sendResponse(201, null);
559    } // postConversionApprove
560
561
562    /**
563     * Set one conversion as rejected
564     *
565     * @author gbh
566     * @see http://swagger-humanverify.fruitbat.systems/#post-/api/conversions/-id-/rejected
567     */
568    public function postConversionReject(Request $request, Int $id):JsonResponse
569    {
570        $googleAdsCustomers = $this->googleAds->getGoogleAdsCustomers();
571        $googleAdsCampaignIds = self::getGoogleCampaignIds($googleAdsCustomers, $request);
572
573        /**
574         * Get conversion
575         */
576        $conversion =  Conversion::where('id', '=', $id)
577                        ->whereIn('cmpid', $googleAdsCampaignIds) // only return campaigns of the session user
578                        ->first();
579
580        /**
581         * No record
582         * HTTP 404
583         */
584        if (is_null($conversion)) {
585            return $this->sendError(404, null, null);
586        }
587
588        /**
589         * Get status id for 'rejected'
590         */
591        $statusId = ConversionStatus::where('name', '=', 'rejected')->first()->id;
592
593        /**
594         * Update status id
595         */
596        $conversion->status_id = $statusId;
597        $conversion->status_user_id = $this->getLoggedInUserId();
598        $conversion->save();
599
600        /**
601         * Write transaction
602         */
603        $conversionTransaction = new ConversionTransaction();
604        $conversionTransaction->conversion_id = $id;
605        $conversionTransaction->user_id = $this->getLoggedInUserId();
606        $conversionTransaction->status_id = $statusId;
607        $conversionTransaction->event = "rejected";
608        $conversionTransaction->save();
609
610        /**
611         * HTTP 201
612         */
613        return $this->sendResponse(201, null);
614    } // postConversionReject
615
616
617    /**
618     * Set on conversion as flagged
619     *
620     * @author gbh
621     * @see http://swagger-humanverify.fruitbat.systems/#post-/api/conversions/-id-/flagged
622     */
623    public function postConversionFlag(Request $request, Int $id):JsonResponse
624    {
625        $googleAdsCustomers = $this->googleAds->getGoogleAdsCustomers();
626        $googleAdsCampaignIds = self::getGoogleCampaignIds($googleAdsCustomers, $request);
627
628        /**
629         * Get conversion
630         */
631        $conversion =  Conversion::where('id', '=', $id)
632                        ->whereIn('cmpid', $googleAdsCampaignIds) // only return campaigns of the session user
633                        ->first();
634
635        /**
636         * No record
637         * HTTP 404
638         */
639        if (is_null($conversion)) {
640            return $this->sendError(404, null, null);
641        }
642
643        /**
644         * Get status id for 'flagged'
645         */
646        $statusId = ConversionStatus::where('name', '=', 'flagged')->first()->id;
647
648        /**
649         * Update status id
650         */
651        $conversion->status_id = $statusId;
652        $conversion->status_user_id = $this->getLoggedInUserId();
653        $conversion->save();
654
655        /**
656         * Write transaction
657         */
658        $conversionTransaction = new ConversionTransaction();
659        $conversionTransaction->conversion_id = $id;
660        $conversionTransaction->user_id = $this->getLoggedInUserId();
661        $conversionTransaction->status_id = $statusId;
662        $conversionTransaction->event = "flagged";
663        $conversionTransaction->save();
664
665        /**
666         * HTTP 201
667         */
668        return $this->sendResponse(201, null);
669    } // postConversionFlag
670
671
672    /**
673     * Post one note to a conversion
674     *
675     * @author gbh
676     * @see http://swagger-humanverify.fruitbat.systems/#post-/api/conversions/-id-/note
677     */
678    public function postConversionNote(Int $id, Request $request):JsonResponse
679    {
680        $googleAdsCustomers = $this->googleAds->getGoogleAdsCustomers();
681        $googleAdsCampaignIds = self::getGoogleCampaignIds($googleAdsCustomers, $request);
682
683        /**
684         * Get conversion
685         */
686        $conversion =  Conversion::where('id', '=', $id)
687                        ->whereIn('cmpid', $googleAdsCampaignIds) // only return campaigns of the session user
688                        ->first();
689
690        /**
691         * No record
692         * HTTP 404
693         */
694        if (is_null($conversion)) {
695            return $this->sendError(404, null, null);
696        }
697
698        /**
699         * Validate request body
700         * HTTP 400
701         */
702        $validationTest = $this->__validateNoteRequest($request);
703        if (is_object($validationTest)) {
704            return $validationTest;
705        }
706
707        /**
708         * Create the note
709         */
710        $conversionNote = new ConversionNote();
711        $conversionNote->conversion_id = $id;
712        $conversionNote->note = $request->note;
713        $conversionNote->user_id = $this->getLoggedInUserId();
714        $conversionNote->save();
715
716        /**
717         * Write transaction
718         */
719        $conversionTransaction = new ConversionTransaction();
720        $conversionTransaction->conversion_id = $id;
721        $conversionTransaction->user_id = $this->getLoggedInUserId();
722        $conversionTransaction->status_id = $conversion->status_id;
723        $conversionTransaction->event = "note";
724        $conversionTransaction->note = $request->note;
725        $conversionTransaction->save();
726
727        /**
728         * HTTP 201
729         */
730        return $this->sendResponse(201, null);
731    } // postConversionNote
732
733
734    /**
735     * Validates request body of a Note
736     *
737     */
738    protected function __validateNoteRequest(Request $request) // @phpstan-ignore-line
739    {
740        $itemValidationRules = [
741            'note' => 'required|string',
742        ];
743
744        $validationResult = $this->__validateJson($itemValidationRules, $request);
745
746        if (is_array($validationResult)) {
747            return $this->sendError(400, $validationResult, "Validation error");
748        } else {
749            return true;
750        }
751    } // __validateNoteRequest
752
753
754    /**
755     * Validates request body of a Conversion
756     *
757     */
758    protected function __validateConversionRequest(Request $request) // @phpstan-ignore-line
759    {
760        $itemValidationRules = [
761            'website_url' => 'required|string',
762        ];
763
764        $validationResult = $this->__validateJson($itemValidationRules, $request);
765
766        if (is_array($validationResult)) {
767            return $this->sendError(400, $validationResult, "Validation error");
768        } else {
769            return true;
770        }
771    } // __validateConversionRequest
772
773
774    /**
775     * Validates that string $date is a valid yyyy-mm-dd date
776     */
777    protected function __validateYYYYMMDD(String $date):bool
778    {
779        if (preg_match("/^([0-9]{4})-([0-9]{2})-([0-9]{2})$/", $date, $parts)) {
780            if (checkdate($parts[2], $parts[3], $parts[1])) {
781                return true;
782            }
783        }
784        return false;
785    }
786
787
788    /**
789     * Validates request body of conversion approval
790     *
791     */
792    protected function __validateConversionApprovalRequest(Request $request) // @phpstan-ignore-line
793    {
794        $itemValidationRules = [
795            'conversion_action_id' => 'required|integer',
796            'conversion_value' => 'required|numeric|min:0.01',
797            'order_id' => 'string',
798        ];
799
800        $validationResult = $this->__validateJson($itemValidationRules, $request);
801
802        if (is_array($validationResult)) {
803            return $this->sendError(400, $validationResult, "Validation error");
804        } else {
805            return true;
806        }
807    } // __validateNoteRequest
808
809
810    protected static function getGoogleCampaignIds(array $googleAdsCustomers, Request $request)
811    {
812        // refresh cache of customers and campaigns if required. select all customers
813        //$googleCustomers = $this->googleAds->getGoogleAdsCustomers();
814
815        // unique, non-null ids as array
816        $googleCustomerIds = array_unique(
817            array_filter(
818                array_map(
819                    fn ($n) => $n['customer_id'],
820                    $googleAdsCustomers
821                ),
822                fn ($n) => strlen($n) > 0
823            )
824        );
825           
826        // campaign cache has just been refreshed so safe to select from it
827        return GoogleAdsCampaign::whereIn('customer_id', $googleCustomerIds)->pluck('campaign_id')->toArray();
828    }
829}