MediaWiki  master
LocalFile.php
Go to the documentation of this file.
1 <?php
26 use Wikimedia\AtEase\AtEase;
30 
56 class LocalFile extends File {
57  const VERSION = 11; // cache version
58 
59  const CACHE_FIELD_MAX_LEN = 1000;
60 
62  protected $fileExists;
63 
65  protected $width;
66 
68  protected $height;
69 
71  protected $bits;
72 
74  protected $media_type;
75 
77  protected $mime;
78 
80  protected $size;
81 
83  protected $metadata;
84 
86  protected $sha1;
87 
89  protected $dataLoaded;
90 
92  protected $extraDataLoaded;
93 
95  protected $deleted;
96 
98  protected $repoClass = LocalRepo::class;
99 
101  private $historyLine;
102 
104  private $historyRes;
105 
107  private $major_mime;
108 
110  private $minor_mime;
111 
113  private $timestamp;
114 
116  private $user;
117 
119  private $description;
120 
123 
125  private $upgraded;
126 
128  private $upgrading;
129 
131  private $locked;
132 
134  private $lockedOwnTrx;
135 
137  private $missing;
138 
139  // @note: higher than IDBAccessObject constants
140  const LOAD_ALL = 16; // integer; load all the lazy fields too (like metadata)
141 
142  const ATOMIC_SECTION_LOCK = 'LocalFile::lockingTransaction';
143 
156  static function newFromTitle( $title, $repo, $unused = null ) {
157  return new static( $title, $repo );
158  }
159 
169  static function newFromRow( $row, $repo ) {
170  $title = Title::makeTitle( NS_FILE, $row->img_name );
171  $file = new static( $title, $repo );
172  $file->loadFromRow( $row );
173 
174  return $file;
175  }
176 
186  static function newFromKey( $sha1, $repo, $timestamp = false ) {
187  $dbr = $repo->getReplicaDB();
188 
189  $conds = [ 'img_sha1' => $sha1 ];
190  if ( $timestamp ) {
191  $conds['img_timestamp'] = $dbr->timestamp( $timestamp );
192  }
193 
194  $fileQuery = static::getQueryInfo();
195  $row = $dbr->selectRow(
196  $fileQuery['tables'], $fileQuery['fields'], $conds, __METHOD__, [], $fileQuery['joins']
197  );
198  if ( $row ) {
199  return static::newFromRow( $row, $repo );
200  } else {
201  return false;
202  }
203  }
204 
216  public static function getQueryInfo( array $options = [] ) {
217  $commentQuery = MediaWikiServices::getInstance()->getCommentStore()->getJoin( 'img_description' );
218  $actorQuery = ActorMigration::newMigration()->getJoin( 'img_user' );
219  $ret = [
220  'tables' => [ 'image' ] + $commentQuery['tables'] + $actorQuery['tables'],
221  'fields' => [
222  'img_name',
223  'img_size',
224  'img_width',
225  'img_height',
226  'img_metadata',
227  'img_bits',
228  'img_media_type',
229  'img_major_mime',
230  'img_minor_mime',
231  'img_timestamp',
232  'img_sha1',
233  ] + $commentQuery['fields'] + $actorQuery['fields'],
234  'joins' => $commentQuery['joins'] + $actorQuery['joins'],
235  ];
236 
237  if ( in_array( 'omit-nonlazy', $options, true ) ) {
238  // Internal use only for getting only the lazy fields
239  $ret['fields'] = [];
240  }
241  if ( !in_array( 'omit-lazy', $options, true ) ) {
242  // Note: Keep this in sync with self::getLazyCacheFields()
243  $ret['fields'][] = 'img_metadata';
244  }
245 
246  return $ret;
247  }
248 
254  function __construct( $title, $repo ) {
255  parent::__construct( $title, $repo );
256 
257  $this->metadata = '';
258  $this->historyLine = 0;
259  $this->historyRes = null;
260  $this->dataLoaded = false;
261  $this->extraDataLoaded = false;
262 
263  $this->assertRepoDefined();
264  $this->assertTitleDefined();
265  }
266 
272  function getCacheKey() {
273  return $this->repo->getSharedCacheKey( 'file', sha1( $this->getName() ) );
274  }
275 
282  return [ $this->getCacheKey() ];
283  }
284 
288  private function loadFromCache() {
289  $this->dataLoaded = false;
290  $this->extraDataLoaded = false;
291 
292  $key = $this->getCacheKey();
293  if ( !$key ) {
294  $this->loadFromDB( self::READ_NORMAL );
295 
296  return;
297  }
298 
299  $cache = MediaWikiServices::getInstance()->getMainWANObjectCache();
300  $cachedValues = $cache->getWithSetCallback(
301  $key,
302  $cache::TTL_WEEK,
303  function ( $oldValue, &$ttl, array &$setOpts ) use ( $cache ) {
304  $setOpts += Database::getCacheSetOptions( $this->repo->getReplicaDB() );
305 
306  $this->loadFromDB( self::READ_NORMAL );
307 
308  $fields = $this->getCacheFields( '' );
309  $cacheVal = [];
310  $cacheVal['fileExists'] = $this->fileExists;
311  if ( $this->fileExists ) {
312  foreach ( $fields as $field ) {
313  $cacheVal[$field] = $this->$field;
314  }
315  }
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;
319 
320  // Strip off excessive entries from the subset of fields that can become large.
321  // If the cache value gets to large it will not fit in memcached and nothing will
322  // get cached at all, causing master queries for any file access.
323  foreach ( $this->getLazyCacheFields( '' ) as $field ) {
324  if ( isset( $cacheVal[$field] )
325  && strlen( $cacheVal[$field] ) > 100 * 1024
326  ) {
327  unset( $cacheVal[$field] ); // don't let the value get too big
328  }
329  }
330 
331  if ( $this->fileExists ) {
332  $ttl = $cache->adaptiveTTL( wfTimestamp( TS_UNIX, $this->timestamp ), $ttl );
333  } else {
334  $ttl = $cache::TTL_DAY;
335  }
336 
337  return $cacheVal;
338  },
339  [ 'version' => self::VERSION ]
340  );
341 
342  $this->fileExists = $cachedValues['fileExists'];
343  if ( $this->fileExists ) {
344  $this->setProps( $cachedValues );
345  }
346 
347  $this->dataLoaded = true;
348  $this->extraDataLoaded = true;
349  foreach ( $this->getLazyCacheFields( '' ) as $field ) {
350  $this->extraDataLoaded = $this->extraDataLoaded && isset( $cachedValues[$field] );
351  }
352  }
353 
357  public function invalidateCache() {
358  $key = $this->getCacheKey();
359  if ( !$key ) {
360  return;
361  }
362 
363  $this->repo->getMasterDB()->onTransactionPreCommitOrIdle(
364  function () use ( $key ) {
365  MediaWikiServices::getInstance()->getMainWANObjectCache()->delete( $key );
366  },
367  __METHOD__
368  );
369  }
370 
374  function loadFromFile() {
375  $props = $this->repo->getFileProps( $this->getVirtualUrl() );
376  $this->setProps( $props );
377  }
378 
385  protected function getCacheFields( $prefix = 'img_' ) {
386  if ( $prefix !== '' ) {
387  throw new InvalidArgumentException(
388  __METHOD__ . ' with a non-empty prefix is no longer supported.'
389  );
390  }
391 
392  // See self::getQueryInfo() for the fetching of the data from the DB,
393  // self::loadFromRow() for the loading of the object from the DB row,
394  // and self::loadFromCache() for the caching, and self::setProps() for
395  // populating the object from an array of data.
396  return [ 'size', 'width', 'height', 'bits', 'media_type',
397  'major_mime', 'minor_mime', 'metadata', 'timestamp', 'sha1', 'description' ];
398  }
399 
407  protected function getLazyCacheFields( $prefix = 'img_' ) {
408  if ( $prefix !== '' ) {
409  throw new InvalidArgumentException(
410  __METHOD__ . ' with a non-empty prefix is no longer supported.'
411  );
412  }
413 
414  // Keep this in sync with the omit-lazy option in self::getQueryInfo().
415  return [ 'metadata' ];
416  }
417 
422  function loadFromDB( $flags = 0 ) {
423  $fname = static::class . '::' . __FUNCTION__;
424 
425  # Unconditionally set loaded=true, we don't want the accessors constantly rechecking
426  $this->dataLoaded = true;
427  $this->extraDataLoaded = true;
428 
429  $dbr = ( $flags & self::READ_LATEST )
430  ? $this->repo->getMasterDB()
431  : $this->repo->getReplicaDB();
432 
433  $fileQuery = static::getQueryInfo();
434  $row = $dbr->selectRow(
435  $fileQuery['tables'],
436  $fileQuery['fields'],
437  [ 'img_name' => $this->getName() ],
438  $fname,
439  [],
440  $fileQuery['joins']
441  );
442 
443  if ( $row ) {
444  $this->loadFromRow( $row );
445  } else {
446  $this->fileExists = false;
447  }
448  }
449 
454  protected function loadExtraFromDB() {
455  if ( !$this->title ) {
456  return; // Avoid hard failure when the file does not exist. T221812
457  }
458 
459  $fname = static::class . '::' . __FUNCTION__;
460 
461  # Unconditionally set loaded=true, we don't want the accessors constantly rechecking
462  $this->extraDataLoaded = true;
463 
464  $fieldMap = $this->loadExtraFieldsWithTimestamp( $this->repo->getReplicaDB(), $fname );
465  if ( !$fieldMap ) {
466  $fieldMap = $this->loadExtraFieldsWithTimestamp( $this->repo->getMasterDB(), $fname );
467  }
468 
469  if ( $fieldMap ) {
470  foreach ( $fieldMap as $name => $value ) {
471  $this->$name = $value;
472  }
473  } else {
474  throw new MWException( "Could not find data for image '{$this->getName()}'." );
475  }
476  }
477 
483  private function loadExtraFieldsWithTimestamp( $dbr, $fname ) {
484  $fieldMap = false;
485 
486  $fileQuery = self::getQueryInfo( [ 'omit-nonlazy' ] );
487  $row = $dbr->selectRow(
488  $fileQuery['tables'],
489  $fileQuery['fields'],
490  [
491  'img_name' => $this->getName(),
492  'img_timestamp' => $dbr->timestamp( $this->getTimestamp() ),
493  ],
494  $fname,
495  [],
496  $fileQuery['joins']
497  );
498  if ( $row ) {
499  $fieldMap = $this->unprefixRow( $row, 'img_' );
500  } else {
501  # File may have been uploaded over in the meantime; check the old versions
502  $fileQuery = OldLocalFile::getQueryInfo( [ 'omit-nonlazy' ] );
503  $row = $dbr->selectRow(
504  $fileQuery['tables'],
505  $fileQuery['fields'],
506  [
507  'oi_name' => $this->getName(),
508  'oi_timestamp' => $dbr->timestamp( $this->getTimestamp() ),
509  ],
510  $fname,
511  [],
512  $fileQuery['joins']
513  );
514  if ( $row ) {
515  $fieldMap = $this->unprefixRow( $row, 'oi_' );
516  }
517  }
518 
519  if ( isset( $fieldMap['metadata'] ) ) {
520  $fieldMap['metadata'] = $this->repo->getReplicaDB()->decodeBlob( $fieldMap['metadata'] );
521  }
522 
523  return $fieldMap;
524  }
525 
532  protected function unprefixRow( $row, $prefix = 'img_' ) {
533  $array = (array)$row;
534  $prefixLength = strlen( $prefix );
535 
536  // Sanity check prefix once
537  if ( substr( key( $array ), 0, $prefixLength ) !== $prefix ) {
538  throw new MWException( __METHOD__ . ': incorrect $prefix parameter' );
539  }
540 
541  $decoded = [];
542  foreach ( $array as $name => $value ) {
543  $decoded[substr( $name, $prefixLength )] = $value;
544  }
545 
546  return $decoded;
547  }
548 
557  function decodeRow( $row, $prefix = 'img_' ) {
558  $decoded = $this->unprefixRow( $row, $prefix );
559 
560  $decoded['description'] = MediaWikiServices::getInstance()->getCommentStore()
561  ->getComment( 'description', (object)$decoded )->text;
562 
563  $decoded['user'] = User::newFromAnyId(
564  $decoded['user'] ?? null,
565  $decoded['user_text'] ?? null,
566  $decoded['actor'] ?? null
567  );
568  unset( $decoded['user_text'], $decoded['actor'] );
569 
570  $decoded['timestamp'] = wfTimestamp( TS_MW, $decoded['timestamp'] );
571 
572  $decoded['metadata'] = $this->repo->getReplicaDB()->decodeBlob( $decoded['metadata'] );
573 
574  if ( empty( $decoded['major_mime'] ) ) {
575  $decoded['mime'] = 'unknown/unknown';
576  } else {
577  if ( !$decoded['minor_mime'] ) {
578  $decoded['minor_mime'] = 'unknown';
579  }
580  $decoded['mime'] = $decoded['major_mime'] . '/' . $decoded['minor_mime'];
581  }
582 
583  // Trim zero padding from char/binary field
584  $decoded['sha1'] = rtrim( $decoded['sha1'], "\0" );
585 
586  // Normalize some fields to integer type, per their database definition.
587  // Use unary + so that overflows will be upgraded to double instead of
588  // being trucated as with intval(). This is important to allow >2GB
589  // files on 32-bit systems.
590  foreach ( [ 'size', 'width', 'height', 'bits' ] as $field ) {
591  $decoded[$field] = +$decoded[$field];
592  }
593 
594  return $decoded;
595  }
596 
603  function loadFromRow( $row, $prefix = 'img_' ) {
604  $this->dataLoaded = true;
605  $this->extraDataLoaded = true;
606 
607  $array = $this->decodeRow( $row, $prefix );
608 
609  foreach ( $array as $name => $value ) {
610  $this->$name = $value;
611  }
612 
613  $this->fileExists = true;
614  }
615 
620  function load( $flags = 0 ) {
621  if ( !$this->dataLoaded ) {
622  if ( $flags & self::READ_LATEST ) {
623  $this->loadFromDB( $flags );
624  } else {
625  $this->loadFromCache();
626  }
627  }
628 
629  if ( ( $flags & self::LOAD_ALL ) && !$this->extraDataLoaded ) {
630  // @note: loads on name/timestamp to reduce race condition problems
631  $this->loadExtraFromDB();
632  }
633  }
634 
638  protected function maybeUpgradeRow() {
640 
641  if ( wfReadOnly() || $this->upgrading ) {
642  return;
643  }
644 
645  $upgrade = false;
646  if ( $this->media_type === null || $this->mime == 'image/svg' ) {
647  $upgrade = true;
648  } else {
649  $handler = $this->getHandler();
650  if ( $handler ) {
651  $validity = $handler->isMetadataValid( $this, $this->getMetadata() );
652  if ( $validity === MediaHandler::METADATA_BAD ) {
653  $upgrade = true;
654  } elseif ( $validity === MediaHandler::METADATA_COMPATIBLE ) {
655  $upgrade = $wgUpdateCompatibleMetadata;
656  }
657  }
658  }
659 
660  if ( $upgrade ) {
661  $this->upgrading = true;
662  // Defer updates unless in auto-commit CLI mode
664  $this->upgrading = false; // avoid duplicate updates
665  try {
666  $this->upgradeRow();
667  } catch ( LocalFileLockError $e ) {
668  // let the other process handle it (or do it next time)
669  }
670  } );
671  }
672  }
673 
677  function getUpgraded() {
678  return $this->upgraded;
679  }
680 
684  function upgradeRow() {
685  $this->lock();
686 
687  $this->loadFromFile();
688 
689  # Don't destroy file info of missing files
690  if ( !$this->fileExists ) {
691  $this->unlock();
692  wfDebug( __METHOD__ . ": file does not exist, aborting\n" );
693 
694  return;
695  }
696 
697  $dbw = $this->repo->getMasterDB();
698  list( $major, $minor ) = self::splitMime( $this->mime );
699 
700  if ( wfReadOnly() ) {
701  $this->unlock();
702 
703  return;
704  }
705  wfDebug( __METHOD__ . ': upgrading ' . $this->getName() . " to the current schema\n" );
706 
707  $dbw->update( 'image',
708  [
709  'img_size' => $this->size, // sanity
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,
718  ],
719  [ 'img_name' => $this->getName() ],
720  __METHOD__
721  );
722 
723  $this->invalidateCache();
724 
725  $this->unlock();
726  $this->upgraded = true; // avoid rework/retries
727  }
728 
739  function setProps( $info ) {
740  $this->dataLoaded = true;
741  $fields = $this->getCacheFields( '' );
742  $fields[] = 'fileExists';
743 
744  foreach ( $fields as $field ) {
745  if ( isset( $info[$field] ) ) {
746  $this->$field = $info[$field];
747  }
748  }
749 
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
755  );
756  }
757 
758  // Fix up mime fields
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 );
764  }
765  }
766 
781  function isMissing() {
782  if ( $this->missing === null ) {
783  $fileExists = $this->repo->fileExists( $this->getVirtualUrl() );
784  $this->missing = !$fileExists;
785  }
786 
787  return $this->missing;
788  }
789 
796  public function getWidth( $page = 1 ) {
797  $page = (int)$page;
798  if ( $page < 1 ) {
799  $page = 1;
800  }
801 
802  $this->load();
803 
804  if ( $this->isMultipage() ) {
805  $handler = $this->getHandler();
806  if ( !$handler ) {
807  return 0;
808  }
809  $dim = $handler->getPageDimensions( $this, $page );
810  if ( $dim ) {
811  return $dim['width'];
812  } else {
813  // For non-paged media, the false goes through an
814  // intval, turning failure into 0, so do same here.
815  return 0;
816  }
817  } else {
818  return $this->width;
819  }
820  }
821 
828  public function getHeight( $page = 1 ) {
829  $page = (int)$page;
830  if ( $page < 1 ) {
831  $page = 1;
832  }
833 
834  $this->load();
835 
836  if ( $this->isMultipage() ) {
837  $handler = $this->getHandler();
838  if ( !$handler ) {
839  return 0;
840  }
841  $dim = $handler->getPageDimensions( $this, $page );
842  if ( $dim ) {
843  return $dim['height'];
844  } else {
845  // For non-paged media, the false goes through an
846  // intval, turning failure into 0, so do same here.
847  return 0;
848  }
849  } else {
850  return $this->height;
851  }
852  }
853 
861  function getUser( $type = 'text' ) {
862  $this->load();
863 
864  if ( !$this->user ) {
865  // If the file does not exist, $this->user will be null, see T221812.
866  // Note: 'Unknown user' this is a reserved user name.
867  if ( $type === 'object' ) {
868  return User::newFromName( 'Unknown user', false );
869  } elseif ( $type === 'text' ) {
870  return 'Unknown user';
871  } elseif ( $type === 'id' ) {
872  return 0;
873  }
874  } else {
875  if ( $type === 'object' ) {
876  return $this->user;
877  } elseif ( $type === 'text' ) {
878  return $this->user->getName();
879  } elseif ( $type === 'id' ) {
880  return $this->user->getId();
881  }
882  }
883 
884  throw new MWException( "Unknown type '$type'." );
885  }
886 
894  public function getDescriptionShortUrl() {
895  if ( !$this->title ) {
896  return null; // Avoid hard failure when the file does not exist. T221812
897  }
898 
899  $pageId = $this->title->getArticleID();
900 
901  if ( $pageId ) {
902  $url = $this->repo->makeUrl( [ 'curid' => $pageId ] );
903  if ( $url !== false ) {
904  return $url;
905  }
906  }
907  return null;
908  }
909 
914  function getMetadata() {
915  $this->load( self::LOAD_ALL ); // large metadata is loaded in another step
916  return $this->metadata;
917  }
918 
922  function getBitDepth() {
923  $this->load();
924 
925  return (int)$this->bits;
926  }
927 
932  public function getSize() {
933  $this->load();
934 
935  return $this->size;
936  }
937 
942  function getMimeType() {
943  $this->load();
944 
945  return $this->mime;
946  }
947 
953  function getMediaType() {
954  $this->load();
955 
956  return $this->media_type;
957  }
958 
969  public function exists() {
970  $this->load();
971 
972  return $this->fileExists;
973  }
974 
990  function getThumbnails( $archiveName = false ) {
991  if ( $archiveName ) {
992  $dir = $this->getArchiveThumbPath( $archiveName );
993  } else {
994  $dir = $this->getThumbPath();
995  }
996 
997  $backend = $this->repo->getBackend();
998  $files = [ $dir ];
999  try {
1000  $iterator = $backend->getFileList( [ 'dir' => $dir ] );
1001  if ( $iterator !== null ) {
1002  foreach ( $iterator as $file ) {
1003  $files[] = $file;
1004  }
1005  }
1006  } catch ( FileBackendError $e ) {
1007  } // suppress (T56674)
1008 
1009  return $files;
1010  }
1011 
1015  function purgeMetadataCache() {
1016  $this->invalidateCache();
1017  }
1018 
1026  function purgeCache( $options = [] ) {
1027  // Refresh metadata cache
1028  $this->maybeUpgradeRow();
1029  $this->purgeMetadataCache();
1030 
1031  // Delete thumbnails
1032  $this->purgeThumbnails( $options );
1033 
1034  // Purge CDN cache for this file
1036  new CdnCacheUpdate( [ $this->getUrl() ] ),
1038  );
1039  }
1040 
1045  function purgeOldThumbnails( $archiveName ) {
1046  // Get a list of old thumbnails and URLs
1047  $files = $this->getThumbnails( $archiveName );
1048 
1049  // Purge any custom thumbnail caches
1050  Hooks::run( 'LocalFilePurgeThumbnails', [ $this, $archiveName ] );
1051 
1052  // Delete thumbnails
1053  $dir = array_shift( $files );
1054  $this->purgeThumbList( $dir, $files );
1055 
1056  // Purge the CDN
1057  $urls = [];
1058  foreach ( $files as $file ) {
1059  $urls[] = $this->getArchiveThumbUrl( $archiveName, $file );
1060  }
1062  }
1063 
1069  public function purgeThumbnails( $options = [] ) {
1070  $files = $this->getThumbnails();
1071  // Always purge all files from CDN regardless of handler filters
1072  $urls = [];
1073  foreach ( $files as $file ) {
1074  $urls[] = $this->getThumbUrl( $file );
1075  }
1076  array_shift( $urls ); // don't purge directory
1077 
1078  // Give media handler a chance to filter the file purge list
1079  if ( !empty( $options['forThumbRefresh'] ) ) {
1080  $handler = $this->getHandler();
1081  if ( $handler ) {
1082  $handler->filterThumbnailPurgeList( $files, $options );
1083  }
1084  }
1085 
1086  // Purge any custom thumbnail caches
1087  Hooks::run( 'LocalFilePurgeThumbnails', [ $this, false ] );
1088 
1089  // Delete thumbnails
1090  $dir = array_shift( $files );
1091  $this->purgeThumbList( $dir, $files );
1092 
1093  // Purge the CDN
1095  }
1096 
1102  public function prerenderThumbnails() {
1104 
1105  $jobs = [];
1106 
1107  $sizes = $wgUploadThumbnailRenderMap;
1108  rsort( $sizes );
1109 
1110  foreach ( $sizes as $size ) {
1111  if ( $this->isVectorized() || $this->getWidth() > $size ) {
1112  $jobs[] = new ThumbnailRenderJob(
1113  $this->getTitle(),
1114  [ 'transformParams' => [ 'width' => $size ] ]
1115  );
1116  }
1117  }
1118 
1119  if ( $jobs ) {
1120  JobQueueGroup::singleton()->lazyPush( $jobs );
1121  }
1122  }
1123 
1129  protected function purgeThumbList( $dir, $files ) {
1130  $fileListDebug = strtr(
1131  var_export( $files, true ),
1132  [ "\n" => '' ]
1133  );
1134  wfDebug( __METHOD__ . ": $fileListDebug\n" );
1135 
1136  $purgeList = [];
1137  foreach ( $files as $file ) {
1138  if ( $this->repo->supportsSha1URLs() ) {
1139  $reference = $this->getSha1();
1140  } else {
1141  $reference = $this->getName();
1142  }
1143 
1144  # Check that the reference (filename or sha1) is part of the thumb name
1145  # This is a basic sanity check to avoid erasing unrelated directories
1146  if ( strpos( $file, $reference ) !== false
1147  || strpos( $file, "-thumbnail" ) !== false // "short" thumb name
1148  ) {
1149  $purgeList[] = "{$dir}/{$file}";
1150  }
1151  }
1152 
1153  # Delete the thumbnails
1154  $this->repo->quickPurgeBatch( $purgeList );
1155  # Clear out the thumbnail directory if empty
1156  $this->repo->quickCleanDir( $dir );
1157  }
1158 
1169  function getHistory( $limit = null, $start = null, $end = null, $inc = true ) {
1170  if ( !$this->exists() ) {
1171  return []; // Avoid hard failure when the file does not exist. T221812
1172  }
1173 
1174  $dbr = $this->repo->getReplicaDB();
1175  $oldFileQuery = OldLocalFile::getQueryInfo();
1176 
1177  $tables = $oldFileQuery['tables'];
1178  $fields = $oldFileQuery['fields'];
1179  $join_conds = $oldFileQuery['joins'];
1180  $conds = $opts = [];
1181  $eq = $inc ? '=' : '';
1182  $conds[] = "oi_name = " . $dbr->addQuotes( $this->title->getDBkey() );
1183 
1184  if ( $start ) {
1185  $conds[] = "oi_timestamp <$eq " . $dbr->addQuotes( $dbr->timestamp( $start ) );
1186  }
1187 
1188  if ( $end ) {
1189  $conds[] = "oi_timestamp >$eq " . $dbr->addQuotes( $dbr->timestamp( $end ) );
1190  }
1191 
1192  if ( $limit ) {
1193  $opts['LIMIT'] = $limit;
1194  }
1195 
1196  // Search backwards for time > x queries
1197  $order = ( !$start && $end !== null ) ? 'ASC' : 'DESC';
1198  $opts['ORDER BY'] = "oi_timestamp $order";
1199  $opts['USE INDEX'] = [ 'oldimage' => 'oi_name_timestamp' ];
1200 
1201  // Avoid PHP 7.1 warning from passing $this by reference
1202  $localFile = $this;
1203  Hooks::run( 'LocalFile::getHistory', [ &$localFile, &$tables, &$fields,
1204  &$conds, &$opts, &$join_conds ] );
1205 
1206  $res = $dbr->select( $tables, $fields, $conds, __METHOD__, $opts, $join_conds );
1207  $r = [];
1208 
1209  foreach ( $res as $row ) {
1210  $r[] = $this->repo->newFileFromRow( $row );
1211  }
1212 
1213  if ( $order == 'ASC' ) {
1214  $r = array_reverse( $r ); // make sure it ends up descending
1215  }
1216 
1217  return $r;
1218  }
1219 
1229  public function nextHistoryLine() {
1230  if ( !$this->exists() ) {
1231  return false; // Avoid hard failure when the file does not exist. T221812
1232  }
1233 
1234  # Polymorphic function name to distinguish foreign and local fetches
1235  $fname = static::class . '::' . __FUNCTION__;
1236 
1237  $dbr = $this->repo->getReplicaDB();
1238 
1239  if ( $this->historyLine == 0 ) { // called for the first time, return line from cur
1240  $fileQuery = self::getQueryInfo();
1241  $this->historyRes = $dbr->select( $fileQuery['tables'],
1242  $fileQuery['fields'] + [
1243  'oi_archive_name' => $dbr->addQuotes( '' ),
1244  'oi_deleted' => 0,
1245  ],
1246  [ 'img_name' => $this->title->getDBkey() ],
1247  $fname,
1248  [],
1249  $fileQuery['joins']
1250  );
1251 
1252  if ( $dbr->numRows( $this->historyRes ) == 0 ) {
1253  $this->historyRes = null;
1254 
1255  return false;
1256  }
1257  } elseif ( $this->historyLine == 1 ) {
1258  $fileQuery = OldLocalFile::getQueryInfo();
1259  $this->historyRes = $dbr->select(
1260  $fileQuery['tables'],
1261  $fileQuery['fields'],
1262  [ 'oi_name' => $this->title->getDBkey() ],
1263  $fname,
1264  [ 'ORDER BY' => 'oi_timestamp DESC' ],
1265  $fileQuery['joins']
1266  );
1267  }
1268  $this->historyLine++;
1269 
1270  return $dbr->fetchObject( $this->historyRes );
1271  }
1272 
1276  public function resetHistory() {
1277  $this->historyLine = 0;
1278 
1279  if ( $this->historyRes !== null ) {
1280  $this->historyRes = null;
1281  }
1282  }
1283 
1317  function upload( $src, $comment, $pageText, $flags = 0, $props = false,
1318  $timestamp = false, $user = null, $tags = [],
1319  $createNullRevision = true, $revert = false
1320  ) {
1321  if ( $this->getRepo()->getReadOnlyReason() !== false ) {
1322  return $this->readOnlyFatalStatus();
1323  } elseif ( MediaWikiServices::getInstance()->getRevisionStore()->isReadOnly() ) {
1324  // Check this in advance to avoid writing to FileBackend and the file tables,
1325  // only to fail on insert the revision due to the text store being unavailable.
1326  return $this->readOnlyFatalStatus();
1327  }
1328 
1329  $srcPath = ( $src instanceof FSFile ) ? $src->getPath() : $src;
1330  if ( !$props ) {
1331  if ( FileRepo::isVirtualUrl( $srcPath )
1332  || FileBackend::isStoragePath( $srcPath )
1333  ) {
1334  $props = $this->repo->getFileProps( $srcPath );
1335  } else {
1336  $mwProps = new MWFileProps( MediaWikiServices::getInstance()->getMimeAnalyzer() );
1337  $props = $mwProps->getPropsFromPath( $srcPath, true );
1338  }
1339  }
1340 
1341  $options = [];
1342  $handler = MediaHandler::getHandler( $props['mime'] );
1343  if ( $handler ) {
1344  $metadata = AtEase::quietCall( 'unserialize', $props['metadata'] );
1345 
1346  if ( !is_array( $metadata ) ) {
1347  $metadata = [];
1348  }
1349 
1350  $options['headers'] = $handler->getContentHeaders( $metadata );
1351  } else {
1352  $options['headers'] = [];
1353  }
1354 
1355  // Trim spaces on user supplied text
1356  $comment = trim( $comment );
1357 
1358  $this->lock();
1359  $status = $this->publish( $src, $flags, $options );
1360 
1361  if ( $status->successCount >= 2 ) {
1362  // There will be a copy+(one of move,copy,store).
1363  // The first succeeding does not commit us to updating the DB
1364  // since it simply copied the current version to a timestamped file name.
1365  // It is only *preferable* to avoid leaving such files orphaned.
1366  // Once the second operation goes through, then the current version was
1367  // updated and we must therefore update the DB too.
1368  $oldver = $status->value;
1369  $uploadStatus = $this->recordUpload2(
1370  $oldver,
1371  $comment,
1372  $pageText,
1373  $props,
1374  $timestamp,
1375  $user,
1376  $tags,
1377  $createNullRevision,
1378  $revert
1379  );
1380  if ( !$uploadStatus->isOK() ) {
1381  if ( $uploadStatus->hasMessage( 'filenotfound' ) ) {
1382  // update filenotfound error with more specific path
1383  $status->fatal( 'filenotfound', $srcPath );
1384  } else {
1385  $status->merge( $uploadStatus );
1386  }
1387  }
1388  }
1389 
1390  $this->unlock();
1391  return $status;
1392  }
1393 
1406  function recordUpload( $oldver, $desc, $license = '', $copyStatus = '', $source = '',
1407  $watch = false, $timestamp = false, User $user = null ) {
1408  if ( !$user ) {
1409  global $wgUser;
1410  $user = $wgUser;
1411  }
1412 
1413  $pageText = SpecialUpload::getInitialPageText( $desc, $license, $copyStatus, $source );
1414 
1415  if ( !$this->recordUpload2( $oldver, $desc, $pageText, false, $timestamp, $user )->isOK() ) {
1416  return false;
1417  }
1418 
1419  if ( $watch ) {
1420  $user->addWatch( $this->getTitle() );
1421  }
1422 
1423  return true;
1424  }
1425 
1440  function recordUpload2(
1441  $oldver, $comment, $pageText, $props = false, $timestamp = false, $user = null, $tags = [],
1442  $createNullRevision = true, $revert = false
1443  ) {
1444  if ( $user === null ) {
1445  global $wgUser;
1446  $user = $wgUser;
1447  }
1448 
1449  $dbw = $this->repo->getMasterDB();
1450 
1451  # Imports or such might force a certain timestamp; otherwise we generate
1452  # it and can fudge it slightly to keep (name,timestamp) unique on re-upload.
1453  if ( $timestamp === false ) {
1454  $timestamp = $dbw->timestamp();
1455  $allowTimeKludge = true;
1456  } else {
1457  $allowTimeKludge = false;
1458  }
1459 
1460  $props = $props ?: $this->repo->getFileProps( $this->getVirtualUrl() );
1461  $props['description'] = $comment;
1462  $props['user'] = $user->getId();
1463  $props['user_text'] = $user->getName();
1464  $props['actor'] = $user->getActorId( $dbw );
1465  $props['timestamp'] = wfTimestamp( TS_MW, $timestamp ); // DB -> TS_MW
1466  $this->setProps( $props );
1467 
1468  # Fail now if the file isn't there
1469  if ( !$this->fileExists ) {
1470  wfDebug( __METHOD__ . ": File " . $this->getRel() . " went missing!\n" );
1471 
1472  return Status::newFatal( 'filenotfound', $this->getRel() );
1473  }
1474 
1475  $dbw->startAtomic( __METHOD__ );
1476 
1477  # Test to see if the row exists using INSERT IGNORE
1478  # This avoids race conditions by locking the row until the commit, and also
1479  # doesn't deadlock. SELECT FOR UPDATE causes a deadlock for every race condition.
1480  $commentStore = MediaWikiServices::getInstance()->getCommentStore();
1481  $commentFields = $commentStore->insert( $dbw, 'img_description', $comment );
1482  $actorMigration = ActorMigration::newMigration();
1483  $actorFields = $actorMigration->getInsertValues( $dbw, 'img_user', $user );
1484  $dbw->insert( 'image',
1485  [
1486  'img_name' => $this->getName(),
1487  'img_size' => $this->size,
1488  'img_width' => intval( $this->width ),
1489  'img_height' => intval( $this->height ),
1490  'img_bits' => $this->bits,
1491  'img_media_type' => $this->media_type,
1492  'img_major_mime' => $this->major_mime,
1493  'img_minor_mime' => $this->minor_mime,
1494  'img_timestamp' => $timestamp,
1495  'img_metadata' => $dbw->encodeBlob( $this->metadata ),
1496  'img_sha1' => $this->sha1
1497  ] + $commentFields + $actorFields,
1498  __METHOD__,
1499  [ 'IGNORE' ]
1500  );
1501  $reupload = ( $dbw->affectedRows() == 0 );
1502 
1503  if ( $reupload ) {
1504  $row = $dbw->selectRow(
1505  'image',
1506  [ 'img_timestamp', 'img_sha1' ],
1507  [ 'img_name' => $this->getName() ],
1508  __METHOD__,
1509  [ 'LOCK IN SHARE MODE' ]
1510  );
1511 
1512  if ( $row && $row->img_sha1 === $this->sha1 ) {
1513  $dbw->endAtomic( __METHOD__ );
1514  wfDebug( __METHOD__ . ": File " . $this->getRel() . " already exists!\n" );
1515  $title = Title::newFromText( $this->getName(), NS_FILE );
1516  return Status::newFatal( 'fileexists-no-change', $title->getPrefixedText() );
1517  }
1518 
1519  if ( $allowTimeKludge ) {
1520  # Use LOCK IN SHARE MODE to ignore any transaction snapshotting
1521  $lUnixtime = $row ? wfTimestamp( TS_UNIX, $row->img_timestamp ) : false;
1522  # Avoid a timestamp that is not newer than the last version
1523  # TODO: the image/oldimage tables should be like page/revision with an ID field
1524  if ( $lUnixtime && wfTimestamp( TS_UNIX, $timestamp ) <= $lUnixtime ) {
1525  sleep( 1 ); // fast enough re-uploads would go far in the future otherwise
1526  $timestamp = $dbw->timestamp( $lUnixtime + 1 );
1527  $this->timestamp = wfTimestamp( TS_MW, $timestamp ); // DB -> TS_MW
1528  }
1529  }
1530 
1531  $tables = [ 'image' ];
1532  $fields = [
1533  'oi_name' => 'img_name',
1534  'oi_archive_name' => $dbw->addQuotes( $oldver ),
1535  'oi_size' => 'img_size',
1536  'oi_width' => 'img_width',
1537  'oi_height' => 'img_height',
1538  'oi_bits' => 'img_bits',
1539  'oi_description_id' => 'img_description_id',
1540  'oi_timestamp' => 'img_timestamp',
1541  'oi_metadata' => 'img_metadata',
1542  'oi_media_type' => 'img_media_type',
1543  'oi_major_mime' => 'img_major_mime',
1544  'oi_minor_mime' => 'img_minor_mime',
1545  'oi_sha1' => 'img_sha1',
1546  'oi_actor' => 'img_actor',
1547  ];
1548  $joins = [];
1549 
1550  # (T36993) Note: $oldver can be empty here, if the previous
1551  # version of the file was broken. Allow registration of the new
1552  # version to continue anyway, because that's better than having
1553  # an image that's not fixable by user operations.
1554  # Collision, this is an update of a file
1555  # Insert previous contents into oldimage
1556  $dbw->insertSelect( 'oldimage', $tables, $fields,
1557  [ 'img_name' => $this->getName() ], __METHOD__, [], [], $joins );
1558 
1559  # Update the current image row
1560  $dbw->update( 'image',
1561  [
1562  'img_size' => $this->size,
1563  'img_width' => intval( $this->width ),
1564  'img_height' => intval( $this->height ),
1565  'img_bits' => $this->bits,
1566  'img_media_type' => $this->media_type,
1567  'img_major_mime' => $this->major_mime,
1568  'img_minor_mime' => $this->minor_mime,
1569  'img_timestamp' => $timestamp,
1570  'img_metadata' => $dbw->encodeBlob( $this->metadata ),
1571  'img_sha1' => $this->sha1
1572  ] + $commentFields + $actorFields,
1573  [ 'img_name' => $this->getName() ],
1574  __METHOD__
1575  );
1576  }
1577 
1578  $descTitle = $this->getTitle();
1579  $descId = $descTitle->getArticleID();
1580  $wikiPage = new WikiFilePage( $descTitle );
1581  $wikiPage->setFile( $this );
1582 
1583  // Determine log action. If reupload is done by reverting, use a special log_action.
1584  if ( $revert === true ) {
1585  $logAction = 'revert';
1586  } elseif ( $reupload === true ) {
1587  $logAction = 'overwrite';
1588  } else {
1589  $logAction = 'upload';
1590  }
1591  // Add the log entry...
1592  $logEntry = new ManualLogEntry( 'upload', $logAction );
1593  $logEntry->setTimestamp( $this->timestamp );
1594  $logEntry->setPerformer( $user );
1595  $logEntry->setComment( $comment );
1596  $logEntry->setTarget( $descTitle );
1597  // Allow people using the api to associate log entries with the upload.
1598  // Log has a timestamp, but sometimes different from upload timestamp.
1599  $logEntry->setParameters(
1600  [
1601  'img_sha1' => $this->sha1,
1602  'img_timestamp' => $timestamp,
1603  ]
1604  );
1605  // Note we keep $logId around since during new image
1606  // creation, page doesn't exist yet, so log_page = 0
1607  // but we want it to point to the page we're making,
1608  // so we later modify the log entry.
1609  // For a similar reason, we avoid making an RC entry
1610  // now and wait until the page exists.
1611  $logId = $logEntry->insert();
1612 
1613  if ( $descTitle->exists() ) {
1614  // Use own context to get the action text in content language
1615  $formatter = LogFormatter::newFromEntry( $logEntry );
1616  $formatter->setContext( RequestContext::newExtraneousContext( $descTitle ) );
1617  $editSummary = $formatter->getPlainActionText();
1618 
1619  $nullRevision = $createNullRevision === false ? null : Revision::newNullRevision(
1620  $dbw,
1621  $descId,
1622  $editSummary,
1623  false,
1624  $user
1625  );
1626  if ( $nullRevision ) {
1627  $nullRevision->insertOn( $dbw );
1628  Hooks::run(
1629  'NewRevisionFromEditComplete',
1630  [ $wikiPage, $nullRevision, $nullRevision->getParentId(), $user ]
1631  );
1632  $wikiPage->updateRevisionOn( $dbw, $nullRevision );
1633  // Associate null revision id
1634  $logEntry->setAssociatedRevId( $nullRevision->getId() );
1635  }
1636 
1637  $newPageContent = null;
1638  } else {
1639  // Make the description page and RC log entry post-commit
1640  $newPageContent = ContentHandler::makeContent( $pageText, $descTitle );
1641  }
1642 
1643  # Defer purges, page creation, and link updates in case they error out.
1644  # The most important thing is that files and the DB registry stay synced.
1645  $dbw->endAtomic( __METHOD__ );
1646  $fname = __METHOD__;
1647 
1648  # Do some cache purges after final commit so that:
1649  # a) Changes are more likely to be seen post-purge
1650  # b) They won't cause rollback of the log publish/update above
1652  new AutoCommitUpdate(
1653  $dbw,
1654  __METHOD__,
1656  function () use (
1657  $reupload, $wikiPage, $newPageContent, $comment, $user,
1658  $logEntry, $logId, $descId, $tags, $fname
1659  ) {
1660  # Update memcache after the commit
1661  $this->invalidateCache();
1662 
1663  $updateLogPage = false;
1664  if ( $newPageContent ) {
1665  # New file page; create the description page.
1666  # There's already a log entry, so don't make a second RC entry
1667  # CDN and file cache for the description page are purged by doEditContent.
1668  $status = $wikiPage->doEditContent(
1669  $newPageContent,
1670  $comment,
1672  false,
1673  $user
1674  );
1675 
1676  if ( isset( $status->value['revision'] ) ) {
1678  $rev = $status->value['revision'];
1679  // Associate new page revision id
1680  $logEntry->setAssociatedRevId( $rev->getId() );
1681  }
1682  // This relies on the resetArticleID() call in WikiPage::insertOn(),
1683  // which is triggered on $descTitle by doEditContent() above.
1684  if ( isset( $status->value['revision'] ) ) {
1686  $rev = $status->value['revision'];
1687  $updateLogPage = $rev->getPage();
1688  }
1689  } else {
1690  # Existing file page: invalidate description page cache
1691  $wikiPage->getTitle()->invalidateCache();
1692  $wikiPage->getTitle()->purgeSquid();
1693  # Allow the new file version to be patrolled from the page footer
1695  }
1696 
1697  # Update associated rev id. This should be done by $logEntry->insert() earlier,
1698  # but setAssociatedRevId() wasn't called at that point yet...
1699  $logParams = $logEntry->getParameters();
1700  $logParams['associated_rev_id'] = $logEntry->getAssociatedRevId();
1701  $update = [ 'log_params' => LogEntryBase::makeParamBlob( $logParams ) ];
1702  if ( $updateLogPage ) {
1703  # Also log page, in case where we just created it above
1704  $update['log_page'] = $updateLogPage;
1705  }
1706  $this->getRepo()->getMasterDB()->update(
1707  'logging',
1708  $update,
1709  [ 'log_id' => $logId ],
1710  $fname
1711  );
1712  $this->getRepo()->getMasterDB()->insert(
1713  'log_search',
1714  [
1715  'ls_field' => 'associated_rev_id',
1716  'ls_value' => (string)$logEntry->getAssociatedRevId(),
1717  'ls_log_id' => $logId,
1718  ],
1719  $fname
1720  );
1721 
1722  # Add change tags, if any
1723  if ( $tags ) {
1724  $logEntry->addTags( $tags );
1725  }
1726 
1727  # Uploads can be patrolled
1728  $logEntry->setIsPatrollable( true );
1729 
1730  # Now that the log entry is up-to-date, make an RC entry.
1731  $logEntry->publish( $logId );
1732 
1733  # Run hook for other updates (typically more cache purging)
1734  Hooks::run( 'FileUpload', [ $this, $reupload, !$newPageContent ] );
1735 
1736  if ( $reupload ) {
1737  # Delete old thumbnails
1738  $this->purgeThumbnails();
1739  # Remove the old file from the CDN cache
1741  new CdnCacheUpdate( [ $this->getUrl() ] ),
1743  );
1744  } else {
1745  # Update backlink pages pointing to this title if created
1747  $this->getTitle(),
1748  'imagelinks',
1749  'upload-image',
1750  $user->getName()
1751  );
1752  }
1753 
1754  $this->prerenderThumbnails();
1755  }
1756  ),
1758  );
1759 
1760  if ( !$reupload ) {
1761  # This is a new file, so update the image count
1762  DeferredUpdates::addUpdate( SiteStatsUpdate::factory( [ 'images' => 1 ] ) );
1763  }
1764 
1765  # Invalidate cache for all pages using this file
1767  new HTMLCacheUpdate( $this->getTitle(), 'imagelinks', 'file-upload' )
1768  );
1769 
1770  return Status::newGood();
1771  }
1772 
1788  function publish( $src, $flags = 0, array $options = [] ) {
1789  return $this->publishTo( $src, $this->getRel(), $flags, $options );
1790  }
1791 
1807  function publishTo( $src, $dstRel, $flags = 0, array $options = [] ) {
1808  $srcPath = ( $src instanceof FSFile ) ? $src->getPath() : $src;
1809 
1810  $repo = $this->getRepo();
1811  if ( $repo->getReadOnlyReason() !== false ) {
1812  return $this->readOnlyFatalStatus();
1813  }
1814 
1815  $this->lock();
1816 
1817  if ( $this->isOld() ) {
1818  $archiveRel = $dstRel;
1819  $archiveName = basename( $archiveRel );
1820  } else {
1821  $archiveName = wfTimestamp( TS_MW ) . '!' . $this->getName();
1822  $archiveRel = $this->getArchiveRel( $archiveName );
1823  }
1824 
1825  if ( $repo->hasSha1Storage() ) {
1826  $sha1 = FileRepo::isVirtualUrl( $srcPath )
1827  ? $repo->getFileSha1( $srcPath )
1828  : FSFile::getSha1Base36FromPath( $srcPath );
1830  $wrapperBackend = $repo->getBackend();
1831  '@phan-var FileBackendDBRepoWrapper $wrapperBackend';
1832  $dst = $wrapperBackend->getPathForSHA1( $sha1 );
1833  $status = $repo->quickImport( $src, $dst );
1834  if ( $flags & File::DELETE_SOURCE ) {
1835  unlink( $srcPath );
1836  }
1837 
1838  if ( $this->exists() ) {
1839  $status->value = $archiveName;
1840  }
1841  } else {
1842  $flags = $flags & File::DELETE_SOURCE ? LocalRepo::DELETE_SOURCE : 0;
1843  $status = $repo->publish( $srcPath, $dstRel, $archiveRel, $flags, $options );
1844 
1845  if ( $status->value == 'new' ) {
1846  $status->value = '';
1847  } else {
1848  $status->value = $archiveName;
1849  }
1850  }
1851 
1852  $this->unlock();
1853  return $status;
1854  }
1855 
1873  function move( $target ) {
1874  $localRepo = MediaWikiServices::getInstance()->getRepoGroup()->getLocalRepo();
1875  if ( $this->getRepo()->getReadOnlyReason() !== false ) {
1876  return $this->readOnlyFatalStatus();
1877  }
1878 
1879  wfDebugLog( 'imagemove', "Got request to move {$this->name} to " . $target->getText() );
1880  $batch = new LocalFileMoveBatch( $this, $target );
1881 
1882  $this->lock();
1883  $batch->addCurrent();
1884  $archiveNames = $batch->addOlds();
1885  $status = $batch->execute();
1886  $this->unlock();
1887 
1888  wfDebugLog( 'imagemove', "Finished moving {$this->name}" );
1889 
1890  // Purge the source and target files outside the transaction...
1891  $oldTitleFile = $localRepo->newFile( $this->title );
1892  $newTitleFile = $localRepo->newFile( $target );
1894  new AutoCommitUpdate(
1895  $this->getRepo()->getMasterDB(),
1896  __METHOD__,
1897  function () use ( $oldTitleFile, $newTitleFile, $archiveNames ) {
1898  $oldTitleFile->purgeEverything();
1899  foreach ( $archiveNames as $archiveName ) {
1901  '@phan-var OldLocalFile $oldTitleFile';
1902  $oldTitleFile->purgeOldThumbnails( $archiveName );
1903  }
1904  $newTitleFile->purgeEverything();
1905  }
1906  ),
1908  );
1909 
1910  if ( $status->isOK() ) {
1911  // Now switch the object
1912  $this->title = $target;
1913  // Force regeneration of the name and hashpath
1914  $this->name = null;
1915  $this->hashPath = null;
1916  }
1917 
1918  return $status;
1919  }
1920 
1934  function delete( $reason, $suppress = false, $user = null ) {
1935  if ( $this->getRepo()->getReadOnlyReason() !== false ) {
1936  return $this->readOnlyFatalStatus();
1937  }
1938 
1939  $batch = new LocalFileDeleteBatch( $this, $reason, $suppress, $user );
1940 
1941  $this->lock();
1942  $batch->addCurrent();
1943  // Get old version relative paths
1944  $archiveNames = $batch->addOlds();
1945  $status = $batch->execute();
1946  $this->unlock();
1947 
1948  if ( $status->isOK() ) {
1949  DeferredUpdates::addUpdate( SiteStatsUpdate::factory( [ 'images' => -1 ] ) );
1950  }
1951 
1952  // To avoid slow purges in the transaction, move them outside...
1954  new AutoCommitUpdate(
1955  $this->getRepo()->getMasterDB(),
1956  __METHOD__,
1957  function () use ( $archiveNames ) {
1958  $this->purgeEverything();
1959  foreach ( $archiveNames as $archiveName ) {
1960  $this->purgeOldThumbnails( $archiveName );
1961  }
1962  }
1963  ),
1965  );
1966 
1967  // Purge the CDN
1968  $purgeUrls = [];
1969  foreach ( $archiveNames as $archiveName ) {
1970  $purgeUrls[] = $this->getArchiveUrl( $archiveName );
1971  }
1973 
1974  return $status;
1975  }
1976 
1992  function deleteOld( $archiveName, $reason, $suppress = false, $user = null ) {
1993  if ( $this->getRepo()->getReadOnlyReason() !== false ) {
1994  return $this->readOnlyFatalStatus();
1995  }
1996 
1997  $batch = new LocalFileDeleteBatch( $this, $reason, $suppress, $user );
1998 
1999  $this->lock();
2000  $batch->addOld( $archiveName );
2001  $status = $batch->execute();
2002  $this->unlock();
2003 
2004  $this->purgeOldThumbnails( $archiveName );
2005  if ( $status->isOK() ) {
2006  $this->purgeDescription();
2007  }
2008 
2010  new CdnCacheUpdate( [ $this->getArchiveUrl( $archiveName ) ] ),
2012  );
2013 
2014  return $status;
2015  }
2016 
2028  function restore( $versions = [], $unsuppress = false ) {
2029  if ( $this->getRepo()->getReadOnlyReason() !== false ) {
2030  return $this->readOnlyFatalStatus();
2031  }
2032 
2033  $batch = new LocalFileRestoreBatch( $this, $unsuppress );
2034 
2035  $this->lock();
2036  if ( !$versions ) {
2037  $batch->addAll();
2038  } else {
2039  $batch->addIds( $versions );
2040  }
2041  $status = $batch->execute();
2042  if ( $status->isGood() ) {
2043  $cleanupStatus = $batch->cleanup();
2044  $cleanupStatus->successCount = 0;
2045  $cleanupStatus->failCount = 0;
2046  $status->merge( $cleanupStatus );
2047  }
2048 
2049  $this->unlock();
2050  return $status;
2051  }
2052 
2062  function getDescriptionUrl() {
2063  if ( !$this->title ) {
2064  return false; // Avoid hard failure when the file does not exist. T221812
2065  }
2066 
2067  return $this->title->getLocalURL();
2068  }
2069 
2078  function getDescriptionText( Language $lang = null ) {
2079  if ( !$this->title ) {
2080  return false; // Avoid hard failure when the file does not exist. T221812
2081  }
2082 
2083  $store = MediaWikiServices::getInstance()->getRevisionStore();
2084  $revision = $store->getRevisionByTitle( $this->title, 0, Revision::READ_NORMAL );
2085  if ( !$revision ) {
2086  return false;
2087  }
2088 
2089  $renderer = MediaWikiServices::getInstance()->getRevisionRenderer();
2090  $rendered = $renderer->getRenderedRevision( $revision, new ParserOptions( null, $lang ) );
2091 
2092  if ( !$rendered ) {
2093  // audience check failed
2094  return false;
2095  }
2096 
2097  $pout = $rendered->getRevisionParserOutput();
2098  return $pout->getText();
2099  }
2100 
2106  function getDescription( $audience = self::FOR_PUBLIC, User $user = null ) {
2107  $this->load();
2108  if ( $audience == self::FOR_PUBLIC && $this->isDeleted( self::DELETED_COMMENT ) ) {
2109  return '';
2110  } elseif ( $audience == self::FOR_THIS_USER
2111  && !$this->userCan( self::DELETED_COMMENT, $user )
2112  ) {
2113  return '';
2114  } else {
2115  return $this->description;
2116  }
2117  }
2118 
2122  function getTimestamp() {
2123  $this->load();
2124 
2125  return $this->timestamp;
2126  }
2127 
2131  public function getDescriptionTouched() {
2132  if ( !$this->exists() ) {
2133  return false; // Avoid hard failure when the file does not exist. T221812
2134  }
2135 
2136  // The DB lookup might return false, e.g. if the file was just deleted, or the shared DB repo
2137  // itself gets it from elsewhere. To avoid repeating the DB lookups in such a case, we
2138  // need to differentiate between null (uninitialized) and false (failed to load).
2139  if ( $this->descriptionTouched === null ) {
2140  $cond = [
2141  'page_namespace' => $this->title->getNamespace(),
2142  'page_title' => $this->title->getDBkey()
2143  ];
2144  $touched = $this->repo->getReplicaDB()->selectField( 'page', 'page_touched', $cond, __METHOD__ );
2145  $this->descriptionTouched = $touched ? wfTimestamp( TS_MW, $touched ) : false;
2146  }
2147 
2149  }
2150 
2154  function getSha1() {
2155  $this->load();
2156  // Initialise now if necessary
2157  if ( $this->sha1 == '' && $this->fileExists ) {
2158  $this->lock();
2159 
2160  $this->sha1 = $this->repo->getFileSha1( $this->getPath() );
2161  if ( !wfReadOnly() && strval( $this->sha1 ) != '' ) {
2162  $dbw = $this->repo->getMasterDB();
2163  $dbw->update( 'image',
2164  [ 'img_sha1' => $this->sha1 ],
2165  [ 'img_name' => $this->getName() ],
2166  __METHOD__ );
2167  $this->invalidateCache();
2168  }
2169 
2170  $this->unlock();
2171  }
2172 
2173  return $this->sha1;
2174  }
2175 
2179  function isCacheable() {
2180  $this->load();
2181 
2182  // If extra data (metadata) was not loaded then it must have been large
2183  return $this->extraDataLoaded
2184  && strlen( serialize( $this->metadata ) ) <= self::CACHE_FIELD_MAX_LEN;
2185  }
2186 
2191  public function acquireFileLock() {
2192  return Status::wrap( $this->getRepo()->getBackend()->lockFiles(
2193  [ $this->getPath() ], LockManager::LOCK_EX, 10
2194  ) );
2195  }
2196 
2201  public function releaseFileLock() {
2202  return Status::wrap( $this->getRepo()->getBackend()->unlockFiles(
2203  [ $this->getPath() ], LockManager::LOCK_EX
2204  ) );
2205  }
2206 
2216  public function lock() {
2217  if ( !$this->locked ) {
2218  $logger = LoggerFactory::getInstance( 'LocalFile' );
2219 
2220  $dbw = $this->repo->getMasterDB();
2221  $makesTransaction = !$dbw->trxLevel();
2222  $dbw->startAtomic( self::ATOMIC_SECTION_LOCK );
2223  // T56736: use simple lock to handle when the file does not exist.
2224  // SELECT FOR UPDATE prevents changes, not other SELECTs with FOR UPDATE.
2225  // Also, that would cause contention on INSERT of similarly named rows.
2226  $status = $this->acquireFileLock(); // represents all versions of the file
2227  if ( !$status->isGood() ) {
2228  $dbw->endAtomic( self::ATOMIC_SECTION_LOCK );
2229  $logger->warning( "Failed to lock '{file}'", [ 'file' => $this->name ] );
2230 
2231  throw new LocalFileLockError( $status );
2232  }
2233  // Release the lock *after* commit to avoid row-level contention.
2234  // Make sure it triggers on rollback() as well as commit() (T132921).
2235  $dbw->onTransactionResolution(
2236  function () use ( $logger ) {
2237  $status = $this->releaseFileLock();
2238  if ( !$status->isGood() ) {
2239  $logger->error( "Failed to unlock '{file}'", [ 'file' => $this->name ] );
2240  }
2241  },
2242  __METHOD__
2243  );
2244  // Callers might care if the SELECT snapshot is safely fresh
2245  $this->lockedOwnTrx = $makesTransaction;
2246  }
2247 
2248  $this->locked++;
2249 
2250  return $this->lockedOwnTrx;
2251  }
2252 
2261  public function unlock() {
2262  if ( $this->locked ) {
2263  --$this->locked;
2264  if ( !$this->locked ) {
2265  $dbw = $this->repo->getMasterDB();
2266  $dbw->endAtomic( self::ATOMIC_SECTION_LOCK );
2267  $this->lockedOwnTrx = false;
2268  }
2269  }
2270  }
2271 
2275  protected function readOnlyFatalStatus() {
2276  return $this->getRepo()->newFatal( 'filereadonlyerror', $this->getName(),
2277  $this->getRepo()->getName(), $this->getRepo()->getReadOnlyReason() );
2278  }
2279 
2283  public function __destruct() {
2284  $this->unlock();
2285  }
2286 }
LocalFile\$media_type
string $media_type
MEDIATYPE_xxx (bitmap, drawing, audio...)
Definition: LocalFile.php:74
LocalFile\getSha1
getSha1()
Definition: LocalFile.php:2154
ParserOptions
Set options of the Parser.
Definition: ParserOptions.php:42
LocalFile\ATOMIC_SECTION_LOCK
const ATOMIC_SECTION_LOCK
Definition: LocalFile.php:142
LocalFile\$fileExists
bool $fileExists
Does the file exist on disk? (loadFromXxx)
Definition: LocalFile.php:62
File\getPath
getPath()
Return the storage path to the file.
Definition: File.php:427
$wgUpdateCompatibleMetadata
$wgUpdateCompatibleMetadata
If to automatically update the img_metadata field if the metadata field is outdated but compatible wi...
Definition: DefaultSettings.php:811
SpecialUpload\getInitialPageText
static getInitialPageText( $comment='', $license='', $copyStatus='', $source='', Config $config=null)
Get the initial image page text based on a comment and optional file status information.
Definition: SpecialUpload.php:616
LocalFile\maybeUpgradeRow
maybeUpgradeRow()
Upgrade a row if it needs it.
Definition: LocalFile.php:638
Wikimedia\Rdbms\Database
Relational database abstraction object.
Definition: Database.php:49
LocalFileRestoreBatch
Helper class for file undeletion.
Definition: LocalFileRestoreBatch.php:30
LocalFile\getMutableCacheKeys
getMutableCacheKeys(WANObjectCache $cache)
Definition: LocalFile.php:281
FileRepo\getReadOnlyReason
getReadOnlyReason()
Get an explanatory message if this repo is read-only.
Definition: FileRepo.php:252
LocalFile\unprefixRow
unprefixRow( $row, $prefix='img_')
Definition: LocalFile.php:532
Title\newFromText
static newFromText( $text, $defaultNamespace=NS_MAIN)
Create a new Title from text, such as what one would find in a link.
Definition: Title.php:317
File\$repo
FileRepo LocalRepo ForeignAPIRepo bool $repo
Some member variables can be lazy-initialised using __get().
Definition: File.php:106
LocalFile\$width
int $width
Image width.
Definition: LocalFile.php:65
File\getArchiveThumbPath
getArchiveThumbPath( $archiveName, $suffix=false)
Get the path of an archived file's thumbs, or a particular thumb if $suffix is specified.
Definition: File.php:1665
StatusValue\newFatal
static newFatal( $message,... $parameters)
Factory function for fatal errors.
Definition: StatusValue.php:69
LocalFile\__construct
__construct( $title, $repo)
Do not call this except from inside a repo class.
Definition: LocalFile.php:254
LocalFile\unlock
unlock()
Decrement the lock reference count and end the atomic section if it reaches zero.
Definition: LocalFile.php:2261
User\getId
getId()
Get the user's ID.
Definition: User.php:2255
LocalFile\getTimestamp
getTimestamp()
Definition: LocalFile.php:2122
File\isMultipage
isMultipage()
Returns 'true' if this file is a type which supports multiple pages, e.g.
Definition: File.php:2023
LocalFile\getUser
getUser( $type='text')
Returns user who uploaded the file.
Definition: LocalFile.php:861
User\getActorId
getActorId(IDatabase $dbw=null)
Get the user's actor ID.
Definition: User.php:2323
LocalFile\loadFromDB
loadFromDB( $flags=0)
Load file metadata from the DB.
Definition: LocalFile.php:422
MediaWiki\MediaWikiServices
MediaWikiServices is the service locator for the application scope of MediaWiki.
Definition: MediaWikiServices.php:130
$lang
if(!isset( $args[0])) $lang
Definition: testCompression.php:35
LocalFile\purgeThumbList
purgeThumbList( $dir, $files)
Delete a list of thumbnails visible at urls.
Definition: LocalFile.php:1129
File\getRel
getRel()
Get the path of the file relative to the public zone root.
Definition: File.php:1581
AutoCommitUpdate
Deferrable Update for closure/callback updates that should use auto-commit mode.
Definition: AutoCommitUpdate.php:9
OldLocalFile\getQueryInfo
static getQueryInfo(array $options=[])
Return the tables, fields, and join conditions to be selected to create a new oldlocalfile object.
Definition: OldLocalFile.php:120
LocalFile\$missing
bool $missing
True if file is not present in file system.
Definition: LocalFile.php:137
LocalFileDeleteBatch
Helper class for file deletion.
Definition: LocalFileDeleteBatch.php:31
MediaHandler\filterThumbnailPurgeList
filterThumbnailPurgeList(&$files, $options)
Remove files from the purge list.
Definition: MediaHandler.php:711
wfTimestamp
wfTimestamp( $outputtype=TS_UNIX, $ts=0)
Get a timestamp string in one of various formats.
Definition: GlobalFunctions.php:1871
FileBackendError
File backend exception for checked exceptions (e.g.
Definition: FileBackendError.php:8
LocalFile\upload
upload( $src, $comment, $pageText, $flags=0, $props=false, $timestamp=false, $user=null, $tags=[], $createNullRevision=true, $revert=false)
getHashPath inherited
Definition: LocalFile.php:1317
LocalFile\$upgraded
bool $upgraded
Whether the row was upgraded on load.
Definition: LocalFile.php:125
LocalFile\getDescriptionShortUrl
getDescriptionShortUrl()
Get short description URL for a file based on the page ID.
Definition: LocalFile.php:894
LocalFile\getCacheFields
getCacheFields( $prefix='img_')
Returns the list of object properties that are included as-is in the cache.
Definition: LocalFile.php:385
Title\getPrefixedText
getPrefixedText()
Get the prefixed title with spaces.
Definition: Title.php:1853
DeferredUpdates\addUpdate
static addUpdate(DeferrableUpdate $update, $stage=self::POSTSEND)
Add an update to the deferred list to be run later by execute()
Definition: DeferredUpdates.php:85
LocalFile\getHistory
getHistory( $limit=null, $start=null, $end=null, $inc=true)
purgeDescription inherited
Definition: LocalFile.php:1169
LocalFile\getMediaType
getMediaType()
Returns the type of the media in the file.
Definition: LocalFile.php:953
File\getUrl
getUrl()
Return the URL of the file.
Definition: File.php:358
LocalFile\$dataLoaded
bool $dataLoaded
Whether or not core data has been loaded from the database (loadFromXxx)
Definition: LocalFile.php:89
NS_FILE
const NS_FILE
Definition: Defines.php:66
$file
if(PHP_SAPI !='cli-server') if(!isset( $_SERVER['SCRIPT_FILENAME'])) $file
Item class for a filearchive table row.
Definition: router.php:42
LocalFile\readOnlyFatalStatus
readOnlyFatalStatus()
Definition: LocalFile.php:2275
LocalFile\newFromTitle
static newFromTitle( $title, $repo, $unused=null)
Create a LocalFile from a title Do not call this except from inside a repo class.
Definition: LocalFile.php:156
wfReadOnly
wfReadOnly()
Check whether the wiki is in read-only mode.
Definition: GlobalFunctions.php:1170
User\newFromName
static newFromName( $name, $validate='valid')
Static factory method for creation from username.
Definition: User.php:537
RequestContext\newExtraneousContext
static newExtraneousContext(Title $title, $request=[])
Create a new extraneous context.
Definition: RequestContext.php:617
LocalFile\$upgrading
bool $upgrading
Whether the row was scheduled to upgrade on load.
Definition: LocalFile.php:128
LocalFile\getSize
getSize()
Returns the size of the image file, in bytes.
Definition: LocalFile.php:932
LocalFile\purgeOldThumbnails
purgeOldThumbnails( $archiveName)
Delete cached transformed files for an archived version only.
Definition: LocalFile.php:1045
LocalFile\getThumbnails
getThumbnails( $archiveName=false)
getTransformScript inherited
Definition: LocalFile.php:990
LocalFile\$sha1
string $sha1
SHA-1 base 36 content hash.
Definition: LocalFile.php:86
File\splitMime
static splitMime( $mime)
Split an internet media type into its two components; if not a two-part name, set the minor type to '...
Definition: File.php:283
$res
$res
Definition: testCompression.php:54
serialize
serialize()
Definition: ApiMessageTrait.php:138
LocalFile\$minor_mime
string $minor_mime
Minor MIME type.
Definition: LocalFile.php:110
File\getArchiveRel
getArchiveRel( $suffix=false)
Get the path of an archived file relative to the public zone root.
Definition: File.php:1592
File\isDeleted
isDeleted( $field)
Is this file a "deleted" file in a private archive? STUB.
Definition: File.php:1940
wfDebugLog
wfDebugLog( $logGroup, $text, $dest='all', array $context=[])
Send a line to a supplementary debug log file, if configured, or main debug log if not.
Definition: GlobalFunctions.php:1007
ActorMigration\newMigration
static newMigration()
Static constructor.
Definition: ActorMigration.php:139
LocalFile\acquireFileLock
acquireFileLock()
Definition: LocalFile.php:2191
Wikimedia\Rdbms\IDatabase
Basic database interface for live and lazy-loaded relation database handles.
Definition: IDatabase.php:38
$dbr
$dbr
Definition: testCompression.php:52
LocalFile\isCacheable
isCacheable()
Definition: LocalFile.php:2179
FileRepo\publish
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...
Definition: FileRepo.php:1222
LocalFile\decodeRow
decodeRow( $row, $prefix='img_')
Decode a row from the database (either object or array) to an array with timestamps and MIME types de...
Definition: LocalFile.php:557
LocalFile\$bits
int $bits
Returned by getimagesize (loadFromXxx)
Definition: LocalFile.php:71
LocalFile\purgeThumbnails
purgeThumbnails( $options=[])
Delete cached transformed files for the current version only.
Definition: LocalFile.php:1069
FileRepo\hasSha1Storage
hasSha1Storage()
Returns whether or not storage is SHA-1 based.
Definition: FileRepo.php:1966
MediaHandler\METADATA_COMPATIBLE
const METADATA_COMPATIBLE
Definition: MediaHandler.php:34
File
Implements some public methods and some protected utility functions which are required by multiple ch...
Definition: File.php:61
LocalFile\loadExtraFromDB
loadExtraFromDB()
Load lazy file metadata from the DB.
Definition: LocalFile.php:454
LocalFile\publishTo
publishTo( $src, $dstRel, $flags=0, array $options=[])
Move or copy a file to a specified location.
Definition: LocalFile.php:1807
File\$url
string $url
The URL corresponding to one of the four basic zones.
Definition: File.php:127
MWException
MediaWiki exception.
Definition: MWException.php:26
LocalFile\newFromRow
static newFromRow( $row, $repo)
Create a LocalFile from a title Do not call this except from inside a repo class.
Definition: LocalFile.php:169
LocalFile\getDescriptionTouched
getDescriptionTouched()
Definition: LocalFile.php:2131
LocalFile\purgeMetadataCache
purgeMetadataCache()
Refresh metadata in memcached, but don't touch thumbnails or CDN.
Definition: LocalFile.php:1015
MediaWiki\Logger\LoggerFactory
PSR-3 logger instance factory.
Definition: LoggerFactory.php:45
File\getThumbPath
getThumbPath( $suffix=false)
Get the path of the thumbnail directory, or a particular file if $suffix is specified.
Definition: File.php:1678
Wikimedia\Rdbms\IResultWrapper
Result wrapper for grabbing data queried from an IDatabase object.
Definition: IResultWrapper.php:24
FileRepo\quickImport
quickImport( $src, $dst, $options=null)
Import a file from the local file system into the repo.
Definition: FileRepo.php:1012
LocalFile\getUpgraded
getUpgraded()
Definition: LocalFile.php:677
FileBackend\isStoragePath
static isStoragePath( $path)
Check if a given path is a "mwstore://" path.
Definition: FileBackend.php:1519
LocalFile\deleteOld
deleteOld( $archiveName, $reason, $suppress=false, $user=null)
Delete an old version of the file.
Definition: LocalFile.php:1992
Status\wrap
static wrap( $sv)
Succinct helper method to wrap a StatusValue.
Definition: Status.php:58
LocalFile\CACHE_FIELD_MAX_LEN
const CACHE_FIELD_MAX_LEN
Definition: LocalFile.php:59
LocalFile\loadFromRow
loadFromRow( $row, $prefix='img_')
Load file metadata from a DB result row.
Definition: LocalFile.php:603
LocalFile\__destruct
__destruct()
Clean up any dangling locks.
Definition: LocalFile.php:2283
LocalFile\$deleted
int $deleted
Bitfield akin to rev_deleted.
Definition: LocalFile.php:95
$wgUploadThumbnailRenderMap
$wgUploadThumbnailRenderMap
When defined, is an array of thumbnail widths to be rendered at upload time.
Definition: DefaultSettings.php:1521
FSFile\getSha1Base36FromPath
static getSha1Base36FromPath( $path)
Get a SHA-1 hash of a file in the local filesystem, in base-36 lower case encoding,...
Definition: FSFile.php:225
LocalFile\$historyRes
IResultWrapper null $historyRes
Result of the query for the file's history (nextHistoryLine)
Definition: LocalFile.php:104
LocalFile\getDescriptionText
getDescriptionText(Language $lang=null)
Get the HTML text of the description page This is not used by ImagePage for local files,...
Definition: LocalFile.php:2078
SiteStatsUpdate\factory
static factory(array $deltas)
Definition: SiteStatsUpdate.php:71
MWFileProps
MimeMagic helper wrapper.
Definition: MWFileProps.php:28
LocalFile\prerenderThumbnails
prerenderThumbnails()
Prerenders a configurable set of thumbnails.
Definition: LocalFile.php:1102
Title\makeTitle
static makeTitle( $ns, $title, $fragment='', $interwiki='')
Create a new Title from a namespace index and a DB key.
Definition: Title.php:584
LocalFile\getCacheKey
getCacheKey()
Get the memcached key for the main data for this file, or false if there is no access to the shared c...
Definition: LocalFile.php:272
User\addWatch
addWatch( $title, $checkRights=self::CHECK_USER_RIGHTS)
Watch an article.
Definition: User.php:3738
MediaHandler\getPageDimensions
getPageDimensions(File $image, $page)
Get an associative array of page dimensions Currently "width" and "height" are understood,...
Definition: MediaHandler.php:404
User\newFromAnyId
static newFromAnyId( $userId, $userName, $actorId, $dbDomain=false)
Static factory method for creation from an ID, name, and/or actor ID.
Definition: User.php:618
LocalFile\$mime
string $mime
MIME type, determined by MimeAnalyzer::guessMimeType.
Definition: LocalFile.php:77
wfDebug
wfDebug( $text, $dest='all', array $context=[])
Sends a line to the debug log if enabled or, optionally, to a comment in output.
Definition: GlobalFunctions.php:913
LocalFile\setProps
setProps( $info)
Set properties in this object to be equal to those given in the associative array $info.
Definition: LocalFile.php:739
FileRepo\getFileSha1
getFileSha1( $virtualUrl)
Get the sha1 (base 36) of a file with a given virtual URL/storage path.
Definition: FileRepo.php:1635
LocalFile
Class to represent a local file in the wiki's own database.
Definition: LocalFile.php:56
ContentHandler\makeContent
static makeContent( $text, Title $title=null, $modelId=null, $format=null)
Convenience function for creating a Content object from a given textual representation.
Definition: ContentHandler.php:135
LocalFile\getMimeType
getMimeType()
Returns the MIME type of the file.
Definition: LocalFile.php:942
LocalFile\getDescription
getDescription( $audience=self::FOR_PUBLIC, User $user=null)
Definition: LocalFile.php:2106
LocalFile\$extraDataLoaded
bool $extraDataLoaded
Whether or not lazy-loaded data has been loaded from the database.
Definition: LocalFile.php:92
ThumbnailRenderJob
Job for asynchronous rendering of thumbnails.
Definition: ThumbnailRenderJob.php:31
LocalFile\$size
int $size
Size in bytes (loadFromXxx)
Definition: LocalFile.php:80
HTMLCacheUpdate
Class to invalidate the HTML/file cache of all the pages linking to a given title.
Definition: HTMLCacheUpdate.php:29
File\purgeDescription
purgeDescription()
Purge the file description page, but don't go after pages using the file.
Definition: File.php:1494
LocalFile\LOAD_ALL
const LOAD_ALL
Definition: LocalFile.php:140
LocalFile\lock
lock()
Start an atomic DB section and lock the image for update or increments a reference counter if the loc...
Definition: LocalFile.php:2216
File\isOld
isOld()
Returns true if the image is an old version STUB.
Definition: File.php:1929
File\$handler
MediaHandler $handler
Definition: File.php:124
LocalFileLockError
Definition: LocalFileLockError.php:24
CdnCacheUpdate
Handles purging the appropriate CDN objects given a list of URLs or Title instances.
Definition: CdnCacheUpdate.php:30
File\assertTitleDefined
assertTitleDefined()
Assert that $this->title is set to a Title.
Definition: File.php:2334
StatusValue\newGood
static newGood( $value=null)
Factory function for good results.
Definition: StatusValue.php:81
LocalFile\$historyLine
int $historyLine
Number of line to return by nextHistoryLine() (constructor)
Definition: LocalFile.php:101
LocalFile\releaseFileLock
releaseFileLock()
Definition: LocalFile.php:2201
LocalFile\upgradeRow
upgradeRow()
Fix assorted version-related problems with the image row by reloading it from the file.
Definition: LocalFile.php:684
LocalFile\load
load( $flags=0)
Load file metadata from cache or DB, unless already loaded.
Definition: LocalFile.php:620
WANObjectCache
Multi-datacenter aware caching interface.
Definition: WANObjectCache.php:116
LocalFile\newFromKey
static newFromKey( $sha1, $repo, $timestamp=false)
Create a LocalFile from a SHA-1 key Do not call this except from inside a repo class.
Definition: LocalFile.php:186
LocalFile\nextHistoryLine
nextHistoryLine()
Returns the history of this file, line by line.
Definition: LocalFile.php:1229
File\$title
Title string bool $title
Definition: File.php:109
LocalFile\$metadata
string $metadata
Handler-specific metadata.
Definition: LocalFile.php:83
LocalFile\getBitDepth
getBitDepth()
Definition: LocalFile.php:922
LocalFile\$height
int $height
Image height.
Definition: LocalFile.php:68
LocalFile\$user
User $user
Uploader.
Definition: LocalFile.php:116
File\getArchiveThumbUrl
getArchiveThumbUrl( $archiveName, $suffix=false)
Get the URL of the archived file's thumbs, or a particular thumb if $suffix is specified.
Definition: File.php:1722
File\getName
getName()
Return the name of this file.
Definition: File.php:307
File\getArchiveUrl
getArchiveUrl( $suffix=false)
Get the URL of the archive directory, or a particular file if $suffix is specified.
Definition: File.php:1702
FSFile
Class representing a non-directory file on the file system.
Definition: FSFile.php:32
LocalFile\$timestamp
string $timestamp
Upload timestamp.
Definition: LocalFile.php:113
FileRepo\getBackend
getBackend()
Get the file backend instance.
Definition: FileRepo.php:242
LocalFile\$descriptionTouched
string $descriptionTouched
TS_MW timestamp of the last change of the file description.
Definition: LocalFile.php:122
File\DELETE_SOURCE
const DELETE_SOURCE
Definition: File.php:76
LocalFile\recordUpload
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.
Definition: LocalFile.php:1406
EDIT_NEW
const EDIT_NEW
Definition: Defines.php:132
LocalFile\$locked
bool $locked
True if the image row is locked.
Definition: LocalFile.php:131
LocalFile\$description
string $description
Description of current revision of the file.
Definition: LocalFile.php:119
LocalFile\getLazyCacheFields
getLazyCacheFields( $prefix='img_')
Returns the list of object properties that are included as-is in the cache, only when they're not too...
Definition: LocalFile.php:407
File\getTitle
getTitle()
Return the associated title object.
Definition: File.php:336
LocalFile\getQueryInfo
static getQueryInfo(array $options=[])
Return the tables, fields, and join conditions to be selected to create a new localfile object.
Definition: LocalFile.php:216
LocalFile\getMetadata
getMetadata()
Get handler-specific metadata.
Definition: LocalFile.php:914
JobQueueGroup\singleton
static singleton( $domain=false)
Definition: JobQueueGroup.php:70
MediaHandler\getContentHeaders
getContentHeaders( $metadata)
Get useful response headers for GET/HEAD requests for a file with the given metadata.
Definition: MediaHandler.php:926
$cache
$cache
Definition: mcc.php:33
LocalFile\loadFromFile
loadFromFile()
Load metadata from the file itself.
Definition: LocalFile.php:374
File\assertRepoDefined
assertRepoDefined()
Assert that $this->repo is set to a valid FileRepo instance.
Definition: File.php:2324
DeferredUpdates\PRESEND
const PRESEND
Definition: DeferredUpdates.php:69
LocalFile\recordUpload2
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.
Definition: LocalFile.php:1440
LocalFile\resetHistory
resetHistory()
Reset the history pointer to the first element of the history.
Definition: LocalFile.php:1276
LogEntryBase\makeParamBlob
static makeParamBlob( $params)
Create a blob from a parameter array.
Definition: LogEntryBase.php:58
MediaHandler\getHandler
static getHandler( $type)
Get a MediaHandler for a given MIME type from the instance cache.
Definition: MediaHandler.php:46
LinksUpdate\queueRecursiveJobsForTable
static queueRecursiveJobsForTable(Title $title, $table, $action='unknown', $userName='unknown')
Queue a RefreshLinks job for any table.
Definition: LinksUpdate.php:370
FileRepo\isVirtualUrl
static isVirtualUrl( $url)
Determine if a string is an mwrepo:// URL.
Definition: FileRepo.php:281
LocalFile\loadExtraFieldsWithTimestamp
loadExtraFieldsWithTimestamp( $dbr, $fname)
Definition: LocalFile.php:483
LocalFileMoveBatch
Helper class for file movement.
Definition: LocalFileMoveBatch.php:31
MediaHandler\METADATA_BAD
const METADATA_BAD
Definition: MediaHandler.php:33
$source
$source
Definition: mwdoc-filter.php:34
ManualLogEntry
Class for creating new log entries and inserting them into the database.
Definition: ManualLogEntry.php:38
Revision\newNullRevision
static newNullRevision( $dbw, $pageId, $summary, $minor, $user=null)
Create a new null-revision for insertion into a page's history.
Definition: Revision.php:994
FileRepo\DELETE_SOURCE
const DELETE_SOURCE
Definition: FileRepo.php:42
WikiFilePage
Special handling for file pages.
Definition: WikiFilePage.php:31
File\$name
string $name
The name of a file from its title object.
Definition: File.php:133
LocalFile\publish
publish( $src, $flags=0, array $options=[])
Move or copy a file to its public location.
Definition: LocalFile.php:1788
EDIT_SUPPRESS_RC
const EDIT_SUPPRESS_RC
Definition: Defines.php:135
File\getRepo
getRepo()
Returns the repository.
Definition: File.php:1919
LocalFile\getWidth
getWidth( $page=1)
Return the width of the image.
Definition: LocalFile.php:796
File\getThumbUrl
getThumbUrl( $suffix=false)
Get the URL of the thumbnail directory, or a particular file if $suffix is specified.
Definition: File.php:1758
LocalFile\$major_mime
string $major_mime
Major MIME type.
Definition: LocalFile.php:107
File\isVectorized
isVectorized()
Return true if the file is vectorized.
Definition: File.php:605
File\getHandler
getHandler()
Get a MediaHandler instance for this file.
Definition: File.php:1430
LockManager\LOCK_EX
const LOCK_EX
Definition: LockManager.php:70
LocalFile\getHeight
getHeight( $page=1)
Return the height of the image.
Definition: LocalFile.php:828
LocalFile\isMissing
isMissing()
splitMime inherited
Definition: LocalFile.php:781
LocalFile\invalidateCache
invalidateCache()
Purge the file object/metadata cache.
Definition: LocalFile.php:357
File\userCan
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...
Definition: File.php:2212
LocalFile\$lockedOwnTrx
bool $lockedOwnTrx
True if the image row is locked with a lock initiated transaction.
Definition: LocalFile.php:134
User
The User object encapsulates all of the user-specific settings (user_id, name, rights,...
Definition: User.php:52
DeferredUpdates\addCallableUpdate
static addCallableUpdate( $callable, $stage=self::POSTSEND, $dbw=null)
Add a callable update.
Definition: DeferredUpdates.php:125
LocalFile\VERSION
const VERSION
Definition: LocalFile.php:57
Hooks\run
static run( $event, array $args=[], $deprecatedVersion=null)
Call hook functions defined in Hooks::register and $wgHooks.
Definition: Hooks.php:200
LocalFile\getDescriptionUrl
getDescriptionUrl()
isMultipage inherited
Definition: LocalFile.php:2062
LocalFile\move
move( $target)
getLinksTo inherited
Definition: LocalFile.php:1873
LocalFile\$repoClass
string $repoClass
Definition: LocalFile.php:98
MediaHandler\isMetadataValid
isMetadataValid( $image, $metadata)
Check if the metadata string is valid for this handler.
Definition: MediaHandler.php:198
User\getName
getName()
Get the user name, or the IP of an anonymous user.
Definition: User.php:2284
LocalFile\restore
restore( $versions=[], $unsuppress=false)
Restore all or specified deleted revisions to the given file.
Definition: LocalFile.php:2028
Language
Internationalisation code.
Definition: Language.php:39
File\purgeEverything
purgeEverything()
Purge metadata and all affected pages when the file is created, deleted, or majorly updated.
Definition: File.php:1506
LocalFile\exists
exists()
canRender inherited
Definition: LocalFile.php:969
File\getThumbnails
getThumbnails()
Get all thumbnail names previously generated for this file STUB Overridden by LocalFile.
Definition: File.php:1475
LocalFile\loadFromCache
loadFromCache()
Try to load file metadata from memcached, falling back to the database.
Definition: LocalFile.php:288
LocalFile\purgeCache
purgeCache( $options=[])
Delete all previously generated thumbnails, refresh metadata in memcached and purge the CDN.
Definition: LocalFile.php:1026
File\getVirtualUrl
getVirtualUrl( $suffix=false)
Get the public zone virtual URL for a current version source file.
Definition: File.php:1778
LogFormatter\newFromEntry
static newFromEntry(LogEntry $entry)
Constructs a new formatter suitable for given entry.
Definition: LogFormatter.php:50
Article\purgePatrolFooterCache
static purgePatrolFooterCache( $articleID)
Purge the cache used to check if it is worth showing the patrol footer For example,...
Definition: Article.php:1375
$type
$type
Definition: testCompression.php:50