thumb.php ➔ wfStreamThumb()   F
last analyzed

Complexity

Conditions 56
Paths > 20000

Size

Total Lines 283
Code Lines 177

Duplication

Lines 9
Ratio 3.18 %

Importance

Changes 0
Metric Value
cc 56
eloc 177
nc 750464
nop 1
dl 9
loc 283
rs 2
c 0
b 0
f 0

How to fix   Long Method    Complexity   

Long Method

Small methods make your code easier to understand, in particular if combined with a good name. Besides, if your method is small, finding a good name is usually much easier.

For example, if you find yourself adding comments to a method's body, this is usually a good sign to extract the commented part to a new method, and use the comment as a starting point when coming up with a good name for this new method.

Commonly applied refactorings include:

1
<?php
0 ignored issues
show
Coding Style Compatibility introduced by
For compatibility and reusability of your code, PSR1 recommends that a file should introduce either new symbols (like classes, functions, etc.) or have side-effects (like outputting something, or including other files), but not both at the same time. The first symbol is defined on line 26 and the first side effect is on line 27.

The PSR-1: Basic Coding Standard recommends that a file should either introduce new symbols, that is classes, functions, constants or similar, or have side effects. Side effects are anything that executes logic, like for example printing output, changing ini settings or writing to a file.

The idea behind this recommendation is that merely auto-loading a class should not change the state of an application. It also promotes a cleaner style of programming and makes your code less prone to errors, because the logic is not spread out all over the place.

To learn more about the PSR-1, please see the PHP-FIG site on the PSR-1.

Loading history...
2
/**
3
 * PHP script to stream out an image thumbnail.
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 Media
22
 */
23
24
use MediaWiki\Logger\LoggerFactory;
25
26
define( 'MW_NO_OUTPUT_COMPRESSION', 1 );
27
require __DIR__ . '/includes/WebStart.php';
28
29
// Don't use fancy MIME detection, just check the file extension for jpg/gif/png
30
$wgTrivialMimeDetection = true;
31
32
if ( defined( 'THUMB_HANDLER' ) ) {
33
	// Called from thumb_handler.php via 404; extract params from the URI...
34
	wfThumbHandle404();
35
} else {
36
	// Called directly, use $_GET params
37
	wfStreamThumb( $_GET );
38
}
39
40
$mediawiki = new MediaWiki();
41
$mediawiki->doPostOutputShutdown( 'fast' );
42
43
// --------------------------------------------------------------------------
44
45
/**
46
 * Handle a thumbnail request via thumbnail file URL
47
 *
48
 * @return void
49
 */
50
function wfThumbHandle404() {
51
	global $wgArticlePath;
52
53
	# Set action base paths so that WebRequest::getPathInfo()
54
	# recognizes the "X" as the 'title' in ../thumb_handler.php/X urls.
55
	# Note: If Custom per-extension repo paths are set, this may break.
56
	$repo = RepoGroup::singleton()->getLocalRepo();
57
	$oldArticlePath = $wgArticlePath;
58
	$wgArticlePath = $repo->getZoneUrl( 'thumb' ) . '/$1';
59
60
	$matches = WebRequest::getPathInfo();
61
62
	$wgArticlePath = $oldArticlePath;
63
64
	if ( !isset( $matches['title'] ) ) {
65
		wfThumbError( 404, 'Could not determine the name of the requested thumbnail.' );
66
		return;
67
	}
68
69
	$params = wfExtractThumbRequestInfo( $matches['title'] ); // basic wiki URL param extracting
70
	if ( $params == null ) {
71
		wfThumbError( 400, 'The specified thumbnail parameters are not recognized.' );
72
		return;
73
	}
74
75
	wfStreamThumb( $params ); // stream the thumbnail
76
}
77
78
/**
79
 * Stream a thumbnail specified by parameters
80
 *
81
 * @param array $params List of thumbnailing parameters. In addition to parameters
82
 *  passed to the MediaHandler, this may also includes the keys:
83
 *   f (for filename), archived (if archived file), temp (if temp file),
84
 *   w (alias for width), p (alias for page), r (ignored; historical),
85
 *   rel404 (path for render on 404 to verify hash path correct),
86
 *   thumbName (thumbnail name to potentially extract more parameters from
87
 *   e.g. 'lossy-page1-120px-Foo.tiff' would add page, lossy and width
88
 *   to the parameters)
89
 * @return void
90
 */
