62 private const VERSION = 13;
64 private const CACHE_FIELD_MAX_LEN = 1000;
67 private const MDS_EMPTY =
'empty';
70 private const MDS_LEGACY =
'legacy';
73 private const MDS_PHP =
'php';
76 private const MDS_JSON =
'json';
79 private const MAX_PAGE_RENDER_JOBS = 50;
103 protected $metadataArray = [];
114 protected $metadataBlobs = [];
122 protected $unloadedMetadataBlobs = [];
128 protected $dataLoaded =
false;
131 protected $extraDataLoaded =
false;
137 protected $repoClass = LocalRepo::class;
140 private $historyLine = 0;
143 private $historyRes =
null;
158 private $description;
161 private $descriptionTouched;
173 private $lockedOwnTrx;
179 private $metadataStorageHelper;
182 private const LOAD_ALL = 16;
184 private const ATOMIC_SECTION_LOCK =
'LocalFile::lockingTransaction';
218 $file->loadFromRow( $row );
234 public static function newFromKey( $sha1, $repo, $timestamp =
false ) {
237 $conds = [
'img_sha1' => $sha1 ];
239 $conds[
'img_timestamp'] =
$dbr->timestamp( $timestamp );
242 $fileQuery = static::getQueryInfo();
243 $row =
$dbr->selectRow(
244 $fileQuery[
'tables'], $fileQuery[
'fields'], $conds, __METHOD__, [], $fileQuery[
'joins']
247 return static::newFromRow( $row,
$repo );
273 $commentQuery = MediaWikiServices::getInstance()->getCommentStore()->getJoin(
'img_description' );
277 'image_actor' =>
'actor'
278 ] + $commentQuery[
'tables'],
292 'img_user' =>
'image_actor.actor_user',
293 'img_user_text' =>
'image_actor.actor_name',
294 ] + $commentQuery[
'fields'],
296 'image_actor' => [
'JOIN',
'actor_id=img_actor' ]
297 ] + $commentQuery[
'joins'],
300 if ( in_array(
'omit-nonlazy', $options,
true ) ) {
304 if ( !in_array(
'omit-lazy', $options,
true ) ) {
307 $ret[
'fields'][] =
'img_metadata';
342 return $this->repo->getSharedCacheKey(
'file', sha1( $this->
getName() ) );
348 private function loadFromCache() {
349 $this->dataLoaded =
false;
350 $this->extraDataLoaded =
false;
352 $key = $this->getCacheKey();
354 $this->loadFromDB( self::READ_NORMAL );
359 $cache = MediaWikiServices::getInstance()->getMainWANObjectCache();
360 $cachedValues = $cache->getWithSetCallback(
363 function ( $oldValue, &$ttl, array &$setOpts ) use ( $cache ) {
364 $setOpts += Database::getCacheSetOptions( $this->repo->getReplicaDB() );
370 $cacheVal[
'fileExists'] = $this->fileExists;
371 if ( $this->fileExists ) {
372 foreach ( $fields as $field ) {
373 $cacheVal[$field] = $this->$field;
377 $cacheVal[
'user'] = $this->user->getId();
378 $cacheVal[
'user_text'] = $this->user->getName();
382 if ( $this->metadataBlobs ) {
383 $cacheVal[
'metadata'] = array_diff_key(
384 $this->metadataArray, $this->metadataBlobs );
386 $cacheVal[
'metadataBlobs'] = $this->metadataBlobs;
388 $cacheVal[
'metadata'] = $this->metadataArray;
395 if ( isset( $cacheVal[$field] )
396 && strlen( serialize( $cacheVal[$field] ) ) > 100 * 1024
398 unset( $cacheVal[$field] );
399 if ( $field ===
'metadata' ) {
400 unset( $cacheVal[
'metadataBlobs'] );
405 if ( $this->fileExists ) {
406 $ttl = $cache->adaptiveTTL( (
int)
wfTimestamp( TS_UNIX, $this->timestamp ), $ttl );
408 $ttl = $cache::TTL_DAY;
413 [
'version' => self::VERSION ]
416 $this->fileExists = $cachedValues[
'fileExists'];
417 if ( $this->fileExists ) {
421 $this->dataLoaded =
true;
422 $this->extraDataLoaded =
true;
424 $this->extraDataLoaded = $this->extraDataLoaded && isset( $cachedValues[$field] );
437 $this->repo->getPrimaryDB()->onTransactionPreCommitOrIdle(
438 static function () use ( $key ) {
439 MediaWikiServices::getInstance()->getMainWANObjectCache()->delete( $key );
465 if ( $prefix !==
'' ) {
466 throw new InvalidArgumentException(
467 __METHOD__ .
' with a non-empty prefix is no longer supported.'
475 return [
'size',
'width',
'height',
'bits',
'media_type',
476 'major_mime',
'minor_mime',
'timestamp',
'sha1',
'description' ];
487 if ( $prefix !==
'' ) {
488 throw new InvalidArgumentException(
489 __METHOD__ .
' with a non-empty prefix is no longer supported.'
494 return [
'metadata' ];
503 $fname = static::class .
'::' . __FUNCTION__;
505 # Unconditionally set loaded=true, we don't want the accessors constantly rechecking
506 $this->dataLoaded =
true;
507 $this->extraDataLoaded =
true;
509 $dbr = ( $flags & self::READ_LATEST )
510 ? $this->repo->getPrimaryDB()
511 : $this->repo->getReplicaDB();
513 $fileQuery = static::getQueryInfo();
514 $row =
$dbr->selectRow(
515 $fileQuery[
'tables'],
516 $fileQuery[
'fields'],
517 [
'img_name' => $this->
getName() ],
526 $this->fileExists =
false;
536 if ( !$this->title ) {
540 $fname = static::class .
'::' . __FUNCTION__;
542 # Unconditionally set loaded=true, we don't want the accessors constantly rechecking
543 $this->extraDataLoaded =
true;
545 $db = $this->repo->getReplicaDB();
546 $fieldMap = $this->loadExtraFieldsWithTimestamp( $db, $fname );
548 $db = $this->repo->getPrimaryDB();
549 $fieldMap = $this->loadExtraFieldsWithTimestamp( $db, $fname );
553 if ( isset( $fieldMap[
'metadata'] ) ) {
557 throw new MWException(
"Could not find data for image '{$this->getName()}'." );
566 private function loadExtraFieldsWithTimestamp(
$dbr, $fname ) {
569 $fileQuery = self::getQueryInfo( [
'omit-nonlazy' ] );
570 $row =
$dbr->selectRow(
571 $fileQuery[
'tables'],
572 $fileQuery[
'fields'],
574 'img_name' => $this->getName(),
575 'img_timestamp' =>
$dbr->timestamp( $this->getTimestamp() ),
582 $fieldMap = $this->unprefixRow( $row,
'img_' );
584 # File may have been uploaded over in the meantime; check the old versions
586 $row =
$dbr->selectRow(
587 $fileQuery[
'tables'],
588 $fileQuery[
'fields'],
591 'oi_timestamp' =>
$dbr->timestamp( $this->getTimestamp() ),
612 $array = (array)$row;
613 $prefixLength = strlen( $prefix );
616 if ( substr( array_key_first( $array ), 0, $prefixLength ) !== $prefix ) {
617 throw new MWException( __METHOD__ .
': incorrect $prefix parameter' );
621 foreach ( $array as
$name => $value ) {
622 $decoded[substr(
$name, $prefixLength )] = $value;
644 $this->dataLoaded =
true;
648 $this->name = $unprefixed[
'name'];
649 $this->media_type = $unprefixed[
'media_type'];
651 $services = MediaWikiServices::getInstance();
652 $this->description = $services->getCommentStore()
653 ->getComment(
"{$prefix}description", $row )->text;
655 $this->user = $services->getUserFactory()->newFromAnyId(
656 $unprefixed[
'user'] ??
null,
657 $unprefixed[
'user_text'] ??
null,
658 $unprefixed[
'actor'] ??
null
661 $this->timestamp =
wfTimestamp( TS_MW, $unprefixed[
'timestamp'] );
664 $this->repo->getReplicaDB(), $unprefixed[
'metadata'] );
666 if ( empty( $unprefixed[
'major_mime'] ) ) {
667 $this->major_mime =
'unknown';
668 $this->minor_mime =
'unknown';
669 $this->mime =
'unknown/unknown';
671 if ( !$unprefixed[
'minor_mime'] ) {
672 $unprefixed[
'minor_mime'] =
'unknown';
674 $this->major_mime = $unprefixed[
'major_mime'];
675 $this->minor_mime = $unprefixed[
'minor_mime'];
676 $this->mime = $unprefixed[
'major_mime'] .
'/' . $unprefixed[
'minor_mime'];
680 $this->sha1 = rtrim( $unprefixed[
'sha1'],
"\0" );
686 $this->size = +$unprefixed[
'size'];
687 $this->width = +$unprefixed[
'width'];
688 $this->height = +$unprefixed[
'height'];
689 $this->bits = +$unprefixed[
'bits'];
692 $extraFields = array_diff(
693 array_keys( $unprefixed ),
695 'name',
'media_type',
'description_text',
'description_data',
696 'description_cid',
'user',
'user_text',
'actor',
'timestamp',
697 'metadata',
'major_mime',
'minor_mime',
'sha1',
'size',
'width',
701 if ( $extraFields ) {
703 'Passing extra fields (' .
704 implode(
', ', $extraFields )
705 .
') to ' . __METHOD__ .
' was deprecated in MediaWiki 1.37. ' .
706 'Property assignment will be removed in a later version.',
708 foreach ( $extraFields as $field ) {
709 $this->$field = $unprefixed[$field];
713 $this->fileExists =
true;
721 public function load( $flags = 0 ) {
722 if ( !$this->dataLoaded ) {
723 if ( $flags & self::READ_LATEST ) {
726 $this->loadFromCache();
730 if ( ( $flags & self::LOAD_ALL ) && !$this->extraDataLoaded ) {
741 if ( MediaWikiServices::getInstance()->getReadOnlyMode()->isReadOnly() || $this->upgrading ) {
746 $reserialize =
false;
747 if ( $this->media_type ===
null || $this->mime ==
'image/svg' ) {
756 && $this->repo->isMetadataUpdateEnabled()
759 } elseif ( $this->repo->isJsonMetadataEnabled()
760 && $this->repo->isMetadataReserializeEnabled()
762 if ( $this->repo->isSplitMetadataEnabled() && $this->isMetadataOversize() ) {
764 } elseif ( $this->metadataSerializationFormat !== self::MDS_EMPTY &&
765 $this->metadataSerializationFormat !== self::MDS_JSON ) {
772 if ( $upgrade || $reserialize ) {
773 $this->upgrading =
true;
775 DeferredUpdates::addCallableUpdate(
function () use ( $upgrade ) {
776 $this->upgrading =
false;
794 return $this->upgraded;
802 $dbw = $this->repo->getPrimaryDB();
806 $freshnessCondition = [
'img_timestamp' => $dbw->timestamp( $this->
getTimestamp() ) ];
810 # Don't destroy file info of missing files
811 if ( !$this->fileExists ) {
812 wfDebug( __METHOD__ .
": file does not exist, aborting" );
817 [ $major, $minor ] = self::splitMime( $this->mime );
819 wfDebug( __METHOD__ .
': upgrading ' . $this->
getName() .
" to the current schema" );
821 $dbw->update(
'image',
823 'img_size' => $this->size,
824 'img_width' => $this->width,
825 'img_height' => $this->height,
826 'img_bits' => $this->bits,
827 'img_media_type' => $this->media_type,
828 'img_major_mime' => $major,
829 'img_minor_mime' => $minor,
831 'img_sha1' => $this->sha1,
834 [
'img_name' => $this->
getName() ],
842 $this->upgraded =
true;
850 if ( MediaWikiServices::getInstance()->getReadOnlyMode()->isReadOnly() ) {
853 $dbw = $this->repo->getPrimaryDB();
858 'img_name' => $this->name,
859 'img_timestamp' => $dbw->timestamp( $this->timestamp ),
863 $this->upgraded =
true;
878 $this->dataLoaded =
true;
880 $fields[] =
'fileExists';
882 foreach ( $fields as $field ) {
883 if ( isset( $info[$field] ) ) {
884 $this->$field = $info[$field];
889 if ( isset( $info[
'user'] ) &&
890 isset( $info[
'user_text'] ) &&
891 $info[
'user_text'] !==
''
897 if ( isset( $info[
'major_mime'] ) ) {
898 $this->mime =
"{$info['major_mime']}/{$info['minor_mime']}";
899 } elseif ( isset( $info[
'mime'] ) ) {
900 $this->mime = $info[
'mime'];
901 [ $this->major_mime, $this->minor_mime ] = self::splitMime( $this->mime );
904 if ( isset( $info[
'metadata'] ) ) {
905 if ( is_string( $info[
'metadata'] ) ) {
907 } elseif ( is_array( $info[
'metadata'] ) ) {
908 $this->metadataArray = $info[
'metadata'];
909 if ( isset( $info[
'metadataBlobs'] ) ) {
910 $this->metadataBlobs = $info[
'metadataBlobs'];
911 $this->unloadedMetadataBlobs = array_diff_key(
912 $this->metadataBlobs,
916 $this->metadataBlobs = [];
917 $this->unloadedMetadataBlobs = [];
920 $logger = LoggerFactory::getInstance(
'LocalFile' );
921 $logger->warning( __METHOD__ .
' given invalid metadata of type ' .
922 gettype( $info[
'metadata'] ) );
923 $this->metadataArray = [];
925 $this->extraDataLoaded =
true;
945 if ( $this->missing ===
null ) {
946 $fileExists = $this->repo->fileExists( $this->
getVirtualUrl() );
947 $this->missing = !$fileExists;
950 return $this->missing;
975 return $dim[
'width'];
1008 return $dim[
'height'];
1015 return $this->height;
1027 if ( !$this->title ) {
1031 $pageId = $this->title->getArticleID();
1034 $url = $this->repo->makeUrl( [
'curid' => $pageId ] );
1035 if (
$url !==
false ) {
1052 } elseif ( array_keys( $data ) === [
'_error' ] ) {
1054 return $data[
'_error'];
1067 $this->
load( self::LOAD_ALL );
1068 if ( $this->unloadedMetadataBlobs ) {
1070 array_unique( array_merge(
1071 array_keys( $this->metadataArray ),
1072 array_keys( $this->unloadedMetadataBlobs )
1076 return $this->metadataArray;
1080 $this->load( self::LOAD_ALL );
1083 foreach ( $itemNames as $itemName ) {
1084 if ( array_key_exists( $itemName, $this->metadataArray ) ) {
1085 $result[$itemName] = $this->metadataArray[$itemName];
1086 } elseif ( isset( $this->unloadedMetadataBlobs[$itemName] ) ) {
1087 $addresses[$itemName] = $this->unloadedMetadataBlobs[$itemName];
1092 $resultFromBlob = $this->metadataStorageHelper->getMetadataFromBlobStore( $addresses );
1093 foreach ( $addresses as $itemName => $address ) {
1094 unset( $this->unloadedMetadataBlobs[$itemName] );
1095 $value = $resultFromBlob[$itemName] ??
null;
1096 if ( $value !==
null ) {
1097 $result[$itemName] = $value;
1098 $this->metadataArray[$itemName] = $value;
1117 $this->load( self::LOAD_ALL );
1118 if ( !$this->metadataArray && !$this->metadataBlobs ) {
1120 } elseif ( $this->repo->isJsonMetadataEnabled() ) {
1121 $s = $this->getJsonMetadata();
1123 $s = serialize( $this->getMetadataArray() );
1125 if ( !is_string( $s ) ) {
1126 throw new MWException(
'Could not serialize image metadata value for DB' );
1137 private function getJsonMetadata() {
1140 'data' => array_diff_key( $this->metadataArray, $this->metadataBlobs )
1144 if ( $this->metadataBlobs ) {
1145 $envelope[
'blobs'] = $this->metadataBlobs;
1148 [ $s, $blobAddresses ] = $this->metadataStorageHelper->getJsonMetadata( $this, $envelope );
1151 $this->metadataBlobs += $blobAddresses;
1162 private function isMetadataOversize() {
1163 if ( !$this->repo->isSplitMetadataEnabled() ) {
1166 $threshold = $this->repo->getSplitMetadataThreshold();
1167 $directItems = array_diff_key( $this->metadataArray, $this->metadataBlobs );
1168 foreach ( $directItems as $value ) {
1169 if ( strlen( $this->metadataStorageHelper->jsonEncode( $value ) ) > $threshold ) {
1185 $this->loadMetadataFromString( $db->
decodeBlob( $metadataBlob ) );
1196 $this->extraDataLoaded =
true;
1197 $this->metadataArray = [];
1198 $this->metadataBlobs = [];
1199 $this->unloadedMetadataBlobs = [];
1200 $metadataString = (string)$metadataString;
1201 if ( $metadataString ===
'' ) {
1202 $this->metadataSerializationFormat = self::MDS_EMPTY;
1205 if ( $metadataString[0] ===
'{' ) {
1206 $envelope = $this->metadataStorageHelper->jsonDecode( $metadataString );
1209 $this->metadataArray = [
'_error' => $metadataString ];
1210 $this->metadataSerializationFormat = self::MDS_LEGACY;
1212 $this->metadataSerializationFormat = self::MDS_JSON;
1213 if ( isset( $envelope[
'data'] ) ) {
1214 $this->metadataArray = $envelope[
'data'];
1216 if ( isset( $envelope[
'blobs'] ) ) {
1217 $this->metadataBlobs = $this->unloadedMetadataBlobs = $envelope[
'blobs'];
1222 $data = @unserialize( $metadataString );
1223 if ( !is_array( $data ) ) {
1225 $data = [
'_error' => $metadataString ];
1226 $this->metadataSerializationFormat = self::MDS_LEGACY;
1228 $this->metadataSerializationFormat = self::MDS_PHP;
1230 $this->metadataArray = $data;
1241 return (
int)$this->bits;
1275 return $this->media_type;
1292 return $this->fileExists;
1312 if ( $archiveName ) {
1313 $dir = $this->getArchiveThumbPath( $archiveName );
1315 $dir = $this->getThumbPath();
1318 $backend = $this->repo->getBackend();
1321 $iterator = $backend->getFileList( [
'dir' => $dir ] );
1322 if ( $iterator !==
null ) {
1323 foreach ( $iterator as
$file ) {
1336 private function purgeMetadataCache() {
1337 $this->invalidateCache();
1350 $this->maybeUpgradeRow();
1351 $this->purgeMetadataCache();
1354 $this->purgeThumbnails( $options );
1357 $hcu = MediaWikiServices::getInstance()->getHtmlCacheUpdater();
1360 !empty( $options[
'forThumbRefresh'] )
1361 ? $hcu::PURGE_PRESEND
1362 : $hcu::PURGE_INTENT_TXROUND_REFLECTED
1373 $thumbs = $this->getThumbnails( $archiveName );
1376 $dir = array_shift( $thumbs );
1377 $this->purgeThumbList( $dir, $thumbs );
1380 foreach ( $thumbs as $thumb ) {
1381 $urls[] = $this->getArchiveThumbUrl( $archiveName, $thumb );
1385 $this->getHookRunner()->onLocalFilePurgeThumbnails( $this, $archiveName, $urls );
1388 $hcu = MediaWikiServices::getInstance()->getHtmlCacheUpdater();
1389 $hcu->purgeUrls( $urls, $hcu::PURGE_PRESEND );
1399 $thumbs = $this->getThumbnails();
1402 $dir = array_shift( $thumbs );
1403 $this->purgeThumbList( $dir, $thumbs );
1407 foreach ( $thumbs as $thumb ) {
1408 $urls[] = $this->getThumbUrl( $thumb );
1412 if ( !empty( $options[
'forThumbRefresh'] ) ) {
1413 $handler = $this->getHandler();
1415 $handler->filterThumbnailPurgeList( $thumbs, $options );
1420 $this->getHookRunner()->onLocalFilePurgeThumbnails( $this,
false, $urls );
1423 $hcu = MediaWikiServices::getInstance()->getHtmlCacheUpdater();
1426 !empty( $options[
'forThumbRefresh'] )
1427 ? $hcu::PURGE_PRESEND
1428 : $hcu::PURGE_INTENT_TXROUND_REFLECTED
1439 $uploadThumbnailRenderMap = MediaWikiServices::getInstance()
1440 ->getMainConfig()->get( MainConfigNames::UploadThumbnailRenderMap );
1444 $sizes = $uploadThumbnailRenderMap;
1447 foreach ( $sizes as $size ) {
1448 if ( $this->isMultipage() ) {
1451 $pageLimit = min( $this->pageCount(), self::MAX_PAGE_RENDER_JOBS );
1453 for ( $page = 1; $page <= $pageLimit; $page++ ) {
1456 [
'transformParams' => [
1462 } elseif ( $this->isVectorized() || $this->getWidth() > $size ) {
1465 [
'transformParams' => [
'width' => $size ] ]
1471 MediaWikiServices::getInstance()->getJobQueueGroup()->lazyPush( $jobs );
1482 $fileListDebug = strtr(
1483 var_export( $files,
true ),
1486 wfDebug( __METHOD__ .
": $fileListDebug" );
1488 if ( $this->repo->supportsSha1URLs() ) {
1489 $reference = $this->getSha1();
1491 $reference = $this->getName();
1495 foreach ( $files as
$file ) {
1496 # Check that the reference (filename or sha1) is part of the thumb name
1497 # This is a basic check to avoid erasing unrelated directories
1498 if ( str_contains(
$file, $reference )
1499 || str_contains(
$file,
"-thumbnail" )
1501 $purgeList[] =
"{$dir}/{$file}";
1505 # Delete the thumbnails
1506 $this->repo->quickPurgeBatch( $purgeList );
1507 # Clear out the thumbnail directory if empty
1508 $this->repo->quickCleanDir( $dir );
1522 public function getHistory( $limit =
null, $start =
null, $end =
null, $inc =
true ) {
1523 if ( !$this->exists() ) {
1527 $dbr = $this->repo->getReplicaDB();
1528 $oldFileQuery = OldLocalFile::getQueryInfo();
1530 $tables = $oldFileQuery[
'tables'];
1531 $fields = $oldFileQuery[
'fields'];
1532 $join_conds = $oldFileQuery[
'joins'];
1533 $conds = $opts = [];
1534 $eq = $inc ?
'=' :
'';
1535 $conds[] =
"oi_name = " .
$dbr->addQuotes( $this->title->getDBkey() );
1538 $conds[] =
"oi_timestamp <$eq " .
$dbr->addQuotes(
$dbr->timestamp( $start ) );
1542 $conds[] =
"oi_timestamp >$eq " .
$dbr->addQuotes(
$dbr->timestamp( $end ) );
1546 $opts[
'LIMIT'] = $limit;
1550 $order = ( !$start && $end !== null ) ?
'ASC' :
'DESC';
1551 $opts[
'ORDER BY'] =
"oi_timestamp $order";
1552 $opts[
'USE INDEX'] = [
'oldimage' =>
'oi_name_timestamp' ];
1554 $this->getHookRunner()->onLocalFile__getHistory( $this, $tables, $fields,
1555 $conds, $opts, $join_conds );
1557 $res =
$dbr->select( $tables, $fields, $conds, __METHOD__, $opts, $join_conds );
1560 foreach (
$res as $row ) {
1561 $r[] = $this->repo->newFileFromRow( $row );
1564 if ( $order ==
'ASC' ) {
1565 $r = array_reverse( $r );
1582 if ( !$this->exists() ) {
1586 # Polymorphic function name to distinguish foreign and local fetches
1587 $fname = static::class .
'::' . __FUNCTION__;
1589 $dbr = $this->repo->getReplicaDB();
1591 if ( $this->historyLine == 0 ) {
1592 $fileQuery = self::getQueryInfo();
1593 $this->historyRes =
$dbr->select( $fileQuery[
'tables'],
1594 $fileQuery[
'fields'] + [
1595 'oi_archive_name' =>
$dbr->addQuotes(
'' ),
1598 [
'img_name' => $this->title->getDBkey() ],
1604 if ( $this->historyRes->numRows() == 0 ) {
1605 $this->historyRes =
null;
1609 } elseif ( $this->historyLine == 1 ) {
1610 $fileQuery = OldLocalFile::getQueryInfo();
1611 $this->historyRes =
$dbr->select(
1612 $fileQuery[
'tables'],
1613 $fileQuery[
'fields'],
1614 [
'oi_name' => $this->title->getDBkey() ],
1616 [
'ORDER BY' =>
'oi_timestamp DESC' ],
1620 $this->historyLine++;
1622 return $this->historyRes->fetchObject();
1630 $this->historyLine = 0;
1632 if ( $this->historyRes !==
null ) {
1633 $this->historyRes =
null;
1670 public function upload( $src, $comment, $pageText, $flags = 0, $props =
false,
1671 $timestamp =
false,
Authority $uploader =
null, $tags = [],
1672 $createNullRevision =
true, $revert =
false
1674 if ( $this->getRepo()->getReadOnlyReason() !==
false ) {
1675 return $this->readOnlyFatalStatus();
1676 } elseif ( MediaWikiServices::getInstance()->getRevisionStore()->isReadOnly() ) {
1679 return $this->readOnlyFatalStatus();
1682 $srcPath = ( $src instanceof
FSFile ) ? $src->getPath() : $src;
1687 $props = $this->repo->getFileProps( $srcPath );
1689 $mwProps =
new MWFileProps( MediaWikiServices::getInstance()->getMimeAnalyzer() );
1690 $props = $mwProps->getPropsFromPath( $srcPath,
true );
1697 if ( is_string( $props[
'metadata'] ) ) {
1702 $metadata = @unserialize( $props[
'metadata'] );
1704 $metadata = $props[
'metadata'];
1707 if ( is_array( $metadata ) ) {
1708 $options[
'headers'] = $handler->getContentHeaders( $metadata );
1711 $options[
'headers'] = [];
1715 $comment = trim( $comment );
1717 $status = $this->publish( $src, $flags, $options );
1719 if ( $status->successCount >= 2 ) {
1726 $oldver = $status->value;
1728 $uploadStatus = $this->recordUpload3(
1732 $uploader ?? RequestContext::getMain()->
getAuthority(),
1736 $createNullRevision,
1739 if ( !$uploadStatus->isOK() ) {
1740 if ( $uploadStatus->hasMessage(
'filenotfound' ) ) {
1742 $status->fatal(
'filenotfound', $srcPath );
1744 $status->merge( $uploadStatus );
1776 bool $createNullRevision =
true,
1777 bool $revert =
false
1779 $dbw = $this->repo->getPrimaryDB();
1781 # Imports or such might force a certain timestamp; otherwise we generate
1782 # it and can fudge it slightly to keep (name,timestamp) unique on re-upload.
1783 if ( $timestamp ===
false ) {
1784 $timestamp = $dbw->timestamp();
1785 $allowTimeKludge =
true;
1787 $allowTimeKludge =
false;
1790 $props = $props ?: $this->repo->getFileProps( $this->getVirtualUrl() );
1791 $props[
'description'] = $comment;
1792 $props[
'timestamp'] =
wfTimestamp( TS_MW, $timestamp );
1793 $this->setProps( $props );
1794 $mimeAnalyzer = MediaWikiServices::getInstance()->getMimeAnalyzer();
1795 if ( !$mimeAnalyzer->isValidMajorMimeType( $this->major_mime ) ) {
1796 $this->major_mime =
'unknown';
1799 # Fail now if the file isn't there
1800 if ( !$this->fileExists ) {
1801 wfDebug( __METHOD__ .
": File " . $this->getRel() .
" went missing!" );
1806 $actorNormalizaton = MediaWikiServices::getInstance()->getActorNormalization();
1808 $dbw->startAtomic( __METHOD__ );
1810 $actorId = $actorNormalizaton->acquireActorId( $performer->
getUser(), $dbw );
1811 $this->user = $performer->
getUser();
1813 # Test to see if the row exists using INSERT IGNORE
1814 # This avoids race conditions by locking the row until the commit, and also
1815 # doesn't deadlock. SELECT FOR UPDATE causes a deadlock for every race condition.
1816 $commentStore = MediaWikiServices::getInstance()->getCommentStore();
1817 $commentFields = $commentStore->insert( $dbw,
'img_description', $comment );
1818 $actorFields = [
'img_actor' => $actorId ];
1819 $dbw->insert(
'image',
1821 'img_name' => $this->getName(),
1822 'img_size' => $this->size,
1823 'img_width' => intval( $this->width ),
1824 'img_height' => intval( $this->height ),
1825 'img_bits' => $this->bits,
1826 'img_media_type' => $this->media_type,
1827 'img_major_mime' => $this->major_mime,
1828 'img_minor_mime' => $this->minor_mime,
1829 'img_timestamp' => $dbw->timestamp( $timestamp ),
1830 'img_metadata' => $this->getMetadataForDb( $dbw ),
1831 'img_sha1' => $this->sha1
1832 ] + $commentFields + $actorFields,
1836 $reupload = ( $dbw->affectedRows() == 0 );
1839 $row = $dbw->selectRow(
1841 [
'img_timestamp',
'img_sha1' ],
1842 [
'img_name' => $this->getName() ],
1844 [
'LOCK IN SHARE MODE' ]
1847 if ( $row && $row->img_sha1 === $this->sha1 ) {
1848 $dbw->endAtomic( __METHOD__ );
1849 wfDebug( __METHOD__ .
": File " . $this->getRel() .
" already exists!" );
1854 if ( $allowTimeKludge ) {
1855 # Use LOCK IN SHARE MODE to ignore any transaction snapshotting
1856 $lUnixtime = $row ? (int)
wfTimestamp( TS_UNIX, $row->img_timestamp ) : false;
1857 # Avoid a timestamp that is not newer than the last version
1858 # TODO: the image/oldimage tables should be like page/revision with an ID field
1859 if ( $lUnixtime && (
int)
wfTimestamp( TS_UNIX, $timestamp ) <= $lUnixtime ) {
1861 $timestamp = $dbw->timestamp( $lUnixtime + 1 );
1862 $this->timestamp =
wfTimestamp( TS_MW, $timestamp );
1866 $tables = [
'image' ];
1868 'oi_name' =>
'img_name',
1869 'oi_archive_name' => $dbw->addQuotes( $oldver ),
1870 'oi_size' =>
'img_size',
1871 'oi_width' =>
'img_width',
1872 'oi_height' =>
'img_height',
1873 'oi_bits' =>
'img_bits',
1874 'oi_description_id' =>
'img_description_id',
1875 'oi_timestamp' =>
'img_timestamp',
1876 'oi_metadata' =>
'img_metadata',
1877 'oi_media_type' =>
'img_media_type',
1878 'oi_major_mime' =>
'img_major_mime',
1879 'oi_minor_mime' =>
'img_minor_mime',
1880 'oi_sha1' =>
'img_sha1',
1881 'oi_actor' =>
'img_actor',
1885 # (T36993) Note: $oldver can be empty here, if the previous
1886 # version of the file was broken. Allow registration of the new
1887 # version to continue anyway, because that's better than having
1888 # an image that's not fixable by user operations.
1889 # Collision, this is an update of a file
1890 # Insert previous contents into oldimage
1891 $dbw->insertSelect(
'oldimage', $tables, $fields,
1892 [
'img_name' => $this->getName() ], __METHOD__, [], [], $joins );
1894 # Update the current image row
1895 $dbw->update(
'image',
1897 'img_size' => $this->size,
1898 'img_width' => intval( $this->width ),
1899 'img_height' => intval( $this->height ),
1900 'img_bits' => $this->bits,
1901 'img_media_type' => $this->media_type,
1902 'img_major_mime' => $this->major_mime,
1903 'img_minor_mime' => $this->minor_mime,
1904 'img_timestamp' => $dbw->timestamp( $timestamp ),
1905 'img_metadata' => $this->getMetadataForDb( $dbw ),
1906 'img_sha1' => $this->sha1
1907 ] + $commentFields + $actorFields,
1908 [
'img_name' => $this->getName() ],
1914 $descId = $descTitle->getArticleID();
1915 $wikiPage = MediaWikiServices::getInstance()->getWikiPageFactory()->newFromTitle( $descTitle );
1917 throw new MWException(
'Cannot instance WikiFilePage for ' . $this->getName()
1918 .
', got instance of ' . get_class( $wikiPage ) );
1920 $wikiPage->setFile( $this );
1924 $logAction =
'revert';
1925 } elseif ( $reupload ) {
1926 $logAction =
'overwrite';
1928 $logAction =
'upload';
1932 $logEntry->setTimestamp( $this->timestamp );
1933 $logEntry->setPerformer( $performer->
getUser() );
1934 $logEntry->setComment( $comment );
1935 $logEntry->setTarget( $descTitle );
1938 $logEntry->setParameters(
1940 'img_sha1' => $this->sha1,
1941 'img_timestamp' => $timestamp,
1950 $logId = $logEntry->insert();
1952 if ( $descTitle->exists() ) {
1953 if ( $createNullRevision ) {
1954 $revStore = MediaWikiServices::getInstance()->getRevisionStore();
1957 $formatter->setContext( RequestContext::newExtraneousContext( $descTitle ) );
1958 $editSummary = $formatter->getPlainActionText();
1959 $summary = CommentStoreComment::newUnsavedComment( $editSummary );
1960 $nullRevRecord =
$revStore->newNullRevision(
1968 if ( $nullRevRecord ) {
1969 $inserted =
$revStore->insertRevisionOn( $nullRevRecord, $dbw );
1971 $this->getHookRunner()->onRevisionFromEditComplete(
1974 $inserted->getParentId(),
1979 $wikiPage->updateRevisionOn( $dbw, $inserted );
1981 $logEntry->setAssociatedRevId( $inserted->getId() );
1985 $newPageContent =
null;
1994 $dbw->endAtomic( __METHOD__ );
1995 $fname = __METHOD__;
1997 # Do some cache purges after final commit so that:
1998 # a) Changes are more likely to be seen post-purge
1999 # b) They won't cause rollback of the log publish/update above
2004 $reupload, $wikiPage, $newPageContent, $comment, $performer,
2005 $logEntry, $logId, $descId, $tags, $fname
2007 # Update memcache after the commit
2008 $this->invalidateCache();
2010 $updateLogPage =
false;
2011 if ( $newPageContent ) {
2012 # New file page; create the description page.
2013 # There's already a log entry, so don't make a second RC entry
2014 # CDN and file cache for the description page are purged by doUserEditContent.
2015 $status = $wikiPage->doUserEditContent(
2022 $revRecord = $status->getNewRevision();
2025 $logEntry->setAssociatedRevId( $revRecord->getId() );
2029 $updateLogPage = $revRecord->getPageId();
2032 # Existing file page: invalidate description page cache
2033 $title = $wikiPage->getTitle();
2034 $title->invalidateCache();
2035 $hcu = MediaWikiServices::getInstance()->getHtmlCacheUpdater();
2036 $hcu->purgeTitleUrls(
$title, $hcu::PURGE_INTENT_TXROUND_REFLECTED );
2037 # Allow the new file version to be patrolled from the page footer
2041 # Update associated rev id. This should be done by $logEntry->insert() earlier,
2042 # but setAssociatedRevId() wasn't called at that point yet...
2043 $logParams = $logEntry->getParameters();
2044 $logParams[
'associated_rev_id'] = $logEntry->getAssociatedRevId();
2046 if ( $updateLogPage ) {
2047 # Also log page, in case where we just created it above
2048 $update[
'log_page'] = $updateLogPage;
2050 $this->getRepo()->getPrimaryDB()->update(
2053 [
'log_id' => $logId ],
2056 $this->getRepo()->getPrimaryDB()->insert(
2059 'ls_field' =>
'associated_rev_id',
2060 'ls_value' => (
string)$logEntry->getAssociatedRevId(),
2061 'ls_log_id' => $logId,
2066 # Add change tags, if any
2068 $logEntry->addTags( $tags );
2071 # Uploads can be patrolled
2072 $logEntry->setIsPatrollable(
true );
2074 # Now that the log entry is up-to-date, make an RC entry.
2075 $logEntry->publish( $logId );
2077 # Run hook for other updates (typically more cache purging)
2078 $this->getHookRunner()->onFileUpload( $this, $reupload, !$newPageContent );
2081 # Delete old thumbnails
2082 $this->purgeThumbnails();
2083 # Remove the old file from the CDN cache
2084 $hcu = MediaWikiServices::getInstance()->getHtmlCacheUpdater();
2085 $hcu->purgeUrls( $this->getUrl(), $hcu::PURGE_INTENT_TXROUND_REFLECTED );
2087 # Update backlink pages pointing to this title if created
2088 $blcFactory = MediaWikiServices::getInstance()->getBacklinkCacheFactory();
2089 LinksUpdate::queueRecursiveJobsForTable(
2093 $performer->
getUser()->getName(),
2094 $blcFactory->getBacklinkCache( $this->getTitle() )
2098 $this->prerenderThumbnails();
2102 # Invalidate cache for all pages using this file
2106 [
'causeAction' =>
'file-upload',
'causeAgent' => $performer->
getUser()->getName() ]
2114 $dbw->onTransactionCommitOrIdle(
static function () use ( $reupload, $purgeUpdate, $cacheUpdateJob ) {
2122 MediaWikiServices::getInstance()->getJobQueueGroup()->lazyPush( $cacheUpdateJob );
2144 public function publish( $src, $flags = 0, array $options = [] ) {
2145 return $this->publishTo( $src, $this->getRel(), $flags, $options );
2164 protected function publishTo( $src, $dstRel, $flags = 0, array $options = [] ) {
2165 $srcPath = ( $src instanceof
FSFile ) ? $src->getPath() : $src;
2167 $repo = $this->getRepo();
2168 if ( $repo->getReadOnlyReason() !==
false ) {
2169 return $this->readOnlyFatalStatus();
2172 $status = $this->acquireFileLock();
2173 if ( !$status->isOK() ) {
2177 if ( $this->isOld() ) {
2178 $archiveRel = $dstRel;
2179 $archiveName = basename( $archiveRel );
2181 $archiveName =
wfTimestamp( TS_MW ) .
'!' . $this->getName();
2182 $archiveRel = $this->getArchiveRel( $archiveName );
2185 if ( $repo->hasSha1Storage() ) {
2187 ? $repo->getFileSha1( $srcPath )
2190 $wrapperBackend = $repo->getBackend();
2191 '@phan-var FileBackendDBRepoWrapper $wrapperBackend';
2192 $dst = $wrapperBackend->getPathForSHA1( $sha1 );
2193 $status = $repo->quickImport( $src, $dst );
2198 if ( $this->exists() ) {
2199 $status->value = $archiveName;
2203 $status = $repo->publish( $srcPath, $dstRel, $archiveRel, $flags, $options );
2205 if ( $status->value ==
'new' ) {
2206 $status->value =
'';
2208 $status->value = $archiveName;
2212 $this->releaseFileLock();
2235 $localRepo = MediaWikiServices::getInstance()->getRepoGroup()->getLocalRepo();
2236 if ( $this->getRepo()->getReadOnlyReason() !==
false ) {
2237 return $this->readOnlyFatalStatus();
2240 wfDebugLog(
'imagemove',
"Got request to move {$this->name} to " . $target->getText() );
2243 $status = $batch->addCurrent();
2244 if ( !$status->isOK() ) {
2247 $archiveNames = $batch->addOlds();
2248 $status = $batch->execute();
2250 wfDebugLog(
'imagemove',
"Finished moving {$this->name}" );
2253 $oldTitleFile = $localRepo->newFile( $this->title );
2254 $newTitleFile = $localRepo->newFile( $target );
2255 DeferredUpdates::addUpdate(
2257 $this->getRepo()->getPrimaryDB(),
2259 static function () use ( $oldTitleFile, $newTitleFile, $archiveNames ) {
2260 $oldTitleFile->purgeEverything();
2261 foreach ( $archiveNames as $archiveName ) {
2263 '@phan-var OldLocalFile $oldTitleFile';
2264 $oldTitleFile->purgeOldThumbnails( $archiveName );
2266 $newTitleFile->purgeEverything();
2269 DeferredUpdates::PRESEND
2272 if ( $status->isOK() ) {
2274 $this->title = $target;
2277 $this->hashPath =
null;
2300 if ( $this->getRepo()->getReadOnlyReason() !==
false ) {
2301 return $this->readOnlyFatalStatus();
2306 $batch->addCurrent();
2308 $archiveNames = $batch->addOlds();
2309 $status = $batch->execute();
2311 if ( $status->isOK() ) {
2312 DeferredUpdates::addUpdate( SiteStatsUpdate::factory( [
'images' => -1 ] ) );
2316 DeferredUpdates::addUpdate(
2318 $this->getRepo()->getPrimaryDB(),
2320 function () use ( $archiveNames ) {
2321 $this->purgeEverything();
2322 foreach ( $archiveNames as $archiveName ) {
2323 $this->purgeOldThumbnails( $archiveName );
2327 DeferredUpdates::PRESEND
2332 foreach ( $archiveNames as $archiveName ) {
2333 $purgeUrls[] = $this->getArchiveUrl( $archiveName );
2336 $hcu = MediaWikiServices::getInstance()->getHtmlCacheUpdater();
2337 $hcu->purgeUrls( $purgeUrls, $hcu::PURGE_INTENT_TXROUND_REFLECTED );
2361 if ( $this->getRepo()->getReadOnlyReason() !==
false ) {
2362 return $this->readOnlyFatalStatus();
2367 $batch->addOld( $archiveName );
2368 $status = $batch->execute();
2370 $this->purgeOldThumbnails( $archiveName );
2371 if ( $status->isOK() ) {
2372 $this->purgeDescription();
2375 $url = $this->getArchiveUrl( $archiveName );
2376 $hcu = MediaWikiServices::getInstance()->getHtmlCacheUpdater();
2377 $hcu->purgeUrls( $url, $hcu::PURGE_INTENT_TXROUND_REFLECTED );
2394 public function restore( $versions = [], $unsuppress =
false ) {
2395 if ( $this->getRepo()->getReadOnlyReason() !==
false ) {
2396 return $this->readOnlyFatalStatus();
2404 $batch->addIds( $versions );
2406 $status = $batch->execute();
2407 if ( $status->isGood() ) {
2408 $cleanupStatus = $batch->cleanup();
2409 $cleanupStatus->successCount = 0;
2410 $cleanupStatus->failCount = 0;
2411 $status->merge( $cleanupStatus );
2429 return $this->title ? $this->title->getLocalURL() :
false;
2442 if ( !$this->title ) {
2446 $services = MediaWikiServices::getInstance();
2447 $page = $services->getPageStore()->getPageByReference( $this->
getTitle() );
2453 $parserOptions = ParserOptions::newFromUserAndLang(
2454 RequestContext::getMain()->
getUser(),
2458 $parserOptions = ParserOptions::newFromContext( RequestContext::getMain() );
2461 $parseStatus = $services->getParserOutputAccess()
2462 ->getParserOutput( $page, $parserOptions );
2464 if ( !$parseStatus->isGood() ) {
2468 return $parseStatus->getValue()->getText();
2480 if ( $audience === self::FOR_PUBLIC && $this->isDeleted( self::DELETED_USER ) ) {
2482 } elseif ( $audience === self::FOR_THIS_USER && !$this->userCan( self::DELETED_USER, $performer ) ) {
2497 if ( $audience == self::FOR_PUBLIC && $this->isDeleted( self::DELETED_COMMENT ) ) {
2499 } elseif ( $audience == self::FOR_THIS_USER && !$this->userCan( self::DELETED_COMMENT, $performer ) ) {
2502 return $this->description;
2513 return $this->timestamp;
2521 if ( !$this->exists() ) {
2528 if ( $this->descriptionTouched ===
null ) {
2530 'page_namespace' => $this->title->getNamespace(),
2531 'page_title' => $this->title->getDBkey()
2533 $touched = $this->repo->getReplicaDB()->selectField(
'page',
'page_touched', $cond, __METHOD__ );
2534 $this->descriptionTouched = $touched ?
wfTimestamp( TS_MW, $touched ) :
false;
2537 return $this->descriptionTouched;
2556 return $this->extraDataLoaded
2557 && strlen( serialize( $this->metadataArray ) ) <= self::CACHE_FIELD_MAX_LEN;
2569 return Status::wrap( $this->getRepo()->getBackend()->lockFiles(
2570 [ $this->getPath() ], LockManager::LOCK_EX, $timeout
2581 return Status::wrap( $this->getRepo()->getBackend()->unlockFiles(
2582 [ $this->getPath() ], LockManager::LOCK_EX
2597 if ( !$this->locked ) {
2598 $logger = LoggerFactory::getInstance(
'LocalFile' );
2600 $dbw = $this->repo->getPrimaryDB();
2601 $makesTransaction = !$dbw->trxLevel();
2602 $dbw->startAtomic( self::ATOMIC_SECTION_LOCK );
2606 $status = $this->acquireFileLock( 10 );
2607 if ( !$status->isGood() ) {
2608 $dbw->endAtomic( self::ATOMIC_SECTION_LOCK );
2609 $logger->warning(
"Failed to lock '{file}'", [
'file' => $this->name ] );
2615 $dbw->onTransactionResolution(
2616 function () use ( $logger ) {
2617 $status = $this->releaseFileLock();
2618 if ( !$status->isGood() ) {
2619 $logger->error(
"Failed to unlock '{file}'", [
'file' => $this->name ] );
2625 $this->lockedOwnTrx = $makesTransaction;
2630 return $this->lockedOwnTrx;
2644 if ( $this->locked ) {
2646 if ( !$this->locked ) {
2647 $dbw = $this->repo->getPrimaryDB();
2648 $dbw->endAtomic( self::ATOMIC_SECTION_LOCK );
2649 $this->lockedOwnTrx =
false;
2658 return $this->getRepo()->newFatal(
'filereadonlyerror', $this->getName(),
2659 $this->getRepo()->getName(), $this->getRepo()->getReadOnlyReason() );
wfDebug( $text, $dest='all', array $context=[])
Sends a line to the debug log if enabled or, optionally, to a comment in output.
wfDeprecatedMsg( $msg, $version=false, $component=false, $callerOffset=2)
Log a deprecation warning with arbitrary message text.
wfDebugLog( $logGroup, $text, $dest='all', array $context=[])
Send a line to a supplementary debug log file, if configured, or main debug log if not.
wfTimestamp( $outputtype=TS_UNIX, $ts=0)
Get a timestamp string in one of various formats.
static purgePatrolFooterCache( $articleID)
Purge the cache used to check if it is worth showing the patrol footer For example,...
Deferrable Update for closure/callback updates that should use auto-commit mode.
static makeContent( $text, Title $title=null, $modelId=null, $format=null)
Convenience function for creating a Content object from a given textual representation.
static addUpdate(DeferrableUpdate $update, $stage=self::POSTSEND)
Add an update to the pending update queue for execution at the appropriate time.
Class representing a non-directory file on the file system.
static getSha1Base36FromPath( $path)
Get a SHA-1 hash of a file in the local filesystem, in base-36 lower case encoding,...
File backend exception for checked exceptions (e.g.
static isStoragePath( $path)
Check if a given path is a "mwstore://" path.
static isVirtualUrl( $url)
Determine if a string is an mwrepo:// URL.
Implements some public methods and some protected utility functions which are required by multiple ch...
string $url
The URL corresponding to one of the four basic zones.
assertRepoDefined()
Assert that $this->repo is set to a valid FileRepo instance.
getName()
Return the name of this file.
getVirtualUrl( $suffix=false)
Get the public zone virtual URL for a current version source file.
assertTitleDefined()
Assert that $this->title is set to a Title.
FileRepo LocalRepo ForeignAPIRepo false $repo
Some member variables can be lazy-initialised using __get().
isMultipage()
Returns 'true' if this file is a type which supports multiple pages, e.g.
getHandler()
Get a MediaHandler instance for this file.
string null $name
The name of a file from its title object.
static newForBacklinks(PageReference $page, $table, $params=[])
Base class for language-specific code.
Helper class for file deletion.
Helper class for file movement.
Helper class for file undeletion.
Local file in the wiki's own database.
exists()
canRender inherited
setProps( $info)
Set properties in this object to be equal to those given in the associative array $info.
maybeUpgradeRow()
Upgrade a row if it needs it.
static newFromKey( $sha1, $repo, $timestamp=false)
Create a LocalFile from a SHA-1 key Do not call this except from inside a repo class.
array $metadataArray
Unserialized metadata.
getMediaType()
Returns the type of the media in the file.
string[] $unloadedMetadataBlobs
Map of metadata item name to blob address for items that exist but have not yet been loaded into $thi...
deleteOldFile( $archiveName, $reason, UserIdentity $user, $suppress=false)
Delete an old version of the file.
move( $target)
getLinksTo inherited
lock()
Start an atomic DB section and lock the image for update or increments a reference counter if the loc...
loadFromRow( $row, $prefix='img_')
Load file metadata from a DB result row.
getCacheKey()
Get the memcached key for the main data for this file, or false if there is no access to the shared c...
getWidth( $page=1)
Return the width of the image.
__destruct()
Clean up any dangling locks.
string $mime
MIME type, determined by MimeAnalyzer::guessMimeType.
reserializeMetadata()
Write the metadata back to the database with the current serialization format.
isMissing()
splitMime inherited
getDescriptionUrl()
isMultipage inherited
getHistory( $limit=null, $start=null, $end=null, $inc=true)
purgeDescription inherited
static getQueryInfo(array $options=[])
Return the tables, fields, and join conditions to be selected to create a new localfile object.
releaseFileLock()
Release a lock acquired with acquireFileLock().
getUploader(int $audience=self::FOR_PUBLIC, Authority $performer=null)
loadMetadataFromDbFieldValue(IDatabase $db, $metadataBlob)
Unserialize a metadata blob which came from the database and store it in $this.
loadFromDB( $flags=0)
Load file metadata from the DB.
load( $flags=0)
Load file metadata from cache or DB, unless already loaded.
loadMetadataFromString( $metadataString)
Unserialize a metadata string which came from some non-DB source, or is the return value of IDatabase...
string $media_type
MEDIATYPE_xxx (bitmap, drawing, audio...)
deleteFile( $reason, UserIdentity $user, $suppress=false)
Delete all versions of the file.
acquireFileLock( $timeout=0)
Acquire an exclusive lock on the file, indicating an intention to write to the file backend.
purgeCache( $options=[])
Delete all previously generated thumbnails, refresh metadata in memcached and purge the CDN.
loadFromFile( $path=null)
Load metadata from the file itself.
string null $metadataSerializationFormat
One of the MDS_* constants, giving the format of the metadata as stored in the DB,...
int $size
Size in bytes (loadFromXxx)
getDescriptionShortUrl()
Get short description URL for a file based on the page ID.
getThumbnails( $archiveName=false)
getTransformScript inherited
static newFromTitle( $title, $repo, $unused=null)
Create a LocalFile from a title Do not call this except from inside a repo class.
getMetadataForDb(IDatabase $db)
Serialize the metadata array for insertion into img_metadata, oi_metadata or fa_metadata.
purgeOldThumbnails( $archiveName)
Delete cached transformed files for an archived version only.
publishTo( $src, $dstRel, $flags=0, array $options=[])
Move or copy a file to a specified location.
purgeThumbList( $dir, $files)
Delete a list of thumbnails visible at urls.
unlock()
Decrement the lock reference count and end the atomic section if it reaches zero.
getLazyCacheFields( $prefix='img_')
Returns the list of object properties that are included as-is in the cache, only when they're not too...
getSize()
Returns the size of the image file, in bytes.
invalidateCache()
Purge the file object/metadata cache.
getMimeType()
Returns the MIME type of the file.
bool $extraDataLoaded
Whether or not lazy-loaded data has been loaded from the database.
string $sha1
SHA-1 base 36 content hash.
getDescription( $audience=self::FOR_PUBLIC, Authority $performer=null)
getHeight( $page=1)
Return the height of the image.
prerenderThumbnails()
Prerenders a configurable set of thumbnails.
resetHistory()
Reset the history pointer to the first element of the history.
unprefixRow( $row, $prefix='img_')
static newFromRow( $row, $repo)
Create a LocalFile from a title Do not call this except from inside a repo class.
publish( $src, $flags=0, array $options=[])
Move or copy a file to its public location.
restore( $versions=[], $unsuppress=false)
Restore all or specified deleted revisions to the given file.
getCacheFields( $prefix='img_')
Returns the list of object properties that are included as-is in the cache.
int $bits
Returned by getimagesize (loadFromXxx)
getMetadataItems(array $itemNames)
Get multiple elements of the unserialized handler-specific metadata.
getDescriptionText(Language $lang=null)
Get the HTML text of the description page This is not used by ImagePage for local files,...
purgeThumbnails( $options=[])
Delete cached transformed files for the current version only.
loadExtraFromDB()
Load lazy file metadata from the DB.
nextHistoryLine()
Returns the history of this file, line by line.
upgradeRow()
Fix assorted version-related problems with the image row by reloading it from the file.
int $deleted
Bitfield akin to rev_deleted.
getMetadata()
Get handler-specific metadata as a serialized string.
getMetadataArray()
Get unserialized handler-specific metadata.
__construct( $title, $repo)
Do not call this except from inside a repo class.
bool $dataLoaded
Whether or not core data has been loaded from the database (loadFromXxx)
bool $fileExists
Does the file exist on disk? (loadFromXxx)
upload( $src, $comment, $pageText, $flags=0, $props=false, $timestamp=false, Authority $uploader=null, $tags=[], $createNullRevision=true, $revert=false)
getHashPath inherited
recordUpload3(string $oldver, string $comment, string $pageText, Authority $performer, $props=false, $timestamp=false, $tags=[], bool $createNullRevision=true, bool $revert=false)
Record a file upload in the upload log and the image table (version 3)
string[] $metadataBlobs
Map of metadata item name to blob address.
static makeParamBlob( $params)
Create a blob from a parameter array.
MimeMagic helper wrapper.
Class for creating new log entries and inserting them into the database.
A class containing constants representing the names of configuration variables.
static getQueryInfo(array $options=[])
Return the tables, fields, and join conditions to be selected to create a new oldlocalfile object.
static newFatal( $message,... $parameters)
Factory function for fatal errors.
static newGood( $value=null)
Factory function for good results.
Generic operation result class Has warning/error list, boolean status and arbitrary value.
Job for asynchronous rendering of thumbnails, e.g.
Special handling for representing file pages.
if(PHP_SAPI !='cli-server') if(!isset( $_SERVER['SCRIPT_FILENAME'])) $file
Item class for a filearchive table row.
if(!isset( $args[0])) $lang