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
 * 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 ==
 
 
 == Bugfixes since 1.10 ==
 
index 8fc3ff8..a256449 100644 (file)
@@ -1,22 +1,24 @@
 <?php
 
 <?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:
  *
 
 /**
  * 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 ) ) {
  *   $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 {
  *       $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
  */
  *   }
  * 
  * 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',
                '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',
                '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',
 
                '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',
                # 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
         */
        /**
         * Add a page in the image namespace
         */
-       function addImage( $title, $sortkey, $pageLength, $isRedirect = false ) {
+       function addImage( Title $title, $sortkey, $pageLength, $isRedirect = false ) {
                if ( $this->showGallery ) {
                        $image = new Image( $title );
                        if( $this->flip ) {
                if ( $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 );
 
                        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 );
                                $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
 
 $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
 /**
  * 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.
  * 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 */
  */
 $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);
 
 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";
                        $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;
                        $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;
 }
        $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
 <?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
 /**
  * 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.
         *
        /**
         * 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.
         */
         * @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.
        *
        }
 
        /**
        * 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.
        */
        * @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 ) {
                $params = array( 'width' => $this->mWidths, 'height' => $this->mHeights );
                $i = 0;
                foreach ( $this->mImages as $pair ) {
-                       $img =& $pair[0];
+                       $nt = $pair[0];
                        $text = $pair[1];
 
                        $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>';
                                # 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 ) {
                        //$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 {
                                        $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;
 
        /* 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
        /**
         * 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;
 
        function view() {
                global $wgOut, $wgShowEXIF, $wgRequest, $wgUser;
 
-               $this->img = new Image( $this->mTitle );
-
                $diff = $wgRequest->getVal( 'diff' );
                $diffOnly = $wgRequest->getBool( 'diffonly', $wgUser->getOption( 'diffonly' ) );
 
                $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() {
         * 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();
                        return '';
                }
                return Article::getContent();
@@ -332,26 +338,26 @@ class ImagePage extends Article {
                                $dirmark = $wgContLang->getDirMark();
                                if (!$this->img->isSafeFile()) {
                                        $warning = wfMsg( 'mediawarning' );
                                $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>
 <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 {
                                                );
                                } else {
-                                       $wgOut->addWikiText( <<<END
+                                       $wgOut->addWikiText( <<<EOT
 <div class="fullMedia">$infores
 [[Media:$filename|$filename]]$dirmark <span class="fileInfo"> $info</span>
 </div>
 <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 {
                                $this->printSharedImageText();
                        }
                } else {
@@ -365,27 +371,21 @@ END
        }
 
        function printSharedImageText() {
        }
 
        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();
                        $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;
 
        function uploadLinksBox() {
                global $wgUser, $wgOut;
 
-               if( $this->img->fromSharedDirectory )
+               if( !$this->img->isLocal() )
                        return;
 
                $sk = $wgUser->getSkin();
                        return;
 
                $sk = $wgUser->getSkin();
@@ -441,7 +441,7 @@ END
                $line = $this->img->nextHistoryLine();
 
                if ( $line ) {
                $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,
                        $s = $list->beginImageHistoryList() .
                                $list->imageHistoryLine( true, wfTimestamp(TS_MW, $line->img_timestamp),
                                        $this->mTitle->getDBkey(),  $line->img_user,
@@ -530,8 +530,6 @@ END
                        return;
                }
 
                        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 ) ) {
                # 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;
                }
                        $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
                        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' ) );
 
 
                $wgOut->setPagetitle( wfMsg( 'actioncomplete' ) );
                $wgOut->setRobotpolicy( 'noindex,nofollow' );
                $wgOut->addHTML( wfMsg( 'imagereverted' ) );
 
-               $descTitle = $img->getTitle();
+               $descTitle = $this->img->getTitle();
                $wgOut->returnToMain( false, $descTitle->getPrefixedText() );
        }
        
                $wgOut->returnToMain( false, $descTitle->getPrefixedText() );
        }
        
@@ -695,7 +677,6 @@ END
         * Override handling of action=purge
         */
        function doPurge() {
         * 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' );
                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();
        }
 
                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 {
  * @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() {
        }
 
        function beginImageHistoryList() {
@@ -738,11 +733,12 @@ class ImageHistoryList {
                $del = wfMsgHtml( 'deleteimg' );
                $delall = wfMsgHtml( 'deleteimgcompletely' );
                $cur = wfMsgHtml( 'cur' );
                $del = wfMsgHtml( 'deleteimg' );
                $delall = wfMsgHtml( 'deleteimgcompletely' );
                $cur = wfMsgHtml( 'cur' );
+               $local = $this->img->isLocal();
 
                if ( $iscur ) {
 
                if ( $iscur ) {
-                       $url = Image::imageUrl( $img );
+                       $url = htmlspecialchars( $this->img->getURL() );
                        $rlink = $cur;
                        $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 );
                                $link = $wgTitle->escapeLocalURL( 'image=' . $wgTitle->getPartialURL() .
                                  '&action=delete' );
                                $style = $this->skin->getInternalLinkAttributes( $link, $delall );
@@ -752,8 +748,8 @@ class ImageHistoryList {
                                $dlink = $del;
                        }
                } else {
                                $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=' .
                                $token = urlencode( $wgUser->editToken( $img ) );
                                $rlink = $this->skin->makeKnownLinkObj( $wgTitle,
                                           wfMsgHtml( 'revertimg' ), 'action=revert&oldimage=' .
@@ -769,8 +765,12 @@ class ImageHistoryList {
                                $dlink = $del;
                        }
                }
                                $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 );
                $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;
        }
                $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 );
                        # $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 )
                $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;
        }
 
                        : null;
        }
 
index 326b3bc..6abcaa3 100644 (file)
@@ -440,13 +440,14 @@ class Linker {
        * @return string
        */
        function makeImageLinkObj( $nt, $label, $alt, $align = '', $params = array(), $framed = false,
        * @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;
 
        {
                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 );
                }
 
                        return $this->makeKnownLinkObj( $nt );
                }
 
@@ -459,7 +460,7 @@ class Linker {
                        $postfix = '</div>';
                        $align   = 'none';
                }
                        $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' );
                        $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';
                        }
                        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 {
                        # Create a resized image, without the additional thumbnail features
                        $thumb = $img->transform( $params );
                } else {
@@ -524,7 +525,7 @@ class Linker {
                );
 
                if ( !$thumb ) {
                );
 
                if ( !$thumb ) {
-                       $s = $this->makeBrokenImageLinkObj( $img->getTitle() );
+                       $s = $this->makeBrokenImageLinkObj( $nt );
                } else {
                        $s = $thumb->toHtml( $imgAttribs, $linkAttribs );
                }
                } else {
                        $s = $thumb->toHtml( $imgAttribs, $linkAttribs );
                }
