Code Coverage |
||||||||||
Lines |
Functions and Methods |
Classes and Traits |
||||||||
Total | |
33.59% |
44 / 131 |
|
18.75% |
3 / 16 |
CRAP | |
0.00% |
0 / 1 |
SpecialRedirect | |
33.85% |
44 / 130 |
|
18.75% |
3 / 16 |
744.12 | |
0.00% |
0 / 1 |
__construct | |
0.00% |
0 / 5 |
|
0.00% |
0 / 1 |
2 | |||
setParameter | |
0.00% |
0 / 3 |
|
0.00% |
0 / 1 |
6 | |||
dispatchUser | |
90.91% |
10 / 11 |
|
0.00% |
0 / 1 |
5.02 | |||
dispatchFile | |
47.06% |
8 / 17 |
|
0.00% |
0 / 1 |
24.84 | |||
dispatchRevision | |
100.00% |
9 / 9 |
|
100.00% |
1 / 1 |
3 | |||
dispatchPage | |
100.00% |
9 / 9 |
|
100.00% |
1 / 1 |
3 | |||
dispatchLog | |
100.00% |
8 / 8 |
|
100.00% |
1 / 1 |
3 | |||
dispatch | |
0.00% |
0 / 33 |
|
0.00% |
0 / 1 |
156 | |||
getFormFields | |
0.00% |
0 / 20 |
|
0.00% |
0 / 1 |
2 | |||
onSubmit | |
0.00% |
0 / 3 |
|
0.00% |
0 / 1 |
12 | |||
onSuccess | |
0.00% |
0 / 1 |
|
0.00% |
0 / 1 |
2 | |||
alterForm | |
0.00% |
0 / 1 |
|
0.00% |
0 / 1 |
2 | |||
getDisplayFormat | |
0.00% |
0 / 1 |
|
0.00% |
0 / 1 |
2 | |||
getSubpagesForPrefixSearch | |
0.00% |
0 / 7 |
|
0.00% |
0 / 1 |
2 | |||
requiresPost | |
0.00% |
0 / 1 |
|
0.00% |
0 / 1 |
2 | |||
getGroupName | |
0.00% |
0 / 1 |
|
0.00% |
0 / 1 |
2 |
1 | <?php |
2 | /** |
3 | * Implements Special:Redirect |
4 | * |
5 | * This program is free software; you can redistribute it and/or modify |
6 | * it under the terms of the GNU General Public License as published by |
7 | * the Free Software Foundation; either version 2 of the License, or |
8 | * (at your option) any later version. |
9 | * |
10 | * This program is distributed in the hope that it will be useful, |
11 | * but WITHOUT ANY WARRANTY; without even the implied warranty of |
12 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
13 | * GNU General Public License for more details. |
14 | * |
15 | * You should have received a copy of the GNU General Public License along |
16 | * with this program; if not, write to the Free Software Foundation, Inc., |
17 | * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. |
18 | * http://www.gnu.org/copyleft/gpl.html |
19 | * |
20 | * @file |
21 | * @ingroup SpecialPage |
22 | */ |
23 | |
24 | namespace MediaWiki\Specials; |
25 | |
26 | use MediaWiki\HTMLForm\HTMLForm; |
27 | use MediaWiki\SpecialPage\FormSpecialPage; |
28 | use MediaWiki\Status\Status; |
29 | use MediaWiki\Title\MalformedTitleException; |
30 | use MediaWiki\Title\Title; |
31 | use MediaWiki\User\UserFactory; |
32 | use PermissionsError; |
33 | use RepoGroup; |
34 | |
35 | /** |
36 | * A special page that redirects to: the user for a numeric user id, |
37 | * the file for a given filename, or the page for a given revision id. |
38 | * |
39 | * @ingroup SpecialPage |
40 | * @since 1.22 |
41 | */ |
42 | class SpecialRedirect extends FormSpecialPage { |
43 | |
44 | /** |
45 | * The type of the redirect (user/file/revision) |
46 | * |
47 | * Example value: `'user'` |
48 | * |
49 | * @var string|null |
50 | */ |
51 | protected $mType; |
52 | |
53 | /** |
54 | * The identifier/value for the redirect (which id, which file) |
55 | * |
56 | * Example value: `'42'` |
57 | * |
58 | * @var string|null |
59 | */ |
60 | protected $mValue; |
61 | |
62 | private RepoGroup $repoGroup; |
63 | private UserFactory $userFactory; |
64 | |
65 | /** |
66 | * @param RepoGroup $repoGroup |
67 | * @param UserFactory $userFactory |
68 | */ |
69 | public function __construct( |
70 | RepoGroup $repoGroup, |
71 | UserFactory $userFactory |
72 | ) { |
73 | parent::__construct( 'Redirect' ); |
74 | $this->mType = null; |
75 | $this->mValue = null; |
76 | |
77 | $this->repoGroup = $repoGroup; |
78 | $this->userFactory = $userFactory; |
79 | } |
80 | |
81 | /** |
82 | * Set $mType and $mValue based on parsed value of $subpage. |
83 | * @param string|null $subpage |
84 | */ |
85 | public function setParameter( $subpage ) { |
86 | // parse $subpage to pull out the parts |
87 | $parts = $subpage !== null ? explode( '/', $subpage, 2 ) : []; |
88 | $this->mType = $parts[0] ?? null; |
89 | $this->mValue = $parts[1] ?? null; |
90 | } |
91 | |
92 | /** |
93 | * Handle Special:Redirect/user/xxxx (by redirecting to User:YYYY) |
94 | * |
95 | * @return Status A good status contains the url to redirect to |
96 | */ |
97 | public function dispatchUser() { |
98 | if ( !ctype_digit( $this->mValue ) ) { |
99 | return Status::newFatal( 'redirect-not-numeric' ); |
100 | } |
101 | $user = $this->userFactory->newFromId( (int)$this->mValue ); |
102 | $user->load(); // Make sure the id is validated by loading the user |
103 | if ( $user->isAnon() ) { |
104 | return Status::newFatal( 'redirect-not-exists' ); |
105 | } |
106 | if ( $user->isHidden() && !$this->getAuthority()->isAllowed( 'hideuser' ) ) { |
107 | throw new PermissionsError( null, [ 'badaccess-group0' ] ); |
108 | } |
109 | |
110 | return Status::newGood( [ |
111 | $user->getUserPage()->getFullURL( '', false, PROTO_CURRENT ), 302 |
112 | ] ); |
113 | } |
114 | |
115 | /** |
116 | * Handle Special:Redirect/file/xxxx |
117 | * |
118 | * @return Status A good status contains the url to redirect to |
119 | */ |
120 | public function dispatchFile() { |
121 | try { |
122 | $title = Title::newFromTextThrow( $this->mValue, NS_FILE ); |
123 | if ( $title && !$title->inNamespace( NS_FILE ) ) { |
124 | // If the given value contains a namespace enforce file namespace |
125 | $title = Title::newFromTextThrow( Title::makeName( NS_FILE, $this->mValue ) ); |
126 | } |
127 | } catch ( MalformedTitleException $e ) { |
128 | return Status::newFatal( $e->getMessageObject() ); |
129 | } |
130 | // @phan-suppress-next-line PhanTypeMismatchArgumentNullable False positive |
131 | $file = $this->repoGroup->findFile( $title ); |
132 | |
133 | if ( !$file || !$file->exists() ) { |
134 | return Status::newFatal( 'redirect-not-exists' ); |
135 | } |
136 | // Default behavior: Use the direct link to the file. |
137 | $url = $file->getUrl(); |
138 | $request = $this->getRequest(); |
139 | $width = $request->getInt( 'width', -1 ); |
140 | $height = $request->getInt( 'height', -1 ); |
141 | |
142 | // If a width is requested... |
143 | if ( $width != -1 ) { |
144 | $mto = $file->transform( [ 'width' => $width, 'height' => $height ] ); |
145 | // ... and we can |
146 | if ( $mto && !$mto->isError() ) { |
147 | // ... change the URL to point to a thumbnail. |
148 | // Note: This url is more temporary as can change |
149 | // if file is reuploaded and has different aspect ratio. |
150 | $url = [ $mto->getUrl(), $height === -1 ? 301 : 302 ]; |
151 | } |
152 | } |
153 | |
154 | return Status::newGood( $url ); |
155 | } |
156 | |
157 | /** |
158 | * Handle Special:Redirect/revision/xxx |
159 | * (by redirecting to index.php?oldid=xxx) |
160 | * |
161 | * @return Status A good status contains the url to redirect to |
162 | */ |
163 | public function dispatchRevision() { |
164 | $oldid = $this->mValue; |
165 | if ( !ctype_digit( $oldid ) ) { |
166 | return Status::newFatal( 'redirect-not-numeric' ); |
167 | } |
168 | $oldid = (int)$oldid; |
169 | if ( $oldid === 0 ) { |
170 | return Status::newFatal( 'redirect-not-exists' ); |
171 | } |
172 | |
173 | return Status::newGood( wfAppendQuery( wfScript( 'index' ), [ |
174 | 'oldid' => $oldid |
175 | ] ) ); |
176 | } |
177 | |
178 | /** |
179 | * Handle Special:Redirect/page/xxx (by redirecting to index.php?curid=xxx) |
180 | * |
181 | * @return Status A good status contains the url to redirect to |
182 | */ |
183 | public function dispatchPage() { |
184 | $curid = $this->mValue; |
185 | if ( !ctype_digit( $curid ) ) { |
186 | return Status::newFatal( 'redirect-not-numeric' ); |
187 | } |
188 | $curid = (int)$curid; |
189 | if ( $curid === 0 ) { |
190 | return Status::newFatal( 'redirect-not-exists' ); |
191 | } |
192 | |
193 | return Status::newGood( wfAppendQuery( wfScript( 'index' ), [ |
194 | 'curid' => $curid |
195 | ] ) ); |
196 | } |
197 | |
198 | /** |
199 | * Handle Special:Redirect/logid/xxx |
200 | * (by redirecting to index.php?title=Special:Log&logid=xxx) |
201 | * |
202 | * @since 1.27 |
203 | * @return Status A good status contains the url to redirect to |
204 | */ |
205 | public function dispatchLog() { |
206 | $logid = $this->mValue; |
207 | if ( !ctype_digit( $logid ) ) { |
208 | return Status::newFatal( 'redirect-not-numeric' ); |
209 | } |
210 | $logid = (int)$logid; |
211 | if ( $logid === 0 ) { |
212 | return Status::newFatal( 'redirect-not-exists' ); |
213 | } |
214 | $query = [ 'title' => 'Special:Log', 'logid' => $logid ]; |
215 | return Status::newGood( wfAppendQuery( wfScript( 'index' ), $query ) ); |
216 | } |
217 | |
218 | /** |
219 | * Use appropriate dispatch* method to obtain a redirection URL, |
220 | * and either: redirect, set a 404 error code and error message, |
221 | * or do nothing (if $mValue wasn't set) allowing the form to be |
222 | * displayed. |
223 | * |
224 | * @return Status|bool True if a redirect was successfully handled. |
225 | */ |
226 | private function dispatch() { |
227 | // the various namespaces supported by Special:Redirect |
228 | switch ( $this->mType ) { |
229 | case 'user': |
230 | $status = $this->dispatchUser(); |
231 | break; |
232 | case 'file': |
233 | $status = $this->dispatchFile(); |
234 | break; |
235 | case 'revision': |
236 | $status = $this->dispatchRevision(); |
237 | break; |
238 | case 'page': |
239 | $status = $this->dispatchPage(); |
240 | break; |
241 | case 'logid': |
242 | $status = $this->dispatchLog(); |
243 | break; |
244 | default: |
245 | $status = null; |
246 | break; |
247 | } |
248 | if ( $status && $status->isGood() ) { |
249 | // These urls can sometimes be linked from prominent places, |
250 | // so varnish cache. |
251 | $value = $status->getValue(); |
252 | if ( is_array( $value ) ) { |
253 | [ $url, $code ] = $value; |
254 | } else { |
255 | $url = $value; |
256 | $code = 301; |
257 | } |
258 | if ( $code === 301 ) { |
259 | $this->getOutput()->setCdnMaxage( 60 * 60 ); |
260 | } else { |
261 | $this->getOutput()->setCdnMaxage( 10 ); |
262 | } |
263 | $this->getOutput()->redirect( $url, $code ); |
264 | |
265 | return true; |
266 | } |
267 | if ( $this->mValue !== null ) { |
268 | $this->getOutput()->setStatusCode( 404 ); |
269 | |
270 | // @phan-suppress-next-line PhanTypeMismatchReturnNullable Null of $status seems unreachable |
271 | return $status; |
272 | } |
273 | |
274 | return false; |
275 | } |
276 | |
277 | protected function getFormFields() { |
278 | return [ |
279 | 'type' => [ |
280 | 'type' => 'select', |
281 | 'label-message' => 'redirect-lookup', |
282 | 'options-messages' => [ |
283 | 'redirect-user' => 'user', |
284 | 'redirect-page' => 'page', |
285 | 'redirect-revision' => 'revision', |
286 | 'redirect-file' => 'file', |
287 | 'redirect-logid' => 'logid', |
288 | ], |
289 | 'default' => $this->mType, |
290 | ], |
291 | 'value' => [ |
292 | 'type' => 'text', |
293 | 'label-message' => 'redirect-value', |
294 | 'default' => $this->mValue, |
295 | 'required' => true, |
296 | ], |
297 | ]; |
298 | } |
299 | |
300 | public function onSubmit( array $data ) { |
301 | if ( !empty( $data['type'] ) && !empty( $data['value'] ) ) { |
302 | $this->setParameter( $data['type'] . '/' . $data['value'] ); |
303 | } |
304 | |
305 | /* if this returns false, will show the form */ |
306 | return $this->dispatch(); |
307 | } |
308 | |
309 | public function onSuccess() { |
310 | /* do nothing, we redirect in $this->dispatch if successful. */ |
311 | } |
312 | |
313 | protected function alterForm( HTMLForm $form ) { |
314 | // tweak label on submit button |
315 | $form->setSubmitTextMsg( 'redirect-submit' ); |
316 | } |
317 | |
318 | protected function getDisplayFormat() { |
319 | return 'ooui'; |
320 | } |
321 | |
322 | /** |
323 | * Return an array of subpages that this special page will accept. |
324 | * |
325 | * @return string[] subpages |
326 | */ |
327 | protected function getSubpagesForPrefixSearch() { |
328 | return [ |
329 | 'file', |
330 | 'page', |
331 | 'revision', |
332 | 'user', |
333 | 'logid', |
334 | ]; |
335 | } |
336 | |
337 | /** |
338 | * @return bool |
339 | */ |
340 | public function requiresPost() { |
341 | return false; |
342 | } |
343 | |
344 | protected function getGroupName() { |
345 | return 'redirects'; |
346 | } |
347 | } |
348 | |
349 | /** |
350 | * Retain the old class name for backwards compatibility. |
351 | * @deprecated since 1.41 |
352 | */ |
353 | class_alias( SpecialRedirect::class, 'SpecialRedirect' ); |