Passed
Branch v3 (3b6795)
by Andrew
07:36
created

RedirectsController::actionShortlinks()   A

Complexity

Conditions 3
Paths 4

Size

Total Lines 44
Code Lines 28

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 0
CRAP Score 12

Importance

Changes 0
Metric Value
eloc 28
c 0
b 0
f 0
dl 0
loc 44
ccs 0
cts 37
cp 0
rs 9.472
cc 3
nc 4
nop 1
crap 12
1
<?php
2
/**
3
 * Retour plugin for Craft CMS 3.x
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\helpers\UrlHelper;
16
use craft\web\Controller;
17
use nystudio107\retour\assetbundles\retour\RetourAsset;
18
use nystudio107\retour\assetbundles\retour\RetourRedirectsAsset;
19
use nystudio107\retour\helpers\MultiSite as MultiSiteHelper;
20
use nystudio107\retour\helpers\Permission as PermissionHelper;
21
use nystudio107\retour\models\StaticRedirects as StaticRedirectsModel;
22
use nystudio107\retour\Retour;
23
use yii\base\InvalidConfigException;
24
use yii\web\NotFoundHttpException;
25
use yii\web\Response;
26
27
/**
0 ignored issues
show
Coding Style introduced by
Missing short description in doc comment
Loading history...
28
 * @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...
29
 * @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...
30
 * @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...
31
 */
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...
32
class RedirectsController extends Controller
33
{
34
    // Constants
35
    // =========================================================================
36
37
    const DOCUMENTATION_URL = 'https://github.com/nystudio107/craft-retour/';
38
39
    // Protected Properties
40
    // =========================================================================
41
42
    protected $allowAnonymous = [];
43
44
    // Public Methods
45
    // =========================================================================
46
47
    /**
48
     * Show the redirects table
49
     *
50
     * @param string|null $siteHandle
0 ignored issues
show
Coding Style introduced by
Missing parameter comment
Loading history...
51
     *
52
     * @return Response
53
     * @throws \yii\web\ForbiddenHttpException
54
     * @throws \yii\web\NotFoundHttpException
55
     */
56
    public function actionRedirects(string $siteHandle = null): Response
57
    {
58
        $variables = [];
59
        PermissionHelper::controllerPermissionCheck('retour:redirects');
60
        // Get the site to edit
61
        $siteId = MultiSiteHelper::getSiteIdFromHandle($siteHandle);
62
        $pluginName = Retour::$settings->pluginName;
63
        $templateTitle = Craft::t('retour', 'Redirects');
64
        $view = Craft::$app->getView();
65
        // Asset bundle
66
        try {
67
            $view->registerAssetBundle(RetourRedirectsAsset::class);
68
        } catch (InvalidConfigException $e) {
69
            Craft::error($e->getMessage(), __METHOD__);
70
        }
71
        $variables['baseAssetsUrl'] = Craft::$app->assetManager->getPublishedUrl(
72
            '@nystudio107/retour/web/assets/dist',
73
            true
0 ignored issues
show
Unused Code introduced by
The call to yii\web\AssetManager::getPublishedUrl() has too many arguments starting with true. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-call  annotation

73
        /** @scrutinizer ignore-call */ 
74
        $variables['baseAssetsUrl'] = Craft::$app->assetManager->getPublishedUrl(

This check compares calls to functions or methods with their respective definitions. If the call has more arguments than are defined, it raises an issue.

If a function is defined several times with a different number of parameters, the check may pick up the wrong definition and report false positives. One codebase where this has been known to happen is Wordpress. Please note the @ignore annotation hint above.

Loading history...
74
        );
75
        // Enabled sites
76
        MultiSiteHelper::setMultiSiteVariables($siteHandle, $siteId, $variables);
77
        $variables['controllerHandle'] = 'redirects';
78
79
        // Basic variables
80
        $variables['fullPageForm'] = false;
81
        $variables['docsUrl'] = self::DOCUMENTATION_URL;
82
        $variables['pluginName'] = $pluginName;
83
        $variables['title'] = $templateTitle;
84
        $siteHandleUri = Craft::$app->isMultiSite ? '/' . $siteHandle : '';
85
        $variables['crumbs'] = [
86
            [
87
                'label' => $pluginName,
88
                'url' => UrlHelper::cpUrl('retour'),
89
            ],
90
            [
91
                'label' => $templateTitle,
92
                'url' => UrlHelper::cpUrl('retour/redirects' . $siteHandleUri),
93
            ],
94
        ];
95
        $variables['docTitle'] = "{$pluginName} - {$templateTitle}";
96
        $variables['selectedSubnavItem'] = 'redirects';
97
98
        // Render the template
99
        return $this->renderTemplate('retour/redirects/index', $variables);
100
    }
101
102
    /**
103
     * Edit the redirect
104
     *
105
     * @param int $redirectId
0 ignored issues
show
Coding Style introduced by
Missing parameter comment
Loading history...
Coding Style introduced by
Expected 23 spaces after parameter type; 1 found
Loading history...
106
     * @param string $defaultUrl
0 ignored issues
show
Coding Style introduced by
Missing parameter comment
Loading history...
Coding Style introduced by
Expected 20 spaces after parameter type; 1 found
Loading history...
107
     * @param int $siteId
0 ignored issues
show
Coding Style introduced by
Missing parameter comment
Loading history...
Coding Style introduced by
Expected 23 spaces after parameter type; 1 found
Loading history...
108
     * @param null|StaticRedirectsModel $redirect
0 ignored issues
show
Coding Style introduced by
Missing parameter comment
Loading history...
109
     *
110
     * @return Response
111
     * @throws NotFoundHttpException
112
     * @throws \yii\web\ForbiddenHttpException
113
     */
114
    public function actionEditRedirect(
115
        int                  $redirectId = 0,
116
        string               $defaultUrl = '',
117
        int                  $siteId = 0,
118
        StaticRedirectsModel $redirect = null
119
    ): Response
120
    {
0 ignored issues
show
Coding Style introduced by
The closing parenthesis and the opening brace of a multi-line function declaration must be on the same line
Loading history...
121
        $variables = [];
122
        PermissionHelper::controllerPermissionCheck('retour:redirects');
123
124
        // Load in the redirect
125
        if ($redirectId === 0) {
126
            $redirect = new StaticRedirectsModel([
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...
127
                'id' => 0,
128
                'siteId' => $siteId,
129
                'redirectSrcUrl' => urldecode($defaultUrl),
130
            ]);
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...
131
        }
132
        if ($redirect === null) {
133
            $redirectConfig = Retour::$plugin->redirects->getRedirectById($redirectId);
134
            if ($redirectConfig === null) {
135
                $redirectConfig = [];
136
                Craft::error(
137
                    Craft::t(
138
                        'retour',
139
                        "Couldn't load redirect id {id}",
140
                        ['id' => $redirectId]
141
                    ),
142
                    __METHOD__
143
                );
144
            }
145
            $redirect = new StaticRedirectsModel($redirectConfig);
146
        }
147
        $redirect->validate();
148
        // Ensure the user has permissions to edit this redirect
149
        $sites = Craft::$app->getSites();
150
        if ($redirect->siteId) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $redirect->siteId of type integer|null is loosely compared to true; this is ambiguous if the integer can be 0. You might want to explicitly use !== null instead.

In PHP, under loose comparison (like ==, or !=, or switch conditions), values of different types might be equal.

For integer values, zero is a special case, in particular the following results might be unexpected:

0   == false // true
0   == null  // true
123 == false // false
123 == null  // false

// It is often better to use strict comparison
0 === false // false
0 === null  // false
Loading history...
151
            $site = $sites->getSiteById($redirect->siteId);
152
            if ($site) {
153
                MultiSiteHelper::requirePermission('editSite:' . $site->uid);
154
            }
155
        }
156
        if ($siteId) {
157
            $site = $sites->getSiteById($siteId);
158
            if ($site) {
159
                MultiSiteHelper::requirePermission('editSite:' . $site->uid);
160
            }
161
        }
162
        $pluginName = Retour::$settings->pluginName;
163
        $templateTitle = Craft::t('retour', 'Edit Redirect');
164
        $view = Craft::$app->getView();
165
        // Asset bundle
166
        try {
167
            $view->registerAssetBundle(RetourAsset::class);
168
        } catch (InvalidConfigException $e) {
169
            Craft::error($e->getMessage(), __METHOD__);
170
        }
171
        $variables['baseAssetsUrl'] = Craft::$app->assetManager->getPublishedUrl(
172
            '@nystudio107/retour/web/assets/dist',
173
            true
0 ignored issues
show
Unused Code introduced by
The call to yii\web\AssetManager::getPublishedUrl() has too many arguments starting with true. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-call  annotation

173
        /** @scrutinizer ignore-call */ 
174
        $variables['baseAssetsUrl'] = Craft::$app->assetManager->getPublishedUrl(

This check compares calls to functions or methods with their respective definitions. If the call has more arguments than are defined, it raises an issue.

If a function is defined several times with a different number of parameters, the check may pick up the wrong definition and report false positives. One codebase where this has been known to happen is Wordpress. Please note the @ignore annotation hint above.

Loading history...
174
        );
175
        // Sites menu
176
        MultiSiteHelper::setSitesMenuVariables($variables);
177
        $variables['controllerHandle'] = 'redirects';
178
179
        // Basic variables
180
        $variables['fullPageForm'] = true;
181
        $variables['docsUrl'] = self::DOCUMENTATION_URL;
182
        $variables['pluginName'] = $pluginName;
183
        $variables['title'] = $templateTitle;
184
        $variables['crumbs'] = [
185
            [
186
                'label' => $pluginName,
187
                'url' => UrlHelper::cpUrl('retour'),
188
            ],
189
            [
190
                'label' => 'Redirects',
191
                'url' => UrlHelper::cpUrl('retour/redirects'),
192
            ],
193
            [
194
                'label' => $templateTitle,
195
                'url' => UrlHelper::cpUrl('retour/edit-redirect/' . $redirectId),
196
            ],
197
        ];
198
        $variables['docTitle'] = "{$pluginName} - Redirects - {$templateTitle}";
199
        $variables['selectedSubnavItem'] = 'redirects';
200
        $variables['redirect'] = $redirect;
201
202
        // Render the template
203
        return $this->renderTemplate('retour/redirects/_edit', $variables);
204
    }
205
206
    /**
0 ignored issues
show
Coding Style introduced by
Missing short description in doc comment
Loading history...
207
     * @return Response|void
208
     * @throws \craft\errors\MissingComponentException
209
     * @throws \yii\web\ForbiddenHttpException
210
     */
211
    public function actionDeleteRedirects()
212
    {
213
        PermissionHelper::controllerPermissionCheck('retour:redirects');
214
        $request = Craft::$app->getRequest();
215
        $redirectIds = $request->getRequiredBodyParam('redirectIds');
216
        $stickyError = false;
217
        foreach ($redirectIds as $redirectId) {
218
            if (Retour::$plugin->redirects->deleteRedirectById($redirectId) === 0) {
219
                $stickyError = true;
220
            }
221
        }
222
        Retour::$plugin->clearAllCaches();
223
        // Handle any cumulative errors
224
        if (!$stickyError) {
225
            // Clear the caches and continue on
226
            Craft::$app->getSession()->setNotice(Craft::t('retour', 'Retour redirects deleted.'));
227
228
            return $this->redirect('retour/redirects');
229
        }
230
        Craft::$app->getSession()->setError(Craft::t('retour', "Couldn't delete redirect."));
231
232
        return;
233
    }
234
235
    /**
236
     * Save the redirect
237
     *
238
     * @return null|Response
239
     * @throws \craft\errors\MissingComponentException
240
     * @throws \yii\web\BadRequestHttpException
241
     * @throws \yii\web\ForbiddenHttpException
242
     * @throws NotFoundHttpException
243
     */
244
    public function actionSaveRedirect()
245
    {
246
        PermissionHelper::controllerPermissionCheck('retour:redirects');
247
        $this->requirePostRequest();
248
        /** @var StaticRedirectsModel $redirect */
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...
249
        $redirectConfig = Craft::$app->getRequest()->getRequiredBodyParam('redirectConfig');
250
        if ($redirectConfig === null) {
251
            throw new NotFoundHttpException('Redirect not found');
252
        }
253
        $redirectConfig['id'] = (int)$redirectConfig['id'];
254
        // Handle enforcing trailing slashes
255
        $generalConfig = Craft::$app->getConfig()->getGeneral();
256
        if ($generalConfig->addTrailingSlashesToUrls && $redirectConfig['redirectMatchType'] === 'exactmatch') {
257
            $destUrl = $redirectConfig['redirectDestUrl'] ?? '';
258
            $redirectConfig['redirectDestUrl'] = $this->addSlashToSiteUrls($destUrl);
259
        }
260
        // Handle URL encoded URLs by decoding them before saving them
261
        if ($redirectConfig['redirectMatchType'] === 'exactmatch') {
262
            $redirectConfig['redirectSrcUrl'] = urldecode($redirectConfig['redirectSrcUrl'] ?? '');
263
            $redirectConfig['redirectSrcUrlParsed'] = urldecode($redirectConfig['redirectSrcUrlParsed'] ?? '');
264
        }
265
        $redirect = new StaticRedirectsModel($redirectConfig);
266
        // Make sure the redirect validates
267
        if (!$redirect->validate()) {
268
            Craft::$app->getSession()->setError(Craft::t('app', "Couldn't save redirect settings."));
269
            // Send the redirect back to the template
270
            Craft::$app->getUrlManager()->setRouteParams([
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...
271
                'redirect' => $redirect,
272
            ]);
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...
273
274
            return null;
275
        }
276
        // Save the redirect
277
        $redirectConfig = $redirect->getAttributes();
278
        Retour::$plugin->redirects->saveRedirect($redirectConfig);
279
        // Handle the case where the redirect wasn't saved because it'd create a redirect loop
280
        $testRedirectConfig = Retour::$plugin->redirects->getRedirectByRedirectSrcUrl(
281
            $redirectConfig['redirectSrcUrl'],
282
            $redirectConfig['siteId']
283
        );
284
        if ($testRedirectConfig === null) {
285
            Craft::$app->getSession()->setError(Craft::t('app', "Couldn't save redirect settings because it'd create a redirect loop."));
286
            // Send the redirect back to the template
287
            Craft::$app->getUrlManager()->setRouteParams([
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...
288
                'redirect' => $redirect,
289
            ]);
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...
290
291
            return null;
292
        }
293
        // Clear the caches and continue on
294
        Retour::$plugin->clearAllCaches();
295
        Craft::$app->getSession()->setNotice(Craft::t('retour', 'Redirect settings saved.'));
296
297
        return $this->redirectToPostedUrl();
298
    }
299
300
    /**
301
     * Show the short links table
302
     *
303
     * @param string|null $siteHandle
0 ignored issues
show
Coding Style introduced by
Missing parameter comment
Loading history...
304
     *
305
     * @return Response
306
     * @throws \yii\web\ForbiddenHttpException
307
     * @throws \yii\web\NotFoundHttpException
308
     */
309
    public function actionShortlinks(string $siteHandle = null): Response
310
    {
311
        $variables = [];
312
        PermissionHelper::controllerPermissionCheck('retour:shortlinks');
313
        // Get the site to edit
314
        $siteId = MultiSiteHelper::getSiteIdFromHandle($siteHandle);
315
        $pluginName = Retour::$settings->pluginName;
316
        $templateTitle = Craft::t('retour', 'Short Links');
317
        $view = Craft::$app->getView();
318
        // Asset bundle
319
        try {
320
            $view->registerAssetBundle(RetourRedirectsAsset::class);
321
        } catch (InvalidConfigException $e) {
322
            Craft::error($e->getMessage(), __METHOD__);
323
        }
324
        $variables['baseAssetsUrl'] = Craft::$app->assetManager->getPublishedUrl(
325
            '@nystudio107/retour/web/assets/dist',
326
            true
0 ignored issues
show
Unused Code introduced by
The call to yii\web\AssetManager::getPublishedUrl() has too many arguments starting with true. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-call  annotation

326
        /** @scrutinizer ignore-call */ 
327
        $variables['baseAssetsUrl'] = Craft::$app->assetManager->getPublishedUrl(

This check compares calls to functions or methods with their respective definitions. If the call has more arguments than are defined, it raises an issue.

If a function is defined several times with a different number of parameters, the check may pick up the wrong definition and report false positives. One codebase where this has been known to happen is Wordpress. Please note the @ignore annotation hint above.

Loading history...
327
        );
328
        // Enabled sites
329
        MultiSiteHelper::setMultiSiteVariables($siteHandle, $siteId, $variables);
330
        $variables['controllerHandle'] = 'shortlinks';
331
332
        // Basic variables
333
        $variables['fullPageForm'] = false;
334
        $variables['docsUrl'] = self::DOCUMENTATION_URL;
335
        $variables['pluginName'] = $pluginName;
336
        $variables['title'] = $templateTitle;
337
        $siteHandleUri = Craft::$app->isMultiSite ? '/' . $siteHandle : '';
338
        $variables['crumbs'] = [
339
            [
340
                'label' => $pluginName,
341
                'url' => UrlHelper::cpUrl('retour'),
342
            ],
343
            [
344
                'label' => $templateTitle,
345
                'url' => UrlHelper::cpUrl('retour/shortlinks' . $siteHandleUri),
346
            ],
347
        ];
348
        $variables['docTitle'] = "{$pluginName} - {$templateTitle}";
349
        $variables['selectedSubnavItem'] = 'shortlinks';
350
351
        // Render the template
352
        return $this->renderTemplate('retour/shortlinks/index', $variables);
353
    }
354
355
    /**
0 ignored issues
show
Coding Style introduced by
Missing short description in doc comment
Loading history...
356
     * @return Response|void
357
     * @throws \craft\errors\MissingComponentException
358
     * @throws \yii\web\ForbiddenHttpException
359
     */
360
    public function actionDeleteShortlinks()
361
    {
362
        PermissionHelper::controllerPermissionCheck('retour:shortlinks');
363
        $request = Craft::$app->getRequest();
364
        $redirectIds = $request->getRequiredBodyParam('redirectIds');
365
        $stickyError = false;
366
        foreach ($redirectIds as $redirectId) {
367
            if (Retour::$plugin->redirects->deleteShortlinkById($redirectId) === 0) {
0 ignored issues
show
Bug introduced by
Are you sure the usage of nystudio107\retour\Retou...rtlinkById($redirectId) targeting nystudio107\retour\servi...::deleteShortlinkById() seems to always return null.

This check looks for function or method calls that always return null and whose return value is used.

class A
{
    function getObject()
    {
        return null;
    }

}

$a = new A();
if ($a->getObject()) {

The method getObject() can return nothing but null, so it makes no sense to use the return value.

The reason is most likely that a function or method is imcomplete or has been reduced for debug purposes.

Loading history...
368
                $stickyError = true;
369
            }
370
        }
371
        Retour::$plugin->clearAllCaches();
372
        // Handle any cumulative errors
373
        if (!$stickyError) {
374
            // Clear the caches and continue on
375
            Craft::$app->getSession()->setNotice(Craft::t('retour', 'Retour Short Links deleted.'));
376
377
            return $this->redirect('retour/shortlinks');
378
        }
379
        Craft::$app->getSession()->setError(Craft::t('retour', "Couldn't delete Short Link."));
380
381
        return;
382
    }
383
384
    // Protected Methods
385
    // =========================================================================
386
387
    /**
388
     * If the $url appears to be a site URL, add a slash to the end of it
389
     *
390
     * @param string $url
0 ignored issues
show
Coding Style introduced by
Missing parameter comment
Loading history...
391
     *
392
     * @return string
393
     */
394
    protected function addSlashToSiteUrls(string $url): string
395
    {
396
        // Make sure the URL doesn't end with a file extension, e.g.: .jpg or have a query string
397
        if (!preg_match('/\.[^\/]+$/', $url) && strpos($url, '?') === false) {
398
            // If it's a root relative URL, assume it's a site URL
399
            if (UrlHelper::isRootRelativeUrl($url)) {
400
                return rtrim($url, '/') . '/';
401
            }
402
            // If the URL matches any of the site's base URLs, assume it's a site URL
403
            $sites = Craft::$app->getSites()->getAllSites();
404
            foreach ($sites as $site) {
405
                if (strpos($url, $site->getBaseUrl()) === 0) {
406
                    return rtrim($url, '/') . '/';
407
                }
408
            }
409
        }
410
411
        return $url;
412
    }
413
}
414