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/ |
||||||
9 | * @copyright Copyright (c) 2018 nystudio107 |
||||||
10 | */ |
||||||
11 | |||||||
12 | namespace nystudio107\retour\controllers; |
||||||
13 | |||||||
14 | use nystudio107\retour\Retour; |
||||||
15 | use nystudio107\retour\assetbundles\retour\RetourAsset; |
||||||
16 | use nystudio107\retour\assetbundles\retour\RetourRedirectsAsset; |
||||||
17 | use nystudio107\retour\helpers\MultiSite as MultiSiteHelper; |
||||||
18 | use nystudio107\retour\helpers\Permission as PermissionHelper; |
||||||
19 | use nystudio107\retour\models\StaticRedirects as StaticRedirectsModel; |
||||||
20 | |||||||
21 | use Craft; |
||||||
22 | use craft\web\Controller; |
||||||
23 | use craft\helpers\UrlHelper; |
||||||
24 | |||||||
25 | use yii\base\InvalidConfigException; |
||||||
26 | use yii\web\NotFoundHttpException; |
||||||
27 | use yii\web\Response; |
||||||
28 | |||||||
29 | /** |
||||||
30 | * @author nystudio107 |
||||||
31 | * @package Retour |
||||||
32 | * @since 3.0.0 |
||||||
33 | */ |
||||||
34 | class RedirectsController extends Controller |
||||||
35 | { |
||||||
36 | // Constants |
||||||
37 | // ========================================================================= |
||||||
38 | |||||||
39 | const DOCUMENTATION_URL = 'https://github.com/nystudio107/craft-retour/'; |
||||||
40 | |||||||
41 | // Protected Properties |
||||||
42 | // ========================================================================= |
||||||
43 | |||||||
44 | protected $allowAnonymous = []; |
||||||
45 | |||||||
46 | // Public Methods |
||||||
47 | // ========================================================================= |
||||||
48 | |||||||
49 | /** |
||||||
50 | * Show the redirects table |
||||||
51 | * |
||||||
52 | * @param string|null $siteHandle |
||||||
53 | * |
||||||
54 | * @return Response |
||||||
55 | * @throws \yii\web\ForbiddenHttpException |
||||||
56 | * @throws \yii\web\NotFoundHttpException |
||||||
57 | */ |
||||||
58 | public function actionRedirects(string $siteHandle = null): Response |
||||||
59 | { |
||||||
60 | $variables = []; |
||||||
61 | PermissionHelper::controllerPermissionCheck('retour:redirects'); |
||||||
62 | // Get the site to edit |
||||||
63 | $siteId = MultiSiteHelper::getSiteIdFromHandle($siteHandle); |
||||||
64 | $pluginName = Retour::$settings->pluginName; |
||||||
65 | $templateTitle = Craft::t('retour', 'Redirects'); |
||||||
66 | $view = Craft::$app->getView(); |
||||||
67 | // Asset bundle |
||||||
68 | try { |
||||||
69 | $view->registerAssetBundle(RetourRedirectsAsset::class); |
||||||
70 | } catch (InvalidConfigException $e) { |
||||||
71 | Craft::error($e->getMessage(), __METHOD__); |
||||||
72 | } |
||||||
73 | $variables['baseAssetsUrl'] = Craft::$app->assetManager->getPublishedUrl( |
||||||
74 | '@nystudio107/retour/assetbundles/retour/dist', |
||||||
75 | true |
||||||
0 ignored issues
–
show
|
|||||||
76 | ); |
||||||
77 | // Enabled sites |
||||||
78 | MultiSiteHelper::setMultiSiteVariables($siteHandle, $siteId, $variables); |
||||||
79 | $variables['controllerHandle'] = 'redirects'; |
||||||
80 | |||||||
81 | // Basic variables |
||||||
82 | $variables['fullPageForm'] = false; |
||||||
83 | $variables['docsUrl'] = self::DOCUMENTATION_URL; |
||||||
84 | $variables['pluginName'] = $pluginName; |
||||||
85 | $variables['title'] = $templateTitle; |
||||||
86 | $siteHandleUri = Craft::$app->isMultiSite ? '/'.$siteHandle : ''; |
||||||
87 | $variables['crumbs'] = [ |
||||||
88 | [ |
||||||
89 | 'label' => $pluginName, |
||||||
90 | 'url' => UrlHelper::cpUrl('retour'), |
||||||
91 | ], |
||||||
92 | [ |
||||||
93 | 'label' => $templateTitle, |
||||||
94 | 'url' => UrlHelper::cpUrl('retour/redirects'.$siteHandleUri), |
||||||
95 | ], |
||||||
96 | ]; |
||||||
97 | $variables['docTitle'] = "{$pluginName} - {$templateTitle}"; |
||||||
98 | $variables['selectedSubnavItem'] = 'redirects'; |
||||||
99 | |||||||
100 | // Render the template |
||||||
101 | return $this->renderTemplate('retour/redirects/index', $variables); |
||||||
102 | } |
||||||
103 | |||||||
104 | /** |
||||||
105 | * Edit the redirect |
||||||
106 | * |
||||||
107 | * @param int $redirectId |
||||||
108 | * @param string $defaultUrl |
||||||
109 | * @param int $siteId |
||||||
110 | * @param null|StaticRedirectsModel $redirect |
||||||
111 | * |
||||||
112 | * @return Response |
||||||
113 | * @throws NotFoundHttpException |
||||||
114 | * @throws \yii\web\ForbiddenHttpException |
||||||
115 | */ |
||||||
116 | public function actionEditRedirect( |
||||||
117 | int $redirectId = 0, |
||||||
118 | string $defaultUrl = '', |
||||||
119 | int $siteId = 0, |
||||||
120 | StaticRedirectsModel $redirect = null |
||||||
121 | ): Response { |
||||||
122 | $variables = []; |
||||||
123 | PermissionHelper::controllerPermissionCheck('retour:redirects'); |
||||||
124 | |||||||
125 | // Load in the redirect |
||||||
126 | if ($redirectId === 0) { |
||||||
127 | $redirect = new StaticRedirectsModel([ |
||||||
128 | 'id' => 0, |
||||||
129 | 'siteId' => $siteId, |
||||||
130 | 'redirectSrcUrl' => urldecode($defaultUrl), |
||||||
131 | ]); |
||||||
132 | } |
||||||
133 | if ($redirect === null) { |
||||||
134 | $redirectConfig = Retour::$plugin->redirects->getRedirectById($redirectId); |
||||||
135 | if ($redirectConfig === null) { |
||||||
136 | $redirectConfig = []; |
||||||
137 | Craft::error( |
||||||
138 | Craft::t( |
||||||
139 | 'retour', |
||||||
140 | "Couldn't load redirect id {id}", |
||||||
141 | ['id' => $redirectId] |
||||||
142 | ), |
||||||
143 | __METHOD__ |
||||||
144 | ); |
||||||
145 | } |
||||||
146 | $redirect = new StaticRedirectsModel($redirectConfig); |
||||||
147 | } |
||||||
148 | $redirect->validate(); |
||||||
149 | // Ensure the user has permissions to edit this redirect |
||||||
150 | $sites = Craft::$app->getSites(); |
||||||
151 | if ($redirect->siteId) { |
||||||
152 | $site = $sites->getSiteById($redirect->siteId); |
||||||
153 | if ($site) { |
||||||
154 | MultiSiteHelper::requirePermission('editSite:'.$site->uid); |
||||||
155 | } |
||||||
156 | } |
||||||
157 | if ($siteId) { |
||||||
158 | $site = $sites->getSiteById($siteId); |
||||||
159 | if ($site) { |
||||||
160 | MultiSiteHelper::requirePermission('editSite:'.$site->uid); |
||||||
161 | } |
||||||
162 | } |
||||||
163 | $pluginName = Retour::$settings->pluginName; |
||||||
164 | $templateTitle = Craft::t('retour', 'Edit Redirect'); |
||||||
165 | $view = Craft::$app->getView(); |
||||||
166 | // Asset bundle |
||||||
167 | try { |
||||||
168 | $view->registerAssetBundle(RetourAsset::class); |
||||||
169 | } catch (InvalidConfigException $e) { |
||||||
170 | Craft::error($e->getMessage(), __METHOD__); |
||||||
171 | } |
||||||
172 | $variables['baseAssetsUrl'] = Craft::$app->assetManager->getPublishedUrl( |
||||||
173 | '@nystudio107/retour/assetbundles/retour/dist', |
||||||
174 | true |
||||||
0 ignored issues
–
show
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
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...
|
|||||||
175 | ); |
||||||
176 | // Sites menu |
||||||
177 | MultiSiteHelper::setSitesMenuVariables($variables); |
||||||
178 | $variables['controllerHandle'] = 'redirects'; |
||||||
179 | |||||||
180 | // Basic variables |
||||||
181 | $variables['fullPageForm'] = true; |
||||||
182 | $variables['docsUrl'] = self::DOCUMENTATION_URL; |
||||||
183 | $variables['pluginName'] = $pluginName; |
||||||
184 | $variables['title'] = $templateTitle; |
||||||
185 | $variables['crumbs'] = [ |
||||||
186 | [ |
||||||
187 | 'label' => $pluginName, |
||||||
188 | 'url' => UrlHelper::cpUrl('retour'), |
||||||
189 | ], |
||||||
190 | [ |
||||||
191 | 'label' => 'Redirects', |
||||||
192 | 'url' => UrlHelper::cpUrl('retour/redirects'), |
||||||
193 | ], |
||||||
194 | [ |
||||||
195 | 'label' => $templateTitle, |
||||||
196 | 'url' => UrlHelper::cpUrl('retour/edit-redirect/'.$redirectId), |
||||||
197 | ], |
||||||
198 | ]; |
||||||
199 | $variables['docTitle'] = "{$pluginName} - Redirects - {$templateTitle}"; |
||||||
200 | $variables['selectedSubnavItem'] = 'redirects'; |
||||||
201 | $variables['redirect'] = $redirect; |
||||||
202 | |||||||
203 | // Render the template |
||||||
204 | return $this->renderTemplate('retour/redirects/_edit', $variables); |
||||||
205 | } |
||||||
206 | |||||||
207 | /** |
||||||
208 | * @return Response|void |
||||||
209 | * @throws \craft\errors\MissingComponentException |
||||||
210 | * @throws \yii\web\ForbiddenHttpException |
||||||
211 | */ |
||||||
212 | public function actionDeleteRedirects() |
||||||
213 | { |
||||||
214 | PermissionHelper::controllerPermissionCheck('retour:redirects'); |
||||||
215 | $request = Craft::$app->getRequest(); |
||||||
216 | $redirectIds = $request->getRequiredBodyParam('redirectIds'); |
||||||
217 | $stickyError = false; |
||||||
218 | foreach ($redirectIds as $redirectId) { |
||||||
219 | if (Retour::$plugin->redirects->deleteRedirectById($redirectId) === 0) { |
||||||
220 | $stickyError = true; |
||||||
221 | } |
||||||
222 | } |
||||||
223 | Retour::$plugin->clearAllCaches(); |
||||||
224 | // Handle any cumulative errors |
||||||
225 | if (!$stickyError) { |
||||||
226 | // Clear the caches and continue on |
||||||
227 | Craft::$app->getSession()->setNotice(Craft::t('retour', 'Retour redirects deleted.')); |
||||||
228 | |||||||
229 | return $this->redirect('retour/redirects'); |
||||||
230 | } |
||||||
231 | Craft::$app->getSession()->setError(Craft::t('retour', "Couldn't delete redirect.")); |
||||||
232 | |||||||
233 | return; |
||||||
234 | } |
||||||
235 | |||||||
236 | /** |
||||||
237 | * Save the redirect |
||||||
238 | * |
||||||
239 | * @return null|Response |
||||||
240 | * @throws \craft\errors\MissingComponentException |
||||||
241 | * @throws \yii\web\BadRequestHttpException |
||||||
242 | * @throws \yii\web\ForbiddenHttpException |
||||||
243 | * @throws NotFoundHttpException |
||||||
244 | */ |
||||||
245 | public function actionSaveRedirect() |
||||||
246 | { |
||||||
247 | PermissionHelper::controllerPermissionCheck('retour:redirects'); |
||||||
248 | $this->requirePostRequest(); |
||||||
249 | /** @var StaticRedirectsModel $redirect */ |
||||||
250 | $redirectConfig = Craft::$app->getRequest()->getRequiredBodyParam('redirectConfig'); |
||||||
251 | if ($redirectConfig === null) { |
||||||
252 | throw new NotFoundHttpException('Redirect not found'); |
||||||
253 | } |
||||||
254 | $redirectConfig['id'] = (int)$redirectConfig['id']; |
||||||
255 | // Handle enforcing trailing slashes |
||||||
256 | $generalConfig = Craft::$app->getConfig()->getGeneral(); |
||||||
257 | if ($generalConfig->addTrailingSlashesToUrls && $redirectConfig['redirectMatchType'] === 'exactmatch') { |
||||||
258 | $destUrl = $redirectConfig['redirectDestUrl'] ?? ''; |
||||||
259 | $redirectConfig['redirectDestUrl'] = $this->addSlashToSiteUrls($destUrl); |
||||||
260 | } |
||||||
261 | // Handle URL encoded URLs by decoding them before saving them |
||||||
262 | if ($redirectConfig['redirectMatchType'] === 'exactmatch') { |
||||||
263 | $redirectConfig['redirectSrcUrl'] = urldecode($redirectConfig['redirectSrcUrl'] ?? ''); |
||||||
264 | $redirectConfig['redirectSrcUrlParsed'] = urldecode($redirectConfig['redirectSrcUrlParsed'] ?? ''); |
||||||
265 | } |
||||||
266 | $redirect = new StaticRedirectsModel($redirectConfig); |
||||||
267 | // Make sure the redirect validates |
||||||
268 | if (!$redirect->validate()) { |
||||||
269 | Craft::$app->getSession()->setError(Craft::t('app', "Couldn't save redirect settings.")); |
||||||
270 | // Send the redirect back to the template |
||||||
271 | Craft::$app->getUrlManager()->setRouteParams([ |
||||||
272 | 'redirect' => $redirect, |
||||||
273 | ]); |
||||||
274 | |||||||
275 | return null; |
||||||
276 | } |
||||||
277 | // Save the redirect |
||||||
278 | $redirectConfig = $redirect->getAttributes(); |
||||||
279 | Retour::$plugin->redirects->saveRedirect($redirectConfig); |
||||||
280 | // Handle the case where the redirect wasn't saved because it'd create a redirect loop |
||||||
281 | $testRedirectConfig = Retour::$plugin->redirects->getRedirectByRedirectSrcUrl( |
||||||
282 | $redirectConfig['redirectSrcUrl'], |
||||||
283 | $redirectConfig['siteId'] |
||||||
284 | ); |
||||||
285 | if ($testRedirectConfig === null) { |
||||||
286 | Craft::$app->getSession()->setError(Craft::t('app', "Couldn't save redirect settings because it'd create a redirect loop.")); |
||||||
287 | // Send the redirect back to the template |
||||||
288 | Craft::$app->getUrlManager()->setRouteParams([ |
||||||
289 | 'redirect' => $redirect, |
||||||
290 | ]); |
||||||
291 | |||||||
292 | return null; |
||||||
293 | } |
||||||
294 | // Clear the caches and continue on |
||||||
295 | Retour::$plugin->clearAllCaches(); |
||||||
296 | Craft::$app->getSession()->setNotice(Craft::t('retour', 'Redirect settings saved.')); |
||||||
297 | |||||||
298 | return $this->redirectToPostedUrl(); |
||||||
299 | } |
||||||
300 | |||||||
301 | // Protected Methods |
||||||
302 | // ========================================================================= |
||||||
303 | |||||||
304 | /** |
||||||
305 | * If the $url appears to be a site URL, add a slash to the end of it |
||||||
306 | * |
||||||
307 | * @param string $url |
||||||
308 | * |
||||||
309 | * @return string |
||||||
310 | */ |
||||||
311 | protected function addSlashToSiteUrls(string $url): string |
||||||
312 | { |
||||||
313 | // Make sure the URL doesn't end with a file extension, e.g.: .jpg or have a query string |
||||||
314 | if (!preg_match('/\.[^\/]+$/', $url) && strpos($url, '?') === false) { |
||||||
315 | // If it's a root relative URL, assume it's a site URL |
||||||
316 | if (UrlHelper::isRootRelativeUrl($url)) { |
||||||
317 | return rtrim($url, '/') . '/'; |
||||||
318 | } |
||||||
319 | // If the URL matches any of the site's base URLs, assume it's a site URL |
||||||
320 | $sites = Craft::$app->getSites()->getAllSites(); |
||||||
321 | foreach ($sites as $site) { |
||||||
322 | if (strpos($url, $site->getBaseUrl()) === 0) { |
||||||
323 | return rtrim($url, '/') . '/'; |
||||||
324 | } |
||||||
325 | } |
||||||
326 | } |
||||||
327 | |||||||
328 | return $url; |
||||||
329 | } |
||||||
330 | } |
||||||
331 |
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.