TablesController   A
last analyzed

Complexity

Total Complexity 32

Size/Duplication

Total Lines 266
Duplicated Lines 0 %

Test Coverage

Coverage 0%

Importance

Changes 6
Bugs 2 Features 0
Metric Value
wmc 32
eloc 150
c 6
b 2
f 0
dl 0
loc 266
ccs 0
cts 173
cp 0
rs 9.84

2 Methods

Rating   Name   Duplication   Size   Complexity  
C actionDashboard() 0 89 15
F actionRedirects() 0 101 17
1
<?php
2
/**
3
 * Retour plugin for Craft CMS
4
 *
5
 * Retour allows you to intelligently redirect legacy URLs, so that you don't
6
 * lose SEO value when rebuilding & restructuring a website
7
 *
8
 * @link      https://nystudio107.com/
0 ignored issues
show
Coding Style introduced by
The tag in position 1 should be the @copyright tag
Loading history...
9
 * @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...
10
 */
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...
11
12
namespace nystudio107\retour\controllers;
13
14
use Craft;
15
use craft\db\Query;
16
use craft\errors\SiteNotFoundException;
17
use craft\helpers\ElementHelper;
18
use craft\web\Controller;
19
use nystudio107\retour\helpers\Permission as PermissionHelper;
20
use nystudio107\retour\helpers\UrlHelper;
21
use yii\web\BadRequestHttpException;
22
use yii\web\ForbiddenHttpException;
23
use yii\web\Response;
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   Retour
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     3.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 HANDLED_MAP = [
36
        'handled' => 1,
37
        'nothandled' => 0,
38
    ];
39
40
    const SORT_MAP = [
41
        'DESC' => SORT_DESC,
42
        'ASC' => SORT_ASC,
43
    ];
44
45
    const ALLOWED_STATS_SORT_FIELDS = [
46
        'redirectSrcUrl',
47
        'referrerUrl',
48
        'remoteIp',
49
        'hitCount',
50
        'hitLastTime',
51
        'handledByRetour',
52
    ];
53
54
    const ALLOWED_REDIRECTS_SORT_FIELDS = [
55
        'redirectSrcUrl',
56
        'redirectDestUrl',
57
        'redirectMatchType',
58
        'siteId',
59
        'redirectHttpCode',
60
        'priority',
61
        'hitCount',
62
        'hitLastTime',
63
    ];
64
65
    // Protected Properties
66
    // =========================================================================
67
68
    /**
0 ignored issues
show
Coding Style introduced by
Missing short description in doc comment
Loading history...
69
     * @inheritdoc
70
     */
71
    protected $allowAnonymous = [
72
    ];
73
74
    // Public Methods
75
    // =========================================================================
76
77
    /**
78
     * Handle requests for the dashboard statistics table
79
     *
80
     * @param string $sort
0 ignored issues
show
Coding Style introduced by
Missing parameter comment
Loading history...
Coding Style introduced by
Expected 6 spaces after parameter type; 1 found
Loading history...
81
     * @param int $page
0 ignored issues
show
Coding Style introduced by
Missing parameter comment
Loading history...
Coding Style introduced by
Expected 9 spaces after parameter type; 1 found
Loading history...
82
     * @param int $per_page
0 ignored issues
show
Coding Style introduced by
Missing parameter comment
Loading history...
Coding Style introduced by
Expected 9 spaces after parameter type; 1 found
Loading history...
83
     * @param string $filter
0 ignored issues
show
Coding Style introduced by
Missing parameter comment
Loading history...
Coding Style introduced by
Expected 6 spaces after parameter type; 1 found
Loading history...
84
     * @param int $siteId
0 ignored issues
show
Coding Style introduced by
Missing parameter comment
Loading history...
Coding Style introduced by
Expected 9 spaces after parameter type; 1 found
Loading history...
85
     * @param string|null $handled
0 ignored issues
show
Coding Style introduced by
Missing parameter comment
Loading history...
86
     *
87
     * @return Response
88
     * @throws ForbiddenHttpException
89
     * @throws BadRequestHttpException
90
     */