91
function wfStreamThumb( array $params ) {
0 ignored issues
show
Coding Style introduced by
wfStreamThumb uses the super-global variable $_SERVER which is generally not recommended.

Instead of super-globals, we recommend to explicitly inject the dependencies of your class. This makes your code less dependent on global state and it becomes generally more testable:

// Bad
class Router
{
    public function generate($path)
    {
        return $_SERVER['HOST'].$path;
    }
}

// Better
class Router
{
    private $host;

    public function __construct($host)
    {
        $this->host = $host;
    }

    public function generate($path)
    {
        return $this->host.$path;
    }
}

class Controller
{
    public function myAction(Request $request)
    {
        // Instead of
        $page = isset($_GET['page']) ? intval($_GET['page']) : 1;

        // Better (assuming you use the Symfony2 request)
        $page = $request->query->get('page', 1);
    }
}
Loading history...
92
	global $wgVaryOnXFP;
93
94
	$headers = []; // HTTP headers to send
95
96
	$fileName = isset( $params['f'] ) ? $params['f'] : '';
97
98
	// Backwards compatibility parameters
99
	if ( isset( $params['w'] ) ) {
100
		$params['width'] = $params['w'];
101
		unset( $params['w'] );
102
	}
103
	if ( isset( $params['width'] ) && substr( $params['width'], -2 ) == 'px' ) {
104
		// strip the px (pixel) suffix, if found
105
		$params['width'] = substr( $params['width'], 0, -2 );
106
	}
107
	if ( isset( $params['p'] ) ) {
108
		$params['page'] = $params['p'];
109
	}
110
111
	// Is this a thumb of an archived file?
112
	$isOld = ( isset( $params['archived'] ) && $params['archived'] );
113
	unset( $params['archived'] ); // handlers don't care
114
115
	// Is this a thumb of a temp file?
116
	$isTemp = ( isset( $params['temp'] ) && $params['temp'] );
117
	unset( $params['temp'] ); // handlers don't care
118
119
	// Some basic input validation
120
	$fileName = strtr( $fileName, '\\/', '__' );
121
122
	// Actually fetch the image. Method depends on whether it is archived or not.
123
	if ( $isTemp ) {
124
		$repo = RepoGroup::singleton()->getLocalRepo()->getTempRepo();
125
		$img = new UnregisteredLocalFile( null, $repo,
126
			# Temp files are hashed based on the name without the timestamp.
127
			# The thumbnails will be hashed based on the entire name however.
128
			# @todo fix this convention to actually be reasonable.
129
			$repo->getZonePath( 'public' ) . '/' . $repo->getTempHashPath( $fileName ) . $fileName
130
		);
131
	} elseif ( $isOld ) {
132
		// Format is <timestamp>!<name>
133
		$bits = explode( '!', $fileName, 2 );
134
		if ( count( $bits ) != 2 ) {
135
			wfThumbError( 404, wfMessage( 'badtitletext' )->parse() );
136
			return;
137
		}
138
		$title = Title::makeTitleSafe( NS_FILE, $bits[1] );
139
		if ( !$title ) {
140
			wfThumbError( 404, wfMessage( 'badtitletext' )->parse() );
141
			return;
142
		}
143
		$img = RepoGroup::singleton()->getLocalRepo()->newFromArchiveName( $title, $fileName );
144
	} else {
145
		$img = wfLocalFile( $fileName );
146
	}
147
148
	// Check the source file title
149
	if ( !$img ) {
150
		wfThumbError( 404, wfMessage( 'badtitletext' )->parse() );
151
		return;
152
	}
153
154
	// Check permissions if there are read restrictions
155
	$varyHeader = [];
156
	if ( !in_array( 'read', User::getGroupPermissions( [ '*' ] ), true ) ) {
157
		if ( !$img->getTitle() || !$img->getTitle()->userCan( 'read' ) ) {
158
			wfThumbError( 403, 'Access denied. You do not have permission to access ' .
159
				'the source file.' );
160
			return;
161
		}
162
		$headers[] = 'Cache-Control: private';
163
		$varyHeader[] = 'Cookie';
164
	}
165
166
	// Check if the file is hidden
167
	if ( $img->isDeleted( File::DELETED_FILE ) ) {
168
		wfThumbErrorText( 404, "The source file '$fileName' does not exist." );
169
		return;
170
	}
171
172
	// Do rendering parameters extraction from thumbnail name.
173
	if ( isset( $params['thumbName'] ) ) {
174
		$params = wfExtractThumbParams( $img, $params );
175
	}
176
	if ( $params == null ) {
177
		wfThumbError( 400, 'The specified thumbnail parameters are not recognized.' );
178
		return;
179
	}
180
181
	// Check the source file storage path
182
	if ( !$img->exists() ) {
183
		$redirectedLocation = false;
184
		if ( !$isTemp ) {
185
			// Check for file redirect
186
			// Since redirects are associated with pages, not versions of files,
187
			// we look for the most current version to see if its a redirect.
188
			$possRedirFile = RepoGroup::singleton()->getLocalRepo()->findFile( $img->getName() );
189
			if ( $possRedirFile && !is_null( $possRedirFile->getRedirected() ) ) {
190
				$redirTarget = $possRedirFile->getName();
191
				$targetFile = wfLocalFile( Title::makeTitleSafe( NS_FILE, $redirTarget ) );
0 ignored issues
show
Bug introduced by
It seems like \Title::makeTitleSafe(NS_FILE, $redirTarget) can be null; however, wfLocalFile() does not accept null, maybe add an additional type check?

Unless you are absolutely sure that the expression can never be null because of other conditions, we strongly recommend to add an additional type check to your code:

/** @return stdClass|null */
function mayReturnNull() { }

function doesNotAcceptNull(stdClass $x) { }

// With potential error.
function withoutCheck() {
    $x = mayReturnNull();
    doesNotAcceptNull($x); // Potential error here.
}

// Safe - Alternative 1
function withCheck1() {
    $x = mayReturnNull();
    if ( ! $x instanceof stdClass) {
        throw new \LogicException('$x must be defined.');
    }
    doesNotAcceptNull($x);
}

// Safe - Alternative 2
function withCheck2() {
    $x = mayReturnNull();
    if ($x instanceof stdClass) {
        doesNotAcceptNull($x);
    }
}
Loading history...
192
				if ( $targetFile->exists() ) {
193
					$newThumbName = $targetFile->thumbName( $params );
194
					if ( $isOld ) {
195
						/** @var array $bits */
196
						$newThumbUrl = $targetFile->getArchiveThumbUrl(
197
							$bits[0] . '!' . $targetFile->getName(), $newThumbName );
0 ignored issues
show
Bug introduced by
The variable $bits 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...
198
					} else {
199
						$newThumbUrl = $targetFile->getThumbUrl( $newThumbName );
200
					}
201
					$redirectedLocation = wfExpandUrl( $newThumbUrl, PROTO_CURRENT );
202
				}
203
			}
204
		}
205
206
		if ( $redirectedLocation ) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $redirectedLocation of type string|false is loosely compared to true; this is ambiguous if the string can be empty. You might want to explicitly use !== false instead.

In PHP, under loose comparison (like ==, or !=, or switch conditions), values of different types might be equal.

For string values, the empty string '' is a special case, in particular the following results might be unexpected:

''   == false // true
''   == null  // true
'ab' == false // false
'ab' == null  // false

// It is often better to use strict comparison
'' === false // false
'' === null  // false
Loading history...
207
			// File has been moved. Give redirect.
208
			$response = RequestContext::getMain()->getRequest()->response();
209
			$response->statusHeader( 302 );
210
			$response->header( 'Location: ' . $redirectedLocation );
211
			$response->header( 'Expires: ' .
212
				gmdate( 'D, d M Y H:i:s', time() + 12 * 3600 ) . ' GMT' );
213
			if ( $wgVaryOnXFP ) {
214
				$varyHeader[] = 'X-Forwarded-Proto';
215
			}
216
			if ( count( $varyHeader ) ) {
217
				$response->header( 'Vary: ' . implode( ', ', $varyHeader ) );
218
			}
219
			$response->header( 'Content-Length: 0' );
220
			return;
221
		}
222
223
		// If its not a redirect that has a target as a local file, give 404.
224
		wfThumbErrorText( 404, "The source file '$fileName' does not exist." );
225
		return;
226
	} elseif ( $img->getPath() === false ) {
227
		wfThumbErrorText( 400, "The source file '$fileName' is not locally accessible." );
228
		return;
229
	}
