Passed
Push — v4 ( 483dd2...9bab8e )
by Andrew
15:49 queued 09:35
created

Analytics::beginCheckout()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 2
Code Lines 1

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 1
eloc 1
c 0
b 0
f 0
nc 1
nop 1
dl 0
loc 2
rs 10
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\Dto\Response\BaseResponse;
17
use Br33f\Ga4\MeasurementProtocol\HttpClient;
18
use Craft;
19
use craft\commerce\elements\Order;
0 ignored issues
show
Bug introduced by
The type craft\commerce\elements\Order was not found. Maybe you did not declare it correctly or list all dependencies?

The issue could also be caused by a filter entry in the build configuration. If the path has been excluded in your configuration, e.g. excluded_paths: ["lib/*"], you can move it to the dependency path list as follows:

filter:
    dependency_paths: ["lib/*"]

For further information see https://scrutinizer-ci.com/docs/tools/php/php-scrutinizer/#list-dependency-paths

Loading history...
20
use craft\commerce\elements\Product;
0 ignored issues
show
Bug introduced by
The type craft\commerce\elements\Product was not found. Maybe you did not declare it correctly or list all dependencies?

The issue could also be caused by a filter entry in the build configuration. If the path has been excluded in your configuration, e.g. excluded_paths: ["lib/*"], you can move it to the dependency path list as follows:

filter:
    dependency_paths: ["lib/*"]

For further information see https://scrutinizer-ci.com/docs/tools/php/php-scrutinizer/#list-dependency-paths

Loading history...
21
use craft\commerce\elements\Variant;
0 ignored issues
show
Bug introduced by
The type craft\commerce\elements\Variant was not found. Maybe you did not declare it correctly or list all dependencies?

The issue could also be caused by a filter entry in the build configuration. If the path has been excluded in your configuration, e.g. excluded_paths: ["lib/*"], you can move it to the dependency path list as follows:

filter:
    dependency_paths: ["lib/*"]

For further information see https://scrutinizer-ci.com/docs/tools/php/php-scrutinizer/#list-dependency-paths

Loading history...
22
use craft\errors\MissingComponentException;
23
use craft\helpers\App;
24
use nystudio107\instantanalyticsGa4\helpers\Analytics as AnalyticsHelper;
25
use nystudio107\instantanalyticsGa4\InstantAnalytics;
26
use nystudio107\seomatic\Seomatic;
0 ignored issues
show
Bug introduced by
The type nystudio107\seomatic\Seomatic was not found. Maybe you did not declare it correctly or list all dependencies?

The issue could also be caused by a filter entry in the build configuration. If the path has been excluded in your configuration, e.g. excluded_paths: ["lib/*"], you can move it to the dependency path list as follows:

filter:
    dependency_paths: ["lib/*"]

For further information see https://scrutinizer-ci.com/docs/tools/php/php-scrutinizer/#list-dependency-paths

