Merge "Fix typos in code"
[lhc/web/wiklou.git] / includes / specials / SpecialRedirect.php
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 use MediaWiki\MediaWikiServices;
25
26 /**
27 * A special page that redirects to: the user for a numeric user id,
28 * the file for a given filename, or the page for a given revision id.
29 *
30 * @ingroup SpecialPage
31 * @since 1.22
32 */
33 class SpecialRedirect extends FormSpecialPage {
34
35 /**
36 * The type of the redirect (user/file/revision)
37 *
38 * Example value: `'user'`
39 *
40 * @var string $mType
41 */
42 protected $mType;
43
44 /**
45 * The identifier/value for the redirect (which id, which file)
46 *
47 * Example value: `'42'`
48 *
49 * @var string $mValue
50 */
51 protected $mValue;
52
53 function __construct() {
54 parent::__construct( 'Redirect' );
55 $this->mType = null;
56 $this->mValue = null;
57 }
58
59 /**
60 * Set $mType and $mValue based on parsed value of $subpage.
61 * @param string $subpage
62 */
63 function setParameter( $subpage ) {
64 // parse $subpage to pull out the parts
65 $parts = explode( '/', $subpage, 2 );
66 $this->mType = $parts[0];
67 $this->mValue = $parts[1] ?? null;
68 }
69
70 /**
71 * Handle Special:Redirect/user/xxxx (by redirecting to User:YYYY)
72 *
73 * @return Status A good status contains the url to redirect to
74 */
75 function dispatchUser() {
76 if ( !ctype_digit( $this->mValue ) ) {
77 // Message: redirect-not-numeric
78 return Status::newFatal( $this->getMessagePrefix() . '-not-numeric' );
79 }
80 $user = User::newFromId( (int)$this->mValue );
81 $username = $user->getName(); // load User as side-effect
82 if ( $user->isAnon() ) {
83 // Message: redirect-not-exists
84 return Status::newFatal( $this->getMessagePrefix() . '-not-exists' );
85 }
86 if ( $user->isHidden() && !MediaWikiServices::getInstance()->getPermissionManager()
87 ->userHasRight( $this->getUser(), 'hideuser' )
88 ) {
89 throw new PermissionsError( null, [ 'badaccess-group0' ] );
90 }
91 $userpage = Title::makeTitle( NS_USER, $username );
92
93 return Status::newGood( $userpage->getFullURL( '', false, PROTO_CURRENT ) );
94 }
95
96 /**
97 * Handle Special:Redirect/file/xxxx
98 *
99 * @return Status A good status contains the url to redirect to
100 */
101 function dispatchFile() {
102 try {
103 $title = Title::newFromTextThrow( $this->mValue, NS_FILE );
104 if ( $title && !$title->inNamespace( NS_FILE ) ) {
105 // If the given value contains a namespace enforce file namespace
106 $title = Title::newFromTextThrow( Title::makeName( NS_FILE, $this->mValue ) );
107 }
108 } catch ( MalformedTitleException $e ) {
109 return Status::newFatal( $e->getMessageObject() );
110 }
111 $file = MediaWikiServices::getInstance()->getRepoGroup()->findFile( $title );
112
113 if ( !$file || !$file->exists() ) {
114 // Message: redirect-not-exists
115 return Status::newFatal( $this->getMessagePrefix() . '-not-exists' );
116 }
117 // Default behavior: Use the direct link to the file.
118 $url = $file->getUrl();
119 $request = $this->getRequest();
120 $width = $request->getInt( 'width', -1 );
121 $height = $request->getInt( 'height', -1 );
122
123 // If a width is requested...
124 if ( $width != -1 ) {
125 $mto = $file->transform( [ 'width' => $width, 'height' => $height ] );
126 // ... and we can
127 if ( $mto && !$mto->isError() ) {
128 // ... change the URL to point to a thumbnail.
129 // Note: This url is more temporary as can change
130 // if file is reuploaded and has different aspect ratio.
131 $url = [ $mto->getUrl(), $height === -1 ? 301 : 302 ];
132 }
133 }
134
135 return Status::newGood( $url );
136 }
137
138 /**
139 * Handle Special:Redirect/revision/xxx
140 * (by redirecting to index.php?oldid=xxx)
141 *
142 * @return Status A good status contains the url to redirect to
143 */
144 function dispatchRevision() {
145 $oldid = $this->mValue;
146 if ( !ctype_digit( $oldid ) ) {
147 // Message: redirect-not-numeric
148 return Status::newFatal( $this->getMessagePrefix() . '-not-numeric' );
149 }
150 $oldid = (int)$oldid;
151 if ( $oldid === 0 ) {
152 // Message: redirect-not-exists
153 return Status::newFatal( $this->getMessagePrefix() . '-not-exists' );
154 }
155
156 return Status::newGood( wfAppendQuery( wfScript( 'index' ), [
157 'oldid' => $oldid
158 ] ) );
159 }
160
161 /**
162 * Handle Special:Redirect/page/xxx (by redirecting to index.php?curid=xxx)
163 *
164 * @return Status A good status contains the url to redirect to
165 */
166 function dispatchPage() {
167 $curid = $this->mValue;
168 if ( !ctype_digit( $curid ) ) {
169 // Message: redirect-not-numeric
170 return Status::newFatal( $this->getMessagePrefix() . '-not-numeric' );
171 }
172 $curid = (int)$curid;
173 if ( $curid === 0 ) {
174 // Message: redirect-not-exists
175 return Status::newFatal( $this->getMessagePrefix() . '-not-exists' );
176 }
177
178 return Status::newGood( wfAppendQuery( wfScript( 'index' ), [
179 'curid' => $curid
180 ] ) );
181 }
182
183 /**
184 * Handle Special:Redirect/logid/xxx
185 * (by redirecting to index.php?title=Special:Log&logid=xxx)
186 *
187 * @since 1.27
188 * @return Status A good status contains the url to redirect to
189 */
190 function dispatchLog() {
191 $logid = $this->mValue;
192 if ( !ctype_digit( $logid ) ) {
193 // Message: redirect-not-numeric
194 return Status::newFatal( $this->getMessagePrefix() . '-not-numeric' );
195 }
196 $logid = (int)$logid;
197 if ( $logid === 0 ) {
198 // Message: redirect-not-exists
199 return Status::newFatal( $this->getMessagePrefix() . '-not-exists' );
200 }
201 $query = [ 'title' => 'Special:Log', 'logid' => $logid ];
202 return Status::newGood( wfAppendQuery( wfScript( 'index' ), $query ) );
203 }
204
205 /**
206 * Use appropriate dispatch* method to obtain a redirection URL,
207 * and either: redirect, set a 404 error code and error message,
208 * or do nothing (if $mValue wasn't set) allowing the form to be
209 * displayed.
210 *
211 * @return Status|bool True if a redirect was successfully handled.
212 */
213 function dispatch() {
214 // the various namespaces supported by Special:Redirect
215 switch ( $this->mType ) {
216 case 'user':
217 $status = $this->dispatchUser();
218 break;
219 case 'file':
220 $status = $this->dispatchFile();
221 break;
222 case 'revision':
223 $status = $this->dispatchRevision();
224 break;
225 case 'page':
226 $status = $this->dispatchPage();
227 break;
228 case 'logid':
229 $status = $this->dispatchLog();
230 break;
231 default:
232 $status = null;
233 break;
234 }
235 if ( $status && $status->isGood() ) {
236 // These urls can sometimes be linked from prominent places,
237 // so varnish cache.
238 $value = $status->getValue();
239 if ( is_array( $value ) ) {
240 list( $url, $code ) = $value;
241 } else {
242 $url = $value;
243 $code = 301;
244 }
245 if ( $code === 301 ) {
246 $this->getOutput()->setCdnMaxage( 60 * 60 );
247 } else {
248 $this->getOutput()->setCdnMaxage( 10 );
249 }
250 $this->getOutput()->redirect( $url, $code );
251
252 return true;
253 }
254 if ( !is_null( $this->mValue ) ) {
255 $this->getOutput()->setStatusCode( 404 );
256
257 return $status;
258 }
259
260 return false;
261 }
262
263 protected function getFormFields() {
264 $mp = $this->getMessagePrefix();
265 $ns = [
266 // subpage => message
267 // Messages: redirect-user, redirect-page, redirect-revision,
268 // redirect-file, redirect-logid
269 'user' => $mp . '-user',
270 'page' => $mp . '-page',
271 'revision' => $mp . '-revision',
272 'file' => $mp . '-file',
273 'logid' => $mp . '-logid',
274 ];
275 $a = [];
276 $a['type'] = [
277 'type' => 'select',
278 'label-message' => $mp . '-lookup', // Message: redirect-lookup
279 'options' => [],
280 'default' => current( array_keys( $ns ) ),
281 ];
282 foreach ( $ns as $n => $m ) {
283 $m = $this->msg( $m )->text();
284 $a['type']['options'][$m] = $n;
285 }
286 $a['value'] = [
287 'type' => 'text',
288 'label-message' => $mp . '-value' // Message: redirect-value
289 ];
290 // set the defaults according to the parsed subpage path
291 if ( !empty( $this->mType ) ) {
292 $a['type']['default'] = $this->mType;
293 }
294 if ( !empty( $this->mValue ) ) {
295 $a['value']['default'] = $this->mValue;
296 }
297
298 return $a;
299 }
300
301 public function onSubmit( array $data ) {
302 if ( !empty( $data['type'] ) && !empty( $data['value'] ) ) {
303 $this->setParameter( $data['type'] . '/' . $data['value'] );
304 }
305
306 /* if this returns false, will show the form */
307 return $this->dispatch();
308 }
309
310 public function onSuccess() {
311 /* do nothing, we redirect in $this->dispatch if successful. */
312 }
313
314 protected function alterForm( HTMLForm $form ) {
315 /* display summary at top of page */
316 $this->outputHeader();
317 // tweak label on submit button
318 // Message: redirect-submit
319 $form->setSubmitTextMsg( $this->getMessagePrefix() . '-submit' );
320 /* submit form every time */
321 $form->setMethod( 'get' );
322 }
323
324 protected function getDisplayFormat() {
325 return 'ooui';
326 }
327
328 /**
329 * Return an array of subpages that this special page will accept.
330 *
331 * @return string[] subpages
332 */
333 protected function getSubpagesForPrefixSearch() {
334 return [
335 'file',
336 'page',
337 'revision',
338 'user',
339 'logid',
340 ];
341 }
342
343 /**
344 * @return bool
345 */
346 public function requiresWrite() {
347 return false;
348 }
349
350 /**
351 * @return bool
352 */
353 public function requiresUnblock() {
354 return false;
355 }
356
357 protected function getGroupName() {
358 return 'redirects';
359 }
360 }