Don't assume newFileFromKey always returns a File object (some repos many not support...
[lhc/web/wiklou.git] / includes / filerepo / LocalFile.php
index 24beb30..d495c00 100644 (file)
@@ -32,9 +32,10 @@ class LocalFile extends File {
        /**#@+
         * @private
         */
-       var     $fileExists,       # does the file file exist on disk? (loadFromXxx)
-               $historyLine,      # Number of line to return by nextHistoryLine() (constructor)
-               $historyRes,       # result of the query for the file's history (nextHistoryLine)
+       var
+               $fileExists,       # does the file file exist on disk? (loadFromXxx)
+               $historyLine,      # Number of line to return by nextHistoryLine() (constructor)
+               $historyRes,       # result of the query for the file's history (nextHistoryLine)
                $width,            # \
                $height,           #  |
                $bits,             #   --- returned by getimagesize (loadFromXxx)
@@ -53,7 +54,7 @@ class LocalFile extends File {
                $upgraded,         # Whether the row was upgraded on load
                $locked,           # True if the image row is locked
                $missing,          # True if file is not present in file system. Not to be cached in memcached
-               $deleted;       # Bitfield akin to rev_deleted
+               $deleted;          # Bitfield akin to rev_deleted
 
        /**#@-*/
 
@@ -82,6 +83,9 @@ class LocalFile extends File {
        /**
         * Create a LocalFile from a SHA-1 key
         * Do not call this except from inside a repo class.
+        * @param $sha1
+        * @param $repo LocalRepo
+        * @param $timestamp
         */
        static function newFromKey( $sha1, $repo, $timestamp = false ) {
                $conds = array( 'img_sha1' => $sha1 );
@@ -655,7 +659,7 @@ class LocalFile extends File {
         * Delete cached transformed files
         */
        function purgeThumbnails() {
-               global $wgUseSquid;
+               global $wgUseSquid, $wgExcludeFromThumbnailPurge;
 
                // Delete thumbnails
                $files = $this->getThumbnails();
@@ -663,6 +667,12 @@ class LocalFile extends File {
                $urls = array();
 
                foreach ( $files as $file ) {
+                       // Only remove files not in the $wgExcludeFromThumbnailPurge configuration variable
+                       $ext = pathinfo( "$dir/$file", PATHINFO_EXTENSION );
+                       if ( in_array( $ext, $wgExcludeFromThumbnailPurge ) ) {
+                               continue;
+                       }
+                       
                        # 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 ) {
@@ -712,7 +722,7 @@ class LocalFile extends File {
                $res = $dbr->select( $tables, $fields, $conds, __METHOD__, $opts, $join_conds );
                $r = array();
 
-               while ( $row = $dbr->fetchObject( $res ) ) {
+               foreach ( $res as $row ) {
                        if ( $this->repo->oldFileFromRowFactory ) {
                                $r[] = call_user_func( $this->repo->oldFileFromRowFactory, $row, $this->repo );
                        } else {
@@ -1126,7 +1136,7 @@ class LocalFile extends File {
                $result = $dbw->select( 'oldimage',
                        array( 'oi_archive_name' ),
                        array( 'oi_name' => $this->getName() ) );
-               while ( $row = $dbw->fetchObject( $result ) ) {
+               foreach ( $result as $row ) {
                        $batch->addOld( $row->oi_archive_name );
                }
                $status = $batch->execute();
@@ -1196,7 +1206,7 @@ class LocalFile extends File {
 
                $status = $batch->execute();
 
-               if ( !$status->ok ) {
+               if ( !$status->isGood() ) {
                        return $status;
                }
 
@@ -1213,8 +1223,27 @@ class LocalFile extends File {
        /** scaleHeight inherited */
        /** getImageSize inherited */
 
-       /** getDescriptionUrl inherited */
-       /** getDescriptionText 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;
+               $pout = $wgParser->parse( $text, $this->title, new ParserOptions() );
+               return $pout->getText();
+       }
 
        function getDescription() {
                $this->load();
@@ -1291,7 +1320,13 @@ class LocalFile extends File {
  * @ingroup FileRepo
  */
 class LocalFileDeleteBatch {
-       var $file, $reason, $srcRels = array(), $archiveUrls = array(), $deletionBatch, $suppress;
+
+       /**
+        * @var LocalFile
+        */
+       var $file;
+
+       var $reason, $srcRels = array(), $archiveUrls = array(), $deletionBatch, $suppress;
        var $status;
 
        function __construct( File $file, $reason = '', $suppress = false ) {
@@ -1340,7 +1375,7 @@ class LocalFileDeleteBatch {
                                __METHOD__
                        );
 
-                       while ( $row = $dbw->fetchObject( $res ) ) {
+                       foreach ( $res as $row ) {
                                if ( rtrim( $row->oi_sha1, "\0" ) === '' ) {
                                        // Get the hash from the file
                                        $oldUrl = $this->file->getArchiveVirtualUrl( $row->oi_archive_name );
@@ -1503,7 +1538,7 @@ class LocalFileDeleteBatch {
                                        $dbw->bitAnd( 'oi_deleted', File::DELETED_FILE ) => File::DELETED_FILE ),
                                __METHOD__ );
 
-                       while ( $row = $dbw->fetchObject( $res ) ) {
+                       foreach ( $res as $row ) {
                                $privateFiles[$row->oi_archive_name] = 1;
                        }
                }
@@ -1602,7 +1637,12 @@ class LocalFileDeleteBatch {
  * @ingroup FileRepo
  */
 class LocalFileRestoreBatch {
-       var $file, $cleanupBatch, $ids, $all, $unsuppress = false;
+       /**
+        * @var LocalFile
+        */
+       var $file;
+
+       var $cleanupBatch, $ids, $all, $unsuppress = false;
 
        function __construct( File $file, $unsuppress = false ) {
                $this->file = $file;
@@ -1673,7 +1713,7 @@ class LocalFileRestoreBatch {
                $first = true;
                $archiveNames = array();
 
-               while ( $row = $dbw->fetchObject( $result ) ) {
+               foreach ( $result as $row ) {
                        $idsPresent[] = $row->fa_id;
 
                        if ( $row->fa_name != $this->file->getName() ) {
@@ -1806,9 +1846,11 @@ class LocalFileRestoreBatch {
                $storeStatus = $this->file->repo->storeBatch( $storeBatch, FileRepo::OVERWRITE_SAME );
                $status->merge( $storeStatus );
 
-               if ( !$status->ok ) {
-                       // Store batch returned a critical error -- this usually means nothing was stored
-                       // Stop now and return an error
+               if ( !$status->isGood() ) {
+                       // Even if some files could be copied, fail entirely as that is the
+                       // easiest thing to do without data loss
+                       $this->cleanupFailedBatch( $storeStatus, $storeBatch );
+                       $status->ok = false;
                        $this->file->unlock();
 
                        return $status;
@@ -1913,6 +1955,17 @@ class LocalFileRestoreBatch {
 
                return $status;
        }
+       
+       function cleanupFailedBatch( $storeStatus, $storeBatch ) {
+               $cleanupBatch = array(); 
+               
+               foreach ( $storeStatus->success as $i => $success ) {
+                       if ( $success ) {
+                               $cleanupBatch[] = array( $storeBatch[$i][1], $storeBatch[$i][1] );
+                       }
+               }
+               $this->file->repo->cleanupBatch( $cleanupBatch );
+       }
 }
 
 # ------------------------------------------------------------------------------
@@ -1957,7 +2010,7 @@ class LocalFileMoveBatch {
                        __METHOD__
                );
 
-               while ( $row = $this->db->fetchObject( $result ) ) {
+               foreach ( $result as $row ) {
                        $oldName = $row->oi_archive_name;
                        $bits = explode( '!', $oldName, 2 );
 
@@ -1996,16 +2049,32 @@ class LocalFileMoveBatch {
                $triplets = $this->getMoveTriplets();
 
                $triplets = $this->removeNonexistentFiles( $triplets );
-               $statusDb = $this->doDBUpdates();
-               wfDebugLog( 'imagemove', "Renamed {$this->file->name} in database: {$statusDb->successCount} successes, {$statusDb->failCount} failures" );
-               $statusMove = $repo->storeBatch( $triplets, FSRepo::DELETE_SOURCE );
+               
+               // Copy the files into their new location
+               $statusMove = $repo->storeBatch( $triplets );
                wfDebugLog( 'imagemove', "Moved files for {$this->file->name}: {$statusMove->successCount} successes, {$statusMove->failCount} failures" );
-
-               if ( !$statusMove->isOk() ) {
+               if ( !$statusMove->isGood() ) {
                        wfDebugLog( 'imagemove', "Error in moving files: " . $statusMove->getWikiText() );
-                       $this->db->rollback();
+                       $this->cleanupTarget( $triplets );
+                       $statusMove->ok = false;
+                       return $statusMove;
                }
 
+               $this->db->begin();
+               $statusDb = $this->doDBUpdates();
+               wfDebugLog( 'imagemove', "Renamed {$this->file->name} in database: {$statusDb->successCount} successes, {$statusDb->failCount} failures" );
+               if ( !$statusDb->isGood() ) {
+                       $this->db->rollback();
+                       // Something went wrong with the DB updates, so remove the target files
+                       $this->cleanupTarget( $triplets );
+                       $statusDb->ok = false;
+                       return $statusDb;
+               }
+               $this->db->commit();
+               
+               // Everything went ok, remove the source files
+               $this->cleanupSource( $triplets );
+               
                $status->merge( $statusDb );
                $status->merge( $statusMove );
 
@@ -2035,6 +2104,8 @@ class LocalFileMoveBatch {
                        $status->successCount++;
                } else {
                        $status->failCount++;
+                       $status->fatal( 'imageinvalidfilename' );
+                       return $status;
                }
 
                // Update old images
@@ -2052,6 +2123,9 @@ class LocalFileMoveBatch {
                $total = $this->oldCount;
                $status->successCount += $affected;
                $status->failCount += $total - $affected;
+               if ( $status->failCount ) {
+                       $status->error( 'imageinvalidfilename' );
+               }
 
                return $status;
        }
@@ -2096,4 +2170,32 @@ class LocalFileMoveBatch {
 
                return $filteredTriplets;
        }
+       
+       /**
+        * Cleanup a partially moved array of triplets by deleting the target 
+        * files. Called if something went wrong half way.
+        */
+       function cleanupTarget( $triplets ) {
+               // Create dest pairs from the triplets
+               $pairs = array();
+               foreach ( $triplets as $triplet ) {
+                       $pairs[] = array( $triplet[1], $triplet[2] );
+               }
+               
+               $this->file->repo->cleanupBatch( $pairs );
+       }
+       
+       /**
+        * Cleanup a fully moved array of triplets by deleting the source files.
+        * Called at the end of the move process if everything else went ok. 
+        */
+       function cleanupSource( $triplets ) {
+               // Create source file names from the triplets
+               $files = array();
+               foreach ( $triplets as $triplet ) {
+                       $files[] = $triplet[0];
+               }
+               
+               $this->file->repo->cleanupBatch( $files );
+       }
 }