91
    public function actionDashboard(
92
        string $sort = 'hitCount|desc',
93
        int    $page = 1,
94
        int    $per_page = 20,
95
               $filter = '',
0 ignored issues
show
Coding Style introduced by
Multi-line function declaration not indented correctly; expected 8 spaces but found 15
Loading history...
96
               $siteId = 0,
0 ignored issues
show
Coding Style introduced by
Multi-line function declaration not indented correctly; expected 8 spaces but found 15
Loading history...
97
               $handled = 'all'
0 ignored issues
show
Coding Style introduced by
Multi-line function declaration not indented correctly; expected 8 spaces but found 15
Loading history...
98
    ): Response {
99
        PermissionHelper::controllerPermissionCheck('retour:dashboard');
100
        $data = [];
101
        $sortField = 'hitCount';
102
        $sortType = 'DESC';
103
        // Figure out the sorting type
104
        if ($sort !== '') {
105
            if (strpos($sort, '|') === false) {
106
                $sortField = $sort;
107
            } else {
108
                list($sortField, $sortType) = explode('|', $sort);
109
            }
110
        }
111
        $sortType = strtoupper($sortType);
112
        $sortType = self::SORT_MAP[$sortType] ?? self::SORT_MAP['DESC'];
113
        // Validate untrusted data
114
        if (!in_array($sortField, self::ALLOWED_STATS_SORT_FIELDS, true)) {
115
            throw new BadRequestHttpException(Craft::t('retour', 'Invalid sort field specified.'));
116
        }
117
        // Query the db table
118
        $offset = ($page - 1) * $per_page;
119
        $query = (new Query())
120
            ->from(['{{%retour_stats}}'])
121
            ->offset($offset)
122
            ->limit($per_page)
123
            ->orderBy([$sortField => $sortType])
124
            ->filterWhere(['like', 'redirectSrcUrl', $filter])
125
            ->orFilterWhere(['like', 'referrerUrl', $filter]);
126
        if ((int)$siteId !== 0) {
127
            $query->andWhere(['siteId' => $siteId]);
128
        }
129
        if ($handled !== 'all') {
130
            $query->andWhere(['handledByRetour' => self::HANDLED_MAP[$handled]]);
131
        }
132
        $stats = $query->all();
133
        if ($stats) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $stats of type array 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...
134
            // Add in the `addLink` field
135
            foreach ($stats as &$stat) {
136
                // Normalize the `redirectSrcUrl` to point to a valid frontend site URL
137
                $stat['redirectSrcUrlFull'] = $stat['redirectSrcUrl'];
138
                if (!UrlHelper::isAbsoluteUrl($stat['redirectSrcUrlFull'])) {
139
                    $sites = Craft::$app->getSites();
140
                    $site = $sites->getSiteById($stat['siteId'], true);
141
                    if ($site) {
142
                        $stat['redirectSrcUrlFull'] = UrlHelper::mergeUrlWithPath($site->baseUrl, $stat['redirectSrcUrlFull']);
143
                    }
144
                }
145
                $stat['addLink'] = '';
146
                if (!$stat['handledByRetour']) {
147
                    $encodedUrl = urlencode('/' . ltrim($stat['redirectSrcUrl'], '/'));
148
                    // Add the siteId to the URL, but keep the current behavior of passing in siteId=0 for "all"
149
                    $statSiteId = $stat['siteId'] ?? 0;
150
                    try {
151
                        $primarySite = Craft::$app->getSites()->getPrimarySite();
152
                    } catch (SiteNotFoundException $e) {
153
                        $primarySite = null;
154
                    }
155
                    if ($primarySite !== null && $statSiteId == (int)$primarySite->id) {
156
                        $statSiteId = 0;
157
                    }
158
                    $stat['addLink'] = UrlHelper::cpUrl('retour/add-redirect', [
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...
159
                        'defaultUrl' => $encodedUrl,
160
                        'siteId' => $statSiteId,
161
                    ]);
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...
162
                }
163
            }
164
            // Format the data for the API
165
            $data['data'] = $stats;
166
            $count = $query->count();
167
            $data['links']['pagination'] = [
168
                'total' => $count,
169
                'per_page' => $per_page,
170
                'current_page' => $page,
171
                'last_page' => ceil($count / $per_page),
172
                'next_page_url' => null,
173
                'prev_page_url' => null,
174
                'from' => $offset + 1,
175
                'to' => $offset + ($count > $per_page ? $per_page : $count),
176
            ];
177
        }
178
179
        return $this->asJson($data);
180
    }
181
182
    /**
0 ignored issues
show
Coding Style introduced by
Parameter $shortLinks should have a doc-comment as per coding-style.
Loading history...
183
     * Handle requests for the dashboard redirects table
184
     *
185
     * @param string $sort
0 ignored issues
show
Coding Style introduced by
Missing parameter comment
Loading history...
186
     * @param int $page
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...
187
     * @param int $per_page
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...
188
     * @param string $filter
0 ignored issues
show
Coding Style introduced by
Missing parameter comment
Loading history...
189
     * @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...
190
     *
191
     * @return Response
192
     * @throws ForbiddenHttpException
193
     * @throws BadRequestHttpException
194
     */
