Completed
Branch master (939199)
by
unknown
39:35
created

includes/specials/SpecialRedirect.php (1 issue)

Upgrade to new PHP Analysis Engine

These results are based on our legacy PHP analysis, consider migrating to our new PHP analysis engine instead. Learn more

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 View Code Duplication
	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 View Code Duplication
	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 ) ];
0 ignored issues
show
The variable $matchedRows does not seem to be defined for all execution paths leading up to this point.

If you define a variable conditionally, it can happen that it is not defined for all execution paths.

Let’s take a look at an example:

function myFunction($a) {
    switch ($a) {
        case 'foo':
            $x = 1;
            break;

        case 'bar':
            $x = 2;
            break;
    }

    // $x is potentially undefined here.
    echo $x;
}

In the above example, the variable $x is defined if you pass “foo” or “bar” as argument for $a. However, since the switch statement has no default case statement, if you pass any other value, the variable $x would be undefined.

Available Fixes

  1. Check for existence of the variable explicitly:

    function myFunction($a) {
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
        }
    
        if (isset($x)) { // Make sure it's always set.
            echo $x;
        }
    }
    
  2. Define a default value for the variable:

    function myFunction($a) {
        $x = ''; // Set a default which gets overridden for certain paths.
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
        }
    
        echo $x;
    }
    
  3. Add a value for the missing path:

    function myFunction($a) {
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
    
            // We add support for the missing case.
            default:
                $x = '';
                break;
        }
    
        echo $x;
    }
    
Loading history...
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
}
392