00001 <?php
00008 define( 'MW_FILE_VERSION', 8 );
00009
00027 class LocalFile extends File {
00031 var $fileExists, # does the file file exist on disk? (loadFromXxx)
00032 $historyLine, # Number of line to return by nextHistoryLine() (constructor)
00033 $historyRes, # result of the query for the file's history (nextHistoryLine)
00034 $width, # \
00035 $height, # |
00036 $bits, # --- returned by getimagesize (loadFromXxx)
00037 $attr, # /
00038 $media_type, # MEDIATYPE_xxx (bitmap, drawing, audio...)
00039 $mime, # MIME type, determined by MimeMagic::guessMimeType
00040 $major_mime, # Major mime type
00041 $minor_mime, # Minor mime type
00042 $size, # Size in bytes (loadFromXxx)
00043 $metadata, # Handler-specific metadata
00044 $timestamp, # Upload timestamp
00045 $sha1, # SHA-1 base 36 content hash
00046 $user, $user_text, # User, who uploaded the file
00047 $description, # Description of current revision of the file
00048 $dataLoaded, # Whether or not all this has been loaded from the database (loadFromXxx)
00049 $upgraded, # Whether the row was upgraded on load
00050 $locked, # True if the image row is locked
00051 $missing, # True if file is not present in file system. Not to be cached in memcached
00052 $deleted; # Bitfield akin to rev_deleted
00053
00062 static function newFromTitle( $title, $repo, $unused = null ) {
00063 return new self( $title, $repo );
00064 }
00065
00070 static function newFromRow( $row, $repo ) {
00071 $title = Title::makeTitle( NS_FILE, $row->img_name );
00072 $file = new self( $title, $repo );
00073 $file->loadFromRow( $row );
00074 return $file;
00075 }
00076
00081 static function newFromKey( $sha1, $repo, $timestamp = false ) {
00082 # Polymorphic function name to distinguish foreign and local fetches
00083 $fname = get_class( $this ) . '::' . __FUNCTION__;
00084
00085 $conds = array( 'img_sha1' => $sha1 );
00086 if( $timestamp ) {
00087 $conds['img_timestamp'] = $timestamp;
00088 }
00089 $row = $dbr->selectRow( 'image', $this->getCacheFields( 'img_' ), $conds, $fname );
00090 if( $row ) {
00091 return self::newFromRow( $row, $repo );
00092 } else {
00093 return false;
00094 }
00095 }
00096
00100 static function selectFields() {
00101 return array(
00102 'img_name',
00103 'img_size',
00104 'img_width',
00105 'img_height',
00106 'img_metadata',
00107 'img_bits',
00108 'img_media_type',
00109 'img_major_mime',
00110 'img_minor_mime',
00111 'img_description',
00112 'img_user',
00113 'img_user_text',
00114 'img_timestamp',
00115 'img_sha1',
00116 );
00117 }
00118
00123 function __construct( $title, $repo ) {
00124 if( !is_object( $title ) ) {
00125 throw new MWException( __CLASS__ . ' constructor given bogus title.' );
00126 }
00127 parent::__construct( $title, $repo );
00128 $this->metadata = '';
00129 $this->historyLine = 0;
00130 $this->historyRes = null;
00131 $this->dataLoaded = false;
00132 }
00133
00138 function getCacheKey() {
00139 $hashedName = md5( $this->getName() );
00140 return $this->repo->getSharedCacheKey( 'file', $hashedName );
00141 }
00142
00146 function loadFromCache() {
00147 global $wgMemc;
00148 wfProfileIn( __METHOD__ );
00149 $this->dataLoaded = false;
00150 $key = $this->getCacheKey();
00151 if ( !$key ) {
00152 wfProfileOut( __METHOD__ );
00153 return false;
00154 }
00155 $cachedValues = $wgMemc->get( $key );
00156
00157 // Check if the key existed and belongs to this version of MediaWiki
00158 if ( isset( $cachedValues['version'] ) && ( $cachedValues['version'] == MW_FILE_VERSION ) ) {
00159 wfDebug( "Pulling file metadata from cache key $key\n" );
00160 $this->fileExists = $cachedValues['fileExists'];
00161 if ( $this->fileExists ) {
00162 $this->setProps( $cachedValues );
00163 }
00164 $this->dataLoaded = true;
00165 }
00166 if ( $this->dataLoaded ) {
00167 wfIncrStats( 'image_cache_hit' );
00168 } else {
00169 wfIncrStats( 'image_cache_miss' );
00170 }
00171
00172 wfProfileOut( __METHOD__ );
00173 return $this->dataLoaded;
00174 }
00175
00179 function saveToCache() {
00180 global $wgMemc;
00181 $this->load();
00182 $key = $this->getCacheKey();
00183 if ( !$key ) {
00184 return;
00185 }
00186 $fields = $this->getCacheFields( '' );
00187 $cache = array( 'version' => MW_FILE_VERSION );
00188 $cache['fileExists'] = $this->fileExists;
00189 if ( $this->fileExists ) {
00190 foreach ( $fields as $field ) {
00191 $cache[$field] = $this->$field;
00192 }
00193 }
00194
00195 $wgMemc->set( $key, $cache, 60 * 60 * 24 * 7 ); // A week
00196 }
00197
00201 function loadFromFile() {
00202 $this->setProps( self::getPropsFromPath( $this->getPath() ) );
00203 }
00204
00205 function getCacheFields( $prefix = 'img_' ) {
00206 static $fields = array( 'size', 'width', 'height', 'bits', 'media_type',
00207 'major_mime', 'minor_mime', 'metadata', 'timestamp', 'sha1', 'user', 'user_text', 'description' );
00208 static $results = array();
00209 if ( $prefix == '' ) {
00210 return $fields;
00211 }
00212 if ( !isset( $results[$prefix] ) ) {
00213 $prefixedFields = array();
00214 foreach ( $fields as $field ) {
00215 $prefixedFields[] = $prefix . $field;
00216 }
00217 $results[$prefix] = $prefixedFields;
00218 }
00219 return $results[$prefix];
00220 }
00221
00225 function loadFromDB() {
00226 # Polymorphic function name to distinguish foreign and local fetches
00227 $fname = get_class( $this ) . '::' . __FUNCTION__;
00228 wfProfileIn( $fname );
00229
00230 # Unconditionally set loaded=true, we don't want the accessors constantly rechecking
00231 $this->dataLoaded = true;
00232
00233 $dbr = $this->repo->getMasterDB();
00234
00235 $row = $dbr->selectRow( 'image', $this->getCacheFields( 'img_' ),
00236 array( 'img_name' => $this->getName() ), $fname );
00237 if ( $row ) {
00238 $this->loadFromRow( $row );
00239 } else {
00240 $this->fileExists = false;
00241 }
00242
00243 wfProfileOut( $fname );
00244 }
00245
00250 function decodeRow( $row, $prefix = 'img_' ) {
00251 $array = (array)$row;
00252 $prefixLength = strlen( $prefix );
00253
00254 if ( substr( key( $array ), 0, $prefixLength ) !== $prefix ) {
00255 throw new MWException( __METHOD__ . ': incorrect $prefix parameter' );
00256 }
00257 $decoded = array();
00258 foreach ( $array as $name => $value ) {
00259 $decoded[substr( $name, $prefixLength )] = $value;
00260 }
00261 $decoded['timestamp'] = wfTimestamp( TS_MW, $decoded['timestamp'] );
00262 if ( empty( $decoded['major_mime'] ) ) {
00263 $decoded['mime'] = 'unknown/unknown';
00264 } else {
00265 if ( !$decoded['minor_mime'] ) {
00266 $decoded['minor_mime'] = 'unknown';
00267 }
00268 $decoded['mime'] = $decoded['major_mime'] . '/' . $decoded['minor_mime'];
00269 }
00270 # Trim zero padding from char/binary field
00271 $decoded['sha1'] = rtrim( $decoded['sha1'], "\0" );
00272 return $decoded;
00273 }
00274
00278 function loadFromRow( $row, $prefix = 'img_' ) {
00279 $this->dataLoaded = true;
00280 $array = $this->decodeRow( $row, $prefix );
00281 foreach ( $array as $name => $value ) {
00282 $this->$name = $value;
00283 }
00284 $this->fileExists = true;
00285 $this->maybeUpgradeRow();
00286 }
00287
00291 function load() {
00292 if ( !$this->dataLoaded ) {
00293 if ( !$this->loadFromCache() ) {
00294 $this->loadFromDB();
00295 $this->saveToCache();
00296 }
00297 $this->dataLoaded = true;
00298 }
00299 }
00300
00304 function maybeUpgradeRow() {
00305 if ( wfReadOnly() ) {
00306 return;
00307 }
00308 if ( is_null( $this->media_type ) ||
00309 $this->mime == 'image/svg'
00310 ) {
00311 $this->upgradeRow();
00312 $this->upgraded = true;
00313 } else {
00314 $handler = $this->getHandler();
00315 if ( $handler && !$handler->isMetadataValid( $this, $this->metadata ) ) {
00316 $this->upgradeRow();
00317 $this->upgraded = true;
00318 }
00319 }
00320 }
00321
00322 function getUpgraded() {
00323 return $this->upgraded;
00324 }
00325
00329 function upgradeRow() {
00330 wfProfileIn( __METHOD__ );
00331
00332 $this->loadFromFile();
00333
00334 # Don't destroy file info of missing files
00335 if ( !$this->fileExists ) {
00336 wfDebug( __METHOD__ . ": file does not exist, aborting\n" );
00337 wfProfileOut( __METHOD__ );
00338 return;
00339 }
00340 $dbw = $this->repo->getMasterDB();
00341 list( $major, $minor ) = self::splitMime( $this->mime );
00342
00343 if ( wfReadOnly() ) {
00344 wfProfileOut( __METHOD__ );
00345 return;
00346 }
00347 wfDebug( __METHOD__ . ': upgrading ' . $this->getName() . " to the current schema\n" );
00348
00349 $dbw->update( 'image',
00350 array(
00351 'img_width' => $this->width,
00352 'img_height' => $this->height,
00353 'img_bits' => $this->bits,
00354 'img_media_type' => $this->media_type,
00355 'img_major_mime' => $major,
00356 'img_minor_mime' => $minor,
00357 'img_metadata' => $this->metadata,
00358 'img_sha1' => $this->sha1,
00359 ), array( 'img_name' => $this->getName() ),
00360 __METHOD__
00361 );
00362 $this->saveToCache();
00363 wfProfileOut( __METHOD__ );
00364 }
00365
00373 function setProps( $info ) {
00374 $this->dataLoaded = true;
00375 $fields = $this->getCacheFields( '' );
00376 $fields[] = 'fileExists';
00377 foreach ( $fields as $field ) {
00378 if ( isset( $info[$field] ) ) {
00379 $this->$field = $info[$field];
00380 }
00381 }
00382
00383 if ( isset( $info['major_mime'] ) ) {
00384 $this->mime = "{$info['major_mime']}/{$info['minor_mime']}";
00385 } elseif ( isset( $info['mime'] ) ) {
00386 list( $this->major_mime, $this->minor_mime ) = self::splitMime( $this->mime );
00387 }
00388 }
00389
00398 function isMissing() {
00399 if( $this->missing === null ) {
00400 list( $fileExists ) = $this->repo->fileExistsBatch( array( $this->getVirtualUrl() ), FileRepo::FILES_ONLY );
00401 $this->missing = !$fileExists;
00402 }
00403 return $this->missing;
00404 }
00405
00411 public function getWidth( $page = 1 ) {
00412 $this->load();
00413 if ( $this->isMultipage() ) {
00414 $dim = $this->getHandler()->getPageDimensions( $this, $page );
00415 if ( $dim ) {
00416 return $dim['width'];
00417 } else {
00418 return false;
00419 }
00420 } else {
00421 return $this->width;
00422 }
00423 }
00424
00430 public function getHeight( $page = 1 ) {
00431 $this->load();
00432 if ( $this->isMultipage() ) {
00433 $dim = $this->getHandler()->getPageDimensions( $this, $page );
00434 if ( $dim ) {
00435 return $dim['height'];
00436 } else {
00437 return false;
00438 }
00439 } else {
00440 return $this->height;
00441 }
00442 }
00443
00449 function getUser( $type = 'text' ) {
00450 $this->load();
00451 if( $type == 'text' ) {
00452 return $this->user_text;
00453 } elseif( $type == 'id' ) {
00454 return $this->user;
00455 }
00456 }
00457
00461 function getMetadata() {
00462 $this->load();
00463 return $this->metadata;
00464 }
00465
00466 function getBitDepth() {
00467 $this->load();
00468 return $this->bits;
00469 }
00470
00474 public function getSize() {
00475 $this->load();
00476 return $this->size;
00477 }
00478
00482 function getMimeType() {
00483 $this->load();
00484 return $this->mime;
00485 }
00486
00491 function getMediaType() {
00492 $this->load();
00493 return $this->media_type;
00494 }
00495
00506 public function exists() {
00507 $this->load();
00508 return $this->fileExists;
00509 }
00510
00521 function migrateThumbFile( $thumbName ) {
00522 $thumbDir = $this->getThumbPath();
00523 $thumbPath = "$thumbDir/$thumbName";
00524 if ( is_dir( $thumbPath ) ) {
00525
00526
00527
00528 for ( $i = 0; $i < 100 ; $i++ ) {
00529 $broken = $this->repo->getZonePath( 'public' ) . "/broken-$i-$thumbName";
00530 if ( !file_exists( $broken ) ) {
00531 rename( $thumbPath, $broken );
00532 break;
00533 }
00534 }
00535
00536 clearstatcache();
00537 }
00538 if ( is_file( $thumbDir ) ) {
00539
00540 unlink( $thumbDir );
00541
00542 clearstatcache();
00543 }
00544 }
00545
00553 function getThumbnails() {
00554 $this->load();
00555 $files = array();
00556 $dir = $this->getThumbPath();
00557
00558 if ( is_dir( $dir ) ) {
00559 $handle = opendir( $dir );
00560
00561 if ( $handle ) {
00562 while ( false !== ( $file = readdir( $handle ) ) ) {
00563 if ( $file{0} != '.' ) {
00564 $files[] = $file;
00565 }
00566 }
00567 closedir( $handle );
00568 }
00569 }
00570
00571 return $files;
00572 }
00573
00577 function purgeMetadataCache() {
00578 $this->loadFromDB();
00579 $this->saveToCache();
00580 $this->purgeHistory();
00581 }
00582
00586 function purgeHistory() {
00587 global $wgMemc;
00588 $hashedName = md5( $this->getName() );
00589 $oldKey = $this->repo->getSharedCacheKey( 'oldfile', $hashedName );
00590 if ( $oldKey ) {
00591 $wgMemc->delete( $oldKey );
00592 }
00593 }
00594
00598 function purgeCache() {
00599
00600 $this->purgeMetadataCache();
00601
00602
00603 $this->purgeThumbnails();
00604
00605
00606 SquidUpdate::purge( array( $this->getURL() ) );
00607 }
00608
00612 function purgeThumbnails() {
00613 global $wgUseSquid;
00614
00615 $files = $this->getThumbnails();
00616 $dir = $this->getThumbPath();
00617 $urls = array();
00618 foreach ( $files as $file ) {
00619 # Check that the base file name is part of the thumb name
00620 # This is a basic sanity check to avoid erasing unrelated directories
00621 if ( strpos( $file, $this->getName() ) !== false ) {
00622 $url = $this->getThumbUrl( $file );
00623 $urls[] = $url;
00624 @unlink( "$dir/$file" );
00625 }
00626 }
00627
00628
00629 if ( $wgUseSquid ) {
00630 SquidUpdate::purge( $urls );
00631 }
00632 }
00633
00637 function getHistory( $limit = null, $start = null, $end = null, $inc = true ) {
00638 $dbr = $this->repo->getSlaveDB();
00639 $tables = array( 'oldimage' );
00640 $fields = OldLocalFile::selectFields();
00641 $conds = $opts = $join_conds = array();
00642 $eq = $inc ? '=' : '';
00643 $conds[] = "oi_name = " . $dbr->addQuotes( $this->title->getDBkey() );
00644 if( $start ) {
00645 $conds[] = "oi_timestamp <$eq " . $dbr->addQuotes( $dbr->timestamp( $start ) );
00646 }
00647 if( $end ) {
00648 $conds[] = "oi_timestamp >$eq " . $dbr->addQuotes( $dbr->timestamp( $end ) );
00649 }
00650 if( $limit ) {
00651 $opts['LIMIT'] = $limit;
00652 }
00653
00654 $order = ( !$start && $end !== null ) ? 'ASC' : 'DESC';
00655 $opts['ORDER BY'] = "oi_timestamp $order";
00656 $opts['USE INDEX'] = array( 'oldimage' => 'oi_name_timestamp' );
00657
00658 wfRunHooks( 'LocalFile::getHistory', array( &$this, &$tables, &$fields,
00659 &$conds, &$opts, &$join_conds ) );
00660
00661 $res = $dbr->select( $tables, $fields, $conds, __METHOD__, $opts, $join_conds );
00662 $r = array();
00663 while( $row = $dbr->fetchObject( $res ) ) {
00664 $r[] = OldLocalFile::newFromRow( $row, $this->repo );
00665 }
00666 if( $order == 'ASC' ) {
00667 $r = array_reverse( $r );
00668 }
00669 return $r;
00670 }
00671
00680 public function nextHistoryLine() {
00681 # Polymorphic function name to distinguish foreign and local fetches
00682 $fname = get_class( $this ) . '::' . __FUNCTION__;
00683
00684 $dbr = $this->repo->getSlaveDB();
00685
00686 if ( $this->historyLine == 0 ) {
00687 $this->historyRes = $dbr->select( 'image',
00688 array(
00689 '*',
00690 "'' AS oi_archive_name",
00691 '0 as oi_deleted',
00692 'img_sha1'
00693 ),
00694 array( 'img_name' => $this->title->getDBkey() ),
00695 $fname
00696 );
00697 if ( 0 == $dbr->numRows( $this->historyRes ) ) {
00698 $dbr->freeResult( $this->historyRes );
00699 $this->historyRes = null;
00700 return false;
00701 }
00702 } elseif ( $this->historyLine == 1 ) {
00703 $dbr->freeResult( $this->historyRes );
00704 $this->historyRes = $dbr->select( 'oldimage', '*',
00705 array( 'oi_name' => $this->title->getDBkey() ),
00706 $fname,
00707 array( 'ORDER BY' => 'oi_timestamp DESC' )
00708 );
00709 }
00710 $this->historyLine ++;
00711
00712 return $dbr->fetchObject( $this->historyRes );
00713 }
00714
00718 public function resetHistory() {
00719 $this->historyLine = 0;
00720 if ( !is_null( $this->historyRes ) ) {
00721 $this->repo->getSlaveDB()->freeResult( $this->historyRes );
00722 $this->historyRes = null;
00723 }
00724 }
00725
00754 function upload( $srcPath, $comment, $pageText, $flags = 0, $props = false, $timestamp = false, $user = null ) {
00755 $this->lock();
00756 $status = $this->publish( $srcPath, $flags );
00757 if ( $status->ok ) {
00758 if ( !$this->recordUpload2( $status->value, $comment, $pageText, $props, $timestamp, $user ) ) {
00759 $status->fatal( 'filenotfound', $srcPath );
00760 }
00761 }
00762 $this->unlock();
00763 return $status;
00764 }
00765
00770 function recordUpload( $oldver, $desc, $license = '', $copyStatus = '', $source = '',
00771 $watch = false, $timestamp = false )
00772 {
00773 $pageText = SpecialUpload::getInitialPageText( $desc, $license, $copyStatus, $source );
00774 if ( !$this->recordUpload2( $oldver, $desc, $pageText ) ) {
00775 return false;
00776 }
00777 if ( $watch ) {
00778 global $wgUser;
00779 $wgUser->addWatch( $this->getTitle() );
00780 }
00781 return true;
00782
00783 }
00784
00788 function recordUpload2( $oldver, $comment, $pageText, $props = false, $timestamp = false, $user = null )
00789 {
00790 if( is_null( $user ) ) {
00791 global $wgUser;
00792 $user = $wgUser;
00793 }
00794
00795 $dbw = $this->repo->getMasterDB();
00796 $dbw->begin();
00797
00798 if ( !$props ) {
00799 $props = $this->repo->getFileProps( $this->getVirtualUrl() );
00800 }
00801 $props['description'] = $comment;
00802 $props['user'] = $user->getId();
00803 $props['user_text'] = $user->getName();
00804 $props['timestamp'] = wfTimestamp( TS_MW );
00805 $this->setProps( $props );
00806
00807
00808 $this->purgeThumbnails();
00809 $this->saveToCache();
00810 SquidUpdate::purge( array( $this->getURL() ) );
00811
00812
00813 if ( !$this->fileExists ) {
00814 wfDebug( __METHOD__ . ": File " . $this->getPath() . " went missing!\n" );
00815 return false;
00816 }
00817
00818 $reupload = false;
00819 if ( $timestamp === false ) {
00820 $timestamp = $dbw->timestamp();
00821 }
00822
00823 # Test to see if the row exists using INSERT IGNORE
00824 # This avoids race conditions by locking the row until the commit, and also
00825 # doesn't deadlock. SELECT FOR UPDATE causes a deadlock for every race condition.
00826 $dbw->insert( 'image',
00827 array(
00828 'img_name' => $this->getName(),
00829 'img_size'=> $this->size,
00830 'img_width' => intval( $this->width ),
00831 'img_height' => intval( $this->height ),
00832 'img_bits' => $this->bits,
00833 'img_media_type' => $this->media_type,
00834 'img_major_mime' => $this->major_mime,
00835 'img_minor_mime' => $this->minor_mime,
00836 'img_timestamp' => $timestamp,
00837 'img_description' => $comment,
00838 'img_user' => $user->getId(),
00839 'img_user_text' => $user->getName(),
00840 'img_metadata' => $this->metadata,
00841 'img_sha1' => $this->sha1
00842 ),
00843 __METHOD__,
00844 'IGNORE'
00845 );
00846
00847 if( $dbw->affectedRows() == 0 ) {
00848 $reupload = true;
00849
00850 # Collision, this is an update of a file
00851 # Insert previous contents into oldimage
00852 $dbw->insertSelect( 'oldimage', 'image',
00853 array(
00854 'oi_name' => 'img_name',
00855 'oi_archive_name' => $dbw->addQuotes( $oldver ),
00856 'oi_size' => 'img_size',
00857 'oi_width' => 'img_width',
00858 'oi_height' => 'img_height',
00859 'oi_bits' => 'img_bits',
00860 'oi_timestamp' => 'img_timestamp',
00861 'oi_description' => 'img_description',
00862 'oi_user' => 'img_user',
00863 'oi_user_text' => 'img_user_text',
00864 'oi_metadata' => 'img_metadata',
00865 'oi_media_type' => 'img_media_type',
00866 'oi_major_mime' => 'img_major_mime',
00867 'oi_minor_mime' => 'img_minor_mime',
00868 'oi_sha1' => 'img_sha1'
00869 ), array( 'img_name' => $this->getName() ), __METHOD__
00870 );
00871
00872 # Update the current image row
00873 $dbw->update( 'image',
00874 array(
00875 'img_size' => $this->size,
00876 'img_width' => intval( $this->width ),
00877 'img_height' => intval( $this->height ),
00878 'img_bits' => $this->bits,
00879 'img_media_type' => $this->media_type,
00880 'img_major_mime' => $this->major_mime,
00881 'img_minor_mime' => $this->minor_mime,
00882 'img_timestamp' => $timestamp,
00883 'img_description' => $comment,
00884 'img_user' => $user->getId(),
00885 'img_user_text' => $user->getName(),
00886 'img_metadata' => $this->metadata,
00887 'img_sha1' => $this->sha1
00888 ), array(
00889 'img_name' => $this->getName()
00890 ), __METHOD__
00891 );
00892 } else {
00893 # This is a new file
00894 # Update the image count
00895 $site_stats = $dbw->tableName( 'site_stats' );
00896 $dbw->query( "UPDATE $site_stats SET ss_images=ss_images+1", __METHOD__ );
00897 }
00898
00899 $descTitle = $this->getTitle();
00900 $article = new ImagePage( $descTitle );
00901 $article->setFile( $this );
00902
00903 # Add the log entry
00904 $log = new LogPage( 'upload' );
00905 $action = $reupload ? 'overwrite' : 'upload';
00906 $log->addEntry( $action, $descTitle, $comment, array(), $user );
00907
00908 if( $descTitle->exists() ) {
00909 # Create a null revision
00910 $latest = $descTitle->getLatestRevID();
00911 $nullRevision = Revision::newNullRevision( $dbw, $descTitle->getArticleId(),
00912 $log->getRcComment(), false );
00913 $nullRevision->insertOn( $dbw );
00914
00915 wfRunHooks( 'NewRevisionFromEditComplete', array( $article, $nullRevision, $latest, $user ) );
00916 $article->updateRevisionOn( $dbw, $nullRevision );
00917
00918 # Invalidate the cache for the description page
00919 $descTitle->invalidateCache();
00920 $descTitle->purgeSquid();
00921 } else {
00922
00923
00924 $article->doEdit( $pageText, $comment, EDIT_NEW | EDIT_SUPPRESS_RC );
00925 }
00926
00927 # Hooks, hooks, the magic of hooks...
00928 wfRunHooks( 'FileUpload', array( $this ) );
00929
00930 # Commit the transaction now, in case something goes wrong later
00931 # The most important thing is that files don't get lost, especially archives
00932 $dbw->commit();
00933
00934 # Invalidate cache for all pages using this file
00935 $update = new HTMLCacheUpdate( $this->getTitle(), 'imagelinks' );
00936 $update->doUpdate();
00937 # Invalidate cache for all pages that redirects on this page
00938 $redirs = $this->getTitle()->getRedirectsHere();
00939 foreach( $redirs as $redir ) {
00940 $update = new HTMLCacheUpdate( $redir, 'imagelinks' );
00941 $update->doUpdate();
00942 }
00943
00944 return true;
00945 }
00946
00963 function publish( $srcPath, $flags = 0 ) {
00964 $this->lock();
00965 $dstRel = $this->getRel();
00966 $archiveName = gmdate( 'YmdHis' ) . '!'. $this->getName();
00967 $archiveRel = 'archive/' . $this->getHashPath() . $archiveName;
00968 $flags = $flags & File::DELETE_SOURCE ? LocalRepo::DELETE_SOURCE : 0;
00969 $status = $this->repo->publish( $srcPath, $dstRel, $archiveRel, $flags );
00970 if ( $status->value == 'new' ) {
00971 $status->value = '';
00972 } else {
00973 $status->value = $archiveName;
00974 }
00975 $this->unlock();
00976 return $status;
00977 }
00978
00996 function move( $target ) {
00997 wfDebugLog( 'imagemove', "Got request to move {$this->name} to " . $target->getText() );
00998 $this->lock();
00999 $batch = new LocalFileMoveBatch( $this, $target );
01000 $batch->addCurrent();
01001 $batch->addOlds();
01002
01003 $status = $batch->execute();
01004 wfDebugLog( 'imagemove', "Finished moving {$this->name}" );
01005 $this->purgeEverything();
01006 $this->unlock();
01007
01008 if ( $status->isOk() ) {
01009
01010 $this->title = $target;
01011
01012 unset( $this->name );
01013 unset( $this->hashPath );
01014
01015 $this->purgeEverything();
01016 }
01017
01018 return $status;
01019 }
01020
01033 function delete( $reason, $suppress = false ) {
01034 $this->lock();
01035 $batch = new LocalFileDeleteBatch( $this, $reason, $suppress );
01036 $batch->addCurrent();
01037
01038 # Get old version relative paths
01039 $dbw = $this->repo->getMasterDB();
01040 $result = $dbw->select( 'oldimage',
01041 array( 'oi_archive_name' ),
01042 array( 'oi_name' => $this->getName() ) );
01043 while ( $row = $dbw->fetchObject( $result ) ) {
01044 $batch->addOld( $row->oi_archive_name );
01045 }
01046 $status = $batch->execute();
01047
01048 if ( $status->ok ) {
01049
01050 $site_stats = $dbw->tableName( 'site_stats' );
01051 $dbw->query( "UPDATE $site_stats SET ss_images=ss_images-1", __METHOD__ );
01052 $this->purgeEverything();
01053 }
01054
01055 $this->unlock();
01056 return $status;
01057 }
01058
01072 function deleteOld( $archiveName, $reason, $suppress=false ) {
01073 $this->lock();
01074 $batch = new LocalFileDeleteBatch( $this, $reason, $suppress );
01075 $batch->addOld( $archiveName );
01076 $status = $batch->execute();
01077 $this->unlock();
01078 if ( $status->ok ) {
01079 $this->purgeDescription();
01080 $this->purgeHistory();
01081 }
01082 return $status;
01083 }
01084
01096 function restore( $versions = array(), $unsuppress = false ) {
01097 $batch = new LocalFileRestoreBatch( $this, $unsuppress );
01098 if ( !$versions ) {
01099 $batch->addAll();
01100 } else {
01101 $batch->addIds( $versions );
01102 }
01103 $status = $batch->execute();
01104 if ( !$status->ok ) {
01105 return $status;
01106 }
01107
01108 $cleanupStatus = $batch->cleanup();
01109 $cleanupStatus->successCount = 0;
01110 $cleanupStatus->failCount = 0;
01111 $status->merge( $cleanupStatus );
01112 return $status;
01113 }
01114
01123 function getDescriptionUrl() {
01124 return $this->title->getLocalUrl();
01125 }
01126
01132 function getDescriptionText() {
01133 global $wgParser;
01134 $revision = Revision::newFromTitle( $this->title );
01135 if ( !$revision ) return false;
01136 $text = $revision->getText();
01137 if ( !$text ) return false;
01138 $pout = $wgParser->parse( $text, $this->title, new ParserOptions() );
01139 return $pout->getText();
01140 }
01141
01142 function getDescription() {
01143 $this->load();
01144 return $this->description;
01145 }
01146
01147 function getTimestamp() {
01148 $this->load();
01149 return $this->timestamp;
01150 }
01151
01152 function getSha1() {
01153 $this->load();
01154
01155 if ( $this->sha1 == '' && $this->fileExists ) {
01156 $this->sha1 = File::sha1Base36( $this->getPath() );
01157 if ( !wfReadOnly() && strval( $this->sha1 ) != '' ) {
01158 $dbw = $this->repo->getMasterDB();
01159 $dbw->update( 'image',
01160 array( 'img_sha1' => $this->sha1 ),
01161 array( 'img_name' => $this->getName() ),
01162 __METHOD__ );
01163 $this->saveToCache();
01164 }
01165 }
01166
01167 return $this->sha1;
01168 }
01169
01175 function lock() {
01176 $dbw = $this->repo->getMasterDB();
01177 if ( !$this->locked ) {
01178 $dbw->begin();
01179 $this->locked++;
01180 }
01181 return $dbw->selectField( 'image', '1', array( 'img_name' => $this->getName() ), __METHOD__ );
01182 }
01183
01188 function unlock() {
01189 if ( $this->locked ) {
01190 --$this->locked;
01191 if ( !$this->locked ) {
01192 $dbw = $this->repo->getMasterDB();
01193 $dbw->commit();
01194 }
01195 }
01196 }
01197
01201 function unlockAndRollback() {
01202 $this->locked = false;
01203 $dbw = $this->repo->getMasterDB();
01204 $dbw->rollback();
01205 }
01206 }
01207
01208 #------------------------------------------------------------------------------
01209
01214 class LocalFileDeleteBatch {
01215 var $file, $reason, $srcRels = array(), $archiveUrls = array(), $deletionBatch, $suppress;
01216 var $status;
01217
01218 function __construct( File $file, $reason = '', $suppress = false ) {
01219 $this->file = $file;
01220 $this->reason = $reason;
01221 $this->suppress = $suppress;
01222 $this->status = $file->repo->newGood();
01223 }
01224
01225 function addCurrent() {
01226 $this->srcRels['.'] = $this->file->getRel();
01227 }
01228
01229 function addOld( $oldName ) {
01230 $this->srcRels[$oldName] = $this->file->getArchiveRel( $oldName );
01231 $this->archiveUrls[] = $this->file->getArchiveUrl( $oldName );
01232 }
01233
01234 function getOldRels() {
01235 if ( !isset( $this->srcRels['.'] ) ) {
01236 $oldRels =& $this->srcRels;
01237 $deleteCurrent = false;
01238 } else {
01239 $oldRels = $this->srcRels;
01240 unset( $oldRels['.'] );
01241 $deleteCurrent = true;
01242 }
01243 return array( $oldRels, $deleteCurrent );
01244 }
01245
01246 function getHashes() {
01247 $hashes = array();
01248 list( $oldRels, $deleteCurrent ) = $this->getOldRels();
01249 if ( $deleteCurrent ) {
01250 $hashes['.'] = $this->file->getSha1();
01251 }
01252 if ( count( $oldRels ) ) {
01253 $dbw = $this->file->repo->getMasterDB();
01254 $res = $dbw->select( 'oldimage', array( 'oi_archive_name', 'oi_sha1' ),
01255 'oi_archive_name IN(' . $dbw->makeList( array_keys( $oldRels ) ) . ')',
01256 __METHOD__ );
01257 while ( $row = $dbw->fetchObject( $res ) ) {
01258 if ( rtrim( $row->oi_sha1, "\0" ) === '' ) {
01259
01260 $oldUrl = $this->file->getArchiveVirtualUrl( $row->oi_archive_name );
01261 $props = $this->file->repo->getFileProps( $oldUrl );
01262 if ( $props['fileExists'] ) {
01263
01264 $dbw->update( 'oldimage',
01265 array( 'oi_sha1' => $props['sha1'] ),
01266 array( 'oi_name' => $this->file->getName(), 'oi_archive_name' => $row->oi_archive_name ),
01267 __METHOD__ );
01268 $hashes[$row->oi_archive_name] = $props['sha1'];
01269 } else {
01270 $hashes[$row->oi_archive_name] = false;
01271 }
01272 } else {
01273 $hashes[$row->oi_archive_name] = $row->oi_sha1;
01274 }
01275 }
01276 }
01277 $missing = array_diff_key( $this->srcRels, $hashes );
01278 foreach ( $missing as $name => $rel ) {
01279 $this->status->error( 'filedelete-old-unregistered', $name );
01280 }
01281 foreach ( $hashes as $name => $hash ) {
01282 if ( !$hash ) {
01283 $this->status->error( 'filedelete-missing', $this->srcRels[$name] );
01284 unset( $hashes[$name] );
01285 }
01286 }
01287
01288 return $hashes;
01289 }
01290
01291 function doDBInserts() {
01292 global $wgUser;
01293 $dbw = $this->file->repo->getMasterDB();
01294 $encTimestamp = $dbw->addQuotes( $dbw->timestamp() );
01295 $encUserId = $dbw->addQuotes( $wgUser->getId() );
01296 $encReason = $dbw->addQuotes( $this->reason );
01297 $encGroup = $dbw->addQuotes( 'deleted' );
01298 $ext = $this->file->getExtension();
01299 $dotExt = $ext === '' ? '' : ".$ext";
01300 $encExt = $dbw->addQuotes( $dotExt );
01301 list( $oldRels, $deleteCurrent ) = $this->getOldRels();
01302
01303
01304 if ( $this->suppress ) {
01305 $bitfield = 0;
01306
01307 $bitfield |= Revision::DELETED_TEXT;
01308 $bitfield |= Revision::DELETED_COMMENT;
01309 $bitfield |= Revision::DELETED_USER;
01310 $bitfield |= Revision::DELETED_RESTRICTED;
01311 } else {
01312 $bitfield = 'oi_deleted';
01313 }
01314
01315 if ( $deleteCurrent ) {
01316 $concat = $dbw->buildConcat( array( "img_sha1", $encExt ) );
01317 $where = array( 'img_name' => $this->file->getName() );
01318 $dbw->insertSelect( 'filearchive', 'image',
01319 array(
01320 'fa_storage_group' => $encGroup,
01321 'fa_storage_key' => "CASE WHEN img_sha1='' THEN '' ELSE $concat END",
01322 'fa_deleted_user' => $encUserId,
01323 'fa_deleted_timestamp' => $encTimestamp,
01324 'fa_deleted_reason' => $encReason,
01325 'fa_deleted' => $this->suppress ? $bitfield : 0,
01326
01327 'fa_name' => 'img_name',
01328 'fa_archive_name' => 'NULL',
01329 'fa_size' => 'img_size',
01330 'fa_width' => 'img_width',
01331 'fa_height' => 'img_height',
01332 'fa_metadata' => 'img_metadata',
01333 'fa_bits' => 'img_bits',
01334 'fa_media_type' => 'img_media_type',
01335 'fa_major_mime' => 'img_major_mime',
01336 'fa_minor_mime' => 'img_minor_mime',
01337 'fa_description' => 'img_description',
01338 'fa_user' => 'img_user',
01339 'fa_user_text' => 'img_user_text',
01340 'fa_timestamp' => 'img_timestamp'
01341 ), $where, __METHOD__ );
01342 }
01343
01344 if ( count( $oldRels ) ) {
01345 $concat = $dbw->buildConcat( array( "oi_sha1", $encExt ) );
01346 $where = array(
01347 'oi_name' => $this->file->getName(),
01348 'oi_archive_name IN (' . $dbw->makeList( array_keys( $oldRels ) ) . ')' );
01349 $dbw->insertSelect( 'filearchive', 'oldimage',
01350 array(
01351 'fa_storage_group' => $encGroup,
01352 'fa_storage_key' => "CASE WHEN oi_sha1='' THEN '' ELSE $concat END",
01353 'fa_deleted_user' => $encUserId,
01354 'fa_deleted_timestamp' => $encTimestamp,
01355 'fa_deleted_reason' => $encReason,
01356 'fa_deleted' => $this->suppress ? $bitfield : 'oi_deleted',
01357
01358 'fa_name' => 'oi_name',
01359 'fa_archive_name' => 'oi_archive_name',
01360 'fa_size' => 'oi_size',
01361 'fa_width' => 'oi_width',
01362 'fa_height' => 'oi_height',
01363 'fa_metadata' => 'oi_metadata',
01364 'fa_bits' => 'oi_bits',
01365 'fa_media_type' => 'oi_media_type',
01366 'fa_major_mime' => 'oi_major_mime',
01367 'fa_minor_mime' => 'oi_minor_mime',
01368 'fa_description' => 'oi_description',
01369 'fa_user' => 'oi_user',
01370 'fa_user_text' => 'oi_user_text',
01371 'fa_timestamp' => 'oi_timestamp',
01372 'fa_deleted' => $bitfield
01373 ), $where, __METHOD__ );
01374 }
01375 }
01376
01377 function doDBDeletes() {
01378 $dbw = $this->file->repo->getMasterDB();
01379 list( $oldRels, $deleteCurrent ) = $this->getOldRels();
01380 if ( count( $oldRels ) ) {
01381 $dbw->delete( 'oldimage',
01382 array(
01383 'oi_name' => $this->file->getName(),
01384 'oi_archive_name' => array_keys( $oldRels )
01385 ), __METHOD__ );
01386 }
01387 if ( $deleteCurrent ) {
01388 $dbw->delete( 'image', array( 'img_name' => $this->file->getName() ), __METHOD__ );
01389 }
01390 }
01391
01395 function execute() {
01396 global $wgUseSquid;
01397 wfProfileIn( __METHOD__ );
01398
01399 $this->file->lock();
01400
01401 $privateFiles = array();
01402 list( $oldRels, $deleteCurrent ) = $this->getOldRels();
01403 $dbw = $this->file->repo->getMasterDB();
01404 if( !empty( $oldRels ) ) {
01405 $res = $dbw->select( 'oldimage',
01406 array( 'oi_archive_name' ),
01407 array( 'oi_name' => $this->file->getName(),
01408 'oi_archive_name IN (' . $dbw->makeList( array_keys($oldRels) ) . ')',
01409 $dbw->bitAnd('oi_deleted', File::DELETED_FILE) => File::DELETED_FILE ),
01410 __METHOD__ );
01411 while( $row = $dbw->fetchObject( $res ) ) {
01412 $privateFiles[$row->oi_archive_name] = 1;
01413 }
01414 }
01415
01416 $hashes = $this->getHashes();
01417 $this->deletionBatch = array();
01418 $ext = $this->file->getExtension();
01419 $dotExt = $ext === '' ? '' : ".$ext";
01420 foreach ( $this->srcRels as $name => $srcRel ) {
01421
01422
01423 if ( isset( $hashes[$name] ) && !array_key_exists( $name, $privateFiles ) ) {
01424 $hash = $hashes[$name];
01425 $key = $hash . $dotExt;
01426 $dstRel = $this->file->repo->getDeletedHashPath( $key ) . $key;
01427 $this->deletionBatch[$name] = array( $srcRel, $dstRel );
01428 }
01429 }
01430
01431
01432
01433
01434
01435
01436
01437 $this->doDBInserts();
01438
01439
01440 $this->deletionBatch = $this->removeNonexistentFiles( $this->deletionBatch );
01441
01442
01443 $status = $this->file->repo->deleteBatch( $this->deletionBatch );
01444 if ( !$status->isGood() ) {
01445 $this->status->merge( $status );
01446 }
01447
01448 if ( !$this->status->ok ) {
01449
01450
01451
01452 $this->file->unlockAndRollback();
01453 wfProfileOut( __METHOD__ );
01454 return $this->status;
01455 }
01456
01457
01458 if ( $wgUseSquid ) {
01459 $urls = array();
01460 foreach ( $this->srcRels as $srcRel ) {
01461 $urlRel = str_replace( '%2F', '/', rawurlencode( $srcRel ) );
01462 $urls[] = $this->file->repo->getZoneUrl( 'public' ) . '/' . $urlRel;
01463 }
01464 SquidUpdate::purge( $urls );
01465 }
01466
01467
01468 $this->doDBDeletes();
01469
01470
01471 $this->file->unlock();
01472 wfProfileOut( __METHOD__ );
01473 return $this->status;
01474 }
01475
01479 function removeNonexistentFiles( $batch ) {
01480 $files = $newBatch = array();
01481 foreach( $batch as $batchItem ) {
01482 list( $src, $dest ) = $batchItem;
01483 $files[$src] = $this->file->repo->getVirtualUrl( 'public' ) . '/' . rawurlencode( $src );
01484 }
01485 $result = $this->file->repo->fileExistsBatch( $files, FSRepo::FILES_ONLY );
01486 foreach( $batch as $batchItem )
01487 if( $result[$batchItem[0]] )
01488 $newBatch[] = $batchItem;
01489 return $newBatch;
01490 }
01491 }
01492
01493 #------------------------------------------------------------------------------
01494
01499 class LocalFileRestoreBatch {
01500 var $file, $cleanupBatch, $ids, $all, $unsuppress = false;
01501
01502 function __construct( File $file, $unsuppress = false ) {
01503 $this->file = $file;
01504 $this->cleanupBatch = $this->ids = array();
01505 $this->ids = array();
01506 $this->unsuppress = $unsuppress;
01507 }
01508
01512 function addId( $fa_id ) {
01513 $this->ids[] = $fa_id;
01514 }
01515
01519 function addIds( $ids ) {
01520 $this->ids = array_merge( $this->ids, $ids );
01521 }
01522
01526 function addAll() {
01527 $this->all = true;
01528 }
01529
01537 function execute() {
01538 global $wgLang;
01539 if ( !$this->all && !$this->ids ) {
01540
01541 return $this->file->repo->newGood();
01542 }
01543
01544 $exists = $this->file->lock();
01545 $dbw = $this->file->repo->getMasterDB();
01546 $status = $this->file->repo->newGood();
01547
01548
01549
01550 $conditions = array( 'fa_name' => $this->file->getName() );
01551 if( !$this->all ) {
01552 $conditions[] = 'fa_id IN (' . $dbw->makeList( $this->ids ) . ')';
01553 }
01554
01555 $result = $dbw->select( 'filearchive', '*',
01556 $conditions,
01557 __METHOD__,
01558 array( 'ORDER BY' => 'fa_timestamp DESC' )
01559 );
01560
01561 $idsPresent = array();
01562 $storeBatch = array();
01563 $insertBatch = array();
01564 $insertCurrent = false;
01565 $deleteIds = array();
01566 $first = true;
01567 $archiveNames = array();
01568 while( $row = $dbw->fetchObject( $result ) ) {
01569 $idsPresent[] = $row->fa_id;
01570
01571 if ( $row->fa_name != $this->file->getName() ) {
01572 $status->error( 'undelete-filename-mismatch', $wgLang->timeanddate( $row->fa_timestamp ) );
01573 $status->failCount++;
01574 continue;
01575 }
01576 if ( $row->fa_storage_key == '' ) {
01577
01578 $status->error( 'undelete-bad-store-key', $wgLang->timeanddate( $row->fa_timestamp ) );
01579 $status->failCount++;
01580 continue;
01581 }
01582
01583 $deletedRel = $this->file->repo->getDeletedHashPath( $row->fa_storage_key ) . $row->fa_storage_key;
01584 $deletedUrl = $this->file->repo->getVirtualUrl() . '/deleted/' . $deletedRel;
01585
01586 $sha1 = substr( $row->fa_storage_key, 0, strcspn( $row->fa_storage_key, '.' ) );
01587 # Fix leading zero
01588 if ( strlen( $sha1 ) == 32 && $sha1[0] == '0' ) {
01589 $sha1 = substr( $sha1, 1 );
01590 }
01591
01592 if( is_null( $row->fa_major_mime ) || $row->fa_major_mime == 'unknown'
01593 || is_null( $row->fa_minor_mime ) || $row->fa_minor_mime == 'unknown'
01594 || is_null( $row->fa_media_type ) || $row->fa_media_type == 'UNKNOWN'
01595 || is_null( $row->fa_metadata ) ) {
01596
01597
01598 $props = RepoGroup::singleton()->getFileProps( $deletedUrl );
01599 } else {
01600 $props = array(
01601 'minor_mime' => $row->fa_minor_mime,
01602 'major_mime' => $row->fa_major_mime,
01603 'media_type' => $row->fa_media_type,
01604 'metadata' => $row->fa_metadata
01605 );
01606 }
01607
01608 if ( $first && !$exists ) {
01609
01610 $destRel = $this->file->getRel();
01611 $insertCurrent = array(
01612 'img_name' => $row->fa_name,
01613 'img_size' => $row->fa_size,
01614 'img_width' => $row->fa_width,
01615 'img_height' => $row->fa_height,
01616 'img_metadata' => $props['metadata'],
01617 'img_bits' => $row->fa_bits,
01618 'img_media_type' => $props['media_type'],
01619 'img_major_mime' => $props['major_mime'],
01620 'img_minor_mime' => $props['minor_mime'],
01621 'img_description' => $row->fa_description,
01622 'img_user' => $row->fa_user,
01623 'img_user_text' => $row->fa_user_text,
01624 'img_timestamp' => $row->fa_timestamp,
01625 'img_sha1' => $sha1
01626 );
01627
01628 if( !$this->unsuppress && $row->fa_deleted ) {
01629 $storeBatch[] = array( $deletedUrl, 'public', $destRel );
01630 $this->cleanupBatch[] = $row->fa_storage_key;
01631 }
01632 } else {
01633 $archiveName = $row->fa_archive_name;
01634 if( $archiveName == '' ) {
01635
01636
01637
01638 $timestamp = wfTimestamp( TS_UNIX, $row->fa_deleted_timestamp );
01639 do {
01640 $archiveName = wfTimestamp( TS_MW, $timestamp ) . '!' . $row->fa_name;
01641 $timestamp++;
01642 } while ( isset( $archiveNames[$archiveName] ) );
01643 }
01644 $archiveNames[$archiveName] = true;
01645 $destRel = $this->file->getArchiveRel( $archiveName );
01646 $insertBatch[] = array(
01647 'oi_name' => $row->fa_name,
01648 'oi_archive_name' => $archiveName,
01649 'oi_size' => $row->fa_size,
01650 'oi_width' => $row->fa_width,
01651 'oi_height' => $row->fa_height,
01652 'oi_bits' => $row->fa_bits,
01653 'oi_description' => $row->fa_description,
01654 'oi_user' => $row->fa_user,
01655 'oi_user_text' => $row->fa_user_text,
01656 'oi_timestamp' => $row->fa_timestamp,
01657 'oi_metadata' => $props['metadata'],
01658 'oi_media_type' => $props['media_type'],
01659 'oi_major_mime' => $props['major_mime'],
01660 'oi_minor_mime' => $props['minor_mime'],
01661 'oi_deleted' => $this->unsuppress ? 0 : $row->fa_deleted,
01662 'oi_sha1' => $sha1 );
01663 }
01664
01665 $deleteIds[] = $row->fa_id;
01666 if( !$this->unsuppress && $row->fa_deleted & File::DELETED_FILE ) {
01667
01668 $status->successCount++;
01669 } else {
01670 $storeBatch[] = array( $deletedUrl, 'public', $destRel );
01671 $this->cleanupBatch[] = $row->fa_storage_key;
01672 }
01673 $first = false;
01674 }
01675 unset( $result );
01676
01677
01678 $missingIds = array_diff( $this->ids, $idsPresent );
01679 foreach ( $missingIds as $id ) {
01680 $status->error( 'undelete-missing-filearchive', $id );
01681 }
01682
01683
01684 $storeBatch = $this->removeNonexistentFiles( $storeBatch );
01685
01686
01687
01688 $storeStatus = $this->file->repo->storeBatch( $storeBatch, FileRepo::OVERWRITE_SAME );
01689 $status->merge( $storeStatus );
01690
01691 if ( !$status->ok ) {
01692
01693
01694 $this->file->unlock();
01695 return $status;
01696 }
01697
01698
01699
01700
01701
01702
01703
01704 if ( $insertCurrent ) {
01705 $dbw->insert( 'image', $insertCurrent, __METHOD__ );
01706 }
01707 if ( $insertBatch ) {
01708 $dbw->insert( 'oldimage', $insertBatch, __METHOD__ );
01709 }
01710 if ( $deleteIds ) {
01711 $dbw->delete( 'filearchive',
01712 array( 'fa_id IN (' . $dbw->makeList( $deleteIds ) . ')' ),
01713 __METHOD__ );
01714 }
01715
01716
01717 if( $status->successCount > 0 || !$storeBatch ) {
01718 if( !$exists ) {
01719 wfDebug( __METHOD__ . " restored {$status->successCount} items, creating a new current\n" );
01720
01721
01722 $site_stats = $dbw->tableName( 'site_stats' );
01723 $dbw->query( "UPDATE $site_stats SET ss_images=ss_images+1", __METHOD__ );
01724
01725 $this->file->purgeEverything();
01726 } else {
01727 wfDebug( __METHOD__ . " restored {$status->successCount} as archived versions\n" );
01728 $this->file->purgeDescription();
01729 $this->file->purgeHistory();
01730 }
01731 }
01732 $this->file->unlock();
01733 return $status;
01734 }
01735
01739 function removeNonexistentFiles( $triplets ) {
01740 $files = $filteredTriplets = array();
01741 foreach( $triplets as $file )
01742 $files[$file[0]] = $file[0];
01743 $result = $this->file->repo->fileExistsBatch( $files, FSRepo::FILES_ONLY );
01744 foreach( $triplets as $file )
01745 if( $result[$file[0]] )
01746 $filteredTriplets[] = $file;
01747 return $filteredTriplets;
01748 }
01749
01753 function removeNonexistentFromCleanup( $batch ) {
01754 $files = $newBatch = array();
01755 $repo = $this->file->repo;
01756 foreach( $batch as $file ) {
01757 $files[$file] = $repo->getVirtualUrl( 'deleted' ) . '/' .
01758 rawurlencode( $repo->getDeletedHashPath( $file ) . $file );
01759 }
01760
01761 $result = $repo->fileExistsBatch( $files, FSRepo::FILES_ONLY );
01762 foreach( $batch as $file )
01763 if( $result[$file] )
01764 $newBatch[] = $file;
01765 return $newBatch;
01766 }
01767
01772 function cleanup() {
01773 if ( !$this->cleanupBatch ) {
01774 return $this->file->repo->newGood();
01775 }
01776 $this->cleanupBatch = $this->removeNonexistentFromCleanup( $this->cleanupBatch );
01777 $status = $this->file->repo->cleanupDeletedBatch( $this->cleanupBatch );
01778 return $status;
01779 }
01780 }
01781
01782 #------------------------------------------------------------------------------
01783
01788 class LocalFileMoveBatch {
01789 var $file, $cur, $olds, $oldCount, $archive, $target, $db;
01790
01791 function __construct( File $file, Title $target ) {
01792 $this->file = $file;
01793 $this->target = $target;
01794 $this->oldHash = $this->file->repo->getHashPath( $this->file->getName() );
01795 $this->newHash = $this->file->repo->getHashPath( $this->target->getDBkey() );
01796 $this->oldName = $this->file->getName();
01797 $this->newName = $this->file->repo->getNameFromTitle( $this->target );
01798 $this->oldRel = $this->oldHash . $this->oldName;
01799 $this->newRel = $this->newHash . $this->newName;
01800 $this->db = $file->repo->getMasterDb();
01801 }
01802
01806 function addCurrent() {
01807 $this->cur = array( $this->oldRel, $this->newRel );
01808 }
01809
01813 function addOlds() {
01814 $archiveBase = 'archive';
01815 $this->olds = array();
01816 $this->oldCount = 0;
01817
01818 $result = $this->db->select( 'oldimage',
01819 array( 'oi_archive_name', 'oi_deleted' ),
01820 array( 'oi_name' => $this->oldName ),
01821 __METHOD__
01822 );
01823 while( $row = $this->db->fetchObject( $result ) ) {
01824 $oldName = $row->oi_archive_name;
01825 $bits = explode( '!', $oldName, 2 );
01826 if( count( $bits ) != 2 ) {
01827 wfDebug( "Invalid old file name: $oldName \n" );
01828 continue;
01829 }
01830 list( $timestamp, $filename ) = $bits;
01831 if( $this->oldName != $filename ) {
01832 wfDebug( "Invalid old file name: $oldName \n" );
01833 continue;
01834 }
01835 $this->oldCount++;
01836
01837 if( $row->oi_deleted & File::DELETED_FILE ) {
01838 continue;
01839 }
01840 $this->olds[] = array(
01841 "{$archiveBase}/{$this->oldHash}{$oldName}",
01842 "{$archiveBase}/{$this->newHash}{$timestamp}!{$this->newName}"
01843 );
01844 }
01845 $this->db->freeResult( $result );
01846 }
01847
01851 function execute() {
01852 $repo = $this->file->repo;
01853 $status = $repo->newGood();
01854 $triplets = $this->getMoveTriplets();
01855
01856 $triplets = $this->removeNonexistentFiles( $triplets );
01857 $statusDb = $this->doDBUpdates();
01858 wfDebugLog( 'imagemove', "Renamed {$this->file->name} in database: {$statusDb->successCount} successes, {$statusDb->failCount} failures" );
01859 $statusMove = $repo->storeBatch( $triplets, FSRepo::DELETE_SOURCE );
01860 wfDebugLog( 'imagemove', "Moved files for {$this->file->name}: {$statusMove->successCount} successes, {$statusMove->failCount} failures" );
01861 if( !$statusMove->isOk() ) {
01862 wfDebugLog( 'imagemove', "Error in moving files: " . $statusMove->getWikiText() );
01863 $this->db->rollback();
01864 }
01865
01866 $status->merge( $statusDb );
01867 $status->merge( $statusMove );
01868 return $status;
01869 }
01870
01875 function doDBUpdates() {
01876 $repo = $this->file->repo;
01877 $status = $repo->newGood();
01878 $dbw = $this->db;
01879
01880
01881 $dbw->update(
01882 'image',
01883 array( 'img_name' => $this->newName ),
01884 array( 'img_name' => $this->oldName ),
01885 __METHOD__
01886 );
01887 if( $dbw->affectedRows() ) {
01888 $status->successCount++;
01889 } else {
01890 $status->failCount++;
01891 }
01892
01893
01894 $dbw->update(
01895 'oldimage',
01896 array(
01897 'oi_name' => $this->newName,
01898 'oi_archive_name = ' . $dbw->strreplace( 'oi_archive_name', $dbw->addQuotes($this->oldName), $dbw->addQuotes($this->newName) ),
01899 ),
01900 array( 'oi_name' => $this->oldName ),
01901 __METHOD__
01902 );
01903 $affected = $dbw->affectedRows();
01904 $total = $this->oldCount;
01905 $status->successCount += $affected;
01906 $status->failCount += $total - $affected;
01907
01908 return $status;
01909 }
01910
01914 function getMoveTriplets() {
01915 $moves = array_merge( array( $this->cur ), $this->olds );
01916 $triplets = array();
01917 foreach( $moves as $move ) {
01918
01919 $srcUrl = $this->file->repo->getVirtualUrl() . '/public/' . rawurlencode( $move[0] );
01920 $triplets[] = array( $srcUrl, 'public', $move[1] );
01921 wfDebugLog( 'imagemove', "Generated move triplet for {$this->file->name}: {$srcUrl} :: public :: {$move[1]}" );
01922 }
01923 return $triplets;
01924 }
01925
01929 function removeNonexistentFiles( $triplets ) {
01930 $files = array();
01931 foreach( $triplets as $file )
01932 $files[$file[0]] = $file[0];
01933 $result = $this->file->repo->fileExistsBatch( $files, FSRepo::FILES_ONLY );
01934 $filteredTriplets = array();
01935 foreach( $triplets as $file )
01936 if( $result[$file[0]] ) {
01937 $filteredTriplets[] = $file;
01938 } else {
01939 wfDebugLog( 'imagemove', "File {$file[0]} does not exist" );
01940 }
01941 return $filteredTriplets;
01942 }
01943 }