69 private const MDS_EMPTY =
'empty';
72 private const MDS_LEGACY =
'legacy';
75 private const MDS_PHP =
'php';
78 private const MDS_JSON =
'json';
81 private const MAX_PAGE_RENDER_JOBS = 50;
105 protected $metadataArray = [];
116 protected $metadataBlobs = [];
124 protected $unloadedMetadataBlobs = [];
139 protected $repoClass = LocalRepo::class;
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 );
271 $commentQuery = MediaWikiServices::getInstance()->getCommentStore()->getJoin(
'img_description' );
275 'image_actor' =>
'actor'
276 ] + $commentQuery[
'tables'],
290 'img_user' =>
'image_actor.actor_user',
291 'img_user_text' =>
'image_actor.actor_name',
292 ] + $commentQuery[
'fields'],
294 'image_actor' => [
'JOIN',
'actor_id=img_actor' ]
295 ] + $commentQuery[
'joins'],
298 if ( in_array(
'omit-nonlazy', $options,
true ) ) {
302 if ( !in_array(
'omit-lazy', $options,
true ) ) {
305 $ret[
'fields'][] =
'img_metadata';
321 $this->historyLine = 0;
322 $this->historyRes =
null;
323 $this->dataLoaded =
false;
324 $this->extraDataLoaded =
false;
344 return $this->repo->getSharedCacheKey(
'file', sha1( $this->
getName() ) );
360 $this->dataLoaded =
false;
361 $this->extraDataLoaded =
false;
370 $cache = MediaWikiServices::getInstance()->getMainWANObjectCache();
371 $cachedValues =
$cache->getWithSetCallback(
374 function ( $oldValue, &$ttl, array &$setOpts ) use (
$cache ) {
375 $setOpts += Database::getCacheSetOptions( $this->repo->getReplicaDB() );
381 $cacheVal[
'fileExists'] = $this->fileExists;
382 if ( $this->fileExists ) {
383 foreach ( $fields as $field ) {
384 $cacheVal[$field] = $this->$field;
388 $cacheVal[
'user'] = $this->user->getId();
389 $cacheVal[
'user_text'] = $this->user->getName();
393 if ( $this->metadataBlobs ) {
394 $cacheVal[
'metadata'] = array_diff_key(
395 $this->metadataArray, $this->metadataBlobs );
397 $cacheVal[
'metadataBlobs'] = $this->metadataBlobs;
399 $cacheVal[
'metadata'] = $this->metadataArray;
406 if ( isset( $cacheVal[$field] )
407 && strlen(
serialize( $cacheVal[$field] ) ) > 100 * 1024
409 unset( $cacheVal[$field] );
410 if ( $field ===
'metadata' ) {
411 unset( $cacheVal[
'metadataBlobs'] );
416 if ( $this->fileExists ) {
419 $ttl = $cache::TTL_DAY;
424 [
'version' => self::VERSION ]
427 $this->fileExists = $cachedValues[
'fileExists'];
428 if ( $this->fileExists ) {
432 $this->dataLoaded =
true;
433 $this->extraDataLoaded =
true;
435 $this->extraDataLoaded = $this->extraDataLoaded && isset( $cachedValues[$field] );
448 $this->repo->getPrimaryDB()->onTransactionPreCommitOrIdle(
449 static function () use ( $key ) {
450 MediaWikiServices::getInstance()->getMainWANObjectCache()->delete( $key );
476 if ( $prefix !==
'' ) {
477 throw new InvalidArgumentException(
478 __METHOD__ .
' with a non-empty prefix is no longer supported.'
486 return [
'size',
'width',
'height',
'bits',
'media_type',
487 'major_mime',
'minor_mime',
'timestamp',
'sha1',
'description' ];
498 if ( $prefix !==
'' ) {
499 throw new InvalidArgumentException(
500 __METHOD__ .
' with a non-empty prefix is no longer supported.'
505 return [
'metadata' ];
514 $fname = static::class .
'::' . __FUNCTION__;
516 # Unconditionally set loaded=true, we don't want the accessors constantly rechecking
517 $this->dataLoaded =
true;
518 $this->extraDataLoaded =
true;
520 $dbr = ( $flags & self::READ_LATEST )
521 ? $this->repo->getPrimaryDB()
522 : $this->repo->getReplicaDB();
524 $fileQuery = static::getQueryInfo();
525 $row =
$dbr->selectRow(
526 $fileQuery[
'tables'],
527 $fileQuery[
'fields'],
528 [
'img_name' => $this->
getName() ],
537 $this->fileExists =
false;
547 if ( !$this->title ) {
551 $fname = static::class .
'::' . __FUNCTION__;
553 # Unconditionally set loaded=true, we don't want the accessors constantly rechecking
554 $this->extraDataLoaded =
true;
556 $db = $this->repo->getReplicaDB();
559 $db = $this->repo->getPrimaryDB();
564 if ( isset( $fieldMap[
'metadata'] ) ) {
568 throw new MWException(
"Could not find data for image '{$this->getName()}'." );
580 $fileQuery = self::getQueryInfo( [
'omit-nonlazy' ] );
581 $row =
$dbr->selectRow(
582 $fileQuery[
'tables'],
583 $fileQuery[
'fields'],
585 'img_name' => $this->
getName(),
586 'img_timestamp' =>
$dbr->timestamp( $this->getTimestamp() ),
595 # File may have been uploaded over in the meantime; check the old versions
597 $row =
$dbr->selectRow(
598 $fileQuery[
'tables'],
599 $fileQuery[
'fields'],
602 'oi_timestamp' =>
$dbr->timestamp( $this->getTimestamp() ),
623 $array = (array)$row;
624 $prefixLength = strlen( $prefix );
627 if ( substr( key( $array ), 0, $prefixLength ) !== $prefix ) {
628 throw new MWException( __METHOD__ .
': incorrect $prefix parameter' );
632 foreach ( $array as
$name => $value ) {
633 $decoded[substr(
$name, $prefixLength )] = $value;
655 $this->dataLoaded =
true;
659 $this->name = $unprefixed[
'name'];
660 $this->media_type = $unprefixed[
'media_type'];
662 $this->description = MediaWikiServices::getInstance()->getCommentStore()
663 ->getComment(
"{$prefix}description", $row )->text;
666 $unprefixed[
'user'] ??
null,
667 $unprefixed[
'user_text'] ??
null,
668 $unprefixed[
'actor'] ??
null
671 $this->timestamp =
wfTimestamp( TS_MW, $unprefixed[
'timestamp'] );
674 $this->repo->getReplicaDB(), $unprefixed[
'metadata'] );
676 if ( empty( $unprefixed[
'major_mime'] ) ) {
677 $this->major_mime =
'unknown';
678 $this->minor_mime =
'unknown';
679 $this->mime =
'unknown/unknown';
681 if ( !$unprefixed[
'minor_mime'] ) {
682 $unprefixed[
'minor_mime'] =
'unknown';
684 $this->major_mime = $unprefixed[
'major_mime'];
685 $this->minor_mime = $unprefixed[
'minor_mime'];
686 $this->mime = $unprefixed[
'major_mime'] .
'/' . $unprefixed[
'minor_mime'];
690 $this->sha1 = rtrim( $unprefixed[
'sha1'],
"\0" );
696 $this->size = +$unprefixed[
'size'];
697 $this->width = +$unprefixed[
'width'];
698 $this->height = +$unprefixed[
'height'];
699 $this->bits = +$unprefixed[
'bits'];
702 $extraFields = array_diff(
703 array_keys( $unprefixed ),
705 'name',
'media_type',
'description_text',
'description_data',
706 'description_cid',
'user',
'user_text',
'actor',
'timestamp',
707 'metadata',
'major_mime',
'minor_mime',
'sha1',
'size',
'width',
711 if ( $extraFields ) {
713 'Passing extra fields (' .
714 implode(
', ', $extraFields )
715 .
') to ' . __METHOD__ .
' was deprecated in MediaWiki 1.37. ' .
716 'Property assignment will be removed in a later version.',
718 foreach ( $extraFields as $field ) {
719 $this->$field = $unprefixed[$field];
723 $this->fileExists =
true;
731 public function load( $flags = 0 ) {
732 if ( !$this->dataLoaded ) {
733 if ( $flags & self::READ_LATEST ) {
740 if ( ( $flags & self::LOAD_ALL ) && !$this->extraDataLoaded ) {
756 $reserialize =
false;
757 if ( $this->media_type ===
null || $this->mime ==
'image/svg' ) {
766 && $this->repo->isMetadataUpdateEnabled()
769 } elseif ( $this->repo->isJsonMetadataEnabled()
770 && $this->repo->isMetadataReserializeEnabled()
772 if ( $this->repo->isSplitMetadataEnabled() && $this->isMetadataOversize() ) {
774 } elseif ( $this->metadataSerializationFormat !== self::MDS_EMPTY &&
775 $this->metadataSerializationFormat !== self::MDS_JSON ) {
782 if ( $upgrade || $reserialize ) {
783 $this->upgrading =
true;
785 DeferredUpdates::addCallableUpdate(
function () use ( $upgrade ) {
786 $this->upgrading =
false;
804 return $this->upgraded;
816 # Don't destroy file info of missing files
817 if ( !$this->fileExists ) {
819 wfDebug( __METHOD__ .
": file does not exist, aborting" );
824 $dbw = $this->repo->getPrimaryDB();
825 list( $major, $minor ) = self::splitMime( $this->mime );
832 wfDebug( __METHOD__ .
': upgrading ' . $this->
getName() .
" to the current schema" );
834 $dbw->update(
'image',
836 'img_size' => $this->size,
837 'img_width' => $this->width,
838 'img_height' => $this->height,
839 'img_bits' => $this->bits,
840 'img_media_type' => $this->media_type,
841 'img_major_mime' => $major,
842 'img_minor_mime' => $minor,
844 'img_sha1' => $this->sha1,
846 [
'img_name' => $this->
getName() ],
853 $this->upgraded =
true;
864 $dbw = $this->repo->getPrimaryDB();
869 'img_name' => $this->name,
870 'img_timestamp' => $dbw->timestamp( $this->timestamp ),
874 $this->upgraded =
true;
889 $this->dataLoaded =
true;
891 $fields[] =
'fileExists';
893 foreach ( $fields as $field ) {
894 if ( isset( $info[$field] ) ) {
895 $this->$field = $info[$field];
900 if ( isset( $info[
'user'] ) &&
901 isset( $info[
'user_text'] ) &&
902 $info[
'user_text'] !==
''
908 if ( isset( $info[
'major_mime'] ) ) {
909 $this->mime =
"{$info['major_mime']}/{$info['minor_mime']}";
910 } elseif ( isset( $info[
'mime'] ) ) {
911 $this->mime = $info[
'mime'];
912 list( $this->major_mime, $this->minor_mime ) = self::splitMime( $this->mime );
915 if ( isset( $info[
'metadata'] ) ) {
916 if ( is_string( $info[
'metadata'] ) ) {
918 } elseif ( is_array( $info[
'metadata'] ) ) {
919 $this->metadataArray = $info[
'metadata'];
920 if ( isset( $info[
'metadataBlobs'] ) ) {
921 $this->metadataBlobs = $info[
'metadataBlobs'];
922 $this->unloadedMetadataBlobs = array_diff_key(
923 $this->metadataBlobs,
927 $this->metadataBlobs = [];
928 $this->unloadedMetadataBlobs = [];
931 $logger = LoggerFactory::getInstance(
'LocalFile' );
932 $logger->warning( __METHOD__ .
' given invalid metadata of type ' .
933 gettype( $info[
'metadata'] ) );
934 $this->metadataArray = [];
936 $this->extraDataLoaded =
true;
956 if ( $this->missing ===
null ) {
957 $fileExists = $this->repo->fileExists( $this->
getVirtualUrl() );
958 $this->missing = !$fileExists;
961 return $this->missing;
986 return $dim[
'width'];
1019 return $dim[
'height'];
1026 return $this->height;
1038 if ( !$this->title ) {
1042 $pageId = $this->title->getArticleID();
1045 $url = $this->repo->makeUrl( [
'curid' => $pageId ] );
1046 if (
$url !==
false ) {
1063 } elseif ( array_keys( $data ) === [
'_error' ] ) {
1065 return $data[
'_error'];
1079 if ( $this->unloadedMetadataBlobs ) {
1081 array_unique( array_merge(
1082 array_keys( $this->metadataArray ),
1083 array_keys( $this->unloadedMetadataBlobs )
1087 return $this->metadataArray;
1091 $this->load( self::LOAD_ALL );
1094 foreach ( $itemNames as $itemName ) {
1095 if ( array_key_exists( $itemName, $this->metadataArray ) ) {
1096 $result[$itemName] = $this->metadataArray[$itemName];
1097 } elseif ( isset( $this->unloadedMetadataBlobs[$itemName] ) ) {
1098 $addresses[$itemName] = $this->unloadedMetadataBlobs[$itemName];
1102 $blobStore = $this->repo->getBlobStore();
1103 if ( !$blobStore ) {
1104 LoggerFactory::getInstance(
'LocalFile' )->warning(
1105 "Unable to load metadata: repo has no blob store" );
1108 $status = $blobStore->getBlobBatch( $addresses );
1109 if ( !$status->isGood() ) {
1111 false,
false,
'en' );
1112 LoggerFactory::getInstance(
'LocalFile' )->warning(
1113 "Error loading metadata from BlobStore: $msg" );
1115 foreach ( $addresses as $itemName => $address ) {
1116 unset( $this->unloadedMetadataBlobs[$itemName] );
1117 $json = $status->getValue()[$address] ??
null;
1118 if ( $json !==
null ) {
1119 $value = $this->jsonDecode( $json );
1120 $result[$itemName] = $value;
1121 $this->metadataArray[$itemName] = $value;
1137 $s = json_encode( $data,
1138 JSON_INVALID_UTF8_IGNORE |
1139 JSON_UNESCAPED_SLASHES |
1140 JSON_UNESCAPED_UNICODE );
1141 if (
$s ===
false ) {
1142 throw new MWException( __METHOD__ .
': metadata is not JSON-serializable ' .
1143 '(type = ' . $this->getMimeType() .
')' );
1161 return @json_decode(
$s,
true, 512, JSON_INVALID_UTF8_IGNORE );
1176 $this->load( self::LOAD_ALL );
1177 if ( !$this->metadataArray && !$this->metadataBlobs ) {
1179 } elseif ( $this->repo->isJsonMetadataEnabled() ) {
1180 $s = $this->getJsonMetadata();
1184 if ( !is_string(
$s ) ) {
1185 throw new MWException(
'Could not serialize image metadata value for DB' );
1199 'data' => array_diff_key( $this->metadataArray, $this->metadataBlobs )
1203 if ( $this->metadataBlobs ) {
1204 $envelope[
'blobs'] = $this->metadataBlobs;
1208 $s = $this->jsonEncode( $envelope );
1212 if ( !$this->repo->isSplitMetadataEnabled()
1213 || !$this->getHandler()
1214 || !$this->getHandler()->useSplitMetadata()
1218 $threshold = $this->repo->getSplitMetadataThreshold();
1219 if ( !$threshold || strlen(
$s ) <= $threshold ) {
1222 $blobStore = $this->repo->getBlobStore();
1223 if ( !$blobStore ) {
1229 $blobAddresses = [];
1230 foreach ( $envelope[
'data'] as $name => $value ) {
1231 $encoded = $this->jsonEncode( $value );
1232 if ( strlen( $encoded ) > $threshold ) {
1233 $blobAddresses[$name] = $blobStore->storeBlob(
1235 [ BlobStore::IMAGE_HINT => $this->getName() ]
1240 $envelope[
'data'] = array_diff_key( $envelope[
'data'], $blobAddresses );
1241 $envelope[
'blobs'] = $blobAddresses;
1242 $s = $this->jsonEncode( $envelope );
1245 $this->metadataBlobs += $blobAddresses;
1257 if ( !$this->repo->isSplitMetadataEnabled() ) {
1260 $threshold = $this->repo->getSplitMetadataThreshold();
1261 $directItems = array_diff_key( $this->metadataArray, $this->metadataBlobs );
1262 foreach ( $directItems as $value ) {
1263 if ( strlen( $this->jsonEncode( $value ) ) > $threshold ) {
1279 $this->loadMetadataFromString( $db->
decodeBlob( $metadataBlob ) );
1290 $this->extraDataLoaded =
true;
1291 $this->metadataArray = [];
1292 $this->metadataBlobs = [];
1293 $this->unloadedMetadataBlobs = [];
1294 $metadataString = (string)$metadataString;
1295 if ( $metadataString ===
'' ) {
1296 $this->metadataSerializationFormat = self::MDS_EMPTY;
1299 if ( $metadataString[0] ===
'{' ) {
1300 $envelope = $this->jsonDecode( $metadataString );
1303 $this->metadataArray = [
'_error' => $metadataString ];
1304 $this->metadataSerializationFormat = self::MDS_LEGACY;
1306 $this->metadataSerializationFormat = self::MDS_JSON;
1307 if ( isset( $envelope[
'data'] ) ) {
1308 $this->metadataArray = $envelope[
'data'];
1310 if ( isset( $envelope[
'blobs'] ) ) {
1311 $this->metadataBlobs = $this->unloadedMetadataBlobs = $envelope[
'blobs'];
1317 if ( !is_array( $data ) ) {
1319 $data = [
'_error' => $metadataString ];
1320 $this->metadataSerializationFormat = self::MDS_LEGACY;
1322 $this->metadataSerializationFormat = self::MDS_PHP;
1324 $this->metadataArray = $data;
1335 return (
int)$this->bits;
1369 return $this->media_type;
1386 return $this->fileExists;
1406 if ( $archiveName ) {
1407 $dir = $this->getArchiveThumbPath( $archiveName );
1409 $dir = $this->getThumbPath();
1412 $backend = $this->repo->getBackend();
1415 $iterator = $backend->getFileList( [
'dir' => $dir ] );
1416 if ( $iterator !==
null ) {
1417 foreach ( $iterator as
$file ) {
1431 $this->invalidateCache();
1444 $this->maybeUpgradeRow();
1445 $this->purgeMetadataCache();
1448 $this->purgeThumbnails( $options );
1451 $hcu = MediaWikiServices::getInstance()->getHtmlCacheUpdater();
1454 !empty( $options[
'forThumbRefresh'] )
1455 ? $hcu::PURGE_PRESEND
1456 : $hcu::PURGE_INTENT_TXROUND_REFLECTED
1467 $thumbs = $this->getThumbnails( $archiveName );
1470 $dir = array_shift( $thumbs );
1471 $this->purgeThumbList( $dir, $thumbs );
1474 foreach ( $thumbs as $thumb ) {
1475 $urls[] = $this->getArchiveThumbUrl( $archiveName, $thumb );
1479 $this->getHookRunner()->onLocalFilePurgeThumbnails( $this, $archiveName, $urls );
1482 $hcu = MediaWikiServices::getInstance()->getHtmlCacheUpdater();
1483 $hcu->purgeUrls( $urls, $hcu::PURGE_PRESEND );
1493 $thumbs = $this->getThumbnails();
1496 $dir = array_shift( $thumbs );
1497 $this->purgeThumbList( $dir, $thumbs );
1501 foreach ( $thumbs as $thumb ) {
1502 $urls[] = $this->getThumbUrl( $thumb );
1506 if ( !empty( $options[
'forThumbRefresh'] ) ) {
1507 $handler = $this->getHandler();
1509 $handler->filterThumbnailPurgeList( $thumbs, $options );
1514 $this->getHookRunner()->onLocalFilePurgeThumbnails( $this,
false, $urls );
1517 $hcu = MediaWikiServices::getInstance()->getHtmlCacheUpdater();
1520 !empty( $options[
'forThumbRefresh'] )
1521 ? $hcu::PURGE_PRESEND
1522 : $hcu::PURGE_INTENT_TXROUND_REFLECTED
1540 foreach ( $sizes as $size ) {
1541 if ( $this->isMultipage() ) {
1544 $pageLimit = min( $this->pageCount(), self::MAX_PAGE_RENDER_JOBS );
1546 for ( $page = 1; $page <= $pageLimit; $page++ ) {
1549 [
'transformParams' => [
1555 } elseif ( $this->isVectorized() || $this->getWidth() > $size ) {
1558 [
'transformParams' => [
'width' => $size ] ]
1564 JobQueueGroup::singleton()->lazyPush( $jobs );
1575 $fileListDebug = strtr(
1576 var_export( $files,
true ),
1579 wfDebug( __METHOD__ .
": $fileListDebug" );
1581 if ( $this->repo->supportsSha1URLs() ) {
1582 $reference = $this->getSha1();
1584 $reference = $this->getName();
1588 foreach ( $files as
$file ) {
1589 # Check that the reference (filename or sha1) is part of the thumb name
1590 # This is a basic sanity check to avoid erasing unrelated directories
1591 if ( strpos(
$file, $reference ) !==
false
1592 || strpos(
$file,
"-thumbnail" ) !==
false
1594 $purgeList[] =
"{$dir}/{$file}";
1598 # Delete the thumbnails
1599 $this->repo->quickPurgeBatch( $purgeList );
1600 # Clear out the thumbnail directory if empty
1601 $this->repo->quickCleanDir( $dir );
1615 public function getHistory( $limit =
null, $start =
null, $end =
null, $inc =
true ) {
1616 if ( !$this->exists() ) {
1620 $dbr = $this->repo->getReplicaDB();
1623 $tables = $oldFileQuery[
'tables'];
1624 $fields = $oldFileQuery[
'fields'];
1625 $join_conds = $oldFileQuery[
'joins'];
1626 $conds = $opts = [];
1627 $eq = $inc ?
'=' :
'';
1628 $conds[] =
"oi_name = " .
$dbr->addQuotes( $this->title->getDBkey() );
1631 $conds[] =
"oi_timestamp <$eq " .
$dbr->addQuotes(
$dbr->timestamp( $start ) );
1635 $conds[] =
"oi_timestamp >$eq " .
$dbr->addQuotes(
$dbr->timestamp( $end ) );
1639 $opts[
'LIMIT'] = $limit;
1643 $order = ( !$start && $end !== null ) ?
'ASC' :
'DESC';
1644 $opts[
'ORDER BY'] =
"oi_timestamp $order";
1645 $opts[
'USE INDEX'] = [
'oldimage' =>
'oi_name_timestamp' ];
1647 $this->getHookRunner()->onLocalFile__getHistory( $this, $tables, $fields,
1648 $conds, $opts, $join_conds );
1650 $res =
$dbr->select( $tables, $fields, $conds, __METHOD__, $opts, $join_conds );
1653 foreach (
$res as $row ) {
1654 $r[] = $this->repo->newFileFromRow( $row );
1657 if ( $order ==
'ASC' ) {
1658 $r = array_reverse( $r );
1675 if ( !$this->exists() ) {
1679 # Polymorphic function name to distinguish foreign and local fetches
1680 $fname = static::class .
'::' . __FUNCTION__;
1682 $dbr = $this->repo->getReplicaDB();
1684 if ( $this->historyLine == 0 ) {
1685 $fileQuery = self::getQueryInfo();
1686 $this->historyRes =
$dbr->select( $fileQuery[
'tables'],
1687 $fileQuery[
'fields'] + [
1688 'oi_archive_name' =>
$dbr->addQuotes(
'' ),
1691 [
'img_name' => $this->title->getDBkey() ],
1697 if (
$dbr->numRows( $this->historyRes ) == 0 ) {
1698 $this->historyRes =
null;
1702 } elseif ( $this->historyLine == 1 ) {
1704 $this->historyRes =
$dbr->select(
1705 $fileQuery[
'tables'],
1706 $fileQuery[
'fields'],
1707 [
'oi_name' => $this->title->getDBkey() ],
1709 [
'ORDER BY' =>
'oi_timestamp DESC' ],
1713 $this->historyLine++;
1715 return $dbr->fetchObject( $this->historyRes );
1723 $this->historyLine = 0;
1725 if ( $this->historyRes !==
null ) {
1726 $this->historyRes =
null;
1763 public function upload( $src, $comment, $pageText, $flags = 0, $props =
false,
1764 $timestamp =
false,
Authority $uploader =
null, $tags = [],
1765 $createNullRevision =
true, $revert =
false
1767 if ( $this->getRepo()->getReadOnlyReason() !==
false ) {
1768 return $this->readOnlyFatalStatus();
1769 } elseif ( MediaWikiServices::getInstance()->getRevisionStore()->isReadOnly() ) {
1772 return $this->readOnlyFatalStatus();
1775 $srcPath = ( $src instanceof
FSFile ) ? $src->getPath() : $src;
1780 $props = $this->repo->getFileProps( $srcPath );
1782 $mwProps =
new MWFileProps( MediaWikiServices::getInstance()->getMimeAnalyzer() );
1783 $props = $mwProps->getPropsFromPath( $srcPath,
true );
1790 if ( is_string( $props[
'metadata'] ) ) {
1797 $metadata = $props[
'metadata'];
1800 if ( is_array( $metadata ) ) {
1801 $options[
'headers'] = $handler->getContentHeaders( $metadata );
1804 $options[
'headers'] = [];
1808 $comment = trim( $comment );
1811 $status = $this->publish( $src, $flags, $options );
1813 if ( $status->successCount >= 2 ) {
1820 $oldver = $status->value;
1822 if ( $uploader ===
null ) {
1824 $uploader = RequestContext::getMain()->getAuthority();
1827 $uploadStatus = $this->recordUpload3(
1835 $createNullRevision,
1838 if ( !$uploadStatus->isOK() ) {
1839 if ( $uploadStatus->hasMessage(
'filenotfound' ) ) {
1841 $status->fatal(
'filenotfound', $srcPath );
1843 $status->merge( $uploadStatus );
1876 bool $createNullRevision =
true,
1877 bool $revert =
false
1879 $dbw = $this->repo->getPrimaryDB();
1881 # Imports or such might force a certain timestamp; otherwise we generate
1882 # it and can fudge it slightly to keep (name,timestamp) unique on re-upload.
1883 if ( $timestamp ===
false ) {
1884 $timestamp = $dbw->timestamp();
1885 $allowTimeKludge =
true;
1887 $allowTimeKludge =
false;
1890 $props = $props ?: $this->repo->getFileProps( $this->getVirtualUrl() );
1891 $props[
'description'] = $comment;
1892 $props[
'timestamp'] =
wfTimestamp( TS_MW, $timestamp );
1893 $this->setProps( $props );
1895 # Fail now if the file isn't there
1896 if ( !$this->fileExists ) {
1897 wfDebug( __METHOD__ .
": File " . $this->getRel() .
" went missing!" );
1902 $actorNormalizaton = MediaWikiServices::getInstance()->getActorNormalization();
1904 $dbw->startAtomic( __METHOD__ );
1906 $actorId = $actorNormalizaton->acquireActorId( $performer->
getUser(), $dbw );
1907 $this->user = $performer->
getUser();
1909 # Test to see if the row exists using INSERT IGNORE
1910 # This avoids race conditions by locking the row until the commit, and also
1911 # doesn't deadlock. SELECT FOR UPDATE causes a deadlock for every race condition.
1912 $commentStore = MediaWikiServices::getInstance()->getCommentStore();
1913 $commentFields = $commentStore->insert( $dbw,
'img_description', $comment );
1914 $actorFields = [
'img_actor' => $actorId ];
1915 $dbw->insert(
'image',
1917 'img_name' => $this->getName(),
1918 'img_size' => $this->size,
1919 'img_width' => intval( $this->width ),
1920 'img_height' => intval( $this->height ),
1921 'img_bits' => $this->bits,
1922 'img_media_type' => $this->media_type,
1923 'img_major_mime' => $this->major_mime,
1924 'img_minor_mime' => $this->minor_mime,
1925 'img_timestamp' => $timestamp,
1926 'img_metadata' => $this->getMetadataForDb( $dbw ),
1927 'img_sha1' => $this->sha1
1928 ] + $commentFields + $actorFields,
1932 $reupload = ( $dbw->affectedRows() == 0 );
1935 $row = $dbw->selectRow(
1937 [
'img_timestamp',
'img_sha1' ],
1938 [
'img_name' => $this->getName() ],
1940 [
'LOCK IN SHARE MODE' ]
1943 if ( $row && $row->img_sha1 === $this->sha1 ) {
1944 $dbw->endAtomic( __METHOD__ );
1945 wfDebug( __METHOD__ .
": File " . $this->getRel() .
" already exists!" );
1950 if ( $allowTimeKludge ) {
1951 # Use LOCK IN SHARE MODE to ignore any transaction snapshotting
1952 $lUnixtime = $row ?
wfTimestamp( TS_UNIX, $row->img_timestamp ) : false;
1953 # Avoid a timestamp that is not newer than the last version
1954 # TODO: the image/oldimage tables should be like page/revision with an ID field
1955 if ( $lUnixtime &&
wfTimestamp( TS_UNIX, $timestamp ) <= $lUnixtime ) {
1957 $timestamp = $dbw->timestamp( $lUnixtime + 1 );
1958 $this->timestamp =
wfTimestamp( TS_MW, $timestamp );
1962 $tables = [
'image' ];
1964 'oi_name' =>
'img_name',
1965 'oi_archive_name' => $dbw->addQuotes( $oldver ),
1966 'oi_size' =>
'img_size',
1967 'oi_width' =>
'img_width',
1968 'oi_height' =>
'img_height',
1969 'oi_bits' =>
'img_bits',
1970 'oi_description_id' =>
'img_description_id',
1971 'oi_timestamp' =>
'img_timestamp',
1972 'oi_metadata' =>
'img_metadata',
1973 'oi_media_type' =>
'img_media_type',
1974 'oi_major_mime' =>
'img_major_mime',
1975 'oi_minor_mime' =>
'img_minor_mime',
1976 'oi_sha1' =>
'img_sha1',
1977 'oi_actor' =>
'img_actor',
1981 # (T36993) Note: $oldver can be empty here, if the previous
1982 # version of the file was broken. Allow registration of the new
1983 # version to continue anyway, because that's better than having
1984 # an image that's not fixable by user operations.
1985 # Collision, this is an update of a file
1986 # Insert previous contents into oldimage
1987 $dbw->insertSelect(
'oldimage', $tables, $fields,
1988 [
'img_name' => $this->getName() ], __METHOD__, [], [], $joins );
1990 # Update the current image row
1991 $dbw->update(
'image',
1993 'img_size' => $this->size,
1994 'img_width' => intval( $this->width ),
1995 'img_height' => intval( $this->height ),
1996 'img_bits' => $this->bits,
1997 'img_media_type' => $this->media_type,
1998 'img_major_mime' => $this->major_mime,
1999 'img_minor_mime' => $this->minor_mime,
2000 'img_timestamp' => $timestamp,
2001 'img_metadata' => $this->getMetadataForDb( $dbw ),
2002 'img_sha1' => $this->sha1
2003 ] + $commentFields + $actorFields,
2004 [
'img_name' => $this->getName() ],
2010 $descId = $descTitle->getArticleID();
2012 $wikiPage->setFile( $this );
2016 $logAction =
'revert';
2017 } elseif ( $reupload ) {
2018 $logAction =
'overwrite';
2020 $logAction =
'upload';
2024 $logEntry->setTimestamp( $this->timestamp );
2025 $logEntry->setPerformer( $performer->
getUser() );
2026 $logEntry->setComment( $comment );
2027 $logEntry->setTarget( $descTitle );
2030 $logEntry->setParameters(
2032 'img_sha1' => $this->sha1,
2033 'img_timestamp' => $timestamp,
2042 $logId = $logEntry->insert();
2044 if ( $descTitle->exists() ) {
2045 if ( $createNullRevision ) {
2046 $revStore = MediaWikiServices::getInstance()->getRevisionStore();
2049 $formatter->setContext( RequestContext::newExtraneousContext( $descTitle ) );
2050 $editSummary = $formatter->getPlainActionText();
2052 $nullRevRecord =
$revStore->newNullRevision(
2060 if ( $nullRevRecord ) {
2061 $inserted =
$revStore->insertRevisionOn( $nullRevRecord, $dbw );
2063 $this->getHookRunner()->onRevisionFromEditComplete(
2066 $inserted->getParentId(),
2071 $wikiPage->updateRevisionOn( $dbw, $inserted );
2073 $logEntry->setAssociatedRevId( $inserted->getId() );
2077 $newPageContent =
null;
2086 $dbw->endAtomic( __METHOD__ );
2087 $fname = __METHOD__;
2089 # Do some cache purges after final commit so that:
2090 # a) Changes are more likely to be seen post-purge
2091 # b) They won't cause rollback of the log publish/update above
2097 $reupload, $wikiPage, $newPageContent, $comment, $performer,
2098 $logEntry, $logId, $descId, $tags, $fname
2100 # Update memcache after the commit
2101 $this->invalidateCache();
2103 $updateLogPage =
false;
2104 if ( $newPageContent ) {
2105 # New file page; create the description page.
2106 # There's already a log entry, so don't make a second RC entry
2107 # CDN and file cache for the description page are purged by doUserEditContent.
2108 $status = $wikiPage->doUserEditContent(
2115 if ( isset( $status->value[
'revision-record'] ) ) {
2117 $revRecord = $status->value[
'revision-record'];
2119 $logEntry->setAssociatedRevId( $revRecord->getId() );
2123 if ( isset( $status->value[
'revision-record'] ) ) {
2125 $revRecord = $status->value[
'revision-record'];
2126 $updateLogPage = $revRecord->getPageId();
2129 # Existing file page: invalidate description page cache
2130 $title = $wikiPage->getTitle();
2131 $title->invalidateCache();
2132 $hcu = MediaWikiServices::getInstance()->getHtmlCacheUpdater();
2133 $hcu->purgeTitleUrls(
$title, $hcu::PURGE_INTENT_TXROUND_REFLECTED );
2134 # Allow the new file version to be patrolled from the page footer
2138 # Update associated rev id. This should be done by $logEntry->insert() earlier,
2139 # but setAssociatedRevId() wasn't called at that point yet...
2140 $logParams = $logEntry->getParameters();
2141 $logParams[
'associated_rev_id'] = $logEntry->getAssociatedRevId();
2143 if ( $updateLogPage ) {
2144 # Also log page, in case where we just created it above
2145 $update[
'log_page'] = $updateLogPage;
2147 $this->getRepo()->getPrimaryDB()->update(
2150 [
'log_id' => $logId ],
2153 $this->getRepo()->getPrimaryDB()->insert(
2156 'ls_field' =>
'associated_rev_id',
2157 'ls_value' => (
string)$logEntry->getAssociatedRevId(),
2158 'ls_log_id' => $logId,
2163 # Add change tags, if any
2165 $logEntry->addTags( $tags );
2168 # Uploads can be patrolled
2169 $logEntry->setIsPatrollable(
true );
2171 # Now that the log entry is up-to-date, make an RC entry.
2172 $logEntry->publish( $logId );
2174 # Run hook for other updates (typically more cache purging)
2175 $this->getHookRunner()->onFileUpload( $this, $reupload, !$newPageContent );
2178 # Delete old thumbnails
2179 $this->purgeThumbnails();
2180 # Remove the old file from the CDN cache
2181 $hcu = MediaWikiServices::getInstance()->getHtmlCacheUpdater();
2182 $hcu->purgeUrls( $this->getUrl(), $hcu::PURGE_INTENT_TXROUND_REFLECTED );
2184 # Update backlink pages pointing to this title if created
2185 $blcFactory = MediaWikiServices::getInstance()->getBacklinkCacheFactory();
2190 $performer->
getUser()->getName(),
2191 $blcFactory->getBacklinkCache( $this->getTitle() )
2195 $this->prerenderThumbnails();
2199 # Invalidate cache for all pages using this file
2203 [
'causeAction' =>
'file-upload',
'causeAgent' => $performer->
getUser()->getName() ]
2212 $dbw->onTransactionCommitOrIdle(
static function () use ( $reupload, $purgeUpdate, $cacheUpdateJob ) {
2242 public function publish( $src, $flags = 0, array $options = [] ) {
2243 return $this->publishTo( $src, $this->getRel(), $flags, $options );
2262 protected function publishTo( $src, $dstRel, $flags = 0, array $options = [] ) {
2263 $srcPath = ( $src instanceof
FSFile ) ? $src->getPath() : $src;
2265 $repo = $this->getRepo();
2266 if ( $repo->getReadOnlyReason() !==
false ) {
2267 return $this->readOnlyFatalStatus();
2272 if ( $this->isOld() ) {
2273 $archiveRel = $dstRel;
2274 $archiveName = basename( $archiveRel );
2276 $archiveName =
wfTimestamp( TS_MW ) .
'!' . $this->getName();
2277 $archiveRel = $this->getArchiveRel( $archiveName );
2280 if ( $repo->hasSha1Storage() ) {
2282 ? $repo->getFileSha1( $srcPath )
2285 $wrapperBackend = $repo->getBackend();
2286 '@phan-var FileBackendDBRepoWrapper $wrapperBackend';
2287 $dst = $wrapperBackend->getPathForSHA1( $sha1 );
2288 $status = $repo->quickImport( $src, $dst );
2293 if ( $this->exists() ) {
2294 $status->value = $archiveName;
2298 $status = $repo->publish( $srcPath, $dstRel, $archiveRel, $flags, $options );
2300 if ( $status->value ==
'new' ) {
2301 $status->value =
'';
2303 $status->value = $archiveName;
2330 $localRepo = MediaWikiServices::getInstance()->getRepoGroup()->getLocalRepo();
2331 if ( $this->getRepo()->getReadOnlyReason() !==
false ) {
2332 return $this->readOnlyFatalStatus();
2335 wfDebugLog(
'imagemove',
"Got request to move {$this->name} to " . $target->getText() );
2339 $batch->addCurrent();
2340 $archiveNames = $batch->addOlds();
2341 $status = $batch->execute();
2344 wfDebugLog(
'imagemove',
"Finished moving {$this->name}" );
2347 $oldTitleFile = $localRepo->newFile( $this->title );
2348 $newTitleFile = $localRepo->newFile( $target );
2349 DeferredUpdates::addUpdate(
2351 $this->getRepo()->getPrimaryDB(),
2353 static function () use ( $oldTitleFile, $newTitleFile, $archiveNames ) {
2354 $oldTitleFile->purgeEverything();
2355 foreach ( $archiveNames as $archiveName ) {
2357 '@phan-var OldLocalFile $oldTitleFile';
2358 $oldTitleFile->purgeOldThumbnails( $archiveName );
2360 $newTitleFile->purgeEverything();
2363 DeferredUpdates::PRESEND
2366 if ( $status->isOK() ) {
2368 $this->title = $target;
2371 $this->hashPath =
null;
2394 if ( $this->getRepo()->getReadOnlyReason() !==
false ) {
2395 return $this->readOnlyFatalStatus();
2401 $batch->addCurrent();
2403 $archiveNames = $batch->addOlds();
2404 $status = $batch->execute();
2407 if ( $status->isOK() ) {
2408 DeferredUpdates::addUpdate( SiteStatsUpdate::factory( [
'images' => -1 ] ) );
2412 DeferredUpdates::addUpdate(
2414 $this->getRepo()->getPrimaryDB(),
2416 function () use ( $archiveNames ) {
2417 $this->purgeEverything();
2418 foreach ( $archiveNames as $archiveName ) {
2419 $this->purgeOldThumbnails( $archiveName );
2423 DeferredUpdates::PRESEND
2428 foreach ( $archiveNames as $archiveName ) {
2429 $purgeUrls[] = $this->getArchiveUrl( $archiveName );
2432 $hcu = MediaWikiServices::getInstance()->getHtmlCacheUpdater();
2433 $hcu->purgeUrls( $purgeUrls, $hcu::PURGE_INTENT_TXROUND_REFLECTED );
2457 if ( $this->getRepo()->getReadOnlyReason() !==
false ) {
2458 return $this->readOnlyFatalStatus();
2464 $batch->addOld( $archiveName );
2465 $status = $batch->execute();
2468 $this->purgeOldThumbnails( $archiveName );
2469 if ( $status->isOK() ) {
2470 $this->purgeDescription();
2473 $url = $this->getArchiveUrl( $archiveName );
2474 $hcu = MediaWikiServices::getInstance()->getHtmlCacheUpdater();
2475 $hcu->purgeUrls( $url, $hcu::PURGE_INTENT_TXROUND_REFLECTED );
2492 public function restore( $versions = [], $unsuppress =
false ) {
2493 if ( $this->getRepo()->getReadOnlyReason() !==
false ) {
2494 return $this->readOnlyFatalStatus();
2503 $batch->addIds( $versions );
2505 $status = $batch->execute();
2506 if ( $status->isGood() ) {
2507 $cleanupStatus = $batch->cleanup();
2508 $cleanupStatus->successCount = 0;
2509 $cleanupStatus->failCount = 0;
2510 $status->merge( $cleanupStatus );
2528 if ( !$this->title ) {
2532 return $this->title->getLocalURL();
2545 if ( !$this->title ) {
2549 $store = MediaWikiServices::getInstance()->getRevisionStore();
2550 $revision = $store->getRevisionByTitle( $this->title, 0, RevisionStore::READ_NORMAL );
2555 $renderer = MediaWikiServices::getInstance()->getRevisionRenderer();
2556 $rendered = $renderer->getRenderedRevision(
2558 ParserOptions::newFromUserAndLang(
2559 RequestContext::getMain()->getUser(),
2569 $pout = $rendered->getRevisionParserOutput();
2570 return $pout->getText();
2582 if ( $audience === self::FOR_PUBLIC && $this->isDeleted( self::DELETED_USER ) ) {
2584 } elseif ( $audience === self::FOR_THIS_USER && !$this->userCan( self::DELETED_USER, $performer ) ) {
2599 if ( $audience == self::FOR_PUBLIC && $this->isDeleted( self::DELETED_COMMENT ) ) {
2601 } elseif ( $audience == self::FOR_THIS_USER && !$this->userCan( self::DELETED_COMMENT, $performer ) ) {
2604 return $this->description;
2615 return $this->timestamp;
2623 if ( !$this->exists() ) {
2630 if ( $this->descriptionTouched ===
null ) {
2632 'page_namespace' => $this->title->getNamespace(),
2633 'page_title' => $this->title->getDBkey()
2635 $touched = $this->repo->getReplicaDB()->selectField(
'page',
'page_touched', $cond, __METHOD__ );
2636 $this->descriptionTouched = $touched ?
wfTimestamp( TS_MW, $touched ) :
false;
2639 return $this->descriptionTouched;
2649 if ( $this->sha1 ==
'' && $this->fileExists ) {
2652 $this->sha1 = $this->repo->getFileSha1( $this->getPath() );
2653 if ( !
wfReadOnly() && strval( $this->sha1 ) !=
'' ) {
2654 $dbw = $this->repo->getPrimaryDB();
2655 $dbw->update(
'image',
2656 [
'img_sha1' => $this->sha1 ],
2657 [
'img_name' => $this->getName() ],
2659 $this->invalidateCache();
2675 return $this->extraDataLoaded
2676 && strlen(
serialize( $this->metadataArray ) ) <= self::CACHE_FIELD_MAX_LEN;
2684 return Status::wrap( $this->getRepo()->getBackend()->lockFiles(
2685 [ $this->getPath() ], LockManager::LOCK_EX, 10
2694 return Status::wrap( $this->getRepo()->getBackend()->unlockFiles(
2695 [ $this->getPath() ], LockManager::LOCK_EX
2709 if ( !$this->locked ) {
2710 $logger = LoggerFactory::getInstance(
'LocalFile' );
2712 $dbw = $this->repo->getPrimaryDB();
2713 $makesTransaction = !$dbw->trxLevel();
2714 $dbw->startAtomic( self::ATOMIC_SECTION_LOCK );
2718 $status = $this->acquireFileLock();
2719 if ( !$status->isGood() ) {
2720 $dbw->endAtomic( self::ATOMIC_SECTION_LOCK );
2721 $logger->warning(
"Failed to lock '{file}'", [
'file' => $this->name ] );
2727 $dbw->onTransactionResolution(
2728 function () use ( $logger ) {
2729 $status = $this->releaseFileLock();
2730 if ( !$status->isGood() ) {
2731 $logger->error(
"Failed to unlock '{file}'", [
'file' => $this->name ] );
2737 $this->lockedOwnTrx = $makesTransaction;
2742 return $this->lockedOwnTrx;
2754 if ( $this->locked ) {
2756 if ( !$this->locked ) {
2757 $dbw = $this->repo->getPrimaryDB();
2758 $dbw->endAtomic( self::ATOMIC_SECTION_LOCK );
2759 $this->lockedOwnTrx =
false;
2768 return $this->getRepo()->newFatal(
'filereadonlyerror', $this->getName(),
2769 $this->getRepo()->getName(), $this->getRepo()->getReadOnlyReason() );
unserialize( $serialized)
$wgUploadThumbnailRenderMap
When defined, is an array of thumbnail widths to be rendered at upload time.
wfDebug( $text, $dest='all', array $context=[])
Sends a line to the debug log if enabled or, optionally, to a comment in output.
wfReadOnly()
Check whether the wiki is in read-only mode.
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.
string $name
The name of a file from its title object.
FileRepo LocalRepo ForeignAPIRepo bool $repo
Some member variables can be lazy-initialised using __get().
getHandler()
Get a MediaHandler instance for this file.
static newForBacklinks(PageReference $page, $table, $params=[])
static singleton( $domain=false)
Internationalisation code See https://www.mediawiki.org/wiki/Special:MyLanguage/Localisation for more...
static queueRecursiveJobsForTable(PageIdentity $page, $table, $action='unknown', $userName='unknown', ?BacklinkCache $backlinkCache=null)
Queue a RefreshLinks job for any table.
Helper class for file deletion.
Helper class for file movement.
Helper class for file undeletion.
Class to represent a 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.
string $major_mime
Major MIME type.
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
isMetadataOversize()
Determine whether the loaded metadata may be a candidate for splitting, by measuring its serialized s...
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.
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...
bool $upgrading
Whether the row was scheduled to upgrade on load.
string $media_type
MEDIATYPE_xxx (bitmap, drawing, audio...)
deleteFile( $reason, UserIdentity $user, $suppress=false)
Delete all versions of the file.
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)
string $minor_mime
Minor MIME type.
jsonDecode(string $s)
Do JSON decoding with local flags.
getDescriptionShortUrl()
Get short description URL for a file based on the page ID.
getThumbnails( $archiveName=false)
getTransformScript inherited
bool $upgraded
Whether the row was upgraded on load.
static newFromTitle( $title, $repo, $unused=null)
Create a LocalFile from a title Do not call this except from inside a repo class.
purgeMetadataCache()
Refresh metadata in memcached, but don't touch thumbnails or CDN.
getMetadataForDb(IDatabase $db)
Serialize the metadata array for insertion into img_metadata, oi_metadata or fa_metadata.
string $timestamp
Upload timestamp.
jsonEncode( $data)
Do JSON encoding with local flags.
const ATOMIC_SECTION_LOCK
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.
UserIdentity null $user
Uploader.
purgeThumbList( $dir, $files)
Delete a list of thumbnails visible at urls.
string $description
Description of current revision of the file.
const CACHE_FIELD_MAX_LEN
unlock()
Decrement the lock reference count and end the atomic section if it reaches zero.
IResultWrapper null $historyRes
Result of the query for the file's history (nextHistoryLine)
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.
loadExtraFieldsWithTimestamp( $dbr, $fname)
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.
int $historyLine
Number of line to return by nextHistoryLine() (constructor)
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.
bool $locked
True if the image row is locked.
prerenderThumbnails()
Prerenders a configurable set of thumbnails.
bool $lockedOwnTrx
True if the image row is locked with a lock initiated transaction.
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.
getJsonMetadata()
Get metadata in JSON format ready for DB insertion, optionally splitting items out to BlobStore.
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.
bool $missing
True if file is not present in file system.
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.
loadFromCache()
Try to load file metadata from memcached, falling back to the database.
string $descriptionTouched
TS_MW timestamp of the last change of the file description.
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.
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.
static wrap( $sv)
Succinct helper method to wrap a StatusValue.
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.
static newFromAnyId( $userId, $userName, $actorId, $dbDomain=false)
Static factory method for creation from an ID, name, and/or actor ID.
Multi-datacenter aware caching interface.
Special handling for 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