76 private const VERSION = 13;
78 private const CACHE_FIELD_MAX_LEN = 1000;
81 private const MDS_EMPTY =
'empty';
84 private const MDS_LEGACY =
'legacy';
87 private const MDS_PHP =
'php';
90 private const MDS_JSON =
'json';
93 private const MAX_PAGE_RENDER_JOBS = 50;
117 protected $metadataArray = [];
128 protected $metadataBlobs = [];
136 protected $unloadedMetadataBlobs = [];
142 protected $dataLoaded =
false;
145 protected $extraDataLoaded =
false;
151 protected $repoClass = LocalRepo::class;
154 private $historyLine = 0;
157 private $historyRes =
null;
172 private $description;
175 private $descriptionTouched;
187 private $lockedOwnTrx;
193 private $metadataStorageHelper;
196 private const LOAD_ALL = 16;
198 private const ATOMIC_SECTION_LOCK =
'LocalFile::lockingTransaction';
232 $file->loadFromRow( $row );
248 public static function newFromKey( $sha1, $repo, $timestamp =
false ) {
249 $dbr =
$repo->getReplicaDB();
250 $queryBuilder = FileSelectQueryBuilder::newForFile( $dbr );
252 $queryBuilder->where( [
'img_sha1' => $sha1 ] );
255 $queryBuilder->andWhere( [
'img_timestamp' => $dbr->
timestamp( $timestamp ) ] );
258 $row = $queryBuilder->caller( __METHOD__ )->fetchRow();
260 return static::newFromRow( $row,
$repo );
287 $dbr = MediaWikiServices::getInstance()->getConnectionProvider()->getReplicaDatabase();
288 $queryInfo = FileSelectQueryBuilder::newForFile( $dbr, $options )->getQueryInfo();
291 'tables' => $queryInfo[
'tables'],
292 'fields' => $queryInfo[
'fields'],
293 'joins' => $queryInfo[
'join_conds'],
326 return $this->repo->getSharedCacheKey(
'file', sha1( $this->
getName() ) );
332 private function loadFromCache() {
333 $this->dataLoaded =
false;
334 $this->extraDataLoaded =
false;
338 $this->loadFromDB( IDBAccessObject::READ_NORMAL );
343 $cache = MediaWikiServices::getInstance()->getMainWANObjectCache();
344 $cachedValues = $cache->getWithSetCallback(
347 function ( $oldValue, &$ttl, array &$setOpts ) use ( $cache ) {
348 $setOpts += Database::getCacheSetOptions( $this->repo->getReplicaDB() );
350 $this->
loadFromDB( IDBAccessObject::READ_NORMAL );
354 $cacheVal[
'fileExists'] = $this->fileExists;
355 if ( $this->fileExists ) {
356 foreach ( $fields as $field ) {
357 $cacheVal[$field] = $this->$field;
361 $cacheVal[
'user'] = $this->user->getId();
362 $cacheVal[
'user_text'] = $this->user->getName();
366 if ( $this->metadataBlobs ) {
367 $cacheVal[
'metadata'] = array_diff_key(
368 $this->metadataArray, $this->metadataBlobs );
370 $cacheVal[
'metadataBlobs'] = $this->metadataBlobs;
372 $cacheVal[
'metadata'] = $this->metadataArray;
379 if ( isset( $cacheVal[$field] )
380 && strlen( serialize( $cacheVal[$field] ) ) > 100 * 1024
382 unset( $cacheVal[$field] );
383 if ( $field ===
'metadata' ) {
384 unset( $cacheVal[
'metadataBlobs'] );
389 if ( $this->fileExists ) {
390 $ttl = $cache->adaptiveTTL( (
int)
wfTimestamp( TS_UNIX, $this->timestamp ), $ttl );
392 $ttl = $cache::TTL_DAY;
397 [
'version' => self::VERSION ]
400 $this->fileExists = $cachedValues[
'fileExists'];
401 if ( $this->fileExists ) {
405 $this->dataLoaded =
true;
406 $this->extraDataLoaded =
true;
408 $this->extraDataLoaded = $this->extraDataLoaded && isset( $cachedValues[$field] );
421 $this->repo->getPrimaryDB()->onTransactionPreCommitOrIdle(
422 static function () use ( $key ) {
423 MediaWikiServices::getInstance()->getMainWANObjectCache()->delete( $key );
449 if ( $prefix !==
'' ) {
450 throw new InvalidArgumentException(
451 __METHOD__ .
' with a non-empty prefix is no longer supported.'
459 return [
'size',
'width',
'height',
'bits',
'media_type',
460 'major_mime',
'minor_mime',
'timestamp',
'sha1',
'description' ];
471 if ( $prefix !==
'' ) {
472 throw new InvalidArgumentException(
473 __METHOD__ .
' with a non-empty prefix is no longer supported.'
478 return [
'metadata' ];
487 $fname = static::class .
'::' . __FUNCTION__;
489 # Unconditionally set loaded=true, we don't want the accessors constantly rechecking
490 $this->dataLoaded =
true;
491 $this->extraDataLoaded =
true;
493 $dbr = ( $flags & IDBAccessObject::READ_LATEST )
494 ? $this->repo->getPrimaryDB()
495 : $this->repo->getReplicaDB();
496 $queryBuilder = FileSelectQueryBuilder::newForFile( $dbr );
498 $queryBuilder->where( [
'img_name' => $this->
getName() ] );
499 $row = $queryBuilder->caller( $fname )->fetchRow();
504 $this->fileExists =
false;
514 if ( !$this->title ) {
518 $fname = static::class .
'::' . __FUNCTION__;
520 # Unconditionally set loaded=true, we don't want the accessors constantly rechecking
521 $this->extraDataLoaded =
true;
523 $db = $this->repo->getReplicaDB();
524 $fieldMap = $this->loadExtraFieldsWithTimestamp( $db, $fname );
526 $db = $this->repo->getPrimaryDB();
527 $fieldMap = $this->loadExtraFieldsWithTimestamp( $db, $fname );
531 if ( isset( $fieldMap[
'metadata'] ) ) {
535 throw new RuntimeException(
"Could not find data for image '{$this->getName()}'." );
544 private function loadExtraFieldsWithTimestamp(
IReadableDatabase $dbr, $fname ) {
547 $queryBuilder = FileSelectQueryBuilder::newForFile( $dbr, [
'omit-nonlazy' ] );
548 $queryBuilder->where( [
'img_name' => $this->getName() ] )
549 ->andWhere( [
'img_timestamp' => $dbr->
timestamp( $this->getTimestamp() ) ] );
550 $row = $queryBuilder->caller( $fname )->fetchRow();
552 $fieldMap = $this->unprefixRow( $row,
'img_' );
554 # File may have been uploaded over in the meantime; check the old versions
555 $queryBuilder = FileSelectQueryBuilder::newForOldFile( $dbr, [
'omit-nonlazy' ] );
556 $row = $queryBuilder->where( [
'oi_name' => $this->
getName() ] )
557 ->andWhere( [
'oi_timestamp' => $dbr->
timestamp( $this->getTimestamp() ) ] )
558 ->caller( __METHOD__ )->fetchRow();
573 $array = (array)$row;
574 $prefixLength = strlen( $prefix );
577 if ( substr( array_key_first( $array ), 0, $prefixLength ) !== $prefix ) {
578 throw new InvalidArgumentException( __METHOD__ .
': incorrect $prefix parameter' );
582 foreach ( $array as
$name => $value ) {
583 $decoded[substr(
$name, $prefixLength )] = $value;
605 $this->dataLoaded =
true;
609 $this->name = $unprefixed[
'name'];
610 $this->media_type = $unprefixed[
'media_type'];
612 $services = MediaWikiServices::getInstance();
613 $this->description = $services->getCommentStore()
614 ->getComment(
"{$prefix}description", $row )->text;
616 $this->user = $services->getUserFactory()->newFromAnyId(
617 $unprefixed[
'user'] ??
null,
618 $unprefixed[
'user_text'] ??
null,
619 $unprefixed[
'actor'] ??
null
622 $this->timestamp =
wfTimestamp( TS_MW, $unprefixed[
'timestamp'] );
625 $this->repo->getReplicaDB(), $unprefixed[
'metadata'] );
627 if ( empty( $unprefixed[
'major_mime'] ) ) {
628 $this->major_mime =
'unknown';
629 $this->minor_mime =
'unknown';
630 $this->mime =
'unknown/unknown';
632 if ( !$unprefixed[
'minor_mime'] ) {
633 $unprefixed[
'minor_mime'] =
'unknown';
635 $this->major_mime = $unprefixed[
'major_mime'];
636 $this->minor_mime = $unprefixed[
'minor_mime'];
637 $this->mime = $unprefixed[
'major_mime'] .
'/' . $unprefixed[
'minor_mime'];
641 $this->sha1 = rtrim( $unprefixed[
'sha1'],
"\0" );
647 $this->size = +$unprefixed[
'size'];
648 $this->width = +$unprefixed[
'width'];
649 $this->height = +$unprefixed[
'height'];
650 $this->bits = +$unprefixed[
'bits'];
653 $extraFields = array_diff(
654 array_keys( $unprefixed ),
656 'name',
'media_type',
'description_text',
'description_data',
657 'description_cid',
'user',
'user_text',
'actor',
'timestamp',
658 'metadata',
'major_mime',
'minor_mime',
'sha1',
'size',
'width',
662 if ( $extraFields ) {
664 'Passing extra fields (' .
665 implode(
', ', $extraFields )
666 .
') to ' . __METHOD__ .
' was deprecated in MediaWiki 1.37. ' .
667 'Property assignment will be removed in a later version.',
669 foreach ( $extraFields as $field ) {
670 $this->$field = $unprefixed[$field];
674 $this->fileExists =
true;
682 public function load( $flags = 0 ) {
683 if ( !$this->dataLoaded ) {
684 if ( $flags & IDBAccessObject::READ_LATEST ) {
687 $this->loadFromCache();
691 if ( ( $flags & self::LOAD_ALL ) && !$this->extraDataLoaded ) {
702 if ( MediaWikiServices::getInstance()->getReadOnlyMode()->isReadOnly() || $this->upgrading ) {
707 $reserialize =
false;
708 if ( $this->media_type ===
null || $this->mime ==
'image/svg' ) {
714 if ( $validity === MediaHandler::METADATA_BAD ) {
716 } elseif ( $validity === MediaHandler::METADATA_COMPATIBLE
717 && $this->repo->isMetadataUpdateEnabled()
720 } elseif ( $this->repo->isJsonMetadataEnabled()
721 && $this->repo->isMetadataReserializeEnabled()
723 if ( $this->repo->isSplitMetadataEnabled() && $this->isMetadataOversize() ) {
725 } elseif ( $this->metadataSerializationFormat !== self::MDS_EMPTY &&
726 $this->metadataSerializationFormat !== self::MDS_JSON ) {
733 if ( $upgrade || $reserialize ) {
734 $this->upgrading =
true;
736 DeferredUpdates::addCallableUpdate(
function () use ( $upgrade ) {
737 $this->upgrading =
false;
755 return $this->upgraded;
763 $dbw = $this->repo->getPrimaryDB();
767 $freshnessCondition = [
'img_timestamp' => $dbw->timestamp( $this->
getTimestamp() ) ];
771 # Don't destroy file info of missing files
772 if ( !$this->fileExists ) {
773 wfDebug( __METHOD__ .
": file does not exist, aborting" );
778 [ $major, $minor ] = self::splitMime( $this->mime );
780 wfDebug( __METHOD__ .
': upgrading ' . $this->
getName() .
" to the current schema" );
782 $dbw->newUpdateQueryBuilder()
785 'img_size' => $this->size,
786 'img_width' => $this->width,
787 'img_height' => $this->height,
788 'img_bits' => $this->bits,
789 'img_media_type' => $this->media_type,
790 'img_major_mime' => $major,
791 'img_minor_mime' => $minor,
793 'img_sha1' => $this->sha1,
795 ->where( [
'img_name' => $this->
getName() ] )
796 ->andWhere( $freshnessCondition )
797 ->caller( __METHOD__ )->execute();
801 $this->upgraded =
true;
809 if ( MediaWikiServices::getInstance()->getReadOnlyMode()->isReadOnly() ) {
812 $dbw = $this->repo->getPrimaryDB();
813 $dbw->newUpdateQueryBuilder()
817 'img_name' => $this->name,
818 'img_timestamp' => $dbw->timestamp( $this->timestamp ),
820 ->caller( __METHOD__ )->execute();
821 $this->upgraded =
true;
836 $this->dataLoaded =
true;
838 $fields[] =
'fileExists';
840 foreach ( $fields as $field ) {
841 if ( isset( $info[$field] ) ) {
842 $this->$field = $info[$field];
847 if ( isset( $info[
'user'] ) &&
848 isset( $info[
'user_text'] ) &&
849 $info[
'user_text'] !==
''
855 if ( isset( $info[
'major_mime'] ) ) {
856 $this->mime =
"{$info['major_mime']}/{$info['minor_mime']}";
857 } elseif ( isset( $info[
'mime'] ) ) {
858 $this->mime = $info[
'mime'];
859 [ $this->major_mime, $this->minor_mime ] = self::splitMime( $this->mime );
862 if ( isset( $info[
'metadata'] ) ) {
863 if ( is_string( $info[
'metadata'] ) ) {
865 } elseif ( is_array( $info[
'metadata'] ) ) {
866 $this->metadataArray = $info[
'metadata'];
867 if ( isset( $info[
'metadataBlobs'] ) ) {
868 $this->metadataBlobs = $info[
'metadataBlobs'];
869 $this->unloadedMetadataBlobs = array_diff_key(
870 $this->metadataBlobs,
874 $this->metadataBlobs = [];
875 $this->unloadedMetadataBlobs = [];
878 $logger = LoggerFactory::getInstance(
'LocalFile' );
879 $logger->warning( __METHOD__ .
' given invalid metadata of type ' .
880 get_debug_type( $info[
'metadata'] ) );
881 $this->metadataArray = [];
883 $this->extraDataLoaded =
true;
903 if ( $this->missing ===
null ) {
904 $fileExists = $this->repo->fileExists( $this->
getVirtualUrl() );
905 $this->missing = !$fileExists;
908 return $this->missing;
933 return $dim[
'width'];
966 return $dim[
'height'];
973 return $this->height;
985 if ( !$this->title ) {
989 $pageId = $this->title->getArticleID();
992 $url = $this->repo->makeUrl( [
'curid' => $pageId ] );
993 if (
$url !==
false ) {
1010 } elseif ( array_keys( $data ) === [
'_error' ] ) {
1012 return $data[
'_error'];
1025 $this->
load( self::LOAD_ALL );
1026 if ( $this->unloadedMetadataBlobs ) {
1028 array_unique( array_merge(
1029 array_keys( $this->metadataArray ),
1030 array_keys( $this->unloadedMetadataBlobs )
1034 return $this->metadataArray;
1038 $this->load( self::LOAD_ALL );
1041 foreach ( $itemNames as $itemName ) {
1042 if ( array_key_exists( $itemName, $this->metadataArray ) ) {
1043 $result[$itemName] = $this->metadataArray[$itemName];
1044 } elseif ( isset( $this->unloadedMetadataBlobs[$itemName] ) ) {
1045 $addresses[$itemName] = $this->unloadedMetadataBlobs[$itemName];
1050 $resultFromBlob = $this->metadataStorageHelper->getMetadataFromBlobStore( $addresses );
1051 foreach ( $addresses as $itemName => $address ) {
1052 unset( $this->unloadedMetadataBlobs[$itemName] );
1053 $value = $resultFromBlob[$itemName] ??
null;
1054 if ( $value !==
null ) {
1055 $result[$itemName] = $value;
1056 $this->metadataArray[$itemName] = $value;
1075 $this->load( self::LOAD_ALL );
1076 if ( !$this->metadataArray && !$this->metadataBlobs ) {
1078 } elseif ( $this->repo->isJsonMetadataEnabled() ) {
1079 $s = $this->getJsonMetadata();
1081 $s = serialize( $this->getMetadataArray() );
1083 if ( !is_string( $s ) ) {
1084 throw new RuntimeException(
'Could not serialize image metadata value for DB' );
1095 private function getJsonMetadata() {
1098 'data' => array_diff_key( $this->metadataArray, $this->metadataBlobs )
1102 if ( $this->metadataBlobs ) {
1103 $envelope[
'blobs'] = $this->metadataBlobs;
1106 [ $s, $blobAddresses ] = $this->metadataStorageHelper->getJsonMetadata( $this, $envelope );
1109 $this->metadataBlobs += $blobAddresses;
1120 private function isMetadataOversize() {
1121 if ( !$this->repo->isSplitMetadataEnabled() ) {
1124 $threshold = $this->repo->getSplitMetadataThreshold();
1125 $directItems = array_diff_key( $this->metadataArray, $this->metadataBlobs );
1126 foreach ( $directItems as $value ) {
1127 if ( strlen( $this->metadataStorageHelper->jsonEncode( $value ) ) > $threshold ) {
1143 $this->loadMetadataFromString( $db->
decodeBlob( $metadataBlob ) );
1154 $this->extraDataLoaded =
true;
1155 $this->metadataArray = [];
1156 $this->metadataBlobs = [];
1157 $this->unloadedMetadataBlobs = [];
1158 $metadataString = (string)$metadataString;
1159 if ( $metadataString ===
'' ) {
1160 $this->metadataSerializationFormat = self::MDS_EMPTY;
1163 if ( $metadataString[0] ===
'{' ) {
1164 $envelope = $this->metadataStorageHelper->jsonDecode( $metadataString );
1167 $this->metadataArray = [
'_error' => $metadataString ];
1168 $this->metadataSerializationFormat = self::MDS_LEGACY;
1170 $this->metadataSerializationFormat = self::MDS_JSON;
1171 if ( isset( $envelope[
'data'] ) ) {
1172 $this->metadataArray = $envelope[
'data'];
1174 if ( isset( $envelope[
'blobs'] ) ) {
1175 $this->metadataBlobs = $this->unloadedMetadataBlobs = $envelope[
'blobs'];
1180 $data = @unserialize( $metadataString );
1181 if ( !is_array( $data ) ) {
1183 $data = [
'_error' => $metadataString ];
1184 $this->metadataSerializationFormat = self::MDS_LEGACY;
1186 $this->metadataSerializationFormat = self::MDS_PHP;
1188 $this->metadataArray = $data;
1199 return (
int)$this->bits;
1233 return $this->media_type;
1250 return $this->fileExists;
1275 if ( $archiveName ) {
1276 $dir = $this->getArchiveThumbPath( $archiveName );
1278 $dir = $this->getThumbPath();
1281 $backend = $this->repo->getBackend();
1284 $iterator = $backend->getFileList( [
'dir' => $dir,
'forWrite' =>
true ] );
1285 if ( $iterator !==
null ) {
1286 foreach ( $iterator as $file ) {
1306 $this->maybeUpgradeRow();
1307 $this->invalidateCache();
1310 $this->purgeThumbnails( $options );
1313 $hcu = MediaWikiServices::getInstance()->getHtmlCacheUpdater();
1316 !empty( $options[
'forThumbRefresh'] )
1317 ? $hcu::PURGE_PRESEND
1318 : $hcu::PURGE_INTENT_TXROUND_REFLECTED
1329 $thumbs = $this->getThumbnails( $archiveName );
1332 $dir = array_shift( $thumbs );
1333 $this->purgeThumbList( $dir, $thumbs );
1336 foreach ( $thumbs as $thumb ) {
1337 $urls[] = $this->getArchiveThumbUrl( $archiveName, $thumb );
1341 $this->getHookRunner()->onLocalFilePurgeThumbnails( $this, $archiveName, $urls );
1344 $hcu = MediaWikiServices::getInstance()->getHtmlCacheUpdater();
1345 $hcu->purgeUrls( $urls, $hcu::PURGE_PRESEND );
1355 $thumbs = $this->getThumbnails();
1358 $dir = array_shift( $thumbs );
1359 $this->purgeThumbList( $dir, $thumbs );
1363 foreach ( $thumbs as $thumb ) {
1364 $urls[] = $this->getThumbUrl( $thumb );
1368 if ( !empty( $options[
'forThumbRefresh'] ) ) {
1369 $handler = $this->getHandler();
1371 $handler->filterThumbnailPurgeList( $thumbs, $options );
1376 $this->getHookRunner()->onLocalFilePurgeThumbnails( $this,
false, $urls );
1379 $hcu = MediaWikiServices::getInstance()->getHtmlCacheUpdater();
1382 !empty( $options[
'forThumbRefresh'] )
1383 ? $hcu::PURGE_PRESEND
1384 : $hcu::PURGE_INTENT_TXROUND_REFLECTED
1395 $uploadThumbnailRenderMap = MediaWikiServices::getInstance()
1396 ->getMainConfig()->get( MainConfigNames::UploadThumbnailRenderMap );
1400 $sizes = $uploadThumbnailRenderMap;
1403 foreach ( $sizes as $size ) {
1404 if ( $this->isMultipage() ) {
1407 $pageLimit = min( $this->pageCount(), self::MAX_PAGE_RENDER_JOBS );
1412 'transformParams' => [
'width' => $size,
'page' => 1 ],
1413 'enqueueNextPage' =>
true,
1414 'pageLimit' => $pageLimit
1417 } elseif ( $this->isVectorized() || $this->getWidth() > $size ) {
1420 [
'transformParams' => [
'width' => $size ] ]
1426 MediaWikiServices::getInstance()->getJobQueueGroup()->lazyPush( $jobs );
1437 $fileListDebug = strtr(
1438 var_export( $files,
true ),
1441 wfDebug( __METHOD__ .
": $fileListDebug" );
1443 if ( $this->repo->supportsSha1URLs() ) {
1444 $reference = $this->getSha1();
1446 $reference = $this->getName();
1450 foreach ( $files as $file ) {
1451 # Check that the reference (filename or sha1) is part of the thumb name
1452 # This is a basic check to avoid erasing unrelated directories
1453 if ( str_contains( $file, $reference )
1454 || str_contains( $file,
"-thumbnail" )
1456 $purgeList[] =
"{$dir}/{$file}";
1460 # Delete the thumbnails
1461 $this->repo->quickPurgeBatch( $purgeList );
1462 # Clear out the thumbnail directory if empty
1463 $this->repo->quickCleanDir( $dir );
1477 public function getHistory( $limit =
null, $start =
null, $end =
null, $inc =
true ) {
1478 if ( !$this->exists() ) {
1482 $dbr = $this->repo->getReplicaDB();
1483 $oldFileQuery = OldLocalFile::getQueryInfo();
1485 $tables = $oldFileQuery[
'tables'];
1486 $fields = $oldFileQuery[
'fields'];
1487 $join_conds = $oldFileQuery[
'joins'];
1488 $conds = $opts = [];
1489 $eq = $inc ?
'=' :
'';
1490 $conds[] = $dbr->
expr(
'oi_name',
'=', $this->title->getDBkey() );
1493 $conds[] = $dbr->
expr(
'oi_timestamp',
"<$eq", $dbr->
timestamp( $start ) );
1497 $conds[] = $dbr->
expr(
'oi_timestamp',
">$eq", $dbr->
timestamp( $end ) );
1501 $opts[
'LIMIT'] = $limit;
1505 $order = ( !$start && $end !== null ) ?
'ASC' :
'DESC';
1506 $opts[
'ORDER BY'] =
"oi_timestamp $order";
1507 $opts[
'USE INDEX'] = [
'oldimage' =>
'oi_name_timestamp' ];
1509 $this->getHookRunner()->onLocalFile__getHistory( $this, $tables, $fields,
1510 $conds, $opts, $join_conds );
1516 ->caller( __METHOD__ )
1518 ->joinConds( $join_conds )
1522 foreach ( $res as $row ) {
1523 $r[] = $this->repo->newFileFromRow( $row );
1526 if ( $order ==
'ASC' ) {
1527 $r = array_reverse( $r );
1544 if ( !$this->exists() ) {
1548 # Polymorphic function name to distinguish foreign and local fetches
1549 $fname = static::class .
'::' . __FUNCTION__;
1551 $dbr = $this->repo->getReplicaDB();
1553 if ( $this->historyLine == 0 ) {
1554 $queryBuilder = FileSelectQueryBuilder::newForFile( $dbr );
1556 $queryBuilder->fields( [
'oi_archive_name' => $dbr->
addQuotes(
'' ),
'oi_deleted' =>
'0' ] )
1557 ->where( [
'img_name' => $this->title->getDBkey() ] );
1558 $this->historyRes = $queryBuilder->caller( $fname )->fetchResultSet();
1560 if ( $this->historyRes->numRows() == 0 ) {
1561 $this->historyRes =
null;
1565 } elseif ( $this->historyLine == 1 ) {
1566 $queryBuilder = FileSelectQueryBuilder::newForOldFile( $dbr );
1568 $this->historyRes = $queryBuilder->where( [
'oi_name' => $this->title->getDBkey() ] )
1569 ->orderBy(
'oi_timestamp', SelectQueryBuilder::SORT_DESC )
1570 ->caller( $fname )->fetchResultSet();
1572 $this->historyLine++;
1574 return $this->historyRes->fetchObject();
1582 $this->historyLine = 0;
1584 if ( $this->historyRes !==
null ) {
1585 $this->historyRes =
null;
1622 public function upload( $src, $comment, $pageText, $flags = 0, $props =
false,
1623 $timestamp =
false, ?
Authority $uploader =
null, $tags = [],
1624 $createNullRevision =
true, $revert =
false
1626 if ( $this->getRepo()->getReadOnlyReason() !==
false ) {
1627 return $this->readOnlyFatalStatus();
1628 } elseif ( MediaWikiServices::getInstance()->getRevisionStore()->isReadOnly() ) {
1631 return $this->readOnlyFatalStatus();
1634 $srcPath = ( $src instanceof
FSFile ) ? $src->getPath() : $src;
1637 || FileBackend::isStoragePath( $srcPath )
1639 $props = $this->repo->getFileProps( $srcPath );
1641 $mwProps =
new MWFileProps( MediaWikiServices::getInstance()->getMimeAnalyzer() );
1642 $props = $mwProps->getPropsFromPath( $srcPath,
true );
1647 $handler = MediaHandler::getHandler( $props[
'mime'] );
1649 if ( is_string( $props[
'metadata'] ) ) {
1654 $metadata = @unserialize( $props[
'metadata'] );
1656 $metadata = $props[
'metadata'];
1659 if ( is_array( $metadata ) ) {
1660 $options[
'headers'] = $handler->getContentHeaders( $metadata );
1663 $options[
'headers'] = [];
1667 $comment = trim( $comment );
1669 $status = $this->publish( $src, $flags, $options );
1671 if ( $status->successCount >= 2 ) {
1678 $oldver = $status->value;
1680 $uploadStatus = $this->recordUpload3(
1684 $uploader ?? RequestContext::getMain()->getAuthority(),
1688 $createNullRevision,
1691 if ( !$uploadStatus->isOK() ) {
1692 if ( $uploadStatus->hasMessage(
'filenotfound' ) ) {
1694 $status->fatal(
'filenotfound', $srcPath );
1696 $status->merge( $uploadStatus );
1728 bool $createNullRevision =
true,
1729 bool $revert =
false
1731 $dbw = $this->repo->getPrimaryDB();
1733 # Imports or such might force a certain timestamp; otherwise we generate
1734 # it and can fudge it slightly to keep (name,timestamp) unique on re-upload.
1735 if ( $timestamp ===
false ) {
1736 $timestamp = $dbw->timestamp();
1737 $allowTimeKludge =
true;
1739 $allowTimeKludge =
false;
1742 $props = $props ?: $this->repo->getFileProps( $this->getVirtualUrl() );
1743 $props[
'description'] = $comment;
1744 $props[
'timestamp'] =
wfTimestamp( TS_MW, $timestamp );
1745 $this->setProps( $props );
1747 # Fail now if the file isn't there
1748 if ( !$this->fileExists ) {
1749 wfDebug( __METHOD__ .
": File " . $this->getRel() .
" went missing!" );
1751 return Status::newFatal(
'filenotfound', $this->getRel() );
1754 $mimeAnalyzer = MediaWikiServices::getInstance()->getMimeAnalyzer();
1755 if ( !$mimeAnalyzer->isValidMajorMimeType( $this->major_mime ) ) {
1756 $this->major_mime =
'unknown';
1759 $actorNormalizaton = MediaWikiServices::getInstance()->getActorNormalization();
1761 $dbw->startAtomic( __METHOD__ );
1763 $actorId = $actorNormalizaton->acquireActorId( $performer->
getUser(), $dbw );
1764 $this->user = $performer->
getUser();
1766 # Test to see if the row exists using INSERT IGNORE
1767 # This avoids race conditions by locking the row until the commit, and also
1768 # doesn't deadlock. SELECT FOR UPDATE causes a deadlock for every race condition.
1769 $commentStore = MediaWikiServices::getInstance()->getCommentStore();
1770 $commentFields = $commentStore->insert( $dbw,
'img_description', $comment );
1771 $actorFields = [
'img_actor' => $actorId ];
1772 $dbw->newInsertQueryBuilder()
1773 ->insertInto(
'image' )
1776 'img_name' => $this->
getName(),
1777 'img_size' => $this->size,
1778 'img_width' => intval( $this->width ),
1779 'img_height' => intval( $this->height ),
1780 'img_bits' => $this->bits,
1781 'img_media_type' => $this->media_type,
1782 'img_major_mime' => $this->major_mime,
1783 'img_minor_mime' => $this->minor_mime,
1784 'img_timestamp' => $dbw->timestamp( $timestamp ),
1785 'img_metadata' => $this->getMetadataForDb( $dbw ),
1786 'img_sha1' => $this->sha1
1787 ] + $commentFields + $actorFields )
1788 ->caller( __METHOD__ )->execute();
1789 $reupload = ( $dbw->affectedRows() == 0 );
1792 $row = $dbw->newSelectQueryBuilder()
1793 ->select( [
'img_timestamp',
'img_sha1' ] )
1795 ->where( [
'img_name' => $this->
getName() ] )
1796 ->caller( __METHOD__ )->fetchRow();
1798 if ( $row && $row->img_sha1 === $this->sha1 ) {
1799 $dbw->endAtomic( __METHOD__ );
1800 wfDebug( __METHOD__ .
": File " . $this->getRel() .
" already exists!" );
1802 return Status::newFatal(
'fileexists-no-change', $title->getPrefixedText() );
1805 if ( $allowTimeKludge ) {
1806 # Use LOCK IN SHARE MODE to ignore any transaction snapshotting
1807 $lUnixtime = $row ? (int)
wfTimestamp( TS_UNIX, $row->img_timestamp ) : false;
1808 # Avoid a timestamp that is not newer than the last version
1809 # TODO: the image/oldimage tables should be like page/revision with an ID field
1810 if ( $lUnixtime && (
int)
wfTimestamp( TS_UNIX, $timestamp ) <= $lUnixtime ) {
1812 $timestamp = $dbw->timestamp( $lUnixtime + 1 );
1813 $this->timestamp =
wfTimestamp( TS_MW, $timestamp );
1817 $tables = [
'image' ];
1819 'oi_name' =>
'img_name',
1820 'oi_archive_name' => $dbw->addQuotes( $oldver ),
1821 'oi_size' =>
'img_size',
1822 'oi_width' =>
'img_width',
1823 'oi_height' =>
'img_height',
1824 'oi_bits' =>
'img_bits',
1825 'oi_description_id' =>
'img_description_id',
1826 'oi_timestamp' =>
'img_timestamp',
1827 'oi_metadata' =>
'img_metadata',
1828 'oi_media_type' =>
'img_media_type',
1829 'oi_major_mime' =>
'img_major_mime',
1830 'oi_minor_mime' =>
'img_minor_mime',
1831 'oi_sha1' =>
'img_sha1',
1832 'oi_actor' =>
'img_actor',
1836 # (T36993) Note: $oldver can be empty here, if the previous
1837 # version of the file was broken. Allow registration of the new
1838 # version to continue anyway, because that's better than having
1839 # an image that's not fixable by user operations.
1840 # Collision, this is an update of a file
1841 # Insert previous contents into oldimage
1842 $dbw->insertSelect(
'oldimage', $tables, $fields,
1843 [
'img_name' => $this->
getName() ], __METHOD__, [], [], $joins );
1845 # Update the current image row
1846 $dbw->newUpdateQueryBuilder()
1849 'img_size' => $this->size,
1850 'img_width' => intval( $this->width ),
1851 'img_height' => intval( $this->height ),
1852 'img_bits' => $this->bits,
1853 'img_media_type' => $this->media_type,
1854 'img_major_mime' => $this->major_mime,
1855 'img_minor_mime' => $this->minor_mime,
1856 'img_timestamp' => $dbw->timestamp( $timestamp ),
1857 'img_metadata' => $this->getMetadataForDb( $dbw ),
1858 'img_sha1' => $this->sha1
1859 ] + $commentFields + $actorFields )
1860 ->where( [
'img_name' => $this->
getName() ] )
1861 ->caller( __METHOD__ )->execute();
1865 $descId = $descTitle->getArticleID();
1866 $wikiPage = MediaWikiServices::getInstance()->getWikiPageFactory()->newFromTitle( $descTitle );
1868 throw new UnexpectedValueException(
'Cannot obtain instance of WikiFilePage for ' . $this->
getName()
1869 .
', got instance of ' . get_class( $wikiPage ) );
1871 $wikiPage->setFile( $this );
1875 $logAction =
'revert';
1876 } elseif ( $reupload ) {
1877 $logAction =
'overwrite';
1879 $logAction =
'upload';
1883 $logEntry->setTimestamp( $this->timestamp );
1884 $logEntry->setPerformer( $performer->
getUser() );
1885 $logEntry->setComment( $comment );
1886 $logEntry->setTarget( $descTitle );
1889 $logEntry->setParameters(
1891 'img_sha1' => $this->sha1,
1892 'img_timestamp' => $timestamp,
1901 $logId = $logEntry->insert();
1903 if ( $descTitle->exists() ) {
1904 if ( $createNullRevision ) {
1905 $services = MediaWikiServices::getInstance();
1906 $revStore = $services->getRevisionStore();
1908 $formatter = $services->getLogFormatterFactory()->newFromEntry( $logEntry );
1909 $formatter->setContext( RequestContext::newExtraneousContext( $descTitle ) );
1910 $editSummary = $formatter->getPlainActionText();
1911 $summary = CommentStoreComment::newUnsavedComment( $editSummary );
1912 $nullRevRecord = $revStore->newNullRevision(
1920 if ( $nullRevRecord ) {
1921 $inserted = $revStore->insertRevisionOn( $nullRevRecord, $dbw );
1923 $this->getHookRunner()->onRevisionFromEditComplete(
1926 $inserted->getParentId(),
1931 $wikiPage->updateRevisionOn( $dbw, $inserted );
1933 $logEntry->setAssociatedRevId( $inserted->getId() );
1937 $newPageContent =
null;
1940 $newPageContent = ContentHandler::makeContent( $pageText, $descTitle );
1946 $dbw->endAtomic( __METHOD__ );
1947 $fname = __METHOD__;
1949 # Do some cache purges after final commit so that:
1950 # a) Changes are more likely to be seen post-purge
1951 # b) They won't cause rollback of the log publish/update above
1956 $reupload, $wikiPage, $newPageContent, $comment, $performer,
1957 $logEntry, $logId, $descId, $tags, $fname
1959 # Update memcache after the commit
1960 $this->invalidateCache();
1962 $updateLogPage =
false;
1963 if ( $newPageContent ) {
1964 # New file page; create the description page.
1965 # There's already a log entry, so don't make a second RC entry
1966 # CDN and file cache for the description page are purged by doUserEditContent.
1967 $status = $wikiPage->doUserEditContent(
1974 $revRecord = $status->getNewRevision();
1977 $logEntry->setAssociatedRevId( $revRecord->getId() );
1981 $updateLogPage = $revRecord->getPageId();
1984 # Existing file page: invalidate description page cache
1985 $title = $wikiPage->getTitle();
1986 $title->invalidateCache();
1987 $hcu = MediaWikiServices::getInstance()->getHtmlCacheUpdater();
1988 $hcu->purgeTitleUrls( $title, $hcu::PURGE_INTENT_TXROUND_REFLECTED );
1989 # Allow the new file version to be patrolled from the page footer
1993 # Update associated rev id. This should be done by $logEntry->insert() earlier,
1994 # but setAssociatedRevId() wasn't called at that point yet...
1995 $logParams = $logEntry->getParameters();
1996 $logParams[
'associated_rev_id'] = $logEntry->getAssociatedRevId();
1998 if ( $updateLogPage ) {
1999 # Also log page, in case where we just created it above
2000 $update[
'log_page'] = $updateLogPage;
2002 $this->getRepo()->getPrimaryDB()->newUpdateQueryBuilder()
2003 ->update(
'logging' )
2005 ->where( [
'log_id' => $logId ] )
2006 ->caller( $fname )->execute();
2008 $this->getRepo()->getPrimaryDB()->newInsertQueryBuilder()
2009 ->insertInto(
'log_search' )
2011 'ls_field' =>
'associated_rev_id',
2012 'ls_value' => (
string)$logEntry->getAssociatedRevId(),
2013 'ls_log_id' => $logId,
2015 ->caller( $fname )->execute();
2017 # Add change tags, if any
2019 $logEntry->addTags( $tags );
2022 # Uploads can be patrolled
2023 $logEntry->setIsPatrollable(
true );
2025 # Now that the log entry is up-to-date, make an RC entry.
2026 $logEntry->publish( $logId );
2028 # Run hook for other updates (typically more cache purging)
2029 $this->getHookRunner()->onFileUpload( $this, $reupload, !$newPageContent );
2032 # Delete old thumbnails
2033 $this->purgeThumbnails();
2034 # Remove the old file from the CDN cache
2035 $hcu = MediaWikiServices::getInstance()->getHtmlCacheUpdater();
2036 $hcu->purgeUrls( $this->getUrl(), $hcu::PURGE_INTENT_TXROUND_REFLECTED );
2038 # Update backlink pages pointing to this title if created
2039 $blcFactory = MediaWikiServices::getInstance()->getBacklinkCacheFactory();
2040 LinksUpdate::queueRecursiveJobsForTable(
2044 $performer->
getUser()->getName(),
2045 $blcFactory->getBacklinkCache( $this->getTitle() )
2049 $this->prerenderThumbnails();
2053 # Invalidate cache for all pages using this file
2057 [
'causeAction' =>
'file-upload',
'causeAgent' => $performer->
getUser()->getName() ]
2065 $dbw->onTransactionCommitOrIdle(
static function () use ( $reupload, $purgeUpdate, $cacheUpdateJob ) {
2066 DeferredUpdates::addUpdate( $purgeUpdate, DeferredUpdates::PRESEND );
2070 DeferredUpdates::addUpdate( SiteStatsUpdate::factory( [
'images' => 1 ] ) );
2073 MediaWikiServices::getInstance()->getJobQueueGroup()->lazyPush( $cacheUpdateJob );
2076 return Status::newGood();
2095 public function publish( $src, $flags = 0, array $options = [] ) {
2096 return $this->publishTo( $src, $this->getRel(), $flags, $options );
2115 protected function publishTo( $src, $dstRel, $flags = 0, array $options = [] ) {
2116 $srcPath = ( $src instanceof
FSFile ) ? $src->getPath() : $src;
2118 $repo = $this->getRepo();
2119 if ( $repo->getReadOnlyReason() !==
false ) {
2120 return $this->readOnlyFatalStatus();
2123 $status = $this->acquireFileLock();
2124 if ( !$status->isOK() ) {
2128 if ( $this->isOld() ) {
2129 $archiveRel = $dstRel;
2130 $archiveName = basename( $archiveRel );
2132 $archiveName =
wfTimestamp( TS_MW ) .
'!' . $this->getName();
2133 $archiveRel = $this->getArchiveRel( $archiveName );
2136 if ( $repo->hasSha1Storage() ) {
2138 ? $repo->getFileSha1( $srcPath )
2139 : FSFile::getSha1Base36FromPath( $srcPath );
2141 $wrapperBackend = $repo->getBackend();
2142 '@phan-var FileBackendDBRepoWrapper $wrapperBackend';
2143 $dst = $wrapperBackend->getPathForSHA1( $sha1 );
2144 $status = $repo->quickImport( $src, $dst );
2149 if ( $this->exists() ) {
2150 $status->value = $archiveName;
2154 $status = $repo->publish( $srcPath, $dstRel, $archiveRel, $flags, $options );
2156 if ( $status->value ==
'new' ) {
2157 $status->value =
'';
2159 $status->value = $archiveName;
2163 $this->releaseFileLock();
2186 $localRepo = MediaWikiServices::getInstance()->getRepoGroup()->getLocalRepo();
2187 if ( $this->getRepo()->getReadOnlyReason() !==
false ) {
2188 return $this->readOnlyFatalStatus();
2191 wfDebugLog(
'imagemove',
"Got request to move {$this->name} to " . $target->getText() );
2194 $status = $batch->addCurrent();
2195 if ( !$status->isOK() ) {
2198 $archiveNames = $batch->addOlds();
2199 $status = $batch->execute();
2201 wfDebugLog(
'imagemove',
"Finished moving {$this->name}" );
2204 $oldTitleFile = $localRepo->newFile( $this->title );
2205 $newTitleFile = $localRepo->newFile( $target );
2206 DeferredUpdates::addUpdate(
2208 $this->getRepo()->getPrimaryDB(),
2210 static function () use ( $oldTitleFile, $newTitleFile, $archiveNames ) {
2211 $oldTitleFile->purgeEverything();
2212 foreach ( $archiveNames as $archiveName ) {
2214 '@phan-var OldLocalFile $oldTitleFile';
2215 $oldTitleFile->purgeOldThumbnails( $archiveName );
2217 $newTitleFile->purgeEverything();
2220 DeferredUpdates::PRESEND
2223 if ( $status->isOK() ) {
2225 $this->title = $target;
2228 $this->hashPath =
null;
2251 if ( $this->getRepo()->getReadOnlyReason() !==
false ) {
2252 return $this->readOnlyFatalStatus();
2257 $batch->addCurrent();
2259 $archiveNames = $batch->addOlds();
2260 $status = $batch->execute();
2262 if ( $status->isOK() ) {
2263 DeferredUpdates::addUpdate( SiteStatsUpdate::factory( [
'images' => -1 ] ) );
2267 DeferredUpdates::addUpdate(
2269 $this->getRepo()->getPrimaryDB(),
2271 function () use ( $archiveNames ) {
2272 $this->purgeEverything();
2273 foreach ( $archiveNames as $archiveName ) {
2274 $this->purgeOldThumbnails( $archiveName );
2278 DeferredUpdates::PRESEND
2283 foreach ( $archiveNames as $archiveName ) {
2284 $purgeUrls[] = $this->getArchiveUrl( $archiveName );
2287 $hcu = MediaWikiServices::getInstance()->getHtmlCacheUpdater();
2288 $hcu->purgeUrls( $purgeUrls, $hcu::PURGE_INTENT_TXROUND_REFLECTED );
2311 if ( $this->getRepo()->getReadOnlyReason() !==
false ) {
2312 return $this->readOnlyFatalStatus();
2317 $batch->addOld( $archiveName );
2318 $status = $batch->execute();
2320 $this->purgeOldThumbnails( $archiveName );
2321 if ( $status->isOK() ) {
2322 $this->purgeDescription();
2325 $url = $this->getArchiveUrl( $archiveName );
2326 $hcu = MediaWikiServices::getInstance()->getHtmlCacheUpdater();
2327 $hcu->purgeUrls(
$url, $hcu::PURGE_INTENT_TXROUND_REFLECTED );
2344 public function restore( $versions = [], $unsuppress =
false ) {
2345 if ( $this->getRepo()->getReadOnlyReason() !==
false ) {
2346 return $this->readOnlyFatalStatus();
2354 $batch->addIds( $versions );
2356 $status = $batch->execute();
2357 if ( $status->isGood() ) {
2358 $cleanupStatus = $batch->cleanup();
2359 $cleanupStatus->successCount = 0;
2360 $cleanupStatus->failCount = 0;
2361 $status->merge( $cleanupStatus );
2379 return $this->title ? $this->title->getLocalURL() :
false;
2392 if ( !$this->title ) {
2396 $services = MediaWikiServices::getInstance();
2397 $page = $services->getPageStore()->getPageByReference( $this->
getTitle() );
2403 $parserOptions = ParserOptions::newFromUserAndLang(
2404 RequestContext::getMain()->getUser(),
2408 $parserOptions = ParserOptions::newFromContext( RequestContext::getMain() );
2411 $parseStatus = $services->getParserOutputAccess()
2412 ->getParserOutput( $page, $parserOptions );
2414 if ( !$parseStatus->isGood() ) {
2418 return $parseStatus->getValue()->getText();
2430 if ( $audience === self::FOR_PUBLIC && $this->isDeleted( self::DELETED_USER ) ) {
2432 } elseif ( $audience === self::FOR_THIS_USER && !$this->userCan( self::DELETED_USER, $performer ) ) {
2447 if ( $audience == self::FOR_PUBLIC && $this->isDeleted( self::DELETED_COMMENT ) ) {
2449 } elseif ( $audience == self::FOR_THIS_USER && !$this->userCan( self::DELETED_COMMENT, $performer ) ) {
2452 return $this->description;
2463 return $this->timestamp;
2471 if ( !$this->exists() ) {
2478 if ( $this->descriptionTouched ===
null ) {
2479 $touched = $this->repo->getReplicaDB()->newSelectQueryBuilder()
2480 ->select(
'page_touched' )
2482 ->where( [
'page_namespace' => $this->title->getNamespace() ] )
2483 ->andWhere( [
'page_title' => $this->title->getDBkey() ] )
2484 ->caller( __METHOD__ )->fetchField();
2485 $this->descriptionTouched = $touched ?
wfTimestamp( TS_MW, $touched ) :
false;
2488 return $this->descriptionTouched;
2507 return $this->extraDataLoaded
2508 && strlen( serialize( $this->metadataArray ) ) <= self::CACHE_FIELD_MAX_LEN;
2520 return Status::wrap( $this->getRepo()->getBackend()->lockFiles(
2521 [ $this->getPath() ], LockManager::LOCK_EX, $timeout
2532 return Status::wrap( $this->getRepo()->getBackend()->unlockFiles(
2533 [ $this->getPath() ], LockManager::LOCK_EX
2548 if ( !$this->locked ) {
2549 $logger = LoggerFactory::getInstance(
'LocalFile' );
2551 $dbw = $this->repo->getPrimaryDB();
2552 $makesTransaction = !$dbw->trxLevel();
2553 $dbw->startAtomic( self::ATOMIC_SECTION_LOCK );
2557 $status = $this->acquireFileLock( 10 );
2558 if ( !$status->isGood() ) {
2559 $dbw->endAtomic( self::ATOMIC_SECTION_LOCK );
2560 $logger->warning(
"Failed to lock '{file}'", [
'file' => $this->name ] );
2566 $dbw->onTransactionResolution(
2567 function () use ( $logger ) {
2568 $status = $this->releaseFileLock();
2569 if ( !$status->isGood() ) {
2570 $logger->error(
"Failed to unlock '{file}'", [
'file' => $this->name ] );
2576 $this->lockedOwnTrx = $makesTransaction;
2581 return $this->lockedOwnTrx;
2595 if ( $this->locked ) {
2597 if ( !$this->locked ) {
2598 $dbw = $this->repo->getPrimaryDB();
2599 $dbw->endAtomic( self::ATOMIC_SECTION_LOCK );
2600 $this->lockedOwnTrx =
false;
2609 return $this->getRepo()->newFatal(
'filereadonlyerror', $this->getName(),
2610 $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.
getCacheKey()
Get the cache key used to store status.
static purgePatrolFooterCache( $articleID)
Purge the cache used to check if it is worth showing the patrol footer For example,...
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...
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.
Title string false $title
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=[])
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.
loadMetadataFromDbFieldValue(IReadableDatabase $db, $metadataBlob)
Unserialize a metadata blob which came from the database and store it in $this.
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().
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 IReadable...
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.
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.
getMetadataForDb(IReadableDatabase $db)
Serialize the metadata array for insertion into img_metadata, oi_metadata or fa_metadata.
purgeThumbList( $dir, $files)
Delete a list of thumbnails visible at urls.
getDescriptionText(?Language $lang=null)
Get the HTML text of the description page This is not used by ImagePage for local files,...
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.
getHeight( $page=1)
Return the height of the image.
prerenderThumbnails()
Prerenders a configurable set of thumbnails.
getDescription( $audience=self::FOR_PUBLIC, ?Authority $performer=null)
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.
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.
upload( $src, $comment, $pageText, $flags=0, $props=false, $timestamp=false, ?Authority $uploader=null, $tags=[], $createNullRevision=true, $revert=false)
getHashPath inherited
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.
getUploader(int $audience=self::FOR_PUBLIC, ?Authority $performer=null)
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)
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 content handler knows how do deal with a specific type of content on a wiki page.
Group all the pieces relevant to the context of a request into one instance.
A class containing constants representing the names of configuration variables.
Job for asynchronous rendering of thumbnails, e.g.
Special handling for representing file pages.