summaryrefslogtreecommitdiff
path: root/includes/filerepo/file/File.php
diff options
context:
space:
mode:
Diffstat (limited to 'includes/filerepo/file/File.php')
-rw-r--r--includes/filerepo/file/File.php219
1 files changed, 168 insertions, 51 deletions
diff --git a/includes/filerepo/file/File.php b/includes/filerepo/file/File.php
index f74fb678..557609d4 100644
--- a/includes/filerepo/file/File.php
+++ b/includes/filerepo/file/File.php
@@ -9,6 +9,21 @@
/**
* Base code for files.
*
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ * http://www.gnu.org/copyleft/gpl.html
+ *
* @file
* @ingroup FileAbstraction
*/
@@ -48,6 +63,14 @@ abstract class File {
const DELETE_SOURCE = 1;
+ // Audience options for File::getDescription()
+ const FOR_PUBLIC = 1;
+ const FOR_THIS_USER = 2;
+ const RAW = 3;
+
+ // Options for File::thumbName()
+ const THUMB_FULL_NAME = 1;
+
/**
* Some member variables can be lazy-initialised using __get(). The
* initialisation function for these variables is always a function named
@@ -68,19 +91,19 @@ abstract class File {
*/
/**
- * @var FileRepo|false
+ * @var FileRepo|bool
*/
var $repo;
/**
- * @var Title|false
+ * @var Title
*/
var $title;
var $lastError, $redirected, $redirectedTitle;
/**
- * @var FSFile|false
+ * @var FSFile|bool False if undefined
*/
protected $fsFile;
@@ -94,6 +117,8 @@ abstract class File {
*/
protected $url, $extension, $name, $path, $hashPath, $pageCount, $transformScript;
+ protected $redirectTitle;
+
/**
* @var bool
*/
@@ -111,8 +136,8 @@ abstract class File {
* may return false or throw exceptions if they are not set.
* Most subclasses will want to call assertRepoDefined() here.
*
- * @param $title Title|string|false
- * @param $repo FileRepo|false
+ * @param $title Title|string|bool
+ * @param $repo FileRepo|bool
*/
function __construct( $title, $repo ) {
if ( $title !== false ) { // subclasses may not use MW titles
@@ -127,7 +152,8 @@ abstract class File {
* valid Title object with namespace NS_FILE or null
*
* @param $title Title|string
- * @param $exception string|false Use 'exception' to throw an error on bad titles
+ * @param $exception string|bool Use 'exception' to throw an error on bad titles
+ * @throws MWException
* @return Title|null
*/
static function normalizeTitle( $title, $exception = false ) {
@@ -223,6 +249,18 @@ abstract class File {
}
/**
+ * Callback for usort() to do file sorts by name
+ *
+ * @param $a File
+ * @param $b File
+ *
+ * @return Integer: result of name comparison
+ */
+ public static function compare( File $a, File $b ) {
+ return strcmp( $a->getName(), $b->getName() );
+ }
+
+ /**
* Return the name of this file
*
* @return string
@@ -252,7 +290,7 @@ abstract class File {
/**
* Return the associated title object
*
- * @return Title|false
+ * @return Title
*/
public function getTitle() {
return $this->title;
@@ -319,17 +357,17 @@ abstract class File {
}
/**
- * Return the storage path to the file. Note that this does
- * not mean that a file actually exists under that location.
- *
- * This path depends on whether directory hashing is active or not,
- * i.e. whether the files are all found in the same directory,
- * or in hashed paths like /images/3/3c.
- *
- * Most callers don't check the return value, but ForeignAPIFile::getPath
- * returns false.
+ * Return the storage path to the file. Note that this does
+ * not mean that a file actually exists under that location.
+ *
+ * This path depends on whether directory hashing is active or not,
+ * i.e. whether the files are all found in the same directory,
+ * or in hashed paths like /images/3/3c.
+ *
+ * Most callers don't check the return value, but ForeignAPIFile::getPath
+ * returns false.
*
- * @return string|false
+ * @return string|bool ForeignAPIFile::getPath can return false
*/
public function getPath() {
if ( !isset( $this->path ) ) {
@@ -344,7 +382,7 @@ abstract class File {
* Returns false on failure. Callers must not alter the file.
* Temporary files are cleared automatically.
*
- * @return string|false
+ * @return string|bool False on failure
*/
public function getLocalRefPath() {
$this->assertRepoDefined();
@@ -383,7 +421,7 @@ abstract class File {
*
* @param $page int
*
- * @return false|number
+ * @return bool|number False on failure
*/
public function getHeight( $page = 1 ) {
return false;
@@ -430,9 +468,43 @@ abstract class File {
}
/**
+ * Will the thumbnail be animated if one would expect it to be.
+ *
+ * Currently used to add a warning to the image description page
+ *
+ * @return bool false if the main image is both animated
+ * and the thumbnail is not. In all other cases must return
+ * true. If image is not renderable whatsoever, should
+ * return true.
+ */
+ public function canAnimateThumbIfAppropriate() {
+ $handler = $this->getHandler();
+ if ( !$handler ) {
+ // We cannot handle image whatsoever, thus
+ // one would not expect it to be animated
+ // so true.
+ return true;
+ } else {
+ if ( $this->allowInlineDisplay()
+ && $handler->isAnimatedImage( $this )
+ && !$handler->canAnimateThumbnail( $this )
+ ) {
+ // Image is animated, but thumbnail isn't.
+ // This is unexpected to the user.
+ return false;
+ } else {
+ // Image is not animated, so one would
+ // not expect thumb to be
+ return true;
+ }
+ }
+ }
+
+ /**
* Get handler-specific metadata
* Overridden by LocalFile, UnregisteredLocalFile
* STUB
+ * @return bool
*/
public function getMetadata() {
return false;
@@ -462,6 +534,7 @@ abstract class File {
* Return the bit depth of the file
* Overridden by LocalFile
* STUB
+ * @return int
*/
public function getBitDepth() {
return 0;
@@ -471,6 +544,7 @@ abstract class File {
* Return the size of the image file, in bytes
* Overridden by LocalFile, UnregisteredLocalFile
* STUB
+ * @return bool
*/
public function getSize() {
return false;
@@ -492,6 +566,7 @@ abstract class File {
* Use the value returned by this function with the MEDIATYPE_xxx constants.
* Overridden by LocalFile,
* STUB
+ * @return string
*/
function getMediaType() {
return MEDIATYPE_UNKNOWN;
@@ -518,6 +593,7 @@ abstract class File {
/**
* Accessor for __get()
+ * @return bool
*/
protected function getCanRender() {
return $this->canRender();
@@ -686,15 +762,19 @@ abstract class File {
}
/**
- * Return the file name of a thumbnail with the specified parameters
+ * Return the file name of a thumbnail with the specified parameters.
+ * Use File::THUMB_FULL_NAME to always get a name like "<params>-<source>".
+ * Otherwise, the format may be "<params>-<source>" or "<params>-thumbnail.<ext>".
*
* @param $params Array: handler-specific parameters
- * @private -ish
- *
+ * @param $flags integer Bitfield that supports THUMB_* constants
* @return string
*/
- function thumbName( $params ) {
- return $this->generateThumbName( $this->getName(), $params );
+ public function thumbName( $params, $flags = 0 ) {
+ $name = ( $this->repo && !( $flags & self::THUMB_FULL_NAME ) )
+ ? $this->repo->nameForThumb( $this->getName() )
+ : $this->getName();
+ return $this->generateThumbName( $name, $params );
}
/**
@@ -705,7 +785,7 @@ abstract class File {
*
* @return string
*/
- function generateThumbName( $name, $params ) {
+ public function generateThumbName( $name, $params ) {
if ( !$this->getHandler() ) {
return null;
}
@@ -750,7 +830,7 @@ abstract class File {
/**
* Return either a MediaTransformError or placeholder thumbnail (if $wgIgnoreImageErrors)
- *
+ *
* @param $thumbPath string Thumbnail storage path
* @param $thumbUrl string Thumbnail URL
* @param $params Array
@@ -764,7 +844,7 @@ abstract class File {
return $this->handler->getTransform( $this, $thumbPath, $thumbUrl, $params );
} else {
return new MediaTransformError( 'thumbnail_error',
- $params['width'], 0, wfMsg( 'thumbnail-dest-create' ) );
+ $params['width'], 0, wfMessage( 'thumbnail-dest-create' )->text() );
}
}
@@ -774,7 +854,7 @@ abstract class File {
* @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
+ * @return MediaTransformOutput|bool False on failure
*/
function transform( $params, $flags = 0 ) {
global $wgUseSquid, $wgIgnoreImageErrors, $wgThumbnailEpoch;
@@ -837,6 +917,13 @@ abstract class File {
}
}
+ // If the backend is ready-only, don't keep generating thumbnails
+ // only to return transformation errors, just return the error now.
+ if ( $this->repo->getReadOnlyReason() !== false ) {
+ $thumb = $this->transformErrorOutput( $thumbPath, $thumbUrl, $params, $flags );
+ break;
+ }
+
// Create a temp FS file with the same extension and the thumbnail
$thumbExt = FileBackend::extensionFromPath( $thumbPath );
$tmpFile = TempFSFile::factory( 'transform_', $thumbExt );
@@ -847,7 +934,9 @@ abstract class File {
$tmpThumbPath = $tmpFile->getPath(); // path of 0-byte temp file
// Actually render the thumbnail...
+ wfProfileIn( __METHOD__ . '-doTransform' );
$thumb = $this->handler->doTransform( $this, $tmpThumbPath, $thumbUrl, $params );
+ wfProfileOut( __METHOD__ . '-doTransform' );
$tmpFile->bind( $thumb ); // keep alive with $thumb
if ( !$thumb ) { // bad params?
@@ -859,19 +948,16 @@ abstract class File {
$thumb = $this->handler->getTransform( $this, $tmpThumbPath, $thumbUrl, $params );
}
} elseif ( $this->repo && $thumb->hasFile() && !$thumb->fileIsSource() ) {
- $backend = $this->repo->getBackend();
- // Copy the thumbnail from the file system into storage. This avoids using
- // FileRepo::store(); getThumbPath() uses a different zone in some subclasses.
- $backend->prepare( array( 'dir' => dirname( $thumbPath ) ) );
- $status = $backend->store(
- array( 'src' => $tmpThumbPath, 'dst' => $thumbPath, 'overwrite' => 1 ),
- array( 'force' => 1, 'nonLocking' => 1, 'allowStale' => 1 )
- );
+ // Copy the thumbnail from the file system into storage...
+ $disposition = $this->getThumbDisposition( $thumbName );
+ $status = $this->repo->quickImport( $tmpThumbPath, $thumbPath, $disposition );
if ( $status->isOK() ) {
$thumb->setStoragePath( $thumbPath );
} else {
$thumb = $this->transformErrorOutput( $thumbPath, $thumbUrl, $params, $flags );
}
+ // Give extensions a chance to do something with this thumbnail...
+ wfRunHooks( 'FileTransformed', array( $this, $thumb, $tmpThumbPath, $thumbPath ) );
}
// Purge. Useful in the event of Core -> Squid connection failure or squid
@@ -889,6 +975,19 @@ abstract class File {
}
/**
+ * @param $thumbName string Thumbnail name
+ * @return string Content-Disposition header value
+ */
+ function getThumbDisposition( $thumbName ) {
+ $fileName = $this->name; // file name to suggest
+ $thumbExt = FileBackend::extensionFromPath( $thumbName );
+ if ( $thumbExt != '' && $thumbExt !== $this->getExtension() ) {
+ $fileName .= ".$thumbExt";
+ }
+ return FileBackend::makeContentDisposition( 'inline', $fileName );
+ }
+
+ /**
* Hook into transform() to allow migration of thumbnail files
* STUB
* Overridden by LocalFile
@@ -920,7 +1019,8 @@ abstract class File {
$path = '/common/images/icons/' . $icon;
$filepath = $wgStyleDirectory . $path;
if ( file_exists( $filepath ) ) { // always FS
- return new ThumbnailImage( $this, $wgStylePath . $path, 120, 120 );
+ $params = array( 'width' => 120, 'height' => 120 );
+ return new ThumbnailImage( $this, $wgStylePath . $path, false, $params );
}
}
return null;
@@ -938,6 +1038,7 @@ abstract class File {
* Get all thumbnail names previously generated for this file
* STUB
* Overridden by LocalFile
+ * @return array
*/
function getThumbnails() {
return array();
@@ -987,13 +1088,13 @@ abstract class File {
*
* STUB
* @param $limit integer Limit of rows to return
- * @param $start timestamp Only revisions older than $start will be returned
- * @param $end timestamp Only revisions newer than $end will be returned
+ * @param $start string timestamp Only revisions older than $start will be returned
+ * @param $end string timestamp Only revisions newer than $end will be returned
* @param $inc bool Include the endpoints of the time range
*
* @return array
*/
- function getHistory($limit = null, $start = null, $end = null, $inc=true) {
+ function getHistory( $limit = null, $start = null, $end = null, $inc=true ) {
return array();
}
@@ -1004,6 +1105,7 @@ abstract class File {
*
* STUB
* Overridden in LocalFile
+ * @return bool
*/
public function nextHistoryLine() {
return false;
@@ -1185,7 +1287,7 @@ abstract class File {
*
* @param $suffix bool|string if not false, the name of a thumbnail file
*
- * @return path
+ * @return string path
*/
function getThumbUrl( $suffix = false ) {
$this->assertRepoDefined();
@@ -1251,7 +1353,7 @@ abstract class File {
*/
function isHashed() {
$this->assertRepoDefined();
- return $this->repo->isHashed();
+ return (bool)$this->repo->getHashLevels();
}
/**
@@ -1329,7 +1431,7 @@ abstract class File {
/**
* Returns the repository
*
- * @return FileRepo|false
+ * @return FileRepo|bool
*/
function getRepo() {
return $this->repo;
@@ -1360,6 +1462,7 @@ abstract class File {
/**
* Return the deletion bitfield
* STUB
+ * @return int
*/
function getVisibility() {
return 0;
@@ -1401,7 +1504,7 @@ abstract class File {
*
* @param $reason String
* @param $suppress Boolean: hide content from sysops?
- * @return true on success, false on some kind of failure
+ * @return bool on success, false on some kind of failure
* STUB
* Overridden by LocalFile
*/
@@ -1418,7 +1521,7 @@ abstract class File {
* @param $versions array set of record ids of deleted items to restore,
* or empty to restore all revisions.
* @param $unsuppress bool remove restrictions on content upon restoration?
- * @return int|false the number of file revisions restored if successful,
+ * @return int|bool the number of file revisions restored if successful,
* or false on failure
* STUB
* Overridden by LocalFile
@@ -1442,7 +1545,7 @@ abstract class File {
* Returns the number of pages of a multipage document, or false for
* documents which aren't multipage documents
*
- * @return false|int
+ * @return bool|int
*/
function pageCount() {
if ( !isset( $this->pageCount ) ) {
@@ -1536,19 +1639,25 @@ abstract class File {
}
/**
- * Get discription of file revision
+ * Get description of file revision
* STUB
*
+ * @param $audience Integer: one of:
+ * File::FOR_PUBLIC to be displayed to all users
+ * File::FOR_THIS_USER to be displayed to the given user
+ * File::RAW get the description regardless of permissions
+ * @param $user User object to check for, only if FOR_THIS_USER is passed
+ * to the $audience parameter
* @return string
*/
- function getDescription() {
+ function getDescription( $audience = self::FOR_PUBLIC, User $user = null ) {
return null;
}
/**
* Get the 14-character timestamp of the file upload
*
- * @return string|false TS_MW timestamp or false on failure
+ * @return string|bool TS_MW timestamp or false on failure
*/
function getTimestamp() {
$this->assertRepoDefined();
@@ -1566,7 +1675,7 @@ abstract class File {
}
/**
- * Get the deletion archive key, <sha1>.<ext>
+ * Get the deletion archive key, "<sha1>.<ext>"
*
* @return string
*/
@@ -1618,7 +1727,7 @@ abstract class File {
*
* @param $path string
*
- * @return false|string False on failure
+ * @return bool|string False on failure
*/
static function sha1Base36( $path ) {
wfDeprecated( __METHOD__, '1.19' );
@@ -1698,6 +1807,14 @@ abstract class File {
}
/**
+ * Check if this file object is small and can be cached
+ * @return boolean
+ */
+ public function isCacheable() {
+ return true;
+ }
+
+ /**
* Assert that $this->repo is set to a valid FileRepo instance
* @throws MWException
*/