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

Analytics::getUserId()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 9
Code Lines 4

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 2
eloc 4
c 0
b 0
f 0
nc 2
nop 0
dl 0
loc 9
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\helpers;
13
14
use Craft;
15
use craft\elements\User as UserElement;
16
use craft\helpers\StringHelper;
17
use craft\helpers\UrlHelper;
18
use Jaybizzle\CrawlerDetect\CrawlerDetect;
19
use nystudio107\instantanalyticsGa4\InstantAnalytics;
20
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...
21
use yii\base\Exception;
22
23
/**
0 ignored issues
show
Coding Style introduced by
Missing short description in doc comment
Loading history...
24
 * @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...
25
 * @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...
26
 * @since     5.0.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...
27
 */
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...
28
class Analytics
29
{
30
    /**
31
     * If SEOmatic is installed, set the page title from it
32
     */
0 ignored issues
show
Coding Style introduced by
Missing @return tag in function comment
Loading history...
33
    public static function getTitleFromSeomatic(): ?string
34
    {
35
        if (!InstantAnalytics::$seomaticPlugin) {
36
            return null;
37
        }
38
        if (!Seomatic::$settings->renderEnabled) {
39
            return null;
40
        }
41
        $titleTag = Seomatic::$plugin->title->get('title');
42
43
        if ($titleTag === null) {
44
            return null;
45
        }
46
47
        $titleArray = $titleTag->renderAttributes();
48
49
        if (empty($titleArray['title'])) {
50
            return null;
51
        }
52
53
        return $titleArray['title'];
54
    }
55
56
    /**
57
     * Return a sanitized documentPath from a URL
58
     *
59
     * @param string $url
0 ignored issues
show
Coding Style introduced by
Missing parameter comment
Loading history...
60
     *
61
     * @return string
62
     */
63
    public static function getDocumentPathFromUrl(string $url = ''): string
64
    {
65
        if ($url === '') {
66
            $url = Craft::$app->getRequest()->getFullPath();
67
        }
68
69
        // We want to send just a path to GA for page views
70
        if (UrlHelper::isAbsoluteUrl($url)) {
71
            $urlParts = parse_url($url);
72
            $url = $urlParts['path'] ?? '/';
73
            if (isset($urlParts['query'])) {
74
                $url .= '?' . $urlParts['query'];
75
            }
76
        }
77
78
        // We don't want to send protocol-relative URLs either
79
        if (UrlHelper::isProtocolRelativeUrl($url)) {
80
            $url = substr($url, 1);
81
        }
82
83
        // Strip the query string if that's the global config setting
84
        if (InstantAnalytics::$settings) {
85
            if (InstantAnalytics::$settings->stripQueryString !== null
86
                && InstantAnalytics::$settings->stripQueryString) {
0 ignored issues
show
Coding Style introduced by
Closing parenthesis of a multi-line IF statement must be on a new line
Loading history...
87
                $url = UrlHelper::stripQueryString($url);
88
            }
89
        }
90
91
        // We always want the path to be / rather than empty
92
        if ($url === '') {
93
            $url = '/';
94
        }
95
96
        return $url;
97
    }
98
99
    /**
100
     * Get a PageView tracking URL
101
     *
102
     * @param $url
0 ignored issues
show
Coding Style introduced by
Missing parameter comment
Loading history...
103
     * @param $title
0 ignored issues
show
Coding Style introduced by
Missing parameter comment
Loading history...
104
     *
105
     * @return string
106
     * @throws Exception
107
     */
108
    public static function getPageViewTrackingUrl($url, $title): string
109
    {
110
        $urlParams = compact('url', 'title');
111
112
        $path = parse_url($url, PHP_URL_PATH);
113
        $pathFragments = explode('/', rtrim($path, '/'));
114
        $fileName = end($pathFragments);
115
        $trackingUrl = UrlHelper::siteUrl('instantanalytics/pageViewTrack/' . $fileName, $urlParams);
116
117
        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

117
        InstantAnalytics::$plugin->/** @scrutinizer ignore-call */ 
118
                                   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...
118
            'Created pageViewTrackingUrl for: {trackingUrl}',
119
            [
120
                'trackingUrl' => $trackingUrl
121
            ],
122
            __METHOD__
123
        );
124
125
        return $trackingUrl;
126
    }