230
231
	// Check IMS against the source file
232
	// This means that clients can keep a cached copy even after it has been deleted on the server
233
	if ( !empty( $_SERVER['HTTP_IF_MODIFIED_SINCE'] ) ) {
234
		// Fix IE brokenness
235
		$imsString = preg_replace( '/;.*$/', '', $_SERVER["HTTP_IF_MODIFIED_SINCE"] );
236
		// Calculate time
237
		MediaWiki\suppressWarnings();
238
		$imsUnix = strtotime( $imsString );
239
		MediaWiki\restoreWarnings();
240
		if ( wfTimestamp( TS_UNIX, $img->getTimestamp() ) <= $imsUnix ) {
241
			HttpStatus::header( 304 );
242
			return;
243
		}
244
	}
245
246
	$rel404 = isset( $params['rel404'] ) ? $params['rel404'] : null;
247
	unset( $params['r'] ); // ignore 'r' because we unconditionally pass File::RENDER
248
	unset( $params['f'] ); // We're done with 'f' parameter.
249
	unset( $params['rel404'] ); // moved to $rel404
250
251
	// Get the normalized thumbnail name from the parameters...
252
	try {
253
		$thumbName = $img->thumbName( $params );
254
		if ( !strlen( $thumbName ) ) { // invalid params?
255
			throw new MediaTransformInvalidParametersException(
256
				'Empty return from File::thumbName'
257
			);
258
		}
259
		$thumbName2 = $img->thumbName( $params, File::THUMB_FULL_NAME ); // b/c; "long" style
260
	} catch ( MediaTransformInvalidParametersException $e ) {
261
		wfThumbError(
262
			400,
263
			'The specified thumbnail parameters are not valid: ' . $e->getMessage()
264
		);
265
		return;
266
	} catch ( MWException $e ) {
267
		wfThumbError( 500, $e->getHTML(), 'Exception caught while extracting thumb name',
268
			[ 'exception' => $e ] );
269
		return;
270
	}
