MediaWiki  master
LocalFile.php
Go to the documentation of this file.
1 <?php
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 ( is_null( $this->media_type ) || $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  foreach ( $iterator as $file ) {
1002  $files[] = $file;
1003  }
1004  } catch ( FileBackendError $e ) {
1005  } // suppress (T56674)
1006 
1007  return $files;
1008  }
1009 
1013  function purgeMetadataCache() {
1014  $this->invalidateCache();
1015  }
1016 
1024  function purgeCache( $options = [] ) {
1025  // Refresh metadata cache
1026  $this->maybeUpgradeRow();
1027  $this->purgeMetadataCache();
1028 
1029  // Delete thumbnails
1030  $this->purgeThumbnails( $options );
1031 
1032  // Purge CDN cache for this file
1034  new CdnCacheUpdate( [ $this->getUrl() ] ),
1036  );
1037  }
1038 
1043  function purgeOldThumbnails( $archiveName ) {
1044  // Get a list of old thumbnails and URLs
1045  $files = $this->getThumbnails( $archiveName );
1046 
1047  // Purge any custom thumbnail caches
1048  Hooks::run( 'LocalFilePurgeThumbnails', [ $this, $archiveName ] );
1049 
1050  // Delete thumbnails
1051  $dir = array_shift( $files );
1052  $this->purgeThumbList( $dir, $files );
1053 
1054  // Purge the CDN
1055  $urls = [];
1056  foreach ( $files as $file ) {
1057  $urls[] = $this->getArchiveThumbUrl( $archiveName, $file );
1058  }
1060  }
1061 
1067  public function purgeThumbnails( $options = [] ) {
1068  $files = $this->getThumbnails();
1069  // Always purge all files from CDN regardless of handler filters
1070  $urls = [];
1071  foreach ( $files as $file ) {
1072  $urls[] = $this->getThumbUrl( $file );
1073  }
1074  array_shift( $urls ); // don't purge directory
1075 
1076  // Give media handler a chance to filter the file purge list
1077  if ( !empty( $options['forThumbRefresh'] ) ) {
1078  $handler = $this->getHandler();
1079  if ( $handler ) {
1080  $handler->filterThumbnailPurgeList( $files, $options );
1081  }
1082  }
1083 
1084  // Purge any custom thumbnail caches
1085  Hooks::run( 'LocalFilePurgeThumbnails', [ $this, false ] );
1086 
1087  // Delete thumbnails
1088  $dir = array_shift( $files );
1089  $this->purgeThumbList( $dir, $files );
1090 
1091  // Purge the CDN
1093  }
1094 
1100  public function prerenderThumbnails() {
1102 
1103  $jobs = [];
1104 
1105  $sizes = $wgUploadThumbnailRenderMap;
1106  rsort( $sizes );
1107 
1108  foreach ( $sizes as $size ) {
1109  if ( $this->isVectorized() || $this->getWidth() > $size ) {
1110  $jobs[] = new ThumbnailRenderJob(
1111  $this->getTitle(),
1112  [ 'transformParams' => [ 'width' => $size ] ]
1113  );
1114  }
1115  }
1116 
1117  if ( $jobs ) {
1118  JobQueueGroup::singleton()->lazyPush( $jobs );
1119  }
1120  }
1121 
1127  protected function purgeThumbList( $dir, $files ) {
1128  $fileListDebug = strtr(
1129  var_export( $files, true ),
1130  [ "\n" => '' ]
1131  );
1132  wfDebug( __METHOD__ . ": $fileListDebug\n" );
1133 
1134  $purgeList = [];
1135  foreach ( $files as $file ) {
1136  if ( $this->repo->supportsSha1URLs() ) {
1137  $reference = $this->getSha1();
1138  } else {
1139  $reference = $this->getName();
1140  }
1141 
1142  # Check that the reference (filename or sha1) is part of the thumb name
1143  # This is a basic sanity check to avoid erasing unrelated directories
1144  if ( strpos( $file, $reference ) !== false
1145  || strpos( $file, "-thumbnail" ) !== false // "short" thumb name
1146  ) {
1147  $purgeList[] = "{$dir}/{$file}";
1148  }
1149  }
1150 
1151  # Delete the thumbnails
1152  $this->repo->quickPurgeBatch( $purgeList );
1153  # Clear out the thumbnail directory if empty
1154  $this->repo->quickCleanDir( $dir );
1155  }
1156 
1167  function getHistory( $limit = null, $start = null, $end = null, $inc = true ) {
1168  if ( !$this->exists() ) {
1169  return []; // Avoid hard failure when the file does not exist. T221812
1170  }
1171 
1172  $dbr = $this->repo->getReplicaDB();
1173  $oldFileQuery = OldLocalFile::getQueryInfo();
1174 
1175  $tables = $oldFileQuery['tables'];
1176  $fields = $oldFileQuery['fields'];
1177  $join_conds = $oldFileQuery['joins'];
1178  $conds = $opts = [];
1179  $eq = $inc ? '=' : '';
1180  $conds[] = "oi_name = " . $dbr->addQuotes( $this->title->getDBkey() );
1181 
1182  if ( $start ) {
1183  $conds[] = "oi_timestamp <$eq " . $dbr->addQuotes( $dbr->timestamp( $start ) );
1184  }
1185 
1186  if ( $end ) {
1187  $conds[] = "oi_timestamp >$eq " . $dbr->addQuotes( $dbr->timestamp( $end ) );
1188  }
1189 
1190  if ( $limit ) {
1191  $opts['LIMIT'] = $limit;
1192  }
1193 
1194  // Search backwards for time > x queries
1195  $order = ( !$start && $end !== null ) ? 'ASC' : 'DESC';
1196  $opts['ORDER BY'] = "oi_timestamp $order";
1197  $opts['USE INDEX'] = [ 'oldimage' => 'oi_name_timestamp' ];
1198 
1199  // Avoid PHP 7.1 warning from passing $this by reference
1200  $localFile = $this;
1201  Hooks::run( 'LocalFile::getHistory', [ &$localFile, &$tables, &$fields,
1202  &$conds, &$opts, &$join_conds ] );
1203 
1204  $res = $dbr->select( $tables, $fields, $conds, __METHOD__, $opts, $join_conds );
1205  $r = [];
1206 
1207  foreach ( $res as $row ) {
1208  $r[] = $this->repo->newFileFromRow( $row );
1209  }
1210 
1211  if ( $order == 'ASC' ) {
1212  $r = array_reverse( $r ); // make sure it ends up descending
1213  }
1214 
1215  return $r;
1216  }
1217 
1227  public function nextHistoryLine() {
1228  if ( !$this->exists() ) {
1229  return false; // Avoid hard failure when the file does not exist. T221812
1230  }
1231 
1232  # Polymorphic function name to distinguish foreign and local fetches
1233  $fname = static::class . '::' . __FUNCTION__;
1234 
1235  $dbr = $this->repo->getReplicaDB();
1236 
1237  if ( $this->historyLine == 0 ) { // called for the first time, return line from cur
1238  $fileQuery = self::getQueryInfo();
1239  $this->historyRes = $dbr->select( $fileQuery['tables'],
1240  $fileQuery['fields'] + [
1241  'oi_archive_name' => $dbr->addQuotes( '' ),
1242  'oi_deleted' => 0,
1243  ],
1244  [ 'img_name' => $this->title->getDBkey() ],
1245  $fname,
1246  [],
1247  $fileQuery['joins']
1248  );
1249 
1250  if ( $dbr->numRows( $this->historyRes ) == 0 ) {
1251  $this->historyRes = null;
1252 
1253  return false;
1254  }
1255  } elseif ( $this->historyLine == 1 ) {
1256  $fileQuery = OldLocalFile::getQueryInfo();
1257  $this->historyRes = $dbr->select(
1258  $fileQuery['tables'],
1259  $fileQuery['fields'],
1260  [ 'oi_name' => $this->title->getDBkey() ],
1261  $fname,
1262  [ 'ORDER BY' => 'oi_timestamp DESC' ],
1263  $fileQuery['joins']
1264  );
1265  }
1266  $this->historyLine++;
1267 
1268  return $dbr->fetchObject( $this->historyRes );
1269  }
1270 
1274  public function resetHistory() {
1275  $this->historyLine = 0;
1276 
1277  if ( !is_null( $this->historyRes ) ) {
1278  $this->historyRes = null;
1279  }
1280  }
1281 
1315  function upload( $src, $comment, $pageText, $flags = 0, $props = false,
1316  $timestamp = false, $user = null, $tags = [],
1317  $createNullRevision = true, $revert = false
1318  ) {
1319  if ( $this->getRepo()->getReadOnlyReason() !== false ) {
1320  return $this->readOnlyFatalStatus();
1321  } elseif ( MediaWikiServices::getInstance()->getRevisionStore()->isReadOnly() ) {
1322  // Check this in advance to avoid writing to FileBackend and the file tables,
1323  // only to fail on insert the revision due to the text store being unavailable.
1324  return $this->readOnlyFatalStatus();
1325  }
1326 
1327  $srcPath = ( $src instanceof FSFile ) ? $src->getPath() : $src;
1328  if ( !$props ) {
1329  if ( FileRepo::isVirtualUrl( $srcPath )
1330  || FileBackend::isStoragePath( $srcPath )
1331  ) {
1332  $props = $this->repo->getFileProps( $srcPath );
1333  } else {
1334  $mwProps = new MWFileProps( MediaWikiServices::getInstance()->getMimeAnalyzer() );
1335  $props = $mwProps->getPropsFromPath( $srcPath, true );
1336  }
1337  }
1338 
1339  $options = [];
1340  $handler = MediaHandler::getHandler( $props['mime'] );
1341  if ( $handler ) {
1342  $metadata = AtEase::quietCall( 'unserialize', $props['metadata'] );
1343 
1344  if ( !is_array( $metadata ) ) {
1345  $metadata = [];
1346  }
1347 
1348  $options['headers'] = $handler->getContentHeaders( $metadata );
1349  } else {
1350  $options['headers'] = [];
1351  }
1352 
1353  // Trim spaces on user supplied text
1354  $comment = trim( $comment );
1355 
1356  $this->lock();
1357  $status = $this->publish( $src, $flags, $options );
1358 
1359  if ( $status->successCount >= 2 ) {
1360  // There will be a copy+(one of move,copy,store).
1361  // The first succeeding does not commit us to updating the DB
1362  // since it simply copied the current version to a timestamped file name.
1363  // It is only *preferable* to avoid leaving such files orphaned.
1364  // Once the second operation goes through, then the current version was
1365  // updated and we must therefore update the DB too.
1366  $oldver = $status->value;
1367  $uploadStatus = $this->recordUpload2(
1368  $oldver,
1369  $comment,
1370  $pageText,
1371  $props,
1372  $timestamp,
1373  $user,
1374  $tags,
1375  $createNullRevision,
1376  $revert
1377  );
1378  if ( !$uploadStatus->isOK() ) {
1379  if ( $uploadStatus->hasMessage( 'filenotfound' ) ) {
1380  // update filenotfound error with more specific path
1381  $status->fatal( 'filenotfound', $srcPath );
1382  } else {
1383  $status->merge( $uploadStatus );
1384  }
1385  }
1386  }
1387 
1388  $this->unlock();
1389  return $status;
1390  }
1391 
1404  function recordUpload( $oldver, $desc, $license = '', $copyStatus = '', $source = '',
1405  $watch = false, $timestamp = false, User $user = null ) {
1406  if ( !$user ) {
1407  global $wgUser;
1408  $user = $wgUser;
1409  }
1410 
1411  $pageText = SpecialUpload::getInitialPageText( $desc, $license, $copyStatus, $source );
1412 
1413  if ( !$this->recordUpload2( $oldver, $desc, $pageText, false, $timestamp, $user )->isOK() ) {
1414  return false;
1415  }
1416 
1417  if ( $watch ) {
1418  $user->addWatch( $this->getTitle() );
1419  }
1420 
1421  return true;
1422  }
1423 
1438  function recordUpload2(
1439  $oldver, $comment, $pageText, $props = false, $timestamp = false, $user = null, $tags = [],
1440  $createNullRevision = true, $revert = false
1441  ) {
1442  if ( is_null( $user ) ) {
1443  global $wgUser;
1444  $user = $wgUser;
1445  }
1446 
1447  $dbw = $this->repo->getMasterDB();
1448 
1449  # Imports or such might force a certain timestamp; otherwise we generate
1450  # it and can fudge it slightly to keep (name,timestamp) unique on re-upload.
1451  if ( $timestamp === false ) {
1452  $timestamp = $dbw->timestamp();
1453  $allowTimeKludge = true;
1454  } else {
1455  $allowTimeKludge = false;
1456  }
1457 
1458  $props = $props ?: $this->repo->getFileProps( $this->getVirtualUrl() );
1459  $props['description'] = $comment;
1460  $props['user'] = $user->getId();
1461  $props['user_text'] = $user->getName();
1462  $props['actor'] = $user->getActorId( $dbw );
1463  $props['timestamp'] = wfTimestamp( TS_MW, $timestamp ); // DB -> TS_MW
1464  $this->setProps( $props );
1465 
1466  # Fail now if the file isn't there
1467  if ( !$this->fileExists ) {
1468  wfDebug( __METHOD__ . ": File " . $this->getRel() . " went missing!\n" );
1469 
1470  return Status::newFatal( 'filenotfound', $this->getRel() );
1471  }
1472 
1473  $dbw->startAtomic( __METHOD__ );
1474 
1475  # Test to see if the row exists using INSERT IGNORE
1476  # This avoids race conditions by locking the row until the commit, and also
1477  # doesn't deadlock. SELECT FOR UPDATE causes a deadlock for every race condition.
1478  $commentStore = MediaWikiServices::getInstance()->getCommentStore();
1479  $commentFields = $commentStore->insert( $dbw, 'img_description', $comment );
1480  $actorMigration = ActorMigration::newMigration();
1481  $actorFields = $actorMigration->getInsertValues( $dbw, 'img_user', $user );
1482  $dbw->insert( 'image',
1483  [
1484  'img_name' => $this->getName(),
1485  'img_size' => $this->size,
1486  'img_width' => intval( $this->width ),
1487  'img_height' => intval( $this->height ),
1488  'img_bits' => $this->bits,
1489  'img_media_type' => $this->media_type,
1490  'img_major_mime' => $this->major_mime,
1491  'img_minor_mime' => $this->minor_mime,
1492  'img_timestamp' => $timestamp,
1493  'img_metadata' => $dbw->encodeBlob( $this->metadata ),
1494  'img_sha1' => $this->sha1
1495  ] + $commentFields + $actorFields,
1496  __METHOD__,
1497  [ 'IGNORE' ]
1498  );
1499  $reupload = ( $dbw->affectedRows() == 0 );
1500 
1501  if ( $reupload ) {
1502  $row = $dbw->selectRow(
1503  'image',
1504  [ 'img_timestamp', 'img_sha1' ],
1505  [ 'img_name' => $this->getName() ],
1506  __METHOD__,
1507  [ 'LOCK IN SHARE MODE' ]
1508  );
1509 
1510  if ( $row && $row->img_sha1 === $this->sha1 ) {
1511  $dbw->endAtomic( __METHOD__ );
1512  wfDebug( __METHOD__ . ": File " . $this->getRel() . " already exists!\n" );
1513  $title = Title::newFromText( $this->getName(), NS_FILE );
1514  return Status::newFatal( 'fileexists-no-change', $title->getPrefixedText() );
1515  }
1516 
1517  if ( $allowTimeKludge ) {
1518  # Use LOCK IN SHARE MODE to ignore any transaction snapshotting
1519  $lUnixtime = $row ? wfTimestamp( TS_UNIX, $row->img_timestamp ) : false;
1520  # Avoid a timestamp that is not newer than the last version
1521  # TODO: the image/oldimage tables should be like page/revision with an ID field
1522  if ( $lUnixtime && wfTimestamp( TS_UNIX, $timestamp ) <= $lUnixtime ) {
1523  sleep( 1 ); // fast enough re-uploads would go far in the future otherwise
1524  $timestamp = $dbw->timestamp( $lUnixtime + 1 );
1525  $this->timestamp = wfTimestamp( TS_MW, $timestamp ); // DB -> TS_MW
1526  }
1527  }
1528 
1529  $tables = [ 'image' ];
1530  $fields = [
1531  'oi_name' => 'img_name',
1532  'oi_archive_name' => $dbw->addQuotes( $oldver ),
1533  'oi_size' => 'img_size',
1534  'oi_width' => 'img_width',
1535  'oi_height' => 'img_height',
1536  'oi_bits' => 'img_bits',
1537  'oi_description_id' => 'img_description_id',
1538  'oi_timestamp' => 'img_timestamp',
1539  'oi_metadata' => 'img_metadata',
1540  'oi_media_type' => 'img_media_type',
1541  'oi_major_mime' => 'img_major_mime',
1542  'oi_minor_mime' => 'img_minor_mime',
1543  'oi_sha1' => 'img_sha1',
1544  'oi_actor' => 'img_actor',
1545  ];
1546  $joins = [];
1547 
1548  # (T36993) Note: $oldver can be empty here, if the previous
1549  # version of the file was broken. Allow registration of the new
1550  # version to continue anyway, because that's better than having
1551  # an image that's not fixable by user operations.
1552  # Collision, this is an update of a file
1553  # Insert previous contents into oldimage
1554  $dbw->insertSelect( 'oldimage', $tables, $fields,
1555  [ 'img_name' => $this->getName() ], __METHOD__, [], [], $joins );
1556 
1557  # Update the current image row
1558  $dbw->update( 'image',
1559  [
1560  'img_size' => $this->size,
1561  'img_width' => intval( $this->width ),
1562  'img_height' => intval( $this->height ),
1563  'img_bits' => $this->bits,
1564  'img_media_type' => $this->media_type,
1565  'img_major_mime' => $this->major_mime,
1566  'img_minor_mime' => $this->minor_mime,
1567  'img_timestamp' => $timestamp,
1568  'img_metadata' => $dbw->encodeBlob( $this->metadata ),
1569  'img_sha1' => $this->sha1
1570  ] + $commentFields + $actorFields,
1571  [ 'img_name' => $this->getName() ],
1572  __METHOD__
1573  );
1574  }
1575 
1576  $descTitle = $this->getTitle();
1577  $descId = $descTitle->getArticleID();
1578  $wikiPage = new WikiFilePage( $descTitle );
1579  $wikiPage->setFile( $this );
1580 
1581  // Determine log action. If reupload is done by reverting, use a special log_action.
1582  if ( $revert === true ) {
1583  $logAction = 'revert';
1584  } elseif ( $reupload === true ) {
1585  $logAction = 'overwrite';
1586  } else {
1587  $logAction = 'upload';
1588  }
1589  // Add the log entry...
1590  $logEntry = new ManualLogEntry( 'upload', $logAction );
1591  $logEntry->setTimestamp( $this->timestamp );
1592  $logEntry->setPerformer( $user );
1593  $logEntry->setComment( $comment );
1594  $logEntry->setTarget( $descTitle );
1595  // Allow people using the api to associate log entries with the upload.
1596  // Log has a timestamp, but sometimes different from upload timestamp.
1597  $logEntry->setParameters(
1598  [
1599  'img_sha1' => $this->sha1,
1600  'img_timestamp' => $timestamp,
1601  ]
1602  );
1603  // Note we keep $logId around since during new image
1604  // creation, page doesn't exist yet, so log_page = 0
1605  // but we want it to point to the page we're making,
1606  // so we later modify the log entry.
1607  // For a similar reason, we avoid making an RC entry
1608  // now and wait until the page exists.
1609  $logId = $logEntry->insert();
1610 
1611  if ( $descTitle->exists() ) {
1612  // Use own context to get the action text in content language
1613  $formatter = LogFormatter::newFromEntry( $logEntry );
1614  $formatter->setContext( RequestContext::newExtraneousContext( $descTitle ) );
1615  $editSummary = $formatter->getPlainActionText();
1616 
1617  $nullRevision = $createNullRevision === false ? null : Revision::newNullRevision(
1618  $dbw,
1619  $descId,
1620  $editSummary,
1621  false,
1622  $user
1623  );
1624  if ( $nullRevision ) {
1625  $nullRevision->insertOn( $dbw );
1626  Hooks::run(
1627  'NewRevisionFromEditComplete',
1628  [ $wikiPage, $nullRevision, $nullRevision->getParentId(), $user ]
1629  );
1630  $wikiPage->updateRevisionOn( $dbw, $nullRevision );
1631  // Associate null revision id
1632  $logEntry->setAssociatedRevId( $nullRevision->getId() );
1633  }
1634 
1635  $newPageContent = null;
1636  } else {
1637  // Make the description page and RC log entry post-commit
1638  $newPageContent = ContentHandler::makeContent( $pageText, $descTitle );
1639  }
1640 
1641  # Defer purges, page creation, and link updates in case they error out.
1642  # The most important thing is that files and the DB registry stay synced.
1643  $dbw->endAtomic( __METHOD__ );
1644  $fname = __METHOD__;
1645 
1646  # Do some cache purges after final commit so that:
1647  # a) Changes are more likely to be seen post-purge
1648  # b) They won't cause rollback of the log publish/update above
1650  new AutoCommitUpdate(
1651  $dbw,
1652  __METHOD__,
1653  function () use (
1654  $reupload, $wikiPage, $newPageContent, $comment, $user,
1655  $logEntry, $logId, $descId, $tags, $fname
1656  ) {
1657  # Update memcache after the commit
1658  $this->invalidateCache();
1659 
1660  $updateLogPage = false;
1661  if ( $newPageContent ) {
1662  # New file page; create the description page.
1663  # There's already a log entry, so don't make a second RC entry
1664  # CDN and file cache for the description page are purged by doEditContent.
1665  $status = $wikiPage->doEditContent(
1666  $newPageContent,
1667  $comment,
1669  false,
1670  $user
1671  );
1672 
1673  if ( isset( $status->value['revision'] ) ) {
1675  $rev = $status->value['revision'];
1676  // Associate new page revision id
1677  $logEntry->setAssociatedRevId( $rev->getId() );
1678  }
1679  // This relies on the resetArticleID() call in WikiPage::insertOn(),
1680  // which is triggered on $descTitle by doEditContent() above.
1681  if ( isset( $status->value['revision'] ) ) {
1683  $rev = $status->value['revision'];
1684  $updateLogPage = $rev->getPage();
1685  }
1686  } else {
1687  # Existing file page: invalidate description page cache
1688  $wikiPage->getTitle()->invalidateCache();
1689  $wikiPage->getTitle()->purgeSquid();
1690  # Allow the new file version to be patrolled from the page footer
1692  }
1693 
1694  # Update associated rev id. This should be done by $logEntry->insert() earlier,
1695  # but setAssociatedRevId() wasn't called at that point yet...
1696  $logParams = $logEntry->getParameters();
1697  $logParams['associated_rev_id'] = $logEntry->getAssociatedRevId();
1698  $update = [ 'log_params' => LogEntryBase::makeParamBlob( $logParams ) ];
1699  if ( $updateLogPage ) {
1700  # Also log page, in case where we just created it above
1701  $update['log_page'] = $updateLogPage;
1702  }
1703  $this->getRepo()->getMasterDB()->update(
1704  'logging',
1705  $update,
1706  [ 'log_id' => $logId ],
1707  $fname
1708  );
1709  $this->getRepo()->getMasterDB()->insert(
1710  'log_search',
1711  [
1712  'ls_field' => 'associated_rev_id',
1713  'ls_value' => $logEntry->getAssociatedRevId(),
1714  'ls_log_id' => $logId,
1715  ],
1716  $fname
1717  );
1718 
1719  # Add change tags, if any
1720  if ( $tags ) {
1721  $logEntry->addTags( $tags );
1722  }
1723 
1724  # Uploads can be patrolled
1725  $logEntry->setIsPatrollable( true );
1726 
1727  # Now that the log entry is up-to-date, make an RC entry.
1728  $logEntry->publish( $logId );
1729 
1730  # Run hook for other updates (typically more cache purging)
1731  Hooks::run( 'FileUpload', [ $this, $reupload, !$newPageContent ] );
1732 
1733  if ( $reupload ) {
1734  # Delete old thumbnails
1735  $this->purgeThumbnails();
1736  # Remove the old file from the CDN cache
1738  new CdnCacheUpdate( [ $this->getUrl() ] ),
1740  );
1741  } else {
1742  # Update backlink pages pointing to this title if created
1744  $this->getTitle(),
1745  'imagelinks',
1746  'upload-image',
1747  $user->getName()
1748  );
1749  }
1750 
1751  $this->prerenderThumbnails();
1752  }
1753  ),
1755  );
1756 
1757  if ( !$reupload ) {
1758  # This is a new file, so update the image count
1759  DeferredUpdates::addUpdate( SiteStatsUpdate::factory( [ 'images' => 1 ] ) );
1760  }
1761 
1762  # Invalidate cache for all pages using this file
1764  new HTMLCacheUpdate( $this->getTitle(), 'imagelinks', 'file-upload' )
1765  );
1766 
1767  return Status::newGood();
1768  }
1769 
1785  function publish( $src, $flags = 0, array $options = [] ) {
1786  return $this->publishTo( $src, $this->getRel(), $flags, $options );
1787  }
1788 
1804  function publishTo( $src, $dstRel, $flags = 0, array $options = [] ) {
1805  $srcPath = ( $src instanceof FSFile ) ? $src->getPath() : $src;
1806 
1807  $repo = $this->getRepo();
1808  if ( $repo->getReadOnlyReason() !== false ) {
1809  return $this->readOnlyFatalStatus();
1810  }
1811 
1812  $this->lock();
1813 
1814  if ( $this->isOld() ) {
1815  $archiveRel = $dstRel;
1816  $archiveName = basename( $archiveRel );
1817  } else {
1818  $archiveName = wfTimestamp( TS_MW ) . '!' . $this->getName();
1819  $archiveRel = $this->getArchiveRel( $archiveName );
1820  }
1821 
1822  if ( $repo->hasSha1Storage() ) {
1823  $sha1 = FileRepo::isVirtualUrl( $srcPath )
1824  ? $repo->getFileSha1( $srcPath )
1825  : FSFile::getSha1Base36FromPath( $srcPath );
1827  $wrapperBackend = $repo->getBackend();
1828  '@phan-var FileBackendDBRepoWrapper $wrapperBackend';
1829  $dst = $wrapperBackend->getPathForSHA1( $sha1 );
1830  $status = $repo->quickImport( $src, $dst );
1831  if ( $flags & File::DELETE_SOURCE ) {
1832  unlink( $srcPath );
1833  }
1834 
1835  if ( $this->exists() ) {
1836  $status->value = $archiveName;
1837  }
1838  } else {
1839  $flags = $flags & File::DELETE_SOURCE ? LocalRepo::DELETE_SOURCE : 0;
1840  $status = $repo->publish( $srcPath, $dstRel, $archiveRel, $flags, $options );
1841 
1842  if ( $status->value == 'new' ) {
1843  $status->value = '';
1844  } else {
1845  $status->value = $archiveName;
1846  }
1847  }
1848 
1849  $this->unlock();
1850  return $status;
1851  }
1852 
1870  function move( $target ) {
1871  $localRepo = MediaWikiServices::getInstance()->getRepoGroup()->getLocalRepo();
1872  if ( $this->getRepo()->getReadOnlyReason() !== false ) {
1873  return $this->readOnlyFatalStatus();
1874  }
1875 
1876  wfDebugLog( 'imagemove', "Got request to move {$this->name} to " . $target->getText() );
1877  $batch = new LocalFileMoveBatch( $this, $target );
1878 
1879  $this->lock();
1880  $batch->addCurrent();
1881  $archiveNames = $batch->addOlds();
1882  $status = $batch->execute();
1883  $this->unlock();
1884 
1885  wfDebugLog( 'imagemove', "Finished moving {$this->name}" );
1886 
1887  // Purge the source and target files outside the transaction...
1888  $oldTitleFile = $localRepo->newFile( $this->title );
1889  $newTitleFile = $localRepo->newFile( $target );
1891  new AutoCommitUpdate(
1892  $this->getRepo()->getMasterDB(),
1893  __METHOD__,
1894  function () use ( $oldTitleFile, $newTitleFile, $archiveNames ) {
1895  $oldTitleFile->purgeEverything();
1896  foreach ( $archiveNames as $archiveName ) {
1898  '@phan-var OldLocalFile $oldTitleFile';
1899  $oldTitleFile->purgeOldThumbnails( $archiveName );
1900  }
1901  $newTitleFile->purgeEverything();
1902  }
1903  ),
1905  );
1906 
1907  if ( $status->isOK() ) {
1908  // Now switch the object
1909  $this->title = $target;
1910  // Force regeneration of the name and hashpath
1911  $this->name = null;
1912  $this->hashPath = null;
1913  }
1914 
1915  return $status;
1916  }
1917 
1931  function delete( $reason, $suppress = false, $user = null ) {
1932  if ( $this->getRepo()->getReadOnlyReason() !== false ) {
1933  return $this->readOnlyFatalStatus();
1934  }
1935 
1936  $batch = new LocalFileDeleteBatch( $this, $reason, $suppress, $user );
1937 
1938  $this->lock();
1939  $batch->addCurrent();
1940  // Get old version relative paths
1941  $archiveNames = $batch->addOlds();
1942  $status = $batch->execute();
1943  $this->unlock();
1944 
1945  if ( $status->isOK() ) {
1946  DeferredUpdates::addUpdate( SiteStatsUpdate::factory( [ 'images' => -1 ] ) );
1947  }
1948 
1949  // To avoid slow purges in the transaction, move them outside...
1951  new AutoCommitUpdate(
1952  $this->getRepo()->getMasterDB(),
1953  __METHOD__,
1954  function () use ( $archiveNames ) {
1955  $this->purgeEverything();
1956  foreach ( $archiveNames as $archiveName ) {
1957  $this->purgeOldThumbnails( $archiveName );
1958  }
1959  }
1960  ),
1962  );
1963 
1964  // Purge the CDN
1965  $purgeUrls = [];
1966  foreach ( $archiveNames as $archiveName ) {
1967  $purgeUrls[] = $this->getArchiveUrl( $archiveName );
1968  }
1970 
1971  return $status;
1972  }
1973 
1989  function deleteOld( $archiveName, $reason, $suppress = false, $user = null ) {
1990  if ( $this->getRepo()->getReadOnlyReason() !== false ) {
1991  return $this->readOnlyFatalStatus();
1992  }
1993 
1994  $batch = new LocalFileDeleteBatch( $this, $reason, $suppress, $user );
1995 
1996  $this->lock();
1997  $batch->addOld( $archiveName );
1998  $status = $batch->execute();
1999  $this->unlock();
2000 
2001  $this->purgeOldThumbnails( $archiveName );
2002  if ( $status->isOK() ) {
2003  $this->purgeDescription();
2004  }
2005 
2007  new CdnCacheUpdate( [ $this->getArchiveUrl( $archiveName ) ] ),
2009  );
2010 
2011  return $status;
2012  }
2013 
2025  function restore( $versions = [], $unsuppress = false ) {
2026  if ( $this->getRepo()->getReadOnlyReason() !== false ) {
2027  return $this->readOnlyFatalStatus();
2028  }
2029 
2030  $batch = new LocalFileRestoreBatch( $this, $unsuppress );
2031 
2032  $this->lock();
2033  if ( !$versions ) {
2034  $batch->addAll();
2035  } else {
2036  $batch->addIds( $versions );
2037  }
2038  $status = $batch->execute();
2039  if ( $status->isGood() ) {
2040  $cleanupStatus = $batch->cleanup();
2041  $cleanupStatus->successCount = 0;
2042  $cleanupStatus->failCount = 0;
2043  $status->merge( $cleanupStatus );
2044  }
2045 
2046  $this->unlock();
2047  return $status;
2048  }
2049 
2059  function getDescriptionUrl() {
2060  if ( !$this->title ) {
2061  return false; // Avoid hard failure when the file does not exist. T221812
2062  }
2063 
2064  return $this->title->getLocalURL();
2065  }
2066 
2075  function getDescriptionText( Language $lang = null ) {
2076  if ( !$this->title ) {
2077  return false; // Avoid hard failure when the file does not exist. T221812
2078  }
2079 
2080  $store = MediaWikiServices::getInstance()->getRevisionStore();
2081  $revision = $store->getRevisionByTitle( $this->title, 0, Revision::READ_NORMAL );
2082  if ( !$revision ) {
2083  return false;
2084  }
2085 
2086  $renderer = MediaWikiServices::getInstance()->getRevisionRenderer();
2087  $rendered = $renderer->getRenderedRevision( $revision, new ParserOptions( null, $lang ) );
2088 
2089  if ( !$rendered ) {
2090  // audience check failed
2091  return false;
2092  }
2093 
2094  $pout = $rendered->getRevisionParserOutput();
2095  return $pout->getText();
2096  }
2097 
2103  function getDescription( $audience = self::FOR_PUBLIC, User $user = null ) {
2104  $this->load();
2105  if ( $audience == self::FOR_PUBLIC && $this->isDeleted( self::DELETED_COMMENT ) ) {
2106  return '';
2107  } elseif ( $audience == self::FOR_THIS_USER
2108  && !$this->userCan( self::DELETED_COMMENT, $user )
2109  ) {
2110  return '';
2111  } else {
2112  return $this->description;
2113  }
2114  }
2115 
2119  function getTimestamp() {
2120  $this->load();
2121 
2122  return $this->timestamp;
2123  }
2124 
2128  public function getDescriptionTouched() {
2129  if ( !$this->exists() ) {
2130  return false; // Avoid hard failure when the file does not exist. T221812
2131  }
2132 
2133  // The DB lookup might return false, e.g. if the file was just deleted, or the shared DB repo
2134  // itself gets it from elsewhere. To avoid repeating the DB lookups in such a case, we
2135  // need to differentiate between null (uninitialized) and false (failed to load).
2136  if ( $this->descriptionTouched === null ) {
2137  $cond = [
2138  'page_namespace' => $this->title->getNamespace(),
2139  'page_title' => $this->title->getDBkey()
2140  ];
2141  $touched = $this->repo->getReplicaDB()->selectField( 'page', 'page_touched', $cond, __METHOD__ );
2142  $this->descriptionTouched = $touched ? wfTimestamp( TS_MW, $touched ) : false;
2143  }
2144 
2146  }
2147 
2151  function getSha1() {
2152  $this->load();
2153  // Initialise now if necessary
2154  if ( $this->sha1 == '' && $this->fileExists ) {
2155  $this->lock();
2156 
2157  $this->sha1 = $this->repo->getFileSha1( $this->getPath() );
2158  if ( !wfReadOnly() && strval( $this->sha1 ) != '' ) {
2159  $dbw = $this->repo->getMasterDB();
2160  $dbw->update( 'image',
2161  [ 'img_sha1' => $this->sha1 ],
2162  [ 'img_name' => $this->getName() ],
2163  __METHOD__ );
2164  $this->invalidateCache();
2165  }
2166 
2167  $this->unlock();
2168  }
2169 
2170  return $this->sha1;
2171  }
2172 
2176  function isCacheable() {
2177  $this->load();
2178 
2179  // If extra data (metadata) was not loaded then it must have been large
2180  return $this->extraDataLoaded
2181  && strlen( serialize( $this->metadata ) ) <= self::CACHE_FIELD_MAX_LEN;
2182  }
2183 
2188  public function acquireFileLock() {
2189  return Status::wrap( $this->getRepo()->getBackend()->lockFiles(
2190  [ $this->getPath() ], LockManager::LOCK_EX, 10
2191  ) );
2192  }
2193 
2198  public function releaseFileLock() {
2199  return Status::wrap( $this->getRepo()->getBackend()->unlockFiles(
2200  [ $this->getPath() ], LockManager::LOCK_EX
2201  ) );
2202  }
2203 
2213  public function lock() {
2214  if ( !$this->locked ) {
2215  $logger = LoggerFactory::getInstance( 'LocalFile' );
2216 
2217  $dbw = $this->repo->getMasterDB();
2218  $makesTransaction = !$dbw->trxLevel();
2219  $dbw->startAtomic( self::ATOMIC_SECTION_LOCK );
2220  // T56736: use simple lock to handle when the file does not exist.
2221  // SELECT FOR UPDATE prevents changes, not other SELECTs with FOR UPDATE.
2222  // Also, that would cause contention on INSERT of similarly named rows.
2223  $status = $this->acquireFileLock(); // represents all versions of the file
2224  if ( !$status->isGood() ) {
2225  $dbw->endAtomic( self::ATOMIC_SECTION_LOCK );
2226  $logger->warning( "Failed to lock '{file}'", [ 'file' => $this->name ] );
2227 
2228  throw new LocalFileLockError( $status );
2229  }
2230  // Release the lock *after* commit to avoid row-level contention.
2231  // Make sure it triggers on rollback() as well as commit() (T132921).
2232  $dbw->onTransactionResolution(
2233  function () use ( $logger ) {
2234  $status = $this->releaseFileLock();
2235  if ( !$status->isGood() ) {
2236  $logger->error( "Failed to unlock '{file}'", [ 'file' => $this->name ] );
2237  }
2238  },
2239  __METHOD__
2240  );
2241  // Callers might care if the SELECT snapshot is safely fresh
2242  $this->lockedOwnTrx = $makesTransaction;
2243  }
2244 
2245  $this->locked++;
2246 
2247  return $this->lockedOwnTrx;
2248  }
2249 
2258  public function unlock() {
2259  if ( $this->locked ) {
2260  --$this->locked;
2261  if ( !$this->locked ) {
2262  $dbw = $this->repo->getMasterDB();
2263  $dbw->endAtomic( self::ATOMIC_SECTION_LOCK );
2264  $this->lockedOwnTrx = false;
2265  }
2266  }
2267  }
2268 
2272  protected function readOnlyFatalStatus() {
2273  return $this->getRepo()->newFatal( 'filereadonlyerror', $this->getName(),
2274  $this->getRepo()->getName(), $this->getRepo()->getReadOnlyReason() );
2275  }
2276 
2280  function __destruct() {
2281  $this->unlock();
2282  }
2283 }
static newFatal( $message,... $parameters)
Factory function for fatal errors.
Definition: StatusValue.php:69
if(PHP_SAPI !='cli-server') if(!isset( $_SERVER['SCRIPT_FILENAME'])) $file
Item class for a filearchive table row.
Definition: router.php:42
exists()
canRender inherited
Definition: LocalFile.php:969
User $user
Uploader.
Definition: LocalFile.php:116
invalidateCache()
Purge the file object/metadata cache.
Definition: LocalFile.php:357
getCacheFields( $prefix='img_')
Returns the list of object properties that are included as-is in the cache.
Definition: LocalFile.php:385
MediaHandler $handler
Definition: File.php:124
addWatch( $title, $checkRights=self::CHECK_USER_RIGHTS)
Watch an article.
Definition: User.php:3659
string $media_type
MEDIATYPE_xxx (bitmap, drawing, audio...)
Definition: LocalFile.php:74
bool $extraDataLoaded
Whether or not lazy-loaded data has been loaded from the database.
Definition: LocalFile.php:92
static getInitialPageText( $comment='', $license='', $copyStatus='', $source='', Config $config=null)
Get the initial image page text based on a comment and optional file status information.
assertTitleDefined()
Assert that $this->title is set to a Title.
Definition: File.php:2294
const VERSION
Definition: LocalFile.php:57
serialize()
unprefixRow( $row, $prefix='img_')
Definition: LocalFile.php:532
string $minor_mime
Minor MIME type.
Definition: LocalFile.php:110
getArchiveThumbUrl( $archiveName, $suffix=false)
Get the URL of the archived file&#39;s thumbs, or a particular thumb if $suffix is specified.
Definition: File.php:1682
purgeMetadataCache()
Refresh metadata in memcached, but don&#39;t touch thumbnails or CDN.
Definition: LocalFile.php:1013
releaseFileLock()
Definition: LocalFile.php:2198
const DELETE_SOURCE
Definition: File.php:76
if(!isset( $args[0])) $lang
static newFromEntry(LogEntry $entry)
Constructs a new formatter suitable for given entry.
IResultWrapper null $historyRes
Result of the query for the file&#39;s history (nextHistoryLine)
Definition: LocalFile.php:104
const DELETE_SOURCE
Definition: FileRepo.php:40
getSize()
Returns the size of the image file, in bytes.
Definition: LocalFile.php:932
Handles purging the appropriate CDN objects given a list of URLs or Title instances.
static newFromAnyId( $userId, $userName, $actorId, $dbDomain=false)
Static factory method for creation from an ID, name, and/or actor ID.
Definition: User.php:596
setProps( $info)
Set properties in this object to be equal to those given in the associative array $info...
Definition: LocalFile.php:739
getThumbnails( $archiveName=false)
getTransformScript inherited
Definition: LocalFile.php:990
Helper class for file undeletion.
unlock()
Decrement the lock reference count and end the atomic section if it reaches zero. ...
Definition: LocalFile.php:2258
string $major_mime
Major MIME type.
Definition: LocalFile.php:107
string $sha1
SHA-1 base 36 content hash.
Definition: LocalFile.php:86
$source
isMissing()
splitMime inherited
Definition: LocalFile.php:781
assertRepoDefined()
Assert that $this->repo is set to a valid FileRepo instance.
Definition: File.php:2284
move( $target)
getLinksTo inherited
Definition: LocalFile.php:1870
getName()
Return the name of this file.
Definition: File.php:307
string $name
The name of a file from its title object.
Definition: File.php:133
getRepo()
Returns the repository.
Definition: File.php:1879
purgeThumbnails( $options=[])
Delete cached transformed files for the current version only.
Definition: LocalFile.php:1067
getArchiveUrl( $suffix=false)
Get the URL of the archive directory, or a particular file if $suffix is specified.
Definition: File.php:1662
getPrefixedText()
Get the prefixed title with spaces.
Definition: Title.php:1858
quickImport( $src, $dst, $options=null)
Import a file from the local file system into the repo.
Definition: FileRepo.php:995
isOld()
Returns true if the image is an old version STUB.
Definition: File.php:1889
const METADATA_BAD
static isStoragePath( $path)
Check if a given path is a "mwstore://" path.
int $bits
Returned by getimagesize (loadFromXxx)
Definition: LocalFile.php:71
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:1205
loadFromDB( $flags=0)
Load file metadata from the DB.
Definition: LocalFile.php:422
const ATOMIC_SECTION_LOCK
Definition: LocalFile.php:142
bool $fileExists
Does the file exist on disk? (loadFromXxx)
Definition: LocalFile.php:62
getTitle()
Return the associated title object.
Definition: File.php:336
Title string bool $title
Definition: File.php:109
getName()
Get the user name, or the IP of an anonymous user.
Definition: User.php:2229
restore( $versions=[], $unsuppress=false)
Restore all or specified deleted revisions to the given file.
Definition: LocalFile.php:2025
purgeEverything()
Purge metadata and all affected pages when the file is created, deleted, or majorly updated...
Definition: File.php:1466
__destruct()
Clean up any dangling locks.
Definition: LocalFile.php:2280
getBackend()
Get the file backend instance.
Definition: FileRepo.php:225
static newFromRow( $row, $repo)
Create a LocalFile from a title Do not call this except from inside a repo class. ...
Definition: LocalFile.php:169
getThumbnails()
Get all thumbnail names previously generated for this file STUB Overridden by LocalFile.
Definition: File.php:1435
getVirtualUrl( $suffix=false)
Get the public zone virtual URL for a current version source file.
Definition: File.php:1738
getDescriptionShortUrl()
Get short description URL for a file based on the page ID.
Definition: LocalFile.php:894
nextHistoryLine()
Returns the history of this file, line by line.
Definition: LocalFile.php:1227
The User object encapsulates all of the user-specific settings (user_id, name, rights, email address, options, last login time).
Definition: User.php:51
wfTimestamp( $outputtype=TS_UNIX, $ts=0)
Get a timestamp string in one of various formats.
publish( $src, $flags=0, array $options=[])
Move or copy a file to its public location.
Definition: LocalFile.php:1785
getPath()
Return the storage path to the file.
Definition: File.php:427
getHistory( $limit=null, $start=null, $end=null, $inc=true)
purgeDescription inherited
Definition: LocalFile.php:1167
bool $locked
True if the image row is locked.
Definition: LocalFile.php:131
static addCallableUpdate( $callable, $stage=self::POSTSEND, $dbw=null)
Add a callable update.
getReadOnlyReason()
Get an explanatory message if this repo is read-only.
Definition: FileRepo.php:235
wfReadOnly()
Check whether the wiki is in read-only mode.
string $metadata
Handler-specific metadata.
Definition: LocalFile.php:83
string $descriptionTouched
TS_MW timestamp of the last change of the file description.
Definition: LocalFile.php:122
static newMigration()
Static constructor.
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
string $repoClass
Definition: LocalFile.php:98
getThumbUrl( $suffix=false)
Get the URL of the thumbnail directory, or a particular file if $suffix is specified.
Definition: File.php:1718
File backend exception for checked exceptions (e.g.
isVectorized()
Return true if the file is vectorized.
Definition: File.php:565
Class to invalidate the HTML/file cache of all the pages linking to a given title.
isMultipage()
Returns &#39;true&#39; if this file is a type which supports multiple pages, e.g.
Definition: File.php:1983
getLazyCacheFields( $prefix='img_')
Returns the list of object properties that are included as-is in the cache, only when they&#39;re not too...
Definition: LocalFile.php:407
const LOAD_ALL
Definition: LocalFile.php:140
getHandler()
Get a MediaHandler instance for this file.
Definition: File.php:1390
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
static factory(array $deltas)
getDescriptionTouched()
Definition: LocalFile.php:2128
const LOCK_EX
Definition: LockManager.php:70
getFileSha1( $virtualUrl)
Get the sha1 (base 36) of a file with a given virtual URL/storage path.
Definition: FileRepo.php:1618
getRel()
Get the path of the file relative to the public zone root.
Definition: File.php:1541
userCan( $field, User $user=null)
Determine if the current user is allowed to view a particular field of this file, if it&#39;s marked as d...
Definition: File.php:2172
static newGood( $value=null)
Factory function for good results.
Definition: StatusValue.php:81
upload( $src, $comment, $pageText, $flags=0, $props=false, $timestamp=false, $user=null, $tags=[], $createNullRevision=true, $revert=false)
getHashPath inherited
Definition: LocalFile.php:1315
wfDebug( $text, $dest='all', array $context=[])
Sends a line to the debug log if enabled or, optionally, to a comment in output.
purgeCache( $options=[])
Delete all previously generated thumbnails, refresh metadata in memcached and purge the CDN...
Definition: LocalFile.php:1024
$wgUploadThumbnailRenderMap
When defined, is an array of thumbnail widths to be rendered at upload time.
acquireFileLock()
Definition: LocalFile.php:2188
static purgePatrolFooterCache( $articleID)
Purge the cache used to check if it is worth showing the patrol footer For example, it is done during re-uploads when file patrol is used.
Definition: Article.php:1361
const CACHE_FIELD_MAX_LEN
Definition: LocalFile.php:59
loadFromRow( $row, $prefix='img_')
Load file metadata from a DB result row.
Definition: LocalFile.php:603
static isVirtualUrl( $url)
Determine if a string is an mwrepo:// URL.
Definition: FileRepo.php:264
loadExtraFromDB()
Load lazy file metadata from the DB.
Definition: LocalFile.php:454
$cache
Definition: mcc.php:33
const EDIT_SUPPRESS_RC
Definition: Defines.php:135
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
maybeUpgradeRow()
Upgrade a row if it needs it.
Definition: LocalFile.php:638
readOnlyFatalStatus()
Definition: LocalFile.php:2272
Helper class for file movement.
bool $upgraded
Whether the row was upgraded on load.
Definition: LocalFile.php:125
static newExtraneousContext(Title $title, $request=[])
Create a new extraneous context.
bool $lockedOwnTrx
True if the image row is locked with a lock initiated transaction.
Definition: LocalFile.php:134
bool $dataLoaded
Whether or not core data has been loaded from the database (loadFromXxx)
Definition: LocalFile.php:89
const NS_FILE
Definition: Defines.php:66
FileRepo LocalRepo ForeignAPIRepo bool $repo
Some member variables can be lazy-initialised using __get().
Definition: File.php:106
prerenderThumbnails()
Prerenders a configurable set of thumbnails.
Definition: LocalFile.php:1100
getMetadata()
Get handler-specific metadata.
Definition: LocalFile.php:914
purgeOldThumbnails( $archiveName)
Delete cached transformed files for an archived version only.
Definition: LocalFile.php:1043
string $mime
MIME type, determined by MimeAnalyzer::guessMimeType.
Definition: LocalFile.php:77
static getSha1Base36FromPath( $path)
Get a SHA-1 hash of a file in the local filesystem, in base-36 lower case encoding, zero padded to 31 digits.
Definition: FSFile.php:225
Special handling for file pages.
Helper class for file deletion.
purgeThumbList( $dir, $files)
Delete a list of thumbnails visible at urls.
Definition: LocalFile.php:1127
getDescription( $audience=self::FOR_PUBLIC, User $user=null)
Definition: LocalFile.php:2103
static wrap( $sv)
Succinct helper method to wrap a StatusValue.
Definition: Status.php:55
static getQueryInfo(array $options=[])
Return the tables, fields, and join conditions to be selected to create a new oldlocalfile object...
getArchiveRel( $suffix=false)
Get the path of an archived file relative to the public zone root.
Definition: File.php:1552
getDescriptionText(Language $lang=null)
Get the HTML text of the description page This is not used by ImagePage for local files...
Definition: LocalFile.php:2075
static makeTitle( $ns, $title, $fragment='', $interwiki='')
Create a new Title from a namespace index and a DB key.
Definition: Title.php:586
static queueRecursiveJobsForTable(Title $title, $table, $action='unknown', $userName='unknown')
Queue a RefreshLinks job for any table.
getArchiveThumbPath( $archiveName, $suffix=false)
Get the path of an archived file&#39;s thumbs, or a particular thumb if $suffix is specified.
Definition: File.php:1625
isMetadataValid( $image, $metadata)
Check if the metadata string is valid for this handler.
static addUpdate(DeferrableUpdate $update, $stage=self::POSTSEND)
Add an update to the deferred list to be run later by execute()
upgradeRow()
Fix assorted version-related problems with the image row by reloading it from the file...
Definition: LocalFile.php:684
getContentHeaders( $metadata)
Get useful response headers for GET/HEAD requests for a file with the given metadata.
getId()
Get the user&#39;s ID.
Definition: User.php:2200
Class representing a non-directory file on the file system.
Definition: FSFile.php:32
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:1438
deleteOld( $archiveName, $reason, $suppress=false, $user=null)
Delete an old version of the file.
Definition: LocalFile.php:1989
getActorId(IDatabase $dbw=null)
Get the user&#39;s actor ID.
Definition: User.php:2268
const EDIT_NEW
Definition: Defines.php:132
Job for asynchronous rendering of thumbnails.
bool $upgrading
Whether the row was scheduled to upgrade on load.
Definition: LocalFile.php:128
lock()
Start an atomic DB section and lock the image for update or increments a reference counter if the loc...
Definition: LocalFile.php:2213
string $url
The URL corresponding to one of the four basic zones.
Definition: File.php:127
int $deleted
Bitfield akin to rev_deleted.
Definition: LocalFile.php:95
wfDebugLog( $logGroup, $text, $dest='all', array $context=[])
Send a line to a supplementary debug log file, if configured, or main debug log if not...
loadExtraFieldsWithTimestamp( $dbr, $fname)
Definition: LocalFile.php:483
filterThumbnailPurgeList(&$files, $options)
Remove files from the purge list.
getUrl()
Return the URL of the file.
Definition: File.php:358
bool $missing
True if file is not present in file system.
Definition: LocalFile.php:137
const METADATA_COMPATIBLE
getDescriptionUrl()
isMultipage inherited
Definition: LocalFile.php:2059
getPageDimensions(File $image, $page)
Get an associative array of page dimensions Currently "width" and "height" are understood, but this might be expanded in the future.
Class to represent a local file in the wiki&#39;s own database.
Definition: LocalFile.php:56
MimeMagic helper wrapper.
Definition: MWFileProps.php:28
static singleton( $domain=false)
string $timestamp
Upload timestamp.
Definition: LocalFile.php:113
getHeight( $page=1)
Return the height of the image.
Definition: LocalFile.php:828
int $height
Image height.
Definition: LocalFile.php:68
loadFromFile()
Load metadata from the file itself.
Definition: LocalFile.php:374
string $description
Description of current revision of the file.
Definition: LocalFile.php:119
publishTo( $src, $dstRel, $flags=0, array $options=[])
Move or copy a file to a specified location.
Definition: LocalFile.php:1804
static getQueryInfo(array $options=[])
Return the tables, fields, and join conditions to be selected to create a new localfile object...
Definition: LocalFile.php:216
static newFromName( $name, $validate='valid')
Static factory method for creation from username.
Definition: User.php:515
Implements some public methods and some protected utility functions which are required by multiple ch...
Definition: File.php:61
static getHandler( $type)
Get a MediaHandler for a given MIME type from the instance cache.
static makeContent( $text, Title $title=null, $modelId=null, $format=null)
Convenience function for creating a Content object from a given textual representation.
getMediaType()
Returns the type of the media in the file.
Definition: LocalFile.php:953
loadFromCache()
Try to load file metadata from memcached, falling back to the database.
Definition: LocalFile.php:288
getMutableCacheKeys(WANObjectCache $cache)
Definition: LocalFile.php:281
int $width
Image width.
Definition: LocalFile.php:65
getMimeType()
Returns the MIME type of the file.
Definition: LocalFile.php:942
resetHistory()
Reset the history pointer to the first element of the history.
Definition: LocalFile.php:1274
static newNullRevision( $dbw, $pageId, $summary, $minor, $user=null)
Create a new null-revision for insertion into a page&#39;s history.
Definition: Revision.php:994
int $size
Size in bytes (loadFromXxx)
Definition: LocalFile.php:80
hasSha1Storage()
Returns whether or not storage is SHA-1 based.
Definition: FileRepo.php:1946
isDeleted( $field)
Is this file a "deleted" file in a private archive? STUB.
Definition: File.php:1900
getWidth( $page=1)
Return the width of the image.
Definition: LocalFile.php:796
load( $flags=0)
Load file metadata from cache or DB, unless already loaded.
Definition: LocalFile.php:620
purgeDescription()
Purge the file description page, but don&#39;t go after pages using the file.
Definition: File.php:1454
getThumbPath( $suffix=false)
Get the path of the thumbnail directory, or a particular file if $suffix is specified.
Definition: File.php:1638
$wgUpdateCompatibleMetadata
If to automatically update the img_metadata field if the metadata field is outdated but compatible wi...
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
getUser( $type='text')
Returns user who uploaded the file.
Definition: LocalFile.php:861
static run( $event, array $args=[], $deprecatedVersion=null)
Call hook functions defined in Hooks::register and $wgHooks.
Definition: Hooks.php:200
int $historyLine
Number of line to return by nextHistoryLine() (constructor)
Definition: LocalFile.php:101
__construct( $title, $repo)
Do not call this except from inside a repo class.
Definition: LocalFile.php:254
static makeParamBlob( $params)
Create a blob from a parameter array.
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:1404
static newFromText( $text, $defaultNamespace=NS_MAIN)
Create a new Title from text, such as what one would find in a link.
Definition: Title.php:319