Localisation updates from https://translatewiki.net.
[mediawiki.git] / includes / specials / SpecialRedirect.php
blob9b88a2ff25721a880be26c91b9a7f6e8180cd9c7
1 <?php
2 /**
3 * This program is free software; you can redistribute it and/or modify
4 * it under the terms of the GNU General Public License as published by
5 * the Free Software Foundation; either version 2 of the License, or
6 * (at your option) any later version.
8 * This program is distributed in the hope that it will be useful,
9 * but WITHOUT ANY WARRANTY; without even the implied warranty of
10 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 * GNU General Public License for more details.
13 * You should have received a copy of the GNU General Public License along
14 * with this program; if not, write to the Free Software Foundation, Inc.,
15 * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
16 * http://www.gnu.org/copyleft/gpl.html
18 * @file
21 namespace MediaWiki\Specials;
23 use MediaWiki\HTMLForm\HTMLForm;
24 use MediaWiki\SpecialPage\FormSpecialPage;
25 use MediaWiki\Status\Status;
26 use MediaWiki\Title\MalformedTitleException;
27 use MediaWiki\Title\Title;
28 use MediaWiki\User\UserFactory;
29 use PermissionsError;
30 use RepoGroup;
32 /**
33 * Redirect dispatcher for user IDs, thumbnails, and various permalinks.
35 * - user: the user page for a given numeric user ID.
36 * - file: the file thumbnail URL for a given filename.
37 * - revision: permalink for any revision.
38 * - page: permalink for page by numeric page ID.
39 * - logid: permalink for any log entry.
41 * @ingroup SpecialPage
42 * @since 1.22
44 class SpecialRedirect extends FormSpecialPage {
46 /**
47 * The type of the redirect (user/file/revision)
49 * Example value: `'user'`
51 * @var string|null
53 protected $mType;
55 /**
56 * The identifier/value for the redirect (which id, which file)
58 * Example value: `'42'`
60 * @var string|null
62 protected $mValue;
64 private RepoGroup $repoGroup;
65 private UserFactory $userFactory;
67 /**
68 * @param RepoGroup $repoGroup
69 * @param UserFactory $userFactory
71 public function __construct(
72 RepoGroup $repoGroup,
73 UserFactory $userFactory
74 ) {
75 parent::__construct( 'Redirect' );
76 $this->mType = null;
77 $this->mValue = null;
79 $this->repoGroup = $repoGroup;
80 $this->userFactory = $userFactory;
83 /**
84 * Set $mType and $mValue based on parsed value of $subpage.
85 * @param string|null $subpage
87 public function setParameter( $subpage ) {
88 // parse $subpage to pull out the parts
89 $parts = $subpage !== null ? explode( '/', $subpage, 2 ) : [];
90 $this->mType = $parts[0] ?? null;
91 $this->mValue = $parts[1] ?? null;
94 /**
95 * Handle Special:Redirect/user/xxxx (by redirecting to User:YYYY)
97 * @return Status A good status contains the url to redirect to
99 public function dispatchUser() {
100 if ( !ctype_digit( $this->mValue ) ) {
101 return Status::newFatal( 'redirect-not-numeric' );
103 $user = $this->userFactory->newFromId( (int)$this->mValue );
104 $user->load(); // Make sure the id is validated by loading the user
105 if ( $user->isAnon() ) {
106 return Status::newFatal( 'redirect-not-exists' );
108 if ( $user->isHidden() && !$this->getAuthority()->isAllowed( 'hideuser' ) ) {
109 throw new PermissionsError( null, [ 'badaccess-group0' ] );
112 return Status::newGood( [
113 $user->getUserPage()->getFullURL( '', false, PROTO_CURRENT ), 302
114 ] );
118 * Handle Special:Redirect/file/xxxx
120 * @return Status A good status contains the url to redirect to
122 public function dispatchFile() {
123 try {
124 $title = Title::newFromTextThrow( $this->mValue, NS_FILE );
125 if ( $title && !$title->inNamespace( NS_FILE ) ) {
126 // If the given value contains a namespace enforce file namespace
127 $title = Title::newFromTextThrow( Title::makeName( NS_FILE, $this->mValue ) );
129 } catch ( MalformedTitleException $e ) {
130 return Status::newFatal( $e->getMessageObject() );
132 // @phan-suppress-next-line PhanTypeMismatchArgumentNullable False positive
133 $file = $this->repoGroup->findFile( $title );
135 if ( !$file || !$file->exists() ) {
136 return Status::newFatal( 'redirect-not-exists' );
138 // Default behavior: Use the direct link to the file.
139 $url = $file->getUrl();
140 $request = $this->getRequest();
141 $width = $request->getInt( 'width', -1 );
142 $height = $request->getInt( 'height', -1 );
144 // If a width is requested...
145 if ( $width != -1 ) {
146 $mto = $file->transform( [ 'width' => $width, 'height' => $height ] );
147 // ... and we can
148 if ( $mto && !$mto->isError() ) {
149 // ... change the URL to point to a thumbnail.
150 // Note: This url is more temporary as can change
151 // if file is reuploaded and has different aspect ratio.
152 $url = [ $mto->getUrl(), $height === -1 ? 301 : 302 ];
156 return Status::newGood( $url );
160 * Handle Special:Redirect/revision/xxx
161 * (by redirecting to index.php?oldid=xxx)
163 * @return Status A good status contains the url to redirect to
165 public function dispatchRevision() {
166 $oldid = $this->mValue;
167 if ( !ctype_digit( $oldid ) ) {
168 return Status::newFatal( 'redirect-not-numeric' );
170 $oldid = (int)$oldid;
171 if ( $oldid === 0 ) {
172 return Status::newFatal( 'redirect-not-exists' );
175 return Status::newGood( wfAppendQuery( wfScript( 'index' ), [
176 'oldid' => $oldid
177 ] ) );
181 * Handle Special:Redirect/page/xxx (by redirecting to index.php?curid=xxx)
183 * @return Status A good status contains the url to redirect to
185 public function dispatchPage() {
186 $curid = $this->mValue;
187 if ( !ctype_digit( $curid ) ) {
188 return Status::newFatal( 'redirect-not-numeric' );
190 $curid = (int)$curid;
191 if ( $curid === 0 ) {
192 return Status::newFatal( 'redirect-not-exists' );
195 return Status::newGood( wfAppendQuery( wfScript( 'index' ), [
196 'curid' => $curid
197 ] ) );
201 * Handle Special:Redirect/logid/xxx
202 * (by redirecting to index.php?title=Special:Log&logid=xxx)
204 * @since 1.27
205 * @return Status A good status contains the url to redirect to
207 public function dispatchLog() {
208 $logid = $this->mValue;
209 if ( !ctype_digit( $logid ) ) {
210 return Status::newFatal( 'redirect-not-numeric' );
212 $logid = (int)$logid;
213 if ( $logid === 0 ) {
214 return Status::newFatal( 'redirect-not-exists' );
216 $query = [ 'title' => 'Special:Log', 'logid' => $logid ];
217 return Status::newGood( wfAppendQuery( wfScript( 'index' ), $query ) );
221 * Use appropriate dispatch* method to obtain a redirection URL,
222 * and either: redirect, set a 404 error code and error message,
223 * or do nothing (if $mValue wasn't set) allowing the form to be
224 * displayed.
226 * @return Status|bool True if a redirect was successfully handled.
228 private function dispatch() {
229 // the various namespaces supported by Special:Redirect
230 switch ( $this->mType ) {
231 case 'user':
232 $status = $this->dispatchUser();
233 break;
234 case 'file':
235 $status = $this->dispatchFile();
236 break;
237 case 'revision':
238 $status = $this->dispatchRevision();
239 break;
240 case 'page':
241 $status = $this->dispatchPage();
242 break;
243 case 'logid':
244 $status = $this->dispatchLog();
245 break;
246 default:
247 $status = null;
248 break;
250 if ( $status && $status->isGood() ) {
251 // These urls can sometimes be linked from prominent places,
252 // so varnish cache.
253 $value = $status->getValue();
254 if ( is_array( $value ) ) {
255 [ $url, $code ] = $value;
256 } else {
257 $url = $value;
258 $code = 301;
260 if ( $code === 301 ) {
261 $this->getOutput()->setCdnMaxage( 60 * 60 );
262 } else {
263 $this->getOutput()->setCdnMaxage( 10 );
265 $this->getOutput()->redirect( $url, $code );
267 return true;
269 if ( $this->mValue !== null ) {
270 $this->getOutput()->setStatusCode( 404 );
272 // @phan-suppress-next-line PhanTypeMismatchReturnNullable Null of $status seems unreachable
273 return $status;
276 return false;
279 protected function getFormFields() {
280 return [
281 'type' => [
282 'type' => 'select',
283 'label-message' => 'redirect-lookup',
284 'options-messages' => [
285 'redirect-user' => 'user',
286 'redirect-page' => 'page',
287 'redirect-revision' => 'revision',
288 'redirect-file' => 'file',
289 'redirect-logid' => 'logid',
291 'default' => $this->mType,
293 'value' => [
294 'type' => 'text',
295 'label-message' => 'redirect-value',
296 'default' => $this->mValue,
297 'required' => true,
302 public function onSubmit( array $data ) {
303 if ( !empty( $data['type'] ) && !empty( $data['value'] ) ) {
304 $this->setParameter( $data['type'] . '/' . $data['value'] );
307 /* if this returns false, will show the form */
308 return $this->dispatch();
311 public function onSuccess() {
312 /* do nothing, we redirect in $this->dispatch if successful. */
315 protected function alterForm( HTMLForm $form ) {
316 // tweak label on submit button
317 $form->setSubmitTextMsg( 'redirect-submit' );
320 protected function getDisplayFormat() {
321 return 'ooui';
325 * Return an array of subpages that this special page will accept.
327 * @return string[] subpages
329 protected function getSubpagesForPrefixSearch() {
330 return [
331 'file',
332 'page',
333 'revision',
334 'user',
335 'logid',
340 * @return bool
342 public function requiresPost() {
343 return false;
346 protected function getGroupName() {
347 return 'redirects';
352 * Retain the old class name for backwards compatibility.
353 * @deprecated since 1.41
355 class_alias( SpecialRedirect::class, 'SpecialRedirect' );