271
272
	// For 404 handled thumbnails, we only use the base name of the URI
273
	// for the thumb params and the parent directory for the source file name.
274
	// Check that the zone relative path matches up so squid caches won't pick
275
	// up thumbs that would not be purged on source file deletion (bug 34231).
276
	if ( $rel404 !== null ) { // thumbnail was handled via 404
277
		if ( rawurldecode( $rel404 ) === $img->getThumbRel( $thumbName ) ) {
0 ignored issues
show
Unused Code introduced by
This if statement is empty and can be removed.

This check looks for the bodies of if statements that have no statements or where all statements have been commented out. This may be the result of changes for debugging or the code may simply be obsolete.

These if bodies can be removed. If you have an empty if but statements in the else branch, consider inverting the condition.

if (rand(1, 6) > 3) {
//print "Check failed";
} else {
    print "Check succeeded";
}

could be turned into

if (rand(1, 6) <= 3) {
    print "Check succeeded";
}

This is much more concise to read.

Loading history...
278
			// Request for the canonical thumbnail name
279
		} elseif ( rawurldecode( $rel404 ) === $img->getThumbRel( $thumbName2 ) ) {
280
			// Request for the "long" thumbnail name; redirect to canonical name
281
			$response = RequestContext::getMain()->getRequest()->response();
282
			$response->statusHeader( 301 );
283
			$response->header( 'Location: ' .
284
				wfExpandUrl( $img->getThumbUrl( $thumbName ), PROTO_CURRENT ) );
285
			$response->header( 'Expires: ' .
286
				gmdate( 'D, d M Y H:i:s', time() + 7 * 86400 ) . ' GMT' );
287
			if ( $wgVaryOnXFP ) {
288
				$varyHeader[] = 'X-Forwarded-Proto';
289
			}
290
			if ( count( $varyHeader ) ) {
291
				$response->header( 'Vary: ' . implode( ', ', $varyHeader ) );
292
			}
293
			return;
294
		} else {
295
			wfThumbErrorText( 404, "The given path of the specified thumbnail is incorrect;
296
				expected '" . $img->getThumbRel( $thumbName ) . "' but got '" .
297
				rawurldecode( $rel404 ) . "'." );
298
			return;
299
		}
300
	}
