* Added support for configuration of an arbitrary number of commons-style file repositories.
* Split Image.php into filerepo/File.php and filerepo/LocalFile.php
* Renamed Image::getImagePath() to File::getPath()
* Added initial support for timestamp-based file fetching (OldLocalFile), to be expanded upon by aaron.
* Changed the interface for Image/File object creation: use wfFindFile() or wfLocalFile() depending on semantics
* ImageGallery::add() now accepts a title object as the first parameter
* Moved file handling operations on upload from SpecialUpload to File
* Removed path-related functions from ImageFunctions.php. Removed static path accessors from File.
* Added a Content-Disposition header to thumb.php output
* Improved thumb.php error handling
* Updated the unit test suite to kind of partially work with modern computers. RunTests.php doesn't work just yet. Fixed an actual regression that the test suite detected -- moved some defines to Defines.php where they will be loaded consistently.
* Introducing 'frameless' keyword to [[Image:]] syntax which respects the
user preferences for image width like 'thumb' but without a frame.
* (bug 7960) Link to "what links here" for each "what links here" entry
+* Added support for configuration of an arbitrary number of commons-style
+ file repositories.
+* Added a Content-Disposition header to thumb.php output
+* Improved thumb.php error handling
+
== Bugfixes since 1.10 ==
<?php
-require_once( dirname(__FILE__).'/includes/ProfilerStub.php' );
+#require_once( './includes/ProfilerStub.php' );
/**
* To use a profiler, delete the line above and add something like this:
*
- * require_once( dirname(__FILE__).'/includes/Profiler.php' );
+ * require_once( './includes/Profiler.php' );
* $wgProfiler = new Profiler;
*
* Or for a sampling profiler:
* if ( !mt_rand( 0, 100 ) ) {
- * require_once( dirname(__FILE__).'/includes/Profiler.php' );
+ * require_once( './includes/Profiler.php' );
* $wgProfiler = new Profiler;
* } else {
- * require_once( dirname(__FILE__).'/includes/ProfilerStub.php' );
+ * require_once( './includes/ProfilerStub.php' );
* }
*
* Configuration of the profiler output can be done in LocalSettings.php
*/
+require_once( dirname(__FILE__).'/includes/Profiler.php' );
+$wgProfiler = new Profiler;
?>
'HTMLCacheUpdateJob' => 'includes/HTMLCacheUpdate.php',
'EnotifNotifyJob' => 'includes/JobQueue.php',
'Http' => 'includes/HttpFunctions.php',
- 'Image' => 'includes/Image.php',
- 'ArchivedFile' => 'includes/Image.php',
'IP' => 'includes/IP.php',
'ThumbnailImage' => 'includes/Image.php',
'ImageGallery' => 'includes/ImageGallery.php',
'memcached' => 'includes/memcached-client.php',
'EmaillingJob' => 'includes/JobQueue.php',
+ # filerepo
+ 'ArchivedFile' => 'includes/filerepo/ArchivedFile.php',
+ 'File' => 'includes/filerepo/File.php',
+ 'ForeignDBFile' => 'includes/filerepo/ForeignDBFile.php',
+ 'ForeignDBRepo' => 'includes/filerepo/ForeignDBRepo.php',
+ 'FSRepo' => 'includes/filerepo/FSRepo.php',
+ 'Image' => 'includes/filerepo/LocalFile.php',
+ 'LocalFile' => 'includes/filerepo/LocalFile.php',
+ 'LocalRepo' => 'includes/filerepo/LocalRepo.php',
+ 'OldLocalFile' => 'includes/filerepo/OldLocalFile.php',
+ 'RepoGroup' => 'includes/filerepo/RepoGroup.php',
+ 'UnregisteredLocalFile' => 'includes/filerepo/UnregisteredLocalFile.php',
+
# Media
'BitmapHandler' => 'includes/media/Bitmap.php',
'BmpHandler' => 'includes/media/BMP.php',
/**
* Add a page in the image namespace
*/
- function addImage( $title, $sortkey, $pageLength, $isRedirect = false ) {
+ function addImage( Title $title, $sortkey, $pageLength, $isRedirect = false ) {
if ( $this->showGallery ) {
$image = new Image( $title );
if( $this->flip ) {
if( $title->getNamespace() == NS_CATEGORY ) {
$this->addSubcategory( $title, $x->cl_sortkey, $x->page_len );
- } elseif( $title->getNamespace() == NS_IMAGE ) {
+ } elseif( $this->showGallery && $title->getNamespace() == NS_IMAGE ) {
$this->addImage( $title, $x->cl_sortkey, $x->page_len, $x->page_is_redirect );
} else {
$this->addPage( $title, $x->cl_sortkey, $x->page_len, $x->page_is_redirect );
$wgFileStore['deleted']['url'] = null; // Private
$wgFileStore['deleted']['hash'] = 3; // 3-level subdirectory split
+/**#@+
+ * File repository structures
+ *
+ * $wgLocalFileRepo is a single repository structure, and $wgForeignFileRepo is
+ * a an array of such structures. Each repository structure is an associative
+ * array of properties configuring the repository.
+ *
+ * Properties required for all repos:
+ * class The class name for the repository. May come from the core or an extension.
+ * The core repository classes are LocalRepo, ForeignDBRepo, FSRepo.
+ *
+ * name A unique name for the repository.
+ *
+ * For all core repos:
+ * url Base public URL
+ * hashLevels The number of directory levels for hash-based division of files
+ * thumbScriptUrl The URL for thumb.php (optional, not recommended)
+ * transformVia404 Whether to skip media file transformation on parse and rely on a 404
+ * handler instead.
+ *
+ * These settings describe a foreign MediaWiki installation. They are optional, and will be ignored
+ * for local repositories:
+ * descBaseUrl URL of image description pages, e.g. http://en.wikipedia.org/wiki/Image:
+ * scriptDirUrl URL of the MediaWiki installation, equivalent to $wgScriptPath, e.g.
+ * http://en.wikipedia.org/w
+ *
+ * articleUrl Equivalent to $wgArticlePath, e.g. http://en.wikipedia.org/wiki/$1
+ * fetchDescription Fetch the text of the remote file description page. Equivalent to
+ * $wgFetchCommonsDescriptions.
+ *
+ * ForeignDBRepo:
+ * dbType, dbServer, dbUser, dbPassword, dbName, dbFlags
+ * equivalent to the corresponding member of $wgDBservers
+ * tablePrefix Table prefix, the foreign wiki's $wgDBprefix
+ * hasSharedCache True if the wiki's shared cache is accessible via the local $wgMemc
+ *
+ * The default is to initialise these arrays from the MW<1.11 backwards compatible settings:
+ * $wgUploadPath, $wgThumbnailScriptPath, $wgSharedUploadDirectory, etc.
+ */
+$wgLocalFileRepo = false;
+$wgForeignFileRepos = array();
+/**#@-*/
+
/**
* Allowed title characters -- regex character class
* Don't change this unless you know what you're doing
* no file of the given name is found in the local repository (for [[Image:..]],
* [[Media:..]] links). Thumbnails will also be looked for and generated in this
* directory.
+ *
+ * Note that these configuration settings can now be defined on a per-
+ * repository basis for an arbitrary number of file repositories, using the
+ * $wgForeignFileRepos variable.
*/
$wgUseSharedUploads = false;
/** Full path on the web server where shared uploads can be found */
define( 'LIST_NAMES', 3);
define( 'LIST_OR', 4);
+/**
+ * Unicode and normalisation related
+ */
+define( 'UNICODE_HANGUL_FIRST', 0xac00 );
+define( 'UNICODE_HANGUL_LAST', 0xd7a3 );
+
+define( 'UNICODE_HANGUL_LBASE', 0x1100 );
+define( 'UNICODE_HANGUL_VBASE', 0x1161 );
+define( 'UNICODE_HANGUL_TBASE', 0x11a7 );
+
+define( 'UNICODE_HANGUL_LCOUNT', 19 );
+define( 'UNICODE_HANGUL_VCOUNT', 21 );
+define( 'UNICODE_HANGUL_TCOUNT', 28 );
+define( 'UNICODE_HANGUL_NCOUNT', UNICODE_HANGUL_VCOUNT * UNICODE_HANGUL_TCOUNT );
+
+define( 'UNICODE_HANGUL_LEND', UNICODE_HANGUL_LBASE + UNICODE_HANGUL_LCOUNT - 1 );
+define( 'UNICODE_HANGUL_VEND', UNICODE_HANGUL_VBASE + UNICODE_HANGUL_VCOUNT - 1 );
+define( 'UNICODE_HANGUL_TEND', UNICODE_HANGUL_TBASE + UNICODE_HANGUL_TCOUNT - 1 );
+
+define( 'UNICODE_SURROGATE_FIRST', 0xd800 );
+define( 'UNICODE_SURROGATE_LAST', 0xdfff );
+define( 'UNICODE_MAX', 0x10ffff );
+define( 'UNICODE_REPLACEMENT', 0xfffd );
+
+
+define( 'UTF8_HANGUL_FIRST', "\xea\xb0\x80" /*codepointToUtf8( UNICODE_HANGUL_FIRST )*/ );
+define( 'UTF8_HANGUL_LAST', "\xed\x9e\xa3" /*codepointToUtf8( UNICODE_HANGUL_LAST )*/ );
+
+define( 'UTF8_HANGUL_LBASE', "\xe1\x84\x80" /*codepointToUtf8( UNICODE_HANGUL_LBASE )*/ );
+define( 'UTF8_HANGUL_VBASE', "\xe1\x85\xa1" /*codepointToUtf8( UNICODE_HANGUL_VBASE )*/ );
+define( 'UTF8_HANGUL_TBASE', "\xe1\x86\xa7" /*codepointToUtf8( UNICODE_HANGUL_TBASE )*/ );
+
+define( 'UTF8_HANGUL_LEND', "\xe1\x84\x92" /*codepointToUtf8( UNICODE_HANGUL_LEND )*/ );
+define( 'UTF8_HANGUL_VEND', "\xe1\x85\xb5" /*codepointToUtf8( UNICODE_HANGUL_VEND )*/ );
+define( 'UTF8_HANGUL_TEND', "\xe1\x87\x82" /*codepointToUtf8( UNICODE_HANGUL_TEND )*/ );
+
+define( 'UTF8_SURROGATE_FIRST', "\xed\xa0\x80" /*codepointToUtf8( UNICODE_SURROGATE_FIRST )*/ );
+define( 'UTF8_SURROGATE_LAST', "\xed\xbf\xbf" /*codepointToUtf8( UNICODE_SURROGATE_LAST )*/ );
+define( 'UTF8_MAX', "\xf4\x8f\xbf\xbf" /*codepointToUtf8( UNICODE_MAX )*/ );
+define( 'UTF8_REPLACEMENT', "\xef\xbf\xbd" /*codepointToUtf8( UNICODE_REPLACEMENT )*/ );
+#define( 'UTF8_REPLACEMENT', '!' );
+
+define( 'UTF8_OVERLONG_A', "\xc1\xbf" );
+define( 'UTF8_OVERLONG_B', "\xe0\x9f\xbf" );
+define( 'UTF8_OVERLONG_C', "\xf0\x8f\xbf\xbf" );
+
+# These two ranges are illegal
+define( 'UTF8_FDD0', "\xef\xb7\x90" /*codepointToUtf8( 0xfdd0 )*/ );
+define( 'UTF8_FDEF', "\xef\xb7\xaf" /*codepointToUtf8( 0xfdef )*/ );
+define( 'UTF8_FFFE', "\xef\xbf\xbe" /*codepointToUtf8( 0xfffe )*/ );
+define( 'UTF8_FFFF', "\xef\xbf\xbf" /*codepointToUtf8( 0xffff )*/ );
+
+define( 'UTF8_HEAD', false );
+define( 'UTF8_TAIL', true );
+
+
?>
$extension="wiki";
} elseif($this->mMode=="file") {
$type="Edit file";
- $image = new Image( $this->mTitle );
+ $image = wfLocalFile( $this->mTitle );
$img_url = $image->getURL();
if(strpos($img_url,"://")) {
$url = $img_url;
$ret = $wgLoadBalancer->getConnection( $db, true, $groups );
return $ret;
}
+
+/**
+ * Find a file.
+ * Shortcut for RepoGroup::singleton()->findFile()
+ * @param mixed $title Title object or string. May be interwiki.
+ * @param mixed $time Requested time for an archived image, or false for the
+ * current version. An image object will be returned which
+ * existed at or before the specified time.
+ * @return File, or false if the file does not exist
+ */
+function wfFindFile( $title, $time = false ) {
+ return RepoGroup::singleton()->findFile( $title, $time );
+}
+
+/**
+ * Get an object referring to a locally registered file.
+ * Returns a valid placeholder object if the file does not exist.
+ */
+function wfLocalFile( $title ) {
+ return RepoGroup::singleton()->getLocalRepo()->newFile( $title );
+}
+
?>
+++ /dev/null
-<?php
-/**
- */
-
-/**
- * NOTE FOR WINDOWS USERS:
- * To enable EXIF functions, add the folloing lines to the
- * "Windows extensions" section of php.ini:
- *
- * extension=extensions/php_mbstring.dll
- * extension=extensions/php_exif.dll
- */
-
-/**
- * Bump this number when serialized cache records may be incompatible.
- */
-define( 'MW_IMAGE_VERSION', 2 );
-
-/**
- * Class to represent an image
- *
- * Provides methods to retrieve paths (physical, logical, URL),
- * to generate thumbnails or for uploading.
- *
- * @addtogroup Media
- */
-class Image
-{
- const DELETED_FILE = 1;
- const DELETED_COMMENT = 2;
- const DELETED_USER = 4;
- const DELETED_RESTRICTED = 8;
- const RENDER_NOW = 1;
-
- /**#@+
- * @private
- */
- var $name, # name of the image (constructor)
- $imagePath, # Path of the image (loadFromXxx)
- $url, # Image URL (accessor)
- $title, # Title object for this image (constructor)
- $fileExists, # does the image file exist on disk? (loadFromXxx)
- $fromSharedDirectory, # load this image from $wgSharedUploadDirectory (loadFromXxx)
- $historyLine, # Number of line to return by nextHistoryLine() (constructor)
- $historyRes, # result of the query for the image's history (nextHistoryLine)
- $width, # \
- $height, # |
- $bits, # --- returned by getimagesize (loadFromXxx)
- $attr, # /
- $type, # MEDIATYPE_xxx (bitmap, drawing, audio...)
- $mime, # MIME type, determined by MimeMagic::guessMimeType
- $extension, # The file extension (constructor)
- $size, # Size in bytes (loadFromXxx)
- $metadata, # Metadata
- $dataLoaded, # Whether or not all this has been loaded from the database (loadFromXxx)
- $page, # Page to render when creating thumbnails
- $lastError; # Error string associated with a thumbnail display error
-
-
- /**#@-*/
-
- /**
- * Create an Image object from an image name
- *
- * @param string $name name of the image, used to create a title object using Title::makeTitleSafe
- * @return Image
- * @public
- */
- public static function newFromName( $name ) {
- $title = Title::makeTitleSafe( NS_IMAGE, $name );
- if ( is_object( $title ) ) {
- return new Image( $title );
- } else {
- return NULL;
- }
- }
-
- /**
- * Obsolete factory function, use constructor
- * @param Title $title
- * @return Image
- * @deprecated
- */
- function newFromTitle( $title ) {
- return new Image( $title );
- }
-
- /**
- * Constructor
- * @param Title $title
- * @return void
- */
- function Image( $title ) {
- if( !is_object( $title ) ) {
- throw new MWException( 'Image constructor given bogus title.' );
- }
- $this->title =& $title;
- $this->name = $title->getDBkey();
- $this->metadata = '';
-
- $n = strrpos( $this->name, '.' );
- $this->extension = Image::normalizeExtension( $n ?
- substr( $this->name, $n + 1 ) : '' );
- $this->historyLine = 0;
-
- $this->dataLoaded = false;
- }
-
- /**
- * Normalize a file extension to the common form, and ensure it's clean.
- * Extensions with non-alphanumeric characters will be discarded.
- *
- * @param string $ext (without the .)
- * @return string
- */
- static function normalizeExtension( $ext ) {
- $lower = strtolower( $ext );
- $squish = array(
- 'htm' => 'html',
- 'jpeg' => 'jpg',
- 'mpeg' => 'mpg',
- 'tiff' => 'tif' );
- if( isset( $squish[$lower] ) ) {
- return $squish[$lower];
- } elseif( preg_match( '/^[0-9a-z]+$/', $lower ) ) {
- return $lower;
- } else {
- return '';
- }
- }
-
- /**
- * Get the memcached keys
- * @return array[int]mixed Returns an array, first element is the local cache key, second is the shared cache key, if there is one
- */
- function getCacheKeys( ) {
- global $wgUseSharedUploads, $wgSharedUploadDBname, $wgCacheSharedUploads;
-
- $hashedName = md5($this->name);
- $keys = array( wfMemcKey( 'Image', $hashedName ) );
- if ( $wgUseSharedUploads && $wgSharedUploadDBname && $wgCacheSharedUploads ) {
- $keys[] = wfForeignMemcKey( $wgSharedUploadDBname, false, 'Image', $hashedName );
- }
- return $keys;
- }
-
- /**
- * Try to load image metadata from memcached. Returns true on success.
- */
- function loadFromCache() {
- global $wgUseSharedUploads, $wgMemc;
- wfProfileIn( __METHOD__ );
- $this->dataLoaded = false;
- $keys = $this->getCacheKeys();
- $cachedValues = $wgMemc->get( $keys[0] );
-
- // Check if the key existed and belongs to this version of MediaWiki
- if (!empty($cachedValues) && is_array($cachedValues)
- && isset($cachedValues['version']) && ( $cachedValues['version'] == MW_IMAGE_VERSION )
- && isset( $cachedValues['mime'] ) && isset( $cachedValues['metadata'] ) )
- {
- if ( $wgUseSharedUploads && $cachedValues['fromShared']) {
- # if this is shared file, we need to check if image
- # in shared repository has not changed
- if ( isset( $keys[1] ) ) {
- $commonsCachedValues = $wgMemc->get( $keys[1] );
- if (!empty($commonsCachedValues) && is_array($commonsCachedValues)
- && isset($commonsCachedValues['version'])
- && ( $commonsCachedValues['version'] == MW_IMAGE_VERSION )
- && isset($commonsCachedValues['mime'])) {
- wfDebug( "Pulling image metadata from shared repository cache\n" );
- $this->name = $commonsCachedValues['name'];
- $this->imagePath = $commonsCachedValues['imagePath'];
- $this->fileExists = $commonsCachedValues['fileExists'];
- $this->width = $commonsCachedValues['width'];
- $this->height = $commonsCachedValues['height'];
- $this->bits = $commonsCachedValues['bits'];
- $this->type = $commonsCachedValues['type'];
- $this->mime = $commonsCachedValues['mime'];
- $this->metadata = $commonsCachedValues['metadata'];
- $this->size = $commonsCachedValues['size'];
- $this->fromSharedDirectory = true;
- $this->dataLoaded = true;
- $this->imagePath = $this->getFullPath(true);
- }
- }
- } else {
- wfDebug( "Pulling image metadata from local cache\n" );
- $this->name = $cachedValues['name'];
- $this->imagePath = $cachedValues['imagePath'];
- $this->fileExists = $cachedValues['fileExists'];
- $this->width = $cachedValues['width'];
- $this->height = $cachedValues['height'];
- $this->bits = $cachedValues['bits'];
- $this->type = $cachedValues['type'];
- $this->mime = $cachedValues['mime'];
- $this->metadata = $cachedValues['metadata'];
- $this->size = $cachedValues['size'];
- $this->fromSharedDirectory = false;
- $this->dataLoaded = true;
- $this->imagePath = $this->getFullPath();
- }
- }
- if ( $this->dataLoaded ) {
- wfIncrStats( 'image_cache_hit' );
- } else {
- wfIncrStats( 'image_cache_miss' );
- }
-
- wfProfileOut( __METHOD__ );
- return $this->dataLoaded;
- }
-
- /**
- * Save the image metadata to memcached
- */
- function saveToCache() {
- global $wgMemc, $wgUseSharedUploads;
- $this->load();
- $keys = $this->getCacheKeys();
- // We can't cache negative metadata for non-existent files,
- // because if the file later appears in commons, the local
- // keys won't be purged.
- if ( $this->fileExists || !$wgUseSharedUploads ) {
- $cachedValues = array(
- 'version' => MW_IMAGE_VERSION,
- 'name' => $this->name,
- 'imagePath' => $this->imagePath,
- 'fileExists' => $this->fileExists,
- 'fromShared' => $this->fromSharedDirectory,
- 'width' => $this->width,
- 'height' => $this->height,
- 'bits' => $this->bits,
- 'type' => $this->type,
- 'mime' => $this->mime,
- 'metadata' => $this->metadata,
- 'size' => $this->size );
-
- $wgMemc->set( $keys[0], $cachedValues, 60 * 60 * 24 * 7 ); // A week
- } else {
- // However we should clear them, so they aren't leftover
- // if we've deleted the file.
- $wgMemc->delete( $keys[0] );
- }
- }
-
- /**
- * Load metadata from the file itself
- */
- function loadFromFile() {
- global $wgUseSharedUploads, $wgSharedUploadDirectory, $wgContLang;
- wfProfileIn( __METHOD__ );
- $this->imagePath = $this->getFullPath();
- $this->fileExists = file_exists( $this->imagePath );
- $this->fromSharedDirectory = false;
- $gis = array();
-
- if (!$this->fileExists) wfDebug(__METHOD__.': '.$this->imagePath." not found locally!\n");
-
- # If the file is not found, and a shared upload directory is used, look for it there.
- if (!$this->fileExists && $wgUseSharedUploads && $wgSharedUploadDirectory) {
- # In case we're on a wgCapitalLinks=false wiki, we
- # capitalize the first letter of the filename before
- # looking it up in the shared repository.
- $sharedImage = Image::newFromName( $wgContLang->ucfirst($this->name) );
- $this->fileExists = $sharedImage && file_exists( $sharedImage->getFullPath(true) );
- if ( $this->fileExists ) {
- $this->name = $sharedImage->name;
- $this->imagePath = $this->getFullPath(true);
- $this->fromSharedDirectory = true;
- }
- }
-
-
- if ( $this->fileExists ) {
- $magic=& MimeMagic::singleton();
-
- $this->mime = $magic->guessMimeType($this->imagePath,true);
- $this->type = $magic->getMediaType($this->imagePath,$this->mime);
- $handler = MediaHandler::getHandler( $this->mime );
-
- # Get size in bytes
- $this->size = filesize( $this->imagePath );
-
- # Height, width and metadata
- if ( $handler ) {
- $gis = $handler->getImageSize( $this, $this->imagePath );
- $this->metadata = $handler->getMetadata( $this, $this->imagePath );
- } else {
- $gis = false;
- $this->metadata = '';
- }
-
- wfDebug(__METHOD__.': '.$this->imagePath." loaded, ".$this->size." bytes, ".$this->mime.".\n");
- }
- else {
- $this->mime = NULL;
- $this->type = MEDIATYPE_UNKNOWN;
- $this->metadata = '';
- wfDebug(__METHOD__.': '.$this->imagePath." NOT FOUND!\n");
- }
-
- if( $gis ) {
- $this->width = $gis[0];
- $this->height = $gis[1];
- } else {
- $this->width = 0;
- $this->height = 0;
- }
-
- #NOTE: $gis[2] contains a code for the image type. This is no longer used.
-
- #NOTE: we have to set this flag early to avoid load() to be called
- # be some of the functions below. This may lead to recursion or other bad things!
- # as ther's only one thread of execution, this should be safe anyway.
- $this->dataLoaded = true;
-
- if ( isset( $gis['bits'] ) ) $this->bits = $gis['bits'];
- else $this->bits = 0;
-
- wfProfileOut( __METHOD__ );
- }
-
- /**
- * Load image metadata from the DB
- */
- function loadFromDB() {
- global $wgUseSharedUploads, $wgSharedUploadDBname, $wgSharedUploadDBprefix, $wgContLang;
- wfProfileIn( __METHOD__ );
-
- $dbr = wfGetDB( DB_SLAVE );
-
- $row = $dbr->selectRow( 'image',
- array( 'img_size', 'img_width', 'img_height', 'img_bits',
- 'img_media_type', 'img_major_mime', 'img_minor_mime', 'img_metadata' ),
- array( 'img_name' => $this->name ), __METHOD__ );
- if ( $row ) {
- $this->fromSharedDirectory = false;
- $this->fileExists = true;
- $this->loadFromRow( $row );
- $this->imagePath = $this->getFullPath();
- // Check for rows from a previous schema, quietly upgrade them
- $this->maybeUpgradeRow();
- } elseif ( $wgUseSharedUploads && $wgSharedUploadDBname ) {
- # In case we're on a wgCapitalLinks=false wiki, we
- # capitalize the first letter of the filename before
- # looking it up in the shared repository.
- $name = $wgContLang->ucfirst($this->name);
- $dbc = Image::getCommonsDB();
-
- $row = $dbc->selectRow( "`$wgSharedUploadDBname`.{$wgSharedUploadDBprefix}image",
- array(
- 'img_size', 'img_width', 'img_height', 'img_bits',
- 'img_media_type', 'img_major_mime', 'img_minor_mime', 'img_metadata' ),
- array( 'img_name' => $name ), __METHOD__ );
- if ( $row ) {
- $this->fromSharedDirectory = true;
- $this->fileExists = true;
- $this->imagePath = $this->getFullPath(true);
- $this->name = $name;
- $this->loadFromRow( $row );
-
- // Check for rows from a previous schema, quietly upgrade them
- $this->maybeUpgradeRow();
- }
- }
-
- if ( !$row ) {
- $this->size = 0;
- $this->width = 0;
- $this->height = 0;
- $this->bits = 0;
- $this->type = 0;
- $this->fileExists = false;
- $this->fromSharedDirectory = false;
- $this->metadata = '';
- $this->mime = false;
- }
-
- # Unconditionally set loaded=true, we don't want the accessors constantly rechecking
- $this->dataLoaded = true;
- wfProfileOut( __METHOD__ );
- }
-
- /*
- * Load image metadata from a DB result row
- */
- function loadFromRow( &$row ) {
- $this->size = $row->img_size;
- $this->width = $row->img_width;
- $this->height = $row->img_height;
- $this->bits = $row->img_bits;
- $this->type = $row->img_media_type;
-
- $major= $row->img_major_mime;
- $minor= $row->img_minor_mime;
-
- if (!$major) $this->mime = "unknown/unknown";
- else {
- if (!$minor) $minor= "unknown";
- $this->mime = $major.'/'.$minor;
- }
- $this->metadata = $row->img_metadata;
-
- $this->dataLoaded = true;
- }
-
- /**
- * Load image metadata from cache or DB, unless already loaded
- */
- function load() {
- global $wgSharedUploadDBname, $wgUseSharedUploads;
- if ( !$this->dataLoaded ) {
- if ( !$this->loadFromCache() ) {
- $this->loadFromDB();
- if ( !$wgSharedUploadDBname && $wgUseSharedUploads ) {
- $this->loadFromFile();
- } elseif ( $this->fileExists || !$wgUseSharedUploads ) {
- // We can do negative caching for local images, because the cache
- // will be purged on upload. But we can't do it when shared images
- // are enabled, since updates to that won't purge foreign caches.
- $this->saveToCache();
- }
- }
- $this->dataLoaded = true;
- }
- }
-
- /**
- * Upgrade a row if it needs it
- * @return void
- */
- function maybeUpgradeRow() {
- if ( is_null($this->type) || $this->mime == 'image/svg' ) {
- $this->upgradeRow();
- } else {
- $handler = $this->getHandler();
- if ( $handler && !$handler->isMetadataValid( $this, $this->metadata ) ) {
- $this->upgradeRow();
- }
- }
- }
-
- /**
- * Fix assorted version-related problems with the image row by reloading it from the file
- */
- function upgradeRow() {
- global $wgDBname, $wgSharedUploadDBname;
- wfProfileIn( __METHOD__ );
-
- $this->loadFromFile();
-
- if ( $this->fromSharedDirectory ) {
- if ( !$wgSharedUploadDBname ) {
- wfProfileOut( __METHOD__ );
- return;
- }
-
- // Write to the other DB using selectDB, not database selectors
- // This avoids breaking replication in MySQL
- $dbw = Image::getCommonsDB();
- } else {
- $dbw = wfGetDB( DB_MASTER );
- }
-
- list( $major, $minor ) = self::splitMime( $this->mime );
-
- wfDebug(__METHOD__.': upgrading '.$this->name." to the current schema\n");
-
- $dbw->update( 'image',
- array(
- 'img_width' => $this->width,
- 'img_height' => $this->height,
- 'img_bits' => $this->bits,
- 'img_media_type' => $this->type,
- 'img_major_mime' => $major,
- 'img_minor_mime' => $minor,
- 'img_metadata' => $this->metadata,
- ), array( 'img_name' => $this->name ), __METHOD__
- );
- if ( $this->fromSharedDirectory ) {
- $dbw->selectDB( $wgDBname );
- }
- wfProfileOut( __METHOD__ );
- }
-
- /**
- * Split an internet media type into its two components; if not
- * a two-part name, set the minor type to 'unknown'.
- *
- * @param string $mime "text/html" etc
- * @return array ("text", "html") etc
- */
- static function splitMime( $mime ) {
- if( strpos( $mime, '/' ) !== false ) {
- return explode( '/', $mime, 2 );
- } else {
- return array( $mime, 'unknown' );
- }
- }
-
- /**
- * Return the name of this image
- * @public
- */
- function getName() {
- return $this->name;
- }
-
- /**
- * Return the associated title object
- * @public
- */
- function getTitle() {
- return $this->title;
- }
-
- /**
- * Return the URL of the image file
- * @public
- */
- function getURL() {
- if ( !$this->url ) {
- $this->load();
- if($this->fileExists) {
- $this->url = Image::imageUrl( $this->name, $this->fromSharedDirectory );
- } else {
- $this->url = '';
- }
- }
- return $this->url;
- }
-
- function getViewURL() {
- if( $this->mustRender()) {
- if( $this->canRender() ) {
- return $this->createThumb( $this->getWidth() );
- }
- else {
- wfDebug('Image::getViewURL(): supposed to render '.$this->name.' ('.$this->mime."), but can't!\n");
- return $this->getURL(); #hm... return NULL?
- }
- } else {
- return $this->getURL();
- }
- }
-
- /**
- * Return the image path of the image in the
- * local file system as an absolute path
- * @public
- */
- function getImagePath() {
- $this->load();
- return $this->imagePath;
- }
-
- /**
- * @return mixed Return the width of the image; returns false on error.
- * @param int $page Page number to find the width of.
- * @public
- */
- function getWidth( $page = 1 ) {
- $this->load();
- if ( $this->isMultipage() ) {
- $dim = $this->getHandler()->getPageDimensions( $this, $page );
- if ( $dim ) {
- return $dim['width'];
- } else {
- return false;
- }
- } else {
- return $this->width;
- }
- }
-
- /**
- * @return mixed Return the height of the image; Returns false on error.
- * @param int $page Page number to find the height of.
- * @public
- */
- function getHeight( $page = 1 ) {
- $this->load();
- if ( $this->isMultipage() ) {
- $dim = $this->getHandler()->getPageDimensions( $this, $page );
- if ( $dim ) {
- return $dim['height'];
- } else {
- return false;
- }
- } else {
- return $this->height;
- }
- }
-
- /**
- * Get handler-specific metadata
- */
- function getMetadata() {
- $this->load();
- return $this->metadata;
- }
-
- /**
- * @return int the size of the image file, in bytes
- * @public
- */
- function getSize() {
- $this->load();
- return $this->size;
- }
-
- /**
- * @return string the mime type of the file.
- */
- function getMimeType() {
- $this->load();
- return $this->mime;
- }
-
- /**
- * Return the type of the media in the file.
- * Use the value returned by this function with the MEDIATYPE_xxx constants.
- */
- function getMediaType() {
- $this->load();
- return $this->type;
- }
-
- /**
- * Checks if the file can be presented to the browser as a bitmap.
- *
- * Currently, this checks if the file is an image format
- * that can be converted to a format
- * supported by all browsers (namely GIF, PNG and JPEG),
- * or if it is an SVG image and SVG conversion is enabled.
- *
- * @todo remember the result of this check.
- * @return boolean
- */
- function canRender() {
- $handler = $this->getHandler();
- return $handler && $handler->canRender();
- }
-
- /**
- * Return true if the file is of a type that can't be directly
- * rendered by typical browsers and needs to be re-rasterized.
- *
- * This returns true for everything but the bitmap types
- * supported by all browsers, i.e. JPEG; GIF and PNG. It will
- * also return true for any non-image formats.
- *
- * @return bool
- */
- function mustRender() {
- $handler = $this->getHandler();
- return $handler && $handler->mustRender();
- }
-
- /**
- * Determines if this media file may be shown inline on a page.
- *
- * This is currently synonymous to canRender(), but this could be
- * extended to also allow inline display of other media,
- * like flash animations or videos. If you do so, please keep in mind that
- * that could be a security risk.
- */
- function allowInlineDisplay() {
- return $this->canRender();
- }
-
- /**
- * Determines if this media file is in a format that is unlikely to
- * contain viruses or malicious content. It uses the global
- * $wgTrustedMediaFormats list to determine if the file is safe.
- *
- * This is used to show a warning on the description page of non-safe files.
- * It may also be used to disallow direct [[media:...]] links to such files.
- *
- * Note that this function will always return true if allowInlineDisplay()
- * or isTrustedFile() is true for this file.
- *
- * @return boolean
- */
- function isSafeFile() {
- if ($this->allowInlineDisplay()) return true;
- if ($this->isTrustedFile()) return true;
-
- global $wgTrustedMediaFormats;
-
- $type= $this->getMediaType();
- $mime= $this->getMimeType();
- #wfDebug("Image::isSafeFile: type= $type, mime= $mime\n");
-
- if (!$type || $type===MEDIATYPE_UNKNOWN) return false; #unknown type, not trusted
- if ( in_array( $type, $wgTrustedMediaFormats) ) return true;
-
- if ($mime==="unknown/unknown") return false; #unknown type, not trusted
- if ( in_array( $mime, $wgTrustedMediaFormats) ) return true;
-
- return false;
- }
-
- /**
- * Returns true if the file is flagged as trusted. Files flagged that way
- * can be linked to directly, even if that is not allowed for this type of
- * file normally.
- *
- * This is a dummy function right now and always returns false. It could be
- * implemented to extract a flag from the database. The trusted flag could be
- * set on upload, if the user has sufficient privileges, to bypass script-
- * and html-filters. It may even be coupled with cryptographics signatures
- * or such.
- * @return boolean
- */
- function isTrustedFile() {
- #this could be implemented to check a flag in the database,
- #look for signatures, etc
- return false;
- }
-
- /**
- * Return the escapeLocalURL of this image
- * @param string $query URL query string
- * @public
- */
- function getEscapeLocalURL( $query=false) {
- return $this->getTitle()->escapeLocalURL( $query );
- }
-
- /**
- * Return the escapeFullURL of this image
- * @public
- */
- function getEscapeFullURL() {
- $this->getTitle();
- return $this->title->escapeFullURL();
- }
-
- /**
- * Return the URL of an image, provided its name.
- *
- * @param string $name Name of the image, without the leading "Image:"
- * @param boolean $fromSharedDirectory Should this be in $wgSharedUploadPath?
- * @return string URL of $name image
- * @public
- */
- static function imageUrl( $name, $fromSharedDirectory = false ) {
- global $wgUploadPath,$wgUploadBaseUrl,$wgSharedUploadPath;
- if($fromSharedDirectory) {
- $base = '';
- $path = $wgSharedUploadPath;
- } else {
- $base = $wgUploadBaseUrl;
- $path = $wgUploadPath;
- }
- $url = "{$base}{$path}" . wfGetHashPath($name, $fromSharedDirectory) . "{$name}";
- return wfUrlencode( $url );
- }
-
- /**
- * Returns true if the image file exists on disk.
- * @return boolean Whether image file exist on disk.
- * @public
- */
- function exists() {
- $this->load();
- return $this->fileExists;
- }
-
- /**
- * @todo document
- * @param string $thumbName
- * @param string $subdir
- * @return string
- * @private
- */
- function thumbUrlFromName( $thumbName, $subdir = 'thumb' ) {
- global $wgUploadPath, $wgUploadBaseUrl, $wgSharedUploadPath;
- if($this->fromSharedDirectory) {
- $base = '';
- $path = $wgSharedUploadPath;
- } else {
- $base = $wgUploadBaseUrl;
- $path = $wgUploadPath;
- }
- if ( Image::isHashed( $this->fromSharedDirectory ) ) {
- $hashdir = wfGetHashPath($this->name, $this->fromSharedDirectory) .
- wfUrlencode( $this->name );
- } else {
- $hashdir = '';
- }
- $url = "{$base}{$path}/{$subdir}{$hashdir}/" . wfUrlencode( $thumbName );
- return $url;
- }
-
- /**
- * @deprecated Use $image->transform()->getUrl() or thumbUrlFromName()
- */
- function thumbUrl( $width, $subdir = 'thumb' ) {
- $name = $this->thumbName( array( 'width' => $width ) );
- if ( strval( $name ) !== '' ) {
- return array( false, $this->thumbUrlFromName( $name, $subdir ) );
- } else {
- return array( false, false );
- }
- }
-
- /**
- * @return mixed
- */
- function getTransformScript() {
- global $wgSharedThumbnailScriptPath, $wgThumbnailScriptPath;
- if ( $this->fromSharedDirectory ) {
- $script = $wgSharedThumbnailScriptPath;
- } else {
- $script = $wgThumbnailScriptPath;
- }
- if ( $script ) {
- return "$script?f=" . urlencode( $this->name );
- } else {
- return false;
- }
- }
-
- /**
- * Get a ThumbnailImage which is the same size as the source
- * @param mixed $page
- * @return MediaTransformOutput
- */
- function getUnscaledThumb( $page = false ) {
- if ( $page ) {
- $params = array(
- 'page' => $page,
- 'width' => $this->getWidth( $page )
- );
- } else {
- $params = array( 'width' => $this->getWidth() );
- }
- return $this->transform( $params );
- }
-
- /**
- * Return the file name of a thumbnail with the specified parameters
- *
- * @param array $params Handler-specific parameters
- * @return string file name of a thumbnail with the specified parameters
- * @private
- */
- function thumbName( $params ) {
- $handler = $this->getHandler();
- if ( !$handler ) {
- return null;
- }
- list( $thumbExt, /* $thumbMime */ ) = self::getThumbType( $this->extension, $this->mime );
- $thumbName = $handler->makeParamString( $params ) . '-' . $this->name;
- if ( $thumbExt != $this->extension ) {
- $thumbName .= ".$thumbExt";
- }
- return $thumbName;
- }
-
- /**
- * Create a thumbnail of the image having the specified width/height.
- * The thumbnail will not be created if the width is larger than the
- * image's width. Let the browser do the scaling in this case.
- * The thumbnail is stored on disk and is only computed if the thumbnail
- * file does not exist OR if it is older than the image.
- * Returns the URL.
- *
- * Keeps aspect ratio of original image. If both width and height are
- * 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)
- * @public
- */
- function createThumb( $width, $height = -1 ) {
- $params = array( 'width' => $width );
- if ( $height != -1 ) {
- $params['height'] = $height;
- }
- $thumb = $this->transform( $params );
- if( is_null( $thumb ) || $thumb->isError() ) return '';
- return $thumb->getUrl();
- }
-
- /**
- * As createThumb, but returns a ThumbnailImage object. This can
- * provide access to the actual file, the real size of the thumb,
- * and can produce a convenient <img> tag for you.
- *
- * For non-image formats, this may return a filetype-specific icon.
- *
- * @param integer $width maximum width of the generated thumbnail
- * @param integer $height maximum height of the image (optional)
- * @param boolean $render True to render the thumbnail if it doesn't exist,
- * false to just return the URL
- *
- * @return ThumbnailImage or null on failure
- * @public
- *
- * @deprecated use transform()
- */
- function getThumbnail( $width, $height=-1, $render = true ) {
- $params = array( 'width' => $width );
- if ( $height != -1 ) {
- $params['height'] = $height;
- }
- $flags = $render ? self::RENDER_NOW : 0;
- return $this->transform( $params, $flags );
- }
-
- /**
- * Transform a media file
- *
- * @param array[string]mixed $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
- */
- function transform( $params, $flags = 0 ) {
- global $wgGenerateThumbnailOnParse, $wgUseSquid, $wgIgnoreImageErrors;
-
- wfProfileIn( __METHOD__ );
- do {
- $handler = $this->getHandler();
- if ( !$handler || !$handler->canRender() ) {
- // not a bitmap or renderable image, don't try.
- $thumb = $this->iconThumb();
- break;
- }
-
- $script = $this->getTransformScript();
- if ( $script && !($flags & self::RENDER_NOW) ) {
- // Use a script to transform on client request
- $thumb = $handler->getScriptedTransform( $this, $script, $params );
- break;
- }
-
- $normalisedParams = $params;
- $handler->normaliseParams( $this, $normalisedParams );
- $thumbName = $this->thumbName( $normalisedParams );
- $thumbPath = wfImageThumbDir( $this->name, $this->fromSharedDirectory ) . "/$thumbName";
- $thumbUrl = $this->thumbUrlFromName( $thumbName );
-
-
- if ( !$wgGenerateThumbnailOnParse && !($flags & self::RENDER_NOW ) ) {
- $thumb = $handler->getTransform( $this, $thumbPath, $thumbUrl, $params );
- break;
- }
-
- wfDebug( "Doing stat for $thumbPath\n" );
- $this->migrateThumbFile( $thumbName );
- if ( file_exists( $thumbPath ) ) {
- $thumb = $handler->getTransform( $this, $thumbPath, $thumbUrl, $params );
- break;
- }
-
- $thumb = $handler->doTransform( $this, $thumbPath, $thumbUrl, $params );
-
- // Ignore errors if requested
- if ( !$thumb ) {
- $thumb = null;
- } elseif ( $thumb->isError() ) {
- $this->lastError = $thumb->toText();
- if ( $wgIgnoreImageErrors && !($flags & self::RENDER_NOW) ) {
- $thumb = $handler->getTransform( $this, $thumbPath, $thumbUrl, $params );
- }
- }
-
- if ( $wgUseSquid ) {
- wfPurgeSquidServers( array( $thumbUrl ) );
- }
- } while (false);
-
- wfProfileOut( __METHOD__ );
- return $thumb;
- }
-
- /**
- * Fix thumbnail files from 1.4 or before, with extreme prejudice
- * @param string $thumbName File name of thumbnail.
- * @return void
- */
- function migrateThumbFile( $thumbName ) {
- $thumbDir = wfImageThumbDir( $this->name, $this->fromSharedDirectory );
- $thumbPath = "$thumbDir/$thumbName";
- if ( is_dir( $thumbPath ) ) {
- // Directory where file should be
- // This happened occasionally due to broken migration code in 1.5
- // Rename to broken-*
- global $wgUploadDirectory;
- for ( $i = 0; $i < 100 ; $i++ ) {
- $broken = "$wgUploadDirectory/broken-$i-$thumbName";
- if ( !file_exists( $broken ) ) {
- rename( $thumbPath, $broken );
- break;
- }
- }
- // Doesn't exist anymore
- clearstatcache();
- }
- if ( is_file( $thumbDir ) ) {
- // File where directory should be
- unlink( $thumbDir );
- // Doesn't exist anymore
- clearstatcache();
- }
- }
-
- /**
- * Get a MediaHandler instance for this image
- */
- function getHandler() {
- return MediaHandler::getHandler( $this->getMimeType() );
- }
-
- /**
- * Get a ThumbnailImage representing a file type icon
- * @return ThumbnailImage
- */
- function iconThumb() {
- global $wgStylePath, $wgStyleDirectory;
-
- $icons = array( 'fileicon-' . $this->extension . '.png', 'fileicon.png' );
- foreach( $icons as $icon ) {
- $path = '/common/images/icons/' . $icon;
- $filepath = $wgStyleDirectory . $path;
- if( file_exists( $filepath ) ) {
- return new ThumbnailImage( $wgStylePath . $path, 120, 120 );
- }
- }
- return null;
- }
-
- /**
- * Get last thumbnailing error.
- * Largely obsolete.
- * @return mixed
- */
- function getLastError() {
- return $this->lastError;
- }
-
- /**
- * Get all thumbnail names previously generated for this image
- * @param boolean $shared
- * @return array[]string
- */
- function getThumbnails( $shared = false ) {
- if ( Image::isHashed( $shared ) ) {
- $this->load();
- $files = array();
- $dir = wfImageThumbDir( $this->name, $shared );
-
- if ( is_dir( $dir ) ) {
- $handle = opendir( $dir );
-
- if ( $handle ) {
- while ( false !== ( $file = readdir($handle) ) ) {
- if ( $file[0] != '.' ) {
- $files[] = $file;
- }
- }
- closedir( $handle );
- }
- }
- } else {
- $files = array();
- }
-
- return $files;
- }
-
- /**
- * Refresh metadata in memcached, but don't touch thumbnails or squid
- * @return void
- */
- function purgeMetadataCache() {
- clearstatcache();
- $this->loadFromFile();
- $this->saveToCache();
- }
-
- /**
- * Delete all previously generated thumbnails, refresh metadata in memcached and purge the squid
- * @param array $archiveFiles
- * @param boolean $shared
- * @return void
- */
- function purgeCache( $archiveFiles = array(), $shared = false ) {
- global $wgUseSquid;
-
- // Refresh metadata cache
- $this->purgeMetadataCache();
-
- // Delete thumbnails
- $files = $this->getThumbnails( $shared );
- $dir = wfImageThumbDir( $this->name, $shared );
- $urls = array();
- foreach ( $files as $file ) {
- # Check that the base image name is part of the thumb name
- # This is a basic sanity check to avoid erasing unrelated directories
- if ( strpos( $file, $this->name ) !== false ) {
- $url = $this->thumbUrlFromName( $file );
- $urls[] = $url;
- @unlink( "$dir/$file" );
- }
- }
-
- // Purge the squid
- if ( $wgUseSquid ) {
- $urls[] = $this->getURL();
- foreach ( $archiveFiles as $file ) {
- $urls[] = wfImageArchiveUrl( $file );
- }
- wfPurgeSquidServers( $urls );
- }
- }
-
- /**
- * Purge the image description page, but don't go after
- * pages using the image. Use when modifying file history
- * but not the current data.
- * @return void
- */
- function purgeDescription() {
- $page = Title::makeTitle( NS_IMAGE, $this->name );
- $page->invalidateCache();
- $page->purgeSquid();
- }
-
- /**
- * Purge metadata and all affected pages when the image is created,
- * deleted, or majorly updated.
- * @param array $urlArray A set of additional URLs may be passed to purge,
- * such as specific image files which have changed (param not used?)
- * @return void
- */
- function purgeEverything( $urlArr=array() ) {
- // Delete thumbnails and refresh image metadata cache
- $this->purgeCache();
- $this->purgeDescription();
-
- // Purge cache of all pages using this image
- $update = new HTMLCacheUpdate( $this->getTitle(), 'imagelinks' );
- $update->doUpdate();
- }
-
- /**
- * Return the image history of this image, line by line.
- * starts with current version, then old versions.
- * uses $this->historyLine to check which line to return:
- * 0 return line for current version
- * 1 query for old versions, return first one
- * 2, ... return next old version from above query
- *
- * @public
- * @return mixed false on no next history, object otherwise.
- */
- function nextHistoryLine() {
- $dbr = wfGetDB( DB_SLAVE );
-
- if ( $this->historyLine == 0 ) {// called for the first time, return line from cur
- $this->historyRes = $dbr->select( 'image',
- array(
- 'img_size',
- 'img_description',
- 'img_user','img_user_text',
- 'img_timestamp',
- 'img_width',
- 'img_height',
- "'' AS oi_archive_name"
- ),
- array( 'img_name' => $this->title->getDBkey() ),
- __METHOD__
- );
- if ( 0 == $dbr->numRows( $this->historyRes ) ) {
- return FALSE;
- }
- } else if ( $this->historyLine == 1 ) {
- $this->historyRes = $dbr->select( 'oldimage',
- array(
- 'oi_size AS img_size',
- 'oi_description AS img_description',
- 'oi_user AS img_user',
- 'oi_user_text AS img_user_text',
- 'oi_timestamp AS img_timestamp',
- 'oi_width as img_width',
- 'oi_height as img_height',
- 'oi_archive_name'
- ),
- array( 'oi_name' => $this->title->getDBkey() ),
- __METHOD__,
- array( 'ORDER BY' => 'oi_timestamp DESC' )
- );
- }
- $this->historyLine ++;
-
- return $dbr->fetchObject( $this->historyRes );
- }
-
- /**
- * Reset the history pointer to the first element of the history
- * @public
- * @return void
- */
- function resetHistory() {
- $this->historyLine = 0;
- }
-
- /**
- * Return the full filesystem 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 images are all found in the same directory,
- * or in hashed paths like /images/3/3c.
- *
- * @public
- * @param boolean $fromSharedDirectory Return the path to the file
- * in a shared repository (see $wgUseSharedRepository and related
- * options in DefaultSettings.php) instead of a local one.
- * @return string Full filesystem path to the file.
- */
- function getFullPath( $fromSharedRepository = false ) {
- global $wgUploadDirectory, $wgSharedUploadDirectory;
-
- $dir = $fromSharedRepository ? $wgSharedUploadDirectory :
- $wgUploadDirectory;
-
- // $wgSharedUploadDirectory may be false, if thumb.php is used
- if ( $dir ) {
- $fullpath = $dir . wfGetHashPath($this->name, $fromSharedRepository) . $this->name;
- } else {
- $fullpath = false;
- }
-
- return $fullpath;
- }
-
- /**
- * @param boolean $shared
- * @return bool
- */
- public static function isHashed( $shared ) {
- global $wgHashedUploadDirectory, $wgHashedSharedUploadDirectory;
- return $shared ? $wgHashedSharedUploadDirectory : $wgHashedUploadDirectory;
- }
-
- /**
- * Record an image upload in the upload log and the image table
- * @param string $oldver
- * @param string $desc
- * @param string $license
- * @param string $copyStatus
- * @param string $source
- * @param boolean $watch
- * @return boolean
- */
- function recordUpload( $oldver, $desc, $license = '', $copyStatus = '', $source = '', $watch = false ) {
- global $wgUser, $wgUseCopyrightUpload;
-
- $dbw = wfGetDB( DB_MASTER );
-
- // Delete thumbnails and refresh the metadata cache
- $this->purgeCache();
-
- // Fail now if the image isn't there
- if ( !$this->fileExists || $this->fromSharedDirectory ) {
- wfDebug( "Image::recordUpload: File ".$this->imagePath." went missing!\n" );
- return false;
- }
-
- if ( $wgUseCopyrightUpload ) {
- if ( $license != '' ) {
- $licensetxt = '== ' . wfMsgForContent( 'license' ) . " ==\n" . '{{' . $license . '}}' . "\n";
- }
- $textdesc = '== ' . wfMsg ( 'filedesc' ) . " ==\n" . $desc . "\n" .
- '== ' . wfMsgForContent ( 'filestatus' ) . " ==\n" . $copyStatus . "\n" .
- "$licensetxt" .
- '== ' . wfMsgForContent ( 'filesource' ) . " ==\n" . $source ;
- } else {
- if ( $license != '' ) {
- $filedesc = $desc == '' ? '' : '== ' . wfMsg ( 'filedesc' ) . " ==\n" . $desc . "\n";
- $textdesc = $filedesc .
- '== ' . wfMsgForContent ( 'license' ) . " ==\n" . '{{' . $license . '}}' . "\n";
- } else {
- $textdesc = $desc;
- }
- }
-
- $now = $dbw->timestamp();
-
- #split mime type
- if (strpos($this->mime,'/')!==false) {
- list($major,$minor)= explode('/',$this->mime,2);
- }
- else {
- $major= $this->mime;
- $minor= "unknown";
- }
-
- # Test to see if the row exists using INSERT IGNORE
- # This avoids race conditions by locking the row until the commit, and also
- # doesn't deadlock. SELECT FOR UPDATE causes a deadlock for every race condition.
- $dbw->insert( 'image',
- array(
- 'img_name' => $this->name,
- 'img_size'=> $this->size,
- 'img_width' => intval( $this->width ),
- 'img_height' => intval( $this->height ),
- 'img_bits' => $this->bits,
- 'img_media_type' => $this->type,
- 'img_major_mime' => $major,
- 'img_minor_mime' => $minor,
- 'img_timestamp' => $now,
- 'img_description' => $desc,
- 'img_user' => $wgUser->getID(),
- 'img_user_text' => $wgUser->getName(),
- 'img_metadata' => $this->metadata,
- ),
- __METHOD__,
- 'IGNORE'
- );
-
- if( $dbw->affectedRows() == 0 ) {
- # Collision, this is an update of an image
- # Insert previous contents into oldimage
- $dbw->insertSelect( 'oldimage', 'image',
- array(
- 'oi_name' => 'img_name',
- 'oi_archive_name' => $dbw->addQuotes( $oldver ),
- 'oi_size' => 'img_size',
- 'oi_width' => 'img_width',
- 'oi_height' => 'img_height',
- 'oi_bits' => 'img_bits',
- 'oi_timestamp' => 'img_timestamp',
- 'oi_description' => 'img_description',
- 'oi_user' => 'img_user',
- 'oi_user_text' => 'img_user_text',
- ), array( 'img_name' => $this->name ), __METHOD__
- );
-
- # Update the current image row
- $dbw->update( 'image',
- array( /* SET */
- 'img_size' => $this->size,
- 'img_width' => intval( $this->width ),
- 'img_height' => intval( $this->height ),
- 'img_bits' => $this->bits,
- 'img_media_type' => $this->type,
- 'img_major_mime' => $major,
- 'img_minor_mime' => $minor,
- 'img_timestamp' => $now,
- 'img_description' => $desc,
- 'img_user' => $wgUser->getID(),
- 'img_user_text' => $wgUser->getName(),
- 'img_metadata' => $this->metadata,
- ), array( /* WHERE */
- 'img_name' => $this->name
- ), __METHOD__
- );
- } else {
- # This is a new image
- # Update the image count
- $site_stats = $dbw->tableName( 'site_stats' );
- $dbw->query( "UPDATE $site_stats SET ss_images=ss_images+1", __METHOD__ );
- }
-
- $descTitle = $this->getTitle();
- $article = new Article( $descTitle );
- $minor = false;
- $watch = $watch || $wgUser->isWatched( $descTitle );
- $suppressRC = true; // There's already a log entry, so don't double the RC load
-
- if( $descTitle->exists() ) {
- // TODO: insert a null revision into the page history for this update.
- if( $watch ) {
- $wgUser->addWatch( $descTitle );
- }
-
- # Invalidate the cache for the description page
- $descTitle->invalidateCache();
- $descTitle->purgeSquid();
- } else {
- // New image; create the description page.
- $article->insertNewArticle( $textdesc, $desc, $minor, $watch, $suppressRC );
- }
-
- # Hooks, hooks, the magic of hooks...
- wfRunHooks( 'FileUpload', array( $this ) );
-
- # Add the log entry
- $log = new LogPage( 'upload' );
- $log->addEntry( 'upload', $descTitle, $desc );
-
- # Commit the transaction now, in case something goes wrong later
- # The most important thing is that images don't get lost, especially archives
- $dbw->immediateCommit();
-
- # Invalidate cache for all pages using this image
- $update = new HTMLCacheUpdate( $this->getTitle(), 'imagelinks' );
- $update->doUpdate();
-
- return true;
- }
-
- /**
- * Get an array of Title objects which are articles which use this image
- * Also adds their IDs to the link cache
- *
- * This is mostly copied from Title::getLinksTo()
- *
- * @deprecated Use HTMLCacheUpdate, this function uses too much memory
- * @param string $options
- * @return array[int]Title
- */
- function getLinksTo( $options = '' ) {
- wfProfileIn( __METHOD__ );
-
- if ( $options ) {
- $db = wfGetDB( DB_MASTER );
- } else {
- $db = wfGetDB( DB_SLAVE );
- }
- $linkCache =& LinkCache::singleton();
-
- list( $page, $imagelinks ) = $db->tableNamesN( 'page', 'imagelinks' );
- $encName = $db->addQuotes( $this->name );
- $sql = "SELECT page_namespace,page_title,page_id FROM $page,$imagelinks WHERE page_id=il_from AND il_to=$encName $options";
- $res = $db->query( $sql, __METHOD__ );
-
- $retVal = array();
- if ( $db->numRows( $res ) ) {
- while ( $row = $db->fetchObject( $res ) ) {
- if ( $titleObj = Title::makeTitle( $row->page_namespace, $row->page_title ) ) {
- $linkCache->addGoodLinkObj( $row->page_id, $titleObj );
- $retVal[] = $titleObj;
- }
- }
- }
- $db->freeResult( $res );
- wfProfileOut( __METHOD__ );
- return $retVal;
- }
-
- /**
- * @return array
- */
- function getExifData() {
- $handler = $this->getHandler();
- if ( !$handler || $handler->getMetadataType( $this ) != 'exif' ) {
- return array();
- }
- if ( !$this->metadata ) {
- return array();
- }
- $exif = unserialize( $this->metadata );
- if ( !$exif ) {
- return array();
- }
- unset( $exif['MEDIAWIKI_EXIF_VERSION'] );
- $format = new FormatExif( $exif );
-
- return $format->getFormattedData();
- }
-
- /**
- * Returns true if the image does not come from the shared
- * image repository.
- *
- * @return bool
- */
- function isLocal() {
- return !$this->fromSharedDirectory;
- }
-
- /**
- * Was this image ever deleted from the wiki?
- *
- * @return bool
- */
- function wasDeleted() {
- $title = Title::makeTitle( NS_IMAGE, $this->name );
- return ( $title->isDeleted() > 0 );
- }
-
- /**
- * Delete all versions of the image.
- *
- * Moves the files into an archive directory (or deletes them)
- * and removes the database rows.
- *
- * Cache purging is done; logging is caller's responsibility.
- *
- * @param string $reason
- * @param boolean $suppress
- * @return boolean true on success, false on some kind of failure
- */
- function delete( $reason, $suppress=false ) {
- $transaction = new FSTransaction();
- $urlArr = array( $this->getURL() );
-
- if( !FileStore::lock() ) {
- wfDebug( __METHOD__.": failed to acquire file store lock, aborting\n" );
- return false;
- }
-
- try {
- $dbw = wfGetDB( DB_MASTER );
- $dbw->begin();
-
- // Delete old versions
- $result = $dbw->select( 'oldimage',
- array( 'oi_archive_name' ),
- array( 'oi_name' => $this->name ) );
-
- while( $row = $dbw->fetchObject( $result ) ) {
- $oldName = $row->oi_archive_name;
-
- $transaction->add( $this->prepareDeleteOld( $oldName, $reason, $suppress ) );
-
- // We'll need to purge this URL from caches...
- $urlArr[] = wfImageArchiveUrl( $oldName );
- }
- $dbw->freeResult( $result );
-
- // And the current version...
- $transaction->add( $this->prepareDeleteCurrent( $reason, $suppress ) );
-
- $dbw->immediateCommit();
- } catch( MWException $e ) {
- wfDebug( __METHOD__.": db error, rolling back file transactions\n" );
- $transaction->rollback();
- FileStore::unlock();
- throw $e;
- }
-
- wfDebug( __METHOD__.": deleted db items, applying file transactions\n" );
- $transaction->commit();
- FileStore::unlock();
-
-
- // Update site_stats
- $site_stats = $dbw->tableName( 'site_stats' );
- $dbw->query( "UPDATE $site_stats SET ss_images=ss_images-1", __METHOD__ );
-
- $this->purgeEverything( $urlArr );
-
- return true;
- }
-
-
- /**
- * Delete an old version of the image.
- *
- * Moves the file into an archive directory (or deletes it)
- * and removes the database row.
- *
- * Cache purging is done; logging is caller's responsibility.
- *
- * @param string $archiveName
- * @param string $reason
- * @param boolean $suppress
- * @throws MWException or FSException on database or filestore failure
- * @return boolean true on success, false on some kind of failure
- */
- function deleteOld( $archiveName, $reason, $suppress=false ) {
- $transaction = new FSTransaction();
- $urlArr = array();
-
- if( !FileStore::lock() ) {
- wfDebug( __METHOD__.": failed to acquire file store lock, aborting\n" );
- return false;
- }
-
- $transaction = new FSTransaction();
- try {
- $dbw = wfGetDB( DB_MASTER );
- $dbw->begin();
- $transaction->add( $this->prepareDeleteOld( $archiveName, $reason, $suppress ) );
- $dbw->immediateCommit();
- } catch( MWException $e ) {
- wfDebug( __METHOD__.": db error, rolling back file transaction\n" );
- $transaction->rollback();
- FileStore::unlock();
- throw $e;
- }
-
- wfDebug( __METHOD__.": deleted db items, applying file transaction\n" );
- $transaction->commit();
- FileStore::unlock();
-
- $this->purgeDescription();
-
- // Squid purging
- global $wgUseSquid;
- if ( $wgUseSquid ) {
- $urlArr = array(
- wfImageArchiveUrl( $archiveName ),
- );
- wfPurgeSquidServers( $urlArr );
- }
- return true;
- }
-
- /**
- * Delete the current version of a file.
- * May throw a database error.
- * @param string $reason
- * @param boolean $suppress
- * @return boolean true on success, false on failure
- */
- private function prepareDeleteCurrent( $reason, $suppress=false ) {
- return $this->prepareDeleteVersion(
- $this->getFullPath(),
- $reason,
- 'image',
- array(
- 'fa_name' => 'img_name',
- 'fa_archive_name' => 'NULL',
- 'fa_size' => 'img_size',
- 'fa_width' => 'img_width',
- 'fa_height' => 'img_height',
- 'fa_metadata' => 'img_metadata',
- 'fa_bits' => 'img_bits',
- 'fa_media_type' => 'img_media_type',
- 'fa_major_mime' => 'img_major_mime',
- 'fa_minor_mime' => 'img_minor_mime',
- 'fa_description' => 'img_description',
- 'fa_user' => 'img_user',
- 'fa_user_text' => 'img_user_text',
- 'fa_timestamp' => 'img_timestamp' ),
- array( 'img_name' => $this->name ),
- $suppress,
- __METHOD__ );
- }
-
- /**
- * Delete a given older version of a file.
- * May throw a database error.
- * @param string $archiveName
- * @param string $reason
- * @param boolean $suppress
- * @return boolean true on success, false on failure
- */
- private function prepareDeleteOld( $archiveName, $reason, $suppress=false ) {
- $oldpath = wfImageArchiveDir( $this->name ) .
- DIRECTORY_SEPARATOR . $archiveName;
- return $this->prepareDeleteVersion(
- $oldpath,
- $reason,
- 'oldimage',
- array(
- 'fa_name' => 'oi_name',
- 'fa_archive_name' => 'oi_archive_name',
- 'fa_size' => 'oi_size',
- 'fa_width' => 'oi_width',
- 'fa_height' => 'oi_height',
- 'fa_metadata' => 'NULL',
- 'fa_bits' => 'oi_bits',
- 'fa_media_type' => 'NULL',
- 'fa_major_mime' => 'NULL',
- 'fa_minor_mime' => 'NULL',
- 'fa_description' => 'oi_description',
- 'fa_user' => 'oi_user',
- 'fa_user_text' => 'oi_user_text',
- 'fa_timestamp' => 'oi_timestamp' ),
- array(
- 'oi_name' => $this->name,
- 'oi_archive_name' => $archiveName ),
- $suppress,
- __METHOD__ );
- }
-
- /**
- * Do the dirty work of backing up an image row and its file
- * (if $wgSaveDeletedFiles is on) and removing the originals.
- *
- * Must be run while the file store is locked and a database
- * transaction is open to avoid race conditions.
- *
- * @return FSTransaction
- */
- private function prepareDeleteVersion( $path, $reason, $table, $fieldMap, $where, $suppress=false, $fname ) {
- global $wgUser, $wgSaveDeletedFiles;
-
- // Dupe the file into the file store
- if( file_exists( $path ) ) {
- if( $wgSaveDeletedFiles ) {
- $group = 'deleted';
-
- $store = FileStore::get( $group );
- $key = FileStore::calculateKey( $path, $this->extension );
- $transaction = $store->insert( $key, $path,
- FileStore::DELETE_ORIGINAL );
- } else {
- $group = null;
- $key = null;
- $transaction = FileStore::deleteFile( $path );
- }
- } else {
- wfDebug( __METHOD__." deleting already-missing '$path'; moving on to database\n" );
- $group = null;
- $key = null;
- $transaction = new FSTransaction(); // empty
- }
-
- if( $transaction === false ) {
- // Fail to restore?
- wfDebug( __METHOD__.": import to file store failed, aborting\n" );
- throw new MWException( "Could not archive and delete file $path" );
- return false;
- }
-
- // Bitfields to further supress the image content
- // Note that currently, live images are stored elsewhere
- // and cannot be partially deleted
- $bitfield = 0;
- if ( $suppress ) {
- $bitfield |= self::DELETED_FILE;
- $bitfield |= self::DELETED_COMMENT;
- $bitfield |= self::DELETED_USER;
- $bitfield |= self::DELETED_RESTRICTED;
- }
-
- $dbw = wfGetDB( DB_MASTER );
- $storageMap = array(
- 'fa_storage_group' => $dbw->addQuotes( $group ),
- 'fa_storage_key' => $dbw->addQuotes( $key ),
-
- 'fa_deleted_user' => $dbw->addQuotes( $wgUser->getId() ),
- 'fa_deleted_timestamp' => $dbw->timestamp(),
- 'fa_deleted_reason' => $dbw->addQuotes( $reason ),
- 'fa_deleted' => $bitfield);
- $allFields = array_merge( $storageMap, $fieldMap );
-
- try {
- if( $wgSaveDeletedFiles ) {
- $dbw->insertSelect( 'filearchive', $table, $allFields, $where, $fname );
- }
- $dbw->delete( $table, $where, $fname );
- } catch( DBQueryError $e ) {
- // Something went horribly wrong!
- // Leave the file as it was...
- wfDebug( __METHOD__.": database error, rolling back file transaction\n" );
- $transaction->rollback();
- throw $e;
- }
-
- return $transaction;
- }
-
- /**
- * Restore all or specified deleted revisions to the given file.
- * Permissions and logging are left to the caller.
- *
- * May throw database exceptions on error.
- *
- * @param $versions set of record ids of deleted items to restore,
- * or empty to restore all revisions.
- * @return the number of file revisions restored if successful,
- * or false on failure
- */
- function restore( $versions=array(), $Unsuppress=false ) {
- global $wgUser;
-
- if( !FileStore::lock() ) {
- wfDebug( __METHOD__." could not acquire filestore lock\n" );
- return false;
- }
-
- $transaction = new FSTransaction();
- try {
- $dbw = wfGetDB( DB_MASTER );
- $dbw->begin();
-
- // Re-confirm whether this image presently exists;
- // if no we'll need to create an image record for the
- // first item we restore.
- $exists = $dbw->selectField( 'image', '1',
- array( 'img_name' => $this->name ),
- __METHOD__ );
-
- // Fetch all or selected archived revisions for the file,
- // sorted from the most recent to the oldest.
- $conditions = array( 'fa_name' => $this->name );
- if( $versions ) {
- $conditions['fa_id'] = $versions;
- }
-
- $result = $dbw->select( 'filearchive', '*',
- $conditions,
- __METHOD__,
- array( 'ORDER BY' => 'fa_timestamp DESC' ) );
-
- if( $dbw->numRows( $result ) < count( $versions ) ) {
- // There's some kind of conflict or confusion;
- // we can't restore everything we were asked to.
- wfDebug( __METHOD__.": couldn't find requested items\n" );
- $dbw->rollback();
- FileStore::unlock();
- return false;
- }
-
- if( $dbw->numRows( $result ) == 0 ) {
- // Nothing to do.
- wfDebug( __METHOD__.": nothing to do\n" );
- $dbw->rollback();
- FileStore::unlock();
- return true;
- }
-
- $revisions = 0;
- while( $row = $dbw->fetchObject( $result ) ) {
- if ( $Unsuppress ) {
- // Currently, fa_deleted flags fall off upon restore, lets be careful about this
- } else if ( ($row->fa_deleted & Revision::DELETED_RESTRICTED) && !$wgUser->isAllowed('hiderevision') ) {
- // Skip restoring file revisions that the user cannot restore
- continue;
- }
- $revisions++;
- $store = FileStore::get( $row->fa_storage_group );
- if( !$store ) {
- wfDebug( __METHOD__.": skipping row with no file.\n" );
- continue;
- }
-
- if( $revisions == 1 && !$exists ) {
- $destDir = wfImageDir( $row->fa_name );
- if ( !is_dir( $destDir ) ) {
- wfMkdirParents( $destDir );
- }
- $destPath = $destDir . DIRECTORY_SEPARATOR . $row->fa_name;
-
- // We may have to fill in data if this was originally
- // an archived file revision.
- if( is_null( $row->fa_metadata ) ) {
- $tempFile = $store->filePath( $row->fa_storage_key );
-
- $magic = MimeMagic::singleton();
- $mime = $magic->guessMimeType( $tempFile, true );
- $media_type = $magic->getMediaType( $tempFile, $mime );
- list( $major_mime, $minor_mime ) = self::splitMime( $mime );
- $handler = MediaHandler::getHandler( $mime );
- if ( $handler ) {
- $metadata = $handler->getMetadata( $image, $tempFile );
- } else {
- $metadata = '';
- }
- } else {
- $metadata = $row->fa_metadata;
- $major_mime = $row->fa_major_mime;
- $minor_mime = $row->fa_minor_mime;
- $media_type = $row->fa_media_type;
- }
-
- $table = 'image';
- $fields = array(
- 'img_name' => $row->fa_name,
- 'img_size' => $row->fa_size,
- 'img_width' => $row->fa_width,
- 'img_height' => $row->fa_height,
- 'img_metadata' => $metadata,
- 'img_bits' => $row->fa_bits,
- 'img_media_type' => $media_type,
- 'img_major_mime' => $major_mime,
- 'img_minor_mime' => $minor_mime,
- 'img_description' => $row->fa_description,
- 'img_user' => $row->fa_user,
- 'img_user_text' => $row->fa_user_text,
- 'img_timestamp' => $row->fa_timestamp );
- } else {
- $archiveName = $row->fa_archive_name;
- if( $archiveName == '' ) {
- // This was originally a current version; we
- // have to devise a new archive name for it.
- // Format is <timestamp of archiving>!<name>
- $archiveName =
- wfTimestamp( TS_MW, $row->fa_deleted_timestamp ) .
- '!' . $row->fa_name;
- }
- $destDir = wfImageArchiveDir( $row->fa_name );
- if ( !is_dir( $destDir ) ) {
- wfMkdirParents( $destDir );
- }
- $destPath = $destDir . DIRECTORY_SEPARATOR . $archiveName;
-
- $table = 'oldimage';
- $fields = array(
- 'oi_name' => $row->fa_name,
- 'oi_archive_name' => $archiveName,
- 'oi_size' => $row->fa_size,
- 'oi_width' => $row->fa_width,
- 'oi_height' => $row->fa_height,
- 'oi_bits' => $row->fa_bits,
- 'oi_description' => $row->fa_description,
- 'oi_user' => $row->fa_user,
- 'oi_user_text' => $row->fa_user_text,
- 'oi_timestamp' => $row->fa_timestamp );
- }
-
- $dbw->insert( $table, $fields, __METHOD__ );
- // @todo this delete is not totally safe, potentially
- $dbw->delete( 'filearchive',
- array( 'fa_id' => $row->fa_id ),
- __METHOD__ );
-
- // Check if any other stored revisions use this file;
- // if so, we shouldn't remove the file from the deletion
- // archives so they will still work.
- $useCount = $dbw->selectField( 'filearchive',
- 'COUNT(*)',
- array(
- 'fa_storage_group' => $row->fa_storage_group,
- 'fa_storage_key' => $row->fa_storage_key ),
- __METHOD__ );
- if( $useCount == 0 ) {
- wfDebug( __METHOD__.": nothing else using {$row->fa_storage_key}, will deleting after\n" );
- $flags = FileStore::DELETE_ORIGINAL;
- } else {
- $flags = 0;
- }
-
- $transaction->add( $store->export( $row->fa_storage_key,
- $destPath, $flags ) );
- }
-
- $dbw->immediateCommit();
- } catch( MWException $e ) {
- wfDebug( __METHOD__." caught error, aborting\n" );
- $transaction->rollback();
- throw $e;
- }
-
- $transaction->commit();
- FileStore::unlock();
-
- if( $revisions > 0 ) {
- if( !$exists ) {
- wfDebug( __METHOD__." restored $revisions items, creating a new current\n" );
-
- // Update site_stats
- $site_stats = $dbw->tableName( 'site_stats' );
- $dbw->query( "UPDATE $site_stats SET ss_images=ss_images+1", __METHOD__ );
-
- $this->purgeEverything();
- } else {
- wfDebug( __METHOD__." restored $revisions as archived versions\n" );
- $this->purgeDescription();
- }
- }
-
- return $revisions;
- }
-
- /**
- * Returns 'true' if this image is a multipage document, e.g. a DJVU
- * document.
- *
- * @return Bool
- */
- function isMultipage() {
- $handler = $this->getHandler();
- return $handler && $handler->isMultiPage();
- }
-
- /**
- * Returns the number of pages of a multipage document, or NULL for
- * documents which aren't multipage documents
- */
- function pageCount() {
- $handler = $this->getHandler();
- if ( $handler && $handler->isMultiPage() ) {
- return $handler->pageCount( $this );
- } else {
- return null;
- }
- }
-
- static function getCommonsDB() {
- static $dbc;
- global $wgLoadBalancer, $wgSharedUploadDBname;
- if ( !isset( $dbc ) ) {
- $i = $wgLoadBalancer->getGroupIndex( 'commons' );
- $dbinfo = $wgLoadBalancer->mServers[$i];
- $dbc = new Database( $dbinfo['host'], $dbinfo['user'],
- $dbinfo['password'], $wgSharedUploadDBname );
- }
- return $dbc;
- }
-
- /**
- * Calculate the height of a thumbnail using the source and destination width
- */
- static function scaleHeight( $srcWidth, $srcHeight, $dstWidth ) {
- // Exact integer multiply followed by division
- if ( $srcWidth == 0 ) {
- return 0;
- } else {
- return round( $srcHeight * $dstWidth / $srcWidth );
- }
- }
-
- /**
- * 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
- */
- function getImageSize( $fileName ) {
- $handler = $this->getHandler();
- return $handler->getImageSize( $this, $fileName );
- }
-
- /**
- * Get the thumbnail extension and MIME type for a given source MIME type
- * @return array thumbnail extension and MIME type
- */
- static function getThumbType( $ext, $mime ) {
- $handler = MediaHandler::getHandler( $mime );
- if ( $handler ) {
- return $handler->getThumbType( $ext, $mime );
- } else {
- return array( $ext, $mime );
- }
- }
-
-} //class
-
-
-/**
- * @addtogroup Media
- */
-class ArchivedFile
-{
- /**
- * Returns a file object from the filearchive table
- * In the future, all current and old image storage
- * may use FileStore. There will be a "old" storage
- * for current and previous file revisions as well as
- * the "deleted" group for archived revisions
- * @param $title, the corresponding image page title
- * @param $id, the image id, a unique key
- * @param $key, optional storage key
- * @return ResultWrapper
- */
- function ArchivedFile( $title, $id=0, $key='' ) {
- if( !is_object( $title ) ) {
- throw new MWException( 'Image constructor given bogus title.' );
- }
- $conds = ($id) ? "fa_id = $id" : "fa_storage_key = '$key'";
- if( $title->getNamespace() == NS_IMAGE ) {
- $dbr = wfGetDB( DB_SLAVE );
- $res = $dbr->select( 'filearchive',
- array(
- 'fa_id',
- 'fa_name',
- 'fa_storage_key',
- 'fa_storage_group',
- 'fa_size',
- 'fa_bits',
- 'fa_width',
- 'fa_height',
- 'fa_metadata',
- 'fa_media_type',
- 'fa_major_mime',
- 'fa_minor_mime',
- 'fa_description',
- 'fa_user',
- 'fa_user_text',
- 'fa_timestamp',
- 'fa_deleted' ),
- array(
- 'fa_name' => $title->getDbKey(),
- $conds ),
- __METHOD__,
- array( 'ORDER BY' => 'fa_timestamp DESC' ) );
-
- if ( $dbr->numRows( $res ) == 0 ) {
- // this revision does not exist?
- return;
- }
- $ret = $dbr->resultObject( $res );
- $row = $ret->fetchObject();
-
- // initialize fields for filestore image object
- $this->mId = intval($row->fa_id);
- $this->mName = $row->fa_name;
- $this->mGroup = $row->fa_storage_group;
- $this->mKey = $row->fa_storage_key;
- $this->mSize = $row->fa_size;
- $this->mBits = $row->fa_bits;
- $this->mWidth = $row->fa_width;
- $this->mHeight = $row->fa_height;
- $this->mMetaData = $row->fa_metadata;
- $this->mMime = "$row->fa_major_mime/$row->fa_minor_mime";
- $this->mType = $row->fa_media_type;
- $this->mDescription = $row->fa_description;
- $this->mUser = $row->fa_user;
- $this->mUserText = $row->fa_user_text;
- $this->mTimestamp = $row->fa_timestamp;
- $this->mDeleted = $row->fa_deleted;
- } else {
- throw new MWException( 'This title does not correspond to an image page.' );
- return;
- }
- return true;
- }
-
- /**
- * int $field one of DELETED_* bitfield constants
- * for file or revision rows
- * @return bool
- */
- function isDeleted( $field ) {
- return ($this->mDeleted & $field) == $field;
- }
-
- /**
- * Determine if the current user is allowed to view a particular
- * field of this FileStore image file, if it's marked as deleted.
- * @param int $field
- * @return bool
- */
- function userCan( $field ) {
- if( isset($this->mDeleted) && ($this->mDeleted & $field) == $field ) {
- // images
- global $wgUser;
- $permission = ( $this->mDeleted & Revision::DELETED_RESTRICTED ) == Revision::DELETED_RESTRICTED
- ? 'hiderevision'
- : 'deleterevision';
- wfDebug( "Checking for $permission due to $field match on $this->mDeleted\n" );
- return $wgUser->isAllowed( $permission );
- } else {
- return true;
- }
- }
-}
-
-/**
- * Aliases for backwards compatibility with 1.6
- */
-define( 'MW_IMG_DELETED_FILE', Image::DELETED_FILE );
-define( 'MW_IMG_DELETED_COMMENT', Image::DELETED_COMMENT );
-define( 'MW_IMG_DELETED_USER', Image::DELETED_USER );
-define( 'MW_IMG_DELETED_RESTRICTED', Image::DELETED_RESTRICTED );
-
-?>
<?php
-
-/**
- * Returns the image directory of an image
- * The result is an absolute path.
- *
- * This function is called from thumb.php before Setup.php is included
- *
- * @param $fname String: file name of the image file.
- * @public
- */
-function wfImageDir( $fname ) {
- global $wgUploadDirectory, $wgHashedUploadDirectory;
-
- if (!$wgHashedUploadDirectory) { return $wgUploadDirectory; }
-
- $hash = md5( $fname );
- $dest = $wgUploadDirectory . '/' . $hash{0} . '/' . substr( $hash, 0, 2 );
-
- return $dest;
-}
-
-/**
- * Returns the image directory of an image's thumbnail
- * The result is an absolute path.
- *
- * This function is called from thumb.php before Setup.php is included
- *
- * @param $fname String: file name of the original image file
- * @param $shared Boolean: (optional) use the shared upload directory (default: 'false').
- * @public
- */
-function wfImageThumbDir( $fname, $shared = false ) {
- $base = wfImageArchiveDir( $fname, 'thumb', $shared );
- if ( Image::isHashed( $shared ) ) {
- $dir = "$base/$fname";
- } else {
- $dir = $base;
- }
-
- return $dir;
-}
-
-/**
- * Old thumbnail directory, kept for conversion
- */
-function wfDeprecatedThumbDir( $thumbName , $subdir='thumb', $shared=false) {
- return wfImageArchiveDir( $thumbName, $subdir, $shared );
-}
-
-/**
- * Returns the image directory of an image's old version
- * The result is an absolute path.
- *
- * This function is called from thumb.php before Setup.php is included
- *
- * @param $fname String: file name of the thumbnail file, including file size prefix.
- * @param $subdir String: subdirectory of the image upload directory that should be used for storing the old version. Default is 'archive'.
- * @param $shared Boolean use the shared upload directory (only relevant for other functions which call this one). Default is 'false'.
- * @public
- */
-function wfImageArchiveDir( $fname , $subdir='archive', $shared=false ) {
- global $wgUploadDirectory, $wgHashedUploadDirectory;
- global $wgSharedUploadDirectory, $wgHashedSharedUploadDirectory;
- $dir = $shared ? $wgSharedUploadDirectory : $wgUploadDirectory;
- $hashdir = $shared ? $wgHashedSharedUploadDirectory : $wgHashedUploadDirectory;
- if (!$hashdir) { return $dir.'/'.$subdir; }
- $hash = md5( $fname );
-
- return $dir.'/'.$subdir.'/'.$hash[0].'/'.substr( $hash, 0, 2 );
-}
-
-
-/*
- * Return the hash path component of an image path (URL or filesystem),
- * e.g. "/3/3c/", or just "/" if hashing is not used.
- *
- * @param $dbkey The filesystem / database name of the file
- * @param $fromSharedDirectory Use the shared file repository? It may
- * use different hash settings from the local one.
- */
-function wfGetHashPath ( $dbkey, $fromSharedDirectory = false ) {
- if( Image::isHashed( $fromSharedDirectory ) ) {
- $hash = md5($dbkey);
- return '/' . $hash{0} . '/' . substr( $hash, 0, 2 ) . '/';
- } else {
- return '/';
- }
-}
-
-/**
- * Returns the image URL of an image's old version
- *
- * @param $name String: file name of the image file
- * @param $subdir String: (optional) subdirectory of the image upload directory that is used by the old version. Default is 'archive'
- * @public
- */
-function wfImageArchiveUrl( $name, $subdir='archive' ) {
- global $wgUploadPath, $wgHashedUploadDirectory;
-
- if ($wgHashedUploadDirectory) {
- $hash = md5( substr( $name, 15) );
- $url = $wgUploadPath.'/'.$subdir.'/' . $hash{0} . '/' .
- substr( $hash, 0, 2 ) . '/'.$name;
- } else {
- $url = $wgUploadPath.'/'.$subdir.'/'.$name;
- }
- return wfUrlencode($url);
-}
-
/**
* Return a rounded pixel equivalent for a labeled CSS/SVG length.
* http://www.w3.org/TR/SVG11/coords.html#UnitIdentifiers
/**
* Add an image to the gallery.
*
- * @param $image Image object that is added to the gallery
+ * @param $title Title object of the image that is added to the gallery
* @param $html String: additional HTML text to be shown. The name and size of the image are always shown.
*/
- function add( $image, $html='' ) {
- $this->mImages[] = array( &$image, $html );
- wfDebug( "ImageGallery::add " . $image->getName() . "\n" );
+ function add( $title, $html='' ) {
+ if ( $title instanceof File ) {
+ // Old calling convention
+ $title = $title->getTitle();
+ }
+ $this->mImages[] = array( $title, $html );
+ wfDebug( "ImageGallery::add " . $title->getText() . "\n" );
}
/**
* Add an image at the beginning of the gallery.
*
- * @param $image Image object that is added to the gallery
+ * @param $title Title object of the image that is added to the gallery
* @param $html String: Additional HTML text to be shown. The name and size of the image are always shown.
*/
- function insert( $image, $html='' ) {
- array_unshift( $this->mImages, array( &$image, $html ) );
+ function insert( $title, $html='' ) {
+ array_unshift( $this->mImages, array( &$title, $html ) );
}
$params = array( 'width' => $this->mWidths, 'height' => $this->mHeights );
$i = 0;
foreach ( $this->mImages as $pair ) {
- $img =& $pair[0];
+ $nt = $pair[0];
$text = $pair[1];
- $nt = $img->getTitle();
+ $img = wfFindFile( $nt );
- if( $nt->getNamespace() != NS_IMAGE ) {
+ if( $nt->getNamespace() != NS_IMAGE || !$img ) {
# We're dealing with a non-image, spit out the name and be done with it.
$thumbhtml = "\n\t\t\t".'<div style="height: '.($this->mHeights*1.25+2).'px;">'
. htmlspecialchars( $nt->getText() ) . '</div>';
//$ul = $sk->makeLink( $wgContLang->getNsText( Namespace::getUser() ) . ":{$ut}", $ut );
if( $this->mShowBytes ) {
- if( $img->exists() ) {
+ if( $img ) {
$nb = wfMsgExt( 'nbytes', array( 'parsemag', 'escape'),
$wgLang->formatNum( $img->getSize() ) );
} else {
/* private */ var $img; // Image object this page is shown for
var $mExtraDescription = false;
+ function __construct( $title ) {
+ parent::__construct( $title );
+ $this->img = wfFindFile( $this->mTitle );
+ if ( !$this->img ) {
+ $this->img = wfLocalFile( $this->mTitle );
+ }
+ }
+
/**
* Handler for action=render
* Include body text only; none of the image extras
function view() {
global $wgOut, $wgShowEXIF, $wgRequest, $wgUser;
- $this->img = new Image( $this->mTitle );
-
$diff = $wgRequest->getVal( 'diff' );
$diffOnly = $wgRequest->getBool( 'diffonly', $wgUser->getOption( 'diffonly' ) );
* shared upload server if possible.
*/
function getContent() {
- if( $this->img && $this->img->fromSharedDirectory && 0 == $this->getID() ) {
+ if( $this->img && !$this->img->isLocal() && 0 == $this->getID() ) {
return '';
}
return Article::getContent();
$dirmark = $wgContLang->getDirMark();
if (!$this->img->isSafeFile()) {
$warning = wfMsg( 'mediawarning' );
- $wgOut->addWikiText( <<<END
+ $wgOut->addWikiText( <<<EOT
<div class="fullMedia">$infores
<span class="dangerousLink">[[Media:$filename|$filename]]</span>$dirmark
<span class="fileInfo"> $info</span>
</div>
<div class="mediaWarning">$warning</div>
-END
+EOT
);
} else {
- $wgOut->addWikiText( <<<END
+ $wgOut->addWikiText( <<<EOT
<div class="fullMedia">$infores
[[Media:$filename|$filename]]$dirmark <span class="fileInfo"> $info</span>
</div>
-END
+EOT
);
}
}
- if($this->img->fromSharedDirectory) {
+ if(!$this->img->isLocal()) {
$this->printSharedImageText();
}
} else {
}
function printSharedImageText() {
- global $wgRepositoryBaseUrl, $wgFetchCommonsDescriptions, $wgOut, $wgUser;
-
- $url = $wgRepositoryBaseUrl . urlencode($this->mTitle->getDBkey());
- $sharedtext = "<div class='sharedUploadNotice'>" . wfMsgWikiHtml("sharedupload");
- if ($wgRepositoryBaseUrl && !$wgFetchCommonsDescriptions) {
+ global $wgOut, $wgUser;
+ $descUrl = $this->img->getDescriptionUrl();
+ $descText = $this->img->getDescriptionText();
+ $s = "<div class='sharedUploadNotice'>" . wfMsgWikiHtml("sharedupload");
+ if ( $descUrl && !$descText) {
$sk = $wgUser->getSkin();
- $title = SpecialPage::getTitleFor( 'Upload' );
- $link = $sk->makeKnownLinkObj($title, wfMsgHtml('shareduploadwiki-linktext'),
- array( 'wpDestFile' => urlencode( $this->img->getName() )));
- $sharedtext .= " " . wfMsgWikiHtml('shareduploadwiki', $link);
+ $link = $sk->makeExternalLink( $descUrl, wfMsg('shareduploadwiki-linktext') );
+ $s .= " " . wfMsgWikiHtml('shareduploadwiki', $link);
}
- $sharedtext .= "</div>";
- $wgOut->addHTML($sharedtext);
+ $s .= "</div>";
+ $wgOut->addHTML($s);
- if ($wgRepositoryBaseUrl && $wgFetchCommonsDescriptions) {
- $renderUrl = wfAppendQuery( $url, 'action=render' );
- wfDebug( "Fetching shared description from $renderUrl\n" );
- $text = Http::get( $renderUrl );
- if ($text)
- $this->mExtraDescription = $text;
+ if ( $descText ) {
+ $this->mExtraDescription = $descText;
}
}
function uploadLinksBox() {
global $wgUser, $wgOut;
- if( $this->img->fromSharedDirectory )
+ if( !$this->img->isLocal() )
return;
$sk = $wgUser->getSkin();
$line = $this->img->nextHistoryLine();
if ( $line ) {
- $list = new ImageHistoryList( $sk );
+ $list = new ImageHistoryList( $sk, $this->img );
$s = $list->beginImageHistoryList() .
$list->imageHistoryLine( true, wfTimestamp(TS_MW, $line->img_timestamp),
$this->mTitle->getDBkey(), $line->img_user,
return;
}
- $this->img = new Image( $this->mTitle );
-
# Deleting old images doesn't require confirmation
if ( !is_null( $oldimage ) || $confirm ) {
if( $wgUser->matchEditToken( $wgRequest->getVal( 'wpEditToken' ), $oldimage ) ) {
$wgOut->showErrorPage( 'internalerror', 'sessionfailure' );
return;
}
- $name = substr( $oldimage, 15 );
-
- $dest = wfImageDir( $name );
- $archive = wfImageArchiveDir( $name );
- $curfile = "{$dest}/{$name}";
- if ( !is_dir( $dest ) ) wfMkdirParents( $dest );
- if ( !is_dir( $archive ) ) wfMkdirParents( $archive );
+ $sourcePath = $this->img->getArchiveVirtualUrl( $oldimage );
+ $result = $this->img->publish( $sourcePath );
- if ( ! is_file( $curfile ) ) {
- $wgOut->showFileNotFoundError( htmlspecialchars( $curfile ) );
- return;
- }
- $oldver = wfTimestampNow() . "!{$name}";
-
- if ( ! rename( $curfile, "${archive}/{$oldver}" ) ) {
- $wgOut->showFileRenameError( $curfile, "${archive}/{$oldver}" );
- return;
- }
- if ( ! copy( "{$archive}/{$oldimage}", $curfile ) ) {
- $wgOut->showFileCopyError( "${archive}/{$oldimage}", $curfile );
+ if ( WikiError::isError( $result ) ) {
+ $this->showError( $result );
return;
}
# Record upload and update metadata cache
- $img = Image::newFromName( $name );
- $img->recordUpload( $oldver, wfMsg( "reverted" ) );
+ $this->img->recordUpload( $result, wfMsg( "reverted" ) );
$wgOut->setPagetitle( wfMsg( 'actioncomplete' ) );
$wgOut->setRobotpolicy( 'noindex,nofollow' );
$wgOut->addHTML( wfMsg( 'imagereverted' ) );
- $descTitle = $img->getTitle();
+ $descTitle = $this->img->getTitle();
$wgOut->returnToMain( false, $descTitle->getPrefixedText() );
}
* Override handling of action=purge
*/
function doPurge() {
- $this->img = new Image( $this->mTitle );
if( $this->img->exists() ) {
wfDebug( "ImagePage::doPurge purging " . $this->img->getName() . "\n" );
$update = new HTMLCacheUpdate( $this->mTitle, 'imagelinks' );
parent::doPurge();
}
+ /**
+ * Display an error from a wikitext-formatted WikiError object
+ */
+ function showError( WikiError $error ) {
+ global $wgOut;
+ $wgOut->setPageTitle( wfMsg( "internalerror" ) );
+ $wgOut->setRobotpolicy( "noindex,nofollow" );
+ $wgOut->setArticleRelated( false );
+ $wgOut->enableClientCache( false );
+ $wgOut->addWikiText( $error->getMessage() );
+ }
+
}
/**
* @addtogroup Media
*/
class ImageHistoryList {
- function ImageHistoryList( &$skin ) {
- $this->skin =& $skin;
+ var $img, $skin;
+ function ImageHistoryList( $skin, $img ) {
+ $this->skin = $skin;
+ $this->img = $img;
}
function beginImageHistoryList() {
$del = wfMsgHtml( 'deleteimg' );
$delall = wfMsgHtml( 'deleteimgcompletely' );
$cur = wfMsgHtml( 'cur' );
+ $local = $this->img->isLocal();
if ( $iscur ) {
- $url = Image::imageUrl( $img );
+ $url = htmlspecialchars( $this->img->getURL() );
$rlink = $cur;
- if ( $wgUser->isAllowed('delete') ) {
+ if ( $local && $wgUser->isAllowed('delete') ) {
$link = $wgTitle->escapeLocalURL( 'image=' . $wgTitle->getPartialURL() .
'&action=delete' );
$style = $this->skin->getInternalLinkAttributes( $link, $delall );
$dlink = $del;
}
} else {
- $url = htmlspecialchars( wfImageArchiveUrl( $img ) );
- if( $wgUser->getID() != 0 && $wgTitle->userCan( 'edit' ) ) {
+ $url = htmlspecialchars( $this->img->getArchiveUrl( $img ) );
+ if( $local && $wgUser->getID() != 0 && $wgTitle->userCan( 'edit' ) ) {
$token = urlencode( $wgUser->editToken( $img ) );
$rlink = $this->skin->makeKnownLinkObj( $wgTitle,
wfMsgHtml( 'revertimg' ), 'action=revert&oldimage=' .
$dlink = $del;
}
}
-
- $userlink = $this->skin->userLink( $user, $usertext ) . $this->skin->userToolLinks( $user, $usertext );
+
+ if ( $local ) {
+ $userlink = $this->skin->userLink( $user, $usertext ) . $this->skin->userToolLinks( $user, $usertext );
+ } else {
+ $userlink = htmlspecialchars( $usertext );
+ }
$nbytes = wfMsgExt( 'nbytes', array( 'parsemag', 'escape' ),
$wgLang->formatNum( $size ) );
$widthheight = wfMsgHtml( 'widthheight', $width, $height );
$s .= "</li>\n";
return $s;
}
-
}
# $num [should update this to use a Pager]
for( $i = 0; $i < $num && $row = $dbr->fetchObject( $res ); $i++ ) {
$image = $this->prepareImage( $row );
- if( $image instanceof Image ) {
- $gallery->add( $image, $this->getCellHtml( $row ) );
+ if( $image ) {
+ $gallery->add( $image->getTitle(), $this->getCellHtml( $row ) );
}
}
$namespace = isset( $row->namespace ) ? $row->namespace : NS_IMAGE;
$title = Title::makeTitleSafe( $namespace, $row->title );
return ( $title instanceof Title && $title->getNamespace() == NS_IMAGE )
- ? new Image( $title )
+ ? wfFindFile( $title )
: null;
}
* @return string
*/
function makeImageLinkObj( $nt, $label, $alt, $align = '', $params = array(), $framed = false,
- $thumb = false, $manual_thumb = '', $valign = '' )
+ $thumb = false, $manual_thumb = '', $valign = '', $time = false )
{
global $wgContLang, $wgUser, $wgThumbLimits, $wgThumbUpright;
- $img = new Image( $nt );
+ $img = wfFindFile( $nt, $time );
- if ( !$img->allowInlineDisplay() && $img->exists() ) {
+ if ( $img && !$img->allowInlineDisplay() ) {
+ wfDebug( __METHOD__.': '.$nt->getPrefixedDBkey()." does not allow inline display\n" );
return $this->makeKnownLinkObj( $nt );
}
$postfix = '</div>';
$align = 'none';
}
- if ( !isset( $params['width'] ) ) {
+ if ( $img && !isset( $params['width'] ) ) {
$params['width'] = $img->getWidth( $page );
if( $thumb || $framed || isset( $params['frameless'] ) ) {
$wopt = $wgUser->getOption( 'thumbsize' );
if ( $align == '' ) {
$align = $wgContLang->isRTL() ? 'left' : 'right';
}
- return $prefix.$this->makeThumbLinkObj( $img, $label, $alt, $align, $params, $framed, $manual_thumb ).$postfix;
+ return $prefix.$this->makeThumbLinkObj( $nt, $img, $label, $alt, $align, $params, $framed, $manual_thumb ).$postfix;
}
- if ( $params['width'] && $img->exists() ) {
+ if ( $img && $params['width'] ) {
# Create a resized image, without the additional thumbnail features
$thumb = $img->transform( $params );
} else {
);
if ( !$thumb ) {
- $s = $this->makeBrokenImageLinkObj( $img->getTitle() );
+ $s = $this->makeBrokenImageLinkObj( $nt );
} else {
$s = $thumb->toHtml( $imgAttribs, $linkAttribs );
}
/**
* Make HTML for a thumbnail including image, border and caption
- * $img is an Image object
+ * @param Title $nt
+ * @param Image $img Image object or false if it doesn't exist
*/
- function makeThumbLinkObj( $img, $label = '', $alt, $align = 'right', $params = array(), $framed=false , $manual_thumb = "" ) {
+ function makeThumbLinkObj( Title $nt, $img, $label = '', $alt, $align = 'right', $params = array(), $framed=false , $manual_thumb = "" ) {
global $wgStylePath, $wgContLang;
+ $exists = $img && $img->exists();
$page = isset( $params['page'] ) ? $params['page'] : false;
$params['width'] = isset( $params['upright'] ) ? 130 : 180;
}
$thumb = false;
- if ( $manual_thumb != '' ) {
- # Use manually specified thumbnail
- $manual_title = Title::makeTitleSafe( NS_IMAGE, $manual_thumb );
- if( $manual_title ) {
- $manual_img = new Image( $manual_title );
- $thumb = $manual_img->getUnscaledThumb();
- }
- } elseif ( $framed ) {
- // Use image dimensions, don't scale
- $thumb = $img->getUnscaledThumb( $page );
+
+ if ( !$exists ) {
+ $outerWidth = $params['width'] + 2;
} else {
- # Do not present an image bigger than the source, for bitmap-style images
- # This is a hack to maintain compatibility with arbitrary pre-1.10 behaviour
- $srcWidth = $img->getWidth( $page );
- if ( $srcWidth && !$img->mustRender() && $params['width'] > $srcWidth ) {
- $params['width'] = $srcWidth;
+ if ( $manual_thumb != '' ) {
+ # Use manually specified thumbnail
+ $manual_title = Title::makeTitleSafe( NS_IMAGE, $manual_thumb );
+ if( $manual_title ) {
+ $manual_img = wfFindFile( $manual_title );
+ if ( $manual_img ) {
+ $thumb = $manual_img->getUnscaledThumb();
+ } else {
+ $exists = false;
+ }
+ }
+ } elseif ( $framed ) {
+ // Use image dimensions, don't scale
+ $thumb = $img->getUnscaledThumb( $page );
+ } else {
+ # Do not present an image bigger than the source, for bitmap-style images
+ # This is a hack to maintain compatibility with arbitrary pre-1.10 behaviour
+ $srcWidth = $img->getWidth( $page );
+ if ( $srcWidth && !$img->mustRender() && $params['width'] > $srcWidth ) {
+ $params['width'] = $srcWidth;
+ }
+ $thumb = $img->transform( $params );
}
- $thumb = $img->transform( $params );
- }
- if ( $thumb ) {
- $outerWidth = $thumb->getWidth() + 2;
- } else {
- $outerWidth = $params['width'] + 2;
+ if ( $thumb ) {
+ $outerWidth = $thumb->getWidth() + 2;
+ } else {
+ $outerWidth = $params['width'] + 2;
+ }
}
$query = $page ? 'page=' . urlencode( $page ) : '';
- $u = $img->getTitle()->getLocalURL( $query );
+ $u = $nt->getLocalURL( $query );
$more = htmlspecialchars( wfMsg( 'thumbnail-more' ) );
$magnifyalign = $wgContLang->isRTL() ? 'left' : 'right';
$textalign = $wgContLang->isRTL() ? ' style="text-align:right"' : '';
$s = "<div class=\"thumb t{$align}\"><div class=\"thumbinner\" style=\"width:{$outerWidth}px;\">";
- if ( !$thumb ) {
- $s .= htmlspecialchars( wfMsg( 'thumbnail_error', '' ) );
+ if( !$exists ) {
+ $s .= $this->makeBrokenImageLinkObj( $nt );
$zoomicon = '';
- } elseif( !$img->exists() ) {
- $s .= $this->makeBrokenImageLinkObj( $img->getTitle() );
+ } elseif ( !$thumb ) {
+ $s .= htmlspecialchars( wfMsg( 'thumbnail_error', '' ) );
$zoomicon = '';
} else {
$imgAttribs = array(
return $s;
}
- /** @todo document */
- function makeMediaLink( $name, /* wtf?! */ $url, $alt = '' ) {
+ /** @deprecated use Linker::makeMediaLinkObj() */
+ function makeMediaLink( $name, $unused = '', $text = '' ) {
$nt = Title::makeTitleSafe( NS_IMAGE, $name );
- return $this->makeMediaLinkObj( $nt, $alt );
+ return $this->makeMediaLinkObj( $nt, $text );
}
/**
### HOTFIX. Instead of breaking, return empty string.
return $text;
} else {
- $img = new Image( $title );
- if( $img->exists() ) {
+ $img = wfFindFile( $title );
+ if( $img ) {
$url = $img->getURL();
$class = 'internal';
} else {
$upload = SpecialPage::getTitleFor( 'Upload' );
- $url = $upload->getLocalUrl( 'wpDestFile=' . urlencode( $img->getName() ) );
+ $url = $upload->getLocalUrl( 'wpDestFile=' . urlencode( $title->getText() ) );
$class = 'new';
}
$alt = htmlspecialchars( $title->getText() );
<?php
/**
- * Base class for the output of MediaHandler::doTransform() and Image::transform().
+ * Base class for the output of MediaHandler::doTransform() and File::transform().
*
* @addtogroup Media
*/
}
continue;
} elseif( $ns == NS_IMAGE ) {
- $img = new Image( $nt );
- if( $img->exists() ) {
+ $img = wfFindFile( $nt );
+ if( $img ) {
// Force a blue link if the file exists; may be a remote
// upload on the shared repository, and we want to see its
// auto-generated page.
);
$html = $pout->getText();
- $ig->add( new Image( $nt ), $html );
+ $ig->add( $nt, $html );
# Only add real images (bug #5586)
if ( $nt->getNamespace() == NS_IMAGE ) {
# There may have been a funny upload, or it may be on a shared
# file repository such as Wikimedia Commons.
if( $title->getNamespace() == NS_IMAGE ) {
- $image = new Image( $title );
- if( $image->exists() ) {
+ $image = wfFindFile( $title );
+ if( $image ) {
return $title;
}
}
if( $wgReadOnlyFile === false ) $wgReadOnlyFile = "{$wgUploadDirectory}/lock_yBgMBwiR";
if( $wgFileCacheDirectory === false ) $wgFileCacheDirectory = "{$wgUploadDirectory}/cache";
+/**
+ * Initialise $wgLocalFileRepo from backwards-compatible settings
+ */
+if ( !$wgLocalFileRepo ) {
+ $wgLocalFileRepo = array(
+ 'class' => 'LocalRepo',
+ 'name' => 'local',
+ 'directory' => $wgUploadDirectory,
+ 'url' => $wgUploadBaseUrl ? $wgUploadBaseUrl . $wgUploadPath : $wgUploadPath,
+ 'hashLevels' => $wgHashedUploadDirectory ? 2 : 0,
+ 'thumbScriptUrl' => $wgThumbnailScriptPath,
+ 'transformVia404' => !$wgGenerateThumbnailOnParse,
+ );
+}
+/**
+ * Initialise shared repo from backwards-compatible settings
+ */
+if ( $wgUseSharedUploads ) {
+ if ( $wgSharedUploadDBname ) {
+ $wgForeignFileRepos[] = array(
+ 'class' => 'ForeignDBRepo',
+ 'name' => 'shared',
+ 'directory' => $wgSharedUploadDirectory,
+ 'url' => $wgSharedUploadPath,
+ 'hashLevels' => $wgHashedSharedUploadDirectory ? 2 : 0,
+ 'thumbScriptUrl' => $wgSharedThumbnailScriptPath,
+ 'transformVia404' => !$wgGenerateThumbnailOnParse,
+ 'dbType' => $wgDBtype,
+ 'dbServer' => $wgDBserver,
+ 'dbUser' => $wgDBuser,
+ 'dbPassword' => $wgDBpassword,
+ 'dbName' => $wgSharedUploadDBname,
+ 'dbFlags' => DBO_DEFAULT,
+ 'tablePrefix' => $wgSharedUploadDBprefix,
+ 'hasSharedCache' => $wgCacheSharedUploads,
+ 'descBaseUrl' => $wgRepositoryBaseUrl,
+ 'fetchDescription' => $wgFetchCommonsDescriptions,
+ );
+ } else {
+ $wgForeignFileRepos[] = array(
+ 'class' => 'FSRepo',
+ 'name' => 'shared',
+ 'directory' => $wgSharedUploadDirectory,
+ 'url' => $wgSharedUploadPath,
+ 'hashLevels' => $wgHashedSharedUploadDirectory ? 2 : 0,
+ 'thumbScriptUrl' => $wgSharedThumbnailScriptPath,
+ 'transformVia404' => !$wgGenerateThumbnailOnParse,
+ 'descBaseUrl' => $wgRepositoryBaseUrl,
+ 'fetchDescription' => $wgFetchCommonsDescriptions,
+ );
+ }
+}
+
require_once( "$IP/includes/AutoLoader.php" );
wfProfileIn( $fname.'-exception' );
if ( $wgOut->isArticleRelated() ) {
if ( $wgTitle->getNamespace() == NS_IMAGE ) {
$name = $wgTitle->getDBkey();
- $image = new Image( $wgTitle );
- if( $image->exists() ) {
+ $image = wfFindFile( $wgTitle );
+ if( $image ) {
$link = htmlspecialchars( $image->getURL() );
$style = $this->getInternalLinkAttributes( $link, $name );
$s .= " | <a href=\"{$link}\"{$style}>{$name}</a>";
case 'img_name':
$name = $this->mCurrentRow->img_name;
$link = $this->getSkin()->makeKnownLinkObj( Title::makeTitle( NS_IMAGE, $name ), $value );
- $download = Xml::element('a', array( "href" => Image::imageUrl( $name ) ), $this->mMessages['imgfile'] );
+ $image = wfLocalFile( $value );
+ $url = $image->getURL();
+ $download = Xml::element('a', array( "href" => $url ), $this->mMessages['imgfile'] );
return "$link ($download)";
case 'img_user_text':
if ( $this->mCurrentRow->img_user ) {
$text = $wgContLang->convert( $nt->getText() );
$plink = $skin->makeLink( $nt->getPrefixedText(), $text );
- $download = $skin->makeMediaLink( $nt->getText(), 'fuck me!', wfMsgHtml( 'download' ) );
+ $download = $skin->makeMediaLinkObj( $nt, wfMsgHtml( 'download' ) );
$bytes = wfMsgExt( 'nbytes', array( 'parsemag', 'escape'),
$wgLang->formatNum( $result->img_size ) );
$dimensions = wfMsgHtml( 'widthheight', $wgLang->formatNum( $result->img_width ),
$ut = $s->img_user_text;
$nt = Title::newFromText( $name, NS_IMAGE );
- $img = new Image( $nt );
$ul = $sk->makeLinkObj( Title::makeTitle( NS_USER, $ut ), $ut );
- $gallery->add( $img, "$ul<br />\n<i>".$wgLang->timeanddate( $s->img_timestamp, true )."</i><br />\n" );
+ $gallery->add( $nt, "$ul<br />\n<i>".$wgLang->timeanddate( $s->img_timestamp, true )."</i><br />\n" );
$timestamp = wfTimestamp( TS_MW, $s->img_timestamp );
if( empty( $firstTimestamp ) ) {
$restoreFiles = $restoreAll || !empty( $fileVersions );
if( $restoreFiles && $this->title->getNamespace() == NS_IMAGE ) {
- $img = new Image( $this->title );
+ $img = wfLocalFile( $this->title );
$filesRestored = $img->restore( $fileVersions );
} else {
$filesRestored = 0;
var $mUploadCopyStatus, $mUploadSource, $mReUpload, $mAction, $mUpload;
var $mOname, $mSessionKey, $mStashed, $mDestFile, $mRemoveTempFile, $mSourceType;
var $mUploadTempFileSize = 0;
+ var $mImage;
# Placeholders for text injection by hooks (must be HTML)
# extensions should take care to _append_ to the present value
global $wgUser;
$sk = $wgUser->getSkin();
- $image = new Image( $nt );
+ $image = wfLocalFile( $nt );
// Check for uppercase extension. We allow these filenames but check if an image
// with lowercase extension exists already
if ( $finalExt != strtolower( $finalExt ) ) {
$nt_lc = Title::newFromText( $partname . '.' . strtolower( $finalExt ) );
- $image_lc = new Image( $nt_lc );
+ $image_lc = wfLocalFile( $nt_lc );
}
if( $image->exists() ) {
} elseif ( ( substr( $partname , 3, 3 ) == 'px-' || substr( $partname , 2, 3 ) == 'px-' ) && ereg( "[0-9]{2}" , substr( $partname , 0, 2) ) ) {
# Check for filenames like 50px- or 180px-, these are mostly thumbnails
$nt_thb = Title::newFromText( substr( $partname , strpos( $partname , '-' ) +1 ) . '.' . $finalExt );
- $image_thb = new Image( $nt_thb );
+ $image_thb = wfLocalFile( $nt_thb );
if ($image_thb->exists() ) {
# Check if an image without leading '180px-' (or similiar) exists
$dlink = $sk->makeKnownLinkObj( $nt_thb);
* Update the upload log and create the description page
* if it's a new file.
*/
- $img = Image::newFromName( $this->mUploadSaveName );
- $success = $img->recordUpload( $this->mUploadOldVersion,
+ $this->mImage = wfLocalFile( $this->mUploadSaveName );
+ $success = $this->mImage->recordUpload( $this->mUploadOldVersion,
$this->mUploadDescription,
$this->mLicense,
$this->mUploadCopyStatus,
$this->showSuccess();
wfRunHooks( 'UploadComplete', array( &$img ) );
} else {
- // Image::recordUpload() fails if the image went missing, which is
+ // File::recordUpload() fails if the image went missing, which is
// unlikely, hence the lack of a specialised message
$wgOut->showFileNotFoundError( $this->mUploadSaveName );
}
function saveUploadedFile( $saveName, $tempName, $useRename = false ) {
global $wgOut, $wgAllowCopyUploads;
- if ( !$useRename AND $wgAllowCopyUploads AND $this->mSourceType == 'web' ) $useRename = true;
-
- $fname= "SpecialUpload::saveUploadedFile";
-
- $dest = wfImageDir( $saveName );
- $archive = wfImageArchiveDir( $saveName );
- if ( !is_dir( $dest ) ) wfMkdirParents( $dest );
- if ( !is_dir( $archive ) ) wfMkdirParents( $archive );
-
- $this->mSavedFile = "{$dest}/{$saveName}";
-
- if( is_file( $this->mSavedFile ) ) {
- $this->mUploadOldVersion = gmdate( 'YmdHis' ) . "!{$saveName}";
- wfSuppressWarnings();
- $success = rename( $this->mSavedFile, "${archive}/{$this->mUploadOldVersion}" );
- wfRestoreWarnings();
-
- if( ! $success ) {
- $wgOut->showFileRenameError( $this->mSavedFile,
- "${archive}/{$this->mUploadOldVersion}" );
- return false;
- }
- else wfDebug("$fname: moved file ".$this->mSavedFile." to ${archive}/{$this->mUploadOldVersion}\n");
- }
- else {
- $this->mUploadOldVersion = '';
- }
-
- wfSuppressWarnings();
- $success = $useRename
- ? rename( $tempName, $this->mSavedFile )
- : move_uploaded_file( $tempName, $this->mSavedFile );
- wfRestoreWarnings();
-
- if( ! $success ) {
- $wgOut->showFileCopyError( $tempName, $this->mSavedFile );
+ $image = wfLocalFile( $saveName );
+ $archiveName = $image->publish( $tempName, File::DELETE_SOURCE );
+ if ( WikiError::isError( $archiveName ) ) {
+ $this->showError( $archiveName );
return false;
- } else {
- wfDebug("$fname: wrote tempfile $tempName to ".$this->mSavedFile."\n");
}
-
- chmod( $this->mSavedFile, 0644 );
+ $this->mUploadOldVersion = $archiveName;
return true;
}
*/
function saveTempUploadedFile( $saveName, $tempName ) {
global $wgOut;
- $archive = wfImageArchiveDir( $saveName, 'temp' );
- if ( !is_dir ( $archive ) ) wfMkdirParents( $archive );
- $stash = $archive . '/' . gmdate( "YmdHis" ) . '!' . $saveName;
-
- $success = $this->mRemoveTempFile
- ? rename( $tempName, $stash )
- : move_uploaded_file( $tempName, $stash );
- if ( !$success ) {
- $wgOut->showFileCopyError( $tempName, $stash );
+ $repo = RepoGroup::singleton()->getLocalRepo();
+ $result = $repo->storeTemp( $saveName, $tempName );
+ if ( WikiError::isError( $result ) ) {
+ $this->showError( $result );
return false;
+ } else {
+ return $result;
}
-
- return $stash;
}
/**
global $wgUser, $wgOut, $wgContLang;
$sk = $wgUser->getSkin();
- $ilink = $sk->makeMediaLink( $this->mUploadSaveName, Image::imageUrl( $this->mUploadSaveName ) );
+ $ilink = $sk->makeMediaLinkObj( $this->mImage->getTitle() );
$dname = $wgContLang->getNsText( NS_IMAGE ) . ':'.$this->mUploadSaveName;
$dlink = $sk->makeKnownLink( $dname, $dname );
* @access private
*/
function checkOverwrite( $name ) {
- $img = Image::newFromName( $name );
- if( is_null( $img ) ) {
- // Uh... this shouldn't happen ;)
- // But if it does, fall through to previous behavior
- return false;
- }
+ $img = wfFindFile( $name );
$error = '';
- if( $img->exists() ) {
+ if( $img ) {
global $wgUser, $wgOut;
if( $img->isLocal() ) {
if( !self::userCanReUpload( $wgUser, $img->name ) ) {
return $user->getID() == $row->img_user;
}
+
+ /**
+ * Display an error from a wikitext-formatted WikiError object
+ */
+ function showError( WikiError $error ) {
+ global $wgOut;
+ $wgOut->setPageTitle( wfMsg( "internalerror" ) );
+ $wgOut->setRobotpolicy( "noindex,nofollow" );
+ $wgOut->setArticleRelated( false );
+ $wgOut->enableClientCache( false );
+ $wgOut->addWikiText( $error->getMessage() );
+ }
}
?>
header('Content-type: application/x-wiki');
}
+ global $wgContLanguageCode;
+ header( "Content-Disposition: inline;filename*=utf-8'$wgContLanguageCode'" . urlencode( basename( $fname ) ) );
+
if ( !empty( $_SERVER['HTTP_IF_MODIFIED_SINCE'] ) ) {
$modsince = preg_replace( '/;.*$/', '', $_SERVER['HTTP_IF_MODIFIED_SINCE'] );
$sinceTime = strtotime( $modsince );
--- /dev/null
+<?php
+
+/**
+ * @addtogroup Media
+ */
+class ArchivedFile
+{
+ /**
+ * Returns a file object from the filearchive table
+ * In the future, all current and old image storage
+ * may use FileStore. There will be a "old" storage
+ * for current and previous file revisions as well as
+ * the "deleted" group for archived revisions
+ * @param $title, the corresponding image page title
+ * @param $id, the image id, a unique key
+ * @param $key, optional storage key
+ * @return ResultWrapper
+ */
+ function ArchivedFile( $title, $id=0, $key='' ) {
+ if( !is_object( $title ) ) {
+ throw new MWException( 'ArchivedFile constructor given bogus title.' );
+ }
+ $conds = ($id) ? "fa_id = $id" : "fa_storage_key = '$key'";
+ if( $title->getNamespace() == NS_IMAGE ) {
+ $dbr = wfGetDB( DB_SLAVE );
+ $res = $dbr->select( 'filearchive',
+ array(
+ 'fa_id',
+ 'fa_name',
+ 'fa_storage_key',
+ 'fa_storage_group',
+ 'fa_size',
+ 'fa_bits',
+ 'fa_width',
+ 'fa_height',
+ 'fa_metadata',
+ 'fa_media_type',
+ 'fa_major_mime',
+ 'fa_minor_mime',
+ 'fa_description',
+ 'fa_user',
+ 'fa_user_text',
+ 'fa_timestamp',
+ 'fa_deleted' ),
+ array(
+ 'fa_name' => $title->getDbKey(),
+ $conds ),
+ __METHOD__,
+ array( 'ORDER BY' => 'fa_timestamp DESC' ) );
+
+ if ( $dbr->numRows( $res ) == 0 ) {
+ // this revision does not exist?
+ return;
+ }
+ $ret = $dbr->resultObject( $res );
+ $row = $ret->fetchObject();
+
+ // initialize fields for filestore image object
+ $this->mId = intval($row->fa_id);
+ $this->mName = $row->fa_name;
+ $this->mGroup = $row->fa_storage_group;
+ $this->mKey = $row->fa_storage_key;
+ $this->mSize = $row->fa_size;
+ $this->mBits = $row->fa_bits;
+ $this->mWidth = $row->fa_width;
+ $this->mHeight = $row->fa_height;
+ $this->mMetaData = $row->fa_metadata;
+ $this->mMime = "$row->fa_major_mime/$row->fa_minor_mime";
+ $this->mType = $row->fa_media_type;
+ $this->mDescription = $row->fa_description;
+ $this->mUser = $row->fa_user;
+ $this->mUserText = $row->fa_user_text;
+ $this->mTimestamp = $row->fa_timestamp;
+ $this->mDeleted = $row->fa_deleted;
+ } else {
+ throw new MWException( 'This title does not correspond to an image page.' );
+ return;
+ }
+ return true;
+ }
+
+ /**
+ * int $field one of DELETED_* bitfield constants
+ * for file or revision rows
+ * @return bool
+ */
+ function isDeleted( $field ) {
+ return ($this->mDeleted & $field) == $field;
+ }
+
+ /**
+ * Determine if the current user is allowed to view a particular
+ * field of this FileStore image file, if it's marked as deleted.
+ * @param int $field
+ * @return bool
+ */
+ function userCan( $field ) {
+ if( isset($this->mDeleted) && ($this->mDeleted & $field) == $field ) {
+ // images
+ global $wgUser;
+ $permission = ( $this->mDeleted & File::DELETED_RESTRICTED ) == File::DELETED_RESTRICTED
+ ? 'hiderevision'
+ : 'deleterevision';
+ wfDebug( "Checking for $permission due to $field match on $this->mDeleted\n" );
+ return $wgUser->isAllowed( $permission );
+ } else {
+ return true;
+ }
+ }
+}
+
+?>
--- /dev/null
+<?php
+
+/**
+ * A repository for files accessible via the local filesystem. Does not support
+ * database access or registration.
+ */
+
+class FSRepo {
+ const DELETE_SOURCE = 1;
+
+ var $directory, $url, $hashLevels, $thumbScriptUrl, $transformVia404;
+ var $descBaseUrl, $scriptDirUrl, $articleUrl, $fetchDescription;
+ var $fileFactory = array( 'UnregisteredLocalFile', 'newFromTitle' );
+
+ function __construct( $info ) {
+ // Required settings
+ $this->name = $info['name'];
+ $this->directory = $info['directory'];
+ $this->url = $info['url'];
+ $this->hashLevels = $info['hashLevels'];
+ $this->transformVia404 = !empty( $info['transformVia404'] );
+
+ // Optional settings
+ foreach ( array( 'descBaseUrl', 'scriptDirUrl', 'articleUrl', 'fetchDescription',
+ 'thumbScriptUrl' ) as $var )
+ {
+ if ( isset( $info[$var] ) ) {
+ $this->$var = $info[$var];
+ }
+ }
+ }
+
+ /**
+ * Create a new File object from the local repository
+ * @param mixed $title Title object or string
+ * @param mixed $time Time at which the image is supposed to have existed.
+ * 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) ) {
+ $title = Title::makeTitleSafe( NS_IMAGE, $title );
+ if ( !is_object( $title ) ) {
+ return null;
+ }
+ }
+ if ( $time ) {
+ return call_user_func( $this->oldFileFactor, $title, $this, $time );
+ } else {
+ return call_user_func( $this->fileFactory, $title, $this );
+ }
+ }
+
+ /**
+ * Find an instance of the named file that existed at the specified time
+ * Returns false if the file did not exist. Repositories not supporting
+ * version control should return false if the time is specified.
+ *
+ * @param mixed $time 14-character timestamp, or false for the current version
+ */
+ function findFile( $title, $time = false ) {
+ $img = $this->newFile( $title );
+ if ( !$img ) {
+ return false;
+ }
+ if ( $img->exists() && $img->getTimestamp() <= $time ) {
+ return $img;
+ }
+ $img = $this->newFile( $title, $time );
+ if ( $img->exists() ) {
+ return $img;
+ }
+ }
+
+ function getRootDirectory() {
+ return $this->directory;
+ }
+
+ function getRootUrl() {
+ return $this->url;
+ }
+
+ function isHashed() {
+ return (bool)$this->hashLevels;
+ }
+
+ function getThumbScriptUrl() {
+ return $this->thumbScriptUrl;
+ }
+
+ function canTransformVia404() {
+ return $this->transformVia404;
+ }
+
+ function getZonePath( $zone ) {
+ switch ( $zone ) {
+ case 'public':
+ return $this->directory;
+ case 'temp':
+ return "{$this->directory}/temp";
+ case 'deleted':
+ return $GLOBALS['wgFileStore']['deleted']['directory'];
+ default:
+ return false;
+ }
+ }
+
+ function getZoneUrl( $zone ) {
+ switch ( $zone ) {
+ case 'public':
+ return $this->url;
+ case 'temp':
+ return "{$this->url}/temp";
+ case 'deleted':
+ return $GLOBALS['wgFileStore']['deleted']['url'];
+ default:
+ return false;
+ }
+ }
+
+ /**
+ * Get a URL referring to this repository, with the private mwrepo protocol.
+ */
+ function getVirtualUrl( $suffix = false ) {
+ $path = 'mwrepo://';
+ if ( $suffix !== false ) {
+ $path .= '/' . $suffix;
+ }
+ return $path;
+ }
+
+ /**
+ * Get the local path corresponding to a virtual URL
+ */
+ function resolveVirtualUrl( $url ) {
+ if ( substr( $url, 0, 9 ) != 'mwrepo://' ) {
+ throw new MWException( __METHOD__.': unknown protoocl' );
+ }
+
+ $bits = explode( '/', substr( $url, 9 ), 3 );
+ if ( count( $bits ) != 3 ) {
+ throw new MWException( __METHOD__.": invalid mwrepo URL: $url" );
+ }
+ list( $host, $zone, $rel ) = $bits;
+ if ( $host !== '' ) {
+ throw new MWException( __METHOD__.": fetching from a foreign repo is not supported" );
+ }
+ $base = $this->getZonePath( $zone );
+ if ( !$base ) {
+ throw new MWException( __METHOD__.": invalid zone: $zone" );
+ }
+ return $base . '/' . urldecode( $rel );
+ }
+
+ /**
+ * Store a file to a given destination.
+ */
+ function store( $srcPath, $dstZone, $dstRel, $flags = 0 ) {
+ $root = $this->getZonePath( $dstZone );
+ if ( !$root ) {
+ throw new MWException( "Invalid zone: $dstZone" );
+ }
+ $dstPath = "$root/$dstRel";
+
+ if ( !is_dir( dirname( $dstPath ) ) ) {
+ wfMkdirParents( dirname( $dstPath ) );
+ }
+
+ if ( substr( $srcPath, 0, 9 ) == 'mwrepo://' ) {
+ $srcPath = $this->resolveVirtualUrl( $srcPath );
+ }
+
+ if ( $flags & self::DELETE_SOURCE ) {
+ if ( !rename( $srcPath, $dstPath ) ) {
+ return new WikiErrorMsg( 'filerenameerror', wfEscapeWikiText( $srcPath ),
+ wfEscapeWikiText( $dstPath ) );
+ }
+ } else {
+ if ( !copy( $srcPath, $dstPath ) ) {
+ return new WikiErrorMsg( 'filecopyerror', wfEscapeWikiText( $srcPath ),
+ wfEscapeWikiText( $dstPath ) );
+ }
+ }
+ chmod( $dstPath, 0644 );
+ return true;
+ }
+
+ /**
+ * Pick a random name in the temp zone and store a file to it.
+ * Returns the URL, or a WikiError on failure.
+ * @param string $originalName The base name of the file as specified
+ * by the user. The file extension will be maintained.
+ * @param string $srcPath The current location of the file.
+ */
+ function storeTemp( $originalName, $srcPath ) {
+ $dstRel = $this->getHashPath( $originalName ) .
+ gmdate( "YmdHis" ) . '!' . $originalName;
+ $result = $this->store( $srcPath, 'temp', $dstRel );
+ if ( WikiError::isError( $result ) ) {
+ return $result;
+ } else {
+ return $this->getVirtualUrl( "temp/$dstRel" );
+ }
+ }
+
+ function publish( $srcPath, $dstPath, $archivePath, $flags = 0 ) {
+ if ( substr( $srcPath, 0, 9 ) == 'mwrepo://' ) {
+ $srcPath = $this->resolveVirtualUrl( $srcPath );
+ }
+ $dstDir = dirname( $dstPath );
+ if ( !is_dir( $dstDir ) ) wfMkdirParents( $dstDir );
+
+ if( is_file( $dstPath ) ) {
+ $archiveDir = dirname( $archivePath );
+ if ( !is_dir( $archiveDir ) ) wfMkdirParents( $archiveDir );
+ wfSuppressWarnings();
+ $success = rename( $dstPath, $archivePath );
+ wfRestoreWarnings();
+
+ if( ! $success ) {
+ return new WikiErrorMsg( 'filerenameerror', wfEscapeWikiText( $dstPath ),
+ wfEscapeWikiText( $archivePath ) );
+ }
+ else wfDebug(__METHOD__.": moved file $dstPath to $archivePath\n");
+ $status = 'archived';
+ }
+ else {
+ $status = 'new';
+ }
+
+ $error = false;
+ wfSuppressWarnings();
+ if ( $flags & self::DELETE_SOURCE ) {
+ if ( !rename( $srcPath, $dstPath ) ) {
+ $error = new WikiErrorMsg( 'filerenameerror', wfEscapeWikiText( $srcPath ),
+ wfEscapeWikiText( $dstPath ) );
+ }
+ } else {
+ if ( !copy( $srcPath, $dstPath ) ) {
+ $error = new WikiErrorMsg( 'filerenameerror', wfEscapeWikiText( $srcPath ),
+ wfEscapeWikiText( $dstPath ) );
+ }
+ }
+ wfRestoreWarnings();
+
+ if( $error ) {
+ return $error;
+ } else {
+ wfDebug(__METHOD__.": wrote tempfile $srcPath to $dstPath\n");
+ }
+
+ chmod( $dstPath, 0644 );
+ return $status;
+ }
+
+ /**
+ * Get a relative path including trailing slash, e.g. f/fa/
+ * If the repo is not hashed, returns an empty string
+ */
+ function getHashPath( $name ) {
+ if ( $this->isHashed() ) {
+ $hash = md5( $name );
+ $path = '';
+ for ( $i = 1; $i <= $this->hashLevels; $i++ ) {
+ $path .= substr( $hash, 0, $i ) . '/';
+ }
+ return $path;
+ } else {
+ return '';
+ }
+ }
+
+ function getName() {
+ return $this->name;
+ }
+
+ /**
+ * Get the file description page base URL, or false if there isn't one.
+ * @private
+ */
+ function getDescBaseUrl() {
+ if ( is_null( $this->descBaseUrl ) ) {
+ if ( !is_null( $this->articleUrl ) ) {
+ $this->descBaseUrl = str_replace( '$1',
+ urlencode( Namespace::getCanonicalName( NS_IMAGE ) ) . ':', $this->articleUrl );
+ } elseif ( !is_null( $this->scriptDirUrl ) ) {
+ $this->descBaseUrl = $this->scriptDirUrl . '/index.php?title=' .
+ urlencode( Namespace::getCanonicalName( NS_IMAGE ) ) . ':';
+ } else {
+ $this->descBaseUrl = false;
+ }
+ }
+ return $this->descBaseUrl;
+ }
+
+ /**
+ * Get the URL of an image description page. May return false if it is
+ * unknown or not applicable. In general this should only be called by the
+ * File class, since it may return invalid results for certain kinds of
+ * repositories. Use File::getDescriptionUrl() in user code.
+ *
+ * In particular, it uses the article paths as specified to the repository
+ * constructor, whereas local repositories use the local Title functions.
+ */
+ function getDescriptionUrl( $name ) {
+ $base = $this->getDescBaseUrl();
+ if ( $base ) {
+ return $base . wfUrlencode( $name );
+ } else {
+ return false;
+ }
+ }
+
+ /**
+ * Get the URL of the content-only fragment of the description page. For
+ * 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().
+ */
+ function getDescriptionRenderUrl( $name ) {
+ if ( isset( $this->scriptDirUrl ) ) {
+ return $this->scriptDirUrl . '/index.php?title=' .
+ wfUrlencode( Namespace::getCanonicalName( NS_IMAGE ) . ':' . $name ) .
+ '&action=render';
+ } else {
+ $descBase = $this->getDescBaseUrl();
+ if ( $descBase ) {
+ return wfAppendQuery( $descBase . wfUrlencode( $name ), 'action=render' );
+ } else {
+ return false;
+ }
+ }
+ }
+
+ /**
+ * Call a callback function for every file in the repository.
+ * Uses the filesystem even in child classes.
+ */
+ function enumFilesInFS( $callback ) {
+ $numDirs = 1 << ( $this->hashLevels * 4 );
+ for ( $flatIndex = 0; $flatIndex < $numDirs; $flatIndex++ ) {
+ $hexString = sprintf( "%0{$this->hashLevels}x", $flatIndex );
+ $path = $this->directory;
+ for ( $hexPos = 0; $hexPos < $this->hashLevels; $hexPos++ ) {
+ $path .= '/' . substr( $hexString, 0, $hexPos + 1 );
+ }
+ if ( !file_exists( $path ) || !is_dir( $path ) ) {
+ continue;
+ }
+ $dir = opendir( $path );
+ while ( false !== ( $name = readdir( $dir ) ) ) {
+ call_user_func( $callback, $path . '/' . $name );
+ }
+ }
+ }
+
+ /**
+ * Call a callaback function for every file in the repository
+ * May use either the database or the filesystem
+ */
+ function enumFiles( $callback ) {
+ $this->enumFilesInFS( $callback );
+ }
+}
+
+?>
--- /dev/null
+<?php
+
+/**
+ * Base file class. Do not instantiate.
+ *
+ * Implements some public methods and some protected utility functions which
+ * are required by multiple child classes. Contains stub functionality for
+ * unimplemented public methods.
+ *
+ * Stub functions which should be overridden are marked with STUB. Some more
+ * concrete functions are also typically overridden by child classes.
+ *
+ *
+ * NOTE FOR WINDOWS USERS:
+ * To enable EXIF functions, add the folloing lines to the
+ * "Windows extensions" section of php.ini:
+ *
+ * extension=extensions/php_mbstring.dll
+ * extension=extensions/php_exif.dll
+ *
+ * @addtogroup FileRepo
+ */
+class File {
+ const DELETED_FILE = 1;
+ const DELETED_COMMENT = 2;
+ const DELETED_USER = 4;
+ const DELETED_RESTRICTED = 8;
+ const RENDER_NOW = 1;
+
+ const DELETE_SOURCE = 1;
+
+ /**
+ * Some member variables can be lazy-initialised using __get(). The
+ * initialisation function for these variables is always a function named
+ * like getVar(), where Var is the variable name with upper-case first
+ * letter.
+ *
+ * The following variables are initialised in this way in this base class:
+ * name, extension, handler, path, canRender, isSafeFile,
+ * transformScript, hashPath, pageCount, url
+ *
+ * Code within this class should generally use the accessor function
+ * directly, since __get() isn't re-entrant and therefore causes bugs that
+ * depend on initialisation order.
+ */
+
+ /**
+ * The following member variables are not lazy-initialised
+ */
+ var $repo, $title, $lastError;
+
+ function __construct( $title, $repo ) {
+ $this->title = $title;
+ $this->repo = $repo;
+ }
+
+ function __get( $name ) {
+ $function = array( $this, 'get' . ucfirst( $name ) );
+ if ( !is_callable( $function ) ) {
+ return null;
+ } else {
+ $this->$name = call_user_func( $function );
+ return $this->$name;
+ }
+ }
+
+ /**
+ * Normalize a file extension to the common form, and ensure it's clean.
+ * Extensions with non-alphanumeric characters will be discarded.
+ *
+ * @param $ext string (without the .)
+ * @return string
+ */
+ static function normalizeExtension( $ext ) {
+ $lower = strtolower( $ext );
+ $squish = array(
+ 'htm' => 'html',
+ 'jpeg' => 'jpg',
+ 'mpeg' => 'mpg',
+ 'tiff' => 'tif' );
+ if( isset( $squish[$lower] ) ) {
+ return $squish[$lower];
+ } elseif( preg_match( '/^[0-9a-z]+$/', $lower ) ) {
+ return $lower;
+ } else {
+ return '';
+ }
+ }
+
+ /**
+ * Upgrade the database row if there is one
+ * Called by ImagePage
+ * STUB
+ */
+ function upgradeRow() {}
+
+ /**
+ * Split an internet media type into its two components; if not
+ * a two-part name, set the minor type to 'unknown'.
+ *
+ * @param $mime "text/html" etc
+ * @return array ("text", "html") etc
+ */
+ static function splitMime( $mime ) {
+ if( strpos( $mime, '/' ) !== false ) {
+ return explode( '/', $mime, 2 );
+ } else {
+ return array( $mime, 'unknown' );
+ }
+ }
+
+ /**
+ * Return the name of this file
+ * @public
+ */
+ function getName() {
+ if ( !isset( $this->name ) ) {
+ $this->name = $this->title->getDBkey();
+ }
+ return $this->name;
+ }
+
+ /**
+ * Get the file extension, e.g. "svg"
+ */
+ function getExtension() {
+ if ( !isset( $this->extension ) ) {
+ $n = strrpos( $this->getName(), '.' );
+ $this->extension = self::normalizeExtension(
+ $n ? substr( $this->getName(), $n + 1 ) : '' );
+ }
+ return $this->extension;
+ }
+
+ /**
+ * Return the associated title object
+ * @public
+ */
+ function getTitle() { return $this->title; }
+
+ /**
+ * Return the URL of the file
+ * @public
+ */
+ function getUrl() {
+ if ( !isset( $this->url ) ) {
+ $this->url = $this->repo->getZoneUrl( 'public' ) . '/' . $this->getUrlRel();
+ }
+ return $this->url;
+ }
+
+ function getViewURL() {
+ if( $this->mustRender()) {
+ if( $this->canRender() ) {
+ return $this->createThumb( $this->getWidth() );
+ }
+ else {
+ wfDebug(__METHOD__.': supposed to render '.$this->getName().' ('.$this->getMimeType()."), but can't!\n");
+ return $this->getURL(); #hm... return NULL?
+ }
+ } else {
+ return $this->getURL();
+ }
+ }
+
+ /**
+ * Return the full filesystem 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.
+ *
+ * May return false if the file is not locally accessible.
+ *
+ * @public
+ */
+ function getPath() {
+ if ( !isset( $this->path ) ) {
+ $this->path = $this->repo->getZonePath('public') . '/' . $this->getRel();
+ }
+ return $this->path;
+ }
+
+ /**
+ * Alias for getPath()
+ * @public
+ */
+ function getFullPath() {
+ return $this->getPath();
+ }
+
+ /**
+ * Return the width of the image. Returns false if the width is unknown
+ * or undefined.
+ *
+ * STUB
+ * Overridden by LocalFile, UnregisteredLocalFile
+ * @public
+ */
+ function getWidth( $page = 1 ) { return false; }
+
+ /**
+ * Return the height of the image. Returns false if the height is unknown
+ * or undefined
+ *
+ * STUB
+ * Overridden by LocalFile, UnregisteredLocalFile
+ * @public
+ */
+ function getHeight( $page = 1 ) { return false; }
+
+ /**
+ * Get handler-specific metadata
+ * Overridden by LocalFile, UnregisteredLocalFile
+ * STUB
+ */
+ function getMetadata() { return false; }
+
+ /**
+ * Return the size of the image file, in bytes
+ * Overridden by LocalFile, UnregisteredLocalFile
+ * STUB
+ * @public
+ */
+ function getSize() { return false; }
+
+ /**
+ * Returns the mime type of the file.
+ * Overridden by LocalFile, UnregisteredLocalFile
+ * STUB
+ */
+ function getMimeType() { return 'unknown/unknown'; }
+
+ /**
+ * Return the type of the media in the file.
+ * Use the value returned by this function with the MEDIATYPE_xxx constants.
+ * Overridden by LocalFile,
+ * STUB
+ */
+ function getMediaType() { return MEDIATYPE_UNKNOWN; }
+
+ /**
+ * Checks if the file can be presented to the browser as a bitmap.
+ *
+ * Currently, this checks if the file is an image format
+ * that can be converted to a format
+ * supported by all browsers (namely GIF, PNG and JPEG),
+ * or if it is an SVG image and SVG conversion is enabled.
+ */
+ function canRender() {
+ if ( !isset( $this->canRender ) ) {
+ $this->canRender = $this->getHandler() && $this->handler->canRender();
+ }
+ return $this->canRender;
+ }
+
+ /**
+ * Accessor for __get()
+ */
+ protected function getCanRender() {
+ return $this->canRender();
+ }
+
+ /**
+ * Return true if the file is of a type that can't be directly
+ * rendered by typical browsers and needs to be re-rasterized.
+ *
+ * This returns true for everything but the bitmap types
+ * supported by all browsers, i.e. JPEG; GIF and PNG. It will
+ * also return true for any non-image formats.
+ *
+ * @return bool
+ */
+ function mustRender() {
+ return $this->getHandler() && $this->handler->mustRender();
+ }
+
+ /**
+ * Determines if this media file may be shown inline on a page.
+ *
+ * This is currently synonymous to canRender(), but this could be
+ * extended to also allow inline display of other media,
+ * like flash animations or videos. If you do so, please keep in mind that
+ * that could be a security risk.
+ */
+ function allowInlineDisplay() {
+ return $this->canRender();
+ }
+
+ /**
+ * Determines if this media file is in a format that is unlikely to
+ * contain viruses or malicious content. It uses the global
+ * $wgTrustedMediaFormats list to determine if the file is safe.
+ *
+ * This is used to show a warning on the description page of non-safe files.
+ * It may also be used to disallow direct [[media:...]] links to such files.
+ *
+ * Note that this function will always return true if allowInlineDisplay()
+ * or isTrustedFile() is true for this file.
+ */
+ function isSafeFile() {
+ if ( !isset( $this->isSafeFile ) ) {
+ $this->isSafeFile = $this->_getIsSafeFile();
+ }
+ return $this->isSafeFile;
+ }
+
+ /** Accessor for __get() */
+ protected function getIsSafeFile() {
+ return $this->isSafeFile();
+ }
+
+ /** Uncached accessor */
+ protected function _getIsSafeFile() {
+ if ($this->allowInlineDisplay()) return true;
+ if ($this->isTrustedFile()) return true;
+
+ global $wgTrustedMediaFormats;
+
+ $type= $this->getMediaType();
+ $mime= $this->getMimeType();
+ #wfDebug("LocalFile::isSafeFile: type= $type, mime= $mime\n");
+
+ if (!$type || $type===MEDIATYPE_UNKNOWN) return false; #unknown type, not trusted
+ if ( in_array( $type, $wgTrustedMediaFormats) ) return true;
+
+ if ($mime==="unknown/unknown") return false; #unknown type, not trusted
+ if ( in_array( $mime, $wgTrustedMediaFormats) ) return true;
+
+ return false;
+ }
+
+ /** Returns true if the file is flagged as trusted. Files flagged that way
+ * can be linked to directly, even if that is not allowed for this type of
+ * file normally.
+ *
+ * This is a dummy function right now and always returns false. It could be
+ * implemented to extract a flag from the database. The trusted flag could be
+ * set on upload, if the user has sufficient privileges, to bypass script-
+ * and html-filters. It may even be coupled with cryptographics signatures
+ * or such.
+ */
+ function isTrustedFile() {
+ #this could be implemented to check a flag in the databas,
+ #look for signatures, etc
+ return false;
+ }
+
+ /**
+ * Returns true if file exists in the repository.
+ *
+ * Overridden by LocalFile to avoid unnecessary stat calls.
+ *
+ * @return boolean Whether file exists in the repository.
+ * @public
+ */
+ function exists() {
+ return $this->getPath() && file_exists( $this->path );
+ }
+
+ function getTransformScript() {
+ if ( !isset( $this->transformScript ) ) {
+ $this->transformScript = false;
+ if ( $this->repo ) {
+ $script = $this->repo->getThumbScriptUrl();
+ if ( $script ) {
+ $this->transformScript = "$script?f=" . urlencode( $this->getName() );
+ }
+ }
+ }
+ return $this->transformScript;
+ }
+
+ /**
+ * Get a ThumbnailImage which is the same size as the source
+ */
+ function getUnscaledThumb( $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 );
+ }
+
+ /**
+ * Return the file name of a thumbnail with the specified parameters
+ *
+ * @param array $params Handler-specific parameters
+ * @private
+ */
+ function thumbName( $params ) {
+ if ( !$this->getHandler() ) {
+ return null;
+ }
+ $extension = $this->getExtension();
+ list( $thumbExt, $thumbMime ) = $this->handler->getThumbType( $extension, $this->getMimeType() );
+ $thumbName = $this->handler->makeParamString( $params ) . '-' . $this->getName();
+ if ( $thumbExt != $extension ) {
+ $thumbName .= ".$thumbExt";
+ }
+ return $thumbName;
+ }
+
+ /**
+ * Create a thumbnail of the image having the specified width/height.
+ * The thumbnail will not be created if the width is larger than the
+ * image's width. Let the browser do the scaling in this case.
+ * The thumbnail is stored on disk and is only computed if the thumbnail
+ * file does not exist OR if it is older than the image.
+ * Returns the URL.
+ *
+ * Keeps aspect ratio of original image. If both width and height are
+ * 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)
+ * @public
+ */
+ function createThumb( $width, $height = -1 ) {
+ $params = array( 'width' => $width );
+ if ( $height != -1 ) {
+ $params['height'] = $height;
+ }
+ $thumb = $this->transform( $params );
+ if( is_null( $thumb ) || $thumb->isError() ) return '';
+ return $thumb->getUrl();
+ }
+
+ /**
+ * As createThumb, but returns a ThumbnailImage object. This can
+ * provide access to the actual file, the real size of the thumb,
+ * and can produce a convenient <img> tag for you.
+ *
+ * For non-image formats, this may return a filetype-specific icon.
+ *
+ * @param integer $width maximum width of the generated thumbnail
+ * @param integer $height maximum height of the image (optional)
+ * @param boolean $render True to render the thumbnail if it doesn't exist,
+ * false to just return the URL
+ *
+ * @return ThumbnailImage or null on failure
+ * @public
+ *
+ * @deprecated use transform()
+ */
+ function getThumbnail( $width, $height=-1, $render = true ) {
+ $params = array( 'width' => $width );
+ if ( $height != -1 ) {
+ $params['height'] = $height;
+ }
+ $flags = $render ? self::RENDER_NOW : 0;
+ return $this->transform( $params, $flags );
+ }
+
+ /**
+ * 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
+ */
+ function transform( $params, $flags = 0 ) {
+ global $wgUseSquid, $wgIgnoreImageErrors;
+
+ wfProfileIn( __METHOD__ );
+ do {
+ if ( !$this->getHandler() || !$this->handler->canRender() ) {
+ // not a bitmap or renderable image, don't try.
+ $thumb = $this->iconThumb();
+ break;
+ }
+
+ $script = $this->getTransformScript();
+ if ( $script && !($flags & self::RENDER_NOW) ) {
+ // Use a script to transform on client request
+ $thumb = $this->handler->getScriptedTransform( $this, $script, $params );
+ break;
+ }
+
+ $normalisedParams = $params;
+ $this->handler->normaliseParams( $this, $normalisedParams );
+ $thumbName = $this->thumbName( $normalisedParams );
+ $thumbPath = $this->getThumbPath( $thumbName );
+ $thumbUrl = $this->getThumbUrl( $thumbName );
+
+ if ( $this->repo->canTransformVia404() && !($flags & self::RENDER_NOW ) ) {
+ $thumb = $this->handler->getTransform( $this, $thumbPath, $thumbUrl, $params );
+ break;
+ }
+
+ wfDebug( "Doing stat for $thumbPath\n" );
+ $this->migrateThumbFile( $thumbName );
+ if ( file_exists( $thumbPath ) ) {
+ $thumb = $this->handler->getTransform( $this, $thumbPath, $thumbUrl, $params );
+ break;
+ }
+ $thumb = $this->handler->doTransform( $this, $thumbPath, $thumbUrl, $params );
+
+ // Ignore errors if requested
+ if ( !$thumb ) {
+ $thumb = null;
+ } elseif ( $thumb->isError() ) {
+ $this->lastError = $thumb->toText();
+ if ( $wgIgnoreImageErrors && !($flags & self::RENDER_NOW) ) {
+ $thumb = $this->handler->getTransform( $this, $thumbPath, $thumbUrl, $params );
+ }
+ }
+
+ if ( $wgUseSquid ) {
+ wfPurgeSquidServers( array( $thumbUrl ) );
+ }
+ } while (false);
+
+ wfProfileOut( __METHOD__ );
+ return $thumb;
+ }
+
+ /**
+ * Hook into transform() to allow migration of thumbnail files
+ * STUB
+ * Overridden by LocalFile
+ */
+ function migrateThumbFile() {}
+
+ /**
+ * Get a MediaHandler instance for this file
+ */
+ function getHandler() {
+ if ( !isset( $this->handler ) ) {
+ $this->handler = MediaHandler::getHandler( $this->getMimeType() );
+ }
+ return $this->handler;
+ }
+
+ /**
+ * Get a ThumbnailImage representing a file type icon
+ * @return ThumbnailImage
+ */
+ function iconThumb() {
+ global $wgStylePath, $wgStyleDirectory;
+
+ $try = array( 'fileicon-' . $this->getExtension() . '.png', 'fileicon.png' );
+ foreach( $try as $icon ) {
+ $path = '/common/images/icons/' . $icon;
+ $filepath = $wgStyleDirectory . $path;
+ if( file_exists( $filepath ) ) {
+ return new ThumbnailImage( $wgStylePath . $path, 120, 120 );
+ }
+ }
+ return null;
+ }
+
+ /**
+ * Get last thumbnailing error.
+ * Largely obsolete.
+ */
+ function getLastError() {
+ return $this->lastError;
+ }
+
+ /**
+ * Get all thumbnail names previously generated for this file
+ * STUB
+ * Overridden by LocalFile
+ */
+ function getThumbnails() { return array(); }
+
+ /**
+ * Purge shared caches such as thumbnails and DB data caching
+ * STUB
+ * Overridden by LocalFile
+ */
+ function purgeCache( $archiveFiles = array() ) {}
+
+ /**
+ * Purge the file description page, but don't go after
+ * pages using the file. Use when modifying file history
+ * but not the current data.
+ */
+ function purgeDescription() {
+ $title = $this->getTitle();
+ if ( $title ) {
+ $title->invalidateCache();
+ $title->purgeSquid();
+ }
+ }
+
+ /**
+ * Purge metadata and all affected pages when the file is created,
+ * deleted, or majorly updated. A set of additional URLs may be
+ * passed to purge, such as specific file files which have changed.
+ * @param $urlArray array
+ */
+ function purgeEverything( $urlArr=array() ) {
+ // Delete thumbnails and refresh file metadata cache
+ $this->purgeCache();
+ $this->purgeDescription();
+
+ // Purge cache of all pages using this file
+ $title = $this->getTitle();
+ if ( $title ) {
+ $update = new HTMLCacheUpdate( $title, 'imagelinks' );
+ $update->doUpdate();
+ }
+ }
+
+ /**
+ * Return the history of this file, line by line. Starts with current version,
+ * then old versions. Should return an object similar to an image/oldimage
+ * database row.
+ *
+ * @public
+ * STUB
+ * Overridden in LocalFile
+ */
+ function nextHistoryLine() {
+ return false;
+ }
+
+ /**
+ * Reset the history pointer to the first element of the history
+ * @public
+ * STUB
+ * Overridden in LocalFile.
+ */
+ function resetHistory() {}
+
+ /**
+ * Get the filename hash component of the directory including trailing slash,
+ * e.g. f/fa/
+ * If the repository is not hashed, returns an empty string.
+ */
+ function getHashPath() {
+ if ( !isset( $this->hashPath ) ) {
+ $this->hashPath = $this->repo->getHashPath( $this->getName() );
+ }
+ return $this->hashPath;
+ }
+
+ /**
+ * Get the path of the file relative to the public zone root
+ */
+ function getRel() {
+ return $this->getHashPath() . $this->getName();
+ }
+
+ /**
+ * Get urlencoded relative path of the file
+ */
+ function getUrlRel() {
+ return $this->getHashPath() . urlencode( $this->getName() );
+ }
+
+ /** Get the path of the archive directory, or a particular file if $suffix is specified */
+ function getArchivePath( $suffix = false ) {
+ $path = $this->repo->getZonePath('public') . '/archive/' . $this->getHashPath();
+ if ( $suffix !== false ) {
+ $path .= '/' . $suffix;
+ }
+ return $path;
+ }
+
+ /** Get the path of the thumbnail directory, or a particular file if $suffix is specified */
+ function getThumbPath( $suffix = false ) {
+ $path = $this->repo->getZonePath('public') . '/thumb/' . $this->getRel();
+ if ( $suffix !== false ) {
+ $path .= '/' . $suffix;
+ }
+ return $path;
+ }
+
+ /** Get the URL of the archive directory, or a particular file if $suffix is specified */
+ function getArchiveUrl( $suffix = false ) {
+ $path = $this->repo->getZoneUrl('public') . '/archive/' . $this->getHashPath();
+ if ( $suffix !== false ) {
+ $path .= '/' . urlencode( $suffix );
+ }
+ return $path;
+ }
+
+ /** Get the URL of the thumbnail directory, or a particular file if $suffix is specified */
+ function getThumbUrl( $suffix = false ) {
+ $path = $this->repo->getZoneUrl('public') . '/thumb/' . $this->getUrlRel();
+ if ( $suffix !== false ) {
+ $path .= '/' . urlencode( $suffix );
+ }
+ return $path;
+ }
+
+ /** Get the virtual URL for an archive file or directory */
+ function getArchiveVirtualUrl( $suffix = false ) {
+ $path = $this->repo->getVirtualUrl() . '/public/archive/' . $this->getHashPath();
+ if ( $suffix !== false ) {
+ $path .= '/' . urlencode( $suffix );
+ }
+ return $path;
+ }
+
+ /** Get the virtual URL for a thumbnail file or directory */
+ function getThumbVirtualUrl( $suffix = false ) {
+ $path = $this->repo->getVirtualUrl() . '/public/thumb/' . $this->getHashPath();
+ if ( $suffix !== false ) {
+ $path .= '/' . urlencode( $suffix );
+ }
+ return $path;
+ }
+
+ /**
+ * @return bool
+ */
+ function isHashed() {
+ return $this->repo->isHashed();
+ }
+
+ function readOnlyError() {
+ throw new MWException( get_class($this) . ': write operations are not supported' );
+ }
+
+ /**
+ * Record a file upload in the upload log and the image table
+ * STUB
+ * Overridden by LocalFile
+ */
+ function recordUpload( $oldver, $desc, $license = '', $copyStatus = '', $source = '', $watch = false ) {
+ $this->readOnlyError();
+ }
+
+ /**
+ * 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.
+ *
+ * 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:
+ * 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.
+ *
+ * STUB
+ * Overridden by LocalFile
+ */
+ function publish( $srcPath, $flags = 0 ) {
+ $this->readOnlyError();
+ }
+
+ /**
+ * Get an array of Title objects which are articles which use this file
+ * Also adds their IDs to the link cache
+ *
+ * This is mostly copied from Title::getLinksTo()
+ *
+ * @deprecated Use HTMLCacheUpdate, this function uses too much memory
+ */
+ function getLinksTo( $options = '' ) {
+ wfProfileIn( __METHOD__ );
+
+ // Note: use local DB not repo DB, we want to know local links
+ if ( $options ) {
+ $db = wfGetDB( DB_MASTER );
+ } else {
+ $db = wfGetDB( DB_SLAVE );
+ }
+ $linkCache =& LinkCache::singleton();
+
+ list( $page, $imagelinks ) = $db->tableNamesN( 'page', 'imagelinks' );
+ $encName = $db->addQuotes( $this->getName() );
+ $sql = "SELECT page_namespace,page_title,page_id FROM $page,$imagelinks WHERE page_id=il_from AND il_to=$encName $options";
+ $res = $db->query( $sql, __METHOD__ );
+
+ $retVal = array();
+ if ( $db->numRows( $res ) ) {
+ while ( $row = $db->fetchObject( $res ) ) {
+ if ( $titleObj = Title::makeTitle( $row->page_namespace, $row->page_title ) ) {
+ $linkCache->addGoodLinkObj( $row->page_id, $titleObj );
+ $retVal[] = $titleObj;
+ }
+ }
+ }
+ $db->freeResult( $res );
+ wfProfileOut( __METHOD__ );
+ return $retVal;
+ }
+
+ function getExifData() {
+ if ( !$this->getHandler() || $this->handler->getMetadataType( $this ) != 'exif' ) {
+ return array();
+ }
+ $metadata = $this->getMetadata();
+ if ( !$metadata ) {
+ return array();
+ }
+ $exif = unserialize( $metadata );
+ if ( !$exif ) {
+ return array();
+ }
+ unset( $exif['MEDIAWIKI_EXIF_VERSION'] );
+ $format = new FormatExif( $exif );
+
+ return $format->getFormattedData();
+ }
+
+ /**
+ * Returns true if the file comes from the local file repository.
+ *
+ * @return bool
+ */
+ function isLocal() {
+ return $this->repo && $this->repo->getName() == 'local';
+ }
+
+ /**
+ * Returns true if the image is an old version
+ * STUB
+ */
+ function isOld() {
+ return false;
+ }
+
+ /**
+ * Is this file a "deleted" file in a private archive?
+ * STUB
+ */
+ function isDeleted( $field ) {
+ return false;
+ }
+
+ /**
+ * Was this file ever deleted from the wiki?
+ *
+ * @return bool
+ */
+ function wasDeleted() {
+ $title = $this->getTitle();
+ return $title && $title->isDeleted() > 0;
+ }
+
+ /**
+ * Delete all versions of the file.
+ *
+ * Moves the files into an archive directory (or deletes them)
+ * and removes the database rows.
+ *
+ * Cache purging is done; logging is caller's responsibility.
+ *
+ * @param $reason
+ * @return true on success, false on some kind of failure
+ * STUB
+ * Overridden by LocalFile
+ */
+ function delete( $reason, $suppress=false ) {
+ $this->readOnlyError();
+ }
+
+ /**
+ * Restore all or specified deleted revisions to the given file.
+ * Permissions and logging are left to the caller.
+ *
+ * May throw database exceptions on error.
+ *
+ * @param $versions set of record ids of deleted items to restore,
+ * or empty to restore all revisions.
+ * @return the number of file revisions restored if successful,
+ * or false on failure
+ * STUB
+ * Overridden by LocalFile
+ */
+ function restore( $versions=array(), $Unsuppress=false ) {
+ $this->readOnlyError();
+ }
+
+ /**
+ * Returns 'true' if this image is a multipage document, e.g. a DJVU
+ * document.
+ *
+ * @return Bool
+ */
+ function isMultipage() {
+ return $this->getHandler() && $this->handler->isMultiPage();
+ }
+
+ /**
+ * Returns the number of pages of a multipage document, or NULL for
+ * documents which aren't multipage documents
+ */
+ function pageCount() {
+ if ( !isset( $this->pageCount ) ) {
+ if ( $this->getHandler() && $this->handler->isMultiPage() ) {
+ $this->pageCount = $this->handler->pageCount( $this );
+ } else {
+ $this->pageCount = false;
+ }
+ }
+ return $this->pageCount;
+ }
+
+ /**
+ * Calculate the height of a thumbnail using the source and destination width
+ */
+ static function scaleHeight( $srcWidth, $srcHeight, $dstWidth ) {
+ // Exact integer multiply followed by division
+ if ( $srcWidth == 0 ) {
+ return 0;
+ } else {
+ return round( $srcHeight * $dstWidth / $srcWidth );
+ }
+ }
+
+ /**
+ * 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
+ */
+ function getImageSize( $fileName ) {
+ if ( !$this->getHandler() ) {
+ return false;
+ }
+ return $this->handler->getImageSize( $this, $fileName );
+ }
+
+ /**
+ * Get the URL of the image description page. May return false if it is
+ * unknown or not applicable.
+ */
+ function getDescriptionUrl() {
+ return $this->repo->getDescriptionUrl( $this->getName() );
+ }
+
+ /**
+ * Get the HTML text of the description page, if available
+ */
+ function getDescriptionText() {
+ if ( !$this->repo->fetchDescription ) {
+ return false;
+ }
+ $renderUrl = $this->repo->getDescriptionRenderUrl( $this->getName() );
+ if ( $renderUrl ) {
+ wfDebug( "Fetching shared description from $renderUrl\n" );
+ return Http::get( $renderUrl );
+ } else {
+ return false;
+ }
+ }
+
+ /**
+ * Get the 14-character timestamp of the file upload, or false if
+ */
+ function getTimestmap() {
+ $path = $this->getPath();
+ if ( !file_exists( $path ) ) {
+ return false;
+ }
+ return wfTimestamp( filemtime( $path ) );
+ }
+
+ /**
+ * 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
+ */
+ function userCan( $field ) {
+ return true;
+ }
+}
+
+?>
--- /dev/null
+<?php
+
+class ForeignDBFile extends LocalFile {
+ function newFromTitle( $title, $repo ) {
+ return new self( $title, $repo );
+ }
+
+ function getCacheKey() {
+ if ( $this->repo->hasSharedCache ) {
+ $hashedName = md5($this->name);
+ return wfForeignMemcKey( $this->repo->dbName, $this->repo->tablePrefix,
+ 'file', $hashedName );
+ } else {
+ return false;
+ }
+ }
+
+ function publish( /*...*/ ) {
+ $this->readOnlyError();
+ }
+
+ function recordUpload( /*...*/ ) {
+ $this->readOnlyError();
+ }
+ function restore( /*...*/ ) {
+ $this->readOnlyError();
+ }
+
+ function getDescriptionUrl() {
+ // Restore remote behaviour
+ return File::getDescriptionUrl();
+ }
+
+ function getDescriptionText() {
+ // Restore remote behaviour
+ return File::getDescriptionText();
+ }
+}
+?>
--- /dev/null
+<?php
+
+/**
+ * A foreign repository with an accessible MediaWiki database
+ */
+
+class ForeignDBRepo extends LocalRepo {
+ # Settings
+ var $dbType, $dbServer, $dbUser, $dbPassword, $dbName, $dbFlags,
+ $tablePrefix, $hasSharedCache;
+
+ # Other stuff
+ var $dbConn;
+ var $fileFactory = array( 'ForeignDBFile', 'newFromTitle' );
+
+ function __construct( $info ) {
+ parent::__construct( $info );
+ $this->dbType = $info['dbType'];
+ $this->dbServer = $info['dbServer'];
+ $this->dbUser = $info['dbUser'];
+ $this->dbPassword = $info['dbPassword'];
+ $this->dbName = $info['dbName'];
+ $this->dbFlags = $info['dbFlags'];
+ $this->tablePrefix = $info['tablePrefix'];
+ $this->hasSharedCache = $info['hasSharedCache'];
+ }
+
+ 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 );
+ }
+ return $this->dbConn;
+ }
+
+ function getSlaveDB() {
+ return $this->getMasterDB();
+ }
+
+ function hasSharedCache() {
+ return $this->hasSharedCache;
+ }
+
+ function store( /*...*/ ) {
+ throw new MWException( get_class($this) . ': write operations are not supported' );
+ }
+}
+
+?>
--- /dev/null
+<?php
+/**
+ */
+
+/**
+ * Bump this number when serialized cache records may be incompatible.
+ */
+define( 'MW_FILE_VERSION', 4 );
+
+/**
+ * Class to represent a local file in the wiki's own database
+ *
+ * Provides methods to retrieve paths (physical, logical, URL),
+ * to generate image thumbnails or for uploading.
+ *
+ * @addtogroup FileRepo
+ */
+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)
+ $width, # \
+ $height, # |
+ $bits, # --- returned by getimagesize (loadFromXxx)
+ $attr, # /
+ $media_type, # MEDIATYPE_xxx (bitmap, drawing, audio...)
+ $mime, # MIME type, determined by MimeMagic::guessMimeType
+ $major_mime, # Major mime type
+ $minor_mine, # Minor mime type
+ $size, # Size in bytes (loadFromXxx)
+ $metadata, # Metadata
+ $timestamp, # Upload timestamp
+ $dataLoaded, # Whether or not all this has been loaded from the database (loadFromXxx)
+ $upgraded; # Whether the row was upgraded on load
+
+ /**#@-*/
+
+ function newFromTitle( $title, $repo ) {
+ return new self( $title, $repo );
+ }
+
+ function newFromRow( $row, $repo ) {
+ $title = Title::makeTitle( NS_IMAGE, $row->img_name );
+ $file = new self( $title, $repo );
+ $file->loadFromRow( $row );
+ return $file;
+ }
+
+ function __construct( $title, $repo ) {
+ if( !is_object( $title ) ) {
+ throw new MWException( __CLASS__.' constructor given bogus title.' );
+ }
+ parent::__construct( $title, $repo );
+ $this->metadata = '';
+ $this->historyLine = 0;
+ $this->dataLoaded = false;
+ }
+
+ /**
+ * Get the memcached key
+ */
+ function getCacheKey() {
+ $hashedName = md5($this->getName());
+ return wfMemcKey( 'file', $hashedName );
+ }
+
+ /**
+ * Try to load file metadata from memcached. Returns true on success.
+ */
+ function loadFromCache() {
+ global $wgMemc;
+ wfProfileIn( __METHOD__ );
+ $this->dataLoaded = false;
+ $key = $this->getCacheKey();
+ if ( !$key ) {
+ return false;
+ }
+ $cachedValues = $wgMemc->get( $key );
+
+ // Check if the key existed and belongs to this version of MediaWiki
+ if ( isset($cachedValues['version']) && ( $cachedValues['version'] == MW_FILE_VERSION ) ) {
+ wfDebug( "Pulling file metadata from cache key $key\n" );
+ $this->fileExists = $cachedValues['fileExists'];
+ if ( $this->fileExists ) {
+ unset( $cachedValues['version'] );
+ unset( $cachedValues['fileExists'] );
+ foreach ( $cachedValues as $name => $value ) {
+ $this->$name = $value;
+ }
+ }
+ }
+ if ( $this->dataLoaded ) {
+ wfIncrStats( 'image_cache_hit' );
+ } else {
+ wfIncrStats( 'image_cache_miss' );
+ }
+
+ wfProfileOut( __METHOD__ );
+ return $this->dataLoaded;
+ }
+
+ /**
+ * Save the file metadata to memcached
+ */
+ 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;
+ }
+ }
+
+ $wgMemc->set( $key, $cache, 60 * 60 * 24 * 7 ); // A week
+ }
+
+ /**
+ * Load metadata from the file itself
+ */
+ function loadFromFile() {
+ wfProfileIn( __METHOD__ );
+ $path = $this->getPath();
+ $this->fileExists = file_exists( $path );
+ $gis = array();
+
+ if ( $this->fileExists ) {
+ $magic=& MimeMagic::singleton();
+
+ $this->mime = $magic->guessMimeType($path,true);
+ list( $this->major_mime, $this->minor_mime ) = self::splitMime( $this->mime );
+ $this->media_type = $magic->getMediaType($path,$this->mime);
+ $handler = MediaHandler::getHandler( $this->mime );
+
+ # Get size in bytes
+ $this->size = filesize( $path );
+
+ # Height, width and metadata
+ if ( $handler ) {
+ $gis = $handler->getImageSize( $this, $path );
+ $this->metadata = $handler->getMetadata( $this, $path );
+ } else {
+ $gis = false;
+ $this->metadata = '';
+ }
+
+ wfDebug(__METHOD__.": $path loaded, {$this->size} bytes, {$this->mime}.\n");
+ } else {
+ $this->mime = NULL;
+ $this->media_type = MEDIATYPE_UNKNOWN;
+ $this->metadata = '';
+ wfDebug(__METHOD__.": $path NOT FOUND!\n");
+ }
+
+ if( $gis ) {
+ $this->width = $gis[0];
+ $this->height = $gis[1];
+ } else {
+ $this->width = 0;
+ $this->height = 0;
+ }
+
+ #NOTE: $gis[2] contains a code for the image type. This is no longer used.
+
+ #NOTE: we have to set this flag early to avoid load() to be called
+ # be some of the functions below. This may lead to recursion or other bad things!
+ # as ther's only one thread of execution, this should be safe anyway.
+ $this->dataLoaded = true;
+
+ if ( isset( $gis['bits'] ) ) $this->bits = $gis['bits'];
+ else $this->bits = 0;
+
+ wfProfileOut( __METHOD__ );
+ }
+
+ function getCacheFields( $prefix = 'img_' ) {
+ static $fields = array( 'size', 'width', 'height', 'bits', 'media_type',
+ 'major_mime', 'minor_mime', 'metadata', 'timestamp' );
+ static $results = array();
+ if ( $prefix == '' ) {
+ return $fields;
+ }
+ if ( !isset( $results[$prefix] ) ) {
+ $prefixedFields = array();
+ foreach ( $fields as $field ) {
+ $prefixedFields[] = $prefix . $field;
+ }
+ $results[$prefix] = $prefixedFields;
+ }
+ return $results[$prefix];
+ }
+
+ /**
+ * Load file metadata from the DB
+ */
+ function loadFromDB() {
+ wfProfileIn( __METHOD__ );
+
+ # Unconditionally set loaded=true, we don't want the accessors constantly rechecking
+ $this->dataLoaded = true;
+
+ $dbr = $this->repo->getSlaveDB();
+
+ $row = $dbr->selectRow( 'image', $this->getCacheFields( 'img_' ),
+ array( 'img_name' => $this->getName() ), __METHOD__ );
+ if ( $row ) {
+ $this->loadFromRow( $row );
+ } else {
+ $this->fileExists = false;
+ }
+
+ wfProfileOut( __METHOD__ );
+ }
+
+ /**
+ * Decode a row from the database (either object or array) to an array
+ * with timestamps and MIME types decoded, and the field prefix removed.
+ */
+ 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 ) {
+ $deprefixedName = substr( $name, $prefixLength );
+ $decoded[substr( $name, $prefixLength )] = $value;
+ }
+ $decoded['timestamp'] = wfTimestamp( TS_MW, $decoded['timestamp'] );
+ if ( empty( $decoded['major_mime'] ) ) {
+ $decoded['mime'] = "unknown/unknown";
+ } else {
+ if (!$decoded['minor_mime']) {
+ $decoded['minor_mime'] = "unknown";
+ }
+ $decoded['mime'] = $decoded['major_mime'].'/'.$decoded['minor_mime'];
+ }
+ return $decoded;
+ }
+
+ /*
+ * Load file metadata from a DB result row
+ */
+ function loadFromRow( $row, $prefix = 'img_' ) {
+ $array = $this->decodeRow( $row, $prefix );
+ foreach ( $array as $name => $value ) {
+ $this->$name = $value;
+ }
+ $this->fileExists = true;
+ // Check for rows from a previous schema, quietly upgrade them
+ $this->maybeUpgradeRow();
+ }
+
+ /**
+ * Load file metadata from cache or DB, unless already loaded
+ */
+ function load() {
+ if ( !$this->dataLoaded ) {
+ if ( !$this->loadFromCache() ) {
+ $this->loadFromDB();
+ $this->saveToCache();
+ }
+ $this->dataLoaded = true;
+ }
+ }
+
+ /**
+ * Upgrade a row if it needs it
+ */
+ function maybeUpgradeRow() {
+ if ( wfReadOnly() ) {
+ return;
+ }
+ if ( is_null($this->media_type) || $this->mime == 'image/svg' ) {
+ $this->upgradeRow();
+ $this->upgraded = true;
+ } else {
+ $handler = $this->getHandler();
+ if ( $handler && !$handler->isMetadataValid( $this, $this->metadata ) ) {
+ $this->upgradeRow();
+ $this->upgraded = true;
+ }
+ }
+ }
+
+ function getUpgraded() {
+ return $this->upgraded;
+ }
+
+ /**
+ * Fix assorted version-related problems with the image row by reloading it from the file
+ */
+ function upgradeRow() {
+ wfProfileIn( __METHOD__ );
+
+ $this->loadFromFile();
+
+ $dbw = $this->repo->getMasterDB();
+ list( $major, $minor ) = self::splitMime( $this->mime );
+
+ wfDebug(__METHOD__.': upgrading '.$this->getName()." to the current schema\n");
+
+ $dbw->update( 'image',
+ array(
+ 'img_width' => $this->width,
+ 'img_height' => $this->height,
+ 'img_bits' => $this->bits,
+ 'img_media_type' => $this->media_type,
+ 'img_major_mime' => $major,
+ 'img_minor_mime' => $minor,
+ 'img_metadata' => $this->metadata,
+ ), array( 'img_name' => $this->getName() ),
+ __METHOD__
+ );
+ $this->saveToCache();
+ wfProfileOut( __METHOD__ );
+ }
+
+ /** splitMime inherited */
+ /** getName inherited */
+ /** getTitle inherited */
+ /** getURL inherited */
+ /** getViewURL inherited */
+ /** getPath inherited */
+
+ /**
+ * Return the width of the image
+ *
+ * Returns false on error
+ * @public
+ */
+ function getWidth( $page = 1 ) {
+ $this->load();
+ if ( $this->isMultipage() ) {
+ $dim = $this->getHandler()->getPageDimensions( $this, $page );
+ if ( $dim ) {
+ return $dim['width'];
+ } else {
+ return false;
+ }
+ } else {
+ return $this->width;
+ }
+ }
+
+ /**
+ * Return the height of the image
+ *
+ * Returns false on error
+ * @public
+ */
+ function getHeight( $page = 1 ) {
+ $this->load();
+ if ( $this->isMultipage() ) {
+ $dim = $this->getHandler()->getPageDimensions( $this, $page );
+ if ( $dim ) {
+ return $dim['height'];
+ } else {
+ return false;
+ }
+ } else {
+ return $this->height;
+ }
+ }
+
+ /**
+ * Get handler-specific metadata
+ */
+ function getMetadata() {
+ $this->load();
+ return $this->metadata;
+ }
+
+ /**
+ * Return the size of the image file, in bytes
+ * @public
+ */
+ function getSize() {
+ $this->load();
+ return $this->size;
+ }
+
+ /**
+ * Returns the mime type of the file.
+ */
+ function getMimeType() {
+ $this->load();
+ return $this->mime;
+ }
+
+ /**
+ * Return the type of the media in the file.
+ * Use the value returned by this function with the MEDIATYPE_xxx constants.
+ */
+ function getMediaType() {
+ $this->load();
+ return $this->media_type;
+ }
+
+ /** canRender inherited */
+ /** mustRender inherited */
+ /** allowInlineDisplay inherited */
+ /** isSafeFile inherited */
+ /** isTrustedFile inherited */
+
+ /**
+ * Returns true if the file file exists on disk.
+ * @return boolean Whether file file exist on disk.
+ * @public
+ */
+ function exists() {
+ $this->load();
+ return $this->fileExists;
+ }
+
+ /** getTransformScript inherited */
+ /** getUnscaledThumb inherited */
+ /** thumbName inherited */
+ /** createThumb inherited */
+ /** getThumbnail inherited */
+ /** transform inherited */
+
+ /**
+ * Fix thumbnail files from 1.4 or before, with extreme prejudice
+ */
+ 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
+ // Rename to broken-*
+ for ( $i = 0; $i < 100 ; $i++ ) {
+ $broken = $this->repo->getZonePath('public') . "/broken-$i-$thumbName";
+ if ( !file_exists( $broken ) ) {
+ rename( $thumbPath, $broken );
+ break;
+ }
+ }
+ // Doesn't exist anymore
+ clearstatcache();
+ }
+ if ( is_file( $thumbDir ) ) {
+ // File where directory should be
+ unlink( $thumbDir );
+ // Doesn't exist anymore
+ clearstatcache();
+ }
+ }
+
+ /** getHandler inherited */
+ /** iconThumb inherited */
+ /** getLastError inherited */
+
+ /**
+ * Get all thumbnail names previously generated for this file
+ */
+ function getThumbnails() {
+ if ( $this->isHashed() ) {
+ $this->load();
+ $files = array();
+ $dir = $this->getThumbPath();
+
+ if ( is_dir( $dir ) ) {
+ $handle = opendir( $dir );
+
+ if ( $handle ) {
+ while ( false !== ( $file = readdir($handle) ) ) {
+ if ( $file{0} != '.' ) {
+ $files[] = $file;
+ }
+ }
+ closedir( $handle );
+ }
+ }
+ } else {
+ $files = array();
+ }
+
+ return $files;
+ }
+
+ /**
+ * Refresh metadata in memcached, but don't touch thumbnails or squid
+ */
+ function purgeMetadataCache() {
+ clearstatcache();
+ $this->loadFromFile();
+ $this->saveToCache();
+ }
+
+ /**
+ * Delete all previously generated thumbnails, refresh metadata in memcached and purge the squid
+ */
+ function purgeCache( $archiveFiles = array() ) {
+ global $wgUseSquid;
+
+ // Refresh metadata cache
+ $this->purgeMetadataCache();
+
+ // Delete thumbnails
+ $files = $this->getThumbnails();
+ $dir = $this->getThumbPath();
+ $urls = array();
+ foreach ( $files as $file ) {
+ $m = array();
+ # Check that the base file name is part of the thumb name
+ # This is a basic sanity check to avoid erasing unrelated directories
+ if ( strpos( $file, $this->getName() ) !== false ) {
+ $url = $this->getThumbUrl( $file );
+ $urls[] = $url;
+ @unlink( "$dir/$file" );
+ }
+ }
+
+ // Purge the squid
+ if ( $wgUseSquid ) {
+ $urls[] = $this->getURL();
+ foreach ( $archiveFiles as $file ) {
+ $urls[] = $this->getArchiveUrl( $file );
+ }
+ wfPurgeSquidServers( $urls );
+ }
+ }
+
+ /** purgeDescription inherited */
+ /** purgeEverything inherited */
+
+ /**
+ * Return the history of this file, line by line.
+ * starts with current version, then old versions.
+ * uses $this->historyLine to check which line to return:
+ * 0 return line for current version
+ * 1 query for old versions, return first one
+ * 2, ... return next old version from above query
+ *
+ * @public
+ */
+ function nextHistoryLine() {
+ $dbr = $this->repo->getSlaveDB();
+
+ if ( $this->historyLine == 0 ) {// called for the first time, return line from cur
+ $this->historyRes = $dbr->select( 'image',
+ array(
+ 'img_size',
+ 'img_description',
+ 'img_user','img_user_text',
+ 'img_timestamp',
+ 'img_width',
+ 'img_height',
+ "'' AS oi_archive_name"
+ ),
+ array( 'img_name' => $this->title->getDBkey() ),
+ __METHOD__
+ );
+ if ( 0 == $dbr->numRows( $this->historyRes ) ) {
+ return FALSE;
+ }
+ } else if ( $this->historyLine == 1 ) {
+ $this->historyRes = $dbr->select( 'oldimage',
+ array(
+ 'oi_size AS img_size',
+ 'oi_description AS img_description',
+ 'oi_user AS img_user',
+ 'oi_user_text AS img_user_text',
+ 'oi_timestamp AS img_timestamp',
+ 'oi_width as img_width',
+ 'oi_height as img_height',
+ 'oi_archive_name'
+ ),
+ array( 'oi_name' => $this->title->getDBkey() ),
+ __METHOD__,
+ array( 'ORDER BY' => 'oi_timestamp DESC' )
+ );
+ }
+ $this->historyLine ++;
+
+ return $dbr->fetchObject( $this->historyRes );
+ }
+
+ /**
+ * Reset the history pointer to the first element of the history
+ * @public
+ */
+ function resetHistory() {
+ $this->historyLine = 0;
+ }
+
+ /** getFullPath inherited */
+ /** getHashPath inherited */
+ /** getRel inherited */
+ /** getUrlRel inherited */
+ /** getArchivePath inherited */
+ /** getThumbPath inherited */
+ /** getArchiveUrl inherited */
+ /** getThumbUrl inherited */
+ /** getArchiveVirtualUrl inherited */
+ /** getThumbVirtualUrl inherited */
+ /** isHashed inherited */
+
+ /**
+ * Record a file upload in the upload log and the image table
+ */
+ function recordUpload( $oldver, $desc, $license = '', $copyStatus = '', $source = '',
+ $watch = false, $timestamp = false )
+ {
+ global $wgUser, $wgUseCopyrightUpload;
+
+ $dbw = $this->repo->getMasterDB();
+
+ // Delete thumbnails and refresh the metadata cache
+ $this->purgeCache();
+
+ // Fail now if the file isn't there
+ if ( !$this->fileExists ) {
+ wfDebug( __METHOD__.": File ".$this->getPath()." went missing!\n" );
+ return false;
+ }
+
+ if ( $wgUseCopyrightUpload ) {
+ if ( $license != '' ) {
+ $licensetxt = '== ' . wfMsgForContent( 'license' ) . " ==\n" . '{{' . $license . '}}' . "\n";
+ }
+ $textdesc = '== ' . wfMsg ( 'filedesc' ) . " ==\n" . $desc . "\n" .
+ '== ' . wfMsgForContent ( 'filestatus' ) . " ==\n" . $copyStatus . "\n" .
+ "$licensetxt" .
+ '== ' . wfMsgForContent ( 'filesource' ) . " ==\n" . $source ;
+ } else {
+ if ( $license != '' ) {
+ $filedesc = $desc == '' ? '' : '== ' . wfMsg ( 'filedesc' ) . " ==\n" . $desc . "\n";
+ $textdesc = $filedesc .
+ '== ' . wfMsgForContent ( 'license' ) . " ==\n" . '{{' . $license . '}}' . "\n";
+ } else {
+ $textdesc = $desc;
+ }
+ }
+
+ if ( $timestamp === false ) {
+ $timestamp = $dbw->timestamp();
+ }
+
+ #split mime type
+ if (strpos($this->mime,'/')!==false) {
+ list($major,$minor)= explode('/',$this->mime,2);
+ }
+ else {
+ $major= $this->mime;
+ $minor= "unknown";
+ }
+
+ # Test to see if the row exists using INSERT IGNORE
+ # This avoids race conditions by locking the row until the commit, and also
+ # doesn't deadlock. SELECT FOR UPDATE causes a deadlock for every race condition.
+ $dbw->insert( 'image',
+ array(
+ 'img_name' => $this->getName(),
+ 'img_size'=> $this->size,
+ 'img_width' => intval( $this->width ),
+ 'img_height' => intval( $this->height ),
+ 'img_bits' => $this->bits,
+ 'img_media_type' => $this->media_type,
+ 'img_major_mime' => $major,
+ 'img_minor_mime' => $minor,
+ 'img_timestamp' => $timestamp,
+ 'img_description' => $desc,
+ 'img_user' => $wgUser->getID(),
+ 'img_user_text' => $wgUser->getName(),
+ 'img_metadata' => $this->metadata,
+ ),
+ __METHOD__,
+ 'IGNORE'
+ );
+
+ if( $dbw->affectedRows() == 0 ) {
+ # Collision, this is an update of a file
+ # Insert previous contents into oldimage
+ $dbw->insertSelect( 'oldimage', 'image',
+ array(
+ 'oi_name' => 'img_name',
+ 'oi_archive_name' => $dbw->addQuotes( $oldver ),
+ 'oi_size' => 'img_size',
+ 'oi_width' => 'img_width',
+ 'oi_height' => 'img_height',
+ 'oi_bits' => 'img_bits',
+ 'oi_timestamp' => 'img_timestamp',
+ 'oi_description' => 'img_description',
+ 'oi_user' => 'img_user',
+ 'oi_user_text' => 'img_user_text',
+ ), array( 'img_name' => $this->getName() ), __METHOD__
+ );
+
+ # Update the current image row
+ $dbw->update( 'image',
+ array( /* SET */
+ 'img_size' => $this->size,
+ 'img_width' => intval( $this->width ),
+ 'img_height' => intval( $this->height ),
+ 'img_bits' => $this->bits,
+ 'img_media_type' => $this->media_type,
+ 'img_major_mime' => $major,
+ 'img_minor_mime' => $minor,
+ 'img_timestamp' => $timestamp,
+ 'img_description' => $desc,
+ 'img_user' => $wgUser->getID(),
+ 'img_user_text' => $wgUser->getName(),
+ 'img_metadata' => $this->metadata,
+ ), array( /* WHERE */
+ 'img_name' => $this->getName()
+ ), __METHOD__
+ );
+ } else {
+ # This is a new file
+ # Update the image count
+ $site_stats = $dbw->tableName( 'site_stats' );
+ $dbw->query( "UPDATE $site_stats SET ss_images=ss_images+1", __METHOD__ );
+ }
+
+ $descTitle = $this->getTitle();
+ $article = new Article( $descTitle );
+ $minor = false;
+ $watch = $watch || $wgUser->isWatched( $descTitle );
+ $suppressRC = true; // There's already a log entry, so don't double the RC load
+
+ if( $descTitle->exists() ) {
+ // TODO: insert a null revision into the page history for this update.
+ if( $watch ) {
+ $wgUser->addWatch( $descTitle );
+ }
+
+ # Invalidate the cache for the description page
+ $descTitle->invalidateCache();
+ $descTitle->purgeSquid();
+ } else {
+ // New file; create the description page.
+ $article->insertNewArticle( $textdesc, $desc, $minor, $watch, $suppressRC );
+ }
+
+ # Hooks, hooks, the magic of hooks...
+ wfRunHooks( 'FileUpload', array( $this ) );
+
+ # Add the log entry
+ $log = new LogPage( 'upload' );
+ $log->addEntry( 'upload', $descTitle, $desc );
+
+ # Commit the transaction now, in case something goes wrong later
+ # The most important thing is that files don't get lost, especially archives
+ $dbw->immediateCommit();
+
+ # Invalidate cache for all pages using this file
+ $update = new HTMLCacheUpdate( $this->getTitle(), 'imagelinks' );
+ $update->doUpdate();
+
+ return true;
+ }
+
+ /**
+ * 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.
+ *
+ * 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:
+ * 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.
+ */
+ function publish( $srcPath, $flags = 0 ) {
+ $dstPath = $this->getFullPath();
+ $archiveName = gmdate( 'YmdHis' ) . '!'. $this->getName();
+ $archivePath = $this->getArchivePath( $archiveName );
+ $flags = $flags & File::DELETE_SOURCE ? LocalRepo::DELETE_SOURCE : 0;
+ $status = $this->repo->publish( $srcPath, $dstPath, $archivePath, $flags );
+ if ( WikiError::isError( $status ) ) {
+ return $status;
+ } elseif ( $status == 'new' ) {
+ return '';
+ } else {
+ return $archiveName;
+ }
+ }
+
+ /** getLinksTo inherited */
+ /** getExifData inherited */
+ /** isLocal inherited */
+ /** wasDeleted inherited */
+
+ /**
+ * Delete all versions of the file.
+ *
+ * Moves the files into an archive directory (or deletes them)
+ * and removes the database rows.
+ *
+ * Cache purging is done; logging is caller's responsibility.
+ *
+ * @param $reason
+ * @return true on success, false on some kind of failure
+ */
+ function delete( $reason, $suppress=false ) {
+ $transaction = new FSTransaction();
+ $urlArr = array( $this->getURL() );
+
+ if( !FileStore::lock() ) {
+ wfDebug( __METHOD__.": failed to acquire file store lock, aborting\n" );
+ return false;
+ }
+
+ try {
+ $dbw = $this->repo->getMasterDB();
+ $dbw->begin();
+
+ // Delete old versions
+ $result = $dbw->select( 'oldimage',
+ array( 'oi_archive_name' ),
+ array( 'oi_name' => $this->getName() ) );
+
+ while( $row = $dbw->fetchObject( $result ) ) {
+ $oldName = $row->oi_archive_name;
+
+ $transaction->add( $this->prepareDeleteOld( $oldName, $reason, $suppress ) );
+
+ // We'll need to purge this URL from caches...
+ $urlArr[] = $this->getArchiveUrl( $oldName );
+ }
+ $dbw->freeResult( $result );
+
+ // And the current version...
+ $transaction->add( $this->prepareDeleteCurrent( $reason, $suppress ) );
+
+ $dbw->immediateCommit();
+ } catch( MWException $e ) {
+ wfDebug( __METHOD__.": db error, rolling back file transactions\n" );
+ $transaction->rollback();
+ FileStore::unlock();
+ throw $e;
+ }
+
+ wfDebug( __METHOD__.": deleted db items, applying file transactions\n" );
+ $transaction->commit();
+ FileStore::unlock();
+
+
+ // Update site_stats
+ $site_stats = $dbw->tableName( 'site_stats' );
+ $dbw->query( "UPDATE $site_stats SET ss_images=ss_images-1", __METHOD__ );
+
+ $this->purgeEverything( $urlArr );
+
+ return true;
+ }
+
+
+ /**
+ * Delete an old version of the file.
+ *
+ * Moves the file into an archive directory (or deletes it)
+ * and removes the database row.
+ *
+ * Cache purging is done; logging is caller's responsibility.
+ *
+ * @param $reason
+ * @throws MWException or FSException on database or filestore failure
+ * @return true on success, false on some kind of failure
+ */
+ function deleteOld( $archiveName, $reason, $suppress=false ) {
+ $transaction = new FSTransaction();
+ $urlArr = array();
+
+ if( !FileStore::lock() ) {
+ wfDebug( __METHOD__.": failed to acquire file store lock, aborting\n" );
+ return false;
+ }
+
+ $transaction = new FSTransaction();
+ try {
+ $dbw = $this->repo->getMasterDB();
+ $dbw->begin();
+ $transaction->add( $this->prepareDeleteOld( $archiveName, $reason, $suppress ) );
+ $dbw->immediateCommit();
+ } catch( MWException $e ) {
+ wfDebug( __METHOD__.": db error, rolling back file transaction\n" );
+ $transaction->rollback();
+ FileStore::unlock();
+ throw $e;
+ }
+
+ wfDebug( __METHOD__.": deleted db items, applying file transaction\n" );
+ $transaction->commit();
+ FileStore::unlock();
+
+ $this->purgeDescription();
+
+ // Squid purging
+ global $wgUseSquid;
+ if ( $wgUseSquid ) {
+ $urlArr = array(
+ $this->getArchiveUrl( $archiveName ),
+ );
+ wfPurgeSquidServers( $urlArr );
+ }
+ return true;
+ }
+
+ /**
+ * Delete the current version of a file.
+ * May throw a database error.
+ * @return true on success, false on failure
+ */
+ private function prepareDeleteCurrent( $reason, $suppress=false ) {
+ return $this->prepareDeleteVersion(
+ $this->getFullPath(),
+ $reason,
+ 'image',
+ array(
+ 'fa_name' => 'img_name',
+ 'fa_archive_name' => 'NULL',
+ 'fa_size' => 'img_size',
+ 'fa_width' => 'img_width',
+ 'fa_height' => 'img_height',
+ 'fa_metadata' => 'img_metadata',
+ 'fa_bits' => 'img_bits',
+ 'fa_media_type' => 'img_media_type',
+ 'fa_major_mime' => 'img_major_mime',
+ 'fa_minor_mime' => 'img_minor_mime',
+ 'fa_description' => 'img_description',
+ 'fa_user' => 'img_user',
+ 'fa_user_text' => 'img_user_text',
+ 'fa_timestamp' => 'img_timestamp' ),
+ array( 'img_name' => $this->getName() ),
+ $suppress,
+ __METHOD__ );
+ }
+
+ /**
+ * Delete a given older version of a file.
+ * May throw a database error.
+ * @return true on success, false on failure
+ */
+ private function prepareDeleteOld( $archiveName, $reason, $suppress=false ) {
+ $oldpath = $this->getArchivePath() .
+ DIRECTORY_SEPARATOR . $archiveName;
+ return $this->prepareDeleteVersion(
+ $oldpath,
+ $reason,
+ 'oldimage',
+ array(
+ 'fa_name' => 'oi_name',
+ 'fa_archive_name' => 'oi_archive_name',
+ 'fa_size' => 'oi_size',
+ 'fa_width' => 'oi_width',
+ 'fa_height' => 'oi_height',
+ 'fa_metadata' => 'NULL',
+ 'fa_bits' => 'oi_bits',
+ 'fa_media_type' => 'NULL',
+ 'fa_major_mime' => 'NULL',
+ 'fa_minor_mime' => 'NULL',
+ 'fa_description' => 'oi_description',
+ 'fa_user' => 'oi_user',
+ 'fa_user_text' => 'oi_user_text',
+ 'fa_timestamp' => 'oi_timestamp' ),
+ array(
+ 'oi_name' => $this->getName(),
+ 'oi_archive_name' => $archiveName ),
+ $suppress,
+ __METHOD__ );
+ }
+
+ /**
+ * Do the dirty work of backing up an image row and its file
+ * (if $wgSaveDeletedFiles is on) and removing the originals.
+ *
+ * Must be run while the file store is locked and a database
+ * transaction is open to avoid race conditions.
+ *
+ * @return FSTransaction
+ */
+ private function prepareDeleteVersion( $path, $reason, $table, $fieldMap, $where, $suppress=false, $fname ) {
+ global $wgUser, $wgSaveDeletedFiles;
+
+ // Dupe the file into the file store
+ if( file_exists( $path ) ) {
+ if( $wgSaveDeletedFiles ) {
+ $group = 'deleted';
+
+ $store = FileStore::get( $group );
+ $key = FileStore::calculateKey( $path, $this->getExtension() );
+ $transaction = $store->insert( $key, $path,
+ FileStore::DELETE_ORIGINAL );
+ } else {
+ $group = null;
+ $key = null;
+ $transaction = FileStore::deleteFile( $path );
+ }
+ } else {
+ wfDebug( __METHOD__." deleting already-missing '$path'; moving on to database\n" );
+ $group = null;
+ $key = null;
+ $transaction = new FSTransaction(); // empty
+ }
+
+ if( $transaction === false ) {
+ // Fail to restore?
+ wfDebug( __METHOD__.": import to file store failed, aborting\n" );
+ throw new MWException( "Could not archive and delete file $path" );
+ return false;
+ }
+
+ // Bitfields to further supress the file content
+ // Note that currently, live files are stored elsewhere
+ // and cannot be partially deleted
+ $bitfield = 0;
+ if ( $suppress ) {
+ $bitfield |= self::DELETED_FILE;
+ $bitfield |= self::DELETED_COMMENT;
+ $bitfield |= self::DELETED_USER;
+ $bitfield |= self::DELETED_RESTRICTED;
+ }
+
+ $dbw = $this->repo->getMasterDB();
+ $storageMap = array(
+ 'fa_storage_group' => $dbw->addQuotes( $group ),
+ 'fa_storage_key' => $dbw->addQuotes( $key ),
+
+ 'fa_deleted_user' => $dbw->addQuotes( $wgUser->getId() ),
+ 'fa_deleted_timestamp' => $dbw->timestamp(),
+ 'fa_deleted_reason' => $dbw->addQuotes( $reason ),
+ 'fa_deleted' => $bitfield);
+ $allFields = array_merge( $storageMap, $fieldMap );
+
+ try {
+ if( $wgSaveDeletedFiles ) {
+ $dbw->insertSelect( 'filearchive', $table, $allFields, $where, $fname );
+ }
+ $dbw->delete( $table, $where, $fname );
+ } catch( DBQueryError $e ) {
+ // Something went horribly wrong!
+ // Leave the file as it was...
+ wfDebug( __METHOD__.": database error, rolling back file transaction\n" );
+ $transaction->rollback();
+ throw $e;
+ }
+
+ return $transaction;
+ }
+
+ /**
+ * Restore all or specified deleted revisions to the given file.
+ * Permissions and logging are left to the caller.
+ *
+ * May throw database exceptions on error.
+ *
+ * @param $versions set of record ids of deleted items to restore,
+ * or empty to restore all revisions.
+ * @return the number of file revisions restored if successful,
+ * or false on failure
+ */
+ function restore( $versions=array(), $Unsuppress=false ) {
+ global $wgUser;
+
+ if( !FileStore::lock() ) {
+ wfDebug( __METHOD__." could not acquire filestore lock\n" );
+ return false;
+ }
+
+ $transaction = new FSTransaction();
+ try {
+ $dbw = $this->repo->getMasterDB();
+ $dbw->begin();
+
+ // Re-confirm whether this file presently exists;
+ // if no we'll need to create an file record for the
+ // first item we restore.
+ $exists = $dbw->selectField( 'image', '1',
+ array( 'img_name' => $this->getName() ),
+ __METHOD__ );
+
+ // Fetch all or selected archived revisions for the file,
+ // sorted from the most recent to the oldest.
+ $conditions = array( 'fa_name' => $this->getName() );
+ if( $versions ) {
+ $conditions['fa_id'] = $versions;
+ }
+
+ $result = $dbw->select( 'filearchive', '*',
+ $conditions,
+ __METHOD__,
+ array( 'ORDER BY' => 'fa_timestamp DESC' ) );
+
+ if( $dbw->numRows( $result ) < count( $versions ) ) {
+ // There's some kind of conflict or confusion;
+ // we can't restore everything we were asked to.
+ wfDebug( __METHOD__.": couldn't find requested items\n" );
+ $dbw->rollback();
+ FileStore::unlock();
+ return false;
+ }
+
+ if( $dbw->numRows( $result ) == 0 ) {
+ // Nothing to do.
+ wfDebug( __METHOD__.": nothing to do\n" );
+ $dbw->rollback();
+ FileStore::unlock();
+ return true;
+ }
+
+ $revisions = 0;
+ while( $row = $dbw->fetchObject( $result ) ) {
+ if ( $Unsuppress ) {
+ // Currently, fa_deleted flags fall off upon restore, lets be careful about this
+ } else if ( ($row->fa_deleted & Revision::DELETED_RESTRICTED) && !$wgUser->isAllowed('hiderevision') ) {
+ // Skip restoring file revisions that the user cannot restore
+ continue;
+ }
+ $revisions++;
+ $store = FileStore::get( $row->fa_storage_group );
+ if( !$store ) {
+ wfDebug( __METHOD__.": skipping row with no file.\n" );
+ continue;
+ }
+
+ $restoredImage = new self( $row->fa_name, $this->repo );
+
+ if( $revisions == 1 && !$exists ) {
+ $destPath = $restoredImage->getFullPath();
+ $destDir = dirname( $destPath );
+ if ( !is_dir( $destDir ) ) {
+ wfMkdirParents( $destDir );
+ }
+
+ // We may have to fill in data if this was originally
+ // an archived file revision.
+ if( is_null( $row->fa_metadata ) ) {
+ $tempFile = $store->filePath( $row->fa_storage_key );
+
+ $magic = MimeMagic::singleton();
+ $mime = $magic->guessMimeType( $tempFile, true );
+ $media_type = $magic->getMediaType( $tempFile, $mime );
+ list( $major_mime, $minor_mime ) = self::splitMime( $mime );
+ $handler = MediaHandler::getHandler( $mime );
+ if ( $handler ) {
+ $metadata = $handler->getMetadata( false, $tempFile );
+ } else {
+ $metadata = '';
+ }
+ } else {
+ $metadata = $row->fa_metadata;
+ $major_mime = $row->fa_major_mime;
+ $minor_mime = $row->fa_minor_mime;
+ $media_type = $row->fa_media_type;
+ }
+
+ $table = 'image';
+ $fields = array(
+ 'img_name' => $row->fa_name,
+ 'img_size' => $row->fa_size,
+ 'img_width' => $row->fa_width,
+ 'img_height' => $row->fa_height,
+ 'img_metadata' => $metadata,
+ 'img_bits' => $row->fa_bits,
+ 'img_media_type' => $media_type,
+ 'img_major_mime' => $major_mime,
+ 'img_minor_mime' => $minor_mime,
+ 'img_description' => $row->fa_description,
+ 'img_user' => $row->fa_user,
+ 'img_user_text' => $row->fa_user_text,
+ 'img_timestamp' => $row->fa_timestamp );
+ } else {
+ $archiveName = $row->fa_archive_name;
+ if( $archiveName == '' ) {
+ // This was originally a current version; we
+ // have to devise a new archive name for it.
+ // Format is <timestamp of archiving>!<name>
+ $archiveName =
+ wfTimestamp( TS_MW, $row->fa_deleted_timestamp ) .
+ '!' . $row->fa_name;
+ }
+ $restoredImage = new self( $row->fa_name, $this->repo );
+ $destDir = $restoredImage->getArchivePath();
+ if ( !is_dir( $destDir ) ) {
+ wfMkdirParents( $destDir );
+ }
+ $destPath = $destDir . DIRECTORY_SEPARATOR . $archiveName;
+
+ $table = 'oldimage';
+ $fields = array(
+ 'oi_name' => $row->fa_name,
+ 'oi_archive_name' => $archiveName,
+ 'oi_size' => $row->fa_size,
+ 'oi_width' => $row->fa_width,
+ 'oi_height' => $row->fa_height,
+ 'oi_bits' => $row->fa_bits,
+ 'oi_description' => $row->fa_description,
+ 'oi_user' => $row->fa_user,
+ 'oi_user_text' => $row->fa_user_text,
+ 'oi_timestamp' => $row->fa_timestamp );
+ }
+
+ $dbw->insert( $table, $fields, __METHOD__ );
+ // @todo this delete is not totally safe, potentially
+ $dbw->delete( 'filearchive',
+ array( 'fa_id' => $row->fa_id ),
+ __METHOD__ );
+
+ // Check if any other stored revisions use this file;
+ // if so, we shouldn't remove the file from the deletion
+ // archives so they will still work.
+ $useCount = $dbw->selectField( 'filearchive',
+ 'COUNT(*)',
+ array(
+ 'fa_storage_group' => $row->fa_storage_group,
+ 'fa_storage_key' => $row->fa_storage_key ),
+ __METHOD__ );
+ if( $useCount == 0 ) {
+ wfDebug( __METHOD__.": nothing else using {$row->fa_storage_key}, will deleting after\n" );
+ $flags = FileStore::DELETE_ORIGINAL;
+ } else {
+ $flags = 0;
+ }
+
+ $transaction->add( $store->export( $row->fa_storage_key,
+ $destPath, $flags ) );
+ }
+
+ $dbw->immediateCommit();
+ } catch( MWException $e ) {
+ wfDebug( __METHOD__." caught error, aborting\n" );
+ $transaction->rollback();
+ throw $e;
+ }
+
+ $transaction->commit();
+ FileStore::unlock();
+
+ if( $revisions > 0 ) {
+ if( !$exists ) {
+ wfDebug( __METHOD__." restored $revisions items, creating a new current\n" );
+
+ // Update site_stats
+ $site_stats = $dbw->tableName( 'site_stats' );
+ $dbw->query( "UPDATE $site_stats SET ss_images=ss_images+1", __METHOD__ );
+
+ $this->purgeEverything();
+ } else {
+ wfDebug( __METHOD__." restored $revisions as archived versions\n" );
+ $this->purgeDescription();
+ }
+ }
+
+ return $revisions;
+ }
+
+ /** isMultipage inherited */
+ /** pageCount inherited */
+ /** scaleHeight inherited */
+ /** getImageSize inherited */
+
+ /**
+ * Get the URL of the file description page.
+ */
+ function getDescriptionUrl() {
+ return $this->title->getLocalUrl();
+ }
+
+ /**
+ * Get the HTML text of the description page
+ * This is not used by ImagePage for local files, since (among other things)
+ * it skips the parser cache.
+ */
+ function getDescriptionText() {
+ global $wgParser;
+ $revision = Revision::newFromTitle( $this->title );
+ if ( !$revision ) return false;
+ $text = $revision->getText();
+ if ( !$text ) return false;
+ $html = $wgParser->parse( $text, new ParserOptions );
+ return $html;
+ }
+
+ function getTimestamp() {
+ $this->load();
+ return $this->timestamp;
+ }
+} // LocalFile class
+
+/**
+ * Backwards compatibility class
+ */
+class Image extends LocalFile {
+ function __construct( $title ) {
+ $repo = FileRepoGroup::singleton()->getLocalRepo();
+ parent::__construct( $title, $repo );
+ }
+
+ /**
+ * Wrapper for wfFindFile(), for backwards-compatibility only
+ * Do not use in core code.
+ */
+ function newFromTitle( $title, $time = false ) {
+ $img = wfFindFile( $title, $time );
+ if ( !$img ) {
+ $img = wfLocalFile( $title );
+ }
+ return $img;
+ }
+}
+
+/**
+ * Aliases for backwards compatibility with 1.6
+ */
+define( 'MW_IMG_DELETED_FILE', File::DELETED_FILE );
+define( 'MW_IMG_DELETED_COMMENT', File::DELETED_COMMENT );
+define( 'MW_IMG_DELETED_USER', File::DELETED_USER );
+define( 'MW_IMG_DELETED_RESTRICTED', File::DELETED_RESTRICTED );
+
+?>
--- /dev/null
+<?php
+/**
+ * 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.
+ */
+class LocalRepo extends FSRepo {
+ var $fileFactory = array( 'LocalFile', 'newFromTitle' );
+
+ function getSlaveDB() {
+ return wfGetDB( DB_SLAVE );
+ }
+
+ function getMasterDB() {
+ return wfGetDB( DB_MASTER );
+ }
+
+ function newFileFromRow( $row ) {
+ if ( isset( $row->img_name ) ) {
+ return LocalFile::newFromRow( $row, $this );
+ } elseif ( isset( $row->oi_name ) ) {
+ return OldLocalFile::newFromRow( $row, $this );
+ } else {
+ throw new MWException( __METHOD__.': invalid row' );
+ }
+ }
+}
--- /dev/null
+<?php\r
+\r
+/**\r
+ * Class to represent a file in the oldimage table\r
+ *\r
+ * @addtogroup FileRepo\r
+ */\r
+class OldLocalFile extends LocalFile {\r
+ var $requestedTime, $archive_name;\r
+\r
+ const CACHE_VERSION = 1;\r
+ const MAX_CACHE_ROWS = 20;\r
+\r
+ function newFromTitle( $title, $repo, $time ) {\r
+ return new self( $title, $repo, $time, null );\r
+ }\r
+\r
+ function newFromArchiveName( $title, $repo, $archiveName ) {\r
+ return new self( $title, $repo, null, $archiveName );\r
+ }\r
+\r
+ function newFromRow( $row, $repo ) {\r
+ $title = Title::makeTitle( NS_IMAGE, $row->oi_name );\r
+ $file = new self( $title, $repo, null, $row->oi_archive_name );\r
+ $file->loadFromRow( $row, 'oi_' );\r
+ return $file;\r
+ }\r
+\r
+ /**\r
+ * @param Title $title\r
+ * @param FileRepo $repo\r
+ * @param string $time Timestamp or null to load by archive name\r
+ * @param string $archiveName Archive name or null to load by timestamp\r
+ */\r
+ function __construct( $title, $repo, $time, $archiveName ) {\r
+ parent::__construct( $title, $repo );\r
+ $this->requestedTime = $time;\r
+ $this->archive_name = $archiveName;\r
+ if ( is_null( $time ) && is_null( $archiveName ) ) {\r
+ throw new MWException( __METHOD__.': must specify at least one of $time or $archiveName' );\r
+ }\r
+ }\r
+\r
+ function getCacheKey() {\r
+ $hashedName = md5($this->getName());\r
+ return wfMemcKey( 'oldfile', $hashedName );\r
+ }\r
+\r
+ function getArchiveName() {\r
+ if ( !isset( $this->archive_name ) ) {\r
+ $this->load();\r
+ }\r
+ return $this->archive_name;\r
+ }\r
+\r
+ function isOld() {\r
+ return true;\r
+ }\r
+\r
+ /**\r
+ * Try to load file metadata from memcached. Returns true on success.\r
+ */\r
+ function loadFromCache() {\r
+ global $wgMemc;\r
+ wfProfileIn( __METHOD__ );\r
+ $this->dataLoaded = false;\r
+ $key = $this->getCacheKey();\r
+ if ( !$key ) {\r
+ return false;\r
+ }\r
+ $oldImages = $wgMemc->get( $key );\r
+\r
+ if ( isset( $oldImages['version'] ) && $oldImages['version'] == MW_OLDFILE_VERSION ) {\r
+ unset( $oldImages['version'] );\r
+ $more = isset( $oldImages['more'] );\r
+ unset( $oldImages['more'] );\r
+ $found = false;\r
+ if ( is_null( $this->requestedTime ) ) {\r
+ foreach ( $oldImages as $timestamp => $info ) {\r
+ if ( $info['archive_name'] == $this->archive_name ) {\r
+ $found = true;\r
+ break;\r
+ }\r
+ }\r
+ } else {\r
+ krsort( $oldImages );\r
+ foreach ( $oldImages as $timestamp => $info ) {\r
+ if ( $timestamp <= $this->requestedTime ) {\r
+ $found = true;\r
+ break;\r
+ }\r
+ }\r
+ }\r
+ if ( $found ) {\r
+ wfDebug( "Pulling file metadata from cache key {$key}[{$timestamp}]\n" );\r
+ $this->dataLoaded = true;\r
+ foreach ( $cachedValues as $name => $value ) {\r
+ $this->$name = $value;\r
+ }\r
+ } elseif ( $more ) {\r
+ wfDebug( "Cache key was truncated, oldimage row might be found in the database\n" );\r
+ } else {\r
+ wfDebug( "Image did not exist at the specified time.\n" );\r
+ $this->fileExists = false;\r
+ $this->dataLoaded = true;\r
+ }\r
+ }\r
+\r
+ if ( $this->dataLoaded ) {\r
+ wfIncrStats( 'image_cache_hit' );\r
+ } else {\r
+ wfIncrStats( 'image_cache_miss' );\r
+ }\r
+\r
+ wfProfileOut( __METHOD__ );\r
+ return $this->dataLoaded;\r
+ }\r
+\r
+ function saveToCache() {\r
+ // Cache the entire history of the image (up to MAX_CACHE_ROWS).\r
+ // This is expensive, so we only do it if $wgMemc is real\r
+ global $wgMemc;\r
+ if ( $wgMemc instanceof FakeMemcachedClient ) {\r
+ return;\r
+ }\r
+ $key = $this->getCacheKey();\r
+ if ( !$key ) { \r
+ return;\r
+ }\r
+ wfProfileIn( __METHOD__ );\r
+\r
+ $dbr = $this->repo->getSlaveDB();\r
+ $res = $dbr->select( 'oldimage', $this->getCacheFields(),\r
+ array( 'oi_name' => $this->getName() ), __METHOD__, \r
+ array( \r
+ 'LIMIT' => self::MAX_CACHE_ROWS + 1,\r
+ 'ORDER BY' => 'oi_timestamp DESC',\r
+ ));\r
+ $cache = array( 'version' => self::CACHE_VERSION );\r
+ $numRows = $dbr->numRows( $res );\r
+ if ( $numRows > self::MAX_CACHE_ROWS ) {\r
+ $cache['more'] = true;\r
+ $numRows--;\r
+ }\r
+ for ( $i = 0; $i < $numRows; $i++ ) {\r
+ $row = $dbr->fetchObject( $res );\r
+ $this->decodeRow( $row, 'oi_' );\r
+ $cache[$row->oi_timestamp] = $row;\r
+ }\r
+ $dbr->freeResult( $res );\r
+ $wgMemc->set( $key, $cache, 7*86400 /* 1 week */ );\r
+ wfProfileOut( __METHOD__ );\r
+ }\r
+\r
+ function loadFromDB() {\r
+ wfProfileIn( __METHOD__ );\r
+ $dbr = $this->repo->getSlaveDB();\r
+ $conds = array( 'oi_name' => $this->getName() );\r
+ if ( is_null( $this->requestedTimestamp ) ) {\r
+ $conds['oi_archive_name'] = $this->archive_name;\r
+ } else {\r
+ $conds[] = 'oi_timestamp <= ' . $dbr->addQuotes( $this->requestedTimestamp );\r
+ }\r
+ $row = $dbr->selectRow( 'oldimage', $this->getCacheFields( 'oi_' ),\r
+ $conds, __METHOD__, array( 'ORDER BY' => 'oi_timestamp DESC' ) );\r
+ if ( $row ) {\r
+ $this->loadFromRow( $row, 'oi_' );\r
+ } else {\r
+ $this->fileExists = false;\r
+ }\r
+ $this->dataLoaded = true;\r
+ }\r
+\r
+ function getCacheFields( $prefix = 'img_' ) {\r
+ $fields = parent::getCacheFields( $prefix );\r
+ $fields[] = $prefix . 'archive_name';\r
+\r
+ // XXX: Temporary hack before schema update\r
+ $fields = array_diff( $fields, array( \r
+ 'oi_media_type', 'oi_major_mime', 'oi_minor_mime', 'oi_metadata' ) );\r
+ return $fields;\r
+ }\r
+\r
+ function getRel() {\r
+ return 'archive/' . $this->getHashPath() . $this->getArchiveName();\r
+ }\r
+\r
+ function getUrlRel() {\r
+ return 'archive/' . $this->getHashPath() . urlencode( $this->getArchiveName() );\r
+ }\r
+ \r
+ function upgradeRow() {\r
+ wfProfileIn( __METHOD__ );\r
+\r
+ $this->loadFromFile();\r
+\r
+ $dbw = $this->repo->getMasterDB();\r
+ list( $major, $minor ) = self::splitMime( $this->mime );\r
+\r
+ wfDebug(__METHOD__.': upgrading '.$this->archive_name." to the current schema\n");\r
+ $dbw->update( 'oldimage',\r
+ array(\r
+ 'oi_width' => $this->width,\r
+ 'oi_height' => $this->height,\r
+ 'oi_bits' => $this->bits,\r
+ #'oi_media_type' => $this->media_type,\r
+ #'oi_major_mime' => $major,\r
+ #'oi_minor_mime' => $minor,\r
+ #'oi_metadata' => $this->metadata,\r
+ ), array( 'oi_name' => $this->getName(), 'oi_timestamp' => $this->requestedTime ),\r
+ __METHOD__\r
+ );\r
+ wfProfileOut( __METHOD__ );\r
+ }\r
+\r
+ // XXX: Temporary hack before schema update\r
+ function maybeUpgradeRow() {}\r
+\r
+}\r
+\r
+\r
+?>\r
--- /dev/null
+<?php
+
+class RepoGroup {
+ var $localRepo, $foreignRepos, $reposInitialised = false;
+ var $localInfo, $foreignInfo;
+
+ protected static $instance;
+
+ function singleton() {
+ if ( self::$instance ) {
+ return self::$instance;
+ }
+ global $wgLocalFileRepo, $wgForeignFileRepos;
+ self::$instance = new RepoGroup( $wgLocalFileRepo, $wgForeignFileRepos );
+ return self::$instance;
+ }
+
+ /**
+ * Construct a group of file repositories.
+ * @param array $data Array of repository info arrays.
+ * Each info array is an associative array with the 'class' member
+ * giving the class name. The entire array is passed to the repository
+ * constructor as the first parameter.
+ */
+ function __construct( $localInfo, $foreignInfo ) {
+ $this->localInfo = $localInfo;
+ $this->foreignInfo = $foreignInfo;
+ }
+
+ /**
+ * Search repositories for an image.
+ * You can also use wfGetFile() to do this.
+ * @param mixed $title Title object or string
+ * @param mixed $time The 14-char timestamp before which the file should
+ * have been uploaded, or false for the current version
+ * @return File object or false if it is not found
+ */
+ function findFile( $title, $time = false ) {
+ if ( !$this->reposInitialised ) {
+ $this->initialiseRepos();
+ }
+
+ $image = $this->localRepo->findFile( $title, $time );
+ if ( $image ) {
+ return $image;
+ }
+ foreach ( $this->foreignRepos as $repo ) {
+ $image = $repo->findFile( $image, $time );
+ if ( $image ) {
+ return $image;
+ }
+ }
+ return false;
+ }
+
+ /**
+ * Get the repo instance with a given key.
+ */
+ function getRepo( $index ) {
+ if ( !$this->reposInitialised ) {
+ $this->initialiseRepos();
+ }
+ if ( $index == 'local' ) {
+ return $this->localRepo;
+ } elseif ( isset( $this->foreignRepos[$index] ) ) {
+ return $this->foreignRepos[$index];
+ } else {
+ return false;
+ }
+ }
+
+ function getLocalRepo() {
+ return $this->getRepo( 'local' );
+ }
+
+ /**
+ * Initialise the $repos array
+ */
+ function initialiseRepos() {
+ if ( $this->reposInitialised ) {
+ return;
+ }
+ $this->reposInitialised = true;
+
+ $this->localRepo = $this->newRepo( $this->localInfo );
+ $this->foreignRepos = array();
+ foreach ( $this->foreignInfo as $key => $info ) {
+ $this->foreignRepos[$key] = $this->newRepo( $info );
+ }
+ }
+
+ function newRepo( $info ) {
+ $class = $info['class'];
+ return new $class( $info );
+ }
+}
+
+?>
--- /dev/null
+<?php
+
+/**
+ * A file object referring to either a standalone local file, or a file in a
+ * local repository with no database, for example an FSRepo repository.
+ *
+ * Read-only.
+ *
+ * TODO: Currently it doesn't really work in the repository role, there are
+ * lots of functions missing. It is used by the WebStore extension in the
+ * standalone role.
+ */
+class UnregisteredLocalFile extends File {
+ var $title, $path, $mime, $handler, $dims;
+
+ function newFromPath( $path, $mime ) {
+ return new UnregisteredLocalFile( false, false, $path, $mime );
+ }
+
+ function newFromTitle( $title, $repo ) {
+ return new UnregisteredLocalFile( $title, $repo, false, false );
+ }
+
+ function __construct( $title = false, $repo = false, $path = false, $mime = false ) {
+ if ( !( $title && $repo ) && !$path ) {
+ throw new MWException( __METHOD__.': not enough parameters, must specify title and repo, or a full path' );
+ }
+ if ( $title ) {
+ $this->title = $title;
+ $this->name = $title->getDBkey();
+ } else {
+ $this->name = basename( $path );
+ $this->title = Title::makeTitleSafe( NS_IMAGE, $this->name );
+ }
+ $this->repo = $repo;
+ if ( $path ) {
+ $this->path = $path;
+ } else {
+ $this->path = $repo->getRootDirectory() . '/' . $repo->getHashPath( $this->name ) . $this->name;
+ }
+ if ( $mime ) {
+ $this->mime = $mime;
+ }
+ $this->dims = array();
+ }
+
+ function getPageDimensions( $page = 1 ) {
+ if ( !isset( $this->dims[$page] ) ) {
+ if ( !$this->getHandler() ) {
+ return false;
+ }
+ $this->dims[$page] = $this->handler->getPageDimensions( $this, $page );
+ }
+ return $this->dims[$page];
+ }
+
+ function getWidth( $page = 1 ) {
+ $dim = $this->getPageDimensions( $page );
+ return $dim['width'];
+ }
+
+ function getHeight( $page = 1 ) {
+ $dim = $this->getPageDimensions( $page );
+ return $dim['height'];
+ }
+
+ function getMimeType() {
+ if ( !isset( $this->mime ) ) {
+ $magic = MimeMagic::singleton();
+ $this->mime = $magic->guessMimeType( $this->path );
+ }
+ return $this->mime;
+ }
+
+ function getImageSize() {
+ if ( !$this->getHandler() ) {
+ return false;
+ }
+ return $this->handler->getImageSize( $this, $this->getPath() );
+ }
+
+ function getMetadata() {
+ if ( !isset( $this->metadata ) ) {
+ if ( !$this->getHandler() ) {
+ $this->metadata = false;
+ } else {
+ $this->metadata = $this->handler->getMetadata( $this, $this->getPath() );
+ }
+ }
+ return $this->metadata;
+ }
+
+ function getURL() {
+ if ( $this->repo ) {
+ return $this->repo->getZoneUrl( 'public' ) . $this->repo->getHashPath( $this->name ) . urlencode( $this->name );
+ } else {
+ return false;
+ }
+ }
+
+ function getSize() {
+ if ( file_exists( $this->path ) ) {
+ return filesize( $this->path );
+ } else {
+ return false;
+ }
+ }
+}
+?>
$srcWidth = $image->getWidth();
$srcHeight = $image->getHeight();
$mimeType = $image->getMimeType();
- $srcPath = $image->getImagePath();
+ $srcPath = $image->getPath();
$retval = 0;
wfDebug( __METHOD__.": creating {$physicalWidth}x{$physicalHeight} thumbnail at $dstPath\n" );
return new ThumbnailImage( $image->getURL(), $clientWidth, $clientHeight, $srcPath );
}
- if ( $wgUseImageMagick ) {
+ if ( !$dstPath ) {
+ // No output path available, client side scaling only
+ $scaler = 'client';
+ } elseif ( $wgUseImageMagick ) {
$scaler = 'im';
} elseif ( $wgCustomConvertCommand ) {
$scaler = 'custom';
}
$width = $params['width'];
$height = $params['height'];
- $srcPath = $image->getImagePath();
+ $srcPath = $image->getPath();
$page = $params['page'];
if ( $page > $this->pageCount( $image ) ) {
return new MediaTransformError( 'thumbnail_error', $width, $height, wfMsg( 'djvu_page_error' ) );
* Returns false if unknown or if the document is not multi-page.
*/
function getPageDimensions( $image, $page ) {
- $gis = $this->getImageSize( $image, $image->getImagePath() );
+ $gis = $this->getImageSize( $image, $image->getPath() );
return array(
'width' => $gis[0],
'height' => $gis[1]
$params['width'] = wfFitBoxWidth( $srcWidth, $srcHeight, $params['height'] );
}
}
- $params['height'] = Image::scaleHeight( $srcWidth, $srcHeight, $params['width'] );
+ $params['height'] = File::scaleHeight( $srcWidth, $srcHeight, $params['width'] );
if ( !$this->validateThumbParams( $params['width'], $params['height'], $srcWidth, $srcHeight, $mimeType ) ) {
return false;
}
return false;
}
- $height = Image::scaleHeight( $srcWidth, $srcHeight, $width );
+ $height = File::scaleHeight( $srcWidth, $srcHeight, $width );
return true;
}
$srcWidth = $image->getWidth( $params['page'] );
$srcHeight = $image->getHeight( $params['page'] );
$params['physicalWidth'] = $wgSVGMaxSize;
- $params['physicalHeight'] = Image::scaleHeight( $srcWidth, $srcHeight, $wgSVGMaxSize );
+ $params['physicalHeight'] = File::scaleHeight( $srcWidth, $srcHeight, $wgSVGMaxSize );
}
return true;
}
$clientHeight = $params['height'];
$physicalWidth = $params['physicalWidth'];
$physicalHeight = $params['physicalHeight'];
- $srcPath = $image->getImagePath();
+ $srcPath = $image->getPath();
if ( $flags & self::TRANSFORM_LATER ) {
return new ThumbnailImage( $dstUrl, $clientWidth, $clientHeight, $dstPath );
global $utfCompatibilityDecomp;
$utfCompatibilityDecomp = NULL;
-define( 'UNICODE_HANGUL_FIRST', 0xac00 );
-define( 'UNICODE_HANGUL_LAST', 0xd7a3 );
-
-define( 'UNICODE_HANGUL_LBASE', 0x1100 );
-define( 'UNICODE_HANGUL_VBASE', 0x1161 );
-define( 'UNICODE_HANGUL_TBASE', 0x11a7 );
-
-define( 'UNICODE_HANGUL_LCOUNT', 19 );
-define( 'UNICODE_HANGUL_VCOUNT', 21 );
-define( 'UNICODE_HANGUL_TCOUNT', 28 );
-define( 'UNICODE_HANGUL_NCOUNT', UNICODE_HANGUL_VCOUNT * UNICODE_HANGUL_TCOUNT );
-
-define( 'UNICODE_HANGUL_LEND', UNICODE_HANGUL_LBASE + UNICODE_HANGUL_LCOUNT - 1 );
-define( 'UNICODE_HANGUL_VEND', UNICODE_HANGUL_VBASE + UNICODE_HANGUL_VCOUNT - 1 );
-define( 'UNICODE_HANGUL_TEND', UNICODE_HANGUL_TBASE + UNICODE_HANGUL_TCOUNT - 1 );
-
-define( 'UNICODE_SURROGATE_FIRST', 0xd800 );
-define( 'UNICODE_SURROGATE_LAST', 0xdfff );
-define( 'UNICODE_MAX', 0x10ffff );
-define( 'UNICODE_REPLACEMENT', 0xfffd );
-
-
-define( 'UTF8_HANGUL_FIRST', "\xea\xb0\x80" /*codepointToUtf8( UNICODE_HANGUL_FIRST )*/ );
-define( 'UTF8_HANGUL_LAST', "\xed\x9e\xa3" /*codepointToUtf8( UNICODE_HANGUL_LAST )*/ );
-
-define( 'UTF8_HANGUL_LBASE', "\xe1\x84\x80" /*codepointToUtf8( UNICODE_HANGUL_LBASE )*/ );
-define( 'UTF8_HANGUL_VBASE', "\xe1\x85\xa1" /*codepointToUtf8( UNICODE_HANGUL_VBASE )*/ );
-define( 'UTF8_HANGUL_TBASE', "\xe1\x86\xa7" /*codepointToUtf8( UNICODE_HANGUL_TBASE )*/ );
-
-define( 'UTF8_HANGUL_LEND', "\xe1\x84\x92" /*codepointToUtf8( UNICODE_HANGUL_LEND )*/ );
-define( 'UTF8_HANGUL_VEND', "\xe1\x85\xb5" /*codepointToUtf8( UNICODE_HANGUL_VEND )*/ );
-define( 'UTF8_HANGUL_TEND', "\xe1\x87\x82" /*codepointToUtf8( UNICODE_HANGUL_TEND )*/ );
-
-define( 'UTF8_SURROGATE_FIRST', "\xed\xa0\x80" /*codepointToUtf8( UNICODE_SURROGATE_FIRST )*/ );
-define( 'UTF8_SURROGATE_LAST', "\xed\xbf\xbf" /*codepointToUtf8( UNICODE_SURROGATE_LAST )*/ );
-define( 'UTF8_MAX', "\xf4\x8f\xbf\xbf" /*codepointToUtf8( UNICODE_MAX )*/ );
-define( 'UTF8_REPLACEMENT', "\xef\xbf\xbd" /*codepointToUtf8( UNICODE_REPLACEMENT )*/ );
-#define( 'UTF8_REPLACEMENT', '!' );
-
-define( 'UTF8_OVERLONG_A', "\xc1\xbf" );
-define( 'UTF8_OVERLONG_B', "\xe0\x9f\xbf" );
-define( 'UTF8_OVERLONG_C', "\xf0\x8f\xbf\xbf" );
-
-# These two ranges are illegal
-define( 'UTF8_FDD0', "\xef\xb7\x90" /*codepointToUtf8( 0xfdd0 )*/ );
-define( 'UTF8_FDEF', "\xef\xb7\xaf" /*codepointToUtf8( 0xfdef )*/ );
-define( 'UTF8_FFFE', "\xef\xbf\xbe" /*codepointToUtf8( 0xfffe )*/ );
-define( 'UTF8_FFFF', "\xef\xbf\xbf" /*codepointToUtf8( 0xffff )*/ );
-
-define( 'UTF8_HEAD', false );
-define( 'UTF8_TAIL', true );
-
-
/**
* For using the ICU wrapper
*/
return $copy;
}
- function imageInfo( $name, $subdirCallback='wfImageDir', $basename = null ) {
- if( is_null( $basename ) ) $basename = $name;
- $dir = call_user_func( $subdirCallback, $basename );
- $filename = $dir . '/' . $name;
+ function imageInfo( $filename ) {
$info = array(
'width' => 0,
'height' => 0,
$info['media'] = $magic->getMediaType( $filename, $mime );
- # Height and width
- $gis = false;
- if( $mime == 'image/svg' ) {
- $gis = wfGetSVGsize( $filename );
- } elseif( $magic->isPHPImageType( $mime ) ) {
- $gis = getimagesize( $filename );
- } else {
- $this->log( "Surprising mime type: $mime" );
- }
- if( $gis ) {
- $info['width' ] = $gis[0];
- $info['height'] = $gis[1];
- }
- if( isset( $gis['bits'] ) ) {
+ $image = UnregisteredLocalFile::newFromPath( $filename, $mime );
+
+ $info['width'] = $image->getWidth();
+ $info['height'] = $image->getHeight();
+
+ $gis = $image->getImageSize();
+ if ( isset( $gis['bits'] ) ) {
$info['bits'] = $gis['bits'];
}
}
function upgradeLogging() {
- $tabledef = <<<END
+ $tabledef = <<<ENDS
CREATE TABLE $1 (
-- Symbolic keys for the general log type and the action type
-- within the log. The output format will be controlled by the
KEY page_time (log_namespace, log_title, log_timestamp)
) TYPE=InnoDB
-END;
+ENDS;
$fields = array(
'log_type' => MW_UPGRADE_COPY,
'log_action' => MW_UPGRADE_COPY,
}
function upgradeArchive() {
- $tabledef = <<<END
+ $tabledef = <<<ENDS
CREATE TABLE $1 (
ar_namespace int NOT NULL default '0',
ar_title varchar(255) binary NOT NULL default '',
KEY name_title_timestamp (ar_namespace,ar_title,ar_timestamp)
) TYPE=InnoDB
-END;
+ENDS;
$fields = array(
'ar_namespace' => MW_UPGRADE_COPY,
'ar_title' => MW_UPGRADE_ENCODE,
function upgradeImagelinks() {
global $wgUseLatin1;
if( $wgUseLatin1 ) {
- $tabledef = <<<END
+ $tabledef = <<<ENDS
CREATE TABLE $1 (
-- Key to page_id of the page containing the image / media link.
il_from int(8) unsigned NOT NULL default '0',
KEY (il_to)
) TYPE=InnoDB
-END;
+ENDS;
$fields = array(
'il_from' => MW_UPGRADE_COPY,
'il_to' => MW_UPGRADE_ENCODE );
function upgradeCategorylinks() {
global $wgUseLatin1;
if( $wgUseLatin1 ) {
- $tabledef = <<<END
+ $tabledef = <<<ENDS
CREATE TABLE $1 (
cl_from int(8) unsigned NOT NULL default '0',
cl_to varchar(255) binary NOT NULL default '',
KEY cl_sortkey(cl_to,cl_sortkey),
KEY cl_timestamp(cl_to,cl_timestamp)
) TYPE=InnoDB
-END;
+ENDS;
$fields = array(
'cl_from' => MW_UPGRADE_COPY,
'cl_to' => MW_UPGRADE_ENCODE,
function upgradeIpblocks() {
global $wgUseLatin1;
if( $wgUseLatin1 ) {
- $tabledef = <<<END
+ $tabledef = <<<ENDS
CREATE TABLE $1 (
ipb_id int(8) NOT NULL auto_increment,
ipb_address varchar(40) binary NOT NULL default '',
INDEX ipb_user (ipb_user)
) TYPE=InnoDB
-END;
+ENDS;
$fields = array(
'ipb_id' => MW_UPGRADE_COPY,
'ipb_address' => MW_UPGRADE_COPY,
function upgradeRecentchanges() {
// There's a format change in the namespace field
- $tabledef = <<<END
+ $tabledef = <<<ENDS
CREATE TABLE $1 (
rc_id int(8) NOT NULL auto_increment,
rc_timestamp varchar(14) binary NOT NULL default '',
INDEX rc_ip (rc_ip)
) TYPE=InnoDB
-END;
+ENDS;
$fields = array(
'rc_id' => MW_UPGRADE_COPY,
'rc_timestamp' => MW_UPGRADE_COPY,
function upgradeQuerycache() {
// There's a format change in the namespace field
- $tabledef = <<<END
+ $tabledef = <<<ENDS
CREATE TABLE $1 (
-- A key name, generally the base name of of the special page.
qc_type char(32) NOT NULL,
KEY (qc_type,qc_value)
) TYPE=InnoDB
-END;
+ENDS;
$fields = array(
'qc_type' => MW_UPGRADE_COPY,
'qc_value' => MW_UPGRADE_COPY,
}
function filePath( $name ) {
- return wfImageDir( $name ) . "/$name";
+ if ( !isset( $this->repo ) ) {
+ $this->repo = RepoGroup::singleton()->getLocalRepo();
+ }
+ return $this->repo->getRootDirectory() . '/' . $this->repo->getHashPath( $name ) . $name;
}
function pokeFile( $orig, $new ) {
return array( $fname, $ext );
}
-/**
- * Given an image hash, check that the structure exists to save the image file
- * and create it if it doesn't
- *
- * @param $hash Part of an image hash, e.g. /f/fd/
- */
-function makeHashPath( $hash ) {
- global $wgUploadDirectory;
- $parts = explode( '/', substr( $hash, 1, strlen( $hash ) - 2 ) );
- if( !is_dir( $wgUploadDirectory . '/' . $parts[0] ) )
- mkdir( $wgUploadDirectory . '/' . $parts[0] );
- if( !is_dir( $wgUploadDirectory . '/' . $hash ) )
- mkdir( $wgUploadDirectory . '/' . $hash );
-}
-
-
-?>
\ No newline at end of file
+?>
$license = isset( $options['license'] ) ? $options['license'] : '';
# Batch "upload" operation
+ global $wgUploadDirectory;
foreach( $files as $file ) {
-
$base = wfBaseName( $file );
# Validate a title
$title = Title::makeTitleSafe( NS_IMAGE, $base );
- if( is_object( $title ) ) {
-
- # Check existence
- $image = new Image( $title );
- if( !$image->exists() ) {
-
- global $wgUploadDirectory;
-
- # copy() doesn't create paths so if the hash path doesn't exist, we
- # have to create it
- makeHashPath( wfGetHashPath( $image->name ) );
-
- # Stash the file
- echo( "Saving {$base}..." );
-
- if( copy( $file, $image->getFullPath() ) ) {
-
- echo( "importing..." );
-
- # Grab the metadata
- $image->loadFromFile();
-
- # Record the upload
- if( $image->recordUpload( '', $comment, $license ) ) {
-
- # We're done!
- echo( "done.\n" );
+ if( !is_object( $title ) ) {
+ echo( "{$base} could not be imported; a valid title cannot be produced\n" );
+ continue;
+ }
- } else {
- echo( "failed.\n" );
- }
+ # Check existence
+ $image = wfLocalFile( $title );
+ if( $image->exists() ) {
+ echo( "{$base} could not be imported; a file with this name exists in the wiki\n" );
+ continue;
+ }
- } else {
- echo( "failed.\n" );
- }
+ # Stash the file
+ echo( "Saving {$base}..." );
- } else {
- echo( "{$base} could not be imported; a file with this name exists in the wiki\n" );
- }
+ $archive = $image->publish( $file );
+ if ( WikiError::isError( $archive ) ) {
+ echo( "failed.\n" );
+ continue;
+ }
+ echo( "importing..." );
+ if ( $image->recordUpload( $archive, $comment, $license ) ) {
+ # We're done!
+ echo( "done.\n" );
} else {
- echo( "{$base} could not be imported; a valid title cannot be produced\n" );
+ echo( "failed.\n" );
}
-
}
} else {
$this->maxLag = 10; # if slaves are lagged more than 10 secs, wait
$this->dryrun = $dryrun;
+ if ( $dryrun ) {
+ $GLOBALS['wgReadOnly'] = 'Dry run mode, image upgrades are suppressed';
+ }
+ }
+
+ function getRepo() {
+ if ( !isset( $this->repo ) ) {
+ $this->repo = RepoGroup::singleton()->getLocalRepo();
+ }
+ return $this->repo;
}
function build() {
while( $row = $this->dbr->fetchObject( $result ) ) {
$update = call_user_func( $callback, $row );
- if( is_array( $update ) ) {
- if( !$this->dryrun ) {
- $this->dbw->update( $table,
- $update,
- array( $key => $row->$key ),
- $fname );
- }
+ if( $update ) {
$this->progress( 1 );
} else {
$this->progress( 0 );
}
function imageCallback( $row ) {
- if( $row->img_width ) {
- // Already processed
- return null;
- }
-
- // Fill in the new image info fields
- $info = $this->imageInfo( $row->img_name );
-
- global $wgMemc;
- $key = wfMemcKey( "Image", md5( $row->img_name ) );
- $wgMemc->delete( $key );
-
- return array(
- 'img_width' => $info['width'],
- 'img_height' => $info['height'],
- 'img_bits' => $info['bits'],
- 'img_media_type' => $info['media'],
- 'img_major_mime' => $info['major'],
- 'img_minor_mime' => $info['minor'] );
+ // Create a File object from the row
+ // This will also upgrade it
+ $file = $this->getRepo()->newFileFromRow( $row );
+ return $file->getUpgraded();
}
-
function buildOldImage() {
$this->buildTable( 'oldimage', 'oi_archive_name',
array( &$this, 'oldimageCallback' ) );
}
function oldimageCallback( $row ) {
- if( $row->oi_width ) {
- return null;
+ // Create a File object from the row
+ // This will also upgrade it
+ if ( $row->oi_archive_name == '' ) {
+ $this->log( "Empty oi_archive_name for oi_name={$row->oi_name}" );
+ return false;
}
-
- // Fill in the new image info fields
- $info = $this->imageInfo( $row->oi_archive_name, 'wfImageArchiveDir', $row->oi_name );
- return array(
- 'oi_width' => $info['width' ],
- 'oi_height' => $info['height'],
- 'oi_bits' => $info['bits' ] );
+ $file = $this->getRepo()->newFileFromRow( $row );
+ return $file->getUpgraded();
}
function crawlMissing() {
- global $wgUploadDirectory, $wgHashedUploadDirectory;
- if( $wgHashedUploadDirectory ) {
- for( $i = 0; $i < 16; $i++ ) {
- for( $j = 0; $j < 16; $j++ ) {
- $dir = sprintf( '%s%s%01x%s%02x',
- $wgUploadDirectory,
- DIRECTORY_SEPARATOR,
- $i,
- DIRECTORY_SEPARATOR,
- $i * 16 + $j );
- $this->crawlDirectory( $dir );
- }
- }
- } else {
- $this->crawlDirectory( $wgUploadDirectory );
- }
+ $repo = RepoGroup::singleton()->getLocalRepo();
+ $repo->enumFilesInFS( array( $this, 'checkMissingImage' ) );
}
- function crawlDirectory( $dir ) {
- if( !file_exists( $dir ) ) {
- return $this->log( "no directory, skipping $dir" );
- }
- if( !is_dir( $dir ) ) {
- return $this->log( "not a directory?! skipping $dir" );
- }
- if( !is_readable( $dir ) ) {
- return $this->log( "dir not readable, skipping $dir" );
- }
- $source = opendir( $dir );
- if( $source === false ) {
- return $this->log( "couldn't open dir, skipping $dir" );
+ function checkMissingImage( $fullpath ) {
+ $fname = 'ImageBuilder::checkMissingImage';
+ $filename = wfBaseName( $fullpath );
+ if( is_dir( $fullpath ) ) {
+ return;
}
-
- $this->log( "crawling $dir" );
- while( false !== ( $filename = readdir( $source ) ) ) {
- $fullpath = $dir . DIRECTORY_SEPARATOR . $filename;
- if( is_dir( $fullpath ) ) {
- continue;
- }
- if( is_link( $fullpath ) ) {
- $this->log( "skipping symlink at $fullpath" );
- continue;
- }
- $this->checkMissingImage( $filename, $fullpath );
+ if( is_link( $fullpath ) ) {
+ $this->log( "skipping symlink at $fullpath" );
+ return;
}
- closedir( $source );
- }
-
- function checkMissingImage( $filename, $fullpath ) {
- $fname = 'ImageBuilder::checkMissingImage';
$row = $this->dbw->selectRow( 'image',
array( 'img_name' ),
array( 'img_name' => $filename ),
$fname = 'ImageBuilder::addMissingImage';
$size = filesize( $fullpath );
- $info = $this->imageInfo( $filename );
+ $info = $this->imageInfo( $fullpath );
$timestamp = $this->dbw->timestamp( filemtime( $fullpath ) );
global $wgContLang;
$this->log( "Empty filename for $fullpath" );
return;
}
-
- $fields = array(
- 'img_name' => $filename,
- 'img_size' => $size,
- 'img_width' => $info['width'],
- 'img_height' => $info['height'],
- 'img_metadata' => '', // filled in on-demand
- 'img_bits' => $info['bits'],
- 'img_media_type' => $info['media'],
- 'img_major_mime' => $info['major'],
- 'img_minor_mime' => $info['minor'],
- 'img_description' => '(recovered file, missing upload log entry)',
- 'img_user' => 0,
- 'img_user_text' => 'Conversion script',
- 'img_timestamp' => $timestamp );
- if( !$this->dryrun ) {
- $this->dbw->insert( 'image', $fields, $fname );
+ if ( !$this->dryrun ) {
+ $file = wfLocalFile( $filename );
+ if ( !$file->recordUpload( '', '(recovered file, missing upload log entry)', '', '', '',
+ false, $timestamp ) )
+ {
+ $this->log( "Error uploading file $fullpath" );
+ return;
+ }
}
$this->log( $fullpath );
}
<?php
-require_once( 'PHPUnit.php' );
-require_once( '../includes/Defines.php' );
-require_once( '../includes/Article.php' );
-require_once( '../includes/Revision.php' );
-require_once( '../includes/ProfilerStub.php' );
-require_once( '../includes/normal/UtfNormal.php' );
-
-class ArticleTest extends PHPUnit_TestCase {
+class ArticleTest extends PHPUnit_Framework_TestCase {
var $saveGlobals = array();
- function ArticleTest( $name ) {
- $this->PHPUnit_TestCase( $name );
- }
-
function setUp() {
$globalSet = array(
'wgLegacyEncoding' => false,
Revision::getRevisionText( $row ), "getRevisionText" );
}
- function testCompressRevisionTextLatin1() {
- $GLOBALS['wgUseLatin1'] = true;
- $row->old_text = "Wiki est l'\xe9cole superieur !";
- $row->old_flags = Revision::compressRevisionText( $row->old_text );
- $this->assertFalse( false !== strpos( $row->old_flags, 'utf-8' ),
- "Flags should not contain 'utf-8'" );
- $this->assertFalse( false !== strpos( $row->old_flags, 'gzip' ),
- "Flags should not contain 'gzip'" );
- $this->assertEquals( "Wiki est l'\xe9cole superieur !",
- $row->old_text, "Direct check" );
- $this->assertEquals( "Wiki est l'\xe9cole superieur !",
- Revision::getRevisionText( $row ), "getRevisionText" );
- }
-
function testCompressRevisionTextUtf8Gzip() {
$GLOBALS['wgCompressRevisions'] = true;
$row->old_text = "Wiki est l'\xc3\xa9cole superieur !";
$this->assertEquals( "Wiki est l'\xc3\xa9cole superieur !",
Revision::getRevisionText( $row ), "getRevisionText" );
}
-
- function testCompressRevisionTextLatin1Gzip() {
- $GLOBALS['wgCompressRevisions'] = true;
- $GLOBALS['wgUseLatin1'] = true;
- $row = new stdClass;
- $row->old_text = "Wiki est l'\xe9cole superieur !";
- $row->old_flags = Revision::compressRevisionText( $row->old_text );
- $this->assertFalse( false !== strpos( $row->old_flags, 'utf-8' ),
- "Flags should not contain 'utf-8'" );
- $this->assertTrue( false !== strpos( $row->old_flags, 'gzip' ),
- "Flags should contain 'gzip'" );
- $this->assertEquals( "Wiki est l'\xe9cole superieur !",
- gzinflate( $row->old_text ), "Direct check" );
- $this->assertEquals( "Wiki est l'\xe9cole superieur !",
- Revision::getRevisionText( $row ), "getRevisionText" );
- }
-
}
?>
<?php
-require_once( 'PHPUnit.php' );
-require_once( '../includes/Defines.php' );
-require_once( '../includes/Database.php' );
-require_once( '../includes/GlobalFunctions.php' );
-
-class DatabaseTest extends PHPUnit_TestCase {
+class DatabaseTest extends PHPUnit_Framework_TestCase {
var $db;
- function DatabaseTest( $name ) {
- $this->PHPUnit_TestCase( $name );
- }
-
function setUp() {
- $this->db = new Database();
- }
-
- function tearDown() {
- unset( $this->db );
+ $this->db = wfGetDB( DB_SLAVE );
}
function testAddQuotesNull() {
<?php
-require_once( 'PHPUnit.php' );
-require_once( '../includes/Defines.php' );
-require_once( '../includes/GlobalFunctions.php' );
-require_once( '../includes/Exception.php' );
-
-class GlobalTest extends PHPUnit_TestCase {
- function GlobalTest( $name ) {
- $this->PHPUnit_TestCase( $name );
- }
-
- function setUp() {
- $this->save = array();
- $saveVars = array( 'wgReadOnlyFile' );
- foreach( $saveVars as $var ) {
- if( isset( $GLOBALS[$var] ) ) {
- $this->save[$var] = $GLOBALS[$var];
- }
- }
- $GLOBALS['wgReadOnlyFile'] = wfTempDir() . '/testReadOnly-' . mt_rand();
- }
-
- function tearDown() {
- foreach( $this->save as $var => $data ) {
- $GLOBALS[$var] = $data;
- }
- }
-
+class GlobalTest extends PHPUnit_Framework_TestCase {
function testRandom() {
# This could hypothetically fail, but it shouldn't ;)
$this->assertFalse(
--- /dev/null
+<?php
+
+class ImageFunctionsTest extends PHPUnit_Framework_TestCase {
+ function testFitBoxWidth() {
+ $vals = array(
+ array(
+ 'width' => 50,
+ 'height' => 50,
+ 'tests' => array(
+ 50 => 50,
+ 17 => 17,
+ 18 => 18 ) ),
+ array(
+ 'width' => 366,
+ 'height' => 300,
+ 'tests' => array(
+ 50 => 61,
+ 17 => 21,
+ 18 => 22 ) ),
+ array(
+ 'width' => 300,
+ 'height' => 366,
+ 'tests' => array(
+ 50 => 41,
+ 17 => 14,
+ 18 => 15 ) ),
+ array(
+ 'width' => 100,
+ 'height' => 400,
+ 'tests' => array(
+ 50 => 12,
+ 17 => 4,
+ 18 => 4 ) ) );
+ foreach( $vals as $row ) {
+ extract( $row );
+ foreach( $tests as $max => $expected ) {
+ $y = round( $expected * $height / $width );
+ $result = wfFitBoxWidth( $width, $height, $max );
+ $y2 = round( $result * $height / $width );
+ $this->assertEquals( $expected,
+ $result,
+ "($width, $height, $max) wanted: {$expected}x$y, got: {$result}x$y2" );
+ }
+ }
+ }
+}
+
+?>
+++ /dev/null
-<?php
-
-require_once( 'PHPUnit.php' );
-require_once( '../includes/Defines.php' );
-#require_once( '../includes/Profiling.php' );
-require_once( '../includes/GlobalFunctions.php' );
-require_once( '../includes/Image.php' );
-require_once( '../includes/ImageFunctions.php' );
-
-class ImageTest extends PHPUnit_TestCase {
- function ImageTest( $name ) {
- $this->PHPUnit_TestCase( $name );
- }
-
- function setUp() {
- }
-
- function tearDown() {
- }
-
- function testFitBoxWidth() {
- $vals = array(
- array(
- 'width' => 50,
- 'height' => 50,
- 'tests' => array(
- 50 => 50,
- 17 => 17,
- 18 => 18 ) ),
- array(
- 'width' => 366,
- 'height' => 300,
- 'tests' => array(
- 50 => 61,
- 17 => 21,
- 18 => 22 ) ),
- array(
- 'width' => 300,
- 'height' => 366,
- 'tests' => array(
- 50 => 41,
- 17 => 14,
- 18 => 15 ) ),
- array(
- 'width' => 100,
- 'height' => 400,
- 'tests' => array(
- 50 => 12,
- 17 => 4,
- 18 => 4 ) ) );
- foreach( $vals as $row ) {
- extract( $row );
- foreach( $tests as $max => $expected ) {
- $y = round( $expected * $height / $width );
- $result = wfFitBoxWidth( $width, $height, $max );
- $y2 = round( $result * $height / $width );
- $this->assertEquals( $expected,
- $result,
- "($width, $height, $max) wanted: {$expected}x$y, got: {$result}x$y2" );
- }
- }
- }
-
- /* TODO: many more! */
-}
-
-?>
Some quickie unit tests done with the PHPUnit testing framework. To run the
test suite, run 'make test' in this dir or 'php RunTests.php'
-You can install PHPUnit via pear like this:
-Firstly, register phpunit channel (it only need to be done once):
+PHPUnit is no longer maintained by PEAR. To get the current version of
+PHPUnit, first uninstall any old version of PHPUnit or PHPUnit2 from PEAR,
+then install the current version from phpunit.de like this:
+
# pear channel-discover pear.phpunit.de
-Then install the package:
# pear install phpunit/PHPUnit
-
-Or fetch and install it manually:
-http://www.phpunit.de/
<?php
-if( php_sapi_name() != 'cli' ) {
- echo 'Must be run from the command line.';
- die( -1 );
-}
+die( "This is broken, use run-test.php for now.\n" );
+require_once( dirname( __FILE__ ) . '/../maintenance/commandLine.inc' );
+ini_set( 'include_path', get_include_path() . PATH_SEPARATOR . /*$_SERVER['PHP_PEAR_INSTALL_DIR']*/ 'C:\php\pear' );
error_reporting( E_ALL );
-define( "MEDIAWIKI", true );
-
-set_include_path( get_include_path() . PATH_SEPARATOR . 'PHPUnit' );
-set_include_path( get_include_path() . PATH_SEPARATOR . '..' );
-
-// Error handling when requiring PHPUnit.php
-function phpunitErrorHandler( $erno, $errstr, $errfile, $errline) {
- echo "Unable to include PHPUnit.php, you should install it first (see README).\n";
- exit(1);
-}
-
-set_error_handler('phpunitErrorHandler');
-require_once( 'PHPUnit.php' );
-restore_error_handler();
+require_once( 'PHPUnit/Framework.php' );
$testOptions = array(
'mysql4' => array(
'database' => null ),
);
-if( file_exists( 'LocalTestSettings.php' ) ) {
- include( './LocalTestSettings.php' );
-}
-
$tests = array(
'GlobalTest',
'DatabaseTest',
'ImageTest'
);
-if( isset( $_SERVER['argv'][1] ) ) {
+if( count( $args ) ) {
// to override...
- $tests = array( $_SERVER['argv'][1] );
+ $tests = $args;
}
foreach( $tests as $test ) {
require_once( $test . '.php' );
- $suite = new PHPUnit_TestSuite( $test );
+ $suite = new PHPUnit_Framework_TestSuite( $test );
$result = PHPUnit::run( $suite );
echo $result->toString();
}
<?php
-require_once( 'PHPUnit.php' );
-require_once( '../includes/Defines.php' );
-#require_once( '../includes/Profiling.php' );
-require_once( '../includes/GlobalFunctions.php' );
-require_once( '../includes/Sanitizer.php' );
-
-class SanitizerTest extends PHPUnit_TestCase {
- function SanitizerTest( $name ) {
- $this->PHPUnit_TestCase( $name );
- }
-
- function setUp() {
- }
-
- function tearDown() {
- }
-
+class SanitizerTest extends PHPUnit_Framework_TestCase {
function testDecodeNamed() {
$this->assertEquals(
"\xc3\xa9cole",
<?php
-$IP = '..';
-require_once( 'PHPUnit.php' );
-require_once( '../includes/Defines.php' );
-require_once( '../includes/DefaultSettings.php' );
-#require_once( '../includes/Profiling.php' );
-require_once( '../includes/Hooks.php' );
-require_once( '../includes/MagicWord.php' );
-require_once( '../languages/Language.php' );
-
-require_once( '../includes/SearchEngine.php' );
-require_once( '../includes/SearchMySQL.php' );
-require_once( '../includes/SearchMySQL4.php' );
-
/** @todo document */
-class SearchEngine_TestCase extends PHPUnit_TestCase {
+class SearchEngine_TestCase extends PHPUnit_Framework_TestCase {
var $db, $search;
function insertSearchData() {
<?php
-
require_once( 'SearchEngineTest.php' );
-require_once( '../includes/SearchMySQL4.php' );
class SearchMySQL4Test extends SearchEngine_TestCase {
var $db;
--- /dev/null
+<?php
+
+require_once( dirname(__FILE__) . '/../maintenance/commandLine.inc' );
+ini_set( 'include_path', get_include_path() . PATH_SEPARATOR . /*$_SERVER['PHP_PEAR_INSTALL_DIR']*/ 'C:\php\pear' );
+require( 'PHPUnit/TextUI/Command.php' );
+
+?>
/**
* PHP script to stream out an image thumbnail.
- * If the file exists, we make do with abridged MediaWiki initialisation.
*/
-define( 'MW_NO_SETUP', 1 );
define( 'MW_NO_OUTPUT_COMPRESSION', 1 );
require_once( './includes/WebStart.php' );
wfProfileIn( 'thumb.php' );
wfProfileIn( 'thumb.php-start' );
-require_once( "$IP/includes/GlobalFunctions.php" );
-require_once( "$IP/includes/ImageFunctions.php" );
$wgTrivialMimeDetection = true; //don't use fancy mime detection, just check the file extension for jpg/gif/png.
require_once( "$IP/includes/StreamFile.php" );
-require_once( "$IP/includes/AutoLoader.php" );
// Get input parameters
if ( get_magic_quotes_gpc() ) {
// Some basic input validation
$fileName = strtr( $fileName, '\\/', '__' );
-// Work out paths, carefully avoiding constructing an Image object because that won't work yet
+// Stream the file if it exists already
try {
- $handler = thumbGetHandler( $fileName );
- if ( $handler ) {
- $imagePath = wfImageDir( $fileName ) . '/' . $fileName;
- $thumbName = $handler->makeParamString( $params ) . "-$fileName";
- $thumbPath = wfImageThumbDir( $fileName ) . '/' . $thumbName;
+ $img = wfLocalFile( $fileName );
+ if ( $img && false != ( $thumbName = $img->thumbName( $params ) ) ) {
+ $thumbPath = $img->getThumbPath( $thumbName );
- if ( is_file( $thumbPath ) && filemtime( $thumbPath ) >= filemtime( $imagePath ) ) {
+ if ( is_file( $thumbPath ) ) {
wfStreamFile( $thumbPath );
- // Can't log profiling data with no Setup.php
+ wfLogProfilingData();
exit;
}
}
} catch ( MWException $e ) {
- require_once( './includes/Setup.php' );
thumbInternalError( $e->getHTML() );
+ wfLogProfilingData();
exit;
}
-
-// OK, no valid thumbnail, time to get out the heavy machinery
wfProfileOut( 'thumb.php-start' );
-require_once( './includes/Setup.php' );
wfProfileIn( 'thumb.php-render' );
-$img = Image::newFromName( $fileName );
try {
if ( $img ) {
- $thumb = $img->transform( $params, Image::RENDER_NOW );
+ $thumb = $img->transform( $params, File::RENDER_NOW );
} else {
$thumb = false;
}
if ( $thumb && $thumb->getPath() && file_exists( $thumb->getPath() ) ) {
wfStreamFile( $thumb->getPath() );
-} elseif ( $img ) {
- if ( !$thumb ) {
- $msg = wfMsgHtml( 'thumbnail_error', 'Image::transform() returned false' );
+} else {
+ if ( !$img ) {
+ $msg = wfMsg( 'badtitletext' );
+ } elseif ( !$thumb ) {
+ $msg = wfMsgHtml( 'thumbnail_error', 'File::transform() returned false' );
} elseif ( $thumb->isError() ) {
$msg = $thumb->getHtmlMsg();
} elseif ( !$thumb->getPath() ) {
$msg = wfMsgHtml( 'thumbnail_error', 'Output file missing' );
}
thumbInternalError( $msg );
-} else {
- $badtitle = wfMsg( 'badtitle' );
- $badtitletext = wfMsg( 'badtitletext' );
- header( 'Cache-Control: no-cache' );
- header( 'Content-Type: text/html; charset=utf-8' );
- header( 'HTTP/1.1 500 Internal server error' );
- echo "<html><head>
- <title>$badtitle</title>
- <body>
-<h1>$badtitle</h1>
-<p>$badtitletext</p>
-</body></html>
-";
}
wfProfileOut( 'thumb.php-render' );
//--------------------------------------------------------------------------
-function thumbGetHandler( $fileName ) {
- // Determine type
- $magic = MimeMagic::singleton();
- $extPos = strrpos( $fileName, '.' );
- if ( $extPos === false ) {
- return false;
- }
- $mime = $magic->guessTypesForExtension( substr( $fileName, $extPos + 1 ) );
- return MediaHandler::getHandler( $mime );
-}
-
function thumbInternalError( $msg ) {
header( 'Cache-Control: no-cache' );
header( 'Content-Type: text/html; charset=utf-8' );