127
128
    /**
129
     * Get an Event tracking URL
130
     *
131
     * @param string $url
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...
132
     * @param string $eventName
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...
133
     * @param array $params
0 ignored issues
show
Coding Style introduced by
Missing parameter comment
Loading history...
Coding Style introduced by
Expected 2 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...
134
     * @return string
0 ignored issues
show
Coding Style introduced by
Tag @return cannot be grouped with parameter tags in a doc comment
Loading history...
135
     * @throws Exception
0 ignored issues
show
Coding Style introduced by
Tag @throws cannot be grouped with parameter tags in a doc comment
Loading history...
136
     */
137
    public static function getEventTrackingUrl(
138
        string $url,
139
        string $eventName,
140
        array $params = [],
141
    ): string
142
    {
0 ignored issues
show
Coding Style introduced by
The closing parenthesis and the opening brace of a multi-line function declaration must be on the same line
Loading history...
143
        $urlParams = compact('url', 'eventName', 'params');
144
145
        $fileName = pathinfo(parse_url($url, PHP_URL_PATH), PATHINFO_BASENAME);
146
        $trackingUrl = UrlHelper::siteUrl('instantanalytics/eventTrack/' . $fileName, $urlParams);
0 ignored issues
show
Bug introduced by
Are you sure $fileName of type array|string can be used in concatenation? ( Ignorable by Annotation )

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

146
        $trackingUrl = UrlHelper::siteUrl('instantanalytics/eventTrack/' . /** @scrutinizer ignore-type */ $fileName, $urlParams);
Loading history...
147
148
        InstantAnalytics::$plugin->logAnalyticsEvent(
149
            'Created eventTrackingUrl for: {trackingUrl}',
150
            [
151
                'trackingUrl' => $trackingUrl
152
            ],
153
            __METHOD__
154
        );
155
156
        return $trackingUrl;
157
    }
158
159
    /**
160
     * _shouldSendAnalytics determines whether we should be sending Google
161
     * Analytics data
162
     *
163
     * @return bool
164
     */
165
    public static function shouldSendAnalytics(): bool
166
    {
167
        $result = true;
168
        $request = Craft::$app->getRequest();
169
170
        $logExclusion = static function (string $setting)
171
        {
0 ignored issues
show
Coding Style introduced by
Opening brace should be on the same line as the declaration
Loading history...
172
            if (InstantAnalytics::$settings->logExcludedAnalytics) {
173
                $request = Craft::$app->getRequest();
174
                $requestIp = $request->getUserIP();
175
                InstantAnalytics::$plugin->logAnalyticsEvent(
176
                    'Analytics excluded for:: {requestIp} due to: `{setting}`',
177
                    compact('requestIp', 'setting'),
178
                    __METHOD__
179
                );
180
            }
181
        };
182
183
        if (!InstantAnalytics::$settings->sendAnalyticsData) {
184
            $logExclusion('sendAnalyticsData');
185
            return false;
186
        }
187
188
        if (!InstantAnalytics::$settings->sendAnalyticsInDevMode && Craft::$app->getConfig()->getGeneral()->devMode) {
189
            $logExclusion('sendAnalyticsInDevMode');
190
            return false;
191
        }
192
193
        if ($request->getIsConsoleRequest()) {
194
            $logExclusion('Craft::$app->getRequest()->getIsConsoleRequest()');
195
            return false;
196
        }
197
198
        if ($request->getIsCpRequest()) {
199
            $logExclusion('Craft::$app->getRequest()->getIsCpRequest()');
200
            return false;
201
        }
202
203
        if ($request->getIsLivePreview()) {
204
            $logExclusion('Craft::$app->getRequest()->getIsLivePreview()');
205
            return false;
206
        }
207
208
        // Check the $_SERVER[] super-global exclusions
209
        if (InstantAnalytics::$settings->serverExcludes !== null
210
            && is_array(InstantAnalytics::$settings->serverExcludes)) {
0 ignored issues
show
Coding Style introduced by
Closing parenthesis of a multi-line IF statement must be on a new line
Loading history...
211
            foreach (InstantAnalytics::$settings->serverExcludes as $match => $matchArray) {
212
                if (isset($_SERVER[$match])) {
213
                    foreach ($matchArray as $matchItem) {
214
                        if (preg_match($matchItem, $_SERVER[$match])) {
215
                            $logExclusion('serverExcludes');
216
217
                            return false;
218
                        }
219
                    }
220
                }
221
            }
222
        }
223
224
        // Filter out bot/spam requests via UserAgent
225
        if (InstantAnalytics::$settings->filterBotUserAgents) {
226
            $crawlerDetect = new CrawlerDetect;
227
            // Check the user agent of the current 'visitor'
228
            if ($crawlerDetect->isCrawler()) {
229
                $logExclusion('filterBotUserAgents');
230
231
                return false;
232
            }
233
        }
234
235
        // Filter by user group
236
        $userService = Craft::$app->getUser();
237
        /** @var UserElement $user */
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...
238
        $user = $userService->getIdentity();
239
        if ($user) {
0 ignored issues
show
introduced by
$user is of type craft\elements\User, thus it always evaluated to true.
Loading history...
240
            if (InstantAnalytics::$settings->adminExclude && $user->admin) {
241
                $logExclusion('adminExclude');
242
243
                return false;
244
            }
245
246
            if (InstantAnalytics::$settings->groupExcludes !== null
247
                && is_array(InstantAnalytics::$settings->groupExcludes)) {
0 ignored issues
show
Coding Style introduced by
Closing parenthesis of a multi-line IF statement must be on a new line
Loading history...
248
                foreach (InstantAnalytics::$settings->groupExcludes as $matchItem) {
249
                    if ($user->isInGroup($matchItem)) {
250
                        $logExclusion('groupExcludes');
251
252
                        return false;
253
                    }
254
                }
255
            }
256
        }
257
258
        return $result;
259
    }