301
302
	$dispositionType = isset( $params['download'] ) ? 'attachment' : 'inline';
303
304
	// Suggest a good name for users downloading this thumbnail
305
	$headers[] =
306
		"Content-Disposition: {$img->getThumbDisposition( $thumbName, $dispositionType )}";
307
308
	if ( count( $varyHeader ) ) {
309
		$headers[] = 'Vary: ' . implode( ', ', $varyHeader );
310
	}
311
312
	// Stream the file if it exists already...
313
	$thumbPath = $img->getThumbPath( $thumbName );
314
	if ( $img->getRepo()->fileExists( $thumbPath ) ) {
315
		$starttime = microtime( true );
316
		$status = $img->getRepo()->streamFileWithStatus( $thumbPath, $headers );
317
		$streamtime = microtime( true ) - $starttime;
318
319
		if ( $status->isOK() ) {
320
			RequestContext::getMain()->getStats()->timing( 'media.thumbnail.stream', $streamtime );
321 View Code Duplication
		} else {
322
			wfThumbError( 500, 'Could not stream the file', null, [ 'file' => $thumbName,
323
				'path' => $thumbPath, 'error' => $status->getWikiText( false, false, 'en' ) ] );
324
		}
325
		return;
326
	}
327
328
	$user = RequestContext::getMain()->getUser();
329
	if ( !wfThumbIsStandard( $img, $params ) && $user->pingLimiter( 'renderfile-nonstandard' ) ) {
330
		wfThumbError( 429, wfMessage( 'actionthrottledtext' )->parse() );
331
		return;
332
	} elseif ( $user->pingLimiter( 'renderfile' ) ) {
333
		wfThumbError( 429, wfMessage( 'actionthrottledtext' )->parse() );
334
		return;
335
	}
336
337
	list( $thumb, $errorMsg ) = wfGenerateThumbnail( $img, $params, $thumbName, $thumbPath );
338
339
	/** @var MediaTransformOutput|MediaTransformError|bool $thumb */
340
341
	// Check for thumbnail generation errors...
342
	$msg = wfMessage( 'thumbnail_error' );
343
	$errorCode = 500;
344
	if ( !$thumb ) {
345
		$errorMsg = $errorMsg ?: $msg->rawParams( 'File::transform() returned false' )->escaped();
346
		if ( $errorMsg instanceof MessageSpecifier &&
347
			$errorMsg->getKey() === 'thumbnail_image-failure-limit'
348
		) {
349
			$errorCode = 429;
350
		}
351
	} elseif ( $thumb->isError() ) {
352
		$errorMsg = $thumb->getHtmlMsg();
353
	} elseif ( !$thumb->hasFile() ) {
354
		$errorMsg = $msg->rawParams( 'No path supplied in thumbnail object' )->escaped();
355
	} elseif ( $thumb->fileIsSource() ) {
356
		$errorMsg = $msg
357
			->rawParams( 'Image was not scaled, is the requested width bigger than the source?' )
358
			->escaped();
359
		$errorCode = 400;
360
	}
361
362
	if ( $errorMsg !== false ) {
363
		wfThumbError( $errorCode, $errorMsg, null, [ 'file' => $thumbName, 'path' => $thumbPath ] );
364
	} else {
365
		// Stream the file if there were no errors
366
		$status = $thumb->streamFileWithStatus( $headers );
367 View Code Duplication
		if ( !$status->isOK() ) {
368
			wfThumbError( 500, 'Could not stream the file', null, [
369
				'file' => $thumbName, 'path' => $thumbPath,
370
				'error' => $status->getWikiText( false, false, 'en' ) ] );
371
		}
372
	}
