Go to the documentation of this file.
28 use Wikimedia\AtEase\AtEase;
179 $file->loadFromRow( $row );
198 $conds = [
'img_sha1' =>
$sha1 ];
203 $fileQuery = static::getQueryInfo();
204 $row =
$dbr->selectRow(
205 $fileQuery[
'tables'], $fileQuery[
'fields'], $conds, __METHOD__, [], $fileQuery[
'joins']
208 return static::newFromRow( $row,
$repo );
228 $commentQuery = MediaWikiServices::getInstance()->getCommentStore()->getJoin(
'img_description' );
231 'tables' => [
'image' ] + $commentQuery[
'tables'] + $actorQuery[
'tables'],
244 ] + $commentQuery[
'fields'] + $actorQuery[
'fields'],
245 'joins' => $commentQuery[
'joins'] + $actorQuery[
'joins'],
248 if ( in_array(
'omit-nonlazy', $options,
true ) ) {
252 if ( !in_array(
'omit-lazy', $options,
true ) ) {
254 $ret[
'fields'][] =
'img_metadata';
270 $this->metadata =
'';
271 $this->historyLine = 0;
272 $this->historyRes =
null;
273 $this->dataLoaded =
false;
274 $this->extraDataLoaded =
false;
294 return $this->repo->getSharedCacheKey(
'file', sha1( $this->
getName() ) );
310 $this->dataLoaded =
false;
311 $this->extraDataLoaded =
false;
320 $cache = MediaWikiServices::getInstance()->getMainWANObjectCache();
321 $cachedValues =
$cache->getWithSetCallback(
324 function ( $oldValue, &$ttl, array &$setOpts ) use (
$cache ) {
325 $setOpts += Database::getCacheSetOptions( $this->repo->getReplicaDB() );
332 if ( $this->fileExists ) {
333 foreach ( $fields as $field ) {
334 $cacheVal[$field] = $this->$field;
337 $cacheVal[
'user'] = $this->user ? $this->user->getId() : 0;
338 $cacheVal[
'user_text'] = $this->user ? $this->user->getName() :
'';
339 $cacheVal[
'actor'] = $this->user ? $this->user->getActorId() :
null;
345 if ( isset( $cacheVal[$field] )
346 && strlen( $cacheVal[$field] ) > 100 * 1024
348 unset( $cacheVal[$field] );
352 if ( $this->fileExists ) {
355 $ttl = $cache::TTL_DAY;
363 $this->fileExists = $cachedValues[
'fileExists'];
364 if ( $this->fileExists ) {
368 $this->dataLoaded =
true;
369 $this->extraDataLoaded =
true;
371 $this->extraDataLoaded = $this->extraDataLoaded && isset( $cachedValues[$field] );
384 $this->repo->getMasterDB()->onTransactionPreCommitOrIdle(
385 static function () use ( $key ) {
386 MediaWikiServices::getInstance()->getMainWANObjectCache()->delete( $key );
396 $props = $this->repo->getFileProps( $this->
getVirtualUrl() );
408 if ( $prefix !==
'' ) {
409 throw new InvalidArgumentException(
410 __METHOD__ .
' with a non-empty prefix is no longer supported.'
418 return [
'size',
'width',
'height',
'bits',
'media_type',
419 'major_mime',
'minor_mime',
'metadata',
'timestamp',
'sha1',
'description' ];
430 if ( $prefix !==
'' ) {
431 throw new InvalidArgumentException(
432 __METHOD__ .
' with a non-empty prefix is no longer supported.'
437 return [
'metadata' ];
446 $fname = static::class .
'::' . __FUNCTION__;
448 # Unconditionally set loaded=true, we don't want the accessors constantly rechecking
449 $this->dataLoaded =
true;
450 $this->extraDataLoaded =
true;
452 $dbr = ( $flags & self::READ_LATEST )
453 ? $this->repo->getMasterDB()
454 : $this->repo->getReplicaDB();
456 $fileQuery = static::getQueryInfo();
457 $row =
$dbr->selectRow(
458 $fileQuery[
'tables'],
459 $fileQuery[
'fields'],
460 [
'img_name' => $this->
getName() ],
469 $this->fileExists =
false;
479 if ( !$this->title ) {
483 $fname = static::class .
'::' . __FUNCTION__;
485 # Unconditionally set loaded=true, we don't want the accessors constantly rechecking
486 $this->extraDataLoaded =
true;
494 foreach ( $fieldMap as
$name => $value ) {
495 $this->
$name = $value;
498 throw new MWException(
"Could not find data for image '{$this->getName()}'." );
511 $row =
$dbr->selectRow(
512 $fileQuery[
'tables'],
513 $fileQuery[
'fields'],
515 'img_name' => $this->
getName(),
516 'img_timestamp' =>
$dbr->timestamp( $this->getTimestamp() ),
525 # File may have been uploaded over in the meantime; check the old versions
527 $row =
$dbr->selectRow(
528 $fileQuery[
'tables'],
529 $fileQuery[
'fields'],
532 'oi_timestamp' =>
$dbr->timestamp( $this->getTimestamp() ),
543 if ( isset( $fieldMap[
'metadata'] ) ) {
544 $fieldMap[
'metadata'] = $this->repo->getReplicaDB()->decodeBlob( $fieldMap[
'metadata'] );
557 $array = (array)$row;
558 $prefixLength = strlen( $prefix );
561 if ( substr( key( $array ), 0, $prefixLength ) !== $prefix ) {
562 throw new MWException( __METHOD__ .
': incorrect $prefix parameter' );
566 foreach ( $array as
$name => $value ) {
567 $decoded[substr(
$name, $prefixLength )] = $value;
584 $decoded[
'description'] = MediaWikiServices::getInstance()->getCommentStore()
585 ->getComment(
'description', (
object)$decoded )->text;
588 $decoded[
'user'] ??
null,
589 $decoded[
'user_text'] ??
null,
590 $decoded[
'actor'] ??
null
592 unset( $decoded[
'user_text'], $decoded[
'actor'] );
594 $decoded[
'timestamp'] =
wfTimestamp( TS_MW, $decoded[
'timestamp'] );
596 $decoded[
'metadata'] = $this->repo->getReplicaDB()->decodeBlob( $decoded[
'metadata'] );
598 if ( empty( $decoded[
'major_mime'] ) ) {
599 $decoded[
'mime'] =
'unknown/unknown';
601 if ( !$decoded[
'minor_mime'] ) {
602 $decoded[
'minor_mime'] =
'unknown';
604 $decoded[
'mime'] = $decoded[
'major_mime'] .
'/' . $decoded[
'minor_mime'];
608 $decoded[
'sha1'] = rtrim( $decoded[
'sha1'],
"\0" );
614 foreach ( [
'size',
'width',
'height',
'bits' ] as $field ) {
615 $decoded[$field] = +$decoded[$field];
629 $this->dataLoaded =
true;
630 $this->extraDataLoaded =
true;
632 $array = $this->
decodeRow( $row, $prefix );
634 foreach ( $array as
$name => $value ) {
635 $this->
$name = $value;
638 $this->fileExists =
true;
646 public function load( $flags = 0 ) {
647 if ( !$this->dataLoaded ) {
648 if ( $flags & self::READ_LATEST ) {
655 if ( ( $flags & self::LOAD_ALL ) && !$this->extraDataLoaded ) {
672 if ( $this->media_type ===
null || $this->mime ==
'image/svg' ) {
687 $this->upgrading =
true;
690 $this->upgrading =
false;
716 # Don't destroy file info of missing files
717 if ( !$this->fileExists ) {
719 wfDebug( __METHOD__ .
": file does not exist, aborting" );
724 $dbw = $this->repo->getMasterDB();
732 wfDebug( __METHOD__ .
': upgrading ' . $this->
getName() .
" to the current schema" );
734 $dbw->update(
'image',
736 'img_size' => $this->size,
737 'img_width' => $this->width,
738 'img_height' => $this->height,
739 'img_bits' => $this->bits,
740 'img_media_type' => $this->media_type,
741 'img_major_mime' => $major,
742 'img_minor_mime' => $minor,
743 'img_metadata' => $dbw->encodeBlob( $this->metadata ),
744 'img_sha1' => $this->sha1,
746 [
'img_name' => $this->getName() ],
753 $this->upgraded =
true;
768 $this->dataLoaded =
true;
770 $fields[] =
'fileExists';
772 foreach ( $fields as $field ) {
773 if ( isset( $info[$field] ) ) {
774 $this->$field = $info[$field];
778 if ( isset( $info[
'user'] ) || isset( $info[
'user_text'] ) || isset( $info[
'actor'] ) ) {
780 $info[
'user'] ??
null,
781 $info[
'user_text'] ??
null,
782 $info[
'actor'] ??
null
787 if ( isset( $info[
'major_mime'] ) ) {
788 $this->mime =
"{$info['major_mime']}/{$info['minor_mime']}";
789 } elseif ( isset( $info[
'mime'] ) ) {
790 $this->mime = $info[
'mime'];
791 list( $this->major_mime, $this->minor_mime ) =
self::splitMime( $this->mime );
811 if ( $this->missing ===
null ) {
841 return $dim[
'width'];
874 return $dim[
'height'];
896 if ( !$this->user ) {
899 if (
$type ===
'object' ) {
901 } elseif (
$type ===
'text' ) {
902 return 'Unknown user';
903 } elseif (
$type ===
'id' ) {
907 if (
$type ===
'object' ) {
909 } elseif (
$type ===
'text' ) {
911 } elseif (
$type ===
'id' ) {
912 return $this->user->getId();
927 if ( !$this->title ) {
931 $pageId = $this->title->getArticleID();
934 $url = $this->repo->makeUrl( [
'curid' => $pageId ] );
935 if (
$url !==
false ) {
948 $this->
load( self::LOAD_ALL );
1030 if ( $archiveName ) {
1036 $backend = $this->repo->getBackend();
1039 $iterator = $backend->getFileList( [
'dir' => $dir ] );
1040 if ( $iterator !==
null ) {
1041 foreach ( $iterator as
$file ) {
1075 $hcu = MediaWikiServices::getInstance()->getHtmlCacheUpdater();
1078 !empty( $options[
'forThumbRefresh'] )
1079 ? $hcu::PURGE_PRESEND
1080 : $hcu::PURGE_INTENT_TXROUND_REFLECTED
1094 $this->hookRunner->onLocalFilePurgeThumbnails( $this, $archiveName );
1097 $dir = array_shift( $files );
1102 foreach ( $files as
$file ) {
1106 $hcu = MediaWikiServices::getInstance()->getHtmlCacheUpdater();
1107 $hcu->purgeUrls( $urls, $hcu::PURGE_PRESEND );
1120 foreach ( $files as
$file ) {
1123 array_shift( $urls );
1126 if ( !empty( $options[
'forThumbRefresh'] ) ) {
1134 $this->getHookRunner()->onLocalFilePurgeThumbnails( $this,
false );
1137 $dir = array_shift( $files );
1141 $hcu = MediaWikiServices::getInstance()->getHtmlCacheUpdater();
1144 !empty( $options[
'forThumbRefresh'] )
1145 ? $hcu::PURGE_PRESEND
1146 : $hcu::PURGE_INTENT_TXROUND_REFLECTED
1164 foreach ( $sizes as
$size ) {
1168 [
'transformParams' => [
'width' => $size ] ]
1185 $fileListDebug = strtr(
1186 var_export( $files,
true ),
1189 wfDebug( __METHOD__ .
": $fileListDebug" );
1192 foreach ( $files as
$file ) {
1193 if ( $this->repo->supportsSha1URLs() ) {
1194 $reference = $this->
getSha1();
1196 $reference = $this->
getName();
1199 # Check that the reference (filename or sha1) is part of the thumb name
1200 # This is a basic sanity check to avoid erasing unrelated directories
1201 if ( strpos(
$file, $reference ) !==
false
1202 || strpos(
$file,
"-thumbnail" ) !==
false
1204 $purgeList[] =
"{$dir}/{$file}";
1208 # Delete the thumbnails
1209 $this->repo->quickPurgeBatch( $purgeList );
1210 # Clear out the thumbnail directory if empty
1211 $this->repo->quickCleanDir( $dir );
1225 public function getHistory( $limit =
null, $start =
null, $end =
null, $inc =
true ) {
1226 if ( !$this->
exists() ) {
1230 $dbr = $this->repo->getReplicaDB();
1233 $tables = $oldFileQuery[
'tables'];
1234 $fields = $oldFileQuery[
'fields'];
1235 $join_conds = $oldFileQuery[
'joins'];
1236 $conds = $opts = [];
1237 $eq = $inc ?
'=' :
'';
1238 $conds[] =
"oi_name = " .
$dbr->addQuotes( $this->title->getDBkey() );
1241 $conds[] =
"oi_timestamp <$eq " .
$dbr->addQuotes(
$dbr->timestamp( $start ) );
1245 $conds[] =
"oi_timestamp >$eq " .
$dbr->addQuotes(
$dbr->timestamp( $end ) );
1249 $opts[
'LIMIT'] = $limit;
1253 $order = ( !$start && $end !== null ) ?
'ASC' :
'DESC';
1254 $opts[
'ORDER BY'] =
"oi_timestamp $order";
1255 $opts[
'USE INDEX'] = [
'oldimage' =>
'oi_name_timestamp' ];
1257 $this->getHookRunner()->onLocalFile__getHistory( $this, $tables, $fields,
1258 $conds, $opts, $join_conds );
1260 $res =
$dbr->select( $tables, $fields, $conds, __METHOD__, $opts, $join_conds );
1263 foreach (
$res as $row ) {
1264 $r[] = $this->repo->newFileFromRow( $row );
1267 if ( $order ==
'ASC' ) {
1268 $r = array_reverse( $r );
1285 if ( !$this->
exists() ) {
1289 # Polymorphic function name to distinguish foreign and local fetches
1290 $fname = static::class .
'::' . __FUNCTION__;
1292 $dbr = $this->repo->getReplicaDB();
1294 if ( $this->historyLine == 0 ) {
1296 $this->historyRes =
$dbr->select( $fileQuery[
'tables'],
1297 $fileQuery[
'fields'] + [
1298 'oi_archive_name' =>
$dbr->addQuotes(
'' ),
1301 [
'img_name' => $this->title->getDBkey() ],
1307 if (
$dbr->numRows( $this->historyRes ) == 0 ) {
1308 $this->historyRes =
null;
1312 } elseif ( $this->historyLine == 1 ) {
1314 $this->historyRes =
$dbr->select(
1315 $fileQuery[
'tables'],
1316 $fileQuery[
'fields'],
1317 [
'oi_name' => $this->title->getDBkey() ],
1319 [
'ORDER BY' =>
'oi_timestamp DESC' ],
1323 $this->historyLine++;
1325 return $dbr->fetchObject( $this->historyRes );
1333 $this->historyLine = 0;
1335 if ( $this->historyRes !==
null ) {
1336 $this->historyRes =
null;
1373 public function upload( $src, $comment, $pageText, $flags = 0, $props =
false,
1375 $createNullRevision =
true, $revert =
false
1377 if ( $this->
getRepo()->getReadOnlyReason() !==
false ) {
1379 } elseif ( MediaWikiServices::getInstance()->getRevisionStore()->isReadOnly() ) {
1385 $srcPath = ( $src instanceof
FSFile ) ? $src->getPath() : $src;
1390 $props = $this->repo->getFileProps( $srcPath );
1392 $mwProps =
new MWFileProps( MediaWikiServices::getInstance()->getMimeAnalyzer() );
1393 $props = $mwProps->getPropsFromPath( $srcPath,
true );
1400 $metadata = AtEase::quietCall(
'unserialize', $props[
'metadata'] );
1408 $options[
'headers'] = [];
1412 $comment = trim( $comment );
1415 $status = $this->
publish( $src, $flags, $options );
1417 if ( $status->successCount >= 2 ) {
1424 $oldver = $status->value;
1426 if (
$user ===
null ) {
1439 $createNullRevision,
1442 if ( !$uploadStatus->isOK() ) {
1443 if ( $uploadStatus->hasMessage(
'filenotfound' ) ) {
1445 $status->fatal(
'filenotfound', $srcPath );
1447 $status->merge( $uploadStatus );
1472 $oldver, $comment, $pageText, $props =
false,
$timestamp =
false,
$user =
null, $tags = [],
1473 $createNullRevision =
true, $revert =
false
1476 if (
$user ===
null ) {
1481 $oldver, $comment, $pageText,
1483 $createNullRevision, $revert
1511 bool $createNullRevision =
true,
1512 bool $revert =
false
1514 $dbw = $this->repo->getMasterDB();
1516 # Imports or such might force a certain timestamp; otherwise we generate
1517 # it and can fudge it slightly to keep (name,timestamp) unique on re-upload.
1520 $allowTimeKludge =
true;
1522 $allowTimeKludge =
false;
1525 $props = $props ?: $this->repo->getFileProps( $this->
getVirtualUrl() );
1526 $props[
'description'] = $comment;
1530 # Fail now if the file isn't there
1531 if ( !$this->fileExists ) {
1532 wfDebug( __METHOD__ .
": File " . $this->
getRel() .
" went missing!" );
1537 $actorNormalizaton = MediaWikiServices::getInstance()->getActorNormalization();
1539 $dbw->startAtomic( __METHOD__ );
1541 $actorId = $actorNormalizaton->acquireActorId(
$user );
1542 $this->user =
$user;
1544 # Test to see if the row exists using INSERT IGNORE
1545 # This avoids race conditions by locking the row until the commit, and also
1546 # doesn't deadlock. SELECT FOR UPDATE causes a deadlock for every race condition.
1547 $commentStore = MediaWikiServices::getInstance()->getCommentStore();
1548 $commentFields = $commentStore->insert( $dbw,
'img_description', $comment );
1549 $actorFields = [
'img_actor' => $actorId ];
1550 $dbw->insert(
'image',
1552 'img_name' => $this->
getName(),
1553 'img_size' => $this->size,
1554 'img_width' => intval( $this->width ),
1555 'img_height' => intval( $this->height ),
1556 'img_bits' => $this->bits,
1557 'img_media_type' => $this->media_type,
1558 'img_major_mime' => $this->major_mime,
1559 'img_minor_mime' => $this->minor_mime,
1561 'img_metadata' => $dbw->encodeBlob( $this->metadata ),
1562 'img_sha1' => $this->sha1
1563 ] + $commentFields + $actorFields,
1567 $reupload = ( $dbw->affectedRows() == 0 );
1570 $row = $dbw->selectRow(
1572 [
'img_timestamp',
'img_sha1' ],
1573 [
'img_name' => $this->
getName() ],
1575 [
'LOCK IN SHARE MODE' ]
1578 if ( $row && $row->img_sha1 === $this->sha1 ) {
1579 $dbw->endAtomic( __METHOD__ );
1580 wfDebug( __METHOD__ .
": File " . $this->
getRel() .
" already exists!" );
1585 if ( $allowTimeKludge ) {
1586 # Use LOCK IN SHARE MODE to ignore any transaction snapshotting
1587 $lUnixtime = $row ?
wfTimestamp( TS_UNIX, $row->img_timestamp ) :
false;
1588 # Avoid a timestamp that is not newer than the last version
1589 # TODO: the image/oldimage tables should be like page/revision with an ID field
1592 $timestamp = $dbw->timestamp( $lUnixtime + 1 );
1597 $tables = [
'image' ];
1599 'oi_name' =>
'img_name',
1600 'oi_archive_name' => $dbw->addQuotes( $oldver ),
1601 'oi_size' =>
'img_size',
1602 'oi_width' =>
'img_width',
1603 'oi_height' =>
'img_height',
1604 'oi_bits' =>
'img_bits',
1605 'oi_description_id' =>
'img_description_id',
1606 'oi_timestamp' =>
'img_timestamp',
1607 'oi_metadata' =>
'img_metadata',
1608 'oi_media_type' =>
'img_media_type',
1609 'oi_major_mime' =>
'img_major_mime',
1610 'oi_minor_mime' =>
'img_minor_mime',
1611 'oi_sha1' =>
'img_sha1',
1612 'oi_actor' =>
'img_actor',
1616 # (T36993) Note: $oldver can be empty here, if the previous
1617 # version of the file was broken. Allow registration of the new
1618 # version to continue anyway, because that's better than having
1619 # an image that's not fixable by user operations.
1620 # Collision, this is an update of a file
1621 # Insert previous contents into oldimage
1622 $dbw->insertSelect(
'oldimage', $tables, $fields,
1623 [
'img_name' => $this->
getName() ], __METHOD__, [], [], $joins );
1625 # Update the current image row
1626 $dbw->update(
'image',
1628 'img_size' => $this->size,
1629 'img_width' => intval( $this->width ),
1630 'img_height' => intval( $this->height ),
1631 'img_bits' => $this->bits,
1632 'img_media_type' => $this->media_type,
1633 'img_major_mime' => $this->major_mime,
1634 'img_minor_mime' => $this->minor_mime,
1636 'img_metadata' => $dbw->encodeBlob( $this->metadata ),
1637 'img_sha1' => $this->sha1
1638 ] + $commentFields + $actorFields,
1639 [
'img_name' => $this->getName() ],
1645 $descId = $descTitle->getArticleID();
1647 $wikiPage->setFile( $this );
1650 if ( $revert ===
true ) {
1651 $logAction =
'revert';
1652 } elseif ( $reupload ===
true ) {
1653 $logAction =
'overwrite';
1655 $logAction =
'upload';
1659 $logEntry->setTimestamp( $this->timestamp );
1660 $logEntry->setPerformer(
$user );
1661 $logEntry->setComment( $comment );
1662 $logEntry->setTarget( $descTitle );
1665 $logEntry->setParameters(
1667 'img_sha1' => $this->sha1,
1677 $logId = $logEntry->insert();
1679 if ( $descTitle->exists() ) {
1680 if ( $createNullRevision !==
false ) {
1681 $revStore = MediaWikiServices::getInstance()->getRevisionStore();
1685 $editSummary = $formatter->getPlainActionText();
1687 $nullRevRecord =
$revStore->newNullRevision(
1695 if ( $nullRevRecord ) {
1696 $inserted =
$revStore->insertRevisionOn( $nullRevRecord, $dbw );
1698 $this->getHookRunner()->onRevisionFromEditComplete(
1701 $inserted->getParentId(),
1707 if ( $this->getHookContainer()->isRegistered(
'NewRevisionFromEditComplete' ) ) {
1709 $nullRevision =
new Revision( $inserted );
1710 $this->getHookRunner()->onNewRevisionFromEditComplete(
1713 $inserted->getParentId(),
1719 $wikiPage->updateRevisionOn( $dbw, $inserted );
1721 $logEntry->setAssociatedRevId( $inserted->getId() );
1725 $newPageContent =
null;
1731 # Defer purges, page creation, and link updates in case they error out.
1732 # The most important thing is that files and the DB registry stay synced.
1733 $dbw->endAtomic( __METHOD__ );
1734 $fname = __METHOD__;
1736 # Do some cache purges after final commit so that:
1737 # a) Changes are more likely to be seen post-purge
1738 # b) They won't cause rollback of the log publish/update above
1745 $reupload, $wikiPage, $newPageContent, $comment,
$user,
1746 $logEntry, $logId, $descId, $tags, $fname
1748 # Update memcache after the commit
1751 $updateLogPage =
false;
1752 if ( $newPageContent ) {
1753 # New file page; create the description page.
1754 # There's already a log entry, so don't make a second RC entry
1755 # CDN and file cache for the description page are purged by doEditContent.
1756 $status = $wikiPage->doEditContent(
1764 if ( isset( $status->value[
'revision-record'] ) ) {
1766 $revRecord = $status->value[
'revision-record'];
1768 $logEntry->setAssociatedRevId( $revRecord->getId() );
1772 if ( isset( $status->value[
'revision-record'] ) ) {
1774 $revRecord = $status->value[
'revision-record'];
1775 $updateLogPage = $revRecord->getPageId();
1778 # Existing file page: invalidate description page cache
1779 $title = $wikiPage->getTitle();
1781 $hcu = MediaWikiServices::getInstance()->getHtmlCacheUpdater();
1782 $hcu->purgeTitleUrls(
$title, $hcu::PURGE_INTENT_TXROUND_REFLECTED );
1783 # Allow the new file version to be patrolled from the page footer
1787 # Update associated rev id. This should be done by $logEntry->insert() earlier,
1788 # but setAssociatedRevId() wasn't called at that point yet...
1789 $logParams = $logEntry->getParameters();
1790 $logParams[
'associated_rev_id'] = $logEntry->getAssociatedRevId();
1792 if ( $updateLogPage ) {
1793 # Also log page, in case where we just created it above
1794 $update[
'log_page'] = $updateLogPage;
1796 $this->
getRepo()->getMasterDB()->update(
1799 [
'log_id' => $logId ],
1802 $this->
getRepo()->getMasterDB()->insert(
1805 'ls_field' =>
'associated_rev_id',
1806 'ls_value' => (
string)$logEntry->getAssociatedRevId(),
1807 'ls_log_id' => $logId,
1812 # Add change tags, if any
1814 $logEntry->addTags( $tags );
1817 # Uploads can be patrolled
1818 $logEntry->setIsPatrollable(
true );
1820 # Now that the log entry is up-to-date, make an RC entry.
1821 $logEntry->publish( $logId );
1823 # Run hook for other updates (typically more cache purging)
1824 $this->getHookRunner()->onFileUpload( $this, $reupload, !$newPageContent );
1827 # Delete old thumbnails
1829 # Remove the old file from the CDN cache
1830 $hcu = MediaWikiServices::getInstance()->getHtmlCacheUpdater();
1831 $hcu->purgeUrls( $this->
getUrl(), $hcu::PURGE_INTENT_TXROUND_REFLECTED );
1833 # Update backlink pages pointing to this title if created
1845 DeferredUpdates::PRESEND
1849 # This is a new file, so update the image count
1853 # Invalidate cache for all pages using this file
1857 [
'causeAction' =>
'file-upload',
'causeAgent' => $user->
getName() ]
1880 public function publish( $src, $flags = 0, array $options = [] ) {
1900 protected function publishTo( $src, $dstRel, $flags = 0, array $options = [] ) {
1901 $srcPath = ( $src instanceof
FSFile ) ? $src->getPath() : $src;
1910 if ( $this->
isOld() ) {
1911 $archiveRel = $dstRel;
1912 $archiveName = basename( $archiveRel );
1924 '@phan-var FileBackendDBRepoWrapper $wrapperBackend';
1925 $dst = $wrapperBackend->getPathForSHA1(
$sha1 );
1932 $status->value = $archiveName;
1936 $status =
$repo->
publish( $srcPath, $dstRel, $archiveRel, $flags, $options );
1938 if ( $status->value ==
'new' ) {
1939 $status->value =
'';
1941 $status->value = $archiveName;
1968 $localRepo = MediaWikiServices::getInstance()->getRepoGroup()->getLocalRepo();
1969 if ( $this->
getRepo()->getReadOnlyReason() !==
false ) {
1973 wfDebugLog(
'imagemove',
"Got request to move {$this->name} to " . $target->getText() );
1977 $batch->addCurrent();
1978 $archiveNames = $batch->addOlds();
1979 $status = $batch->execute();
1982 wfDebugLog(
'imagemove',
"Finished moving {$this->name}" );
1985 $oldTitleFile = $localRepo->newFile( $this->title );
1986 $newTitleFile = $localRepo->newFile( $target );
1989 $this->
getRepo()->getMasterDB(),
1991 static function () use ( $oldTitleFile, $newTitleFile, $archiveNames ) {
1992 $oldTitleFile->purgeEverything();
1993 foreach ( $archiveNames as $archiveName ) {
1995 '@phan-var OldLocalFile $oldTitleFile';
1996 $oldTitleFile->purgeOldThumbnails( $archiveName );
1998 $newTitleFile->purgeEverything();
2001 DeferredUpdates::PRESEND
2004 if ( $status->isOK() ) {
2006 $this->title = $target;
2009 $this->hashPath =
null;
2032 if ( $this->
getRepo()->getReadOnlyReason() !==
false ) {
2039 $batch->addCurrent();
2041 $archiveNames = $batch->addOlds();
2042 $status = $batch->execute();
2045 if ( $status->isOK() ) {
2052 $this->
getRepo()->getMasterDB(),
2054 function () use ( $archiveNames ) {
2056 foreach ( $archiveNames as $archiveName ) {
2061 DeferredUpdates::PRESEND
2066 foreach ( $archiveNames as $archiveName ) {
2070 $hcu = MediaWikiServices::getInstance()->getHtmlCacheUpdater();
2071 $hcu->purgeUrls( $purgeUrls, $hcu::PURGE_INTENT_TXROUND_REFLECTED );
2095 if ( $this->
getRepo()->getReadOnlyReason() !==
false ) {
2102 $batch->addOld( $archiveName );
2103 $status = $batch->execute();
2107 if ( $status->isOK() ) {
2112 $hcu = MediaWikiServices::getInstance()->getHtmlCacheUpdater();
2113 $hcu->purgeUrls(
$url, $hcu::PURGE_INTENT_TXROUND_REFLECTED );
2130 public function restore( $versions = [], $unsuppress =
false ) {
2131 if ( $this->
getRepo()->getReadOnlyReason() !==
false ) {
2141 $batch->addIds( $versions );
2143 $status = $batch->execute();
2144 if ( $status->isGood() ) {
2145 $cleanupStatus = $batch->cleanup();
2146 $cleanupStatus->successCount = 0;
2147 $cleanupStatus->failCount = 0;
2148 $status->merge( $cleanupStatus );
2166 if ( !$this->title ) {
2170 return $this->title->getLocalURL();
2183 if ( !$this->title ) {
2187 $store = MediaWikiServices::getInstance()->getRevisionStore();
2188 $revision = $store->getRevisionByTitle( $this->title, 0, RevisionStore::READ_NORMAL );
2193 $renderer = MediaWikiServices::getInstance()->getRevisionRenderer();
2194 $rendered = $renderer->getRenderedRevision(
2207 $pout = $rendered->getRevisionParserOutput();
2208 return $pout->getText();
2219 if ( $audience == self::FOR_PUBLIC && $this->
isDeleted( self::DELETED_COMMENT ) ) {
2221 } elseif ( $audience == self::FOR_THIS_USER
2245 if ( !$this->
exists() ) {
2252 if ( $this->descriptionTouched ===
null ) {
2254 'page_namespace' => $this->title->getNamespace(),
2255 'page_title' => $this->title->getDBkey()
2257 $touched = $this->repo->getReplicaDB()->selectField(
'page',
'page_touched', $cond, __METHOD__ );
2258 $this->descriptionTouched = $touched ?
wfTimestamp( TS_MW, $touched ) :
false;
2271 if ( $this->sha1 ==
'' && $this->fileExists ) {
2274 $this->sha1 = $this->repo->getFileSha1( $this->
getPath() );
2275 if ( !
wfReadOnly() && strval( $this->sha1 ) !=
'' ) {
2276 $dbw = $this->repo->getMasterDB();
2277 $dbw->update(
'image',
2278 [
'img_sha1' => $this->sha1 ],
2279 [
'img_name' => $this->
getName() ],
2297 return $this->extraDataLoaded
2331 if ( !$this->locked ) {
2332 $logger = LoggerFactory::getInstance(
'LocalFile' );
2334 $dbw = $this->repo->getMasterDB();
2335 $makesTransaction = !$dbw->trxLevel();
2336 $dbw->startAtomic( self::ATOMIC_SECTION_LOCK );
2341 if ( !$status->isGood() ) {
2342 $dbw->endAtomic( self::ATOMIC_SECTION_LOCK );
2343 $logger->warning(
"Failed to lock '{file}'", [
'file' => $this->name ] );
2349 $dbw->onTransactionResolution(
2350 function () use ( $logger ) {
2352 if ( !$status->isGood() ) {
2353 $logger->error(
"Failed to unlock '{file}'", [
'file' => $this->name ] );
2359 $this->lockedOwnTrx = $makesTransaction;
2376 if ( $this->locked ) {
2378 if ( !$this->locked ) {
2379 $dbw = $this->repo->getMasterDB();
2380 $dbw->endAtomic( self::ATOMIC_SECTION_LOCK );
2381 $this->lockedOwnTrx =
false;
2390 return $this->
getRepo()->newFatal(
'filereadonlyerror', $this->
getName(),
string $media_type
MEDIATYPE_xxx (bitmap, drawing, audio...)
getSha1()
Stable to override.
const ATOMIC_SECTION_LOCK
bool $fileExists
Does the file exist on disk? (loadFromXxx)
getPath()
Return the storage path to the file.
$wgUpdateCompatibleMetadata
If to automatically update the img_metadata field if the metadata field is outdated but compatible wi...
maybeUpgradeRow()
Upgrade a row if it needs it.
Helper class for file undeletion.
getMutableCacheKeys(WANObjectCache $cache)
getReadOnlyReason()
Get an explanatory message if this repo is read-only.
unprefixRow( $row, $prefix='img_')
static newFromText( $text, $defaultNamespace=NS_MAIN)
Create a new Title from text, such as what one would find in a link.
FileRepo LocalRepo ForeignAPIRepo bool $repo
Some member variables can be lazy-initialised using __get().
getArchiveThumbPath( $archiveName, $suffix=false)
Get the path of an archived file's thumbs, or a particular thumb if $suffix is specified.
deleteFile( $reason, User $user, $suppress=false)
Delete all versions of the file.
static newFatal( $message,... $parameters)
Factory function for fatal errors.
__construct( $title, $repo)
Do not call this except from inside a repo class.
unlock()
Decrement the lock reference count and end the atomic section if it reaches zero.
getTimestamp()
Stable to override.
userCan( $field, User $user)
Determine if the current user is allowed to view a particular field of this file, if it's marked as d...
isMultipage()
Returns 'true' if this file is a type which supports multiple pages, e.g.
getUser( $type='text')
Returns user who uploaded the file @stable to override.
loadFromDB( $flags=0)
Load file metadata from the DB Stable to override.
if(!isset( $args[0])) $lang
purgeThumbList( $dir, $files)
Delete a list of thumbnails visible at urls Stable to override.
getRel()
Get the path of the file relative to the public zone root.
Deferrable Update for closure/callback updates that should use auto-commit mode.
static getQueryInfo(array $options=[])
Return the tables, fields, and join conditions to be selected to create a new oldlocalfile object.
bool $missing
True if file is not present in file system.
Helper class for file deletion.
wfTimestamp( $outputtype=TS_UNIX, $ts=0)
Get a timestamp string in one of various formats.
File backend exception for checked exceptions (e.g.
upload( $src, $comment, $pageText, $flags=0, $props=false, $timestamp=false, $user=null, $tags=[], $createNullRevision=true, $revert=false)
getHashPath inherited
bool $upgraded
Whether the row was upgraded on load.
getDescriptionShortUrl()
Get short description URL for a file based on the page ID.
getCacheFields( $prefix='img_')
Returns the list of object properties that are included as-is in the cache.
getPrefixedText()
Get the prefixed title with spaces.
static addUpdate(DeferrableUpdate $update, $stage=self::POSTSEND)
Add an update to the pending update queue for execution at the appropriate time.
getHistory( $limit=null, $start=null, $end=null, $inc=true)
purgeDescription inherited
getMediaType()
Returns the type of the media in the file.
getUrl()
Return the URL of the file Stable to override.
bool $dataLoaded
Whether or not core data has been loaded from the database (loadFromXxx)
if(PHP_SAPI !='cli-server') if(!isset( $_SERVER['SCRIPT_FILENAME'])) $file
Item class for a filearchive table row.
static newFromTitle( $title, $repo, $unused=null)
Create a LocalFile from a title Do not call this except from inside a repo class.
wfReadOnly()
Check whether the wiki is in read-only mode.
static newFromName( $name, $validate='valid')
static newExtraneousContext(Title $title, $request=[])
Create a new extraneous context.
bool $upgrading
Whether the row was scheduled to upgrade on load.
getSize()
Returns the size of the image file, in bytes @stable to override.
purgeOldThumbnails( $archiveName)
Delete cached transformed files for an archived version only.
getThumbnails( $archiveName=false)
getTransformScript inherited
string $sha1
SHA-1 base 36 content hash.
static splitMime( $mime)
Split an internet media type into its two components; if not a two-part name, set the minor type to '...
string $minor_mime
Minor MIME type.
getArchiveRel( $suffix=false)
Get the path of an archived file relative to the public zone root Stable to override.
isDeleted( $field)
Is this file a "deleted" file in a private archive? STUB.
wfDebugLog( $logGroup, $text, $dest='all', array $context=[])
Send a line to a supplementary debug log file, if configured, or main debug log if not.
static newMigration()
Static constructor.
static newForBacklinks(Title $title, $table, $params=[])
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...
Generic operation result class Has warning/error list, boolean status and arbitrary value.
decodeRow( $row, $prefix='img_')
Decode a row from the database (either object or array) to an array with timestamps and MIME types de...
int $bits
Returned by getimagesize (loadFromXxx)
purgeThumbnails( $options=[])
Delete cached transformed files for the current version only.
hasSha1Storage()
Returns whether or not storage is SHA-1 based.
Implements some public methods and some protected utility functions which are required by multiple ch...
loadExtraFromDB()
Load lazy file metadata from the DB.
publishTo( $src, $dstRel, $flags=0, array $options=[])
Move or copy a file to a specified location.
string $url
The URL corresponding to one of the four basic zones.
static newFromRow( $row, $repo)
Create a LocalFile from a title Do not call this except from inside a repo class.
getDescriptionTouched()
Stable to override.
wfDeprecated( $function, $version=false, $component=false, $callerOffset=2)
Logs a warning that $function is deprecated.
purgeMetadataCache()
Refresh metadata in memcached, but don't touch thumbnails or CDN.
getThumbPath( $suffix=false)
Get the path of the thumbnail directory, or a particular file if $suffix is specified Stable to overr...
static newFromUserAndLang(User $user, Language $lang)
Get a ParserOptions object from a given user and language.
quickImport( $src, $dst, $options=null)
Import a file from the local file system into the repo.
static isStoragePath( $path)
Check if a given path is a "mwstore://" path.
static wrap( $sv)
Succinct helper method to wrap a StatusValue.
const CACHE_FIELD_MAX_LEN
loadFromRow( $row, $prefix='img_')
Load file metadata from a DB result row Stable to override.
__destruct()
Clean up any dangling locks.
int $deleted
Bitfield akin to rev_deleted.
$wgUploadThumbnailRenderMap
When defined, is an array of thumbnail widths to be rendered at upload time.
static getSha1Base36FromPath( $path)
Get a SHA-1 hash of a file in the local filesystem, in base-36 lower case encoding,...
IResultWrapper null $historyRes
Result of the query for the file's history (nextHistoryLine)
getDescriptionText(Language $lang=null)
Get the HTML text of the description page This is not used by ImagePage for local files,...
static factory(array $deltas)
MimeMagic helper wrapper.
prerenderThumbnails()
Prerenders a configurable set of thumbnails Stable to override.
static makeTitle( $ns, $title, $fragment='', $interwiki='')
Create a new Title from a namespace index and a DB key.
getCacheKey()
Get the memcached key for the main data for this file, or false if there is no access to the shared c...
static newFromAnyId( $userId, $userName, $actorId, $dbDomain=false)
Static factory method for creation from an ID, name, and/or actor ID.
string $mime
MIME type, determined by MimeAnalyzer::guessMimeType.
wfDebug( $text, $dest='all', array $context=[])
Sends a line to the debug log if enabled or, optionally, to a comment in output.
setProps( $info)
Set properties in this object to be equal to those given in the associative array $info.
getFileSha1( $virtualUrl)
Get the sha1 (base 36) of a file with a given virtual URL/storage path.
Class to represent a local file in the wiki's own database.
static makeContent( $text, Title $title=null, $modelId=null, $format=null)
Convenience function for creating a Content object from a given textual representation.
getMimeType()
Returns the MIME type of the file.
getDescription( $audience=self::FOR_PUBLIC, User $user=null)
Stable to override.
bool $extraDataLoaded
Whether or not lazy-loaded data has been loaded from the database.
Job for asynchronous rendering of thumbnails.
int $size
Size in bytes (loadFromXxx)
purgeDescription()
Purge the file description page, but don't go after pages using the file.
lock()
Start an atomic DB section and lock the image for update or increments a reference counter if the loc...
isOld()
Returns true if the image is an old version STUB.
@newable Stable to extend
assertTitleDefined()
Assert that $this->title is set to a Title.
static newGood( $value=null)
Factory function for good results.
int $historyLine
Number of line to return by nextHistoryLine() (constructor)
upgradeRow()
Fix assorted version-related problems with the image row by reloading it from the file Stable to over...
load( $flags=0)
Load file metadata from cache or DB, unless already loaded Stable to override.
Multi-datacenter aware caching interface.
static newFromKey( $sha1, $repo, $timestamp=false)
Create a LocalFile from a SHA-1 key Do not call this except from inside a repo class.
nextHistoryLine()
Returns the history of this file, line by line.
string $metadata
Handler-specific metadata.
getBitDepth()
@stable to override
getArchiveThumbUrl( $archiveName, $suffix=false)
Get the URL of the archived file's thumbs, or a particular thumb if $suffix is specified Stable to ov...
getName()
Return the name of this file.
getArchiveUrl( $suffix=false)
Get the URL of the archive directory, or a particular file if $suffix is specified Stable to override...
Class representing a non-directory file on the file system.
string $timestamp
Upload timestamp.
getBackend()
Get the file backend instance.
static getMain()
Get the RequestContext object associated with the main request.
string $descriptionTouched
TS_MW timestamp of the last change of the file description.
bool $locked
True if the image row is locked.
string $description
Description of current revision of the file.
getLazyCacheFields( $prefix='img_')
Returns the list of object properties that are included as-is in the cache, only when they're not too...
getTitle()
Return the associated title object.
static getQueryInfo(array $options=[])
Return the tables, fields, and join conditions to be selected to create a new localfile object.
getMetadata()
Get handler-specific metadata @stable to override.
deleteOldFile( $archiveName, $reason, User $user, $suppress=false)
Delete an old version of the file.
static singleton( $domain=false)
loadFromFile()
Load metadata from the file itself.
assertRepoDefined()
Assert that $this->repo is set to a valid FileRepo instance.
recordUpload3(string $oldver, string $comment, string $pageText, User $user, $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)
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.
if(count( $args)< 1) $job
resetHistory()
Reset the history pointer to the first element of the history Stable to override.
static makeParamBlob( $params)
Create a blob from a parameter array.
static queueRecursiveJobsForTable(Title $title, $table, $action='unknown', $userName='unknown')
Queue a RefreshLinks job for any table.
static isVirtualUrl( $url)
Determine if a string is an mwrepo:// URL.
loadExtraFieldsWithTimestamp( $dbr, $fname)
Helper class for file movement.
Class for creating new log entries and inserting them into the database.
Special handling for file pages.
string $name
The name of a file from its title object.
publish( $src, $flags=0, array $options=[])
Move or copy a file to its public location.
invalidateCache( $purgeTime=null)
Updates page_touched for this page; called from LinksUpdate.php.
getWidth( $page=1)
Return the width of the image @stable to override.
getThumbUrl( $suffix=false)
Get the URL of the thumbnail directory, or a particular file if $suffix is specified Stable to overri...
string $major_mime
Major MIME type.
isVectorized()
Return true if the file is vectorized.
getHandler()
Get a MediaHandler instance for this file.
getHeight( $page=1)
Return the height of the image @stable to override.
isMissing()
splitMime inherited
invalidateCache()
Purge the file object/metadata cache.
bool $lockedOwnTrx
True if the image row is locked with a lock initiated transaction.
The User object encapsulates all of the user-specific settings (user_id, name, rights,...
static addCallableUpdate( $callable, $stage=self::POSTSEND, $dbw=null)
Add an update to the pending update queue that invokes the specified callback when run.
getDescriptionUrl()
isMultipage inherited
move( $target)
getLinksTo inherited
getName()
Get the user name, or the IP of an anonymous user.
restore( $versions=[], $unsuppress=false)
Restore all or specified deleted revisions to the given file.
Internationalisation code See https://www.mediawiki.org/wiki/Special:MyLanguage/Localisation for more...
purgeEverything()
Purge metadata and all affected pages when the file is created, deleted, or majorly updated.
exists()
canRender inherited
getThumbnails()
Get all thumbnail names previously generated for this file STUB Overridden by LocalFile Stable to ove...
loadFromCache()
Try to load file metadata from memcached, falling back to the database.
purgeCache( $options=[])
Delete all previously generated thumbnails, refresh metadata in memcached and purge the CDN.
getVirtualUrl( $suffix=false)
Get the public zone virtual URL for a current version source file Stable to override.
static purgePatrolFooterCache( $articleID)
Purge the cache used to check if it is worth showing the patrol footer For example,...