@@ -536,10 +537,12 @@ class Linker {
 
        /**
         * Make HTML for a thumbnail including image, border and caption
 
        /**
         * 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;
                global $wgStylePath, $wgContLang;
+               $exists = $img && $img->exists();
 
                $page = isset( $params['page'] ) ? $params['page'] : false;
 
 
                $page = isset( $params['page'] ) ? $params['page'] : false;
 
@@ -548,45 +551,54 @@ class Linker {
                        $params['width'] = isset( $params['upright'] ) ? 130 : 180;
                }
                $thumb = false;
                        $params['width'] = isset( $params['upright'] ) ? 130 : 180;
                }
                $thumb = false;
-               if ( $manual_thumb != '' ) {
-                       # Use manually specified thumbnail
-                       $manual_title = Title::makeTitleSafe( NS_IMAGE, $manual_thumb );
-                       if( $manual_title ) {
-                               $manual_img = new Image( $manual_title );
-                               $thumb = $manual_img->getUnscaledThumb();
-                       }
-               } elseif ( $framed ) {
-                       // Use image dimensions, don't scale
-                       $thumb = $img->getUnscaledThumb( $page );
+
+               if ( !$exists ) {
+                       $outerWidth = $params['width'] + 2;
                } else {
                } 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 ) : '';
                }
 
                $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;\">";
 
                $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 = '';
                        $zoomicon = '';
-               } elseif( !$img->exists() ) {
-                       $s .= $this->makeBrokenImageLinkObj( $img->getTitle() );
+               } elseif ( !$thumb ) {
+                       $s .= htmlspecialchars( wfMsg( 'thumbnail_error', '' ) );
                        $zoomicon = '';
                } else {
                        $imgAttribs = array(
                        $zoomicon = '';
                } else {
                        $imgAttribs = array(
@@ -645,10 +657,10 @@ class Linker {
                return $s;
        }
 
                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 );
                $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 {
                        ### 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  = $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() );
                                $class = 'new';
                        }
                        $alt = htmlspecialchars( $title->getText() );
index f3e024a..28cdd06 100644 (file)
@@ -1,7 +1,7 @@
 <?php
 
 /**
 <?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
  */
  *
  * @addtogroup Media
  */
index a570423..dbd8f03 100644 (file)
@@ -1806,8 +1806,8 @@ class Parser
                                }
                                continue;
                        } elseif( $ns == NS_IMAGE ) {
                                }
                                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.
                                        // 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();
 
                        );
                        $html = $pout->getText();
 