260
261
    /**
262
     * getClientId handles the parsing of the _ga cookie or setting it to a
0 ignored issues
show
Coding Style introduced by
Doc comment short description must start with a capital letter
Loading history...
263
     * unique identifier
264
     *
265
     * @return string the cid
266
     */
267
    public static function getClientId(): string
268
    {
269
        $cid = '';
270
        $cookieName = '_ga_' . StringHelper::removeLeft(InstantAnalytics::$settings->googleAnalyticsMeasurementId, 'G-');
271
        if (isset($_COOKIE[$cookieName])) {
272
            $parts = explode(".", $_COOKIE[$cookieName], 5);
273
            if ($parts !== false) {
274
                $cid = implode('.', array_slice($parts, 2, 2));
275
            }
276
        } elseif (isset($_COOKIE['_ia']) && $_COOKIE['_ia'] !== '') {
277
            $cid = $_COOKIE['_ia'];
278
        } else {
279
            // Generate our own client id, otherwise.
280
            $cid = static::gaGenUUID() . '.1';
281
        }
282
283
        if (InstantAnalytics::$settings->createGclidCookie && !empty($cid)) {
284
            setcookie('_ia', $cid, strtotime('+' . InstantAnalytics::$settings->sessionDuration . ' minutes'), '/'); // Two years
285
        }
286
287
        return $cid;
288
    }
289
290
    /**
291
     * Get the user id.
292
     *
293
     * @return string
294
     */
295
    public static function getUserId(): string
296
    {
297
        $userId = Craft::$app->getUser()->getId();
298
299
        if (!$userId) {
300
            return '';
301
        }
302
303
        return $userId;
304
    }
305
306
    /**
307
     * gaGenUUID Generate UUID v4 function - needed to generate a CID when one
0 ignored issues
show
Coding Style introduced by
Doc comment short description must start with a capital letter
Loading history...
308
     * isn't available
309
     *
310
     * @return string The generated UUID
311
     */
312
    protected static function gaGenUUID()
313
    {
314
        return sprintf(
315
            '%04x%04x-%04x-%04x-%04x-%04x%04x%04x',
316
            // 32 bits for "time_low"
317
            mt_rand(0, 0xffff),
318
            mt_rand(0, 0xffff),
319
            // 16 bits for "time_mid"
320
            mt_rand(0, 0xffff),
321
            // 16 bits for "time_hi_and_version",
322
            // four most significant bits holds version number 4
323
            mt_rand(0, 0x0fff) | 0x4000,
324
            // 16 bits, 8 bits for "clk_seq_hi_res",
325
            // 8 bits for "clk_seq_low",
326
            // two most significant bits holds zero and one for variant DCE1.1
327
            mt_rand(0, 0x3fff) | 0x8000,
328
            // 48 bits for "node"
329
            mt_rand(0, 0xffff),
330
            mt_rand(0, 0xffff),
331
            mt_rand(0, 0xffff)
332
        );
333
    }
334
}
335