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