summaryrefslogtreecommitdiff
path: root/includes/filerepo/file
diff options
context:
space:
mode:
Diffstat (limited to 'includes/filerepo/file')
-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
7 files changed, 1482 insertions, 803 deletions
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 ) {