summaryrefslogtreecommitdiff
path: root/includes/filerepo
diff options
context:
space:
mode:
authorPierre Schmitz <pierre@archlinux.de>2014-12-27 15:41:37 +0100
committerPierre Schmitz <pierre@archlinux.de>2014-12-31 11:43:28 +0100
commitc1f9b1f7b1b77776192048005dcc66dcf3df2bfb (patch)
tree2b38796e738dd74cb42ecd9bfd151803108386bc /includes/filerepo
parentb88ab0086858470dd1f644e64cb4e4f62bb2be9b (diff)
Update to MediaWiki 1.24.1
Diffstat (limited to 'includes/filerepo')
-rw-r--r--includes/filerepo/FSRepo.php6
-rw-r--r--includes/filerepo/FileRepo.php453
-rw-r--r--includes/filerepo/FileRepoStatus.php11
-rw-r--r--includes/filerepo/ForeignAPIRepo.php139
-rw-r--r--includes/filerepo/ForeignDBRepo.php46
-rw-r--r--includes/filerepo/ForeignDBViaLBRepo.php24
-rw-r--r--includes/filerepo/LocalRepo.php195
-rw-r--r--includes/filerepo/NullRepo.php6
-rw-r--r--includes/filerepo/RepoGroup.php186
-rw-r--r--includes/filerepo/file/ArchivedFile.php165
-rw-r--r--includes/filerepo/file/File.php827
-rw-r--r--includes/filerepo/file/ForeignAPIFile.php53
-rw-r--r--includes/filerepo/file/ForeignDBFile.php58
-rw-r--r--includes/filerepo/file/LocalFile.php994
-rw-r--r--includes/filerepo/file/OldLocalFile.php125
-rw-r--r--includes/filerepo/file/UnregisteredLocalFile.php63
16 files changed, 2215 insertions, 1136 deletions
diff --git a/includes/filerepo/FSRepo.php b/includes/filerepo/FSRepo.php
index 42c9c945..5896aba1 100644
--- a/includes/filerepo/FSRepo.php
+++ b/includes/filerepo/FSRepo.php
@@ -31,9 +31,8 @@
* @deprecated since 1.19
*/
class FSRepo extends FileRepo {
-
/**
- * @param $info array
+ * @param array $info
* @throws MWException
*/
function __construct( array $info ) {
@@ -57,7 +56,8 @@ class FSRepo extends FileRepo {
// Get the FS backend configuration
$backend = new FSFileBackend( array(
'name' => $info['name'] . '-backend',
- 'lockManager' => 'fsLockManager',
+ 'wikiId' => wfWikiID(),
+ 'lockManager' => LockManagerGroup::singleton( wfWikiID() )->get( 'fsLockManager' ),
'containerPaths' => array(
"{$repoName}-public" => "{$directory}",
"{$repoName}-temp" => "{$directory}/temp",
diff --git a/includes/filerepo/FileRepo.php b/includes/filerepo/FileRepo.php
index 1195d5f8..59295257 100644
--- a/includes/filerepo/FileRepo.php
+++ b/includes/filerepo/FileRepo.php
@@ -40,29 +40,91 @@ class FileRepo {
const OVERWRITE_SAME = 4;
const SKIP_LOCKING = 8;
+ const NAME_AND_TIME_ONLY = 1;
+
+ /** @var bool Whether to fetch commons image description pages and display
+ * them on the local wiki */
+ public $fetchDescription;
+
+ /** @var int */
+ public $descriptionCacheExpiry;
+
/** @var FileBackend */
protected $backend;
- /** @var Array Map of zones to config */
+
+ /** @var array Map of zones to config */
protected $zones = array();
- var $thumbScriptUrl, $transformVia404;
- var $descBaseUrl, $scriptDirUrl, $scriptExtension, $articleUrl;
- var $fetchDescription, $initialCapital;
- var $pathDisclosureProtection = 'simple'; // 'paranoid'
- var $descriptionCacheExpiry, $url, $thumbUrl;
- var $hashLevels, $deletedHashLevels;
+ /** @var string URL of thumb.php */
+ protected $thumbScriptUrl;
+
+ /** @var bool Whether to skip media file transformation on parse and rely
+ * on a 404 handler instead. */
+ protected $transformVia404;
+
+ /** @var string URL of image description pages, e.g.
+ * http://en.wikipedia.org/wiki/File:
+ */
+ protected $descBaseUrl;
+
+ /** @var string URL of the MediaWiki installation, equivalent to
+ * $wgScriptPath, e.g. https://en.wikipedia.org/w
+ */
+ protected $scriptDirUrl;
+
+ /** @var string Script extension of the MediaWiki installation, equivalent
+ * to $wgScriptExtension, e.g. .php5 defaults to .php */
+ protected $scriptExtension;
+
+ /** @var string Equivalent to $wgArticlePath, e.g. http://en.wikipedia.org/wiki/$1 */
+ protected $articleUrl;
+
+ /** @var bool Equivalent to $wgCapitalLinks (or $wgCapitalLinkOverrides[NS_FILE],
+ * determines whether filenames implicitly start with a capital letter.
+ * The current implementation may give incorrect description page links
+ * when the local $wgCapitalLinks and initialCapital are mismatched.
+ */
+ protected $initialCapital;
+
+ /** @var string May be 'paranoid' to remove all parameters from error
+ * messages, 'none' to leave the paths in unchanged, or 'simple' to
+ * replace paths with placeholders. Default for LocalRepo is
+ * 'simple'.
+ */
+ protected $pathDisclosureProtection = 'simple';
+
+ /** @var bool Public zone URL. */
+ protected $url;
+
+ /** @var string The base thumbnail URL. Defaults to "<url>/thumb". */
+ protected $thumbUrl;
+
+ /** @var int The number of directory levels for hash-based division of files */
+ protected $hashLevels;
+
+ /** @var int The number of directory levels for hash-based division of deleted files */
+ protected $deletedHashLevels;
+
+ /** @var int File names over this size will use the short form of thumbnail
+ * names. Short thumbnail names only have the width, parameters, and the
+ * extension.
+ */
protected $abbrvThreshold;
+ /** @var string The URL of the repo's favicon, if any */
+ protected $favicon;
+
/**
* Factory functions for creating new files
* Override these in the base class
*/
- var $fileFactory = array( 'UnregisteredLocalFile', 'newFromTitle' );
- var $oldFileFactory = false;
- var $fileFactoryKey = false, $oldFileFactoryKey = false;
+ protected $fileFactory = array( 'UnregisteredLocalFile', 'newFromTitle' );
+ protected $oldFileFactory = false;
+ protected $fileFactoryKey = false;
+ protected $oldFileFactoryKey = false;
/**
- * @param $info array|null
+ * @param array|null $info
* @throws MWException
*/
public function __construct( array $info = null ) {
@@ -72,7 +134,8 @@ class FileRepo {
|| !array_key_exists( 'name', $info )
|| !array_key_exists( 'backend', $info )
) {
- throw new MWException( __CLASS__ . " requires an array of options having both 'name' and 'backend' keys.\n" );
+ throw new MWException( __CLASS__ .
+ " requires an array of options having both 'name' and 'backend' keys.\n" );
}
// Required settings
@@ -87,7 +150,7 @@ class FileRepo {
$optionalSettings = array(
'descBaseUrl', 'scriptDirUrl', 'articleUrl', 'fetchDescription',
'thumbScriptUrl', 'pathDisclosureProtection', 'descriptionCacheExpiry',
- 'scriptExtension'
+ 'scriptExtension', 'favicon'
);
foreach ( $optionalSettings as $var ) {
if ( isset( $info[$var] ) ) {
@@ -167,13 +230,14 @@ class FileRepo {
throw new MWException( "No '$zone' zone defined in the {$this->name} repo." );
}
}
+
return $status;
}
/**
* Determine if a string is an mwrepo:// URL
*
- * @param $url string
+ * @param string $url
* @return bool
*/
public static function isVirtualUrl( $url ) {
@@ -185,7 +249,7 @@ class FileRepo {
* The suffix, if supplied, is considered to be unencoded, and will be
* URL-encoded before being returned.
*
- * @param $suffix string|bool
+ * @param string|bool $suffix
* @return string
*/
public function getVirtualUrl( $suffix = false ) {
@@ -193,6 +257,7 @@ class FileRepo {
if ( $suffix !== false ) {
$path .= '/' . rawurlencode( $suffix );
}
+
return $path;
}
@@ -201,14 +266,17 @@ class FileRepo {
*
* @param string $zone One of: public, deleted, temp, thumb
* @param string|null $ext Optional file extension
- * @return String or false
+ * @return string|bool
*/
public function getZoneUrl( $zone, $ext = null ) {
- if ( in_array( $zone, array( 'public', 'temp', 'thumb', 'transcoded' ) ) ) { // standard public zones
+ if ( in_array( $zone, array( 'public', 'temp', 'thumb', 'transcoded' ) ) ) {
+ // standard public zones
if ( $ext !== null && isset( $this->zones[$zone]['urlsByExt'][$ext] ) ) {
- return $this->zones[$zone]['urlsByExt'][$ext]; // custom URL for extension/zone
+ // custom URL for extension/zone
+ return $this->zones[$zone]['urlsByExt'][$ext];
} elseif ( isset( $this->zones[$zone]['url'] ) ) {
- return $this->zones[$zone]['url']; // custom URL for zone
+ // custom URL for zone
+ return $this->zones[$zone]['url'];
}
}
switch ( $zone ) {
@@ -228,32 +296,17 @@ class FileRepo {
}
/**
- * Get the thumb zone URL configured to be handled by scripts like thumb_handler.php.
- * This is probably only useful for internal requests, such as from a fast frontend server
- * to a slower backend server.
- *
- * Large sites may use a different host name for uploads than for wikis. In any case, the
- * wiki configuration is needed in order to use thumb.php. To avoid extracting the wiki ID
- * from the URL path, one can configure thumb_handler.php to recognize a special path on the
- * same host name as the wiki that is used for viewing thumbnails.
- *
- * @param string $zone one of: public, deleted, temp, thumb
- * @return String or false
+ * @return bool Whether non-ASCII path characters are allowed
*/
- public function getZoneHandlerUrl( $zone ) {
- if ( isset( $this->zones[$zone]['handlerUrl'] )
- && in_array( $zone, array( 'public', 'temp', 'thumb', 'transcoded' ) ) )
- {
- return $this->zones[$zone]['handlerUrl'];
- }
- return false;
+ public function backendSupportsUnicodePaths() {
+ return ( $this->getBackend()->getFeatures() & FileBackend::ATTR_UNICODE_PATHS );
}
/**
* Get the backend storage path corresponding to a virtual URL.
* Use this function wisely.
*
- * @param $url string
+ * @param string $url
* @throws MWException
* @return string
*/
@@ -273,26 +326,28 @@ class FileRepo {
if ( !$base ) {
throw new MWException( __METHOD__ . ": invalid zone: $zone" );
}
+
return $base . '/' . rawurldecode( $rel );
}
/**
* The the storage container and base path of a zone
*
- * @param $zone string
- * @return Array (container, base path) or (null, null)
+ * @param string $zone
+ * @return array (container, base path) or (null, null)
*/
protected function getZoneLocation( $zone ) {
if ( !isset( $this->zones[$zone] ) ) {
return array( null, null ); // bogus
}
+
return array( $this->zones[$zone]['container'], $this->zones[$zone]['directory'] );
}
/**
* Get the storage path corresponding to one of the zones
*
- * @param $zone string
+ * @param string $zone
* @return string|null Returns null if the zone is not defined
*/
public function getZonePath( $zone ) {
@@ -304,18 +359,19 @@ class FileRepo {
if ( $base != '' ) { // may not be set
$base = "/{$base}";
}
+
return "mwstore://$backendName/{$container}{$base}";
}
/**
* Create a new File object from the local repository
*
- * @param $title Mixed: Title object or string
- * @param $time Mixed: Time at which the image was uploaded.
- * If this is specified, the returned object will be an
- * instance of the repository's old file class instead of a
- * current file. Repositories not supporting version control
- * should return false if this parameter is set.
+ * @param Title|string $title Title object or string
+ * @param bool|string $time Time at which the image was uploaded. If this
+ * is specified, the returned object will be an instance of the
+ * repository's old file class instead of a current file. Repositories
+ * not supporting version control should return false if this parameter
+ * is set.
* @return File|null A File, or null if passed an invalid Title
*/
public function newFile( $title, $time = false ) {
@@ -339,17 +395,15 @@ class FileRepo {
* Returns false if the file does not exist. Repositories not supporting
* version control should return false if the time is specified.
*
- * @param $title Mixed: Title object or string
+ * @param Title|string $title Title object or string
* @param array $options Associative array of options:
- * time: requested time for a specific file version, or false for the
- * current version. An image object will be returned which was
- * created at the specified time (which may be archived or current).
- *
- * ignoreRedirect: If true, do not follow file redirects
- *
- * private: If true, return restricted (deleted) files if the current
- * user is allowed to view them. Otherwise, such files will not
- * be found.
+ * time: requested time for a specific file version, or false for the
+ * current version. An image object will be returned which was
+ * created at the specified time (which may be archived or current).
+ * ignoreRedirect: If true, do not follow file redirects
+ * private: If true, return restricted (deleted) files if the current
+ * user is allowed to view them. Otherwise, such files will not
+ * be found. If a User object, use that user instead of the current.
* @return File|bool False on failure
*/
public function findFile( $title, $options = array() ) {
@@ -372,7 +426,11 @@ class FileRepo {
if ( $img && $img->exists() ) {
if ( !$img->isDeleted( File::DELETED_FILE ) ) {
return $img; // always OK
- } elseif ( !empty( $options['private'] ) && $img->userCan( File::DELETED_FILE ) ) {
+ } elseif ( !empty( $options['private'] ) &&
+ $img->userCan( File::DELETED_FILE,
+ $options['private'] instanceof User ? $options['private'] : null
+ )
+ ) {
return $img;
}
}
@@ -390,9 +448,11 @@ class FileRepo {
}
if ( $img->exists() ) {
$img->redirectedFrom( $title->getDBkey() );
+
return $img;
}
}
+
return false;
}
@@ -405,9 +465,15 @@ class FileRepo {
* $findItem = array( 'title' => $title, 'private' => true );
* $findBatch = array( $findItem );
* $repo->findFiles( $findBatch );
- * @return array
+ *
+ * No title should appear in $items twice, as the result use titles as keys
+ * @param int $flags Supports:
+ * - FileRepo::NAME_AND_TIME_ONLY : return a (search title => (title,timestamp)) map.
+ * The search title uses the input titles; the other is the final post-redirect title.
+ * All titles are returned as string DB keys and the inner array is associative.
+ * @return array Map of (file name => File objects) for matches
*/
- public function findFiles( array $items ) {
+ public function findFiles( array $items, $flags = 0 ) {
$result = array();
foreach ( $items as $item ) {
if ( is_array( $item ) ) {
@@ -420,9 +486,18 @@ class FileRepo {
}
$file = $this->findFile( $title, $options );
if ( $file ) {
- $result[$file->getTitle()->getDBkey()] = $file;
+ $searchName = File::normalizeTitle( $title )->getDBkey(); // must be valid
+ if ( $flags & self::NAME_AND_TIME_ONLY ) {
+ $result[$searchName] = array(
+ 'title' => $file->getTitle()->getDBkey(),
+ 'timestamp' => $file->getTimestamp()
+ );
+ } else {
+ $result[$searchName] = $file;
+ }
}
}
+
return $result;
}
@@ -431,7 +506,7 @@ class FileRepo {
* Returns false if the file does not exist. Repositories not supporting
* version control should return false if the time is specified.
*
- * @param string $sha1 base 36 SHA-1 hash
+ * @param string $sha1 Base 36 SHA-1 hash
* @param array $options Option array, same as findFile().
* @return File|bool False on failure
*/
@@ -452,11 +527,16 @@ class FileRepo {
if ( $img && $img->exists() ) {
if ( !$img->isDeleted( File::DELETED_FILE ) ) {
return $img; // always OK
- } elseif ( !empty( $options['private'] ) && $img->userCan( File::DELETED_FILE ) ) {
+ } elseif ( !empty( $options['private'] ) &&
+ $img->userCan( File::DELETED_FILE,
+ $options['private'] instanceof User ? $options['private'] : null
+ )
+ ) {
return $img;
}
}
}
+
return false;
}
@@ -465,8 +545,8 @@ class FileRepo {
* SHA-1 content hash.
*
* STUB
- * @param $hash
- * @return array
+ * @param string $hash SHA-1 hash
+ * @return File[]
*/
public function findBySha1( $hash ) {
return array();
@@ -487,6 +567,7 @@ class FileRepo {
$result[$hash] = $files;
}
}
+
return $result;
}
@@ -531,10 +612,10 @@ class FileRepo {
}
/**
- * Get the name of an image from its title object
+ * Get the name of a file from its title object
*
- * @param $title Title
- * @return String
+ * @param Title $title
+ * @return string
*/
public function getNameFromTitle( Title $title ) {
global $wgContLang;
@@ -546,6 +627,7 @@ class FileRepo {
} else {
$name = $title->getDBkey();
}
+
return $name;
}
@@ -583,8 +665,8 @@ class FileRepo {
}
/**
- * @param $name
- * @param $levels
+ * @param string $name
+ * @param int $levels
* @return string
*/
protected static function getHashPathForLevel( $name, $levels ) {
@@ -596,6 +678,7 @@ class FileRepo {
for ( $i = 1; $i <= $levels; $i++ ) {
$path .= substr( $hash, 0, $i ) . '/';
}
+
return $path;
}
}
@@ -603,7 +686,7 @@ class FileRepo {
/**
* Get the number of hash directory levels
*
- * @return integer
+ * @return int
*/
public function getHashLevels() {
return $this->hashLevels;
@@ -621,15 +704,17 @@ class FileRepo {
/**
* Make an url to this repo
*
- * @param $query mixed Query string to append
+ * @param string $query Query string to append
* @param string $entry Entry point; defaults to index
* @return string|bool False on failure
*/
public function makeUrl( $query = '', $entry = 'index' ) {
if ( isset( $this->scriptDirUrl ) ) {
$ext = isset( $this->scriptExtension ) ? $this->scriptExtension : '.php';
+
return wfAppendQuery( "{$this->scriptDirUrl}/{$entry}{$ext}", $query );
}
+
return false;
}
@@ -642,13 +727,13 @@ class FileRepo {
* In particular, it uses the article paths as specified to the repository
* constructor, whereas local repositories use the local Title functions.
*
- * @param $name string
+ * @param string $name
* @return string
*/
public function getDescriptionUrl( $name ) {
$encName = wfUrlencode( $name );
if ( !is_null( $this->descBaseUrl ) ) {
- # "http://example.com/wiki/Image:"
+ # "http://example.com/wiki/File:"
return $this->descBaseUrl . $encName;
}
if ( !is_null( $this->articleUrl ) ) {
@@ -667,6 +752,7 @@ class FileRepo {
# and just sort of hope index.php is right. ;)
return $this->makeUrl( "title=Image:$encName" );
}
+
return false;
}
@@ -676,8 +762,8 @@ class FileRepo {
* repository's file class, since it may return invalid results. User code
* should use File::getDescriptionText().
*
- * @param string $name name of image to fetch
- * @param string $lang language to fetch it in, if any.
+ * @param string $name Name of image to fetch
+ * @param string $lang Language to fetch it in, if any.
* @return string
*/
public function getDescriptionRenderUrl( $name, $lang = null ) {
@@ -710,21 +796,22 @@ class FileRepo {
return $this->makeUrl( 'title=MediaWiki:Filepage.css&' .
wfArrayToCgi( Skin::getDynamicStylesheetQuery() ) );
}
+
return false;
}
/**
* Store a file to a given destination.
*
- * @param string $srcPath source file system path, storage path, or virtual URL
- * @param string $dstZone destination zone
- * @param string $dstRel destination relative path
- * @param $flags Integer: bitwise combination of the following flags:
- * self::DELETE_SOURCE Delete the source file after upload
- * self::OVERWRITE Overwrite an existing destination file instead of failing
- * self::OVERWRITE_SAME Overwrite the file if the destination exists and has the
- * same contents as the source
- * self::SKIP_LOCKING Skip any file locking when doing the store
+ * @param string $srcPath Source file system path, storage path, or virtual URL
+ * @param string $dstZone Destination zone
+ * @param string $dstRel Destination relative path
+ * @param int $flags Bitwise combination of the following flags:
+ * self::DELETE_SOURCE Delete the source file after upload
+ * self::OVERWRITE Overwrite an existing destination file instead of failing
+ * self::OVERWRITE_SAME Overwrite the file if the destination exists and has the
+ * same contents as the source
+ * self::SKIP_LOCKING Skip any file locking when doing the store
* @return FileRepoStatus
*/
public function store( $srcPath, $dstZone, $dstRel, $flags = 0 ) {
@@ -742,12 +829,12 @@ class FileRepo {
* Store a batch of files
*
* @param array $triplets (src, dest zone, dest rel) triplets as per store()
- * @param $flags Integer: bitwise combination of the following flags:
- * self::DELETE_SOURCE Delete the source file after upload
- * self::OVERWRITE Overwrite an existing destination file instead of failing
- * self::OVERWRITE_SAME Overwrite the file if the destination exists and has the
- * same contents as the source
- * self::SKIP_LOCKING Skip any file locking when doing the store
+ * @param int $flags Bitwise combination of the following flags:
+ * self::DELETE_SOURCE Delete the source file after upload
+ * self::OVERWRITE Overwrite an existing destination file instead of failing
+ * self::OVERWRITE_SAME Overwrite the file if the destination exists and has the
+ * same contents as the source
+ * self::SKIP_LOCKING Skip any file locking when doing the store
* @throws MWException
* @return FileRepoStatus
*/
@@ -824,8 +911,8 @@ class FileRepo {
* It will try to delete each file, but ignores any errors that may occur.
*
* @param array $files List of files to delete
- * @param $flags Integer: bitwise combination of the following flags:
- * self::SKIP_LOCKING Skip any file locking when doing the deletions
+ * @param int $flags Bitwise combination of the following flags:
+ * self::SKIP_LOCKING Skip any file locking when doing the deletions
* @return FileRepoStatus
*/
public function cleanupBatch( array $files, $flags = 0 ) {
@@ -863,11 +950,13 @@ class FileRepo {
*
* @param string $src Source file system path, storage path, or virtual URL
* @param string $dst Virtual URL or storage path
- * @param string|null $disposition Content-Disposition if given and supported
+ * @param array|string|null $options An array consisting of a key named headers
+ * listing extra headers. If a string, taken as content-disposition header.
+ * (Support for array of options new in 1.23)
* @return FileRepoStatus
*/
- final public function quickImport( $src, $dst, $disposition = null ) {
- return $this->quickImportBatch( array( array( $src, $dst, $disposition ) ) );
+ final public function quickImport( $src, $dst, $options = null ) {
+ return $this->quickImportBatch( array( array( $src, $dst, $options ) ) );
}
/**
@@ -904,7 +993,7 @@ class FileRepo {
* This is intended for copying generated thumbnails into the repo.
*
* All path parameters may be a file system path, storage path, or virtual URL.
- * When "dispositions" are given they are used as Content-Disposition if supported.
+ * When "headers" are given they are used as HTTP headers if supported.
*
* @param array $triples List of (source path, destination path, disposition)
* @return FileRepoStatus
@@ -916,11 +1005,21 @@ class FileRepo {
list( $src, $dst ) = $triple;
$src = $this->resolveToStoragePath( $src );
$dst = $this->resolveToStoragePath( $dst );
+
+ if ( !isset( $triple[2] ) ) {
+ $headers = array();
+ } elseif ( is_string( $triple[2] ) ) {
+ // back-compat
+ $headers = array( 'Content-Disposition' => $triple[2] );
+ } elseif ( is_array( $triple[2] ) && isset( $triple[2]['headers'] ) ) {
+ $headers = $triple[2]['headers'];
+ }
+ // @fixme: $headers might not be defined
$operations[] = array(
'op' => FileBackend::isStoragePath( $src ) ? 'copy' : 'store',
'src' => $src,
'dst' => $dst,
- 'disposition' => isset( $triple[2] ) ? $triple[2] : null
+ 'headers' => $headers
);
$status->merge( $this->initDirectory( dirname( $dst ) ) );
}
@@ -957,10 +1056,10 @@ class FileRepo {
* Returns a FileRepoStatus object with the file Virtual URL in the value,
* file can later be disposed using FileRepo::freeTemp().
*
- * @param string $originalName the base name of the file as specified
- * by the user. The file extension will be maintained.
- * @param string $srcPath the current location of the file.
- * @return FileRepoStatus object with the URL in the value.
+ * @param string $originalName The base name of the file as specified
+ * by the user. The file extension will be maintained.
+ * @param string $srcPath The current location of the file.
+ * @return FileRepoStatus Object with the URL in the value.
*/
public function storeTemp( $originalName, $srcPath ) {
$this->assertWritableRepo(); // fail out if read-only
@@ -979,8 +1078,8 @@ class FileRepo {
/**
* Remove a temporary file or mark it for garbage collection
*
- * @param string $virtualUrl the virtual URL returned by FileRepo::storeTemp()
- * @return Boolean: true on success, false on failure
+ * @param string $virtualUrl The virtual URL returned by FileRepo::storeTemp()
+ * @return bool True on success, false on failure
*/
public function freeTemp( $virtualUrl ) {
$this->assertWritableRepo(); // fail out if read-only
@@ -988,6 +1087,7 @@ class FileRepo {
$temp = $this->getVirtualUrl( 'temp' );
if ( substr( $virtualUrl, 0, strlen( $temp ) ) != $temp ) {
wfDebug( __METHOD__ . ": Invalid temp virtual URL\n" );
+
return false;
}
@@ -999,8 +1099,8 @@ class FileRepo {
*
* @param array $srcPaths Ordered list of source virtual URLs/storage paths
* @param string $dstPath Target file system path
- * @param $flags Integer: bitwise combination of the following flags:
- * self::DELETE_SOURCE Delete the source files
+ * @param int $flags Bitwise combination of the following flags:
+ * self::DELETE_SOURCE Delete the source files
* @return FileRepoStatus
*/
public function concatenate( array $srcPaths, $dstPath, $flags = 0 ) {
@@ -1043,12 +1143,12 @@ class FileRepo {
* Options to $options include:
* - headers : name/value map of HTTP headers to use in response to GET/HEAD requests
*
- * @param string $srcPath the source file system path, storage path, or URL
- * @param string $dstRel the destination relative path
- * @param string $archiveRel the relative path where the existing file is to
- * be archived, if there is one. Relative to the public zone root.
- * @param $flags Integer: bitfield, may be FileRepo::DELETE_SOURCE to indicate
- * that the source file should be deleted if possible
+ * @param string $srcPath The source file system path, storage path, or URL
+ * @param string $dstRel The destination relative path
+ * @param string $archiveRel The relative path where the existing file is to
+ * be archived, if there is one. Relative to the public zone root.
+ * @param int $flags Bitfield, may be FileRepo::DELETE_SOURCE to indicate
+ * that the source file should be deleted if possible
* @param array $options Optional additional parameters
* @return FileRepoStatus
*/
@@ -1075,9 +1175,9 @@ class FileRepo {
* Publish a batch of files
*
* @param array $ntuples (source, dest, archive) triplets or
- * (source, dest, archive, options) 4-tuples as per publish().
- * @param $flags Integer: bitfield, may be FileRepo::DELETE_SOURCE to indicate
- * that the source files should be deleted if possible
+ * (source, dest, archive, options) 4-tuples as per publish().
+ * @param int $flags Bitfield, may be FileRepo::DELETE_SOURCE to indicate
+ * that the source files should be deleted if possible
* @throws MWException
* @return FileRepoStatus
*/
@@ -1129,7 +1229,7 @@ class FileRepo {
// This will check if the archive file also exists and fail if does.
// This is a sanity check to avoid data loss. On Windows and Linux,
// copy() will overwrite, so the existence check is vulnerable to
- // race conditions unless an functioning LockManager is used.
+ // race conditions unless a functioning LockManager is used.
// LocalFile also uses SELECT FOR UPDATE for synchronization.
$operations[] = array(
'op' => 'copy',
@@ -1238,6 +1338,7 @@ class FileRepo {
*/
public function fileExists( $file ) {
$result = $this->fileExistsBatch( array( $file ) );
+
return $result[0];
}
@@ -1245,14 +1346,18 @@ class FileRepo {
* Checks existence of an array of files.
*
* @param array $files Virtual URLs (or storage paths) of files to check
- * @return array|bool Either array of files and existence flags, or false
+ * @return array Map of files and existence flags, or false
*/
public function fileExistsBatch( array $files ) {
+ $paths = array_map( array( $this, 'resolveToStoragePath' ), $files );
+ $this->backend->preloadFileStat( array( 'srcs' => $paths ) );
+
$result = array();
foreach ( $files as $key => $file ) {
- $file = $this->resolveToStoragePath( $file );
- $result[$key] = $this->backend->fileExists( array( 'src' => $file ) );
+ $path = $this->resolveToStoragePath( $file );
+ $result[$key] = $this->backend->fileExists( array( 'src' => $path ) );
}
+
return $result;
}
@@ -1261,10 +1366,10 @@ class FileRepo {
* If no valid deletion archive exists, this may either delete the file
* or throw an exception, depending on the preference of the repository
*
- * @param $srcRel Mixed: relative path for the file to be deleted
- * @param $archiveRel Mixed: relative path for the archive location.
- * Relative to a private archive directory.
- * @return FileRepoStatus object
+ * @param mixed $srcRel Relative path for the file to be deleted
+ * @param mixed $archiveRel Relative path for the archive location.
+ * Relative to a private archive directory.
+ * @return FileRepoStatus
*/
public function delete( $srcRel, $archiveRel ) {
$this->assertWritableRepo(); // fail out if read-only
@@ -1282,10 +1387,10 @@ class FileRepo {
* assumes a naming scheme in the deleted zone based on content hash, as
* opposed to the public zone which is assumed to be unique.
*
- * @param array $sourceDestPairs of source/destination pairs. Each element
- * is a two-element array containing the source file path relative to the
- * public root in the first element, and the archive file path relative
- * to the deleted zone root in the second element.
+ * @param array $sourceDestPairs Array of source/destination pairs. Each element
+ * is a two-element array containing the source file path relative to the
+ * public root in the first element, and the archive file path relative
+ * to the deleted zone root in the second element.
* @throws MWException
* @return FileRepoStatus
*/
@@ -1346,6 +1451,7 @@ class FileRepo {
* Delete files in the deleted directory if they are not referenced in the filearchive table
*
* STUB
+ * @param array $storageKeys
*/
public function cleanupDeletedBatch( array $storageKeys ) {
$this->assertWritableRepo();
@@ -1355,7 +1461,7 @@ class FileRepo {
* Get a relative path for a deletion archive key,
* e.g. s/z/a/ for sza251lrxrc1jad41h5mgilp8nysje52.jpg
*
- * @param $key string
+ * @param string $key
* @throws MWException
* @return string
*/
@@ -1367,6 +1473,7 @@ class FileRepo {
for ( $i = 0; $i < $this->deletedHashLevels; $i++ ) {
$path .= $key[$i] . '/';
}
+
return $path;
}
@@ -1374,7 +1481,7 @@ class FileRepo {
* If a path is a virtual URL, resolve it to a storage path.
* Otherwise, just return the path as it is.
*
- * @param $path string
+ * @param string $path
* @return string
* @throws MWException
*/
@@ -1382,6 +1489,7 @@ class FileRepo {
if ( $this->isVirtualUrl( $path ) ) {
return $this->resolveVirtualUrl( $path );
}
+
return $path;
}
@@ -1389,11 +1497,12 @@ class FileRepo {
* Get a local FS copy of a file with a given virtual URL/storage path.
* Temporary files may be purged when the file object falls out of scope.
*
- * @param $virtualUrl string
+ * @param string $virtualUrl
* @return TempFSFile|null Returns null on failure
*/
public function getLocalCopy( $virtualUrl ) {
$path = $this->resolveToStoragePath( $virtualUrl );
+
return $this->backend->getLocalCopy( array( 'src' => $path ) );
}
@@ -1402,11 +1511,12 @@ class FileRepo {
* The file is either an original or a copy. It should not be changed.
* Temporary files may be purged when the file object falls out of scope.
*
- * @param $virtualUrl string
+ * @param string $virtualUrl
* @return FSFile|null Returns null on failure.
*/
public function getLocalReference( $virtualUrl ) {
$path = $this->resolveToStoragePath( $virtualUrl );
+
return $this->backend->getLocalReference( array( 'src' => $path ) );
}
@@ -1414,57 +1524,62 @@ class FileRepo {
* Get properties of a file with a given virtual URL/storage path.
* Properties should ultimately be obtained via FSFile::getProps().
*
- * @param $virtualUrl string
- * @return Array
+ * @param string $virtualUrl
+ * @return array
*/
public function getFileProps( $virtualUrl ) {
$path = $this->resolveToStoragePath( $virtualUrl );
+
return $this->backend->getFileProps( array( 'src' => $path ) );
}
/**
* Get the timestamp of a file with a given virtual URL/storage path
*
- * @param $virtualUrl string
+ * @param string $virtualUrl
* @return string|bool False on failure
*/
public function getFileTimestamp( $virtualUrl ) {
$path = $this->resolveToStoragePath( $virtualUrl );
+
return $this->backend->getFileTimestamp( array( 'src' => $path ) );
}
/**
* Get the size of a file with a given virtual URL/storage path
*
- * @param $virtualUrl string
- * @return integer|bool False on failure
+ * @param string $virtualUrl
+ * @return int|bool False on failure
*/
public function getFileSize( $virtualUrl ) {
$path = $this->resolveToStoragePath( $virtualUrl );
+
return $this->backend->getFileSize( array( 'src' => $path ) );
}
/**
* Get the sha1 (base 36) of a file with a given virtual URL/storage path
*
- * @param $virtualUrl string
+ * @param string $virtualUrl
* @return string|bool
*/
public function getFileSha1( $virtualUrl ) {
$path = $this->resolveToStoragePath( $virtualUrl );
+
return $this->backend->getFileSha1Base36( array( 'src' => $path ) );
}
/**
* Attempt to stream a file with the given virtual URL/storage path
*
- * @param $virtualUrl string
+ * @param string $virtualUrl
* @param array $headers Additional HTTP headers to send on success
* @return bool Success
*/
public function streamFile( $virtualUrl, $headers = array() ) {
$path = $this->resolveToStoragePath( $virtualUrl );
$params = array( 'src' => $path, 'headers' => $headers );
+
return $this->backend->streamFile( $params )->isOK();
}
@@ -1473,7 +1588,7 @@ class FileRepo {
* This only acts on the current version of files, not any old versions.
* May use either the database or the filesystem.
*
- * @param $callback Array|string
+ * @param callable $callback
* @return void
*/
public function enumFiles( $callback ) {
@@ -1484,7 +1599,7 @@ class FileRepo {
* Call a callback function for every public file in the repository.
* May use either the database or the filesystem.
*
- * @param $callback Array|string
+ * @param callable $callback
* @return void
*/
protected function enumFilesInStorage( $callback ) {
@@ -1509,20 +1624,21 @@ class FileRepo {
/**
* Determine if a relative path is valid, i.e. not blank or involving directory traveral
*
- * @param $filename string
+ * @param string $filename
* @return bool
*/
public function validateFilename( $filename ) {
if ( strval( $filename ) == '' ) {
return false;
}
+
return FileBackend::isPathTraversalFree( $filename );
}
/**
* Get a callback function to use for cleaning error message parameters
*
- * @return Array
+ * @return array
*/
function getErrorCleanupFunction() {
switch ( $this->pathDisclosureProtection ) {
@@ -1539,7 +1655,7 @@ class FileRepo {
/**
* Path disclosure protection function
*
- * @param $param string
+ * @param string $param
* @return string
*/
function paranoidClean( $param ) {
@@ -1549,7 +1665,7 @@ class FileRepo {
/**
* Path disclosure protection function
*
- * @param $param string
+ * @param string $param
* @return string
*/
function passThrough( $param ) {
@@ -1559,18 +1675,20 @@ class FileRepo {
/**
* Create a new fatal error
*
+ * @param string $message
* @return FileRepoStatus
*/
public function newFatal( $message /*, parameters...*/ ) {
$params = func_get_args();
array_unshift( $params, $this );
+
return call_user_func_array( array( 'FileRepoStatus', 'newFatal' ), $params );
}
/**
* Create a new good result
*
- * @param $value null|string
+ * @param null|string $value
* @return FileRepoStatus
*/
public function newGood( $value = null ) {
@@ -1582,8 +1700,8 @@ class FileRepo {
* title object. If not, return false.
* STUB
*
- * @param $title Title of image
- * @return Bool
+ * @param Title $title Title of image
+ * @return bool
*/
public function checkRedirect( Title $title ) {
return false;
@@ -1594,9 +1712,10 @@ class FileRepo {
* Doesn't do anything for repositories that don't support image redirects.
*
* STUB
- * @param $title Title of image
+ * @param Title $title Title of image
*/
- public function invalidateImageRedirect( Title $title ) {}
+ public function invalidateImageRedirect( Title $title ) {
+ }
/**
* Get the human-readable name of the repo
@@ -1604,10 +1723,12 @@ class FileRepo {
* @return string
*/
public function getDisplayName() {
- // We don't name our own repo, return nothing
+ global $wgSitename;
+
if ( $this->isLocal() ) {
- return null;
+ return $wgSitename;
}
+
// 'shared-repo-name-wikimediacommons' is used when $wgUseInstantCommons = true
return wfMessageFallback( 'shared-repo-name-' . $this->name, 'shared-repo' )->text();
}
@@ -1616,7 +1737,7 @@ class FileRepo {
* Get the portion of the file that contains the origin file name.
* If that name is too long, then the name "thumbnail.<ext>" will be given.
*
- * @param $name string
+ * @param string $name
* @return string
*/
public function nameForThumb( $name ) {
@@ -1624,6 +1745,7 @@ class FileRepo {
$ext = FileBackend::extensionFromPath( $name );
$name = ( $ext == '' ) ? 'thumbnail' : "thumbnail.$ext";
}
+
return $name;
}
@@ -1658,6 +1780,7 @@ class FileRepo {
public function getLocalCacheKey( /*...*/ ) {
$args = func_get_args();
array_unshift( $args, 'filerepo', $this->getName() );
+
return call_user_func_array( 'wfMemcKey', $args );
}
@@ -1680,13 +1803,13 @@ class FileRepo {
),
'thumb' => array(
'container' => $this->zones['thumb']['container'],
- 'directory' => ( $this->zones['thumb']['directory'] == '' )
+ 'directory' => $this->zones['thumb']['directory'] == ''
? 'temp'
: $this->zones['thumb']['directory'] . '/temp'
),
'transcoded' => array(
'container' => $this->zones['transcoded']['container'],
- 'directory' => ( $this->zones['transcoded']['directory'] == '' )
+ 'directory' => $this->zones['transcoded']['directory'] == ''
? 'temp'
: $this->zones['transcoded']['directory'] . '/temp'
)
@@ -1701,7 +1824,7 @@ class FileRepo {
/**
* Get an UploadStash associated with this repo.
*
- * @param $user User
+ * @param User $user
* @return UploadStash
*/
public function getUploadStash( User $user = null ) {
@@ -1715,8 +1838,8 @@ class FileRepo {
* @return void
* @throws MWException
*/
- protected function assertWritableRepo() {}
-
+ protected function assertWritableRepo() {
+ }
/**
* Return information about the repository.
@@ -1725,12 +1848,24 @@ class FileRepo {
* @since 1.22
*/
public function getInfo() {
- return array(
+ $ret = array(
'name' => $this->getName(),
'displayname' => $this->getDisplayName(),
- 'rootUrl' => $this->getRootUrl(),
+ 'rootUrl' => $this->getZoneUrl( 'public' ),
'local' => $this->isLocal(),
);
+
+ $optionalSettings = array(
+ 'url', 'thumbUrl', 'initialCapital', 'descBaseUrl', 'scriptDirUrl', 'articleUrl',
+ 'fetchDescription', 'descriptionCacheExpiry', 'scriptExtension', 'favicon'
+ );
+ foreach ( $optionalSettings as $k ) {
+ if ( isset( $this->$k ) ) {
+ $ret[$k] = $this->$k;
+ }
+ }
+
+ return $ret;
}
}
diff --git a/includes/filerepo/FileRepoStatus.php b/includes/filerepo/FileRepoStatus.php
index 6f28b104..56848df2 100644
--- a/includes/filerepo/FileRepoStatus.php
+++ b/includes/filerepo/FileRepoStatus.php
@@ -29,8 +29,7 @@ class FileRepoStatus extends Status {
/**
* Factory function for fatal errors
*
- * @param $repo FileRepo
- *
+ * @param FileRepo $repo
* @return FileRepoStatus
*/
static function newFatal( $repo /*, parameters...*/ ) {
@@ -38,22 +37,24 @@ class FileRepoStatus extends Status {
$result = new self( $repo );
call_user_func_array( array( &$result, 'error' ), $params );
$result->ok = false;
+
return $result;
}
/**
- * @param $repo FileRepo
- * @param $value
+ * @param FileRepo|bool $repo Default: false
+ * @param mixed $value
* @return FileRepoStatus
*/
static function newGood( $repo = false, $value = null ) {
$result = new self( $repo );
$result->value = $value;
+
return $result;
}
/**
- * @param $repo FileRepo
+ * @param bool|FileRepo $repo
*/
function __construct( $repo = false ) {
if ( $repo ) {
diff --git a/includes/filerepo/ForeignAPIRepo.php b/includes/filerepo/ForeignAPIRepo.php
index 5eec9a50..6924f0a6 100644
--- a/includes/filerepo/ForeignAPIRepo.php
+++ b/includes/filerepo/ForeignAPIRepo.php
@@ -42,17 +42,31 @@ class ForeignAPIRepo extends FileRepo {
* Update the version every time you make breaking or significant changes. */
const VERSION = "2.1";
- var $fileFactory = array( 'ForeignAPIFile', 'newFromTitle' );
- /* Check back with Commons after a day */
- var $apiThumbCacheExpiry = 86400; /* 24*60*60 */
- /* Redownload thumbnail files after a month */
- var $fileCacheExpiry = 2592000; /* 86400*30 */
+ /**
+ * List of iiprop values for the thumbnail fetch queries.
+ * @since 1.23
+ */
+ protected static $imageInfoProps = array(
+ 'url',
+ 'thumbnail',
+ 'timestamp',
+ );
+
+ protected $fileFactory = array( 'ForeignAPIFile', 'newFromTitle' );
+ /** @var int Check back with Commons after a day (24*60*60) */
+ protected $apiThumbCacheExpiry = 86400;
+
+ /** @var int Redownload thumbnail files after a month (86400*30) */
+ protected $fileCacheExpiry = 2592000;
- protected $mQueryCache = array();
+ /** @var array */
protected $mFileExists = array();
+ /** @var array */
+ private $mQueryCache = array();
+
/**
- * @param $info array|null
+ * @param array|null $info
*/
function __construct( $info ) {
global $wgLocalFileRepo;
@@ -92,19 +106,20 @@ class ForeignAPIRepo extends FileRepo {
* Per docs in FileRepo, this needs to return false if we don't support versioned
* files. Well, we don't.
*
- * @param $title Title
- * @param $time string|bool
+ * @param Title $title
+ * @param string|bool $time
* @return File
*/
function newFile( $title, $time = false ) {
if ( $time ) {
return false;
}
+
return parent::newFile( $title, $time );
}
/**
- * @param $files array
+ * @param array $files
* @return array
*/
function fileExistsBatch( array $files ) {
@@ -126,8 +141,11 @@ class ForeignAPIRepo extends FileRepo {
}
}
- $data = $this->fetchImageQuery( array( 'titles' => implode( $files, '|' ),
- 'prop' => 'imageinfo' ) );
+ $data = $this->fetchImageQuery( array(
+ 'titles' => implode( $files, '|' ),
+ 'prop' => 'imageinfo' )
+ );
+
if ( isset( $data['query']['pages'] ) ) {
# First, get results from the query. Note we only care whether the image exists,
# not whether it has a description page.
@@ -151,11 +169,12 @@ class ForeignAPIRepo extends FileRepo {
$results[$key] = $this->mFileExists[$file];
}
}
+
return $results;
}
/**
- * @param $virtualUrl string
+ * @param string $virtualUrl
* @return bool
*/
function getFileProps( $virtualUrl ) {
@@ -163,11 +182,11 @@ class ForeignAPIRepo extends FileRepo {
}
/**
- * @param $query array
+ * @param array $query
* @return string
*/
function fetchImageQuery( $query ) {
- global $wgMemc, $wgLanguageCode;
+ global $wgLanguageCode;
$query = array_merge( $query,
array(
@@ -190,7 +209,7 @@ class ForeignAPIRepo extends FileRepo {
}
/**
- * @param $data array
+ * @param array $data
* @return bool|array
*/
function getImageInfo( $data ) {
@@ -201,11 +220,12 @@ class ForeignAPIRepo extends FileRepo {
}
}
}
+
return false;
}
/**
- * @param $hash string
+ * @param string $hash
* @return array
*/
function findBySha1( $hash ) {
@@ -224,21 +244,23 @@ class ForeignAPIRepo extends FileRepo {
$ret[] = new ForeignAPIFile( Title::makeTitle( NS_FILE, $img['name'] ), $this, $img );
}
}
+
return $ret;
}
/**
- * @param $name string
- * @param $width int
- * @param $height int
- * @param $result null
- * @param $otherParams string
+ * @param string $name
+ * @param int $width
+ * @param int $height
+ * @param array $result Out parameter that will be changed by the function.
+ * @param string $otherParams
+ *
* @return bool
*/
function getThumbUrl( $name, $width = -1, $height = -1, &$result = null, $otherParams = '' ) {
$data = $this->fetchImageQuery( array(
'titles' => 'File:' . $name,
- 'iiprop' => 'url|timestamp',
+ 'iiprop' => self::getIIProps(),
'iiurlwidth' => $width,
'iiurlheight' => $height,
'iiurlparam' => $otherParams,
@@ -248,6 +270,7 @@ class ForeignAPIRepo extends FileRepo {
if ( $data && $info && isset( $info['thumburl'] ) ) {
wfDebug( __METHOD__ . " got remote thumb " . $info['thumburl'] . "\n" );
$result = $info;
+
return $info['thumburl'];
} else {
return false;
@@ -255,17 +278,18 @@ class ForeignAPIRepo extends FileRepo {
}
/**
- * @param $name string
- * @param $width int
- * @param $height int
- * @param $otherParams string
+ * @param string $name
+ * @param int $width
+ * @param int $height
+ * @param string $otherParams
+ * @param string $lang Language code for language of error
* @return bool|MediaTransformError
* @since 1.22
*/
function getThumbError( $name, $width = -1, $height = -1, $otherParams = '', $lang = null ) {
$data = $this->fetchImageQuery( array(
'titles' => 'File:' . $name,
- 'iiprop' => 'url|timestamp',
+ 'iiprop' => self::getIIProps(),
'iiurlwidth' => $width,
'iiurlheight' => $height,
'iiurlparam' => $otherParams,
@@ -276,6 +300,7 @@ class ForeignAPIRepo extends FileRepo {
if ( $data && $info && isset( $info['thumberror'] ) ) {
wfDebug( __METHOD__ . " got remote thumb error " . $info['thumberror'] . "\n" );
+
return new MediaTransformError(
'thumbnail_error_remote',
$width,
@@ -294,10 +319,11 @@ class ForeignAPIRepo extends FileRepo {
* If the url has been requested today, get it from cache
* Otherwise retrieve remote thumb url, check for local file.
*
- * @param string $name is a dbkey form of a title
- * @param $width
- * @param $height
- * @param string $params Other rendering parameters (page number, etc) from handler's makeParamString.
+ * @param string $name Is a dbkey form of a title
+ * @param int $width
+ * @param int $height
+ * @param string $params Other rendering parameters (page number, etc)
+ * from handler's makeParamString.
* @return bool|string
*/
function getThumbUrlFromCache( $name, $width, $height, $params = "" ) {
@@ -322,6 +348,7 @@ class ForeignAPIRepo extends FileRepo {
if ( isset( $knownThumbUrls[$sizekey] ) ) {
wfDebug( __METHOD__ . ': Got thumburl from local cache: ' .
"{$knownThumbUrls[$sizekey]} \n" );
+
return $knownThumbUrls[$sizekey];
}
/* This size is not yet known */
@@ -332,6 +359,7 @@ class ForeignAPIRepo extends FileRepo {
if ( !$foreignUrl ) {
wfDebug( __METHOD__ . " Could not find thumburl\n" );
+
return false;
}
@@ -339,14 +367,17 @@ class ForeignAPIRepo extends FileRepo {
$fileName = rawurldecode( pathinfo( $foreignUrl, PATHINFO_BASENAME ) );
if ( !$this->validateFilename( $fileName ) ) {
wfDebug( __METHOD__ . " The deduced filename $fileName is not safe\n" );
+
return false;
}
$localPath = $this->getZonePath( 'thumb' ) . "/" . $this->getHashPath( $name ) . $name;
$localFilename = $localPath . "/" . $fileName;
- $localUrl = $this->getZoneUrl( 'thumb' ) . "/" . $this->getHashPath( $name ) . rawurlencode( $name ) . "/" . rawurlencode( $fileName );
+ $localUrl = $this->getZoneUrl( 'thumb' ) . "/" . $this->getHashPath( $name ) .
+ rawurlencode( $name ) . "/" . rawurlencode( $fileName );
if ( $backend->fileExists( array( 'src' => $localFilename ) )
- && isset( $metadata['timestamp'] ) ) {
+ && isset( $metadata['timestamp'] )
+ ) {
wfDebug( __METHOD__ . " Thumbnail was already downloaded before\n" );
$modified = $backend->getFileTimestamp( array( 'src' => $localFilename ) );
$remoteModified = strtotime( $metadata['timestamp'] );
@@ -356,6 +387,7 @@ class ForeignAPIRepo extends FileRepo {
/* Use our current and already downloaded thumbnail */
$knownThumbUrls[$sizekey] = $localUrl;
$wgMemc->set( $key, $knownThumbUrls, $this->apiThumbCacheExpiry );
+
return $localUrl;
}
/* There is a new Commons file, or existing thumbnail older than a month */
@@ -363,6 +395,7 @@ class ForeignAPIRepo extends FileRepo {
$thumb = self::httpGet( $foreignUrl );
if ( !$thumb ) {
wfDebug( __METHOD__ . " Could not download thumb\n" );
+
return false;
}
@@ -371,19 +404,21 @@ class ForeignAPIRepo extends FileRepo {
$params = array( 'dst' => $localFilename, 'content' => $thumb );
if ( !$backend->quickCreate( $params )->isOK() ) {
wfDebug( __METHOD__ . " could not write to thumb path '$localFilename'\n" );
+
return $foreignUrl;
}
$knownThumbUrls[$sizekey] = $localUrl;
$wgMemc->set( $key, $knownThumbUrls, $this->apiThumbCacheExpiry );
wfDebug( __METHOD__ . " got local thumb $localUrl, saving to cache \n" );
+
return $localUrl;
}
/**
* @see FileRepo::getZoneUrl()
- * @param $zone String
+ * @param string $zone
* @param string|null $ext Optional file extension
- * @return String
+ * @return string
*/
function getZoneUrl( $zone, $ext = null ) {
switch ( $zone ) {
@@ -398,7 +433,7 @@ class ForeignAPIRepo extends FileRepo {
/**
* Get the local directory corresponding to one of the basic zones
- * @param $zone string
+ * @param string $zone
* @return bool|null|string
*/
function getZonePath( $zone ) {
@@ -406,6 +441,7 @@ class ForeignAPIRepo extends FileRepo {
if ( in_array( $zone, $supported ) ) {
return parent::getZonePath( $zone );
}
+
return false;
}
@@ -450,6 +486,10 @@ class ForeignAPIRepo extends FileRepo {
$info['articlepath'] = $general['articlepath'];
$info['server'] = $general['server'];
+
+ if ( isset( $general['favicon'] ) ) {
+ $info['favicon'] = $general['favicon'];
+ }
}
return $info;
@@ -458,10 +498,10 @@ class ForeignAPIRepo extends FileRepo {
/**
* Like a Http:get request, but with custom User-Agent.
* @see Http:get
- * @param $url string
- * @param $timeout string
- * @param $options array
- * @return bool|String
+ * @param string $url
+ * @param string $timeout
+ * @param array $options
+ * @return bool|string
*/
public static function httpGet( $url, $timeout = 'default', $options = array() ) {
$options['timeout'] = $timeout;
@@ -486,10 +526,19 @@ class ForeignAPIRepo extends FileRepo {
}
/**
+ * @return string
+ * @since 1.23
+ */
+ protected static function getIIProps() {
+ return join( '|', self::$imageInfoProps );
+ }
+
+ /**
* HTTP GET request to a mediawiki API (with caching)
- * @param $target string Used in cache key creation, mostly
- * @param $query array The query parameters for the API request
- * @param $cacheTTL int Time to live for the memcached caching
+ * @param string $target Used in cache key creation, mostly
+ * @param array $query The query parameters for the API request
+ * @param int $cacheTTL Time to live for the memcached caching
+ * @return null
*/
public function httpGetCached( $target, $query, $cacheTTL = 3600 ) {
if ( $this->mApiBase ) {
@@ -526,7 +575,7 @@ class ForeignAPIRepo extends FileRepo {
}
/**
- * @param $callback Array|string
+ * @param callable $callback
* @throws MWException
*/
function enumFiles( $callback ) {
diff --git a/includes/filerepo/ForeignDBRepo.php b/includes/filerepo/ForeignDBRepo.php
index 37c65723..6e9e6add 100644
--- a/includes/filerepo/ForeignDBRepo.php
+++ b/includes/filerepo/ForeignDBRepo.php
@@ -27,17 +27,37 @@
* @ingroup FileRepo
*/
class ForeignDBRepo extends LocalRepo {
- # Settings
- var $dbType, $dbServer, $dbUser, $dbPassword, $dbName, $dbFlags,
- $tablePrefix, $hasSharedCache;
+ /** @var string */
+ protected $dbType;
+
+ /** @var string */
+ protected $dbServer;
+
+ /** @var string */
+ protected $dbUser;
+
+ /** @var string */
+ protected $dbPassword;
+
+ /** @var string */
+ protected $dbName;
+
+ /** @var string */
+ protected $dbFlags;
+
+ /** @var string */
+ protected $tablePrefix;
+
+ /** @var bool */
+ protected $hasSharedCache;
# Other stuff
- var $dbConn;
- var $fileFactory = array( 'ForeignDBFile', 'newFromTitle' );
- var $fileFromRowFactory = array( 'ForeignDBFile', 'newFromRow' );
+ protected $dbConn;
+ protected $fileFactory = array( 'ForeignDBFile', 'newFromTitle' );
+ protected $fileFromRowFactory = array( 'ForeignDBFile', 'newFromRow' );
/**
- * @param $info array|null
+ * @param array|null $info
*/
function __construct( $info ) {
parent::__construct( $info );
@@ -68,6 +88,7 @@ class ForeignDBRepo extends LocalRepo {
)
);
}
+
return $this->dbConn;
}
@@ -95,6 +116,7 @@ class ForeignDBRepo extends LocalRepo {
if ( $this->hasSharedCache() ) {
$args = func_get_args();
array_unshift( $args, $this->dbName, $this->tablePrefix );
+
return call_user_func_array( 'wfForeignMemcKey', $args );
} else {
return false;
@@ -104,4 +126,14 @@ class ForeignDBRepo extends LocalRepo {
protected function assertWritableRepo() {
throw new MWException( get_class( $this ) . ': write operations are not supported.' );
}
+
+ /**
+ * Return information about the repository.
+ *
+ * @return array
+ * @since 1.22
+ */
+ function getInfo() {
+ return FileRepo::getInfo();
+ }
}
diff --git a/includes/filerepo/ForeignDBViaLBRepo.php b/includes/filerepo/ForeignDBViaLBRepo.php
index 7951fb13..8153ffb4 100644
--- a/includes/filerepo/ForeignDBViaLBRepo.php
+++ b/includes/filerepo/ForeignDBViaLBRepo.php
@@ -27,12 +27,23 @@
* @ingroup FileRepo
*/
class ForeignDBViaLBRepo extends LocalRepo {
- var $wiki, $dbName, $tablePrefix;
- var $fileFactory = array( 'ForeignDBFile', 'newFromTitle' );
- var $fileFromRowFactory = array( 'ForeignDBFile', 'newFromRow' );
+ /** @var string */
+ protected $wiki;
+
+ /** @var string */
+ protected $dbName;
+
+ /** @var string */
+ protected $tablePrefix;
+
+ /** @var array */
+ protected $fileFactory = array( 'ForeignDBFile', 'newFromTitle' );
+
+ /** @var array */
+ protected $fileFromRowFactory = array( 'ForeignDBFile', 'newFromRow' );
/**
- * @param $info array|null
+ * @param array|null $info
*/
function __construct( $info ) {
parent::__construct( $info );
@@ -69,6 +80,7 @@ class ForeignDBViaLBRepo extends LocalRepo {
if ( $this->hasSharedCache() ) {
$args = func_get_args();
array_unshift( $args, $this->wiki );
+
return implode( ':', $args );
} else {
return false;
@@ -78,4 +90,8 @@ class ForeignDBViaLBRepo extends LocalRepo {
protected function assertWritableRepo() {
throw new MWException( get_class( $this ) . ': write operations are not supported.' );
}
+
+ public function getInfo() {
+ return FileRepo::getInfo();
+ }
}
diff --git a/includes/filerepo/LocalRepo.php b/includes/filerepo/LocalRepo.php
index 9b62243b..926fd0b8 100644
--- a/includes/filerepo/LocalRepo.php
+++ b/includes/filerepo/LocalRepo.php
@@ -29,16 +29,27 @@
* @ingroup FileRepo
*/
class LocalRepo extends FileRepo {
- var $fileFactory = array( 'LocalFile' , 'newFromTitle' );
- var $fileFactoryKey = array( 'LocalFile' , 'newFromKey' );
- var $fileFromRowFactory = array( 'LocalFile' , 'newFromRow' );
- var $oldFileFactory = array( 'OldLocalFile', 'newFromTitle' );
- var $oldFileFactoryKey = array( 'OldLocalFile', 'newFromKey' );
- var $oldFileFromRowFactory = array( 'OldLocalFile', 'newFromRow' );
+ /** @var array */
+ protected $fileFactory = array( 'LocalFile', 'newFromTitle' );
+
+ /** @var array */
+ protected $fileFactoryKey = array( 'LocalFile', 'newFromKey' );
+
+ /** @var array */
+ protected $fileFromRowFactory = array( 'LocalFile', 'newFromRow' );
+
+ /** @var array */
+ protected $oldFileFromRowFactory = array( 'OldLocalFile', 'newFromRow' );
+
+ /** @var array */
+ protected $oldFileFactory = array( 'OldLocalFile', 'newFromTitle' );
+
+ /** @var array */
+ protected $oldFileFactoryKey = array( 'OldLocalFile', 'newFromKey' );
/**
* @throws MWException
- * @param $row
+ * @param stdClass $row
* @return LocalFile
*/
function newFileFromRow( $row ) {
@@ -52,8 +63,8 @@ class LocalRepo extends FileRepo {
}
/**
- * @param $title
- * @param $archiveName
+ * @param Title $title
+ * @param string $archiveName
* @return OldLocalFile
*/
function newFromArchiveName( $title, $archiveName ) {
@@ -66,7 +77,7 @@ class LocalRepo extends FileRepo {
* interleave database locks with file operations, which is potentially a
* remote operation.
*
- * @param $storageKeys array
+ * @param array $storageKeys
*
* @return FileRepoStatus
*/
@@ -80,7 +91,7 @@ class LocalRepo extends FileRepo {
$hashPath = $this->getDeletedHashPath( $key );
$path = "$root/$hashPath$key";
$dbw->begin( __METHOD__ );
- // Check for usage in deleted/hidden files and pre-emptively
+ // Check for usage in deleted/hidden files and preemptively
// lock the key to avoid any future use until we are finished.
$deleted = $this->deletedFileHasKey( $key, 'lock' );
$hidden = $this->hiddenFileHasKey( $key, 'lock' );
@@ -97,6 +108,7 @@ class LocalRepo extends FileRepo {
}
$dbw->commit( __METHOD__ );
}
+
return $status;
}
@@ -111,6 +123,7 @@ class LocalRepo extends FileRepo {
$options = ( $lock === 'lock' ) ? array( 'FOR UPDATE' ) : array();
$dbw = $this->getMasterDB();
+
return (bool)$dbw->selectField( 'filearchive', '1',
array( 'fa_storage_group' => 'deleted', 'fa_storage_key' => $key ),
__METHOD__, $options
@@ -131,6 +144,7 @@ class LocalRepo extends FileRepo {
$ext = File::normalizeExtension( substr( $key, strcspn( $key, '.' ) + 1 ) );
$dbw = $this->getMasterDB();
+
return (bool)$dbw->selectField( 'oldimage', '1',
array( 'oi_sha1' => $sha1,
'oi_archive_name ' . $dbw->buildLike( $dbw->anyString(), ".$ext" ),
@@ -152,8 +166,8 @@ class LocalRepo extends FileRepo {
/**
* Checks if there is a redirect named as $title
*
- * @param $title Title of file
- * @return bool
+ * @param Title $title Title of file
+ * @return bool|Title
*/
function checkRedirect( Title $title ) {
global $wgMemc;
@@ -178,6 +192,7 @@ class LocalRepo extends FileRepo {
$id = $this->getArticleID( $title );
if ( !$id ) {
$wgMemc->add( $memcKey, " ", $expiry );
+
return false;
}
$dbr = $this->getSlaveDB();
@@ -191,9 +206,11 @@ class LocalRepo extends FileRepo {
if ( $row && $row->rd_namespace == NS_FILE ) {
$targetTitle = Title::makeTitle( $row->rd_namespace, $row->rd_title );
$wgMemc->add( $memcKey, $targetTitle->getDBkey(), $expiry );
+
return $targetTitle;
} else {
$wgMemc->add( $memcKey, '', $expiry );
+
return false;
}
}
@@ -202,7 +219,7 @@ class LocalRepo extends FileRepo {
* Function link Title::getArticleID().
* We can't say Title object, what database it should use, so we duplicate that function here.
*
- * @param $title Title
+ * @param Title $title
* @return bool|int|mixed
*/
protected function getArticleID( $title ) {
@@ -219,15 +236,141 @@ class LocalRepo extends FileRepo {
),
__METHOD__ //Function name
);
+
return $id;
}
+ public function findFiles( array $items, $flags = 0 ) {
+ $finalFiles = array(); // map of (DB key => corresponding File) for matches
+
+ $searchSet = array(); // map of (normalized DB key => search params)
+ foreach ( $items as $item ) {
+ if ( is_array( $item ) ) {
+ $title = File::normalizeTitle( $item['title'] );
+ if ( $title ) {
+ $searchSet[$title->getDBkey()] = $item;
+ }
+ } else {
+ $title = File::normalizeTitle( $item );
+ if ( $title ) {
+ $searchSet[$title->getDBkey()] = array();
+ }
+ }
+ }
+
+ $fileMatchesSearch = function ( File $file, array $search ) {
+ // Note: file name comparison done elsewhere (to handle redirects)
+ $user = ( !empty( $search['private'] ) && $search['private'] instanceof User )
+ ? $search['private']
+ : null;
+
+ return (
+ $file->exists() &&
+ (
+ ( empty( $search['time'] ) && !$file->isOld() ) ||
+ ( !empty( $search['time'] ) && $search['time'] === $file->getTimestamp() )
+ ) &&
+ ( !empty( $search['private'] ) || !$file->isDeleted( File::DELETED_FILE ) ) &&
+ $file->userCan( File::DELETED_FILE, $user )
+ );
+ };
+
+ $repo = $this;
+ $applyMatchingFiles = function ( ResultWrapper $res, &$searchSet, &$finalFiles )
+ use ( $repo, $fileMatchesSearch, $flags )
+ {
+ global $wgContLang;
+ $info = $repo->getInfo();
+ foreach ( $res as $row ) {
+ $file = $repo->newFileFromRow( $row );
+ // There must have been a search for this DB key, but this has to handle the
+ // cases were title capitalization is different on the client and repo wikis.
+ $dbKeysLook = array( str_replace( ' ', '_', $file->getName() ) );
+ if ( !empty( $info['initialCapital'] ) ) {
+ // Search keys for "hi.png" and "Hi.png" should use the "Hi.png file"
+ $dbKeysLook[] = $wgContLang->lcfirst( $file->getName() );
+ }
+ foreach ( $dbKeysLook as $dbKey ) {
+ if ( isset( $searchSet[$dbKey] )
+ && $fileMatchesSearch( $file, $searchSet[$dbKey] )
+ ) {
+ $finalFiles[$dbKey] = ( $flags & FileRepo::NAME_AND_TIME_ONLY )
+ ? array( 'title' => $dbKey, 'timestamp' => $file->getTimestamp() )
+ : $file;
+ unset( $searchSet[$dbKey] );
+ }
+ }
+ }
+ };
+
+ $dbr = $this->getSlaveDB();
+
+ // Query image table
+ $imgNames = array();
+ foreach ( array_keys( $searchSet ) as $dbKey ) {
+ $imgNames[] = $this->getNameFromTitle( File::normalizeTitle( $dbKey ) );
+ }
+
+ if ( count( $imgNames ) ) {
+ $res = $dbr->select( 'image',
+ LocalFile::selectFields(), array( 'img_name' => $imgNames ), __METHOD__ );
+ $applyMatchingFiles( $res, $searchSet, $finalFiles );
+ }
+
+ // Query old image table
+ $oiConds = array(); // WHERE clause array for each file
+ foreach ( $searchSet as $dbKey => $search ) {
+ if ( isset( $search['time'] ) ) {
+ $oiConds[] = $dbr->makeList(
+ array(
+ 'oi_name' => $this->getNameFromTitle( File::normalizeTitle( $dbKey ) ),
+ 'oi_timestamp' => $dbr->timestamp( $search['time'] )
+ ),
+ LIST_AND
+ );
+ }
+ }
+
+ if ( count( $oiConds ) ) {
+ $res = $dbr->select( 'oldimage',
+ OldLocalFile::selectFields(), $dbr->makeList( $oiConds, LIST_OR ), __METHOD__ );
+ $applyMatchingFiles( $res, $searchSet, $finalFiles );
+ }
+
+ // Check for redirects...
+ foreach ( $searchSet as $dbKey => $search ) {
+ if ( !empty( $search['ignoreRedirect'] ) ) {
+ continue;
+ }
+
+ $title = File::normalizeTitle( $dbKey );
+ $redir = $this->checkRedirect( $title ); // hopefully hits memcached
+
+ if ( $redir && $redir->getNamespace() == NS_FILE ) {
+ $file = $this->newFile( $redir );
+ if ( $file && $fileMatchesSearch( $file, $search ) ) {
+ $file->redirectedFrom( $title->getDBkey() );
+ if ( $flags & FileRepo::NAME_AND_TIME_ONLY ) {
+ $finalFiles[$dbKey] = array(
+ 'title' => $file->getTitle()->getDBkey(),
+ 'timestamp' => $file->getTimestamp()
+ );
+ } else {
+ $finalFiles[$dbKey] = $file;
+ }
+ }
+ }
+ }
+
+ return $finalFiles;
+ }
+
/**
* Get an array or iterator of file objects for files that have a given
* SHA-1 content hash.
*
- * @param string $hash a sha1 hash to look for
- * @return Array
+ * @param string $hash A sha1 hash to look for
+ * @return File[]
*/
function findBySha1( $hash ) {
$dbr = $this->getSlaveDB();
@@ -299,13 +442,14 @@ class LocalRepo extends FileRepo {
'img_name ' . $dbr->buildLike( $prefix, $dbr->anyString() ),
__METHOD__,
$selectOptions
- );
+ );
// Build file objects
$files = array();
foreach ( $res as $row ) {
$files[] = $this->newFileFromRow( $row );
}
+
return $files;
}
@@ -334,13 +478,14 @@ class LocalRepo extends FileRepo {
*/
function getSharedCacheKey( /*...*/ ) {
$args = func_get_args();
+
return call_user_func_array( 'wfMemcKey', $args );
}
/**
* Invalidates image redirect cache related to that image
*
- * @param $title Title of page
+ * @param Title $title Title of page
* @return void
*/
function invalidateImageRedirect( Title $title ) {
@@ -354,4 +499,18 @@ class LocalRepo extends FileRepo {
$wgMemc->set( $memcKey, ' PURGED', 12 );
}
}
+
+ /**
+ * Return information about the repository.
+ *
+ * @return array
+ * @since 1.22
+ */
+ function getInfo() {
+ global $wgFavicon;
+
+ return array_merge( parent::getInfo(), array(
+ 'favicon' => wfExpandUrl( $wgFavicon ),
+ ) );
+ }
}
diff --git a/includes/filerepo/NullRepo.php b/includes/filerepo/NullRepo.php
index dda51cea..f2b7395c 100644
--- a/includes/filerepo/NullRepo.php
+++ b/includes/filerepo/NullRepo.php
@@ -26,11 +26,11 @@
* @ingroup FileRepo
*/
class NullRepo extends FileRepo {
-
/**
- * @param $info array|null
+ * @param array|null $info
*/
- function __construct( $info ) {}
+ function __construct( $info ) {
+ }
protected function assertWritableRepo() {
throw new MWException( get_class( $this ) . ': write operations are not supported.' );
diff --git a/includes/filerepo/RepoGroup.php b/includes/filerepo/RepoGroup.php
index b2b9477a..fab42162 100644
--- a/includes/filerepo/RepoGroup.php
+++ b/includes/filerepo/RepoGroup.php
@@ -27,19 +27,28 @@
* @ingroup FileRepo
*/
class RepoGroup {
- /**
- * @var LocalRepo
- */
- var $localRepo;
+ /** @var LocalRepo */
+ protected $localRepo;
- var $foreignRepos, $reposInitialised = false;
- var $localInfo, $foreignInfo;
- var $cache;
+ /** @var FileRepo[] */
+ protected $foreignRepos;
- /**
- * @var RepoGroup
- */
+ /** @var bool */
+ protected $reposInitialised = false;
+
+ /** @var array */
+ protected $localInfo;
+
+ /** @var array */
+ protected $foreignInfo;
+
+ /** @var ProcessCacheLRU */
+ protected $cache;
+
+ /** @var RepoGroup */
protected static $instance;
+
+ /** Maximum number of cache items */
const MAX_CACHE_SIZE = 500;
/**
@@ -53,6 +62,7 @@ class RepoGroup {
}
global $wgLocalFileRepo, $wgForeignFileRepos;
self::$instance = new RepoGroup( $wgLocalFileRepo, $wgForeignFileRepos );
+
return self::$instance;
}
@@ -70,7 +80,7 @@ class RepoGroup {
* It's not enough to just create a superclass ... you have
* to get people to call into it even though all they know is RepoGroup::singleton()
*
- * @param $instance RepoGroup
+ * @param RepoGroup $instance
*/
static function setSingleton( $instance ) {
self::$instance = $instance;
@@ -80,35 +90,32 @@ class RepoGroup {
* Construct a group of file repositories.
*
* @param array $localInfo Associative array for local repo's info
- * @param array $foreignInfo of repository info arrays.
- * Each info array is an associative array with the 'class' member
- * giving the class name. The entire array is passed to the repository
- * constructor as the first parameter.
+ * @param array $foreignInfo Array of repository info arrays.
+ * Each info array is an associative array with the 'class' member
+ * giving the class name. The entire array is passed to the repository
+ * constructor as the first parameter.
*/
function __construct( $localInfo, $foreignInfo ) {
$this->localInfo = $localInfo;
$this->foreignInfo = $foreignInfo;
- $this->cache = array();
+ $this->cache = new ProcessCacheLRU( self::MAX_CACHE_SIZE );
}
/**
* Search repositories for an image.
* You can also use wfFindFile() to do this.
*
- * @param $title Title|string Title object or string
+ * @param Title|string $title Title object or string
* @param array $options Associative array of options:
- * time: requested time for an archived image, or false for the
- * current version. An image object will be returned which was
- * created at the specified time.
- *
- * ignoreRedirect: If true, do not follow file redirects
- *
- * private: If true, return restricted (deleted) files if the current
- * user is allowed to view them. Otherwise, such files will not
- * be found.
- *
- * bypassCache: If true, do not use the process-local cache of File objects
- * @return File object or false if it is not found
+ * time: requested time for an archived image, or false for the
+ * current version. An image object will be returned which was
+ * created at the specified time.
+ * ignoreRedirect: If true, do not follow file redirects
+ * private: If true, return restricted (deleted) files if the current
+ * user is allowed to view them. Otherwise, such files will not
+ * be found.
+ * bypassCache: If true, do not use the process-local cache of File objects
+ * @return File|bool False if title is not found
*/
function findFile( $title, $options = array() ) {
if ( !is_array( $options ) ) {
@@ -126,16 +133,12 @@ class RepoGroup {
# Check the cache
if ( empty( $options['ignoreRedirect'] )
&& empty( $options['private'] )
- && empty( $options['bypassCache'] ) )
- {
+ && empty( $options['bypassCache'] )
+ ) {
$time = isset( $options['time'] ) ? $options['time'] : '';
$dbkey = $title->getDBkey();
- if ( isset( $this->cache[$dbkey][$time] ) ) {
- wfDebug( __METHOD__ . ": got File:$dbkey from process cache\n" );
- # Move it to the end of the list so that we can delete the LRU entry later
- $this->pingCache( $dbkey );
- # Return the entry
- return $this->cache[$dbkey][$time];
+ if ( $this->cache->has( $dbkey, $time, 60 ) ) {
+ return $this->cache->get( $dbkey, $time );
}
$useCache = true;
} else {
@@ -158,18 +161,30 @@ class RepoGroup {
$image = $image ? $image : false; // type sanity
# Cache file existence or non-existence
if ( $useCache && ( !$image || $image->isCacheable() ) ) {
- $this->trimCache();
- $this->cache[$dbkey][$time] = $image;
+ $this->cache->set( $dbkey, $time, $image );
}
return $image;
}
/**
- * @param $inputItems array
- * @return array
+ * Search repositories for many files at once.
+ *
+ * @param array $inputItems An array of titles, or an array of findFile() options with
+ * the "title" option giving the title. Example:
+ *
+ * $findItem = array( 'title' => $title, 'private' => true );
+ * $findBatch = array( $findItem );
+ * $repo->findFiles( $findBatch );
+ *
+ * No title should appear in $items twice, as the result use titles as keys
+ * @param int $flags Supports:
+ * - FileRepo::NAME_AND_TIME_ONLY : return a (search title => (title,timestamp)) map.
+ * The search title uses the input titles; the other is the final post-redirect title.
+ * All titles are returned as string DB keys and the inner array is associative.
+ * @return array Map of (file name => File objects) for matches
*/
- function findFiles( $inputItems ) {
+ function findFiles( array $inputItems, $flags = 0 ) {
if ( !$this->reposInitialised ) {
$this->initialiseRepos();
}
@@ -185,7 +200,7 @@ class RepoGroup {
}
}
- $images = $this->localRepo->findFiles( $items );
+ $images = $this->localRepo->findFiles( $items, $flags );
foreach ( $this->foreignRepos as $repo ) {
// Remove found files from $items
@@ -193,15 +208,16 @@ class RepoGroup {
unset( $items[$name] );
}
- $images = array_merge( $images, $repo->findFiles( $items ) );
+ $images = array_merge( $images, $repo->findFiles( $items, $flags ) );
}
+
return $images;
}
/**
* Interface for FileRepo::checkRedirect()
- * @param $title Title
- * @return bool
+ * @param Title $title
+ * @return bool|Title
*/
function checkRedirect( Title $title ) {
if ( !$this->reposInitialised ) {
@@ -212,12 +228,14 @@ class RepoGroup {
if ( $redir ) {
return $redir;
}
+
foreach ( $this->foreignRepos as $repo ) {
$redir = $repo->checkRedirect( $title );
if ( $redir ) {
return $redir;
}
}
+
return false;
}
@@ -225,9 +243,9 @@ class RepoGroup {
* Find an instance of the file with this key, created at the specified time
* Returns false if the file does not exist.
*
- * @param string $hash base 36 SHA-1 hash
+ * @param string $hash Base 36 SHA-1 hash
* @param array $options Option array, same as findFile()
- * @return File object or false if it is not found
+ * @return File|bool File object or false if it is not found
*/
function findFileFromKey( $hash, $options = array() ) {
if ( !$this->reposInitialised ) {
@@ -243,14 +261,15 @@ class RepoGroup {
}
}
}
+
return $file;
}
/**
* Find all instances of files with this key
*
- * @param string $hash base 36 SHA-1 hash
- * @return Array of File objects
+ * @param string $hash Base 36 SHA-1 hash
+ * @return File[]
*/
function findBySha1( $hash ) {
if ( !$this->reposInitialised ) {
@@ -262,14 +281,15 @@ class RepoGroup {
$result = array_merge( $result, $repo->findBySha1( $hash ) );
}
usort( $result, 'File::compare' );
+
return $result;
}
/**
* Find all instances of files with this keys
*
- * @param array $hashes base 36 SHA-1 hashes
- * @return Array of array of File objects
+ * @param array $hashes Base 36 SHA-1 hashes
+ * @return array Array of array of File objects
*/
function findBySha1s( array $hashes ) {
if ( !$this->reposInitialised ) {
@@ -284,12 +304,13 @@ class RepoGroup {
foreach ( $result as $hash => $files ) {
usort( $result[$hash], 'File::compare' );
}
+
return $result;
}
/**
* Get the repo instance with a given key.
- * @param $index string|int
+ * @param string|int $index
* @return bool|LocalRepo
*/
function getRepo( $index ) {
@@ -307,7 +328,7 @@ class RepoGroup {
/**
* Get the repo instance by its name
- * @param $name string
+ * @param string $name
* @return bool
*/
function getRepoByName( $name ) {
@@ -319,6 +340,7 @@ class RepoGroup {
return $repo;
}
}
+
return false;
}
@@ -336,25 +358,32 @@ class RepoGroup {
* Call a function for each foreign repo, with the repo object as the
* first parameter.
*
- * @param $callback Callback: the function to call
- * @param array $params optional additional parameters to pass to the function
+ * @param callable $callback The function to call
+ * @param array $params Optional additional parameters to pass to the function
* @return bool
*/
function forEachForeignRepo( $callback, $params = array() ) {
+ if ( !$this->reposInitialised ) {
+ $this->initialiseRepos();
+ }
foreach ( $this->foreignRepos as $repo ) {
$args = array_merge( array( $repo ), $params );
if ( call_user_func_array( $callback, $args ) ) {
return true;
}
}
+
return false;
}
/**
* Does the installation have any foreign repos set up?
- * @return Boolean
+ * @return bool
*/
function hasForeignRepos() {
+ if ( !$this->reposInitialised ) {
+ $this->initialiseRepos();
+ }
return (bool)$this->foreignRepos;
}
@@ -376,17 +405,20 @@ class RepoGroup {
/**
* Create a repo class based on an info structure
+ * @param array $info
+ * @return FileRepo
*/
protected function newRepo( $info ) {
$class = $info['class'];
+
return new $class( $info );
}
/**
* Split a virtual URL into repo, zone and rel parts
- * @param $url string
+ * @param string $url
* @throws MWException
- * @return array containing repo, zone and rel
+ * @return array Containing repo, zone and rel
*/
function splitVirtualUrl( $url ) {
if ( substr( $url, 0, 9 ) != 'mwrepo://' ) {
@@ -397,11 +429,12 @@ class RepoGroup {
if ( count( $bits ) != 3 ) {
throw new MWException( __METHOD__ . ": invalid mwrepo URL: $url" );
}
+
return $bits;
}
/**
- * @param $fileName string
+ * @param string $fileName
* @return array
*/
function getFileProps( $fileName ) {
@@ -411,6 +444,7 @@ class RepoGroup {
$repoName = 'local';
}
$repo = $this->getRepo( $repoName );
+
return $repo->getFileProps( $fileName );
} else {
return FSFile::getPropsFromPath( $fileName );
@@ -418,40 +452,14 @@ class RepoGroup {
}
/**
- * Move a cache entry to the top (such as when accessed)
- */
- protected function pingCache( $key ) {
- if ( isset( $this->cache[$key] ) ) {
- $tmp = $this->cache[$key];
- unset( $this->cache[$key] );
- $this->cache[$key] = $tmp;
- }
- }
-
- /**
- * Limit cache memory
- */
- protected function trimCache() {
- while ( count( $this->cache ) >= self::MAX_CACHE_SIZE ) {
- reset( $this->cache );
- $key = key( $this->cache );
- wfDebug( __METHOD__ . ": evicting $key\n" );
- unset( $this->cache[$key] );
- }
- }
-
- /**
* Clear RepoGroup process cache used for finding a file
- * @param $title Title|null Title of the file or null to clear all files
+ * @param Title|null $title Title of the file or null to clear all files
*/
public function clearCache( Title $title = null ) {
if ( $title == null ) {
- $this->cache = array();
+ $this->cache->clear();
} else {
- $dbKey = $title->getDBkey();
- if ( isset( $this->cache[$dbKey] ) ) {
- unset( $this->cache[$dbKey] );
- }
+ $this->cache->clear( $title->getDBkey() );
}
}
}
diff --git a/includes/filerepo/file/ArchivedFile.php b/includes/filerepo/file/ArchivedFile.php
index 749f11a5..5b0d8e2b 100644
--- a/includes/filerepo/file/ArchivedFile.php
+++ b/includes/filerepo/file/ArchivedFile.php
@@ -27,40 +27,73 @@
* @ingroup FileAbstraction
*/
class ArchivedFile {
- /**#@+
- * @private
- */
- var $id, # filearchive row ID
- $name, # image name
- $group, # FileStore storage group
- $key, # FileStore sha1 key
- $size, # file dimensions
- $bits, # size in bytes
- $width, # width
- $height, # height
- $metadata, # metadata string
- $mime, # mime type
- $media_type, # media type
- $description, # upload description
- $user, # user ID of uploader
- $user_text, # user name of uploader
- $timestamp, # time of upload
- $dataLoaded, # Whether or not all this has been loaded from the database (loadFromXxx)
- $deleted, # Bitfield akin to rev_deleted
- $sha1, # sha1 hash of file content
- $pageCount,
- $archive_name;
+ /** @var int Filearchive row ID */
+ private $id;
- /**
- * @var MediaHandler
- */
- var $handler;
- /**
- * @var Title
+ /** @var string File name */
+ private $name;
+
+ /** @var string FileStore storage group */
+ private $group;
+
+ /** @var string FileStore SHA-1 key */
+ private $key;
+
+ /** @var int File size in bytes */
+ private $size;
+
+ /** @var int Size in bytes */
+ private $bits;
+
+ /** @var int Width */
+ private $width;
+
+ /** @var int Height */
+ private $height;
+
+ /** @var string Metadata string */
+ private $metadata;
+
+ /** @var string MIME type */
+ private $mime;
+
+ /** @var string Media type */
+ private $media_type;
+
+ /** @var string Upload description */
+ private $description;
+
+ /** @var int User ID of uploader */
+ private $user;
+
+ /** @var string User name of uploader */
+ private $user_text;
+
+ /** @var string Time of upload */
+ private $timestamp;
+
+ /** @var bool Whether or not all this has been loaded from the database (loadFromXxx) */
+ private $dataLoaded;
+
+ /** @var int Bitfield akin to rev_deleted */
+ private $deleted;
+
+ /** @var string SHA-1 hash of file content */
+ private $sha1;
+
+ /** @var string Number of pages of a multipage document, or false for
+ * documents which aren't multipage documents
*/
- var $title; # image title
+ private $pageCount;
+
+ /** @var string Original base filename */
+ private $archive_name;
- /**#@-*/
+ /** @var MediaHandler */
+ protected $handler;
+
+ /** @var Title */
+ protected $title; # image title
/**
* @throws MWException
@@ -162,13 +195,13 @@ class ArchivedFile {
/**
* Loads a file object from the filearchive table
*
- * @param $row
- *
+ * @param stdClass $row
* @return ArchivedFile
*/
public static function newFromRow( $row ) {
$file = new ArchivedFile( Title::makeTitle( NS_FILE, $row->fa_name ) );
$file->loadFromRow( $row );
+
return $file;
}
@@ -204,7 +237,7 @@ class ArchivedFile {
/**
* Load ArchivedFile object fields from a DB row.
*
- * @param $row Object database row
+ * @param stdClass $row Object database row
* @since 1.21
*/
public function loadFromRow( $row ) {
@@ -231,6 +264,9 @@ class ArchivedFile {
// old row, populate from key
$this->sha1 = LocalRepo::getHashFromKey( $this->key );
}
+ if ( !$this->title ) {
+ $this->title = Title::makeTitleSafe( NS_FILE, $row->fa_name );
+ }
}
/**
@@ -239,6 +275,9 @@ class ArchivedFile {
* @return Title
*/
public function getTitle() {
+ if ( !$this->title ) {
+ $this->load();
+ }
return $this->title;
}
@@ -248,6 +287,10 @@ class ArchivedFile {
* @return string
*/
public function getName() {
+ if ( $this->name === false ) {
+ $this->load();
+ }
+
return $this->name;
}
@@ -256,6 +299,7 @@ class ArchivedFile {
*/
public function getID() {
$this->load();
+
return $this->id;
}
@@ -264,6 +308,7 @@ class ArchivedFile {
*/
public function exists() {
$this->load();
+
return $this->exists;
}
@@ -273,6 +318,7 @@ class ArchivedFile {
*/
public function getKey() {
$this->load();
+
return $this->key;
}
@@ -298,6 +344,7 @@ class ArchivedFile {
*/
public function getWidth() {
$this->load();
+
return $this->width;
}
@@ -307,6 +354,7 @@ class ArchivedFile {
*/
public function getHeight() {
$this->load();
+
return $this->height;
}
@@ -316,6 +364,7 @@ class ArchivedFile {
*/
public function getMetadata() {
$this->load();
+
return $this->metadata;
}
@@ -325,6 +374,7 @@ class ArchivedFile {
*/
public function getSize() {
$this->load();
+
return $this->size;
}
@@ -334,15 +384,17 @@ class ArchivedFile {
*/
public function getBits() {
$this->load();
+
return $this->bits;
}
/**
- * Returns the mime type of the file.
+ * Returns the MIME type of the file.
* @return string
*/
public function getMimeType() {
$this->load();
+
return $this->mime;
}
@@ -354,12 +406,14 @@ class ArchivedFile {
if ( !isset( $this->handler ) ) {
$this->handler = MediaHandler::getHandler( $this->getMimeType() );
}
+
return $this->handler;
}
/**
* Returns the number of pages of a multipage document, or false for
* documents which aren't multipage documents
+ * @return bool|int
*/
function pageCount() {
if ( !isset( $this->pageCount ) ) {
@@ -369,6 +423,7 @@ class ArchivedFile {
$this->pageCount = false;
}
}
+
return $this->pageCount;
}
@@ -379,6 +434,7 @@ class ArchivedFile {
*/
public function getMediaType() {
$this->load();
+
return $this->media_type;
}
@@ -389,6 +445,7 @@ class ArchivedFile {
*/
public function getTimestamp() {
$this->load();
+
return wfTimestamp( TS_MW, $this->timestamp );
}
@@ -400,29 +457,40 @@ class ArchivedFile {
*/
function getSha1() {
$this->load();
+
return $this->sha1;
}
/**
- * Return the user ID of the uploader.
+ * Returns ID or name of user who uploaded the file
*
- * @return int
+ * @note Prior to MediaWiki 1.23, this method always
+ * returned the user id, and was inconsistent with
+ * the rest of the file classes.
+ * @param string $type 'text' or 'id'
+ * @return int|string
+ * @throws MWException
*/
- public function getUser() {
+ public function getUser( $type = 'text' ) {
$this->load();
- if ( $this->isDeleted( File::DELETED_USER ) ) {
- return 0;
- } else {
+
+ if ( $type == 'text' ) {
+ return $this->user_text;
+ } elseif ( $type == 'id' ) {
return $this->user;
}
+
+ throw new MWException( "Unknown type '$type'." );
}
/**
* Return the user name of the uploader.
*
+ * @deprecated since 1.23 Use getUser( 'text' ) instead.
* @return string
*/
public function getUserText() {
+ wfDeprecated( __METHOD__, '1.23' );
$this->load();
if ( $this->isDeleted( File::DELETED_USER ) ) {
return 0;
@@ -452,6 +520,7 @@ class ArchivedFile {
*/
public function getRawUser() {
$this->load();
+
return $this->user;
}
@@ -462,6 +531,7 @@ class ArchivedFile {
*/
public function getRawUserText() {
$this->load();
+
return $this->user_text;
}
@@ -472,6 +542,7 @@ class ArchivedFile {
*/
public function getRawDescription() {
$this->load();
+
return $this->description;
}
@@ -481,29 +552,33 @@ class ArchivedFile {
*/
public function getVisibility() {
$this->load();
+
return $this->deleted;
}
/**
* for file or revision rows
*
- * @param $field Integer: one of DELETED_* bitfield constants
+ * @param int $field One of DELETED_* bitfield constants
* @return bool
*/
public function isDeleted( $field ) {
$this->load();
+
return ( $this->deleted & $field ) == $field;
}
/**
* Determine if the current user is allowed to view a particular
* field of this FileStore image file, if it's marked as deleted.
- * @param $field Integer
- * @param $user User object to check, or null to use $wgUser
+ * @param int $field
+ * @param null|User $user User object to check, or null to use $wgUser
* @return bool
*/
public function userCan( $field, User $user = null ) {
$this->load();
- return Revision::userCanBitfield( $this->deleted, $field, $user );
+
+ $title = $this->getTitle();
+ return Revision::userCanBitfield( $this->deleted, $field, $user, $title ? : null );
}
}
diff --git a/includes/filerepo/file/File.php b/includes/filerepo/file/File.php
index ec5f927b..b574c5e7 100644
--- a/includes/filerepo/file/File.php
+++ b/includes/filerepo/file/File.php
@@ -91,45 +91,67 @@ abstract class File {
* The following member variables are not lazy-initialised
*/
- /**
- * @var FileRepo|bool
- */
- var $repo;
+ /** @var FileRepo|LocalRepo|ForeignAPIRepo|bool */
+ public $repo;
- /**
- * @var Title
- */
- var $title;
+ /** @var Title|string|bool */
+ protected $title;
- var $lastError, $redirected, $redirectedTitle;
+ /** @var string Text of last error */
+ protected $lastError;
- /**
- * @var FSFile|bool False if undefined
- */
+ /** @var string Main part of the title, with underscores (Title::getDBkey) */
+ protected $redirected;
+
+ /** @var Title */
+ protected $redirectedTitle;
+
+ /** @var FSFile|bool False if undefined */
protected $fsFile;
- /**
- * @var MediaHandler
- */
+ /** @var MediaHandler */
protected $handler;
- /**
- * @var string
+ /** @var string The URL corresponding to one of the four basic zones */
+ protected $url;
+
+ /** @var string File extension */
+ protected $extension;
+
+ /** @var string The name of a file from its title object */
+ protected $name;
+
+ /** @var string The storage path corresponding to one of the zones */
+ protected $path;
+
+ /** @var string Relative path including trailing slash */
+ protected $hashPath;
+
+ /** @var string Number of pages of a multipage document, or false for
+ * documents which aren't multipage documents
*/
- protected $url, $extension, $name, $path, $hashPath, $pageCount, $transformScript;
+ protected $pageCount;
+ /** @var string URL of transformscript (for example thumb.php) */
+ protected $transformScript;
+
+ /** @var Title */
protected $redirectTitle;
- /**
- * @var bool
- */
- protected $canRender, $isSafeFile;
+ /** @var bool Wether the output of transform() for this file is likely to be valid. */
+ protected $canRender;
- /**
- * @var string Required Repository class type
+ /** @var bool Wether this media file is in a format that is unlikely to
+ * contain viruses or malicious content
*/
+ protected $isSafeFile;
+
+ /** @var string Required Repository class type */
protected $repoClass = 'FileRepo';
+ /** @var array Cache of tmp filepaths pointing to generated bucket thumbnails, keyed by width */
+ protected $tmpBucketedThumbCache = array();
+
/**
* Call this constructor from child classes.
*
@@ -137,8 +159,8 @@ abstract class File {
* may return false or throw exceptions if they are not set.
* Most subclasses will want to call assertRepoDefined() here.
*
- * @param $title Title|string|bool
- * @param $repo FileRepo|bool
+ * @param Title|string|bool $title
+ * @param FileRepo|bool $repo
*/
function __construct( $title, $repo ) {
if ( $title !== false ) { // subclasses may not use MW titles
@@ -152,7 +174,7 @@ abstract class File {
* Given a string or Title object return either a
* valid Title object with namespace NS_FILE or null
*
- * @param $title Title|string
+ * @param Title|string $title
* @param string|bool $exception Use 'exception' to throw an error on bad titles
* @throws MWException
* @return Title|null
@@ -174,6 +196,7 @@ abstract class File {
if ( !$ret && $exception !== false ) {
throw new MWException( "`$title` is not a valid file title." );
}
+
return $ret;
}
@@ -183,6 +206,7 @@ abstract class File {
return null;
} else {
$this->$name = call_user_func( $function );
+
return $this->$name;
}
}
@@ -214,7 +238,7 @@ abstract class File {
/**
* Checks if file extensions are compatible
*
- * @param $old File Old file
+ * @param File $old Old file
* @param string $new New name
*
* @return bool|null
@@ -224,6 +248,7 @@ abstract class File {
$n = strrpos( $new, '.' );
$newExt = self::normalizeExtension( $n ? substr( $new, $n + 1 ) : '' );
$mimeMagic = MimeMagic::singleton();
+
return $mimeMagic->isMatchingExtension( $newExt, $oldMime );
}
@@ -232,7 +257,8 @@ abstract class File {
* Called by ImagePage
* STUB
*/
- function upgradeRow() {}
+ function upgradeRow() {
+ }
/**
* Split an internet media type into its two components; if not
@@ -252,10 +278,9 @@ abstract class File {
/**
* Callback for usort() to do file sorts by name
*
- * @param $a File
- * @param $b File
- *
- * @return Integer: result of name comparison
+ * @param File $a
+ * @param File $b
+ * @return int Result of name comparison
*/
public static function compare( File $a, File $b ) {
return strcmp( $a->getName(), $b->getName() );
@@ -271,6 +296,7 @@ abstract class File {
$this->assertRepoDefined();
$this->name = $this->repo->getNameFromTitle( $this->title );
}
+
return $this->name;
}
@@ -285,6 +311,7 @@ abstract class File {
$this->extension = self::normalizeExtension(
$n ? substr( $this->getName(), $n + 1 ) : '' );
}
+
return $this->extension;
}
@@ -306,6 +333,7 @@ abstract class File {
if ( $this->redirected ) {
return $this->getRedirectedTitle();
}
+
return $this->title;
}
@@ -320,6 +348,7 @@ abstract class File {
$ext = $this->getExtension();
$this->url = $this->repo->getZoneUrl( 'public', $ext ) . '/' . $this->getUrlRel();
}
+
return $this->url;
}
@@ -328,7 +357,7 @@ abstract class File {
* Upload URL paths _may or may not_ be fully qualified, so
* we check. Local paths are assumed to belong on $wgServer.
*
- * @return String
+ * @return string
*/
public function getFullUrl() {
return wfExpandUrl( $this->getUrl(), PROTO_RELATIVE );
@@ -351,6 +380,7 @@ abstract class File {
} else {
wfDebug( __METHOD__ . ': supposed to render ' . $this->getName() .
' (' . $this->getMimeType() . "), but can't!\n" );
+
return $this->getURL(); #hm... return NULL?
}
} else {
@@ -376,6 +406,7 @@ abstract class File {
$this->assertRepoDefined();
$this->path = $this->repo->getZonePath( 'public' ) . '/' . $this->getRel();
}
+
return $this->path;
}
@@ -394,6 +425,7 @@ abstract class File {
$this->fsFile = false; // null => false; cache negative hits
}
}
+
return ( $this->fsFile )
? $this->fsFile->getPath()
: false;
@@ -406,9 +438,8 @@ abstract class File {
* STUB
* Overridden by LocalFile, UnregisteredLocalFile
*
- * @param $page int
- *
- * @return number
+ * @param int $page
+ * @return int|bool
*/
public function getWidth( $page = 1 ) {
return false;
@@ -421,20 +452,62 @@ abstract class File {
* STUB
* Overridden by LocalFile, UnregisteredLocalFile
*
- * @param $page int
- *
- * @return bool|number False on failure
+ * @param int $page
+ * @return bool|int False on failure
*/
public function getHeight( $page = 1 ) {
return false;
}
/**
+ * Return the smallest bucket from $wgThumbnailBuckets which is at least
+ * $wgThumbnailMinimumBucketDistance larger than $desiredWidth. The returned bucket, if any,
+ * will always be bigger than $desiredWidth.
+ *
+ * @param int $desiredWidth
+ * @param int $page
+ * @return bool|int
+ */
+ public function getThumbnailBucket( $desiredWidth, $page = 1 ) {
+ global $wgThumbnailBuckets, $wgThumbnailMinimumBucketDistance;
+
+ $imageWidth = $this->getWidth( $page );
+
+ if ( $imageWidth === false ) {
+ return false;
+ }
+
+ if ( $desiredWidth > $imageWidth ) {
+ return false;
+ }
+
+ if ( !$wgThumbnailBuckets ) {
+ return false;
+ }
+
+ $sortedBuckets = $wgThumbnailBuckets;
+
+ sort( $sortedBuckets );
+
+ foreach ( $sortedBuckets as $bucket ) {
+ if ( $bucket > $imageWidth ) {
+ return false;
+ }
+
+ if ( $bucket - $wgThumbnailMinimumBucketDistance > $desiredWidth ) {
+ return $bucket;
+ }
+ }
+
+ // Image is bigger than any available bucket
+ return false;
+ }
+
+ /**
* Returns ID or name of user who uploaded the file
* STUB
*
* @param string $type 'text' or 'id'
- *
* @return string|int
*/
public function getUser( $type = 'text' ) {
@@ -444,7 +517,7 @@ abstract class File {
/**
* Get the duration of a media file in seconds
*
- * @return number
+ * @return int
*/
public function getLength() {
$handler = $this->getHandler();
@@ -470,11 +543,47 @@ abstract class File {
}
/**
+ * Gives a (possibly empty) list of languages to render
+ * the file in.
+ *
+ * If the file doesn't have translations, or if the file
+ * format does not support that sort of thing, returns
+ * an empty array.
+ *
+ * @return array
+ * @since 1.23
+ */
+ public function getAvailableLanguages() {
+ $handler = $this->getHandler();
+ if ( $handler ) {
+ return $handler->getAvailableLanguages( $this );
+ } else {
+ return array();
+ }
+ }
+
+ /**
+ * In files that support multiple language, what is the default language
+ * to use if none specified.
+ *
+ * @return string Lang code, or null if filetype doesn't support multiple languages.
+ * @since 1.23
+ */
+ public function getDefaultRenderLanguage() {
+ $handler = $this->getHandler();
+ if ( $handler ) {
+ return $handler->getDefaultRenderLanguage( $this );
+ } else {
+ return null;
+ }
+ }
+
+ /**
* Will the thumbnail be animated if one would expect it to be.
*
* Currently used to add a warning to the image description page
*
- * @return bool false if the main image is both animated
+ * @return bool False if the main image is both animated
* and the thumbnail is not. In all other cases must return
* true. If image is not renderable whatsoever, should
* return true.
@@ -506,18 +615,35 @@ abstract class File {
* Get handler-specific metadata
* Overridden by LocalFile, UnregisteredLocalFile
* STUB
- * @return bool
+ * @return bool|array
*/
public function getMetadata() {
return false;
}
/**
+ * Like getMetadata but returns a handler independent array of common values.
+ * @see MediaHandler::getCommonMetaArray()
+ * @return array|bool Array or false if not supported
+ * @since 1.23
+ */
+ public function getCommonMetaArray() {
+ $handler = $this->getHandler();
+
+ if ( !$handler ) {
+ return false;
+ }
+
+ return $handler->getCommonMetaArray( $this );
+ }
+
+ /**
* get versioned metadata
*
- * @param $metadata Mixed Array or String of (serialized) metadata
- * @param $version integer version number.
- * @return Array containing metadata, or what was passed to it on fail (unserializing if not array)
+ * @param array|string $metadata Array or string of (serialized) metadata
+ * @param int $version Version number.
+ * @return array Array containing metadata, or what was passed to it on fail
+ * (unserializing if not array)
*/
public function convertMetadataVersion( $metadata, $version ) {
$handler = $this->getHandler();
@@ -553,7 +679,7 @@ abstract class File {
}
/**
- * Returns the mime type of the file.
+ * Returns the MIME type of the file.
* Overridden by LocalFile, UnregisteredLocalFile
* STUB
*
@@ -588,8 +714,9 @@ abstract class File {
*/
function canRender() {
if ( !isset( $this->canRender ) ) {
- $this->canRender = $this->getHandler() && $this->handler->canRender( $this );
+ $this->canRender = $this->getHandler() && $this->handler->canRender( $this ) && $this->exists();
}
+
return $this->canRender;
}
@@ -639,8 +766,9 @@ abstract class File {
*/
function isSafeFile() {
if ( !isset( $this->isSafeFile ) ) {
- $this->isSafeFile = $this->_getIsSafeFile();
+ $this->isSafeFile = $this->getIsSafeFileUncached();
}
+
return $this->isSafeFile;
}
@@ -658,7 +786,7 @@ abstract class File {
*
* @return bool
*/
- protected function _getIsSafeFile() {
+ protected function getIsSafeFileUncached() {
global $wgTrustedMediaFormats;
if ( $this->allowInlineDisplay() ) {
@@ -713,7 +841,7 @@ abstract class File {
*
* Overridden by LocalFile to avoid unnecessary stat calls.
*
- * @return boolean Whether file exists in the repository.
+ * @return bool Whether file exists in the repository.
*/
public function exists() {
return $this->getPath() && $this->repo->fileExists( $this->path );
@@ -723,7 +851,7 @@ abstract class File {
* Returns true if file exists in the repository and can be included in a page.
* It would be unsafe to include private images, making public thumbnails inadvertently
*
- * @return boolean Whether file exists in the repository and is includable.
+ * @return bool Whether file exists in the repository and is includable.
*/
public function isVisible() {
return $this->exists();
@@ -742,13 +870,14 @@ abstract class File {
}
}
}
+
return $this->transformScript;
}
/**
* Get a ThumbnailImage which is the same size as the source
*
- * @param $handlerParams array
+ * @param array $handlerParams
*
* @return string
*/
@@ -760,6 +889,9 @@ abstract class File {
return $this->iconThumb();
}
$hp['width'] = $width;
+ // be sure to ignore any height specification as well (bug 62258)
+ unset( $hp['height'] );
+
return $this->transform( $hp );
}
@@ -768,14 +900,15 @@ abstract class File {
* Use File::THUMB_FULL_NAME to always get a name like "<params>-<source>".
* Otherwise, the format may be "<params>-<source>" or "<params>-thumbnail.<ext>".
*
- * @param array $params handler-specific parameters
- * @param $flags integer Bitfield that supports THUMB_* constants
+ * @param array $params Handler-specific parameters
+ * @param int $flags Bitfield that supports THUMB_* constants
* @return string
*/
public function thumbName( $params, $flags = 0 ) {
$name = ( $this->repo && !( $flags & self::THUMB_FULL_NAME ) )
? $this->repo->nameForThumb( $this->getName() )
: $this->getName();
+
return $this->generateThumbName( $name, $params );
}
@@ -784,7 +917,6 @@ abstract class File {
*
* @param string $name
* @param array $params Parameters which will be passed to MediaHandler::makeParamString
- *
* @return string
*/
public function generateThumbName( $name, $params ) {
@@ -792,12 +924,13 @@ abstract class File {
return null;
}
$extension = $this->getExtension();
- list( $thumbExt, $thumbMime ) = $this->handler->getThumbType(
+ list( $thumbExt, ) = $this->getHandler()->getThumbType(
$extension, $this->getMimeType(), $params );
- $thumbName = $this->handler->makeParamString( $params ) . '-' . $name;
+ $thumbName = $this->getHandler()->makeParamString( $params ) . '-' . $name;
if ( $thumbExt != $extension ) {
$thumbName .= ".$thumbExt";
}
+
return $thumbName;
}
@@ -813,8 +946,8 @@ abstract class File {
* specified, the generated image will be no bigger than width x height,
* and will also have correct aspect ratio.
*
- * @param $width Integer: maximum width of the generated thumbnail
- * @param $height Integer: maximum height of the image (optional)
+ * @param int $width Maximum width of the generated thumbnail
+ * @param int $height Maximum height of the image (optional)
*
* @return string
*/
@@ -824,9 +957,10 @@ abstract class File {
$params['height'] = $height;
}
$thumb = $this->transform( $params );
- if ( is_null( $thumb ) || $thumb->isError() ) {
+ if ( !$thumb || $thumb->isError() ) {
return '';
}
+
return $thumb->getUrl();
}
@@ -835,8 +969,8 @@ abstract class File {
*
* @param string $thumbPath Thumbnail storage path
* @param string $thumbUrl Thumbnail URL
- * @param $params Array
- * @param $flags integer
+ * @param array $params
+ * @param int $flags
* @return MediaTransformOutput
*/
protected function transformErrorOutput( $thumbPath, $thumbUrl, $params, $flags ) {
@@ -854,13 +988,13 @@ abstract class File {
/**
* Transform a media file
*
- * @param array $params an associative array of handler-specific parameters.
- * Typical keys are width, height and page.
- * @param $flags Integer: a bitfield, may contain self::RENDER_NOW to force rendering
+ * @param array $params An associative array of handler-specific parameters.
+ * Typical keys are width, height and page.
+ * @param int $flags A bitfield, may contain self::RENDER_NOW to force rendering
* @return MediaTransformOutput|bool False on failure
*/
function transform( $params, $flags = 0 ) {
- global $wgUseSquid, $wgIgnoreImageErrors, $wgThumbnailEpoch;
+ global $wgThumbnailEpoch;
wfProfileIn( __METHOD__ );
do {
@@ -895,15 +1029,13 @@ abstract class File {
if ( $this->repo ) {
// Defer rendering if a 404 handler is set up...
if ( $this->repo->canTransformVia404() && !( $flags & self::RENDER_NOW ) ) {
- wfDebug( __METHOD__ . " transformation deferred." );
+ wfDebug( __METHOD__ . " transformation deferred.\n" );
// XXX: Pass in the storage path even though we are not rendering anything
// and the path is supposed to be an FS path. This is due to getScalerType()
// getting called on the path and clobbering $thumb->getUrl() if it's false.
$thumb = $handler->getTransform( $this, $thumbPath, $thumbUrl, $params );
break;
}
- // Clean up broken thumbnails as needed
- $this->migrateThumbFile( $thumbName );
// Check if an up-to-date thumbnail already exists...
wfDebug( __METHOD__ . ": Doing stat for $thumbPath\n" );
if ( !( $flags & self::RENDER_FORCE ) && $this->repo->fileExists( $thumbPath ) ) {
@@ -919,94 +1051,274 @@ abstract class File {
} elseif ( $flags & self::RENDER_FORCE ) {
wfDebug( __METHOD__ . " forcing rendering per flag File::RENDER_FORCE\n" );
}
- }
- // If the backend is ready-only, don't keep generating thumbnails
- // only to return transformation errors, just return the error now.
- if ( $this->repo->getReadOnlyReason() !== false ) {
- $thumb = $this->transformErrorOutput( $thumbPath, $thumbUrl, $params, $flags );
- break;
+ // If the backend is ready-only, don't keep generating thumbnails
+ // only to return transformation errors, just return the error now.
+ if ( $this->repo->getReadOnlyReason() !== false ) {
+ $thumb = $this->transformErrorOutput( $thumbPath, $thumbUrl, $params, $flags );
+ break;
+ }
}
- // Create a temp FS file with the same extension and the thumbnail
- $thumbExt = FileBackend::extensionFromPath( $thumbPath );
- $tmpFile = TempFSFile::factory( 'transform_', $thumbExt );
+ $tmpFile = $this->makeTransformTmpFile( $thumbPath );
+
if ( !$tmpFile ) {
$thumb = $this->transformErrorOutput( $thumbPath, $thumbUrl, $params, $flags );
- break;
+ } else {
+ $thumb = $this->generateAndSaveThumb( $tmpFile, $params, $flags );
}
- $tmpThumbPath = $tmpFile->getPath(); // path of 0-byte temp file
-
- // Actually render the thumbnail...
- wfProfileIn( __METHOD__ . '-doTransform' );
- $thumb = $handler->doTransform( $this, $tmpThumbPath, $thumbUrl, $params );
- wfProfileOut( __METHOD__ . '-doTransform' );
- $tmpFile->bind( $thumb ); // keep alive with $thumb
-
- if ( !$thumb ) { // bad params?
- $thumb = null;
- } elseif ( $thumb->isError() ) { // transform error
- $this->lastError = $thumb->toText();
- // Ignore errors if requested
- if ( $wgIgnoreImageErrors && !( $flags & self::RENDER_NOW ) ) {
- $thumb = $handler->getTransform( $this, $tmpThumbPath, $thumbUrl, $params );
- }
- } elseif ( $this->repo && $thumb->hasFile() && !$thumb->fileIsSource() ) {
- // Copy the thumbnail from the file system into storage...
- $disposition = $this->getThumbDisposition( $thumbName );
- $status = $this->repo->quickImport( $tmpThumbPath, $thumbPath, $disposition );
- if ( $status->isOK() ) {
- $thumb->setStoragePath( $thumbPath );
- } else {
- $thumb = $this->transformErrorOutput( $thumbPath, $thumbUrl, $params, $flags );
+ } while ( false );
+
+ wfProfileOut( __METHOD__ );
+
+ return is_object( $thumb ) ? $thumb : false;
+ }
+
+ /**
+ * Generates a thumbnail according to the given parameters and saves it to storage
+ * @param TempFSFile $tmpFile Temporary file where the rendered thumbnail will be saved
+ * @param array $transformParams
+ * @param int $flags
+ * @return bool|MediaTransformOutput
+ */
+ public function generateAndSaveThumb( $tmpFile, $transformParams, $flags ) {
+ global $wgUseSquid, $wgIgnoreImageErrors;
+
+ $handler = $this->getHandler();
+
+ $normalisedParams = $transformParams;
+ $handler->normaliseParams( $this, $normalisedParams );
+
+ $thumbName = $this->thumbName( $normalisedParams );
+ $thumbUrl = $this->getThumbUrl( $thumbName );
+ $thumbPath = $this->getThumbPath( $thumbName ); // final thumb path
+
+ $tmpThumbPath = $tmpFile->getPath();
+
+ if ( $handler->supportsBucketing() ) {
+ $this->generateBucketsIfNeeded( $normalisedParams, $flags );
+ }
+
+ // Actually render the thumbnail...
+ wfProfileIn( __METHOD__ . '-doTransform' );
+ $thumb = $handler->doTransform( $this, $tmpThumbPath, $thumbUrl, $transformParams );
+ wfProfileOut( __METHOD__ . '-doTransform' );
+ $tmpFile->bind( $thumb ); // keep alive with $thumb
+
+ if ( !$thumb ) { // bad params?
+ $thumb = false;
+ } elseif ( $thumb->isError() ) { // transform error
+ $this->lastError = $thumb->toText();
+ // Ignore errors if requested
+ if ( $wgIgnoreImageErrors && !( $flags & self::RENDER_NOW ) ) {
+ $thumb = $handler->getTransform( $this, $tmpThumbPath, $thumbUrl, $transformParams );
+ }
+ } elseif ( $this->repo && $thumb->hasFile() && !$thumb->fileIsSource() ) {
+ // Copy the thumbnail from the file system into storage...
+ $disposition = $this->getThumbDisposition( $thumbName );
+ $status = $this->repo->quickImport( $tmpThumbPath, $thumbPath, $disposition );
+ if ( $status->isOK() ) {
+ $thumb->setStoragePath( $thumbPath );
+ } else {
+ $thumb = $this->transformErrorOutput( $thumbPath, $thumbUrl, $transformParams, $flags );
+ }
+ // Give extensions a chance to do something with this thumbnail...
+ wfRunHooks( 'FileTransformed', array( $this, $thumb, $tmpThumbPath, $thumbPath ) );
+ }
+
+ // Purge. Useful in the event of Core -> Squid connection failure or squid
+ // purge collisions from elsewhere during failure. Don't keep triggering for
+ // "thumbs" which have the main image URL though (bug 13776)
+ if ( $wgUseSquid ) {
+ if ( !$thumb || $thumb->isError() || $thumb->getUrl() != $this->getURL() ) {
+ SquidUpdate::purge( array( $thumbUrl ) );
+ }
+ }
+
+ return $thumb;
+ }
+
+ /**
+ * Generates chained bucketed thumbnails if needed
+ * @param array $params
+ * @param int $flags
+ * @return bool Whether at least one bucket was generated
+ */
+ protected function generateBucketsIfNeeded( $params, $flags = 0 ) {
+ if ( !$this->repo
+ || !isset( $params['physicalWidth'] )
+ || !isset( $params['physicalHeight'] )
+ || !( $bucket = $this->getThumbnailBucket( $params['physicalWidth'] ) )
+ || $bucket == $params['physicalWidth'] ) {
+ return false;
+ }
+
+ $bucketPath = $this->getBucketThumbPath( $bucket );
+
+ if ( $this->repo->fileExists( $bucketPath ) ) {
+ return false;
+ }
+
+ $params['physicalWidth'] = $bucket;
+ $params['width'] = $bucket;
+
+ $params = $this->getHandler()->sanitizeParamsForBucketing( $params );
+
+ $bucketName = $this->getBucketThumbName( $bucket );
+
+ $tmpFile = $this->makeTransformTmpFile( $bucketPath );
+
+ if ( !$tmpFile ) {
+ return false;
+ }
+
+ $thumb = $this->generateAndSaveThumb( $tmpFile, $params, $flags );
+
+ if ( !$thumb || $thumb->isError() ) {
+ return false;
+ }
+
+ $this->tmpBucketedThumbCache[$bucket] = $tmpFile->getPath();
+ // For the caching to work, we need to make the tmp file survive as long as
+ // this object exists
+ $tmpFile->bind( $this );
+
+ return true;
+ }
+
+ /**
+ * Returns the most appropriate source image for the thumbnail, given a target thumbnail size
+ * @param array $params
+ * @return array Source path and width/height of the source
+ */
+ public function getThumbnailSource( $params ) {
+ if ( $this->repo
+ && $this->getHandler()->supportsBucketing()
+ && isset( $params['physicalWidth'] )
+ && $bucket = $this->getThumbnailBucket( $params['physicalWidth'] )
+ ) {
+ if ( $this->getWidth() != 0 ) {
+ $bucketHeight = round( $this->getHeight() * ( $bucket / $this->getWidth() ) );
+ } else {
+ $bucketHeight = 0;
+ }
+
+ // Try to avoid reading from storage if the file was generated by this script
+ if ( isset( $this->tmpBucketedThumbCache[$bucket] ) ) {
+ $tmpPath = $this->tmpBucketedThumbCache[$bucket];
+
+ if ( file_exists( $tmpPath ) ) {
+ return array(
+ 'path' => $tmpPath,
+ 'width' => $bucket,
+ 'height' => $bucketHeight
+ );
}
- // Give extensions a chance to do something with this thumbnail...
- wfRunHooks( 'FileTransformed', array( $this, $thumb, $tmpThumbPath, $thumbPath ) );
}
- // Purge. Useful in the event of Core -> Squid connection failure or squid
- // purge collisions from elsewhere during failure. Don't keep triggering for
- // "thumbs" which have the main image URL though (bug 13776)
- if ( $wgUseSquid ) {
- if ( !$thumb || $thumb->isError() || $thumb->getUrl() != $this->getURL() ) {
- SquidUpdate::purge( array( $thumbUrl ) );
+ $bucketPath = $this->getBucketThumbPath( $bucket );
+
+ if ( $this->repo->fileExists( $bucketPath ) ) {
+ $fsFile = $this->repo->getLocalReference( $bucketPath );
+
+ if ( $fsFile ) {
+ return array(
+ 'path' => $fsFile->getPath(),
+ 'width' => $bucket,
+ 'height' => $bucketHeight
+ );
}
}
- } while ( false );
+ }
- wfProfileOut( __METHOD__ );
- return is_object( $thumb ) ? $thumb : false;
+ // Thumbnailing a very large file could result in network saturation if
+ // everyone does it at once.
+ if ( $this->getSize() >= 1e7 ) { // 10MB
+ $that = $this;
+ $work = new PoolCounterWorkViaCallback( 'GetLocalFileCopy', sha1( $this->getName() ),
+ array(
+ 'doWork' => function() use ( $that ) {
+ return $that->getLocalRefPath();
+ }
+ )
+ );
+ $srcPath = $work->execute();
+ } else {
+ $srcPath = $this->getLocalRefPath();
+ }
+
+ // Original file
+ return array(
+ 'path' => $srcPath,
+ 'width' => $this->getWidth(),
+ 'height' => $this->getHeight()
+ );
+ }
+
+ /**
+ * Returns the repo path of the thumb for a given bucket
+ * @param int $bucket
+ * @return string
+ */
+ protected function getBucketThumbPath( $bucket ) {
+ $thumbName = $this->getBucketThumbName( $bucket );
+ return $this->getThumbPath( $thumbName );
+ }
+
+ /**
+ * Returns the name of the thumb for a given bucket
+ * @param int $bucket
+ * @return string
+ */
+ protected function getBucketThumbName( $bucket ) {
+ return $this->thumbName( array( 'physicalWidth' => $bucket ) );
+ }
+
+ /**
+ * Creates a temp FS file with the same extension and the thumbnail
+ * @param string $thumbPath Thumbnail path
+ * @return TempFSFile
+ */
+ protected function makeTransformTmpFile( $thumbPath ) {
+ $thumbExt = FileBackend::extensionFromPath( $thumbPath );
+ return TempFSFile::factory( 'transform_', $thumbExt );
}
/**
* @param string $thumbName Thumbnail name
+ * @param string $dispositionType Type of disposition (either "attachment" or "inline")
* @return string Content-Disposition header value
*/
- function getThumbDisposition( $thumbName ) {
+ function getThumbDisposition( $thumbName, $dispositionType = 'inline' ) {
$fileName = $this->name; // file name to suggest
$thumbExt = FileBackend::extensionFromPath( $thumbName );
if ( $thumbExt != '' && $thumbExt !== $this->getExtension() ) {
$fileName .= ".$thumbExt";
}
- return FileBackend::makeContentDisposition( 'inline', $fileName );
+
+ return FileBackend::makeContentDisposition( $dispositionType, $fileName );
}
/**
* Hook into transform() to allow migration of thumbnail files
* STUB
* Overridden by LocalFile
+ * @param string $thumbName
*/
- function migrateThumbFile( $thumbName ) {}
+ function migrateThumbFile( $thumbName ) {
+ }
/**
* Get a MediaHandler instance for this file
*
- * @return MediaHandler|boolean Registered MediaHandler for file's mime type or false if none found
+ * @return MediaHandler|bool Registered MediaHandler for file's MIME type
+ * or false if none found
*/
function getHandler() {
if ( !isset( $this->handler ) ) {
$this->handler = MediaHandler::getHandler( $this->getMimeType() );
}
+
return $this->handler;
}
@@ -1016,23 +1328,26 @@ abstract class File {
* @return ThumbnailImage
*/
function iconThumb() {
- global $wgStylePath, $wgStyleDirectory;
+ global $wgResourceBasePath, $IP;
+ $assetsPath = "$wgResourceBasePath/resources/assets/file-type-icons/";
+ $assetsDirectory = "$IP/resources/assets/file-type-icons/";
$try = array( 'fileicon-' . $this->getExtension() . '.png', 'fileicon.png' );
foreach ( $try as $icon ) {
- $path = '/common/images/icons/' . $icon;
- $filepath = $wgStyleDirectory . $path;
- if ( file_exists( $filepath ) ) { // always FS
+ if ( file_exists( $assetsDirectory . $icon ) ) { // always FS
$params = array( 'width' => 120, 'height' => 120 );
- return new ThumbnailImage( $this, $wgStylePath . $path, false, $params );
+
+ return new ThumbnailImage( $this, $assetsPath . $icon, false, $params );
}
}
+
return null;
}
/**
* Get last thumbnailing error.
* Largely obsolete.
+ * @return string
*/
function getLastError() {
return $this->lastError;
@@ -1053,9 +1368,10 @@ abstract class File {
* STUB
* Overridden by LocalFile
* @param array $options Options, which include:
- * 'forThumbRefresh' : The purging is only to refresh thumbnails
+ * 'forThumbRefresh' : The purging is only to refresh thumbnails
*/
- function purgeCache( $options = array() ) {}
+ function purgeCache( $options = array() ) {
+ }
/**
* Purge the file description page, but don't go after
@@ -1091,9 +1407,9 @@ abstract class File {
* Return a fragment of the history of file.
*
* STUB
- * @param $limit integer Limit of rows to return
- * @param string $start timestamp Only revisions older than $start will be returned
- * @param string $end timestamp Only revisions newer than $end will be returned
+ * @param int $limit Limit of rows to return
+ * @param string $start Only revisions older than $start will be returned
+ * @param string $end Only revisions newer than $end will be returned
* @param bool $inc Include the endpoints of the time range
*
* @return array
@@ -1121,7 +1437,8 @@ abstract class File {
* STUB
* Overridden in LocalFile.
*/
- public function resetHistory() {}
+ public function resetHistory() {
+ }
/**
* Get the filename hash component of the directory including trailing slash,
@@ -1135,6 +1452,7 @@ abstract class File {
$this->assertRepoDefined();
$this->hashPath = $this->repo->getHashPath( $this->getName() );
}
+
return $this->hashPath;
}
@@ -1151,7 +1469,7 @@ abstract class File {
/**
* Get the path of an archived file relative to the public zone root
*
- * @param bool|string $suffix if not false, the name of an archived thumbnail file
+ * @param bool|string $suffix If not false, the name of an archived thumbnail file
*
* @return string
*/
@@ -1162,6 +1480,7 @@ abstract class File {
} else {
$path .= $suffix;
}
+
return $path;
}
@@ -1169,8 +1488,7 @@ abstract class File {
* Get the path, relative to the thumbnail zone root, of the
* thumbnail directory or a particular file if $suffix is specified
*
- * @param bool|string $suffix if not false, the name of a thumbnail file
- *
+ * @param bool|string $suffix If not false, the name of a thumbnail file
* @return string
*/
function getThumbRel( $suffix = false ) {
@@ -1178,6 +1496,7 @@ abstract class File {
if ( $suffix !== false ) {
$path .= '/' . $suffix;
}
+
return $path;
}
@@ -1195,9 +1514,8 @@ abstract class File {
* Get the path, relative to the thumbnail zone root, for an archived file's thumbs directory
* or a specific thumb if the $suffix is given.
*
- * @param string $archiveName the timestamped name of an archived image
- * @param bool|string $suffix if not false, the name of a thumbnail file
- *
+ * @param string $archiveName The timestamped name of an archived image
+ * @param bool|string $suffix If not false, the name of a thumbnail file
* @return string
*/
function getArchiveThumbRel( $archiveName, $suffix = false ) {
@@ -1207,64 +1525,64 @@ abstract class File {
} else {
$path .= $suffix;
}
+
return $path;
}
/**
* Get the path of the archived file.
*
- * @param bool|string $suffix if not false, the name of an archived file.
- *
+ * @param bool|string $suffix If not false, the name of an archived file.
* @return string
*/
function getArchivePath( $suffix = false ) {
$this->assertRepoDefined();
+
return $this->repo->getZonePath( 'public' ) . '/' . $this->getArchiveRel( $suffix );
}
/**
* Get the path of an archived file's thumbs, or a particular thumb if $suffix is specified
*
- * @param string $archiveName the timestamped name of an archived image
- * @param bool|string $suffix if not false, the name of a thumbnail file
- *
+ * @param string $archiveName The timestamped name of an archived image
+ * @param bool|string $suffix If not false, the name of a thumbnail file
* @return string
*/
function getArchiveThumbPath( $archiveName, $suffix = false ) {
$this->assertRepoDefined();
+
return $this->repo->getZonePath( 'thumb' ) . '/' .
- $this->getArchiveThumbRel( $archiveName, $suffix );
+ $this->getArchiveThumbRel( $archiveName, $suffix );
}
/**
* Get the path of the thumbnail directory, or a particular file if $suffix is specified
*
- * @param bool|string $suffix if not false, the name of a thumbnail file
- *
+ * @param bool|string $suffix If not false, the name of a thumbnail file
* @return string
*/
function getThumbPath( $suffix = false ) {
$this->assertRepoDefined();
+
return $this->repo->getZonePath( 'thumb' ) . '/' . $this->getThumbRel( $suffix );
}
/**
* Get the path of the transcoded directory, or a particular file if $suffix is specified
*
- * @param bool|string $suffix if not false, the name of a media file
- *
+ * @param bool|string $suffix If not false, the name of a media file
* @return string
*/
function getTranscodedPath( $suffix = false ) {
$this->assertRepoDefined();
+
return $this->repo->getZonePath( 'transcoded' ) . '/' . $this->getThumbRel( $suffix );
}
/**
* Get the URL of the archive directory, or a particular file if $suffix is specified
*
- * @param bool|string $suffix if not false, the name of an archived file
- *
+ * @param bool|string $suffix If not false, the name of an archived file
* @return string
*/
function getArchiveUrl( $suffix = false ) {
@@ -1276,15 +1594,15 @@ abstract class File {
} else {
$path .= rawurlencode( $suffix );
}
+
return $path;
}
/**
* Get the URL of the archived file's thumbs, or a particular thumb if $suffix is specified
*
- * @param string $archiveName the timestamped name of an archived image
- * @param bool|string $suffix if not false, the name of a thumbnail file
- *
+ * @param string $archiveName The timestamped name of an archived image
+ * @param bool|string $suffix If not false, the name of a thumbnail file
* @return string
*/
function getArchiveThumbUrl( $archiveName, $suffix = false ) {
@@ -1297,16 +1615,16 @@ abstract class File {
} else {
$path .= rawurlencode( $suffix );
}
+
return $path;
}
/**
* Get the URL of the zone directory, or a particular file if $suffix is specified
*
- * @param string $zone name of requested zone
- * @param bool|string $suffix if not false, the name of a file in zone
- *
- * @return string path
+ * @param string $zone Name of requested zone
+ * @param bool|string $suffix If not false, the name of a file in zone
+ * @return string Path
*/
function getZoneUrl( $zone, $suffix = false ) {
$this->assertRepoDefined();
@@ -1315,15 +1633,15 @@ abstract class File {
if ( $suffix !== false ) {
$path .= '/' . rawurlencode( $suffix );
}
+
return $path;
}
/**
* Get the URL of the thumbnail directory, or a particular file if $suffix is specified
*
- * @param bool|string $suffix if not false, the name of a thumbnail file
- *
- * @return string path
+ * @param bool|string $suffix If not false, the name of a thumbnail file
+ * @return string Path
*/
function getThumbUrl( $suffix = false ) {
return $this->getZoneUrl( 'thumb', $suffix );
@@ -1332,9 +1650,8 @@ abstract class File {
/**
* Get the URL of the transcoded directory, or a particular file if $suffix is specified
*
- * @param bool|string $suffix if not false, the name of a media file
- *
- * @return string path
+ * @param bool|string $suffix If not false, the name of a media file
+ * @return string Path
*/
function getTranscodedUrl( $suffix = false ) {
return $this->getZoneUrl( 'transcoded', $suffix );
@@ -1343,8 +1660,7 @@ abstract class File {
/**
* Get the public zone virtual URL for a current version source file
*
- * @param bool|string $suffix if not false, the name of a thumbnail file
- *
+ * @param bool|string $suffix If not false, the name of a thumbnail file
* @return string
*/
function getVirtualUrl( $suffix = false ) {
@@ -1353,14 +1669,14 @@ abstract class File {
if ( $suffix !== false ) {
$path .= '/' . rawurlencode( $suffix );
}
+
return $path;
}
/**
* Get the public zone virtual URL for an archived version source file
*
- * @param bool|string $suffix if not false, the name of a thumbnail file
- *
+ * @param bool|string $suffix If not false, the name of a thumbnail file
* @return string
*/
function getArchiveVirtualUrl( $suffix = false ) {
@@ -1371,14 +1687,14 @@ abstract class File {
} else {
$path .= rawurlencode( $suffix );
}
+
return $path;
}
/**
* Get the virtual URL for a thumbnail file or directory
*
- * @param bool|string $suffix if not false, the name of a thumbnail file
- *
+ * @param bool|string $suffix If not false, the name of a thumbnail file
* @return string
*/
function getThumbVirtualUrl( $suffix = false ) {
@@ -1387,6 +1703,7 @@ abstract class File {
if ( $suffix !== false ) {
$path .= '/' . rawurlencode( $suffix );
}
+
return $path;
}
@@ -1395,6 +1712,7 @@ abstract class File {
*/
function isHashed() {
$this->assertRepoDefined();
+
return (bool)$this->repo->getHashLevels();
}
@@ -1409,18 +1727,20 @@ abstract class File {
* Record a file upload in the upload log and the image table
* STUB
* Overridden by LocalFile
- * @param $oldver
- * @param $desc
- * @param $license string
- * @param $copyStatus string
- * @param $source string
- * @param $watch bool
- * @param $timestamp string|bool
- * @param $user User object or null to use $wgUser
+ * @param string $oldver
+ * @param string $desc
+ * @param string $license
+ * @param string $copyStatus
+ * @param string $source
+ * @param bool $watch
+ * @param string|bool $timestamp
+ * @param null|User $user User object or null to use $wgUser
* @return bool
* @throws MWException
*/
- function recordUpload( $oldver, $desc, $license = '', $copyStatus = '', $source = '', $watch = false, $timestamp = false, User $user = null ) {
+ function recordUpload( $oldver, $desc, $license = '', $copyStatus = '', $source = '',
+ $watch = false, $timestamp = false, User $user = null
+ ) {
$this->readOnlyError();
}
@@ -1435,13 +1755,12 @@ abstract class File {
* Options to $options include:
* - headers : name/value map of HTTP headers to use in response to GET/HEAD requests
*
- * @param string $srcPath local filesystem path to the source image
- * @param $flags Integer: a bitwise combination of:
- * File::DELETE_SOURCE Delete the source file, i.e. move
- * rather than copy
+ * @param string $srcPath Local filesystem path to the source image
+ * @param int $flags A bitwise combination of:
+ * File::DELETE_SOURCE Delete the source file, i.e. move rather than copy
* @param array $options Optional additional parameters
- * @return FileRepoStatus object. On success, the value member contains the
- * archive name, or an empty string if it was a new file.
+ * @return FileRepoStatus On success, the value member contains the
+ * archive name, or an empty string if it was a new file.
*
* STUB
* Overridden by LocalFile
@@ -1457,6 +1776,7 @@ abstract class File {
if ( !$this->getHandler() ) {
return false;
}
+
return $this->getHandler()->formatMetadata( $this, $this->getMetadata() );
}
@@ -1481,7 +1801,7 @@ abstract class File {
/**
* Returns the repository
*
- * @return FileRepo|bool
+ * @return FileRepo|LocalRepo|bool
*/
function getRepo() {
return $this->repo;
@@ -1501,8 +1821,7 @@ abstract class File {
* Is this file a "deleted" file in a private archive?
* STUB
*
- * @param integer $field one of DELETED_* bitfield constants
- *
+ * @param int $field One of DELETED_* bitfield constants
* @return bool
*/
function isDeleted( $field ) {
@@ -1525,6 +1844,7 @@ abstract class File {
*/
function wasDeleted() {
$title = $this->getTitle();
+
return $title && $title->isDeletedQuick();
}
@@ -1537,8 +1857,8 @@ abstract class File {
* Cache purging is done; checks for validity
* and logging are caller's responsibility
*
- * @param $target Title New file name
- * @return FileRepoStatus object.
+ * @param Title $target New file name
+ * @return FileRepoStatus
*/
function move( $target ) {
$this->readOnlyError();
@@ -1552,13 +1872,14 @@ abstract class File {
*
* Cache purging is done; logging is caller's responsibility.
*
- * @param $reason String
- * @param $suppress Boolean: hide content from sysops?
- * @return bool on success, false on some kind of failure
+ * @param string $reason
+ * @param bool $suppress Hide content from sysops?
+ * @param User|null $user
+ * @return bool Boolean on success, false on some kind of failure
* STUB
* Overridden by LocalFile
*/
- function delete( $reason, $suppress = false ) {
+ function delete( $reason, $suppress = false, $user = null ) {
$this->readOnlyError();
}
@@ -1568,11 +1889,11 @@ abstract class File {
*
* May throw database exceptions on error.
*
- * @param array $versions set of record ids of deleted items to restore,
- * or empty to restore all revisions.
- * @param bool $unsuppress remove restrictions on content upon restoration?
- * @return int|bool the number of file revisions restored if successful,
- * or false on failure
+ * @param array $versions Set of record ids of deleted items to restore,
+ * or empty to restore all revisions.
+ * @param bool $unsuppress Remove restrictions on content upon restoration?
+ * @return int|bool The number of file revisions restored if successful,
+ * or false on failure
* STUB
* Overridden by LocalFile
*/
@@ -1585,7 +1906,7 @@ abstract class File {
* e.g. DJVU or PDF. Note that this may be true even if the file in
* question only has a single page.
*
- * @return Bool
+ * @return bool
*/
function isMultipage() {
return $this->getHandler() && $this->handler->isMultiPage( $this );
@@ -1605,15 +1926,16 @@ abstract class File {
$this->pageCount = false;
}
}
+
return $this->pageCount;
}
/**
* Calculate the height of a thumbnail using the source and destination width
*
- * @param $srcWidth
- * @param $srcHeight
- * @param $dstWidth
+ * @param int $srcWidth
+ * @param int $srcHeight
+ * @param int $dstWidth
*
* @return int
*/
@@ -1628,16 +1950,20 @@ abstract class File {
/**
* Get an image size array like that returned by getImageSize(), or false if it
- * can't be determined.
+ * can't be determined. Loads the image size directly from the file ignoring caches.
*
- * @param string $fileName The filename
- * @return Array
+ * @note Use getWidth()/getHeight() instead of this method unless you have a
+ * a good reason. This method skips all caches.
+ *
+ * @param string $filePath The path to the file (e.g. From getLocalPathRef() )
+ * @return array The width, followed by height, with optionally more things after
*/
- function getImageSize( $fileName ) {
+ function getImageSize( $filePath ) {
if ( !$this->getHandler() ) {
return false;
}
- return $this->handler->getImageSize( $this, $fileName );
+
+ return $this->getHandler()->getImageSize( $this, $filePath );
}
/**
@@ -1657,7 +1983,7 @@ abstract class File {
/**
* Get the HTML text of the description page, if available
*
- * @param $lang Language Optional language to fetch description in
+ * @param bool|Language $lang Optional language to fetch description in
* @return string
*/
function getDescriptionText( $lang = false ) {
@@ -1672,11 +1998,16 @@ abstract class File {
if ( $renderUrl ) {
if ( $this->repo->descriptionCacheExpiry > 0 ) {
wfDebug( "Attempting to get the description from cache..." );
- $key = $this->repo->getLocalCacheKey( 'RemoteFileDescription', 'url', $lang->getCode(),
- $this->getName() );
+ $key = $this->repo->getLocalCacheKey(
+ 'RemoteFileDescription',
+ 'url',
+ $lang->getCode(),
+ $this->getName()
+ );
$obj = $wgMemc->get( $key );
if ( $obj ) {
wfDebug( "success!\n" );
+
return $obj;
}
wfDebug( "miss\n" );
@@ -1686,6 +2017,7 @@ abstract class File {
if ( $res && $this->repo->descriptionCacheExpiry > 0 ) {
$wgMemc->set( $key, $res, $this->repo->descriptionCacheExpiry );
}
+
return $res;
} else {
return false;
@@ -1696,12 +2028,12 @@ abstract class File {
* Get description of file revision
* STUB
*
- * @param $audience Integer: one of:
- * File::FOR_PUBLIC to be displayed to all users
- * File::FOR_THIS_USER to be displayed to the given user
- * File::RAW get the description regardless of permissions
- * @param $user User object to check for, only if FOR_THIS_USER is passed
- * to the $audience parameter
+ * @param int $audience One of:
+ * File::FOR_PUBLIC to be displayed to all users
+ * File::FOR_THIS_USER to be displayed to the given user
+ * File::RAW get the description regardless of permissions
+ * @param User $user User object to check for, only if FOR_THIS_USER is
+ * passed to the $audience parameter
* @return string
*/
function getDescription( $audience = self::FOR_PUBLIC, User $user = null ) {
@@ -1715,6 +2047,7 @@ abstract class File {
*/
function getTimestamp() {
$this->assertRepoDefined();
+
return $this->repo->getFileTimestamp( $this->getPath() );
}
@@ -1725,6 +2058,7 @@ abstract class File {
*/
function getSha1() {
$this->assertRepoDefined();
+
return $this->repo->getFileSha1( $this->getPath() );
}
@@ -1740,6 +2074,7 @@ abstract class File {
}
$ext = $this->getExtension();
$dotExt = $ext === '' ? '' : ".$ext";
+
return $hash . $dotExt;
}
@@ -1747,53 +2082,16 @@ abstract class File {
* Determine if the current user is allowed to view a particular
* field of this file, if it's marked as deleted.
* STUB
- * @param $field Integer
- * @param $user User object to check, or null to use $wgUser
- * @return Boolean
+ * @param int $field
+ * @param User $user User object to check, or null to use $wgUser
+ * @return bool
*/
function userCan( $field, User $user = null ) {
return true;
}
/**
- * Get an associative array containing information about a file in the local filesystem.
- *
- * @param string $path absolute local filesystem path
- * @param $ext Mixed: the file extension, or true to extract it from the filename.
- * Set it to false to ignore the extension.
- *
- * @return array
- * @deprecated since 1.19
- */
- static function getPropsFromPath( $path, $ext = true ) {
- wfDebug( __METHOD__ . ": Getting file info for $path\n" );
- wfDeprecated( __METHOD__, '1.19' );
-
- $fsFile = new FSFile( $path );
- return $fsFile->getProps();
- }
-
- /**
- * Get a SHA-1 hash of a file in the local filesystem, in base-36 lower case
- * encoding, zero padded to 31 digits.
- *
- * 160 log 2 / log 36 = 30.95, so the 160-bit hash fills 31 digits in base 36
- * fairly neatly.
- *
- * @param $path string
- *
- * @return bool|string False on failure
- * @deprecated since 1.19
- */
- static function sha1Base36( $path ) {
- wfDeprecated( __METHOD__, '1.19' );
-
- $fsFile = new FSFile( $path );
- return $fsFile->getSha1Base36();
- }
-
- /**
- * @return Array HTTP header name/value map to use for HEAD/GET request responses
+ * @return array HTTP header name/value map to use for HEAD/GET request responses
*/
function getStreamHeaders() {
$handler = $this->getHandler();
@@ -1841,7 +2139,7 @@ abstract class File {
}
/**
- * @return
+ * @return string
*/
function getRedirected() {
return $this->redirected;
@@ -1855,13 +2153,15 @@ abstract class File {
if ( !$this->redirectTitle ) {
$this->redirectTitle = Title::makeTitle( NS_FILE, $this->redirected );
}
+
return $this->redirectTitle;
}
+
return null;
}
/**
- * @param $from
+ * @param string $from
* @return void
*/
function redirectedFrom( $from ) {
@@ -1877,7 +2177,7 @@ abstract class File {
/**
* Check if this file object is small and can be cached
- * @return boolean
+ * @return bool
*/
public function isCacheable() {
return true;
@@ -1902,4 +2202,13 @@ abstract class File {
throw new MWException( "A Title object is not set for this File.\n" );
}
}
+
+ /**
+ * True if creating thumbnails from the file is large or otherwise resource-intensive.
+ * @return bool
+ */
+ public function isExpensiveToThumbnail() {
+ $handler = $this->getHandler();
+ return $handler ? $handler->isExpensiveToThumbnail( $this ) : false;
+ }
}
diff --git a/includes/filerepo/file/ForeignAPIFile.php b/includes/filerepo/file/ForeignAPIFile.php
index ed96d446..3d5d5d60 100644
--- a/includes/filerepo/file/ForeignAPIFile.php
+++ b/includes/filerepo/file/ForeignAPIFile.php
@@ -33,9 +33,9 @@ class ForeignAPIFile extends File {
protected $repoClass = 'ForeignApiRepo';
/**
- * @param $title
- * @param $repo ForeignApiRepo
- * @param $info
+ * @param Title|string|bool $title
+ * @param ForeignApiRepo $repo
+ * @param array $info
* @param bool $exists
*/
function __construct( $title, $repo, $info, $exists = false ) {
@@ -48,8 +48,8 @@ class ForeignAPIFile extends File {
}
/**
- * @param $title Title
- * @param $repo ForeignApiRepo
+ * @param Title $title
+ * @param ForeignApiRepo $repo
* @return ForeignAPIFile|null
*/
static function newFromTitle( Title $title, $repo ) {
@@ -57,7 +57,10 @@ class ForeignAPIFile extends File {
'titles' => 'File:' . $title->getDBkey(),
'iiprop' => self::getProps(),
'prop' => 'imageinfo',
- 'iimetadataversion' => MediaHandler::getMetadataVersion()
+ 'iimetadataversion' => MediaHandler::getMetadataVersion(),
+ // extmetadata is language-dependant, accessing the current language here
+ // would be problematic, so we just get them all
+ 'iiextmetadatamultilang' => 1,
) );
$info = $repo->getImageInfo( $data );
@@ -75,6 +78,7 @@ class ForeignAPIFile extends File {
} else {
$img = new self( $title, $repo, $info, true );
}
+
return $img;
} else {
return null;
@@ -86,7 +90,7 @@ class ForeignAPIFile extends File {
* @return string
*/
static function getProps() {
- return 'timestamp|user|comment|url|size|sha1|metadata|mime|mediatype';
+ return 'timestamp|user|comment|url|size|sha1|metadata|mime|mediatype|extmetadata';
}
// Dummy functions...
@@ -130,6 +134,7 @@ class ForeignAPIFile extends File {
);
if ( $thumbUrl === false ) {
global $wgLang;
+
return $this->repo->getThumbError(
$this->getName(),
$width,
@@ -138,13 +143,14 @@ class ForeignAPIFile extends File {
$wgLang->getCode()
);
}
+
return $this->handler->getTransform( $this, 'bogus', $thumbUrl, $params );
}
// Info we can get from API...
/**
- * @param $page int
+ * @param int $page
* @return int|number
*/
public function getWidth( $page = 1 ) {
@@ -152,7 +158,7 @@ class ForeignAPIFile extends File {
}
/**
- * @param $page int
+ * @param int $page
* @return int
*/
public function getHeight( $page = 1 ) {
@@ -166,11 +172,24 @@ class ForeignAPIFile extends File {
if ( isset( $this->mInfo['metadata'] ) ) {
return serialize( self::parseMetadata( $this->mInfo['metadata'] ) );
}
+
return null;
}
/**
- * @param $metadata array
+ * @return array|null Extended metadata (see imageinfo API for format) or
+ * null on error
+ */
+ public function getExtendedMetadata() {
+ if ( isset( $this->mInfo['extmetadata'] ) ) {
+ return $this->mInfo['extmetadata'];
+ }
+
+ return null;
+ }
+
+ /**
+ * @param array $metadata
* @return array
*/
public static function parseMetadata( $metadata ) {
@@ -181,6 +200,7 @@ class ForeignAPIFile extends File {
foreach ( $metadata as $meta ) {
$ret[$meta['name']] = self::parseMetadata( $meta['value'] );
}
+
return $ret;
}
@@ -207,6 +227,8 @@ class ForeignAPIFile extends File {
}
/**
+ * @param int $audience
+ * @param User $user
* @return null|string
*/
public function getDescription( $audience = self::FOR_PUBLIC, User $user = null ) {
@@ -214,7 +236,7 @@ class ForeignAPIFile extends File {
}
/**
- * @return null|String
+ * @return null|string
*/
function getSha1() {
return isset( $this->mInfo['sha1'] )
@@ -223,7 +245,7 @@ class ForeignAPIFile extends File {
}
/**
- * @return bool|Mixed|string
+ * @return bool|string
*/
function getTimestamp() {
return wfTimestamp( TS_MW,
@@ -241,6 +263,7 @@ class ForeignAPIFile extends File {
$magic = MimeMagic::singleton();
$this->mInfo['mime'] = $magic->guessTypesForExtension( $this->getExtension() );
}
+
return $this->mInfo['mime'];
}
@@ -252,6 +275,7 @@ class ForeignAPIFile extends File {
return $this->mInfo['mediatype'];
}
$magic = MimeMagic::singleton();
+
return $magic->getMediaType( null, $this->getMimeType() );
}
@@ -266,7 +290,7 @@ class ForeignAPIFile extends File {
/**
* Only useful if we're locally caching thumbs anyway...
- * @param $suffix string
+ * @param string $suffix
* @return null|string
*/
function getThumbPath( $suffix = '' ) {
@@ -275,6 +299,7 @@ class ForeignAPIFile extends File {
if ( $suffix ) {
$path = $path . $suffix . '/';
}
+
return $path;
} else {
return null;
@@ -314,7 +339,7 @@ class ForeignAPIFile extends File {
}
/**
- * @param $options array
+ * @param array $options
*/
function purgeThumbnails( $options = array() ) {
global $wgMemc;
diff --git a/includes/filerepo/file/ForeignDBFile.php b/includes/filerepo/file/ForeignDBFile.php
index 01d6b0f5..561ead75 100644
--- a/includes/filerepo/file/ForeignDBFile.php
+++ b/includes/filerepo/file/ForeignDBFile.php
@@ -27,11 +27,10 @@
* @ingroup FileAbstraction
*/
class ForeignDBFile extends LocalFile {
-
/**
- * @param $title
- * @param $repo
- * @param $unused
+ * @param Title $title
+ * @param FileRepo $repo
+ * @param null $unused
* @return ForeignDBFile
*/
static function newFromTitle( $title, $repo, $unused = null ) {
@@ -42,23 +41,23 @@ class ForeignDBFile extends LocalFile {
* Create a ForeignDBFile from a title
* Do not call this except from inside a repo class.
*
- * @param $row
- * @param $repo
- *
+ * @param stdClass $row
+ * @param FileRepo $repo
* @return ForeignDBFile
*/
static function newFromRow( $row, $repo ) {
$title = Title::makeTitle( NS_FILE, $row->img_name );
$file = new self( $title, $repo );
$file->loadFromRow( $row );
+
return $file;
}
/**
- * @param $srcPath String
- * @param $flags int
- * @param $options Array
- * @return \FileRepoStatus
+ * @param string $srcPath
+ * @param int $flags
+ * @param array $options
+ * @return FileRepoStatus
* @throws MWException
*/
function publish( $srcPath, $flags = 0, array $options = array() ) {
@@ -66,14 +65,14 @@ class ForeignDBFile extends LocalFile {
}
/**
- * @param $oldver
- * @param $desc string
- * @param $license string
- * @param $copyStatus string
- * @param $source string
- * @param $watch bool
- * @param $timestamp bool|string
- * @param $user User object or null to use $wgUser
+ * @param string $oldver
+ * @param string $desc
+ * @param string $license
+ * @param string $copyStatus
+ * @param string $source
+ * @param bool $watch
+ * @param bool|string $timestamp
+ * @param User $user User object or null to use $wgUser
* @return bool
* @throws MWException
*/
@@ -83,9 +82,9 @@ class ForeignDBFile extends LocalFile {
}
/**
- * @param $versions array
- * @param $unsuppress bool
- * @return \FileRepoStatus
+ * @param array $versions
+ * @param bool $unsuppress
+ * @return FileRepoStatus
* @throws MWException
*/
function restore( $versions = array(), $unsuppress = false ) {
@@ -93,18 +92,19 @@ class ForeignDBFile extends LocalFile {
}
/**
- * @param $reason string
- * @param $suppress bool
- * @return \FileRepoStatus
+ * @param string $reason
+ * @param bool $suppress
+ * @param User|null $user
+ * @return FileRepoStatus
* @throws MWException
*/
- function delete( $reason, $suppress = false ) {
+ function delete( $reason, $suppress = false, $user = null ) {
$this->readOnlyError();
}
/**
- * @param $target Title
- * @return \FileRepoStatus
+ * @param Title $target
+ * @return FileRepoStatus
* @throws MWException
*/
function move( $target ) {
@@ -120,7 +120,7 @@ class ForeignDBFile extends LocalFile {
}
/**
- * @param $lang Language Optional language to fetch description in.
+ * @param bool|Language $lang Optional language to fetch description in.
* @return string
*/
function getDescriptionText( $lang = false ) {
diff --git a/includes/filerepo/file/LocalFile.php b/includes/filerepo/file/LocalFile.php
index d18f42e4..8824b669 100644
--- a/includes/filerepo/file/LocalFile.php
+++ b/includes/filerepo/file/LocalFile.php
@@ -46,45 +46,88 @@ define( 'MW_FILE_VERSION', 9 );
class LocalFile extends File {
const CACHE_FIELD_MAX_LEN = 1000;
- /**#@+
- * @private
- */
- var
- $fileExists, # does the file exist on disk? (loadFromXxx)
- $historyLine, # Number of line to return by nextHistoryLine() (constructor)
- $historyRes, # result of the query for the file's history (nextHistoryLine)
- $width, # \
- $height, # |
- $bits, # --- returned by getimagesize (loadFromXxx)
- $attr, # /
- $media_type, # MEDIATYPE_xxx (bitmap, drawing, audio...)
- $mime, # MIME type, determined by MimeMagic::guessMimeType
- $major_mime, # Major mime type
- $minor_mime, # Minor mime type
- $size, # Size in bytes (loadFromXxx)
- $metadata, # Handler-specific metadata
- $timestamp, # Upload timestamp
- $sha1, # SHA-1 base 36 content hash
- $user, $user_text, # User, who uploaded the file
- $description, # Description of current revision of the file
- $dataLoaded, # Whether or not core data has been loaded from the database (loadFromXxx)
- $extraDataLoaded, # Whether or not lazy-loaded data has been loaded from the database
- $upgraded, # Whether the row was upgraded on load
- $locked, # True if the image row is locked
- $lockedOwnTrx, # True if the image row is locked with a lock initiated transaction
- $missing, # True if file is not present in file system. Not to be cached in memcached
- $deleted; # Bitfield akin to rev_deleted
-
- /**#@-*/
-
- /**
- * @var LocalRepo
- */
- var $repo;
+ /** @var bool Does the file exist on disk? (loadFromXxx) */
+ protected $fileExists;
+ /** @var int Image width */
+ protected $width;
+
+ /** @var int Image height */
+ protected $height;
+
+ /** @var int Returned by getimagesize (loadFromXxx) */
+ protected $bits;
+
+ /** @var string MEDIATYPE_xxx (bitmap, drawing, audio...) */
+ protected $media_type;
+
+ /** @var string MIME type, determined by MimeMagic::guessMimeType */
+ protected $mime;
+
+ /** @var int Size in bytes (loadFromXxx) */
+ protected $size;
+
+ /** @var string Handler-specific metadata */
+ protected $metadata;
+
+ /** @var string SHA-1 base 36 content hash */
+ protected $sha1;
+
+ /** @var bool Whether or not core data has been loaded from the database (loadFromXxx) */
+ protected $dataLoaded;
+
+ /** @var bool Whether or not lazy-loaded data has been loaded from the database */
+ protected $extraDataLoaded;
+
+ /** @var int Bitfield akin to rev_deleted */
+ protected $deleted;
+
+ /** @var string */
protected $repoClass = 'LocalRepo';
+ /** @var int Number of line to return by nextHistoryLine() (constructor) */
+ private $historyLine;
+
+ /** @var int Result of the query for the file's history (nextHistoryLine) */
+ private $historyRes;
+
+ /** @var string Major MIME type */
+ private $major_mime;
+
+ /** @var string Minor MIME type */
+ private $minor_mime;
+
+ /** @var string Upload timestamp */
+ private $timestamp;
+
+ /** @var int User ID of uploader */
+ private $user;
+
+ /** @var string User name of uploader */
+ private $user_text;
+
+ /** @var string Description of current revision of the file */
+ private $description;
+
+ /** @var bool Whether the row was upgraded on load */
+ private $upgraded;
+
+ /** @var bool True if the image row is locked */
+ private $locked;
+
+ /** @var bool True if the image row is locked with a lock initiated transaction */
+ private $lockedOwnTrx;
+
+ /** @var bool True if file is not present in file system. Not to be cached in memcached */
+ private $missing;
+
+ /** @var int UNIX timestamp of last markVolatile() call */
+ private $lastMarkedVolatile = 0;
+
const LOAD_ALL = 1; // integer; load all the lazy fields too (like metadata)
+ const LOAD_VIA_SLAVE = 2; // integer; use a slave to load the data
+
+ const VOLATILE_TTL = 300; // integer; seconds
/**
* Create a LocalFile from a title
@@ -92,9 +135,9 @@ class LocalFile extends File {
*
* Note: $unused param is only here to avoid an E_STRICT
*
- * @param $title
- * @param $repo
- * @param $unused
+ * @param Title $title
+ * @param FileRepo $repo
+ * @param null $unused
*
* @return LocalFile
*/
@@ -106,8 +149,8 @@ class LocalFile extends File {
* Create a LocalFile from a title
* Do not call this except from inside a repo class.
*
- * @param $row
- * @param $repo
+ * @param stdClass $row
+ * @param FileRepo $repo
*
* @return LocalFile
*/
@@ -123,10 +166,9 @@ class LocalFile extends File {
* Create a LocalFile from a SHA-1 key
* Do not call this except from inside a repo class.
*
- * @param string $sha1 base-36 SHA-1
- * @param $repo LocalRepo
+ * @param string $sha1 Base-36 SHA-1
+ * @param LocalRepo $repo
* @param string|bool $timestamp MW_timestamp (optional)
- *
* @return bool|LocalFile
*/
static function newFromKey( $sha1, $repo, $timestamp = false ) {
@@ -171,6 +213,8 @@ class LocalFile extends File {
/**
* Constructor.
* Do not call this except from inside a repo class.
+ * @param Title $title
+ * @param FileRepo $repo
*/
function __construct( $title, $repo ) {
parent::__construct( $title, $repo );
@@ -188,7 +232,7 @@ class LocalFile extends File {
/**
* Get the memcached key for the main data for this file, or false if
* there is no access to the shared cache.
- * @return bool
+ * @return string|bool
*/
function getCacheKey() {
$hashedName = md5( $this->getName() );
@@ -210,6 +254,7 @@ class LocalFile extends File {
if ( !$key ) {
wfProfileOut( __METHOD__ );
+
return false;
}
@@ -236,6 +281,7 @@ class LocalFile extends File {
}
wfProfileOut( __METHOD__ );
+
return $this->dataLoaded;
}
@@ -284,12 +330,13 @@ class LocalFile extends File {
}
/**
- * @param $prefix string
+ * @param string $prefix
* @return array
*/
function getCacheFields( $prefix = 'img_' ) {
static $fields = array( 'size', 'width', 'height', 'bits', 'media_type',
- 'major_mime', 'minor_mime', 'metadata', 'timestamp', 'sha1', 'user', 'user_text', 'description' );
+ 'major_mime', 'minor_mime', 'metadata', 'timestamp', 'sha1', 'user',
+ 'user_text', 'description' );
static $results = array();
if ( $prefix == '' ) {
@@ -308,6 +355,7 @@ class LocalFile extends File {
}
/**
+ * @param string $prefix
* @return array
*/
function getLazyCacheFields( $prefix = 'img_' ) {
@@ -331,8 +379,9 @@ class LocalFile extends File {
/**
* Load file metadata from the DB
+ * @param int $flags
*/
- function loadFromDB() {
+ function loadFromDB( $flags = 0 ) {
# Polymorphic function name to distinguish foreign and local fetches
$fname = get_class( $this ) . '::' . __FUNCTION__;
wfProfileIn( $fname );
@@ -341,7 +390,10 @@ class LocalFile extends File {
$this->dataLoaded = true;
$this->extraDataLoaded = true;
- $dbr = $this->repo->getMasterDB();
+ $dbr = ( $flags & self::LOAD_VIA_SLAVE )
+ ? $this->repo->getSlaveDB()
+ : $this->repo->getMasterDB();
+
$row = $dbr->selectRow( 'image', $this->getCacheFields( 'img_' ),
array( 'img_name' => $this->getName() ), $fname );
@@ -366,19 +418,13 @@ class LocalFile extends File {
# Unconditionally set loaded=true, we don't want the accessors constantly rechecking
$this->extraDataLoaded = true;
- $dbr = $this->repo->getSlaveDB();
- // In theory the file could have just been renamed/deleted...oh well
- $row = $dbr->selectRow( 'image', $this->getLazyCacheFields( 'img_' ),
- array( 'img_name' => $this->getName() ), $fname );
-
- if ( !$row ) { // fallback to master
- $dbr = $this->repo->getMasterDB();
- $row = $dbr->selectRow( 'image', $this->getLazyCacheFields( 'img_' ),
- array( 'img_name' => $this->getName() ), $fname );
+ $fieldMap = $this->loadFieldsWithTimestamp( $this->repo->getSlaveDB(), $fname );
+ if ( !$fieldMap ) {
+ $fieldMap = $this->loadFieldsWithTimestamp( $this->repo->getMasterDB(), $fname );
}
- if ( $row ) {
- foreach ( $this->unprefixRow( $row, 'img_' ) as $name => $value ) {
+ if ( $fieldMap ) {
+ foreach ( $fieldMap as $name => $value ) {
$this->$name = $value;
}
} else {
@@ -390,9 +436,36 @@ class LocalFile extends File {
}
/**
- * @param Row $row
- * @param $prefix string
- * @return Array
+ * @param DatabaseBase $dbr
+ * @param string $fname
+ * @return array|bool
+ */
+ private function loadFieldsWithTimestamp( $dbr, $fname ) {
+ $fieldMap = false;
+
+ $row = $dbr->selectRow( 'image', $this->getLazyCacheFields( 'img_' ),
+ array( 'img_name' => $this->getName(), 'img_timestamp' => $this->getTimestamp() ),
+ $fname );
+ if ( $row ) {
+ $fieldMap = $this->unprefixRow( $row, 'img_' );
+ } else {
+ # File may have been uploaded over in the meantime; check the old versions
+ $row = $dbr->selectRow( 'oldimage', $this->getLazyCacheFields( 'oi_' ),
+ array( 'oi_name' => $this->getName(), 'oi_timestamp' => $this->getTimestamp() ),
+ $fname );
+ if ( $row ) {
+ $fieldMap = $this->unprefixRow( $row, 'oi_' );
+ }
+ }
+
+ return $fieldMap;
+ }
+
+ /**
+ * @param array $row Row
+ * @param string $prefix
+ * @throws MWException
+ * @return array
*/
protected function unprefixRow( $row, $prefix = 'img_' ) {
$array = (array)$row;
@@ -407,14 +480,15 @@ class LocalFile extends File {
foreach ( $array as $name => $value ) {
$decoded[substr( $name, $prefixLength )] = $value;
}
+
return $decoded;
}
/**
* Decode a row from the database (either object or array) to an array
* with timestamps and MIME types decoded, and the field prefix removed.
- * @param $row
- * @param $prefix string
+ * @param object $row
+ * @param string $prefix
* @throws MWException
* @return array
*/
@@ -442,6 +516,9 @@ class LocalFile extends File {
/**
* Load file metadata from a DB result row
+ *
+ * @param object $row
+ * @param string $prefix
*/
function loadFromRow( $row, $prefix = 'img_' ) {
$this->dataLoaded = true;
@@ -459,12 +536,12 @@ class LocalFile extends File {
/**
* Load file metadata from cache or DB, unless already loaded
- * @param integer $flags
+ * @param int $flags
*/
function load( $flags = 0 ) {
if ( !$this->dataLoaded ) {
if ( !$this->loadFromCache() ) {
- $this->loadFromDB();
+ $this->loadFromDB( $this->isVolatile() ? 0 : self::LOAD_VIA_SLAVE );
$this->saveToCache();
}
$this->dataLoaded = true;
@@ -518,8 +595,10 @@ class LocalFile extends File {
# Don't destroy file info of missing files
if ( !$this->fileExists ) {
+ $this->unlock();
wfDebug( __METHOD__ . ": file does not exist, aborting\n" );
wfProfileOut( __METHOD__ );
+
return;
}
@@ -527,7 +606,9 @@ class LocalFile extends File {
list( $major, $minor ) = self::splitMime( $this->mime );
if ( wfReadOnly() ) {
+ $this->unlock();
wfProfileOut( __METHOD__ );
+
return;
}
wfDebug( __METHOD__ . ': upgrading ' . $this->getName() . " to the current schema\n" );
@@ -541,7 +622,7 @@ class LocalFile extends File {
'img_media_type' => $this->media_type,
'img_major_mime' => $major,
'img_minor_mime' => $minor,
- 'img_metadata' => $dbw->encodeBlob($this->metadata),
+ 'img_metadata' => $dbw->encodeBlob( $this->metadata ),
'img_sha1' => $this->sha1,
),
array( 'img_name' => $this->getName() ),
@@ -562,6 +643,8 @@ class LocalFile extends File {
*
* If 'mime' is given, it will be split into major_mime/minor_mime.
* If major_mime/minor_mime are given, $this->mime will also be set.
+ *
+ * @param array $info
*/
function setProps( $info ) {
$this->dataLoaded = true;
@@ -599,13 +682,14 @@ class LocalFile extends File {
list( $fileExists ) = $this->repo->fileExists( $this->getVirtualUrl() );
$this->missing = !$fileExists;
}
+
return $this->missing;
}
/**
* Return the width of the image
*
- * @param $page int
+ * @param int $page
* @return int
*/
public function getWidth( $page = 1 ) {
@@ -632,7 +716,7 @@ class LocalFile extends File {
/**
* Return the height of the image
*
- * @param $page int
+ * @param int $page
* @return int
*/
public function getHeight( $page = 1 ) {
@@ -686,34 +770,38 @@ class LocalFile extends File {
*/
function getBitDepth() {
$this->load();
+
return $this->bits;
}
/**
- * Return the size of the image file, in bytes
+ * Returns the size of the image file, in bytes
* @return int
*/
public function getSize() {
$this->load();
+
return $this->size;
}
/**
- * Returns the mime type of the file.
+ * Returns the MIME type of the file.
* @return string
*/
function getMimeType() {
$this->load();
+
return $this->mime;
}
/**
- * Return the type of the media in the file.
+ * Returns the type of the media in the file.
* Use the value returned by this function with the MEDIATYPE_xxx constants.
* @return string
*/
function getMediaType() {
$this->load();
+
return $this->media_type;
}
@@ -725,10 +813,11 @@ class LocalFile extends File {
/**
* Returns true if the file exists on disk.
- * @return boolean Whether file exist on disk.
+ * @return bool Whether file exist on disk.
*/
public function exists() {
$this->load();
+
return $this->fileExists;
}
@@ -738,40 +827,6 @@ class LocalFile extends File {
/** createThumb inherited */
/** transform inherited */
- /**
- * Fix thumbnail files from 1.4 or before, with extreme prejudice
- * @todo : do we still care about this? Perhaps a maintenance script
- * can be made instead. Enabling this code results in a serious
- * RTT regression for wikis without 404 handling.
- */
- function migrateThumbFile( $thumbName ) {
- /* Old code for bug 2532
- $thumbDir = $this->getThumbPath();
- $thumbPath = "$thumbDir/$thumbName";
- if ( is_dir( $thumbPath ) ) {
- // Directory where file should be
- // This happened occasionally due to broken migration code in 1.5
- // Rename to broken-*
- for ( $i = 0; $i < 100; $i++ ) {
- $broken = $this->repo->getZonePath( 'public' ) . "/broken-$i-$thumbName";
- if ( !file_exists( $broken ) ) {
- rename( $thumbPath, $broken );
- break;
- }
- }
- // Doesn't exist anymore
- clearstatcache();
- }
- */
-
- /*
- if ( $this->repo->fileExists( $thumbDir ) ) {
- // Delete file where directory should be
- $this->repo->cleanupBatch( array( $thumbDir ) );
- }
- */
- }
-
/** getHandler inherited */
/** iconThumb inherited */
/** getLastError inherited */
@@ -779,7 +834,7 @@ class LocalFile extends File {
/**
* Get all thumbnail names previously generated for this file
* @param string|bool $archiveName Name of an archive file, default false
- * @return array first element is the base dir, then files in that base dir.
+ * @return array First element is the base dir, then files in that base dir.
*/
function getThumbnails( $archiveName = false ) {
if ( $archiveName ) {
@@ -795,7 +850,8 @@ class LocalFile extends File {
foreach ( $iterator as $file ) {
$files[] = $file;
}
- } catch ( FileBackendError $e ) {} // suppress (bug 54674)
+ } catch ( FileBackendError $e ) {
+ } // suppress (bug 54674)
return $files;
}
@@ -828,7 +884,7 @@ class LocalFile extends File {
/**
* Delete all previously generated thumbnails, refresh metadata in memcached and purge the squid.
*
- * @param Array $options An array potentially with the key forThumbRefresh.
+ * @param array $options An array potentially with the key forThumbRefresh.
*
* @note This used to purge old thumbnails by default as well, but doesn't anymore.
*/
@@ -847,7 +903,7 @@ class LocalFile extends File {
/**
* Delete cached transformed files for an archived version only.
- * @param string $archiveName name of the archived file
+ * @param string $archiveName Name of the archived file
*/
function purgeOldThumbnails( $archiveName ) {
global $wgUseSquid;
@@ -855,12 +911,13 @@ class LocalFile extends File {
// Get a list of old thumbnails and URLs
$files = $this->getThumbnails( $archiveName );
- $dir = array_shift( $files );
- $this->purgeThumbList( $dir, $files );
// Purge any custom thumbnail caches
wfRunHooks( 'LocalFilePurgeThumbnails', array( $this, $archiveName ) );
+ $dir = array_shift( $files );
+ $this->purgeThumbList( $dir, $files );
+
// Purge the squid
if ( $wgUseSquid ) {
$urls = array();
@@ -875,6 +932,7 @@ class LocalFile extends File {
/**
* Delete cached transformed files for the current version only.
+ * @param array $options
*/
function purgeThumbnails( $options = array() ) {
global $wgUseSquid;
@@ -883,8 +941,8 @@ class LocalFile extends File {
// Delete thumbnails
$files = $this->getThumbnails();
// Always purge all files from squid regardless of handler filters
+ $urls = array();
if ( $wgUseSquid ) {
- $urls = array();
foreach ( $files as $file ) {
$urls[] = $this->getThumbUrl( $file );
}
@@ -899,12 +957,12 @@ class LocalFile extends File {
}
}
- $dir = array_shift( $files );
- $this->purgeThumbList( $dir, $files );
-
// Purge any custom thumbnail caches
wfRunHooks( 'LocalFilePurgeThumbnails', array( $this, false ) );
+ $dir = array_shift( $files );
+ $this->purgeThumbList( $dir, $files );
+
// Purge the squid
if ( $wgUseSquid ) {
SquidUpdate::purge( $urls );
@@ -915,8 +973,8 @@ class LocalFile extends File {
/**
* Delete a list of thumbnails visible at urls
- * @param string $dir base dir of the files.
- * @param array $files of strings: relative filenames (to $dir)
+ * @param string $dir Base dir of the files.
+ * @param array $files Array of strings: relative filenames (to $dir)
*/
protected function purgeThumbList( $dir, $files ) {
$fileListDebug = strtr(
@@ -946,10 +1004,10 @@ class LocalFile extends File {
/** purgeEverything inherited */
/**
- * @param $limit null
- * @param $start null
- * @param $end null
- * @param $inc bool
+ * @param int $limit Optional: Limit to number of results
+ * @param int $start Optional: Timestamp, start from
+ * @param int $end Optional: Timestamp, end at
+ * @param bool $inc
* @return array
*/
function getHistory( $limit = null, $start = null, $end = null, $inc = true ) {
@@ -984,11 +1042,7 @@ class LocalFile extends File {
$r = array();
foreach ( $res as $row ) {
- if ( $this->repo->oldFileFromRowFactory ) {
- $r[] = call_user_func( $this->repo->oldFileFromRowFactory, $row, $this->repo );
- } else {
- $r[] = OldLocalFile::newFromRow( $row, $this->repo );
- }
+ $r[] = $this->repo->newFileFromRow( $row );
}
if ( $order == 'ASC' ) {
@@ -999,7 +1053,7 @@ class LocalFile extends File {
}
/**
- * Return the history of this file, line by line.
+ * Returns the history of this file, line by line.
* starts with current version, then old versions.
* uses $this->historyLine to check which line to return:
* 0 return line for current version
@@ -1013,7 +1067,7 @@ class LocalFile extends File {
$dbr = $this->repo->getSlaveDB();
- if ( $this->historyLine == 0 ) {// called for the first time, return line from cur
+ if ( $this->historyLine == 0 ) { // called for the first time, return line from cur
$this->historyRes = $dbr->select( 'image',
array(
'*',
@@ -1027,6 +1081,7 @@ class LocalFile extends File {
if ( 0 == $dbr->numRows( $this->historyRes ) ) {
$this->historyRes = null;
+
return false;
}
} elseif ( $this->historyLine == 1 ) {
@@ -1036,7 +1091,7 @@ class LocalFile extends File {
array( 'ORDER BY' => 'oi_timestamp DESC' )
);
}
- $this->historyLine ++;
+ $this->historyLine++;
return $dbr->fetchObject( $this->historyRes );
}
@@ -1066,21 +1121,24 @@ class LocalFile extends File {
/**
* Upload a file and record it in the DB
- * @param string $srcPath source storage path, virtual URL, or filesystem path
- * @param string $comment upload description
- * @param string $pageText text to use for the new description page,
- * if a new description page is created
- * @param $flags Integer|bool: flags for publish()
- * @param array|bool $props File properties, if known. This can be used to reduce the
- * upload time when uploading virtual URLs for which the file info
- * is already known
- * @param string|bool $timestamp timestamp for img_timestamp, or false to use the current time
- * @param $user User|null: User object or null to use $wgUser
+ * @param string $srcPath Source storage path, virtual URL, or filesystem path
+ * @param string $comment Upload description
+ * @param string $pageText Text to use for the new description page,
+ * if a new description page is created
+ * @param int|bool $flags Flags for publish()
+ * @param array|bool $props File properties, if known. This can be used to
+ * reduce the upload time when uploading virtual URLs for which the file
+ * info is already known
+ * @param string|bool $timestamp Timestamp for img_timestamp, or false to use the
+ * current time
+ * @param User|null $user User object or null to use $wgUser
*
- * @return FileRepoStatus object. On success, the value member contains the
+ * @return FileRepoStatus On success, the value member contains the
* archive name, or an empty string if it was a new file.
*/
- function upload( $srcPath, $comment, $pageText, $flags = 0, $props = false, $timestamp = false, $user = null ) {
+ function upload( $srcPath, $comment, $pageText, $flags = 0, $props = false,
+ $timestamp = false, $user = null
+ ) {
global $wgContLang;
if ( $this->getRepo()->getReadOnlyReason() !== false ) {
@@ -1090,8 +1148,8 @@ class LocalFile extends File {
if ( !$props ) {
wfProfileIn( __METHOD__ . '-getProps' );
if ( $this->repo->isVirtualUrl( $srcPath )
- || FileBackend::isStoragePath( $srcPath ) )
- {
+ || FileBackend::isStoragePath( $srcPath )
+ ) {
$props = $this->repo->getFileProps( $srcPath );
} else {
$props = FSFile::getPropsFromPath( $srcPath );
@@ -1110,16 +1168,19 @@ class LocalFile extends File {
// Trim spaces on user supplied text
$comment = trim( $comment );
- // truncate nicely or the DB will do it for us
+ // Truncate nicely or the DB will do it for us
// non-nicely (dangling multi-byte chars, non-truncated version in cache).
$comment = $wgContLang->truncate( $comment, 255 );
$this->lock(); // begin
$status = $this->publish( $srcPath, $flags, $options );
- if ( $status->successCount > 0 ) {
- # Essentially we are displacing any existing current file and saving
- # a new current file at the old location. If just the first succeeded,
- # we still need to displace the current DB entry and put in a new one.
+ if ( $status->successCount >= 2 ) {
+ // There will be a copy+(one of move,copy,store).
+ // The first succeeding does not commit us to updating the DB
+ // since it simply copied the current version to a timestamped file name.
+ // It is only *preferable* to avoid leaving such files orphaned.
+ // Once the second operation goes through, then the current version was
+ // updated and we must therefore update the DB too.
if ( !$this->recordUpload2( $status->value, $comment, $pageText, $props, $timestamp, $user ) ) {
$status->fatal( 'filenotfound', $srcPath );
}
@@ -1132,19 +1193,18 @@ class LocalFile extends File {
/**
* Record a file upload in the upload log and the image table
- * @param $oldver
- * @param $desc string
- * @param $license string
- * @param $copyStatus string
- * @param $source string
- * @param $watch bool
- * @param $timestamp string|bool
- * @param $user User object or null to use $wgUser
+ * @param string $oldver
+ * @param string $desc
+ * @param string $license
+ * @param string $copyStatus
+ * @param string $source
+ * @param bool $watch
+ * @param string|bool $timestamp
+ * @param User|null $user User object or null to use $wgUser
* @return bool
*/
function recordUpload( $oldver, $desc, $license = '', $copyStatus = '', $source = '',
- $watch = false, $timestamp = false, User $user = null )
- {
+ $watch = false, $timestamp = false, User $user = null ) {
if ( !$user ) {
global $wgUser;
$user = $wgUser;
@@ -1159,21 +1219,22 @@ class LocalFile extends File {
if ( $watch ) {
$user->addWatch( $this->getTitle() );
}
+
return true;
}
/**
* Record a file upload in the upload log and the image table
- * @param $oldver
- * @param $comment string
- * @param $pageText string
- * @param $props bool|array
- * @param $timestamp bool|string
- * @param $user null|User
+ * @param string $oldver
+ * @param string $comment
+ * @param string $pageText
+ * @param bool|array $props
+ * @param string|bool $timestamp
+ * @param null|User $user
* @return bool
*/
- function recordUpload2(
- $oldver, $comment, $pageText, $props = false, $timestamp = false, $user = null
+ function recordUpload2( $oldver, $comment, $pageText, $props = false, $timestamp = false,
+ $user = null
) {
wfProfileIn( __METHOD__ );
@@ -1191,8 +1252,13 @@ class LocalFile extends File {
wfProfileOut( __METHOD__ . '-getProps' );
}
+ # Imports or such might force a certain timestamp; otherwise we generate
+ # it and can fudge it slightly to keep (name,timestamp) unique on re-upload.
if ( $timestamp === false ) {
$timestamp = $dbw->timestamp();
+ $allowTimeKludge = true;
+ } else {
+ $allowTimeKludge = false;
}
$props['description'] = $comment;
@@ -1204,7 +1270,9 @@ class LocalFile extends File {
# Fail now if the file isn't there
if ( !$this->fileExists ) {
wfDebug( __METHOD__ . ": File " . $this->getRel() . " went missing!\n" );
+ $dbw->rollback( __METHOD__ );
wfProfileOut( __METHOD__ );
+
return false;
}
@@ -1227,13 +1295,27 @@ class LocalFile extends File {
'img_description' => $comment,
'img_user' => $user->getId(),
'img_user_text' => $user->getName(),
- 'img_metadata' => $dbw->encodeBlob($this->metadata),
+ 'img_metadata' => $dbw->encodeBlob( $this->metadata ),
'img_sha1' => $this->sha1
),
__METHOD__,
'IGNORE'
);
if ( $dbw->affectedRows() == 0 ) {
+ if ( $allowTimeKludge ) {
+ # Use FOR UPDATE to ignore any transaction snapshotting
+ $ltimestamp = $dbw->selectField( 'image', 'img_timestamp',
+ array( 'img_name' => $this->getName() ), __METHOD__, array( 'FOR UPDATE' ) );
+ $lUnixtime = $ltimestamp ? wfTimestamp( TS_UNIX, $ltimestamp ) : false;
+ # Avoid a timestamp that is not newer than the last version
+ # TODO: the image/oldimage tables should be like page/revision with an ID field
+ if ( $lUnixtime && wfTimestamp( TS_UNIX, $timestamp ) <= $lUnixtime ) {
+ sleep( 1 ); // fast enough re-uploads would go far in the future otherwise
+ $timestamp = $dbw->timestamp( $lUnixtime + 1 );
+ $this->timestamp = wfTimestamp( TS_MW, $timestamp ); // DB -> TS_MW
+ }
+ }
+
# (bug 34993) Note: $oldver can be empty here, if the previous
# version of the file was broken. Allow registration of the new
# version to continue anyway, because that's better than having
@@ -1244,21 +1326,21 @@ class LocalFile extends File {
# Insert previous contents into oldimage
$dbw->insertSelect( 'oldimage', 'image',
array(
- 'oi_name' => 'img_name',
+ 'oi_name' => 'img_name',
'oi_archive_name' => $dbw->addQuotes( $oldver ),
- 'oi_size' => 'img_size',
- 'oi_width' => 'img_width',
- 'oi_height' => 'img_height',
- 'oi_bits' => 'img_bits',
- 'oi_timestamp' => 'img_timestamp',
- 'oi_description' => 'img_description',
- 'oi_user' => 'img_user',
- 'oi_user_text' => 'img_user_text',
- 'oi_metadata' => 'img_metadata',
- 'oi_media_type' => 'img_media_type',
- 'oi_major_mime' => 'img_major_mime',
- 'oi_minor_mime' => 'img_minor_mime',
- 'oi_sha1' => 'img_sha1'
+ 'oi_size' => 'img_size',
+ 'oi_width' => 'img_width',
+ 'oi_height' => 'img_height',
+ 'oi_bits' => 'img_bits',
+ 'oi_timestamp' => 'img_timestamp',
+ 'oi_description' => 'img_description',
+ 'oi_user' => 'img_user',
+ 'oi_user_text' => 'img_user_text',
+ 'oi_metadata' => 'img_metadata',
+ 'oi_media_type' => 'img_media_type',
+ 'oi_major_mime' => 'img_major_mime',
+ 'oi_minor_mime' => 'img_minor_mime',
+ 'oi_sha1' => 'img_sha1'
),
array( 'img_name' => $this->getName() ),
__METHOD__
@@ -1267,19 +1349,19 @@ class LocalFile extends File {
# Update the current image row
$dbw->update( 'image',
array( /* SET */
- 'img_size' => $this->size,
- 'img_width' => intval( $this->width ),
- 'img_height' => intval( $this->height ),
- 'img_bits' => $this->bits,
- 'img_media_type' => $this->media_type,
- 'img_major_mime' => $this->major_mime,
- 'img_minor_mime' => $this->minor_mime,
- 'img_timestamp' => $timestamp,
+ 'img_size' => $this->size,
+ 'img_width' => intval( $this->width ),
+ 'img_height' => intval( $this->height ),
+ 'img_bits' => $this->bits,
+ 'img_media_type' => $this->media_type,
+ 'img_major_mime' => $this->major_mime,
+ 'img_minor_mime' => $this->minor_mime,
+ 'img_timestamp' => $timestamp,
'img_description' => $comment,
- 'img_user' => $user->getId(),
- 'img_user_text' => $user->getName(),
- 'img_metadata' => $dbw->encodeBlob($this->metadata),
- 'img_sha1' => $this->sha1
+ 'img_user' => $user->getId(),
+ 'img_user_text' => $user->getName(),
+ 'img_metadata' => $dbw->encodeBlob( $this->metadata ),
+ 'img_sha1' => $this->sha1
),
array( 'img_name' => $this->getName() ),
__METHOD__
@@ -1333,7 +1415,8 @@ class LocalFile extends File {
$dbw,
$descTitle->getArticleID(),
$editSummary,
- false
+ false,
+ $user
);
if ( !is_null( $nullRevision ) ) {
$nullRevision->insertOn( $dbw );
@@ -1349,6 +1432,12 @@ class LocalFile extends File {
# to after $wikiPage->doEdit has been called.
$dbw->commit( __METHOD__ );
+ # Save to memcache.
+ # We shall not saveToCache before the commit since otherwise
+ # in case of a rollback there is an usable file from memcached
+ # which in fact doesn't really exist (bug 24978)
+ $this->saveToCache();
+
if ( $exists ) {
# Invalidate the cache for the description page
$descTitle->invalidateCache();
@@ -1358,7 +1447,13 @@ class LocalFile extends File {
# There's already a log entry, so don't make a second RC entry
# Squid and file cache for the description page are purged by doEditContent.
$content = ContentHandler::makeContent( $pageText, $descTitle );
- $status = $wikiPage->doEditContent( $content, $comment, EDIT_NEW | EDIT_SUPPRESS_RC, false, $user );
+ $status = $wikiPage->doEditContent(
+ $content,
+ $comment,
+ EDIT_NEW | EDIT_SUPPRESS_RC,
+ false,
+ $user
+ );
$dbw->begin( __METHOD__ ); // XXX; doEdit() uses a transaction
// Now that the page exists, make an RC entry.
@@ -1373,15 +1468,8 @@ class LocalFile extends File {
$dbw->commit( __METHOD__ ); // commit before anything bad can happen
}
-
wfProfileOut( __METHOD__ . '-edit' );
- # Save to cache and purge the squid
- # We shall not saveToCache before the commit since otherwise
- # in case of a rollback there is an usable file from memcached
- # which in fact doesn't really exist (bug 24978)
- $this->saveToCache();
-
if ( $reupload ) {
# Delete old thumbnails
wfProfileIn( __METHOD__ . '-purge' );
@@ -1404,18 +1492,8 @@ class LocalFile extends File {
LinksUpdate::queueRecursiveJobsForTable( $this->getTitle(), 'imagelinks' );
}
- # Invalidate cache for all pages that redirects on this page
- $redirs = $this->getTitle()->getRedirectsHere();
-
- foreach ( $redirs as $redir ) {
- if ( !$reupload && $redir->getNamespace() === NS_FILE ) {
- LinksUpdate::queueRecursiveJobsForTable( $redir, 'imagelinks' );
- }
- $update = new HTMLCacheUpdate( $redir, 'imagelinks' );
- $update->doUpdate();
- }
-
wfProfileOut( __METHOD__ );
+
return true;
}
@@ -1427,11 +1505,11 @@ class LocalFile extends File {
* The archive name should be passed through to recordUpload for database
* registration.
*
- * @param string $srcPath local filesystem path to the source image
- * @param $flags Integer: a bitwise combination of:
- * File::DELETE_SOURCE Delete the source file, i.e. move rather than copy
+ * @param string $srcPath Local filesystem path to the source image
+ * @param int $flags A bitwise combination of:
+ * File::DELETE_SOURCE Delete the source file, i.e. move rather than copy
* @param array $options Optional additional parameters
- * @return FileRepoStatus object. On success, the value member contains the
+ * @return FileRepoStatus On success, the value member contains the
* archive name, or an empty string if it was a new file.
*/
function publish( $srcPath, $flags = 0, array $options = array() ) {
@@ -1445,12 +1523,12 @@ class LocalFile extends File {
* The archive name should be passed through to recordUpload for database
* registration.
*
- * @param string $srcPath local filesystem path to the source image
- * @param string $dstRel target relative path
- * @param $flags Integer: a bitwise combination of:
- * File::DELETE_SOURCE Delete the source file, i.e. move rather than copy
+ * @param string $srcPath Local filesystem path to the source image
+ * @param string $dstRel Target relative path
+ * @param int $flags A bitwise combination of:
+ * File::DELETE_SOURCE Delete the source file, i.e. move rather than copy
* @param array $options Optional additional parameters
- * @return FileRepoStatus object. On success, the value member contains the
+ * @return FileRepoStatus On success, the value member contains the
* archive name, or an empty string if it was a new file.
*/
function publishTo( $srcPath, $dstRel, $flags = 0, array $options = array() ) {
@@ -1490,8 +1568,8 @@ class LocalFile extends File {
* Cache purging is done; checks for validity
* and logging are caller's responsibility
*
- * @param $target Title New file name
- * @return FileRepoStatus object.
+ * @param Title $target New file name
+ * @return FileRepoStatus
*/
function move( $target ) {
if ( $this->getRepo()->getReadOnlyReason() !== false ) {
@@ -1515,7 +1593,7 @@ class LocalFile extends File {
// Hack: the lock()/unlock() pair is nested in a transaction so the locking is not
// tied to BEGIN/COMMIT. To avoid slow purges in the transaction, move them outside.
$this->getRepo()->getMasterDB()->onTransactionIdle(
- function() use ( $oldTitleFile, $newTitleFile, $archiveNames ) {
+ function () use ( $oldTitleFile, $newTitleFile, $archiveNames ) {
$oldTitleFile->purgeEverything();
foreach ( $archiveNames as $archiveName ) {
$oldTitleFile->purgeOldThumbnails( $archiveName );
@@ -1543,16 +1621,17 @@ class LocalFile extends File {
*
* Cache purging is done; logging is caller's responsibility.
*
- * @param $reason
- * @param $suppress
- * @return FileRepoStatus object.
+ * @param string $reason
+ * @param bool $suppress
+ * @param User|null $user
+ * @return FileRepoStatus
*/
- function delete( $reason, $suppress = false ) {
+ function delete( $reason, $suppress = false, $user = null ) {
if ( $this->getRepo()->getReadOnlyReason() !== false ) {
return $this->readOnlyFatalStatus();
}
- $batch = new LocalFileDeleteBatch( $this, $reason, $suppress );
+ $batch = new LocalFileDeleteBatch( $this, $reason, $suppress, $user );
$this->lock(); // begin
$batch->addCurrent();
@@ -1569,7 +1648,7 @@ class LocalFile extends File {
// tied to BEGIN/COMMIT. To avoid slow purges in the transaction, move them outside.
$file = $this;
$this->getRepo()->getMasterDB()->onTransactionIdle(
- function() use ( $file, $archiveNames ) {
+ function () use ( $file, $archiveNames ) {
global $wgUseSquid;
$file->purgeEverything();
@@ -1599,19 +1678,20 @@ class LocalFile extends File {
*
* Cache purging is done; logging is caller's responsibility.
*
- * @param $archiveName String
- * @param $reason String
- * @param $suppress Boolean
- * @throws MWException or FSException on database or file store failure
- * @return FileRepoStatus object.
+ * @param string $archiveName
+ * @param string $reason
+ * @param bool $suppress
+ * @param User|null $user
+ * @throws MWException Exception on database or file store failure
+ * @return FileRepoStatus
*/
- function deleteOld( $archiveName, $reason, $suppress = false ) {
+ function deleteOld( $archiveName, $reason, $suppress = false, $user = null ) {
global $wgUseSquid;
if ( $this->getRepo()->getReadOnlyReason() !== false ) {
return $this->readOnlyFatalStatus();
}
- $batch = new LocalFileDeleteBatch( $this, $reason, $suppress );
+ $batch = new LocalFileDeleteBatch( $this, $reason, $suppress, $user );
$this->lock(); // begin
$batch->addOld( $archiveName );
@@ -1638,9 +1718,9 @@ class LocalFile extends File {
*
* May throw database exceptions on error.
*
- * @param array $versions set of record ids of deleted items to restore,
- * or empty to restore all revisions.
- * @param $unsuppress Boolean
+ * @param array $versions Set of record ids of deleted items to restore,
+ * or empty to restore all revisions.
+ * @param bool $unsuppress
* @return FileRepoStatus
*/
function restore( $versions = array(), $unsuppress = false ) {
@@ -1675,7 +1755,7 @@ class LocalFile extends File {
/**
* Get the URL of the file description page.
- * @return String
+ * @return string
*/
function getDescriptionUrl() {
return $this->title->getLocalURL();
@@ -1686,7 +1766,7 @@ class LocalFile extends File {
* This is not used by ImagePage for local files, since (among other things)
* it skips the parser cache.
*
- * @param $lang Language What language to get description in (Optional)
+ * @param Language $lang What language to get description in (Optional)
* @return bool|mixed
*/
function getDescriptionText( $lang = null ) {
@@ -1699,10 +1779,13 @@ class LocalFile extends File {
return false;
}
$pout = $content->getParserOutput( $this->title, null, new ParserOptions( null, $lang ) );
+
return $pout->getText();
}
/**
+ * @param int $audience
+ * @param User $user
* @return string
*/
function getDescription( $audience = self::FOR_PUBLIC, User $user = null ) {
@@ -1710,8 +1793,8 @@ class LocalFile extends File {
if ( $audience == self::FOR_PUBLIC && $this->isDeleted( self::DELETED_COMMENT ) ) {
return '';
} elseif ( $audience == self::FOR_THIS_USER
- && !$this->userCan( self::DELETED_COMMENT, $user ) )
- {
+ && !$this->userCan( self::DELETED_COMMENT, $user )
+ ) {
return '';
} else {
return $this->description;
@@ -1723,6 +1806,7 @@ class LocalFile extends File {
*/
function getTimestamp() {
$this->load();
+
return $this->timestamp;
}
@@ -1756,15 +1840,17 @@ class LocalFile extends File {
*/
function isCacheable() {
$this->load();
+
// If extra data (metadata) was not loaded then it must have been large
return $this->extraDataLoaded
- && strlen( serialize( $this->metadata ) ) <= self::CACHE_FIELD_MAX_LEN;
+ && strlen( serialize( $this->metadata ) ) <= self::CACHE_FIELD_MAX_LEN;
}
/**
* Start a transaction and lock the image for update
* Increments a reference counter if the lock is already held
- * @return boolean True if the image exists, false otherwise
+ * @throws MWException Throws an error if the lock was not acquired
+ * @return bool Success
*/
function lock() {
$dbw = $this->repo->getMasterDB();
@@ -1776,19 +1862,22 @@ class LocalFile extends File {
}
$this->locked++;
// Bug 54736: use simple lock to handle when the file does not exist.
- // SELECT FOR UPDATE only locks records not the gaps where there are none.
- $cache = wfGetMainCache();
- $key = $this->getCacheKey();
- if ( !$cache->lock( $key, 60 ) ) {
+ // SELECT FOR UPDATE prevents changes, not other SELECTs with FOR UPDATE.
+ // Also, that would cause contention on INSERT of similarly named rows.
+ $backend = $this->getRepo()->getBackend();
+ $lockPaths = array( $this->getPath() ); // represents all versions of the file
+ $status = $backend->lockFiles( $lockPaths, LockManager::LOCK_EX, 5 );
+ if ( !$status->isGood() ) {
throw new MWException( "Could not acquire lock for '{$this->getName()}.'" );
}
- $dbw->onTransactionIdle( function() use ( $cache, $key ) {
- $cache->unlock( $key ); // release on commit
+ $dbw->onTransactionIdle( function () use ( $backend, $lockPaths ) {
+ $backend->unlockFiles( $lockPaths, LockManager::LOCK_EX ); // release on commit
} );
}
- return $dbw->selectField( 'image', '1',
- array( 'img_name' => $this->getName() ), __METHOD__, array( 'FOR UPDATE' ) );
+ $this->markVolatile(); // file may change soon
+
+ return true;
}
/**
@@ -1807,6 +1896,48 @@ class LocalFile extends File {
}
/**
+ * Mark a file as about to be changed
+ *
+ * This sets a cache key that alters master/slave DB loading behavior
+ *
+ * @return bool Success
+ */
+ protected function markVolatile() {
+ global $wgMemc;
+
+ $key = $this->repo->getSharedCacheKey( 'file-volatile', md5( $this->getName() ) );
+ if ( $key ) {
+ $this->lastMarkedVolatile = time();
+ return $wgMemc->set( $key, $this->lastMarkedVolatile, self::VOLATILE_TTL );
+ }
+
+ return true;
+ }
+
+ /**
+ * Check if a file is about to be changed or has been changed recently
+ *
+ * @see LocalFile::isVolatile()
+ * @return bool Whether the file is volatile
+ */
+ protected function isVolatile() {
+ global $wgMemc;
+
+ $key = $this->repo->getSharedCacheKey( 'file-volatile', md5( $this->getName() ) );
+ if ( !$key ) {
+ // repo unavailable; bail.
+ return false;
+ }
+
+ if ( $this->lastMarkedVolatile === 0 ) {
+ $this->lastMarkedVolatile = $wgMemc->get( $key ) ?: 0;
+ }
+
+ $volatileDuration = time() - $this->lastMarkedVolatile;
+ return $volatileDuration <= self::VOLATILE_TTL;
+ }
+
+ /**
* Roll back the DB transaction and mark the image unlocked
*/
function unlockAndRollback() {
@@ -1823,6 +1954,13 @@ class LocalFile extends File {
return $this->getRepo()->newFatal( 'filereadonlyerror', $this->getName(),
$this->getRepo()->getName(), $this->getRepo()->getReadOnlyReason() );
}
+
+ /**
+ * Clean up any dangling locks
+ */
+ function __destruct() {
+ $this->unlock();
+ }
} // LocalFile class
# ------------------------------------------------------------------------------
@@ -1832,24 +1970,46 @@ class LocalFile extends File {
* @ingroup FileAbstraction
*/
class LocalFileDeleteBatch {
+ /** @var LocalFile */
+ private $file;
- /**
- * @var LocalFile
- */
- var $file;
+ /** @var string */
+ private $reason;
+
+ /** @var array */
+ private $srcRels = array();
+
+ /** @var array */
+ private $archiveUrls = array();
+
+ /** @var array Items to be processed in the deletion batch */
+ private $deletionBatch;
- var $reason, $srcRels = array(), $archiveUrls = array(), $deletionBatch, $suppress;
- var $status;
+ /** @var bool Wether to suppress all suppressable fields when deleting */
+ private $suppress;
+
+ /** @var FileRepoStatus */
+ private $status;
+
+ /** @var User */
+ private $user;
/**
- * @param $file File
- * @param $reason string
- * @param $suppress bool
+ * @param File $file
+ * @param string $reason
+ * @param bool $suppress
+ * @param User|null $user
*/
- function __construct( File $file, $reason = '', $suppress = false ) {
+ function __construct( File $file, $reason = '', $suppress = false, $user = null ) {
$this->file = $file;
$this->reason = $reason;
$this->suppress = $suppress;
+ if ( $user ) {
+ $this->user = $user;
+ } else {
+ global $wgUser;
+ $this->user = $wgUser;
+ }
$this->status = $file->repo->newGood();
}
@@ -1858,7 +2018,7 @@ class LocalFileDeleteBatch {
}
/**
- * @param $oldName string
+ * @param string $oldName
*/
function addOld( $oldName ) {
$this->srcRels[$oldName] = $this->file->getArchiveRel( $oldName );
@@ -1867,7 +2027,7 @@ class LocalFileDeleteBatch {
/**
* Add the old versions of the image to the batch
- * @return Array List of archive names from old versions
+ * @return array List of archive names from old versions
*/
function addOlds() {
$archiveNames = array();
@@ -1919,7 +2079,8 @@ class LocalFileDeleteBatch {
$res = $dbw->select(
'oldimage',
array( 'oi_archive_name', 'oi_sha1' ),
- 'oi_archive_name IN (' . $dbw->makeList( array_keys( $oldRels ) ) . ')',
+ array( 'oi_archive_name' => array_keys( $oldRels ),
+ 'oi_name' => $this->file->getName() ), // performance
__METHOD__
);
@@ -1962,11 +2123,9 @@ class LocalFileDeleteBatch {
}
function doDBInserts() {
- global $wgUser;
-
$dbw = $this->file->repo->getMasterDB();
$encTimestamp = $dbw->addQuotes( $dbw->timestamp() );
- $encUserId = $dbw->addQuotes( $wgUser->getId() );
+ $encUserId = $dbw->addQuotes( $this->user->getId() );
$encReason = $dbw->addQuotes( $this->reason );
$encGroup = $dbw->addQuotes( 'deleted' );
$ext = $this->file->getExtension();
@@ -1992,27 +2151,31 @@ class LocalFileDeleteBatch {
$dbw->insertSelect( 'filearchive', 'image',
array(
'fa_storage_group' => $encGroup,
- 'fa_storage_key' => "CASE WHEN img_sha1='' THEN '' ELSE $concat END",
- 'fa_deleted_user' => $encUserId,
+ 'fa_storage_key' => $dbw->conditional(
+ array( 'img_sha1' => '' ),
+ $dbw->addQuotes( '' ),
+ $concat
+ ),
+ 'fa_deleted_user' => $encUserId,
'fa_deleted_timestamp' => $encTimestamp,
- 'fa_deleted_reason' => $encReason,
- 'fa_deleted' => $this->suppress ? $bitfield : 0,
+ 'fa_deleted_reason' => $encReason,
+ 'fa_deleted' => $this->suppress ? $bitfield : 0,
- 'fa_name' => 'img_name',
+ 'fa_name' => 'img_name',
'fa_archive_name' => 'NULL',
- 'fa_size' => 'img_size',
- 'fa_width' => 'img_width',
- 'fa_height' => 'img_height',
- 'fa_metadata' => 'img_metadata',
- 'fa_bits' => 'img_bits',
- 'fa_media_type' => 'img_media_type',
- 'fa_major_mime' => 'img_major_mime',
- 'fa_minor_mime' => 'img_minor_mime',
- 'fa_description' => 'img_description',
- 'fa_user' => 'img_user',
- 'fa_user_text' => 'img_user_text',
- 'fa_timestamp' => 'img_timestamp',
- 'fa_sha1' => 'img_sha1',
+ 'fa_size' => 'img_size',
+ 'fa_width' => 'img_width',
+ 'fa_height' => 'img_height',
+ 'fa_metadata' => 'img_metadata',
+ 'fa_bits' => 'img_bits',
+ 'fa_media_type' => 'img_media_type',
+ 'fa_major_mime' => 'img_major_mime',
+ 'fa_minor_mime' => 'img_minor_mime',
+ 'fa_description' => 'img_description',
+ 'fa_user' => 'img_user',
+ 'fa_user_text' => 'img_user_text',
+ 'fa_timestamp' => 'img_timestamp',
+ 'fa_sha1' => 'img_sha1',
), $where, __METHOD__ );
}
@@ -2020,31 +2183,35 @@ class LocalFileDeleteBatch {
$concat = $dbw->buildConcat( array( "oi_sha1", $encExt ) );
$where = array(
'oi_name' => $this->file->getName(),
- 'oi_archive_name IN (' . $dbw->makeList( array_keys( $oldRels ) ) . ')' );
+ 'oi_archive_name' => array_keys( $oldRels ) );
$dbw->insertSelect( 'filearchive', 'oldimage',
array(
'fa_storage_group' => $encGroup,
- 'fa_storage_key' => "CASE WHEN oi_sha1='' THEN '' ELSE $concat END",
- 'fa_deleted_user' => $encUserId,
+ 'fa_storage_key' => $dbw->conditional(
+ array( 'oi_sha1' => '' ),
+ $dbw->addQuotes( '' ),
+ $concat
+ ),
+ 'fa_deleted_user' => $encUserId,
'fa_deleted_timestamp' => $encTimestamp,
- 'fa_deleted_reason' => $encReason,
- 'fa_deleted' => $this->suppress ? $bitfield : 'oi_deleted',
+ 'fa_deleted_reason' => $encReason,
+ 'fa_deleted' => $this->suppress ? $bitfield : 'oi_deleted',
- 'fa_name' => 'oi_name',
+ 'fa_name' => 'oi_name',
'fa_archive_name' => 'oi_archive_name',
- 'fa_size' => 'oi_size',
- 'fa_width' => 'oi_width',
- 'fa_height' => 'oi_height',
- 'fa_metadata' => 'oi_metadata',
- 'fa_bits' => 'oi_bits',
- 'fa_media_type' => 'oi_media_type',
- 'fa_major_mime' => 'oi_major_mime',
- 'fa_minor_mime' => 'oi_minor_mime',
- 'fa_description' => 'oi_description',
- 'fa_user' => 'oi_user',
- 'fa_user_text' => 'oi_user_text',
- 'fa_timestamp' => 'oi_timestamp',
- 'fa_sha1' => 'oi_sha1',
+ 'fa_size' => 'oi_size',
+ 'fa_width' => 'oi_width',
+ 'fa_height' => 'oi_height',
+ 'fa_metadata' => 'oi_metadata',
+ 'fa_bits' => 'oi_bits',
+ 'fa_media_type' => 'oi_media_type',
+ 'fa_major_mime' => 'oi_major_mime',
+ 'fa_minor_mime' => 'oi_minor_mime',
+ 'fa_description' => 'oi_description',
+ 'fa_user' => 'oi_user',
+ 'fa_user_text' => 'oi_user_text',
+ 'fa_timestamp' => 'oi_timestamp',
+ 'fa_sha1' => 'oi_sha1',
), $where, __METHOD__ );
}
}
@@ -2083,7 +2250,7 @@ class LocalFileDeleteBatch {
$res = $dbw->select( 'oldimage',
array( 'oi_archive_name' ),
array( 'oi_name' => $this->file->getName(),
- 'oi_archive_name IN (' . $dbw->makeList( array_keys( $oldRels ) ) . ')',
+ 'oi_archive_name' => array_keys( $oldRels ),
$dbw->bitAnd( 'oi_deleted', File::DELETED_FILE ) => File::DELETED_FILE ),
__METHOD__ );
@@ -2117,7 +2284,12 @@ class LocalFileDeleteBatch {
$this->doDBInserts();
// Removes non-existent file from the batch, so we don't get errors.
- $this->deletionBatch = $this->removeNonexistentFiles( $this->deletionBatch );
+ $checkStatus = $this->removeNonexistentFiles( $this->deletionBatch );
+ if ( !$checkStatus->isGood() ) {
+ $this->status->merge( $checkStatus );
+ return $this->status;
+ }
+ $this->deletionBatch = $checkStatus->value;
// Execute the file deletion batch
$status = $this->file->repo->deleteBatch( $this->deletionBatch );
@@ -2132,6 +2304,7 @@ class LocalFileDeleteBatch {
// TODO: delete the defunct filearchive rows if we are using a non-transactional DB
$this->file->unlockAndRollback();
wfProfileOut( __METHOD__ );
+
return $this->status;
}
@@ -2147,8 +2320,8 @@ class LocalFileDeleteBatch {
/**
* Removes non-existent files from a deletion batch.
- * @param $batch array
- * @return array
+ * @param array $batch
+ * @return Status
*/
function removeNonexistentFiles( $batch ) {
$files = $newBatch = array();
@@ -2159,6 +2332,10 @@ class LocalFileDeleteBatch {
}
$result = $this->file->repo->fileExistsBatch( $files );
+ if ( in_array( null, $result, true ) ) {
+ return Status::newFatal( 'backend-fail-internal',
+ $this->file->repo->getBackend()->getName() );
+ }
foreach ( $batch as $batchItem ) {
if ( $result[$batchItem[0]] ) {
@@ -2166,7 +2343,7 @@ class LocalFileDeleteBatch {
}
}
- return $newBatch;
+ return Status::newGood( $newBatch );
}
}
@@ -2177,16 +2354,24 @@ class LocalFileDeleteBatch {
* @ingroup FileAbstraction
*/
class LocalFileRestoreBatch {
- /**
- * @var LocalFile
- */
- var $file;
+ /** @var LocalFile */
+ private $file;
+
+ /** @var array List of file IDs to restore */
+ private $cleanupBatch;
+
+ /** @var array List of file IDs to restore */
+ private $ids;
+
+ /** @var bool Add all revisions of the file */
+ private $all;
- var $cleanupBatch, $ids, $all, $unsuppress = false;
+ /** @var bool Wether to remove all settings for suppressed fields */
+ private $unsuppress = false;
/**
- * @param $file File
- * @param $unsuppress bool
+ * @param File $file
+ * @param bool $unsuppress
*/
function __construct( File $file, $unsuppress = false ) {
$this->file = $file;
@@ -2197,6 +2382,7 @@ class LocalFileRestoreBatch {
/**
* Add a file by ID
+ * @param int $fa_id
*/
function addId( $fa_id ) {
$this->ids[] = $fa_id;
@@ -2204,6 +2390,7 @@ class LocalFileRestoreBatch {
/**
* Add a whole lot of files by ID
+ * @param int[] $ids
*/
function addIds( $ids ) {
$this->ids = array_merge( $this->ids, $ids );
@@ -2232,16 +2419,20 @@ class LocalFileRestoreBatch {
return $this->file->repo->newGood();
}
- $exists = $this->file->lock();
+ $this->file->lock();
+
$dbw = $this->file->repo->getMasterDB();
$status = $this->file->repo->newGood();
+ $exists = (bool)$dbw->selectField( 'image', '1',
+ array( 'img_name' => $this->file->getName() ), __METHOD__, array( 'FOR UPDATE' ) );
+
// Fetch all or selected archived revisions for the file,
// sorted from the most recent to the oldest.
$conditions = array( 'fa_name' => $this->file->getName() );
if ( !$this->all ) {
- $conditions[] = 'fa_id IN (' . $dbw->makeList( $this->ids ) . ')';
+ $conditions['fa_id'] = $this->ids;
}
$result = $dbw->select(
@@ -2276,7 +2467,8 @@ class LocalFileRestoreBatch {
continue;
}
- $deletedRel = $this->file->repo->getDeletedHashPath( $row->fa_storage_key ) . $row->fa_storage_key;
+ $deletedRel = $this->file->repo->getDeletedHashPath( $row->fa_storage_key ) .
+ $row->fa_storage_key;
$deletedUrl = $this->file->repo->getVirtualUrl() . '/deleted/' . $deletedRel;
if ( isset( $row->fa_sha1 ) ) {
@@ -2294,7 +2486,8 @@ class LocalFileRestoreBatch {
if ( is_null( $row->fa_major_mime ) || $row->fa_major_mime == 'unknown'
|| is_null( $row->fa_minor_mime ) || $row->fa_minor_mime == 'unknown'
|| is_null( $row->fa_media_type ) || $row->fa_media_type == 'UNKNOWN'
- || is_null( $row->fa_metadata ) ) {
+ || is_null( $row->fa_metadata )
+ ) {
// Refresh our metadata
// Required for a new current revision; nice for older ones too. :)
$props = RepoGroup::singleton()->getFileProps( $deletedUrl );
@@ -2303,7 +2496,7 @@ class LocalFileRestoreBatch {
'minor_mime' => $row->fa_minor_mime,
'major_mime' => $row->fa_major_mime,
'media_type' => $row->fa_media_type,
- 'metadata' => $row->fa_metadata
+ 'metadata' => $row->fa_metadata
);
}
@@ -2311,20 +2504,20 @@ class LocalFileRestoreBatch {
// This revision will be published as the new current version
$destRel = $this->file->getRel();
$insertCurrent = array(
- 'img_name' => $row->fa_name,
- 'img_size' => $row->fa_size,
- 'img_width' => $row->fa_width,
- 'img_height' => $row->fa_height,
- 'img_metadata' => $props['metadata'],
- 'img_bits' => $row->fa_bits,
- 'img_media_type' => $props['media_type'],
- 'img_major_mime' => $props['major_mime'],
- 'img_minor_mime' => $props['minor_mime'],
+ 'img_name' => $row->fa_name,
+ 'img_size' => $row->fa_size,
+ 'img_width' => $row->fa_width,
+ 'img_height' => $row->fa_height,
+ 'img_metadata' => $props['metadata'],
+ 'img_bits' => $row->fa_bits,
+ 'img_media_type' => $props['media_type'],
+ 'img_major_mime' => $props['major_mime'],
+ 'img_minor_mime' => $props['minor_mime'],
'img_description' => $row->fa_description,
- 'img_user' => $row->fa_user,
- 'img_user_text' => $row->fa_user_text,
- 'img_timestamp' => $row->fa_timestamp,
- 'img_sha1' => $sha1
+ 'img_user' => $row->fa_user,
+ 'img_user_text' => $row->fa_user_text,
+ 'img_timestamp' => $row->fa_timestamp,
+ 'img_sha1' => $sha1
);
// The live (current) version cannot be hidden!
@@ -2350,22 +2543,22 @@ class LocalFileRestoreBatch {
$archiveNames[$archiveName] = true;
$destRel = $this->file->getArchiveRel( $archiveName );
$insertBatch[] = array(
- 'oi_name' => $row->fa_name,
+ 'oi_name' => $row->fa_name,
'oi_archive_name' => $archiveName,
- 'oi_size' => $row->fa_size,
- 'oi_width' => $row->fa_width,
- 'oi_height' => $row->fa_height,
- 'oi_bits' => $row->fa_bits,
- 'oi_description' => $row->fa_description,
- 'oi_user' => $row->fa_user,
- 'oi_user_text' => $row->fa_user_text,
- 'oi_timestamp' => $row->fa_timestamp,
- 'oi_metadata' => $props['metadata'],
- 'oi_media_type' => $props['media_type'],
- 'oi_major_mime' => $props['major_mime'],
- 'oi_minor_mime' => $props['minor_mime'],
- 'oi_deleted' => $this->unsuppress ? 0 : $row->fa_deleted,
- 'oi_sha1' => $sha1 );
+ 'oi_size' => $row->fa_size,
+ 'oi_width' => $row->fa_width,
+ 'oi_height' => $row->fa_height,
+ 'oi_bits' => $row->fa_bits,
+ 'oi_description' => $row->fa_description,
+ 'oi_user' => $row->fa_user,
+ 'oi_user_text' => $row->fa_user_text,
+ 'oi_timestamp' => $row->fa_timestamp,
+ 'oi_metadata' => $props['metadata'],
+ 'oi_media_type' => $props['media_type'],
+ 'oi_major_mime' => $props['major_mime'],
+ 'oi_minor_mime' => $props['minor_mime'],
+ 'oi_deleted' => $this->unsuppress ? 0 : $row->fa_deleted,
+ 'oi_sha1' => $sha1 );
}
$deleteIds[] = $row->fa_id;
@@ -2391,7 +2584,12 @@ class LocalFileRestoreBatch {
}
// Remove missing files from batch, so we don't get errors when undeleting them
- $storeBatch = $this->removeNonexistentFiles( $storeBatch );
+ $checkStatus = $this->removeNonexistentFiles( $storeBatch );
+ if ( !$checkStatus->isGood() ) {
+ $status->merge( $checkStatus );
+ return $status;
+ }
+ $storeBatch = $checkStatus->value;
// Run the store batch
// Use the OVERWRITE_SAME flag to smooth over a common error
@@ -2424,7 +2622,7 @@ class LocalFileRestoreBatch {
if ( $deleteIds ) {
$dbw->delete( 'filearchive',
- array( 'fa_id IN (' . $dbw->makeList( $deleteIds ) . ')' ),
+ array( 'fa_id' => $deleteIds ),
__METHOD__ );
}
@@ -2450,8 +2648,8 @@ class LocalFileRestoreBatch {
/**
* Removes non-existent files from a store batch.
- * @param $triplets array
- * @return array
+ * @param array $triplets
+ * @return Status
*/
function removeNonexistentFiles( $triplets ) {
$files = $filteredTriplets = array();
@@ -2460,6 +2658,10 @@ class LocalFileRestoreBatch {
}
$result = $this->file->repo->fileExistsBatch( $files );
+ if ( in_array( null, $result, true ) ) {
+ return Status::newFatal( 'backend-fail-internal',
+ $this->file->repo->getBackend()->getName() );
+ }
foreach ( $triplets as $file ) {
if ( $result[$file[0]] ) {
@@ -2467,12 +2669,12 @@ class LocalFileRestoreBatch {
}
}
- return $filteredTriplets;
+ return Status::newGood( $filteredTriplets );
}
/**
* Removes non-existent files from a cleanup batch.
- * @param $batch array
+ * @param array $batch
* @return array
*/
function removeNonexistentFromCleanup( $batch ) {
@@ -2541,23 +2743,22 @@ class LocalFileRestoreBatch {
* @ingroup FileAbstraction
*/
class LocalFileMoveBatch {
+ /** @var LocalFile */
+ protected $file;
- /**
- * @var LocalFile
- */
- var $file;
+ /** @var Title */
+ protected $target;
- /**
- * @var Title
- */
- var $target;
+ protected $cur;
- var $cur, $olds, $oldCount, $archive;
+ protected $olds;
- /**
- * @var DatabaseBase
- */
- var $db;
+ protected $oldCount;
+
+ protected $archive;
+
+ /** @var DatabaseBase */
+ protected $db;
/**
* @param File $file
@@ -2584,7 +2785,7 @@ class LocalFileMoveBatch {
/**
* Add the old versions of the image to the batch
- * @return Array List of archive names from old versions
+ * @return array List of archive names from old versions
*/
function addOlds() {
$archiveBase = 'archive';
@@ -2595,7 +2796,8 @@ class LocalFileMoveBatch {
$result = $this->db->select( 'oldimage',
array( 'oi_archive_name', 'oi_deleted' ),
array( 'oi_name' => $this->oldName ),
- __METHOD__
+ __METHOD__,
+ array( 'FOR UPDATE' ) // ignore snapshot
);
foreach ( $result as $row ) {
@@ -2640,9 +2842,16 @@ class LocalFileMoveBatch {
$status = $repo->newGood();
$triplets = $this->getMoveTriplets();
- $triplets = $this->removeNonexistentFiles( $triplets );
+ $checkStatus = $this->removeNonexistentFiles( $triplets );
+ if ( !$checkStatus->isGood() ) {
+ $status->merge( $checkStatus );
+ return $status;
+ }
+ $triplets = $checkStatus->value;
+ $destFile = wfLocalFile( $this->target );
$this->file->lock(); // begin
+ $destFile->lock(); // quickly fail if destination is not available
// Rename the file versions metadata in the DB.
// This implicitly locks the destination file, which avoids race conditions.
// If we moved the files from A -> C before DB updates, another process could
@@ -2650,25 +2859,32 @@ class LocalFileMoveBatch {
// cleanupTarget() to trigger. It would delete the C files and cause data loss.
$statusDb = $this->doDBUpdates();
if ( !$statusDb->isGood() ) {
+ $destFile->unlock();
$this->file->unlockAndRollback();
$statusDb->ok = false;
+
return $statusDb;
}
- wfDebugLog( 'imagemove', "Renamed {$this->file->getName()} in database: {$statusDb->successCount} successes, {$statusDb->failCount} failures" );
+ wfDebugLog( 'imagemove', "Renamed {$this->file->getName()} in database: " .
+ "{$statusDb->successCount} successes, {$statusDb->failCount} failures" );
// Copy the files into their new location.
// If a prior process fataled copying or cleaning up files we tolerate any
// of the existing files if they are identical to the ones being stored.
$statusMove = $repo->storeBatch( $triplets, FileRepo::OVERWRITE_SAME );
- wfDebugLog( 'imagemove', "Moved files for {$this->file->getName()}: {$statusMove->successCount} successes, {$statusMove->failCount} failures" );
+ wfDebugLog( 'imagemove', "Moved files for {$this->file->getName()}: " .
+ "{$statusMove->successCount} successes, {$statusMove->failCount} failures" );
if ( !$statusMove->isGood() ) {
// Delete any files copied over (while the destination is still locked)
$this->cleanupTarget( $triplets );
+ $destFile->unlock();
$this->file->unlockAndRollback(); // unlocks the destination
wfDebugLog( 'imagemove', "Error in moving files: " . $statusMove->getWikiText() );
$statusMove->ok = false;
+
return $statusMove;
}
+ $destFile->unlock();
$this->file->unlock(); // done
// Everything went ok, remove the source files
@@ -2704,6 +2920,7 @@ class LocalFileMoveBatch {
} else {
$status->failCount++;
$status->fatal( 'imageinvalidfilename' );
+
return $status;
}
@@ -2745,7 +2962,10 @@ class LocalFileMoveBatch {
// $move: (oldRelativePath, newRelativePath)
$srcUrl = $this->file->repo->getVirtualUrl() . '/public/' . rawurlencode( $move[0] );
$triplets[] = array( $srcUrl, 'public', $move[1] );
- wfDebugLog( 'imagemove', "Generated move triplet for {$this->file->getName()}: {$srcUrl} :: public :: {$move[1]}" );
+ wfDebugLog(
+ 'imagemove',
+ "Generated move triplet for {$this->file->getName()}: {$srcUrl} :: public :: {$move[1]}"
+ );
}
return $triplets;
@@ -2753,8 +2973,8 @@ class LocalFileMoveBatch {
/**
* Removes non-existent files from move batch.
- * @param $triplets array
- * @return array
+ * @param array $triplets
+ * @return Status
*/
function removeNonexistentFiles( $triplets ) {
$files = array();
@@ -2764,8 +2984,12 @@ class LocalFileMoveBatch {
}
$result = $this->file->repo->fileExistsBatch( $files );
- $filteredTriplets = array();
+ if ( in_array( null, $result, true ) ) {
+ return Status::newFatal( 'backend-fail-internal',
+ $this->file->repo->getBackend()->getName() );
+ }
+ $filteredTriplets = array();
foreach ( $triplets as $file ) {
if ( $result[$file[0]] ) {
$filteredTriplets[] = $file;
@@ -2774,12 +2998,13 @@ class LocalFileMoveBatch {
}
}
- return $filteredTriplets;
+ return Status::newGood( $filteredTriplets );
}
/**
* Cleanup a partially moved array of triplets by deleting the target
* files. Called if something went wrong half way.
+ * @param array $triplets
*/
function cleanupTarget( $triplets ) {
// Create dest pairs from the triplets
@@ -2795,6 +3020,7 @@ class LocalFileMoveBatch {
/**
* Cleanup a fully moved array of triplets by deleting the source files.
* Called at the end of the move process if everything else went ok.
+ * @param array $triplets
*/
function cleanupSource( $triplets ) {
// Create source file names from the triplets
diff --git a/includes/filerepo/file/OldLocalFile.php b/includes/filerepo/file/OldLocalFile.php
index 2c545963..710058fb 100644
--- a/includes/filerepo/file/OldLocalFile.php
+++ b/includes/filerepo/file/OldLocalFile.php
@@ -27,15 +27,19 @@
* @ingroup FileAbstraction
*/
class OldLocalFile extends LocalFile {
- var $requestedTime, $archive_name;
+ /** @var string Timestamp */
+ protected $requestedTime;
+
+ /** @var string Archive name */
+ protected $archive_name;
const CACHE_VERSION = 1;
const MAX_CACHE_ROWS = 20;
/**
- * @param $title Title
- * @param $repo FileRepo
- * @param $time null
+ * @param Title $title
+ * @param FileRepo $repo
+ * @param null|int $time Timestamp or null
* @return OldLocalFile
* @throws MWException
*/
@@ -44,13 +48,14 @@ class OldLocalFile extends LocalFile {
if ( $time === null ) {
throw new MWException( __METHOD__ . ' got null for $time parameter' );
}
+
return new self( $title, $repo, $time, null );
}
/**
- * @param $title Title
- * @param $repo FileRepo
- * @param $archiveName
+ * @param Title $title
+ * @param FileRepo $repo
+ * @param string $archiveName
* @return OldLocalFile
*/
static function newFromArchiveName( $title, $repo, $archiveName ) {
@@ -58,14 +63,15 @@ class OldLocalFile extends LocalFile {
}
/**
- * @param $row
- * @param $repo FileRepo
+ * @param stdClass $row
+ * @param FileRepo $repo
* @return OldLocalFile
*/
static function newFromRow( $row, $repo ) {
$title = Title::makeTitle( NS_FILE, $row->oi_name );
$file = new self( $title, $repo, null, $row->oi_archive_name );
$file->loadFromRow( $row, 'oi_' );
+
return $file;
}
@@ -73,8 +79,8 @@ class OldLocalFile extends LocalFile {
* Create a OldLocalFile from a SHA-1 key
* Do not call this except from inside a repo class.
*
- * @param string $sha1 base-36 SHA-1
- * @param $repo LocalRepo
+ * @param string $sha1 Base-36 SHA-1
+ * @param LocalRepo $repo
* @param string|bool $timestamp MW_timestamp (optional)
*
* @return bool|OldLocalFile
@@ -121,10 +127,10 @@ class OldLocalFile extends LocalFile {
}
/**
- * @param $title Title
- * @param $repo FileRepo
- * @param string $time timestamp or null to load by archive name
- * @param string $archiveName archive name or null to load by timestamp
+ * @param Title $title
+ * @param FileRepo $repo
+ * @param string $time Timestamp or null to load by archive name
+ * @param string $archiveName Archive name or null to load by timestamp
* @throws MWException
*/
function __construct( $title, $repo, $time, $archiveName ) {
@@ -144,12 +150,13 @@ class OldLocalFile extends LocalFile {
}
/**
- * @return String
+ * @return string
*/
function getArchiveName() {
if ( !isset( $this->archive_name ) ) {
$this->load();
}
+
return $this->archive_name;
}
@@ -167,10 +174,11 @@ class OldLocalFile extends LocalFile {
return $this->exists() && !$this->isDeleted( File::DELETED_FILE );
}
- function loadFromDB() {
+ function loadFromDB( $flags = 0 ) {
wfProfileIn( __METHOD__ );
$this->dataLoaded = true;
+
$dbr = $this->repo->getSlaveDB();
$conds = array( 'oi_name' => $this->getName() );
if ( is_null( $this->requestedTime ) ) {
@@ -226,13 +234,14 @@ class OldLocalFile extends LocalFile {
}
/**
- * @param $prefix string
+ * @param string $prefix
* @return array
*/
function getCacheFields( $prefix = 'img_' ) {
$fields = parent::getCacheFields( $prefix );
$fields[] = $prefix . 'archive_name';
$fields[] = $prefix . 'deleted';
+
return $fields;
}
@@ -258,6 +267,7 @@ class OldLocalFile extends LocalFile {
if ( !$this->fileExists ) {
wfDebug( __METHOD__ . ": file does not exist, aborting\n" );
wfProfileOut( __METHOD__ );
+
return;
}
@@ -267,15 +277,15 @@ class OldLocalFile extends LocalFile {
wfDebug( __METHOD__ . ': upgrading ' . $this->archive_name . " to the current schema\n" );
$dbw->update( 'oldimage',
array(
- 'oi_size' => $this->size, // sanity
- 'oi_width' => $this->width,
- 'oi_height' => $this->height,
- 'oi_bits' => $this->bits,
+ 'oi_size' => $this->size, // sanity
+ 'oi_width' => $this->width,
+ 'oi_height' => $this->height,
+ 'oi_bits' => $this->bits,
'oi_media_type' => $this->media_type,
'oi_major_mime' => $major,
'oi_minor_mime' => $minor,
- 'oi_metadata' => $this->metadata,
- 'oi_sha1' => $this->sha1,
+ 'oi_metadata' => $this->metadata,
+ 'oi_sha1' => $this->sha1,
), array(
'oi_name' => $this->getName(),
'oi_archive_name' => $this->archive_name ),
@@ -285,12 +295,13 @@ class OldLocalFile extends LocalFile {
}
/**
- * @param $field Integer: one of DELETED_* bitfield constants
- * for file or revision rows
+ * @param int $field One of DELETED_* bitfield constants for file or
+ * revision rows
* @return bool
*/
function isDeleted( $field ) {
$this->load();
+
return ( $this->deleted & $field ) == $field;
}
@@ -300,6 +311,7 @@ class OldLocalFile extends LocalFile {
*/
function getVisibility() {
$this->load();
+
return (int)$this->deleted;
}
@@ -307,12 +319,13 @@ class OldLocalFile extends LocalFile {
* Determine if the current user is allowed to view a particular
* field of this image file, if it's marked as deleted.
*
- * @param $field Integer
- * @param $user User object to check, or null to use $wgUser
+ * @param int $field
+ * @param User|null $user User object to check, or null to use $wgUser
* @return bool
*/
function userCan( $field, User $user = null ) {
$this->load();
+
return Revision::userCanBitfield( $this->deleted, $field, $user );
}
@@ -321,12 +334,11 @@ class OldLocalFile extends LocalFile {
*
* @param string $srcPath File system path of the source file
* @param string $archiveName Full archive name of the file, in the form
- * $timestamp!$filename, where $filename must match $this->getName()
- *
- * @param $timestamp string
- * @param $comment string
- * @param $user
- * @param $flags int
+ * $timestamp!$filename, where $filename must match $this->getName()
+ * @param string $timestamp
+ * @param string $comment
+ * @param User $user
+ * @param int $flags
* @return FileRepoStatus
*/
function uploadOld( $srcPath, $archiveName, $timestamp, $comment, $user, $flags = 0 ) {
@@ -353,9 +365,9 @@ class OldLocalFile extends LocalFile {
*
* @param string $srcPath File system path to the source file
* @param string $archiveName The archive name of the file
- * @param $timestamp string
+ * @param string $timestamp
* @param string $comment Upload comment
- * @param $user User User who did this upload
+ * @param User $user User who did this upload
* @return bool
*/
function recordOldUpload( $srcPath, $archiveName, $timestamp, $comment, $user ) {
@@ -370,21 +382,21 @@ class OldLocalFile extends LocalFile {
$dbw->insert( 'oldimage',
array(
- 'oi_name' => $this->getName(),
+ 'oi_name' => $this->getName(),
'oi_archive_name' => $archiveName,
- 'oi_size' => $props['size'],
- 'oi_width' => intval( $props['width'] ),
- 'oi_height' => intval( $props['height'] ),
- 'oi_bits' => $props['bits'],
- 'oi_timestamp' => $dbw->timestamp( $timestamp ),
- 'oi_description' => $comment,
- 'oi_user' => $user->getId(),
- 'oi_user_text' => $user->getName(),
- 'oi_metadata' => $props['metadata'],
- 'oi_media_type' => $props['media_type'],
- 'oi_major_mime' => $props['major_mime'],
- 'oi_minor_mime' => $props['minor_mime'],
- 'oi_sha1' => $props['sha1'],
+ 'oi_size' => $props['size'],
+ 'oi_width' => intval( $props['width'] ),
+ 'oi_height' => intval( $props['height'] ),
+ 'oi_bits' => $props['bits'],
+ 'oi_timestamp' => $dbw->timestamp( $timestamp ),
+ 'oi_description' => $comment,
+ 'oi_user' => $user->getId(),
+ 'oi_user_text' => $user->getName(),
+ 'oi_metadata' => $props['metadata'],
+ 'oi_media_type' => $props['media_type'],
+ 'oi_major_mime' => $props['major_mime'],
+ 'oi_minor_mime' => $props['minor_mime'],
+ 'oi_sha1' => $props['sha1'],
), __METHOD__
);
@@ -393,4 +405,17 @@ class OldLocalFile extends LocalFile {
return true;
}
+ /**
+ * If archive name is an empty string, then file does not "exist"
+ *
+ * This is the case for a couple files on Wikimedia servers where
+ * the old version is "lost".
+ */
+ public function exists() {
+ $archiveName = $this->getArchiveName();
+ if ( $archiveName === '' || !is_string( $archiveName ) ) {
+ return false;
+ }
+ return parent::exists();
+ }
}
diff --git a/includes/filerepo/file/UnregisteredLocalFile.php b/includes/filerepo/file/UnregisteredLocalFile.php
index 47ba6d6b..5a3e4e9c 100644
--- a/includes/filerepo/file/UnregisteredLocalFile.php
+++ b/includes/filerepo/file/UnregisteredLocalFile.php
@@ -27,23 +27,34 @@
*
* Read-only.
*
- * TODO: Currently it doesn't really work in the repository role, there are
+ * @todo Currently it doesn't really work in the repository role, there are
* lots of functions missing. It is used by the WebStore extension in the
* standalone role.
*
* @ingroup FileAbstraction
*/
class UnregisteredLocalFile extends File {
- var $title, $path, $mime, $dims, $metadata;
+ /** @var Title */
+ protected $title;
- /**
- * @var MediaHandler
- */
- var $handler;
+ /** @var string */
+ protected $path;
+
+ /** @var bool|string */
+ protected $mime;
+
+ /** @var array Dimension data */
+ protected $dims;
+
+ /** @var bool|string Handler-specific metadata which will be saved in the img_metadata field */
+ protected $metadata;
+
+ /** @var MediaHandler */
+ public $handler;
/**
* @param string $path Storage path
- * @param $mime string
+ * @param string $mime
* @return UnregisteredLocalFile
*/
static function newFromPath( $path, $mime ) {
@@ -51,8 +62,8 @@ class UnregisteredLocalFile extends File {
}
/**
- * @param $title
- * @param $repo
+ * @param Title $title
+ * @param FileRepo $repo
* @return UnregisteredLocalFile
*/
static function newFromTitle( $title, $repo ) {
@@ -64,14 +75,15 @@ class UnregisteredLocalFile extends File {
* A FileRepo object is not required here, unlike most other File classes.
*
* @throws MWException
- * @param $title Title|bool
- * @param $repo FileRepo|bool
- * @param $path string|bool
- * @param $mime string|bool
+ * @param Title|bool $title
+ * @param FileRepo|bool $repo
+ * @param string|bool $path
+ * @param string|bool $mime
*/
function __construct( $title = false, $repo = false, $path = false, $mime = false ) {
if ( !( $title && $repo ) && !$path ) {
- throw new MWException( __METHOD__ . ': not enough parameters, must specify title and repo, or a full path' );
+ throw new MWException( __METHOD__ .
+ ': not enough parameters, must specify title and repo, or a full path' );
}
if ( $title instanceof Title ) {
$this->title = File::normalizeTitle( $title, 'exception' );
@@ -95,7 +107,7 @@ class UnregisteredLocalFile extends File {
}
/**
- * @param $page int
+ * @param int $page
* @return bool
*/
private function cachePageDimensions( $page = 1 ) {
@@ -105,24 +117,27 @@ class UnregisteredLocalFile extends File {
}
$this->dims[$page] = $this->handler->getPageDimensions( $this, $page );
}
+
return $this->dims[$page];
}
/**
- * @param $page int
- * @return number
+ * @param int $page
+ * @return int
*/
function getWidth( $page = 1 ) {
$dim = $this->cachePageDimensions( $page );
+
return $dim['width'];
}
/**
- * @param $page int
- * @return number
+ * @param int $page
+ * @return int
*/
function getHeight( $page = 1 ) {
$dim = $this->cachePageDimensions( $page );
+
return $dim['height'];
}
@@ -134,17 +149,19 @@ class UnregisteredLocalFile extends File {
$magic = MimeMagic::singleton();
$this->mime = $magic->guessMimeType( $this->getLocalRefPath() );
}
+
return $this->mime;
}
/**
- * @param $filename String
- * @return Array|bool
+ * @param string $filename
+ * @return array|bool
*/
function getImageSize( $filename ) {
if ( !$this->getHandler() ) {
return false;
}
+
return $this->handler->getImageSize( $this, $this->getLocalRefPath() );
}
@@ -159,6 +176,7 @@ class UnregisteredLocalFile extends File {
$this->metadata = $this->handler->getMetadata( $this, $this->getLocalRefPath() );
}
}
+
return $this->metadata;
}
@@ -179,6 +197,7 @@ class UnregisteredLocalFile extends File {
*/
function getSize() {
$this->assertRepoDefined();
+
return $this->repo->getFileSize( $this->path );
}
@@ -187,7 +206,7 @@ class UnregisteredLocalFile extends File {
* The file at the path of $fsFile should not be deleted (or at least
* not until the end of the request). This is mostly a performance hack.
*
- * @param $fsFile FSFile
+ * @param FSFile $fsFile
* @return void
*/
public function setLocalReference( FSFile $fsFile ) {