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
![]() |
|||||
9 | * @copyright Copyright (c) 2018 nystudio107 |
||||
0 ignored issues
–
show
|
|||||
10 | */ |
||||
0 ignored issues
–
show
|
|||||
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
|
|||||
26 | * @author nystudio107 |
||||
0 ignored issues
–
show
Content of the @author tag must be in the form "Display Name <[email protected]>"
![]() |
|||||
27 | * @package Retour |
||||
0 ignored issues
–
show
|
|||||
28 | * @since 3.0.0 |
||||
0 ignored issues
–
show
|
|||||
29 | */ |
||||
0 ignored issues
–
show
|
|||||
30 | class TablesController extends Controller |
||||
31 | { |
||||
32 | // Constants |
||||
33 | // ========================================================================= |
||||
34 | |||||
35 | protected const HANDLED_MAP = [ |
||||
36 | 'handled' => 1, |
||||
37 | 'nothandled' => 0, |
||||
38 | ]; |
||||
39 | |||||
40 | protected const SORT_MAP = [ |
||||
41 | 'DESC' => SORT_DESC, |
||||
42 | 'ASC' => SORT_ASC, |
||||
43 | ]; |
||||
44 | |||||
45 | protected const ALLOWED_STATS_SORT_FIELDS = [ |
||||
46 | 'redirectSrcUrl', |
||||
47 | 'referrerUrl', |
||||
48 | 'remoteIp', |
||||
49 | 'hitCount', |
||||
50 | 'hitLastTime', |
||||
51 | 'handledByRetour', |
||||
52 | ]; |
||||
53 | |||||
54 | protected 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
|
|||||
69 | * @inheritdoc |
||||
70 | */ |
||||
71 | protected array|bool|int $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
|
|||||
81 | * @param int $page |
||||
0 ignored issues
–
show
|
|||||
82 | * @param int $per_page |
||||
0 ignored issues
–
show
|
|||||
83 | * @param string $filter |
||||
0 ignored issues
–
show
|
|||||
84 | * @param int $siteId |
||||
0 ignored issues
–
show
|
|||||
85 | * @param string|null $handled |
||||
0 ignored issues
–
show
|
|||||
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
|
|||||
96 | $siteId = 0, |
||||
0 ignored issues
–
show
|
|||||
97 | $handled = 'all', |
||||
0 ignored issues
–
show
|
|||||
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) { |
||||
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
|
|||||
159 | 'defaultUrl' => $encodedUrl, |
||||
160 | 'siteId' => $statSiteId, |
||||
161 | ]); |
||||
0 ignored issues
–
show
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.
![]() |
|||||
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
|
|||||
183 | * Handle requests for the dashboard redirects table |
||||
184 | * |
||||
185 | * @param string $sort |
||||
0 ignored issues
–
show
|
|||||
186 | * @param int $page |
||||
0 ignored issues
–
show
|
|||||
187 | * @param int $per_page |
||||
0 ignored issues
–
show
|
|||||
188 | * @param string $filter |
||||
0 ignored issues
–
show
|
|||||
189 | * @param int $siteId |
||||
0 ignored issues
–
show
|
|||||
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
|
|||||
200 | $siteId = 0, |
||||
0 ignored issues
–
show
|
|||||
201 | $shortLinks = false, |
||||
0 ignored issues
–
show
|
|||||
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); |
||||
0 ignored issues
–
show
The function
craft\helpers\ElementHelper::rootElement() has been deprecated: in 5.4.0. Use [[ElementInterface::getRootOwner()]] instead.
(
Ignorable by Annotation
)
If this is a false-positive, you can also ignore this issue in your code via the
This function has been deprecated. The supplier of the function has supplied an explanatory message. The explanatory message should give you some clue as to whether and when the function will be removed and what other function to use instead. ![]() |
|||||
251 | $redirect['elementTitle'] = $element->title; |
||||
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 |
||||
263 | $redirect['siteName'] = Craft::t('retour', 'All Sites'); |
||||
264 | if ($redirect['siteId']) { |
||||
265 | $sites = Craft::$app->getSites(); |
||||
266 | $site = $sites->getSiteById($redirect['siteId']); |
||||
267 | if ($site) { |
||||
268 | $redirect['siteName'] = $site->name; |
||||
269 | } |
||||
270 | } |
||||
271 | |||||
272 | $redirect['editLink'] = UrlHelper::cpUrl('retour/edit-redirect/' . $redirect['id']); |
||||
273 | } |
||||
274 | // Format the data for the API |
||||
275 | if ($redirects) { |
||||
276 | $data['data'] = $redirects; |
||||
277 | $count = $query->count(); |
||||
278 | $data['links']['pagination'] = [ |
||||
279 | 'total' => $count, |
||||
280 | 'per_page' => $per_page, |
||||
281 | 'current_page' => $page, |
||||
282 | 'last_page' => ceil($count / $per_page), |
||||
283 | 'next_page_url' => null, |
||||
284 | 'prev_page_url' => null, |
||||
285 | 'from' => $offset + 1, |
||||
286 | 'to' => $offset + ($count > $per_page ? $per_page : $count), |
||||
287 | ]; |
||||
288 | } |
||||
289 | |||||
290 | return $this->asJson($data); |
||||
291 | } |
||||
292 | |||||
293 | // Protected Methods |
||||
294 | // ========================================================================= |
||||
295 | } |
||||
296 |