3 * Implements Special:Redirect
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.
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.
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
21 * @ingroup SpecialPage
24 use MediaWiki\MediaWikiServices
;
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.
30 * @ingroup SpecialPage
33 class SpecialRedirect
extends FormSpecialPage
{
36 * The type of the redirect (user/file/revision)
38 * Example value: `'user'`
45 * The identifier/value for the redirect (which id, which file)
47 * Example value: `'42'`
53 function __construct() {
54 parent
::__construct( 'Redirect' );
60 * Set $mType and $mValue based on parsed value of $subpage.
61 * @param string $subpage
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;
71 * Handle Special:Redirect/user/xxxx (by redirecting to User:YYYY)
73 * @return Status A good status contains the url to redirect to
75 function dispatchUser() {
76 if ( !ctype_digit( $this->mValue
) ) {
77 // Message: redirect-not-numeric
78 return Status
::newFatal( $this->getMessagePrefix() . '-not-numeric' );
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' );
86 if ( $user->isHidden() && !MediaWikiServices
::getInstance()->getPermissionManager()
87 ->userHasRight( $this->getUser(), 'hideuser' )
89 throw new PermissionsError( null, [ 'badaccess-group0' ] );
91 $userpage = Title
::makeTitle( NS_USER
, $username );
93 return Status
::newGood( [
94 $userpage->getFullURL( '', false, PROTO_CURRENT
), 302
99 * Handle Special:Redirect/file/xxxx
101 * @return Status A good status contains the url to redirect to
103 function dispatchFile() {
105 $title = Title
::newFromTextThrow( $this->mValue
, NS_FILE
);
106 if ( $title && !$title->inNamespace( NS_FILE
) ) {
107 // If the given value contains a namespace enforce file namespace
108 $title = Title
::newFromTextThrow( Title
::makeName( NS_FILE
, $this->mValue
) );
110 } catch ( MalformedTitleException
$e ) {
111 return Status
::newFatal( $e->getMessageObject() );
113 $file = MediaWikiServices
::getInstance()->getRepoGroup()->findFile( $title );
115 if ( !$file ||
!$file->exists() ) {
116 // Message: redirect-not-exists
117 return Status
::newFatal( $this->getMessagePrefix() . '-not-exists' );
119 // Default behavior: Use the direct link to the file.
120 $url = $file->getUrl();
121 $request = $this->getRequest();
122 $width = $request->getInt( 'width', -1 );
123 $height = $request->getInt( 'height', -1 );
125 // If a width is requested...
126 if ( $width != -1 ) {
127 $mto = $file->transform( [ 'width' => $width, 'height' => $height ] );
129 if ( $mto && !$mto->isError() ) {
130 // ... change the URL to point to a thumbnail.
131 // Note: This url is more temporary as can change
132 // if file is reuploaded and has different aspect ratio.
133 $url = [ $mto->getUrl(), $height === -1 ?
301 : 302 ];
137 return Status
::newGood( $url );
141 * Handle Special:Redirect/revision/xxx
142 * (by redirecting to index.php?oldid=xxx)
144 * @return Status A good status contains the url to redirect to
146 function dispatchRevision() {
147 $oldid = $this->mValue
;
148 if ( !ctype_digit( $oldid ) ) {
149 // Message: redirect-not-numeric
150 return Status
::newFatal( $this->getMessagePrefix() . '-not-numeric' );
152 $oldid = (int)$oldid;
153 if ( $oldid === 0 ) {
154 // Message: redirect-not-exists
155 return Status
::newFatal( $this->getMessagePrefix() . '-not-exists' );
158 return Status
::newGood( wfAppendQuery( wfScript( 'index' ), [
164 * Handle Special:Redirect/page/xxx (by redirecting to index.php?curid=xxx)
166 * @return Status A good status contains the url to redirect to
168 function dispatchPage() {
169 $curid = $this->mValue
;
170 if ( !ctype_digit( $curid ) ) {
171 // Message: redirect-not-numeric
172 return Status
::newFatal( $this->getMessagePrefix() . '-not-numeric' );
174 $curid = (int)$curid;
175 if ( $curid === 0 ) {
176 // Message: redirect-not-exists
177 return Status
::newFatal( $this->getMessagePrefix() . '-not-exists' );
180 return Status
::newGood( wfAppendQuery( wfScript( 'index' ), [
186 * Handle Special:Redirect/logid/xxx
187 * (by redirecting to index.php?title=Special:Log&logid=xxx)
190 * @return Status A good status contains the url to redirect to
192 function dispatchLog() {
193 $logid = $this->mValue
;
194 if ( !ctype_digit( $logid ) ) {
195 // Message: redirect-not-numeric
196 return Status
::newFatal( $this->getMessagePrefix() . '-not-numeric' );
198 $logid = (int)$logid;
199 if ( $logid === 0 ) {
200 // Message: redirect-not-exists
201 return Status
::newFatal( $this->getMessagePrefix() . '-not-exists' );
203 $query = [ 'title' => 'Special:Log', 'logid' => $logid ];
204 return Status
::newGood( wfAppendQuery( wfScript( 'index' ), $query ) );
208 * Use appropriate dispatch* method to obtain a redirection URL,
209 * and either: redirect, set a 404 error code and error message,
210 * or do nothing (if $mValue wasn't set) allowing the form to be
213 * @return Status|bool True if a redirect was successfully handled.
215 function dispatch() {
216 // the various namespaces supported by Special:Redirect
217 switch ( $this->mType
) {
219 $status = $this->dispatchUser();
222 $status = $this->dispatchFile();
225 $status = $this->dispatchRevision();
228 $status = $this->dispatchPage();
231 $status = $this->dispatchLog();
237 if ( $status && $status->isGood() ) {
238 // These urls can sometimes be linked from prominent places,
240 $value = $status->getValue();
241 if ( is_array( $value ) ) {
242 list( $url, $code ) = $value;
247 if ( $code === 301 ) {
248 $this->getOutput()->setCdnMaxage( 60 * 60 );
250 $this->getOutput()->setCdnMaxage( 10 );
252 $this->getOutput()->redirect( $url, $code );
256 if ( !is_null( $this->mValue
) ) {
257 $this->getOutput()->setStatusCode( 404 );
265 protected function getFormFields() {
266 $mp = $this->getMessagePrefix();
268 // subpage => message
269 // Messages: redirect-user, redirect-page, redirect-revision,
270 // redirect-file, redirect-logid
271 'user' => $mp . '-user',
272 'page' => $mp . '-page',
273 'revision' => $mp . '-revision',
274 'file' => $mp . '-file',
275 'logid' => $mp . '-logid',
280 'label-message' => $mp . '-lookup', // Message: redirect-lookup
282 'default' => current( array_keys( $ns ) ),
284 foreach ( $ns as $n => $m ) {
285 $m = $this->msg( $m )->text();
286 $a['type']['options'][$m] = $n;
290 'label-message' => $mp . '-value' // Message: redirect-value
292 // set the defaults according to the parsed subpage path
293 if ( !empty( $this->mType
) ) {
294 $a['type']['default'] = $this->mType
;
296 if ( !empty( $this->mValue
) ) {
297 $a['value']['default'] = $this->mValue
;
303 public function onSubmit( array $data ) {
304 if ( !empty( $data['type'] ) && !empty( $data['value'] ) ) {
305 $this->setParameter( $data['type'] . '/' . $data['value'] );
308 /* if this returns false, will show the form */
309 return $this->dispatch();
312 public function onSuccess() {
313 /* do nothing, we redirect in $this->dispatch if successful. */
316 protected function alterForm( HTMLForm
$form ) {
317 /* display summary at top of page */
318 $this->outputHeader();
319 // tweak label on submit button
320 // Message: redirect-submit
321 $form->setSubmitTextMsg( $this->getMessagePrefix() . '-submit' );
322 /* submit form every time */
323 $form->setMethod( 'get' );
326 protected function getDisplayFormat() {
331 * Return an array of subpages that this special page will accept.
333 * @return string[] subpages
335 protected function getSubpagesForPrefixSearch() {
348 public function requiresWrite() {
355 public function requiresUnblock() {
359 protected function getGroupName() {