Merged filerepo-work branch:
authorTim Starling <tstarling@users.mediawiki.org>
Wed, 30 May 2007 21:02:32 +0000 (21:02 +0000)
committerTim Starling <tstarling@users.mediawiki.org>
Wed, 30 May 2007 21:02:32 +0000 (21:02 +0000)
* 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.

57 files changed:
RELEASE-NOTES
StartProfiler.php
includes/AutoLoader.php
includes/CategoryPage.php
includes/DefaultSettings.php
includes/Defines.php
includes/ExternalEdit.php
includes/GlobalFunctions.php
includes/Image.php [deleted file]
includes/ImageFunctions.php
includes/ImageGallery.php
includes/ImagePage.php
includes/ImageQueryPage.php
includes/Linker.php
includes/MediaTransformOutput.php
includes/Parser.php
includes/SearchEngine.php
includes/Setup.php
includes/Skin.php
includes/SpecialImagelist.php
includes/SpecialMIMEsearch.php
includes/SpecialNewimages.php
includes/SpecialUndelete.php
includes/SpecialUpload.php
includes/StreamFile.php
includes/filerepo/ArchivedFile.php [new file with mode: 0644]
includes/filerepo/FSRepo.php [new file with mode: 0644]
includes/filerepo/File.php [new file with mode: 0644]
includes/filerepo/ForeignDBFile.php [new file with mode: 0644]
includes/filerepo/ForeignDBRepo.php [new file with mode: 0644]
includes/filerepo/LocalFile.php [new file with mode: 0644]
includes/filerepo/LocalRepo.php [new file with mode: 0644]
includes/filerepo/OldLocalFile.php [new file with mode: 0644]
includes/filerepo/RepoGroup.php [new file with mode: 0644]
includes/filerepo/UnregisteredLocalFile.php [new file with mode: 0644]
includes/media/Bitmap.php
includes/media/DjVu.php
includes/media/Generic.php
includes/media/SVG.php
includes/normal/UtfNormal.php
maintenance/FiveUpgrade.inc
maintenance/cleanupImages.php
maintenance/importImages.inc.php
maintenance/importImages.php
maintenance/rebuildImages.php
tests/ArticleTest.php
tests/DatabaseTest.php
tests/GlobalTest.php
tests/ImageFunctionsTest.php [new file with mode: 0644]
tests/ImageTest.php [deleted file]
tests/README
tests/RunTests.php
tests/SanitizerTest.php
tests/SearchEngineTest.php
tests/SearchMySQL4Test.php
tests/run-test.php [new file with mode: 0644]
thumb.php

index 309ddd5..49e4204 100644 (file)
@@ -43,6 +43,11 @@ it from source control: http://www.mediawiki.org/wiki/Download_from_SVN
 * 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 ==
 
index 8fc3ff8..a256449 100644 (file)
@@ -1,22 +1,24 @@
 <?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;
 
 ?>
