Analytics::sendCollectedEvents()   B
last analyzed

Complexity

Conditions 8
Paths 14

Size

Total Lines 61
Code Lines 31

Duplication

Lines 0
Ratio 0 %

Importance

Changes 2
Bugs 1 Features 0
Metric Value
cc 8
eloc 31
c 2
b 1
f 0
nc 14
nop 0
dl 0
loc 61
rs 8.1795

How to fix   Long Method   

Long Method

Small methods make your code easier to understand, in particular if combined with a good name. Besides, if your method is small, finding a good name is usually much easier.

For example, if you find yourself adding comments to a method's body, this is usually a good sign to extract the commented part to a new method, and use the comment as a starting point when coming up with a good name for this new method.

Commonly applied refactorings include:

1
<?php
2
/**
3
 * Instant Analytics plugin for Craft CMS
4
 *
5
 * @author    nystudio107
0 ignored issues
show
Coding Style introduced by
The tag in position 1 should be the @package tag
Loading history...
Coding Style introduced by
Content of the @author tag must be in the form "Display Name <[email protected]>"
Loading history...
6
 * @copyright Copyright (c) 2017 nystudio107
0 ignored issues
show
Coding Style introduced by
The tag in position 2 should be the @author tag
Loading history...
Coding Style introduced by
@copyright tag must contain a year and the name of the copyright holder
Loading history...
7
 * @link      http://nystudio107.com
0 ignored issues
show
Coding Style introduced by
The tag in position 3 should be the @copyright tag
Loading history...
8
 * @package   InstantAnalytics
0 ignored issues
show
Coding Style introduced by
The tag in position 4 should be the @link tag
Loading history...
9
 * @since     1.0.0
10
 */
0 ignored issues
show
Coding Style introduced by
PHP version not specified
Loading history...
Coding Style introduced by
Missing @category tag in file comment
Loading history...
Coding Style introduced by
Missing @license tag in file comment
Loading history...
11
12
namespace nystudio107\instantanalyticsGa4\ga4;
13
14
use Br33f\Ga4\MeasurementProtocol\Dto\Event\AbstractEvent;
15
use Br33f\Ga4\MeasurementProtocol\Dto\Request\BaseRequest;
16
use Br33f\Ga4\MeasurementProtocol\Exception\HydrationException;
17
use Br33f\Ga4\MeasurementProtocol\Exception\ValidationException;
18
use Br33f\Ga4\MeasurementProtocol\HttpClient;
19
use Craft;
20
use craft\commerce\elements\Order;
21
use craft\commerce\elements\Product;
22
use craft\commerce\elements\Variant;
23
use craft\errors\MissingComponentException;
24
use craft\helpers\App;
25
use nystudio107\instantanalyticsGa4\helpers\Analytics as AnalyticsHelper;
26
use nystudio107\instantanalyticsGa4\InstantAnalytics;
27
use nystudio107\seomatic\Seomatic;
28
use yii\base\InvalidConfigException;
29
30
/**
0 ignored issues
show
Coding Style introduced by
Missing short description in doc comment
Loading history...
31
 * @author    nystudio107
0 ignored issues
show
Coding Style introduced by
The tag in position 1 should be the @package tag
Loading history...
Coding Style introduced by
Content of the @author tag must be in the form "Display Name <[email protected]>"
Loading history...
Coding Style introduced by
Tag value for @author tag indented incorrectly; expected 2 spaces but found 4
Loading history...
32
 * @package   InstantAnalytics
0 ignored issues
show
Coding Style introduced by
Tag value for @package tag indented incorrectly; expected 1 spaces but found 3
Loading history...
33
 * @since     1.2.0
0 ignored issues
show
Coding Style introduced by
The tag in position 3 should be the @author tag
Loading history...
Coding Style introduced by
Tag value for @since tag indented incorrectly; expected 3 spaces but found 5
Loading history...
34
 *
35
 * @method Analytics setAllowGoogleSignals(string $value)
36
 * @method Analytics setAllowAdPersonalizationSignals(string $value)
37
 * @method Analytics setCampaignContent(string $value)
38
 * @method Analytics setCampaignId(string $value)
39
 * @method Analytics setCampaignMedium(string $value)
40
 * @method Analytics setCampaignName(string $value)
41
 * @method Analytics setCampaignSource(string $value)
42
 * @method Analytics setCampaignTerm(string $value)
43
 * @method Analytics setCampaign(string $value)
44
 * @method Analytics setClientId(string $value)
45
 * @method Analytics setContentGroup(string $value)
46
 * @method Analytics setCookieDomain(string $value)
47
 * @method Analytics setCookieExpires(string $value)
48
 * @method Analytics setCookieFlags(string $value)
49
 * @method Analytics setCookiePath(string $value)
50
 * @method Analytics setCookiePrefix(string $value)
51
 * @method Analytics setCookieUpdate(string $value)
52
 * @method Analytics setLanguage(string $value)
53
 * @method Analytics setPageLocation(string $value)
54
 * @method Analytics setPageReferrer(string $value)
55
 * @method Analytics setPageTitle(string $value)
56
 * @method Analytics setSendPageView(string $value)
57
 * @method Analytics setScreenResolution(string $value)
58
 * @method Analytics setUserId(string $value)
59
 */