373
}
374
375
/**
376
 * Actually try to generate a new thumbnail
377
 *
378
 * @param File $file
379
 * @param array $params
380
 * @param string $thumbName
381
 * @param string $thumbPath
382
 * @return array (MediaTransformOutput|bool, string|bool error message HTML)
383
 */
384
function wfGenerateThumbnail( File $file, array $params, $thumbName, $thumbPath ) {
385
	global $wgAttemptFailureEpoch;
386
387
	$cache = ObjectCache::getLocalClusterInstance();
388
	$key = $cache->makeKey(
389
		'attempt-failures',
390
		$wgAttemptFailureEpoch,
391
		$file->getRepo()->getName(),
392
		$file->getSha1(),
393
		md5( $thumbName )
394
	);
395
396
	// Check if this file keeps failing to render
397
	if ( $cache->get( $key ) >= 4 ) {
398
		return [ false, wfMessage( 'thumbnail_image-failure-limit', 4 ) ];
399
	}
400
401
	$done = false;
402
	// Record failures on PHP fatals in addition to caching exceptions
403
	register_shutdown_function( function () use ( $cache, &$done, $key ) {
404
		if ( !$done ) { // transform() gave a fatal
405
			// Randomize TTL to reduce stampedes
406
			$cache->incrWithInit( $key, $cache::TTL_HOUR + mt_rand( 0, 300 ) );
407
		}
408
	} );
409
410
	$thumb = false;
411
	$errorHtml = false;
412
413
	// guard thumbnail rendering with PoolCounter to avoid stampedes
414
	// expensive files use a separate PoolCounter config so it is possible
415
	// to set up a global limit on them
416
	if ( $file->isExpensiveToThumbnail() ) {
417
		$poolCounterType = 'FileRenderExpensive';
418
	} else {
419
		$poolCounterType = 'FileRender';
420
	}
421
422
	// Thumbnail isn't already there, so create the new thumbnail...
423
	try {
424
		$work = new PoolCounterWorkViaCallback( $poolCounterType, sha1( $file->getName() ),
425
			[
426
				'doWork' => function () use ( $file, $params ) {
427
					return $file->transform( $params, File::RENDER_NOW );
428
				},
429
				'doCachedWork' => function () use ( $file, $params, $thumbPath ) {
430
					// If the worker that finished made this thumbnail then use it.
431
					// Otherwise, it probably made a different thumbnail for this file.
432
					return $file->getRepo()->fileExists( $thumbPath )
433
						? $file->transform( $params, File::RENDER_NOW )
434
						: false; // retry once more in exclusive mode
435
				},
436
				'error' => function ( Status $status ) {
437
					return wfMessage( 'generic-pool-error' )->parse() . '<hr>' . $status->getHTML();
438
				}
439
			]
440
		);
441
		$result = $work->execute();
442
		if ( $result instanceof MediaTransformOutput ) {
443
			$thumb = $result;
444
		} elseif ( is_string( $result ) ) { // error
445
			$errorHtml = $result;
446
		}
447
	} catch ( Exception $e ) {
448
		// Tried to select a page on a non-paged file?
449
	}
450
451
	/** @noinspection PhpUnusedLocalVariableInspection */
452
	$done = true; // no PHP fatal occured
0 ignored issues
show
Unused Code introduced by
$done is not used, you could remove the assignment.

This check looks for variable assignements that are either overwritten by other assignments or where the variable is not used subsequently.

$myVar = 'Value';
$higher = false;

if (rand(1, 6) > 3) {
    $higher = true;
} else {
    $higher = false;
}

Both the $myVar assignment in line 1 and the $higher assignment in line 2 are dead. The first because $myVar is never used and the second because $higher is always overwritten for every possible time line.

Loading history...
453
454
	if ( !$thumb || $thumb->isError() ) {
455
		// Randomize TTL to reduce stampedes
456
		$cache->incrWithInit( $key, $cache::TTL_HOUR + mt_rand( 0, 300 ) );
457
	}
458
459
	return [ $thumb, $errorHtml ];
460
}
461
462
/**
463
 * Convert pathinfo type parameter, into normal request parameters
464
 *
465
 * So for example, if the request was redirected from
466
 * /w/images/thumb/a/ab/Foo.png/120px-Foo.png. The $thumbRel parameter
467
 * of this function would be set to "a/ab/Foo.png/120px-Foo.png".
468
 * This method is responsible for turning that into an array
469
 * with the folowing keys:
470
 *  * f => the filename (Foo.png)
471
 *  * rel404 => the whole thing (a/ab/Foo.png/120px-Foo.png)
472
 *  * archived => 1 (If the request is for an archived thumb)
473
 *  * temp => 1 (If the file is in the "temporary" zone)
474
 *  * thumbName => the thumbnail name, including parameters (120px-Foo.png)
475
 *
476
 * Transform specific parameters are set later via wfExtractThumbParams().
477
 *
478
 * @param string $thumbRel Thumbnail path relative to the thumb zone
479
 * @return array|null Associative params array or null
480
 */
