Passed
Push — develop ( 2cb041...56d118 )
by Andrew
02:49
created

TablesController::actionPagesIndex()   F

Complexity

Conditions 19
Paths 63

Size

Total Lines 142
Code Lines 96

Duplication

Lines 0
Ratio 0 %

Importance

Changes 13
Bugs 0 Features 0
Metric Value
eloc 96
c 13
b 0
f 0
dl 0
loc 142
rs 3.7006
cc 19
nc 63
nop 7

How to fix   Long Method    Complexity   

Long Method

Small methods make your code easier to understand, in particular if combined with a good name. Besides, if your method is small, finding a good name is usually much easier.

For example, if you find yourself adding comments to a method's body, this is usually a good sign to extract the commented part to a new method, and use the comment as a starting point when coming up with a good name for this new method.

Commonly applied refactorings include:

1
<?php
2
/**
3
 * Webperf plugin for Craft CMS 3.x
4
 *
5
 * Monitor the performance of your webpages through real-world user timing data
6
 *
7
 * @link      https://nystudio107.com
0 ignored issues
show
Coding Style introduced by
The tag in position 1 should be the @copyright tag
Loading history...
8
 * @copyright Copyright (c) 2019 nystudio107
0 ignored issues
show
Coding Style introduced by
@copyright tag must contain a year and the name of the copyright holder
Loading history...
9
 */
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 @package tag in file comment
Loading history...
Coding Style introduced by
Missing @author tag in file comment
Loading history...
Coding Style introduced by
Missing @license tag in file comment
Loading history...
10
11
namespace nystudio107\webperf\controllers;
12
13
use nystudio107\webperf\helpers\Permission as PermissionHelper;
14
15
use Craft;
0 ignored issues
show
Bug introduced by
The type Craft 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...
16
use craft\db\Query;
0 ignored issues
show
Bug introduced by
The type craft\db\Query 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...
17
use craft\helpers\DateTimeHelper;
0 ignored issues
show
Bug introduced by
The type craft\helpers\DateTimeHelper 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...
18
use craft\helpers\UrlHelper;
0 ignored issues
show
Bug introduced by
The type craft\helpers\UrlHelper 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...
19
use craft\web\Controller;
0 ignored issues
show
Bug introduced by
The type craft\web\Controller 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
21
use yii\web\BadRequestHttpException;
0 ignored issues
show
Bug introduced by
The type yii\web\BadRequestHttpException 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 yii\web\ForbiddenHttpException;
0 ignored issues
show
Bug introduced by
The type yii\web\ForbiddenHttpException 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...
23
use yii\web\Response;
0 ignored issues
show
Bug introduced by
The type yii\web\Response 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...
24
25
/**
0 ignored issues
show
Coding Style introduced by
Missing short description in doc comment
Loading history...
26
 * @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...
27
 * @package   Webperf
0 ignored issues
show
Coding Style introduced by
Tag value for @package tag indented incorrectly; expected 1 spaces but found 3
Loading history...
28
 * @since     1.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...
29
 */
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...
30
class TablesController extends Controller
31
{
32
    // Constants
33
    // =========================================================================
34
35
    const SORT_MAP = [
36
        'DESC' => SORT_DESC,
37
        'ASC' => SORT_ASC,
38
    ];
39
40
    const ALLOWED_PAGE_INDEX_SORT_FIELDS = [
41
        'url',
42
        'pageLoad',
43
        'craftDbCnt',
44
        'craftTwigCnt',
45
        'craftOtherCnt',
46
        'craftTotalMemory',
47
        'cnt',
48
    ];
49
50
    const ALLOWED_PAGE_DETAIL_SORT_FIELDS = [
51
        'dateCreated',
52
        'pageLoad',
53
        'craftDbCnt',
54
        'craftTwigCnt',
55
        'craftOtherCnt',
56
        'craftTotalMemory',
57
        'device',
58
        'os',
59
        'browser',
60
        'countryCode',
61
    ];
62
63
    const ALLOWED_ERRORS_INDEX_SORT_FIELDS = [
64
        'url',
65
        'latestErrorDate',
66
        'craftCount',
67
        'boomerangCount',
68
        'cnt',
69
    ];
70
71
    const ALLOWED_ERRORS_DETAIL_SORT_FIELDS = [
72
        'dateCreated',
73
        'pageErrors',
74
        'device',
75
        'os',
76
        'browser',
77
        'countryCode',
78
    ];
79
80
    // Protected Properties
81
    // =========================================================================
82
83
    /**
0 ignored issues
show
Coding Style introduced by
Missing short description in doc comment
Loading history...
84
     * @var    bool|array
0 ignored issues
show
Coding Style introduced by
Tag value for @var tag indented incorrectly; expected 1 spaces but found 4
Loading history...
85
     */
86
    protected $allowAnonymous = [];
87
88
    // Public Methods
89
    // =========================================================================
90
91
    /**
92
     * Handle requests for the performance index table
93
     *
94
     * @param string $sort
0 ignored issues
show
Coding Style introduced by
Missing parameter comment
Loading history...
Coding Style introduced by
Doc comment for parameter $sort does not match actual variable name $start
Loading history...
95
     * @param int    $page
0 ignored issues
show
Coding Style introduced by
Missing parameter comment
Loading history...
Coding Style introduced by
Doc comment for parameter $page does not match actual variable name $end
Loading history...
96
     * @param int    $per_page
0 ignored issues
show
Coding Style introduced by
Missing parameter comment
Loading history...
Coding Style introduced by
Doc comment for parameter $per_page does not match actual variable name $sort
Loading history...
97
     * @param string $filter
0 ignored issues
show
Coding Style introduced by
Missing parameter comment
Loading history...
Coding Style introduced by
Doc comment for parameter $filter does not match actual variable name $page
Loading history...
98
     * @param string $start
0 ignored issues
show
Coding Style introduced by
Missing parameter comment
Loading history...
Coding Style introduced by
Doc comment for parameter $start does not match actual variable name $per_page
Loading history...
99
     * @param string $end
0 ignored issues
show
Coding Style introduced by
Missing parameter comment
Loading history...
Coding Style introduced by
Doc comment for parameter $end does not match actual variable name $filter
Loading history...
100
     * @param int    $siteId
0 ignored issues
show
Coding Style introduced by
Missing parameter comment
Loading history...
101
     *
102
     * @return Response
103
     * @throws ForbiddenHttpException
104
     * @throws BadRequestHttpException
105
     */
106
    public function actionPagesIndex(
107
        string $start = '',
108
        string $end = '',
109
        string $sort = 'pageLoad|DESC',
110
        int $page = 1,
111
        int $per_page = 20,
112
        $filter = '',
113
        $siteId = 0
114
    ): Response {
115
        PermissionHelper::controllerPermissionCheck('webperf:performance');
116
        $data = [];
117
        $sortField = 'pageLoad';
118
        $sortType = 'DESC';
119
        // Add a day since YYYY-MM-DD is really YYYY-MM-DD 00:00:00
120
        $end = date('Y-m-d', strtotime($end.'+1 day'));
121
        // Figure out the sorting type
122
        if ($sort !== '') {
123
            if (strpos($sort, '|') === false) {
124
                $sortField = $sort;
125
            } else {
126
                list($sortField, $sortType) = explode('|', $sort);
127
            }
128
        }
129
        $sortType = strtoupper($sortType);
130
        $sortType = self::SORT_MAP[$sortType] ?? self::SORT_MAP['DESC'];
131
        // Validate untrusted data
132
        if (!in_array($sortField, self::ALLOWED_PAGE_INDEX_SORT_FIELDS, true)) {
133
            throw new BadRequestHttpException(Craft::t('webperf', 'Invalid sort field specified.'));
134
        }
135
        // Query the db table
136
        $offset = ($page - 1) * $per_page;
137
        $query = (new Query())
138
            ->select([
0 ignored issues
show
Coding Style introduced by
The opening parenthesis of a multi-line function call should be the last content on the line.
Loading history...
139
                '[[url]]',
140
                'MIN([[title]]) AS [[title]]',
141
                'COUNT([[url]]) AS [[cnt]]',
142
                'AVG([[pageLoad]]) AS [[pageLoad]]',
143
                'AVG([[domInteractive]]) AS [[domInteractive]]',
144
                'AVG([[firstContentfulPaint]]) AS [[firstContentfulPaint]]',
145
                'AVG([[firstPaint]]) AS [[firstPaint]]',
146
                'AVG([[firstByte]]) AS [[firstByte]]',
147
                'AVG([[connect]]) AS [[connect]]',
148
                'AVG([[dns]]) AS [[dns]]',
149
                'AVG([[craftTotalMs]]) AS [[craftTotalMs]]',
150
                'AVG([[craftDbCnt]]) AS [[craftDbCnt]]',
151
                'AVG([[craftDbMs]]) AS [[craftDbMs]]',
152
                'AVG([[craftTwigCnt]]) AS [[craftTwigCnt]]',
153
                'AVG([[craftTwigMs]]) AS [[craftTwigMs]]',
154
                'AVG([[craftTotalMemory]]) AS [[craftTotalMemory]]',
155
            ])
0 ignored issues
show
Coding Style introduced by
For multi-line function calls, the closing parenthesis should be on a new line.

If a function call spawns multiple lines, the coding standard suggests to move the closing parenthesis to a new line:

someFunctionCall(
    $firstArgument,
    $secondArgument,
    $thirdArgument
); // Closing parenthesis on a new line.
Loading history...
156
            ->from(['{{%webperf_data_samples}}'])
157
            ->offset($offset)
158
            ->where(['between', 'dateCreated', $start, $end])
0 ignored issues
show
Coding Style introduced by
Space after closing parenthesis of function call prohibited
Loading history...
159
        ;
160
        if ((int)$siteId !== 0) {
161
            $query->andWhere(['siteId' => $siteId]);
162
        }
163
        if ($filter !== '') {
164
            $query
165
                ->andWhere(['like', 'url', $filter])
166
                ->orWhere(['like', 'title', $filter])
0 ignored issues
show
Coding Style introduced by
Space after closing parenthesis of function call prohibited
Loading history...
167
            ;
168
        }
169
        $query
170
            ->orderBy([$sortField => $sortType])
171
            ->groupBy('url')
172
            ->limit($per_page)
0 ignored issues
show
Coding Style introduced by
Space after closing parenthesis of function call prohibited
Loading history...
173
        ;
174
175
        $stats = $query->all();
176
        if ($stats) {
177
            $user = Craft::$app->getUser()->getIdentity();
178
            // Compute the largest page load time
179
            $maxTotalPageLoad = 0;
180
            foreach ($stats as &$stat) {
181
             // Determine the stat type
0 ignored issues
show
Coding Style introduced by
Line indented incorrectly; expected at least 16 spaces, found 13
Loading history...
182
                if (!empty($stat['pageLoad']) && !empty($stat['craftTotalMs'])) {
183
                    $stat['type'] = 'both';
184
                }
185
                if (empty($stat['firstByte'])) {
186
                    $stat['type'] = 'craft';
187
                }
188
                if (empty($stat['craftTotalMs'])) {
189
                    $stat['type'] = 'frontend';
190
                }
191
                if ($stat['pageLoad'] > $maxTotalPageLoad) {
192
                    $maxTotalPageLoad = $stat['pageLoad'];
193
                }
194
            }
195
            // Massage the stats
196
            $index = 1;
197
            foreach ($stats as &$stat) {
198
                $stat['id'] = $index++;
199
                $stat['cnt'] = (int)$stat['cnt'];
200
                $stat['maxTotalPageLoad'] = (int)$maxTotalPageLoad;
201
                // Decode any emojis in the title
202
                if (!empty($stat['title'])) {
203
                    $stat['title'] = html_entity_decode($stat['title'], ENT_NOQUOTES, 'UTF-8');
204
                }
205
                // Set up the appropriate helper links
206
                $stat['deleteLink'] = UrlHelper::actionUrl('webperf/data-samples/delete-samples-by-url', [
0 ignored issues
show
Coding Style introduced by
The opening parenthesis of a multi-line function call should be the last content on the line.
Loading history...
207
                    'pageUrl' => $stat['url'],
208
                    'siteId' => $siteId
209
                ]);
0 ignored issues
show
Coding Style introduced by
For multi-line function calls, the closing parenthesis should be on a new line.

If a function call spawns multiple lines, the coding standard suggests to move the closing parenthesis to a new line:

someFunctionCall(
    $firstArgument,
    $secondArgument,
    $thirdArgument
); // Closing parenthesis on a new line.
Loading history...
210
                $stat['detailPageUrl'] = UrlHelper::cpUrl('webperf/performance/page-detail', [
0 ignored issues
show
Coding Style introduced by
The opening parenthesis of a multi-line function call should be the last content on the line.
Loading history...
211
                    'pageUrl' => $stat['url'],
212
                    'siteId' => $siteId,
213
                ]);
0 ignored issues
show
Coding Style introduced by
For multi-line function calls, the closing parenthesis should be on a new line.

If a function call spawns multiple lines, the coding standard suggests to move the closing parenthesis to a new line:

someFunctionCall(
    $firstArgument,
    $secondArgument,
    $thirdArgument
); // Closing parenthesis on a new line.
Loading history...
214
                // Override based on permissions
215
                if (!$user->can('webperf:delete-data-samples')) {
216
                    $stat['deleteLink'] = '';
217
                }
218
                if (!$user->can('webperf:performance-detail')) {
219
                    $stat['detailPageUrl'] = '';
220
                }
221
            }
222
            // Format the data for the API
223
            $data['data'] = $stats;
224
            $query = (new Query())
225
                ->select(['[[url]]'])
226
                ->from(['{{%webperf_data_samples}}'])
227
                ->groupBy('[[url]]')
228
                ->where(['between', 'dateCreated', $start, $end])
0 ignored issues
show
Coding Style introduced by
Space after closing parenthesis of function call prohibited
Loading history...
229
                ;
230
            if ($filter !== '') {
231
                $query->andWhere(['like', 'url', $filter]);
232
                $query->orWhere(['like', 'title', $filter]);
233
            }
234
            $count = $query->count();
235
            $data['links']['pagination'] = [
236
                'total' => $count,
237
                'per_page' => $per_page,
238
                'current_page' => $page,
239
                'last_page' => ceil($count / $per_page),
240
                'next_page_url' => null,
241
                'prev_page_url' => null,
242
                'from' => $offset + 1,
243
                'to' => $offset + ($count > $per_page ? $per_page : $count),
244
            ];
245
        }
246
247
        return $this->asJson($data);
248
    }
249
250
    /**
251
     * Handle requests for the performance detail table
252
     *
253
     * @param string $sort
0 ignored issues
show
Coding Style introduced by
Missing parameter comment
Loading history...
Coding Style introduced by
Doc comment for parameter $sort does not match actual variable name $start
Loading history...
254
     * @param int    $page
0 ignored issues
show
Coding Style introduced by
Missing parameter comment
Loading history...
Coding Style introduced by
Doc comment for parameter $page does not match actual variable name $end
Loading history...
255
     * @param int    $per_page
0 ignored issues
show
Coding Style introduced by
Missing parameter comment
Loading history...
Coding Style introduced by
Doc comment for parameter $per_page does not match actual variable name $sort
Loading history...
256
     * @param string $filter
0 ignored issues
show
Coding Style introduced by
Missing parameter comment
Loading history...
Coding Style introduced by
Doc comment for parameter $filter does not match actual variable name $page
Loading history...
257
     * @param string $pageUrl
0 ignored issues
show
Coding Style introduced by
Missing parameter comment
Loading history...
Coding Style introduced by
Doc comment for parameter $pageUrl does not match actual variable name $per_page
Loading history...
258
     * @param string $start
0 ignored issues
show
Coding Style introduced by
Missing parameter comment
Loading history...
Coding Style introduced by
Doc comment for parameter $start does not match actual variable name $filter
Loading history...
259
     * @param string $end
0 ignored issues
show
Coding Style introduced by
Missing parameter comment
Loading history...
Coding Style introduced by
Doc comment for parameter $end does not match actual variable name $pageUrl
Loading history...
260
     * @param int    $siteId
0 ignored issues
show
Coding Style introduced by
Missing parameter comment
Loading history...
261
     *
262
     * @return Response
263
     * @throws ForbiddenHttpException
264
     * @throws BadRequestHttpException
265
     */
266
    public function actionPageDetail(
267
        string $start = '',
268
        string $end = '',
269
        string $sort = 'pageLoad|DESC',
270
        int $page = 1,
271
        int $per_page = 20,
272
        $filter = '',
273
        $pageUrl = '',
274
        $siteId = 0
275
    ): Response {
276
        PermissionHelper::controllerPermissionCheck('webperf:performance');
277
        $data = [];
278
        $sortField = 'pageLoad';
279
        $sortType = 'DESC';
280
        // Add a day since YYYY-MM-DD is really YYYY-MM-DD 00:00:00
281
        $end = date('Y-m-d', strtotime($end.'+1 day'));
282
        $pageUrl = urldecode($pageUrl);
283
        // Figure out the sorting type
284
        if ($sort !== '') {
285
            if (strpos($sort, '|') === false) {
286
                $sortField = $sort;
287
            } else {
288
                list($sortField, $sortType) = explode('|', $sort);
289
            }
290
        }
291
        $sortType = strtoupper($sortType);
292
        $sortType = self::SORT_MAP[$sortType] ?? self::SORT_MAP['DESC'];
293
        // Validate untrusted data
294
        if (!in_array($sortField, self::ALLOWED_PAGE_DETAIL_SORT_FIELDS, true)) {
295
            throw new BadRequestHttpException(Craft::t('webperf', 'Invalid sort field specified.'));
296
        }
297
        // Query the db table
298
        $offset = ($page - 1) * $per_page;
299
        $query = (new Query())
300
            ->from(['{{%webperf_data_samples}}'])
301
            ->offset($offset)
302
            ->where(['url' => $pageUrl])
303
            ->andWhere(['between', 'dateCreated', $start, $end])
0 ignored issues
show
Coding Style introduced by
Space after closing parenthesis of function call prohibited
Loading history...
304
        ;
305
        if ((int)$siteId !== 0) {
306
            $query->andWhere(['siteId' => $siteId]);
307
        }
308
        if ($filter !== '') {
309
            $query
310
                ->andWhere(['like', 'device', $filter])
311
                ->orWhere(['like', 'os', $filter])
312
                ->orWhere(['like', 'browser', $filter])
313
                ->orWhere(['like', 'countryCode', $filter])
0 ignored issues
show
Coding Style introduced by
Space after closing parenthesis of function call prohibited
Loading history...
314
            ;
315
        }
316
        $query
317
            ->orderBy([$sortField => $sortType])
318
            ->limit($per_page)
0 ignored issues
show
Coding Style introduced by
Space after closing parenthesis of function call prohibited
Loading history...
319
        ;
320
        $stats = $query->all();
321
        if ($stats) {
322
            $user = Craft::$app->getUser()->getIdentity();
323
            // Compute the largest page load time
324
            $maxTotalPageLoad = 0;
325
            foreach ($stats as &$stat) {
326
                // Determine the stat type
327
                if (!empty($stat['pageLoad']) && !empty($stat['craftTotalMs'])) {
328
                    $stat['type'] = 'both';
329
                }
330
                if (empty($stat['firstByte'])) {
331
                    $stat['type'] = 'craft';
332
                }
333
                if (empty($stat['craftTotalMs'])) {
334
                    $stat['type'] = 'frontend';
335
                }
336
                if ($stat['pageLoad'] > $maxTotalPageLoad) {
337
                    $maxTotalPageLoad = (int)$stat['pageLoad'];
338
                }
339
            }
340
            // Massage the stats
341
            foreach ($stats as &$stat) {
342
                if (!empty($stats['dateCreated'])) {
343
                    $date = DateTimeHelper::toDateTime($stats['dateCreated']);
344
                    $stats['dateCreated'] = $date->format('Y-m-d H:i:s');
345
                }
346
                $stat['mobile'] = (bool)$stat['mobile'];
347
                $stat['maxTotalPageLoad'] = (int)$maxTotalPageLoad;
348
                // Decode any emojis in the title
349
                if (!empty($stat['title'])) {
350
                    $stat['title'] = html_entity_decode($stat['title'], ENT_NOQUOTES, 'UTF-8');
351
                }
352
                $stat['deleteLink'] = UrlHelper::actionUrl('webperf/data-samples/delete-sample-by-id', [
0 ignored issues
show
Coding Style introduced by
The opening parenthesis of a multi-line function call should be the last content on the line.
Loading history...
353
                    'id' => $stat['id']
354
                ]);
0 ignored issues
show
Coding Style introduced by
For multi-line function calls, the closing parenthesis should be on a new line.

If a function call spawns multiple lines, the coding standard suggests to move the closing parenthesis to a new line:

someFunctionCall(
    $firstArgument,
    $secondArgument,
    $thirdArgument
); // Closing parenthesis on a new line.
Loading history...
355
                // Override based on permissions
356
                if (!$user->can('webperf:delete-data-samples')) {
357
                    $stat['deleteLink'] = '';
358
                }
359
            }
360
            // Format the data for the API
361
            $data['data'] = $stats;
362
            $query = (new Query())
363
                ->select(['[[url]]'])
364
                ->from(['{{%webperf_data_samples}}'])
365
                ->where(['url' => $pageUrl])
366
                ->andWhere(['between', 'dateCreated', $start, $end])
0 ignored issues
show
Coding Style introduced by
Space after closing parenthesis of function call prohibited
Loading history...
367
            ;
368
            if ($filter !== '') {
369
                $query
370
                    ->andWhere(['like', 'device', $filter])
371
                    ->orWhere(['like', 'os', $filter])
372
                    ->orWhere(['like', 'browser', $filter])
373
                    ->orWhere(['like', 'countryCode', $filter])
0 ignored issues
show
Coding Style introduced by
Space after closing parenthesis of function call prohibited
Loading history...
374
                ;
375
            }
376
            $count = $query->count();
377
            $data['links']['pagination'] = [
378
                'total' => $count,
379
                'per_page' => $per_page,
380
                'current_page' => $page,
381
                'last_page' => ceil($count / $per_page),
382
                'next_page_url' => null,
383
                'prev_page_url' => null,
384
                'from' => $offset + 1,
385
                'to' => $offset + ($count > $per_page ? $per_page : $count),
386
            ];
387
        }
388
389
        return $this->asJson($data);
390
    }
391
392
    /**
393
     * Handle requests for the pages index table
394
     *
395
     * @param string $sort
0 ignored issues
show
Coding Style introduced by
Missing parameter comment
Loading history...
Coding Style introduced by
Doc comment for parameter $sort does not match actual variable name $start
Loading history...
396
     * @param int    $page
0 ignored issues
show
Coding Style introduced by
Missing parameter comment
Loading history...
Coding Style introduced by
Doc comment for parameter $page does not match actual variable name $end
Loading history...
397
     * @param int    $per_page
0 ignored issues
show
Coding Style introduced by
Missing parameter comment
Loading history...
Coding Style introduced by
Doc comment for parameter $per_page does not match actual variable name $sort
Loading history...
398
     * @param string $filter
0 ignored issues
show
Coding Style introduced by
Missing parameter comment
Loading history...
Coding Style introduced by
Doc comment for parameter $filter does not match actual variable name $page
Loading history...
399
     * @param string $start
0 ignored issues
show
Coding Style introduced by
Missing parameter comment
Loading history...
Coding Style introduced by
Doc comment for parameter $start does not match actual variable name $per_page
Loading history...
400
     * @param string $end
0 ignored issues
show
Coding Style introduced by
Missing parameter comment
Loading history...
Coding Style introduced by
Doc comment for parameter $end does not match actual variable name $filter
Loading history...
401
     * @param int    $siteId
0 ignored issues
show
Coding Style introduced by
Missing parameter comment
Loading history...
402
     *
403
     * @return Response
404
     * @throws ForbiddenHttpException
405
     * @throws BadRequestHttpException
406
     */
407
    public function actionErrorsIndex(
408
        string $start = '',
409
        string $end = '',
410
        string $sort = 'url|DESC',
411
        int $page = 1,
412
        int $per_page = 20,
413
        $filter = '',
414
        $siteId = 0
415
    ): Response {
416
        PermissionHelper::controllerPermissionCheck('webperf:errors');
417
        $data = [];
418
        $sortField = 'url';
419
        $sortType = 'DESC';
420
        // Add a day since YYYY-MM-DD is really YYYY-MM-DD 00:00:00
421
        $end = date('Y-m-d', strtotime($end.'+1 day'));
422
        // Figure out the sorting type
423
        if ($sort !== '') {
424
            if (strpos($sort, '|') === false) {
425
                $sortField = $sort;
426
            } else {
427
                list($sortField, $sortType) = explode('|', $sort);
428
            }
429
        }
430
        $sortType = strtoupper($sortType);
431
        $sortType = self::SORT_MAP[$sortType] ?? self::SORT_MAP['DESC'];
432
        // Validate untrusted data
433
        if (!in_array($sortField, self::ALLOWED_ERRORS_INDEX_SORT_FIELDS, true)) {
434
            throw new BadRequestHttpException(Craft::t('webperf', 'Invalid sort field specified.'));
435
        }
436
        $db = Craft::$app->getDb();
437
        // Query the db table
438
        $offset = ($page - 1) * $per_page;
439
        $query = (new Query())
440
            ->select([
0 ignored issues
show
Coding Style introduced by
The opening parenthesis of a multi-line function call should be the last content on the line.
Loading history...
441
                '[[url]]',
442
                'MIN([[title]]) as [[title]]',
443
                'MAX([[dateCreated]]) as [[latestErrorDate]]',
444
                'COUNT([[url]]) AS cnt',
445
            ])
0 ignored issues
show
Coding Style introduced by
For multi-line function calls, the closing parenthesis should be on a new line.

If a function call spawns multiple lines, the coding standard suggests to move the closing parenthesis to a new line:

someFunctionCall(
    $firstArgument,
    $secondArgument,
    $thirdArgument
); // Closing parenthesis on a new line.
Loading history...
446
            ->from(['{{%webperf_error_samples}}'])
447
            ->offset($offset)
448
            ->where(['between', 'dateCreated', $start, $end])
0 ignored issues
show
Coding Style introduced by
Space after closing parenthesis of function call prohibited
Loading history...
449
        ;
450
        if ($db->getIsMysql()) {
451
            $query
452
                ->addSelect([
0 ignored issues
show
Coding Style introduced by
The opening parenthesis of a multi-line function call should be the last content on the line.
Loading history...
453
                    'SUM([[type]] = \'craft\') as [[craftCount]]',
454
                    'SUM([[type]] = \'boomerang\') as [[boomerangCount]]',
455
                ]);
0 ignored issues
show
Coding Style introduced by
For multi-line function calls, the closing parenthesis should be on a new line.

If a function call spawns multiple lines, the coding standard suggests to move the closing parenthesis to a new line:

someFunctionCall(
    $firstArgument,
    $secondArgument,
    $thirdArgument
); // Closing parenthesis on a new line.
Loading history...
456
        }
457
        if ($db->getIsPgsql()) {
458
            $query
459
                ->addSelect([
0 ignored issues
show
Coding Style introduced by
The opening parenthesis of a multi-line function call should be the last content on the line.
Loading history...
460
                    'SUM(case when [[type]] = \'craft\' then 1 else 0 end) as [[craftCount]]',
461
                    'SUM(case when [[type]] = \'boomerang\' then 1 else 0 end) as [[boomerangCount]]',
462
                ]);
0 ignored issues
show
Coding Style introduced by
For multi-line function calls, the closing parenthesis should be on a new line.

If a function call spawns multiple lines, the coding standard suggests to move the closing parenthesis to a new line:

someFunctionCall(
    $firstArgument,
    $secondArgument,
    $thirdArgument
); // Closing parenthesis on a new line.
Loading history...
463
        }
464
        if ((int)$siteId !== 0) {
465
            $query->andWhere(['siteId' => $siteId]);
466
        }
467
        if ($filter !== '') {
468
            $query
469
                ->andWhere(['like', 'url', $filter])
470
                ->orWhere(['like', 'title', $filter])
471
                ->orWhere(['like', 'pageErrors', $filter])
0 ignored issues
show
Coding Style introduced by
Space after closing parenthesis of function call prohibited
Loading history...
472
            ;
473
        }
474
        $query
475
            ->orderBy([$sortField => $sortType])
476
            ->groupBy('url')
477
            ->limit($per_page)
0 ignored issues
show
Coding Style introduced by
Space after closing parenthesis of function call prohibited
Loading history...
478
        ;
479
480
        $stats = $query->all();
481
        if ($stats) {
482
            $user = Craft::$app->getUser()->getIdentity();
483
            // Massage the stats
484
            foreach ($stats as &$stat) {
485
                $stat['cnt'] = (int)$stat['cnt'];
486
                $stat['craftCount'] = (int)$stat['craftCount'];
487
                $stat['boomerangCount'] = (int)$stat['boomerangCount'];
488
                // Decode any emojis in the title
489
                if (!empty($stat['title'])) {
490
                    $stat['title'] = html_entity_decode($stat['title'], ENT_NOQUOTES, 'UTF-8');
491
                }
492
                // Set up the appropriate helper links
493
                $stat['deleteLink'] = UrlHelper::actionUrl('webperf/error-samples/delete-samples-by-url', [
0 ignored issues
show
Coding Style introduced by
The opening parenthesis of a multi-line function call should be the last content on the line.
Loading history...
494
                    'pageUrl' => $stat['url'],
495
                    'siteId' => $siteId
496
                ]);
0 ignored issues
show
Coding Style introduced by
For multi-line function calls, the closing parenthesis should be on a new line.

If a function call spawns multiple lines, the coding standard suggests to move the closing parenthesis to a new line:

someFunctionCall(
    $firstArgument,
    $secondArgument,
    $thirdArgument
); // Closing parenthesis on a new line.
Loading history...
497
                $stat['detailPageUrl'] = UrlHelper::cpUrl('webperf/errors/page-detail', [
0 ignored issues
show
Coding Style introduced by
The opening parenthesis of a multi-line function call should be the last content on the line.
Loading history...
498
                    'pageUrl' => $stat['url'],
499
                    'siteId' => $siteId,
500
                ]);
0 ignored issues
show
Coding Style introduced by
For multi-line function calls, the closing parenthesis should be on a new line.

If a function call spawns multiple lines, the coding standard suggests to move the closing parenthesis to a new line:

someFunctionCall(
    $firstArgument,
    $secondArgument,
    $thirdArgument
); // Closing parenthesis on a new line.
Loading history...
501
                // Override based on permissions
502
                if (!$user->can('webperf:delete-error-samples')) {
503
                    $stat['deleteLink'] = '';
504
                }
505
                if (!$user->can('webperf:errors-detail')) {
506
                    $stat['detailPageUrl'] = '';
507
                }
508
            }
509
            // Format the data for the API
510
            $data['data'] = $stats;
511
            $query = (new Query())
512
                ->select(['[[url]]'])
513
                ->from(['{{%webperf_error_samples}}'])
514
                ->groupBy('[[url]]')
515
                ->where(['between', 'dateCreated', $start, $end])
0 ignored issues
show
Coding Style introduced by
Space after closing parenthesis of function call prohibited
Loading history...
516
            ;
517
            if ($filter !== '') {
518
                $query
519
                    ->andWhere(['like', 'url', $filter])
520
                    ->orWhere(['like', 'title', $filter])
521
                    ->orWhere(['like', 'pageErrors', $filter])
0 ignored issues
show
Coding Style introduced by
Space after closing parenthesis of function call prohibited
Loading history...
522
                ;
523
            }
524
            $count = $query->count();
525
            $data['links']['pagination'] = [
526
                'total' => $count,
527
                'per_page' => $per_page,
528
                'current_page' => $page,
529
                'last_page' => ceil($count / $per_page),
530
                'next_page_url' => null,
531
                'prev_page_url' => null,
532
                'from' => $offset + 1,
533
                'to' => $offset + ($count > $per_page ? $per_page : $count),
534
            ];
535
        }
536
537
        return $this->asJson($data);
538
    }
539
540
541
    /**
542
     * Handle requests for the performance detail table
543
     *
544
     * @param string $sort
0 ignored issues
show
Coding Style introduced by
Missing parameter comment
Loading history...
Coding Style introduced by
Doc comment for parameter $sort does not match actual variable name $start
Loading history...
545
     * @param int    $page
0 ignored issues
show
Coding Style introduced by
Missing parameter comment
Loading history...
Coding Style introduced by
Doc comment for parameter $page does not match actual variable name $end
Loading history...
546
     * @param int    $per_page
0 ignored issues
show
Coding Style introduced by
Missing parameter comment
Loading history...
Coding Style introduced by
Doc comment for parameter $per_page does not match actual variable name $sort
Loading history...
547
     * @param string $filter
0 ignored issues
show
Coding Style introduced by
Missing parameter comment
Loading history...
Coding Style introduced by
Doc comment for parameter $filter does not match actual variable name $page
Loading history...
548
     * @param string $pageUrl
0 ignored issues
show
Coding Style introduced by
Missing parameter comment
Loading history...
Coding Style introduced by
Doc comment for parameter $pageUrl does not match actual variable name $per_page
Loading history...
549
     * @param string $start
0 ignored issues
show
Coding Style introduced by
Missing parameter comment
Loading history...
Coding Style introduced by
Doc comment for parameter $start does not match actual variable name $filter
Loading history...
550
     * @param string $end
0 ignored issues
show
Coding Style introduced by
Missing parameter comment
Loading history...
Coding Style introduced by
Doc comment for parameter $end does not match actual variable name $pageUrl
Loading history...
551
     * @param int    $siteId
0 ignored issues
show
Coding Style introduced by
Missing parameter comment
Loading history...
552
     *
553
     * @return Response
554
     * @throws ForbiddenHttpException
555
     * @throws BadRequestHttpException
556
     */
557
    public function actionErrorsDetail(
558
        string $start = '',
559
        string $end = '',
560
        string $sort = 'dateCreated|DESC',
561
        int $page = 1,
562
        int $per_page = 20,
563
        $filter = '',
564
        $pageUrl = '',
565
        $siteId = 0
566
    ): Response {
567
        PermissionHelper::controllerPermissionCheck('webperf:errors');
568
        $data = [];
569
        $sortField = 'dateCreated';
570
        $sortType = 'DESC';
571
        // Add a day since YYYY-MM-DD is really YYYY-MM-DD 00:00:00
572
        $end = date('Y-m-d', strtotime($end.'+1 day'));
573
        $pageUrl = urldecode($pageUrl);
574
        // Figure out the sorting type
575
        if ($sort !== '') {
576
            if (strpos($sort, '|') === false) {
577
                $sortField = $sort;
578
            } else {
579
                list($sortField, $sortType) = explode('|', $sort);
580
            }
581
        }
582
        $sortType = strtoupper($sortType);
583
        $sortType = self::SORT_MAP[$sortType] ?? self::SORT_MAP['DESC'];
584
        // Validate untrusted data
585
        if (!in_array($sortField, self::ALLOWED_ERRORS_DETAIL_SORT_FIELDS, true)) {
586
            throw new BadRequestHttpException(Craft::t('webperf', 'Invalid sort field specified.'));
587
        }
588
        // Query the db table
589
        $offset = ($page - 1) * $per_page;
590
        $query = (new Query())
591
            ->from(['{{%webperf_error_samples}} webperf_error_samples'])
592
            ->select([
0 ignored issues
show
Coding Style introduced by
The opening parenthesis of a multi-line function call should be the last content on the line.
Loading history...
593
                '[[webperf_error_samples.url]]',
594
                '[[webperf_error_samples.id]]',
595
                '[[webperf_error_samples.type]]',
596
                '[[webperf_error_samples.dateCreated]]',
597
                '[[webperf_error_samples.pageErrors]]',
598
                '[[webperf_error_samples.id]]',
599
600
                '[[webperf_data_samples.device]]',
601
                '[[webperf_data_samples.os]]',
602
                '[[webperf_data_samples.browser]]',
603
                '[[webperf_data_samples.countryCode]]',
604
                '[[webperf_data_samples.mobile]]',
605
            ])
0 ignored issues
show
Coding Style introduced by
For multi-line function calls, the closing parenthesis should be on a new line.

If a function call spawns multiple lines, the coding standard suggests to move the closing parenthesis to a new line:

someFunctionCall(
    $firstArgument,
    $secondArgument,
    $thirdArgument
); // Closing parenthesis on a new line.
Loading history...
606
            ->offset($offset)
607
            ->where(['[[webperf_error_samples.url]]' => $pageUrl])
608
            ->andWhere(['between', '[[webperf_error_samples.dateCreated]]', $start, $end])
609
            ->leftJoin('{{%webperf_data_samples}} webperf_data_samples', '[[webperf_data_samples.requestId]] = [[webperf_error_samples.requestId]]')
0 ignored issues
show
Coding Style introduced by
Space after closing parenthesis of function call prohibited
Loading history...
610
        ;
611
        if ((int)$siteId !== 0) {
612
            $query->andWhere(['siteId' => $siteId]);
613
        }
614
        if ($filter !== '') {
615
            $query
616
                ->andWhere(['like', 'pageErrors', $filter])
617
                /*
618
                ->orWhere(['like', 'device', $filter])
619
                ->orWhere(['like', 'os', $filter])
620
                ->orWhere(['like', 'browser', $filter])
621
                ->orWhere(['like', 'countryCode', $filter])
622
                */
623
            ;
624
        }
625
        $query
626
            ->orderBy([$sortField => $sortType])
627
            ->limit($per_page)
0 ignored issues
show
Coding Style introduced by
Space after closing parenthesis of function call prohibited
Loading history...
628
        ;
629
        $stats = $query->all();
630
        if ($stats) {
631
            $user = Craft::$app->getUser()->getIdentity();
632
            // Massage the stats
633
            foreach ($stats as &$stat) {
634
                if (!empty($stats['dateCreated'])) {
635
                    $date = DateTimeHelper::toDateTime($stats['dateCreated']);
636
                    $stats['dateCreated'] = $date->format('Y-m-d H:i:s');
637
                }
638
                if (isset($stat['mobile'])) {
639
                    $stat['mobile'] = (bool)$stat['mobile'];
640
                }
641
                // Decode any emojis in the title
642
                if (!empty($stat['title'])) {
643
                    $stat['title'] = html_entity_decode($stat['title'], ENT_NOQUOTES, 'UTF-8');
644
                }
645
                $stat['deleteLink'] = UrlHelper::actionUrl('webperf/error-samples/delete-sample-by-id', [
0 ignored issues
show
Coding Style introduced by
The opening parenthesis of a multi-line function call should be the last content on the line.
Loading history...
646
                    'id' => $stat['id']
647
                ]);
0 ignored issues
show
Coding Style introduced by
For multi-line function calls, the closing parenthesis should be on a new line.

If a function call spawns multiple lines, the coding standard suggests to move the closing parenthesis to a new line:

someFunctionCall(
    $firstArgument,
    $secondArgument,
    $thirdArgument
); // Closing parenthesis on a new line.
Loading history...
648
                // Override based on permissions
649
                if (!$user->can('webperf:delete-error-samples')) {
650
                    $stat['deleteLink'] = '';
651
                }
652
            }
653
            // Format the data for the API
654
            $data['data'] = $stats;
655
            $query = (new Query())
656
                ->select(['[[url]]'])
657
                ->from(['{{%webperf_error_samples}}'])
658
                ->where(['url' => $pageUrl])
659
                ->andWhere(['between', 'dateCreated', $start, $end])
0 ignored issues
show
Coding Style introduced by
Space after closing parenthesis of function call prohibited
Loading history...
660
            ;
661
            if ($filter !== '') {
662
                $query
663
                    ->andWhere(['like', 'pageErrors', $filter])
664
                    /*
665
                    ->orWhere(['like', 'device', $filter])
666
                    ->orWhere(['like', 'os', $filter])
667
                    ->orWhere(['like', 'browser', $filter])
668
                    ->orWhere(['like', 'countryCode', $filter])
669
                    */
670
                ;
671
            }
672
            $count = $query->count();
673
            $data['links']['pagination'] = [
674
                'total' => $count,
675
                'per_page' => $per_page,
676
                'current_page' => $page,
677
                'last_page' => ceil($count / $per_page),
678
                'next_page_url' => null,
679
                'prev_page_url' => null,
680
                'from' => $offset + 1,
681
                'to' => $offset + ($count > $per_page ? $per_page : $count),
682
            ];
683
        }
684
685
        return $this->asJson($data);
686
    }
687
688
    // Protected Methods
689
    // =========================================================================
690
}
691