Issues (127)

src/services/Analytics.php (4 issues)

1
<?php
2
/**
3
 * @link      https://dukt.net/analytics/
4
 * @copyright Copyright (c) 2022, Dukt
5
 * @license   https://github.com/dukt/analytics/blob/master/LICENSE.md
6
 */
7
8
namespace dukt\analytics\services;
9
10
use Craft;
11
use craft\helpers\Json;
12
use craft\web\assets\d3\D3Asset;
13
use yii\base\Component;
14
use dukt\Analytics\Plugin as AnalyticsPlugin;
15
16
class Analytics extends Component
17
{
18
    // Properties
19
    // =========================================================================
20
21
    /**
22
     * @var bool|string|null Demo mode.
23
     */
24
    public $demoMode = false;
25
26
    // Public Methods
27
    // =========================================================================
28
29
    /**
30
     * Returns the real time refresh interval.
31
     *
32
     * @return int|null
33
     */
34
    public function getRealtimeRefreshInterval()
35
    {
36
        $interval = AnalyticsPlugin::$plugin->getSettings()->realtimeRefreshInterval;
37
38
        if ($interval) {
39
            return $interval;
40
        }
41
42
        $plugin = Craft::$app->getPlugins()->getPlugin('analytics');
43
        $settings = $plugin->getSettings();
44
45
        if (!empty($settings['realtimeRefreshInterval'])) {
46
            return $settings['realtimeRefreshInterval'];
47
        }
48
49
        return null;
50
    }
51
52
    /**
53
     * Returns the element URL path.
54
     *
55
     * @param int      $elementId
56
     * @param int|null $siteId
57
     *
58
     * @return string
59
     */
60
    public function getElementUrlPath($elementId, $siteId): string
61
    {
62
        $element = Craft::$app->elements->getElementById($elementId, null, $siteId);
0 ignored issues
show
The method getElementById() 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

62
        /** @scrutinizer ignore-call */ 
63
        $element = Craft::$app->elements->getElementById($elementId, null, $siteId);

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...
63
64
        $url = $element->url;
65
66
        $components = parse_url($url);
67
68
        return substr($url, \strlen($components['scheme'].'://'.$components['host']));
69
    }
70
71
    /**
72
     * Returns the chart language.
73
     *
74
     * @return string
75
     */
76
    public function getChartLanguage()
77
    {
78
        $chartLanguage = Craft::t('analytics', 'analyticsChartLanguage');
79
80
        if ($chartLanguage == 'analyticsChartLanguage') {
81
            $chartLanguage = 'en';
82
        }
83
84
        return $chartLanguage;
85
    }
86
87
    /**
88
     * Returns the Analytics tracking object.
89
     *
90
     * @param bool  $isSsl
91
     * @param bool  $isDisabled
92
     * @param array $options
93
     *
94
     * @throws \InvalidArgumentException
95
     *
96
     * @return \TheIconic\Tracking\GoogleAnalytics\Analytics
97
     */
98
    public function tracking($isSsl = false, $isDisabled = false, array $options = [])
99
    {
100
        $userAgent = Craft::$app->getRequest()->getUserAgent();
101
102
        if (empty($userAgent)) {
103
            $userAgent = "User-Agent:Mozilla/5.0 (Windows; U; Windows NT 5.1; en-US; rv:1.8.1.13) Gecko/20080311 Firefox/2.0.0.13\r\n";
104
        }
105
106
        $referrer = Craft::$app->getRequest()->getReferrer();
107
108
        if (empty($referrer)) {
109
            $referrer = '';
110
        }
111
112
        $analyticsTracking = new \TheIconic\Tracking\GoogleAnalytics\Analytics($isSsl, $isDisabled, $options);
113
        $analyticsTracking
114
            ->setProtocolVersion('1')
115
            ->setUserAgentOverride($userAgent)
116
            ->setDocumentHostName(Craft::$app->getRequest()->getServerName())
117
            ->setDocumentReferrer($referrer)
118
            ->setAsyncRequest(false)
119
            ->setClientId($this->_gaParseCookie());
120
121
        return $analyticsTracking;
122
    }
123
124
    /**
125
     * Checks if the OAuth provider is configured.
126
     *
127
     * @return bool
128
     */
129
    public function isOauthProviderConfigured()
130
    {
131
        $oauthClientId = AnalyticsPlugin::$plugin->getSettings()->oauthClientId;
132
        $oauthClientSecret = AnalyticsPlugin::$plugin->getSettings()->oauthClientSecret;
133
134
        return !empty($oauthClientId) && !empty($oauthClientSecret);
135
    }
136
137
    /**
138
     * Checks plugin requirements (dependencies, configured OAuth provider, and token).
139
     *
140
     * @return bool
141
     * @throws \yii\base\InvalidConfigException
142
     */
143
    public function checkPluginRequirements()
144
    {
145
        if ($this->isOauthProviderConfigured()) {
146
            if ($this->isTokenSet()) {
147
                return true;
148
            }
149
150
            return false;
151
        }
152
153
        return false;
154
    }
155
156
    /**
157
     * Get a currency definition, updated with the currency from the GA view.
158
     *
159
     * @param string|null $gaViewCurrency
160
     * @return array
161
     */
162
    public function getCurrencyDefinition(string $gaViewCurrency = null): array
163
    {
164
        // Get the currency definition from `d3-format`
165
        $d3Asset = new D3Asset();
166
        $localeDefinitionForCurrency = Json::decode($d3Asset->formatDef(Craft::getAlias('@analyticsLib/d3-format')));
0 ignored issues
show
It seems like Craft::getAlias('@analyticsLib/d3-format') can also be of type false; however, parameter $dir of craft\web\assets\d3\D3Asset::formatDef() 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

166
        $localeDefinitionForCurrency = Json::decode($d3Asset->formatDef(/** @scrutinizer ignore-type */ Craft::getAlias('@analyticsLib/d3-format')));
Loading history...
167
        $currencyDefinition = $localeDefinitionForCurrency['currency'];
168
169
        // Define the currency symbol based on the GA view currency
170
        $currencySymbol = ($gaViewCurrency ? Craft::$app->locale->getCurrencySymbol($gaViewCurrency) : '$');
0 ignored issues
show
The method getCurrencySymbol() 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

170
        $currencySymbol = ($gaViewCurrency ? Craft::$app->locale->/** @scrutinizer ignore-call */ getCurrencySymbol($gaViewCurrency) : '$');

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...
171
172
        // Update the currency definition with the new currency symbol
173
        foreach ($currencyDefinition as $key => $row) {
174
            if (!empty($row)) {
175
                // Todo: Check currency symbol replacement with arabic
176
                $pattern = '/[^\s]+/u';
177
                $replacement = $currencySymbol;
178
                $newRow = preg_replace($pattern, $replacement, $row);
179
                $currencyDefinition[$key] = $newRow;
180
            }
181
        }
182
183
        // Return the currency definition
184
        return $currencyDefinition;
185
    }
186
187
    // Private Methods
188
    // =========================================================================
189
190
    /**
191
     * Checks if the token is set.
192
     *
193
     * @return bool
194
     * @throws \yii\base\InvalidConfigException
195
     */
196
    private function isTokenSet()
197
    {
198
        $token = AnalyticsPlugin::$plugin->getOauth()->getToken(false);
199
200
        if ($token) {
201
            return true;
202
        }
203
204
        return false;
205
    }
206
207
208
    /**
209
     * _getGclid get the `gclid` and sets the 'gclid' cookie
210
     */
211
    private function _getGclid()
0 ignored issues
show
The method _getGclid() is not used, and could be removed.

This check looks for private methods that have been defined, but are not used inside the class.

Loading history...
212
    {
213
        $gclid = '';
214
        if (isset($_GET['gclid'])) {
215
            $gclid = $_GET['gclid'];
216
            if (!empty($gclid)) {
217
                setcookie('gclid', $gclid, time() + (10 * 365 * 24 * 60 * 60), '/');
218
            }
219
        }
220
221
        return $gclid;
222
    } /* -- _getGclid */
223
224
    /**
225
     * _gaParseCookie handles the parsing of the _ga cookie or setting it to a unique identifier
226
     *
227
     * @return string the cid
228
     */
229
    private function _gaParseCookie()
230
    {
231
        if (isset($_COOKIE['_ga'])) {
232
            [$version, $domainDepth, $cid1, $cid2] = preg_split('[\.]', $_COOKIE['_ga'], 4);
233
            $contents = ['version' => $version, 'domainDepth' => $domainDepth, 'cid' => $cid1.'.'.$cid2];
234
            $cid = $contents['cid'];
235
        } else {
236
            if (isset($_COOKIE['_ia']) && $_COOKIE['_ia'] != '') {
237
                $cid = $_COOKIE['_ia'];
238
            } else {
239
                $cid = $this->_gaGenUUID();
240
            }
241
        }
242
        setcookie('_ia', $cid, time() + 60 * 60 * 24 * 730, '/'); // Two years
243
244
        return $cid;
245
    } /* -- _gaParseCookie */
246
247
    /**
248
     * _gaGenUUID Generate UUID v4 function - needed to generate a CID when one isn't available
249
     *
250
     * @return string The generated UUID
251
     */
252
    private function _gaGenUUID()
253
    {
254
        return sprintf('%04x%04x-%04x-%04x-%04x-%04x%04x%04x',
255
            // 32 bits for "time_low"
256
            mt_rand(0, 0xffff), mt_rand(0, 0xffff),
257
            // 16 bits for "time_mid"
258
            mt_rand(0, 0xffff),
259
            // 16 bits for "time_hi_and_version",
260
            // four most significant bits holds version number 4
261
            mt_rand(0, 0x0fff) | 0x4000,
262
            // 16 bits, 8 bits for "clk_seq_hi_res",
263
            // 8 bits for "clk_seq_low",
264
            // two most significant bits holds zero and one for variant DCE1.1
265
            mt_rand(0, 0x3fff) | 0x8000,
266
            // 48 bits for "node"
267
            mt_rand(0, 0xffff), mt_rand(0, 0xffff), mt_rand(0, 0xffff)
268
        );
269
    } /* -- _gaGenUUID */
270
}
271