24use Wikimedia\AtEase\AtEase;
172 $file->loadFromRow( $row );
189 $conds = [
'img_sha1' =>
$sha1 ];
194 $fileQuery = static::getQueryInfo();
195 $row =
$dbr->selectRow(
196 $fileQuery[
'tables'], $fileQuery[
'fields'], $conds, __METHOD__, [], $fileQuery[
'joins']
199 return static::newFromRow( $row,
$repo );
217 $commentQuery = MediaWikiServices::getInstance()->getCommentStore()->getJoin(
'img_description' );
218 $actorQuery = ActorMigration::newMigration()->getJoin(
'img_user' );
220 'tables' => [
'image' ] + $commentQuery[
'tables'] + $actorQuery[
'tables'],
233 ] + $commentQuery[
'fields'] + $actorQuery[
'fields'],
234 'joins' => $commentQuery[
'joins'] + $actorQuery[
'joins'],
237 if ( in_array(
'omit-nonlazy', $options,
true ) ) {
241 if ( !in_array(
'omit-lazy', $options,
true ) ) {
243 $ret[
'fields'][] =
'img_metadata';
257 $this->metadata =
'';
258 $this->historyLine = 0;
259 $this->historyRes =
null;
260 $this->dataLoaded =
false;
261 $this->extraDataLoaded =
false;
273 return $this->repo->getSharedCacheKey(
'file', sha1( $this->
getName() ) );
289 $this->dataLoaded =
false;
290 $this->extraDataLoaded =
false;
299 $cache = MediaWikiServices::getInstance()->getMainWANObjectCache();
300 $cachedValues =
$cache->getWithSetCallback(
303 function ( $oldValue, &$ttl, array &$setOpts ) use (
$cache ) {
304 $setOpts += Database::getCacheSetOptions( $this->repo->getReplicaDB() );
311 if ( $this->fileExists ) {
312 foreach ( $fields as $field ) {
313 $cacheVal[$field] = $this->$field;
316 $cacheVal[
'user'] = $this->user ? $this->user->getId() : 0;
317 $cacheVal[
'user_text'] = $this->user ? $this->user->getName() :
'';
318 $cacheVal[
'actor'] = $this->user ? $this->user->getActorId() :
null;
324 if ( isset( $cacheVal[$field] )
325 && strlen( $cacheVal[$field] ) > 100 * 1024
327 unset( $cacheVal[$field] );
331 if ( $this->fileExists ) {
334 $ttl = $cache::TTL_DAY;
342 $this->fileExists = $cachedValues[
'fileExists'];
343 if ( $this->fileExists ) {
347 $this->dataLoaded =
true;
348 $this->extraDataLoaded =
true;
350 $this->extraDataLoaded = $this->extraDataLoaded && isset( $cachedValues[$field] );
363 $this->repo->getMasterDB()->onTransactionPreCommitOrIdle(
364 function () use ( $key ) {
365 MediaWikiServices::getInstance()->getMainWANObjectCache()->delete( $key );
375 $props = $this->repo->getFileProps( $this->
getVirtualUrl() );
386 if ( $prefix !==
'' ) {
387 throw new InvalidArgumentException(
388 __METHOD__ .
' with a non-empty prefix is no longer supported.'
396 return [
'size',
'width',
'height',
'bits',
'media_type',
397 'major_mime',
'minor_mime',
'metadata',
'timestamp',
'sha1',
'description' ];
408 if ( $prefix !==
'' ) {
409 throw new InvalidArgumentException(
410 __METHOD__ .
' with a non-empty prefix is no longer supported.'
415 return [
'metadata' ];
423 $fname = static::class .
'::' . __FUNCTION__;
425 # Unconditionally set loaded=true, we don't want the accessors constantly rechecking
426 $this->dataLoaded =
true;
427 $this->extraDataLoaded =
true;
429 $dbr = ( $flags & self::READ_LATEST )
430 ? $this->repo->getMasterDB()
431 : $this->repo->getReplicaDB();
433 $fileQuery = static::getQueryInfo();
434 $row =
$dbr->selectRow(
435 $fileQuery[
'tables'],
436 $fileQuery[
'fields'],
437 [
'img_name' => $this->
getName() ],
446 $this->fileExists =
false;
455 if ( !$this->title ) {
459 $fname = static::class .
'::' . __FUNCTION__;
461 # Unconditionally set loaded=true, we don't want the accessors constantly rechecking
462 $this->extraDataLoaded =
true;
470 foreach ( $fieldMap as
$name => $value ) {
471 $this->
$name = $value;
474 throw new MWException(
"Could not find data for image '{$this->getName()}'." );
487 $row =
$dbr->selectRow(
488 $fileQuery[
'tables'],
489 $fileQuery[
'fields'],
491 'img_name' => $this->
getName(),
492 'img_timestamp' =>
$dbr->timestamp( $this->getTimestamp() ),
501 # File may have been uploaded over in the meantime; check the old versions
503 $row =
$dbr->selectRow(
504 $fileQuery[
'tables'],
505 $fileQuery[
'fields'],
508 'oi_timestamp' =>
$dbr->timestamp( $this->getTimestamp() ),
519 if ( isset( $fieldMap[
'metadata'] ) ) {
520 $fieldMap[
'metadata'] = $this->repo->getReplicaDB()->decodeBlob( $fieldMap[
'metadata'] );
533 $array = (array)$row;
534 $prefixLength = strlen( $prefix );
537 if ( substr( key( $array ), 0, $prefixLength ) !== $prefix ) {
538 throw new MWException( __METHOD__ .
': incorrect $prefix parameter' );
542 foreach ( $array as
$name => $value ) {
543 $decoded[substr(
$name, $prefixLength )] = $value;
560 $decoded[
'description'] = MediaWikiServices::getInstance()->getCommentStore()
561 ->getComment(
'description', (
object)$decoded )->text;
563 $decoded[
'user'] = User::newFromAnyId(
564 $decoded[
'user'] ??
null,
565 $decoded[
'user_text'] ??
null,
566 $decoded[
'actor'] ??
null
568 unset( $decoded[
'user_text'], $decoded[
'actor'] );
570 $decoded[
'timestamp'] =
wfTimestamp( TS_MW, $decoded[
'timestamp'] );
572 $decoded[
'metadata'] = $this->repo->getReplicaDB()->decodeBlob( $decoded[
'metadata'] );
574 if ( empty( $decoded[
'major_mime'] ) ) {
575 $decoded[
'mime'] =
'unknown/unknown';
577 if ( !$decoded[
'minor_mime'] ) {
578 $decoded[
'minor_mime'] =
'unknown';
580 $decoded[
'mime'] = $decoded[
'major_mime'] .
'/' . $decoded[
'minor_mime'];
584 $decoded[
'sha1'] = rtrim( $decoded[
'sha1'],
"\0" );
590 foreach ( [
'size',
'width',
'height',
'bits' ] as $field ) {
591 $decoded[$field] = +$decoded[$field];
604 $this->dataLoaded =
true;
605 $this->extraDataLoaded =
true;
607 $array = $this->
decodeRow( $row, $prefix );
609 foreach ( $array as
$name => $value ) {
610 $this->
$name = $value;
613 $this->fileExists =
true;
621 if ( !$this->dataLoaded ) {
622 if ( $flags & self::READ_LATEST ) {
629 if ( ( $flags & self::LOAD_ALL ) && !$this->extraDataLoaded ) {
646 if ( is_null( $this->media_type ) || $this->mime ==
'image/svg' ) {
661 $this->upgrading =
true;
663 DeferredUpdates::addCallableUpdate(
function () {
664 $this->upgrading =
false;
689 # Don't destroy file info of missing files
690 if ( !$this->fileExists ) {
692 wfDebug( __METHOD__ .
": file does not exist, aborting\n" );
697 $dbw = $this->repo->getMasterDB();
705 wfDebug( __METHOD__ .
': upgrading ' . $this->
getName() .
" to the current schema\n" );
707 $dbw->update(
'image',
709 'img_size' => $this->size,
710 'img_width' => $this->width,
711 'img_height' => $this->height,
712 'img_bits' => $this->bits,
713 'img_media_type' => $this->media_type,
714 'img_major_mime' => $major,
715 'img_minor_mime' => $minor,
716 'img_metadata' => $dbw->encodeBlob( $this->metadata ),
717 'img_sha1' => $this->sha1,
719 [
'img_name' => $this->getName() ],
726 $this->upgraded =
true;
740 $this->dataLoaded =
true;
742 $fields[] =
'fileExists';
744 foreach ( $fields as $field ) {
745 if ( isset( $info[$field] ) ) {
746 $this->$field = $info[$field];
750 if ( isset( $info[
'user'] ) || isset( $info[
'user_text'] ) || isset( $info[
'actor'] ) ) {
751 $this->user = User::newFromAnyId(
752 $info[
'user'] ??
null,
753 $info[
'user_text'] ??
null,
754 $info[
'actor'] ??
null
759 if ( isset( $info[
'major_mime'] ) ) {
760 $this->mime =
"{$info['major_mime']}/{$info['minor_mime']}";
761 } elseif ( isset( $info[
'mime'] ) ) {
762 $this->mime = $info[
'mime'];
763 list( $this->major_mime, $this->minor_mime ) =
self::splitMime( $this->mime );
782 if ( $this->missing ===
null ) {
811 return $dim[
'width'];
843 return $dim[
'height'];
864 if ( !$this->user ) {
867 if (
$type ===
'object' ) {
868 return User::newFromName(
'Unknown user',
false );
869 } elseif (
$type ===
'text' ) {
870 return 'Unknown user';
871 } elseif (
$type ===
'id' ) {
875 if (
$type ===
'object' ) {
877 } elseif (
$type ===
'text' ) {
879 } elseif (
$type ===
'id' ) {
880 return $this->user->getId();
895 if ( !$this->title ) {
899 $pageId = $this->title->getArticleID();
902 $url = $this->repo->makeUrl( [
'curid' => $pageId ] );
903 if (
$url !==
false ) {
915 $this->
load( self::LOAD_ALL );
991 if ( $archiveName ) {
997 $backend = $this->repo->getBackend();
1000 $iterator = $backend->getFileList( [
'dir' => $dir ] );
1001 foreach ( $iterator as
$file ) {
1033 DeferredUpdates::addUpdate(
1035 DeferredUpdates::PRESEND
1048 Hooks::run(
'LocalFilePurgeThumbnails', [ $this, $archiveName ] );
1051 $dir = array_shift( $files );
1056 foreach ( $files as
$file ) {
1071 foreach ( $files as
$file ) {
1074 array_shift(
$urls );
1077 if ( !empty( $options[
'forThumbRefresh'] ) ) {
1085 Hooks::run(
'LocalFilePurgeThumbnails', [ $this,
false ] );
1088 $dir = array_shift( $files );
1108 foreach ( $sizes as
$size ) {
1112 [
'transformParams' => [
'width' => $size ] ]
1118 JobQueueGroup::singleton()->lazyPush( $jobs );
1128 $fileListDebug = strtr(
1129 var_export( $files,
true ),
1132 wfDebug( __METHOD__ .
": $fileListDebug\n" );
1135 foreach ( $files as
$file ) {
1136 if ( $this->repo->supportsSha1URLs() ) {
1137 $reference = $this->
getSha1();
1139 $reference = $this->
getName();
1142 # Check that the reference (filename or sha1) is part of the thumb name
1143 # This is a basic sanity check to avoid erasing unrelated directories
1144 if ( strpos(
$file, $reference ) !==
false
1145 || strpos(
$file,
"-thumbnail" ) !==
false
1147 $purgeList[] =
"{$dir}/{$file}";
1151 # Delete the thumbnails
1152 $this->repo->quickPurgeBatch( $purgeList );
1153 # Clear out the thumbnail directory if empty
1154 $this->repo->quickCleanDir( $dir );
1167 function getHistory( $limit =
null, $start =
null, $end =
null, $inc =
true ) {
1168 if ( !$this->
exists() ) {
1172 $dbr = $this->repo->getReplicaDB();
1175 $tables = $oldFileQuery[
'tables'];
1176 $fields = $oldFileQuery[
'fields'];
1177 $join_conds = $oldFileQuery[
'joins'];
1178 $conds = $opts = [];
1179 $eq = $inc ?
'=' :
'';
1180 $conds[] =
"oi_name = " .
$dbr->addQuotes( $this->title->getDBkey() );
1183 $conds[] =
"oi_timestamp <$eq " .
$dbr->addQuotes(
$dbr->timestamp( $start ) );
1187 $conds[] =
"oi_timestamp >$eq " .
$dbr->addQuotes(
$dbr->timestamp( $end ) );
1191 $opts[
'LIMIT'] = $limit;
1195 $order = ( !$start && $end !== null ) ?
'ASC' :
'DESC';
1196 $opts[
'ORDER BY'] =
"oi_timestamp $order";
1197 $opts[
'USE INDEX'] = [
'oldimage' =>
'oi_name_timestamp' ];
1201 Hooks::run(
'LocalFile::getHistory', [ &$localFile, &$tables, &$fields,
1202 &$conds, &$opts, &$join_conds ] );
1204 $res =
$dbr->select( $tables, $fields, $conds, __METHOD__, $opts, $join_conds );
1207 foreach (
$res as $row ) {
1208 $r[] = $this->repo->newFileFromRow( $row );
1211 if ( $order ==
'ASC' ) {
1212 $r = array_reverse( $r );
1228 if ( !$this->
exists() ) {
1232 # Polymorphic function name to distinguish foreign and local fetches
1233 $fname = static::class .
'::' . __FUNCTION__;
1235 $dbr = $this->repo->getReplicaDB();
1237 if ( $this->historyLine == 0 ) {
1239 $this->historyRes =
$dbr->select( $fileQuery[
'tables'],
1240 $fileQuery[
'fields'] + [
1241 'oi_archive_name' =>
$dbr->addQuotes(
'' ),
1244 [
'img_name' => $this->title->getDBkey() ],
1250 if (
$dbr->numRows( $this->historyRes ) == 0 ) {
1251 $this->historyRes =
null;
1255 } elseif ( $this->historyLine == 1 ) {
1257 $this->historyRes =
$dbr->select(
1258 $fileQuery[
'tables'],
1259 $fileQuery[
'fields'],
1260 [
'oi_name' => $this->title->getDBkey() ],
1262 [
'ORDER BY' =>
'oi_timestamp DESC' ],
1266 $this->historyLine++;
1268 return $dbr->fetchObject( $this->historyRes );
1275 $this->historyLine = 0;
1277 if ( !is_null( $this->historyRes ) ) {
1278 $this->historyRes =
null;
1315 function upload( $src, $comment, $pageText, $flags = 0, $props =
false,
1317 $createNullRevision =
true, $revert =
false
1319 if ( $this->
getRepo()->getReadOnlyReason() !==
false ) {
1321 } elseif ( MediaWikiServices::getInstance()->getRevisionStore()->isReadOnly() ) {
1327 $srcPath = ( $src instanceof
FSFile ) ? $src->getPath() : $src;
1332 $props = $this->repo->getFileProps( $srcPath );
1334 $mwProps =
new MWFileProps( MediaWikiServices::getInstance()->getMimeAnalyzer() );
1335 $props = $mwProps->getPropsFromPath( $srcPath,
true );
1342 $metadata = AtEase::quietCall(
'unserialize', $props[
'metadata'] );
1350 $options[
'headers'] = [];
1354 $comment = trim( $comment );
1357 $status = $this->
publish( $src, $flags, $options );
1359 if ( $status->successCount >= 2 ) {
1366 $oldver = $status->value;
1375 $createNullRevision,
1378 if ( !$uploadStatus->isOK() ) {
1379 if ( $uploadStatus->hasMessage(
'filenotfound' ) ) {
1381 $status->fatal(
'filenotfound', $srcPath );
1383 $status->merge( $uploadStatus );
1439 $oldver, $comment, $pageText, $props =
false,
$timestamp =
false,
$user =
null, $tags = [],
1440 $createNullRevision =
true, $revert =
false
1442 if ( is_null(
$user ) ) {
1447 $dbw = $this->repo->getMasterDB();
1449 # Imports or such might force a certain timestamp; otherwise we generate
1450 # it and can fudge it slightly to keep (name,timestamp) unique on re-upload.
1453 $allowTimeKludge =
true;
1455 $allowTimeKludge =
false;
1458 $props = $props ?: $this->repo->getFileProps( $this->
getVirtualUrl() );
1459 $props[
'description'] = $comment;
1466 # Fail now if the file isn't there
1467 if ( !$this->fileExists ) {
1468 wfDebug( __METHOD__ .
": File " . $this->
getRel() .
" went missing!\n" );
1470 return Status::newFatal(
'filenotfound', $this->
getRel() );
1473 $dbw->startAtomic( __METHOD__ );
1475 # Test to see if the row exists using INSERT IGNORE
1476 # This avoids race conditions by locking the row until the commit, and also
1477 # doesn't deadlock. SELECT FOR UPDATE causes a deadlock for every race condition.
1478 $commentStore = MediaWikiServices::getInstance()->getCommentStore();
1479 $commentFields = $commentStore->insert( $dbw,
'img_description', $comment );
1480 $actorMigration = ActorMigration::newMigration();
1481 $actorFields = $actorMigration->getInsertValues( $dbw,
'img_user',
$user );
1482 $dbw->insert(
'image',
1484 'img_name' => $this->
getName(),
1485 'img_size' => $this->size,
1486 'img_width' => intval( $this->width ),
1487 'img_height' => intval( $this->height ),
1488 'img_bits' => $this->bits,
1489 'img_media_type' => $this->media_type,
1490 'img_major_mime' => $this->major_mime,
1491 'img_minor_mime' => $this->minor_mime,
1493 'img_metadata' => $dbw->encodeBlob( $this->metadata ),
1494 'img_sha1' => $this->sha1
1495 ] + $commentFields + $actorFields,
1499 $reupload = ( $dbw->affectedRows() == 0 );
1502 $row = $dbw->selectRow(
1504 [
'img_timestamp',
'img_sha1' ],
1505 [
'img_name' => $this->
getName() ],
1507 [
'LOCK IN SHARE MODE' ]
1510 if ( $row && $row->img_sha1 === $this->sha1 ) {
1511 $dbw->endAtomic( __METHOD__ );
1512 wfDebug( __METHOD__ .
": File " . $this->
getRel() .
" already exists!\n" );
1517 if ( $allowTimeKludge ) {
1518 # Use LOCK IN SHARE MODE to ignore any transaction snapshotting
1519 $lUnixtime = $row ?
wfTimestamp( TS_UNIX, $row->img_timestamp ) :
false;
1520 # Avoid a timestamp that is not newer than the last version
1521 # TODO: the image/oldimage tables should be like page/revision with an ID field
1524 $timestamp = $dbw->timestamp( $lUnixtime + 1 );
1529 $tables = [
'image' ];
1531 'oi_name' =>
'img_name',
1532 'oi_archive_name' => $dbw->addQuotes( $oldver ),
1533 'oi_size' =>
'img_size',
1534 'oi_width' =>
'img_width',
1535 'oi_height' =>
'img_height',
1536 'oi_bits' =>
'img_bits',
1537 'oi_description_id' =>
'img_description_id',
1538 'oi_timestamp' =>
'img_timestamp',
1539 'oi_metadata' =>
'img_metadata',
1540 'oi_media_type' =>
'img_media_type',
1541 'oi_major_mime' =>
'img_major_mime',
1542 'oi_minor_mime' =>
'img_minor_mime',
1543 'oi_sha1' =>
'img_sha1',
1544 'oi_actor' =>
'img_actor',
1548 # (T36993) Note: $oldver can be empty here, if the previous
1549 # version of the file was broken. Allow registration of the new
1550 # version to continue anyway, because that's better than having
1551 # an image that's not fixable by user operations.
1552 # Collision, this is an update of a file
1553 # Insert previous contents into oldimage
1554 $dbw->insertSelect(
'oldimage', $tables, $fields,
1555 [
'img_name' => $this->
getName() ], __METHOD__, [], [], $joins );
1557 # Update the current image row
1558 $dbw->update(
'image',
1560 'img_size' => $this->size,
1561 'img_width' => intval( $this->width ),
1562 'img_height' => intval( $this->height ),
1563 'img_bits' => $this->bits,
1564 'img_media_type' => $this->media_type,
1565 'img_major_mime' => $this->major_mime,
1566 'img_minor_mime' => $this->minor_mime,
1568 'img_metadata' => $dbw->encodeBlob( $this->metadata ),
1569 'img_sha1' => $this->sha1
1570 ] + $commentFields + $actorFields,
1571 [
'img_name' => $this->getName() ],
1577 $descId = $descTitle->getArticleID();
1579 $wikiPage->setFile( $this );
1582 if ( $revert ===
true ) {
1583 $logAction =
'revert';
1584 } elseif ( $reupload ===
true ) {
1585 $logAction =
'overwrite';
1587 $logAction =
'upload';
1591 $logEntry->setTimestamp( $this->timestamp );
1592 $logEntry->setPerformer(
$user );
1593 $logEntry->setComment( $comment );
1594 $logEntry->setTarget( $descTitle );
1597 $logEntry->setParameters(
1599 'img_sha1' => $this->sha1,
1609 $logId = $logEntry->insert();
1611 if ( $descTitle->exists() ) {
1614 $formatter->setContext( RequestContext::newExtraneousContext( $descTitle ) );
1615 $editSummary = $formatter->getPlainActionText();
1617 $nullRevision = $createNullRevision ===
false ? null : Revision::newNullRevision(
1624 if ( $nullRevision ) {
1625 $nullRevision->insertOn( $dbw );
1627 'NewRevisionFromEditComplete',
1628 [ $wikiPage, $nullRevision, $nullRevision->getParentId(),
$user ]
1630 $wikiPage->updateRevisionOn( $dbw, $nullRevision );
1632 $logEntry->setAssociatedRevId( $nullRevision->getId() );
1635 $newPageContent =
null;
1638 $newPageContent = ContentHandler::makeContent( $pageText, $descTitle );
1641 # Defer purges, page creation, and link updates in case they error out.
1642 # The most important thing is that files and the DB registry stay synced.
1643 $dbw->endAtomic( __METHOD__ );
1644 $fname = __METHOD__;
1646 # Do some cache purges after final commit so that:
1647 # a) Changes are more likely to be seen post-purge
1648 # b) They won't cause rollback of the log publish/update above
1649 DeferredUpdates::addUpdate(
1654 $reupload, $wikiPage, $newPageContent, $comment,
$user,
1655 $logEntry, $logId, $descId, $tags, $fname
1657 # Update memcache after the commit
1660 $updateLogPage =
false;
1661 if ( $newPageContent ) {
1662 # New file page; create the description page.
1663 # There's already a log entry, so don't make a second RC entry
1664 # CDN and file cache for the description page are purged by doEditContent.
1665 $status = $wikiPage->doEditContent(
1673 if ( isset( $status->value[
'revision'] ) ) {
1675 $rev = $status->value[
'revision'];
1677 $logEntry->setAssociatedRevId( $rev->getId() );
1681 if ( isset( $status->value[
'revision'] ) ) {
1683 $rev = $status->value[
'revision'];
1684 $updateLogPage = $rev->getPage();
1687 # Existing file page: invalidate description page cache
1688 $wikiPage->getTitle()->invalidateCache();
1689 $wikiPage->getTitle()->purgeSquid();
1690 # Allow the new file version to be patrolled from the page footer
1694 # Update associated rev id. This should be done by $logEntry->insert() earlier,
1695 # but setAssociatedRevId() wasn't called at that point yet...
1696 $logParams = $logEntry->getParameters();
1697 $logParams[
'associated_rev_id'] = $logEntry->getAssociatedRevId();
1699 if ( $updateLogPage ) {
1700 # Also log page, in case where we just created it above
1701 $update[
'log_page'] = $updateLogPage;
1703 $this->
getRepo()->getMasterDB()->update(
1706 [
'log_id' => $logId ],
1709 $this->
getRepo()->getMasterDB()->insert(
1712 'ls_field' =>
'associated_rev_id',
1713 'ls_value' => $logEntry->getAssociatedRevId(),
1714 'ls_log_id' => $logId,
1719 # Add change tags, if any
1721 $logEntry->addTags( $tags );
1724 # Uploads can be patrolled
1725 $logEntry->setIsPatrollable(
true );
1727 # Now that the log entry is up-to-date, make an RC entry.
1728 $logEntry->publish( $logId );
1730 # Run hook for other updates (typically more cache purging)
1731 Hooks::run(
'FileUpload', [ $this, $reupload, !$newPageContent ] );
1734 # Delete old thumbnails
1736 # Remove the old file from the CDN cache
1737 DeferredUpdates::addUpdate(
1739 DeferredUpdates::PRESEND
1742 # Update backlink pages pointing to this title if created
1743 LinksUpdate::queueRecursiveJobsForTable(
1754 DeferredUpdates::PRESEND
1758 # This is a new file, so update the image count
1759 DeferredUpdates::addUpdate( SiteStatsUpdate::factory( [
'images' => 1 ] ) );
1762 # Invalidate cache for all pages using this file
1763 DeferredUpdates::addUpdate(
1767 return Status::newGood();
1785 function publish( $src, $flags = 0, array $options = [] ) {
1804 function publishTo( $src, $dstRel, $flags = 0, array $options = [] ) {
1805 $srcPath = ( $src instanceof
FSFile ) ? $src->getPath() : $src;
1814 if ( $this->
isOld() ) {
1815 $archiveRel = $dstRel;
1816 $archiveName = basename( $archiveRel );
1828 '@phan-var FileBackendDBRepoWrapper $wrapperBackend';
1829 $dst = $wrapperBackend->getPathForSHA1(
$sha1 );
1836 $status->value = $archiveName;
1840 $status =
$repo->
publish( $srcPath, $dstRel, $archiveRel, $flags, $options );
1842 if ( $status->value ==
'new' ) {
1843 $status->value =
'';
1845 $status->value = $archiveName;
1871 $localRepo = MediaWikiServices::getInstance()->getRepoGroup()->getLocalRepo();
1872 if ( $this->
getRepo()->getReadOnlyReason() !==
false ) {
1876 wfDebugLog(
'imagemove',
"Got request to move {$this->name} to " . $target->getText() );
1880 $batch->addCurrent();
1881 $archiveNames = $batch->addOlds();
1882 $status = $batch->execute();
1885 wfDebugLog(
'imagemove',
"Finished moving {$this->name}" );
1888 $oldTitleFile = $localRepo->newFile( $this->title );
1889 $newTitleFile = $localRepo->newFile( $target );
1890 DeferredUpdates::addUpdate(
1892 $this->
getRepo()->getMasterDB(),
1894 function () use ( $oldTitleFile, $newTitleFile, $archiveNames ) {
1895 $oldTitleFile->purgeEverything();
1896 foreach ( $archiveNames as $archiveName ) {
1898 '@phan-var OldLocalFile $oldTitleFile';
1899 $oldTitleFile->purgeOldThumbnails( $archiveName );
1901 $newTitleFile->purgeEverything();
1904 DeferredUpdates::PRESEND
1907 if ( $status->isOK() ) {
1909 $this->title = $target;
1912 $this->hashPath =
null;
1931 function delete( $reason, $suppress =
false,
$user = null ) {
1932 if ( $this->
getRepo()->getReadOnlyReason() !== false ) {
1939 $batch->addCurrent();
1941 $archiveNames = $batch->addOlds();
1942 $status = $batch->execute();
1945 if ( $status->isOK() ) {
1946 DeferredUpdates::addUpdate( SiteStatsUpdate::factory( [
'images' => -1 ] ) );
1950 DeferredUpdates::addUpdate(
1952 $this->
getRepo()->getMasterDB(),
1954 function () use ( $archiveNames ) {
1956 foreach ( $archiveNames as $archiveName ) {
1961 DeferredUpdates::PRESEND
1966 foreach ( $archiveNames as $archiveName ) {
1969 DeferredUpdates::addUpdate(
new CdnCacheUpdate( $purgeUrls ), DeferredUpdates::PRESEND );
1990 if ( $this->
getRepo()->getReadOnlyReason() !==
false ) {
1997 $batch->addOld( $archiveName );
1998 $status = $batch->execute();
2002 if ( $status->isOK() ) {
2006 DeferredUpdates::addUpdate(
2008 DeferredUpdates::PRESEND
2025 function restore( $versions = [], $unsuppress =
false ) {
2026 if ( $this->
getRepo()->getReadOnlyReason() !==
false ) {
2036 $batch->addIds( $versions );
2038 $status = $batch->execute();
2039 if ( $status->isGood() ) {
2040 $cleanupStatus = $batch->cleanup();
2041 $cleanupStatus->successCount = 0;
2042 $cleanupStatus->failCount = 0;
2043 $status->merge( $cleanupStatus );
2060 if ( !$this->title ) {
2064 return $this->title->getLocalURL();
2076 if ( !$this->title ) {
2080 $store = MediaWikiServices::getInstance()->getRevisionStore();
2081 $revision = $store->getRevisionByTitle( $this->title, 0, Revision::READ_NORMAL );
2086 $renderer = MediaWikiServices::getInstance()->getRevisionRenderer();
2087 $rendered = $renderer->getRenderedRevision( $revision,
new ParserOptions(
null,
$lang ) );
2094 $pout = $rendered->getRevisionParserOutput();
2095 return $pout->getText();
2105 if ( $audience == self::FOR_PUBLIC && $this->
isDeleted( self::DELETED_COMMENT ) ) {
2107 } elseif ( $audience == self::FOR_THIS_USER
2129 if ( !$this->
exists() ) {
2136 if ( $this->descriptionTouched ===
null ) {
2138 'page_namespace' => $this->title->getNamespace(),
2139 'page_title' => $this->title->getDBkey()
2141 $touched = $this->repo->getReplicaDB()->selectField(
'page',
'page_touched', $cond, __METHOD__ );
2142 $this->descriptionTouched = $touched ?
wfTimestamp( TS_MW, $touched ) :
false;
2154 if ( $this->sha1 ==
'' && $this->fileExists ) {
2157 $this->sha1 = $this->repo->getFileSha1( $this->
getPath() );
2158 if ( !
wfReadOnly() && strval( $this->sha1 ) !=
'' ) {
2159 $dbw = $this->repo->getMasterDB();
2160 $dbw->update(
'image',
2161 [
'img_sha1' => $this->sha1 ],
2162 [
'img_name' => $this->
getName() ],
2180 return $this->extraDataLoaded
2189 return Status::wrap( $this->
getRepo()->getBackend()->lockFiles(
2190 [ $this->
getPath() ], LockManager::LOCK_EX, 10
2199 return Status::wrap( $this->
getRepo()->getBackend()->unlockFiles(
2200 [ $this->
getPath() ], LockManager::LOCK_EX
2214 if ( !$this->locked ) {
2215 $logger = LoggerFactory::getInstance(
'LocalFile' );
2217 $dbw = $this->repo->getMasterDB();
2218 $makesTransaction = !$dbw->trxLevel();
2219 $dbw->startAtomic( self::ATOMIC_SECTION_LOCK );
2224 if ( !$status->isGood() ) {
2225 $dbw->endAtomic( self::ATOMIC_SECTION_LOCK );
2226 $logger->warning(
"Failed to lock '{file}'", [
'file' => $this->name ] );
2232 $dbw->onTransactionResolution(
2233 function () use ( $logger ) {
2235 if ( !$status->isGood() ) {
2236 $logger->error(
"Failed to unlock '{file}'", [
'file' => $this->name ] );
2242 $this->lockedOwnTrx = $makesTransaction;
2259 if ( $this->locked ) {
2261 if ( !$this->locked ) {
2262 $dbw = $this->repo->getMasterDB();
2263 $dbw->endAtomic( self::ATOMIC_SECTION_LOCK );
2264 $this->lockedOwnTrx =
false;
2273 return $this->
getRepo()->newFatal(
'filereadonlyerror', $this->
getName(),
$wgUpdateCompatibleMetadata
If to automatically update the img_metadata field if the metadata field is outdated but compatible wi...
$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.
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.
Handles purging the appropriate CDN objects given a list of URLs or Title instances.
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.
getFileSha1( $virtualUrl)
Get the sha1 (base 36) of a file with a given virtual URL/storage path.
hasSha1Storage()
Returns whether or not storage is SHA-1 based.
publish( $src, $dstRel, $archiveRel, $flags=0, array $options=[])
Copy or move a file either from a storage path, virtual URL, or file system path, into this repositor...
static isVirtualUrl( $url)
Determine if a string is an mwrepo:// URL.
getReadOnlyReason()
Get an explanatory message if this repo is read-only.
quickImport( $src, $dst, $options=null)
Import a file from the local file system into the repo.
getBackend()
Get the file backend instance.
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.
isVectorized()
Return true if the file is vectorized.
purgeDescription()
Purge the file description page, but don't go after pages using the file.
getPath()
Return the storage path to the file.
getThumbPath( $suffix=false)
Get the path of the thumbnail directory, or a particular file if $suffix is specified.
getRel()
Get the path of the file relative to the public zone root.
assertRepoDefined()
Assert that $this->repo is set to a valid FileRepo instance.
getName()
Return the name of this file.
getThumbUrl( $suffix=false)
Get the URL of the thumbnail directory, or a particular file if $suffix is specified.
getVirtualUrl( $suffix=false)
Get the public zone virtual URL for a current version source file.
getRepo()
Returns the repository.
assertTitleDefined()
Assert that $this->title is set to a Title.
getArchiveThumbPath( $archiveName, $suffix=false)
Get the path of an archived file's thumbs, or a particular thumb if $suffix is specified.
isOld()
Returns true if the image is an old version STUB.
getTitle()
Return the associated title object.
isMultipage()
Returns 'true' if this file is a type which supports multiple pages, e.g.
getThumbnails()
Get all thumbnail names previously generated for this file STUB Overridden by LocalFile.
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().
purgeEverything()
Purge metadata and all affected pages when the file is created, deleted, or majorly updated.
static splitMime( $mime)
Split an internet media type into its two components; if not a two-part name, set the minor type to '...
getUrl()
Return the URL of the file.
getArchiveUrl( $suffix=false)
Get the URL of the archive directory, or a particular file if $suffix is specified.
userCan( $field, User $user=null)
Determine if the current user is allowed to view a particular field of this file, if it's marked as d...
getHandler()
Get a MediaHandler instance for this file.
getArchiveRel( $suffix=false)
Get the path of an archived file relative to the public zone root.
isDeleted( $field)
Is this file a "deleted" file in a private archive? STUB.
getArchiveThumbUrl( $archiveName, $suffix=false)
Get the URL of the archived file's thumbs, or a particular thumb if $suffix is specified.
Class to invalidate the HTML/file cache of all the pages linking to a given title.
Internationalisation code.
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.
getMediaType()
Returns the type of the media in the file.
recordUpload( $oldver, $desc, $license='', $copyStatus='', $source='', $watch=false, $timestamp=false, User $user=null)
Record a file upload in the upload log and the image table.
move( $target)
getLinksTo inherited
lock()
Start an atomic DB section and lock the image for update or increments a reference counter if the loc...
recordUpload2( $oldver, $comment, $pageText, $props=false, $timestamp=false, $user=null, $tags=[], $createNullRevision=true, $revert=false)
Record a file upload in the upload log and the image table.
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.
isMissing()
splitMime inherited
deleteOld( $archiveName, $reason, $suppress=false, $user=null)
Delete an old version of the file.
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.
getDescription( $audience=self::FOR_PUBLIC, User $user=null)
loadFromDB( $flags=0)
Load file metadata from the DB.
load( $flags=0)
Load file metadata from cache or DB, unless already loaded.
bool $upgrading
Whether the row was scheduled to upgrade on load.
string $media_type
MEDIATYPE_xxx (bitmap, drawing, audio...)
loadFromFile()
Load metadata from the file itself.
purgeCache( $options=[])
Delete all previously generated thumbnails, refresh metadata in memcached and purge the CDN.
int $size
Size in bytes (loadFromXxx)
string $minor_mime
Minor MIME type.
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.
string $timestamp
Upload timestamp.
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.
purgeThumbList( $dir, $files)
Delete a list of thumbnails visible at urls.
getUser( $type='text')
Returns user who uploaded the file.
decodeRow( $row, $prefix='img_')
Decode a row from the database (either object or array) to an array with timestamps and MIME types de...
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.
upload( $src, $comment, $pageText, $flags=0, $props=false, $timestamp=false, $user=null, $tags=[], $createNullRevision=true, $revert=false)
getHashPath inherited
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.
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.
publish( $src, $flags=0, array $options=[])
Move or copy a file to its public location.
string $metadata
Handler-specific metadata.
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)
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.
__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)
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.
Set options of the Parser.
static getInitialPageText( $comment='', $license='', $copyStatus='', $source='', Config $config=null)
Get the initial image page text based on a comment and optional file status information.
Job for asynchronous rendering of thumbnails.
getPrefixedText()
Get the prefixed title with spaces.
The User object encapsulates all of the user-specific settings (user_id, name, rights,...
addWatch( $title, $checkRights=self::CHECK_USER_RIGHTS)
Watch an article.
getName()
Get the user name, or the IP of an anonymous user.
getId()
Get the user's ID.
getActorId(IDatabase $dbw=null)
Get the user's actor ID.
Multi-datacenter aware caching interface.
Special handling for file pages.
if(PHP_SAPI !='cli-server') if(!isset( $_SERVER['SCRIPT_FILENAME'])) $file
Item class for a filearchive table row.
if(!isset( $args[0])) $lang