195
    public function actionRedirects(
196
        string $sort = 'hitCount|desc',
197
        int    $page = 1,
198
        int    $per_page = 20,
199
               $filter = '',
0 ignored issues
show
Coding Style introduced by
Multi-line function declaration not indented correctly; expected 8 spaces but found 15
Loading history...
200
               $siteId = 0,
0 ignored issues
show
Coding Style introduced by
Multi-line function declaration not indented correctly; expected 8 spaces but found 15
Loading history...
201
               $shortLinks = false
0 ignored issues
show
Coding Style introduced by
Multi-line function declaration not indented correctly; expected 8 spaces but found 15
Loading history...
202
    ): Response {
203
        PermissionHelper::controllerPermissionCheck('retour:redirects');
204
        $data = [];
205
        $sortField = 'hitCount';
206
        $sortType = 'DESC';
207
        // Figure out the sorting type
208
        if ($sort !== '') {
209
            if (strpos($sort, '|') === false) {
210
                $sortField = $sort;
211
            } else {
212
                list($sortField, $sortType) = explode('|', $sort);
213
            }
214
        }
215
        $sortType = strtoupper($sortType);
216
        $sortType = self::SORT_MAP[$sortType] ?? self::SORT_MAP['DESC'];
217
        // Validate untrusted data
218
        if (!in_array($sortField, self::ALLOWED_REDIRECTS_SORT_FIELDS, true)) {
219
            throw new BadRequestHttpException(Craft::t('retour', 'Invalid sort field specified.'));
220
        }
221
        // Query the db table
222
        $offset = ($page - 1) * $per_page;
223
        $query = (new Query())
224
            ->from(['{{%retour_static_redirects}}'])
225
            ->offset($offset)
226
            ->limit($per_page)
227
            ->orderBy([$sortField => $sortType])
228
            ->filterWhere(['like', 'redirectSrcUrl', $filter])
229
            ->orFilterWhere(['like', 'redirectDestUrl', $filter]);
230
        if ((int)$siteId !== 0) {
231
            $query->andWhere(['siteId' => $siteId]);
232
        }
233
        if ($shortLinks) {
234
            $query->andWhere(['not', ['associatedElementId' => 0]]);
235
        } else {
236
            $query->andWhere(['associatedElementId' => 0]);
237
        }
238
        $redirects = $query->all();
239
        // Add in the `deleteLink` field and clean up the redirects
240
        foreach ($redirects as &$redirect) {
241
            // Handle short links by adding the element's title and CP URL
242
            if ($shortLinks) {
243
                $redirect['elementTitle'] = '';
244
                $redirect['elementCpUrl'] = '';
245
                $elementId = $redirect['associatedElementId'] ?? null;
246
                $elementSiteId = $redirect['siteId'] ?? null;
247
                if (!empty($elementId)) {
248
                    $element = Craft::$app->getElements()->getElementById($elementId, null, $elementSiteId);
249
                    if ($element) {
250
                        $element = ElementHelper::rootElement($element);
251
                        $redirect['elementTitle'] = $element->title;
0 ignored issues
show
Bug introduced by
Accessing title on the interface craft\base\ElementInterface suggest that you code against a concrete implementation. How about adding an instanceof check?
Loading history...
252
                        $redirect['elementCpUrl'] = $element->getCpEditUrl();
253
                    }
254
                }
255
            }
256
            // Make sure the destination URL is not a regex
257
            if ($redirect['redirectMatchType'] !== 'exactmatch') {
258
                if (preg_match("/\$\d+/", $redirect['redirectDestUrl'])) {
259
                    $redirect['redirectDestUrl'] = '';
260
                }
261
            }
262
            // Handle extracting the site name and base URL
263
            $redirect['siteName'] = Craft::t('retour', 'All Sites');
264
            $sites = Craft::$app->getSites();
265
            $site = null;
266
            if ($redirect['siteId']) {
267
                $site = $sites->getSiteById($redirect['siteId']);
268
                if ($site) {
269
                    $redirect['siteName'] = $site->name;
270
                }
271
            }
272
            if (!$site) {
273
                $site = $sites->getPrimarySite();
274
            }
275
            $redirect['siteBaseUrl'] = $site->getBaseUrl();
276
277
            $redirect['editLink'] = UrlHelper::cpUrl('retour/edit-redirect/' . $redirect['id']);
278
        }
279
        // Format the data for the API
280
        if ($redirects) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $redirects of type array 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...
281
            $data['data'] = $redirects;
282
            $count = $query->count();
283
            $data['links']['pagination'] = [
284
                'total' => $count,
285
                'per_page' => $per_page,
286
                'current_page' => $page,
287
                'last_page' => ceil($count / $per_page),
288
                'next_page_url' => null,
289
                'prev_page_url' => null,
290
                'from' => $offset + 1,
291
                'to' => $offset + ($count > $per_page ? $per_page : $count),
292
            ];
293
        }
294
295
        return $this->asJson($data);
296
    }
297
298
    // Protected Methods
299
    // =========================================================================
300
}
301