Merge "Don't fallback from uk to ru"
[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 * @var string $mType
37 * @example 'user'
38 */
39 protected $mType;
40
41 /**
42 * The identifier/value for the redirect (which id, which file)
43 *
44 * @var string $mValue
45 * @example '42'
46 */
47 protected $mValue;
48
49 function __construct() {
50 parent::__construct( 'Redirect' );
51 $this->mType = null;
52 $this->mValue = null;
53 }
54
55 /**
56 * Set $mType and $mValue based on parsed value of $subpage.
57 * @param string $subpage
58 */
59 function setParameter( $subpage ) {
60 // parse $subpage to pull out the parts
61 $parts = explode( '/', $subpage, 2 );
62 $this->mType = count( $parts ) > 0 ? $parts[0] : null;
63 $this->mValue = count( $parts ) > 1 ? $parts[1] : null;
64 }
65
66 /**
67 * Handle Special:Redirect/user/xxxx (by redirecting to User:YYYY)
68 *
69 * @return string|null Url to redirect to, or null if $mValue is invalid.
70 */
71 function dispatchUser() {
72 if ( !ctype_digit( $this->mValue ) ) {
73 return null;
74 }
75 $user = User::newFromId( (int)$this->mValue );
76 $username = $user->getName(); // load User as side-effect
77 if ( $user->isAnon() ) {
78 return null;
79 }
80 $userpage = Title::makeTitle( NS_USER, $username );
81
82 return $userpage->getFullURL( '', false, PROTO_CURRENT );
83 }
84
85 /**
86 * Handle Special:Redirect/file/xxxx
87 *
88 * @return string|null Url to redirect to, or null if $mValue is not found.
89 */
90 function dispatchFile() {
91 $title = Title::makeTitleSafe( NS_FILE, $this->mValue );
92
93 if ( !$title instanceof Title ) {
94 return null;
95 }
96 $file = wfFindFile( $title );
97
98 if ( !$file || !$file->exists() ) {
99 return null;
100 }
101 // Default behavior: Use the direct link to the file.
102 $url = $file->getUrl();
103 $request = $this->getRequest();
104 $width = $request->getInt( 'width', -1 );
105 $height = $request->getInt( 'height', -1 );
106
107 // If a width is requested...
108 if ( $width != -1 ) {
109 $mto = $file->transform( [ 'width' => $width, 'height' => $height ] );
110 // ... and we can
111 if ( $mto && !$mto->isError() ) {
112 // ... change the URL to point to a thumbnail.
113 $url = $mto->getUrl();
114 }
115 }
116
117 return $url;
118 }
119
120 /**
121 * Handle Special:Redirect/revision/xxx
122 * (by redirecting to index.php?oldid=xxx)
123 *
124 * @return string|null Url to redirect to, or null if $mValue is invalid.
125 */
126 function dispatchRevision() {
127 $oldid = $this->mValue;
128 if ( !ctype_digit( $oldid ) ) {
129 return null;
130 }
131 $oldid = (int)$oldid;
132 if ( $oldid === 0 ) {
133 return null;
134 }
135
136 return wfAppendQuery( wfScript( 'index' ), [
137 'oldid' => $oldid
138 ] );
139 }
140
141 /**
142 * Handle Special:Redirect/page/xxx (by redirecting to index.php?curid=xxx)
143 *
144 * @return string|null Url to redirect to, or null if $mValue is invalid.
145 */
146 function dispatchPage() {
147 $curid = $this->mValue;
148 if ( !ctype_digit( $curid ) ) {
149 return null;
150 }
151 $curid = (int)$curid;
152 if ( $curid === 0 ) {
153 return null;
154 }
155
156 return wfAppendQuery( wfScript( 'index' ), [
157 'curid' => $curid
158 ] );
159 }
160
161 /**
162 * Handle Special:Redirect/logid/xxx
163 * (by redirecting to index.php?title=Special:Log)
164 *
165 * @since 1.27
166 * @return string|null Url to redirect to, or null if $mValue is invalid.
167 */
168 function dispatchLog() {
169 $logid = $this->mValue;
170 if ( !ctype_digit( $logid ) ) {
171 return null;
172 }
173 $logid = (int)$logid;
174 if ( $logid === 0 ) {
175 return null;
176 }
177
178 $logparams = [
179 'log_id',
180 'log_timestamp',
181 'log_type',
182 'log_user_text',
183 ];
184
185 $dbr = wfGetDB( DB_REPLICA );
186
187 // Gets the nested SQL statement which
188 // returns timestamp of the log with the given log ID
189 $inner = $dbr->selectSQLText(
190 'logging',
191 [ 'log_timestamp' ],
192 [ 'log_id' => $logid ]
193 );
194
195 // Returns all fields mentioned in $logparams of the logs
196 // with the same timestamp as the one returned by the statement above
197 $logsSameTimestamps = $dbr->select(
198 'logging',
199 $logparams,
200 [ "log_timestamp = ($inner)" ]
201 );
202 if ( $logsSameTimestamps->numRows() === 0 ) {
203 return null;
204 }
205
206 // Stores the row with the same log ID as the one given
207 $rowMain = [];
208 foreach ( $logsSameTimestamps as $row ) {
209 if ( (int)$row->log_id === $logid ) {
210 $rowMain = $row;
211 }
212 }
213
214 array_shift( $logparams );
215
216 // Stores all the rows with the same values in each column
217 // as $rowMain
218 foreach ( $logparams as $cond ) {
219 $matchedRows = [];
220 foreach ( $logsSameTimestamps as $row ) {
221 if ( $row->$cond === $rowMain->$cond ) {
222 $matchedRows[] = $row;
223 }
224 }
225 if ( count( $matchedRows ) === 1 ) {
226 break;
227 }
228 $logsSameTimestamps = $matchedRows;
229 }
230 $query = [ 'title' => 'Special:Log', 'limit' => count( $matchedRows ) ];
231
232 // A map of database field names from table 'logging' to the values of $logparams
233 $keys = [
234 'log_timestamp' => 'offset',
235 'log_type' => 'type',
236 'log_user_text' => 'user'
237 ];
238
239 foreach ( $logparams as $logKey ) {
240 $query[$keys[$logKey]] = $matchedRows[0]->$logKey;
241 }
242 $query['offset'] = $query['offset'] + 1;
243 $url = $query;
244
245 return wfAppendQuery( wfScript( 'index' ), $url );
246 }
247
248 /**
249 * Use appropriate dispatch* method to obtain a redirection URL,
250 * and either: redirect, set a 404 error code and error message,
251 * or do nothing (if $mValue wasn't set) allowing the form to be
252 * displayed.
253 *
254 * @return bool True if a redirect was successfully handled.
255 */
256 function dispatch() {
257 // the various namespaces supported by Special:Redirect
258 switch ( $this->mType ) {
259 case 'user':
260 $url = $this->dispatchUser();
261 break;
262 case 'file':
263 $url = $this->dispatchFile();
264 break;
265 case 'revision':
266 $url = $this->dispatchRevision();
267 break;
268 case 'page':
269 $url = $this->dispatchPage();
270 break;
271 case 'logid':
272 $url = $this->dispatchLog();
273 break;
274 default:
275 $url = null;
276 break;
277 }
278 if ( $url ) {
279 $this->getOutput()->redirect( $url );
280
281 return true;
282 }
283 if ( !is_null( $this->mValue ) ) {
284 $this->getOutput()->setStatusCode( 404 );
285 // Message: redirect-not-exists
286 $msg = $this->getMessagePrefix() . '-not-exists';
287
288 return Status::newFatal( $msg );
289 }
290
291 return false;
292 }
293
294 protected function getFormFields() {
295 $mp = $this->getMessagePrefix();
296 $ns = [
297 // subpage => message
298 // Messages: redirect-user, redirect-page, redirect-revision,
299 // redirect-file, redirect-logid
300 'user' => $mp . '-user',
301 'page' => $mp . '-page',
302 'revision' => $mp . '-revision',
303 'file' => $mp . '-file',
304 'logid' => $mp . '-logid',
305 ];
306 $a = [];
307 $a['type'] = [
308 'type' => 'select',
309 'label-message' => $mp . '-lookup', // Message: redirect-lookup
310 'options' => [],
311 'default' => current( array_keys( $ns ) ),
312 ];
313 foreach ( $ns as $n => $m ) {
314 $m = $this->msg( $m )->text();
315 $a['type']['options'][$m] = $n;
316 }
317 $a['value'] = [
318 'type' => 'text',
319 'label-message' => $mp . '-value' // Message: redirect-value
320 ];
321 // set the defaults according to the parsed subpage path
322 if ( !empty( $this->mType ) ) {
323 $a['type']['default'] = $this->mType;
324 }
325 if ( !empty( $this->mValue ) ) {
326 $a['value']['default'] = $this->mValue;
327 }
328
329 return $a;
330 }
331
332 public function onSubmit( array $data ) {
333 if ( !empty( $data['type'] ) && !empty( $data['value'] ) ) {
334 $this->setParameter( $data['type'] . '/' . $data['value'] );
335 }
336
337 /* if this returns false, will show the form */
338 return $this->dispatch();
339 }
340
341 public function onSuccess() {
342 /* do nothing, we redirect in $this->dispatch if successful. */
343 }
344
345 protected function alterForm( HTMLForm $form ) {
346 /* display summary at top of page */
347 $this->outputHeader();
348 // tweak label on submit button
349 // Message: redirect-submit
350 $form->setSubmitTextMsg( $this->getMessagePrefix() . '-submit' );
351 /* submit form every time */
352 $form->setMethod( 'get' );
353 }
354
355 protected function getDisplayFormat() {
356 return 'ooui';
357 }
358
359 /**
360 * Return an array of subpages that this special page will accept.
361 *
362 * @return string[] subpages
363 */
364 protected function getSubpagesForPrefixSearch() {
365 return [
366 'file',
367 'page',
368 'revision',
369 'user',
370 'logid',
371 ];
372 }
373
374 /**
375 * @return bool
376 */
377 public function requiresWrite() {
378 return false;
379 }
380
381 /**
382 * @return bool
383 */
384 public function requiresUnblock() {
385 return false;
386 }
387
388 protected function getGroupName() {
389 return 'redirects';
390 }
391 }