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;
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 );
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() ) );
347 private function loadFromCache() {
348 $this->dataLoaded =
false;
349 $this->extraDataLoaded =
false;
358 $cache = MediaWikiServices::getInstance()->getMainWANObjectCache();
359 $cachedValues = $cache->getWithSetCallback(
362 function ( $oldValue, &$ttl, array &$setOpts ) use ( $cache ) {
363 $setOpts += Database::getCacheSetOptions( $this->repo->getReplicaDB() );
370 if ( $this->fileExists ) {
371 foreach ( $fields as $field ) {
372 $cacheVal[$field] = $this->$field;
376 $cacheVal[
'user'] = $this->user->getId();
377 $cacheVal[
'user_text'] = $this->user->getName();
381 if ( $this->metadataBlobs ) {
382 $cacheVal[
'metadata'] = array_diff_key(
383 $this->metadataArray, $this->metadataBlobs );
394 if ( isset( $cacheVal[$field] )
395 && strlen( serialize( $cacheVal[$field] ) ) > 100 * 1024
397 unset( $cacheVal[$field] );
398 if ( $field ===
'metadata' ) {
399 unset( $cacheVal[
'metadataBlobs'] );
404 if ( $this->fileExists ) {
405 $ttl = $cache->adaptiveTTL( (
int)
wfTimestamp( TS_UNIX, $this->timestamp ), $ttl );
407 $ttl = $cache::TTL_DAY;
412 [
'version' => self::VERSION ]
415 $this->fileExists = $cachedValues[
'fileExists'];
416 if ( $this->fileExists ) {
420 $this->dataLoaded =
true;
421 $this->extraDataLoaded =
true;
423 $this->extraDataLoaded = $this->extraDataLoaded && isset( $cachedValues[$field] );
436 $this->repo->getPrimaryDB()->onTransactionPreCommitOrIdle(
437 static function () use ( $key ) {
438 MediaWikiServices::getInstance()->getMainWANObjectCache()->delete( $key );
464 if ( $prefix !==
'' ) {
465 throw new InvalidArgumentException(
466 __METHOD__ .
' with a non-empty prefix is no longer supported.'
474 return [
'size',
'width',
'height',
'bits',
'media_type',
475 'major_mime',
'minor_mime',
'timestamp',
'sha1',
'description' ];
486 if ( $prefix !==
'' ) {
487 throw new InvalidArgumentException(
488 __METHOD__ .
' with a non-empty prefix is no longer supported.'
493 return [
'metadata' ];
502 $fname = static::class .
'::' . __FUNCTION__;
504 # Unconditionally set loaded=true, we don't want the accessors constantly rechecking
505 $this->dataLoaded =
true;
506 $this->extraDataLoaded =
true;
508 $dbr = ( $flags & self::READ_LATEST )
509 ? $this->repo->getPrimaryDB()
510 : $this->repo->getReplicaDB();
512 $fileQuery = static::getQueryInfo();
513 $row =
$dbr->selectRow(
514 $fileQuery[
'tables'],
515 $fileQuery[
'fields'],
516 [
'img_name' => $this->
getName() ],
525 $this->fileExists =
false;
535 if ( !$this->title ) {
539 $fname = static::class .
'::' . __FUNCTION__;
541 # Unconditionally set loaded=true, we don't want the accessors constantly rechecking
542 $this->extraDataLoaded =
true;
544 $db = $this->repo->getReplicaDB();
545 $fieldMap = $this->loadExtraFieldsWithTimestamp( $db, $fname );
547 $db = $this->repo->getPrimaryDB();
548 $fieldMap = $this->loadExtraFieldsWithTimestamp( $db, $fname );
552 if ( isset( $fieldMap[
'metadata'] ) ) {
556 throw new MWException(
"Could not find data for image '{$this->getName()}'." );
565 private function loadExtraFieldsWithTimestamp(
$dbr, $fname ) {
569 $row =
$dbr->selectRow(
570 $fileQuery[
'tables'],
571 $fileQuery[
'fields'],
573 'img_name' => $this->
getName(),
574 'img_timestamp' =>
$dbr->timestamp( $this->getTimestamp() ),
583 # File may have been uploaded over in the meantime; check the old versions
585 $row =
$dbr->selectRow(
586 $fileQuery[
'tables'],
587 $fileQuery[
'fields'],
590 'oi_timestamp' =>
$dbr->timestamp( $this->getTimestamp() ),
611 $array = (array)$row;
612 $prefixLength = strlen( $prefix );
615 if ( substr( array_key_first( $array ), 0, $prefixLength ) !== $prefix ) {
616 throw new MWException( __METHOD__ .
': incorrect $prefix parameter' );
620 foreach ( $array as
$name => $value ) {
621 $decoded[substr(
$name, $prefixLength )] = $value;
643 $this->dataLoaded =
true;
647 $this->name = $unprefixed[
'name'];
648 $this->media_type = $unprefixed[
'media_type'];
650 $services = MediaWikiServices::getInstance();
651 $this->description = $services->getCommentStore()
652 ->getComment(
"{$prefix}description", $row )->text;
654 $this->user = $services->getUserFactory()->newFromAnyId(
655 $unprefixed[
'user'] ??
null,
656 $unprefixed[
'user_text'] ??
null,
657 $unprefixed[
'actor'] ??
null
660 $this->timestamp =
wfTimestamp( TS_MW, $unprefixed[
'timestamp'] );
663 $this->repo->getReplicaDB(), $unprefixed[
'metadata'] );
665 if ( empty( $unprefixed[
'major_mime'] ) ) {
666 $this->major_mime =
'unknown';
667 $this->minor_mime =
'unknown';
668 $this->mime =
'unknown/unknown';
670 if ( !$unprefixed[
'minor_mime'] ) {
671 $unprefixed[
'minor_mime'] =
'unknown';
673 $this->major_mime = $unprefixed[
'major_mime'];
674 $this->minor_mime = $unprefixed[
'minor_mime'];
675 $this->mime = $unprefixed[
'major_mime'] .
'/' . $unprefixed[
'minor_mime'];
679 $this->sha1 = rtrim( $unprefixed[
'sha1'],
"\0" );
685 $this->size = +$unprefixed[
'size'];
686 $this->width = +$unprefixed[
'width'];
687 $this->height = +$unprefixed[
'height'];
688 $this->bits = +$unprefixed[
'bits'];
691 $extraFields = array_diff(
692 array_keys( $unprefixed ),
694 'name',
'media_type',
'description_text',
'description_data',
695 'description_cid',
'user',
'user_text',
'actor',
'timestamp',
696 'metadata',
'major_mime',
'minor_mime',
'sha1',
'size',
'width',
700 if ( $extraFields ) {
702 'Passing extra fields (' .
703 implode(
', ', $extraFields )
704 .
') to ' . __METHOD__ .
' was deprecated in MediaWiki 1.37. ' .
705 'Property assignment will be removed in a later version.',
707 foreach ( $extraFields as $field ) {
708 $this->$field = $unprefixed[$field];
712 $this->fileExists =
true;
720 public function load( $flags = 0 ) {
721 if ( !$this->dataLoaded ) {
722 if ( $flags & self::READ_LATEST ) {
725 $this->loadFromCache();
729 if ( ( $flags & self::LOAD_ALL ) && !$this->extraDataLoaded ) {
740 if ( MediaWikiServices::getInstance()->getReadOnlyMode()->isReadOnly() || $this->upgrading ) {
745 $reserialize =
false;
746 if ( $this->media_type ===
null || $this->mime ==
'image/svg' ) {
755 && $this->repo->isMetadataUpdateEnabled()
758 } elseif ( $this->repo->isJsonMetadataEnabled()
759 && $this->repo->isMetadataReserializeEnabled()
761 if ( $this->repo->isSplitMetadataEnabled() && $this->isMetadataOversize() ) {
763 } elseif ( $this->metadataSerializationFormat !== self::MDS_EMPTY &&
764 $this->metadataSerializationFormat !== self::MDS_JSON ) {
771 if ( $upgrade || $reserialize ) {
772 $this->upgrading =
true;
775 $this->upgrading =
false;
793 return $this->upgraded;
801 $dbw = $this->repo->getPrimaryDB();
805 $freshnessCondition = [
'img_timestamp' => $dbw->timestamp( $this->
getTimestamp() ) ];
809 # Don't destroy file info of missing files
810 if ( !$this->fileExists ) {
811 wfDebug( __METHOD__ .
": file does not exist, aborting" );
818 wfDebug( __METHOD__ .
': upgrading ' . $this->
getName() .
" to the current schema" );
820 $dbw->update(
'image',
822 'img_size' => $this->size,
823 'img_width' => $this->width,
824 'img_height' => $this->height,
825 'img_bits' => $this->bits,
826 'img_media_type' => $this->media_type,
827 'img_major_mime' => $major,
828 'img_minor_mime' => $minor,
830 'img_sha1' => $this->sha1,
833 [
'img_name' => $this->
getName() ],
841 $this->upgraded =
true;
849 if ( MediaWikiServices::getInstance()->getReadOnlyMode()->isReadOnly() ) {
852 $dbw = $this->repo->getPrimaryDB();
857 'img_name' => $this->name,
858 'img_timestamp' => $dbw->timestamp( $this->timestamp ),
862 $this->upgraded =
true;
877 $this->dataLoaded =
true;
879 $fields[] =
'fileExists';
881 foreach ( $fields as $field ) {
882 if ( isset( $info[$field] ) ) {
883 $this->$field = $info[$field];
888 if ( isset( $info[
'user'] ) &&
889 isset( $info[
'user_text'] ) &&
890 $info[
'user_text'] !==
''
896 if ( isset( $info[
'major_mime'] ) ) {
897 $this->mime =
"{$info['major_mime']}/{$info['minor_mime']}";
898 } elseif ( isset( $info[
'mime'] ) ) {
899 $this->mime = $info[
'mime'];
900 [ $this->major_mime, $this->minor_mime ] =
self::splitMime( $this->mime );
903 if ( isset( $info[
'metadata'] ) ) {
904 if ( is_string( $info[
'metadata'] ) ) {
906 } elseif ( is_array( $info[
'metadata'] ) ) {
907 $this->metadataArray = $info[
'metadata'];
908 if ( isset( $info[
'metadataBlobs'] ) ) {
909 $this->metadataBlobs = $info[
'metadataBlobs'];
910 $this->unloadedMetadataBlobs = array_diff_key(
911 $this->metadataBlobs,
915 $this->metadataBlobs = [];
916 $this->unloadedMetadataBlobs = [];
919 $logger = LoggerFactory::getInstance(
'LocalFile' );
920 $logger->warning( __METHOD__ .
' given invalid metadata of type ' .
921 gettype( $info[
'metadata'] ) );
922 $this->metadataArray = [];
924 $this->extraDataLoaded =
true;
944 if ( $this->missing ===
null ) {
949 return $this->missing;
974 return $dim[
'width'];
1007 return $dim[
'height'];
1026 if ( !$this->title ) {
1030 $pageId = $this->title->getArticleID();
1033 $url = $this->repo->makeUrl( [
'curid' => $pageId ] );
1034 if (
$url !==
false ) {
1051 } elseif ( array_keys( $data ) === [
'_error' ] ) {
1053 return $data[
'_error'];
1066 $this->
load( self::LOAD_ALL );
1067 if ( $this->unloadedMetadataBlobs ) {
1069 array_unique( array_merge(
1070 array_keys( $this->metadataArray ),
1071 array_keys( $this->unloadedMetadataBlobs )
1079 $this->load( self::LOAD_ALL );
1082 foreach ( $itemNames as $itemName ) {
1083 if ( array_key_exists( $itemName, $this->metadataArray ) ) {
1084 $result[$itemName] = $this->metadataArray[$itemName];
1085 } elseif ( isset( $this->unloadedMetadataBlobs[$itemName] ) ) {
1086 $addresses[$itemName] = $this->unloadedMetadataBlobs[$itemName];
1091 $resultFromBlob = $this->metadataStorageHelper->getMetadataFromBlobStore( $addresses );
1092 foreach ( $addresses as $itemName => $address ) {
1093 unset( $this->unloadedMetadataBlobs[$itemName] );
1094 $value = $resultFromBlob[$itemName] ??
null;
1095 if ( $value !==
null ) {
1096 $result[$itemName] = $value;
1097 $this->metadataArray[$itemName] = $value;
1116 $this->load( self::LOAD_ALL );
1117 if ( !$this->metadataArray && !$this->metadataBlobs ) {
1119 } elseif ( $this->repo->isJsonMetadataEnabled() ) {
1120 $s = $this->getJsonMetadata();
1122 $s = serialize( $this->getMetadataArray() );
1124 if ( !is_string(
$s ) ) {
1125 throw new MWException(
'Could not serialize image metadata value for DB' );
1136 private function getJsonMetadata() {
1139 'data' => array_diff_key( $this->metadataArray, $this->metadataBlobs )
1143 if ( $this->metadataBlobs ) {
1144 $envelope[
'blobs'] = $this->metadataBlobs;
1147 [
$s, $blobAddresses ] = $this->metadataStorageHelper->getJsonMetadata( $this, $envelope );
1150 $this->metadataBlobs += $blobAddresses;
1161 private function isMetadataOversize() {
1162 if ( !$this->repo->isSplitMetadataEnabled() ) {
1165 $threshold = $this->repo->getSplitMetadataThreshold();
1166 $directItems = array_diff_key( $this->metadataArray, $this->metadataBlobs );
1167 foreach ( $directItems as $value ) {
1168 if ( strlen( $this->metadataStorageHelper->jsonEncode( $value ) ) > $threshold ) {
1184 $this->loadMetadataFromString( $db->
decodeBlob( $metadataBlob ) );
1195 $this->extraDataLoaded =
true;
1196 $this->metadataArray = [];
1197 $this->metadataBlobs = [];
1198 $this->unloadedMetadataBlobs = [];
1199 $metadataString = (string)$metadataString;
1200 if ( $metadataString ===
'' ) {
1201 $this->metadataSerializationFormat = self::MDS_EMPTY;
1204 if ( $metadataString[0] ===
'{' ) {
1205 $envelope = $this->metadataStorageHelper->jsonDecode( $metadataString );
1208 $this->metadataArray = [
'_error' => $metadataString ];
1209 $this->metadataSerializationFormat = self::MDS_LEGACY;
1211 $this->metadataSerializationFormat = self::MDS_JSON;
1212 if ( isset( $envelope[
'data'] ) ) {
1213 $this->metadataArray = $envelope[
'data'];
1215 if ( isset( $envelope[
'blobs'] ) ) {
1216 $this->metadataBlobs = $this->unloadedMetadataBlobs = $envelope[
'blobs'];
1221 $data = @unserialize( $metadataString );
1222 if ( !is_array( $data ) ) {
1224 $data = [
'_error' => $metadataString ];
1225 $this->metadataSerializationFormat = self::MDS_LEGACY;
1227 $this->metadataSerializationFormat = self::MDS_PHP;
1229 $this->metadataArray = $data;
1240 return (
int)$this->bits;
1274 return $this->media_type;
1291 return $this->fileExists;
1311 if ( $archiveName ) {
1312 $dir = $this->getArchiveThumbPath( $archiveName );
1314 $dir = $this->getThumbPath();
1317 $backend = $this->repo->getBackend();
1320 $iterator = $backend->getFileList( [
'dir' => $dir ] );
1321 if ( $iterator !==
null ) {
1322 foreach ( $iterator as
$file ) {
1335 private function purgeMetadataCache() {
1336 $this->invalidateCache();
1349 $this->maybeUpgradeRow();
1350 $this->purgeMetadataCache();
1353 $this->purgeThumbnails( $options );
1356 $hcu = MediaWikiServices::getInstance()->getHtmlCacheUpdater();
1359 !empty( $options[
'forThumbRefresh'] )
1360 ? $hcu::PURGE_PRESEND
1361 : $hcu::PURGE_INTENT_TXROUND_REFLECTED
1372 $thumbs = $this->getThumbnails( $archiveName );
1375 $dir = array_shift( $thumbs );
1376 $this->purgeThumbList( $dir, $thumbs );
1379 foreach ( $thumbs as $thumb ) {
1380 $urls[] = $this->getArchiveThumbUrl( $archiveName, $thumb );
1384 $this->getHookRunner()->onLocalFilePurgeThumbnails( $this, $archiveName, $urls );
1387 $hcu = MediaWikiServices::getInstance()->getHtmlCacheUpdater();
1388 $hcu->purgeUrls( $urls, $hcu::PURGE_PRESEND );
1398 $thumbs = $this->getThumbnails();
1401 $dir = array_shift( $thumbs );
1402 $this->purgeThumbList( $dir, $thumbs );
1406 foreach ( $thumbs as $thumb ) {
1407 $urls[] = $this->getThumbUrl( $thumb );
1411 if ( !empty( $options[
'forThumbRefresh'] ) ) {
1412 $handler = $this->getHandler();
1414 $handler->filterThumbnailPurgeList( $thumbs, $options );
1419 $this->getHookRunner()->onLocalFilePurgeThumbnails( $this,
false, $urls );
1422 $hcu = MediaWikiServices::getInstance()->getHtmlCacheUpdater();
1425 !empty( $options[
'forThumbRefresh'] )
1426 ? $hcu::PURGE_PRESEND
1427 : $hcu::PURGE_INTENT_TXROUND_REFLECTED
1438 $uploadThumbnailRenderMap = MediaWikiServices::getInstance()
1439 ->getMainConfig()->get( MainConfigNames::UploadThumbnailRenderMap );
1443 $sizes = $uploadThumbnailRenderMap;
1446 foreach ( $sizes as $size ) {
1447 if ( $this->isMultipage() ) {
1450 $pageLimit = min( $this->pageCount(), self::MAX_PAGE_RENDER_JOBS );
1452 for ( $page = 1; $page <= $pageLimit; $page++ ) {
1455 [
'transformParams' => [
1461 } elseif ( $this->isVectorized() || $this->getWidth() > $size ) {
1464 [
'transformParams' => [
'width' => $size ] ]
1470 MediaWikiServices::getInstance()->getJobQueueGroup()->lazyPush( $jobs );
1481 $fileListDebug = strtr(
1482 var_export( $files,
true ),
1485 wfDebug( __METHOD__ .
": $fileListDebug" );
1487 if ( $this->repo->supportsSha1URLs() ) {
1488 $reference = $this->getSha1();
1490 $reference = $this->getName();
1494 foreach ( $files as
$file ) {
1495 # Check that the reference (filename or sha1) is part of the thumb name
1496 # This is a basic check to avoid erasing unrelated directories
1497 if ( str_contains(
$file, $reference )
1498 || str_contains(
$file,
"-thumbnail" )
1500 $purgeList[] =
"{$dir}/{$file}";
1504 # Delete the thumbnails
1505 $this->repo->quickPurgeBatch( $purgeList );
1506 # Clear out the thumbnail directory if empty
1507 $this->repo->quickCleanDir( $dir );
1521 public function getHistory( $limit =
null, $start =
null, $end =
null, $inc =
true ) {
1522 if ( !$this->exists() ) {
1526 $dbr = $this->repo->getReplicaDB();
1529 $tables = $oldFileQuery[
'tables'];
1530 $fields = $oldFileQuery[
'fields'];
1531 $join_conds = $oldFileQuery[
'joins'];
1532 $conds = $opts = [];
1533 $eq = $inc ?
'=' :
'';
1534 $conds[] =
"oi_name = " .
$dbr->addQuotes( $this->title->getDBkey() );
1537 $conds[] =
"oi_timestamp <$eq " .
$dbr->addQuotes(
$dbr->timestamp( $start ) );
1541 $conds[] =
"oi_timestamp >$eq " .
$dbr->addQuotes(
$dbr->timestamp( $end ) );
1545 $opts[
'LIMIT'] = $limit;
1549 $order = ( !$start && $end !== null ) ?
'ASC' :
'DESC';
1550 $opts[
'ORDER BY'] =
"oi_timestamp $order";
1551 $opts[
'USE INDEX'] = [
'oldimage' =>
'oi_name_timestamp' ];
1553 $this->getHookRunner()->onLocalFile__getHistory( $this, $tables, $fields,
1554 $conds, $opts, $join_conds );
1556 $res =
$dbr->select( $tables, $fields, $conds, __METHOD__, $opts, $join_conds );
1559 foreach (
$res as $row ) {
1560 $r[] = $this->repo->newFileFromRow( $row );
1563 if ( $order ==
'ASC' ) {
1564 $r = array_reverse( $r );
1581 if ( !$this->exists() ) {
1585 # Polymorphic function name to distinguish foreign and local fetches
1586 $fname = static::class .
'::' . __FUNCTION__;
1588 $dbr = $this->repo->getReplicaDB();
1590 if ( $this->historyLine == 0 ) {
1591 $fileQuery = self::getQueryInfo();
1592 $this->historyRes =
$dbr->select( $fileQuery[
'tables'],
1593 $fileQuery[
'fields'] + [
1594 'oi_archive_name' =>
$dbr->addQuotes(
'' ),
1597 [
'img_name' => $this->title->getDBkey() ],
1603 if ( $this->historyRes->numRows() == 0 ) {
1604 $this->historyRes =
null;
1608 } elseif ( $this->historyLine == 1 ) {
1610 $this->historyRes =
$dbr->select(
1611 $fileQuery[
'tables'],
1612 $fileQuery[
'fields'],
1613 [
'oi_name' => $this->title->getDBkey() ],
1615 [
'ORDER BY' =>
'oi_timestamp DESC' ],
1619 $this->historyLine++;
1621 return $this->historyRes->fetchObject();
1629 $this->historyLine = 0;
1631 if ( $this->historyRes !==
null ) {
1632 $this->historyRes =
null;
1669 public function upload( $src, $comment, $pageText, $flags = 0, $props =
false,
1670 $timestamp =
false,
Authority $uploader =
null, $tags = [],
1671 $createNullRevision =
true, $revert =
false
1673 if ( $this->getRepo()->getReadOnlyReason() !==
false ) {
1674 return $this->readOnlyFatalStatus();
1675 } elseif ( MediaWikiServices::getInstance()->getRevisionStore()->isReadOnly() ) {
1678 return $this->readOnlyFatalStatus();
1681 $srcPath = ( $src instanceof
FSFile ) ? $src->getPath() : $src;
1686 $props = $this->repo->getFileProps( $srcPath );
1688 $mwProps =
new MWFileProps( MediaWikiServices::getInstance()->getMimeAnalyzer() );
1689 $props = $mwProps->getPropsFromPath( $srcPath,
true );
1696 if ( is_string( $props[
'metadata'] ) ) {
1701 $metadata = @unserialize( $props[
'metadata'] );
1703 $metadata = $props[
'metadata'];
1706 if ( is_array( $metadata ) ) {
1707 $options[
'headers'] = $handler->getContentHeaders( $metadata );
1710 $options[
'headers'] = [];
1714 $comment = trim( $comment );
1716 $status = $this->publish( $src, $flags, $options );
1718 if ( $status->successCount >= 2 ) {
1725 $oldver = $status->value;
1727 $uploadStatus = $this->recordUpload3(
1735 $createNullRevision,
1738 if ( !$uploadStatus->isOK() ) {
1739 if ( $uploadStatus->hasMessage(
'filenotfound' ) ) {
1741 $status->fatal(
'filenotfound', $srcPath );
1743 $status->merge( $uploadStatus );
1775 bool $createNullRevision =
true,
1776 bool $revert =
false
1778 $dbw = $this->repo->getPrimaryDB();
1780 # Imports or such might force a certain timestamp; otherwise we generate
1781 # it and can fudge it slightly to keep (name,timestamp) unique on re-upload.
1782 if ( $timestamp ===
false ) {
1783 $timestamp = $dbw->timestamp();
1784 $allowTimeKludge =
true;
1786 $allowTimeKludge =
false;
1789 $props = $props ?: $this->repo->getFileProps( $this->getVirtualUrl() );
1790 $props[
'description'] = $comment;
1791 $props[
'timestamp'] =
wfTimestamp( TS_MW, $timestamp );
1792 $this->setProps( $props );
1794 # Fail now if the file isn't there
1795 if ( !$this->fileExists ) {
1796 wfDebug( __METHOD__ .
": File " . $this->getRel() .
" went missing!" );
1801 $actorNormalizaton = MediaWikiServices::getInstance()->getActorNormalization();
1803 $dbw->startAtomic( __METHOD__ );
1805 $actorId = $actorNormalizaton->acquireActorId( $performer->
getUser(), $dbw );
1806 $this->user = $performer->
getUser();
1808 # Test to see if the row exists using INSERT IGNORE
1809 # This avoids race conditions by locking the row until the commit, and also
1810 # doesn't deadlock. SELECT FOR UPDATE causes a deadlock for every race condition.
1811 $commentStore = MediaWikiServices::getInstance()->getCommentStore();
1812 $commentFields = $commentStore->insert( $dbw,
'img_description', $comment );
1813 $actorFields = [
'img_actor' => $actorId ];
1814 $dbw->insert(
'image',
1816 'img_name' => $this->getName(),
1817 'img_size' => $this->size,
1818 'img_width' => intval( $this->width ),
1819 'img_height' => intval( $this->height ),
1820 'img_bits' => $this->bits,
1821 'img_media_type' => $this->media_type,
1822 'img_major_mime' => $this->major_mime,
1823 'img_minor_mime' => $this->minor_mime,
1824 'img_timestamp' => $dbw->timestamp( $timestamp ),
1825 'img_metadata' => $this->getMetadataForDb( $dbw ),
1826 'img_sha1' => $this->sha1
1827 ] + $commentFields + $actorFields,
1831 $reupload = ( $dbw->affectedRows() == 0 );
1834 $row = $dbw->selectRow(
1836 [
'img_timestamp',
'img_sha1' ],
1837 [
'img_name' => $this->getName() ],
1839 [
'LOCK IN SHARE MODE' ]
1842 if ( $row && $row->img_sha1 === $this->sha1 ) {
1843 $dbw->endAtomic( __METHOD__ );
1844 wfDebug( __METHOD__ .
": File " . $this->getRel() .
" already exists!" );
1849 if ( $allowTimeKludge ) {
1850 # Use LOCK IN SHARE MODE to ignore any transaction snapshotting
1851 $lUnixtime = $row ? (int)
wfTimestamp( TS_UNIX, $row->img_timestamp ) : false;
1852 # Avoid a timestamp that is not newer than the last version
1853 # TODO: the image/oldimage tables should be like page/revision with an ID field
1854 if ( $lUnixtime && (
int)
wfTimestamp( TS_UNIX, $timestamp ) <= $lUnixtime ) {
1856 $timestamp = $dbw->timestamp( $lUnixtime + 1 );
1857 $this->timestamp =
wfTimestamp( TS_MW, $timestamp );
1861 $tables = [
'image' ];
1863 'oi_name' =>
'img_name',
1864 'oi_archive_name' => $dbw->addQuotes( $oldver ),
1865 'oi_size' =>
'img_size',
1866 'oi_width' =>
'img_width',
1867 'oi_height' =>
'img_height',
1868 'oi_bits' =>
'img_bits',
1869 'oi_description_id' =>
'img_description_id',
1870 'oi_timestamp' =>
'img_timestamp',
1871 'oi_metadata' =>
'img_metadata',
1872 'oi_media_type' =>
'img_media_type',
1873 'oi_major_mime' =>
'img_major_mime',
1874 'oi_minor_mime' =>
'img_minor_mime',
1875 'oi_sha1' =>
'img_sha1',
1876 'oi_actor' =>
'img_actor',
1880 # (T36993) Note: $oldver can be empty here, if the previous
1881 # version of the file was broken. Allow registration of the new
1882 # version to continue anyway, because that's better than having
1883 # an image that's not fixable by user operations.
1884 # Collision, this is an update of a file
1885 # Insert previous contents into oldimage
1886 $dbw->insertSelect(
'oldimage', $tables, $fields,
1887 [
'img_name' => $this->getName() ], __METHOD__, [], [], $joins );
1889 # Update the current image row
1890 $dbw->update(
'image',
1892 'img_size' => $this->size,
1893 'img_width' => intval( $this->width ),
1894 'img_height' => intval( $this->height ),
1895 'img_bits' => $this->bits,
1896 'img_media_type' => $this->media_type,
1897 'img_major_mime' => $this->major_mime,
1898 'img_minor_mime' => $this->minor_mime,
1899 'img_timestamp' => $dbw->timestamp( $timestamp ),
1900 'img_metadata' => $this->getMetadataForDb( $dbw ),
1901 'img_sha1' => $this->sha1
1902 ] + $commentFields + $actorFields,
1903 [
'img_name' => $this->getName() ],
1909 $descId = $descTitle->getArticleID();
1910 $wikiPage = MediaWikiServices::getInstance()->getWikiPageFactory()->newFromTitle( $descTitle );
1912 throw new MWException(
'Cannot instance WikiFilePage for ' . $this->getName()
1913 .
', got instance of ' . get_class( $wikiPage ) );
1915 $wikiPage->setFile( $this );
1919 $logAction =
'revert';
1920 } elseif ( $reupload ) {
1921 $logAction =
'overwrite';
1923 $logAction =
'upload';
1927 $logEntry->setTimestamp( $this->timestamp );
1928 $logEntry->setPerformer( $performer->
getUser() );
1929 $logEntry->setComment( $comment );
1930 $logEntry->setTarget( $descTitle );
1933 $logEntry->setParameters(
1935 'img_sha1' => $this->sha1,
1936 'img_timestamp' => $timestamp,
1945 $logId = $logEntry->insert();
1947 if ( $descTitle->exists() ) {
1948 if ( $createNullRevision ) {
1949 $revStore = MediaWikiServices::getInstance()->getRevisionStore();
1953 $editSummary = $formatter->getPlainActionText();
1954 $summary = CommentStoreComment::newUnsavedComment( $editSummary );
1955 $nullRevRecord =
$revStore->newNullRevision(
1963 if ( $nullRevRecord ) {
1964 $inserted =
$revStore->insertRevisionOn( $nullRevRecord, $dbw );
1966 $this->getHookRunner()->onRevisionFromEditComplete(
1969 $inserted->getParentId(),
1974 $wikiPage->updateRevisionOn( $dbw, $inserted );
1976 $logEntry->setAssociatedRevId( $inserted->getId() );
1980 $newPageContent =
null;
1989 $dbw->endAtomic( __METHOD__ );
1990 $fname = __METHOD__;
1992 # Do some cache purges after final commit so that:
1993 # a) Changes are more likely to be seen post-purge
1994 # b) They won't cause rollback of the log publish/update above
1999 $reupload, $wikiPage, $newPageContent, $comment, $performer,
2000 $logEntry, $logId, $descId, $tags, $fname
2002 # Update memcache after the commit
2003 $this->invalidateCache();
2005 $updateLogPage =
false;
2006 if ( $newPageContent ) {
2007 # New file page; create the description page.
2008 # There's already a log entry, so don't make a second RC entry
2009 # CDN and file cache for the description page are purged by doUserEditContent.
2010 $status = $wikiPage->doUserEditContent(
2017 $revRecord = $status->getNewRevision();
2020 $logEntry->setAssociatedRevId( $revRecord->getId() );
2024 $updateLogPage = $revRecord->getPageId();
2027 # Existing file page: invalidate description page cache
2028 $title = $wikiPage->getTitle();
2029 $title->invalidateCache();
2030 $hcu = MediaWikiServices::getInstance()->getHtmlCacheUpdater();
2031 $hcu->purgeTitleUrls(
$title, $hcu::PURGE_INTENT_TXROUND_REFLECTED );
2032 # Allow the new file version to be patrolled from the page footer
2036 # Update associated rev id. This should be done by $logEntry->insert() earlier,
2037 # but setAssociatedRevId() wasn't called at that point yet...
2038 $logParams = $logEntry->getParameters();
2039 $logParams[
'associated_rev_id'] = $logEntry->getAssociatedRevId();
2041 if ( $updateLogPage ) {
2042 # Also log page, in case where we just created it above
2043 $update[
'log_page'] = $updateLogPage;
2045 $this->getRepo()->getPrimaryDB()->update(
2048 [
'log_id' => $logId ],
2051 $this->getRepo()->getPrimaryDB()->insert(
2054 'ls_field' =>
'associated_rev_id',
2055 'ls_value' => (
string)$logEntry->getAssociatedRevId(),
2056 'ls_log_id' => $logId,
2061 # Add change tags, if any
2063 $logEntry->addTags( $tags );
2066 # Uploads can be patrolled
2067 $logEntry->setIsPatrollable(
true );
2069 # Now that the log entry is up-to-date, make an RC entry.
2070 $logEntry->publish( $logId );
2072 # Run hook for other updates (typically more cache purging)
2073 $this->getHookRunner()->onFileUpload( $this, $reupload, !$newPageContent );
2076 # Delete old thumbnails
2077 $this->purgeThumbnails();
2078 # Remove the old file from the CDN cache
2079 $hcu = MediaWikiServices::getInstance()->getHtmlCacheUpdater();
2080 $hcu->purgeUrls( $this->getUrl(), $hcu::PURGE_INTENT_TXROUND_REFLECTED );
2082 # Update backlink pages pointing to this title if created
2083 $blcFactory = MediaWikiServices::getInstance()->getBacklinkCacheFactory();
2084 LinksUpdate::queueRecursiveJobsForTable(
2088 $performer->
getUser()->getName(),
2089 $blcFactory->getBacklinkCache( $this->getTitle() )
2093 $this->prerenderThumbnails();
2097 # Invalidate cache for all pages using this file
2101 [
'causeAction' =>
'file-upload',
'causeAgent' => $performer->
getUser()->getName() ]
2109 $dbw->onTransactionCommitOrIdle(
static function () use ( $reupload, $purgeUpdate, $cacheUpdateJob ) {
2117 MediaWikiServices::getInstance()->getJobQueueGroup()->lazyPush( $cacheUpdateJob );
2139 public function publish( $src, $flags = 0, array $options = [] ) {
2140 return $this->publishTo( $src, $this->getRel(), $flags, $options );
2159 protected function publishTo( $src, $dstRel, $flags = 0, array $options = [] ) {
2160 $srcPath = ( $src instanceof
FSFile ) ? $src->getPath() : $src;
2162 $repo = $this->getRepo();
2163 if ( $repo->getReadOnlyReason() !==
false ) {
2164 return $this->readOnlyFatalStatus();
2167 $status = $this->acquireFileLock();
2168 if ( !$status->isOK() ) {
2172 if ( $this->isOld() ) {
2173 $archiveRel = $dstRel;
2174 $archiveName = basename( $archiveRel );
2176 $archiveName =
wfTimestamp( TS_MW ) .
'!' . $this->getName();
2177 $archiveRel = $this->getArchiveRel( $archiveName );
2180 if ( $repo->hasSha1Storage() ) {
2182 ? $repo->getFileSha1( $srcPath )
2185 $wrapperBackend = $repo->getBackend();
2186 '@phan-var FileBackendDBRepoWrapper $wrapperBackend';
2187 $dst = $wrapperBackend->getPathForSHA1( $sha1 );
2188 $status = $repo->quickImport( $src, $dst );
2193 if ( $this->exists() ) {
2194 $status->value = $archiveName;
2198 $status = $repo->publish( $srcPath, $dstRel, $archiveRel, $flags, $options );
2200 if ( $status->value ==
'new' ) {
2201 $status->value =
'';
2203 $status->value = $archiveName;
2207 $this->releaseFileLock();
2230 $localRepo = MediaWikiServices::getInstance()->getRepoGroup()->getLocalRepo();
2231 if ( $this->getRepo()->getReadOnlyReason() !==
false ) {
2232 return $this->readOnlyFatalStatus();
2235 wfDebugLog(
'imagemove',
"Got request to move {$this->name} to " . $target->getText() );
2238 $status = $batch->addCurrent();
2239 if ( !$status->isOK() ) {
2242 $archiveNames = $batch->addOlds();
2243 $status = $batch->execute();
2245 wfDebugLog(
'imagemove',
"Finished moving {$this->name}" );
2248 $oldTitleFile = $localRepo->newFile( $this->title );
2249 $newTitleFile = $localRepo->newFile( $target );
2252 $this->getRepo()->getPrimaryDB(),
2254 static function () use ( $oldTitleFile, $newTitleFile, $archiveNames ) {
2255 $oldTitleFile->purgeEverything();
2256 foreach ( $archiveNames as $archiveName ) {
2258 '@phan-var OldLocalFile $oldTitleFile';
2259 $oldTitleFile->purgeOldThumbnails( $archiveName );
2261 $newTitleFile->purgeEverything();
2264 DeferredUpdates::PRESEND
2267 if ( $status->isOK() ) {
2269 $this->title = $target;
2272 $this->hashPath =
null;
2295 if ( $this->getRepo()->getReadOnlyReason() !==
false ) {
2296 return $this->readOnlyFatalStatus();
2301 $batch->addCurrent();
2303 $archiveNames = $batch->addOlds();
2304 $status = $batch->execute();
2306 if ( $status->isOK() ) {
2313 $this->getRepo()->getPrimaryDB(),
2315 function () use ( $archiveNames ) {
2316 $this->purgeEverything();
2317 foreach ( $archiveNames as $archiveName ) {
2318 $this->purgeOldThumbnails( $archiveName );
2322 DeferredUpdates::PRESEND
2327 foreach ( $archiveNames as $archiveName ) {
2328 $purgeUrls[] = $this->getArchiveUrl( $archiveName );
2331 $hcu = MediaWikiServices::getInstance()->getHtmlCacheUpdater();
2332 $hcu->purgeUrls( $purgeUrls, $hcu::PURGE_INTENT_TXROUND_REFLECTED );
2356 if ( $this->getRepo()->getReadOnlyReason() !==
false ) {
2357 return $this->readOnlyFatalStatus();
2362 $batch->addOld( $archiveName );
2363 $status = $batch->execute();
2365 $this->purgeOldThumbnails( $archiveName );
2366 if ( $status->isOK() ) {
2367 $this->purgeDescription();
2370 $url = $this->getArchiveUrl( $archiveName );
2371 $hcu = MediaWikiServices::getInstance()->getHtmlCacheUpdater();
2372 $hcu->purgeUrls( $url, $hcu::PURGE_INTENT_TXROUND_REFLECTED );
2389 public function restore( $versions = [], $unsuppress =
false ) {
2390 if ( $this->getRepo()->getReadOnlyReason() !==
false ) {
2391 return $this->readOnlyFatalStatus();
2399 $batch->addIds( $versions );
2401 $status = $batch->execute();
2402 if ( $status->isGood() ) {
2403 $cleanupStatus = $batch->cleanup();
2404 $cleanupStatus->successCount = 0;
2405 $cleanupStatus->failCount = 0;
2406 $status->merge( $cleanupStatus );
2424 return $this->title ? $this->title->getLocalURL() :
false;
2437 if ( !$this->title ) {
2441 $services = MediaWikiServices::getInstance();
2442 $page = $services->getPageStore()->getPageByReference( $this->
getTitle() );
2456 $parseStatus = $services->getParserOutputAccess()
2457 ->getParserOutput( $page, $parserOptions );
2459 if ( !$parseStatus->isGood() ) {
2463 return $parseStatus->getValue()->getText();
2475 if ( $audience === self::FOR_PUBLIC && $this->isDeleted( self::DELETED_USER ) ) {
2477 } elseif ( $audience === self::FOR_THIS_USER && !$this->userCan( self::DELETED_USER, $performer ) ) {
2492 if ( $audience == self::FOR_PUBLIC && $this->isDeleted( self::DELETED_COMMENT ) ) {
2494 } elseif ( $audience == self::FOR_THIS_USER && !$this->userCan( self::DELETED_COMMENT, $performer ) ) {
2497 return $this->description;
2508 return $this->timestamp;
2516 if ( !$this->exists() ) {
2523 if ( $this->descriptionTouched ===
null ) {
2525 'page_namespace' => $this->title->getNamespace(),
2526 'page_title' => $this->title->getDBkey()
2528 $touched = $this->repo->getReplicaDB()->selectField(
'page',
'page_touched', $cond, __METHOD__ );
2529 $this->descriptionTouched = $touched ?
wfTimestamp( TS_MW, $touched ) :
false;
2532 return $this->descriptionTouched;
2551 return $this->extraDataLoaded
2552 && strlen( serialize( $this->metadataArray ) ) <= self::CACHE_FIELD_MAX_LEN;
2564 return Status::wrap( $this->getRepo()->getBackend()->lockFiles(
2576 return Status::wrap( $this->getRepo()->getBackend()->unlockFiles(
2592 if ( !$this->locked ) {
2593 $logger = LoggerFactory::getInstance(
'LocalFile' );
2595 $dbw = $this->repo->getPrimaryDB();
2596 $makesTransaction = !$dbw->trxLevel();
2597 $dbw->startAtomic( self::ATOMIC_SECTION_LOCK );
2601 $status = $this->acquireFileLock( 10 );
2602 if ( !$status->isGood() ) {
2603 $dbw->endAtomic( self::ATOMIC_SECTION_LOCK );
2604 $logger->warning(
"Failed to lock '{file}'", [
'file' => $this->name ] );
2610 $dbw->onTransactionResolution(
2611 function () use ( $logger ) {
2612 $status = $this->releaseFileLock();
2613 if ( !$status->isGood() ) {
2614 $logger->error(
"Failed to unlock '{file}'", [
'file' => $this->name ] );
2620 $this->lockedOwnTrx = $makesTransaction;
2625 return $this->lockedOwnTrx;
2639 if ( $this->locked ) {
2641 if ( !$this->locked ) {
2642 $dbw = $this->repo->getPrimaryDB();
2643 $dbw->endAtomic( self::ATOMIC_SECTION_LOCK );
2644 $this->lockedOwnTrx =
false;
2653 return $this->getRepo()->newFatal(
'filereadonlyerror', $this->getName(),
2654 $this->getRepo()->getName(), $this->getRepo()->getReadOnlyReason() );
wfDebug( $text, $dest='all', array $context=[])
Sends a line to the debug log if enabled or, optionally, to a comment in output.
wfDeprecatedMsg( $msg, $version=false, $component=false, $callerOffset=2)
Log a deprecation warning with arbitrary message text.
wfDebugLog( $logGroup, $text, $dest='all', array $context=[])
Send a line to a supplementary debug log file, if configured, or main debug log if not.
wfTimestamp( $outputtype=TS_UNIX, $ts=0)
Get a timestamp string in one of various formats.
static purgePatrolFooterCache( $articleID)
Purge the cache used to check if it is worth showing the patrol footer For example,...
Deferrable Update for closure/callback updates that should use auto-commit mode.
static makeContent( $text, Title $title=null, $modelId=null, $format=null)
Convenience function for creating a Content object from a given textual representation.
static addUpdate(DeferrableUpdate $update, $stage=self::POSTSEND)
Add an update to the pending update queue for execution at the appropriate time.
static addCallableUpdate( $callable, $stage=self::POSTSEND, $dbw=null)
Add an update to the pending update queue that invokes the specified callback when run.
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.
static splitMime(?string $mime)
Split an internet media type into its two components; if not a two-part name, set the minor type to '...
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
string $path
The storage path corresponding to one of the zones.
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 @stable to override.
__destruct()
Clean up any dangling locks.
string $mime
MIME type, determined by MimeAnalyzer::guessMimeType.
reserializeMetadata()
Write the metadata back to the database with the current serialization format.
isMissing()
splitMime inherited
getDescriptionUrl()
isMultipage inherited
getHistory( $limit=null, $start=null, $end=null, $inc=true)
purgeDescription inherited
static getQueryInfo(array $options=[])
Return the tables, fields, and join conditions to be selected to create a new localfile object.
releaseFileLock()
Release a lock acquired with acquireFileLock().
getUploader(int $audience=self::FOR_PUBLIC, Authority $performer=null)
loadMetadataFromDbFieldValue(IDatabase $db, $metadataBlob)
Unserialize a metadata blob which came from the database and store it in $this.
loadFromDB( $flags=0)
Load file metadata from the DB.
load( $flags=0)
Load file metadata from cache or DB, unless already loaded.
loadMetadataFromString( $metadataString)
Unserialize a metadata string which came from some non-DB source, or is the return value of IDatabase...
string $media_type
MEDIATYPE_xxx (bitmap, drawing, audio...)
deleteFile( $reason, UserIdentity $user, $suppress=false)
Delete all versions of the file.
acquireFileLock( $timeout=0)
Acquire an exclusive lock on the file, indicating an intention to write to the file backend.
purgeCache( $options=[])
Delete all previously generated thumbnails, refresh metadata in memcached and purge the CDN.
loadFromFile( $path=null)
Load metadata from the file itself.
getBitDepth()
@stable to override
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 @stable to override.
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 @stable to override.
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 newFromUserAndLang(UserIdentity $user, Language $lang)
Get a ParserOptions object from a given user and language.
static newFromContext(IContextSource $context)
Get a ParserOptions object from a IContextSource object.
static newExtraneousContext(Title $title, $request=[])
Create a new extraneous context.
static getMain()
Get the RequestContext object associated with the main request.
static factory(array $deltas)
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.
static wrap( $sv)
Succinct helper method to wrap a StatusValue.
Job for asynchronous rendering of thumbnails, e.g.
static newFromText( $text, $defaultNamespace=NS_MAIN)
Create a new Title from text, such as what one would find in a link.
static makeTitle( $ns, $title, $fragment='', $interwiki='')
Create a new Title from a namespace index and a DB key.
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