0 ignored issues
show
Coding Style introduced by
Missing @category tag in class comment
Loading history...
Coding Style introduced by
Missing @license tag in class comment
Loading history...
Coding Style introduced by
Missing @link tag in class comment
Loading history...
60
class Analytics
61
{
62
    /**
0 ignored issues
show
Coding Style introduced by
Missing short description in doc comment
Loading history...
63
     * @var BaseRequest|null
64
     */
65
    private ?BaseRequest $_request = null;
66
67
    /**
0 ignored issues
show
Coding Style introduced by
Missing short description in doc comment
Loading history...
68
     * @var Service|null|false
69
     */
70
    private mixed $_service = null;
71
72
    /**
0 ignored issues
show
Coding Style introduced by
Missing short description in doc comment
Loading history...
73
     * @var string|null
74
     */
75
    private ?string $_affiliation = null;
76
77
    private ?bool $_shouldSendAnalytics = null;
78
79
    private ?string $_sessionString = null;
80
81
    private array $eventList = [];
0 ignored issues
show
Coding Style introduced by
Private member variable "eventList" must be prefixed with an underscore
Loading history...
82
83
    /**
84
     * Component factory for creating events.
85
     *
86
     * @return ComponentFactory
87
     */
88
    public function create(): ComponentFactory
89
    {
90
        return new ComponentFactory();
91
    }
92
93
    /**
94
     * Add an event to be sent to Google
95
     *
96
     * @param AbstractEvent $event
0 ignored issues
show
Coding Style introduced by
Missing parameter comment
Loading history...
Coding Style introduced by
Tag value for @param tag indented incorrectly; expected 2 spaces but found 1
Loading history...
97
     * @return void
0 ignored issues
show
Coding Style introduced by
Tag @return cannot be grouped with parameter tags in a doc comment
Loading history...
98
     */
99
    public function addEvent(AbstractEvent $event): void
100
    {
101
        if ($this->_sessionString === null) {
102
            $this->_sessionString = AnalyticsHelper::getSessionString();
103
        }
104
105
        if (str_contains($this->_sessionString, '.')) {
0 ignored issues
show
Bug introduced by
It seems like $this->_sessionString can also be of type null; however, parameter $haystack of str_contains() does only seem to accept string, maybe add an additional type check? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

105
        if (str_contains(/** @scrutinizer ignore-type */ $this->_sessionString, '.')) {
Loading history...
106
            [$sessionId, $sessionNumber] = explode('.', $this->_sessionString);
0 ignored issues
show
Bug introduced by
It seems like $this->_sessionString can also be of type null; however, parameter $string of explode() does only seem to accept string, maybe add an additional type check? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

106
            [$sessionId, $sessionNumber] = explode('.', /** @scrutinizer ignore-type */ $this->_sessionString);
Loading history...
107
            $event->setParamValue('sessionId', $sessionId);
108
            $event->setParamValue('sessionNumber', $sessionNumber);
109
        }
110
111
        $this->eventList[] = $event;
112
    }
113
114
    /**
115
     * Send the events collected so far.
116
     *
117
     * @return ?array
118
     * @throws HydrationException
119
     * @throws ValidationException
120
     */
121
    public function sendCollectedEvents(): ?array
122
    {
123
        if ($this->_shouldSendAnalytics === null) {
124
            $this->_shouldSendAnalytics = AnalyticsHelper::shouldSendAnalytics();
125
        }
126
127
        if (!$this->_shouldSendAnalytics) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $this->_shouldSendAnalytics of type boolean|null is loosely compared to false; this is ambiguous if the boolean can be false. You might want to explicitly use !== null instead.

If an expression can have both false, and null as possible values. It is generally a good practice to always use strict comparison to clearly distinguish between those two values.

$a = canBeFalseAndNull();

// Instead of
if ( ! $a) { }

// Better use one of the explicit versions:
if ($a !== null) { }
if ($a !== false) { }
if ($a !== null && $a !== false) { }
Loading history...
128
            return null;
129
        }
130
131
        $service = $this->service();
132
133
        if (!$service) {
134
            return null;
135
        }
136
137
        $request = $this->request();
138
        $eventCount = count($this->eventList);
139
140
        if (!InstantAnalytics::$settings->sendAnalyticsData) {
141
            InstantAnalytics::$plugin->logAnalyticsEvent(
0 ignored issues
show
Bug introduced by
The method logAnalyticsEvent() does not exist on null. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-call  annotation

141
            InstantAnalytics::$plugin->/** @scrutinizer ignore-call */ 
142
                                       logAnalyticsEvent(

This check looks for calls to methods that do not seem to exist on a given type. It looks for the method on the type itself as well as in inherited classes or implemented interfaces.

This is most likely a typographical error or the method has been renamed.

Loading history...
142
                'Analytics not enabled - skipped sending {count} events',
143
                ['count' => $eventCount],
144
                __METHOD__
145
            );
146
147
            return null;
148
        }
149
150
        if ($eventCount === 0) {
151
            InstantAnalytics::$plugin->logAnalyticsEvent(
152
                'No events collected to send',
153
                [],
154
                __METHOD__
155
            );
156
157
            return null;
158
        }
159
160
        InstantAnalytics::$plugin->logAnalyticsEvent(
161
            'Sending {count} analytics events',
162
            ['count' => $eventCount],
163
            __METHOD__
164
        );
165
166
        // Batch into groups of 25
167
        $responses = [];
168
169
        foreach (array_chunk($this->eventList, 25) as $chunk) {
170
            $request->getEvents()->setEventList([]);
171
172
            /** @var AbstractEvent $event */
0 ignored issues
show
Coding Style introduced by
The open comment tag must be the only content on the line
Loading history...
Coding Style introduced by
Missing short description in doc comment
Loading history...
Coding Style introduced by
The close comment tag must be the only content on the line
Loading history...
173
            foreach ($chunk as $event) {
174
                $request->addEvent($event);
175
            }
176
177
            $responses[] = $service->send($request);
178
        }
179
180
181
        return $responses;
182
    }
183
184
    public function getAffiliation(): ?string
0 ignored issues
show
Coding Style introduced by
Missing doc comment for function getAffiliation()
Loading history...
185
    {
186
        return $this->_affiliation;
187
    }
188
189
    /**
190
     * Set affiliation for all the events that incorporate Commerce Product info for the remaining duration of request.
191
     *
192
     * @param string $affiliation
0 ignored issues
show
Coding Style introduced by
Missing parameter comment
Loading history...
Coding Style introduced by
Tag value for @param tag indented incorrectly; expected 2 spaces but found 1
Loading history...
193
     * @return $this
0 ignored issues
show
Coding Style introduced by
Tag @return cannot be grouped with parameter tags in a doc comment
Loading history...
194
     */
195
    public function setAffiliation(string $affiliation): self
196
    {
197
        $this->_affiliation = $affiliation;
198
        return $this;
199
    }
200
201
    /**
202
     * Add a commerce item list impression.
203
     *
204
     * @param Product|Variant $productVariant
0 ignored issues
show
Coding Style introduced by
Missing parameter comment
Loading history...
Coding Style introduced by
Tag value for @param tag indented incorrectly; expected 2 spaces but found 1
Loading history...
205
     * @param int $index
0 ignored issues
show
Coding Style introduced by
Missing parameter comment
Loading history...
Coding Style introduced by
Tag value for @param tag indented incorrectly; expected 2 spaces but found 1
Loading history...
Coding Style introduced by
Expected 13 spaces after parameter type; 1 found
Loading history...
206
     * @param string $listName
0 ignored issues
show
Coding Style introduced by
Missing parameter comment
Loading history...
Coding Style introduced by
Expected 10 spaces after parameter type; 1 found
Loading history...
Coding Style introduced by
Tag value for @param tag indented incorrectly; expected 2 spaces but found 1
Loading history...
207
     * @throws InvalidConfigException
0 ignored issues
show
Coding Style introduced by
Tag @throws cannot be grouped with parameter tags in a doc comment
Loading history...
208
     */
0 ignored issues
show
Coding Style introduced by
Missing @return tag in function comment
Loading history...
209
    public function addCommerceProductImpression(Product|Variant $productVariant, int $index = 0, string $listName = 'default')
210
    {
211
        InstantAnalytics::$plugin->commerce->addCommerceProductImpression($productVariant);
212
    }
213
214
    /**
215
     * Begin checkout.
216
     *
217
     * @param Order $cart
0 ignored issues
show
Coding Style introduced by
Missing parameter comment
Loading history...
218
     */
0 ignored issues
show
Coding Style introduced by
Missing @return tag in function comment
Loading history...
219
    public function beginCheckout(Order $cart)
220
    {
221
        InstantAnalytics::$plugin->commerce->triggerBeginCheckoutEvent($cart);
222
    }
223
224
    /**
225
     * Add a commerce item list impression.
226
     *
227
     * @param Product|Variant $productVariant
0 ignored issues
show
Coding Style introduced by
Missing parameter comment
Loading history...
Coding Style introduced by
Tag value for @param tag indented incorrectly; expected 6 spaces but found 1
Loading history...
228
     * @param int $index
0 ignored issues
show
Coding Style introduced by
Missing parameter comment
Loading history...
Coding Style introduced by
Expected 13 spaces after parameter type; 1 found
Loading history...
Coding Style introduced by
Tag value for @param tag indented incorrectly; expected 6 spaces but found 1
Loading history...
229
     * @param string $listName
0 ignored issues
show
Coding Style introduced by
Missing parameter comment
Loading history...
Coding Style introduced by
Expected 10 spaces after parameter type; 1 found
Loading history...
Coding Style introduced by
Tag value for @param tag indented incorrectly; expected 6 spaces but found 1
Loading history...
230
     * @throws InvalidConfigException
0 ignored issues
show
Coding Style introduced by
Tag @throws cannot be grouped with parameter tags in a doc comment
Loading history...
Coding Style introduced by
Tag value for @throws tag indented incorrectly; expected 5 spaces but found 1
Loading history...
231
     * @deprecated `Analytics::addCommerceProductDetailView()` is deprecated. Use `Analytics::addCommerceProductImpression()` instead.
0 ignored issues
show
Coding Style introduced by
Tag @deprecated cannot be grouped with parameter tags in a doc comment
Loading history...
232
     */
0 ignored issues
show
Coding Style introduced by
Missing @return tag in function comment
Loading history...
233
    public function addCommerceProductDetailView(Product|Variant $productVariant, int $index = 0, string $listName = 'default')
234
    {
235
        Craft::$app->getDeprecator()->log('Analytics::addCommerceProductDetailView()', '`Analytics::addCommerceProductDetailView()` is deprecated. Use `Analytics::addCommerceProductImpression()` instead.');
236
        $this->addCommerceProductImpression($productVariant);
237
    }
238
239
    /**
240
     * Add a commerce product list impression.
241
     *
242
     * @param array $products
0 ignored issues
show
Coding Style introduced by
Missing parameter comment
Loading history...
243
     * @param $listName
0 ignored issues
show
Coding Style introduced by
Missing parameter comment
Loading history...
244
     */
0 ignored issues
show
Coding Style introduced by
Missing @return tag in function comment
Loading history...
245
    public function addCommerceProductListImpression(array $products, string $listName = 'default')
246
    {
247
        InstantAnalytics::$plugin->commerce->addCommerceProductListImpression($products, $listName);
248
    }
249
250
    /**
251
     * Set the measurement id.
252
     *
253
     * @param string $measurementId
0 ignored issues
show
Coding Style introduced by
Missing parameter comment
Loading history...
Coding Style introduced by
Tag value for @param tag indented incorrectly; expected 2 spaces but found 1
Loading history...
254
     * @return $this
0 ignored issues
show
Coding Style introduced by
Tag @return cannot be grouped with parameter tags in a doc comment
Loading history...
255
     */
256
    public function setMeasurementId(string $measurementId): self
257
    {
258
        $this->service()?->setMeasurementId($measurementId);
259
        return $this;
260
    }
261
262
    /**
263
     * Set the API secret.
264
     *
265
     * @param string $apiSecret
0 ignored issues
show
Coding Style introduced by
Missing parameter comment
Loading history...
Coding Style introduced by
Tag value for @param tag indented incorrectly; expected 2 spaces but found 1
Loading history...
266
     * @return $this
0 ignored issues
show
Coding Style introduced by
Tag @return cannot be grouped with parameter tags in a doc comment
Loading history...
267
     */
268
    public function setApiSecret(string $apiSecret): self
269
    {
270
        $this->service()?->setApiSecret($apiSecret);
271
        return $this;
272
    }
273
274
    public function __call(string $methodName, array $arguments): ?self
0 ignored issues
show
Coding Style introduced by
Missing doc comment for function __call()
Loading history...
275
    {
276
        $knownProperties = [
277
            'allowGoogleSignals' => 'allow_google_signals',
278
            'allowAdPersonalizationSignals' => 'allow_ad_personalization_signals',
279
            'campaignContent' => 'campaign_content',
280
            'campaignId' => 'campaign_id',
281
            'campaignMedium' => 'campaign_medium',
282
            'campaignName' => 'campaign_name',
283
            'campaignSource' => 'campaign_source',
284
            'campaignTerm' => 'campaign_term',
285
            'campaign' => 'campaign',
286
            'clientId' => 'client_id',
287
            'contentGroup' => 'content_group',
288
            'cookieDomain' => 'cookie_domain',
289
            'cookieExpires' => 'cookie_expires',
290
            'cookieFlags' => 'cookie_flags',
291
            'cookiePath' => 'cookie_path',
292
            'cookiePrefix' => 'cookie_prefix',
293
            'cookieUpdate' => 'cookie_update',
294
            'language' => 'language',
295
            'pageLocation' => 'page_location',
296
            'pageReferrer' => 'page_referrer',
297
            'pageTitle' => 'page_title',
298
            'sendPageView' => 'send_page_view',
299
            'screenResolution' => 'screen_resolution',
300
            'userId' => 'user_id',
301
        ];
302
303
        if (str_starts_with($methodName, 'set')) {
304
            $methodName = lcfirst(substr($methodName, 3));
305
306
            $service = $this->service();
307
            if ($service && !empty($knownProperties[$methodName])) {
308
                $service->setAdditionalQueryParam($knownProperties[$methodName], $arguments[0]);
309
310
                return $this;
311
            }
312
        }
313
314
        return null;
315
    }
316
317
    public function request(): BaseRequest
0 ignored issues
show
Coding Style introduced by
Missing doc comment for function request()
Loading history...
318
    {
319
        if ($this->_request === null) {
320
            $this->_request = new BaseRequest();
321
322
            $this->_request->setClientId(AnalyticsHelper::getClientId());
0 ignored issues
show
Bug introduced by
The method setClientId() does not exist on null. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-call  annotation

322
            $this->_request->/** @scrutinizer ignore-call */ 
323
                             setClientId(AnalyticsHelper::getClientId());

This check looks for calls to methods that do not seem to exist on a given type. It looks for the method on the type itself as well as in inherited classes or implemented interfaces.

This is most likely a typographical error or the method has been renamed.

Loading history...
323
324
            if (InstantAnalytics::$settings->sendUserId) {
325
                $userId = AnalyticsHelper::getUserId();
326
327
                if ($userId) {
328
                    $this->request()->setUserId($userId);
329
                }
330
            }
331
        }
332
333
334
        return $this->_request;
0 ignored issues
show
Bug Best Practice introduced by
The expression return $this->_request could return the type null which is incompatible with the type-hinted return Br33f\Ga4\MeasurementPro...Dto\Request\BaseRequest. Consider adding an additional type-check to rule them out.
Loading history...
335
    }
336
337
    /**
338
     * Init the service used to send events
339
     */
0 ignored issues
show
Coding Style introduced by
Missing @return tag in function comment
Loading history...
340
    public function init(): void
341
    {
342
        $this->service();
343
        $this->request();
344
    }
345
346
    protected function service(): ?Service
0 ignored issues
show
Coding Style introduced by
Missing doc comment for function service()
Loading history...
347
    {
348
        if ($this->_service === null) {
349
            $settings = InstantAnalytics::$settings;
350
            $apiSecret = App::parseEnv($settings->googleAnalyticsMeasurementApiSecret);
351
            $measurementId = App::parseEnv($settings->googleAnalyticsMeasurementId);
352
353
            if (empty($apiSecret) || empty($measurementId)) {
354
                InstantAnalytics::$plugin->logAnalyticsEvent(
355
                    'API secret or measurement ID not set up for Instant Analytics',
356
                    [],
357
                    __METHOD__
358
                );
359
                $this->_service = false;
360
361
                return null;
362
            }
363
            $this->_service = new Service($apiSecret, $measurementId);
364
365
            $ga4Client = new HttpClient();
366
            $ga4Client->setClient(Craft::createGuzzleClient());
367
            $this->_service->setHttpClient($ga4Client);
368
369
            $request = Craft::$app->getRequest();
370
            try {
371
                $session = Craft::$app->getSession();
372
            } catch (MissingComponentException $exception) {
373
                $session = null;
374
            }
375
376
            $this->setPageReferrer($request->getReferrer());
377
378
            // Load any campaign values from session or request
379
            $campaignParams = [
380
                'utm_source' => 'CampaignSource',
381
                'utm_medium' => 'CampaignMedium',
382
                'utm_campaign' => 'CampaignName',
383
                'utm_content' => 'CampaignContent',
384
                'utm_term' => 'CampaignTerm',
385
            ];
386
387
            // Load them up for GA4
388
            foreach ($campaignParams as $key => $method) {
389
                $value = $request->getParam($key) ?? $session->get($key) ?? null;
390
                $method = 'set' . $method;
391
392
                $this->$method($value);
393
394
                if ($session && $value) {
395
                    $session->set($key, $value);
396
                }
397
            }
398
399
            // If SEOmatic is installed, set the affiliation as well
400
            if (InstantAnalytics::$seomaticPlugin && Seomatic::$settings->renderEnabled && Seomatic::$plugin->metaContainers->metaSiteVars !== null) {
401
                $siteName = Seomatic::$plugin->metaContainers->metaSiteVars->siteName;
402
                $this->setAffiliation($siteName);
403
            }
404
        }
405
406
        if ($this->_service === false) {
407
            return null;
408
        }
409
410
        return $this->_service;
411
    }
412
}
413