70 private const VERSION = 13;
72 private const CACHE_FIELD_MAX_LEN = 1000;
75 private const MDS_EMPTY =
'empty';
78 private const MDS_LEGACY =
'legacy';
81 private const MDS_PHP =
'php';
84 private const MDS_JSON =
'json';
87 private const MAX_PAGE_RENDER_JOBS = 50;
111 protected $metadataArray = [];
122 protected $metadataBlobs = [];
130 protected $unloadedMetadataBlobs = [];
136 protected $dataLoaded =
false;
139 protected $extraDataLoaded =
false;
145 protected $repoClass = LocalRepo::class;
148 private $historyLine = 0;
151 private $historyRes =
null;
166 private $description;
169 private $descriptionTouched;
181 private $lockedOwnTrx;
187 private $metadataStorageHelper;
190 private const LOAD_ALL = 16;
192 private const ATOMIC_SECTION_LOCK =
'LocalFile::lockingTransaction';
226 $file->loadFromRow( $row );
242 public static function newFromKey( $sha1, $repo, $timestamp =
false ) {
243 $dbr =
$repo->getReplicaDB();
244 $queryBuilder = FileSelectQueryBuilder::newForFile( $dbr );
246 $queryBuilder->where( [
'img_sha1' => $sha1 ] );
249 $queryBuilder->andWhere( [
'img_timestamp' => $dbr->
timestamp( $timestamp ) ] );
252 $row = $queryBuilder->caller( __METHOD__ )->fetchRow();
254 return static::newFromRow( $row,
$repo );
281 $dbr = MediaWikiServices::getInstance()->getConnectionProvider()->getReplicaDatabase();
282 $queryInfo = FileSelectQueryBuilder::newForFile( $dbr, $options )->getQueryInfo();
285 'tables' => $queryInfo[
'tables'],
286 'fields' => $queryInfo[
'fields'],
287 'joins' => $queryInfo[
'join_conds'],
320 return $this->repo->getSharedCacheKey(
'file', sha1( $this->
getName() ) );
326 private function loadFromCache() {
327 $this->dataLoaded =
false;
328 $this->extraDataLoaded =
false;
332 $this->loadFromDB( IDBAccessObject::READ_NORMAL );
337 $cache = MediaWikiServices::getInstance()->getMainWANObjectCache();
338 $cachedValues = $cache->getWithSetCallback(
341 function ( $oldValue, &$ttl, array &$setOpts ) use ( $cache ) {
342 $setOpts += Database::getCacheSetOptions( $this->repo->getReplicaDB() );
344 $this->
loadFromDB( IDBAccessObject::READ_NORMAL );
348 $cacheVal[
'fileExists'] = $this->fileExists;
349 if ( $this->fileExists ) {
350 foreach ( $fields as $field ) {
351 $cacheVal[$field] = $this->$field;
355 $cacheVal[
'user'] = $this->user->getId();
356 $cacheVal[
'user_text'] = $this->user->getName();
360 if ( $this->metadataBlobs ) {
361 $cacheVal[
'metadata'] = array_diff_key(
362 $this->metadataArray, $this->metadataBlobs );
364 $cacheVal[
'metadataBlobs'] = $this->metadataBlobs;
366 $cacheVal[
'metadata'] = $this->metadataArray;
373 if ( isset( $cacheVal[$field] )
374 && strlen( serialize( $cacheVal[$field] ) ) > 100 * 1024
376 unset( $cacheVal[$field] );
377 if ( $field ===
'metadata' ) {
378 unset( $cacheVal[
'metadataBlobs'] );
383 if ( $this->fileExists ) {
384 $ttl = $cache->adaptiveTTL( (
int)
wfTimestamp( TS_UNIX, $this->timestamp ), $ttl );
386 $ttl = $cache::TTL_DAY;
391 [
'version' => self::VERSION ]
394 $this->fileExists = $cachedValues[
'fileExists'];
395 if ( $this->fileExists ) {
399 $this->dataLoaded =
true;
400 $this->extraDataLoaded =
true;
402 $this->extraDataLoaded = $this->extraDataLoaded && isset( $cachedValues[$field] );
415 $this->repo->getPrimaryDB()->onTransactionPreCommitOrIdle(
416 static function () use ( $key ) {
417 MediaWikiServices::getInstance()->getMainWANObjectCache()->delete( $key );
443 if ( $prefix !==
'' ) {
444 throw new InvalidArgumentException(
445 __METHOD__ .
' with a non-empty prefix is no longer supported.'
453 return [
'size',
'width',
'height',
'bits',
'media_type',
454 'major_mime',
'minor_mime',
'timestamp',
'sha1',
'description' ];
465 if ( $prefix !==
'' ) {
466 throw new InvalidArgumentException(
467 __METHOD__ .
' with a non-empty prefix is no longer supported.'
472 return [
'metadata' ];
481 $fname = static::class .
'::' . __FUNCTION__;
483 # Unconditionally set loaded=true, we don't want the accessors constantly rechecking
484 $this->dataLoaded =
true;
485 $this->extraDataLoaded =
true;
487 $dbr = ( $flags & IDBAccessObject::READ_LATEST )
488 ? $this->repo->getPrimaryDB()
489 : $this->repo->getReplicaDB();
490 $queryBuilder = FileSelectQueryBuilder::newForFile( $dbr );
492 $queryBuilder->where( [
'img_name' => $this->
getName() ] );
493 $row = $queryBuilder->caller( $fname )->fetchRow();
498 $this->fileExists =
false;
508 if ( !$this->title ) {
512 $fname = static::class .
'::' . __FUNCTION__;
514 # Unconditionally set loaded=true, we don't want the accessors constantly rechecking
515 $this->extraDataLoaded =
true;
517 $db = $this->repo->getReplicaDB();
518 $fieldMap = $this->loadExtraFieldsWithTimestamp( $db, $fname );
520 $db = $this->repo->getPrimaryDB();
521 $fieldMap = $this->loadExtraFieldsWithTimestamp( $db, $fname );
525 if ( isset( $fieldMap[
'metadata'] ) ) {
529 throw new RuntimeException(
"Could not find data for image '{$this->getName()}'." );
538 private function loadExtraFieldsWithTimestamp(
IReadableDatabase $dbr, $fname ) {
541 $queryBuilder = FileSelectQueryBuilder::newForFile( $dbr, [
'omit-nonlazy' ] );
542 $queryBuilder->where( [
'img_name' => $this->getName() ] )
543 ->andWhere( [
'img_timestamp' => $dbr->
timestamp( $this->getTimestamp() ) ] );
544 $row = $queryBuilder->caller( $fname )->fetchRow();
546 $fieldMap = $this->unprefixRow( $row,
'img_' );
548 # File may have been uploaded over in the meantime; check the old versions
549 $queryBuilder = FileSelectQueryBuilder::newForOldFile( $dbr, [
'omit-nonlazy' ] );
550 $row = $queryBuilder->where( [
'oi_name' => $this->
getName() ] )
551 ->andWhere( [
'oi_timestamp' => $dbr->
timestamp( $this->getTimestamp() ) ] )
552 ->caller( __METHOD__ )->fetchRow();
567 $array = (array)$row;
568 $prefixLength = strlen( $prefix );
571 if ( substr( array_key_first( $array ), 0, $prefixLength ) !== $prefix ) {
572 throw new InvalidArgumentException( __METHOD__ .
': incorrect $prefix parameter' );
576 foreach ( $array as
$name => $value ) {
577 $decoded[substr(
$name, $prefixLength )] = $value;
599 $this->dataLoaded =
true;
603 $this->name = $unprefixed[
'name'];
604 $this->media_type = $unprefixed[
'media_type'];
606 $services = MediaWikiServices::getInstance();
607 $this->description = $services->getCommentStore()
608 ->getComment(
"{$prefix}description", $row )->text;
610 $this->user = $services->getUserFactory()->newFromAnyId(
611 $unprefixed[
'user'] ??
null,
612 $unprefixed[
'user_text'] ??
null,
613 $unprefixed[
'actor'] ??
null
616 $this->timestamp =
wfTimestamp( TS_MW, $unprefixed[
'timestamp'] );
619 $this->repo->getReplicaDB(), $unprefixed[
'metadata'] );
621 if ( empty( $unprefixed[
'major_mime'] ) ) {
622 $this->major_mime =
'unknown';
623 $this->minor_mime =
'unknown';
624 $this->mime =
'unknown/unknown';
626 if ( !$unprefixed[
'minor_mime'] ) {
627 $unprefixed[
'minor_mime'] =
'unknown';
629 $this->major_mime = $unprefixed[
'major_mime'];
630 $this->minor_mime = $unprefixed[
'minor_mime'];
631 $this->mime = $unprefixed[
'major_mime'] .
'/' . $unprefixed[
'minor_mime'];
635 $this->sha1 = rtrim( $unprefixed[
'sha1'],
"\0" );
641 $this->size = +$unprefixed[
'size'];
642 $this->width = +$unprefixed[
'width'];
643 $this->height = +$unprefixed[
'height'];
644 $this->bits = +$unprefixed[
'bits'];
647 $extraFields = array_diff(
648 array_keys( $unprefixed ),
650 'name',
'media_type',
'description_text',
'description_data',
651 'description_cid',
'user',
'user_text',
'actor',
'timestamp',
652 'metadata',
'major_mime',
'minor_mime',
'sha1',
'size',
'width',
656 if ( $extraFields ) {
658 'Passing extra fields (' .
659 implode(
', ', $extraFields )
660 .
') to ' . __METHOD__ .
' was deprecated in MediaWiki 1.37. ' .
661 'Property assignment will be removed in a later version.',
663 foreach ( $extraFields as $field ) {
664 $this->$field = $unprefixed[$field];
668 $this->fileExists =
true;
676 public function load( $flags = 0 ) {
677 if ( !$this->dataLoaded ) {
678 if ( $flags & IDBAccessObject::READ_LATEST ) {
681 $this->loadFromCache();
685 if ( ( $flags & self::LOAD_ALL ) && !$this->extraDataLoaded ) {
696 if ( MediaWikiServices::getInstance()->getReadOnlyMode()->isReadOnly() || $this->upgrading ) {
701 $reserialize =
false;
702 if ( $this->media_type ===
null || $this->mime ==
'image/svg' ) {
708 if ( $validity === MediaHandler::METADATA_BAD ) {
710 } elseif ( $validity === MediaHandler::METADATA_COMPATIBLE
711 && $this->repo->isMetadataUpdateEnabled()
714 } elseif ( $this->repo->isJsonMetadataEnabled()
715 && $this->repo->isMetadataReserializeEnabled()
717 if ( $this->repo->isSplitMetadataEnabled() && $this->isMetadataOversize() ) {
719 } elseif ( $this->metadataSerializationFormat !== self::MDS_EMPTY &&
720 $this->metadataSerializationFormat !== self::MDS_JSON ) {
727 if ( $upgrade || $reserialize ) {
728 $this->upgrading =
true;
730 DeferredUpdates::addCallableUpdate(
function () use ( $upgrade ) {
731 $this->upgrading =
false;
749 return $this->upgraded;
757 $dbw = $this->repo->getPrimaryDB();
761 $freshnessCondition = [
'img_timestamp' => $dbw->timestamp( $this->
getTimestamp() ) ];
765 # Don't destroy file info of missing files
766 if ( !$this->fileExists ) {
767 wfDebug( __METHOD__ .
": file does not exist, aborting" );
772 [ $major, $minor ] = self::splitMime( $this->mime );
774 wfDebug( __METHOD__ .
': upgrading ' . $this->
getName() .
" to the current schema" );
776 $dbw->newUpdateQueryBuilder()
779 'img_size' => $this->size,
780 'img_width' => $this->width,
781 'img_height' => $this->height,
782 'img_bits' => $this->bits,
783 'img_media_type' => $this->media_type,
784 'img_major_mime' => $major,
785 'img_minor_mime' => $minor,
787 'img_sha1' => $this->sha1,
789 ->where( [
'img_name' => $this->
getName() ] )
790 ->andWhere( $freshnessCondition )
791 ->caller( __METHOD__ )->execute();
795 $this->upgraded =
true;
803 if ( MediaWikiServices::getInstance()->getReadOnlyMode()->isReadOnly() ) {
806 $dbw = $this->repo->getPrimaryDB();
807 $dbw->newUpdateQueryBuilder()
811 'img_name' => $this->name,
812 'img_timestamp' => $dbw->timestamp( $this->timestamp ),
814 ->caller( __METHOD__ )->execute();
815 $this->upgraded =
true;
830 $this->dataLoaded =
true;
832 $fields[] =
'fileExists';
834 foreach ( $fields as $field ) {
835 if ( isset( $info[$field] ) ) {
836 $this->$field = $info[$field];
841 if ( isset( $info[
'user'] ) &&
842 isset( $info[
'user_text'] ) &&
843 $info[
'user_text'] !==
''
849 if ( isset( $info[
'major_mime'] ) ) {
850 $this->mime =
"{$info['major_mime']}/{$info['minor_mime']}";
851 } elseif ( isset( $info[
'mime'] ) ) {
852 $this->mime = $info[
'mime'];
853 [ $this->major_mime, $this->minor_mime ] = self::splitMime( $this->mime );
856 if ( isset( $info[
'metadata'] ) ) {
857 if ( is_string( $info[
'metadata'] ) ) {
859 } elseif ( is_array( $info[
'metadata'] ) ) {
860 $this->metadataArray = $info[
'metadata'];
861 if ( isset( $info[
'metadataBlobs'] ) ) {
862 $this->metadataBlobs = $info[
'metadataBlobs'];
863 $this->unloadedMetadataBlobs = array_diff_key(
864 $this->metadataBlobs,
868 $this->metadataBlobs = [];
869 $this->unloadedMetadataBlobs = [];
872 $logger = LoggerFactory::getInstance(
'LocalFile' );
873 $logger->warning( __METHOD__ .
' given invalid metadata of type ' .
874 gettype( $info[
'metadata'] ) );
875 $this->metadataArray = [];
877 $this->extraDataLoaded =
true;
897 if ( $this->missing ===
null ) {
898 $fileExists = $this->repo->fileExists( $this->
getVirtualUrl() );
899 $this->missing = !$fileExists;
902 return $this->missing;
927 return $dim[
'width'];
960 return $dim[
'height'];
967 return $this->height;
979 if ( !$this->title ) {
983 $pageId = $this->title->getArticleID();
986 $url = $this->repo->makeUrl( [
'curid' => $pageId ] );
987 if (
$url !==
false ) {
1004 } elseif ( array_keys( $data ) === [
'_error' ] ) {
1006 return $data[
'_error'];
1019 $this->
load( self::LOAD_ALL );
1020 if ( $this->unloadedMetadataBlobs ) {
1022 array_unique( array_merge(
1023 array_keys( $this->metadataArray ),
1024 array_keys( $this->unloadedMetadataBlobs )
1028 return $this->metadataArray;
1032 $this->load( self::LOAD_ALL );
1035 foreach ( $itemNames as $itemName ) {
1036 if ( array_key_exists( $itemName, $this->metadataArray ) ) {
1037 $result[$itemName] = $this->metadataArray[$itemName];
1038 } elseif ( isset( $this->unloadedMetadataBlobs[$itemName] ) ) {
1039 $addresses[$itemName] = $this->unloadedMetadataBlobs[$itemName];
1044 $resultFromBlob = $this->metadataStorageHelper->getMetadataFromBlobStore( $addresses );
1045 foreach ( $addresses as $itemName => $address ) {
1046 unset( $this->unloadedMetadataBlobs[$itemName] );
1047 $value = $resultFromBlob[$itemName] ??
null;
1048 if ( $value !==
null ) {
1049 $result[$itemName] = $value;
1050 $this->metadataArray[$itemName] = $value;
1069 $this->load( self::LOAD_ALL );
1070 if ( !$this->metadataArray && !$this->metadataBlobs ) {
1072 } elseif ( $this->repo->isJsonMetadataEnabled() ) {
1073 $s = $this->getJsonMetadata();
1075 $s = serialize( $this->getMetadataArray() );
1077 if ( !is_string( $s ) ) {
1078 throw new RuntimeException(
'Could not serialize image metadata value for DB' );
1089 private function getJsonMetadata() {
1092 'data' => array_diff_key( $this->metadataArray, $this->metadataBlobs )
1096 if ( $this->metadataBlobs ) {
1097 $envelope[
'blobs'] = $this->metadataBlobs;
1100 [ $s, $blobAddresses ] = $this->metadataStorageHelper->getJsonMetadata( $this, $envelope );
1103 $this->metadataBlobs += $blobAddresses;
1114 private function isMetadataOversize() {
1115 if ( !$this->repo->isSplitMetadataEnabled() ) {
1118 $threshold = $this->repo->getSplitMetadataThreshold();
1119 $directItems = array_diff_key( $this->metadataArray, $this->metadataBlobs );
1120 foreach ( $directItems as $value ) {
1121 if ( strlen( $this->metadataStorageHelper->jsonEncode( $value ) ) > $threshold ) {
1137 $this->loadMetadataFromString( $db->
decodeBlob( $metadataBlob ) );
1148 $this->extraDataLoaded =
true;
1149 $this->metadataArray = [];
1150 $this->metadataBlobs = [];
1151 $this->unloadedMetadataBlobs = [];
1152 $metadataString = (string)$metadataString;
1153 if ( $metadataString ===
'' ) {
1154 $this->metadataSerializationFormat = self::MDS_EMPTY;
1157 if ( $metadataString[0] ===
'{' ) {
1158 $envelope = $this->metadataStorageHelper->jsonDecode( $metadataString );
1161 $this->metadataArray = [
'_error' => $metadataString ];
1162 $this->metadataSerializationFormat = self::MDS_LEGACY;
1164 $this->metadataSerializationFormat = self::MDS_JSON;
1165 if ( isset( $envelope[
'data'] ) ) {
1166 $this->metadataArray = $envelope[
'data'];
1168 if ( isset( $envelope[
'blobs'] ) ) {
1169 $this->metadataBlobs = $this->unloadedMetadataBlobs = $envelope[
'blobs'];
1174 $data = @unserialize( $metadataString );
1175 if ( !is_array( $data ) ) {
1177 $data = [
'_error' => $metadataString ];
1178 $this->metadataSerializationFormat = self::MDS_LEGACY;
1180 $this->metadataSerializationFormat = self::MDS_PHP;
1182 $this->metadataArray = $data;
1193 return (
int)$this->bits;
1227 return $this->media_type;
1244 return $this->fileExists;
1269 if ( $archiveName ) {
1270 $dir = $this->getArchiveThumbPath( $archiveName );
1272 $dir = $this->getThumbPath();
1275 $backend = $this->repo->getBackend();
1278 $iterator = $backend->getFileList( [
'dir' => $dir,
'forWrite' =>
true ] );
1279 if ( $iterator !==
null ) {
1280 foreach ( $iterator as $file ) {
1300 $this->maybeUpgradeRow();
1301 $this->invalidateCache();
1304 $this->purgeThumbnails( $options );
1307 $hcu = MediaWikiServices::getInstance()->getHtmlCacheUpdater();
1310 !empty( $options[
'forThumbRefresh'] )
1311 ? $hcu::PURGE_PRESEND
1312 : $hcu::PURGE_INTENT_TXROUND_REFLECTED
1323 $thumbs = $this->getThumbnails( $archiveName );
1326 $dir = array_shift( $thumbs );
1327 $this->purgeThumbList( $dir, $thumbs );
1330 foreach ( $thumbs as $thumb ) {
1331 $urls[] = $this->getArchiveThumbUrl( $archiveName, $thumb );
1335 $this->getHookRunner()->onLocalFilePurgeThumbnails( $this, $archiveName, $urls );
1338 $hcu = MediaWikiServices::getInstance()->getHtmlCacheUpdater();
1339 $hcu->purgeUrls( $urls, $hcu::PURGE_PRESEND );
1349 $thumbs = $this->getThumbnails();
1352 $dir = array_shift( $thumbs );
1353 $this->purgeThumbList( $dir, $thumbs );
1357 foreach ( $thumbs as $thumb ) {
1358 $urls[] = $this->getThumbUrl( $thumb );
1362 if ( !empty( $options[
'forThumbRefresh'] ) ) {
1363 $handler = $this->getHandler();
1365 $handler->filterThumbnailPurgeList( $thumbs, $options );
1370 $this->getHookRunner()->onLocalFilePurgeThumbnails( $this,
false, $urls );
1373 $hcu = MediaWikiServices::getInstance()->getHtmlCacheUpdater();
1376 !empty( $options[
'forThumbRefresh'] )
1377 ? $hcu::PURGE_PRESEND
1378 : $hcu::PURGE_INTENT_TXROUND_REFLECTED
1389 $uploadThumbnailRenderMap = MediaWikiServices::getInstance()
1390 ->getMainConfig()->get( MainConfigNames::UploadThumbnailRenderMap );
1394 $sizes = $uploadThumbnailRenderMap;
1397 foreach ( $sizes as $size ) {
1398 if ( $this->isMultipage() ) {
1401 $pageLimit = min( $this->pageCount(), self::MAX_PAGE_RENDER_JOBS );
1406 'transformParams' => [
'width' => $size,
'page' => 1 ],
1407 'enqueueNextPage' =>
true,
1408 'pageLimit' => $pageLimit
1411 } elseif ( $this->isVectorized() || $this->getWidth() > $size ) {
1414 [
'transformParams' => [
'width' => $size ] ]
1420 MediaWikiServices::getInstance()->getJobQueueGroup()->lazyPush( $jobs );
1431 $fileListDebug = strtr(
1432 var_export( $files,
true ),
1435 wfDebug( __METHOD__ .
": $fileListDebug" );
1437 if ( $this->repo->supportsSha1URLs() ) {
1438 $reference = $this->getSha1();
1440 $reference = $this->getName();
1444 foreach ( $files as $file ) {
1445 # Check that the reference (filename or sha1) is part of the thumb name
1446 # This is a basic check to avoid erasing unrelated directories
1447 if ( str_contains( $file, $reference )
1448 || str_contains( $file,
"-thumbnail" )
1450 $purgeList[] =
"{$dir}/{$file}";
1454 # Delete the thumbnails
1455 $this->repo->quickPurgeBatch( $purgeList );
1456 # Clear out the thumbnail directory if empty
1457 $this->repo->quickCleanDir( $dir );
1471 public function getHistory( $limit =
null, $start =
null, $end =
null, $inc =
true ) {
1472 if ( !$this->exists() ) {
1476 $dbr = $this->repo->getReplicaDB();
1477 $oldFileQuery = OldLocalFile::getQueryInfo();
1479 $tables = $oldFileQuery[
'tables'];
1480 $fields = $oldFileQuery[
'fields'];
1481 $join_conds = $oldFileQuery[
'joins'];
1482 $conds = $opts = [];
1483 $eq = $inc ?
'=' :
'';
1484 $conds[] = $dbr->
expr(
'oi_name',
'=', $this->title->getDBkey() );
1487 $conds[] = $dbr->
expr(
'oi_timestamp',
"<$eq", $dbr->
timestamp( $start ) );
1491 $conds[] = $dbr->
expr(
'oi_timestamp',
">$eq", $dbr->
timestamp( $end ) );
1495 $opts[
'LIMIT'] = $limit;
1499 $order = ( !$start && $end !== null ) ?
'ASC' :
'DESC';
1500 $opts[
'ORDER BY'] =
"oi_timestamp $order";
1501 $opts[
'USE INDEX'] = [
'oldimage' =>
'oi_name_timestamp' ];
1503 $this->getHookRunner()->onLocalFile__getHistory( $this, $tables, $fields,
1504 $conds, $opts, $join_conds );
1510 ->caller( __METHOD__ )
1512 ->joinConds( $join_conds )
1516 foreach ( $res as $row ) {
1517 $r[] = $this->repo->newFileFromRow( $row );
1520 if ( $order ==
'ASC' ) {
1521 $r = array_reverse( $r );
1538 if ( !$this->exists() ) {
1542 # Polymorphic function name to distinguish foreign and local fetches
1543 $fname = static::class .
'::' . __FUNCTION__;
1545 $dbr = $this->repo->getReplicaDB();
1547 if ( $this->historyLine == 0 ) {
1548 $queryBuilder = FileSelectQueryBuilder::newForFile( $dbr );
1550 $queryBuilder->fields( [
'oi_archive_name' => $dbr->
addQuotes(
'' ),
'oi_deleted' =>
'0' ] )
1551 ->where( [
'img_name' => $this->title->getDBkey() ] );
1552 $this->historyRes = $queryBuilder->caller( $fname )->fetchResultSet();
1554 if ( $this->historyRes->numRows() == 0 ) {
1555 $this->historyRes =
null;
1559 } elseif ( $this->historyLine == 1 ) {
1560 $queryBuilder = FileSelectQueryBuilder::newForOldFile( $dbr );
1562 $this->historyRes = $queryBuilder->where( [
'oi_name' => $this->title->getDBkey() ] )
1563 ->orderBy(
'oi_timestamp', SelectQueryBuilder::SORT_DESC )
1564 ->caller( $fname )->fetchResultSet();
1566 $this->historyLine++;
1568 return $this->historyRes->fetchObject();
1576 $this->historyLine = 0;
1578 if ( $this->historyRes !==
null ) {
1579 $this->historyRes =
null;
1616 public function upload( $src, $comment, $pageText, $flags = 0, $props =
false,
1617 $timestamp =
false,
Authority $uploader =
null, $tags = [],
1618 $createNullRevision =
true, $revert =
false
1620 if ( $this->getRepo()->getReadOnlyReason() !==
false ) {
1621 return $this->readOnlyFatalStatus();
1622 } elseif ( MediaWikiServices::getInstance()->getRevisionStore()->isReadOnly() ) {
1625 return $this->readOnlyFatalStatus();
1628 $srcPath = ( $src instanceof
FSFile ) ? $src->getPath() : $src;
1631 || FileBackend::isStoragePath( $srcPath )
1633 $props = $this->repo->getFileProps( $srcPath );
1635 $mwProps =
new MWFileProps( MediaWikiServices::getInstance()->getMimeAnalyzer() );
1636 $props = $mwProps->getPropsFromPath( $srcPath,
true );
1641 $handler = MediaHandler::getHandler( $props[
'mime'] );
1643 if ( is_string( $props[
'metadata'] ) ) {
1648 $metadata = @unserialize( $props[
'metadata'] );
1650 $metadata = $props[
'metadata'];
1653 if ( is_array( $metadata ) ) {
1654 $options[
'headers'] = $handler->getContentHeaders( $metadata );
1657 $options[
'headers'] = [];
1661 $comment = trim( $comment );
1663 $status = $this->publish( $src, $flags, $options );
1665 if ( $status->successCount >= 2 ) {
1672 $oldver = $status->value;
1674 $uploadStatus = $this->recordUpload3(
1678 $uploader ?? RequestContext::getMain()->
getAuthority(),
1682 $createNullRevision,
1685 if ( !$uploadStatus->isOK() ) {
1686 if ( $uploadStatus->hasMessage(
'filenotfound' ) ) {
1688 $status->fatal(
'filenotfound', $srcPath );
1690 $status->merge( $uploadStatus );
1722 bool $createNullRevision =
true,
1723 bool $revert =
false
1725 $dbw = $this->repo->getPrimaryDB();
1727 # Imports or such might force a certain timestamp; otherwise we generate
1728 # it and can fudge it slightly to keep (name,timestamp) unique on re-upload.
1729 if ( $timestamp ===
false ) {
1730 $timestamp = $dbw->timestamp();
1731 $allowTimeKludge =
true;
1733 $allowTimeKludge =
false;
1736 $props = $props ?: $this->repo->getFileProps( $this->getVirtualUrl() );
1737 $props[
'description'] = $comment;
1738 $props[
'timestamp'] =
wfTimestamp( TS_MW, $timestamp );
1739 $this->setProps( $props );
1741 # Fail now if the file isn't there
1742 if ( !$this->fileExists ) {
1743 wfDebug( __METHOD__ .
": File " . $this->getRel() .
" went missing!" );
1745 return Status::newFatal(
'filenotfound', $this->getRel() );
1748 $mimeAnalyzer = MediaWikiServices::getInstance()->getMimeAnalyzer();
1749 if ( !$mimeAnalyzer->isValidMajorMimeType( $this->major_mime ) ) {
1750 $this->major_mime =
'unknown';
1753 $actorNormalizaton = MediaWikiServices::getInstance()->getActorNormalization();
1755 $dbw->startAtomic( __METHOD__ );
1757 $actorId = $actorNormalizaton->acquireActorId( $performer->
getUser(), $dbw );
1758 $this->user = $performer->
getUser();
1760 # Test to see if the row exists using INSERT IGNORE
1761 # This avoids race conditions by locking the row until the commit, and also
1762 # doesn't deadlock. SELECT FOR UPDATE causes a deadlock for every race condition.
1763 $commentStore = MediaWikiServices::getInstance()->getCommentStore();
1764 $commentFields = $commentStore->insert( $dbw,
'img_description', $comment );
1765 $actorFields = [
'img_actor' => $actorId ];
1766 $dbw->newInsertQueryBuilder()
1767 ->insertInto(
'image' )
1770 'img_name' => $this->getName(),
1771 'img_size' => $this->size,
1772 'img_width' => intval( $this->width ),
1773 'img_height' => intval( $this->height ),
1774 'img_bits' => $this->bits,
1775 'img_media_type' => $this->media_type,
1776 'img_major_mime' => $this->major_mime,
1777 'img_minor_mime' => $this->minor_mime,
1778 'img_timestamp' => $dbw->timestamp( $timestamp ),
1779 'img_metadata' => $this->getMetadataForDb( $dbw ),
1780 'img_sha1' => $this->sha1
1781 ] + $commentFields + $actorFields )
1782 ->caller( __METHOD__ )->execute();
1783 $reupload = ( $dbw->affectedRows() == 0 );
1786 $row = $dbw->newSelectQueryBuilder()
1787 ->select( [
'img_timestamp',
'img_sha1' ] )
1789 ->where( [
'img_name' => $this->getName() ] )
1790 ->caller( __METHOD__ )->fetchRow();
1792 if ( $row && $row->img_sha1 === $this->sha1 ) {
1793 $dbw->endAtomic( __METHOD__ );
1794 wfDebug( __METHOD__ .
": File " . $this->getRel() .
" already exists!" );
1795 $title = Title::newFromText( $this->getName(),
NS_FILE );
1796 return Status::newFatal(
'fileexists-no-change', $title->getPrefixedText() );
1799 if ( $allowTimeKludge ) {
1800 # Use LOCK IN SHARE MODE to ignore any transaction snapshotting
1801 $lUnixtime = $row ? (int)
wfTimestamp( TS_UNIX, $row->img_timestamp ) : false;
1802 # Avoid a timestamp that is not newer than the last version
1803 # TODO: the image/oldimage tables should be like page/revision with an ID field
1804 if ( $lUnixtime && (
int)
wfTimestamp( TS_UNIX, $timestamp ) <= $lUnixtime ) {
1806 $timestamp = $dbw->timestamp( $lUnixtime + 1 );
1807 $this->timestamp =
wfTimestamp( TS_MW, $timestamp );
1811 $tables = [
'image' ];
1813 'oi_name' =>
'img_name',
1814 'oi_archive_name' => $dbw->addQuotes( $oldver ),
1815 'oi_size' =>
'img_size',
1816 'oi_width' =>
'img_width',
1817 'oi_height' =>
'img_height',
1818 'oi_bits' =>
'img_bits',
1819 'oi_description_id' =>
'img_description_id',
1820 'oi_timestamp' =>
'img_timestamp',
1821 'oi_metadata' =>
'img_metadata',
1822 'oi_media_type' =>
'img_media_type',
1823 'oi_major_mime' =>
'img_major_mime',
1824 'oi_minor_mime' =>
'img_minor_mime',
1825 'oi_sha1' =>
'img_sha1',
1826 'oi_actor' =>
'img_actor',
1830 # (T36993) Note: $oldver can be empty here, if the previous
1831 # version of the file was broken. Allow registration of the new
1832 # version to continue anyway, because that's better than having
1833 # an image that's not fixable by user operations.
1834 # Collision, this is an update of a file
1835 # Insert previous contents into oldimage
1836 $dbw->insertSelect(
'oldimage', $tables, $fields,
1837 [
'img_name' => $this->getName() ], __METHOD__, [], [], $joins );
1839 # Update the current image row
1840 $dbw->newUpdateQueryBuilder()
1843 'img_size' => $this->size,
1844 'img_width' => intval( $this->width ),
1845 'img_height' => intval( $this->height ),
1846 'img_bits' => $this->bits,
1847 'img_media_type' => $this->media_type,
1848 'img_major_mime' => $this->major_mime,
1849 'img_minor_mime' => $this->minor_mime,
1850 'img_timestamp' => $dbw->timestamp( $timestamp ),
1851 'img_metadata' => $this->getMetadataForDb( $dbw ),
1852 'img_sha1' => $this->sha1
1853 ] + $commentFields + $actorFields )
1854 ->where( [
'img_name' => $this->getName() ] )
1855 ->caller( __METHOD__ )->execute();
1859 $descId = $descTitle->getArticleID();
1860 $wikiPage = MediaWikiServices::getInstance()->getWikiPageFactory()->newFromTitle( $descTitle );
1862 throw new UnexpectedValueException(
'Cannot obtain instance of WikiFilePage for ' . $this->getName()
1863 .
', got instance of ' . get_class( $wikiPage ) );
1865 $wikiPage->setFile( $this );
1869 $logAction =
'revert';
1870 } elseif ( $reupload ) {
1871 $logAction =
'overwrite';
1873 $logAction =
'upload';
1877 $logEntry->setTimestamp( $this->timestamp );
1878 $logEntry->setPerformer( $performer->
getUser() );
1879 $logEntry->setComment( $comment );
1880 $logEntry->setTarget( $descTitle );
1883 $logEntry->setParameters(
1885 'img_sha1' => $this->sha1,
1886 'img_timestamp' => $timestamp,
1895 $logId = $logEntry->insert();
1897 if ( $descTitle->exists() ) {
1898 if ( $createNullRevision ) {
1899 $revStore = MediaWikiServices::getInstance()->getRevisionStore();
1902 $formatter->setContext( RequestContext::newExtraneousContext( $descTitle ) );
1903 $editSummary = $formatter->getPlainActionText();
1904 $summary = CommentStoreComment::newUnsavedComment( $editSummary );
1905 $nullRevRecord = $revStore->newNullRevision(
1913 if ( $nullRevRecord ) {
1914 $inserted = $revStore->insertRevisionOn( $nullRevRecord, $dbw );
1916 $this->getHookRunner()->onRevisionFromEditComplete(
1919 $inserted->getParentId(),
1924 $wikiPage->updateRevisionOn( $dbw, $inserted );
1926 $logEntry->setAssociatedRevId( $inserted->getId() );
1930 $newPageContent =
null;
1939 $dbw->endAtomic( __METHOD__ );
1940 $fname = __METHOD__;
1942 # Do some cache purges after final commit so that:
1943 # a) Changes are more likely to be seen post-purge
1944 # b) They won't cause rollback of the log publish/update above
1949 $reupload, $wikiPage, $newPageContent, $comment, $performer,
1950 $logEntry, $logId, $descId, $tags, $fname
1952 # Update memcache after the commit
1953 $this->invalidateCache();
1955 $updateLogPage =
false;
1956 if ( $newPageContent ) {
1957 # New file page; create the description page.
1958 # There's already a log entry, so don't make a second RC entry
1959 # CDN and file cache for the description page are purged by doUserEditContent.
1960 $status = $wikiPage->doUserEditContent(
1967 $revRecord = $status->getNewRevision();
1970 $logEntry->setAssociatedRevId( $revRecord->getId() );
1974 $updateLogPage = $revRecord->getPageId();
1977 # Existing file page: invalidate description page cache
1978 $title = $wikiPage->getTitle();
1979 $title->invalidateCache();
1980 $hcu = MediaWikiServices::getInstance()->getHtmlCacheUpdater();
1981 $hcu->purgeTitleUrls( $title, $hcu::PURGE_INTENT_TXROUND_REFLECTED );
1982 # Allow the new file version to be patrolled from the page footer
1986 # Update associated rev id. This should be done by $logEntry->insert() earlier,
1987 # but setAssociatedRevId() wasn't called at that point yet...
1988 $logParams = $logEntry->getParameters();
1989 $logParams[
'associated_rev_id'] = $logEntry->getAssociatedRevId();
1991 if ( $updateLogPage ) {
1992 # Also log page, in case where we just created it above
1993 $update[
'log_page'] = $updateLogPage;
1995 $this->getRepo()->getPrimaryDB()->newUpdateQueryBuilder()
1996 ->update(
'logging' )
1998 ->where( [
'log_id' => $logId ] )
1999 ->caller( $fname )->execute();
2001 $this->getRepo()->getPrimaryDB()->newInsertQueryBuilder()
2002 ->insertInto(
'log_search' )
2004 'ls_field' =>
'associated_rev_id',
2005 'ls_value' => (
string)$logEntry->getAssociatedRevId(),
2006 'ls_log_id' => $logId,
2008 ->caller( $fname )->execute();
2010 # Add change tags, if any
2012 $logEntry->addTags( $tags );
2015 # Uploads can be patrolled
2016 $logEntry->setIsPatrollable(
true );
2018 # Now that the log entry is up-to-date, make an RC entry.
2019 $logEntry->publish( $logId );
2021 # Run hook for other updates (typically more cache purging)
2022 $this->getHookRunner()->onFileUpload( $this, $reupload, !$newPageContent );
2025 # Delete old thumbnails
2026 $this->purgeThumbnails();
2027 # Remove the old file from the CDN cache
2028 $hcu = MediaWikiServices::getInstance()->getHtmlCacheUpdater();
2029 $hcu->purgeUrls( $this->getUrl(), $hcu::PURGE_INTENT_TXROUND_REFLECTED );
2031 # Update backlink pages pointing to this title if created
2032 $blcFactory = MediaWikiServices::getInstance()->getBacklinkCacheFactory();
2033 LinksUpdate::queueRecursiveJobsForTable(
2037 $performer->
getUser()->getName(),
2038 $blcFactory->getBacklinkCache( $this->getTitle() )
2042 $this->prerenderThumbnails();
2046 # Invalidate cache for all pages using this file
2050 [
'causeAction' =>
'file-upload',
'causeAgent' => $performer->
getUser()->getName() ]
2058 $dbw->onTransactionCommitOrIdle(
static function () use ( $reupload, $purgeUpdate, $cacheUpdateJob ) {
2059 DeferredUpdates::addUpdate( $purgeUpdate, DeferredUpdates::PRESEND );
2063 DeferredUpdates::addUpdate( SiteStatsUpdate::factory( [
'images' => 1 ] ) );
2066 MediaWikiServices::getInstance()->getJobQueueGroup()->lazyPush( $cacheUpdateJob );
2069 return Status::newGood();
2088 public function publish( $src, $flags = 0, array $options = [] ) {
2089 return $this->publishTo( $src, $this->getRel(), $flags, $options );
2108 protected function publishTo( $src, $dstRel, $flags = 0, array $options = [] ) {
2109 $srcPath = ( $src instanceof
FSFile ) ? $src->getPath() : $src;
2111 $repo = $this->getRepo();
2112 if ( $repo->getReadOnlyReason() !==
false ) {
2113 return $this->readOnlyFatalStatus();
2116 $status = $this->acquireFileLock();
2117 if ( !$status->isOK() ) {
2121 if ( $this->isOld() ) {
2122 $archiveRel = $dstRel;
2123 $archiveName = basename( $archiveRel );
2125 $archiveName =
wfTimestamp( TS_MW ) .
'!' . $this->getName();
2126 $archiveRel = $this->getArchiveRel( $archiveName );
2129 if ( $repo->hasSha1Storage() ) {
2131 ? $repo->getFileSha1( $srcPath )
2132 : FSFile::getSha1Base36FromPath( $srcPath );
2134 $wrapperBackend = $repo->getBackend();
2135 '@phan-var FileBackendDBRepoWrapper $wrapperBackend';
2136 $dst = $wrapperBackend->getPathForSHA1( $sha1 );
2137 $status = $repo->quickImport( $src, $dst );
2142 if ( $this->exists() ) {
2143 $status->value = $archiveName;
2147 $status = $repo->publish( $srcPath, $dstRel, $archiveRel, $flags, $options );
2149 if ( $status->value ==
'new' ) {
2150 $status->value =
'';
2152 $status->value = $archiveName;
2156 $this->releaseFileLock();
2179 $localRepo = MediaWikiServices::getInstance()->getRepoGroup()->getLocalRepo();
2180 if ( $this->getRepo()->getReadOnlyReason() !==
false ) {
2181 return $this->readOnlyFatalStatus();
2184 wfDebugLog(
'imagemove',
"Got request to move {$this->name} to " . $target->getText() );
2187 $status = $batch->addCurrent();
2188 if ( !$status->isOK() ) {
2191 $archiveNames = $batch->addOlds();
2192 $status = $batch->execute();
2194 wfDebugLog(
'imagemove',
"Finished moving {$this->name}" );
2197 $oldTitleFile = $localRepo->newFile( $this->title );
2198 $newTitleFile = $localRepo->newFile( $target );
2199 DeferredUpdates::addUpdate(
2201 $this->getRepo()->getPrimaryDB(),
2203 static function () use ( $oldTitleFile, $newTitleFile, $archiveNames ) {
2204 $oldTitleFile->purgeEverything();
2205 foreach ( $archiveNames as $archiveName ) {
2207 '@phan-var OldLocalFile $oldTitleFile';
2208 $oldTitleFile->purgeOldThumbnails( $archiveName );
2210 $newTitleFile->purgeEverything();
2213 DeferredUpdates::PRESEND
2216 if ( $status->isOK() ) {
2218 $this->title = $target;
2221 $this->hashPath =
null;
2244 if ( $this->getRepo()->getReadOnlyReason() !==
false ) {
2245 return $this->readOnlyFatalStatus();
2250 $batch->addCurrent();
2252 $archiveNames = $batch->addOlds();
2253 $status = $batch->execute();
2255 if ( $status->isOK() ) {
2256 DeferredUpdates::addUpdate( SiteStatsUpdate::factory( [
'images' => -1 ] ) );
2260 DeferredUpdates::addUpdate(
2262 $this->getRepo()->getPrimaryDB(),
2264 function () use ( $archiveNames ) {
2265 $this->purgeEverything();
2266 foreach ( $archiveNames as $archiveName ) {
2267 $this->purgeOldThumbnails( $archiveName );
2271 DeferredUpdates::PRESEND
2276 foreach ( $archiveNames as $archiveName ) {
2277 $purgeUrls[] = $this->getArchiveUrl( $archiveName );
2280 $hcu = MediaWikiServices::getInstance()->getHtmlCacheUpdater();
2281 $hcu->purgeUrls( $purgeUrls, $hcu::PURGE_INTENT_TXROUND_REFLECTED );
2304 if ( $this->getRepo()->getReadOnlyReason() !==
false ) {
2305 return $this->readOnlyFatalStatus();
2310 $batch->addOld( $archiveName );
2311 $status = $batch->execute();
2313 $this->purgeOldThumbnails( $archiveName );
2314 if ( $status->isOK() ) {
2315 $this->purgeDescription();
2318 $url = $this->getArchiveUrl( $archiveName );
2319 $hcu = MediaWikiServices::getInstance()->getHtmlCacheUpdater();
2320 $hcu->purgeUrls(
$url, $hcu::PURGE_INTENT_TXROUND_REFLECTED );
2337 public function restore( $versions = [], $unsuppress =
false ) {
2338 if ( $this->getRepo()->getReadOnlyReason() !==
false ) {
2339 return $this->readOnlyFatalStatus();
2347 $batch->addIds( $versions );
2349 $status = $batch->execute();
2350 if ( $status->isGood() ) {
2351 $cleanupStatus = $batch->cleanup();
2352 $cleanupStatus->successCount = 0;
2353 $cleanupStatus->failCount = 0;
2354 $status->merge( $cleanupStatus );
2372 return $this->title ? $this->title->getLocalURL() :
false;
2385 if ( !$this->title ) {
2389 $services = MediaWikiServices::getInstance();
2390 $page = $services->getPageStore()->getPageByReference( $this->
getTitle() );
2396 $parserOptions = ParserOptions::newFromUserAndLang(
2397 RequestContext::getMain()->
getUser(),
2401 $parserOptions = ParserOptions::newFromContext( RequestContext::getMain() );
2404 $parseStatus = $services->getParserOutputAccess()
2405 ->getParserOutput( $page, $parserOptions );
2407 if ( !$parseStatus->isGood() ) {
2411 return $parseStatus->getValue()->getText();
2423 if ( $audience === self::FOR_PUBLIC && $this->isDeleted( self::DELETED_USER ) ) {
2425 } elseif ( $audience === self::FOR_THIS_USER && !$this->userCan( self::DELETED_USER, $performer ) ) {
2440 if ( $audience == self::FOR_PUBLIC && $this->isDeleted( self::DELETED_COMMENT ) ) {
2442 } elseif ( $audience == self::FOR_THIS_USER && !$this->userCan( self::DELETED_COMMENT, $performer ) ) {
2445 return $this->description;
2456 return $this->timestamp;
2464 if ( !$this->exists() ) {
2471 if ( $this->descriptionTouched ===
null ) {
2472 $touched = $this->repo->getReplicaDB()->newSelectQueryBuilder()
2473 ->select(
'page_touched' )
2475 ->where( [
'page_namespace' => $this->title->getNamespace() ] )
2476 ->andWhere( [
'page_title' => $this->title->getDBkey() ] )
2477 ->caller( __METHOD__ )->fetchField();
2478 $this->descriptionTouched = $touched ?
wfTimestamp( TS_MW, $touched ) :
false;
2481 return $this->descriptionTouched;
2500 return $this->extraDataLoaded
2501 && strlen( serialize( $this->metadataArray ) ) <= self::CACHE_FIELD_MAX_LEN;
2513 return Status::wrap( $this->getRepo()->getBackend()->lockFiles(
2514 [ $this->getPath() ], LockManager::LOCK_EX, $timeout
2525 return Status::wrap( $this->getRepo()->getBackend()->unlockFiles(
2526 [ $this->getPath() ], LockManager::LOCK_EX
2541 if ( !$this->locked ) {
2542 $logger = LoggerFactory::getInstance(
'LocalFile' );
2544 $dbw = $this->repo->getPrimaryDB();
2545 $makesTransaction = !$dbw->trxLevel();
2546 $dbw->startAtomic( self::ATOMIC_SECTION_LOCK );
2550 $status = $this->acquireFileLock( 10 );
2551 if ( !$status->isGood() ) {
2552 $dbw->endAtomic( self::ATOMIC_SECTION_LOCK );
2553 $logger->warning(
"Failed to lock '{file}'", [
'file' => $this->name ] );
2559 $dbw->onTransactionResolution(
2560 function () use ( $logger ) {
2561 $status = $this->releaseFileLock();
2562 if ( !$status->isGood() ) {
2563 $logger->error(
"Failed to unlock '{file}'", [
'file' => $this->name ] );
2569 $this->lockedOwnTrx = $makesTransaction;
2574 return $this->lockedOwnTrx;
2588 if ( $this->locked ) {
2590 if ( !$this->locked ) {
2591 $dbw = $this->repo->getPrimaryDB();
2592 $dbw->endAtomic( self::ATOMIC_SECTION_LOCK );
2593 $this->lockedOwnTrx =
false;
2602 return $this->getRepo()->newFatal(
'filereadonlyerror', $this->getName(),
2603 $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 makeContent( $text, Title $title=null, $modelId=null, $format=null)
Convenience function for creating a Content object from a given textual representation.
Class representing a non-directory file on the file system.
File backend exception for checked exceptions (e.g.
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=[])
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.
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().
getUploader(int $audience=self::FOR_PUBLIC, Authority $performer=null)
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.
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.
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.