69 private const VERSION = 13;
71 private const CACHE_FIELD_MAX_LEN = 1000;
74 private const MDS_EMPTY =
'empty';
77 private const MDS_LEGACY =
'legacy';
80 private const MDS_PHP =
'php';
83 private const MDS_JSON =
'json';
86 private const MAX_PAGE_RENDER_JOBS = 50;
110 protected $metadataArray = [];
121 protected $metadataBlobs = [];
129 protected $unloadedMetadataBlobs = [];
135 protected $dataLoaded =
false;
138 protected $extraDataLoaded =
false;
144 protected $repoClass = LocalRepo::class;
147 private $historyLine = 0;
150 private $historyRes =
null;
165 private $description;
168 private $descriptionTouched;
180 private $lockedOwnTrx;
186 private $metadataStorageHelper;
189 private const LOAD_ALL = 16;
191 private const ATOMIC_SECTION_LOCK =
'LocalFile::lockingTransaction';
225 $file->loadFromRow( $row );
241 public static function newFromKey( $sha1, $repo, $timestamp =
false ) {
242 $dbr =
$repo->getReplicaDB();
243 $queryBuilder = FileSelectQueryBuilder::newForFile( $dbr );
245 $queryBuilder->where( [
'img_sha1' => $sha1 ] );
248 $queryBuilder->andWhere( [
'img_timestamp' => $dbr->
timestamp( $timestamp ) ] );
251 $row = $queryBuilder->caller( __METHOD__ )->fetchRow();
253 return static::newFromRow( $row,
$repo );
280 $dbr = MediaWikiServices::getInstance()->getConnectionProvider()->getReplicaDatabase();
281 $queryInfo = FileSelectQueryBuilder::newForFile( $dbr, $options )->getQueryInfo();
284 'tables' => $queryInfo[
'tables'],
285 'fields' => $queryInfo[
'fields'],
286 'joins' => $queryInfo[
'join_conds'],
319 return $this->repo->getSharedCacheKey(
'file', sha1( $this->
getName() ) );
325 private function loadFromCache() {
326 $this->dataLoaded =
false;
327 $this->extraDataLoaded =
false;
331 $this->loadFromDB( IDBAccessObject::READ_NORMAL );
336 $cache = MediaWikiServices::getInstance()->getMainWANObjectCache();
337 $cachedValues = $cache->getWithSetCallback(
340 function ( $oldValue, &$ttl, array &$setOpts ) use ( $cache ) {
341 $setOpts += Database::getCacheSetOptions( $this->repo->getReplicaDB() );
343 $this->
loadFromDB( IDBAccessObject::READ_NORMAL );
347 $cacheVal[
'fileExists'] = $this->fileExists;
348 if ( $this->fileExists ) {
349 foreach ( $fields as $field ) {
350 $cacheVal[$field] = $this->$field;
354 $cacheVal[
'user'] = $this->user->getId();
355 $cacheVal[
'user_text'] = $this->user->getName();
359 if ( $this->metadataBlobs ) {
360 $cacheVal[
'metadata'] = array_diff_key(
361 $this->metadataArray, $this->metadataBlobs );
363 $cacheVal[
'metadataBlobs'] = $this->metadataBlobs;
365 $cacheVal[
'metadata'] = $this->metadataArray;
372 if ( isset( $cacheVal[$field] )
373 && strlen( serialize( $cacheVal[$field] ) ) > 100 * 1024
375 unset( $cacheVal[$field] );
376 if ( $field ===
'metadata' ) {
377 unset( $cacheVal[
'metadataBlobs'] );
382 if ( $this->fileExists ) {
383 $ttl = $cache->adaptiveTTL( (
int)
wfTimestamp( TS_UNIX, $this->timestamp ), $ttl );
385 $ttl = $cache::TTL_DAY;
390 [
'version' => self::VERSION ]
393 $this->fileExists = $cachedValues[
'fileExists'];
394 if ( $this->fileExists ) {
398 $this->dataLoaded =
true;
399 $this->extraDataLoaded =
true;
401 $this->extraDataLoaded = $this->extraDataLoaded && isset( $cachedValues[$field] );
414 $this->repo->getPrimaryDB()->onTransactionPreCommitOrIdle(
415 static function () use ( $key ) {
416 MediaWikiServices::getInstance()->getMainWANObjectCache()->delete( $key );
442 if ( $prefix !==
'' ) {
443 throw new InvalidArgumentException(
444 __METHOD__ .
' with a non-empty prefix is no longer supported.'
452 return [
'size',
'width',
'height',
'bits',
'media_type',
453 'major_mime',
'minor_mime',
'timestamp',
'sha1',
'description' ];
464 if ( $prefix !==
'' ) {
465 throw new InvalidArgumentException(
466 __METHOD__ .
' with a non-empty prefix is no longer supported.'
471 return [
'metadata' ];
480 $fname = static::class .
'::' . __FUNCTION__;
482 # Unconditionally set loaded=true, we don't want the accessors constantly rechecking
483 $this->dataLoaded =
true;
484 $this->extraDataLoaded =
true;
486 $dbr = ( $flags & IDBAccessObject::READ_LATEST )
487 ? $this->repo->getPrimaryDB()
488 : $this->repo->getReplicaDB();
489 $queryBuilder = FileSelectQueryBuilder::newForFile( $dbr );
491 $queryBuilder->where( [
'img_name' => $this->
getName() ] );
492 $row = $queryBuilder->caller( $fname )->fetchRow();
497 $this->fileExists =
false;
507 if ( !$this->title ) {
511 $fname = static::class .
'::' . __FUNCTION__;
513 # Unconditionally set loaded=true, we don't want the accessors constantly rechecking
514 $this->extraDataLoaded =
true;
516 $db = $this->repo->getReplicaDB();
517 $fieldMap = $this->loadExtraFieldsWithTimestamp( $db, $fname );
519 $db = $this->repo->getPrimaryDB();
520 $fieldMap = $this->loadExtraFieldsWithTimestamp( $db, $fname );
524 if ( isset( $fieldMap[
'metadata'] ) ) {
528 throw new RuntimeException(
"Could not find data for image '{$this->getName()}'." );
537 private function loadExtraFieldsWithTimestamp(
IReadableDatabase $dbr, $fname ) {
540 $queryBuilder = FileSelectQueryBuilder::newForFile( $dbr, [
'omit-nonlazy' ] );
541 $queryBuilder->where( [
'img_name' => $this->getName() ] )
542 ->andWhere( [
'img_timestamp' => $dbr->
timestamp( $this->getTimestamp() ) ] );
543 $row = $queryBuilder->caller( $fname )->fetchRow();
545 $fieldMap = $this->unprefixRow( $row,
'img_' );
547 # File may have been uploaded over in the meantime; check the old versions
548 $queryBuilder = FileSelectQueryBuilder::newForOldFile( $dbr, [
'omit-nonlazy' ] );
549 $row = $queryBuilder->where( [
'oi_name' => $this->
getName() ] )
550 ->andWhere( [
'oi_timestamp' => $dbr->
timestamp( $this->getTimestamp() ) ] )
551 ->caller( __METHOD__ )->fetchRow();
566 $array = (array)$row;
567 $prefixLength = strlen( $prefix );
570 if ( substr( array_key_first( $array ), 0, $prefixLength ) !== $prefix ) {
571 throw new InvalidArgumentException( __METHOD__ .
': incorrect $prefix parameter' );
575 foreach ( $array as
$name => $value ) {
576 $decoded[substr(
$name, $prefixLength )] = $value;
598 $this->dataLoaded =
true;
602 $this->name = $unprefixed[
'name'];
603 $this->media_type = $unprefixed[
'media_type'];
605 $services = MediaWikiServices::getInstance();
606 $this->description = $services->getCommentStore()
607 ->getComment(
"{$prefix}description", $row )->text;
609 $this->user = $services->getUserFactory()->newFromAnyId(
610 $unprefixed[
'user'] ??
null,
611 $unprefixed[
'user_text'] ??
null,
612 $unprefixed[
'actor'] ??
null
615 $this->timestamp =
wfTimestamp( TS_MW, $unprefixed[
'timestamp'] );
618 $this->repo->getReplicaDB(), $unprefixed[
'metadata'] );
620 if ( empty( $unprefixed[
'major_mime'] ) ) {
621 $this->major_mime =
'unknown';
622 $this->minor_mime =
'unknown';
623 $this->mime =
'unknown/unknown';
625 if ( !$unprefixed[
'minor_mime'] ) {
626 $unprefixed[
'minor_mime'] =
'unknown';
628 $this->major_mime = $unprefixed[
'major_mime'];
629 $this->minor_mime = $unprefixed[
'minor_mime'];
630 $this->mime = $unprefixed[
'major_mime'] .
'/' . $unprefixed[
'minor_mime'];
634 $this->sha1 = rtrim( $unprefixed[
'sha1'],
"\0" );
640 $this->size = +$unprefixed[
'size'];
641 $this->width = +$unprefixed[
'width'];
642 $this->height = +$unprefixed[
'height'];
643 $this->bits = +$unprefixed[
'bits'];
646 $extraFields = array_diff(
647 array_keys( $unprefixed ),
649 'name',
'media_type',
'description_text',
'description_data',
650 'description_cid',
'user',
'user_text',
'actor',
'timestamp',
651 'metadata',
'major_mime',
'minor_mime',
'sha1',
'size',
'width',
655 if ( $extraFields ) {
657 'Passing extra fields (' .
658 implode(
', ', $extraFields )
659 .
') to ' . __METHOD__ .
' was deprecated in MediaWiki 1.37. ' .
660 'Property assignment will be removed in a later version.',
662 foreach ( $extraFields as $field ) {
663 $this->$field = $unprefixed[$field];
667 $this->fileExists =
true;
675 public function load( $flags = 0 ) {
676 if ( !$this->dataLoaded ) {
677 if ( $flags & IDBAccessObject::READ_LATEST ) {
680 $this->loadFromCache();
684 if ( ( $flags & self::LOAD_ALL ) && !$this->extraDataLoaded ) {
695 if ( MediaWikiServices::getInstance()->getReadOnlyMode()->isReadOnly() || $this->upgrading ) {
700 $reserialize =
false;
701 if ( $this->media_type ===
null || $this->mime ==
'image/svg' ) {
707 if ( $validity === MediaHandler::METADATA_BAD ) {
709 } elseif ( $validity === MediaHandler::METADATA_COMPATIBLE
710 && $this->repo->isMetadataUpdateEnabled()
713 } elseif ( $this->repo->isJsonMetadataEnabled()
714 && $this->repo->isMetadataReserializeEnabled()
716 if ( $this->repo->isSplitMetadataEnabled() && $this->isMetadataOversize() ) {
718 } elseif ( $this->metadataSerializationFormat !== self::MDS_EMPTY &&
719 $this->metadataSerializationFormat !== self::MDS_JSON ) {
726 if ( $upgrade || $reserialize ) {
727 $this->upgrading =
true;
729 DeferredUpdates::addCallableUpdate(
function () use ( $upgrade ) {
730 $this->upgrading =
false;
748 return $this->upgraded;
756 $dbw = $this->repo->getPrimaryDB();
760 $freshnessCondition = [
'img_timestamp' => $dbw->timestamp( $this->
getTimestamp() ) ];
764 # Don't destroy file info of missing files
765 if ( !$this->fileExists ) {
766 wfDebug( __METHOD__ .
": file does not exist, aborting" );
771 [ $major, $minor ] = self::splitMime( $this->mime );
773 wfDebug( __METHOD__ .
': upgrading ' . $this->
getName() .
" to the current schema" );
775 $dbw->newUpdateQueryBuilder()
778 'img_size' => $this->size,
779 'img_width' => $this->width,
780 'img_height' => $this->height,
781 'img_bits' => $this->bits,
782 'img_media_type' => $this->media_type,
783 'img_major_mime' => $major,
784 'img_minor_mime' => $minor,
786 'img_sha1' => $this->sha1,
788 ->where( [
'img_name' => $this->
getName() ] )
789 ->andWhere( $freshnessCondition )
790 ->caller( __METHOD__ )->execute();
794 $this->upgraded =
true;
802 if ( MediaWikiServices::getInstance()->getReadOnlyMode()->isReadOnly() ) {
805 $dbw = $this->repo->getPrimaryDB();
806 $dbw->newUpdateQueryBuilder()
810 'img_name' => $this->name,
811 'img_timestamp' => $dbw->timestamp( $this->timestamp ),
813 ->caller( __METHOD__ )->execute();
814 $this->upgraded =
true;
829 $this->dataLoaded =
true;
831 $fields[] =
'fileExists';
833 foreach ( $fields as $field ) {
834 if ( isset( $info[$field] ) ) {
835 $this->$field = $info[$field];
840 if ( isset( $info[
'user'] ) &&
841 isset( $info[
'user_text'] ) &&
842 $info[
'user_text'] !==
''
848 if ( isset( $info[
'major_mime'] ) ) {
849 $this->mime =
"{$info['major_mime']}/{$info['minor_mime']}";
850 } elseif ( isset( $info[
'mime'] ) ) {
851 $this->mime = $info[
'mime'];
852 [ $this->major_mime, $this->minor_mime ] = self::splitMime( $this->mime );
855 if ( isset( $info[
'metadata'] ) ) {
856 if ( is_string( $info[
'metadata'] ) ) {
858 } elseif ( is_array( $info[
'metadata'] ) ) {
859 $this->metadataArray = $info[
'metadata'];
860 if ( isset( $info[
'metadataBlobs'] ) ) {
861 $this->metadataBlobs = $info[
'metadataBlobs'];
862 $this->unloadedMetadataBlobs = array_diff_key(
863 $this->metadataBlobs,
867 $this->metadataBlobs = [];
868 $this->unloadedMetadataBlobs = [];
871 $logger = LoggerFactory::getInstance(
'LocalFile' );
872 $logger->warning( __METHOD__ .
' given invalid metadata of type ' .
873 gettype( $info[
'metadata'] ) );
874 $this->metadataArray = [];
876 $this->extraDataLoaded =
true;
896 if ( $this->missing ===
null ) {
897 $fileExists = $this->repo->fileExists( $this->
getVirtualUrl() );
898 $this->missing = !$fileExists;
901 return $this->missing;
926 return $dim[
'width'];
959 return $dim[
'height'];
966 return $this->height;
978 if ( !$this->title ) {
982 $pageId = $this->title->getArticleID();
985 $url = $this->repo->makeUrl( [
'curid' => $pageId ] );
986 if (
$url !==
false ) {
1003 } elseif ( array_keys( $data ) === [
'_error' ] ) {
1005 return $data[
'_error'];
1018 $this->
load( self::LOAD_ALL );
1019 if ( $this->unloadedMetadataBlobs ) {
1021 array_unique( array_merge(
1022 array_keys( $this->metadataArray ),
1023 array_keys( $this->unloadedMetadataBlobs )
1027 return $this->metadataArray;
1031 $this->load( self::LOAD_ALL );
1034 foreach ( $itemNames as $itemName ) {
1035 if ( array_key_exists( $itemName, $this->metadataArray ) ) {
1036 $result[$itemName] = $this->metadataArray[$itemName];
1037 } elseif ( isset( $this->unloadedMetadataBlobs[$itemName] ) ) {
1038 $addresses[$itemName] = $this->unloadedMetadataBlobs[$itemName];
1043 $resultFromBlob = $this->metadataStorageHelper->getMetadataFromBlobStore( $addresses );
1044 foreach ( $addresses as $itemName => $address ) {
1045 unset( $this->unloadedMetadataBlobs[$itemName] );
1046 $value = $resultFromBlob[$itemName] ??
null;
1047 if ( $value !==
null ) {
1048 $result[$itemName] = $value;
1049 $this->metadataArray[$itemName] = $value;
1068 $this->load( self::LOAD_ALL );
1069 if ( !$this->metadataArray && !$this->metadataBlobs ) {
1071 } elseif ( $this->repo->isJsonMetadataEnabled() ) {
1072 $s = $this->getJsonMetadata();
1074 $s = serialize( $this->getMetadataArray() );
1076 if ( !is_string( $s ) ) {
1077 throw new RuntimeException(
'Could not serialize image metadata value for DB' );
1088 private function getJsonMetadata() {
1091 'data' => array_diff_key( $this->metadataArray, $this->metadataBlobs )
1095 if ( $this->metadataBlobs ) {
1096 $envelope[
'blobs'] = $this->metadataBlobs;
1099 [ $s, $blobAddresses ] = $this->metadataStorageHelper->getJsonMetadata( $this, $envelope );
1102 $this->metadataBlobs += $blobAddresses;
1113 private function isMetadataOversize() {
1114 if ( !$this->repo->isSplitMetadataEnabled() ) {
1117 $threshold = $this->repo->getSplitMetadataThreshold();
1118 $directItems = array_diff_key( $this->metadataArray, $this->metadataBlobs );
1119 foreach ( $directItems as $value ) {
1120 if ( strlen( $this->metadataStorageHelper->jsonEncode( $value ) ) > $threshold ) {
1136 $this->loadMetadataFromString( $db->
decodeBlob( $metadataBlob ) );
1147 $this->extraDataLoaded =
true;
1148 $this->metadataArray = [];
1149 $this->metadataBlobs = [];
1150 $this->unloadedMetadataBlobs = [];
1151 $metadataString = (string)$metadataString;
1152 if ( $metadataString ===
'' ) {
1153 $this->metadataSerializationFormat = self::MDS_EMPTY;
1156 if ( $metadataString[0] ===
'{' ) {
1157 $envelope = $this->metadataStorageHelper->jsonDecode( $metadataString );
1160 $this->metadataArray = [
'_error' => $metadataString ];
1161 $this->metadataSerializationFormat = self::MDS_LEGACY;
1163 $this->metadataSerializationFormat = self::MDS_JSON;
1164 if ( isset( $envelope[
'data'] ) ) {
1165 $this->metadataArray = $envelope[
'data'];
1167 if ( isset( $envelope[
'blobs'] ) ) {
1168 $this->metadataBlobs = $this->unloadedMetadataBlobs = $envelope[
'blobs'];
1173 $data = @unserialize( $metadataString );
1174 if ( !is_array( $data ) ) {
1176 $data = [
'_error' => $metadataString ];
1177 $this->metadataSerializationFormat = self::MDS_LEGACY;
1179 $this->metadataSerializationFormat = self::MDS_PHP;
1181 $this->metadataArray = $data;
1192 return (
int)$this->bits;
1226 return $this->media_type;
1243 return $this->fileExists;
1268 if ( $archiveName ) {
1269 $dir = $this->getArchiveThumbPath( $archiveName );
1271 $dir = $this->getThumbPath();
1274 $backend = $this->repo->getBackend();
1277 $iterator = $backend->getFileList( [
'dir' => $dir,
'forWrite' =>
true ] );
1278 if ( $iterator !==
null ) {
1279 foreach ( $iterator as $file ) {
1299 $this->maybeUpgradeRow();
1300 $this->invalidateCache();
1303 $this->purgeThumbnails( $options );
1306 $hcu = MediaWikiServices::getInstance()->getHtmlCacheUpdater();
1309 !empty( $options[
'forThumbRefresh'] )
1310 ? $hcu::PURGE_PRESEND
1311 : $hcu::PURGE_INTENT_TXROUND_REFLECTED
1322 $thumbs = $this->getThumbnails( $archiveName );
1325 $dir = array_shift( $thumbs );
1326 $this->purgeThumbList( $dir, $thumbs );
1329 foreach ( $thumbs as $thumb ) {
1330 $urls[] = $this->getArchiveThumbUrl( $archiveName, $thumb );
1334 $this->getHookRunner()->onLocalFilePurgeThumbnails( $this, $archiveName, $urls );
1337 $hcu = MediaWikiServices::getInstance()->getHtmlCacheUpdater();
1338 $hcu->purgeUrls( $urls, $hcu::PURGE_PRESEND );
1348 $thumbs = $this->getThumbnails();
1351 $dir = array_shift( $thumbs );
1352 $this->purgeThumbList( $dir, $thumbs );
1356 foreach ( $thumbs as $thumb ) {
1357 $urls[] = $this->getThumbUrl( $thumb );
1361 if ( !empty( $options[
'forThumbRefresh'] ) ) {
1362 $handler = $this->getHandler();
1364 $handler->filterThumbnailPurgeList( $thumbs, $options );
1369 $this->getHookRunner()->onLocalFilePurgeThumbnails( $this,
false, $urls );
1372 $hcu = MediaWikiServices::getInstance()->getHtmlCacheUpdater();
1375 !empty( $options[
'forThumbRefresh'] )
1376 ? $hcu::PURGE_PRESEND
1377 : $hcu::PURGE_INTENT_TXROUND_REFLECTED
1388 $uploadThumbnailRenderMap = MediaWikiServices::getInstance()
1389 ->getMainConfig()->get( MainConfigNames::UploadThumbnailRenderMap );
1393 $sizes = $uploadThumbnailRenderMap;
1396 foreach ( $sizes as $size ) {
1397 if ( $this->isMultipage() ) {
1400 $pageLimit = min( $this->pageCount(), self::MAX_PAGE_RENDER_JOBS );
1405 'transformParams' => [
'width' => $size,
'page' => 1 ],
1406 'enqueueNextPage' =>
true,
1407 'pageLimit' => $pageLimit
1410 } elseif ( $this->isVectorized() || $this->getWidth() > $size ) {
1413 [
'transformParams' => [
'width' => $size ] ]
1419 MediaWikiServices::getInstance()->getJobQueueGroup()->lazyPush( $jobs );
1430 $fileListDebug = strtr(
1431 var_export( $files,
true ),
1434 wfDebug( __METHOD__ .
": $fileListDebug" );
1436 if ( $this->repo->supportsSha1URLs() ) {
1437 $reference = $this->getSha1();
1439 $reference = $this->getName();
1443 foreach ( $files as $file ) {
1444 # Check that the reference (filename or sha1) is part of the thumb name
1445 # This is a basic check to avoid erasing unrelated directories
1446 if ( str_contains( $file, $reference )
1447 || str_contains( $file,
"-thumbnail" )
1449 $purgeList[] =
"{$dir}/{$file}";
1453 # Delete the thumbnails
1454 $this->repo->quickPurgeBatch( $purgeList );
1455 # Clear out the thumbnail directory if empty
1456 $this->repo->quickCleanDir( $dir );
1470 public function getHistory( $limit =
null, $start =
null, $end =
null, $inc =
true ) {
1471 if ( !$this->exists() ) {
1475 $dbr = $this->repo->getReplicaDB();
1476 $oldFileQuery = OldLocalFile::getQueryInfo();
1478 $tables = $oldFileQuery[
'tables'];
1479 $fields = $oldFileQuery[
'fields'];
1480 $join_conds = $oldFileQuery[
'joins'];
1481 $conds = $opts = [];
1482 $eq = $inc ?
'=' :
'';
1483 $conds[] = $dbr->
expr(
'oi_name',
'=', $this->title->getDBkey() );
1486 $conds[] = $dbr->
expr(
'oi_timestamp',
"<$eq", $dbr->
timestamp( $start ) );
1490 $conds[] = $dbr->
expr(
'oi_timestamp',
">$eq", $dbr->
timestamp( $end ) );
1494 $opts[
'LIMIT'] = $limit;
1498 $order = ( !$start && $end !== null ) ?
'ASC' :
'DESC';
1499 $opts[
'ORDER BY'] =
"oi_timestamp $order";
1500 $opts[
'USE INDEX'] = [
'oldimage' =>
'oi_name_timestamp' ];
1502 $this->getHookRunner()->onLocalFile__getHistory( $this, $tables, $fields,
1503 $conds, $opts, $join_conds );
1509 ->caller( __METHOD__ )
1511 ->joinConds( $join_conds )
1515 foreach ( $res as $row ) {
1516 $r[] = $this->repo->newFileFromRow( $row );
1519 if ( $order ==
'ASC' ) {
1520 $r = array_reverse( $r );
1537 if ( !$this->exists() ) {
1541 # Polymorphic function name to distinguish foreign and local fetches
1542 $fname = static::class .
'::' . __FUNCTION__;
1544 $dbr = $this->repo->getReplicaDB();
1546 if ( $this->historyLine == 0 ) {
1547 $queryBuilder = FileSelectQueryBuilder::newForFile( $dbr );
1549 $queryBuilder->fields( [
'oi_archive_name' => $dbr->
addQuotes(
'' ),
'oi_deleted' =>
'0' ] )
1550 ->where( [
'img_name' => $this->title->getDBkey() ] );
1551 $this->historyRes = $queryBuilder->caller( $fname )->fetchResultSet();
1553 if ( $this->historyRes->numRows() == 0 ) {
1554 $this->historyRes =
null;
1558 } elseif ( $this->historyLine == 1 ) {
1559 $queryBuilder = FileSelectQueryBuilder::newForOldFile( $dbr );
1561 $this->historyRes = $queryBuilder->where( [
'oi_name' => $this->title->getDBkey() ] )
1562 ->orderBy(
'oi_timestamp', SelectQueryBuilder::SORT_DESC )
1563 ->caller( $fname )->fetchResultSet();
1565 $this->historyLine++;
1567 return $this->historyRes->fetchObject();
1575 $this->historyLine = 0;
1577 if ( $this->historyRes !==
null ) {
1578 $this->historyRes =
null;
1615 public function upload( $src, $comment, $pageText, $flags = 0, $props =
false,
1616 $timestamp =
false,
Authority $uploader =
null, $tags = [],
1617 $createNullRevision =
true, $revert =
false
1619 if ( $this->getRepo()->getReadOnlyReason() !==
false ) {
1620 return $this->readOnlyFatalStatus();
1621 } elseif ( MediaWikiServices::getInstance()->getRevisionStore()->isReadOnly() ) {
1624 return $this->readOnlyFatalStatus();
1627 $srcPath = ( $src instanceof
FSFile ) ? $src->getPath() : $src;
1632 $props = $this->repo->getFileProps( $srcPath );
1634 $mwProps =
new MWFileProps( MediaWikiServices::getInstance()->getMimeAnalyzer() );
1635 $props = $mwProps->getPropsFromPath( $srcPath,
true );
1640 $handler = MediaHandler::getHandler( $props[
'mime'] );
1642 if ( is_string( $props[
'metadata'] ) ) {
1647 $metadata = @unserialize( $props[
'metadata'] );
1649 $metadata = $props[
'metadata'];
1652 if ( is_array( $metadata ) ) {
1653 $options[
'headers'] = $handler->getContentHeaders( $metadata );
1656 $options[
'headers'] = [];
1660 $comment = trim( $comment );
1662 $status = $this->publish( $src, $flags, $options );
1664 if ( $status->successCount >= 2 ) {
1671 $oldver = $status->value;
1673 $uploadStatus = $this->recordUpload3(
1677 $uploader ?? RequestContext::getMain()->
getAuthority(),
1681 $createNullRevision,
1684 if ( !$uploadStatus->isOK() ) {
1685 if ( $uploadStatus->hasMessage(
'filenotfound' ) ) {
1687 $status->fatal(
'filenotfound', $srcPath );
1689 $status->merge( $uploadStatus );
1721 bool $createNullRevision =
true,
1722 bool $revert =
false
1724 $dbw = $this->repo->getPrimaryDB();
1726 # Imports or such might force a certain timestamp; otherwise we generate
1727 # it and can fudge it slightly to keep (name,timestamp) unique on re-upload.
1728 if ( $timestamp ===
false ) {
1729 $timestamp = $dbw->timestamp();
1730 $allowTimeKludge =
true;
1732 $allowTimeKludge =
false;
1735 $props = $props ?: $this->repo->getFileProps( $this->getVirtualUrl() );
1736 $props[
'description'] = $comment;
1737 $props[
'timestamp'] =
wfTimestamp( TS_MW, $timestamp );
1738 $this->setProps( $props );
1740 # Fail now if the file isn't there
1741 if ( !$this->fileExists ) {
1742 wfDebug( __METHOD__ .
": File " . $this->getRel() .
" went missing!" );
1744 return Status::newFatal(
'filenotfound', $this->getRel() );
1747 $mimeAnalyzer = MediaWikiServices::getInstance()->getMimeAnalyzer();
1748 if ( !$mimeAnalyzer->isValidMajorMimeType( $this->major_mime ) ) {
1749 $this->major_mime =
'unknown';
1752 $actorNormalizaton = MediaWikiServices::getInstance()->getActorNormalization();
1754 $dbw->startAtomic( __METHOD__ );
1756 $actorId = $actorNormalizaton->acquireActorId( $performer->
getUser(), $dbw );
1757 $this->user = $performer->
getUser();
1759 # Test to see if the row exists using INSERT IGNORE
1760 # This avoids race conditions by locking the row until the commit, and also
1761 # doesn't deadlock. SELECT FOR UPDATE causes a deadlock for every race condition.
1762 $commentStore = MediaWikiServices::getInstance()->getCommentStore();
1763 $commentFields = $commentStore->insert( $dbw,
'img_description', $comment );
1764 $actorFields = [
'img_actor' => $actorId ];
1765 $dbw->newInsertQueryBuilder()
1766 ->insertInto(
'image' )
1769 'img_name' => $this->getName(),
1770 'img_size' => $this->size,
1771 'img_width' => intval( $this->width ),
1772 'img_height' => intval( $this->height ),
1773 'img_bits' => $this->bits,
1774 'img_media_type' => $this->media_type,
1775 'img_major_mime' => $this->major_mime,
1776 'img_minor_mime' => $this->minor_mime,
1777 'img_timestamp' => $dbw->timestamp( $timestamp ),
1778 'img_metadata' => $this->getMetadataForDb( $dbw ),
1779 'img_sha1' => $this->sha1
1780 ] + $commentFields + $actorFields )
1781 ->caller( __METHOD__ )->execute();
1782 $reupload = ( $dbw->affectedRows() == 0 );
1785 $row = $dbw->newSelectQueryBuilder()
1786 ->select( [
'img_timestamp',
'img_sha1' ] )
1788 ->where( [
'img_name' => $this->getName() ] )
1789 ->caller( __METHOD__ )->fetchRow();
1791 if ( $row && $row->img_sha1 === $this->sha1 ) {
1792 $dbw->endAtomic( __METHOD__ );
1793 wfDebug( __METHOD__ .
": File " . $this->getRel() .
" already exists!" );
1794 $title = Title::newFromText( $this->getName(),
NS_FILE );
1795 return Status::newFatal(
'fileexists-no-change', $title->getPrefixedText() );
1798 if ( $allowTimeKludge ) {
1799 # Use LOCK IN SHARE MODE to ignore any transaction snapshotting
1800 $lUnixtime = $row ? (int)
wfTimestamp( TS_UNIX, $row->img_timestamp ) : false;
1801 # Avoid a timestamp that is not newer than the last version
1802 # TODO: the image/oldimage tables should be like page/revision with an ID field
1803 if ( $lUnixtime && (
int)
wfTimestamp( TS_UNIX, $timestamp ) <= $lUnixtime ) {
1805 $timestamp = $dbw->timestamp( $lUnixtime + 1 );
1806 $this->timestamp =
wfTimestamp( TS_MW, $timestamp );
1810 $tables = [
'image' ];
1812 'oi_name' =>
'img_name',
1813 'oi_archive_name' => $dbw->addQuotes( $oldver ),
1814 'oi_size' =>
'img_size',
1815 'oi_width' =>
'img_width',
1816 'oi_height' =>
'img_height',
1817 'oi_bits' =>
'img_bits',
1818 'oi_description_id' =>
'img_description_id',
1819 'oi_timestamp' =>
'img_timestamp',
1820 'oi_metadata' =>
'img_metadata',
1821 'oi_media_type' =>
'img_media_type',
1822 'oi_major_mime' =>
'img_major_mime',
1823 'oi_minor_mime' =>
'img_minor_mime',
1824 'oi_sha1' =>
'img_sha1',
1825 'oi_actor' =>
'img_actor',
1829 # (T36993) Note: $oldver can be empty here, if the previous
1830 # version of the file was broken. Allow registration of the new
1831 # version to continue anyway, because that's better than having
1832 # an image that's not fixable by user operations.
1833 # Collision, this is an update of a file
1834 # Insert previous contents into oldimage
1835 $dbw->insertSelect(
'oldimage', $tables, $fields,
1836 [
'img_name' => $this->getName() ], __METHOD__, [], [], $joins );
1838 # Update the current image row
1839 $dbw->newUpdateQueryBuilder()
1842 'img_size' => $this->size,
1843 'img_width' => intval( $this->width ),
1844 'img_height' => intval( $this->height ),
1845 'img_bits' => $this->bits,
1846 'img_media_type' => $this->media_type,
1847 'img_major_mime' => $this->major_mime,
1848 'img_minor_mime' => $this->minor_mime,
1849 'img_timestamp' => $dbw->timestamp( $timestamp ),
1850 'img_metadata' => $this->getMetadataForDb( $dbw ),
1851 'img_sha1' => $this->sha1
1852 ] + $commentFields + $actorFields )
1853 ->where( [
'img_name' => $this->getName() ] )
1854 ->caller( __METHOD__ )->execute();
1858 $descId = $descTitle->getArticleID();
1859 $wikiPage = MediaWikiServices::getInstance()->getWikiPageFactory()->newFromTitle( $descTitle );
1861 throw new UnexpectedValueException(
'Cannot obtain instance of WikiFilePage for ' . $this->getName()
1862 .
', got instance of ' . get_class( $wikiPage ) );
1864 $wikiPage->setFile( $this );
1868 $logAction =
'revert';
1869 } elseif ( $reupload ) {
1870 $logAction =
'overwrite';
1872 $logAction =
'upload';
1876 $logEntry->setTimestamp( $this->timestamp );
1877 $logEntry->setPerformer( $performer->
getUser() );
1878 $logEntry->setComment( $comment );
1879 $logEntry->setTarget( $descTitle );
1882 $logEntry->setParameters(
1884 'img_sha1' => $this->sha1,
1885 'img_timestamp' => $timestamp,
1894 $logId = $logEntry->insert();
1896 if ( $descTitle->exists() ) {
1897 if ( $createNullRevision ) {
1898 $revStore = MediaWikiServices::getInstance()->getRevisionStore();
1901 $formatter->setContext( RequestContext::newExtraneousContext( $descTitle ) );
1902 $editSummary = $formatter->getPlainActionText();
1903 $summary = CommentStoreComment::newUnsavedComment( $editSummary );
1904 $nullRevRecord = $revStore->newNullRevision(
1912 if ( $nullRevRecord ) {
1913 $inserted = $revStore->insertRevisionOn( $nullRevRecord, $dbw );
1915 $this->getHookRunner()->onRevisionFromEditComplete(
1918 $inserted->getParentId(),
1923 $wikiPage->updateRevisionOn( $dbw, $inserted );
1925 $logEntry->setAssociatedRevId( $inserted->getId() );
1929 $newPageContent =
null;
1938 $dbw->endAtomic( __METHOD__ );
1939 $fname = __METHOD__;
1941 # Do some cache purges after final commit so that:
1942 # a) Changes are more likely to be seen post-purge
1943 # b) They won't cause rollback of the log publish/update above
1948 $reupload, $wikiPage, $newPageContent, $comment, $performer,
1949 $logEntry, $logId, $descId, $tags, $fname
1951 # Update memcache after the commit
1952 $this->invalidateCache();
1954 $updateLogPage =
false;
1955 if ( $newPageContent ) {
1956 # New file page; create the description page.
1957 # There's already a log entry, so don't make a second RC entry
1958 # CDN and file cache for the description page are purged by doUserEditContent.
1959 $status = $wikiPage->doUserEditContent(
1966 $revRecord = $status->getNewRevision();
1969 $logEntry->setAssociatedRevId( $revRecord->getId() );
1973 $updateLogPage = $revRecord->getPageId();
1976 # Existing file page: invalidate description page cache
1977 $title = $wikiPage->getTitle();
1978 $title->invalidateCache();
1979 $hcu = MediaWikiServices::getInstance()->getHtmlCacheUpdater();
1980 $hcu->purgeTitleUrls( $title, $hcu::PURGE_INTENT_TXROUND_REFLECTED );
1981 # Allow the new file version to be patrolled from the page footer
1985 # Update associated rev id. This should be done by $logEntry->insert() earlier,
1986 # but setAssociatedRevId() wasn't called at that point yet...
1987 $logParams = $logEntry->getParameters();
1988 $logParams[
'associated_rev_id'] = $logEntry->getAssociatedRevId();
1990 if ( $updateLogPage ) {
1991 # Also log page, in case where we just created it above
1992 $update[
'log_page'] = $updateLogPage;
1994 $this->getRepo()->getPrimaryDB()->newUpdateQueryBuilder()
1995 ->update(
'logging' )
1997 ->where( [
'log_id' => $logId ] )
1998 ->caller( $fname )->execute();
2000 $this->getRepo()->getPrimaryDB()->newInsertQueryBuilder()
2001 ->insertInto(
'log_search' )
2003 'ls_field' =>
'associated_rev_id',
2004 'ls_value' => (
string)$logEntry->getAssociatedRevId(),
2005 'ls_log_id' => $logId,
2007 ->caller( $fname )->execute();
2009 # Add change tags, if any
2011 $logEntry->addTags( $tags );
2014 # Uploads can be patrolled
2015 $logEntry->setIsPatrollable(
true );
2017 # Now that the log entry is up-to-date, make an RC entry.
2018 $logEntry->publish( $logId );
2020 # Run hook for other updates (typically more cache purging)
2021 $this->getHookRunner()->onFileUpload( $this, $reupload, !$newPageContent );
2024 # Delete old thumbnails
2025 $this->purgeThumbnails();
2026 # Remove the old file from the CDN cache
2027 $hcu = MediaWikiServices::getInstance()->getHtmlCacheUpdater();
2028 $hcu->purgeUrls( $this->getUrl(), $hcu::PURGE_INTENT_TXROUND_REFLECTED );
2030 # Update backlink pages pointing to this title if created
2031 $blcFactory = MediaWikiServices::getInstance()->getBacklinkCacheFactory();
2032 LinksUpdate::queueRecursiveJobsForTable(
2036 $performer->
getUser()->getName(),
2037 $blcFactory->getBacklinkCache( $this->getTitle() )
2041 $this->prerenderThumbnails();
2045 # Invalidate cache for all pages using this file
2049 [
'causeAction' =>
'file-upload',
'causeAgent' => $performer->
getUser()->getName() ]
2057 $dbw->onTransactionCommitOrIdle(
static function () use ( $reupload, $purgeUpdate, $cacheUpdateJob ) {
2058 DeferredUpdates::addUpdate( $purgeUpdate, DeferredUpdates::PRESEND );
2062 DeferredUpdates::addUpdate( SiteStatsUpdate::factory( [
'images' => 1 ] ) );
2065 MediaWikiServices::getInstance()->getJobQueueGroup()->lazyPush( $cacheUpdateJob );
2068 return Status::newGood();
2087 public function publish( $src, $flags = 0, array $options = [] ) {
2088 return $this->publishTo( $src, $this->getRel(), $flags, $options );
2107 protected function publishTo( $src, $dstRel, $flags = 0, array $options = [] ) {
2108 $srcPath = ( $src instanceof
FSFile ) ? $src->getPath() : $src;
2110 $repo = $this->getRepo();
2111 if ( $repo->getReadOnlyReason() !==
false ) {
2112 return $this->readOnlyFatalStatus();
2115 $status = $this->acquireFileLock();
2116 if ( !$status->isOK() ) {
2120 if ( $this->isOld() ) {
2121 $archiveRel = $dstRel;
2122 $archiveName = basename( $archiveRel );
2124 $archiveName =
wfTimestamp( TS_MW ) .
'!' . $this->getName();
2125 $archiveRel = $this->getArchiveRel( $archiveName );
2128 if ( $repo->hasSha1Storage() ) {
2130 ? $repo->getFileSha1( $srcPath )
2133 $wrapperBackend = $repo->getBackend();
2134 '@phan-var FileBackendDBRepoWrapper $wrapperBackend';
2135 $dst = $wrapperBackend->getPathForSHA1( $sha1 );
2136 $status = $repo->quickImport( $src, $dst );
2141 if ( $this->exists() ) {
2142 $status->value = $archiveName;
2146 $status = $repo->publish( $srcPath, $dstRel, $archiveRel, $flags, $options );
2148 if ( $status->value ==
'new' ) {
2149 $status->value =
'';
2151 $status->value = $archiveName;
2155 $this->releaseFileLock();
2178 $localRepo = MediaWikiServices::getInstance()->getRepoGroup()->getLocalRepo();
2179 if ( $this->getRepo()->getReadOnlyReason() !==
false ) {
2180 return $this->readOnlyFatalStatus();
2183 wfDebugLog(
'imagemove',
"Got request to move {$this->name} to " . $target->getText() );
2186 $status = $batch->addCurrent();
2187 if ( !$status->isOK() ) {
2190 $archiveNames = $batch->addOlds();
2191 $status = $batch->execute();
2193 wfDebugLog(
'imagemove',
"Finished moving {$this->name}" );
2196 $oldTitleFile = $localRepo->newFile( $this->title );
2197 $newTitleFile = $localRepo->newFile( $target );
2198 DeferredUpdates::addUpdate(
2200 $this->getRepo()->getPrimaryDB(),
2202 static function () use ( $oldTitleFile, $newTitleFile, $archiveNames ) {
2203 $oldTitleFile->purgeEverything();
2204 foreach ( $archiveNames as $archiveName ) {
2206 '@phan-var OldLocalFile $oldTitleFile';
2207 $oldTitleFile->purgeOldThumbnails( $archiveName );
2209 $newTitleFile->purgeEverything();
2212 DeferredUpdates::PRESEND
2215 if ( $status->isOK() ) {
2217 $this->title = $target;
2220 $this->hashPath =
null;
2243 if ( $this->getRepo()->getReadOnlyReason() !==
false ) {
2244 return $this->readOnlyFatalStatus();
2249 $batch->addCurrent();
2251 $archiveNames = $batch->addOlds();
2252 $status = $batch->execute();
2254 if ( $status->isOK() ) {
2255 DeferredUpdates::addUpdate( SiteStatsUpdate::factory( [
'images' => -1 ] ) );
2259 DeferredUpdates::addUpdate(
2261 $this->getRepo()->getPrimaryDB(),
2263 function () use ( $archiveNames ) {
2264 $this->purgeEverything();
2265 foreach ( $archiveNames as $archiveName ) {
2266 $this->purgeOldThumbnails( $archiveName );
2270 DeferredUpdates::PRESEND
2275 foreach ( $archiveNames as $archiveName ) {
2276 $purgeUrls[] = $this->getArchiveUrl( $archiveName );
2279 $hcu = MediaWikiServices::getInstance()->getHtmlCacheUpdater();
2280 $hcu->purgeUrls( $purgeUrls, $hcu::PURGE_INTENT_TXROUND_REFLECTED );
2303 if ( $this->getRepo()->getReadOnlyReason() !==
false ) {
2304 return $this->readOnlyFatalStatus();
2309 $batch->addOld( $archiveName );
2310 $status = $batch->execute();
2312 $this->purgeOldThumbnails( $archiveName );
2313 if ( $status->isOK() ) {
2314 $this->purgeDescription();
2317 $url = $this->getArchiveUrl( $archiveName );
2318 $hcu = MediaWikiServices::getInstance()->getHtmlCacheUpdater();
2319 $hcu->purgeUrls( $url, $hcu::PURGE_INTENT_TXROUND_REFLECTED );
2336 public function restore( $versions = [], $unsuppress =
false ) {
2337 if ( $this->getRepo()->getReadOnlyReason() !==
false ) {
2338 return $this->readOnlyFatalStatus();
2346 $batch->addIds( $versions );
2348 $status = $batch->execute();
2349 if ( $status->isGood() ) {
2350 $cleanupStatus = $batch->cleanup();
2351 $cleanupStatus->successCount = 0;
2352 $cleanupStatus->failCount = 0;
2353 $status->merge( $cleanupStatus );
2371 return $this->title ? $this->title->getLocalURL() :
false;
2384 if ( !$this->title ) {
2388 $services = MediaWikiServices::getInstance();
2389 $page = $services->getPageStore()->getPageByReference( $this->
getTitle() );
2395 $parserOptions = ParserOptions::newFromUserAndLang(
2396 RequestContext::getMain()->
getUser(),
2400 $parserOptions = ParserOptions::newFromContext( RequestContext::getMain() );
2403 $parseStatus = $services->getParserOutputAccess()
2404 ->getParserOutput( $page, $parserOptions );
2406 if ( !$parseStatus->isGood() ) {
2410 return $parseStatus->getValue()->getText();
2422 if ( $audience === self::FOR_PUBLIC && $this->isDeleted( self::DELETED_USER ) ) {
2424 } elseif ( $audience === self::FOR_THIS_USER && !$this->userCan( self::DELETED_USER, $performer ) ) {
2439 if ( $audience == self::FOR_PUBLIC && $this->isDeleted( self::DELETED_COMMENT ) ) {
2441 } elseif ( $audience == self::FOR_THIS_USER && !$this->userCan( self::DELETED_COMMENT, $performer ) ) {
2444 return $this->description;
2455 return $this->timestamp;
2463 if ( !$this->exists() ) {
2470 if ( $this->descriptionTouched ===
null ) {
2471 $touched = $this->repo->getReplicaDB()->newSelectQueryBuilder()
2472 ->select(
'page_touched' )
2474 ->where( [
'page_namespace' => $this->title->getNamespace() ] )
2475 ->andWhere( [
'page_title' => $this->title->getDBkey() ] )
2476 ->caller( __METHOD__ )->fetchField();
2477 $this->descriptionTouched = $touched ?
wfTimestamp( TS_MW, $touched ) :
false;
2480 return $this->descriptionTouched;
2499 return $this->extraDataLoaded
2500 && strlen( serialize( $this->metadataArray ) ) <= self::CACHE_FIELD_MAX_LEN;
2512 return Status::wrap( $this->getRepo()->getBackend()->lockFiles(
2513 [ $this->getPath() ], LockManager::LOCK_EX, $timeout
2524 return Status::wrap( $this->getRepo()->getBackend()->unlockFiles(
2525 [ $this->getPath() ], LockManager::LOCK_EX
2540 if ( !$this->locked ) {
2541 $logger = LoggerFactory::getInstance(
'LocalFile' );
2543 $dbw = $this->repo->getPrimaryDB();
2544 $makesTransaction = !$dbw->trxLevel();
2545 $dbw->startAtomic( self::ATOMIC_SECTION_LOCK );
2549 $status = $this->acquireFileLock( 10 );
2550 if ( !$status->isGood() ) {
2551 $dbw->endAtomic( self::ATOMIC_SECTION_LOCK );
2552 $logger->warning(
"Failed to lock '{file}'", [
'file' => $this->name ] );
2558 $dbw->onTransactionResolution(
2559 function () use ( $logger ) {
2560 $status = $this->releaseFileLock();
2561 if ( !$status->isGood() ) {
2562 $logger->error(
"Failed to unlock '{file}'", [
'file' => $this->name ] );
2568 $this->lockedOwnTrx = $makesTransaction;
2573 return $this->lockedOwnTrx;
2587 if ( $this->locked ) {
2589 if ( !$this->locked ) {
2590 $dbw = $this->repo->getPrimaryDB();
2591 $dbw->endAtomic( self::ATOMIC_SECTION_LOCK );
2592 $this->lockedOwnTrx =
false;
2601 return $this->getRepo()->newFatal(
'filereadonlyerror', $this->getName(),
2602 $this->getRepo()->getName(), $this->getRepo()->getReadOnlyReason() );
wfDebug( $text, $dest='all', array $context=[])
Sends a line to the debug log if enabled or, optionally, to a comment in output.
wfDeprecatedMsg( $msg, $version=false, $component=false, $callerOffset=2)
Log a deprecation warning with arbitrary message text.
wfDebugLog( $logGroup, $text, $dest='all', array $context=[])
Send a line to a supplementary debug log file, if configured, or main debug log if not.
wfTimestamp( $outputtype=TS_UNIX, $ts=0)
Get a timestamp string in one of various formats.
getCacheKey()
Get the cache key used to store status.
static purgePatrolFooterCache( $articleID)
Purge the cache used to check if it is worth showing the patrol footer For example,...
static makeContent( $text, Title $title=null, $modelId=null, $format=null)
Convenience function for creating a Content object from a given textual representation.
Class representing a non-directory file on the file system.
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.
FileRepo LocalRepo ForeignAPIRepo false $repo
Some member variables can be lazy-initialised using __get().
isMultipage()
Returns 'true' if this file is a type which supports multiple pages, e.g.
Title string false $title
getHandler()
Get a MediaHandler instance for this file.
string null $name
The name of a file from its title object.
static newForBacklinks(PageReference $page, $table, $params=[])
Base class for language-specific code.
Helper class for file deletion.
Helper class for file movement.
Helper class for file undeletion.
Local file in the wiki's own database.
exists()
canRender inherited
setProps( $info)
Set properties in this object to be equal to those given in the associative array $info.
maybeUpgradeRow()
Upgrade a row if it needs it.
static newFromKey( $sha1, $repo, $timestamp=false)
Create a LocalFile from a SHA-1 key Do not call this except from inside a repo class.
array $metadataArray
Unserialized metadata.
getMediaType()
Returns the type of the media in the file.
string[] $unloadedMetadataBlobs
Map of metadata item name to blob address for items that exist but have not yet been loaded into $thi...
deleteOldFile( $archiveName, $reason, UserIdentity $user, $suppress=false)
Delete an old version of the file.
move( $target)
getLinksTo inherited
lock()
Start an atomic DB section and lock the image for update or increments a reference counter if the loc...
loadFromRow( $row, $prefix='img_')
Load file metadata from a DB result row.
loadMetadataFromDbFieldValue(IReadableDatabase $db, $metadataBlob)
Unserialize a metadata blob which came from the database and store it in $this.
getCacheKey()
Get the memcached key for the main data for this file, or false if there is no access to the shared c...
getWidth( $page=1)
Return the width of the image.
__destruct()
Clean up any dangling locks.
string $mime
MIME type, determined by MimeAnalyzer::guessMimeType.
reserializeMetadata()
Write the metadata back to the database with the current serialization format.
isMissing()
splitMime inherited
getDescriptionUrl()
isMultipage inherited
getHistory( $limit=null, $start=null, $end=null, $inc=true)
purgeDescription inherited
static getQueryInfo(array $options=[])
Return the tables, fields, and join conditions to be selected to create a new localfile object.
releaseFileLock()
Release a lock acquired with acquireFileLock().
getUploader(int $audience=self::FOR_PUBLIC, Authority $performer=null)
loadFromDB( $flags=0)
Load file metadata from the DB.
load( $flags=0)
Load file metadata from cache or DB, unless already loaded.
loadMetadataFromString( $metadataString)
Unserialize a metadata string which came from some non-DB source, or is the return value of IReadable...
string $media_type
MEDIATYPE_xxx (bitmap, drawing, audio...)
deleteFile( $reason, UserIdentity $user, $suppress=false)
Delete all versions of the file.
acquireFileLock( $timeout=0)
Acquire an exclusive lock on the file, indicating an intention to write to the file backend.
purgeCache( $options=[])
Delete all previously generated thumbnails, refresh metadata in memcached and purge the CDN.
loadFromFile( $path=null)
Load metadata from the file itself.
string null $metadataSerializationFormat
One of the MDS_* constants, giving the format of the metadata as stored in the DB,...
int $size
Size in bytes (loadFromXxx)
getDescriptionShortUrl()
Get short description URL for a file based on the page ID.
getThumbnails( $archiveName=false)
getTransformScript inherited
static newFromTitle( $title, $repo, $unused=null)
Create a LocalFile from a title Do not call this except from inside a repo class.
purgeOldThumbnails( $archiveName)
Delete cached transformed files for an archived version only.
publishTo( $src, $dstRel, $flags=0, array $options=[])
Move or copy a file to a specified location.
getMetadataForDb(IReadableDatabase $db)
Serialize the metadata array for insertion into img_metadata, oi_metadata or fa_metadata.
purgeThumbList( $dir, $files)
Delete a list of thumbnails visible at urls.
unlock()
Decrement the lock reference count and end the atomic section if it reaches zero.
getLazyCacheFields( $prefix='img_')
Returns the list of object properties that are included as-is in the cache, only when they're not too...
getSize()
Returns the size of the image file, in bytes.
invalidateCache()
Purge the file object/metadata cache.
getMimeType()
Returns the MIME type of the file.
bool $extraDataLoaded
Whether or not lazy-loaded data has been loaded from the database.
string $sha1
SHA-1 base 36 content hash.
getDescription( $audience=self::FOR_PUBLIC, Authority $performer=null)
getHeight( $page=1)
Return the height of the image.
prerenderThumbnails()
Prerenders a configurable set of thumbnails.
resetHistory()
Reset the history pointer to the first element of the history.
unprefixRow( $row, $prefix='img_')
static newFromRow( $row, $repo)
Create a LocalFile from a title Do not call this except from inside a repo class.
publish( $src, $flags=0, array $options=[])
Move or copy a file to its public location.
restore( $versions=[], $unsuppress=false)
Restore all or specified deleted revisions to the given file.
getCacheFields( $prefix='img_')
Returns the list of object properties that are included as-is in the cache.
int $bits
Returned by getimagesize (loadFromXxx)
getMetadataItems(array $itemNames)
Get multiple elements of the unserialized handler-specific metadata.
getDescriptionText(Language $lang=null)
Get the HTML text of the description page This is not used by ImagePage for local files,...
purgeThumbnails( $options=[])
Delete cached transformed files for the current version only.
loadExtraFromDB()
Load lazy file metadata from the DB.
nextHistoryLine()
Returns the history of this file, line by line.
upgradeRow()
Fix assorted version-related problems with the image row by reloading it from the file.
int $deleted
Bitfield akin to rev_deleted.
getMetadata()
Get handler-specific metadata as a serialized string.
getMetadataArray()
Get unserialized handler-specific metadata.
__construct( $title, $repo)
Do not call this except from inside a repo class.
bool $dataLoaded
Whether or not core data has been loaded from the database (loadFromXxx)
bool $fileExists
Does the file exist on disk? (loadFromXxx)
upload( $src, $comment, $pageText, $flags=0, $props=false, $timestamp=false, Authority $uploader=null, $tags=[], $createNullRevision=true, $revert=false)
getHashPath inherited
recordUpload3(string $oldver, string $comment, string $pageText, Authority $performer, $props=false, $timestamp=false, $tags=[], bool $createNullRevision=true, bool $revert=false)
Record a file upload in the upload log and the image table (version 3)
string[] $metadataBlobs
Map of metadata item name to blob address.
static makeParamBlob( $params)
Create a blob from a parameter array.
MimeMagic helper wrapper.
Class for creating new log entries and inserting them into the database.
Group all the pieces relevant to the context of a request into one instance.
A class containing constants representing the names of configuration variables.
Job for asynchronous rendering of thumbnails, e.g.
Special handling for representing file pages.