Issues (4122)

Security Analysis    not enabled

This project does not seem to handle request data directly as such no vulnerable execution paths were found.

  Cross-Site Scripting
Cross-Site Scripting enables an attacker to inject code into the response of a web-request that is viewed by other users. It can for example be used to bypass access controls, or even to take over other users' accounts.
  File Exposure
File Exposure allows an attacker to gain access to local files that he should not be able to access. These files can for example include database credentials, or other configuration files.
  File Manipulation
File Manipulation enables an attacker to write custom data to files. This potentially leads to injection of arbitrary code on the server.
  Object Injection
Object Injection enables an attacker to inject an object into PHP code, and can lead to arbitrary code execution, file exposure, or file manipulation attacks.
  Code Injection
Code Injection enables an attacker to execute arbitrary code on the server.
  Response Splitting
Response Splitting can be used to send arbitrary responses.
  File Inclusion
File Inclusion enables an attacker to inject custom files into PHP's file loading mechanism, either explicitly passed to include, or for example via PHP's auto-loading mechanism.
  Command Injection
Command Injection enables an attacker to inject a shell command that is execute with the privileges of the web-server. This can be used to expose sensitive data, or gain access of your server.
  SQL Injection
SQL Injection enables an attacker to execute arbitrary SQL code on your database server gaining access to user data, or manipulating user data.
  XPath Injection
XPath Injection enables an attacker to modify the parts of XML document that are read. If that XML document is for example used for authentication, this can lead to further vulnerabilities similar to SQL Injection.
  LDAP Injection
LDAP Injection enables an attacker to inject LDAP statements potentially granting permission to run unauthorized queries, or modify content inside the LDAP tree.
  Header Injection
  Other Vulnerability
This category comprises other attack vectors such as manipulating the PHP runtime, loading custom extensions, freezing the runtime, or similar.
  Regex Injection
Regex Injection enables an attacker to execute arbitrary code in your PHP process.
  XML Injection
XML Injection enables an attacker to read files on your local filesystem including configuration files, or can be abused to freeze your web-server process.
  Variable Injection
Variable Injection enables an attacker to overwrite program variables with custom data, and can lead to further vulnerabilities.
Unfortunately, the security analysis is currently not available for your project. If you are a non-commercial open-source project, please contact support to gain access.

includes/filerepo/LocalRepo.php (5 issues)

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
 * Local repository that stores files in the local filesystem and registers them
4
 * in the wiki's own database.
5
 *
6
 * This program is free software; you can redistribute it and/or modify
7
 * it under the terms of the GNU General Public License as published by
8
 * the Free Software Foundation; either version 2 of the License, or
9
 * (at your option) any later version.
10
 *
11
 * This program is distributed in the hope that it will be useful,
12
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
13
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14
 * GNU General Public License for more details.
15
 *
16
 * You should have received a copy of the GNU General Public License along
17
 * with this program; if not, write to the Free Software Foundation, Inc.,
18
 * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
19
 * http://www.gnu.org/copyleft/gpl.html
20
 *
21
 * @file
22
 * @ingroup FileRepo
23
 */
24
25
/**
26
 * A repository that stores files in the local filesystem and registers them
27
 * in the wiki's own database. This is the most commonly used repository class.
28
 *
29
 * @ingroup FileRepo
30
 */