index ca198eb..09426d1 100644 (file)
@@ -96,8 +96,6 @@ function __autoload($className) {
                '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',
@@ -250,6 +248,19 @@ function __autoload($className) {
                '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',
index 48f545d..2c9a163 100644 (file)
@@ -147,7 +147,7 @@ class CategoryViewer {
        /**
         * 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 ) {
@@ -222,7 +222,7 @@ class CategoryViewer {
 
                        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 );
index c2fe7b5..f0bbe3e 100644 (file)
@@ -184,6 +184,49 @@ $wgFileStore['deleted']['directory'] = null; // Don't forget to set this.
 $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
@@ -355,6 +398,10 @@ $wgActionPaths = array();
  * 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 */
index 98e7627..5705c38 100644 (file)
@@ -205,5 +205,61 @@ define( 'LIST_SET', 2 );
 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 );
+
+
 
 ?>
index c8ed8bd..4d9f1e2 100644 (file)
@@ -46,7 +46,7 @@ class ExternalEdit {
                        $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;
index 88e1fe5..6ab81e5 100644 (file)
@@ -2272,4 +2272,26 @@ function &wfGetDB( $db = DB_LAST, $groups = array() ) {
        $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 );
+}
+
 ?>
diff --git a/includes/Image.php b/includes/Image.php
deleted file mode 100644 (file)
index adcee14..0000000
+++ /dev/null
@@ -1,2154 +0,0 @@
-<?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 );
-
-?>
index d04110d..f7c73db 100644 (file)
@@ -1,113 +1,4 @@
 <?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
index fba7714..3e19dec 100644 (file)
@@ -127,22 +127,26 @@ class ImageGallery
        /**
         * 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 ) );
        }
 
 
@@ -195,12 +199,12 @@ class ImageGallery
                $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>';
@@ -222,7 +226,7 @@ class ImageGallery
                        //$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 {
index ac97609..f3109c5 100644 (file)
@@ -18,6 +18,14 @@ class ImagePage extends Article {
        /* 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
@@ -31,8 +39,6 @@ class ImagePage extends Article {
        function view() {
                global $wgOut, $wgShowEXIF, $wgRequest, $wgUser;
 
-               $this->img = new Image( $this->mTitle );
-
                $diff = $wgRequest->getVal( 'diff' );
                $diffOnly = $wgRequest->getBool( 'diffonly', $wgUser->getOption( 'diffonly' ) );
 
@@ -160,7 +166,7 @@ class ImagePage extends Article {
         * 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();
@@ -332,26 +338,26 @@ class ImagePage extends Article {
                                $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 {
@@ -365,27 +371,21 @@ END
        }
 
        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;
                }
        }
 
@@ -402,7 +402,7 @@ END
        function uploadLinksBox() {
                global $wgUser, $wgOut;
 
-               if( $this->img->fromSharedDirectory )
+               if( !$this->img->isLocal() )
                        return;
 
                $sk = $wgUser->getSkin();
@@ -441,7 +441,7 @@ END
                $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,
@@ -530,8 +530,6 @@ END
                        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 ) ) {
@@ -655,39 +653,23 @@ END
                        $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() );
        }
        
@@ -695,7 +677,6 @@ END
         * 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' );
@@ -708,6 +689,18 @@ END
                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() );
+       }
+
 }
 
 /**
@@ -715,8 +708,10 @@ END
  * @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() {
@@ -738,11 +733,12 @@ class ImageHistoryList {
                $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 );
@@ -752,8 +748,8 @@ class ImageHistoryList {
                                $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=' .
@@ -769,8 +765,12 @@ class ImageHistoryList {
                                $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 );
@@ -782,7 +782,6 @@ class ImageHistoryList {
                $s .= "</li>\n";
                return $s;
        }
-
 }
 
 
index 93f090a..a2caa31 100644 (file)
@@ -30,8 +30,8 @@ class ImageQueryPage extends QueryPage {
                        # $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 ) );
                                }
                        }
 
@@ -49,7 +49,7 @@ class ImageQueryPage extends QueryPage {
                $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;
        }
 
index 326b3bc..6abcaa3 100644 (file)
@@ -440,13 +440,14 @@ class Linker {
        * @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 );
                }
 
@@ -459,7 +460,7 @@ class Linker {
                        $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' );
@@ -490,10 +491,10 @@ class Linker {
                        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 {
@@ -524,7 +525,7 @@ class Linker {
                );
 
                if ( !$thumb ) {
-                       $s = $this->makeBrokenImageLinkObj( $img->getTitle() );
+                       $s = $this->makeBrokenImageLinkObj( $nt );
                } else {
                        $s = $thumb->toHtml( $imgAttribs, $linkAttribs );
                }
@@ -536,10 +537,12 @@ class Linker {
 
        /**
         * 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;
 
@@ -548,45 +551,54 @@ class Linker {
                        $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(
@@ -645,10 +657,10 @@ class Linker {
                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 );
        }
 
        /**
@@ -666,13 +678,13 @@ class Linker {
                        ### 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() );
index f3e024a..28cdd06 100644 (file)
@@ -1,7 +1,7 @@
 <?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
  */
index a570423..dbd8f03 100644 (file)
@@ -1806,8 +1806,8 @@ class Parser
                                }
                                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.
@@ -4393,7 +4393,7 @@ class Parser
                        );
                        $html = $pout->getText();
 
-                       $ig->add( new Image( $nt ), $html );
+                       $ig->add( $nt, $html );
 
                        # Only add real images (bug #5586)
                        if ( $nt->getNamespace() == NS_IMAGE ) {
index 7c627ff..005d858 100644 (file)
@@ -122,8 +122,8 @@ class SearchEngine {
                # 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;
                        }
                }
