Passed
Push — develop ( 0a561e...e36461 )
by Andrew
04:18
created

DataSamples::pageTitle()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 17
Code Lines 10

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
eloc 10
dl 0
loc 17
rs 9.9332
c 0
b 0
f 0
cc 2
nc 2
nop 2
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) 2018 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\services;
12
13
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...
14
use nystudio107\webperf\Webperf;
15
use nystudio107\webperf\models\DataSample;
16
17
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...
18
use craft\base\Component;
0 ignored issues
show
Bug introduced by
The type craft\base\Component 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
20
/**
0 ignored issues
show
Coding Style introduced by
Missing short description in doc comment
Loading history...
21
 * @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 indented incorrectly; expected 2 spaces but found 4
Loading history...
22
 * @package   Webperf
0 ignored issues
show
Coding Style introduced by
Tag value indented incorrectly; expected 1 spaces but found 3
Loading history...
23
 * @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 indented incorrectly; expected 3 spaces but found 5
Loading history...
24
 */
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...
25
class DataSamples extends Component
26
{
27
    // Public Methods
28
    // =========================================================================
29
30
    /**
31
     * Get the total number of data samples optionally limited by siteId
32
     *
33
     * @param int    $siteId
0 ignored issues
show
Coding Style introduced by
Missing parameter comment
Loading history...
34
     * @param string $column
0 ignored issues
show
Coding Style introduced by
Missing parameter comment
Loading history...
35
     *
36
     * @return int|string
37
     */
38
    public function totalSamples(int $siteId, string $column)
39
    {
40
        // Get the total number of data samples
41
        $query = (new Query())
42
            ->from(['{{%webperf_data_samples}}'])
43
            ->where(['not', [$column => null]])
0 ignored issues
show
Coding Style introduced by
Space after closing parenthesis of function call prohibited
Loading history...
44
            ;
45
        if ((int)$siteId !== 0) {
46
            $query->andWhere(['siteId' => $siteId]);
47
        }
48
        return $query->count();
49
    }
50
51
    /**
52
     * Get the page title from data samples by URL and optionally siteId
53
     *
54
     * @param string   $url
0 ignored issues
show
Coding Style introduced by
Missing parameter comment
Loading history...
Coding Style introduced by
Expected 1 spaces after parameter type; 3 found
Loading history...
55
     * @param int $siteId
0 ignored issues
show
Coding Style introduced by
Missing parameter comment
Loading history...
Coding Style introduced by
Expected 4 spaces after parameter type; 1 found
Loading history...
56
     *
57
     * @return string
58
     */
59
    public function pageTitle(string $url, int $siteId = 0): string
60
    {
61
        // Get the page title from a URL
62
        $query = (new Query())
63
            ->select(['title'])
64
            ->from(['{{%webperf_data_samples}}'])
65
            ->where([
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...
66
                'and', ['url' => $url],
67
                ['not', ['title' => '']],
68
            ])
0 ignored issues
show
Coding Style introduced by
Space after closing parenthesis of function call prohibited
Loading history...
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...
69
        ;
70
        if ((int)$siteId !== 0) {
71
            $query->andWhere(['siteId' => $siteId]);
72
        }
73
        $result = $query->one();
74
75
        return $result['title'] ?? '';
76
    }
77
78
    /**
79
     * Add a data sample to the webperf_data_samples table
80
     *
81
     * @param DataSample $dataSample
0 ignored issues
show
Coding Style introduced by
Missing parameter comment
Loading history...
82
     */
0 ignored issues
show
Coding Style introduced by
Missing @return tag in function comment
Loading history...
83
    public function addDataSample(DataSample $dataSample)
84
    {
85
        // Validate the model before saving it to the db
86
        if ($dataSample->validate() === false) {
87
            Craft::error(
88
                Craft::t(
89
                    'webperf',
90
                    'Error validating data sample: {errors}',
91
                    ['errors' => print_r($dataSample->getErrors(), true)]
92
                ),
93
                __METHOD__
94
            );
95
96
            return;
97
        }
98
        $isNew = true;
99
        if (!empty($dataSample->requestId)) {
100
            // See if a data sample exists with the same requestId already
101
            $testSample = (new Query())
102
                ->from(['{{%webperf_data_samples}}'])
103
                ->where(['requestId' => $dataSample->requestId])
104
                ->one();
105
            // If it exists, update it rather than having duplicates
106
            if (!empty($testSample)) {
107
                $isNew = false;
108
            }
109
        }
110
        // Get the validated model attributes and save them to the db
111
        $dataSampleConfig = $dataSample->getAttributes($dataSample->fields());
112
        $db = Craft::$app->getDb();
113
        if ($isNew) {
114
            Craft::debug('Creating new data sample', __METHOD__);
115
            // Create a new record
116
            try {
117
                $db->createCommand()->insert(
118
                    '{{%webperf_data_samples}}',
119
                    $dataSampleConfig
120
                )->execute();
121
            } catch (\Exception $e) {
122
                Craft::error($e->getMessage(), __METHOD__);
123
            }
124
        } else {
125
            Craft::debug('Updating existing data sample', __METHOD__);
126
            // Update the existing record
127
            try {
128
                $db->createCommand()->update(
129
                    '{{%webperf_data_samples}}',
130
                    $dataSampleConfig,
131
                    [
132
                        'requestId' => $dataSample->requestId,
133
                    ]
134
                )->execute();
135
            } catch (\Exception $e) {
136
                Craft::error($e->getMessage(), __METHOD__);
137
            }
138
        }
139
        // Trim orphaned samples
140
        $this->trimOrphanedSamples($dataSample->requestId);
141
        // After adding the DataSample, trim the webperf_data_samples db table
142
        if (Webperf::$settings->automaticallyTrimDataSamples) {
143
            $this->trimDataSamples();
144
        }
145
    }
146
147
    /**
148
     * Delete a data sample by id
149
     *
150
     * @param int $id
0 ignored issues
show
Coding Style introduced by
Missing parameter comment
Loading history...
151
     *
152
     * @return int The result
153
     */
154
    public function deleteSampleById(int $id): int
155
    {
156
        $db = Craft::$app->getDb();
157
        // Delete a row from the db table
158
        try {
159
            $result = $db->createCommand()->delete(
160
                '{{%webperf_data_samples}}',
161
                [
162
                    'id' => $id,
163
                ]
164
            )->execute();
165
        } catch (\Exception $e) {
166
            Craft::error($e->getMessage(), __METHOD__);
167
            $result = 0;
168
        }
169
170
        return $result;
171
    }
172
173
    /**
174
     * Delete data samples by URL and optionally siteId
175
     *
176
     * @param string   $url
0 ignored issues
show
Coding Style introduced by
Missing parameter comment
Loading history...
177
     * @param int|null $siteId
0 ignored issues
show
Coding Style introduced by
Missing parameter comment
Loading history...
178
     *
179
     * @return int
180
     */
181
    public function deleteDataSamplesByUrl(string $url, int $siteId = null): int
182
    {
183
        $db = Craft::$app->getDb();
184
        // Delete a row from the db table
185
        try {
186
            $conditions = ['url' => $url];
187
            if ($siteId !== null) {
188
                $conditions['siteId'] = $siteId;
189
            }
190
            $result = $db->createCommand()->delete(
191
                '{{%webperf_data_samples}}',
192
                $conditions
193
            )->execute();
194
        } catch (\Exception $e) {
195
            Craft::error($e->getMessage(), __METHOD__);
196
            $result = 0;
197
        }
198
199
        return $result;
200
    }
201
202
    /**
203
     * Delete data all samples optionally siteId
204
     *
205
     * @param int|null $siteId
0 ignored issues
show
Coding Style introduced by
Missing parameter comment
Loading history...
206
     *
207
     * @return int
208
     */
209
    public function deleteAllDataSamples(int $siteId = null): int
210
    {
211
        $db = Craft::$app->getDb();
212
        // Delete a row from the db table
213
        try {
214
            $conditions = [];
215
            if ($siteId !== null) {
216
                $conditions['siteId'] = $siteId;
217
            }
218
            $result = $db->createCommand()->delete(
219
                '{{%webperf_data_samples}}',
220
                $conditions
221
            )->execute();
222
        } catch (\Exception $e) {
223
            Craft::error($e->getMessage(), __METHOD__);
224
            $result = 0;
225
        }
226
227
        return $result;
228
    }
229
230
    /**
231
     * Trim samples that have the placeholder in the URL, aka they never
232
     * received the Boomerang beacon
233
     *
234
     * @param int $requestId
0 ignored issues
show
Coding Style introduced by
Missing parameter comment
Loading history...
235
     */
0 ignored issues
show
Coding Style introduced by
Missing @return tag in function comment
Loading history...
236
    public function trimOrphanedSamples($requestId)
237
    {
238
        $db = Craft::$app->getDb();
239
        Craft::debug('Trimming orphaned samples', __METHOD__);
240
        // Update the existing record
241
        try {
242
            $result = $db->createCommand()->delete(
243
                '{{%webperf_data_samples}}',
244
                [
245
                    'and', ['url' => DataSample::PLACEHOLDER_URL],
246
                    ['not', ['requestId' => $requestId]],
247
                ]
248
            )->execute();
249
            Craft::debug($result, __METHOD__);
250
        } catch (\Exception $e) {
251
            Craft::error($e->getMessage(), __METHOD__);
252
        }
253
    }
254
255
    /**
256
     * Trim the webperf_data_samples db table based on the dataSamplesStoredLimit
257
     * config.php setting
258
     *
259
     * @param int|null $limit
0 ignored issues
show
Coding Style introduced by
Missing parameter comment
Loading history...
260
     *
261
     * @return int
262
     */
263
    public function trimDataSamples(int $limit = null): int
264
    {
265
        $affectedRows = 0;
266
        $db = Craft::$app->getDb();
267
        $quotedTable = $db->quoteTableName('{{%webperf_data_samples}}');
268
        $limit = $limit ?? Webperf::$settings->dataSamplesStoredLimit;
269
270
        if ($limit !== null) {
0 ignored issues
show
introduced by
The condition $limit !== null is always true.
Loading history...
271
            //  https://stackoverflow.com/questions/578867/sql-query-delete-all-records-from-the-table-except-latest-n
272
            try {
273
                if ($db->getIsMysql()) {
274
                    // Handle MySQL
275
                    $affectedRows = $db->createCommand(/** @lang mysql */
0 ignored issues
show
Coding Style introduced by
The open comment tag must be the only content on the line
Loading history...
Coding Style introduced by
Missing short description in doc comment
Loading history...
Coding Style introduced by
The close comment tag must be the only content on the line
Loading history...
276
                        "
277
                        DELETE FROM {$quotedTable}
278
                        WHERE id NOT IN (
279
                          SELECT id
280
                          FROM (
281
                            SELECT id
282
                            FROM {$quotedTable}
283
                            ORDER BY dateUpdated DESC
284
                            LIMIT {$limit}
285
                          ) foo
286
                        )
287
                        "
288
                    )->execute();
289
                }
290
                if ($db->getIsPgsql()) {
291
                    // Handle Postgres
292
                    $affectedRows = $db->createCommand(/** @lang mysql */
0 ignored issues
show
Coding Style introduced by
The open comment tag must be the only content on the line
Loading history...
Coding Style introduced by
Missing short description in doc comment
Loading history...
Coding Style introduced by
The close comment tag must be the only content on the line
Loading history...
293
                        "
294
                        DELETE FROM {$quotedTable}
295
                        WHERE id NOT IN (
296
                          SELECT id
297
                          FROM (
298
                            SELECT id
299
                            FROM {$quotedTable}
300
                            ORDER BY \"dateUpdated\" DESC
301
                            LIMIT {$limit}
302
                          ) foo
303
                        )
304
                        "
305
                    )->execute();
306
                }
307
            } catch (\Exception $e) {
308
                Craft::error($e->getMessage(), __METHOD__);
309
            }
310
            Craft::info(
311
                Craft::t(
312
                    'webperf',
313
                    'Trimmed {rows} from webperf_data_samples table',
314
                    ['rows' => $affectedRows]
315
                ),
316
                __METHOD__
317
            );
318
        }
319
320
        return $affectedRows;
321
    }
322
}
323