-                       $ig->add( new Image( $nt ), $html );
+                       $ig->add( $nt, $html );
 
                        # Only add real images (bug #5586)
                        if ( $nt->getNamespace() == NS_IMAGE ) {
 
                        # 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 ) {
                # 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;
                        }
                }
                                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";
 
 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' );
 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();
                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>";
                                        $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 );
                        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 ) {
                                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 );
 
                $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 ),
                $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 );
                $ut = $s->img_user_text;
 
                $nt = Title::newFromText( $name, NS_IMAGE );
-               $img = new Image( $nt );
                $ul = $sk->makeLinkObj( Title::makeTitle( NS_USER, $ut ), $ut );
 
                $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 ) ) {
 
                $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 ) {
                $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;
                        $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 $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
 
        # 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();
 
                        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 ) );
 
                        // 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() ) {
                        }
 
                        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 );
                        } 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);
                                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.
                         */
                         * Update the upload log and create the description page
                         * if it's a new file.
                         */
-                       $img = Image::newFromName( $this->mUploadSaveName );
-                       $success = $img->recordUpload( $this->mUploadOldVersion,
+                       $this->mImage = wfLocalFile( $this->mUploadSaveName );
+                       $success = $this->mImage->recordUpload( $this->mUploadOldVersion,
                                                        $this->mUploadDescription,
                                                        $this->mLicense,
                                                        $this->mUploadCopyStatus,
                                                        $this->mUploadDescription,
                                                        $this->mLicense,
                                                        $this->mUploadCopyStatus,
@@ -512,7 +513,7 @@ class UploadForm {
                                $this->showSuccess();
                                wfRunHooks( 'UploadComplete', array( &$img ) );
                        } else {
                                $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 );
                        }
                                // 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;
 
        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;
                        return false;
-               } else {
-                       wfDebug("$fname: wrote tempfile $tempName to ".$this->mSavedFile."\n");
                }
                }
-
-               chmod( $this->mSavedFile, 0644 );
+               $this->mUploadOldVersion = $archiveName;
                return true;
        }
 
                return true;
        }
 
@@ -593,19 +559,14 @@ class UploadForm {
         */
        function saveTempUploadedFile( $saveName, $tempName ) {
                global $wgOut;
         */
        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;
                        return false;
+               } else {
+                       return $result;
                }
                }
-
-               return $stash;
        }
 
        /**
        }
 
        /**
@@ -662,7 +623,7 @@ class UploadForm {
                global $wgUser, $wgOut, $wgContLang;
 
                $sk = $wgUser->getSkin();
                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 );
 
                $dname = $wgContLang->getNsText( NS_IMAGE ) . ':'.$this->mUploadSaveName;
                $dlink = $sk->makeKnownLink( $dname, $dname );
 
@@ -1274,15 +1235,10 @@ class UploadForm {
         * @access private
         */
        function checkOverwrite( $name ) {
         * @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 = '';
 
                $error = '';
-               if( $img->exists() ) {
+               if( $img ) {
                        global $wgUser, $wgOut;
                        if( $img->isLocal() ) {
                                if( !self::userCanReUpload( $wgUser, $img->name ) ) {
                        global $wgUser, $wgOut;
                        if( $img->isLocal() ) {
                                if( !self::userCanReUpload( $wgUser, $img->name ) ) {
@@ -1328,5 +1284,17 @@ class UploadForm {
 
                return $user->getID() == $row->img_user;
        }
 
                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');
        }
 
                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 );
        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();
                $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" );
 
                $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 );
                }
 
                        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';
                        $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'];
                }
                $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' ) );
                $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 ) {
         * 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]
                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['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;
                }
                if ( !$this->validateThumbParams( $params['width'], $params['height'], $srcWidth, $srcHeight, $mimeType ) ) {
                        return false;
                }
@@ -254,7 +254,7 @@ abstract class ImageHandler extends MediaHandler {
                        return false;
                }
 
                        return false;
                }
 