481
function wfExtractThumbRequestInfo( $thumbRel ) {
482
	$repo = RepoGroup::singleton()->getLocalRepo();
483
484
	$hashDirReg = $subdirReg = '';
485
	$hashLevels = $repo->getHashLevels();
486
	for ( $i = 0; $i < $hashLevels; $i++ ) {
487
		$subdirReg .= '[0-9a-f]';
488
		$hashDirReg .= "$subdirReg/";
489
	}
490
491
	// Check if this is a thumbnail of an original in the local file repo
492
	if ( preg_match( "!^((archive/)?$hashDirReg([^/]*)/([^/]*))$!", $thumbRel, $m ) ) {
493
		list( /*all*/, $rel, $archOrTemp, $filename, $thumbname ) = $m;
494
	// Check if this is a thumbnail of an temp file in the local file repo
495
	} elseif ( preg_match( "!^(temp/)($hashDirReg([^/]*)/([^/]*))$!", $thumbRel, $m ) ) {
496
		list( /*all*/, $archOrTemp, $rel, $filename, $thumbname ) = $m;
497
	} else {
498
		return null; // not a valid looking thumbnail request
499
	}
500
501
	$params = [ 'f' => $filename, 'rel404' => $rel ];
502
	if ( $archOrTemp === 'archive/' ) {
503
		$params['archived'] = 1;
504
	} elseif ( $archOrTemp === 'temp/' ) {
505
		$params['temp'] = 1;
506
	}
507
508
	$params['thumbName'] = $thumbname;
509
	return $params;
510
}
511
512
/**
513
 * Convert a thumbnail name (122px-foo.png) to parameters, using
514
 * file handler.
515
 *
516
 * @param File $file File object for file in question
517
 * @param array $params Array of parameters so far
518
 * @return array Parameters array with more parameters
519
 */
520
function wfExtractThumbParams( $file, $params ) {
521
	if ( !isset( $params['thumbName'] ) ) {
522
		throw new InvalidArgumentException( "No thumbnail name passed to wfExtractThumbParams" );
523
	}
524
525
	$thumbname = $params['thumbName'];
526
	unset( $params['thumbName'] );
527
528
	// Do the hook first for older extensions that rely on it.
529
	if ( !Hooks::run( 'ExtractThumbParameters', [ $thumbname, &$params ] ) ) {
530
		// Check hooks if parameters can be extracted
531
		// Hooks return false if they manage to *resolve* the parameters
532
		// This hook should be considered deprecated
533
		wfDeprecated( 'ExtractThumbParameters', '1.22' );
534
		return $params; // valid thumbnail URL (via extension or config)
535
	}
536
537
	// FIXME: Files in the temp zone don't set a MIME type, which means
538
	// they don't have a handler. Which means we can't parse the param
539
	// string. However, not a big issue as what good is a param string
540
	// if you have no handler to make use of the param string and
541
	// actually generate the thumbnail.
542
	$handler = $file->getHandler();
543
544
	// Based on UploadStash::parseKey
545
	$fileNamePos = strrpos( $thumbname, $params['f'] );
546
	if ( $fileNamePos === false ) {
547
		// Maybe using a short filename? (see FileRepo::nameForThumb)
548
		$fileNamePos = strrpos( $thumbname, 'thumbnail' );
549
	}
550
551
	if ( $handler && $fileNamePos !== false ) {
552
		$paramString = substr( $thumbname, 0, $fileNamePos - 1 );
553
		$extraParams = $handler->parseParamString( $paramString );
554
		if ( $extraParams !== false ) {
555
			return $params + $extraParams;
556
		}
557
	}
558
559
	// As a last ditch fallback, use the traditional common parameters
560
	if ( preg_match( '!^(page(\d*)-)*(\d*)px-[^/]*$!', $thumbname, $matches ) ) {
561
		list( /* all */, /* pagefull */, $pagenum, $size ) = $matches;
562
		$params['width'] = $size;
563
		if ( $pagenum ) {
564
			$params['page'] = $pagenum;
565
		}
566
		return $params; // valid thumbnail URL
567
	}
568
	return null;
569
}
570
571
/**
572
 * Output a thumbnail generation error message
573
 *
574
 * @param int $status
575
 * @param string $msgText Plain text (will be html escaped)
576
 * @return void
577
 */