index 7f964a1..3f9746b 100644 (file)
@@ -54,6 +54,59 @@ if( $wgTmpDirectory === false ) $wgTmpDirectory = "{$wgUploadDirectory}/tmp";
 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' );
index e53d579..f10e095 100644 (file)
@@ -740,8 +740,8 @@ END;
                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>";
index 59fc78f..5eed551 100644 (file)
@@ -115,7 +115,9 @@ class ImageListPager extends TablePager {
                        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 ) {
index 5ecfb35..3e7d12f 100644 (file)
@@ -66,7 +66,7 @@ class MIMEsearchPage extends QueryPage {
                $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 ),
index 72b169b..43009a7 100644 (file)
@@ -135,10 +135,9 @@ function wfSpecialNewimages( $par, $specialPage ) {
                $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 ) ) {
index 7a322db..1706833 100644 (file)
@@ -269,7 +269,7 @@ class PageArchive {
                $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;
index e3707f1..79ebef1 100644 (file)
@@ -27,6 +27,7 @@ class UploadForm {
        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
@@ -412,13 +413,13 @@ class UploadForm {
 
                        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() ) {
@@ -452,7 +453,7 @@ class UploadForm {
                        } 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);
@@ -500,8 +501,8 @@ class UploadForm {
                         * 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,
@@ -512,7 +513,7 @@ class UploadForm {
                                $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 );
                        }
@@ -534,48 +535,13 @@ class UploadForm {
        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;
        }
 
@@ -593,19 +559,14 @@ class UploadForm {
         */
        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;
        }
 
        /**
@@ -662,7 +623,7 @@ class UploadForm {
                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 );
 
@@ -1274,15 +1235,10 @@ class UploadForm {
         * @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 ) ) {
@@ -1328,5 +1284,17 @@ class UploadForm {
 
                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() );
+       }
 }
 ?>
index dc653e5..7bda6ae 100644 (file)
@@ -31,6 +31,9 @@ function wfStreamFile( $fname ) {
                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 );
diff --git a/includes/filerepo/ArchivedFile.php b/includes/filerepo/ArchivedFile.php
new file mode 100644 (file)
index 0000000..fc92b9a
--- /dev/null
@@ -0,0 +1,112 @@
+<?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;
+               }
+       }
+}
+
+?>
diff --git a/includes/filerepo/FSRepo.php b/includes/filerepo/FSRepo.php
new file mode 100644 (file)
index 0000000..f1eaf29
--- /dev/null
@@ -0,0 +1,368 @@
+<?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 );
+       }
+}
+
+?>
diff --git a/includes/filerepo/File.php b/includes/filerepo/File.php
new file mode 100644 (file)
index 0000000..1e59d2f
--- /dev/null
@@ -0,0 +1,985 @@
+<?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;
+       }
+}
+
+?>
diff --git a/includes/filerepo/ForeignDBFile.php b/includes/filerepo/ForeignDBFile.php
new file mode 100644 (file)
index 0000000..6c903e9
--- /dev/null
@@ -0,0 +1,39 @@
+<?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();
+       }
+}
+?>
diff --git a/includes/filerepo/ForeignDBRepo.php b/includes/filerepo/ForeignDBRepo.php
new file mode 100644 (file)
index 0000000..fc3c829
--- /dev/null
@@ -0,0 +1,51 @@
+<?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' );
+       }
+}
+
+?>
diff --git a/includes/filerepo/LocalFile.php b/includes/filerepo/LocalFile.php
new file mode 100644 (file)
index 0000000..3a70fd7
--- /dev/null
@@ -0,0 +1,1331 @@
+<?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 );
+
+?>
diff --git a/includes/filerepo/LocalRepo.php b/includes/filerepo/LocalRepo.php
new file mode 100644 (file)
index 0000000..62d937d
--- /dev/null
@@ -0,0 +1,26 @@
+<?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' );
+               }
+       }
+}
diff --git a/includes/filerepo/OldLocalFile.php b/includes/filerepo/OldLocalFile.php
new file mode 100644 (file)
index 0000000..1a802a0
--- /dev/null
@@ -0,0 +1,222 @@
+<?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
diff --git a/includes/filerepo/RepoGroup.php b/includes/filerepo/RepoGroup.php
new file mode 100644 (file)
index 0000000..3055382
--- /dev/null
@@ -0,0 +1,98 @@
+<?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 );
+       }
+}
+
+?>
diff --git a/includes/filerepo/UnregisteredLocalFile.php b/includes/filerepo/UnregisteredLocalFile.php
new file mode 100644 (file)
index 0000000..15dcf00
--- /dev/null
@@ -0,0 +1,109 @@
+<?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;
+               }
+       }
+}
+?>
index 8024cb3..b8ccbd2 100644 (file)
@@ -51,7 +51,7 @@ class BitmapHandler extends ImageHandler {
                $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" );
 
@@ -61,7 +61,10 @@ class BitmapHandler extends ImageHandler {
                        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';
index 00a1b51..1dab2f0 100644 (file)
@@ -69,7 +69,7 @@ class DjVuHandler extends ImageHandler {
                }
                $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' ) );
index 189045a..c21ad79 100644 (file)
@@ -152,7 +152,7 @@ abstract class MediaHandler {
         * 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]
@@ -220,7 +220,7 @@ abstract class ImageHandler extends MediaHandler {
                                $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;
                }
@@ -254,7 +254,7 @@ abstract class ImageHandler extends MediaHandler {
                        return false;
                }
 
-               $height = Image::scaleHeight( $srcWidth, $srcHeight, $width );
+               $height = File::scaleHeight( $srcWidth, $srcHeight, $width );
                return true;
        }
 
index 2dfbd02..03a5560 100644 (file)
@@ -31,7 +31,7 @@ class SvgHandler extends ImageHandler {
                        $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;
        }
@@ -46,7 +46,7 @@ class SvgHandler extends ImageHandler {
                $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 );
index 43bbafd..313fe9a 100644 (file)
@@ -29,59 +29,6 @@ $utfCanonicalDecomp = NULL;
 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
  */
index c32f1b2..9a882bc 100644 (file)
@@ -693,10 +693,7 @@ END;
                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,
@@ -711,20 +708,13 @@ END;
 
                $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'];
                }
 
@@ -896,7 +886,7 @@ END;
        }
 
        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
@@ -926,7 +916,7 @@ CREATE TABLE $1 (
   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,
@@ -940,7 +930,7 @@ END;
        }
 
        function upgradeArchive() {
-               $tabledef = <<<END
+               $tabledef = <<<ENDS
 CREATE TABLE $1 (
   ar_namespace int NOT NULL default '0',
   ar_title varchar(255) binary NOT NULL default '',
@@ -960,7 +950,7 @@ CREATE TABLE $1 (
   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,
@@ -979,7 +969,7 @@ END;
        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',
@@ -993,7 +983,7 @@ CREATE TABLE $1 (
   KEY (il_to)
 
 ) TYPE=InnoDB
-END;
+ENDS;
                        $fields = array(
                                'il_from' => MW_UPGRADE_COPY,
                                'il_to'   => MW_UPGRADE_ENCODE );
@@ -1004,7 +994,7 @@ END;
        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 '',
@@ -1015,7 +1005,7 @@ CREATE TABLE $1 (
   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,
@@ -1028,7 +1018,7 @@ END;
        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 '',
@@ -1044,7 +1034,7 @@ CREATE TABLE $1 (
   INDEX ipb_user (ipb_user)
 
 ) TYPE=InnoDB
-END;
+ENDS;
                        $fields = array(
                                'ipb_id'        => MW_UPGRADE_COPY,
                                'ipb_address'   => MW_UPGRADE_COPY,
@@ -1060,7 +1050,7 @@ END;
 
        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 '',
@@ -1098,7 +1088,7 @@ CREATE TABLE $1 (
   INDEX rc_ip (rc_ip)
 
 ) TYPE=InnoDB
-END;
+ENDS;
                $fields = array(
                        'rc_id'             => MW_UPGRADE_COPY,
                        'rc_timestamp'      => MW_UPGRADE_COPY,
@@ -1124,7 +1114,7 @@ END;
 
        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,
@@ -1139,7 +1129,7 @@ CREATE TABLE $1 (
   KEY (qc_type,qc_value)
 
 ) TYPE=InnoDB
-END;
+ENDS;
                $fields = array(
                        'qc_type'      => MW_UPGRADE_COPY,
                        'qc_value'     => MW_UPGRADE_COPY,
index 3ec2c44..156e06b 100644 (file)
@@ -89,7 +89,10 @@ class ImageCleanup extends TableCleanup {
        }
        
        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 ) {
index c8fbc54..e752996 100644 (file)
@@ -47,20 +47,4 @@ function splitFilename( $filename ) {
        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
+?>
index f2d5398..a3a8f83 100644 (file)
@@ -42,56 +42,40 @@ if( count( $args ) > 1 ) {
        $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 {
index 4c02dc9..4aff75b 100644 (file)
@@ -40,6 +40,16 @@ class ImageBuilder extends FiveUpgrade {
 
                $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() {
@@ -94,13 +104,7 @@ class ImageBuilder extends FiveUpgrade {
 
                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 );
@@ -116,97 +120,43 @@ class ImageBuilder extends FiveUpgrade {
        }
 
        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 ),
@@ -224,7 +174,7 @@ class ImageBuilder extends FiveUpgrade {
                $fname = 'ImageBuilder::addMissingImage';
 
                $size = filesize( $fullpath );
-               $info = $this->imageInfo( $filename );
+               $info = $this->imageInfo( $fullpath );
                $timestamp = $this->dbw->timestamp( filemtime( $fullpath ) );
 
                global $wgContLang;
@@ -242,23 +192,14 @@ class ImageBuilder extends FiveUpgrade {
                        $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 );
        }
index 7bda09e..3276fc7 100644 (file)
@@ -1,19 +1,8 @@
 <?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,
@@ -104,20 +93,6 @@ class ArticleTest extends PHPUnit_TestCase {
                        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 !";
@@ -131,23 +106,6 @@ class ArticleTest extends PHPUnit_TestCase {
                $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" );
-       }
-
 }
 
 ?>
index edb785d..c0347a3 100644 (file)
@@ -1,23 +1,10 @@
 <?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() {
index 5de64f8..e15556e 100644 (file)
@@ -1,32 +1,6 @@
 <?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(
diff --git a/tests/ImageFunctionsTest.php b/tests/ImageFunctionsTest.php
new file mode 100644 (file)
index 0000000..69d46a8
--- /dev/null
@@ -0,0 +1,48 @@
+<?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" );
+                       }
+               }
+       }
+}
+
+?>
diff --git a/tests/ImageTest.php b/tests/ImageTest.php
deleted file mode 100644 (file)
index 5957bec..0000000
+++ /dev/null
@@ -1,67 +0,0 @@
-<?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! */
-}
-
-?>
index df95716..3bbf970 100644 (file)
@@ -1,11 +1,9 @@
 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/
index 1d088dc..aac1eb3 100644 (file)
@@ -1,25 +1,11 @@
 <?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(
@@ -34,10 +20,6 @@ $testOptions = array(
                'database' => null ),
        );
 
-if( file_exists( 'LocalTestSettings.php' ) ) {
-       include( './LocalTestSettings.php' );
-}
-
 $tests = array(
        'GlobalTest',
        'DatabaseTest',
@@ -47,14 +29,14 @@ $tests = array(
        '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();
 }
index ea53f91..0322da6 100644 (file)
@@ -1,22 +1,6 @@
 <?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",
index d780941..1494990 100644 (file)
@@ -1,20 +1,7 @@
 <?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() {
index 6277e6c..4f02f64 100644 (file)
@@ -1,7 +1,5 @@
 <?php
-
 require_once( 'SearchEngineTest.php' );
-require_once( '../includes/SearchMySQL4.php' );
 
 class SearchMySQL4Test extends SearchEngine_TestCase {
        var $db;
diff --git a/tests/run-test.php b/tests/run-test.php
new file mode 100644 (file)
index 0000000..dddc6f7
--- /dev/null
@@ -0,0 +1,7 @@
+<?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' );
+
+?>
index b92a6f7..e6ad8e5 100644 (file)
--- a/thumb.php
+++ b/thumb.php
@@ -2,20 +2,15 @@
 
 /**
  * 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() ) {
@@ -40,36 +35,30 @@ unset( $params['r'] );
 // 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;
        }
@@ -80,9 +69,11 @@ try {
 
 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() ) {
@@ -91,19 +82,6 @@ if ( $thumb && $thumb->getPath() && file_exists( $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' );
@@ -112,17 +90,6 @@ wfLogProfilingData();
 
 //--------------------------------------------------------------------------
 
-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' );