-               $height = Image::scaleHeight( $srcWidth, $srcHeight, $width );
+               $height = File::scaleHeight( $srcWidth, $srcHeight, $width );
                return true;
        }
 
                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;
                        $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;
        }
                }
                return true;
        }
@@ -46,7 +46,7 @@ class SvgHandler extends ImageHandler {
                $clientHeight = $params['height'];
                $physicalWidth = $params['physicalWidth'];
                $physicalHeight = $params['physicalHeight'];
                $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 );
 
                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;
 
 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
  */
 /**
  * For using the ICU wrapper
  */
index c32f1b2..9a882bc 100644 (file)
@@ -693,10 +693,7 @@ END;
                return $copy;
        }
 
                return $copy;
        }
 
-       function imageInfo( $name, $subdirCallback='wfImageDir', $basename = null ) {
-               if( is_null( $basename ) ) $basename = $name;
-               $dir = call_user_func( $subdirCallback, $basename );
-               $filename = $dir . '/' . $name;
+       function imageInfo( $filename ) {
                $info = array(
                        'width'  => 0,
                        'height' => 0,
                $info = array(
                        'width'  => 0,
                        'height' => 0,
@@ -711,20 +708,13 @@ END;
 
                $info['media'] = $magic->getMediaType( $filename, $mime );
 
 
                $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'];
                }
 
                        $info['bits'] = $gis['bits'];
                }
 
@@ -896,7 +886,7 @@ END;
        }
 
        function upgradeLogging() {
        }
 
        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
 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
   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,
                $fields = array(
                        'log_type'      => MW_UPGRADE_COPY,
                        'log_action'    => MW_UPGRADE_COPY,
@@ -940,7 +930,7 @@ END;
        }
 
        function upgradeArchive() {
        }
 
        function upgradeArchive() {
-               $tabledef = <<<END
+               $tabledef = <<<ENDS
 CREATE TABLE $1 (
   ar_namespace int NOT NULL default '0',
   ar_title varchar(255) binary NOT NULL default '',
 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
   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,
                $fields = array(
                        'ar_namespace'  => MW_UPGRADE_COPY,
                        'ar_title'      => MW_UPGRADE_ENCODE,
@@ -979,7 +969,7 @@ END;
        function upgradeImagelinks() {
                global $wgUseLatin1;
                if( $wgUseLatin1 ) {
        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',
 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
   KEY (il_to)
 
 ) TYPE=InnoDB
-END;
+ENDS;
                        $fields = array(
                                'il_from' => MW_UPGRADE_COPY,
                                'il_to'   => MW_UPGRADE_ENCODE );
                        $fields = array(
                                'il_from' => MW_UPGRADE_COPY,
                                'il_to'   => MW_UPGRADE_ENCODE );
@@ -1004,7 +994,7 @@ END;
        function upgradeCategorylinks() {
                global $wgUseLatin1;
                if( $wgUseLatin1 ) {
        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 '',
 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
   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,
                        $fields = array(
                                'cl_from'      => MW_UPGRADE_COPY,
                                'cl_to'        => MW_UPGRADE_ENCODE,
@@ -1028,7 +1018,7 @@ END;
        function upgradeIpblocks() {
                global $wgUseLatin1;
                if( $wgUseLatin1 ) {
        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 '',
 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
   INDEX ipb_user (ipb_user)
 
 ) TYPE=InnoDB
-END;
+ENDS;
                        $fields = array(
                                'ipb_id'        => MW_UPGRADE_COPY,
                                'ipb_address'   => MW_UPGRADE_COPY,
                        $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
 
        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 '',
 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
   INDEX rc_ip (rc_ip)
 
 ) TYPE=InnoDB
-END;
+ENDS;
                $fields = array(
                        'rc_id'             => MW_UPGRADE_COPY,
                        'rc_timestamp'      => MW_UPGRADE_COPY,
                $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
 
        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,
 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
   KEY (qc_type,qc_value)
 
 ) TYPE=InnoDB
-END;
+ENDS;
                $fields = array(
                        'qc_type'      => MW_UPGRADE_COPY,
                        'qc_value'     => MW_UPGRADE_COPY,
                $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 ) {
        }
        
        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 ) {
        }
        
        function pokeFile( $orig, $new ) {
index c8fbc54..e752996 100644 (file)
@@ -47,20 +47,4 @@ function splitFilename( $filename ) {
        return array( $fname, $ext );
 }
 
        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
        $license = isset( $options['license'] ) ? $options['license'] : '';
 
        # Batch "upload" operation
+       global $wgUploadDirectory;
        foreach( $files as $file ) {
        foreach( $files as $file ) {
-
                $base = wfBaseName( $file );
 
                # Validate a title
                $title = Title::makeTitleSafe( NS_IMAGE, $base );
                $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 {
                } else {
-                       echo( "{$base} could not be imported; a valid title cannot be produced\n" );
+                       echo( "failed.\n" );
                }
                }
-
        }
 
 } else {
        }
 
 } 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;
 
                $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() {
        }
 
        function build() {
@@ -94,13 +104,7 @@ class ImageBuilder extends FiveUpgrade {
 
                while( $row = $this->dbr->fetchObject( $result ) ) {
                        $update = call_user_func( $callback, $row );
 
                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 );
                                $this->progress( 1 );
                        } else {
                                $this->progress( 0 );
@@ -116,97 +120,43 @@ class ImageBuilder extends FiveUpgrade {
        }
 
        function imageCallback( $row ) {
        }
 
        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 ) {
        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() {
        }
 
        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 ),
                $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 );
                $fname = 'ImageBuilder::addMissingImage';
 
                $size = filesize( $fullpath );
-               $info = $this->imageInfo( $filename );
+               $info = $this->imageInfo( $fullpath );
                $timestamp = $this->dbw->timestamp( filemtime( $fullpath ) );
 
                global $wgContLang;
                $timestamp = $this->dbw->timestamp( filemtime( $fullpath ) );
 
                global $wgContLang;
@@ -242,23 +192,14 @@ class ImageBuilder extends FiveUpgrade {
                        $this->log( "Empty filename for $fullpath" );
                        return;
                }
                        $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 );
        }
                }
                $this->log( $fullpath );
        }
index 7bda09e..3276fc7 100644 (file)
@@ -1,19 +1,8 @@
 <?php
 
 <?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();
 
        var $saveGlobals = array();
 
-       function ArticleTest( $name ) {
-               $this->PHPUnit_TestCase( $name );
-       }
-
        function setUp() {
                $globalSet = array(
                        'wgLegacyEncoding' => false,
        function setUp() {
                $globalSet = array(
                        'wgLegacyEncoding' => false,
@@ -104,20 +93,6 @@ class ArticleTest extends PHPUnit_TestCase {
                        Revision::getRevisionText( $row ), "getRevisionText" );
        }
 
                        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 !";
        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" );
        }
                $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
 
 <?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;
 
        var $db;
 
-       function DatabaseTest( $name ) {
-               $this->PHPUnit_TestCase( $name );
-       }
-
        function setUp() {
        function setUp() {
-               $this->db = new Database();
-       }
-
-       function tearDown() {
-               unset( $this->db );
+               $this->db = wfGetDB( DB_SLAVE );
        }
 
        function testAddQuotesNull() {
        }
 
        function testAddQuotesNull() {
index 5de64f8..e15556e 100644 (file)
@@ -1,32 +1,6 @@
 <?php
 
 <?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(
        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'
 
 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
 # pear channel-discover pear.phpunit.de
-Then install the package:
 # pear install phpunit/PHPUnit
 # pear install phpunit/PHPUnit
-
-Or fetch and install it manually:
-http://www.phpunit.de/
index 1d088dc..aac1eb3 100644 (file)
@@ -1,25 +1,11 @@
 <?php
 
 <?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 );
 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(
 
 $testOptions = array(
        'mysql4' => array(
@@ -34,10 +20,6 @@ $testOptions = array(
                'database' => null ),
        );
 
                'database' => null ),
        );
 
-if( file_exists( 'LocalTestSettings.php' ) ) {
-       include( './LocalTestSettings.php' );
-}
-
 $tests = array(
        'GlobalTest',
        'DatabaseTest',
 $tests = array(
        'GlobalTest',
        'DatabaseTest',
@@ -47,14 +29,14 @@ $tests = array(
        'ImageTest'
        );
 
        'ImageTest'
        );
 
-if( isset( $_SERVER['argv'][1] ) ) {
+if( count( $args ) ) {
        // to override...
        // to override...
-       $tests = array( $_SERVER['argv'][1] );
+       $tests = $args;
 }
 
 foreach( $tests as $test ) {
        require_once( $test . '.php' );
 }
 
 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();
 }
        $result = PHPUnit::run( $suite );
        echo $result->toString();
 }
index ea53f91..0322da6 100644 (file)
@@ -1,22 +1,6 @@
 <?php
 
 <?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",
        function testDecodeNamed() {
                $this->assertEquals(
                        "\xc3\xa9cole",
index d780941..1494990 100644 (file)
@@ -1,20 +1,7 @@
 <?php
 
 <?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 */
 /** @todo document */
-class SearchEngine_TestCase extends PHPUnit_TestCase {
+class SearchEngine_TestCase extends PHPUnit_Framework_TestCase {
        var $db, $search;
 
        function insertSearchData() {
        var $db, $search;
 
        function insertSearchData() {
index 6277e6c..4f02f64 100644 (file)
@@ -1,7 +1,5 @@
 <?php
 <?php
-
 require_once( 'SearchEngineTest.php' );
 require_once( 'SearchEngineTest.php' );
-require_once( '../includes/SearchMySQL4.php' );
 
 class SearchMySQL4Test extends SearchEngine_TestCase {
        var $db;
 
 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.
 
 /**
  * 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' );
 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" );
 
 $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() ) {
 
 // Get input parameters
 if ( get_magic_quotes_gpc() ) {
@@ -40,36 +35,30 @@ unset( $params['r'] );
 // Some basic input validation
 $fileName = strtr( $fileName, '\\/', '__' );
 
 // 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 {
 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 );
                        wfStreamFile( $thumbPath );
-                       // Can't log profiling data with no Setup.php
+                       wfLogProfilingData();
                        exit;
                }
        }
 } catch ( MWException $e ) {
                        exit;
                }
        }
 } catch ( MWException $e ) {
-       require_once( './includes/Setup.php' );
        thumbInternalError( $e->getHTML() );
        thumbInternalError( $e->getHTML() );
+       wfLogProfilingData();
        exit;
 }
 
        exit;
 }
 
-
-// OK, no valid thumbnail, time to get out the heavy machinery
 wfProfileOut( 'thumb.php-start' );
 wfProfileOut( 'thumb.php-start' );
-require_once( './includes/Setup.php' );
 wfProfileIn( 'thumb.php-render' );
 
 wfProfileIn( 'thumb.php-render' );
 
-$img = Image::newFromName( $fileName );
 try {
        if ( $img ) {
 try {
        if ( $img ) {
-               $thumb = $img->transform( $params, Image::RENDER_NOW );
+               $thumb = $img->transform( $params, File::RENDER_NOW );
        } else {
                $thumb = false;
        }
        } else {
                $thumb = false;
        }
@@ -80,9 +69,11 @@ try {
 
 if ( $thumb && $thumb->getPath() && file_exists( $thumb->getPath() ) ) {
        wfStreamFile( $thumb->getPath() );
 
 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() ) {
        } 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 );
                $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' );
 }
 
 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' );
 function thumbInternalError( $msg ) {
        header( 'Cache-Control: no-cache' );
        header( 'Content-Type: text/html; charset=utf-8' );