Loading history...
27
28
/**
0 ignored issues
show
Coding Style introduced by
Missing short description in doc comment
Loading history...
29
 * @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...
30
 * @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...
31
 * @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...
32
 *
33
 * @method Analytics setAllowGoogleSignals(string $value)
34
 * @method Analytics setAllowAdPersonalizationSignals(string $value)
35
 * @method Analytics setCampaignContent(string $value)
36
 * @method Analytics setCampaignId(string $value)
37
 * @method Analytics setCampaignMedium(string $value)
38
 * @method Analytics setCampaignName(string $value)
39
 * @method Analytics setCampaignSource(string $value)
40
 * @method Analytics setCampaignTerm(string $value)
41
 * @method Analytics setCampaign(string $value)
42
 * @method Analytics setClientId(string $value)
43
 * @method Analytics setContentGroup(string $value)
44
 * @method Analytics setCookieDomain(string $value)
45
 * @method Analytics setCookieExpires(string $value)
46
 * @method Analytics setCookieFlags(string $value)
47
 * @method Analytics setCookiePath(string $value)
48
 * @method Analytics setCookiePrefix(string $value)
49
 * @method Analytics setCookieUpdate(string $value)
50
 * @method Analytics setLanguage(string $value)
51
 * @method Analytics setPageLocation(string $value)
52
 * @method Analytics setPageReferrer(string $value)
53
 * @method Analytics setPageTitle(string $value)
54
 * @method Analytics setSendPageView(string $value)
55
 * @method Analytics setScreenResolution(string $value)
56
 * @method Analytics setUserId(string $value)
57
 */
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...
58
class Analytics
59
{
60
    /**
0 ignored issues
show
Coding Style introduced by
Missing short description in doc comment
Loading history...
61
     * @var BaseRequest|null
62
     */
63
    private ?BaseRequest $_request = null;
64
65
    /**
0 ignored issues
show
Coding Style introduced by
Missing short description in doc comment
Loading history...
66
     * @var Service|null|false
67
     */
68
    private mixed $_service = null;
69
70
    /**
0 ignored issues
show
Coding Style introduced by
Missing short description in doc comment
Loading history...
71
     * @var string|null
72
     */
73
    private ?string $_affiliation = null;
74
75
    private ?bool $_shouldSendAnalytics = null;
76
77
    /**
78
     * Component factory for creating events.
79
     *
80
     * @return ComponentFactory
81
     */
82
    public function create(): ComponentFactory
83
    {
84
        return new ComponentFactory();
85
    }
86
87
    /**
88
     * Add an event to be sent to Google
89
     *
90
     * @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...
91
     * @return BaseRequest
0 ignored issues
show
Coding Style introduced by
Tag @return cannot be grouped with parameter tags in a doc comment
Loading history...
92
     */
93
    public function addEvent(AbstractEvent $event): BaseRequest
94
    {
95
        $clientId = $this->request()->getClientId();
96
97
        if (strpos($clientId, '.') !== false) {
98
            [$sessionId, $sessionNumber] = explode('.', $clientId);
99
            $event->setSessionId($sessionId);
100
            $event->setSessionNumber($sessionNumber);
101
        }
102
103
        return $this->request()->addEvent($event);
104
    }
105
106
    /**
107
     * Send the events collected so far.
108
     *
109
     * @return BaseResponse|null
110
     * @throws \Br33f\Ga4\MeasurementProtocol\Exception\HydrationException
111
     * @throws \Br33f\Ga4\MeasurementProtocol\Exception\ValidationException
112
     */
113
    public function sendCollectedEvents(): ?BaseResponse
114
    {
115
        if ($this->_shouldSendAnalytics === null) {
116
            $this->_shouldSendAnalytics = AnalyticsHelper::shouldSendAnalytics();
117
        }
118
119
        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...
120
            return null;
121
        }
122
123
        $service = $this->service();
124
125
        if (!$service) {
126
            return null;
127
        }
128
129
        $request = $this->request();
130
        $eventCount = count($request->getEvents()->getEventList());
131
132
        if (!InstantAnalytics::$settings->sendAnalyticsData) {
133
            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

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

301
            $this->_request->/** @scrutinizer ignore-call */ 
302
                             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...
302
303
            if (InstantAnalytics::$settings->sendUserId) {
304
                $userId = AnalyticsHelper::getUserId();
305
306
                if ($userId) {
307
                    $this->request()->setUserId($userId);
308
                }
309
            }
310
        }
311
312
313
        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...
314
    }
315
316
    /**
317
     * Init the service used to send events
318
     */
0 ignored issues
show
Coding Style introduced by
Missing @return tag in function comment
Loading history...
319
    public function init(): void
320
    {
321
        $this->service();
322
        $this->request();
323
    }
324
325
    protected function service(): ?Service
0 ignored issues
show
Coding Style introduced by
Missing doc comment for function service()
Loading history...
326
    {
327
        if ($this->_service === null) {
328
            $settings = InstantAnalytics::$settings;
329
            $apiSecret = App::parseEnv($settings->googleAnalyticsMeasurementApiSecret);
330
            $measurementId = App::parseEnv($settings->googleAnalyticsMeasurementId);
331
332
            if (empty($apiSecret) || empty($measurementId)) {
333
                InstantAnalytics::$plugin->logAnalyticsEvent(
334
                    'API secret or measurement ID not set up for Instant Analytics',
335
                    [],
336
                    __METHOD__
337
                );
338
                $this->_service = false;
339
340
                return null;
341
            }
342
            $this->_service = new Service($apiSecret, $measurementId);
343
344
            $ga4Client = new HttpClient();
345
            $ga4Client->setClient(Craft::createGuzzleClient());
346
            $this->_service->setHttpClient($ga4Client);
347
348
            $request = Craft::$app->getRequest();
349
            try {
350
                $session = Craft::$app->getSession();
351
            } catch (MissingComponentException $exception) {
352
                $session = null;
353
            }
354
355
            $this->setPageReferrer($request->getReferrer());
356
357
            // Load any campaign values from session or request
358
            $campaignParams = [
359
                'utm_source' => 'CampaignSource',
360
                'utm_medium' => 'CampaignMedium',
361
                'utm_campaign' => 'CampaignName',
362
                'utm_content' => 'CampaignContent',
363
                'utm_term' => 'CampaignTerm',
364
            ];
365
366
            // Load them up for GA4
367
            foreach ($campaignParams as $key => $method) {
368
                $value = $request->getParam($key) ?? $session->get($key) ?? null;
369
                $method = 'set' . $method;
370
371
                $this->$method($value);
372
373
                if ($session && $value) {
374
                    $session->set($key, $value);
375
                }
376
377
            }
378
379
            // If SEOmatic is installed, set the affiliation as well
380
            if (InstantAnalytics::$seomaticPlugin && Seomatic::$settings->renderEnabled && Seomatic::$plugin->metaContainers->metaSiteVars !== null) {
381
                $siteName = Seomatic::$plugin->metaContainers->metaSiteVars->siteName;
382
                $this->setAffiliation($siteName);
383
            }
384
385
        }
386
387
        if ($this->_service === false) {
388
            return null;
389
        }
390
391
        return $this->_service;
392
    }
393
}
394