00001 <?php
00008 define( 'MW_FILE_VERSION', 8 );
00009
00027 class LocalFile extends File
00028 {
00032 var $fileExists, # does the file file exist on disk? (loadFromXxx)
00033 $historyLine, # Number of line to return by nextHistoryLine() (constructor)
00034 $historyRes, # result of the query for the file's history (nextHistoryLine)
00035 $width, # \
00036 $height, # |
00037 $bits, # --- returned by getimagesize (loadFromXxx)
00038 $attr, # /
00039 $media_type, # MEDIATYPE_xxx (bitmap, drawing, audio...)
00040 $mime, # MIME type, determined by MimeMagic::guessMimeType
00041 $major_mime, # Major mime type
00042 $minor_mime, # Minor mime type
00043 $size, # Size in bytes (loadFromXxx)
00044 $metadata, # Handler-specific metadata
00045 $timestamp, # Upload timestamp
00046 $sha1, # SHA-1 base 36 content hash
00047 $user, $user_text, # User, who uploaded the file
00048 $description, # Description of current revision of the file
00049 $dataLoaded, # Whether or not all this has been loaded from the database (loadFromXxx)
00050 $upgraded, # Whether the row was upgraded on load
00051 $locked, # True if the image row is locked
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
00137 function getCacheKey() {
00138 $hashedName = md5($this->getName());
00139 return wfMemcKey( 'file', $hashedName );
00140 }
00141
00145 function loadFromCache() {
00146 global $wgMemc;
00147 wfProfileIn( __METHOD__ );
00148 $this->dataLoaded = false;
00149 $key = $this->getCacheKey();
00150 if ( !$key ) {
00151 return false;
00152 }
00153 $cachedValues = $wgMemc->get( $key );
00154
00155 // Check if the key existed and belongs to this version of MediaWiki
00156 if ( isset($cachedValues['version']) && ( $cachedValues['version'] == MW_FILE_VERSION ) ) {
00157 wfDebug( "Pulling file metadata from cache key $key\n" );
00158 $this->fileExists = $cachedValues['fileExists'];
00159 if ( $this->fileExists ) {
00160 $this->setProps( $cachedValues );
00161 }
00162 $this->dataLoaded = true;
00163 }
00164 if ( $this->dataLoaded ) {
00165 wfIncrStats( 'image_cache_hit' );
00166 } else {
00167 wfIncrStats( 'image_cache_miss' );
00168 }
00169
00170 wfProfileOut( __METHOD__ );
00171 return $this->dataLoaded;
00172 }
00173
00177 function saveToCache() {
00178 global $wgMemc;
00179 $this->load();
00180 $key = $this->getCacheKey();
00181 if ( !$key ) {
00182 return;
00183 }
00184 $fields = $this->getCacheFields( '' );
00185 $cache = array( 'version' => MW_FILE_VERSION );
00186 $cache['fileExists'] = $this->fileExists;
00187 if ( $this->fileExists ) {
00188 foreach ( $fields as $field ) {
00189 $cache[$field] = $this->$field;
00190 }
00191 }
00192
00193 $wgMemc->set( $key, $cache, 60 * 60 * 24 * 7 ); // A week
00194 }
00195
00199 function loadFromFile() {
00200 $this->setProps( self::getPropsFromPath( $this->getPath() ) );
00201 }
00202
00203 function getCacheFields( $prefix = 'img_' ) {
00204 static $fields = array( 'size', 'width', 'height', 'bits', 'media_type',
00205 'major_mime', 'minor_mime', 'metadata', 'timestamp', 'sha1', 'user', 'user_text', 'description' );
00206 static $results = array();
00207 if ( $prefix == '' ) {
00208 return $fields;
00209 }
00210 if ( !isset( $results[$prefix] ) ) {
00211 $prefixedFields = array();
00212 foreach ( $fields as $field ) {
00213 $prefixedFields[] = $prefix . $field;
00214 }
00215 $results[$prefix] = $prefixedFields;
00216 }
00217 return $results[$prefix];
00218 }
00219
00223 function loadFromDB() {
00224 # Polymorphic function name to distinguish foreign and local fetches
00225 $fname = get_class( $this ) . '::' . __FUNCTION__;
00226 wfProfileIn( $fname );
00227
00228 # Unconditionally set loaded=true, we don't want the accessors constantly rechecking
00229 $this->dataLoaded = true;
00230
00231 $dbr = $this->repo->getMasterDB();
00232
00233 $row = $dbr->selectRow( 'image', $this->getCacheFields( 'img_' ),
00234 array( 'img_name' => $this->getName() ), $fname );
00235 if ( $row ) {
00236 $this->loadFromRow( $row );
00237 } else {
00238 $this->fileExists = false;
00239 }
00240
00241 wfProfileOut( $fname );
00242 }
00243
00248 function decodeRow( $row, $prefix = 'img_' ) {
00249 $array = (array)$row;
00250 $prefixLength = strlen( $prefix );
00251
00252 if ( substr( key( $array ), 0, $prefixLength ) !== $prefix ) {
00253 throw new MWException( __METHOD__. ': incorrect $prefix parameter' );
00254 }
00255 $decoded = array();
00256 foreach ( $array as $name => $value ) {
00257 $decoded[substr( $name, $prefixLength )] = $value;
00258 }
00259 $decoded['timestamp'] = wfTimestamp( TS_MW, $decoded['timestamp'] );
00260 if ( empty( $decoded['major_mime'] ) ) {
00261 $decoded['mime'] = "unknown/unknown";
00262 } else {
00263 if (!$decoded['minor_mime']) {
00264 $decoded['minor_mime'] = "unknown";
00265 }
00266 $decoded['mime'] = $decoded['major_mime'].'/'.$decoded['minor_mime'];
00267 }
00268 # Trim zero padding from char/binary field
00269 $decoded['sha1'] = rtrim( $decoded['sha1'], "\0" );
00270 return $decoded;
00271 }
00272
00273
00274
00275
00276 function loadFromRow( $row, $prefix = 'img_' ) {
00277 $this->dataLoaded = true;
00278 $array = $this->decodeRow( $row, $prefix );
00279 foreach ( $array as $name => $value ) {
00280 $this->$name = $value;
00281 }
00282 $this->fileExists = true;
00283 $this->maybeUpgradeRow();
00284 }
00285
00289 function load() {
00290 if ( !$this->dataLoaded ) {
00291 if ( !$this->loadFromCache() ) {
00292 $this->loadFromDB();
00293 $this->saveToCache();
00294 }
00295 $this->dataLoaded = true;
00296 }
00297 }
00298
00302 function maybeUpgradeRow() {
00303 if ( wfReadOnly() ) {
00304 return;
00305 }
00306 if ( is_null($this->media_type) ||
00307 $this->mime == 'image/svg'
00308 ) {
00309 $this->upgradeRow();
00310 $this->upgraded = true;
00311 } else {
00312 $handler = $this->getHandler();
00313 if ( $handler && !$handler->isMetadataValid( $this, $this->metadata ) ) {
00314 $this->upgradeRow();
00315 $this->upgraded = true;
00316 }
00317 }
00318 }
00319
00320 function getUpgraded() {
00321 return $this->upgraded;
00322 }
00323
00327 function upgradeRow() {
00328 wfProfileIn( __METHOD__ );
00329
00330 $this->loadFromFile();
00331
00332 # Don't destroy file info of missing files
00333 if ( !$this->fileExists ) {
00334 wfDebug( __METHOD__.": file does not exist, aborting\n" );
00335 return;
00336 }
00337 $dbw = $this->repo->getMasterDB();
00338 list( $major, $minor ) = self::splitMime( $this->mime );
00339
00340 if ( wfReadOnly() ) {
00341 return;
00342 }
00343 wfDebug(__METHOD__.': upgrading '.$this->getName()." to the current schema\n");
00344
00345 $dbw->update( 'image',
00346 array(
00347 'img_width' => $this->width,
00348 'img_height' => $this->height,
00349 'img_bits' => $this->bits,
00350 'img_media_type' => $this->media_type,
00351 'img_major_mime' => $major,
00352 'img_minor_mime' => $minor,
00353 'img_metadata' => $this->metadata,
00354 'img_sha1' => $this->sha1,
00355 ), array( 'img_name' => $this->getName() ),
00356 __METHOD__
00357 );
00358 $this->saveToCache();
00359 wfProfileOut( __METHOD__ );
00360 }
00361
00369 function setProps( $info ) {
00370 $this->dataLoaded = true;
00371 $fields = $this->getCacheFields( '' );
00372 $fields[] = 'fileExists';
00373 foreach ( $fields as $field ) {
00374 if ( isset( $info[$field] ) ) {
00375 $this->$field = $info[$field];
00376 }
00377 }
00378
00379 if ( isset( $info['major_mime'] ) ) {
00380 $this->mime = "{$info['major_mime']}/{$info['minor_mime']}";
00381 } elseif ( isset( $info['mime'] ) ) {
00382 list( $this->major_mime, $this->minor_mime ) = self::splitMime( $this->mime );
00383 }
00384 }
00385
00400 function getWidth( $page = 1 ) {
00401 $this->load();
00402 if ( $this->isMultipage() ) {
00403 $dim = $this->getHandler()->getPageDimensions( $this, $page );
00404 if ( $dim ) {
00405 return $dim['width'];
00406 } else {
00407 return false;
00408 }
00409 } else {
00410 return $this->width;
00411 }
00412 }
00413
00420 function getHeight( $page = 1 ) {
00421 $this->load();
00422 if ( $this->isMultipage() ) {
00423 $dim = $this->getHandler()->getPageDimensions( $this, $page );
00424 if ( $dim ) {
00425 return $dim['height'];
00426 } else {
00427 return false;
00428 }
00429 } else {
00430 return $this->height;
00431 }
00432 }
00433
00439 function getUser($type='text') {
00440 $this->load();
00441 if( $type == 'text' ) {
00442 return $this->user_text;
00443 } elseif( $type == 'id' ) {
00444 return $this->user;
00445 }
00446 }
00447
00451 function getMetadata() {
00452 $this->load();
00453 return $this->metadata;
00454 }
00455
00456 function getBitDepth() {
00457 $this->load();
00458 return $this->bits;
00459 }
00460
00465 function getSize() {
00466 $this->load();
00467 return $this->size;
00468 }
00469
00473 function getMimeType() {
00474 $this->load();
00475 return $this->mime;
00476 }
00477
00482 function getMediaType() {
00483 $this->load();
00484 return $this->media_type;
00485 }
00486
00498 function exists() {
00499 $this->load();
00500 return $this->fileExists;
00501 }
00502
00513 function migrateThumbFile( $thumbName ) {
00514 $thumbDir = $this->getThumbPath();
00515 $thumbPath = "$thumbDir/$thumbName";
00516 if ( is_dir( $thumbPath ) ) {
00517
00518
00519
00520 for ( $i = 0; $i < 100 ; $i++ ) {
00521 $broken = $this->repo->getZonePath('public') . "/broken-$i-$thumbName";
00522 if ( !file_exists( $broken ) ) {
00523 rename( $thumbPath, $broken );
00524 break;
00525 }
00526 }
00527
00528 clearstatcache();
00529 }
00530 if ( is_file( $thumbDir ) ) {
00531
00532 unlink( $thumbDir );
00533
00534 clearstatcache();
00535 }
00536 }
00537
00545 function getThumbnails() {
00546 $this->load();
00547 $files = array();
00548 $dir = $this->getThumbPath();
00549
00550 if ( is_dir( $dir ) ) {
00551 $handle = opendir( $dir );
00552
00553 if ( $handle ) {
00554 while ( false !== ( $file = readdir($handle) ) ) {
00555 if ( $file{0} != '.' ) {
00556 $files[] = $file;
00557 }
00558 }
00559 closedir( $handle );
00560 }
00561 }
00562
00563 return $files;
00564 }
00565
00569 function purgeMetadataCache() {
00570 $this->loadFromDB();
00571 $this->saveToCache();
00572 $this->purgeHistory();
00573 }
00574
00578 function purgeHistory() {
00579 global $wgMemc;
00580 $hashedName = md5($this->getName());
00581 $oldKey = wfMemcKey( 'oldfile', $hashedName );
00582 $wgMemc->delete( $oldKey );
00583 }
00584
00588 function purgeCache() {
00589
00590 $this->purgeMetadataCache();
00591
00592
00593 $this->purgeThumbnails();
00594
00595
00596 SquidUpdate::purge( array( $this->getURL() ) );
00597 }
00598
00602 function purgeThumbnails() {
00603 global $wgUseSquid;
00604
00605 $files = $this->getThumbnails();
00606 $dir = $this->getThumbPath();
00607 $urls = array();
00608 foreach ( $files as $file ) {
00609 # Check that the base file name is part of the thumb name
00610 # This is a basic sanity check to avoid erasing unrelated directories
00611 if ( strpos( $file, $this->getName() ) !== false ) {
00612 $url = $this->getThumbUrl( $file );
00613 $urls[] = $url;
00614 @unlink( "$dir/$file" );
00615 }
00616 }
00617
00618
00619 if ( $wgUseSquid ) {
00620 SquidUpdate::purge( $urls );
00621 }
00622 }
00623
00627 function getHistory($limit = null, $start = null, $end = null, $inc = true) {
00628 $dbr = $this->repo->getSlaveDB();
00629 $tables = array('oldimage');
00630 $fields = OldLocalFile::selectFields();
00631 $conds = $opts = $join_conds = array();
00632 $eq = $inc ? "=" : "";
00633 $conds[] = "oi_name = " . $dbr->addQuotes( $this->title->getDBKey() );
00634 if( $start ) {
00635 $conds[] = "oi_timestamp <$eq " . $dbr->addQuotes( $dbr->timestamp( $start ) );
00636 }
00637 if( $end ) {
00638 $conds[] = "oi_timestamp >$eq " . $dbr->addQuotes( $dbr->timestamp( $end ) );
00639 }
00640 if( $limit ) {
00641 $opts['LIMIT'] = $limit;
00642 }
00643
00644 $order = (!$start && $end !== null) ? "ASC" : "DESC";
00645 $opts['ORDER BY'] = "oi_timestamp $order";
00646 $opts['USE INDEX'] = array('oldimage' => 'oi_name_timestamp');
00647
00648 wfRunHooks( 'LocalFile::getHistory', array( &$this, &$tables, &$fields,
00649 &$conds, &$opts, &$join_conds ) );
00650
00651 $res = $dbr->select( $tables, $fields, $conds, __METHOD__, $opts, $join_conds );
00652 $r = array();
00653 while( $row = $dbr->fetchObject($res) ) {
00654 $r[] = OldLocalFile::newFromRow($row, $this->repo);
00655 }
00656 if( $order == "ASC" ) {
00657 $r = array_reverse( $r );
00658 }
00659 return $r;
00660 }
00661
00672 function nextHistoryLine() {
00673 # Polymorphic function name to distinguish foreign and local fetches
00674 $fname = get_class( $this ) . '::' . __FUNCTION__;
00675
00676 $dbr = $this->repo->getSlaveDB();
00677
00678 if ( $this->historyLine == 0 ) {
00679 $this->historyRes = $dbr->select( 'image',
00680 array(
00681 '*',
00682 "'' AS oi_archive_name",
00683 '0 as oi_deleted',
00684 'img_sha1'
00685 ),
00686 array( 'img_name' => $this->title->getDBkey() ),
00687 $fname
00688 );
00689 if ( 0 == $dbr->numRows( $this->historyRes ) ) {
00690 $dbr->freeResult($this->historyRes);
00691 $this->historyRes = null;
00692 return FALSE;
00693 }
00694 } else if ( $this->historyLine == 1 ) {
00695 $dbr->freeResult($this->historyRes);
00696 $this->historyRes = $dbr->select( 'oldimage', '*',
00697 array( 'oi_name' => $this->title->getDBkey() ),
00698 $fname,
00699 array( 'ORDER BY' => 'oi_timestamp DESC' )
00700 );
00701 }
00702 $this->historyLine ++;
00703
00704 return $dbr->fetchObject( $this->historyRes );
00705 }
00706
00711 function resetHistory() {
00712 $this->historyLine = 0;
00713 if (!is_null($this->historyRes)) {
00714 $this->repo->getSlaveDB()->freeResult($this->historyRes);
00715 $this->historyRes = null;
00716 }
00717 }
00718
00747 function upload( $srcPath, $comment, $pageText, $flags = 0, $props = false, $timestamp = false, $user = null ) {
00748 $this->lock();
00749 $status = $this->publish( $srcPath, $flags );
00750 if ( $status->ok ) {
00751 if ( !$this->recordUpload2( $status->value, $comment, $pageText, $props, $timestamp, $user ) ) {
00752 $status->fatal( 'filenotfound', $srcPath );
00753 }
00754 }
00755 $this->unlock();
00756 return $status;
00757 }
00758
00763 function recordUpload( $oldver, $desc, $license = '', $copyStatus = '', $source = '',
00764 $watch = false, $timestamp = false )
00765 {
00766 $pageText = UploadForm::getInitialPageText( $desc, $license, $copyStatus, $source );
00767 if ( !$this->recordUpload2( $oldver, $desc, $pageText ) ) {
00768 return false;
00769 }
00770 if ( $watch ) {
00771 global $wgUser;
00772 $wgUser->addWatch( $this->getTitle() );
00773 }
00774 return true;
00775
00776 }
00777
00781 function recordUpload2( $oldver, $comment, $pageText, $props = false, $timestamp = false, $user = null )
00782 {
00783 if( is_null( $user ) ) {
00784 global $wgUser;
00785 $user = $wgUser;
00786 }
00787
00788 $dbw = $this->repo->getMasterDB();
00789 $dbw->begin();
00790
00791 if ( !$props ) {
00792 $props = $this->repo->getFileProps( $this->getVirtualUrl() );
00793 }
00794 $props['description'] = $comment;
00795 $props['user'] = $user->getId();
00796 $props['user_text'] = $user->getName();
00797 $props['timestamp'] = wfTimestamp( TS_MW );
00798 $this->setProps( $props );
00799
00800
00801 $this->purgeThumbnails();
00802 $this->saveToCache();
00803 SquidUpdate::purge( array( $this->getURL() ) );
00804
00805
00806 if ( !$this->fileExists ) {
00807 wfDebug( __METHOD__.": File ".$this->getPath()." went missing!\n" );
00808 return false;
00809 }
00810
00811 $reupload = false;
00812 if ( $timestamp === false ) {
00813 $timestamp = $dbw->timestamp();
00814 }
00815
00816 # Test to see if the row exists using INSERT IGNORE
00817 # This avoids race conditions by locking the row until the commit, and also
00818 # doesn't deadlock. SELECT FOR UPDATE causes a deadlock for every race condition.
00819 $dbw->insert( 'image',
00820 array(
00821 'img_name' => $this->getName(),
00822 'img_size'=> $this->size,
00823 'img_width' => intval( $this->width ),
00824 'img_height' => intval( $this->height ),
00825 'img_bits' => $this->bits,
00826 'img_media_type' => $this->media_type,
00827 'img_major_mime' => $this->major_mime,
00828 'img_minor_mime' => $this->minor_mime,
00829 'img_timestamp' => $timestamp,
00830 'img_description' => $comment,
00831 'img_user' => $user->getId(),
00832 'img_user_text' => $user->getName(),
00833 'img_metadata' => $this->metadata,
00834 'img_sha1' => $this->sha1
00835 ),
00836 __METHOD__,
00837 'IGNORE'
00838 );
00839
00840 if( $dbw->affectedRows() == 0 ) {
00841 $reupload = true;
00842
00843 # Collision, this is an update of a file
00844 # Insert previous contents into oldimage
00845 $dbw->insertSelect( 'oldimage', 'image',
00846 array(
00847 'oi_name' => 'img_name',
00848 'oi_archive_name' => $dbw->addQuotes( $oldver ),
00849 'oi_size' => 'img_size',
00850 'oi_width' => 'img_width',
00851 'oi_height' => 'img_height',
00852 'oi_bits' => 'img_bits',
00853 'oi_timestamp' => 'img_timestamp',
00854 'oi_description' => 'img_description',
00855 'oi_user' => 'img_user',
00856 'oi_user_text' => 'img_user_text',
00857 'oi_metadata' => 'img_metadata',
00858 'oi_media_type' => 'img_media_type',
00859 'oi_major_mime' => 'img_major_mime',
00860 'oi_minor_mime' => 'img_minor_mime',
00861 'oi_sha1' => 'img_sha1'
00862 ), array( 'img_name' => $this->getName() ), __METHOD__
00863 );
00864
00865 # Update the current image row
00866 $dbw->update( 'image',
00867 array(
00868 'img_size' => $this->size,
00869 'img_width' => intval( $this->width ),
00870 'img_height' => intval( $this->height ),
00871 'img_bits' => $this->bits,
00872 'img_media_type' => $this->media_type,
00873 'img_major_mime' => $this->major_mime,
00874 'img_minor_mime' => $this->minor_mime,
00875 'img_timestamp' => $timestamp,
00876 'img_description' => $comment,
00877 'img_user' => $user->getId(),
00878 'img_user_text' => $user->getName(),
00879 'img_metadata' => $this->metadata,
00880 'img_sha1' => $this->sha1
00881 ), array(
00882 'img_name' => $this->getName()
00883 ), __METHOD__
00884 );
00885 } else {
00886 # This is a new file
00887 # Update the image count
00888 $site_stats = $dbw->tableName( 'site_stats' );
00889 $dbw->query( "UPDATE $site_stats SET ss_images=ss_images+1", __METHOD__ );
00890 }
00891
00892 $descTitle = $this->getTitle();
00893 $article = new ImagePage( $descTitle );
00894 $article->setFile( $this );
00895
00896 # Add the log entry
00897 $log = new LogPage( 'upload' );
00898 $action = $reupload ? 'overwrite' : 'upload';
00899 $log->addEntry( $action, $descTitle, $comment, array(), $user );
00900
00901 if( $descTitle->exists() ) {
00902 # Create a null revision
00903 $latest = $descTitle->getLatestRevID();
00904 $nullRevision = Revision::newNullRevision( $dbw, $descTitle->getArticleId(),
00905 $log->getRcComment(), false );
00906 $nullRevision->insertOn( $dbw );
00907
00908 wfRunHooks( 'NewRevisionFromEditComplete', array($article, $nullRevision, $latest, $user) );
00909 $article->updateRevisionOn( $dbw, $nullRevision );
00910
00911 # Invalidate the cache for the description page
00912 $descTitle->invalidateCache();
00913 $descTitle->purgeSquid();
00914 } else {
00915
00916
00917 $article->doEdit( $pageText, $comment, EDIT_NEW | EDIT_SUPPRESS_RC );
00918 }
00919
00920 # Hooks, hooks, the magic of hooks...
00921 wfRunHooks( 'FileUpload', array( $this ) );
00922
00923 # Commit the transaction now, in case something goes wrong later
00924 # The most important thing is that files don't get lost, especially archives
00925 $dbw->immediateCommit();
00926
00927 # Invalidate cache for all pages using this file
00928 $update = new HTMLCacheUpdate( $this->getTitle(), 'imagelinks' );
00929 $update->doUpdate();
00930 # Invalidate cache for all pages that redirects on this page
00931 $redirs = $this->getTitle()->getRedirectsHere();
00932 foreach( $redirs as $redir ) {
00933 $update = new HTMLCacheUpdate( $redir, 'imagelinks' );
00934 $update->doUpdate();
00935 }
00936
00937 return true;
00938 }
00939
00956 function publish( $srcPath, $flags = 0 ) {
00957 $this->lock();
00958 $dstRel = $this->getRel();
00959 $archiveName = gmdate( 'YmdHis' ) . '!'. $this->getName();
00960 $archiveRel = 'archive/' . $this->getHashPath() . $archiveName;
00961 $flags = $flags & File::DELETE_SOURCE ? LocalRepo::DELETE_SOURCE : 0;
00962 $status = $this->repo->publish( $srcPath, $dstRel, $archiveRel, $flags );
00963 if ( $status->value == 'new' ) {
00964 $status->value = '';
00965 } else {
00966 $status->value = $archiveName;
00967 }
00968 $this->unlock();
00969 return $status;
00970 }
00971
00989 function move( $target ) {
00990 wfDebugLog( 'imagemove', "Got request to move {$this->name} to " . $target->getText() );
00991 $this->lock();
00992 $batch = new LocalFileMoveBatch( $this, $target );
00993 $batch->addCurrent();
00994 $batch->addOlds();
00995
00996 $status = $batch->execute();
00997 wfDebugLog( 'imagemove', "Finished moving {$this->name}" );
00998 $this->purgeEverything();
00999 $this->unlock();
01000
01001 if ( $status->isOk() ) {
01002
01003 $this->title = $target;
01004
01005 unset( $this->name );
01006 unset( $this->hashPath );
01007
01008 $this->purgeEverything();
01009 }
01010
01011 return $status;
01012 }
01013
01026 function delete( $reason, $suppress = false ) {
01027 $this->lock();
01028 $batch = new LocalFileDeleteBatch( $this, $reason, $suppress );
01029 $batch->addCurrent();
01030
01031 # Get old version relative paths
01032 $dbw = $this->repo->getMasterDB();
01033 $result = $dbw->select( 'oldimage',
01034 array( 'oi_archive_name' ),
01035 array( 'oi_name' => $this->getName() ) );
01036 while ( $row = $dbw->fetchObject( $result ) ) {
01037 $batch->addOld( $row->oi_archive_name );
01038 }
01039 $status = $batch->execute();
01040
01041 if ( $status->ok ) {
01042
01043 $site_stats = $dbw->tableName( 'site_stats' );
01044 $dbw->query( "UPDATE $site_stats SET ss_images=ss_images-1", __METHOD__ );
01045 $this->purgeEverything();
01046 }
01047
01048 $this->unlock();
01049 return $status;
01050 }
01051
01065 function deleteOld( $archiveName, $reason, $suppress=false ) {
01066 $this->lock();
01067 $batch = new LocalFileDeleteBatch( $this, $reason, $suppress );
01068 $batch->addOld( $archiveName );
01069 $status = $batch->execute();
01070 $this->unlock();
01071 if ( $status->ok ) {
01072 $this->purgeDescription();
01073 $this->purgeHistory();
01074 }
01075 return $status;
01076 }
01077
01089 function restore( $versions = array(), $unsuppress = false ) {
01090 $batch = new LocalFileRestoreBatch( $this, $unsuppress );
01091 if ( !$versions ) {
01092 $batch->addAll();
01093 } else {
01094 $batch->addIds( $versions );
01095 }
01096 $status = $batch->execute();
01097 if ( !$status->ok ) {
01098 return $status;
01099 }
01100
01101 $cleanupStatus = $batch->cleanup();
01102 $cleanupStatus->successCount = 0;
01103 $cleanupStatus->failCount = 0;
01104 $status->merge( $cleanupStatus );
01105 return $status;
01106 }
01107
01116 function getDescriptionUrl() {
01117 return $this->title->getLocalUrl();
01118 }
01119
01125 function getDescriptionText() {
01126 global $wgParser;
01127 $revision = Revision::newFromTitle( $this->title );
01128 if ( !$revision ) return false;
01129 $text = $revision->getText();
01130 if ( !$text ) return false;
01131 $pout = $wgParser->parse( $text, $this->title, new ParserOptions() );
01132 return $pout->getText();
01133 }
01134
01135 function getDescription() {
01136 $this->load();
01137 return $this->description;
01138 }
01139
01140 function getTimestamp() {
01141 $this->load();
01142 return $this->timestamp;
01143 }
01144
01145 function getSha1() {
01146 $this->load();
01147
01148 if ( $this->sha1 == '' && $this->fileExists ) {
01149 $this->sha1 = File::sha1Base36( $this->getPath() );
01150 if ( !wfReadOnly() && strval( $this->sha1 ) != '' ) {
01151 $dbw = $this->repo->getMasterDB();
01152 $dbw->update( 'image',
01153 array( 'img_sha1' => $this->sha1 ),
01154 array( 'img_name' => $this->getName() ),
01155 __METHOD__ );
01156 $this->saveToCache();
01157 }
01158 }
01159
01160 return $this->sha1;
01161 }
01162
01168 function lock() {
01169 $dbw = $this->repo->getMasterDB();
01170 if ( !$this->locked ) {
01171 $dbw->begin();
01172 $this->locked++;
01173 }
01174 return $dbw->selectField( 'image', '1', array( 'img_name' => $this->getName() ), __METHOD__ );
01175 }
01176
01181 function unlock() {
01182 if ( $this->locked ) {
01183 --$this->locked;
01184 if ( !$this->locked ) {
01185 $dbw = $this->repo->getMasterDB();
01186 $dbw->commit();
01187 }
01188 }
01189 }
01190
01194 function unlockAndRollback() {
01195 $this->locked = false;
01196 $dbw = $this->repo->getMasterDB();
01197 $dbw->rollback();
01198 }
01199 }
01200
01201 #------------------------------------------------------------------------------
01202
01207 class LocalFileDeleteBatch {
01208 var $file, $reason, $srcRels = array(), $archiveUrls = array(), $deletionBatch, $suppress;
01209 var $status;
01210
01211 function __construct( File $file, $reason = '', $suppress = false ) {
01212 $this->file = $file;
01213 $this->reason = $reason;
01214 $this->suppress = $suppress;
01215 $this->status = $file->repo->newGood();
01216 }
01217
01218 function addCurrent() {
01219 $this->srcRels['.'] = $this->file->getRel();
01220 }
01221
01222 function addOld( $oldName ) {
01223 $this->srcRels[$oldName] = $this->file->getArchiveRel( $oldName );
01224 $this->archiveUrls[] = $this->file->getArchiveUrl( $oldName );
01225 }
01226
01227 function getOldRels() {
01228 if ( !isset( $this->srcRels['.'] ) ) {
01229 $oldRels =& $this->srcRels;
01230 $deleteCurrent = false;
01231 } else {
01232 $oldRels = $this->srcRels;
01233 unset( $oldRels['.'] );
01234 $deleteCurrent = true;
01235 }
01236 return array( $oldRels, $deleteCurrent );
01237 }
01238
01239 function getHashes() {
01240 $hashes = array();
01241 list( $oldRels, $deleteCurrent ) = $this->getOldRels();
01242 if ( $deleteCurrent ) {
01243 $hashes['.'] = $this->file->getSha1();
01244 }
01245 if ( count( $oldRels ) ) {
01246 $dbw = $this->file->repo->getMasterDB();
01247 $res = $dbw->select( 'oldimage', array( 'oi_archive_name', 'oi_sha1' ),
01248 'oi_archive_name IN(' . $dbw->makeList( array_keys( $oldRels ) ) . ')',
01249 __METHOD__ );
01250 while ( $row = $dbw->fetchObject( $res ) ) {
01251 if ( rtrim( $row->oi_sha1, "\0" ) === '' ) {
01252
01253 $oldUrl = $this->file->getArchiveVirtualUrl( $row->oi_archive_name );
01254 $props = $this->file->repo->getFileProps( $oldUrl );
01255 if ( $props['fileExists'] ) {
01256
01257 $dbw->update( 'oldimage',
01258 array( 'oi_sha1' => $props['sha1'] ),
01259 array( 'oi_name' => $this->file->getName(), 'oi_archive_name' => $row->oi_archive_name ),
01260 __METHOD__ );
01261 $hashes[$row->oi_archive_name] = $props['sha1'];
01262 } else {
01263 $hashes[$row->oi_archive_name] = false;
01264 }
01265 } else {
01266 $hashes[$row->oi_archive_name] = $row->oi_sha1;
01267 }
01268 }
01269 }
01270 $missing = array_diff_key( $this->srcRels, $hashes );
01271 foreach ( $missing as $name => $rel ) {
01272 $this->status->error( 'filedelete-old-unregistered', $name );
01273 }
01274 foreach ( $hashes as $name => $hash ) {
01275 if ( !$hash ) {
01276 $this->status->error( 'filedelete-missing', $this->srcRels[$name] );
01277 unset( $hashes[$name] );
01278 }
01279 }
01280
01281 return $hashes;
01282 }
01283
01284 function doDBInserts() {
01285 global $wgUser;
01286 $dbw = $this->file->repo->getMasterDB();
01287 $encTimestamp = $dbw->addQuotes( $dbw->timestamp() );
01288 $encUserId = $dbw->addQuotes( $wgUser->getId() );
01289 $encReason = $dbw->addQuotes( $this->reason );
01290 $encGroup = $dbw->addQuotes( 'deleted' );
01291 $ext = $this->file->getExtension();
01292 $dotExt = $ext === '' ? '' : ".$ext";
01293 $encExt = $dbw->addQuotes( $dotExt );
01294 list( $oldRels, $deleteCurrent ) = $this->getOldRels();
01295
01296
01297 if ( $this->suppress ) {
01298 $bitfield = 0;
01299
01300 $bitfield |= Revision::DELETED_TEXT;
01301 $bitfield |= Revision::DELETED_COMMENT;
01302 $bitfield |= Revision::DELETED_USER;
01303 $bitfield |= Revision::DELETED_RESTRICTED;
01304 } else {
01305 $bitfield = 'oi_deleted';
01306 }
01307
01308 if ( $deleteCurrent ) {
01309 $concat = $dbw->buildConcat( array( "img_sha1", $encExt ) );
01310 $where = array( 'img_name' => $this->file->getName() );
01311 $dbw->insertSelect( 'filearchive', 'image',
01312 array(
01313 'fa_storage_group' => $encGroup,
01314 'fa_storage_key' => "CASE WHEN img_sha1='' THEN '' ELSE $concat END",
01315 'fa_deleted_user' => $encUserId,
01316 'fa_deleted_timestamp' => $encTimestamp,
01317 'fa_deleted_reason' => $encReason,
01318 'fa_deleted' => $this->suppress ? $bitfield : 0,
01319
01320 'fa_name' => 'img_name',
01321 'fa_archive_name' => 'NULL',
01322 'fa_size' => 'img_size',
01323 'fa_width' => 'img_width',
01324 'fa_height' => 'img_height',
01325 'fa_metadata' => 'img_metadata',
01326 'fa_bits' => 'img_bits',
01327 'fa_media_type' => 'img_media_type',
01328 'fa_major_mime' => 'img_major_mime',
01329 'fa_minor_mime' => 'img_minor_mime',
01330 'fa_description' => 'img_description',
01331 'fa_user' => 'img_user',
01332 'fa_user_text' => 'img_user_text',
01333 'fa_timestamp' => 'img_timestamp'
01334 ), $where, __METHOD__ );
01335 }
01336
01337 if ( count( $oldRels ) ) {
01338 $concat = $dbw->buildConcat( array( "oi_sha1", $encExt ) );
01339 $where = array(
01340 'oi_name' => $this->file->getName(),
01341 'oi_archive_name IN (' . $dbw->makeList( array_keys( $oldRels ) ) . ')' );
01342 $dbw->insertSelect( 'filearchive', 'oldimage',
01343 array(
01344 'fa_storage_group' => $encGroup,
01345 'fa_storage_key' => "CASE WHEN oi_sha1='' THEN '' ELSE $concat END",
01346 'fa_deleted_user' => $encUserId,
01347 'fa_deleted_timestamp' => $encTimestamp,
01348 'fa_deleted_reason' => $encReason,
01349 'fa_deleted' => $this->suppress ? $bitfield : 'oi_deleted',
01350
01351 'fa_name' => 'oi_name',
01352 'fa_archive_name' => 'oi_archive_name',
01353 'fa_size' => 'oi_size',
01354 'fa_width' => 'oi_width',
01355 'fa_height' => 'oi_height',
01356 'fa_metadata' => 'oi_metadata',
01357 'fa_bits' => 'oi_bits',
01358 'fa_media_type' => 'oi_media_type',
01359 'fa_major_mime' => 'oi_major_mime',
01360 'fa_minor_mime' => 'oi_minor_mime',
01361 'fa_description' => 'oi_description',
01362 'fa_user' => 'oi_user',
01363 'fa_user_text' => 'oi_user_text',
01364 'fa_timestamp' => 'oi_timestamp',
01365 'fa_deleted' => $bitfield
01366 ), $where, __METHOD__ );
01367 }
01368 }
01369
01370 function doDBDeletes() {
01371 $dbw = $this->file->repo->getMasterDB();
01372 list( $oldRels, $deleteCurrent ) = $this->getOldRels();
01373 if ( count( $oldRels ) ) {
01374 $dbw->delete( 'oldimage',
01375 array(
01376 'oi_name' => $this->file->getName(),
01377 'oi_archive_name' => array_keys( $oldRels )
01378 ), __METHOD__ );
01379 }
01380 if ( $deleteCurrent ) {
01381 $dbw->delete( 'image', array( 'img_name' => $this->file->getName() ), __METHOD__ );
01382 }
01383 }
01384
01388 function execute() {
01389 global $wgUser, $wgUseSquid;
01390 wfProfileIn( __METHOD__ );
01391
01392 $this->file->lock();
01393
01394 $privateFiles = array();
01395 list( $oldRels, $deleteCurrent ) = $this->getOldRels();
01396 $dbw = $this->file->repo->getMasterDB();
01397 if( !empty( $oldRels ) ) {
01398 $res = $dbw->select( 'oldimage',
01399 array( 'oi_archive_name' ),
01400 array( 'oi_name' => $this->file->getName(),
01401 'oi_archive_name IN (' . $dbw->makeList( array_keys($oldRels) ) . ')',
01402 'oi_deleted & ' . File::DELETED_FILE => File::DELETED_FILE ),
01403 __METHOD__ );
01404 while( $row = $dbw->fetchObject( $res ) ) {
01405 $privateFiles[$row->oi_archive_name] = 1;
01406 }
01407 }
01408
01409 $hashes = $this->getHashes();
01410 $this->deletionBatch = array();
01411 $ext = $this->file->getExtension();
01412 $dotExt = $ext === '' ? '' : ".$ext";
01413 foreach ( $this->srcRels as $name => $srcRel ) {
01414
01415
01416 if ( isset($hashes[$name]) && !array_key_exists($name,$privateFiles) ) {
01417 $hash = $hashes[$name];
01418 $key = $hash . $dotExt;
01419 $dstRel = $this->file->repo->getDeletedHashPath( $key ) . $key;
01420 $this->deletionBatch[$name] = array( $srcRel, $dstRel );
01421 }
01422 }
01423
01424
01425
01426
01427
01428
01429
01430 $this->doDBInserts();
01431
01432
01433 $status = $this->file->repo->deleteBatch( $this->deletionBatch );
01434 if ( !$status->isGood() ) {
01435 $this->status->merge( $status );
01436 }
01437
01438 if ( !$this->status->ok ) {
01439
01440
01441
01442 $this->file->unlockAndRollback();
01443 return $this->status;
01444 }
01445
01446
01447 if ( $wgUseSquid ) {
01448 $urls = array();
01449 foreach ( $this->srcRels as $srcRel ) {
01450 $urlRel = str_replace( '%2F', '/', rawurlencode( $srcRel ) );
01451 $urls[] = $this->file->repo->getZoneUrl( 'public' ) . '/' . $urlRel;
01452 }
01453 SquidUpdate::purge( $urls );
01454 }
01455
01456
01457 $this->doDBDeletes();
01458
01459
01460 $this->file->unlock();
01461 wfProfileOut( __METHOD__ );
01462 return $this->status;
01463 }
01464 }
01465
01466 #------------------------------------------------------------------------------
01467
01472 class LocalFileRestoreBatch {
01473 var $file, $cleanupBatch, $ids, $all, $unsuppress = false;
01474
01475 function __construct( File $file, $unsuppress = false ) {
01476 $this->file = $file;
01477 $this->cleanupBatch = $this->ids = array();
01478 $this->ids = array();
01479 $this->unsuppress = $unsuppress;
01480 }
01481
01485 function addId( $fa_id ) {
01486 $this->ids[] = $fa_id;
01487 }
01488
01492 function addIds( $ids ) {
01493 $this->ids = array_merge( $this->ids, $ids );
01494 }
01495
01499 function addAll() {
01500 $this->all = true;
01501 }
01502
01510 function execute() {
01511 global $wgUser, $wgLang;
01512 if ( !$this->all && !$this->ids ) {
01513
01514 return $this->file->repo->newGood();
01515 }
01516
01517 $exists = $this->file->lock();
01518 $dbw = $this->file->repo->getMasterDB();
01519 $status = $this->file->repo->newGood();
01520
01521
01522
01523 $conditions = array( 'fa_name' => $this->file->getName() );
01524 if( !$this->all ) {
01525 $conditions[] = 'fa_id IN (' . $dbw->makeList( $this->ids ) . ')';
01526 }
01527
01528 $result = $dbw->select( 'filearchive', '*',
01529 $conditions,
01530 __METHOD__,
01531 array( 'ORDER BY' => 'fa_timestamp DESC' )
01532 );
01533
01534 $idsPresent = array();
01535 $storeBatch = array();
01536 $insertBatch = array();
01537 $insertCurrent = false;
01538 $deleteIds = array();
01539 $first = true;
01540 $archiveNames = array();
01541 while( $row = $dbw->fetchObject( $result ) ) {
01542 $idsPresent[] = $row->fa_id;
01543
01544 if ( $row->fa_name != $this->file->getName() ) {
01545 $status->error( 'undelete-filename-mismatch', $wgLang->timeanddate( $row->fa_timestamp ) );
01546 $status->failCount++;
01547 continue;
01548 }
01549 if ( $row->fa_storage_key == '' ) {
01550
01551 $status->error( 'undelete-bad-store-key', $wgLang->timeanddate( $row->fa_timestamp ) );
01552 $status->failCount++;
01553 continue;
01554 }
01555
01556 $deletedRel = $this->file->repo->getDeletedHashPath( $row->fa_storage_key ) . $row->fa_storage_key;
01557 $deletedUrl = $this->file->repo->getVirtualUrl() . '/deleted/' . $deletedRel;
01558
01559 $sha1 = substr( $row->fa_storage_key, 0, strcspn( $row->fa_storage_key, '.' ) );
01560 # Fix leading zero
01561 if ( strlen( $sha1 ) == 32 && $sha1[0] == '0' ) {
01562 $sha1 = substr( $sha1, 1 );
01563 }
01564
01565 if( is_null( $row->fa_major_mime ) || $row->fa_major_mime == 'unknown'
01566 || is_null( $row->fa_minor_mime ) || $row->fa_minor_mime == 'unknown'
01567 || is_null( $row->fa_media_type ) || $row->fa_media_type == 'UNKNOWN'
01568 || is_null( $row->fa_metadata ) ) {
01569
01570
01571 $props = RepoGroup::singleton()->getFileProps( $deletedUrl );
01572 } else {
01573 $props = array(
01574 'minor_mime' => $row->fa_minor_mime,
01575 'major_mime' => $row->fa_major_mime,
01576 'media_type' => $row->fa_media_type,
01577 'metadata' => $row->fa_metadata
01578 );
01579 }
01580
01581 if ( $first && !$exists ) {
01582
01583 $destRel = $this->file->getRel();
01584 $insertCurrent = array(
01585 'img_name' => $row->fa_name,
01586 'img_size' => $row->fa_size,
01587 'img_width' => $row->fa_width,
01588 'img_height' => $row->fa_height,
01589 'img_metadata' => $props['metadata'],
01590 'img_bits' => $row->fa_bits,
01591 'img_media_type' => $props['media_type'],
01592 'img_major_mime' => $props['major_mime'],
01593 'img_minor_mime' => $props['minor_mime'],
01594 'img_description' => $row->fa_description,
01595 'img_user' => $row->fa_user,
01596 'img_user_text' => $row->fa_user_text,
01597 'img_timestamp' => $row->fa_timestamp,
01598 'img_sha1' => $sha1
01599 );
01600
01601 if( !$this->unsuppress && $row->fa_deleted ) {
01602 $storeBatch[] = array( $deletedUrl, 'public', $destRel );
01603 $this->cleanupBatch[] = $row->fa_storage_key;
01604 }
01605 } else {
01606 $archiveName = $row->fa_archive_name;
01607 if( $archiveName == '' ) {
01608
01609
01610
01611 $timestamp = wfTimestamp( TS_UNIX, $row->fa_deleted_timestamp );
01612 do {
01613 $archiveName = wfTimestamp( TS_MW, $timestamp ) . '!' . $row->fa_name;
01614 $timestamp++;
01615 } while ( isset( $archiveNames[$archiveName] ) );
01616 }
01617 $archiveNames[$archiveName] = true;
01618 $destRel = $this->file->getArchiveRel( $archiveName );
01619 $insertBatch[] = array(
01620 'oi_name' => $row->fa_name,
01621 'oi_archive_name' => $archiveName,
01622 'oi_size' => $row->fa_size,
01623 'oi_width' => $row->fa_width,
01624 'oi_height' => $row->fa_height,
01625 'oi_bits' => $row->fa_bits,
01626 'oi_description' => $row->fa_description,
01627 'oi_user' => $row->fa_user,
01628 'oi_user_text' => $row->fa_user_text,
01629 'oi_timestamp' => $row->fa_timestamp,
01630 'oi_metadata' => $props['metadata'],
01631 'oi_media_type' => $props['media_type'],
01632 'oi_major_mime' => $props['major_mime'],
01633 'oi_minor_mime' => $props['minor_mime'],
01634 'oi_deleted' => $this->unsuppress ? 0 : $row->fa_deleted,
01635 'oi_sha1' => $sha1 );
01636 }
01637
01638 $deleteIds[] = $row->fa_id;
01639 if( !$this->unsuppress && $row->fa_deleted & File::DELETED_FILE ) {
01640
01641 $status->successCount++;
01642 } else {
01643 $storeBatch[] = array( $deletedUrl, 'public', $destRel );
01644 $this->cleanupBatch[] = $row->fa_storage_key;
01645 }
01646 $first = false;
01647 }
01648 unset( $result );
01649
01650
01651 $missingIds = array_diff( $this->ids, $idsPresent );
01652 foreach ( $missingIds as $id ) {
01653 $status->error( 'undelete-missing-filearchive', $id );
01654 }
01655
01656
01657
01658 $storeStatus = $this->file->repo->storeBatch( $storeBatch, FileRepo::OVERWRITE_SAME );
01659 $status->merge( $storeStatus );
01660
01661 if ( !$status->ok ) {
01662
01663
01664 $this->file->unlock();
01665 return $status;
01666 }
01667
01668
01669
01670
01671
01672
01673
01674 if ( $insertCurrent ) {
01675 $dbw->insert( 'image', $insertCurrent, __METHOD__ );
01676 }
01677 if ( $insertBatch ) {
01678 $dbw->insert( 'oldimage', $insertBatch, __METHOD__ );
01679 }
01680 if ( $deleteIds ) {
01681 $dbw->delete( 'filearchive',
01682 array( 'fa_id IN (' . $dbw->makeList( $deleteIds ) . ')' ),
01683 __METHOD__ );
01684 }
01685
01686 if( $status->successCount > 0 ) {
01687 if( !$exists ) {
01688 wfDebug( __METHOD__." restored {$status->successCount} items, creating a new current\n" );
01689
01690
01691 $site_stats = $dbw->tableName( 'site_stats' );
01692 $dbw->query( "UPDATE $site_stats SET ss_images=ss_images+1", __METHOD__ );
01693
01694 $this->file->purgeEverything();
01695 } else {
01696 wfDebug( __METHOD__." restored {$status->successCount} as archived versions\n" );
01697 $this->file->purgeDescription();
01698 $this->file->purgeHistory();
01699 }
01700 }
01701 $this->file->unlock();
01702 return $status;
01703 }
01704
01709 function cleanup() {
01710 if ( !$this->cleanupBatch ) {
01711 return $this->file->repo->newGood();
01712 }
01713 $status = $this->file->repo->cleanupDeletedBatch( $this->cleanupBatch );
01714 return $status;
01715 }
01716 }
01717
01718 #------------------------------------------------------------------------------
01719
01724 class LocalFileMoveBatch {
01725 var $file, $cur, $olds, $oldCount, $archive, $target, $db;
01726
01727 function __construct( File $file, Title $target ) {
01728 $this->file = $file;
01729 $this->target = $target;
01730 $this->oldHash = $this->file->repo->getHashPath( $this->file->getName() );
01731 $this->newHash = $this->file->repo->getHashPath( $this->target->getDBKey() );
01732 $this->oldName = $this->file->getName();
01733 $this->newName = $this->file->repo->getNameFromTitle( $this->target );
01734 $this->oldRel = $this->oldHash . $this->oldName;
01735 $this->newRel = $this->newHash . $this->newName;
01736 $this->db = $file->repo->getMasterDb();
01737 }
01738
01739
01740
01741
01742 function addCurrent() {
01743 $this->cur = array( $this->oldRel, $this->newRel );
01744 }
01745
01746
01747
01748
01749 function addOlds() {
01750 $archiveBase = 'archive';
01751 $this->olds = array();
01752 $this->oldCount = 0;
01753
01754 $result = $this->db->select( 'oldimage',
01755 array( 'oi_archive_name', 'oi_deleted' ),
01756 array( 'oi_name' => $this->oldName ),
01757 __METHOD__
01758 );
01759 while( $row = $this->db->fetchObject( $result ) ) {
01760 $oldName = $row->oi_archive_name;
01761 $bits = explode( '!', $oldName, 2 );
01762 if( count( $bits ) != 2 ) {
01763 wfDebug( "Invalid old file name: $oldName \n" );
01764 continue;
01765 }
01766 list( $timestamp, $filename ) = $bits;
01767 if( $this->oldName != $filename ) {
01768 wfDebug( "Invalid old file name: $oldName \n" );
01769 continue;
01770 }
01771 $this->oldCount++;
01772
01773 if( $row->oi_deleted & File::DELETED_FILE ) {
01774 continue;
01775 }
01776 $this->olds[] = array(
01777 "{$archiveBase}/{$this->oldHash}{$oldName}",
01778 "{$archiveBase}/{$this->newHash}{$timestamp}!{$this->newName}"
01779 );
01780 }
01781 $this->db->freeResult( $result );
01782 }
01783
01784
01785
01786
01787 function execute() {
01788 $repo = $this->file->repo;
01789 $status = $repo->newGood();
01790 $triplets = $this->getMoveTriplets();
01791
01792 $statusDb = $this->doDBUpdates();
01793 wfDebugLog( 'imagemove', "Renamed {$this->file->name} in database: {$statusDb->successCount} successes, {$statusDb->failCount} failures" );
01794 $statusMove = $repo->storeBatch( $triplets, FSRepo::DELETE_SOURCE );
01795 wfDebugLog( 'imagemove', "Moved files for {$this->file->name}: {$statusMove->successCount} successes, {$statusMove->failCount} failures" );
01796 if( !$statusMove->isOk() ) {
01797 wfDebugLog( 'imagemove', "Error in moving files: " . $statusMove->getWikiText() );
01798 $this->db->rollback();
01799 }
01800 $status->merge( $statusDb );
01801 $status->merge( $statusMove );
01802 return $status;
01803 }
01804
01805
01806
01807
01808
01809 function doDBUpdates() {
01810 $repo = $this->file->repo;
01811 $status = $repo->newGood();
01812 $dbw = $this->db;
01813
01814
01815 $dbw->update(
01816 'image',
01817 array( 'img_name' => $this->newName ),
01818 array( 'img_name' => $this->oldName ),
01819 __METHOD__
01820 );
01821 if( $dbw->affectedRows() ) {
01822 $status->successCount++;
01823 } else {
01824 $status->failCount++;
01825 }
01826
01827
01828 $dbw->update(
01829 'oldimage',
01830 array(
01831 'oi_name' => $this->newName,
01832 'oi_archive_name = ' . $dbw->strreplace( 'oi_archive_name', $dbw->addQuotes($this->oldName), $dbw->addQuotes($this->newName) ),
01833 ),
01834 array( 'oi_name' => $this->oldName ),
01835 __METHOD__
01836 );
01837 $affected = $dbw->affectedRows();
01838 $total = $this->oldCount;
01839 $status->successCount += $affected;
01840 $status->failCount += $total - $affected;
01841
01842 return $status;
01843 }
01844
01845
01846
01847
01848 function getMoveTriplets() {
01849 $moves = array_merge( array( $this->cur ), $this->olds );
01850 $triplets = array();
01851 foreach( $moves as $move ) {
01852
01853 $srcUrl = $this->file->repo->getVirtualUrl() . '/public/' . rawurlencode( $move[0] );
01854 $triplets[] = array( $srcUrl, 'public', $move[1] );
01855 wfDebugLog( 'imagemove', "Generated move triplet for {$this->file->name}: {$srcUrl} :: public :: {$move[1]}" );
01856 }
01857 return $triplets;
01858 }
01859 }