578
function wfThumbErrorText( $status, $msgText ) {
579
	wfThumbError( $status, htmlspecialchars( $msgText ) );
580
}
581
582
/**
583
 * Output a thumbnail generation error message
584
 *
585
 * @param int $status
586
 * @param string $msgHtml HTML
587
 * @param string $msgText Short error description, for internal logging. Defaults to $msgHtml.
588
 *   Only used for HTTP 500 errors.
589
 * @param array $context Error context, for internal logging. Only used for HTTP 500 errors.
590
 * @return void
591
 */
592
function wfThumbError( $status, $msgHtml, $msgText = null, $context = [] ) {
0 ignored issues
show
Coding Style introduced by
wfThumbError uses the super-global variable $_SERVER which is generally not recommended.

Instead of super-globals, we recommend to explicitly inject the dependencies of your class. This makes your code less dependent on global state and it becomes generally more testable:

// Bad
class Router
{
    public function generate($path)
    {
        return $_SERVER['HOST'].$path;
    }
}

// Better
class Router
{
    private $host;

    public function __construct($host)
    {
        $this->host = $host;
    }

    public function generate($path)
    {
        return $this->host.$path;
    }
}

class Controller
{
    public function myAction(Request $request)
    {
        // Instead of
        $page = isset($_GET['page']) ? intval($_GET['page']) : 1;

        // Better (assuming you use the Symfony2 request)
        $page = $request->query->get('page', 1);
    }
}
Loading history...
593
	global $wgShowHostnames;
594
595
	header( 'Cache-Control: no-cache' );
596
	header( 'Content-Type: text/html; charset=utf-8' );
597
	if ( $status == 400 || $status == 404 || $status == 429 ) {
598
		HttpStatus::header( $status );
599
	} elseif ( $status == 403 ) {
600
		HttpStatus::header( 403 );
601
		header( 'Vary: Cookie' );
602
	} else {
603
		LoggerFactory::getInstance( 'thumb' )->error( $msgText ?: $msgHtml, $context );
604
		HttpStatus::header( 500 );
605
	}
606
	if ( $wgShowHostnames ) {
607
		header( 'X-MW-Thumbnail-Renderer: ' . wfHostname() );
608
		$url = htmlspecialchars(
609
			isset( $_SERVER['REQUEST_URI'] ) ? $_SERVER['REQUEST_URI'] : ''
610
		);
611
		$hostname = htmlspecialchars( wfHostname() );
612
		$debug = "<!-- $url -->\n<!-- $hostname -->\n";
613
	} else {
614
		$debug = '';
615
	}
616
	$content = <<<EOT
617
<!DOCTYPE html>
618
<html><head>
619
<meta charset="UTF-8" />
620
<title>Error generating thumbnail</title>
621
</head>
622
<body>
623
<h1>Error generating thumbnail</h1>
624
<p>
625
$msgHtml
626
</p>
627
$debug
628
</body>
629
</html>
630
631
EOT;
632
	header( 'Content-Length: ' . strlen( $content ) );
633
	echo $content;
634
}
635