summaryrefslogtreecommitdiff
path: root/includes/filerepo
diff options
context:
space:
mode:
authorPierre Schmitz <pierre@archlinux.de>2011-06-22 11:28:20 +0200
committerPierre Schmitz <pierre@archlinux.de>2011-06-22 11:28:20 +0200
commit9db190c7e736ec8d063187d4241b59feaf7dc2d1 (patch)
tree46d1a0dee7febef5c2d57a9f7b972be16a163b3d /includes/filerepo
parent78677c7bbdcc9739f6c10c75935898a20e1acd9e (diff)
update to MediaWiki 1.17.0
Diffstat (limited to 'includes/filerepo')
-rw-r--r--includes/filerepo/ArchivedFile.php18
-rw-r--r--includes/filerepo/FSRepo.php30
-rw-r--r--includes/filerepo/File.php128
-rw-r--r--includes/filerepo/FileRepo.php173
-rw-r--r--includes/filerepo/FileRepoStatus.php6
-rw-r--r--includes/filerepo/ForeignAPIFile.php83
-rw-r--r--includes/filerepo/ForeignAPIRepo.php258
-rw-r--r--includes/filerepo/ForeignDBFile.php8
-rw-r--r--includes/filerepo/ForeignDBRepo.php23
-rw-r--r--includes/filerepo/ForeignDBViaLBRepo.php7
-rw-r--r--includes/filerepo/Image.php16
-rw-r--r--includes/filerepo/LocalFile.php409
-rw-r--r--includes/filerepo/LocalRepo.php20
-rw-r--r--includes/filerepo/NullRepo.php6
-rw-r--r--includes/filerepo/OldLocalFile.php29
-rw-r--r--includes/filerepo/RepoGroup.php31
-rw-r--r--includes/filerepo/UnregisteredLocalFile.php8
17 files changed, 885 insertions, 368 deletions
diff --git a/includes/filerepo/ArchivedFile.php b/includes/filerepo/ArchivedFile.php
index ffc06303..ecc09978 100644
--- a/includes/filerepo/ArchivedFile.php
+++ b/includes/filerepo/ArchivedFile.php
@@ -1,10 +1,17 @@
<?php
+/**
+ * Deleted file in the 'filearchive' table
+ *
+ * @file
+ * @ingroup FileRepo
+ */
/**
- * @ingroup Media
+ * Class representing a row of the 'filearchive' table
+ *
+ * @ingroup FileRepo
*/
-class ArchivedFile
-{
+class ArchivedFile {
/**#@+
* @private
*/
@@ -29,7 +36,7 @@ class ArchivedFile
/**#@-*/
- function ArchivedFile( $title, $id=0, $key='' ) {
+ function __construct( $title, $id=0, $key='' ) {
$this->id = -1;
$this->title = false;
$this->name = false;
@@ -140,7 +147,6 @@ class ArchivedFile
$this->deleted = $row->fa_deleted;
} else {
throw new MWException( 'This title does not correspond to an image page.' );
- return;
}
$this->dataLoaded = true;
$this->exists = true;
@@ -219,7 +225,7 @@ class ArchivedFile
* Return the FileStore storage group
*/
public function getGroup() {
- return $file->group;
+ return $this->group;
}
/**
diff --git a/includes/filerepo/FSRepo.php b/includes/filerepo/FSRepo.php
index 0dd9d0f7..e2251b2b 100644
--- a/includes/filerepo/FSRepo.php
+++ b/includes/filerepo/FSRepo.php
@@ -1,4 +1,10 @@
<?php
+/**
+ * A repository for files accessible via the local filesystem.
+ *
+ * @file
+ * @ingroup FileRepo
+ */
/**
* A repository for files accessible via the local filesystem. Does not support
@@ -132,8 +138,8 @@ class FSRepo extends FileRepo {
/**
* Store a batch of files
*
- * @param array $triplets (src,zone,dest) triplets as per store()
- * @param integer $flags Bitwise combination of the following flags:
+ * @param $triplets Array: (src,zone,dest) triplets as per store()
+ * @param $flags Integer: bitwise combination of the following flags:
* self::DELETE_SOURCE Delete the source file after upload
* self::OVERWRITE Overwrite an existing destination file instead of failing
* self::OVERWRITE_SAME Overwrite the file if the destination exists and has the
@@ -267,8 +273,8 @@ class FSRepo extends FileRepo {
/**
* Checks existence of specified array of files.
*
- * @param array $files URLs of files to check
- * @param integer $flags Bitwise combination of the following flags:
+ * @param $files Array: URLs of files to check
+ * @param $flags Integer: bitwise combination of the following flags:
* self::FILES_ONLY Mark file as existing only if it is a file (not directory)
* @return Either array of files and existence flags, or false
*/
@@ -307,9 +313,9 @@ class FSRepo extends FileRepo {
/**
* Pick a random name in the temp zone and store a file to it.
- * @param string $originalName The base name of the file as specified
+ * @param $originalName String: the base name of the file as specified
* by the user. The file extension will be maintained.
- * @param string $srcPath The current location of the file.
+ * @param $srcPath String: the current location of the file.
* @return FileRepoStatus object with the URL in the value.
*/
function storeTemp( $originalName, $srcPath ) {
@@ -325,8 +331,8 @@ class FSRepo extends FileRepo {
/**
* Remove a temporary file or mark it for garbage collection
- * @param string $virtualUrl The virtual URL returned by storeTemp
- * @return boolean True on success, false on failure
+ * @param $virtualUrl String: the virtual URL returned by storeTemp
+ * @return Boolean: true on success, false on failure
*/
function freeTemp( $virtualUrl ) {
$temp = "mwrepo://{$this->name}/temp";
@@ -343,8 +349,8 @@ class FSRepo extends FileRepo {
/**
* Publish a batch of files
- * @param array $triplets (source,dest,archive) triplets as per publish()
- * @param integer $flags Bitfield, may be FileRepo::DELETE_SOURCE to indicate
+ * @param $triplets Array: (source,dest,archive) triplets as per publish()
+ * @param $flags Integer: bitfield, may be FileRepo::DELETE_SOURCE to indicate
* that the source files should be deleted if possible
*/
function publishBatch( $triplets, $flags = 0 ) {
@@ -454,7 +460,7 @@ class FSRepo extends FileRepo {
* If no valid deletion archive is configured, this may either delete the
* file or throw an exception, depending on the preference of the repository.
*
- * @param array $sourceDestPairs Array of source/destination pairs. Each element
+ * @param $sourceDestPairs Array of source/destination pairs. Each element
* is a two-element array containing the source file path relative to the
* public root in the first element, and the archive file path relative
* to the deleted zone root in the second element.
@@ -615,7 +621,7 @@ class FSRepo extends FileRepo {
/**
* Chmod a file, supressing the warnings.
- * @param String $path The path to change
+ * @param $path String: the path to change
*/
protected function chmod( $path ) {
wfSuppressWarnings();
diff --git a/includes/filerepo/File.php b/includes/filerepo/File.php
index d79a1661..192e8c8a 100644
--- a/includes/filerepo/File.php
+++ b/includes/filerepo/File.php
@@ -1,4 +1,10 @@
<?php
+/**
+ * Base code for files.
+ *
+ * @file
+ * @ingroup FileRepo
+ */
/**
* Implements some public methods and some protected utility functions which
@@ -177,7 +183,8 @@ abstract class File {
* Return a fully-qualified URL to the 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() );
@@ -260,6 +267,19 @@ 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;
+ }
+ }
+
+
+ /**
* Get handler-specific metadata
* Overridden by LocalFile, UnregisteredLocalFile
* STUB
@@ -437,26 +457,21 @@ abstract class File {
/**
* Get a ThumbnailImage which is the same size as the source
*/
- function getUnscaledThumb( $page = false ) {
+ function getUnscaledThumb( $handlerParams = array() ) {
+ $hp =& $handlerParams;
+ $page = isset( $hp['page'] ) ? $hp['page'] : false;
$width = $this->getWidth( $page );
if ( !$width ) {
return $this->iconThumb();
}
- if ( $page ) {
- $params = array(
- 'page' => $page,
- 'width' => $this->getWidth( $page )
- );
- } else {
- $params = array( 'width' => $this->getWidth() );
- }
- return $this->transform( $params );
+ $hp['width'] = $width;
+ return $this->transform( $hp );
}
/**
* Return the file name of a thumbnail with the specified parameters
*
- * @param array $params Handler-specific parameters
+ * @param $params Array: handler-specific parameters
* @private -ish
*/
function thumbName( $params ) {
@@ -464,7 +479,7 @@ abstract class File {
return null;
}
$extension = $this->getExtension();
- list( $thumbExt, $thumbMime ) = $this->handler->getThumbType( $extension, $this->getMimeType() );
+ list( $thumbExt, $thumbMime ) = $this->handler->getThumbType( $extension, $this->getMimeType(), $params );
$thumbName = $this->handler->makeParamString( $params ) . '-' . $this->getName();
if ( $thumbExt != $extension ) {
$thumbName .= ".$thumbExt";
@@ -484,8 +499,8 @@ abstract class File {
* specified, the generated image will be no bigger than width x height,
* and will also have correct aspect ratio.
*
- * @param integer $width maximum width of the generated thumbnail
- * @param integer $height maximum height of the image (optional)
+ * @param $width Integer: maximum width of the generated thumbnail
+ * @param $height Integer: maximum height of the image (optional)
*/
public function createThumb( $width, $height = -1 ) {
$params = array( 'width' => $width );
@@ -500,19 +515,20 @@ 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.
+ * and can produce a convenient \<img\> tag for you.
*
* For non-image formats, this may return a filetype-specific icon.
*
- * @param integer $width maximum width of the generated thumbnail
- * @param integer $height maximum height of the image (optional)
- * @param boolean $render Deprecated
+ * @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;
@@ -523,10 +539,10 @@ 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 integer $flags A bitfield, may contain self::RENDER_NOW to force rendering
- * @return MediaTransformOutput
+ * @param $params Array: 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
+ * @return MediaTransformOutput | false
*/
function transform( $params, $flags = 0 ) {
global $wgUseSquid, $wgIgnoreImageErrors, $wgThumbnailEpoch, $wgServer;
@@ -560,7 +576,7 @@ abstract class File {
$thumbPath = $this->getThumbPath( $thumbName );
$thumbUrl = $this->getThumbUrl( $thumbName );
- if ( $this->repo->canTransformVia404() && !($flags & self::RENDER_NOW ) ) {
+ if ( $this->repo && $this->repo->canTransformVia404() && !($flags & self::RENDER_NOW ) ) {
$thumb = $this->handler->getTransform( $this, $thumbPath, $thumbUrl, $params );
break;
}
@@ -842,19 +858,18 @@ abstract class File {
/**
* Move or copy a file to its public location. If a file exists at the
- * destination, move it to an archive. Returns the archive name on success
- * or an empty string if it was a new file, and a wikitext-formatted
- * WikiError object on failure.
+ * destination, move it to an archive. 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 string $sourcePath Local filesystem path to the source image
- * @param integer $flags A bitwise combination of:
+ * @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
- * @return The archive name on success or an empty string if it was a new
- * file, and a wikitext-formatted WikiError object on failure.
+ * @return FileRepoStatus object. On success, the value member contains the
+ * archive name, or an empty string if it was a new file.
*
* STUB
* Overridden by LocalFile
@@ -872,6 +887,7 @@ abstract class File {
* @deprecated Use HTMLCacheUpdate, this function uses too much memory
*/
function getLinksTo( $options = array() ) {
+ wfDeprecated( __METHOD__ );
wfProfileIn( __METHOD__ );
// Note: use local DB not repo DB, we want to know local links
@@ -884,21 +900,21 @@ abstract class File {
$encName = $db->addQuotes( $this->getName() );
$res = $db->select( array( 'page', 'imagelinks'),
- array( 'page_namespace', 'page_title', 'page_id', 'page_len', 'page_is_redirect' ),
- array( 'page_id' => 'il_from', 'il_to' => $encName ),
+ 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 ) ) {
- while ( $row = $db->fetchObject( $res ) ) {
- if ( $titleObj = Title::newFromRow( $row ) ) {
- $linkCache->addGoodLinkObj( $row->page_id, $titleObj, $row->page_len, $row->page_is_redirect );
+ 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;
}
}
}
- $db->freeResult( $res );
wfProfileOut( __METHOD__ );
return $retVal;
}
@@ -916,7 +932,8 @@ abstract class File {
* @return bool
*/
function isLocal() {
- return $this->getRepoName() == 'local';
+ $repo = $this->getRepo();
+ return $repo && $repo->isLocal();
}
/**
@@ -992,8 +1009,8 @@ abstract class File {
*
* Cache purging is done; logging is caller's responsibility.
*
- * @param $reason
- * @param $suppress, hide content from sysops?
+ * @param $reason String
+ * @param $suppress Boolean: hide content from sysops?
* @return true on success, false on some kind of failure
* STUB
* Overridden by LocalFile
@@ -1010,7 +1027,7 @@ abstract class File {
*
* @param $versions set of record ids of deleted items to restore,
* or empty to restore all revisions.
- * @param $unsuppress, remove restrictions on content upon restoration?
+ * @param $unsuppress remove restrictions on content upon restoration?
* @return the number of file revisions restored if successful,
* or false on failure
* STUB
@@ -1032,7 +1049,7 @@ abstract class File {
}
/**
- * Returns the number of pages of a multipage document, or NULL for
+ * Returns the number of pages of a multipage document, or false for
* documents which aren't multipage documents
*/
function pageCount() {
@@ -1059,11 +1076,11 @@ abstract class File {
}
/**
- * Get an image size array like that returned by getimagesize(), or false if it
+ * Get an image size array like that returned by getImageSize(), or false if it
* can't be determined.
*
- * @param string $fileName The filename
- * @return array
+ * @param $fileName String: The filename
+ * @return Array
*/
function getImageSize( $fileName ) {
if ( !$this->getHandler() ) {
@@ -1156,8 +1173,8 @@ 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 int $field
- * @return bool
+ * @param $field Integer
+ * @return Boolean
*/
function userCan( $field ) {
return true;
@@ -1166,9 +1183,9 @@ abstract class File {
/**
* Get an associative array containing information about a file in the local filesystem.
*
- * @param string $path Absolute local filesystem path
- * @param mixed $ext The file extension, or true to extract it from the filename.
- * Set it to false to ignore the extension.
+ * @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.
*/
static function getPropsFromPath( $path, $ext = true ) {
wfProfileIn( __METHOD__ );
@@ -1180,7 +1197,16 @@ abstract class File {
if ( $info['fileExists'] ) {
$magic = MimeMagic::singleton();
- $info['mime'] = $magic->guessMimeType( $path, $ext );
+ if ( $ext === true ) {
+ $i = strrpos( $path, '.' );
+ $ext = strtolower( $i ? substr( $path, $i + 1 ) : '' );
+ }
+
+ # mime type according to file contents
+ $info['file-mime'] = $magic->guessMimeType( $path, false );
+ # logical mime type
+ $info['mime'] = $magic->improveTypeFromExtension( $info['file-mime'], $ext );
+
list( $info['major_mime'], $info['minor_mime'] ) = self::splitMime( $info['mime'] );
$info['media_type'] = $magic->getMediaType( $path, $info['mime'] );
diff --git a/includes/filerepo/FileRepo.php b/includes/filerepo/FileRepo.php
index f94709b3..ff73a73c 100644
--- a/includes/filerepo/FileRepo.php
+++ b/includes/filerepo/FileRepo.php
@@ -1,8 +1,15 @@
<?php
+/**
+ * Base code for file repositories.
+ *
+ * @file
+ * @ingroup FileRepo
+ */
/**
- * Base class for file repositories
+ * Base class for file repositories.
* Do not instantiate, use a derived class.
+ *
* @ingroup FileRepo
*/
abstract class FileRepo {
@@ -12,7 +19,8 @@ abstract class FileRepo {
const OVERWRITE_SAME = 4;
var $thumbScriptUrl, $transformVia404;
- var $descBaseUrl, $scriptDirUrl, $articleUrl, $fetchDescription, $initialCapital;
+ var $descBaseUrl, $scriptDirUrl, $scriptExtension, $articleUrl;
+ var $fetchDescription, $initialCapital;
var $pathDisclosureProtection = 'paranoid';
var $descriptionCacheExpiry, $hashLevels, $url, $thumbUrl;
@@ -31,7 +39,8 @@ abstract class FileRepo {
$this->initialCapital = MWNamespace::isCapitalized( NS_FILE );
foreach ( array( 'descBaseUrl', 'scriptDirUrl', 'articleUrl', 'fetchDescription',
'thumbScriptUrl', 'initialCapital', 'pathDisclosureProtection',
- 'descriptionCacheExpiry', 'hashLevels', 'url', 'thumbUrl' ) as $var )
+ 'descriptionCacheExpiry', 'hashLevels', 'url', 'thumbUrl', 'scriptExtension' )
+ as $var )
{
if ( isset( $info[$var] ) ) {
$this->$var = $info[$var];
@@ -49,12 +58,13 @@ abstract class FileRepo {
/**
* Create a new File object from the local repository
- * @param mixed $title Title object or string
- * @param mixed $time Time at which the image was uploaded.
- * If this is specified, the returned object will be an
- * instance of the repository's old file class instead of
- * a current file. Repositories not supporting version
- * control should return false if this parameter is set.
+ *
+ * @param $title Mixed: Title object or string
+ * @param $time Mixed: Time at which the image was uploaded.
+ * If this is specified, the returned object will be an
+ * instance of the repository's old file class instead of a
+ * current file. Repositories not supporting version control
+ * should return false if this parameter is set.
*/
function newFile( $title, $time = false ) {
if ( !($title instanceof Title) ) {
@@ -79,7 +89,7 @@ abstract class FileRepo {
* Returns false if the file does not exist. Repositories not supporting
* version control should return false if the time is specified.
*
- * @param mixed $title Title object or string
+ * @param $title Mixed: Title object or string
* @param $options Associative array of options:
* time: requested time for an archived image, or false for the
* current version. An image object will be returned which was
@@ -144,7 +154,7 @@ abstract class FileRepo {
/*
* Find many files at once.
- * @param array $items, an array of titles, or an array of findFile() options with
+ * @param $items An array of titles, or an array of findFile() options with
* the "title" option giving the title. Example:
*
* $findItem = array( 'title' => $title, 'private' => true );
@@ -153,7 +163,7 @@ abstract class FileRepo {
*/
function findFiles( $items ) {
$result = array();
- foreach ( $items as $index => $item ) {
+ foreach ( $items as $item ) {
if ( is_array( $item ) ) {
$title = $item['title'];
$options = $item;
@@ -163,31 +173,33 @@ abstract class FileRepo {
$options = array();
}
$file = $this->findFile( $title, $options );
- if ( $file )
+ if ( $file ) {
$result[$file->getTitle()->getDBkey()] = $file;
+ }
}
return $result;
}
/**
* Create a new File object from the local repository
- * @param mixed $sha1 SHA-1 key
- * @param mixed $time Time at which the image was uploaded.
- * If this is specified, the returned object will be an
- * instance of the repository's old file class instead of
- * a current file. Repositories not supporting version
- * control should return false if this parameter is set.
+ * @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 {
- return false;
}
} else {
- return call_user_func( $this->fileFactoryKey, $sha1, $this );
+ if ( $this->fileFactoryKey ) {
+ return call_user_func( $this->fileFactoryKey, $sha1, $this );
+ }
}
+ return false;
}
/**
@@ -195,8 +207,8 @@ abstract class FileRepo {
* Returns false if the file does not exist. Repositories not supporting
* version control should return false if the time is specified.
*
- * @param string $sha1 string
- * @param array $options Option array, same as findFile().
+ * @param $sha1 String
+ * @param $options Option array, same as findFile().
*/
function findFileFromKey( $sha1, $options = array() ) {
if ( !is_array( $options ) ) {
@@ -217,7 +229,7 @@ abstract class FileRepo {
# Now try an old version of the file
if ( $time !== false ) {
$img = $this->newFileFromKey( $sha1, $time );
- if ( $img->exists() ) {
+ if ( $img && $img->exists() ) {
if ( !$img->isDeleted(File::DELETED_FILE) ) {
return $img;
} else if ( !empty( $options['private'] ) && $img->userCan(File::DELETED_FILE) ) {
@@ -237,7 +249,7 @@ abstract class FileRepo {
/**
* Get the URL corresponding to one of the four basic zones
- * @param String $zone One of: public, deleted, temp, thumb
+ * @param $zone String: one of: public, deleted, temp, thumb
* @return String or false
*/
function getZoneUrl( $zone ) {
@@ -255,7 +267,6 @@ abstract class FileRepo {
* Get the name of an image from its title object
*/
function getNameFromTitle( $title ) {
- global $wgCapitalLinks;
if ( $this->initialCapital != MWNamespace::isCapitalized( NS_FILE ) ) {
global $wgContLang;
$name = $title->getUserCaseDBKey();
@@ -295,6 +306,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 );
+ }
/**
* Get the URL of an image description page. May return false if it is
@@ -325,8 +348,7 @@ abstract class FileRepo {
# We use "Image:" as the canonical namespace for
# compatibility across all MediaWiki versions,
# and just sort of hope index.php is right. ;)
- return $this->scriptDirUrl .
- "/index.php?title=Image:$encName";
+ return $this->makeUrl( "title=Image:$encName" );
}
return false;
}
@@ -336,8 +358,8 @@ abstract class FileRepo {
* MediaWiki this means action=render. This should only be called by the
* repository's file class, since it may return invalid results. User code
* should use File::getDescriptionText().
- * @param string $name Name of image to fetch
- * @param string $lang Language to fetch it in, if any.
+ * @param $name String: name of image to fetch
+ * @param $lang String: language to fetch it in, if any.
*/
function getDescriptionRenderUrl( $name, $lang = null ) {
$query = 'action=render';
@@ -345,9 +367,10 @@ abstract class FileRepo {
$query .= '&uselang=' . $lang;
}
if ( isset( $this->scriptDirUrl ) ) {
- return $this->scriptDirUrl . '/index.php?title=' .
+ return $this->makeUrl(
+ 'title=' .
wfUrlencode( 'Image:' . $name ) .
- "&$query";
+ "&$query" );
} else {
$descUrl = $this->getDescriptionUrl( $name );
if ( $descUrl ) {
@@ -357,14 +380,25 @@ abstract class FileRepo {
}
}
}
+
+ /**
+ * Get the URL of the stylesheet to apply to description pages
+ * @return string
+ */
+ function getDescriptionStylesheetUrl() {
+ if ( $this->scriptDirUrl ) {
+ return $this->makeUrl( 'title=MediaWiki:Filepage.css&' .
+ wfArrayToCGI( Skin::getDynamicStylesheetQuery() ) );
+ }
+ }
/**
* Store a file to a given destination.
*
- * @param string $srcPath Source path or virtual URL
- * @param string $dstZone Destination zone
- * @param string $dstRel Destination relative path
- * @param integer $flags Bitwise combination of the following flags:
+ * @param $srcPath String: source path or virtual URL
+ * @param $dstZone String: destination zone
+ * @param $dstRel String: destination relative path
+ * @param $flags Integer: bitwise combination of the following flags:
* self::DELETE_SOURCE Delete the source file after upload
* self::OVERWRITE Overwrite an existing destination file instead of failing
* self::OVERWRITE_SAME Overwrite the file if the destination exists and has the
@@ -382,8 +416,8 @@ abstract class FileRepo {
/**
* Store a batch of files
*
- * @param array $triplets (src,zone,dest) triplets as per store()
- * @param integer $flags Flags as per store
+ * @param $triplets Array: (src,zone,dest) triplets as per store()
+ * @param $flags Integer: flags as per store
*/
abstract function storeBatch( $triplets, $flags = 0 );
@@ -391,18 +425,18 @@ abstract class FileRepo {
* Pick a random name in the temp zone and store a file to it.
* Returns a FileRepoStatus object with the URL in the value.
*
- * @param string $originalName The base name of the file as specified
+ * @param $originalName String: the base name of the file as specified
* by the user. The file extension will be maintained.
- * @param string $srcPath The current location of the file.
+ * @param $srcPath String: the current location of the file.
*/
abstract function storeTemp( $originalName, $srcPath );
/**
* Append the contents of the source path to the given file.
- * @param $srcPath string location of the source file
- * @param $toAppendPath string path to append to.
- * @param $flags Bitfield, may be FileRepo::DELETE_SOURCE to indicate
+ * @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
* that the source file should be deleted if possible
* @return mixed Status or false
*/
@@ -410,8 +444,8 @@ abstract class FileRepo {
/**
* Remove a temporary file or mark it for garbage collection
- * @param string $virtualUrl The virtual URL returned by storeTemp
- * @return boolean True on success, false on failure
+ * @param $virtualUrl String: the virtual URL returned by storeTemp
+ * @return Boolean: true on success, false on failure
* STUB
*/
function freeTemp( $virtualUrl ) {
@@ -425,11 +459,11 @@ abstract class FileRepo {
* Returns a FileRepoStatus object. On success, the value contains "new" or
* "archived", to indicate whether the file was new with that name.
*
- * @param string $srcPath The source path or URL
- * @param string $dstRel The destination relative path
- * @param string $archiveRel The relative path where the existing file is to
+ * @param $srcPath String: the source path or URL
+ * @param $dstRel String: the destination relative path
+ * @param $archiveRel String: rhe relative path where the existing file is to
* be archived, if there is one. Relative to the public zone root.
- * @param integer $flags Bitfield, may be FileRepo::DELETE_SOURCE to indicate
+ * @param $flags Integer: bitfield, may be FileRepo::DELETE_SOURCE to indicate
* that the source file should be deleted if possible
*/
function publish( $srcPath, $dstRel, $archiveRel, $flags = 0 ) {
@@ -447,8 +481,8 @@ abstract class FileRepo {
/**
* Publish a batch of files
- * @param array $triplets (source,dest,archive) triplets as per publish()
- * @param integer $flags Bitfield, may be FileRepo::DELETE_SOURCE to indicate
+ * @param $triplets Array: (source,dest,archive) triplets as per publish()
+ * @param $flags Integer: bitfield, may be FileRepo::DELETE_SOURCE to indicate
* that the source files should be deleted if possible
*/
abstract function publishBatch( $triplets, $flags = 0 );
@@ -461,8 +495,8 @@ abstract class FileRepo {
/**
* Checks existence of an array of files.
*
- * @param array $files URLs (or paths) of files to check
- * @param integer $flags Bitwise combination of the following flags:
+ * @param $files Array: URLs (or paths) of files to check
+ * @param $flags Integer: bitwise combination of the following flags:
* self::FILES_ONLY Mark file as existing only if it is a file (not directory)
* @return Either array of files and existence flags, or false
*/
@@ -478,7 +512,7 @@ abstract class FileRepo {
* assumes a naming scheme in the deleted zone based on content hash, as
* opposed to the public zone which is assumed to be unique.
*
- * @param array $sourceDestPairs Array of source/destination pairs. Each element
+ * @param $sourceDestPairs Array of source/destination pairs. Each element
* is a two-element array containing the source file path relative to the
* public root in the first element, and the archive file path relative
* to the deleted zone root in the second element.
@@ -490,10 +524,10 @@ abstract class FileRepo {
* Move a file to the deletion archive.
* If no valid deletion archive exists, this may either delete the file
* or throw an exception, depending on the preference of the repository
- * @param mixed $srcRel Relative path for the file to be deleted
- * @param mixed $archiveRel Relative path for the archive location.
+ * @param $srcRel Mixed: relative path for the file to be deleted
+ * @param $archiveRel Mixed: relative path for the archive location.
* Relative to a private archive directory.
- * @return WikiError object (wikitext-formatted), or true for success
+ * @return FileRepoStatus object
*/
function delete( $srcRel, $archiveRel ) {
return $this->deleteBatch( array( array( $srcRel, $archiveRel ) ) );
@@ -589,7 +623,7 @@ abstract class FileRepo {
* title object. If not, return false.
* STUB
*
- * @param Title $title Title of image
+ * @param $title Title of image
*/
function checkRedirect( $title ) {
return false;
@@ -600,7 +634,7 @@ abstract class FileRepo {
* Doesn't do anything for repositories that don't support image redirects.
*
* STUB
- * @param Title $title Title of image
+ * @param $title Title of image
*/
function invalidateImageRedirect( $title ) {}
@@ -620,7 +654,7 @@ abstract class FileRepo {
*/
public function getDisplayName() {
// We don't name our own repo, return nothing
- if ( $this->name == 'local' ) {
+ if ( $this->isLocal() ) {
return null;
}
// 'shared-repo-name-wikimediacommons' is used when $wgUseInstantCommons = true
@@ -632,6 +666,16 @@ abstract class FileRepo {
}
/**
+ * Returns true if this the local file repository.
+ *
+ * @return bool
+ */
+ function isLocal() {
+ return $this->getName() == 'local';
+ }
+
+
+ /**
* Get a key on the primary cache for this repository.
* Returns false if the repository's cache is not accessible at this site.
* The parameters are the parts of the key, as for wfMemcKey().
@@ -652,4 +696,11 @@ abstract class FileRepo {
array_unshift( $args, 'filerepo', $this->getName() );
return call_user_func_array( 'wfMemcKey', $args );
}
+
+ /**
+ * Get an UploadStash associated with this repo.
+ */
+ function getUploadStash() {
+ return new UploadStash( $this );
+ }
}
diff --git a/includes/filerepo/FileRepoStatus.php b/includes/filerepo/FileRepoStatus.php
index 63460fa8..161284c0 100644
--- a/includes/filerepo/FileRepoStatus.php
+++ b/includes/filerepo/FileRepoStatus.php
@@ -1,4 +1,10 @@
<?php
+/**
+ * Generic operation result for FileRepo-related operations
+ *
+ * @file
+ * @ingroup FileRepo
+ */
/**
* Generic operation result class for FileRepo-related operations
diff --git a/includes/filerepo/ForeignAPIFile.php b/includes/filerepo/ForeignAPIFile.php
index c46b1f8f..56fed75e 100644
--- a/includes/filerepo/ForeignAPIFile.php
+++ b/includes/filerepo/ForeignAPIFile.php
@@ -1,8 +1,14 @@
<?php
+/**
+ * Foreign file accessible through api.php requests.
+ *
+ * @file
+ * @ingroup FileRepo
+ */
-/**
- * Very hacky and inefficient
- * do not use :D
+/**
+ * Foreign file accessible through api.php requests.
+ * Very hacky and inefficient, do not use :D
*
* @ingroup FileRepo
*/
@@ -15,16 +21,47 @@ class ForeignAPIFile extends File {
$this->mInfo = $info;
$this->mExists = $exists;
}
-
+
+ /**
+ * @static
+ * @param $title Title
+ * @param $repo ForeignApiRepo
+ * @return ForeignAPIFile|null
+ */
static function newFromTitle( $title, $repo ) {
- $info = $repo->getImageInfo( $title );
+ $data = $repo->fetchImageQuery( array(
+ 'titles' => 'File:' . $title->getDBKey(),
+ 'iiprop' => self::getProps(),
+ 'prop' => 'imageinfo' ) );
+
+ $info = $repo->getImageInfo( $data );
+
if( $info ) {
- return new ForeignAPIFile( $title, $repo, $info, true );
+ $lastRedirect = isset( $data['query']['redirects'] )
+ ? count( $data['query']['redirects'] ) - 1
+ : -1;
+ if( $lastRedirect >= 0 ) {
+ $newtitle = Title::newFromText( $data['query']['redirects'][$lastRedirect]['to']);
+ $img = new ForeignAPIFile( $newtitle, $repo, $info, true );
+ if( $img ) {
+ $img->redirectedFrom( $title->getDBkey() );
+ }
+ } else {
+ $img = new ForeignAPIFile( $title, $repo, $info, true );
+ }
+ return $img;
} else {
return null;
}
}
+ /**
+ * Get the property string for iiprop and aiprop
+ */
+ static function getProps() {
+ return 'timestamp|user|comment|url|size|sha1|metadata|mime';
+ }
+
// Dummy functions...
public function exists() {
return $this->mExists;
@@ -40,10 +77,10 @@ class ForeignAPIFile extends File {
return parent::transform( $params, $flags );
}
$thumbUrl = $this->repo->getThumbUrlFromCache(
- $this->getName(),
- isset( $params['width'] ) ? $params['width'] : -1,
- isset( $params['height'] ) ? $params['height'] : -1 );
- return $this->handler->getTransform( $this, 'bogus', $thumbUrl, $params );;
+ $this->getName(),
+ isset( $params['width'] ) ? $params['width'] : -1,
+ isset( $params['height'] ) ? $params['height'] : -1 );
+ return $this->handler->getTransform( $this, 'bogus', $thumbUrl, $params );
}
// Info we can get from API...
@@ -74,27 +111,33 @@ class ForeignAPIFile extends File {
}
public function getSize() {
- return intval( @$this->mInfo['size'] );
+ return isset( $this->mInfo['size'] ) ? intval( $this->mInfo['size'] ) : null;
}
public function getUrl() {
- return strval( @$this->mInfo['url'] );
+ return isset( $this->mInfo['url'] ) ? strval( $this->mInfo['url'] ) : null;
}
public function getUser( $method='text' ) {
- return strval( @$this->mInfo['user'] );
+ return isset( $this->mInfo['user'] ) ? strval( $this->mInfo['user'] ) : null;
}
public function getDescription() {
- return strval( @$this->mInfo['comment'] );
+ return isset( $this->mInfo['comment'] ) ? strval( $this->mInfo['comment'] ) : null;
}
function getSha1() {
- return wfBaseConvert( strval( @$this->mInfo['sha1'] ), 16, 36, 31 );
+ return isset( $this->mInfo['sha1'] ) ?
+ wfBaseConvert( strval( $this->mInfo['sha1'] ), 16, 36, 31 ) :
+ null;
}
function getTimestamp() {
- return wfTimestamp( TS_MW, strval( @$this->mInfo['timestamp'] ) );
+ return wfTimestamp( TS_MW,
+ isset( $this->mInfo['timestamp'] ) ?
+ strval( $this->mInfo['timestamp'] ) :
+ null
+ );
}
function getMimeType() {
@@ -122,15 +165,13 @@ class ForeignAPIFile extends File {
*/
function getThumbPath( $suffix = '' ) {
if ( $this->repo->canCacheThumbs() ) {
- global $wgUploadDirectory;
- $path = $wgUploadDirectory . '/thumb/' . $this->getHashPath( $this->getName() );
+ $path = $this->repo->getZonePath('thumb') . '/' . $this->getHashPath( $this->getName() );
if ( $suffix ) {
$path = $path . $suffix . '/';
}
return $path;
- }
- else {
- return null;
+ } else {
+ return null;
}
}
diff --git a/includes/filerepo/ForeignAPIRepo.php b/includes/filerepo/ForeignAPIRepo.php
index 264cb920..e4188d6b 100644
--- a/includes/filerepo/ForeignAPIRepo.php
+++ b/includes/filerepo/ForeignAPIRepo.php
@@ -1,9 +1,13 @@
<?php
+/**
+ * Foreign repository accessible through api.php requests.
+ *
+ * @file
+ * @ingroup FileRepo
+ */
/**
* A foreign repository with a remote MediaWiki with an API thingy
- * Very hacky and inefficient
- * do not use except for testing :D
*
* Example config:
*
@@ -18,17 +22,37 @@
* @ingroup FileRepo
*/
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";
+
var $fileFactory = array( 'ForeignAPIFile', 'newFromTitle' );
+ /* Check back with Commons after a day */
var $apiThumbCacheExpiry = 86400;
+ /* Redownload thumbnail files after a month */
+ var $fileCacheExpiry = 2629743;
+ /* Local image directory */
+ var $directory;
+ var $thumbDir;
+
protected $mQueryCache = array();
protected $mFileExists = array();
function __construct( $info ) {
parent::__construct( $info );
- $this->mApiBase = $info['apibase']; // http://commons.wikimedia.org/w/api.php
+ global $wgUploadDirectory;
+
+ // http://commons.wikimedia.org/w/api.php
+ $this->mApiBase = isset( $info['apibase'] ) ? $info['apibase'] : null;
+ $this->directory = isset( $info['directory'] ) ? $info['directory'] : $wgUploadDirectory;
+
if( isset( $info['apiThumbCacheExpiry'] ) ) {
$this->apiThumbCacheExpiry = $info['apiThumbCacheExpiry'];
}
+ if( isset( $info['fileCacheExpiry'] ) ) {
+ $this->fileCacheExpiry = $info['fileCacheExpiry'];
+ }
if( !$this->scriptDirUrl ) {
// hack for description fetches
$this->scriptDirUrl = dirname( $this->mApiBase );
@@ -41,6 +65,11 @@ class ForeignAPIRepo extends FileRepo {
if( $this->canCacheThumbs() && !$this->thumbUrl ) {
$this->thumbUrl = $this->url . '/thumb';
}
+ if ( isset( $info['thumbDir'] ) ) {
+ $this->thumbDir = $info['thumbDir'];
+ } else {
+ $this->thumbDir = "{$this->directory}/thumb";
+ }
}
/**
@@ -89,7 +118,7 @@ class ForeignAPIRepo extends FileRepo {
}
}
- $results = $this->fetchImageQuery( array( 'titles' => implode( $files, '|' ),
+ $data = $this->fetchImageQuery( array( 'titles' => implode( $files, '|' ),
'prop' => 'imageinfo' ) );
if( isset( $data['query']['pages'] ) ) {
$i = 0;
@@ -98,40 +127,32 @@ class ForeignAPIRepo extends FileRepo {
$i++;
}
}
+ return $results;
}
function getFileProps( $virtualUrl ) {
return false;
}
- protected function queryImage( $query ) {
- $data = $this->fetchImageQuery( $query );
-
- if( isset( $data['query']['pages'] ) ) {
- foreach( $data['query']['pages'] as $pageid => $info ) {
- if( isset( $info['imageinfo'][0] ) ) {
- return $info['imageinfo'][0];
- }
- }
- }
- return false;
- }
-
- protected function fetchImageQuery( $query ) {
+ function fetchImageQuery( $query ) {
global $wgMemc;
- $url = $this->mApiBase .
- '?' .
- wfArrayToCgi(
- array_merge( $query,
- array(
- 'format' => 'json',
- 'action' => 'query' ) ) );
+ $query = array_merge( $query,
+ array(
+ 'format' => 'json',
+ 'action' => 'query',
+ 'redirects' => 'true'
+ ) );
+ if ( $this->mApiBase ) {
+ $url = wfAppendQuery( $this->mApiBase, $query );
+ } else {
+ $url = $this->makeUrl( $query, 'api' );
+ }
if( !isset( $this->mQueryCache[$url] ) ) {
$key = $this->getLocalCacheKey( 'ForeignAPIRepo', 'Metadata', md5( $url ) );
$data = $wgMemc->get( $key );
if( !$data ) {
- $data = Http::get( $url );
+ $data = self::httpGet( $url );
if ( !$data ) {
return null;
}
@@ -147,81 +168,141 @@ class ForeignAPIRepo extends FileRepo {
return FormatJson::decode( $this->mQueryCache[$url], true );
}
- function getImageInfo( $title, $time = false ) {
- return $this->queryImage( array(
- 'titles' => 'Image:' . $title->getText(),
- 'iiprop' => 'timestamp|user|comment|url|size|sha1|metadata|mime',
- 'prop' => 'imageinfo' ) );
+ function getImageInfo( $data ) {
+ if( $data && isset( $data['query']['pages'] ) ) {
+ foreach( $data['query']['pages'] as $info ) {
+ if( isset( $info['imageinfo'][0] ) ) {
+ return $info['imageinfo'][0];
+ }
+ }
+ }
+ return false;
}
function findBySha1( $hash ) {
$results = $this->fetchImageQuery( array(
'aisha1base36' => $hash,
- 'aiprop' => 'timestamp|user|comment|url|size|sha1|metadata|mime',
+ 'aiprop' => ForeignAPIFile::getProps(),
'list' => 'allimages', ) );
$ret = array();
if ( isset( $results['query']['allimages'] ) ) {
foreach ( $results['query']['allimages'] as $img ) {
+ // 1.14 was broken, doesn't return name attribute
+ if( !isset( $img['name'] ) ) {
+ continue;
+ }
$ret[] = new ForeignAPIFile( Title::makeTitle( NS_FILE, $img['name'] ), $this, $img );
}
}
return $ret;
}
- function getThumbUrl( $name, $width=-1, $height=-1 ) {
- $info = $this->queryImage( array(
- 'titles' => 'Image:' . $name,
- 'iiprop' => 'url',
+ function getThumbUrl( $name, $width=-1, $height=-1, &$result=NULL ) {
+ $data = $this->fetchImageQuery( array(
+ 'titles' => 'File:' . $name,
+ 'iiprop' => 'url|timestamp',
'iiurlwidth' => $width,
'iiurlheight' => $height,
'prop' => 'imageinfo' ) );
- if( $info && $info['thumburl'] ) {
+ $info = $this->getImageInfo( $data );
+
+ if( $data && $info && isset( $info['thumburl'] ) ) {
wfDebug( __METHOD__ . " got remote thumb " . $info['thumburl'] . "\n" );
+ $result = $info;
return $info['thumburl'];
} else {
return false;
}
}
+ /*
+ * Return the imageurl from cache if possible
+ *
+ * If the url has been requested today, get it from cache
+ * Otherwise retrieve remote thumb url, check for local file.
+ *
+ * @param $name String is a dbkey form of a title
+ * @param $width
+ * @param $height
+ */
function getThumbUrlFromCache( $name, $width, $height ) {
- global $wgMemc, $wgUploadPath, $wgServer, $wgUploadDirectory;
+ global $wgMemc;
if ( !$this->canCacheThumbs() ) {
return $this->getThumbUrl( $name, $width, $height );
}
-
$key = $this->getLocalCacheKey( 'ForeignAPIRepo', 'ThumbUrl', $name );
- if ( $thumbUrl = $wgMemc->get($key) ) {
- wfDebug("Got thumb from local cache. $thumbUrl \n");
- return $thumbUrl;
- }
- else {
- $foreignUrl = $this->getThumbUrl( $name, $width, $height );
- if( !$foreignUrl ) {
- wfDebug( __METHOD__ . " Could not find thumburl\n" );
- return false;
- }
- $thumb = Http::get( $foreignUrl );
- if( !$thumb ) {
- wfDebug( __METHOD__ . " Could not download thumb\n" );
- return false;
+ $sizekey = "$width:$height";
+
+ /* Get the array of urls that we already know */
+ $knownThumbUrls = $wgMemc->get($key);
+ if( !$knownThumbUrls ) {
+ /* No knownThumbUrls for this file */
+ $knownThumbUrls = array();
+ } else {
+ if( isset( $knownThumbUrls[$sizekey] ) ) {
+ wfDebug("Got thumburl from local cache. {$knownThumbUrls[$sizekey]} \n");
+ return $knownThumbUrls[$sizekey];
}
- // We need the same filename as the remote one :)
- $fileName = rawurldecode( pathinfo( $foreignUrl, PATHINFO_BASENAME ) );
- $path = 'thumb/' . $this->getHashPath( $name ) . $name . "/";
- if ( !is_dir($wgUploadDirectory . '/' . $path) ) {
- wfMkdirParents($wgUploadDirectory . '/' . $path);
+ /* This size is not yet known */
+ }
+
+ $metadata = null;
+ $foreignUrl = $this->getThumbUrl( $name, $width, $height, $metadata );
+
+ if( !$foreignUrl ) {
+ wfDebug( __METHOD__ . " Could not find thumburl\n" );
+ return false;
+ }
+
+ // We need the same filename as the remote one :)
+ $fileName = rawurldecode( pathinfo( $foreignUrl, PATHINFO_BASENAME ) );
+ if( !$this->validateFilename( $fileName ) ) {
+ wfDebug( __METHOD__ . " The deduced filename $fileName is not safe\n" );
+ return false;
+ }
+ $localPath = $this->getZonePath( 'thumb' ) . "/" . $this->getHashPath( $name ) . $name;
+ $localFilename = $localPath . "/" . $fileName;
+ $localUrl = $this->getZoneUrl( 'thumb' ) . "/" . $this->getHashPath( $name ) . rawurlencode( $name ) . "/" . rawurlencode( $fileName );
+
+ if( file_exists( $localFilename ) && isset( $metadata['timestamp'] ) ) {
+ wfDebug( __METHOD__ . " Thumbnail was already downloaded before\n" );
+ $modified = filemtime( $localFilename );
+ $remoteModified = strtotime( $metadata['timestamp'] );
+ $current = time();
+ $diff = abs( $modified - $current );
+ if( $remoteModified < $modified && $diff < $this->fileCacheExpiry ) {
+ /* Use our current and already downloaded thumbnail */
+ $knownThumbUrls["$width:$height"] = $localUrl;
+ $wgMemc->set( $key, $knownThumbUrls, $this->apiThumbCacheExpiry );
+ return $localUrl;
}
- $localUrl = $wgServer . $wgUploadPath . '/' . $path . $fileName;
- # FIXME: Delete old thumbs that aren't being used. Maintenance script?
- if( !file_put_contents($wgUploadDirectory . '/' . $path . $fileName, $thumb ) ) {
- wfDebug( __METHOD__ . " could not write to thumb path\n" );
+ /* There is a new Commons file, or existing thumbnail older than a month */
+ }
+ $thumb = self::httpGet( $foreignUrl );
+ if( !$thumb ) {
+ wfDebug( __METHOD__ . " Could not download thumb\n" );
+ return false;
+ }
+ if ( !is_dir($localPath) ) {
+ if( !wfMkdirParents($localPath) ) {
+ wfDebug( __METHOD__ . " could not create directory $localPath for thumb\n" );
return $foreignUrl;
}
- $wgMemc->set( $key, $localUrl, $this->apiThumbCacheExpiry );
- wfDebug( __METHOD__ . " got local thumb $localUrl, saving to cache \n" );
- return $localUrl;
}
+
+ # FIXME: Delete old thumbs that aren't being used. Maintenance script?
+ wfSuppressWarnings();
+ if( !file_put_contents( $localFilename, $thumb ) ) {
+ wfRestoreWarnings();
+ wfDebug( __METHOD__ . " could not write to thumb path\n" );
+ return $foreignUrl;
+ }
+ wfRestoreWarnings();
+ $knownThumbUrls[$sizekey] = $localUrl;
+ $wgMemc->set( $key, $knownThumbUrls, $this->apiThumbCacheExpiry );
+ wfDebug( __METHOD__ . " got local thumb $localUrl, saving to cache \n" );
+ return $localUrl;
}
/**
@@ -239,10 +320,57 @@ class ForeignAPIRepo extends FileRepo {
}
/**
+ * Get the local directory corresponding to one of the three basic zones
+ */
+ function getZonePath( $zone ) {
+ switch ( $zone ) {
+ case 'public':
+ return $this->directory;
+ case 'thumb':
+ return $this->thumbDir;
+ default:
+ return false;
+ }
+ }
+
+ /**
* Are we locally caching the thumbnails?
* @return bool
*/
public function canCacheThumbs() {
return ( $this->apiThumbCacheExpiry > 0 );
}
+
+ /**
+ * The user agent the ForeignAPIRepo will use.
+ */
+ public static function getUserAgent() {
+ return Http::userAgent() . " ForeignAPIRepo/" . self::VERSION;
+ }
+
+ /**
+ * Like a Http:get request, but with custom User-Agent.
+ * @see Http:get
+ */
+ public static function httpGet( $url, $timeout = 'default', $options = array() ) {
+ $options['timeout'] = $timeout;
+ /* Http::get */
+ $url = wfExpandUrl( $url );
+ wfDebug( "ForeignAPIRepo: HTTP GET: $url\n" );
+ $options['method'] = "GET";
+
+ if ( !isset( $options['timeout'] ) ) {
+ $options['timeout'] = 'default';
+ }
+
+ $req = MWHttpRequest::factory( $url, $options );
+ $req->setUserAgent( ForeignAPIRepo::getUserAgent() );
+ $status = $req->execute();
+
+ if ( $status->isOK() ) {
+ return $req->getContent();
+ } else {
+ return false;
+ }
+ }
}
diff --git a/includes/filerepo/ForeignDBFile.php b/includes/filerepo/ForeignDBFile.php
index a24ff72b..5f04ea73 100644
--- a/includes/filerepo/ForeignDBFile.php
+++ b/includes/filerepo/ForeignDBFile.php
@@ -1,6 +1,14 @@
<?php
+/**
+ * Foreign file with an accessible MediaWiki database
+ *
+ * @file
+ * @ingroup FileRepo
+ */
/**
+ * Foreign file with an accessible MediaWiki database
+ *
* @ingroup FileRepo
*/
class ForeignDBFile extends LocalFile {
diff --git a/includes/filerepo/ForeignDBRepo.php b/includes/filerepo/ForeignDBRepo.php
index 35c2c4bf..590350b4 100644
--- a/includes/filerepo/ForeignDBRepo.php
+++ b/includes/filerepo/ForeignDBRepo.php
@@ -1,7 +1,14 @@
<?php
+/**
+ * A foreign repository with an accessible MediaWiki database
+ *
+ * @file
+ * @ingroup FileRepo
+ */
/**
* A foreign repository with an accessible MediaWiki database
+ *
* @ingroup FileRepo
*/
class ForeignDBRepo extends LocalRepo {
@@ -28,10 +35,16 @@ class ForeignDBRepo extends LocalRepo {
function getMasterDB() {
if ( !isset( $this->dbConn ) ) {
- $class = 'Database' . ucfirst( $this->dbType );
- $this->dbConn = new $class( $this->dbServer, $this->dbUser,
- $this->dbPassword, $this->dbName, false, $this->dbFlags,
- $this->tablePrefix );
+ $this->dbConn = DatabaseBase::newFromType( $this->dbType,
+ array(
+ 'server' => $this->dbServer,
+ 'user' => $this->dbUser,
+ 'password' => $this->dbPassword,
+ 'dbname' => $this->dbName,
+ 'flags' => $this->dbFlags,
+ 'tableprefix' => $this->tablePrefix
+ )
+ );
}
return $this->dbConn;
}
@@ -65,7 +78,7 @@ class ForeignDBRepo extends LocalRepo {
function publish( $srcPath, $dstRel, $archiveRel, $flags = 0 ) {
throw new MWException( get_class($this) . ': write operations are not supported' );
}
- function deleteBatch( $fileMap ) {
+ function deleteBatch( $sourceDestPairs ) {
throw new MWException( get_class($this) . ': write operations are not supported' );
}
}
diff --git a/includes/filerepo/ForeignDBViaLBRepo.php b/includes/filerepo/ForeignDBViaLBRepo.php
index 80325752..4c530b51 100644
--- a/includes/filerepo/ForeignDBViaLBRepo.php
+++ b/includes/filerepo/ForeignDBViaLBRepo.php
@@ -1,7 +1,14 @@
<?php
+/**
+ * A foreign repository with a MediaWiki database accessible via the configured LBFactory
+ *
+ * @file
+ * @ingroup FileRepo
+ */
/**
* A foreign repository with a MediaWiki database accessible via the configured LBFactory
+ *
* @ingroup FileRepo
*/
class ForeignDBViaLBRepo extends LocalRepo {
diff --git a/includes/filerepo/Image.php b/includes/filerepo/Image.php
index 08ce219a..59a07ef9 100644
--- a/includes/filerepo/Image.php
+++ b/includes/filerepo/Image.php
@@ -1,8 +1,14 @@
<?php
+/**
+ * Backward compatibility code for MW < 1.11
+ *
+ * @file
+ */
/**
* Backwards compatibility class
- * @deprecated
+ *
+ * @deprecated. Will be removed in 1.18!
* @ingroup FileRepo
*/
class Image extends LocalFile {
@@ -17,7 +23,7 @@ class Image extends LocalFile {
* Do not use in core code.
* @deprecated
*/
- static function newFromTitle( $title, $time = false ) {
+ static function newFromTitle( $title, $repo, $time = null ) {
wfDeprecated( __METHOD__ );
$img = wfFindFile( $title, array( 'time' => $time ) );
if ( !$img ) {
@@ -30,7 +36,7 @@ class Image extends LocalFile {
* Wrapper for wfFindFile(), for backwards-compatibility only.
* Do not use in core code.
*
- * @param string $name name of the image, used to create a title object using Title::makeTitleSafe
+ * @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
*/
@@ -55,8 +61,8 @@ class Image extends LocalFile {
* Note that fromSharedDirectory will only use the shared path for files
* that actually exist there now, and will return local paths otherwise.
*
- * @param string $name Name of the image, without the leading "Image:"
- * @param boolean $fromSharedDirectory Should this be in $wgSharedUploadPath?
+ * @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
*/
diff --git a/includes/filerepo/LocalFile.php b/includes/filerepo/LocalFile.php
index b6b4bfed..5489ecb2 100644
--- a/includes/filerepo/LocalFile.php
+++ b/includes/filerepo/LocalFile.php
@@ -1,5 +1,9 @@
<?php
/**
+ * Local file in the wiki's own database
+ *
+ * @file
+ * @ingroup FileRepo
*/
/**
@@ -28,9 +32,10 @@ class LocalFile extends File {
/**#@+
* @private
*/
- var $fileExists, # does the file 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)
+ var
+ $fileExists, # does the file 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)
@@ -49,7 +54,7 @@ class LocalFile extends File {
$upgraded, # Whether the row was upgraded on load
$locked, # True if the image row is locked
$missing, # True if file is not present in file system. Not to be cached in memcached
- $deleted; # Bitfield akin to rev_deleted
+ $deleted; # Bitfield akin to rev_deleted
/**#@-*/
@@ -71,29 +76,31 @@ class LocalFile extends File {
$title = Title::makeTitle( NS_FILE, $row->img_name );
$file = new self( $title, $repo );
$file->loadFromRow( $row );
+
return $file;
}
-
+
/**
* Create a LocalFile from a SHA-1 key
* Do not call this except from inside a repo class.
*/
static function newFromKey( $sha1, $repo, $timestamp = false ) {
- # Polymorphic function name to distinguish foreign and local fetches
- $fname = get_class( $this ) . '::' . __FUNCTION__;
-
$conds = array( 'img_sha1' => $sha1 );
- if( $timestamp ) {
+
+ if ( $timestamp ) {
$conds['img_timestamp'] = $timestamp;
}
- $row = $dbr->selectRow( 'image', $this->getCacheFields( 'img_' ), $conds, $fname );
- if( $row ) {
+
+ $dbr = $repo->getSlaveDB();
+ $row = $dbr->selectRow( 'image', self::selectFields(), $conds, __METHOD__ );
+
+ if ( $row ) {
return self::newFromRow( $row, $repo );
} else {
return false;
}
}
-
+
/**
* Fields in the image table
*/
@@ -121,10 +128,12 @@ class LocalFile extends File {
* Do not call this except from inside a repo class.
*/
function __construct( $title, $repo ) {
- if( !is_object( $title ) ) {
+ if ( !is_object( $title ) ) {
throw new MWException( __CLASS__ . ' constructor given bogus title.' );
}
+
parent::__construct( $title, $repo );
+
$this->metadata = '';
$this->historyLine = 0;
$this->historyRes = null;
@@ -132,11 +141,12 @@ class LocalFile extends File {
}
/**
- * Get the memcached key for the main data for this file, or false if
+ * Get the memcached key for the main data for this file, or false if
* there is no access to the shared cache.
*/
function getCacheKey() {
$hashedName = md5( $this->getName() );
+
return $this->repo->getSharedCacheKey( 'file', $hashedName );
}
@@ -145,13 +155,16 @@ class LocalFile extends File {
*/
function loadFromCache() {
global $wgMemc;
+
wfProfileIn( __METHOD__ );
$this->dataLoaded = false;
$key = $this->getCacheKey();
+
if ( !$key ) {
wfProfileOut( __METHOD__ );
return false;
}
+
$cachedValues = $wgMemc->get( $key );
// Check if the key existed and belongs to this version of MediaWiki
@@ -163,6 +176,7 @@ class LocalFile extends File {
}
$this->dataLoaded = true;
}
+
if ( $this->dataLoaded ) {
wfIncrStats( 'image_cache_hit' );
} else {
@@ -178,14 +192,18 @@ class LocalFile extends File {
*/
function saveToCache() {
global $wgMemc;
+
$this->load();
$key = $this->getCacheKey();
+
if ( !$key ) {
return;
}
+
$fields = $this->getCacheFields( '' );
$cache = array( 'version' => MW_FILE_VERSION );
$cache['fileExists'] = $this->fileExists;
+
if ( $this->fileExists ) {
foreach ( $fields as $field ) {
$cache[$field] = $this->$field;
@@ -206,9 +224,11 @@ class LocalFile extends File {
static $fields = array( 'size', 'width', 'height', 'bits', 'media_type',
'major_mime', 'minor_mime', 'metadata', 'timestamp', 'sha1', 'user', 'user_text', 'description' );
static $results = array();
+
if ( $prefix == '' ) {
return $fields;
}
+
if ( !isset( $results[$prefix] ) ) {
$prefixedFields = array();
foreach ( $fields as $field ) {
@@ -216,6 +236,7 @@ class LocalFile extends File {
}
$results[$prefix] = $prefixedFields;
}
+
return $results[$prefix];
}
@@ -234,6 +255,7 @@ class LocalFile extends File {
$row = $dbr->selectRow( 'image', $this->getCacheFields( 'img_' ),
array( 'img_name' => $this->getName() ), $fname );
+
if ( $row ) {
$this->loadFromRow( $row );
} else {
@@ -250,15 +272,20 @@ class LocalFile extends File {
function decodeRow( $row, $prefix = 'img_' ) {
$array = (array)$row;
$prefixLength = strlen( $prefix );
+
// Sanity check prefix once
if ( substr( key( $array ), 0, $prefixLength ) !== $prefix ) {
throw new MWException( __METHOD__ . ': incorrect $prefix parameter' );
}
+
$decoded = array();
+
foreach ( $array as $name => $value ) {
$decoded[substr( $name, $prefixLength )] = $value;
}
+
$decoded['timestamp'] = wfTimestamp( TS_MW, $decoded['timestamp'] );
+
if ( empty( $decoded['major_mime'] ) ) {
$decoded['mime'] = 'unknown/unknown';
} else {
@@ -267,8 +294,10 @@ class LocalFile extends File {
}
$decoded['mime'] = $decoded['major_mime'] . '/' . $decoded['minor_mime'];
}
+
# Trim zero padding from char/binary field
$decoded['sha1'] = rtrim( $decoded['sha1'], "\0" );
+
return $decoded;
}
@@ -278,9 +307,11 @@ class LocalFile extends File {
function loadFromRow( $row, $prefix = 'img_' ) {
$this->dataLoaded = true;
$array = $this->decodeRow( $row, $prefix );
+
foreach ( $array as $name => $value ) {
$this->$name = $value;
}
+
$this->fileExists = true;
$this->maybeUpgradeRow();
}
@@ -305,6 +336,7 @@ class LocalFile extends File {
if ( wfReadOnly() ) {
return;
}
+
if ( is_null( $this->media_type ) ||
$this->mime == 'image/svg'
) {
@@ -337,6 +369,7 @@ class LocalFile extends File {
wfProfileOut( __METHOD__ );
return;
}
+
$dbw = $this->repo->getMasterDB();
list( $major, $minor ) = self::splitMime( $this->mime );
@@ -359,6 +392,7 @@ class LocalFile extends File {
), array( 'img_name' => $this->getName() ),
__METHOD__
);
+
$this->saveToCache();
wfProfileOut( __METHOD__ );
}
@@ -374,15 +408,18 @@ class LocalFile extends File {
$this->dataLoaded = true;
$fields = $this->getCacheFields( '' );
$fields[] = 'fileExists';
+
foreach ( $fields as $field ) {
if ( isset( $info[$field] ) ) {
$this->$field = $info[$field];
}
}
+
// Fix up mime fields
if ( isset( $info['major_mime'] ) ) {
$this->mime = "{$info['major_mime']}/{$info['minor_mime']}";
} elseif ( isset( $info['mime'] ) ) {
+ $this->mime = $info['mime'];
list( $this->major_mime, $this->minor_mime ) = self::splitMime( $this->mime );
}
}
@@ -396,7 +433,7 @@ class LocalFile extends File {
/** isVisible inhereted */
function isMissing() {
- if( $this->missing === null ) {
+ if ( $this->missing === null ) {
list( $fileExists ) = $this->repo->fileExistsBatch( array( $this->getVirtualUrl() ), FileRepo::FILES_ONLY );
$this->missing = !$fileExists;
}
@@ -410,6 +447,7 @@ class LocalFile extends File {
*/
public function getWidth( $page = 1 ) {
$this->load();
+
if ( $this->isMultipage() ) {
$dim = $this->getHandler()->getPageDimensions( $this, $page );
if ( $dim ) {
@@ -429,6 +467,7 @@ class LocalFile extends File {
*/
public function getHeight( $page = 1 ) {
$this->load();
+
if ( $this->isMultipage() ) {
$dim = $this->getHandler()->getPageDimensions( $this, $page );
if ( $dim ) {
@@ -448,9 +487,10 @@ class LocalFile extends File {
*/
function getUser( $type = 'text' ) {
$this->load();
- if( $type == 'text' ) {
+
+ if ( $type == 'text' ) {
return $this->user_text;
- } elseif( $type == 'id' ) {
+ } elseif ( $type == 'id' ) {
return $this->user;
}
}
@@ -521,6 +561,7 @@ class LocalFile extends File {
function migrateThumbFile( $thumbName ) {
$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
@@ -535,6 +576,7 @@ class LocalFile extends File {
// Doesn't exist anymore
clearstatcache();
}
+
if ( is_file( $thumbDir ) ) {
// File where directory should be
unlink( $thumbDir );
@@ -552,6 +594,7 @@ class LocalFile extends File {
*/
function getThumbnails() {
$this->load();
+
$files = array();
$dir = $this->getThumbPath();
@@ -560,10 +603,11 @@ class LocalFile extends File {
if ( $handle ) {
while ( false !== ( $file = readdir( $handle ) ) ) {
- if ( $file{0} != '.' ) {
+ if ( $file { 0 } != '.' ) {
$files[] = $file;
}
}
+
closedir( $handle );
}
}
@@ -585,8 +629,10 @@ class LocalFile extends File {
*/
function purgeHistory() {
global $wgMemc;
+
$hashedName = md5( $this->getName() );
$oldKey = $this->repo->getSharedCacheKey( 'oldfile', $hashedName );
+
if ( $oldKey ) {
$wgMemc->delete( $oldKey );
}
@@ -611,10 +657,12 @@ class LocalFile extends File {
*/
function purgeThumbnails() {
global $wgUseSquid;
+
// Delete thumbnails
$files = $this->getThumbnails();
$dir = $this->getThumbPath();
$urls = array();
+
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
@@ -641,31 +689,42 @@ class LocalFile extends File {
$conds = $opts = $join_conds = array();
$eq = $inc ? '=' : '';
$conds[] = "oi_name = " . $dbr->addQuotes( $this->title->getDBkey() );
- if( $start ) {
+
+ if ( $start ) {
$conds[] = "oi_timestamp <$eq " . $dbr->addQuotes( $dbr->timestamp( $start ) );
}
- if( $end ) {
+
+ if ( $end ) {
$conds[] = "oi_timestamp >$eq " . $dbr->addQuotes( $dbr->timestamp( $end ) );
}
- if( $limit ) {
+
+ if ( $limit ) {
$opts['LIMIT'] = $limit;
}
+
// Search backwards for time > x queries
$order = ( !$start && $end !== null ) ? 'ASC' : 'DESC';
$opts['ORDER BY'] = "oi_timestamp $order";
$opts['USE INDEX'] = array( 'oldimage' => 'oi_name_timestamp' );
- wfRunHooks( 'LocalFile::getHistory', array( &$this, &$tables, &$fields,
+ wfRunHooks( 'LocalFile::getHistory', array( &$this, &$tables, &$fields,
&$conds, &$opts, &$join_conds ) );
$res = $dbr->select( $tables, $fields, $conds, __METHOD__, $opts, $join_conds );
$r = array();
- while( $row = $dbr->fetchObject( $res ) ) {
- $r[] = OldLocalFile::newFromRow( $row, $this->repo );
+
+ 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 );
+ }
}
- if( $order == 'ASC' ) {
+
+ if ( $order == 'ASC' ) {
$r = array_reverse( $r ); // make sure it ends up descending
}
+
return $r;
}
@@ -694,13 +753,12 @@ class LocalFile extends File {
array( 'img_name' => $this->title->getDBkey() ),
$fname
);
+
if ( 0 == $dbr->numRows( $this->historyRes ) ) {
- $dbr->freeResult( $this->historyRes );
$this->historyRes = null;
return false;
}
} elseif ( $this->historyLine == 1 ) {
- $dbr->freeResult( $this->historyRes );
$this->historyRes = $dbr->select( 'oldimage', '*',
array( 'oi_name' => $this->title->getDBkey() ),
$fname,
@@ -717,8 +775,8 @@ class LocalFile extends File {
*/
public function resetHistory() {
$this->historyLine = 0;
+
if ( !is_null( $this->historyRes ) ) {
- $this->repo->getSlaveDB()->freeResult( $this->historyRes );
$this->historyRes = null;
}
}
@@ -739,14 +797,16 @@ class LocalFile extends File {
/**
* Upload a file and record it in the DB
- * @param string $srcPath Source path or virtual URL
- * @param string $comment Upload description
- * @param string $pageText Text to use for the new description page, if a new description page is created
- * @param integer $flags Flags for publish()
- * @param array $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 $timestamp Timestamp for img_timestamp, or false to use the current time
+ * @param $srcPath String: source path or virtual URL
+ * @param $comment String: upload description
+ * @param $pageText String: text to use for the new description page,
+ * if a new description page is created
+ * @param $flags Integer: flags for publish()
+ * @param $props Array: 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 $timestamp String: timestamp for img_timestamp, or false to use the current time
+ * @param $user Mixed: User object or null to use $wgUser
*
* @return FileRepoStatus object. On success, the value member contains the
* archive name, or an empty string if it was a new file.
@@ -754,12 +814,15 @@ class LocalFile extends File {
function upload( $srcPath, $comment, $pageText, $flags = 0, $props = false, $timestamp = false, $user = null ) {
$this->lock();
$status = $this->publish( $srcPath, $flags );
+
if ( $status->ok ) {
if ( !$this->recordUpload2( $status->value, $comment, $pageText, $props, $timestamp, $user ) ) {
$status->fatal( 'filenotfound', $srcPath );
}
}
+
$this->unlock();
+
return $status;
}
@@ -771,9 +834,11 @@ class LocalFile extends File {
$watch = false, $timestamp = false )
{
$pageText = SpecialUpload::getInitialPageText( $desc, $license, $copyStatus, $source );
+
if ( !$this->recordUpload2( $oldver, $desc, $pageText ) ) {
return false;
}
+
if ( $watch ) {
global $wgUser;
$wgUser->addWatch( $this->getTitle() );
@@ -785,11 +850,12 @@ class LocalFile extends File {
/**
* Record a file upload in the upload log and the image table
*/
- function recordUpload2( $oldver, $comment, $pageText, $props = false, $timestamp = false, $user = null )
- {
- if( is_null( $user ) ) {
+ function recordUpload2(
+ $oldver, $comment, $pageText, $props = false, $timestamp = false, $user = null
+ ) {
+ if ( is_null( $user ) ) {
global $wgUser;
- $user = $wgUser;
+ $user = $wgUser;
}
$dbw = $this->repo->getMasterDB();
@@ -798,27 +864,30 @@ class LocalFile extends File {
if ( !$props ) {
$props = $this->repo->getFileProps( $this->getVirtualUrl() );
}
+
+ if ( $timestamp === false ) {
+ $timestamp = $dbw->timestamp();
+ }
+
$props['description'] = $comment;
$props['user'] = $user->getId();
$props['user_text'] = $user->getName();
- $props['timestamp'] = wfTimestamp( TS_MW );
+ $props['timestamp'] = wfTimestamp( TS_MW, $timestamp ); // DB -> TS_MW
$this->setProps( $props );
- // Delete thumbnails and refresh the metadata cache
+ # Delete thumbnails
$this->purgeThumbnails();
- $this->saveToCache();
+
+ # The file is already on its final location, remove it from the squid cache
SquidUpdate::purge( array( $this->getURL() ) );
- // Fail now if the file isn't there
+ # Fail now if the file isn't there
if ( !$this->fileExists ) {
wfDebug( __METHOD__ . ": File " . $this->getPath() . " went missing!\n" );
return false;
}
$reupload = false;
- if ( $timestamp === false ) {
- $timestamp = $dbw->timestamp();
- }
# Test to see if the row exists using INSERT IGNORE
# This avoids race conditions by locking the row until the commit, and also
@@ -826,7 +895,7 @@ class LocalFile extends File {
$dbw->insert( 'image',
array(
'img_name' => $this->getName(),
- 'img_size'=> $this->size,
+ 'img_size' => $this->size,
'img_width' => intval( $this->width ),
'img_height' => intval( $this->height ),
'img_bits' => $this->bits,
@@ -844,7 +913,7 @@ class LocalFile extends File {
'IGNORE'
);
- if( $dbw->affectedRows() == 0 ) {
+ if ( $dbw->affectedRows() == 0 ) {
$reupload = true;
# Collision, this is an update of a file
@@ -905,13 +974,17 @@ class LocalFile extends File {
$action = $reupload ? 'overwrite' : 'upload';
$log->addEntry( $action, $descTitle, $comment, array(), $user );
- if( $descTitle->exists() ) {
+ if ( $descTitle->exists() ) {
# Create a null revision
$latest = $descTitle->getLatestRevID();
- $nullRevision = Revision::newNullRevision( $dbw, $descTitle->getArticleId(),
- $log->getRcComment(), false );
+ $nullRevision = Revision::newNullRevision(
+ $dbw,
+ $descTitle->getArticleId(),
+ $log->getRcComment(),
+ false
+ );
$nullRevision->insertOn( $dbw );
-
+
wfRunHooks( 'NewRevisionFromEditComplete', array( $article, $nullRevision, $latest, $user ) );
$article->updateRevisionOn( $dbw, $nullRevision );
@@ -919,24 +992,33 @@ class LocalFile extends File {
$descTitle->invalidateCache();
$descTitle->purgeSquid();
} else {
- // New file; create the description page.
- // There's already a log entry, so don't make a second RC entry
+ # New file; create the description page.
+ # 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 doEdit.
$article->doEdit( $pageText, $comment, EDIT_NEW | EDIT_SUPPRESS_RC );
}
- # Hooks, hooks, the magic of hooks...
- wfRunHooks( 'FileUpload', array( $this ) );
-
# Commit the transaction now, in case something goes wrong later
# The most important thing is that files don't get lost, especially archives
$dbw->commit();
+ # 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();
+
+ # Hooks, hooks, the magic of hooks...
+ wfRunHooks( 'FileUpload', array( $this, $reupload, $descTitle->exists() ) );
+
# Invalidate cache for all pages using this file
$update = new HTMLCacheUpdate( $this->getTitle(), 'imagelinks' );
$update->doUpdate();
+
# Invalidate cache for all pages that redirects on this page
$redirs = $this->getTitle()->getRedirectsHere();
- foreach( $redirs as $redir ) {
+
+ foreach ( $redirs as $redir ) {
$update = new HTMLCacheUpdate( $redir, 'imagelinks' );
$update->doUpdate();
}
@@ -946,15 +1028,14 @@ class LocalFile extends File {
/**
* Move or copy a file to its public location. If a file exists at the
- * destination, move it to an archive. Returns the archive name on success
- * or an empty string if it was a new file, and a wikitext-formatted
- * WikiError object on failure.
+ * destination, move it to an archive. 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 string $sourcePath Local filesystem path to the source image
- * @param integer $flags A bitwise combination of:
+ * @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
* @return FileRepoStatus object. On success, the value member contains the
@@ -962,17 +1043,21 @@ class LocalFile extends File {
*/
function publish( $srcPath, $flags = 0 ) {
$this->lock();
+
$dstRel = $this->getRel();
- $archiveName = gmdate( 'YmdHis' ) . '!'. $this->getName();
+ $archiveName = gmdate( 'YmdHis' ) . '!' . $this->getName();
$archiveRel = 'archive/' . $this->getHashPath() . $archiveName;
$flags = $flags & File::DELETE_SOURCE ? LocalRepo::DELETE_SOURCE : 0;
$status = $this->repo->publish( $srcPath, $dstRel, $archiveRel, $flags );
+
if ( $status->value == 'new' ) {
$status->value = '';
} else {
$status->value = $archiveName;
}
+
$this->unlock();
+
return $status;
}
@@ -996,12 +1081,14 @@ class LocalFile extends File {
function move( $target ) {
wfDebugLog( 'imagemove', "Got request to move {$this->name} to " . $target->getText() );
$this->lock();
+
$batch = new LocalFileMoveBatch( $this, $target );
$batch->addCurrent();
$batch->addOlds();
$status = $batch->execute();
wfDebugLog( 'imagemove', "Finished moving {$this->name}" );
+
$this->purgeEverything();
$this->unlock();
@@ -1014,7 +1101,7 @@ class LocalFile extends File {
// Purge the new image
$this->purgeEverything();
}
-
+
return $status;
}
@@ -1032,6 +1119,7 @@ class LocalFile extends File {
*/
function delete( $reason, $suppress = false ) {
$this->lock();
+
$batch = new LocalFileDeleteBatch( $this, $reason, $suppress );
$batch->addCurrent();
@@ -1040,7 +1128,7 @@ class LocalFile extends File {
$result = $dbw->select( 'oldimage',
array( 'oi_archive_name' ),
array( 'oi_name' => $this->getName() ) );
- while ( $row = $dbw->fetchObject( $result ) ) {
+ foreach ( $result as $row ) {
$batch->addOld( $row->oi_archive_name );
}
$status = $batch->execute();
@@ -1053,6 +1141,7 @@ class LocalFile extends File {
}
$this->unlock();
+
return $status;
}
@@ -1064,21 +1153,26 @@ class LocalFile extends File {
*
* Cache purging is done; logging is caller's responsibility.
*
- * @param $reason
- * @param $suppress
+ * @param $archiveName String
+ * @param $reason String
+ * @param $suppress Boolean
* @throws MWException or FSException on database or file store failure
* @return FileRepoStatus object.
*/
- function deleteOld( $archiveName, $reason, $suppress=false ) {
+ function deleteOld( $archiveName, $reason, $suppress = false ) {
$this->lock();
+
$batch = new LocalFileDeleteBatch( $this, $reason, $suppress );
$batch->addOld( $archiveName );
$status = $batch->execute();
+
$this->unlock();
+
if ( $status->ok ) {
$this->purgeDescription();
$this->purgeHistory();
}
+
return $status;
}
@@ -1090,17 +1184,20 @@ class LocalFile extends File {
*
* @param $versions set of record ids of deleted items to restore,
* or empty to restore all revisions.
- * @param $unuppress
+ * @param $unsuppress Boolean
* @return FileRepoStatus
*/
function restore( $versions = array(), $unsuppress = false ) {
$batch = new LocalFileRestoreBatch( $this, $unsuppress );
+
if ( !$versions ) {
$batch->addAll();
} else {
$batch->addIds( $versions );
}
+
$status = $batch->execute();
+
if ( !$status->ok ) {
return $status;
}
@@ -1109,6 +1206,7 @@ class LocalFile extends File {
$cleanupStatus->successCount = 0;
$cleanupStatus->failCount = 0;
$status->merge( $cleanupStatus );
+
return $status;
}
@@ -1174,10 +1272,12 @@ class LocalFile extends File {
*/
function lock() {
$dbw = $this->repo->getMasterDB();
+
if ( !$this->locked ) {
$dbw->begin();
$this->locked++;
}
+
return $dbw->selectField( 'image', '1', array( 'img_name' => $this->getName() ), __METHOD__ );
}
@@ -1205,7 +1305,7 @@ class LocalFile extends File {
}
} // LocalFile class
-#------------------------------------------------------------------------------
+# ------------------------------------------------------------------------------
/**
* Helper class for file deletion
@@ -1240,25 +1340,33 @@ class LocalFileDeleteBatch {
unset( $oldRels['.'] );
$deleteCurrent = true;
}
+
return array( $oldRels, $deleteCurrent );
}
/*protected*/ function getHashes() {
$hashes = array();
list( $oldRels, $deleteCurrent ) = $this->getOldRels();
+
if ( $deleteCurrent ) {
$hashes['.'] = $this->file->getSha1();
}
+
if ( count( $oldRels ) ) {
$dbw = $this->file->repo->getMasterDB();
- $res = $dbw->select( 'oldimage', array( 'oi_archive_name', 'oi_sha1' ),
- 'oi_archive_name IN(' . $dbw->makeList( array_keys( $oldRels ) ) . ')',
- __METHOD__ );
- while ( $row = $dbw->fetchObject( $res ) ) {
+ $res = $dbw->select(
+ 'oldimage',
+ array( 'oi_archive_name', 'oi_sha1' ),
+ 'oi_archive_name IN (' . $dbw->makeList( array_keys( $oldRels ) ) . ')',
+ __METHOD__
+ );
+
+ foreach ( $res as $row ) {
if ( rtrim( $row->oi_sha1, "\0" ) === '' ) {
// Get the hash from the file
$oldUrl = $this->file->getArchiveVirtualUrl( $row->oi_archive_name );
$props = $this->file->repo->getFileProps( $oldUrl );
+
if ( $props['fileExists'] ) {
// Upgrade the oldimage row
$dbw->update( 'oldimage',
@@ -1274,10 +1382,13 @@ class LocalFileDeleteBatch {
}
}
}
+
$missing = array_diff_key( $this->srcRels, $hashes );
+
foreach ( $missing as $name => $rel ) {
$this->status->error( 'filedelete-old-unregistered', $name );
}
+
foreach ( $hashes as $name => $hash ) {
if ( !$hash ) {
$this->status->error( 'filedelete-missing', $this->srcRels[$name] );
@@ -1290,6 +1401,7 @@ class LocalFileDeleteBatch {
function doDBInserts() {
global $wgUser;
+
$dbw = $this->file->repo->getMasterDB();
$encTimestamp = $dbw->addQuotes( $dbw->timestamp() );
$encUserId = $dbw->addQuotes( $wgUser->getId() );
@@ -1377,6 +1489,7 @@ class LocalFileDeleteBatch {
function doDBDeletes() {
$dbw = $this->file->repo->getMasterDB();
list( $oldRels, $deleteCurrent ) = $this->getOldRels();
+
if ( count( $oldRels ) ) {
$dbw->delete( 'oldimage',
array(
@@ -1384,6 +1497,7 @@ class LocalFileDeleteBatch {
'oi_archive_name' => array_keys( $oldRels )
), __METHOD__ );
}
+
if ( $deleteCurrent ) {
$dbw->delete( 'image', array( 'img_name' => $this->file->getName() ), __METHOD__ );
}
@@ -1401,14 +1515,16 @@ class LocalFileDeleteBatch {
$privateFiles = array();
list( $oldRels, $deleteCurrent ) = $this->getOldRels();
$dbw = $this->file->repo->getMasterDB();
- if( !empty( $oldRels ) ) {
+
+ if ( !empty( $oldRels ) ) {
$res = $dbw->select( 'oldimage',
array( 'oi_archive_name' ),
array( 'oi_name' => $this->file->getName(),
- 'oi_archive_name IN (' . $dbw->makeList( array_keys($oldRels) ) . ')',
- $dbw->bitAnd('oi_deleted', File::DELETED_FILE) => File::DELETED_FILE ),
+ 'oi_archive_name IN (' . $dbw->makeList( array_keys( $oldRels ) ) . ')',
+ $dbw->bitAnd( 'oi_deleted', File::DELETED_FILE ) => File::DELETED_FILE ),
__METHOD__ );
- while( $row = $dbw->fetchObject( $res ) ) {
+
+ foreach ( $res as $row ) {
$privateFiles[$row->oi_archive_name] = 1;
}
}
@@ -1417,6 +1533,7 @@ class LocalFileDeleteBatch {
$this->deletionBatch = array();
$ext = $this->file->getExtension();
$dotExt = $ext === '' ? '' : ".$ext";
+
foreach ( $this->srcRels as $name => $srcRel ) {
// Skip files that have no hash (missing source).
// Keep private files where they are.
@@ -1441,6 +1558,7 @@ class LocalFileDeleteBatch {
// Execute the file deletion batch
$status = $this->file->repo->deleteBatch( $this->deletionBatch );
+
if ( !$status->isGood() ) {
$this->status->merge( $status );
}
@@ -1457,6 +1575,7 @@ class LocalFileDeleteBatch {
// Purge squid
if ( $wgUseSquid ) {
$urls = array();
+
foreach ( $this->srcRels as $srcRel ) {
$urlRel = str_replace( '%2F', '/', rawurlencode( $srcRel ) );
$urls[] = $this->file->repo->getZoneUrl( 'public' ) . '/' . $urlRel;
@@ -1470,6 +1589,7 @@ class LocalFileDeleteBatch {
// Commit and return
$this->file->unlock();
wfProfileOut( __METHOD__ );
+
return $this->status;
}
@@ -1478,19 +1598,25 @@ class LocalFileDeleteBatch {
*/
function removeNonexistentFiles( $batch ) {
$files = $newBatch = array();
- foreach( $batch as $batchItem ) {
+
+ foreach ( $batch as $batchItem ) {
list( $src, $dest ) = $batchItem;
$files[$src] = $this->file->repo->getVirtualUrl( 'public' ) . '/' . rawurlencode( $src );
}
+
$result = $this->file->repo->fileExistsBatch( $files, FSRepo::FILES_ONLY );
- foreach( $batch as $batchItem )
- if( $result[$batchItem[0]] )
+
+ foreach ( $batch as $batchItem ) {
+ if ( $result[$batchItem[0]] ) {
$newBatch[] = $batchItem;
+ }
+ }
+
return $newBatch;
}
}
-#------------------------------------------------------------------------------
+# ------------------------------------------------------------------------------
/**
* Helper class for file undeletion
@@ -1536,6 +1662,7 @@ class LocalFileRestoreBatch {
*/
function execute() {
global $wgLang;
+
if ( !$this->all && !$this->ids ) {
// Do nothing
return $this->file->repo->newGood();
@@ -1548,7 +1675,8 @@ class LocalFileRestoreBatch {
// 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 ) {
+
+ if ( !$this->all ) {
$conditions[] = 'fa_id IN (' . $dbw->makeList( $this->ids ) . ')';
}
@@ -1565,7 +1693,8 @@ class LocalFileRestoreBatch {
$deleteIds = array();
$first = true;
$archiveNames = array();
- while( $row = $dbw->fetchObject( $result ) ) {
+
+ foreach ( $result as $row ) {
$idsPresent[] = $row->fa_id;
if ( $row->fa_name != $this->file->getName() ) {
@@ -1573,6 +1702,7 @@ class LocalFileRestoreBatch {
$status->failCount++;
continue;
}
+
if ( $row->fa_storage_key == '' ) {
// Revision was missing pre-deletion
$status->error( 'undelete-bad-store-key', $wgLang->timeanddate( $row->fa_timestamp ) );
@@ -1584,12 +1714,13 @@ class LocalFileRestoreBatch {
$deletedUrl = $this->file->repo->getVirtualUrl() . '/deleted/' . $deletedRel;
$sha1 = substr( $row->fa_storage_key, 0, strcspn( $row->fa_storage_key, '.' ) );
+
# Fix leading zero
if ( strlen( $sha1 ) == 32 && $sha1[0] == '0' ) {
$sha1 = substr( $sha1, 1 );
}
- if( is_null( $row->fa_major_mime ) || $row->fa_major_mime == 'unknown'
+ 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 ) ) {
@@ -1624,23 +1755,27 @@ class LocalFileRestoreBatch {
'img_timestamp' => $row->fa_timestamp,
'img_sha1' => $sha1
);
+
// The live (current) version cannot be hidden!
- if( !$this->unsuppress && $row->fa_deleted ) {
+ if ( !$this->unsuppress && $row->fa_deleted ) {
$storeBatch[] = array( $deletedUrl, 'public', $destRel );
$this->cleanupBatch[] = $row->fa_storage_key;
}
} else {
$archiveName = $row->fa_archive_name;
- if( $archiveName == '' ) {
+
+ if ( $archiveName == '' ) {
// This was originally a current version; we
// have to devise a new archive name for it.
// Format is <timestamp of archiving>!<name>
$timestamp = wfTimestamp( TS_UNIX, $row->fa_deleted_timestamp );
+
do {
$archiveName = wfTimestamp( TS_MW, $timestamp ) . '!' . $row->fa_name;
$timestamp++;
} while ( isset( $archiveNames[$archiveName] ) );
}
+
$archiveNames[$archiveName] = true;
$destRel = $this->file->getArchiveRel( $archiveName );
$insertBatch[] = array(
@@ -1663,19 +1798,23 @@ class LocalFileRestoreBatch {
}
$deleteIds[] = $row->fa_id;
- if( !$this->unsuppress && $row->fa_deleted & File::DELETED_FILE ) {
+
+ if ( !$this->unsuppress && $row->fa_deleted & File::DELETED_FILE ) {
// private files can stay where they are
$status->successCount++;
} else {
$storeBatch[] = array( $deletedUrl, 'public', $destRel );
$this->cleanupBatch[] = $row->fa_storage_key;
}
+
$first = false;
}
+
unset( $result );
// Add a warning to the status object for missing IDs
$missingIds = array_diff( $this->ids, $idsPresent );
+
foreach ( $missingIds as $id ) {
$status->error( 'undelete-missing-filearchive', $id );
}
@@ -1692,6 +1831,7 @@ class LocalFileRestoreBatch {
// Store batch returned a critical error -- this usually means nothing was stored
// Stop now and return an error
$this->file->unlock();
+
return $status;
}
@@ -1704,9 +1844,11 @@ class LocalFileRestoreBatch {
if ( $insertCurrent ) {
$dbw->insert( 'image', $insertCurrent, __METHOD__ );
}
+
if ( $insertBatch ) {
$dbw->insert( 'oldimage', $insertBatch, __METHOD__ );
}
+
if ( $deleteIds ) {
$dbw->delete( 'filearchive',
array( 'fa_id IN (' . $dbw->makeList( $deleteIds ) . ')' ),
@@ -1714,8 +1856,8 @@ class LocalFileRestoreBatch {
}
// If store batch is empty (all files are missing), deletion is to be considered successful
- if( $status->successCount > 0 || !$storeBatch ) {
- if( !$exists ) {
+ if ( $status->successCount > 0 || !$storeBatch ) {
+ if ( !$exists ) {
wfDebug( __METHOD__ . " restored {$status->successCount} items, creating a new current\n" );
// Update site_stats
@@ -1729,7 +1871,9 @@ class LocalFileRestoreBatch {
$this->file->purgeHistory();
}
}
+
$this->file->unlock();
+
return $status;
}
@@ -1738,12 +1882,17 @@ class LocalFileRestoreBatch {
*/
function removeNonexistentFiles( $triplets ) {
$files = $filteredTriplets = array();
- foreach( $triplets as $file )
+ foreach ( $triplets as $file )
$files[$file[0]] = $file[0];
+
$result = $this->file->repo->fileExistsBatch( $files, FSRepo::FILES_ONLY );
- foreach( $triplets as $file )
- if( $result[$file[0]] )
+
+ foreach ( $triplets as $file ) {
+ if ( $result[$file[0]] ) {
$filteredTriplets[] = $file;
+ }
+ }
+
return $filteredTriplets;
}
@@ -1753,15 +1902,20 @@ class LocalFileRestoreBatch {
function removeNonexistentFromCleanup( $batch ) {
$files = $newBatch = array();
$repo = $this->file->repo;
- foreach( $batch as $file ) {
+
+ foreach ( $batch as $file ) {
$files[$file] = $repo->getVirtualUrl( 'deleted' ) . '/' .
rawurlencode( $repo->getDeletedHashPath( $file ) . $file );
}
$result = $repo->fileExistsBatch( $files, FSRepo::FILES_ONLY );
- foreach( $batch as $file )
- if( $result[$file] )
+
+ foreach ( $batch as $file ) {
+ if ( $result[$file] ) {
$newBatch[] = $file;
+ }
+ }
+
return $newBatch;
}
@@ -1773,13 +1927,16 @@ class LocalFileRestoreBatch {
if ( !$this->cleanupBatch ) {
return $this->file->repo->newGood();
}
+
$this->cleanupBatch = $this->removeNonexistentFromCleanup( $this->cleanupBatch );
+
$status = $this->file->repo->cleanupDeletedBatch( $this->cleanupBatch );
+
return $status;
}
}
-#------------------------------------------------------------------------------
+# ------------------------------------------------------------------------------
/**
* Helper class for file movement
@@ -1820,29 +1977,35 @@ class LocalFileMoveBatch {
array( 'oi_name' => $this->oldName ),
__METHOD__
);
- while( $row = $this->db->fetchObject( $result ) ) {
+
+ foreach ( $result as $row ) {
$oldName = $row->oi_archive_name;
$bits = explode( '!', $oldName, 2 );
- if( count( $bits ) != 2 ) {
+
+ if ( count( $bits ) != 2 ) {
wfDebug( "Invalid old file name: $oldName \n" );
continue;
}
+
list( $timestamp, $filename ) = $bits;
- if( $this->oldName != $filename ) {
+
+ if ( $this->oldName != $filename ) {
wfDebug( "Invalid old file name: $oldName \n" );
continue;
}
+
$this->oldCount++;
+
// Do we want to add those to oldCount?
- if( $row->oi_deleted & File::DELETED_FILE ) {
+ if ( $row->oi_deleted & File::DELETED_FILE ) {
continue;
}
+
$this->olds[] = array(
"{$archiveBase}/{$this->oldHash}{$oldName}",
"{$archiveBase}/{$this->newHash}{$timestamp}!{$this->newName}"
);
}
- $this->db->freeResult( $result );
}
/**
@@ -1858,19 +2021,23 @@ class LocalFileMoveBatch {
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() ) {
+
+ if ( !$statusMove->isOk() ) {
wfDebugLog( 'imagemove', "Error in moving files: " . $statusMove->getWikiText() );
$this->db->rollback();
}
$status->merge( $statusDb );
$status->merge( $statusMove );
+
return $status;
}
/**
- * Do the database updates and return a new WikiError indicating how many
- * rows where updated.
+ * Do the database updates and return a new FileRepoStatus indicating how
+ * many rows where updated.
+ *
+ * @return FileRepoStatus
*/
function doDBUpdates() {
$repo = $this->file->repo;
@@ -1878,13 +2045,14 @@ class LocalFileMoveBatch {
$dbw = $this->db;
// Update current image
- $dbw->update(
+ $dbw->update(
'image',
array( 'img_name' => $this->newName ),
array( 'img_name' => $this->oldName ),
__METHOD__
);
- if( $dbw->affectedRows() ) {
+
+ if ( $dbw->affectedRows() ) {
$status->successCount++;
} else {
$status->failCount++;
@@ -1895,11 +2063,12 @@ class LocalFileMoveBatch {
'oldimage',
array(
'oi_name' => $this->newName,
- 'oi_archive_name = ' . $dbw->strreplace( 'oi_archive_name', $dbw->addQuotes($this->oldName), $dbw->addQuotes($this->newName) ),
+ 'oi_archive_name = ' . $dbw->strreplace( 'oi_archive_name', $dbw->addQuotes( $this->oldName ), $dbw->addQuotes( $this->newName ) ),
),
array( 'oi_name' => $this->oldName ),
__METHOD__
);
+
$affected = $dbw->affectedRows();
$total = $this->oldCount;
$status->successCount += $affected;
@@ -1910,34 +2079,42 @@ class LocalFileMoveBatch {
/**
* Generate triplets for FSRepo::storeBatch().
- */
+ */
function getMoveTriplets() {
$moves = array_merge( array( $this->cur ), $this->olds );
$triplets = array(); // The format is: (srcUrl, destZone, destUrl)
- foreach( $moves as $move ) {
+
+ foreach ( $moves as $move ) {
// $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->name}: {$srcUrl} :: public :: {$move[1]}" );
}
+
return $triplets;
}
/**
* Removes non-existent files from move batch.
- */
+ */
function removeNonexistentFiles( $triplets ) {
$files = array();
- foreach( $triplets as $file )
+
+ foreach ( $triplets as $file ) {
$files[$file[0]] = $file[0];
+ }
+
$result = $this->file->repo->fileExistsBatch( $files, FSRepo::FILES_ONLY );
$filteredTriplets = array();
- foreach( $triplets as $file )
- if( $result[$file[0]] ) {
+
+ foreach ( $triplets as $file ) {
+ if ( $result[$file[0]] ) {
$filteredTriplets[] = $file;
} else {
wfDebugLog( 'imagemove', "File {$file[0]} does not exist" );
}
+ }
+
return $filteredTriplets;
}
}
diff --git a/includes/filerepo/LocalRepo.php b/includes/filerepo/LocalRepo.php
index 6c4d21a2..02883c53 100644
--- a/includes/filerepo/LocalRepo.php
+++ b/includes/filerepo/LocalRepo.php
@@ -1,12 +1,22 @@
<?php
/**
+ * Local repository that stores files in the local filesystem and registers them
+ * in the wiki's own database.
+ *
+ * @file
+ * @ingroup FileRepo
+ */
+
+/**
* A repository that stores files in the local filesystem and registers them
* in the wiki's own database. This is the most commonly used repository class.
* @ingroup FileRepo
*/
class LocalRepo extends FSRepo {
var $fileFactory = array( 'LocalFile', 'newFromTitle' );
+ var $fileFactoryKey = array( 'LocalFile', 'newFromKey' );
var $oldFileFactory = array( 'OldLocalFile', 'newFromTitle' );
+ var $oldFileFactoryKey = array( 'OldLocalFile', 'newFromKey' );
var $fileFromRowFactory = array( 'LocalFile', 'newFromRow' );
var $oldFileFromRowFactory = array( 'OldLocalFile', 'newFromRow' );
@@ -71,7 +81,7 @@ class LocalRepo extends FSRepo {
/**
* Checks if there is a redirect named as $title
*
- * @param Title $title Title of image
+ * @param $title Title of file
*/
function checkRedirect( $title ) {
global $wgMemc;
@@ -156,9 +166,11 @@ class LocalRepo extends FSRepo {
);
$result = array();
- while ( $row = $res->fetchObject() )
+ foreach ( $res as $row ) {
$result[] = $this->newFileFromRow( $row );
+ }
$res->free();
+
return $result;
}
@@ -189,8 +201,8 @@ class LocalRepo extends FSRepo {
/**
* Invalidates image redirect cache related to that image
*
- * @param Title $title Title of image
- */
+ * @param $title Title of page
+ */
function invalidateImageRedirect( $title ) {
global $wgMemc;
$memcKey = $this->getSharedCacheKey( 'image_redirect', md5( $title->getDBkey() ) );
diff --git a/includes/filerepo/NullRepo.php b/includes/filerepo/NullRepo.php
index 2bc61bde..d5a1ee03 100644
--- a/includes/filerepo/NullRepo.php
+++ b/includes/filerepo/NullRepo.php
@@ -1,4 +1,10 @@
<?php
+/**
+ * File repository with no files.
+ *
+ * @file
+ * @ingroup FileRepo
+ */
/**
* File repository with no files, for performance testing
diff --git a/includes/filerepo/OldLocalFile.php b/includes/filerepo/OldLocalFile.php
index 35f3f9f2..9efe998f 100644
--- a/includes/filerepo/OldLocalFile.php
+++ b/includes/filerepo/OldLocalFile.php
@@ -1,4 +1,10 @@
<?php
+/**
+ * Old file in the in the oldimage table
+ *
+ * @file
+ * @ingroup FileRepo
+ */
/**
* Class to represent a file in the oldimage table
@@ -30,14 +36,12 @@ class OldLocalFile extends LocalFile {
}
static function newFromKey( $sha1, $repo, $timestamp = false ) {
- # Polymorphic function name to distinguish foreign and local fetches
- $fname = get_class( $this ) . '::' . __FUNCTION__;
-
$conds = array( 'oi_sha1' => $sha1 );
if( $timestamp ) {
$conds['oi_timestamp'] = $timestamp;
}
- $row = $dbr->selectRow( 'oldimage', $this->getCacheFields( 'oi_' ), $conds, $fname );
+ $dbr = $repo->getSlaveDB();
+ $row = $dbr->selectRow( 'oldimage', self::selectFields(), $conds, __METHOD__ );
if( $row ) {
return self::newFromRow( $row, $repo );
} else {
@@ -70,10 +74,10 @@ class OldLocalFile extends LocalFile {
}
/**
- * @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
+ * @param $title Title
+ * @param $repo FileRepo
+ * @param $time String: timestamp or null to load by archive name
+ * @param $archiveName String: archive name or null to load by timestamp
*/
function __construct( $title, $repo, $time, $archiveName ) {
parent::__construct( $title, $repo );
@@ -135,7 +139,7 @@ class OldLocalFile extends LocalFile {
}
function getUrlRel() {
- return 'archive/' . $this->getHashPath() . urlencode( $this->getArchiveName() );
+ return 'archive/' . $this->getHashPath() . rawurlencode( $this->getArchiveName() );
}
function upgradeRow() {
@@ -172,8 +176,8 @@ class OldLocalFile extends LocalFile {
}
/**
- * int $field one of DELETED_* bitfield constants
- * for file or revision rows
+ * @param $field Integer: one of DELETED_* bitfield constants
+ * for file or revision rows
* @return bool
*/
function isDeleted( $field ) {
@@ -193,7 +197,8 @@ 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 int $field
+ *
+ * @param $field Integer
* @return bool
*/
function userCan( $field ) {
diff --git a/includes/filerepo/RepoGroup.php b/includes/filerepo/RepoGroup.php
index 1465400c..b9996941 100644
--- a/includes/filerepo/RepoGroup.php
+++ b/includes/filerepo/RepoGroup.php
@@ -1,14 +1,19 @@
<?php
/**
- * @defgroup FileRepo FileRepo
+ * Prioritized list of file repositories
*
* @file
* @ingroup FileRepo
*/
/**
- * @ingroup FileRepo
+ * @defgroup FileRepo FileRepo
+ */
+
+/**
* Prioritized list of file repositories
+ *
+ * @ingroup FileRepo
*/
class RepoGroup {
var $localRepo, $foreignRepos, $reposInitialised = false;
@@ -48,7 +53,9 @@ class RepoGroup {
/**
* Construct a group of file repositories.
- * @param array $data Array of repository info arrays.
+ *
+ * @param $localInfo Associative array for local repo's info
+ * @param $foreignInfo Array of repository info arrays.
* Each info array is an associative array with the 'class' member
* giving the class name. The entire array is passed to the repository
* constructor as the first parameter.
@@ -62,7 +69,8 @@ class RepoGroup {
/**
* Search repositories for an image.
* You can also use wfFindFile() to do this.
- * @param mixed $title Title object or string
+ *
+ * @param $title Mixed: Title object or string
* @param $options Associative array of options:
* time: requested time for an archived image, or false for the
* current version. An image object will be returned which was
@@ -92,10 +100,15 @@ class RepoGroup {
}
}
+ if ( $title->getNamespace() != NS_MEDIA && $title->getNamespace() != NS_FILE ) {
+ throw new MWException( __METHOD__ . ' recieved an Title object with incorrect namespace' );
+ }
+
# Check the cache
if ( empty( $options['ignoreRedirect'] )
&& empty( $options['private'] )
- && empty( $options['bypassCache'] ) )
+ && empty( $options['bypassCache'] )
+ && $title->getNamespace() == NS_FILE )
{
$useCache = true;
$time = isset( $options['time'] ) ? $options['time'] : '';
@@ -224,7 +237,7 @@ class RepoGroup {
if ( !$this->reposInitialised ) {
$this->initialiseRepos();
}
- foreach ( $this->foreignRepos as $key => $repo ) {
+ foreach ( $this->foreignRepos as $repo ) {
if ( $repo->name == $name)
return $repo;
}
@@ -243,8 +256,8 @@ class RepoGroup {
* Call a function for each foreign repo, with the repo object as the
* first parameter.
*
- * @param $callback callback The function to call
- * @param $params array Optional additional parameters to pass to the function
+ * @param $callback Callback: the function to call
+ * @param $params Array: optional additional parameters to pass to the function
*/
function forEachForeignRepo( $callback, $params = array() ) {
foreach( $this->foreignRepos as $repo ) {
@@ -258,7 +271,7 @@ class RepoGroup {
/**
* Does the installation have any foreign repos set up?
- * @return bool
+ * @return Boolean
*/
function hasForeignRepos() {
return (bool)$this->foreignRepos;
diff --git a/includes/filerepo/UnregisteredLocalFile.php b/includes/filerepo/UnregisteredLocalFile.php
index 6f63cb0b..990a218c 100644
--- a/includes/filerepo/UnregisteredLocalFile.php
+++ b/includes/filerepo/UnregisteredLocalFile.php
@@ -1,4 +1,10 @@
<?php
+/**
+ * File without associated database record
+ *
+ * @file
+ * @ingroup FileRepo
+ */
/**
* A file object referring to either a standalone local file, or a file in a
@@ -94,7 +100,7 @@ class UnregisteredLocalFile extends File {
function getURL() {
if ( $this->repo ) {
- return $this->repo->getZoneUrl( 'public' ) . '/' . $this->repo->getHashPath( $this->name ) . urlencode( $this->name );
+ return $this->repo->getZoneUrl( 'public' ) . '/' . $this->repo->getHashPath( $this->name ) . rawurlencode( $this->name );
} else {
return false;
}