Reports::getEcommerceReport()   A
last analyzed

Complexity

Conditions 4
Paths 4

Size

Total Lines 46
Code Lines 37

Duplication

Lines 0
Ratio 0 %

Importance

Changes 3
Bugs 0 Features 0
Metric Value
cc 4
eloc 37
c 3
b 0
f 0
nc 4
nop 2
dl 0
loc 46
rs 9.328
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\StringHelper;
12
use dukt\analytics\errors\InvalidElementException;
13
use dukt\analytics\models\ReportRequestCriteria;
14
use yii\base\Component;
15
use dukt\analytics\Plugin as Analytics;
0 ignored issues
show
Bug introduced by
This use statement conflicts with another class in this namespace, dukt\analytics\services\Analytics. Consider defining an alias.

Let?s assume that you have a directory layout like this:

.
|-- OtherDir
|   |-- Bar.php
|   `-- Foo.php
`-- SomeDir
    `-- Foo.php

and let?s assume the following content of Bar.php:

// Bar.php
namespace OtherDir;

use SomeDir\Foo; // This now conflicts the class OtherDir\Foo

If both files OtherDir/Foo.php and SomeDir/Foo.php are loaded in the same runtime, you will see a PHP error such as the following:

PHP Fatal error:  Cannot use SomeDir\Foo as Foo because the name is already in use in OtherDir/Foo.php

However, as OtherDir/Foo.php does not necessarily have to be loaded and the error is only triggered if it is loaded before OtherDir/Bar.php, this problem might go unnoticed for a while. In order to prevent this error from surfacing, you must import the namespace with a different alias:

// Bar.php
namespace OtherDir;

use SomeDir\Foo as SomeDirFoo; // There is no conflict anymore.
Loading history...
16
use \Google_Service_AnalyticsReporting_Report;
17
18
class Reports extends Component
19
{
20
    // Public Methods
21
    // =========================================================================
22
23
    /**
24
     * Returns a realtime report.
25
     *
26
     * @param array $request
27
     *
28
     * @return array
29
     * @throws \yii\base\InvalidConfigException
30
     */
31
    public function getRealtimeReport(array $request)
32
    {
33
        $view = Analytics::$plugin->getViews()->getViewById($request['viewId']);
34
35
        $tableId = null;
36
37
        if ($view) {
0 ignored issues
show
introduced by
$view is of type dukt\analytics\models\View, thus it always evaluated to true.
Loading history...
38
            $tableId = 'ga:'.$view->gaViewId;
39
        }
40
41
        $metrics = $request['metrics'];
42
        $optParams = $request['optParams'];
43
44
        $cacheId = ['reports.getRealtimeReport', $tableId, $metrics, $optParams];
45
        $response = Analytics::$plugin->cache->get($cacheId);
46
47
        if (!$response) {
48
            $response = Analytics::$plugin->getApis()->getAnalytics()->getService()->data_realtime->get($tableId, $metrics, $optParams);
49
50
            $cacheDuration = Analytics::$plugin->getSettings()->realtimeRefreshInterval;
51
            Analytics::$plugin->cache->set($cacheId, $response, $cacheDuration);
52
        }
53
54
        return (array)$response;
55
    }
56
57
    /**
58
     * Get e-commerce report.
59
     *
60
     * @param $viewId
61
     * @param $period
62
     *
63
     * @return array
64
     */
65
    public function getEcommerceReport($viewId, $period)
66
    {
67
        $startDate = '7daysAgo';
68
        $endDate = 'today';
69
        $dimensions = 'ga:date';
70
71
        switch ($period) {
72
            case 'week':
73
                $startDate = '7daysAgo';
74
                break;
75
            case 'month':
76
                $startDate = '30daysAgo';
77
                break;
78
            case 'year':
79
                $startDate = '365daysAgo';
80
                $dimensions = 'ga:yearMonth';
81
                break;
82
        }
83
84
        $metrics = 'ga:transactionRevenue,ga:revenuePerTransaction,ga:transactions,ga:transactionsPerSession';
85
86
        $criteria = new ReportRequestCriteria;
87
        $criteria->viewId = $viewId;
88
        $criteria->startDate = $startDate;
89
        $criteria->endDate = $endDate;
90
        $criteria->metrics = $metrics;
91
        $criteria->dimensions = $dimensions;
0 ignored issues
show
Documentation Bug introduced by
It seems like $dimensions of type string is incompatible with the declared type array of property $dimensions.

Our type inference engine has found an assignment to a property that is incompatible with the declared type of that property.

Either this assignment is in error or the assigned type should be added to the documentation/type hint for that property..

Loading history...
92
        $criteria->includeEmptyRows = true;
93
94
        $reportResponse = Analytics::$plugin->getApis()->getAnalyticsReporting()->getReport($criteria);
95
        $report = $reportResponse->toSimpleObject();
96
        $reportData = $this->parseReportingReport($reportResponse);
97
98
        $view = Analytics::$plugin->getViews()->getViewById($viewId);
99
100
        return [
101
            'period' => $startDate.' - '.$endDate,
102
            'totalRevenue' => $report->data->totals[0]->values[0],
103
            'totalRevenuePerTransaction' => $report->data->totals[0]->values[1],
104
            'totalTransactions' => $report->data->totals[0]->values[2],
105
            'totalTransactionsPerSession' => $report->data->totals[0]->values[3],
106
            'reportData' => [
107
                'view' => $view->name,
108
                'chart' => $reportData,
109
                'period' => $period,
110
                'periodLabel' => Craft::t('analytics', 'This '.$period)
111
            ],
112
        ];
113
    }
114
115
    /**
116
     * Returns an element report.
117
     *
118
     * @param int      $elementId
119
     * @param int|null $siteId
120
     * @param string   $metric
121
     *
122
     * @return array
123
     * @throws \Exception
124
     */
125
    public function getElementReport($elementId, $siteId, $metric)
126
    {
127
        $uri = Analytics::$plugin->getAnalytics()->getElementUrlPath($elementId, $siteId);
128
129
        if (!$uri) {
130
            throw new InvalidElementException("Element doesn't support URLs.", 1);
131
        }
132
133
        if ($uri === '__home__') {
134
            $uri = '';
135
        }
136
137
        $siteView = Analytics::$plugin->getViews()->getSiteViewBySiteId($siteId);
138
139
        $viewId = null;
140
141
        if ($siteView) {
0 ignored issues
show
introduced by
$siteView is of type dukt\analytics\models\SiteView, thus it always evaluated to true.
Loading history...
142
            $viewId = $siteView->viewId;
143
        }
144
145
        $startDate = date('Y-m-d', strtotime('-1 month'));
146
        $endDate = date('Y-m-d');
147
        $dimensions = 'ga:date';
148
        $metrics = $metric;
149
        $filters = 'ga:pagePath=='.$uri;
150
151
        $request = [
152
            'viewId' => $viewId,
153
            'startDate' => $startDate,
154
            'endDate' => $endDate,
155
            'metrics' => $metrics,
156
            'dimensions' => $dimensions,
157
            'filters' => $filters
158
        ];
159
160
        $cacheId = ['reports.getElementReport', $request];
161
        $response = Analytics::$plugin->cache->get($cacheId);
162
163
        if (!$response) {
164
165
            $criteria = new ReportRequestCriteria;
166
            $criteria->viewId = $viewId;
167
            $criteria->startDate = $startDate;
168
            $criteria->endDate = $endDate;
169
            $criteria->metrics = $metrics;
170
            $criteria->dimensions = $dimensions;
0 ignored issues
show
Documentation Bug introduced by
It seems like $dimensions of type string is incompatible with the declared type array of property $dimensions.

Our type inference engine has found an assignment to a property that is incompatible with the declared type of that property.

Either this assignment is in error or the assigned type should be added to the documentation/type hint for that property..

Loading history...
171
            $criteria->filtersExpression = $filters;
172
173
            $reportResponse = Analytics::$plugin->getApis()->getAnalyticsReporting()->getReport($criteria);
174
            $response = $this->parseReportingReport($reportResponse);
175
176
            if ($response) {
0 ignored issues
show
introduced by
$response is a non-empty array, thus is always true.
Loading history...
Bug Best Practice introduced by
The expression $response of type array<string,array|string[]> is implicitly converted to a boolean; are you sure this is intended? If so, consider using ! empty($expr) instead to make it clear that you intend to check for an array without elements.

This check marks implicit conversions of arrays to boolean values in a comparison. While in PHP an empty array is considered to be equal (but not identical) to false, this is not always apparent.

Consider making the comparison explicit by using empty(..) or ! empty(...) instead.

Loading history...
177
                Analytics::$plugin->cache->set($cacheId, $response);
178
            }
179
        }
180
181
        return $response;
182
    }
183
184
    /**
185
     * Returns an area report.
186
     *
187
     * @param array $request
188
     *
189
     * @return array
190
     * @throws \yii\base\InvalidConfigException
191
     */
192
    public function getAreaReport(array $request)
193
    {
194
        $viewId = ($request['viewId'] ?? null);
195
        $period = ($request['period'] ?? null);
196
        $metricString = ($request['options']['metric'] ?? null);
197
198
        switch ($period) {
199
            case 'year':
200
                $dimensionString = 'ga:yearMonth';
201
                $startDate = date('Y-m-01', strtotime('-1 '.$period));
202
                $endDate = date('Y-m-d');
203
                break;
204
205
            default:
206
                $dimensionString = 'ga:date';
207
                $startDate = date('Y-m-d', strtotime('-1 '.$period));
208
                $endDate = date('Y-m-d');
209
        }
210
211
        $criteria = new ReportRequestCriteria;
212
        $criteria->viewId = $viewId;
213
        $criteria->startDate = $startDate;
214
        $criteria->endDate = $endDate;
215
        $criteria->metrics = $metricString;
216
        $criteria->dimensions = $dimensionString;
0 ignored issues
show
Documentation Bug introduced by
It seems like $dimensionString of type string is incompatible with the declared type array of property $dimensions.

Our type inference engine has found an assignment to a property that is incompatible with the declared type of that property.

Either this assignment is in error or the assigned type should be added to the documentation/type hint for that property..

Loading history...
217
        $criteria->includeEmptyRows = true;
218
219
        $criteria->orderBys = [
220
            ['fieldName' => $dimensionString, 'orderType' => 'VALUE', 'sortOrder' => 'ASCENDING']
221
        ];
222
223
        $reportResponse = Analytics::$plugin->getApis()->getAnalyticsReporting()->getReport($criteria);
224
        $report = $this->parseReportingReport($reportResponse);
225
226
        $total = $report['totals'][0];
227
228
        $view = Analytics::$plugin->getViews()->getViewById($viewId);
229
230
        return [
231
            'view' => $view->name,
232
            'type' => 'area',
233
            'chart' => $report,
234
            'total' => $total,
235
            'metric' => Craft::t('analytics', Analytics::$plugin->metadata->getDimMet($metricString)),
236
            'period' => $period,
237
            'periodLabel' => Craft::t('analytics', 'This '.$period)
238
        ];
239
    }
240
241
    /**
242
     * Returns a counter report.
243
     *
244
     * @param array $request
245
     *
246
     * @return array
247
     * @throws \yii\base\InvalidConfigException
248
     */
249
    public function getCounterReport(array $request)
250
    {
251
        $viewId = ($request['viewId'] ?? null);
252
        $period = ($request['period'] ?? null);
253
        $metricString = ($request['options']['metric'] ?? null);
254
        $startDate = date('Y-m-d', strtotime('-1 '.$period));
255
        $endDate = date('Y-m-d');
256
257
        $criteria = new ReportRequestCriteria;
258
        $criteria->viewId = $viewId;
259
        $criteria->startDate = $startDate;
260
        $criteria->endDate = $endDate;
261
        $criteria->metrics = $metricString;
262
263
264
        $reportResponse = Analytics::$plugin->getApis()->getAnalyticsReporting()->getReport($criteria);
265
        $report = $this->parseReportingReport($reportResponse);
266
267
        $total = 0;
268
269
        if (!empty($report['totals'][0])) {
270
            $total = $report['totals'][0];
271
        }
272
273
        $counter = [
274
            'type' => $report['cols'][0]['type'],
275
            'value' => $total,
276
            'label' => StringHelper::toLowerCase(Craft::t('analytics', Analytics::$plugin->metadata->getDimMet($metricString)))
277
        ];
278
279
        $view = Analytics::$plugin->getViews()->getViewById($viewId);
280
281
        return [
282
            'view' => $view->name,
283
            'type' => 'counter',
284
            'counter' => $counter,
285
            'response' => $report,
286
            'metric' => Craft::t('analytics', Analytics::$plugin->metadata->getDimMet($metricString)),
287
            'period' => $period,
288
            'periodLabel' => Craft::t('analytics', 'this '.$period)
289
        ];
290
    }
291
292
    /**
293
     * Returns a pie report.
294
     *
295
     * @param array $request
296
     *
297
     * @return array
298
     * @throws \yii\base\InvalidConfigException
299
     */
300
    public function getPieReport(array $request)
301
    {
302
        $viewId = ($request['viewId'] ?? null);
303
        $period = ($request['period'] ?? null);
304
        $dimensionString = ($request['options']['dimension'] ?? null);
305
        $metricString = ($request['options']['metric'] ?? null);
306
        $startDate = date('Y-m-d', strtotime('-1 '.$period));
307
        $endDate = date('Y-m-d');
308
309
        $criteria = new ReportRequestCriteria;
310
        $criteria->viewId = $viewId;
311
        $criteria->startDate = $startDate;
312
        $criteria->endDate = $endDate;
313
        $criteria->metrics = $metricString;
314
        $criteria->dimensions = $dimensionString;
315
316
        $reportResponse = Analytics::$plugin->getApis()->getAnalyticsReporting()->getReport($criteria);
317
        $report = $this->parseReportingReport($reportResponse);
318
319
        $view = Analytics::$plugin->getViews()->getViewById($viewId);
320
321
        return [
322
            'view' => $view->name,
323
            'type' => 'pie',
324
            'chart' => $report,
325
            'dimension' => Craft::t('analytics', Analytics::$plugin->metadata->getDimMet($dimensionString)),
326
            'metric' => Craft::t('analytics', Analytics::$plugin->metadata->getDimMet($metricString)),
327
            'period' => $period,
328
            'periodLabel' => Craft::t('analytics', 'this '.$period)
329
        ];
330
    }
331
332
    /**
333
     * Returns a table report.
334
     *
335
     * @param array $request
336
     *
337
     * @return array
338
     * @throws \yii\base\InvalidConfigException
339
     */
340
    public function getTableReport(array $request)
341
    {
342
        $viewId = ($request['viewId'] ?? null);
343
344
        $period = ($request['period'] ?? null);
345
        $dimensionString = ($request['options']['dimension'] ?? null);
346
        $metricString = ($request['options']['metric'] ?? null);
347
348
        $criteria = new ReportRequestCriteria;
349
        $criteria->viewId = $viewId;
350
        $criteria->dimensions = $dimensionString;
351
        $criteria->metrics = $metricString;
352
        $criteria->startDate = date('Y-m-d', strtotime('-1 '.$period));
353
        $criteria->endDate = date('Y-m-d');
354
355
        $reportResponse = Analytics::$plugin->getApis()->getAnalyticsReporting()->getReport($criteria);
356
        $report = $this->parseReportingReport($reportResponse);
357
358
        $view = Analytics::$plugin->getViews()->getViewById($viewId);
359
360
        return [
361
            'view' => $view->name,
362
            'type' => 'table',
363
            'chart' => $report,
364
            'dimension' => Craft::t('analytics', Analytics::$plugin->metadata->getDimMet($dimensionString)),
365
            'metric' => Craft::t('analytics', Analytics::$plugin->metadata->getDimMet($metricString)),
366
            'period' => $period,
367
            'periodLabel' => Craft::t('analytics', 'This '.$period)
368
        ];
369
    }
370
371
    /**
372
     * Returns a geo report.
373
     *
374
     * @param array $request
375
     *
376
     * @return array
377
     * @throws \yii\base\InvalidConfigException
378
     */
379
    public function getGeoReport(array $request)
380
    {
381
        $viewId = ($request['viewId'] ?? null);
382
        $period = ($request['period'] ?? null);
383
        $dimensionString = ($request['options']['dimension'] ?? null);
384
        $metricString = ($request['options']['metric'] ?? null);
385
386
        $originDimension = $dimensionString;
387
388
        if ($dimensionString === 'ga:city') {
389
            $dimensionString = 'ga:latitude,ga:longitude,'.$dimensionString;
390
        }
391
392
        $criteria = new ReportRequestCriteria;
393
        $criteria->viewId = $viewId;
394
        $criteria->dimensions = $dimensionString;
395
        $criteria->metrics = $metricString;
396
        $criteria->startDate = date('Y-m-d', strtotime('-1 '.$period));
397
        $criteria->endDate = date('Y-m-d');
398
399
        $reportResponse = Analytics::$plugin->getApis()->getAnalyticsReporting()->getReport($criteria);
400
        $report = $this->parseReportingReport($reportResponse);
401
402
        $view = Analytics::$plugin->getViews()->getViewById($viewId);
403
404
        return [
405
            'view' => $view->name,
406
            'type' => 'geo',
407
            'chart' => $report,
408
            'dimensionRaw' => $originDimension,
409
            'dimension' => Craft::t('analytics', Analytics::$plugin->metadata->getDimMet($originDimension)),
410
            'metric' => Craft::t('analytics', Analytics::$plugin->metadata->getDimMet($metricString)),
411
            'period' => $period,
412
            'periodLabel' => Craft::t('analytics', 'This '.$period)
413
        ];
414
    }
415
416
    // Private Methods
417
    // =========================================================================
418
419
    /**
420
     * @param Google_Service_AnalyticsReporting_Report $report
421
     * @return array
422
     */
423
    private function parseReportingReport(Google_Service_AnalyticsReporting_Report $report): array
424
    {
425
        $cols = $this->parseReportingReportCols($report);
426
        $rows = $this->parseReportingReportRows($report);
427
        $totals = $report->getData()->getTotals()[0]->getValues();
428
429
        return [
430
            'cols' => $cols,
431
            'rows' => $rows,
432
            'totals' => $totals
433
        ];
434
    }
435
436
    /**
437
     * @param Google_Service_AnalyticsReporting_Report $report
438
     * @return array
439
     */
440
    private function parseReportingReportCols(Google_Service_AnalyticsReporting_Report $report): array
441
    {
442
        $columnHeader = $report->getColumnHeader();
443
        $columnHeaderDimensions = $columnHeader->getDimensions();
444
        $metricHeaderEntries = $columnHeader->getMetricHeader()->getMetricHeaderEntries();
445
446
        $cols = [];
447
448
        if ($columnHeaderDimensions) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $columnHeaderDimensions of type string[] is implicitly converted to a boolean; are you sure this is intended? If so, consider using ! empty($expr) instead to make it clear that you intend to check for an array without elements.

This check marks implicit conversions of arrays to boolean values in a comparison. While in PHP an empty array is considered to be equal (but not identical) to false, this is not always apparent.

Consider making the comparison explicit by using empty(..) or ! empty(...) instead.

Loading history...
449
            foreach ($columnHeaderDimensions as $columnHeaderDimension) {
450
451
                $id = $columnHeaderDimension;
452
                $label = Analytics::$plugin->metadata->getDimMet($columnHeaderDimension);
453
454
                switch ($columnHeaderDimension) {
455
                    case 'ga:date':
456
                    case 'ga:yearMonth':
457
                        $type = 'date';
458
                        break;
459
460
                    case 'ga:continent':
461
                        $type = 'continent';
462
                        break;
463
                    case 'ga:subContinent':
464
                        $type = 'subContinent';
465
                        break;
466
467
                    case 'ga:latitude':
468
                    case 'ga:longitude':
469
                        $type = 'float';
470
                        break;
471
472
                    default:
473
                        $type = 'string';
474
                }
475
476
                $col = [
477
                    'type' => $type,
478
                    'label' => Craft::t('analytics', $label),
479
                    'id' => $id,
480
                ];
481
482
                array_push($cols, $col);
483
            }
484
        }
485
486
        foreach ($metricHeaderEntries as $metricHeaderEntry) {
487
            $label = Analytics::$plugin->metadata->getDimMet($metricHeaderEntry['name']);
488
489
            $col = [
490
                'type' => strtolower($metricHeaderEntry['type']),
0 ignored issues
show
Bug introduced by
It seems like $metricHeaderEntry['type'] can also be of type null; however, parameter $string of strtolower() 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

490
                'type' => strtolower(/** @scrutinizer ignore-type */ $metricHeaderEntry['type']),
Loading history...
491
                'label' => Craft::t('analytics', $label),
492
                'id' => $metricHeaderEntry['name'],
493
            ];
494
495
            array_push($cols, $col);
496
        }
497
498
        return $cols;
499
    }
500
501
    /**
502
     * @param Google_Service_AnalyticsReporting_Report $report
503
     * @return array
504
     */
505
    private function parseReportingReportRows(Google_Service_AnalyticsReporting_Report $report): array
506
    {
507
        $columnHeader = $report->getColumnHeader();
508
        $columnHeaderDimensions = $columnHeader->getDimensions();
509
510
        $rows = [];
511
512
        foreach ($report->getData()->getRows() as $_row) {
513
514
            $colIndex = 0;
515
            $row = [];
516
517
            $dimensions = $_row->getDimensions();
518
519
            if ($dimensions) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $dimensions of type string[] is implicitly converted to a boolean; are you sure this is intended? If so, consider using ! empty($expr) instead to make it clear that you intend to check for an array without elements.

This check marks implicit conversions of arrays to boolean values in a comparison. While in PHP an empty array is considered to be equal (but not identical) to false, this is not always apparent.

Consider making the comparison explicit by using empty(..) or ! empty(...) instead.

Loading history...
520
                foreach ($dimensions as $_dimension) {
521
522
                    $value = $_dimension;
523
524
                    if ($columnHeaderDimensions) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $columnHeaderDimensions of type string[] is implicitly converted to a boolean; are you sure this is intended? If so, consider using ! empty($expr) instead to make it clear that you intend to check for an array without elements.

This check marks implicit conversions of arrays to boolean values in a comparison. While in PHP an empty array is considered to be equal (but not identical) to false, this is not always apparent.

Consider making the comparison explicit by using empty(..) or ! empty(...) instead.

Loading history...
525
                        if (isset($columnHeaderDimensions[$colIndex])) {
526
                            switch ($columnHeaderDimensions[$colIndex]) {
527
                                case 'ga:continent':
528
                                    $value = Analytics::$plugin->geo->getContinentCode($value);
529
                                    break;
530
                                case 'ga:subContinent':
531
                                    $value = Analytics::$plugin->geo->getSubContinentCode($value);
532
                                    break;
533
                            }
534
                        }
535
                    }
536
537
                    array_push($row, $value);
538
539
                    $colIndex++;
540
                }
541
            }
542
543
            foreach ($_row->getMetrics() as $_metric) {
544
                array_push($row, $_metric->getValues()[0]);
545
                $colIndex++;
546
            }
547
548
            array_push($rows, $row);
549
        }
550
551
        return $rows;
552
    }
553
}
554