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