summaryrefslogtreecommitdiff
path: root/includes/filerepo
diff options
context:
space:
mode:
Diffstat (limited to 'includes/filerepo')
-rw-r--r--includes/filerepo/ArchivedFile.php69
-rw-r--r--includes/filerepo/FSRepo.php122
-rw-r--r--includes/filerepo/File.php541
-rw-r--r--includes/filerepo/FileRepo.php122
-rw-r--r--includes/filerepo/FileRepoStatus.php12
-rw-r--r--includes/filerepo/ForeignAPIFile.php29
-rw-r--r--includes/filerepo/ForeignAPIRepo.php51
-rw-r--r--includes/filerepo/ForeignDBFile.php23
-rw-r--r--includes/filerepo/ForeignDBRepo.php4
-rw-r--r--includes/filerepo/Image.php80
-rw-r--r--includes/filerepo/LocalFile.php273
-rw-r--r--includes/filerepo/LocalRepo.php37
-rw-r--r--includes/filerepo/NullRepo.php3
-rw-r--r--includes/filerepo/OldLocalFile.php100
-rw-r--r--includes/filerepo/README18
-rw-r--r--includes/filerepo/RepoGroup.php56
-rw-r--r--includes/filerepo/UnregisteredLocalFile.php24
17 files changed, 1144 insertions, 420 deletions
diff --git a/includes/filerepo/ArchivedFile.php b/includes/filerepo/ArchivedFile.php
index ecc09978..0d9e349b 100644
--- a/includes/filerepo/ArchivedFile.php
+++ b/includes/filerepo/ArchivedFile.php
@@ -16,7 +16,6 @@ class ArchivedFile {
* @private
*/
var $id, # filearchive row ID
- $title, # image title
$name, # image name
$group, # FileStore storage group
$key, # FileStore sha1 key
@@ -32,10 +31,27 @@ class ArchivedFile {
$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
+ $deleted, # Bitfield akin to rev_deleted
+ $pageCount,
+ $archive_name;
+
+ /**
+ * @var MediaHandler
+ */
+ var $handler;
+ /**
+ * @var Title
+ */
+ var $title; # image title
/**#@-*/
+ /**
+ * @throws MWException
+ * @param Title $title
+ * @param int $id
+ * @param string $key
+ */
function __construct( $title, $id=0, $key='' ) {
$this->id = -1;
$this->title = false;
@@ -57,19 +73,22 @@ class ArchivedFile {
$this->dataLoaded = false;
$this->exists = false;
- if( is_object($title) ) {
+ if( is_object( $title ) ) {
$this->title = $title;
$this->name = $title->getDBkey();
}
- if ($id)
+ if ($id) {
$this->id = $id;
+ }
- if ($key)
+ if ($key) {
$this->key = $key;
+ }
- if (!$id && !$key && !is_object($title))
+ if ( !$id && !$key && !is_object( $title ) ) {
throw new MWException( "No specifications provided to ArchivedFile constructor." );
+ }
}
/**
@@ -82,17 +101,20 @@ class ArchivedFile {
}
$conds = array();
- if( $this->id > 0 )
+ if( $this->id > 0 ) {
$conds['fa_id'] = $this->id;
+ }
if( $this->key ) {
$conds['fa_storage_group'] = $this->group;
$conds['fa_storage_key'] = $this->key;
}
- if( $this->title )
+ if( $this->title ) {
$conds['fa_name'] = $this->title->getDBkey();
+ }
- if( !count($conds))
+ if( !count($conds)) {
throw new MWException( "No specific information for retrieving archived file" );
+ }
if( !$this->title || $this->title->getNamespace() == NS_FILE ) {
$dbr = wfGetDB( DB_SLAVE );
@@ -119,8 +141,7 @@ class ArchivedFile {
$conds,
__METHOD__,
array( 'ORDER BY' => 'fa_timestamp DESC' ) );
-
- if ( $dbr->numRows( $res ) == 0 ) {
+ if ( $res == false || $dbr->numRows( $res ) == 0 ) {
// this revision does not exist?
return;
}
@@ -277,6 +298,32 @@ class ArchivedFile {
}
/**
+ * Get a MediaHandler instance for this file
+ * @return MediaHandler
+ */
+ function getHandler() {
+ 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
+ */
+ function pageCount() {
+ if ( !isset( $this->pageCount ) ) {
+ if ( $this->getHandler() && $this->handler->isMultiPage( $this ) ) {
+ $this->pageCount = $this->handler->pageCount( $this );
+ } else {
+ $this->pageCount = false;
+ }
+ }
+ return $this->pageCount;
+ }
+
+ /**
* Return the type of the media in the file.
* Use the value returned by this function with the MEDIATYPE_xxx constants.
*/
diff --git a/includes/filerepo/FSRepo.php b/includes/filerepo/FSRepo.php
index e2251b2b..2610ac6e 100644
--- a/includes/filerepo/FSRepo.php
+++ b/includes/filerepo/FSRepo.php
@@ -65,6 +65,10 @@ class FSRepo extends FileRepo {
/**
* Get the local directory corresponding to one of the three basic zones
+ *
+ * @param $zone string
+ *
+ * @return string
*/
function getZonePath( $zone ) {
switch ( $zone ) {
@@ -83,6 +87,10 @@ class FSRepo extends FileRepo {
/**
* @see FileRepo::getZoneUrl()
+ *
+ * @param $zone string
+ *
+ * @return url
*/
function getZoneUrl( $zone ) {
switch ( $zone ) {
@@ -103,6 +111,10 @@ class FSRepo extends FileRepo {
* Get a URL referring to this repository, with the private mwrepo protocol.
* The suffix, if supplied, is considered to be unencoded, and will be
* URL-encoded before being returned.
+ *
+ * @param $suffix string
+ *
+ * @return string
*/
function getVirtualUrl( $suffix = false ) {
$path = 'mwrepo://' . $this->name;
@@ -114,10 +126,14 @@ class FSRepo extends FileRepo {
/**
* Get the local path corresponding to a virtual URL
+ *
+ * @param $url string
+ *
+ * @return string
*/
function resolveVirtualUrl( $url ) {
if ( substr( $url, 0, 9 ) != 'mwrepo://' ) {
- throw new MWException( __METHOD__.': unknown protoocl' );
+ throw new MWException( __METHOD__.': unknown protocol' );
}
$bits = explode( '/', substr( $url, 9 ), 3 );
@@ -146,16 +162,23 @@ class FSRepo extends FileRepo {
* same contents as the source
*/
function storeBatch( $triplets, $flags = 0 ) {
+ wfDebug( __METHOD__ . ': Storing ' . count( $triplets ) .
+ " triplets; flags: {$flags}\n" );
+
+ // Try creating directories
if ( !wfMkdirParents( $this->directory ) ) {
return $this->newFatal( 'upload_directory_missing', $this->directory );
}
if ( !is_writable( $this->directory ) ) {
return $this->newFatal( 'upload_directory_read_only', $this->directory );
}
+
+ // Validate each triplet
$status = $this->newGood();
foreach ( $triplets as $i => $triplet ) {
list( $srcPath, $dstZone, $dstRel ) = $triplet;
+ // Resolve destination path
$root = $this->getZonePath( $dstZone );
if ( !$root ) {
throw new MWException( "Invalid zone: $dstZone" );
@@ -166,6 +189,7 @@ class FSRepo extends FileRepo {
$dstPath = "$root/$dstRel";
$dstDir = dirname( $dstPath );
+ // Create destination directories for this triplet
if ( !is_dir( $dstDir ) ) {
if ( !wfMkdirParents( $dstDir ) ) {
return $this->newFatal( 'directorycreateerror', $dstDir );
@@ -175,6 +199,7 @@ class FSRepo extends FileRepo {
}
}
+ // Resolve source
if ( self::isVirtualUrl( $srcPath ) ) {
$srcPath = $triplets[$i][0] = $this->resolveVirtualUrl( $srcPath );
}
@@ -183,6 +208,8 @@ class FSRepo extends FileRepo {
$status->fatal( 'filenotfound', $srcPath );
continue;
}
+
+ // Check overwriting
if ( !( $flags & self::OVERWRITE ) && file_exists( $dstPath ) ) {
if ( $flags & self::OVERWRITE_SAME ) {
$hashSource = sha1_file( $srcPath );
@@ -196,6 +223,7 @@ class FSRepo extends FileRepo {
}
}
+ // Windows does not support moving over existing files, so explicitly delete them
$deleteDest = wfIsWindows() && ( $flags & self::OVERWRITE );
// Abort now on failure
@@ -203,7 +231,8 @@ class FSRepo extends FileRepo {
return $status;
}
- foreach ( $triplets as $triplet ) {
+ // Execute the store operation for each triplet
+ foreach ( $triplets as $i => $triplet ) {
list( $srcPath, $dstZone, $dstRel ) = $triplet;
$root = $this->getZonePath( $dstZone );
$dstPath = "$root/$dstRel";
@@ -222,6 +251,20 @@ class FSRepo extends FileRepo {
$status->error( 'filecopyerror', $srcPath, $dstPath );
$good = false;
}
+ if ( !( $flags & self::SKIP_VALIDATION ) ) {
+ wfSuppressWarnings();
+ $hashSource = sha1_file( $srcPath );
+ $hashDest = sha1_file( $dstPath );
+ wfRestoreWarnings();
+
+ if ( $hashDest === false || $hashSource !== $hashDest ) {
+ wfDebug( __METHOD__ . ': File copy validation failed: ' .
+ "$srcPath ($hashSource) to $dstPath ($hashDest)\n" );
+
+ $status->error( 'filecopyerror', $srcPath, $dstPath );
+ $good = false;
+ }
+ }
}
if ( $good ) {
$this->chmod( $dstPath );
@@ -229,47 +272,81 @@ class FSRepo extends FileRepo {
} else {
$status->failCount++;
}
+ $status->success[$i] = $good;
}
return $status;
}
+
+ /**
+ * Deletes a batch of files. Each file can be a (zone, rel) pairs, a
+ * virtual url or a real path. It will try to delete each file, but
+ * ignores any errors that may occur
+ *
+ * @param $pairs array List of files to delete
+ */
+ function cleanupBatch( $files ) {
+ foreach ( $files as $file ) {
+ if ( is_array( $file ) ) {
+ // This is a pair, extract it
+ list( $zone, $rel ) = $file;
+ $root = $this->getZonePath( $zone );
+ $path = "$root/$rel";
+ } else {
+ if ( self::isVirtualUrl( $file ) ) {
+ // This is a virtual url, resolve it
+ $path = $this->resolveVirtualUrl( $file );
+ } else {
+ // This is a full file name
+ $path = $file;
+ }
+ }
+
+ wfSuppressWarnings();
+ unlink( $path );
+ wfRestoreWarnings();
+ }
+ }
function append( $srcPath, $toAppendPath, $flags = 0 ) {
$status = $this->newGood();
// Resolve the virtual URL
- if ( self::isVirtualUrl( $srcPath ) ) {
- $srcPath = $this->resolveVirtualUrl( $srcPath );
+ if ( self::isVirtualUrl( $toAppendPath ) ) {
+ $toAppendPath = $this->resolveVirtualUrl( $toAppendPath );
}
// Make sure the files are there
- if ( !is_file( $srcPath ) )
- $status->fatal( 'filenotfound', $srcPath );
-
if ( !is_file( $toAppendPath ) )
$status->fatal( 'filenotfound', $toAppendPath );
+ if ( !is_file( $srcPath ) )
+ $status->fatal( 'filenotfound', $srcPath );
+
if ( !$status->isOk() ) return $status;
// Do the append
- $chunk = file_get_contents( $toAppendPath );
+ $chunk = file_get_contents( $srcPath );
if( $chunk === false ) {
- $status->fatal( 'fileappenderrorread', $toAppendPath );
+ $status->fatal( 'fileappenderrorread', $srcPath );
}
if( $status->isOk() ) {
- if ( file_put_contents( $srcPath, $chunk, FILE_APPEND ) ) {
- $status->value = $srcPath;
+ if ( file_put_contents( $toAppendPath, $chunk, FILE_APPEND ) ) {
+ $status->value = $toAppendPath;
} else {
- $status->fatal( 'fileappenderror', $toAppendPath, $srcPath);
+ $status->fatal( 'fileappenderror', $srcPath, $toAppendPath);
}
}
if ( $flags & self::DELETE_SOURCE ) {
- unlink( $toAppendPath );
+ unlink( $srcPath );
}
return $status;
}
+ /* We can actually append to the files, so no-op needed here. */
+ function appendFinish( $toAppendPath ) {}
+
/**
* Checks existence of specified array of files.
*
@@ -515,14 +592,18 @@ class FSRepo extends FileRepo {
$good = true;
if ( file_exists( $archivePath ) ) {
# A file with this content hash is already archived
- if ( !@unlink( $srcPath ) ) {
+ wfSuppressWarnings();
+ $good = unlink( $srcPath );
+ wfRestoreWarnings();
+ if ( !$good ) {
$status->error( 'filedeleteerror', $srcPath );
- $good = false;
}
} else{
- if ( !@rename( $srcPath, $archivePath ) ) {
+ wfSuppressWarnings();
+ $good = rename( $srcPath, $archivePath );
+ wfRestoreWarnings();
+ if ( !$good ) {
$status->error( 'filerenameerror', $srcPath, $archivePath );
- $good = false;
} else {
$this->chmod( $archivePath );
}
@@ -564,8 +645,11 @@ class FSRepo extends FileRepo {
continue;
}
$dir = opendir( $path );
- while ( false !== ( $name = readdir( $dir ) ) ) {
- call_user_func( $callback, $path . '/' . $name );
+ if ($dir) {
+ while ( false !== ( $name = readdir( $dir ) ) ) {
+ call_user_func( $callback, $path . '/' . $name );
+ }
+ closedir( $dir );
}
}
}
diff --git a/includes/filerepo/File.php b/includes/filerepo/File.php
index 192e8c8a..6b35102b 100644
--- a/includes/filerepo/File.php
+++ b/includes/filerepo/File.php
@@ -52,7 +52,23 @@ abstract class File {
/**
* The following member variables are not lazy-initialised
*/
- var $repo, $title, $lastError, $redirected, $redirectedTitle;
+
+ /**
+ * @var LocalRepo
+ */
+ var $repo;
+
+ /**
+ * @var Title
+ */
+ var $title;
+
+ var $lastError, $redirected, $redirectedTitle;
+
+ /**
+ * @var MediaHandler
+ */
+ protected $handler;
/**
* Call this constructor from child classes
@@ -101,6 +117,8 @@ abstract class File {
*
* @param $old File Old file
* @param $new string New name
+ *
+ * @return bool|null
*/
static function checkExtensionCompatibility( File $old, $new ) {
$oldMime = $old->getMimeType();
@@ -122,10 +140,10 @@ abstract class File {
* Split an internet media type into its two components; if not
* a two-part name, set the minor type to 'unknown'.
*
- * @param $mime "text/html" etc
+ * @param string $mime "text/html" etc
* @return array ("text", "html") etc
*/
- static function splitMime( $mime ) {
+ public static function splitMime( $mime ) {
if( strpos( $mime, '/' ) !== false ) {
return explode( '/', $mime, 2 );
} else {
@@ -135,6 +153,8 @@ abstract class File {
/**
* Return the name of this file
+ *
+ * @return string
*/
public function getName() {
if ( !isset( $this->name ) ) {
@@ -145,6 +165,8 @@ abstract class File {
/**
* Get the file extension, e.g. "svg"
+ *
+ * @return string
*/
function getExtension() {
if ( !isset( $this->extension ) ) {
@@ -157,20 +179,26 @@ abstract class File {
/**
* Return the associated title object
+ * @return Title
*/
public function getTitle() { return $this->title; }
-
+
/**
* Return the title used to find this file
+ *
+ * @return Title
*/
public function getOriginalTitle() {
- if ( $this->redirected )
+ if ( $this->redirected ) {
return $this->getRedirectedTitle();
+ }
return $this->title;
}
/**
* Return the URL of the file
+ *
+ * @return string
*/
public function getUrl() {
if ( !isset( $this->url ) ) {
@@ -187,15 +215,21 @@ abstract class File {
* @return String
*/
public function getFullUrl() {
- return wfExpandUrl( $this->getUrl() );
+ return wfExpandUrl( $this->getUrl(), PROTO_RELATIVE );
+ }
+
+ public function getCanonicalUrl() {
+ return wfExpandUrl( $this->getUrl(), PROTO_CANONICAL );
}
+ /**
+ * @return string
+ */
function getViewURL() {
if( $this->mustRender()) {
if( $this->canRender() ) {
return $this->createThumb( $this->getWidth() );
- }
- else {
+ } else {
wfDebug(__METHOD__.': supposed to render '.$this->getName().' ('.$this->getMimeType()."), but can't!\n");
return $this->getURL(); #hm... return NULL?
}
@@ -212,7 +246,10 @@ abstract class File {
* i.e. whether the files are all found in the same directory,
* or in hashed paths like /images/3/3c.
*
- * May return false if the file is not locally accessible.
+ * Most callers don't check the return value, but ForeignAPIFile::getPath
+ * returns false.
+ *
+ * @return string|false
*/
public function getPath() {
if ( !isset( $this->path ) ) {
@@ -222,9 +259,14 @@ abstract class File {
}
/**
- * Alias for getPath()
- */
+ * Alias for getPath()
+ *
+ * @deprecated since 1.18 Use getPath().
+ *
+ * @return string
+ */
public function getFullPath() {
+ wfDeprecated( __METHOD__ );
return $this->getPath();
}
@@ -234,8 +276,14 @@ abstract class File {
*
* STUB
* Overridden by LocalFile, UnregisteredLocalFile
+ *
+ * @param $page int
+ *
+ * @return number
*/
- public function getWidth( $page = 1 ) { return false; }
+ public function getWidth( $page = 1 ) {
+ return false;
+ }
/**
* Return the height of the image. Returns false if the height is unknown
@@ -243,19 +291,29 @@ abstract class File {
*
* STUB
* Overridden by LocalFile, UnregisteredLocalFile
+ *
+ * @return false|number
*/
- public function getHeight( $page = 1 ) { return false; }
+ public function getHeight( $page = 1 ) {
+ return false;
+ }
/**
* Returns ID or name of user who uploaded the file
* STUB
*
* @param $type string 'text' or 'id'
+ *
+ * @return string|int
*/
- public function getUser( $type='text' ) { return null; }
+ public function getUser( $type = 'text' ) {
+ return null;
+ }
/**
* Get the duration of a media file in seconds
+ *
+ * @return number
*/
public function getLength() {
$handler = $this->getHandler();
@@ -267,45 +325,76 @@ abstract class File {
}
/**
- * Return true if the file is vectorized
- */
- public function isVectorized() {
- $handler = $this->getHandler();
- if ( $handler ) {
- return $handler->isVectorized( $this );
- } else {
- return false;
- }
- }
-
+ * Return true if the file is vectorized
+ *
+ * @return bool
+ */
+ public function isVectorized() {
+ $handler = $this->getHandler();
+ if ( $handler ) {
+ return $handler->isVectorized( $this );
+ } else {
+ return false;
+ }
+ }
/**
* Get handler-specific metadata
* Overridden by LocalFile, UnregisteredLocalFile
* STUB
*/
- public function getMetadata() { return false; }
+ public function getMetadata() {
+ return false;
+ }
+
+ /**
+ * 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)
+ */
+ public function convertMetadataVersion($metadata, $version) {
+ $handler = $this->getHandler();
+ if ( !is_array( $metadata ) ) {
+ //just to make the return type consistant
+ $metadata = unserialize( $metadata );
+ }
+ if ( $handler ) {
+ return $handler->convertMetadataVersion( $metadata, $version );
+ } else {
+ return $metadata;
+ }
+ }
/**
* Return the bit depth of the file
* Overridden by LocalFile
* STUB
*/
- public function getBitDepth() { return 0; }
+ public function getBitDepth() {
+ return 0;
+ }
/**
* Return the size of the image file, in bytes
* Overridden by LocalFile, UnregisteredLocalFile
* STUB
*/
- public function getSize() { return false; }
+ public function getSize() {
+ return false;
+ }
/**
* Returns the mime type of the file.
* Overridden by LocalFile, UnregisteredLocalFile
* STUB
+ *
+ * @return string
*/
- function getMimeType() { return 'unknown/unknown'; }
+ function getMimeType() {
+ return 'unknown/unknown';
+ }
/**
* Return the type of the media in the file.
@@ -324,6 +413,8 @@ abstract class File {
* that can be converted to a format
* supported by all browsers (namely GIF, PNG and JPEG),
* or if it is an SVG image and SVG conversion is enabled.
+ *
+ * @return bool
*/
function canRender() {
if ( !isset( $this->canRender ) ) {
@@ -355,6 +446,8 @@ abstract class File {
/**
* Alias for canRender()
+ *
+ * @return bool
*/
function allowInlineDisplay() {
return $this->canRender();
@@ -370,6 +463,8 @@ abstract class File {
*
* Note that this function will always return true if allowInlineDisplay()
* or isTrustedFile() is true for this file.
+ *
+ * @return bool
*/
function isSafeFile() {
if ( !isset( $this->isSafeFile ) ) {
@@ -378,41 +473,64 @@ abstract class File {
return $this->isSafeFile;
}
- /** Accessor for __get() */
+ /**
+ * Accessor for __get()
+ *
+ * @return bool
+ */
protected function getIsSafeFile() {
return $this->isSafeFile();
}
- /** Uncached accessor */
+ /**
+ * Uncached accessor
+ *
+ * @return bool
+ */
protected function _getIsSafeFile() {
- if ($this->allowInlineDisplay()) return true;
- if ($this->isTrustedFile()) return true;
+ if ( $this->allowInlineDisplay() ) {
+ return true;
+ }
+ if ($this->isTrustedFile()) {
+ return true;
+ }
global $wgTrustedMediaFormats;
- $type= $this->getMediaType();
- $mime= $this->getMimeType();
+ $type = $this->getMediaType();
+ $mime = $this->getMimeType();
#wfDebug("LocalFile::isSafeFile: type= $type, mime= $mime\n");
- if (!$type || $type===MEDIATYPE_UNKNOWN) return false; #unknown type, not trusted
- if ( in_array( $type, $wgTrustedMediaFormats) ) return true;
+ if ( !$type || $type === MEDIATYPE_UNKNOWN ) {
+ return false; #unknown type, not trusted
+ }
+ if ( in_array( $type, $wgTrustedMediaFormats ) ) {
+ return true;
+ }
- if ($mime==="unknown/unknown") return false; #unknown type, not trusted
- if ( in_array( $mime, $wgTrustedMediaFormats) ) return true;
+ if ( $mime === "unknown/unknown" ) {
+ return false; #unknown type, not trusted
+ }
+ if ( in_array( $mime, $wgTrustedMediaFormats) ) {
+ return true;
+ }
return false;
}
- /** Returns true if the file is flagged as trusted. Files flagged that way
- * can be linked to directly, even if that is not allowed for this type of
- * file normally.
- *
- * This is a dummy function right now and always returns false. It could be
- * implemented to extract a flag from the database. The trusted flag could be
- * set on upload, if the user has sufficient privileges, to bypass script-
- * and html-filters. It may even be coupled with cryptographics signatures
- * or such.
- */
+ /**
+ * Returns true if the file is flagged as trusted. Files flagged that way
+ * can be linked to directly, even if that is not allowed for this type of
+ * file normally.
+ *
+ * This is a dummy function right now and always returns false. It could be
+ * implemented to extract a flag from the database. The trusted flag could be
+ * set on upload, if the user has sufficient privileges, to bypass script-
+ * and html-filters. It may even be coupled with cryptographics signatures
+ * or such.
+ *
+ * @return bool
+ */
function isTrustedFile() {
#this could be implemented to check a flag in the databas,
#look for signatures, etc
@@ -435,12 +553,14 @@ abstract class File {
* It would be unsafe to include private images, making public thumbnails inadvertently
*
* @return boolean Whether file exists in the repository and is includable.
- * @public
*/
- function isVisible() {
+ public function isVisible() {
return $this->exists();
}
+ /**
+ * @return string
+ */
function getTransformScript() {
if ( !isset( $this->transformScript ) ) {
$this->transformScript = false;
@@ -456,6 +576,10 @@ abstract class File {
/**
* Get a ThumbnailImage which is the same size as the source
+ *
+ * @param $handlerParams array
+ *
+ * @return string
*/
function getUnscaledThumb( $handlerParams = array() ) {
$hp =& $handlerParams;
@@ -473,14 +597,28 @@ abstract class File {
*
* @param $params Array: handler-specific parameters
* @private -ish
+ *
+ * @return string
*/
function thumbName( $params ) {
+ return $this->generateThumbName( $this->getName(), $params );
+ }
+
+ /**
+ * Generate a thumbnail file name from a name and specified parameters
+ *
+ * @param string $name
+ * @param array $params Parameters which will be passed to MediaHandler::makeParamString
+ *
+ * @return string
+ */
+ function generateThumbName( $name, $params ) {
if ( !$this->getHandler() ) {
return null;
}
$extension = $this->getExtension();
list( $thumbExt, $thumbMime ) = $this->handler->getThumbType( $extension, $this->getMimeType(), $params );
- $thumbName = $this->handler->makeParamString( $params ) . '-' . $this->getName();
+ $thumbName = $this->handler->makeParamString( $params ) . '-' . $name;
if ( $thumbExt != $extension ) {
$thumbName .= ".$thumbExt";
}
@@ -501,6 +639,8 @@ abstract class File {
*
* @param $width Integer: maximum width of the generated thumbnail
* @param $height Integer: maximum height of the image (optional)
+ *
+ * @return string
*/
public function createThumb( $width, $height = -1 ) {
$params = array( 'width' => $width );
@@ -513,30 +653,6 @@ abstract class File {
}
/**
- * As createThumb, but returns a ThumbnailImage object. This can
- * provide access to the actual file, the real size of the thumb,
- * and can produce a convenient \<img\> tag for you.
- *
- * For non-image formats, this may return a filetype-specific icon.
- *
- * @param $width Integer: maximum width of the generated thumbnail
- * @param $height Integer: maximum height of the image (optional)
- * @param $render Integer: Deprecated
- *
- * @return ThumbnailImage or null on failure
- *
- * @deprecated use transform()
- */
- public function getThumbnail( $width, $height=-1, $render = true ) {
- wfDeprecated( __METHOD__ );
- $params = array( 'width' => $width );
- if ( $height != -1 ) {
- $params['height'] = $height;
- }
- return $this->transform( $params, 0 );
- }
-
- /**
* Transform a media file
*
* @param $params Array: an associative array of handler-specific parameters.
@@ -558,7 +674,7 @@ abstract class File {
// Get the descriptionUrl to embed it as comment into the thumbnail. Bug 19791.
$descriptionUrl = $this->getDescriptionUrl();
if ( $descriptionUrl ) {
- $params['descriptionUrl'] = $wgServer . $descriptionUrl;
+ $params['descriptionUrl'] = wfExpandUrl( $descriptionUrl, PROTO_CANONICAL );
}
$script = $this->getTransformScript();
@@ -586,8 +702,8 @@ abstract class File {
if ( file_exists( $thumbPath )) {
$thumbTime = filemtime( $thumbPath );
if ( $thumbTime !== FALSE &&
- gmdate( 'YmdHis', $thumbTime ) >= $wgThumbnailEpoch ) {
-
+ gmdate( 'YmdHis', $thumbTime ) >= $wgThumbnailEpoch ) {
+
$thumb = $this->handler->getTransform( $this, $thumbPath, $thumbUrl, $params );
break;
}
@@ -603,9 +719,9 @@ abstract class File {
$thumb = $this->handler->getTransform( $this, $thumbPath, $thumbUrl, $params );
}
}
-
- // Purge. Useful in the event of Core -> Squid connection failure or squid
- // purge collisions from elsewhere during failure. Don't keep triggering for
+
+ // 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 && ( !$thumb || $thumb->isError() || $thumb->getUrl() != $this->getURL()) ) {
SquidUpdate::purge( array( $thumbUrl ) );
@@ -625,6 +741,7 @@ abstract class File {
/**
* Get a MediaHandler instance for this file
+ * @return MediaHandler
*/
function getHandler() {
if ( !isset( $this->handler ) ) {
@@ -664,7 +781,9 @@ abstract class File {
* STUB
* Overridden by LocalFile
*/
- function getThumbnails() { return array(); }
+ function getThumbnails() {
+ return array();
+ }
/**
* Purge shared caches such as thumbnails and DB data caching
@@ -711,6 +830,8 @@ abstract class File {
* @param $start timestamp Only revisions older than $start will be returned
* @param $end timestamp Only revisions newer than $end will be returned
* @param $inc bool Include the endpoints of the time range
+ *
+ * @return array
*/
function getHistory($limit = null, $start = null, $end = null, $inc=true) {
return array();
@@ -740,6 +861,8 @@ abstract class File {
* Get the filename hash component of the directory including trailing slash,
* e.g. f/fa/
* If the repository is not hashed, returns an empty string.
+ *
+ * @return string
*/
function getHashPath() {
if ( !isset( $this->hashPath ) ) {
@@ -750,6 +873,8 @@ abstract class File {
/**
* Get the path of the file relative to the public zone root
+ *
+ * @return string
*/
function getRel() {
return $this->getHashPath() . $this->getName();
@@ -757,12 +882,20 @@ abstract class File {
/**
* Get urlencoded relative path of the file
+ *
+ * @return string
*/
function getUrlRel() {
return $this->getHashPath() . rawurlencode( $this->getName() );
}
- /** Get the relative path for an archive file */
+ /**
+ * Get the relative path for an archived file
+ *
+ * @param $suffix bool|string if not false, the name of an archived thumbnail file
+ *
+ * @return string
+ */
function getArchiveRel( $suffix = false ) {
$path = 'archive/' . $this->getHashPath();
if ( $suffix === false ) {
@@ -773,21 +906,68 @@ abstract class File {
return $path;
}
- /** Get the path of the archive directory, or a particular file if $suffix is specified */
+ /**
+ * Get the relative path for an archived file's thumbs directory
+ * or a specific thumb if the $suffix is given.
+ *
+ * @param $archiveName string the timestamped name of an archived image
+ * @param $suffix bool|string if not false, the name of a thumbnail file
+ */
+ function getArchiveThumbRel( $archiveName, $suffix = false ) {
+ $path = 'archive/' . $this->getHashPath() . $archiveName . "/";
+ if ( $suffix === false ) {
+ $path = substr( $path, 0, -1 );
+ } else {
+ $path .= $suffix;
+ }
+ return $path;
+ }
+
+ /**
+ * Get the path of the archived file.
+ *
+ * @param $suffix bool|string if not false, the name of an archived file.
+ *
+ * @return string
+ */
function getArchivePath( $suffix = false ) {
- return $this->repo->getZonePath('public') . '/' . $this->getArchiveRel( $suffix );
+ return $this->repo->getZonePath( 'public' ) . '/' . $this->getArchiveRel( $suffix );
}
- /** Get the path of the thumbnail directory, or a particular file if $suffix is specified */
+ /**
+ * Get the path of the archived file's thumbs, or a particular thumb if $suffix is specified
+ *
+ * @param $archiveName string the timestamped name of an archived image
+ * @param $suffix bool|string if not false, the name of a thumbnail file
+ *
+ * @return string
+ */
+ function getArchiveThumbPath( $archiveName, $suffix = false ) {
+ return $this->repo->getZonePath( 'thumb' ) . '/' . $this->getArchiveThumbRel( $archiveName, $suffix );
+ }
+
+ /**
+ * Get the path of the thumbnail directory, or a particular file if $suffix is specified
+ *
+ * @param $suffix bool|string if not false, the name of a thumbnail file
+ *
+ * @return string
+ */
function getThumbPath( $suffix = false ) {
- $path = $this->repo->getZonePath('thumb') . '/' . $this->getRel();
+ $path = $this->repo->getZonePath( 'thumb' ) . '/' . $this->getRel();
if ( $suffix !== false ) {
$path .= '/' . $suffix;
}
return $path;
}
- /** Get the URL of the archive directory, or a particular file if $suffix is specified */
+ /**
+ * Get the URL of the archive directory, or a particular file if $suffix is specified
+ *
+ * @param $suffix bool|string if not false, the name of an archived file
+ *
+ * @return string
+ */
function getArchiveUrl( $suffix = false ) {
$path = $this->repo->getZoneUrl('public') . '/archive/' . $this->getHashPath();
if ( $suffix === false ) {
@@ -798,7 +978,31 @@ abstract class File {
return $path;
}
- /** Get the URL of the thumbnail directory, or a particular file if $suffix is specified */
+ /**
+ * Get the URL of the archived file's thumbs, or a particular thumb if $suffix is specified
+ *
+ * @param $archiveName string the timestamped name of an archived image
+ * @param $suffix bool|string if not false, the name of a thumbnail file
+ *
+ * @return string
+ */
+ function getArchiveThumbUrl( $archiveName, $suffix = false ) {
+ $path = $this->repo->getZoneUrl('thumb') . '/archive/' . $this->getHashPath() . rawurlencode( $archiveName ) . "/";
+ if ( $suffix === false ) {
+ $path = substr( $path, 0, -1 );
+ } else {
+ $path .= rawurlencode( $suffix );
+ }
+ return $path;
+ }
+
+ /**
+ * Get the URL of the thumbnail directory, or a particular file if $suffix is specified
+ *
+ * @param $suffix bool|string if not false, the name of a thumbnail file
+ *
+ * @return path
+ */
function getThumbUrl( $suffix = false ) {
$path = $this->repo->getZoneUrl('thumb') . '/' . $this->getUrlRel();
if ( $suffix !== false ) {
@@ -807,7 +1011,13 @@ abstract class File {
return $path;
}
- /** Get the virtual URL for an archive file or directory */
+ /**
+ * Get the virtual URL for an archived file's thumbs, or a specific thumb.
+ *
+ * @param $suffix bool|string if not false, the name of a thumbnail file
+ *
+ * @return string
+ */
function getArchiveVirtualUrl( $suffix = false ) {
$path = $this->repo->getVirtualUrl() . '/public/archive/' . $this->getHashPath();
if ( $suffix === false ) {
@@ -818,7 +1028,13 @@ abstract class File {
return $path;
}
- /** Get the virtual URL for a thumbnail file or directory */
+ /**
+ * Get the virtual URL for a thumbnail file or directory
+ *
+ * @param $suffix bool|string if not false, the name of a thumbnail file
+ *
+ * @return string
+ */
function getThumbVirtualUrl( $suffix = false ) {
$path = $this->repo->getVirtualUrl() . '/thumb/' . $this->getUrlRel();
if ( $suffix !== false ) {
@@ -827,7 +1043,13 @@ abstract class File {
return $path;
}
- /** Get the virtual URL for the file itself */
+ /**
+ * Get the virtual URL for the file itself
+ *
+ * @param $suffix bool|string if not false, the name of a thumbnail file
+ *
+ * @return string
+ */
function getVirtualUrl( $suffix = false ) {
$path = $this->repo->getVirtualUrl() . '/public/' . $this->getUrlRel();
if ( $suffix !== false ) {
@@ -843,6 +1065,9 @@ abstract class File {
return $this->repo->isHashed();
}
+ /**
+ * @throws MWException
+ */
function readOnlyError() {
throw new MWException( get_class($this) . ': write operations are not supported' );
}
@@ -851,6 +1076,12 @@ 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
*/
function recordUpload( $oldver, $desc, $license = '', $copyStatus = '', $source = '', $watch = false ) {
$this->readOnlyError();
@@ -879,46 +1110,8 @@ abstract class File {
}
/**
- * Get an array of Title objects which are articles which use this file
- * Also adds their IDs to the link cache
- *
- * This is mostly copied from Title::getLinksTo()
- *
- * @deprecated Use HTMLCacheUpdate, this function uses too much memory
+ * @return bool
*/
- function getLinksTo( $options = array() ) {
- wfDeprecated( __METHOD__ );
- wfProfileIn( __METHOD__ );
-
- // Note: use local DB not repo DB, we want to know local links
- if ( count( $options ) > 0 ) {
- $db = wfGetDB( DB_MASTER );
- } else {
- $db = wfGetDB( DB_SLAVE );
- }
- $linkCache = LinkCache::singleton();
-
- $encName = $db->addQuotes( $this->getName() );
- $res = $db->select( array( 'page', 'imagelinks'),
- array( 'page_namespace', 'page_title', 'page_id', 'page_len', 'page_is_redirect', 'page_latest' ),
- array( 'page_id=il_from', 'il_to' => $encName ),
- __METHOD__,
- $options );
-
- $retVal = array();
- if ( $db->numRows( $res ) ) {
- foreach ( $res as $row ) {
- $titleObj = Title::newFromRow( $row );
- if ( $titleObj ) {
- $linkCache->addGoodLinkObj( $row->page_id, $titleObj, $row->page_len, $row->page_is_redirect, $row->page_latest );
- $retVal[] = $titleObj;
- }
- }
- }
- wfProfileOut( __METHOD__ );
- return $retVal;
- }
-
function formatMetadata() {
if ( !$this->getHandler() ) {
return false;
@@ -944,8 +1137,11 @@ abstract class File {
function getRepoName() {
return $this->repo ? $this->repo->getName() : 'unknown';
}
- /*
+
+ /**
* Returns the repository
+ *
+ * @return FileRepo
*/
function getRepo() {
return $this->repo;
@@ -954,6 +1150,8 @@ abstract class File {
/**
* Returns true if the image is an old version
* STUB
+ *
+ * @return bool
*/
function isOld() {
return false;
@@ -962,15 +1160,19 @@ abstract class File {
/**
* Is this file a "deleted" file in a private archive?
* STUB
+ *
+ * @param $field
+ *
+ * @return bool
*/
function isDeleted( $field ) {
return false;
}
-
+
/**
* Return the deletion bitfield
* STUB
- */
+ */
function getVisibility() {
return 0;
}
@@ -1025,21 +1227,21 @@ abstract class File {
*
* May throw database exceptions on error.
*
- * @param $versions set of record ids of deleted items to restore,
+ * @param $versions array set of record ids of deleted items to restore,
* or empty to restore all revisions.
- * @param $unsuppress remove restrictions on content upon restoration?
- * @return the number of file revisions restored if successful,
+ * @param $unsuppress bool remove restrictions on content upon restoration?
+ * @return int|false the number of file revisions restored if successful,
* or false on failure
* STUB
* Overridden by LocalFile
*/
- function restore( $versions=array(), $unsuppress=false ) {
+ function restore( $versions = array(), $unsuppress = false ) {
$this->readOnlyError();
}
/**
- * Returns 'true' if this file is a type which supports multiple pages,
- * e.g. DJVU or PDF. Note that this may be true even if the file in
+ * Returns 'true' if this file is a type which supports multiple pages,
+ * e.g. DJVU or PDF. Note that this may be true even if the file in
* question only has a single page.
*
* @return Bool
@@ -1051,6 +1253,8 @@ abstract class File {
/**
* Returns the number of pages of a multipage document, or false for
* documents which aren't multipage documents
+ *
+ * @return false|int
*/
function pageCount() {
if ( !isset( $this->pageCount ) ) {
@@ -1065,6 +1269,12 @@ abstract class File {
/**
* Calculate the height of a thumbnail using the source and destination width
+ *
+ * @param $srcWidth
+ * @param $srcHeight
+ * @param $dstWidth
+ *
+ * @return int
*/
static function scaleHeight( $srcWidth, $srcHeight, $dstWidth ) {
// Exact integer multiply followed by division
@@ -1092,6 +1302,8 @@ abstract class File {
/**
* Get the URL of the image description page. May return false if it is
* unknown or not applicable.
+ *
+ * @return string
*/
function getDescriptionUrl() {
return $this->repo->getDescriptionUrl( $this->getName() );
@@ -1099,6 +1311,8 @@ abstract class File {
/**
* Get the HTML text of the description page, if available
+ *
+ * @return string
*/
function getDescriptionText() {
global $wgMemc, $wgLang;
@@ -1109,7 +1323,7 @@ abstract class File {
if ( $renderUrl ) {
if ( $this->repo->descriptionCacheExpiry > 0 ) {
wfDebug("Attempting to get the description from cache...");
- $key = $this->repo->getLocalCacheKey( 'RemoteFileDescription', 'url', $wgLang->getCode(),
+ $key = $this->repo->getLocalCacheKey( 'RemoteFileDescription', 'url', $wgLang->getCode(),
$this->getName() );
$obj = $wgMemc->get($key);
if ($obj) {
@@ -1132,6 +1346,8 @@ abstract class File {
/**
* Get discription of file revision
* STUB
+ *
+ * @return string
*/
function getDescription() {
return null;
@@ -1140,6 +1356,8 @@ abstract class File {
/**
* Get the 14-character timestamp of the file upload, or false if
* it doesn't exist
+ *
+ * @return string
*/
function getTimestamp() {
$path = $this->getPath();
@@ -1151,6 +1369,8 @@ abstract class File {
/**
* Get the SHA-1 base 36 hash of the file
+ *
+ * @return string
*/
function getSha1() {
return self::sha1Base36( $this->getPath() );
@@ -1158,6 +1378,8 @@ abstract class File {
/**
* Get the deletion archive key, <sha1>.<ext>
+ *
+ * @return string
*/
function getStorageKey() {
$hash = $this->getSha1();
@@ -1166,7 +1388,7 @@ abstract class File {
}
$ext = $this->getExtension();
$dotExt = $ext === '' ? '' : ".$ext";
- return $hash . $dotExt;
+ return $hash . $dotExt;
}
/**
@@ -1186,6 +1408,8 @@ abstract class File {
* @param $path String: 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
*/
static function getPropsFromPath( $path, $ext = true ) {
wfProfileIn( __METHOD__ );
@@ -1258,7 +1482,9 @@ abstract class File {
* 160 log 2 / log 36 = 30.95, so the 160-bit hash fills 31 digits in base 36
* fairly neatly.
*
- * Returns false on failure
+ * @param $path string
+ *
+ * @return false|string False on failure
*/
static function sha1Base36( $path ) {
wfSuppressWarnings();
@@ -1271,6 +1497,9 @@ abstract class File {
}
}
+ /**
+ * @return string
+ */
function getLongDesc() {
$handler = $this->getHandler();
if ( $handler ) {
@@ -1280,6 +1509,9 @@ abstract class File {
}
}
+ /**
+ * @return string
+ */
function getShortDesc() {
$handler = $this->getHandler();
if ( $handler ) {
@@ -1289,6 +1521,9 @@ abstract class File {
}
}
+ /**
+ * @return string
+ */
function getDimensionsString() {
$handler = $this->getHandler();
if ( $handler ) {
@@ -1298,22 +1533,36 @@ abstract class File {
}
}
+ /**
+ * @return
+ */
function getRedirected() {
return $this->redirected;
}
-
+
+ /**
+ * @return Title
+ */
function getRedirectedTitle() {
if ( $this->redirected ) {
- if ( !$this->redirectTitle )
+ if ( !$this->redirectTitle ) {
$this->redirectTitle = Title::makeTitle( NS_FILE, $this->redirected );
+ }
return $this->redirectTitle;
}
}
+ /**
+ * @param $from
+ * @return void
+ */
function redirectedFrom( $from ) {
$this->redirected = $from;
}
+ /**
+ * @return bool
+ */
function isMissing() {
return false;
}
diff --git a/includes/filerepo/FileRepo.php b/includes/filerepo/FileRepo.php
index ff73a73c..843f09a9 100644
--- a/includes/filerepo/FileRepo.php
+++ b/includes/filerepo/FileRepo.php
@@ -17,6 +17,7 @@ abstract class FileRepo {
const DELETE_SOURCE = 1;
const OVERWRITE = 2;
const OVERWRITE_SAME = 4;
+ const SKIP_VALIDATION = 8;
var $thumbScriptUrl, $transformVia404;
var $descBaseUrl, $scriptDirUrl, $scriptExtension, $articleUrl;
@@ -39,7 +40,7 @@ abstract class FileRepo {
$this->initialCapital = MWNamespace::isCapitalized( NS_FILE );
foreach ( array( 'descBaseUrl', 'scriptDirUrl', 'articleUrl', 'fetchDescription',
'thumbScriptUrl', 'initialCapital', 'pathDisclosureProtection',
- 'descriptionCacheExpiry', 'hashLevels', 'url', 'thumbUrl', 'scriptExtension' )
+ 'descriptionCacheExpiry', 'hashLevels', 'url', 'thumbUrl', 'scriptExtension' )
as $var )
{
if ( isset( $info[$var] ) ) {
@@ -51,6 +52,10 @@ abstract class FileRepo {
/**
* Determine if a string is an mwrepo:// URL
+ *
+ * @param $url string
+ *
+ * @return bool
*/
static function isVirtualUrl( $url ) {
return substr( $url, 0, 9 ) == 'mwrepo://';
@@ -65,9 +70,11 @@ abstract class FileRepo {
* instance of the repository's old file class instead of a
* current file. Repositories not supporting version control
* should return false if this parameter is set.
+ *
+ * @return File
*/
function newFile( $title, $time = false ) {
- if ( !($title instanceof Title) ) {
+ if ( !( $title instanceof Title ) ) {
$title = Title::makeTitleSafe( NS_FILE, $title );
if ( !is_object( $title ) ) {
return null;
@@ -90,7 +97,7 @@ abstract class FileRepo {
* version control should return false if the time is specified.
*
* @param $title Mixed: Title object or string
- * @param $options Associative array of options:
+ * @param $options array Associative array of options:
* time: requested time for an archived image, or false for the
* current version. An image object will be returned which was
* created at the specified time.
@@ -100,14 +107,11 @@ abstract class FileRepo {
* private: If true, return restricted (deleted) files if the current
* user is allowed to view them. Otherwise, such files will not
* be found.
+ *
+ * @return File|false
*/
function findFile( $title, $options = array() ) {
- if ( !is_array( $options ) ) {
- // MW 1.15 compat
- $time = $options;
- } else {
- $time = isset( $options['time'] ) ? $options['time'] : false;
- }
+ $time = isset( $options['time'] ) ? $options['time'] : false;
if ( !($title instanceof Title) ) {
$title = Title::makeTitleSafe( NS_FILE, $title );
if ( !is_object( $title ) ) {
@@ -126,9 +130,9 @@ abstract class FileRepo {
if ( $time !== false ) {
$img = $this->newFile( $title, $time );
if ( $img && $img->exists() ) {
- if ( !$img->isDeleted(File::DELETED_FILE) ) {
- return $img;
- } else if ( !empty( $options['private'] ) && $img->userCan(File::DELETED_FILE) ) {
+ if ( !$img->isDeleted( File::DELETED_FILE ) ) {
+ return $img; // always OK
+ } elseif ( !empty( $options['private'] ) && $img->userCan( File::DELETED_FILE ) ) {
return $img;
}
}
@@ -139,7 +143,7 @@ abstract class FileRepo {
return false;
}
$redir = $this->checkRedirect( $title );
- if( $redir && $redir->getNamespace() == NS_FILE) {
+ if( $redir && $title->getNamespace() == NS_FILE) {
$img = $this->newFile( $redir );
if( !$img ) {
return false;
@@ -152,7 +156,7 @@ abstract class FileRepo {
return false;
}
- /*
+ /**
* Find many files at once.
* @param $items An array of titles, or an array of findFile() options with
* the "title" option giving the title. Example:
@@ -181,58 +185,32 @@ abstract class FileRepo {
}
/**
- * Create a new File object from the local repository
- * @param $sha1 Mixed: SHA-1 key
- * @param $time Mixed: time at which the image was uploaded.
- * If this is specified, the returned object will be an
- * of the repository's old file class instead of a current
- * file. Repositories not supporting version control should
- * return false if this parameter is set.
- */
- function newFileFromKey( $sha1, $time = false ) {
- if ( $time ) {
- if ( $this->oldFileFactoryKey ) {
- return call_user_func( $this->oldFileFactoryKey, $sha1, $this, $time );
- }
- } else {
- if ( $this->fileFactoryKey ) {
- return call_user_func( $this->fileFactoryKey, $sha1, $this );
- }
- }
- return false;
- }
-
- /**
* Find an instance of the file with this key, created at the specified time
* Returns false if the file does not exist. Repositories not supporting
* version control should return false if the time is specified.
*
- * @param $sha1 String
+ * @param $sha1 String base 36 SHA-1 hash
* @param $options Option array, same as findFile().
*/
function findFileFromKey( $sha1, $options = array() ) {
- if ( !is_array( $options ) ) {
- # MW 1.15 compat
- $time = $options;
- } else {
- $time = isset( $options['time'] ) ? $options['time'] : false;
- }
+ $time = isset( $options['time'] ) ? $options['time'] : false;
- # First try the current version of the file to see if it precedes the timestamp
- $img = $this->newFileFromKey( $sha1 );
- if ( !$img ) {
- return false;
+ # First try to find a matching current version of a file...
+ if ( $this->fileFactoryKey ) {
+ $img = call_user_func( $this->fileFactoryKey, $sha1, $this, $time );
+ } else {
+ return false; // find-by-sha1 not supported
}
- if ( $img->exists() && ( !$time || $img->getTimestamp() == $time ) ) {
+ if ( $img && $img->exists() ) {
return $img;
}
- # Now try an old version of the file
- if ( $time !== false ) {
- $img = $this->newFileFromKey( $sha1, $time );
+ # Now try to find a matching old version of a file...
+ if ( $time !== false && $this->oldFileFactoryKey ) { // find-by-sha1 supported?
+ $img = call_user_func( $this->oldFileFactoryKey, $sha1, $this, $time );
if ( $img && $img->exists() ) {
- if ( !$img->isDeleted(File::DELETED_FILE) ) {
- return $img;
- } else if ( !empty( $options['private'] ) && $img->userCan(File::DELETED_FILE) ) {
+ if ( !$img->isDeleted( File::DELETED_FILE ) ) {
+ return $img; // always OK
+ } elseif ( !empty( $options['private'] ) && $img->userCan( File::DELETED_FILE ) ) {
return $img;
}
}
@@ -265,6 +243,7 @@ abstract class FileRepo {
/**
* Get the name of an image from its title object
+ * @param $title Title
*/
function getNameFromTitle( $title ) {
if ( $this->initialCapital != MWNamespace::isCapitalized( NS_FILE ) ) {
@@ -306,18 +285,18 @@ abstract class FileRepo {
function getName() {
return $this->name;
}
-
+
/**
* Make an url to this repo
- *
+ *
* @param $query mixed Query string to append
* @param $entry string Entry point; defaults to index
* @return string
*/
function makeUrl( $query = '', $entry = 'index' ) {
$ext = isset( $this->scriptExtension ) ? $this->scriptExtension : '.php';
- return wfAppendQuery( "{$this->scriptDirUrl}/{$entry}{$ext}", $query );
- }
+ return wfAppendQuery( "{$this->scriptDirUrl}/{$entry}{$ext}", $query );
+ }
/**
* Get the URL of an image description page. May return false if it is
@@ -367,7 +346,7 @@ abstract class FileRepo {
$query .= '&uselang=' . $lang;
}
if ( isset( $this->scriptDirUrl ) ) {
- return $this->makeUrl(
+ return $this->makeUrl(
'title=' .
wfUrlencode( 'Image:' . $name ) .
"&$query" );
@@ -380,7 +359,7 @@ abstract class FileRepo {
}
}
}
-
+
/**
* Get the URL of the stylesheet to apply to description pages
* @return string
@@ -433,7 +412,8 @@ abstract class FileRepo {
/**
- * Append the contents of the source path to the given file.
+ * Append the contents of the source path to the given file, OR queue
+ * the appending operation in anticipation of a later appendFinish() call.
* @param $srcPath String: location of the source file
* @param $toAppendPath String: path to append to.
* @param $flags Integer: bitfield, may be FileRepo::DELETE_SOURCE to indicate
@@ -443,6 +423,13 @@ abstract class FileRepo {
abstract function append( $srcPath, $toAppendPath, $flags = 0 );
/**
+ * Finish the append operation.
+ * @param $toAppendPath String: path to append to.
+ * @return mixed Status or false
+ */
+ abstract function appendFinish( $toAppendPath );
+
+ /**
* Remove a temporary file or mark it for garbage collection
* @param $virtualUrl String: the virtual URL returned by storeTemp
* @return Boolean: true on success, false on failure
@@ -602,7 +589,7 @@ abstract class FileRepo {
function newFatal( $message /*, parameters...*/ ) {
$params = func_get_args();
array_unshift( $params, $this );
- return call_user_func_array( array( 'FileRepoStatus', 'newFatal' ), $params );
+ return MWInit::callStaticMethod( 'FileRepoStatus', 'newFatal', $params );
}
/**
@@ -624,6 +611,7 @@ abstract class FileRepo {
* STUB
*
* @param $title Title of image
+ * @return Bool
*/
function checkRedirect( $title ) {
return false;
@@ -658,11 +646,7 @@ abstract class FileRepo {
return null;
}
// 'shared-repo-name-wikimediacommons' is used when $wgUseInstantCommons = true
- $repoName = wfMsg( 'shared-repo-name-' . $this->name );
- if ( !wfEmptyMsg( 'shared-repo-name-' . $this->name, $repoName ) ) {
- return $repoName;
- }
- return wfMsg( 'shared-repo' );
+ return wfMessageFallback( 'shared-repo-name-' . $this->name, 'shared-repo' )->text();
}
/**
@@ -696,9 +680,11 @@ abstract class FileRepo {
array_unshift( $args, 'filerepo', $this->getName() );
return call_user_func_array( 'wfMemcKey', $args );
}
-
+
/**
* Get an UploadStash associated with this repo.
+ *
+ * @return UploadStash
*/
function getUploadStash() {
return new UploadStash( $this );
diff --git a/includes/filerepo/FileRepoStatus.php b/includes/filerepo/FileRepoStatus.php
index 161284c0..4eea9030 100644
--- a/includes/filerepo/FileRepoStatus.php
+++ b/includes/filerepo/FileRepoStatus.php
@@ -13,6 +13,10 @@
class FileRepoStatus extends Status {
/**
* Factory function for fatal errors
+ *
+ * @param $repo FileRepo
+ *
+ * @return FileRepoStatus
*/
static function newFatal( $repo /*, parameters...*/ ) {
$params = array_slice( func_get_args(), 1 );
@@ -22,12 +26,20 @@ class FileRepoStatus extends Status {
return $result;
}
+ /**
+ * @param $repo FileRepo
+ * @param $value
+ * @return FileRepoStatus
+ */
static function newGood( $repo = false, $value = null ) {
$result = new self( $repo );
$result->value = $value;
return $result;
}
+ /**
+ * @param $repo FileRepo
+ */
function __construct( $repo = false ) {
if ( $repo ) {
$this->cleanCallback = $repo->getErrorCleanupFunction();
diff --git a/includes/filerepo/ForeignAPIFile.php b/includes/filerepo/ForeignAPIFile.php
index 56fed75e..53c4a3bd 100644
--- a/includes/filerepo/ForeignAPIFile.php
+++ b/includes/filerepo/ForeignAPIFile.php
@@ -15,7 +15,13 @@
class ForeignAPIFile extends File {
private $mExists;
-
+
+ /**
+ * @param $title
+ * @param $repo ForeignApiRepo
+ * @param $info
+ * @param bool $exists
+ */
function __construct( $title, $repo, $info, $exists = false ) {
parent::__construct( $title, $repo );
$this->mInfo = $info;
@@ -23,7 +29,6 @@ class ForeignAPIFile extends File {
}
/**
- * @static
* @param $title Title
* @param $repo ForeignApiRepo
* @return ForeignAPIFile|null
@@ -32,7 +37,9 @@ class ForeignAPIFile extends File {
$data = $repo->fetchImageQuery( array(
'titles' => 'File:' . $title->getDBKey(),
'iiprop' => self::getProps(),
- 'prop' => 'imageinfo' ) );
+ 'prop' => 'imageinfo',
+ 'iimetadataversion' => MediaHandler::getMetadataVersion()
+ ) );
$info = $repo->getImageInfo( $data );
@@ -76,20 +83,26 @@ class ForeignAPIFile extends File {
// show icon
return parent::transform( $params, $flags );
}
+
+ // Note, the this->canRender() check above implies
+ // that we have a handler, and it can do makeParamString.
+ $otherParams = $this->handler->makeParamString( $params );
+
$thumbUrl = $this->repo->getThumbUrlFromCache(
$this->getName(),
isset( $params['width'] ) ? $params['width'] : -1,
- isset( $params['height'] ) ? $params['height'] : -1 );
+ isset( $params['height'] ) ? $params['height'] : -1,
+ $otherParams );
return $this->handler->getTransform( $this, 'bogus', $thumbUrl, $params );
}
// Info we can get from API...
public function getWidth( $page = 1 ) {
- return intval( @$this->mInfo['width'] );
+ return isset( $this->mInfo['width'] ) ? intval( $this->mInfo['width'] ) : 0;
}
public function getHeight( $page = 1 ) {
- return intval( @$this->mInfo['height'] );
+ return isset( $this->mInfo['height'] ) ? intval( $this->mInfo['height'] ) : 0;
}
public function getMetadata() {
@@ -148,7 +161,7 @@ class ForeignAPIFile extends File {
return $this->mInfo['mime'];
}
- /// @todo Fixme: may guess wrong on file types that can be eg audio or video
+ /// @todo FIXME: May guess wrong on file types that can be eg audio or video
function getMediaType() {
$magic = MimeMagic::singleton();
return $magic->getMediaType( null, $this->getMimeType() );
@@ -182,7 +195,7 @@ class ForeignAPIFile extends File {
$handle = opendir( $dir );
if ( $handle ) {
while ( false !== ( $file = readdir($handle) ) ) {
- if ( $file{0} != '.' ) {
+ if ( $file[0] != '.' ) {
$files[] = $file;
}
}
diff --git a/includes/filerepo/ForeignAPIRepo.php b/includes/filerepo/ForeignAPIRepo.php
index e4188d6b..502b8c1d 100644
--- a/includes/filerepo/ForeignAPIRepo.php
+++ b/includes/filerepo/ForeignAPIRepo.php
@@ -25,13 +25,13 @@ class ForeignAPIRepo extends FileRepo {
/* This version string is used in the user agent for requests and will help
* server maintainers in identify ForeignAPI usage.
* Update the version every time you make breaking or significant changes. */
- const VERSION = "2.0";
+ const VERSION = "2.1";
var $fileFactory = array( 'ForeignAPIFile', 'newFromTitle' );
/* Check back with Commons after a day */
- var $apiThumbCacheExpiry = 86400;
+ var $apiThumbCacheExpiry = 86400; /* 24*60*60 */
/* Redownload thumbnail files after a month */
- var $fileCacheExpiry = 2629743;
+ var $fileCacheExpiry = 2592000; /* 86400*30 */
/* Local image directory */
var $directory;
var $thumbDir;
@@ -75,6 +75,8 @@ class ForeignAPIRepo extends FileRepo {
/**
* Per docs in FileRepo, this needs to return false if we don't support versioned
* files. Well, we don't.
+ *
+ * @return File
*/
function newFile( $title, $time = false ) {
if ( $time ) {
@@ -83,26 +85,34 @@ class ForeignAPIRepo extends FileRepo {
return parent::newFile( $title, $time );
}
-/**
- * No-ops
- */
+ /**
+ * No-ops
+ */
+
function storeBatch( $triplets, $flags = 0 ) {
return false;
}
+
function storeTemp( $originalName, $srcPath ) {
return false;
}
+
function append( $srcPath, $toAppendPath, $flags = 0 ){
return false;
}
+
+ function appendFinish( $toAppendPath ){
+ return false;
+ }
+
function publishBatch( $triplets, $flags = 0 ) {
return false;
}
+
function deleteBatch( $sourceDestPairs ) {
return false;
}
-
function fileExistsBatch( $files, $flags = 0 ) {
$results = array();
foreach ( $files as $k => $f ) {
@@ -110,7 +120,7 @@ class ForeignAPIRepo extends FileRepo {
$results[$k] = true;
unset( $files[$k] );
} elseif( self::isVirtualUrl( $f ) ) {
- # TODO! FIXME! We need to be able to handle virtual
+ # @todo FIXME: We need to be able to handle virtual
# URLs better, at least when we know they refer to the
# same repo.
$results[$k] = false;
@@ -129,6 +139,7 @@ class ForeignAPIRepo extends FileRepo {
}
return $results;
}
+
function getFileProps( $virtualUrl ) {
return false;
}
@@ -197,12 +208,13 @@ class ForeignAPIRepo extends FileRepo {
return $ret;
}
- function getThumbUrl( $name, $width=-1, $height=-1, &$result=NULL ) {
+ function getThumbUrl( $name, $width = -1, $height = -1, &$result = null, $otherParams = '' ) {
$data = $this->fetchImageQuery( array(
'titles' => 'File:' . $name,
'iiprop' => 'url|timestamp',
'iiurlwidth' => $width,
'iiurlheight' => $height,
+ 'iiurlparam' => $otherParams,
'prop' => 'imageinfo' ) );
$info = $this->getImageInfo( $data );
@@ -215,7 +227,7 @@ class ForeignAPIRepo extends FileRepo {
}
}
- /*
+ /**
* Return the imageurl from cache if possible
*
* If the url has been requested today, get it from cache
@@ -224,15 +236,17 @@ class ForeignAPIRepo extends FileRepo {
* @param $name String is a dbkey form of a title
* @param $width
* @param $height
+ * @param String $param Other rendering parameters (page number, etc) from handler's makeParamString.
*/
- function getThumbUrlFromCache( $name, $width, $height ) {
+ function getThumbUrlFromCache( $name, $width, $height, $params="" ) {
global $wgMemc;
if ( !$this->canCacheThumbs() ) {
- return $this->getThumbUrl( $name, $width, $height );
+ $result = null; // can't pass "null" by reference, but it's ok as default value
+ return $this->getThumbUrl( $name, $width, $height, $result, $params );
}
$key = $this->getLocalCacheKey( 'ForeignAPIRepo', 'ThumbUrl', $name );
- $sizekey = "$width:$height";
+ $sizekey = "$width:$height:$params";
/* Get the array of urls that we already know */
$knownThumbUrls = $wgMemc->get($key);
@@ -241,14 +255,15 @@ class ForeignAPIRepo extends FileRepo {
$knownThumbUrls = array();
} else {
if( isset( $knownThumbUrls[$sizekey] ) ) {
- wfDebug("Got thumburl from local cache. {$knownThumbUrls[$sizekey]} \n");
+ wfDebug( __METHOD__ . ': Got thumburl from local cache: ' .
+ "{$knownThumbUrls[$sizekey]} \n");
return $knownThumbUrls[$sizekey];
}
/* This size is not yet known */
}
$metadata = null;
- $foreignUrl = $this->getThumbUrl( $name, $width, $height, $metadata );
+ $foreignUrl = $this->getThumbUrl( $name, $width, $height, $metadata, $params );
if( !$foreignUrl ) {
wfDebug( __METHOD__ . " Could not find thumburl\n" );
@@ -273,7 +288,7 @@ class ForeignAPIRepo extends FileRepo {
$diff = abs( $modified - $current );
if( $remoteModified < $modified && $diff < $this->fileCacheExpiry ) {
/* Use our current and already downloaded thumbnail */
- $knownThumbUrls["$width:$height"] = $localUrl;
+ $knownThumbUrls[$sizekey] = $localUrl;
$wgMemc->set( $key, $knownThumbUrls, $this->apiThumbCacheExpiry );
return $localUrl;
}
@@ -291,7 +306,7 @@ class ForeignAPIRepo extends FileRepo {
}
}
- # FIXME: Delete old thumbs that aren't being used. Maintenance script?
+ # @todo FIXME: Delete old thumbs that aren't being used. Maintenance script?
wfSuppressWarnings();
if( !file_put_contents( $localFilename, $thumb ) ) {
wfRestoreWarnings();
@@ -355,7 +370,7 @@ class ForeignAPIRepo extends FileRepo {
public static function httpGet( $url, $timeout = 'default', $options = array() ) {
$options['timeout'] = $timeout;
/* Http::get */
- $url = wfExpandUrl( $url );
+ $url = wfExpandUrl( $url, PROTO_HTTP );
wfDebug( "ForeignAPIRepo: HTTP GET: $url\n" );
$options['method'] = "GET";
diff --git a/includes/filerepo/ForeignDBFile.php b/includes/filerepo/ForeignDBFile.php
index 5f04ea73..09bee39c 100644
--- a/includes/filerepo/ForeignDBFile.php
+++ b/includes/filerepo/ForeignDBFile.php
@@ -12,6 +12,13 @@
* @ingroup FileRepo
*/
class ForeignDBFile extends LocalFile {
+
+ /**
+ * @param $title
+ * @param $repo
+ * @param $unused
+ * @return ForeignDBFile
+ */
static function newFromTitle( $title, $repo, $unused = null ) {
return new self( $title, $repo );
}
@@ -19,6 +26,11 @@ class ForeignDBFile extends LocalFile {
/**
* Create a ForeignDBFile from a title
* Do not call this except from inside a repo class.
+ *
+ * @param $row
+ * @param $repo
+ *
+ * @return ForeignDBFile
*/
static function newFromRow( $row, $repo ) {
$title = Title::makeTitle( NS_FILE, $row->img_name );
@@ -35,21 +47,30 @@ class ForeignDBFile extends LocalFile {
$watch = false, $timestamp = false ) {
$this->readOnlyError();
}
+
function restore( $versions = array(), $unsuppress = false ) {
$this->readOnlyError();
}
+
function delete( $reason, $suppress = false ) {
$this->readOnlyError();
}
+
function move( $target ) {
$this->readOnlyError();
}
-
+
+ /**
+ * @return string
+ */
function getDescriptionUrl() {
// Restore remote behaviour
return File::getDescriptionUrl();
}
+ /**
+ * @return string
+ */
function getDescriptionText() {
// Restore remote behaviour
return File::getDescriptionText();
diff --git a/includes/filerepo/ForeignDBRepo.php b/includes/filerepo/ForeignDBRepo.php
index a756703f..0311ebcd 100644
--- a/includes/filerepo/ForeignDBRepo.php
+++ b/includes/filerepo/ForeignDBRepo.php
@@ -35,14 +35,14 @@ class ForeignDBRepo extends LocalRepo {
function getMasterDB() {
if ( !isset( $this->dbConn ) ) {
- $this->dbConn = DatabaseBase::newFromType( $this->dbType,
+ $this->dbConn = DatabaseBase::factory( $this->dbType,
array(
'host' => $this->dbServer,
'user' => $this->dbUser,
'password' => $this->dbPassword,
'dbname' => $this->dbName,
'flags' => $this->dbFlags,
- 'tableprefix' => $this->tablePrefix
+ 'tablePrefix' => $this->tablePrefix
)
);
}
diff --git a/includes/filerepo/Image.php b/includes/filerepo/Image.php
deleted file mode 100644
index 59a07ef9..00000000
--- a/includes/filerepo/Image.php
+++ /dev/null
@@ -1,80 +0,0 @@
-<?php
-/**
- * Backward compatibility code for MW < 1.11
- *
- * @file
- */
-
-/**
- * Backwards compatibility class
- *
- * @deprecated. Will be removed in 1.18!
- * @ingroup FileRepo
- */
-class Image extends LocalFile {
- function __construct( $title ) {
- wfDeprecated( __METHOD__ );
- $repo = RepoGroup::singleton()->getLocalRepo();
- parent::__construct( $title, $repo );
- }
-
- /**
- * Wrapper for wfFindFile(), for backwards-compatibility only
- * Do not use in core code.
- * @deprecated
- */
- static function newFromTitle( $title, $repo, $time = null ) {
- wfDeprecated( __METHOD__ );
- $img = wfFindFile( $title, array( 'time' => $time ) );
- if ( !$img ) {
- $img = wfLocalFile( $title );
- }
- return $img;
- }
-
- /**
- * Wrapper for wfFindFile(), for backwards-compatibility only.
- * Do not use in core code.
- *
- * @param $name String: name of the image, used to create a title object using Title::makeTitleSafe
- * @return image object or null if invalid title
- * @deprecated
- */
- static function newFromName( $name ) {
- wfDeprecated( __METHOD__ );
- $title = Title::makeTitleSafe( NS_FILE, $name );
- if ( is_object( $title ) ) {
- $img = wfFindFile( $title );
- if ( !$img ) {
- $img = wfLocalFile( $title );
- }
- return $img;
- } else {
- return null;
- }
- }
-
- /**
- * Return the URL of an image, provided its name.
- *
- * Backwards-compatibility for extensions.
- * Note that fromSharedDirectory will only use the shared path for files
- * that actually exist there now, and will return local paths otherwise.
- *
- * @param $name String: name of the image, without the leading "Image:"
- * @param $fromSharedDirectory Boolean: Should this be in $wgSharedUploadPath?
- * @return string URL of $name image
- * @deprecated
- */
- static function imageUrl( $name, $fromSharedDirectory = false ) {
- wfDeprecated( __METHOD__ );
- $image = null;
- if( $fromSharedDirectory ) {
- $image = wfFindFile( $name );
- }
- if( !$image ) {
- $image = wfLocalFile( $name );
- }
- return $image->getUrl();
- }
-}
diff --git a/includes/filerepo/LocalFile.php b/includes/filerepo/LocalFile.php
index 5489ecb2..14da9122 100644
--- a/includes/filerepo/LocalFile.php
+++ b/includes/filerepo/LocalFile.php
@@ -33,7 +33,7 @@ class LocalFile extends File {
* @private
*/
var
- $fileExists, # does the file file exist on disk? (loadFromXxx)
+ $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, # \
@@ -63,6 +63,12 @@ class LocalFile extends File {
* Do not call this except from inside a repo class.
*
* Note: $unused param is only here to avoid an E_STRICT
+ *
+ * @param $title
+ * @param $repo
+ * @param $unused
+ *
+ * @return LocalFile
*/
static function newFromTitle( $title, $repo, $unused = null ) {
return new self( $title, $repo );
@@ -71,6 +77,11 @@ class LocalFile extends File {
/**
* Create a LocalFile from a title
* Do not call this except from inside a repo class.
+ *
+ * @param $row
+ * @param $repo
+ *
+ * @return LocalFile
*/
static function newFromRow( $row, $repo ) {
$title = Title::makeTitle( NS_FILE, $row->img_name );
@@ -83,17 +94,22 @@ class LocalFile extends File {
/**
* Create a LocalFile from a SHA-1 key
* Do not call this except from inside a repo class.
+ *
+ * @param $sha1 string base-36 SHA-1
+ * @param $repo LocalRepo
+ * @param string|bool $timestamp MW_timestamp (optional)
+ *
+ * @return bool|LocalFile
*/
static function newFromKey( $sha1, $repo, $timestamp = false ) {
- $conds = array( 'img_sha1' => $sha1 );
+ $dbr = $repo->getSlaveDB();
+ $conds = array( 'img_sha1' => $sha1 );
if ( $timestamp ) {
- $conds['img_timestamp'] = $timestamp;
+ $conds['img_timestamp'] = $dbr->timestamp( $timestamp );
}
- $dbr = $repo->getSlaveDB();
$row = $dbr->selectRow( 'image', self::selectFields(), $conds, __METHOD__ );
-
if ( $row ) {
return self::newFromRow( $row, $repo );
} else {
@@ -333,6 +349,7 @@ class LocalFile extends File {
* Upgrade a row if it needs it
*/
function maybeUpgradeRow() {
+ global $wgUpdateCompatibleMetadata;
if ( wfReadOnly() ) {
return;
}
@@ -344,9 +361,14 @@ class LocalFile extends File {
$this->upgraded = true;
} else {
$handler = $this->getHandler();
- if ( $handler && !$handler->isMetadataValid( $this, $this->metadata ) ) {
- $this->upgradeRow();
- $this->upgraded = true;
+ if ( $handler ) {
+ $validity = $handler->isMetadataValid( $this, $this->metadata );
+ if ( $validity === MediaHandler::METADATA_BAD
+ || ( $validity === MediaHandler::METADATA_COMPATIBLE && $wgUpdateCompatibleMetadata )
+ ) {
+ $this->upgradeRow();
+ $this->upgraded = true;
+ }
}
}
}
@@ -540,8 +562,8 @@ class LocalFile extends File {
/** isTrustedFile inherited */
/**
- * Returns true if the file file exists on disk.
- * @return boolean Whether file file exist on disk.
+ * Returns true if the file exists on disk.
+ * @return boolean Whether file exist on disk.
*/
public function exists() {
$this->load();
@@ -552,7 +574,6 @@ class LocalFile extends File {
/** getUnscaledThumb inherited */
/** thumbName inherited */
/** createThumb inherited */
- /** getThumbnail inherited */
/** transform inherited */
/**
@@ -591,12 +612,19 @@ class LocalFile extends File {
/**
* Get all thumbnail names previously generated for this file
+ * @param $archiveName string|false Name of an archive file
+ * @return array first element is the base dir, then files in that base dir.
*/
- function getThumbnails() {
+ function getThumbnails( $archiveName = false ) {
$this->load();
+ if ( $archiveName ) {
+ $dir = $this->getArchiveThumbPath( $archiveName );
+ } else {
+ $dir = $this->getThumbPath();
+ }
$files = array();
- $dir = $this->getThumbPath();
+ $files[] = $dir;
if ( is_dir( $dir ) ) {
$handle = opendir( $dir );
@@ -633,6 +661,11 @@ class LocalFile extends File {
$hashedName = md5( $this->getName() );
$oldKey = $this->repo->getSharedCacheKey( 'oldfile', $hashedName );
+ // Must purge thumbnails for old versions too! bug 30192
+ foreach( $this->getHistory() as $oldFile ) {
+ $oldFile->purgeThumbnails();
+ }
+
if ( $oldKey ) {
$wgMemc->delete( $oldKey );
}
@@ -653,32 +686,76 @@ class LocalFile extends File {
}
/**
- * Delete cached transformed files
+ * Delete cached transformed files for archived files
+ * @param $archiveName string name of the archived file
*/
- function purgeThumbnails() {
+ function purgeOldThumbnails( $archiveName ) {
global $wgUseSquid;
+ // get a list of old thumbnails and URLs
+ $files = $this->getThumbnails( $archiveName );
+ $dir = array_shift( $files );
+ $this->purgeThumbList( $dir, $files );
- // Delete thumbnails
- $files = $this->getThumbnails();
- $dir = $this->getThumbPath();
- $urls = array();
+ // Directory should be empty, delete it too. This will probably suck on
+ // something like NFS or if the directory isn't actually empty, so hide
+ // the warnings :D
+ wfSuppressWarnings();
+ if( !rmdir( $dir ) ) {
+ wfDebug( __METHOD__ . ": unable to remove archive directory: $dir\n" );
+ }
+ wfRestoreWarnings();
- foreach ( $files as $file ) {
- # Check that the base file name is part of the thumb name
- # This is a basic sanity check to avoid erasing unrelated directories
- if ( strpos( $file, $this->getName() ) !== false ) {
- $url = $this->getThumbUrl( $file );
- $urls[] = $url;
- @unlink( "$dir/$file" );
+ // Purge the squid
+ if ( $wgUseSquid ) {
+ $urls = array();
+ foreach( $files as $file ) {
+ $urls[] = $this->getArchiveThumbUrl( $archiveName, $file );
}
+ SquidUpdate::purge( $urls );
}
+ }
+
+
+ /**
+ * Delete cached transformed files for the current version only.
+ */
+ function purgeThumbnails() {
+ global $wgUseSquid;
+ // get a list of thumbnails and URLs
+ $files = $this->getThumbnails();
+ $dir = array_shift( $files );
+ $this->purgeThumbList( $dir, $files );
// Purge the squid
if ( $wgUseSquid ) {
+ $urls = array();
+ foreach( $files as $file ) {
+ $urls[] = $this->getThumbUrl( $file );
+ }
SquidUpdate::purge( $urls );
}
}
+ /**
+ * Delete a list of thumbnails visible at urls
+ * @param $dir string base dir of the files.
+ * @param $files array of strings: relative filenames (to $dir)
+ */
+ function purgeThumbList($dir, $files) {
+ global $wgExcludeFromThumbnailPurge;
+
+ wfDebug( __METHOD__ . ": " . var_export( $files, true ) . "\n" );
+ foreach ( $files as $file ) {
+ # Check that the base file name is part of the thumb name
+ # This is a basic sanity check to avoid erasing unrelated directories
+ if ( strpos( $file, $this->getName() ) !== false ) {
+ wfSuppressWarnings();
+ unlink( "$dir/$file" );
+ wfRestoreWarnings();
+ }
+ }
+ }
+
/** purgeDescription inherited */
/** purgeEverything inherited */
@@ -786,7 +863,6 @@ class LocalFile extends File {
/** getRel inherited */
/** getUrlRel inherited */
/** getArchiveRel inherited */
- /** getThumbRel inherited */
/** getArchivePath inherited */
/** getThumbPath inherited */
/** getArchiveUrl inherited */
@@ -828,7 +904,6 @@ class LocalFile extends File {
/**
* Record a file upload in the upload log and the image table
- * @deprecated use upload()
*/
function recordUpload( $oldver, $desc, $license = '', $copyStatus = '', $source = '',
$watch = false, $timestamp = false )
@@ -844,7 +919,6 @@ class LocalFile extends File {
$wgUser->addWatch( $this->getTitle() );
}
return true;
-
}
/**
@@ -883,7 +957,7 @@ class LocalFile extends File {
# Fail now if the file isn't there
if ( !$this->fileExists ) {
- wfDebug( __METHOD__ . ": File " . $this->getPath() . " went missing!\n" );
+ wfDebug( __METHOD__ . ": File " . $this->getRel() . " went missing!\n" );
return false;
}
@@ -961,8 +1035,10 @@ class LocalFile extends File {
} else {
# This is a new file
# Update the image count
+ $dbw->begin();
$site_stats = $dbw->tableName( 'site_stats' );
$dbw->query( "UPDATE $site_stats SET ss_images=ss_images+1", __METHOD__ );
+ $dbw->commit();
}
$descTitle = $this->getTitle();
@@ -1036,16 +1112,32 @@ class LocalFile extends File {
*
* @param $srcPath String: 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
+ * File::DELETE_SOURCE Delete the source file, i.e. move rather than copy
* @return FileRepoStatus object. On success, the value member contains the
* archive name, or an empty string if it was a new file.
*/
function publish( $srcPath, $flags = 0 ) {
+ return $this->publishTo( $srcPath, $this->getRel(), $flags );
+ }
+
+ /**
+ * Move or copy a file to a specified location. Returns a FileRepoStatus
+ * object with the archive name in the "value" member on success.
+ *
+ * The archive name should be passed through to recordUpload for database
+ * registration.
+ *
+ * @param $srcPath String: local filesystem path to the source image
+ * @param $dstRel String: target relative path
+ * @param $flags Integer: a bitwise combination of:
+ * File::DELETE_SOURCE Delete the source file, i.e. move rather than copy
+ * @return FileRepoStatus object. 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 ) {
$this->lock();
- $dstRel = $this->getRel();
- $archiveName = gmdate( 'YmdHis' ) . '!' . $this->getName();
+ $archiveName = wfTimestamp( TS_MW ) . '!'. $this->getName();
$archiveRel = 'archive/' . $this->getHashPath() . $archiveName;
$flags = $flags & File::DELETE_SOURCE ? LocalRepo::DELETE_SOURCE : 0;
$status = $this->repo->publish( $srcPath, $dstRel, $archiveRel, $flags );
@@ -1130,6 +1222,7 @@ class LocalFile extends File {
array( 'oi_name' => $this->getName() ) );
foreach ( $result as $row ) {
$batch->addOld( $row->oi_archive_name );
+ $this->purgeOldThumbnails( $row->oi_archive_name );
}
$status = $batch->execute();
@@ -1164,6 +1257,7 @@ class LocalFile extends File {
$batch = new LocalFileDeleteBatch( $this, $reason, $suppress );
$batch->addOld( $archiveName );
+ $this->purgeOldThumbnails( $archiveName );
$status = $batch->execute();
$this->unlock();
@@ -1198,7 +1292,7 @@ class LocalFile extends File {
$status = $batch->execute();
- if ( !$status->ok ) {
+ if ( !$status->isGood() ) {
return $status;
}
@@ -1312,7 +1406,13 @@ class LocalFile extends File {
* @ingroup FileRepo
*/
class LocalFileDeleteBatch {
- var $file, $reason, $srcRels = array(), $archiveUrls = array(), $deletionBatch, $suppress;
+
+ /**
+ * @var LocalFile
+ */
+ var $file;
+
+ var $reason, $srcRels = array(), $archiveUrls = array(), $deletionBatch, $suppress;
var $status;
function __construct( File $file, $reason = '', $suppress = false ) {
@@ -1344,7 +1444,7 @@ class LocalFileDeleteBatch {
return array( $oldRels, $deleteCurrent );
}
- /*protected*/ function getHashes() {
+ protected function getHashes() {
$hashes = array();
list( $oldRels, $deleteCurrent ) = $this->getOldRels();
@@ -1623,7 +1723,12 @@ class LocalFileDeleteBatch {
* @ingroup FileRepo
*/
class LocalFileRestoreBatch {
- var $file, $cleanupBatch, $ids, $all, $unsuppress = false;
+ /**
+ * @var LocalFile
+ */
+ var $file;
+
+ var $cleanupBatch, $ids, $all, $unsuppress = false;
function __construct( File $file, $unsuppress = false ) {
$this->file = $file;
@@ -1827,9 +1932,11 @@ class LocalFileRestoreBatch {
$storeStatus = $this->file->repo->storeBatch( $storeBatch, FileRepo::OVERWRITE_SAME );
$status->merge( $storeStatus );
- if ( !$status->ok ) {
- // Store batch returned a critical error -- this usually means nothing was stored
- // Stop now and return an error
+ if ( !$status->isGood() ) {
+ // Even if some files could be copied, fail entirely as that is the
+ // easiest thing to do without data loss
+ $this->cleanupFailedBatch( $storeStatus, $storeBatch );
+ $status->ok = false;
$this->file->unlock();
return $status;
@@ -1934,6 +2041,27 @@ class LocalFileRestoreBatch {
return $status;
}
+
+ /**
+ * Cleanup a failed batch. The batch was only partially successful, so
+ * rollback by removing all items that were succesfully copied.
+ *
+ * @param Status $storeStatus
+ * @param array $storeBatch
+ */
+ function cleanupFailedBatch( $storeStatus, $storeBatch ) {
+ $cleanupBatch = array();
+
+ foreach ( $storeStatus->success as $i => $success ) {
+ // Check if this item of the batch was successfully copied
+ if ( $success ) {
+ // Item was successfully copied and needs to be removed again
+ // Extract ($dstZone, $dstRel) from the batch
+ $cleanupBatch[] = array( $storeBatch[$i][1], $storeBatch[$i][2] );
+ }
+ }
+ $this->file->repo->cleanupBatch( $cleanupBatch );
+ }
}
# ------------------------------------------------------------------------------
@@ -1983,14 +2111,14 @@ class LocalFileMoveBatch {
$bits = explode( '!', $oldName, 2 );
if ( count( $bits ) != 2 ) {
- wfDebug( "Invalid old file name: $oldName \n" );
+ wfDebug( "Old file name missing !: '$oldName' \n" );
continue;
}
list( $timestamp, $filename ) = $bits;
if ( $this->oldName != $filename ) {
- wfDebug( "Invalid old file name: $oldName \n" );
+ wfDebug( "Old file name doesn't match: '$oldName' \n" );
continue;
}
@@ -2017,15 +2145,31 @@ class LocalFileMoveBatch {
$triplets = $this->getMoveTriplets();
$triplets = $this->removeNonexistentFiles( $triplets );
- $statusDb = $this->doDBUpdates();
- wfDebugLog( 'imagemove', "Renamed {$this->file->name} in database: {$statusDb->successCount} successes, {$statusDb->failCount} failures" );
- $statusMove = $repo->storeBatch( $triplets, FSRepo::DELETE_SOURCE );
- wfDebugLog( 'imagemove', "Moved files for {$this->file->name}: {$statusMove->successCount} successes, {$statusMove->failCount} failures" );
- if ( !$statusMove->isOk() ) {
+ // Copy the files into their new location
+ $statusMove = $repo->storeBatch( $triplets );
+ wfDebugLog( 'imagemove', "Moved files for {$this->file->name}: {$statusMove->successCount} successes, {$statusMove->failCount} failures" );
+ if ( !$statusMove->isGood() ) {
wfDebugLog( 'imagemove', "Error in moving files: " . $statusMove->getWikiText() );
+ $this->cleanupTarget( $triplets );
+ $statusMove->ok = false;
+ return $statusMove;
+ }
+
+ $this->db->begin();
+ $statusDb = $this->doDBUpdates();
+ wfDebugLog( 'imagemove', "Renamed {$this->file->name} in database: {$statusDb->successCount} successes, {$statusDb->failCount} failures" );
+ if ( !$statusDb->isGood() ) {
$this->db->rollback();
+ // Something went wrong with the DB updates, so remove the target files
+ $this->cleanupTarget( $triplets );
+ $statusDb->ok = false;
+ return $statusDb;
}
+ $this->db->commit();
+
+ // Everything went ok, remove the source files
+ $this->cleanupSource( $triplets );
$status->merge( $statusDb );
$status->merge( $statusMove );
@@ -2056,6 +2200,8 @@ class LocalFileMoveBatch {
$status->successCount++;
} else {
$status->failCount++;
+ $status->fatal( 'imageinvalidfilename' );
+ return $status;
}
// Update old images
@@ -2073,6 +2219,9 @@ class LocalFileMoveBatch {
$total = $this->oldCount;
$status->successCount += $affected;
$status->failCount += $total - $affected;
+ if ( $status->failCount ) {
+ $status->error( 'imageinvalidfilename' );
+ }
return $status;
}
@@ -2117,4 +2266,32 @@ class LocalFileMoveBatch {
return $filteredTriplets;
}
+
+ /**
+ * Cleanup a partially moved array of triplets by deleting the target
+ * files. Called if something went wrong half way.
+ */
+ function cleanupTarget( $triplets ) {
+ // Create dest pairs from the triplets
+ $pairs = array();
+ foreach ( $triplets as $triplet ) {
+ $pairs[] = array( $triplet[1], $triplet[2] );
+ }
+
+ $this->file->repo->cleanupBatch( $pairs );
+ }
+
+ /**
+ * 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.
+ */
+ function cleanupSource( $triplets ) {
+ // Create source file names from the triplets
+ $files = array();
+ foreach ( $triplets as $triplet ) {
+ $files[] = $triplet[0];
+ }
+
+ $this->file->repo->cleanupBatch( $files );
+ }
}
diff --git a/includes/filerepo/LocalRepo.php b/includes/filerepo/LocalRepo.php
index 02883c53..9089f4d7 100644
--- a/includes/filerepo/LocalRepo.php
+++ b/includes/filerepo/LocalRepo.php
@@ -20,6 +20,11 @@ class LocalRepo extends FSRepo {
var $fileFromRowFactory = array( 'LocalFile', 'newFromRow' );
var $oldFileFromRowFactory = array( 'OldLocalFile', 'newFromRow' );
+ /**
+ * @throws MWException
+ * @param $row
+ * @return File
+ */
function newFileFromRow( $row ) {
if ( isset( $row->img_name ) ) {
return call_user_func( $this->fileFromRowFactory, $row, $this );
@@ -30,6 +35,11 @@ class LocalRepo extends FSRepo {
}
}
+ /**
+ * @param $title
+ * @param $archiveName
+ * @return OldLocalFile
+ */
function newFromArchiveName( $title, $archiveName ) {
return OldLocalFile::newFromArchiveName( $title, $this, $archiveName );
}
@@ -39,13 +49,16 @@ class LocalRepo extends FSRepo {
* filearchive table. This needs to be done in the repo because it needs to
* interleave database locks with file operations, which is potentially a
* remote operation.
+ *
+ * @param $storageKeys array
+ *
* @return FileRepoStatus
*/
function cleanupDeletedBatch( $storageKeys ) {
$root = $this->getZonePath( 'deleted' );
$dbw = $this->getMasterDB();
$status = $this->newGood();
- $storageKeys = array_unique($storageKeys);
+ $storageKeys = array_unique( $storageKeys );
foreach ( $storageKeys as $key ) {
$hashPath = $this->getDeletedHashPath( $key );
$path = "$root/$hashPath$key";
@@ -54,8 +67,8 @@ class LocalRepo extends FSRepo {
array( 'fa_storage_group' => 'deleted', 'fa_storage_key' => $key ),
__METHOD__, array( 'FOR UPDATE' ) );
if( !$inuse ) {
- $sha1 = substr( $key, 0, strcspn( $key, '.' ) );
- $ext = substr( $key, strcspn($key,'.') + 1 );
+ $sha1 = self::getHashFromKey( $key );
+ $ext = substr( $key, strcspn( $key, '.' ) + 1 );
$ext = File::normalizeExtension($ext);
$inuse = $dbw->selectField( 'oldimage', '1',
array( 'oi_sha1' => $sha1,
@@ -65,7 +78,10 @@ class LocalRepo extends FSRepo {
}
if ( !$inuse ) {
wfDebug( __METHOD__ . ": deleting $key\n" );
- if ( !@unlink( $path ) ) {
+ wfSuppressWarnings();
+ $unlink = unlink( $path );
+ wfRestoreWarnings();
+ if ( !$unlink ) {
$status->error( 'undelete-cleanup-error', $path );
$status->failCount++;
}
@@ -77,6 +93,16 @@ class LocalRepo extends FSRepo {
}
return $status;
}
+
+ /**
+ * Gets the SHA1 hash from a storage key
+ *
+ * @param string $key
+ * @return string
+ */
+ public static function getHashFromKey( $key ) {
+ return strtok( $key, '.' );
+ }
/**
* Checks if there is a redirect named as $title
@@ -87,7 +113,7 @@ class LocalRepo extends FSRepo {
global $wgMemc;
if( is_string( $title ) ) {
- $title = Title::newFromTitle( $title );
+ $title = Title::newFromText( $title );
}
if( $title instanceof Title && $title->getNamespace() == NS_MEDIA ) {
$title = Title::makeTitle( NS_FILE, $title->getText() );
@@ -135,6 +161,7 @@ class LocalRepo extends FSRepo {
/**
* Function link Title::getArticleID().
* We can't say Title object, what database it should use, so we duplicate that function here.
+ * @param $title Title
*/
protected function getArticleID( $title ) {
if( !$title instanceof Title ) {
diff --git a/includes/filerepo/NullRepo.php b/includes/filerepo/NullRepo.php
index d5a1ee03..cac3e5d8 100644
--- a/includes/filerepo/NullRepo.php
+++ b/includes/filerepo/NullRepo.php
@@ -23,6 +23,9 @@ class NullRepo extends FileRepo {
function append( $srcPath, $toAppendPath, $flags = 0 ){
return false;
}
+ function appendFinish( $toAppendPath ){
+ return false;
+ }
function publishBatch( $triplets, $flags = 0 ) {
return false;
}
diff --git a/includes/filerepo/OldLocalFile.php b/includes/filerepo/OldLocalFile.php
index 9efe998f..bcb22c17 100644
--- a/includes/filerepo/OldLocalFile.php
+++ b/includes/filerepo/OldLocalFile.php
@@ -1,6 +1,6 @@
<?php
/**
- * Old file in the in the oldimage table
+ * Old file in the oldimage table
*
* @file
* @ingroup FileRepo
@@ -19,8 +19,9 @@ class OldLocalFile extends LocalFile {
static function newFromTitle( $title, $repo, $time = null ) {
# The null default value is only here to avoid an E_STRICT
- if( $time === null )
+ if ( $time === null ) {
throw new MWException( __METHOD__.' got null for $time parameter' );
+ }
return new self( $title, $repo, $time, null );
}
@@ -34,15 +35,27 @@ class OldLocalFile extends LocalFile {
$file->loadFromRow( $row, 'oi_' );
return $file;
}
-
+
+ /**
+ * Create a OldLocalFile from a SHA-1 key
+ * Do not call this except from inside a repo class.
+ *
+ * @param $sha1 string base-36 SHA-1
+ * @param $repo LocalRepo
+ * @param string|bool $timestamp MW_timestamp (optional)
+ *
+ * @return bool|OldLocalFile
+ */
static function newFromKey( $sha1, $repo, $timestamp = false ) {
+ $dbr = $repo->getSlaveDB();
+
$conds = array( 'oi_sha1' => $sha1 );
- if( $timestamp ) {
- $conds['oi_timestamp'] = $timestamp;
+ if ( $timestamp ) {
+ $conds['oi_timestamp'] = $dbr->timestamp( $timestamp );
}
- $dbr = $repo->getSlaveDB();
+
$row = $dbr->selectRow( 'oldimage', self::selectFields(), $conds, __METHOD__ );
- if( $row ) {
+ if ( $row ) {
return self::newFromRow( $row, $repo );
} else {
return false;
@@ -205,4 +218,77 @@ class OldLocalFile extends LocalFile {
$this->load();
return Revision::userCanBitfield( $this->deleted, $field );
}
+
+ /**
+ * Upload a file directly into archive. Generally for Special:Import.
+ *
+ * @param $srcPath string File system path of the source file
+ * @param $archiveName string Full archive name of the file, in the form
+ * $timestamp!$filename, where $filename must match $this->getName()
+ *
+ * @return FileRepoStatus
+ */
+ function uploadOld( $srcPath, $archiveName, $timestamp, $comment, $user, $flags = 0 ) {
+ $this->lock();
+
+ $dstRel = 'archive/' . $this->getHashPath() . $archiveName;
+ $status = $this->publishTo( $srcPath, $dstRel,
+ $flags & File::DELETE_SOURCE ? FileRepo::DELETE_SOURCE : 0
+ );
+
+ if ( $status->isGood() ) {
+ if ( !$this->recordOldUpload( $srcPath, $archiveName, $timestamp, $comment, $user ) ) {
+ $status->fatal( 'filenotfound', $srcPath );
+ }
+ }
+
+ $this->unlock();
+
+ return $status;
+ }
+
+ /**
+ * Record a file upload in the oldimage table, without adding log entries.
+ *
+ * @param $srcPath string File system path to the source file
+ * @param $archiveName string The archive name of the file
+ * @param $comment string Upload comment
+ * @param $user User User who did this upload
+ * @return bool
+ */
+ function recordOldUpload( $srcPath, $archiveName, $timestamp, $comment, $user ) {
+ $dbw = $this->repo->getMasterDB();
+ $dbw->begin();
+
+ $dstPath = $this->repo->getZonePath( 'public' ) . '/' . $this->getRel();
+ $props = self::getPropsFromPath( $dstPath );
+ if ( !$props['fileExists'] ) {
+ return false;
+ }
+
+ $dbw->insert( 'oldimage',
+ array(
+ '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'],
+ ), __METHOD__
+ );
+
+ $dbw->commit();
+
+ return true;
+ }
+
}
diff --git a/includes/filerepo/README b/includes/filerepo/README
index d3aea9f0..db46ff8a 100644
--- a/includes/filerepo/README
+++ b/includes/filerepo/README
@@ -39,3 +39,21 @@ LocalRepo.php. LocalRepo provides only file access, and LocalFile provides
database access and higher-level functions such as cache management.
Tim Starling, June 2007
+
+Structure:
+
+File.php defines an abstract class File.
+ ForeignAPIFile.php extends File.
+ LocalFile.php extends File.
+ ForeignDBFile.php extends LocalFile
+ Image.php extends LocalFile
+ UnregisteredLocalFile.php extends File.
+FileRepo.php defined an abstract class FileRepo.
+ ForeignAPIRepo.php extends FileRepo
+ FSRepo extends FileRepo
+ LocalRepo.php extends FSRepo
+ ForeignDBRepo.php extends LocalRepo
+ ForeignDBViaLBRepo.php extends LocalRepo
+ NullRepo extends FileRepo
+
+Russ Nelson, March 2011
diff --git a/includes/filerepo/RepoGroup.php b/includes/filerepo/RepoGroup.php
index b9996941..d4875908 100644
--- a/includes/filerepo/RepoGroup.php
+++ b/includes/filerepo/RepoGroup.php
@@ -16,16 +16,26 @@
* @ingroup FileRepo
*/
class RepoGroup {
- var $localRepo, $foreignRepos, $reposInitialised = false;
+
+ /**
+ * @var LocalRepo
+ */
+ var $localRepo;
+
+ var $foreignRepos, $reposInitialised = false;
var $localInfo, $foreignInfo;
var $cache;
+ /**
+ * @var RepoGroup
+ */
protected static $instance;
const MAX_CACHE_SIZE = 1000;
/**
* Get a RepoGroup instance. At present only one instance of RepoGroup is
* needed in a MediaWiki invocation, this may change in the future.
+ * @return RepoGroup
*/
static function singleton() {
if ( self::$instance ) {
@@ -46,6 +56,8 @@ class RepoGroup {
/**
* Set the singleton instance to a given object
+ *
+ * @param $instance RepoGroup
*/
static function setSingleton( $instance ) {
self::$instance = $instance;
@@ -70,8 +82,8 @@ class RepoGroup {
* Search repositories for an image.
* You can also use wfFindFile() to do this.
*
- * @param $title Mixed: Title object or string
- * @param $options Associative array of options:
+ * @param $title Title|string Title object or string
+ * @param $options array Associative array of options:
* time: requested time for an archived image, or false for the
* current version. An image object will be returned which was
* created at the specified time.
@@ -101,7 +113,7 @@ class RepoGroup {
}
if ( $title->getNamespace() != NS_MEDIA && $title->getNamespace() != NS_FILE ) {
- throw new MWException( __METHOD__ . ' recieved an Title object with incorrect namespace' );
+ throw new MWException( __METHOD__ . ' received an Title object with incorrect namespace' );
}
# Check the cache
@@ -204,14 +216,44 @@ class RepoGroup {
return false;
}
+ /**
+ * Find an instance of the file with this key, created at the specified time
+ * Returns false if the file does not exist.
+ *
+ * @param $hash String base 36 SHA-1 hash
+ * @param $options Option array, same as findFile()
+ * @return File object or false if it is not found
+ */
+ function findFileFromKey( $hash, $options = array() ) {
+ if ( !$this->reposInitialised ) {
+ $this->initialiseRepos();
+ }
+
+ $file = $this->localRepo->findFileFromKey( $hash, $options );
+ if ( !$file ) {
+ foreach ( $this->foreignRepos as $repo ) {
+ $file = $repo->findFileFromKey( $hash, $options );
+ if ( $file ) break;
+ }
+ }
+ return $file;
+ }
+
+ /**
+ * Find all instances of files with this key
+ *
+ * @param $hash String base 36 SHA-1 hash
+ * @return Array of File objects
+ */
function findBySha1( $hash ) {
if ( !$this->reposInitialised ) {
$this->initialiseRepos();
}
$result = $this->localRepo->findBySha1( $hash );
- foreach ( $this->foreignRepos as $repo )
+ foreach ( $this->foreignRepos as $repo ) {
$result = array_merge( $result, $repo->findBySha1( $hash ) );
+ }
return $result;
}
@@ -247,6 +289,8 @@ class RepoGroup {
/**
* Get the local repository, i.e. the one corresponding to the local image
* table. Files are typically uploaded to the local repository.
+ *
+ * @return LocalRepo
*/
function getLocalRepo() {
return $this->getRepo( 'local' );
@@ -307,7 +351,7 @@ class RepoGroup {
*/
function splitVirtualUrl( $url ) {
if ( substr( $url, 0, 9 ) != 'mwrepo://' ) {
- throw new MWException( __METHOD__.': unknown protoocl' );
+ throw new MWException( __METHOD__.': unknown protocol' );
}
$bits = explode( '/', substr( $url, 9 ), 3 );
diff --git a/includes/filerepo/UnregisteredLocalFile.php b/includes/filerepo/UnregisteredLocalFile.php
index 990a218c..2df9a9b5 100644
--- a/includes/filerepo/UnregisteredLocalFile.php
+++ b/includes/filerepo/UnregisteredLocalFile.php
@@ -19,16 +19,38 @@
* @ingroup FileRepo
*/
class UnregisteredLocalFile extends File {
- var $title, $path, $mime, $handler, $dims;
+ var $title, $path, $mime, $dims;
+ /**
+ * @var MediaHandler
+ */
+ var $handler;
+
+ /**
+ * @param $path
+ * @param $mime
+ * @return UnregisteredLocalFile
+ */
static function newFromPath( $path, $mime ) {
return new UnregisteredLocalFile( false, false, $path, $mime );
}
+ /**
+ * @param $title
+ * @param $repo
+ * @return UnregisteredLocalFile
+ */
static function newFromTitle( $title, $repo ) {
return new UnregisteredLocalFile( $title, $repo, false, false );
}
+ /**
+ * @throws MWException
+ * @param $title string
+ * @param $repo FSRepo
+ * @param $path string
+ * @param $mime string
+ */
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' );