61 private const VERSION = 13;
63 private const CACHE_FIELD_MAX_LEN = 1000;
66 private const MDS_EMPTY =
'empty';
69 private const MDS_LEGACY =
'legacy';
72 private const MDS_PHP =
'php';
75 private const MDS_JSON =
'json';
78 private const MAX_PAGE_RENDER_JOBS = 50;
102 protected $metadataArray = [];
113 protected $metadataBlobs = [];
121 protected $unloadedMetadataBlobs = [];
127 protected $dataLoaded =
false;
130 protected $extraDataLoaded =
false;
136 protected $repoClass = LocalRepo::class;
139 private $historyLine = 0;
142 private $historyRes =
null;
157 private $description;
160 private $descriptionTouched;
172 private $lockedOwnTrx;
178 private $metadataStorageHelper;
181 private const LOAD_ALL = 16;
183 private const ATOMIC_SECTION_LOCK =
'LocalFile::lockingTransaction';
217 $file->loadFromRow( $row );
233 public static function newFromKey( $sha1, $repo, $timestamp =
false ) {
236 $conds = [
'img_sha1' => $sha1 ];
238 $conds[
'img_timestamp'] =
$dbr->timestamp( $timestamp );
241 $fileQuery = static::getQueryInfo();
242 $row =
$dbr->selectRow(
243 $fileQuery[
'tables'], $fileQuery[
'fields'], $conds, __METHOD__, [], $fileQuery[
'joins']
246 return static::newFromRow( $row,
$repo );
272 $commentQuery = MediaWikiServices::getInstance()->getCommentStore()->getJoin(
'img_description' );
276 'image_actor' =>
'actor'
277 ] + $commentQuery[
'tables'],
291 'img_user' =>
'image_actor.actor_user',
292 'img_user_text' =>
'image_actor.actor_name',
293 ] + $commentQuery[
'fields'],
295 'image_actor' => [
'JOIN',
'actor_id=img_actor' ]
296 ] + $commentQuery[
'joins'],
299 if ( in_array(
'omit-nonlazy', $options,
true ) ) {
303 if ( !in_array(
'omit-lazy', $options,
true ) ) {
306 $ret[
'fields'][] =
'img_metadata';
341 return $this->repo->getSharedCacheKey(
'file', sha1( $this->
getName() ) );
356 private function loadFromCache() {
357 $this->dataLoaded =
false;
358 $this->extraDataLoaded =
false;
360 $key = $this->getCacheKey();
362 $this->loadFromDB( self::READ_NORMAL );
367 $cache = MediaWikiServices::getInstance()->getMainWANObjectCache();
368 $cachedValues =
$cache->getWithSetCallback(
371 function ( $oldValue, &$ttl, array &$setOpts ) use (
$cache ) {
372 $setOpts += Database::getCacheSetOptions( $this->repo->getReplicaDB() );
378 $cacheVal[
'fileExists'] = $this->fileExists;
379 if ( $this->fileExists ) {
380 foreach ( $fields as $field ) {
381 $cacheVal[$field] = $this->$field;
385 $cacheVal[
'user'] = $this->user->getId();
386 $cacheVal[
'user_text'] = $this->user->getName();
390 if ( $this->metadataBlobs ) {
391 $cacheVal[
'metadata'] = array_diff_key(
392 $this->metadataArray, $this->metadataBlobs );
394 $cacheVal[
'metadataBlobs'] = $this->metadataBlobs;
396 $cacheVal[
'metadata'] = $this->metadataArray;
403 if ( isset( $cacheVal[$field] )
404 && strlen(
serialize( $cacheVal[$field] ) ) > 100 * 1024
406 unset( $cacheVal[$field] );
407 if ( $field ===
'metadata' ) {
408 unset( $cacheVal[
'metadataBlobs'] );
413 if ( $this->fileExists ) {
414 $ttl =
$cache->adaptiveTTL( (
int)
wfTimestamp( TS_UNIX, $this->timestamp ), $ttl );
416 $ttl = $cache::TTL_DAY;
421 [
'version' => self::VERSION ]
424 $this->fileExists = $cachedValues[
'fileExists'];
425 if ( $this->fileExists ) {
429 $this->dataLoaded =
true;
430 $this->extraDataLoaded =
true;
432 $this->extraDataLoaded = $this->extraDataLoaded && isset( $cachedValues[$field] );
445 $this->repo->getPrimaryDB()->onTransactionPreCommitOrIdle(
446 static function () use ( $key ) {
447 MediaWikiServices::getInstance()->getMainWANObjectCache()->delete( $key );
473 if ( $prefix !==
'' ) {
474 throw new InvalidArgumentException(
475 __METHOD__ .
' with a non-empty prefix is no longer supported.'
483 return [
'size',
'width',
'height',
'bits',
'media_type',
484 'major_mime',
'minor_mime',
'timestamp',
'sha1',
'description' ];
495 if ( $prefix !==
'' ) {
496 throw new InvalidArgumentException(
497 __METHOD__ .
' with a non-empty prefix is no longer supported.'
502 return [
'metadata' ];
511 $fname = static::class .
'::' . __FUNCTION__;
513 # Unconditionally set loaded=true, we don't want the accessors constantly rechecking
514 $this->dataLoaded =
true;
515 $this->extraDataLoaded =
true;
517 $dbr = ( $flags & self::READ_LATEST )
518 ? $this->repo->getPrimaryDB()
519 : $this->repo->getReplicaDB();
521 $fileQuery = static::getQueryInfo();
522 $row =
$dbr->selectRow(
523 $fileQuery[
'tables'],
524 $fileQuery[
'fields'],
525 [
'img_name' => $this->
getName() ],
534 $this->fileExists =
false;
544 if ( !$this->title ) {
548 $fname = static::class .
'::' . __FUNCTION__;
550 # Unconditionally set loaded=true, we don't want the accessors constantly rechecking
551 $this->extraDataLoaded =
true;
553 $db = $this->repo->getReplicaDB();
554 $fieldMap = $this->loadExtraFieldsWithTimestamp( $db, $fname );
556 $db = $this->repo->getPrimaryDB();
557 $fieldMap = $this->loadExtraFieldsWithTimestamp( $db, $fname );
561 if ( isset( $fieldMap[
'metadata'] ) ) {
565 throw new MWException(
"Could not find data for image '{$this->getName()}'." );
574 private function loadExtraFieldsWithTimestamp(
$dbr, $fname ) {
577 $fileQuery = self::getQueryInfo( [
'omit-nonlazy' ] );
578 $row =
$dbr->selectRow(
579 $fileQuery[
'tables'],
580 $fileQuery[
'fields'],
582 'img_name' => $this->getName(),
583 'img_timestamp' =>
$dbr->timestamp( $this->getTimestamp() ),
590 $fieldMap = $this->unprefixRow( $row,
'img_' );
592 # File may have been uploaded over in the meantime; check the old versions
594 $row =
$dbr->selectRow(
595 $fileQuery[
'tables'],
596 $fileQuery[
'fields'],
599 'oi_timestamp' =>
$dbr->timestamp( $this->getTimestamp() ),
620 $array = (array)$row;
621 $prefixLength = strlen( $prefix );
624 if ( substr( key( $array ), 0, $prefixLength ) !== $prefix ) {
625 throw new MWException( __METHOD__ .
': incorrect $prefix parameter' );
629 foreach ( $array as
$name => $value ) {
630 $decoded[substr(
$name, $prefixLength )] = $value;
652 $this->dataLoaded =
true;
656 $this->name = $unprefixed[
'name'];
657 $this->media_type = $unprefixed[
'media_type'];
659 $services = MediaWikiServices::getInstance();
660 $this->description = $services->getCommentStore()
661 ->getComment(
"{$prefix}description", $row )->text;
663 $this->user = $services->getUserFactory()->newFromAnyId(
664 $unprefixed[
'user'] ??
null,
665 $unprefixed[
'user_text'] ??
null,
666 $unprefixed[
'actor'] ??
null
669 $this->timestamp =
wfTimestamp( TS_MW, $unprefixed[
'timestamp'] );
672 $this->repo->getReplicaDB(), $unprefixed[
'metadata'] );
674 if ( empty( $unprefixed[
'major_mime'] ) ) {
675 $this->major_mime =
'unknown';
676 $this->minor_mime =
'unknown';
677 $this->mime =
'unknown/unknown';
679 if ( !$unprefixed[
'minor_mime'] ) {
680 $unprefixed[
'minor_mime'] =
'unknown';
682 $this->major_mime = $unprefixed[
'major_mime'];
683 $this->minor_mime = $unprefixed[
'minor_mime'];
684 $this->mime = $unprefixed[
'major_mime'] .
'/' . $unprefixed[
'minor_mime'];
688 $this->sha1 = rtrim( $unprefixed[
'sha1'],
"\0" );
694 $this->size = +$unprefixed[
'size'];
695 $this->width = +$unprefixed[
'width'];
696 $this->height = +$unprefixed[
'height'];
697 $this->bits = +$unprefixed[
'bits'];
700 $extraFields = array_diff(
701 array_keys( $unprefixed ),
703 'name',
'media_type',
'description_text',
'description_data',
704 'description_cid',
'user',
'user_text',
'actor',
'timestamp',
705 'metadata',
'major_mime',
'minor_mime',
'sha1',
'size',
'width',
709 if ( $extraFields ) {
711 'Passing extra fields (' .
712 implode(
', ', $extraFields )
713 .
') to ' . __METHOD__ .
' was deprecated in MediaWiki 1.37. ' .
714 'Property assignment will be removed in a later version.',
716 foreach ( $extraFields as $field ) {
717 $this->$field = $unprefixed[$field];
721 $this->fileExists =
true;
729 public function load( $flags = 0 ) {
730 if ( !$this->dataLoaded ) {
731 if ( $flags & self::READ_LATEST ) {
734 $this->loadFromCache();
738 if ( ( $flags & self::LOAD_ALL ) && !$this->extraDataLoaded ) {
749 if ( MediaWikiServices::getInstance()->getReadOnlyMode()->isReadOnly() || $this->upgrading ) {
754 $reserialize =
false;
755 if ( $this->media_type ===
null || $this->mime ==
'image/svg' ) {
764 && $this->repo->isMetadataUpdateEnabled()
767 } elseif ( $this->repo->isJsonMetadataEnabled()
768 && $this->repo->isMetadataReserializeEnabled()
770 if ( $this->repo->isSplitMetadataEnabled() && $this->isMetadataOversize() ) {
772 } elseif ( $this->metadataSerializationFormat !== self::MDS_EMPTY &&
773 $this->metadataSerializationFormat !== self::MDS_JSON ) {
780 if ( $upgrade || $reserialize ) {
781 $this->upgrading =
true;
783 DeferredUpdates::addCallableUpdate(
function () use ( $upgrade ) {
784 $this->upgrading =
false;
802 return $this->upgraded;
810 $dbw = $this->repo->getPrimaryDB();
814 $freshnessCondition = [
'img_timestamp' => $dbw->timestamp( $this->
getTimestamp() ) ];
818 # Don't destroy file info of missing files
819 if ( !$this->fileExists ) {
820 wfDebug( __METHOD__ .
": file does not exist, aborting" );
825 list( $major, $minor ) = self::splitMime( $this->mime );
827 wfDebug( __METHOD__ .
': upgrading ' . $this->
getName() .
" to the current schema" );
829 $dbw->update(
'image',
831 'img_size' => $this->size,
832 'img_width' => $this->width,
833 'img_height' => $this->height,
834 'img_bits' => $this->bits,
835 'img_media_type' => $this->media_type,
836 'img_major_mime' => $major,
837 'img_minor_mime' => $minor,
839 'img_sha1' => $this->sha1,
842 [
'img_name' => $this->
getName() ],
850 $this->upgraded =
true;
858 if ( MediaWikiServices::getInstance()->getReadOnlyMode()->isReadOnly() ) {
861 $dbw = $this->repo->getPrimaryDB();
866 'img_name' => $this->name,
867 'img_timestamp' => $dbw->timestamp( $this->timestamp ),
871 $this->upgraded =
true;
886 $this->dataLoaded =
true;
888 $fields[] =
'fileExists';
890 foreach ( $fields as $field ) {
891 if ( isset( $info[$field] ) ) {
892 $this->$field = $info[$field];
897 if ( isset( $info[
'user'] ) &&
898 isset( $info[
'user_text'] ) &&
899 $info[
'user_text'] !==
''
905 if ( isset( $info[
'major_mime'] ) ) {
906 $this->mime =
"{$info['major_mime']}/{$info['minor_mime']}";
907 } elseif ( isset( $info[
'mime'] ) ) {
908 $this->mime = $info[
'mime'];
909 list( $this->major_mime, $this->minor_mime ) = self::splitMime( $this->mime );
912 if ( isset( $info[
'metadata'] ) ) {
913 if ( is_string( $info[
'metadata'] ) ) {
915 } elseif ( is_array( $info[
'metadata'] ) ) {
916 $this->metadataArray = $info[
'metadata'];
917 if ( isset( $info[
'metadataBlobs'] ) ) {
918 $this->metadataBlobs = $info[
'metadataBlobs'];
919 $this->unloadedMetadataBlobs = array_diff_key(
920 $this->metadataBlobs,
924 $this->metadataBlobs = [];
925 $this->unloadedMetadataBlobs = [];
928 $logger = LoggerFactory::getInstance(
'LocalFile' );
929 $logger->warning( __METHOD__ .
' given invalid metadata of type ' .
930 gettype( $info[
'metadata'] ) );
931 $this->metadataArray = [];
933 $this->extraDataLoaded =
true;
953 if ( $this->missing ===
null ) {
954 $fileExists = $this->repo->fileExists( $this->
getVirtualUrl() );
955 $this->missing = !$fileExists;
958 return $this->missing;
983 return $dim[
'width'];
1016 return $dim[
'height'];
1023 return $this->height;
1035 if ( !$this->title ) {
1039 $pageId = $this->title->getArticleID();
1042 $url = $this->repo->makeUrl( [
'curid' => $pageId ] );
1043 if (
$url !==
false ) {
1060 } elseif ( array_keys( $data ) === [
'_error' ] ) {
1062 return $data[
'_error'];
1075 $this->
load( self::LOAD_ALL );
1076 if ( $this->unloadedMetadataBlobs ) {
1078 array_unique( array_merge(
1079 array_keys( $this->metadataArray ),
1080 array_keys( $this->unloadedMetadataBlobs )
1084 return $this->metadataArray;
1088 $this->load( self::LOAD_ALL );
1091 foreach ( $itemNames as $itemName ) {
1092 if ( array_key_exists( $itemName, $this->metadataArray ) ) {
1093 $result[$itemName] = $this->metadataArray[$itemName];
1094 } elseif ( isset( $this->unloadedMetadataBlobs[$itemName] ) ) {
1095 $addresses[$itemName] = $this->unloadedMetadataBlobs[$itemName];
1100 $resultFromBlob = $this->metadataStorageHelper->getMetadataFromBlobStore( $addresses );
1101 foreach ( $addresses as $itemName => $address ) {
1102 unset( $this->unloadedMetadataBlobs[$itemName] );
1103 $value = $resultFromBlob[$itemName] ??
null;
1104 if ( $value !==
null ) {
1105 $result[$itemName] = $value;
1106 $this->metadataArray[$itemName] = $value;
1125 $this->load( self::LOAD_ALL );
1126 if ( !$this->metadataArray && !$this->metadataBlobs ) {
1128 } elseif ( $this->repo->isJsonMetadataEnabled() ) {
1129 $s = $this->getJsonMetadata();
1133 if ( !is_string(
$s ) ) {
1134 throw new MWException(
'Could not serialize image metadata value for DB' );
1145 private function getJsonMetadata() {
1148 'data' => array_diff_key( $this->metadataArray, $this->metadataBlobs )
1152 if ( $this->metadataBlobs ) {
1153 $envelope[
'blobs'] = $this->metadataBlobs;
1156 list(
$s, $blobAddresses ) = $this->metadataStorageHelper->getJsonMetadata( $this, $envelope );
1159 $this->metadataBlobs += $blobAddresses;
1170 private function isMetadataOversize() {
1171 if ( !$this->repo->isSplitMetadataEnabled() ) {
1174 $threshold = $this->repo->getSplitMetadataThreshold();
1175 $directItems = array_diff_key( $this->metadataArray, $this->metadataBlobs );
1176 foreach ( $directItems as $value ) {
1177 if ( strlen( $this->metadataStorageHelper->jsonEncode( $value ) ) > $threshold ) {
1193 $this->loadMetadataFromString( $db->
decodeBlob( $metadataBlob ) );
1204 $this->extraDataLoaded =
true;
1205 $this->metadataArray = [];
1206 $this->metadataBlobs = [];
1207 $this->unloadedMetadataBlobs = [];
1208 $metadataString = (string)$metadataString;
1209 if ( $metadataString ===
'' ) {
1210 $this->metadataSerializationFormat = self::MDS_EMPTY;
1213 if ( $metadataString[0] ===
'{' ) {
1214 $envelope = $this->metadataStorageHelper->jsonDecode( $metadataString );
1217 $this->metadataArray = [
'_error' => $metadataString ];
1218 $this->metadataSerializationFormat = self::MDS_LEGACY;
1220 $this->metadataSerializationFormat = self::MDS_JSON;
1221 if ( isset( $envelope[
'data'] ) ) {
1222 $this->metadataArray = $envelope[
'data'];
1224 if ( isset( $envelope[
'blobs'] ) ) {
1225 $this->metadataBlobs = $this->unloadedMetadataBlobs = $envelope[
'blobs'];
1231 if ( !is_array( $data ) ) {
1233 $data = [
'_error' => $metadataString ];
1234 $this->metadataSerializationFormat = self::MDS_LEGACY;
1236 $this->metadataSerializationFormat = self::MDS_PHP;
1238 $this->metadataArray = $data;
1249 return (
int)$this->bits;
1283 return $this->media_type;
1300 return $this->fileExists;
1320 if ( $archiveName ) {
1321 $dir = $this->getArchiveThumbPath( $archiveName );
1323 $dir = $this->getThumbPath();
1326 $backend = $this->repo->getBackend();
1329 $iterator = $backend->getFileList( [
'dir' => $dir ] );
1330 if ( $iterator !==
null ) {
1331 foreach ( $iterator as
$file ) {
1344 private function purgeMetadataCache() {
1345 $this->invalidateCache();
1358 $this->maybeUpgradeRow();
1359 $this->purgeMetadataCache();
1362 $this->purgeThumbnails( $options );
1365 $hcu = MediaWikiServices::getInstance()->getHtmlCacheUpdater();
1368 !empty( $options[
'forThumbRefresh'] )
1369 ? $hcu::PURGE_PRESEND
1370 : $hcu::PURGE_INTENT_TXROUND_REFLECTED
1381 $thumbs = $this->getThumbnails( $archiveName );
1384 $dir = array_shift( $thumbs );
1385 $this->purgeThumbList( $dir, $thumbs );
1388 foreach ( $thumbs as $thumb ) {
1389 $urls[] = $this->getArchiveThumbUrl( $archiveName, $thumb );
1393 $this->getHookRunner()->onLocalFilePurgeThumbnails( $this, $archiveName, $urls );
1396 $hcu = MediaWikiServices::getInstance()->getHtmlCacheUpdater();
1397 $hcu->purgeUrls( $urls, $hcu::PURGE_PRESEND );
1407 $thumbs = $this->getThumbnails();
1410 $dir = array_shift( $thumbs );
1411 $this->purgeThumbList( $dir, $thumbs );
1415 foreach ( $thumbs as $thumb ) {
1416 $urls[] = $this->getThumbUrl( $thumb );
1420 if ( !empty( $options[
'forThumbRefresh'] ) ) {
1421 $handler = $this->getHandler();
1423 $handler->filterThumbnailPurgeList( $thumbs, $options );
1428 $this->getHookRunner()->onLocalFilePurgeThumbnails( $this,
false, $urls );
1431 $hcu = MediaWikiServices::getInstance()->getHtmlCacheUpdater();
1434 !empty( $options[
'forThumbRefresh'] )
1435 ? $hcu::PURGE_PRESEND
1436 : $hcu::PURGE_INTENT_TXROUND_REFLECTED
1447 $uploadThumbnailRenderMap = MediaWikiServices::getInstance()
1448 ->getMainConfig()->get( MainConfigNames::UploadThumbnailRenderMap );
1452 $sizes = $uploadThumbnailRenderMap;
1455 foreach ( $sizes as $size ) {
1456 if ( $this->isMultipage() ) {
1459 $pageLimit = min( $this->pageCount(), self::MAX_PAGE_RENDER_JOBS );
1461 for ( $page = 1; $page <= $pageLimit; $page++ ) {
1464 [
'transformParams' => [
1470 } elseif ( $this->isVectorized() || $this->getWidth() > $size ) {
1473 [
'transformParams' => [
'width' => $size ] ]
1479 MediaWikiServices::getInstance()->getJobQueueGroup()->lazyPush( $jobs );
1490 $fileListDebug = strtr(
1491 var_export( $files,
true ),
1494 wfDebug( __METHOD__ .
": $fileListDebug" );
1496 if ( $this->repo->supportsSha1URLs() ) {
1497 $reference = $this->getSha1();
1499 $reference = $this->getName();
1503 foreach ( $files as
$file ) {
1504 # Check that the reference (filename or sha1) is part of the thumb name
1505 # This is a basic check to avoid erasing unrelated directories
1506 if ( strpos(
$file, $reference ) !==
false
1507 || strpos(
$file,
"-thumbnail" ) !==
false
1509 $purgeList[] =
"{$dir}/{$file}";
1513 # Delete the thumbnails
1514 $this->repo->quickPurgeBatch( $purgeList );
1515 # Clear out the thumbnail directory if empty
1516 $this->repo->quickCleanDir( $dir );
1530 public function getHistory( $limit =
null, $start =
null, $end =
null, $inc =
true ) {
1531 if ( !$this->exists() ) {
1535 $dbr = $this->repo->getReplicaDB();
1536 $oldFileQuery = OldLocalFile::getQueryInfo();
1538 $tables = $oldFileQuery[
'tables'];
1539 $fields = $oldFileQuery[
'fields'];
1540 $join_conds = $oldFileQuery[
'joins'];
1541 $conds = $opts = [];
1542 $eq = $inc ?
'=' :
'';
1543 $conds[] =
"oi_name = " .
$dbr->addQuotes( $this->title->getDBkey() );
1546 $conds[] =
"oi_timestamp <$eq " .
$dbr->addQuotes(
$dbr->timestamp( $start ) );
1550 $conds[] =
"oi_timestamp >$eq " .
$dbr->addQuotes(
$dbr->timestamp( $end ) );
1554 $opts[
'LIMIT'] = $limit;
1558 $order = ( !$start && $end !== null ) ?
'ASC' :
'DESC';
1559 $opts[
'ORDER BY'] =
"oi_timestamp $order";
1560 $opts[
'USE INDEX'] = [
'oldimage' =>
'oi_name_timestamp' ];
1562 $this->getHookRunner()->onLocalFile__getHistory( $this, $tables, $fields,
1563 $conds, $opts, $join_conds );
1565 $res =
$dbr->select( $tables, $fields, $conds, __METHOD__, $opts, $join_conds );
1568 foreach (
$res as $row ) {
1569 $r[] = $this->repo->newFileFromRow( $row );
1572 if ( $order ==
'ASC' ) {
1573 $r = array_reverse( $r );
1590 if ( !$this->exists() ) {
1594 # Polymorphic function name to distinguish foreign and local fetches
1595 $fname = static::class .
'::' . __FUNCTION__;
1597 $dbr = $this->repo->getReplicaDB();
1599 if ( $this->historyLine == 0 ) {
1600 $fileQuery = self::getQueryInfo();
1601 $this->historyRes =
$dbr->select( $fileQuery[
'tables'],
1602 $fileQuery[
'fields'] + [
1603 'oi_archive_name' =>
$dbr->addQuotes(
'' ),
1606 [
'img_name' => $this->title->getDBkey() ],
1612 if ( $this->historyRes->numRows() == 0 ) {
1613 $this->historyRes =
null;
1617 } elseif ( $this->historyLine == 1 ) {
1618 $fileQuery = OldLocalFile::getQueryInfo();
1619 $this->historyRes =
$dbr->select(
1620 $fileQuery[
'tables'],
1621 $fileQuery[
'fields'],
1622 [
'oi_name' => $this->title->getDBkey() ],
1624 [
'ORDER BY' =>
'oi_timestamp DESC' ],
1628 $this->historyLine++;
1630 return $this->historyRes->fetchObject();
1638 $this->historyLine = 0;
1640 if ( $this->historyRes !==
null ) {
1641 $this->historyRes =
null;
1678 public function upload( $src, $comment, $pageText, $flags = 0, $props =
false,
1679 $timestamp =
false,
Authority $uploader =
null, $tags = [],
1680 $createNullRevision =
true, $revert =
false
1682 if ( $this->getRepo()->getReadOnlyReason() !==
false ) {
1683 return $this->readOnlyFatalStatus();
1684 } elseif ( MediaWikiServices::getInstance()->getRevisionStore()->isReadOnly() ) {
1687 return $this->readOnlyFatalStatus();
1690 $srcPath = ( $src instanceof
FSFile ) ? $src->getPath() : $src;
1695 $props = $this->repo->getFileProps( $srcPath );
1697 $mwProps =
new MWFileProps( MediaWikiServices::getInstance()->getMimeAnalyzer() );
1698 $props = $mwProps->getPropsFromPath( $srcPath,
true );
1705 if ( is_string( $props[
'metadata'] ) ) {
1712 $metadata = $props[
'metadata'];
1715 if ( is_array( $metadata ) ) {
1716 $options[
'headers'] = $handler->getContentHeaders( $metadata );
1719 $options[
'headers'] = [];
1723 $comment = trim( $comment );
1725 $status = $this->publish( $src, $flags, $options );
1727 if ( $status->successCount >= 2 ) {
1734 $oldver = $status->value;
1736 if ( $uploader ===
null ) {
1738 $uploader = RequestContext::getMain()->getAuthority();
1741 $uploadStatus = $this->recordUpload3(
1749 $createNullRevision,
1752 if ( !$uploadStatus->isOK() ) {
1753 if ( $uploadStatus->hasMessage(
'filenotfound' ) ) {
1755 $status->fatal(
'filenotfound', $srcPath );
1757 $status->merge( $uploadStatus );
1789 bool $createNullRevision =
true,
1790 bool $revert =
false
1792 $dbw = $this->repo->getPrimaryDB();
1794 # Imports or such might force a certain timestamp; otherwise we generate
1795 # it and can fudge it slightly to keep (name,timestamp) unique on re-upload.
1796 if ( $timestamp ===
false ) {
1797 $timestamp = $dbw->timestamp();
1798 $allowTimeKludge =
true;
1800 $allowTimeKludge =
false;
1803 $props = $props ?: $this->repo->getFileProps( $this->getVirtualUrl() );
1804 $props[
'description'] = $comment;
1805 $props[
'timestamp'] =
wfTimestamp( TS_MW, $timestamp );
1806 $this->setProps( $props );
1807 $mimeAnalyzer = MediaWikiServices::getInstance()->getMimeAnalyzer();
1808 if ( !$mimeAnalyzer->isValidMajorMimeType( $this->major_mime ) ) {
1809 $this->major_mime =
'unknown';
1812 # Fail now if the file isn't there
1813 if ( !$this->fileExists ) {
1814 wfDebug( __METHOD__ .
": File " . $this->getRel() .
" went missing!" );
1819 $actorNormalizaton = MediaWikiServices::getInstance()->getActorNormalization();
1821 $dbw->startAtomic( __METHOD__ );
1823 $actorId = $actorNormalizaton->acquireActorId( $performer->
getUser(), $dbw );
1824 $this->user = $performer->
getUser();
1826 # Test to see if the row exists using INSERT IGNORE
1827 # This avoids race conditions by locking the row until the commit, and also
1828 # doesn't deadlock. SELECT FOR UPDATE causes a deadlock for every race condition.
1829 $commentStore = MediaWikiServices::getInstance()->getCommentStore();
1830 $commentFields = $commentStore->insert( $dbw,
'img_description', $comment );
1831 $actorFields = [
'img_actor' => $actorId ];
1832 $dbw->insert(
'image',
1834 'img_name' => $this->getName(),
1835 'img_size' => $this->size,
1836 'img_width' => intval( $this->width ),
1837 'img_height' => intval( $this->height ),
1838 'img_bits' => $this->bits,
1839 'img_media_type' => $this->media_type,
1840 'img_major_mime' => $this->major_mime,
1841 'img_minor_mime' => $this->minor_mime,
1842 'img_timestamp' => $timestamp,
1843 'img_metadata' => $this->getMetadataForDb( $dbw ),
1844 'img_sha1' => $this->sha1
1845 ] + $commentFields + $actorFields,
1849 $reupload = ( $dbw->affectedRows() == 0 );
1852 $row = $dbw->selectRow(
1854 [
'img_timestamp',
'img_sha1' ],
1855 [
'img_name' => $this->getName() ],
1857 [
'LOCK IN SHARE MODE' ]
1860 if ( $row && $row->img_sha1 === $this->sha1 ) {
1861 $dbw->endAtomic( __METHOD__ );
1862 wfDebug( __METHOD__ .
": File " . $this->getRel() .
" already exists!" );
1867 if ( $allowTimeKludge ) {
1868 # Use LOCK IN SHARE MODE to ignore any transaction snapshotting
1869 $lUnixtime = $row ? (int)
wfTimestamp( TS_UNIX, $row->img_timestamp ) : false;
1870 # Avoid a timestamp that is not newer than the last version
1871 # TODO: the image/oldimage tables should be like page/revision with an ID field
1872 if ( $lUnixtime && (
int)
wfTimestamp( TS_UNIX, $timestamp ) <= $lUnixtime ) {
1874 $timestamp = $dbw->timestamp( $lUnixtime + 1 );
1875 $this->timestamp =
wfTimestamp( TS_MW, $timestamp );
1879 $tables = [
'image' ];
1881 'oi_name' =>
'img_name',
1882 'oi_archive_name' => $dbw->addQuotes( $oldver ),
1883 'oi_size' =>
'img_size',
1884 'oi_width' =>
'img_width',
1885 'oi_height' =>
'img_height',
1886 'oi_bits' =>
'img_bits',
1887 'oi_description_id' =>
'img_description_id',
1888 'oi_timestamp' =>
'img_timestamp',
1889 'oi_metadata' =>
'img_metadata',
1890 'oi_media_type' =>
'img_media_type',
1891 'oi_major_mime' =>
'img_major_mime',
1892 'oi_minor_mime' =>
'img_minor_mime',
1893 'oi_sha1' =>
'img_sha1',
1894 'oi_actor' =>
'img_actor',
1898 # (T36993) Note: $oldver can be empty here, if the previous
1899 # version of the file was broken. Allow registration of the new
1900 # version to continue anyway, because that's better than having
1901 # an image that's not fixable by user operations.
1902 # Collision, this is an update of a file
1903 # Insert previous contents into oldimage
1904 $dbw->insertSelect(
'oldimage', $tables, $fields,
1905 [
'img_name' => $this->getName() ], __METHOD__, [], [], $joins );
1907 # Update the current image row
1908 $dbw->update(
'image',
1910 'img_size' => $this->size,
1911 'img_width' => intval( $this->width ),
1912 'img_height' => intval( $this->height ),
1913 'img_bits' => $this->bits,
1914 'img_media_type' => $this->media_type,
1915 'img_major_mime' => $this->major_mime,
1916 'img_minor_mime' => $this->minor_mime,
1917 'img_timestamp' => $timestamp,
1918 'img_metadata' => $this->getMetadataForDb( $dbw ),
1919 'img_sha1' => $this->sha1
1920 ] + $commentFields + $actorFields,
1921 [
'img_name' => $this->getName() ],
1927 $descId = $descTitle->getArticleID();
1928 $wikiPage = MediaWikiServices::getInstance()->getWikiPageFactory()->newFromTitle( $descTitle );
1930 throw new MWException(
'Cannot instance WikiFilePage for ' . $this->getName()
1931 .
', got instance of ' . get_class( $wikiPage ) );
1933 $wikiPage->setFile( $this );
1937 $logAction =
'revert';
1938 } elseif ( $reupload ) {
1939 $logAction =
'overwrite';
1941 $logAction =
'upload';
1945 $logEntry->setTimestamp( $this->timestamp );
1946 $logEntry->setPerformer( $performer->
getUser() );
1947 $logEntry->setComment( $comment );
1948 $logEntry->setTarget( $descTitle );
1951 $logEntry->setParameters(
1953 'img_sha1' => $this->sha1,
1954 'img_timestamp' => $timestamp,
1963 $logId = $logEntry->insert();
1965 if ( $descTitle->exists() ) {
1966 if ( $createNullRevision ) {
1967 $revStore = MediaWikiServices::getInstance()->getRevisionStore();
1970 $formatter->setContext( RequestContext::newExtraneousContext( $descTitle ) );
1971 $editSummary = $formatter->getPlainActionText();
1973 $nullRevRecord =
$revStore->newNullRevision(
1981 if ( $nullRevRecord ) {
1982 $inserted =
$revStore->insertRevisionOn( $nullRevRecord, $dbw );
1984 $this->getHookRunner()->onRevisionFromEditComplete(
1987 $inserted->getParentId(),
1992 $wikiPage->updateRevisionOn( $dbw, $inserted );
1994 $logEntry->setAssociatedRevId( $inserted->getId() );
1998 $newPageContent =
null;
2007 $dbw->endAtomic( __METHOD__ );
2008 $fname = __METHOD__;
2010 # Do some cache purges after final commit so that:
2011 # a) Changes are more likely to be seen post-purge
2012 # b) They won't cause rollback of the log publish/update above
2018 $reupload, $wikiPage, $newPageContent, $comment, $performer,
2019 $logEntry, $logId, $descId, $tags, $fname
2021 # Update memcache after the commit
2022 $this->invalidateCache();
2024 $updateLogPage =
false;
2025 if ( $newPageContent ) {
2026 # New file page; create the description page.
2027 # There's already a log entry, so don't make a second RC entry
2028 # CDN and file cache for the description page are purged by doUserEditContent.
2029 $status = $wikiPage->doUserEditContent(
2036 if ( isset( $status->value[
'revision-record'] ) ) {
2038 $revRecord = $status->value[
'revision-record'];
2040 $logEntry->setAssociatedRevId( $revRecord->getId() );
2044 if ( isset( $status->value[
'revision-record'] ) ) {
2046 $revRecord = $status->value[
'revision-record'];
2047 $updateLogPage = $revRecord->getPageId();
2050 # Existing file page: invalidate description page cache
2051 $title = $wikiPage->getTitle();
2052 $title->invalidateCache();
2053 $hcu = MediaWikiServices::getInstance()->getHtmlCacheUpdater();
2054 $hcu->purgeTitleUrls(
$title, $hcu::PURGE_INTENT_TXROUND_REFLECTED );
2055 # Allow the new file version to be patrolled from the page footer
2059 # Update associated rev id. This should be done by $logEntry->insert() earlier,
2060 # but setAssociatedRevId() wasn't called at that point yet...
2061 $logParams = $logEntry->getParameters();
2062 $logParams[
'associated_rev_id'] = $logEntry->getAssociatedRevId();
2064 if ( $updateLogPage ) {
2065 # Also log page, in case where we just created it above
2066 $update[
'log_page'] = $updateLogPage;
2068 $this->getRepo()->getPrimaryDB()->update(
2071 [
'log_id' => $logId ],
2074 $this->getRepo()->getPrimaryDB()->insert(
2077 'ls_field' =>
'associated_rev_id',
2078 'ls_value' => (
string)$logEntry->getAssociatedRevId(),
2079 'ls_log_id' => $logId,
2084 # Add change tags, if any
2086 $logEntry->addTags( $tags );
2089 # Uploads can be patrolled
2090 $logEntry->setIsPatrollable(
true );
2092 # Now that the log entry is up-to-date, make an RC entry.
2093 $logEntry->publish( $logId );
2095 # Run hook for other updates (typically more cache purging)
2096 $this->getHookRunner()->onFileUpload( $this, $reupload, !$newPageContent );
2099 # Delete old thumbnails
2100 $this->purgeThumbnails();
2101 # Remove the old file from the CDN cache
2102 $hcu = MediaWikiServices::getInstance()->getHtmlCacheUpdater();
2103 $hcu->purgeUrls( $this->getUrl(), $hcu::PURGE_INTENT_TXROUND_REFLECTED );
2105 # Update backlink pages pointing to this title if created
2106 $blcFactory = MediaWikiServices::getInstance()->getBacklinkCacheFactory();
2107 LinksUpdate::queueRecursiveJobsForTable(
2111 $performer->
getUser()->getName(),
2112 $blcFactory->getBacklinkCache( $this->getTitle() )
2116 $this->prerenderThumbnails();
2120 # Invalidate cache for all pages using this file
2124 [
'causeAction' =>
'file-upload',
'causeAgent' => $performer->
getUser()->getName() ]
2132 $dbw->onTransactionCommitOrIdle(
static function () use ( $reupload, $purgeUpdate, $cacheUpdateJob ) {
2140 MediaWikiServices::getInstance()->getJobQueueGroup()->lazyPush( $cacheUpdateJob );
2162 public function publish( $src, $flags = 0, array $options = [] ) {
2163 return $this->publishTo( $src, $this->getRel(), $flags, $options );
2182 protected function publishTo( $src, $dstRel, $flags = 0, array $options = [] ) {
2183 $srcPath = ( $src instanceof
FSFile ) ? $src->getPath() : $src;
2185 $repo = $this->getRepo();
2186 if ( $repo->getReadOnlyReason() !==
false ) {
2187 return $this->readOnlyFatalStatus();
2190 $status = $this->acquireFileLock();
2191 if ( !$status->isOK() ) {
2195 if ( $this->isOld() ) {
2196 $archiveRel = $dstRel;
2197 $archiveName = basename( $archiveRel );
2199 $archiveName =
wfTimestamp( TS_MW ) .
'!' . $this->getName();
2200 $archiveRel = $this->getArchiveRel( $archiveName );
2203 if ( $repo->hasSha1Storage() ) {
2205 ? $repo->getFileSha1( $srcPath )
2208 $wrapperBackend = $repo->getBackend();
2209 '@phan-var FileBackendDBRepoWrapper $wrapperBackend';
2210 $dst = $wrapperBackend->getPathForSHA1( $sha1 );
2211 $status = $repo->quickImport( $src, $dst );
2216 if ( $this->exists() ) {
2217 $status->value = $archiveName;
2221 $status = $repo->publish( $srcPath, $dstRel, $archiveRel, $flags, $options );
2223 if ( $status->value ==
'new' ) {
2224 $status->value =
'';
2226 $status->value = $archiveName;
2230 $this->releaseFileLock();
2253 $localRepo = MediaWikiServices::getInstance()->getRepoGroup()->getLocalRepo();
2254 if ( $this->getRepo()->getReadOnlyReason() !==
false ) {
2255 return $this->readOnlyFatalStatus();
2258 wfDebugLog(
'imagemove',
"Got request to move {$this->name} to " . $target->getText() );
2261 $status = $batch->addCurrent();
2262 if ( !$status->isOK() ) {
2265 $archiveNames = $batch->addOlds();
2266 $status = $batch->execute();
2268 wfDebugLog(
'imagemove',
"Finished moving {$this->name}" );
2271 $oldTitleFile = $localRepo->newFile( $this->title );
2272 $newTitleFile = $localRepo->newFile( $target );
2273 DeferredUpdates::addUpdate(
2275 $this->getRepo()->getPrimaryDB(),
2277 static function () use ( $oldTitleFile, $newTitleFile, $archiveNames ) {
2278 $oldTitleFile->purgeEverything();
2279 foreach ( $archiveNames as $archiveName ) {
2281 '@phan-var OldLocalFile $oldTitleFile';
2282 $oldTitleFile->purgeOldThumbnails( $archiveName );
2284 $newTitleFile->purgeEverything();
2287 DeferredUpdates::PRESEND
2290 if ( $status->isOK() ) {
2292 $this->title = $target;
2295 $this->hashPath =
null;
2318 if ( $this->getRepo()->getReadOnlyReason() !==
false ) {
2319 return $this->readOnlyFatalStatus();
2324 $batch->addCurrent();
2326 $archiveNames = $batch->addOlds();
2327 $status = $batch->execute();
2329 if ( $status->isOK() ) {
2330 DeferredUpdates::addUpdate( SiteStatsUpdate::factory( [
'images' => -1 ] ) );
2334 DeferredUpdates::addUpdate(
2336 $this->getRepo()->getPrimaryDB(),
2338 function () use ( $archiveNames ) {
2339 $this->purgeEverything();
2340 foreach ( $archiveNames as $archiveName ) {
2341 $this->purgeOldThumbnails( $archiveName );
2345 DeferredUpdates::PRESEND
2350 foreach ( $archiveNames as $archiveName ) {
2351 $purgeUrls[] = $this->getArchiveUrl( $archiveName );
2354 $hcu = MediaWikiServices::getInstance()->getHtmlCacheUpdater();
2355 $hcu->purgeUrls( $purgeUrls, $hcu::PURGE_INTENT_TXROUND_REFLECTED );
2379 if ( $this->getRepo()->getReadOnlyReason() !==
false ) {
2380 return $this->readOnlyFatalStatus();
2385 $batch->addOld( $archiveName );
2386 $status = $batch->execute();
2388 $this->purgeOldThumbnails( $archiveName );
2389 if ( $status->isOK() ) {
2390 $this->purgeDescription();
2393 $url = $this->getArchiveUrl( $archiveName );
2394 $hcu = MediaWikiServices::getInstance()->getHtmlCacheUpdater();
2395 $hcu->purgeUrls( $url, $hcu::PURGE_INTENT_TXROUND_REFLECTED );
2412 public function restore( $versions = [], $unsuppress =
false ) {
2413 if ( $this->getRepo()->getReadOnlyReason() !==
false ) {
2414 return $this->readOnlyFatalStatus();
2422 $batch->addIds( $versions );
2424 $status = $batch->execute();
2425 if ( $status->isGood() ) {
2426 $cleanupStatus = $batch->cleanup();
2427 $cleanupStatus->successCount = 0;
2428 $cleanupStatus->failCount = 0;
2429 $status->merge( $cleanupStatus );
2446 if ( !$this->title ) {
2450 return $this->title->getLocalURL();
2463 if ( !$this->title ) {
2467 $services = MediaWikiServices::getInstance();
2468 $page = $services->getPageStore()->getPageByReference( $this->
getTitle() );
2474 $parserOptions = ParserOptions::newFromUserAndLang(
2475 RequestContext::getMain()->
getUser(),
2479 $parserOptions = ParserOptions::newFromContext( RequestContext::getMain() );
2482 $parseStatus = $services->getParserOutputAccess()
2483 ->getParserOutput( $page, $parserOptions );
2485 if ( !$parseStatus->isGood() ) {
2489 return $parseStatus->getValue()->getText();
2501 if ( $audience === self::FOR_PUBLIC && $this->isDeleted( self::DELETED_USER ) ) {
2503 } elseif ( $audience === self::FOR_THIS_USER && !$this->userCan( self::DELETED_USER, $performer ) ) {
2518 if ( $audience == self::FOR_PUBLIC && $this->isDeleted( self::DELETED_COMMENT ) ) {
2520 } elseif ( $audience == self::FOR_THIS_USER && !$this->userCan( self::DELETED_COMMENT, $performer ) ) {
2523 return $this->description;
2534 return $this->timestamp;
2542 if ( !$this->exists() ) {
2549 if ( $this->descriptionTouched ===
null ) {
2551 'page_namespace' => $this->title->getNamespace(),
2552 'page_title' => $this->title->getDBkey()
2554 $touched = $this->repo->getReplicaDB()->selectField(
'page',
'page_touched', $cond, __METHOD__ );
2555 $this->descriptionTouched = $touched ?
wfTimestamp( TS_MW, $touched ) :
false;
2558 return $this->descriptionTouched;
2577 return $this->extraDataLoaded
2578 && strlen(
serialize( $this->metadataArray ) ) <= self::CACHE_FIELD_MAX_LEN;
2590 return Status::wrap( $this->getRepo()->getBackend()->lockFiles(
2591 [ $this->getPath() ], LockManager::LOCK_EX, $timeout
2602 return Status::wrap( $this->getRepo()->getBackend()->unlockFiles(
2603 [ $this->getPath() ], LockManager::LOCK_EX
2618 if ( !$this->locked ) {
2619 $logger = LoggerFactory::getInstance(
'LocalFile' );
2621 $dbw = $this->repo->getPrimaryDB();
2622 $makesTransaction = !$dbw->trxLevel();
2623 $dbw->startAtomic( self::ATOMIC_SECTION_LOCK );
2627 $status = $this->acquireFileLock( 10 );
2628 if ( !$status->isGood() ) {
2629 $dbw->endAtomic( self::ATOMIC_SECTION_LOCK );
2630 $logger->warning(
"Failed to lock '{file}'", [
'file' => $this->name ] );
2636 $dbw->onTransactionResolution(
2637 function () use ( $logger ) {
2638 $status = $this->releaseFileLock();
2639 if ( !$status->isGood() ) {
2640 $logger->error(
"Failed to unlock '{file}'", [
'file' => $this->name ] );
2646 $this->lockedOwnTrx = $makesTransaction;
2651 return $this->lockedOwnTrx;
2665 if ( $this->locked ) {
2667 if ( !$this->locked ) {
2668 $dbw = $this->repo->getPrimaryDB();
2669 $dbw->endAtomic( self::ATOMIC_SECTION_LOCK );
2670 $this->lockedOwnTrx =
false;
2679 return $this->getRepo()->newFatal(
'filereadonlyerror', $this->getName(),
2680 $this->getRepo()->getName(), $this->getRepo()->getReadOnlyReason() );
unserialize( $serialized)
wfDebug( $text, $dest='all', array $context=[])
Sends a line to the debug log if enabled or, optionally, to a comment in output.
wfDeprecatedMsg( $msg, $version=false, $component=false, $callerOffset=2)
Log a deprecation warning with arbitrary message text.
wfDebugLog( $logGroup, $text, $dest='all', array $context=[])
Send a line to a supplementary debug log file, if configured, or main debug log if not.
wfTimestamp( $outputtype=TS_UNIX, $ts=0)
Get a timestamp string in one of various formats.
static purgePatrolFooterCache( $articleID)
Purge the cache used to check if it is worth showing the patrol footer For example,...
Deferrable Update for closure/callback updates that should use auto-commit mode.
static makeContent( $text, Title $title=null, $modelId=null, $format=null)
Convenience function for creating a Content object from a given textual representation.
static addUpdate(DeferrableUpdate $update, $stage=self::POSTSEND)
Add an update to the pending update queue for execution at the appropriate time.
Class representing a non-directory file on the file system.
static getSha1Base36FromPath( $path)
Get a SHA-1 hash of a file in the local filesystem, in base-36 lower case encoding,...
File backend exception for checked exceptions (e.g.
static isStoragePath( $path)
Check if a given path is a "mwstore://" path.
static isVirtualUrl( $url)
Determine if a string is an mwrepo:// URL.
Implements some public methods and some protected utility functions which are required by multiple ch...
string $url
The URL corresponding to one of the four basic zones.
assertRepoDefined()
Assert that $this->repo is set to a valid FileRepo instance.
getName()
Return the name of this file.
getVirtualUrl( $suffix=false)
Get the public zone virtual URL for a current version source file.
assertTitleDefined()
Assert that $this->title is set to a Title.
isMultipage()
Returns 'true' if this file is a type which supports multiple pages, e.g.
FileRepo LocalRepo ForeignAPIRepo bool $repo
Some member variables can be lazy-initialised using __get().
getHandler()
Get a MediaHandler instance for this file.
string null $name
The name of a file from its title object.
static newForBacklinks(PageReference $page, $table, $params=[])
Base class for language-specific code.
Helper class for file deletion.
Helper class for file movement.
Helper class for file undeletion.
Local file in the wiki's own database.
exists()
canRender inherited
setProps( $info)
Set properties in this object to be equal to those given in the associative array $info.
maybeUpgradeRow()
Upgrade a row if it needs it.
static newFromKey( $sha1, $repo, $timestamp=false)
Create a LocalFile from a SHA-1 key Do not call this except from inside a repo class.
array $metadataArray
Unserialized metadata.
getMediaType()
Returns the type of the media in the file.
string[] $unloadedMetadataBlobs
Map of metadata item name to blob address for items that exist but have not yet been loaded into $thi...
deleteOldFile( $archiveName, $reason, UserIdentity $user, $suppress=false)
Delete an old version of the file.
move( $target)
getLinksTo inherited
lock()
Start an atomic DB section and lock the image for update or increments a reference counter if the loc...
loadFromRow( $row, $prefix='img_')
Load file metadata from a DB result row.
getCacheKey()
Get the memcached key for the main data for this file, or false if there is no access to the shared c...
getWidth( $page=1)
Return the width of the image.
__destruct()
Clean up any dangling locks.
string $mime
MIME type, determined by MimeAnalyzer::guessMimeType.
reserializeMetadata()
Write the metadata back to the database with the current serialization format.
isMissing()
splitMime inherited
getDescriptionUrl()
isMultipage inherited
getHistory( $limit=null, $start=null, $end=null, $inc=true)
purgeDescription inherited
getMutableCacheKeys(WANObjectCache $cache)
static getQueryInfo(array $options=[])
Return the tables, fields, and join conditions to be selected to create a new localfile object.
releaseFileLock()
Release a lock acquired with acquireFileLock().
getUploader(int $audience=self::FOR_PUBLIC, Authority $performer=null)
loadMetadataFromDbFieldValue(IDatabase $db, $metadataBlob)
Unserialize a metadata blob which came from the database and store it in $this.
loadFromDB( $flags=0)
Load file metadata from the DB.
load( $flags=0)
Load file metadata from cache or DB, unless already loaded.
loadMetadataFromString( $metadataString)
Unserialize a metadata string which came from some non-DB source, or is the return value of IDatabase...
string $media_type
MEDIATYPE_xxx (bitmap, drawing, audio...)
deleteFile( $reason, UserIdentity $user, $suppress=false)
Delete all versions of the file.
acquireFileLock( $timeout=0)
Acquire an exclusive lock on the file, indicating an intention to write to the file backend.
purgeCache( $options=[])
Delete all previously generated thumbnails, refresh metadata in memcached and purge the CDN.
loadFromFile( $path=null)
Load metadata from the file itself.
string null $metadataSerializationFormat
One of the MDS_* constants, giving the format of the metadata as stored in the DB,...
int $size
Size in bytes (loadFromXxx)
getDescriptionShortUrl()
Get short description URL for a file based on the page ID.
getThumbnails( $archiveName=false)
getTransformScript inherited
static newFromTitle( $title, $repo, $unused=null)
Create a LocalFile from a title Do not call this except from inside a repo class.
getMetadataForDb(IDatabase $db)
Serialize the metadata array for insertion into img_metadata, oi_metadata or fa_metadata.
purgeOldThumbnails( $archiveName)
Delete cached transformed files for an archived version only.
publishTo( $src, $dstRel, $flags=0, array $options=[])
Move or copy a file to a specified location.
purgeThumbList( $dir, $files)
Delete a list of thumbnails visible at urls.
unlock()
Decrement the lock reference count and end the atomic section if it reaches zero.
getLazyCacheFields( $prefix='img_')
Returns the list of object properties that are included as-is in the cache, only when they're not too...
getSize()
Returns the size of the image file, in bytes.
invalidateCache()
Purge the file object/metadata cache.
getMimeType()
Returns the MIME type of the file.
bool $extraDataLoaded
Whether or not lazy-loaded data has been loaded from the database.
string $sha1
SHA-1 base 36 content hash.
getDescription( $audience=self::FOR_PUBLIC, Authority $performer=null)
getHeight( $page=1)
Return the height of the image.
prerenderThumbnails()
Prerenders a configurable set of thumbnails.
resetHistory()
Reset the history pointer to the first element of the history.
unprefixRow( $row, $prefix='img_')
static newFromRow( $row, $repo)
Create a LocalFile from a title Do not call this except from inside a repo class.
publish( $src, $flags=0, array $options=[])
Move or copy a file to its public location.
restore( $versions=[], $unsuppress=false)
Restore all or specified deleted revisions to the given file.
getCacheFields( $prefix='img_')
Returns the list of object properties that are included as-is in the cache.
int $bits
Returned by getimagesize (loadFromXxx)
getMetadataItems(array $itemNames)
Get multiple elements of the unserialized handler-specific metadata.
getDescriptionText(Language $lang=null)
Get the HTML text of the description page This is not used by ImagePage for local files,...
purgeThumbnails( $options=[])
Delete cached transformed files for the current version only.
loadExtraFromDB()
Load lazy file metadata from the DB.
nextHistoryLine()
Returns the history of this file, line by line.
upgradeRow()
Fix assorted version-related problems with the image row by reloading it from the file.
int $deleted
Bitfield akin to rev_deleted.
getMetadata()
Get handler-specific metadata as a serialized string.
getMetadataArray()
Get unserialized handler-specific metadata.
__construct( $title, $repo)
Do not call this except from inside a repo class.
bool $dataLoaded
Whether or not core data has been loaded from the database (loadFromXxx)
bool $fileExists
Does the file exist on disk? (loadFromXxx)
upload( $src, $comment, $pageText, $flags=0, $props=false, $timestamp=false, Authority $uploader=null, $tags=[], $createNullRevision=true, $revert=false)
getHashPath inherited
recordUpload3(string $oldver, string $comment, string $pageText, Authority $performer, $props=false, $timestamp=false, $tags=[], bool $createNullRevision=true, bool $revert=false)
Record a file upload in the upload log and the image table (version 3)
string[] $metadataBlobs
Map of metadata item name to blob address.
static makeParamBlob( $params)
Create a blob from a parameter array.
MimeMagic helper wrapper.
Class for creating new log entries and inserting them into the database.
A class containing constants representing the names of configuration variables.
static getQueryInfo(array $options=[])
Return the tables, fields, and join conditions to be selected to create a new oldlocalfile object.
static newFatal( $message,... $parameters)
Factory function for fatal errors.
static newGood( $value=null)
Factory function for good results.
Generic operation result class Has warning/error list, boolean status and arbitrary value.
Job for asynchronous rendering of thumbnails.
static newFromText( $text, $defaultNamespace=NS_MAIN)
Create a new Title from text, such as what one would find in a link.
Multi-datacenter aware caching interface.
Special handling for representing file pages.
foreach( $mmfl['setupFiles'] as $fileName) if($queue) if(empty( $mmfl['quiet'])) $s
if(PHP_SAPI !='cli-server') if(!isset( $_SERVER['SCRIPT_FILENAME'])) $file
Item class for a filearchive table row.
if(!isset( $args[0])) $lang