Passed
Push — master ( 833bb5...c81a5e )
by Simon
01:18
created

GoogleAnalyticsReportService::findPageChildren()   B

Complexity

Conditions 5
Paths 3

Size

Total Lines 12
Code Lines 6

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 5
eloc 6
nc 3
nop 2
dl 0
loc 12
rs 8.8571
c 0
b 0
f 0
1
<?php
2
3
/**
4
 * Class GoogleAnalyticsReportService
5
 *
6
 * Base service for getting the Analytics report per page from Google.
7
 */
8
class GoogleAnalyticsReportService
1 ignored issue
show
Coding Style Compatibility introduced by
PSR1 recommends that each class must be in a namespace of at least one level to avoid collisions.

You can fix this by adding a namespace to your class:

namespace YourVendor;

class YourClass { }

When choosing a vendor namespace, try to pick something that is not too generic to avoid conflicts with other libraries.

Loading history...
9
{
10
11
    /**
12
     * @var GoogleClientService
13
     */
14
    protected $client;
15
16
    /**
17
     * @var Google_Service_AnalyticsReporting_DateRange
18
     */
19
    protected $dateRange;
20
21
    /**
22
     * @var Google_Service_AnalyticsReporting
23
     */
24
    protected $analytics;
25
26
    /**
27
     * @var Google_Service_AnalyticsReporting_Metric
28
     */
29
    protected $metrics;
30
31
    public $batched = false;
32
33
    /**
34
     * GoogleReportService constructor.
35
     * @param GoogleClientService $client
36
     */
37
    public function __construct($client)
38
    {
39
        $config = SiteConfig::current_site_config();
0 ignored issues
show
Bug introduced by
The type SiteConfig 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...
40
        $this->client = $client;
41
        $this->setDateRange($config->DateRange);
42
        $this->setAnalytics(new Google_Service_AnalyticsReporting($client->getClient()));
43
        $this->setMetrics($config->Metric);
44
    }
45
46
    /**
47
     * @return mixed
48
     */
49
    public function getReport()
50
    {
51
        $config = SiteConfig::current_site_config();
52
53
        $request = new Google_Service_AnalyticsReporting_ReportRequest();
54
        $request->setDimensions(['name' => 'ga:pagePath']);
55
        $request->setViewId($config->Viewid);
56
        $request->setDimensionFilterClauses($this->getDimensionFilterClauses());
57
        $request->setDateRanges($this->getDateRange());
58
59
        $body = new Google_Service_AnalyticsReporting_GetReportsRequest();
60
        $body->setReportRequests([$request]);
61
62
        return $this->analytics->reports->batchGet($body);
63
    }
64
65
    /**
66
     * @return mixed
67
     */
68
    public function getClient()
69
    {
70
        return $this->client;
71
    }
72
73
    /**
74
     * @param mixed $client
75
     */
76
    public function setClient($client)
77
    {
78
        $this->client = $client;
79
    }
80
81
    /**
82
     * @return Google_Service_AnalyticsReporting_DateRange
83
     */
84
    public function getDateRange()
85
    {
86
        return $this->dateRange;
87
    }
88
89
    /**
90
     * @param int $dateRange
91
     */
92
    public function setDateRange($dateRange)
93
    {
94
        $analyticsRange = new Google_Service_AnalyticsReporting_DateRange();
95
        $analyticsRange->setStartDate($dateRange . 'daysAgo');
96
        $analyticsRange->setEndDate('today');
97
        $this->dateRange = $analyticsRange;
98
    }
99
100
    /**
101
     * @return Google_Service_AnalyticsReporting
102
     */
103
    public function getAnalytics()
104
    {
105
        return $this->analytics;
106
    }
107
108
    /**
109
     * @param Google_Service_AnalyticsReporting $analytics
110
     */
111
    public function setAnalytics($analytics)
112
    {
113
        $this->analytics = $analytics;
114
    }
115
116
    /**
117
     * @return mixed
118
     */
119
    public function getMetrics()
120
    {
121
        return $this->metrics;
122
    }
123
124
    /**
125
     * @param mixed $metrics
126
     */
127
    public function setMetrics($metrics)
128
    {
129
        $metric = new Google_Service_AnalyticsReporting_Metric();
130
        $metric->setExpression($metrics);
131
        $this->metrics = $metric;
132
    }
133
134
    /**
135
     * Set up the Dimension Filters
136
     *
137
     * @return array|Google_Service_AnalyticsReporting_DimensionFilter[]
138
     */
139
    public function getDimensionFilters()
140
    {
141
        $filters = [];
142
        $startWith = config::inst()->get(static::class, 'starts_with');
0 ignored issues
show
Bug introduced by
The type config 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...
143
        $endWith = config::inst()->get(static::class, 'ends_with');
144
        if ($startWith) {
145
            return $this->createFilter('BEGINS_WITH', $startWith, $filters);
146
        }
147
        if ($endWith) {
148
            return $this->createFilter('ENDS_WITH', $endWith, $filters);
149
        }
150
151
        return $this->getPageDimensionFilters();
152
    }
153
154
    /**
155
     * @return array|Google_Service_AnalyticsReporting_DimensionFilter[]
156
     */
157
    public function getPageDimensionFilters()
158
    {
159
        $filters = [];
160
        // Operators are not constants, see here: https://developers.google.com/analytics/devguides/reporting/core/v4/rest/v4/reports/batchGet#Operator
161
        $operator = 'ENDS_WITH';
162
        $pages = $this->getPages();
163
164
        foreach ($pages as $page) {
165
            // Home is recorded as just a slash in GA, we need an
166
            // exception for that
167
            if ($page === 'home') {
168
                $page = '/';
169
                $operator = 'EXACT';
170
            }
171
172
            $filters = $this->createFilter($operator, $page, $filters);
173
            // Reset the operator
174
            if ($operator === 'EXACT') {
175
                $operator = 'ENDS_WITH';
176
            }
177
        }
178
179
        return $filters;
180
    }
181
182
    /**
183
     * @return Google_Service_AnalyticsReporting_DimensionFilterClause
184
     */
185
    public function getDimensionFilterClauses()
186
    {
187
        $dimensionFilters = $this->getDimensionFilters();
188
        $dimensionClauses = new Google_Service_AnalyticsReporting_DimensionFilterClause();
189
        $dimensionClauses->setFilters($dimensionFilters);
190
        $dimensionClauses->setOperator('OR');
191
192
        return $dimensionClauses;
193
    }
194
195
    /**
196
     * Get the pages filtered by optional blacklisting and whitelisting
197
     * Return an array of the URLSegments, to limit memory abuse
198
     * @return array
199
     */
200
    public function getPages()
201
    {
202
        $blacklist = Config::inst()->get(static::class, 'blacklist');
0 ignored issues
show
Bug introduced by
The type Config 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...
203
        $whitelist = Config::inst()->get(static::class, 'whitelist');
204
        /** @var DataList|Page[] $pages */
205
        $pages = SiteTree::get();
0 ignored issues
show
Bug introduced by
The type SiteTree 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...
206
        if ($blacklist) {
207
            $pages = $this->getBlacklist($blacklist, $pages);
208
        }
209
        if ($whitelist) {
210
            $pages = $this->getWhitelistPages($whitelist, $pages);
211
        }
212
213
        if ($pages->count() > 20) {
214
            $this->batched = true;
215
        }
216
217
        // Google limits to 20 reports per complex filter setup
218
        return $pages->limit(20)->column('URLSegment');
219
    }
220
221
    /**
222
     * @param array $blacklist
223
     * @param DataList $pages
224
     * @return DataList
225
     */
226
    protected function getBlacklist($blacklist, $pages)
227
    {
228
        $ids = [];
229
        foreach ($blacklist as $class) {
230
            $blacklistpages = $class::get();
231
            $ids = array_merge($ids, $blacklistpages->column('ID'));
232
        }
233
        $pages = $pages->exclude(['ID' => $ids]);
234
235
        return $pages;
236
    }
237
238
    /**
239
     * @param array $whitelist
240
     * @param DataList $pages
241
     * @return DataList
242
     */
243
    protected function getWhitelistPages($whitelist, $pages)
244
    {
245
        $ids = [];
246
        foreach ($whitelist as $class) {
247
            $nowDate = date('Y-m-d');
248
            $whitelistpages = $class::get()
249
                // This needs to be a where because of `IS NULL`
250
                ->where("(`Page`.`LastAnalyticsUpdate` < $nowDate)
251
                        OR (`Page`.`LastAnalyticsUpdate` IS NULL)");
252
            $ids = array_merge($ids, $whitelistpages->column('ID'));
253
        }
254
        $pages = $pages->filter(['ID' => $ids]);
255
256
        return $pages;
257
    }
258
259
    /**
260
     * @param $operator
261
     * @param $page
262
     * @param $filters
263
     * @return array|Google_Service_AnalyticsReporting_DimensionFilter[]
264
     */
265
    protected function createFilter($operator, $page, $filters)
266
    {
267
        $filter = new Google_Service_AnalyticsReporting_DimensionFilter();
268
        $filter->setOperator($operator);
269
        $filter->setCaseSensitive(false);
270
        $filter->setExpressions([$page]);
271
        $filter->setDimensionName('ga:pagePath');
272
        $filters[] = $filter;
273
274
        return $filters;
275
    }
276
277
    /**
278
     * @param array $rows
279
     * @return int
280
     * @throws \ValidationException
281
     */
282
    public function updateVisits($rows)
283
    {
284
        $count = 0;
285
        foreach ($rows as $row) {
286
            $metrics = $row->getMetrics();
287
            $values = $metrics[0]->getValues();
288
            $dimensions = $row->getDimensions();
289
            $dimensions = explode('/', $dimensions[0]);
290
            $page = $this->findPage($dimensions);
291
            // Only write if a page is found
292
            if ($page) {
293
                $count++;
294
                $this->updatePageVisit($page, $values);
295
            }
296
        }
297
        // If we're not getting any results back, we're out of data from Google.
298
        // Stop the batching process.
299
        if ($count === 0) {
300
            $this->batched = false;
301
        }
302
        return $count;
303
    }
304
305
    /**
306
     * @param array $dimensions
307
     * @return Page
308
     */
309
    public function findPage($dimensions)
310
    {
311
        // Because analytics returns a page link that starts with a forward slash
312
        // The first element of the array needs to be removed as it's empty.
313
        array_shift($dimensions);
314
        $firstItem = array_shift($dimensions);
315
        // If dimensions is 2 empty keys, we're looking at the homepage
316
        if (!$firstItem) {
317
            $firstItem = 'home';
318
        }
319
        // Start at the root of the site page
320
        /** @var Page $page */
321
        $page = Page::get()->filter(['URLSegment' => $firstItem])->first();
0 ignored issues
show
Bug introduced by
The type Page 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...
322
        // If there are items left in the dimensions, continue traversing down
323
        if (count($dimensions) && $page) {
324
            $page = $this->findPageChildren($dimensions, $page);
325
        }
326
327
        return $page;
328
    }
329
330
    /**
331
     * @param array $dimensions
332
     * @param Page $page
333
     * @return mixed
334
     */
335
    protected function findPageChildren($dimensions, $page)
336
    {
337
        foreach ($dimensions as $segment) {
338
            if (strpos($segment, '?') === 0 || !$page || !$segment) {
339
                continue;
340
            }
341
            /** @var DataList|Page[] $children */
342
            $children = $page->AllChildren();
343
            $page = $children->filter(['URLSegment' => $segment])->first();
344
        }
345
346
        return $page;
347
    }
348
349
    /**
350
     * @param Page $page
351
     * @param array $values
352
     * @throws \ValidationException
353
     */
354
    protected function updatePageVisit($page, $values)
355
    {
356
        $rePublish = $page->isPublished();
357
        $page->VisitCount = $values[0];
358
        $page->LastAnalyticsUpdate = date('Y-m-d');
359
        $page->write();
360
        // If the page was published before write, republish or the change won't be applied
361
        if ($rePublish) {
362
            $page->publish('Stage', 'Live');
363
        }
364
        $page->destroy();
365
    }
366
}
367