31
class LocalRepo extends FileRepo {
32
	/** @var callable */
33
	protected $fileFactory = [ 'LocalFile', 'newFromTitle' ];
34
	/** @var callable */
35
	protected $fileFactoryKey = [ 'LocalFile', 'newFromKey' ];
36
	/** @var callable */
37
	protected $fileFromRowFactory = [ 'LocalFile', 'newFromRow' ];
38
	/** @var callable */
39
	protected $oldFileFromRowFactory = [ 'OldLocalFile', 'newFromRow' ];
40
	/** @var callable */
41
	protected $oldFileFactory = [ 'OldLocalFile', 'newFromTitle' ];
42
	/** @var callable */
43
	protected $oldFileFactoryKey = [ 'OldLocalFile', 'newFromKey' ];
44
45
	function __construct( array $info = null ) {
46
		parent::__construct( $info );
47
48
		$this->hasSha1Storage = isset( $info['storageLayout'] )
49
			&& $info['storageLayout'] === 'sha1';
50
51
		if ( $this->hasSha1Storage() ) {
52
			$this->backend = new FileBackendDBRepoWrapper( [
53
				'backend'         => $this->backend,
54
				'repoName'        => $this->name,
0 ignored issues
show
The property name does not exist. Did you maybe forget to declare it?

In PHP it is possible to write to properties without declaring them. For example, the following is perfectly valid PHP code:

class MyClass { }

$x = new MyClass();
$x->foo = true;

Generally, it is a good practice to explictly declare properties to avoid accidental typos and provide IDE auto-completion:

class MyClass {
    public $foo;
}

$x = new MyClass();
$x->foo = true;
Loading history...
55
				'dbHandleFactory' => $this->getDBFactory()
56
			] );
57
		}
58
	}
59
60
	/**
61
	 * @throws MWException
62
	 * @param stdClass $row
63
	 * @return LocalFile
64
	 */
65
	function newFileFromRow( $row ) {
66
		if ( isset( $row->img_name ) ) {
67
			return call_user_func( $this->fileFromRowFactory, $row, $this );
68
		} elseif ( isset( $row->oi_name ) ) {
69
			return call_user_func( $this->oldFileFromRowFactory, $row, $this );
70
		} else {
71
			throw new MWException( __METHOD__ . ': invalid row' );
72
		}
73
	}
74
75
	/**
76
	 * @param Title $title
77
	 * @param string $archiveName
78
	 * @return OldLocalFile
79
	 */
80
	function newFromArchiveName( $title, $archiveName ) {
81
		return OldLocalFile::newFromArchiveName( $title, $this, $archiveName );
82
	}
83
84
	/**
85
	 * Delete files in the deleted directory if they are not referenced in the
86
	 * filearchive table. This needs to be done in the repo because it needs to
87
	 * interleave database locks with file operations, which is potentially a
88
	 * remote operation.
89
	 *
90
	 * @param array $storageKeys
91
	 *
92
	 * @return Status
93
	 */
94
	function cleanupDeletedBatch( array $storageKeys ) {
95
		if ( $this->hasSha1Storage() ) {
96
			wfDebug( __METHOD__ . ": skipped because storage uses sha1 paths\n" );
97
			return Status::newGood();
98
		}
99
100
		$backend = $this->backend; // convenience
101
		$root = $this->getZonePath( 'deleted' );
102
		$dbw = $this->getMasterDB();
103
		$status = $this->newGood();
104
		$storageKeys = array_unique( $storageKeys );
105
		foreach ( $storageKeys as $key ) {
106
			$hashPath = $this->getDeletedHashPath( $key );
107
			$path = "$root/$hashPath$key";
108
			$dbw->startAtomic( __METHOD__ );
109
			// Check for usage in deleted/hidden files and preemptively
110
			// lock the key to avoid any future use until we are finished.
111
			$deleted = $this->deletedFileHasKey( $key, 'lock' );
112
			$hidden = $this->hiddenFileHasKey( $key, 'lock' );
113
			if ( !$deleted && !$hidden ) { // not in use now
114
				wfDebug( __METHOD__ . ": deleting $key\n" );
115
				$op = [ 'op' => 'delete', 'src' => $path ];
116
				if ( !$backend->doOperation( $op )->isOK() ) {
117
					$status->error( 'undelete-cleanup-error', $path );
118
					$status->failCount++;
119
				}
120
			} else {
121
				wfDebug( __METHOD__ . ": $key still in use\n" );
122
				$status->successCount++;
123
			}
124
			$dbw->endAtomic( __METHOD__ );
125
		}
126
127
		return $status;
128
	}
129
130
	/**
131
	 * Check if a deleted (filearchive) file has this sha1 key
132
	 *
133
	 * @param string $key File storage key (base-36 sha1 key with file extension)
134
	 * @param string|null $lock Use "lock" to lock the row via FOR UPDATE
135
	 * @return bool File with this key is in use
136
	 */
137
	protected function deletedFileHasKey( $key, $lock = null ) {
138
		$options = ( $lock === 'lock' ) ? [ 'FOR UPDATE' ] : [];
139
140
		$dbw = $this->getMasterDB();
141
142
		return (bool)$dbw->selectField( 'filearchive', '1',
143
			[ 'fa_storage_group' => 'deleted', 'fa_storage_key' => $key ],
144
			__METHOD__, $options
145
		);
146
	}
147
148
	/**
149
	 * Check if a hidden (revision delete) file has this sha1 key
150
	 *
151
	 * @param string $key File storage key (base-36 sha1 key with file extension)
152
	 * @param string|null $lock Use "lock" to lock the row via FOR UPDATE
153
	 * @return bool File with this key is in use
154
	 */
155
	protected function hiddenFileHasKey( $key, $lock = null ) {
156
		$options = ( $lock === 'lock' ) ? [ 'FOR UPDATE' ] : [];
157
158
		$sha1 = self::getHashFromKey( $key );
159
		$ext = File::normalizeExtension( substr( $key, strcspn( $key, '.' ) + 1 ) );
160
161
		$dbw = $this->getMasterDB();
162
163
		return (bool)$dbw->selectField( 'oldimage', '1',
164
			[ 'oi_sha1' => $sha1,
165
				'oi_archive_name ' . $dbw->buildLike( $dbw->anyString(), ".$ext" ),
166
				$dbw->bitAnd( 'oi_deleted', File::DELETED_FILE ) => File::DELETED_FILE ],
167
			__METHOD__, $options
168
		);
169
	}
170
171
	/**
172
	 * Gets the SHA1 hash from a storage key
173
	 *
174
	 * @param string $key
175
	 * @return string
176
	 */
177
	public static function getHashFromKey( $key ) {
178
		return strtok( $key, '.' );
179
	}
180
181
	/**
182
	 * Checks if there is a redirect named as $title
183
	 *
184
	 * @param Title $title Title of file
185
	 * @return bool|Title
186
	 */
187
	function checkRedirect( Title $title ) {
188
		$title = File::normalizeTitle( $title, 'exception' );
189
190
		$memcKey = $this->getSharedCacheKey( 'image_redirect', md5( $title->getDBkey() ) );
191
		if ( $memcKey === false ) {
192
			$memcKey = $this->getLocalCacheKey( 'image_redirect', md5( $title->getDBkey() ) );
193
			$expiry = 300; // no invalidation, 5 minutes
194
		} else {
195
			$expiry = 86400; // has invalidation, 1 day
196
		}
197
198
		$method = __METHOD__;
199
		$redirDbKey = ObjectCache::getMainWANInstance()->getWithSetCallback(
200
			$memcKey,
201
			$expiry,
202
			function ( $oldValue, &$ttl, array &$setOpts ) use ( $method, $title ) {
203
				$dbr = $this->getSlaveDB(); // possibly remote DB
204
205
				$setOpts += Database::getCacheSetOptions( $dbr );
206
207
				if ( $title instanceof Title ) {
208
					$row = $dbr->selectRow(
209
						[ 'page', 'redirect' ],
210
						[ 'rd_namespace', 'rd_title' ],
211
						[
212
							'page_namespace' => $title->getNamespace(),
213
							'page_title' => $title->getDBkey(),
214
							'rd_from = page_id'
215
						],
216
						$method
217
					);
218
				} else {
219
					$row = false;
220
				}
221
222
				return ( $row && $row->rd_namespace == NS_FILE )
223
					? Title::makeTitle( $row->rd_namespace, $row->rd_title )->getDBkey()
224
					: ''; // negative cache
225
			},
226
			[ 'pcTTL' => WANObjectCache::TTL_PROC_LONG ]
227
		);
228
229
		// @note: also checks " " for b/c
230
		if ( $redirDbKey !== ' ' && strval( $redirDbKey ) !== '' ) {
231
			// Page is a redirect to another file
232
			return Title::newFromText( $redirDbKey, NS_FILE );
233
		}
234
235
		return false; // no redirect
236
	}
237
238
	public function findFiles( array $items, $flags = 0 ) {
239
		$finalFiles = []; // map of (DB key => corresponding File) for matches
240
241
		$searchSet = []; // map of (normalized DB key => search params)
242
		foreach ( $items as $item ) {
243
			if ( is_array( $item ) ) {
244
				$title = File::normalizeTitle( $item['title'] );
245
				if ( $title ) {
246
					$searchSet[$title->getDBkey()] = $item;
247
				}
248
			} else {
249
				$title = File::normalizeTitle( $item );
250
				if ( $title ) {
251
					$searchSet[$title->getDBkey()] = [];
252
				}
253
			}
254
		}
255
256
		$fileMatchesSearch = function ( File $file, array $search ) {
257
			// Note: file name comparison done elsewhere (to handle redirects)
258
			$user = ( !empty( $search['private'] ) && $search['private'] instanceof User )
259
				? $search['private']
260
				: null;
261
262
			return (
263
				$file->exists() &&
264
				(
265
					( empty( $search['time'] ) && !$file->isOld() ) ||
266
					( !empty( $search['time'] ) && $search['time'] === $file->getTimestamp() )
267
				) &&
268
				( !empty( $search['private'] ) || !$file->isDeleted( File::DELETED_FILE ) ) &&
269
				$file->userCan( File::DELETED_FILE, $user )
270
			);
271
		};
272
273
		$that = $this;
274
		$applyMatchingFiles = function ( ResultWrapper $res, &$searchSet, &$finalFiles )
275
			use ( $that, $fileMatchesSearch, $flags )
276
		{
277
			global $wgContLang;
278
			$info = $that->getInfo();
279
			foreach ( $res as $row ) {
280
				$file = $that->newFileFromRow( $row );
0 ignored issues
show
It seems like $row defined by $row on line 279 can be null; however, LocalRepo::newFileFromRow() 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...
281
				// There must have been a search for this DB key, but this has to handle the
282
				// cases were title capitalization is different on the client and repo wikis.
283
				$dbKeysLook = [ strtr( $file->getName(), ' ', '_' ) ];
284
				if ( !empty( $info['initialCapital'] ) ) {
285
					// Search keys for "hi.png" and "Hi.png" should use the "Hi.png file"
286
					$dbKeysLook[] = $wgContLang->lcfirst( $file->getName() );
287
				}
288
				foreach ( $dbKeysLook as $dbKey ) {
289
					if ( isset( $searchSet[$dbKey] )
290
						&& $fileMatchesSearch( $file, $searchSet[$dbKey] )
291
					) {
292
						$finalFiles[$dbKey] = ( $flags & FileRepo::NAME_AND_TIME_ONLY )
293
							? [ 'title' => $dbKey, 'timestamp' => $file->getTimestamp() ]
294
							: $file;
295
						unset( $searchSet[$dbKey] );
296
					}
297
				}
298
			}
299
		};
300
301
		$dbr = $this->getSlaveDB();
302
303
		// Query image table
304
		$imgNames = [];
305
		foreach ( array_keys( $searchSet ) as $dbKey ) {
306
			$imgNames[] = $this->getNameFromTitle( File::normalizeTitle( $dbKey ) );
0 ignored issues
show
It seems like \File::normalizeTitle($dbKey) can be null; however, getNameFromTitle() 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...
307
		}
308
309
		if ( count( $imgNames ) ) {
310
			$res = $dbr->select( 'image',
311
				LocalFile::selectFields(), [ 'img_name' => $imgNames ], __METHOD__ );
312
			$applyMatchingFiles( $res, $searchSet, $finalFiles );
313
		}
314
315
		// Query old image table
316
		$oiConds = []; // WHERE clause array for each file
317
		foreach ( $searchSet as $dbKey => $search ) {
318
			if ( isset( $search['time'] ) ) {
319
				$oiConds[] = $dbr->makeList(
320
					[
321
						'oi_name' => $this->getNameFromTitle( File::normalizeTitle( $dbKey ) ),
0 ignored issues
show
It seems like \File::normalizeTitle($dbKey) can be null; however, getNameFromTitle() 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...
322
						'oi_timestamp' => $dbr->timestamp( $search['time'] )
323
					],
324
					LIST_AND
325
				);
326
			}
327
		}
328
329
		if ( count( $oiConds ) ) {
330
			$res = $dbr->select( 'oldimage',
331
				OldLocalFile::selectFields(), $dbr->makeList( $oiConds, LIST_OR ), __METHOD__ );
332
			$applyMatchingFiles( $res, $searchSet, $finalFiles );
333
		}
334
335
		// Check for redirects...
336
		foreach ( $searchSet as $dbKey => $search ) {
337
			if ( !empty( $search['ignoreRedirect'] ) ) {
338
				continue;
339
			}
340
341
			$title = File::normalizeTitle( $dbKey );
342
			$redir = $this->checkRedirect( $title ); // hopefully hits memcached
0 ignored issues
show
It seems like $title defined by \File::normalizeTitle($dbKey) on line 341 can be null; however, LocalRepo::checkRedirect() 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...
343
344
			if ( $redir && $redir->getNamespace() == NS_FILE ) {
345
				$file = $this->newFile( $redir );
346
				if ( $file && $fileMatchesSearch( $file, $search ) ) {
347
					$file->redirectedFrom( $title->getDBkey() );
348 View Code Duplication
					if ( $flags & FileRepo::NAME_AND_TIME_ONLY ) {
349
						$finalFiles[$dbKey] = [
350
							'title' => $file->getTitle()->getDBkey(),
351
							'timestamp' => $file->getTimestamp()
352
						];
353
					} else {
354
						$finalFiles[$dbKey] = $file;
355
					}
356
				}
357
			}
358
		}
359
360
		return $finalFiles;
361
	}
362
363
	/**
364
	 * Get an array or iterator of file objects for files that have a given
365
	 * SHA-1 content hash.
366
	 *
367
	 * @param string $hash A sha1 hash to look for
368
	 * @return File[]
369
	 */
370
	function findBySha1( $hash ) {
371
		$dbr = $this->getSlaveDB();
372
		$res = $dbr->select(
373
			'image',
374
			LocalFile::selectFields(),
375
			[ 'img_sha1' => $hash ],
376
			__METHOD__,
377
			[ 'ORDER BY' => 'img_name' ]
378
		);
379
380
		$result = [];
381
		foreach ( $res as $row ) {
382
			$result[] = $this->newFileFromRow( $row );
383
		}
384
		$res->free();
385
386
		return $result;
387
	}
388
389
	/**
390
	 * Get an array of arrays or iterators of file objects for files that
391
	 * have the given SHA-1 content hashes.
392
	 *
393
	 * Overrides generic implementation in FileRepo for performance reason
394
	 *
395
	 * @param array $hashes An array of hashes
396
	 * @return array An Array of arrays or iterators of file objects and the hash as key
397
	 */
398
	function findBySha1s( array $hashes ) {
399
		if ( !count( $hashes ) ) {
400
			return []; // empty parameter
401
		}
402
403
		$dbr = $this->getSlaveDB();
404
		$res = $dbr->select(
405
			'image',
406
			LocalFile::selectFields(),
407
			[ 'img_sha1' => $hashes ],
408
			__METHOD__,
409
			[ 'ORDER BY' => 'img_name' ]
410
		);
411
412
		$result = [];
413
		foreach ( $res as $row ) {
414
			$file = $this->newFileFromRow( $row );
415
			$result[$file->getSha1()][] = $file;
416
		}
417
		$res->free();
418
419
		return $result;
420
	}
421
422
	/**
423
	 * Return an array of files where the name starts with $prefix.
424
	 *
425
	 * @param string $prefix The prefix to search for
426
	 * @param int $limit The maximum amount of files to return
427
	 * @return array
428
	 */
429
	public function findFilesByPrefix( $prefix, $limit ) {
430
		$selectOptions = [ 'ORDER BY' => 'img_name', 'LIMIT' => intval( $limit ) ];
431
432
		// Query database
433
		$dbr = $this->getSlaveDB();
434
		$res = $dbr->select(
435
			'image',
436
			LocalFile::selectFields(),
437
			'img_name ' . $dbr->buildLike( $prefix, $dbr->anyString() ),
438
			__METHOD__,
439
			$selectOptions
440
		);
441
442
		// Build file objects
443
		$files = [];
444
		foreach ( $res as $row ) {
445
			$files[] = $this->newFileFromRow( $row );
446
		}
447
448
		return $files;
449
	}
450
451
	/**
452
	 * Get a connection to the replica DB
453
	 * @return IDatabase
454
	 */
455
	function getSlaveDB() {
456
		return wfGetDB( DB_REPLICA );
457
	}
458
459
	/**
460
	 * Get a connection to the master DB
461
	 * @return IDatabase
462
	 */
463
	function getMasterDB() {
464
		return wfGetDB( DB_MASTER );
465
	}
466
467
	/**
468
	 * Get a callback to get a DB handle given an index (DB_REPLICA/DB_MASTER)
469
	 * @return Closure
470
	 */
471
	protected function getDBFactory() {
472
		return function( $index ) {
473
			return wfGetDB( $index );
474
		};
475
	}
476
477
	/**
478
	 * Get a key on the primary cache for this repository.
479
	 * Returns false if the repository's cache is not accessible at this site.
480
	 * The parameters are the parts of the key, as for wfMemcKey().
481
	 *
482
	 * @return string
483
	 */
484
	function getSharedCacheKey( /*...*/ ) {
485
		$args = func_get_args();
486
487
		return call_user_func_array( 'wfMemcKey', $args );
488
	}
489
490
	/**
491
	 * Invalidates image redirect cache related to that image
492
	 *
493
	 * @param Title $title Title of page
494
	 * @return void
495
	 */
496
	function invalidateImageRedirect( Title $title ) {
497
		$key = $this->getSharedCacheKey( 'image_redirect', md5( $title->getDBkey() ) );
498
		if ( $key ) {
499
			$this->getMasterDB()->onTransactionPreCommitOrIdle(
500
				function () use ( $key ) {
501
					ObjectCache::getMainWANInstance()->delete( $key );
502
				},
503
				__METHOD__
504
			);
505
		}
506
	}
507
508
	/**
509
	 * Return information about the repository.
510
	 *
511
	 * @return array
512
	 * @since 1.22
513
	 */
514
	function getInfo() {
515
		global $wgFavicon;
516
517
		return array_merge( parent::getInfo(), [
518
			'favicon' => wfExpandUrl( $wgFavicon ),
519
		] );
520
	}
521
522
	public function store( $srcPath, $dstZone, $dstRel, $flags = 0 ) {
523
		return $this->skipWriteOperationIfSha1( __FUNCTION__, func_get_args() );
524
	}
525
526
	public function storeBatch( array $triplets, $flags = 0 ) {
527
		return $this->skipWriteOperationIfSha1( __FUNCTION__, func_get_args() );
528
	}
529
530
	public function cleanupBatch( array $files, $flags = 0 ) {
531
		return $this->skipWriteOperationIfSha1( __FUNCTION__, func_get_args() );
532
	}
533
534
	public function publish(
535
		$src,
536
		$dstRel,
537
		$archiveRel,
538
		$flags = 0,
539
		array $options = []
540
	) {
541
		return $this->skipWriteOperationIfSha1( __FUNCTION__, func_get_args() );
542
	}
543
544
	public function publishBatch( array $ntuples, $flags = 0 ) {
545
		return $this->skipWriteOperationIfSha1( __FUNCTION__, func_get_args() );
546
	}
547
548
	public function delete( $srcRel, $archiveRel ) {
549
		return $this->skipWriteOperationIfSha1( __FUNCTION__, func_get_args() );
550
	}
551
552
	public function deleteBatch( array $sourceDestPairs ) {
553
		return $this->skipWriteOperationIfSha1( __FUNCTION__, func_get_args() );
554
	}
555
556
	/**
557
	 * Skips the write operation if storage is sha1-based, executes it normally otherwise
558
	 *
559
	 * @param string $function
560
	 * @param array $args
561
	 * @return Status
562
	 */
563
	protected function skipWriteOperationIfSha1( $function, array $args ) {
564
		$this->assertWritableRepo(); // fail out if read-only
565
566
		if ( $this->hasSha1Storage() ) {
567
			wfDebug( __METHOD__ . ": skipped because storage uses sha1 paths\n" );
568
			return Status::newGood();
569
		} else {
570
			return call_user_func_array( 'parent::' . $function, $args );
571
		}
572
	}
573
}
574