MediaWiki  master
LocalFile.php
Go to the documentation of this file.
1 <?php
29 
55 class LocalFile extends File {
56  const VERSION = 11; // cache version
57 
58  const CACHE_FIELD_MAX_LEN = 1000;
59 
61  protected $fileExists;
62 
64  protected $width;
65 
67  protected $height;
68 
70  protected $bits;
71 
73  protected $media_type;
74 
76  protected $mime;
77 
79  protected $size;
80 
82  protected $metadata;
83 
85  protected $sha1;
86 
88  protected $dataLoaded;
89 
91  protected $extraDataLoaded;
92 
94  protected $deleted;
95 
98 
100  private $historyLine;
101 
103  private $historyRes;
104 
106  private $major_mime;
107 
109  private $minor_mime;
110 
112  private $timestamp;
113 
115  private $user;
116 
118  private $description;
119 
122 
124  private $upgraded;
125 
127  private $upgrading;
128 
130  private $locked;
131 
133  private $lockedOwnTrx;
134 
136  private $missing;
137 
138  // @note: higher than IDBAccessObject constants
139  const LOAD_ALL = 16; // integer; load all the lazy fields too (like metadata)
140 
141  const ATOMIC_SECTION_LOCK = 'LocalFile::lockingTransaction';
142 
155  static function newFromTitle( $title, $repo, $unused = null ) {
156  return new static( $title, $repo );
157  }
158 
168  static function newFromRow( $row, $repo ) {
169  $title = Title::makeTitle( NS_FILE, $row->img_name );
170  $file = new static( $title, $repo );
171  $file->loadFromRow( $row );
172 
173  return $file;
174  }
175 
185  static function newFromKey( $sha1, $repo, $timestamp = false ) {
186  $dbr = $repo->getReplicaDB();
187 
188  $conds = [ 'img_sha1' => $sha1 ];
189  if ( $timestamp ) {
190  $conds['img_timestamp'] = $dbr->timestamp( $timestamp );
191  }
192 
193  $fileQuery = static::getQueryInfo();
194  $row = $dbr->selectRow(
195  $fileQuery['tables'], $fileQuery['fields'], $conds, __METHOD__, [], $fileQuery['joins']
196  );
197  if ( $row ) {
198  return static::newFromRow( $row, $repo );
199  } else {
200  return false;
201  }
202  }
203 
209  static function selectFields() {
211 
212  wfDeprecated( __METHOD__, '1.31' );
213  if ( $wgActorTableSchemaMigrationStage & SCHEMA_COMPAT_READ_NEW ) {
214  // If code is using this instead of self::getQueryInfo(), there's a
215  // decent chance it's going to try to directly access
216  // $row->img_user or $row->img_user_text and we can't give it
217  // useful values here once those aren't being used anymore.
218  throw new BadMethodCallException(
219  'Cannot use ' . __METHOD__
220  . ' when $wgActorTableSchemaMigrationStage has SCHEMA_COMPAT_READ_NEW'
221  );
222  }
223 
224  return [
225  'img_name',
226  'img_size',
227  'img_width',
228  'img_height',
229  'img_metadata',
230  'img_bits',
231  'img_media_type',
232  'img_major_mime',
233  'img_minor_mime',
234  'img_user',
235  'img_user_text',
236  'img_actor' => 'NULL',
237  'img_timestamp',
238  'img_sha1',
239  ] + MediaWikiServices::getInstance()->getCommentStore()->getFields( 'img_description' );
240  }
241 
253  public static function getQueryInfo( array $options = [] ) {
254  $commentQuery = MediaWikiServices::getInstance()->getCommentStore()->getJoin( 'img_description' );
255  $actorQuery = ActorMigration::newMigration()->getJoin( 'img_user' );
256  $ret = [
257  'tables' => [ 'image' ] + $commentQuery['tables'] + $actorQuery['tables'],
258  'fields' => [
259  'img_name',
260  'img_size',
261  'img_width',
262  'img_height',
263  'img_metadata',
264  'img_bits',
265  'img_media_type',
266  'img_major_mime',
267  'img_minor_mime',
268  'img_timestamp',
269  'img_sha1',
270  ] + $commentQuery['fields'] + $actorQuery['fields'],
271  'joins' => $commentQuery['joins'] + $actorQuery['joins'],
272  ];
273 
274  if ( in_array( 'omit-nonlazy', $options, true ) ) {
275  // Internal use only for getting only the lazy fields
276  $ret['fields'] = [];
277  }
278  if ( !in_array( 'omit-lazy', $options, true ) ) {
279  // Note: Keep this in sync with self::getLazyCacheFields()
280  $ret['fields'][] = 'img_metadata';
281  }
282 
283  return $ret;
284  }
285 
291  function __construct( $title, $repo ) {
292  parent::__construct( $title, $repo );
293 
294  $this->metadata = '';
295  $this->historyLine = 0;
296  $this->historyRes = null;
297  $this->dataLoaded = false;
298  $this->extraDataLoaded = false;
299 
300  $this->assertRepoDefined();
301  $this->assertTitleDefined();
302  }
303 
309  function getCacheKey() {
310  return $this->repo->getSharedCacheKey( 'file', sha1( $this->getName() ) );
311  }
312 
319  return [ $this->getCacheKey() ];
320  }
321 
325  private function loadFromCache() {
326  $this->dataLoaded = false;
327  $this->extraDataLoaded = false;
328 
329  $key = $this->getCacheKey();
330  if ( !$key ) {
331  $this->loadFromDB( self::READ_NORMAL );
332 
333  return;
334  }
335 
336  $cache = MediaWikiServices::getInstance()->getMainWANObjectCache();
337  $cachedValues = $cache->getWithSetCallback(
338  $key,
339  $cache::TTL_WEEK,
340  function ( $oldValue, &$ttl, array &$setOpts ) use ( $cache ) {
341  $setOpts += Database::getCacheSetOptions( $this->repo->getReplicaDB() );
342 
343  $this->loadFromDB( self::READ_NORMAL );
344 
345  $fields = $this->getCacheFields( '' );
346  $cacheVal['fileExists'] = $this->fileExists;
347  if ( $this->fileExists ) {
348  foreach ( $fields as $field ) {
349  $cacheVal[$field] = $this->$field;
350  }
351  }
352  $cacheVal['user'] = $this->user ? $this->user->getId() : 0;
353  $cacheVal['user_text'] = $this->user ? $this->user->getName() : '';
354  $cacheVal['actor'] = $this->user ? $this->user->getActorId() : null;
355 
356  // Strip off excessive entries from the subset of fields that can become large.
357  // If the cache value gets to large it will not fit in memcached and nothing will
358  // get cached at all, causing master queries for any file access.
359  foreach ( $this->getLazyCacheFields( '' ) as $field ) {
360  if ( isset( $cacheVal[$field] )
361  && strlen( $cacheVal[$field] ) > 100 * 1024
362  ) {
363  unset( $cacheVal[$field] ); // don't let the value get too big
364  }
365  }
366 
367  if ( $this->fileExists ) {
368  $ttl = $cache->adaptiveTTL( wfTimestamp( TS_UNIX, $this->timestamp ), $ttl );
369  } else {
370  $ttl = $cache::TTL_DAY;
371  }
372 
373  return $cacheVal;
374  },
375  [ 'version' => self::VERSION ]
376  );
377 
378  $this->fileExists = $cachedValues['fileExists'];
379  if ( $this->fileExists ) {
380  $this->setProps( $cachedValues );
381  }
382 
383  $this->dataLoaded = true;
384  $this->extraDataLoaded = true;
385  foreach ( $this->getLazyCacheFields( '' ) as $field ) {
386  $this->extraDataLoaded = $this->extraDataLoaded && isset( $cachedValues[$field] );
387  }
388  }
389 
393  public function invalidateCache() {
394  $key = $this->getCacheKey();
395  if ( !$key ) {
396  return;
397  }
398 
399  $this->repo->getMasterDB()->onTransactionPreCommitOrIdle(
400  function () use ( $key ) {
401  MediaWikiServices::getInstance()->getMainWANObjectCache()->delete( $key );
402  },
403  __METHOD__
404  );
405  }
406 
410  function loadFromFile() {
411  $props = $this->repo->getFileProps( $this->getVirtualUrl() );
412  $this->setProps( $props );
413  }
414 
421  protected function getCacheFields( $prefix = 'img_' ) {
422  if ( $prefix !== '' ) {
423  throw new InvalidArgumentException(
424  __METHOD__ . ' with a non-empty prefix is no longer supported.'
425  );
426  }
427 
428  // See self::getQueryInfo() for the fetching of the data from the DB,
429  // self::loadFromRow() for the loading of the object from the DB row,
430  // and self::loadFromCache() for the caching, and self::setProps() for
431  // populating the object from an array of data.
432  return [ 'size', 'width', 'height', 'bits', 'media_type',
433  'major_mime', 'minor_mime', 'metadata', 'timestamp', 'sha1', 'description' ];
434  }
435 
443  protected function getLazyCacheFields( $prefix = 'img_' ) {
444  if ( $prefix !== '' ) {
445  throw new InvalidArgumentException(
446  __METHOD__ . ' with a non-empty prefix is no longer supported.'
447  );
448  }
449 
450  // Keep this in sync with the omit-lazy option in self::getQueryInfo().
451  return [ 'metadata' ];
452  }
453 
458  function loadFromDB( $flags = 0 ) {
459  $fname = static::class . '::' . __FUNCTION__;
460 
461  # Unconditionally set loaded=true, we don't want the accessors constantly rechecking
462  $this->dataLoaded = true;
463  $this->extraDataLoaded = true;
464 
465  $dbr = ( $flags & self::READ_LATEST )
466  ? $this->repo->getMasterDB()
467  : $this->repo->getReplicaDB();
468 
469  $fileQuery = static::getQueryInfo();
470  $row = $dbr->selectRow(
471  $fileQuery['tables'],
472  $fileQuery['fields'],
473  [ 'img_name' => $this->getName() ],
474  $fname,
475  [],
476  $fileQuery['joins']
477  );
478 
479  if ( $row ) {
480  $this->loadFromRow( $row );
481  } else {
482  $this->fileExists = false;
483  }
484  }
485 
490  protected function loadExtraFromDB() {
491  $fname = static::class . '::' . __FUNCTION__;
492 
493  # Unconditionally set loaded=true, we don't want the accessors constantly rechecking
494  $this->extraDataLoaded = true;
495 
496  $fieldMap = $this->loadExtraFieldsWithTimestamp( $this->repo->getReplicaDB(), $fname );
497  if ( !$fieldMap ) {
498  $fieldMap = $this->loadExtraFieldsWithTimestamp( $this->repo->getMasterDB(), $fname );
499  }
500 
501  if ( $fieldMap ) {
502  foreach ( $fieldMap as $name => $value ) {
503  $this->$name = $value;
504  }
505  } else {
506  throw new MWException( "Could not find data for image '{$this->getName()}'." );
507  }
508  }
509 
515  private function loadExtraFieldsWithTimestamp( $dbr, $fname ) {
516  $fieldMap = false;
517 
518  $fileQuery = self::getQueryInfo( [ 'omit-nonlazy' ] );
519  $row = $dbr->selectRow(
520  $fileQuery['tables'],
521  $fileQuery['fields'],
522  [
523  'img_name' => $this->getName(),
524  'img_timestamp' => $dbr->timestamp( $this->getTimestamp() ),
525  ],
526  $fname,
527  [],
528  $fileQuery['joins']
529  );
530  if ( $row ) {
531  $fieldMap = $this->unprefixRow( $row, 'img_' );
532  } else {
533  # File may have been uploaded over in the meantime; check the old versions
534  $fileQuery = OldLocalFile::getQueryInfo( [ 'omit-nonlazy' ] );
535  $row = $dbr->selectRow(
536  $fileQuery['tables'],
537  $fileQuery['fields'],
538  [
539  'oi_name' => $this->getName(),
540  'oi_timestamp' => $dbr->timestamp( $this->getTimestamp() ),
541  ],
542  $fname,
543  [],
544  $fileQuery['joins']
545  );
546  if ( $row ) {
547  $fieldMap = $this->unprefixRow( $row, 'oi_' );
548  }
549  }
550 
551  if ( isset( $fieldMap['metadata'] ) ) {
552  $fieldMap['metadata'] = $this->repo->getReplicaDB()->decodeBlob( $fieldMap['metadata'] );
553  }
554 
555  return $fieldMap;
556  }
557 
564  protected function unprefixRow( $row, $prefix = 'img_' ) {
565  $array = (array)$row;
566  $prefixLength = strlen( $prefix );
567 
568  // Sanity check prefix once
569  if ( substr( key( $array ), 0, $prefixLength ) !== $prefix ) {
570  throw new MWException( __METHOD__ . ': incorrect $prefix parameter' );
571  }
572 
573  $decoded = [];
574  foreach ( $array as $name => $value ) {
575  $decoded[substr( $name, $prefixLength )] = $value;
576  }
577 
578  return $decoded;
579  }
580 
589  function decodeRow( $row, $prefix = 'img_' ) {
590  $decoded = $this->unprefixRow( $row, $prefix );
591 
592  $decoded['description'] = MediaWikiServices::getInstance()->getCommentStore()
593  ->getComment( 'description', (object)$decoded )->text;
594 
595  $decoded['user'] = User::newFromAnyId(
596  $decoded['user'] ?? null,
597  $decoded['user_text'] ?? null,
598  $decoded['actor'] ?? null
599  );
600  unset( $decoded['user_text'], $decoded['actor'] );
601 
602  $decoded['timestamp'] = wfTimestamp( TS_MW, $decoded['timestamp'] );
603 
604  $decoded['metadata'] = $this->repo->getReplicaDB()->decodeBlob( $decoded['metadata'] );
605 
606  if ( empty( $decoded['major_mime'] ) ) {
607  $decoded['mime'] = 'unknown/unknown';
608  } else {
609  if ( !$decoded['minor_mime'] ) {
610  $decoded['minor_mime'] = 'unknown';
611  }
612  $decoded['mime'] = $decoded['major_mime'] . '/' . $decoded['minor_mime'];
613  }
614 
615  // Trim zero padding from char/binary field
616  $decoded['sha1'] = rtrim( $decoded['sha1'], "\0" );
617 
618  // Normalize some fields to integer type, per their database definition.
619  // Use unary + so that overflows will be upgraded to double instead of
620  // being trucated as with intval(). This is important to allow >2GB
621  // files on 32-bit systems.
622  foreach ( [ 'size', 'width', 'height', 'bits' ] as $field ) {
623  $decoded[$field] = +$decoded[$field];
624  }
625 
626  return $decoded;
627  }
628 
635  function loadFromRow( $row, $prefix = 'img_' ) {
636  $this->dataLoaded = true;
637  $this->extraDataLoaded = true;
638 
639  $array = $this->decodeRow( $row, $prefix );
640 
641  foreach ( $array as $name => $value ) {
642  $this->$name = $value;
643  }
644 
645  $this->fileExists = true;
646  }
647 
652  function load( $flags = 0 ) {
653  if ( !$this->dataLoaded ) {
654  if ( $flags & self::READ_LATEST ) {
655  $this->loadFromDB( $flags );
656  } else {
657  $this->loadFromCache();
658  }
659  }
660 
661  if ( ( $flags & self::LOAD_ALL ) && !$this->extraDataLoaded ) {
662  // @note: loads on name/timestamp to reduce race condition problems
663  $this->loadExtraFromDB();
664  }
665  }
666 
670  protected function maybeUpgradeRow() {
672 
673  if ( wfReadOnly() || $this->upgrading ) {
674  return;
675  }
676 
677  $upgrade = false;
678  if ( is_null( $this->media_type ) || $this->mime == 'image/svg' ) {
679  $upgrade = true;
680  } else {
681  $handler = $this->getHandler();
682  if ( $handler ) {
683  $validity = $handler->isMetadataValid( $this, $this->getMetadata() );
684  if ( $validity === MediaHandler::METADATA_BAD ) {
685  $upgrade = true;
686  } elseif ( $validity === MediaHandler::METADATA_COMPATIBLE ) {
687  $upgrade = $wgUpdateCompatibleMetadata;
688  }
689  }
690  }
691 
692  if ( $upgrade ) {
693  $this->upgrading = true;
694  // Defer updates unless in auto-commit CLI mode
696  $this->upgrading = false; // avoid duplicate updates
697  try {
698  $this->upgradeRow();
699  } catch ( LocalFileLockError $e ) {
700  // let the other process handle it (or do it next time)
701  }
702  } );
703  }
704  }
705 
709  function getUpgraded() {
710  return $this->upgraded;
711  }
712 
716  function upgradeRow() {
717  $this->lock();
718 
719  $this->loadFromFile();
720 
721  # Don't destroy file info of missing files
722  if ( !$this->fileExists ) {
723  $this->unlock();
724  wfDebug( __METHOD__ . ": file does not exist, aborting\n" );
725 
726  return;
727  }
728 
729  $dbw = $this->repo->getMasterDB();
730  list( $major, $minor ) = self::splitMime( $this->mime );
731 
732  if ( wfReadOnly() ) {
733  $this->unlock();
734 
735  return;
736  }
737  wfDebug( __METHOD__ . ': upgrading ' . $this->getName() . " to the current schema\n" );
738 
739  $dbw->update( 'image',
740  [
741  'img_size' => $this->size, // sanity
742  'img_width' => $this->width,
743  'img_height' => $this->height,
744  'img_bits' => $this->bits,
745  'img_media_type' => $this->media_type,
746  'img_major_mime' => $major,
747  'img_minor_mime' => $minor,
748  'img_metadata' => $dbw->encodeBlob( $this->metadata ),
749  'img_sha1' => $this->sha1,
750  ],
751  [ 'img_name' => $this->getName() ],
752  __METHOD__
753  );
754 
755  $this->invalidateCache();
756 
757  $this->unlock();
758  $this->upgraded = true; // avoid rework/retries
759  }
760 
771  function setProps( $info ) {
772  $this->dataLoaded = true;
773  $fields = $this->getCacheFields( '' );
774  $fields[] = 'fileExists';
775 
776  foreach ( $fields as $field ) {
777  if ( isset( $info[$field] ) ) {
778  $this->$field = $info[$field];
779  }
780  }
781 
782  if ( isset( $info['user'] ) || isset( $info['user_text'] ) || isset( $info['actor'] ) ) {
783  $this->user = User::newFromAnyId(
784  $info['user'] ?? null,
785  $info['user_text'] ?? null,
786  $info['actor'] ?? null
787  );
788  }
789 
790  // Fix up mime fields
791  if ( isset( $info['major_mime'] ) ) {
792  $this->mime = "{$info['major_mime']}/{$info['minor_mime']}";
793  } elseif ( isset( $info['mime'] ) ) {
794  $this->mime = $info['mime'];
795  list( $this->major_mime, $this->minor_mime ) = self::splitMime( $this->mime );
796  }
797  }
798 
813  function isMissing() {
814  if ( $this->missing === null ) {
815  $fileExists = $this->repo->fileExists( $this->getVirtualUrl() );
816  $this->missing = !$fileExists;
817  }
818 
819  return $this->missing;
820  }
821 
828  public function getWidth( $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['width'];
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->width;
851  }
852  }
853 
860  public function getHeight( $page = 1 ) {
861  $page = (int)$page;
862  if ( $page < 1 ) {
863  $page = 1;
864  }
865 
866  $this->load();
867 
868  if ( $this->isMultipage() ) {
869  $handler = $this->getHandler();
870  if ( !$handler ) {
871  return 0;
872  }
873  $dim = $handler->getPageDimensions( $this, $page );
874  if ( $dim ) {
875  return $dim['height'];
876  } else {
877  // For non-paged media, the false goes through an
878  // intval, turning failure into 0, so do same here.
879  return 0;
880  }
881  } else {
882  return $this->height;
883  }
884  }
885 
893  function getUser( $type = 'text' ) {
894  $this->load();
895 
896  if ( $type === 'object' ) {
897  return $this->user;
898  } elseif ( $type === 'text' ) {
899  return $this->user->getName();
900  } elseif ( $type === 'id' ) {
901  return $this->user->getId();
902  }
903 
904  throw new MWException( "Unknown type '$type'." );
905  }
906 
914  public function getDescriptionShortUrl() {
915  $pageId = $this->title->getArticleID();
916 
917  if ( $pageId !== null ) {
918  $url = $this->repo->makeUrl( [ 'curid' => $pageId ] );
919  if ( $url !== false ) {
920  return $url;
921  }
922  }
923  return null;
924  }
925 
930  function getMetadata() {
931  $this->load( self::LOAD_ALL ); // large metadata is loaded in another step
932  return $this->metadata;
933  }
934 
938  function getBitDepth() {
939  $this->load();
940 
941  return (int)$this->bits;
942  }
943 
948  public function getSize() {
949  $this->load();
950 
951  return $this->size;
952  }
953 
958  function getMimeType() {
959  $this->load();
960 
961  return $this->mime;
962  }
963 
969  function getMediaType() {
970  $this->load();
971 
972  return $this->media_type;
973  }
974 
985  public function exists() {
986  $this->load();
987 
988  return $this->fileExists;
989  }
990 
1006  function getThumbnails( $archiveName = false ) {
1007  if ( $archiveName ) {
1008  $dir = $this->getArchiveThumbPath( $archiveName );
1009  } else {
1010  $dir = $this->getThumbPath();
1011  }
1012 
1013  $backend = $this->repo->getBackend();
1014  $files = [ $dir ];
1015  try {
1016  $iterator = $backend->getFileList( [ 'dir' => $dir ] );
1017  foreach ( $iterator as $file ) {
1018  $files[] = $file;
1019  }
1020  } catch ( FileBackendError $e ) {
1021  } // suppress (T56674)
1022 
1023  return $files;
1024  }
1025 
1029  function purgeMetadataCache() {
1030  $this->invalidateCache();
1031  }
1032 
1040  function purgeCache( $options = [] ) {
1041  // Refresh metadata cache
1042  $this->maybeUpgradeRow();
1043  $this->purgeMetadataCache();
1044 
1045  // Delete thumbnails
1046  $this->purgeThumbnails( $options );
1047 
1048  // Purge CDN cache for this file
1050  new CdnCacheUpdate( [ $this->getUrl() ] ),
1052  );
1053  }
1054 
1059  function purgeOldThumbnails( $archiveName ) {
1060  // Get a list of old thumbnails and URLs
1061  $files = $this->getThumbnails( $archiveName );
1062 
1063  // Purge any custom thumbnail caches
1064  Hooks::run( 'LocalFilePurgeThumbnails', [ $this, $archiveName ] );
1065 
1066  // Delete thumbnails
1067  $dir = array_shift( $files );
1068  $this->purgeThumbList( $dir, $files );
1069 
1070  // Purge the CDN
1071  $urls = [];
1072  foreach ( $files as $file ) {
1073  $urls[] = $this->getArchiveThumbUrl( $archiveName, $file );
1074  }
1076  }
1077 
1082  public function purgeThumbnails( $options = [] ) {
1083  $files = $this->getThumbnails();
1084  // Always purge all files from CDN regardless of handler filters
1085  $urls = [];
1086  foreach ( $files as $file ) {
1087  $urls[] = $this->getThumbUrl( $file );
1088  }
1089  array_shift( $urls ); // don't purge directory
1090 
1091  // Give media handler a chance to filter the file purge list
1092  if ( !empty( $options['forThumbRefresh'] ) ) {
1093  $handler = $this->getHandler();
1094  if ( $handler ) {
1096  }
1097  }
1098 
1099  // Purge any custom thumbnail caches
1100  Hooks::run( 'LocalFilePurgeThumbnails', [ $this, false ] );
1101 
1102  // Delete thumbnails
1103  $dir = array_shift( $files );
1104  $this->purgeThumbList( $dir, $files );
1105 
1106  // Purge the CDN
1108  }
1109 
1115  public function prerenderThumbnails() {
1117 
1118  $jobs = [];
1119 
1120  $sizes = $wgUploadThumbnailRenderMap;
1121  rsort( $sizes );
1122 
1123  foreach ( $sizes as $size ) {
1124  if ( $this->isVectorized() || $this->getWidth() > $size ) {
1125  $jobs[] = new ThumbnailRenderJob(
1126  $this->getTitle(),
1127  [ 'transformParams' => [ 'width' => $size ] ]
1128  );
1129  }
1130  }
1131 
1132  if ( $jobs ) {
1133  JobQueueGroup::singleton()->lazyPush( $jobs );
1134  }
1135  }
1136 
1142  protected function purgeThumbList( $dir, $files ) {
1143  $fileListDebug = strtr(
1144  var_export( $files, true ),
1145  [ "\n" => '' ]
1146  );
1147  wfDebug( __METHOD__ . ": $fileListDebug\n" );
1148 
1149  $purgeList = [];
1150  foreach ( $files as $file ) {
1151  if ( $this->repo->supportsSha1URLs() ) {
1152  $reference = $this->getSha1();
1153  } else {
1154  $reference = $this->getName();
1155  }
1156 
1157  # Check that the reference (filename or sha1) is part of the thumb name
1158  # This is a basic sanity check to avoid erasing unrelated directories
1159  if ( strpos( $file, $reference ) !== false
1160  || strpos( $file, "-thumbnail" ) !== false // "short" thumb name
1161  ) {
1162  $purgeList[] = "{$dir}/{$file}";
1163  }
1164  }
1165 
1166  # Delete the thumbnails
1167  $this->repo->quickPurgeBatch( $purgeList );
1168  # Clear out the thumbnail directory if empty
1169  $this->repo->quickCleanDir( $dir );
1170  }
1171 
1182  function getHistory( $limit = null, $start = null, $end = null, $inc = true ) {
1183  $dbr = $this->repo->getReplicaDB();
1184  $oldFileQuery = OldLocalFile::getQueryInfo();
1185 
1186  $tables = $oldFileQuery['tables'];
1187  $fields = $oldFileQuery['fields'];
1188  $join_conds = $oldFileQuery['joins'];
1189  $conds = $opts = [];
1190  $eq = $inc ? '=' : '';
1191  $conds[] = "oi_name = " . $dbr->addQuotes( $this->title->getDBkey() );
1192 
1193  if ( $start ) {
1194  $conds[] = "oi_timestamp <$eq " . $dbr->addQuotes( $dbr->timestamp( $start ) );
1195  }
1196 
1197  if ( $end ) {
1198  $conds[] = "oi_timestamp >$eq " . $dbr->addQuotes( $dbr->timestamp( $end ) );
1199  }
1200 
1201  if ( $limit ) {
1202  $opts['LIMIT'] = $limit;
1203  }
1204 
1205  // Search backwards for time > x queries
1206  $order = ( !$start && $end !== null ) ? 'ASC' : 'DESC';
1207  $opts['ORDER BY'] = "oi_timestamp $order";
1208  $opts['USE INDEX'] = [ 'oldimage' => 'oi_name_timestamp' ];
1209 
1210  // Avoid PHP 7.1 warning from passing $this by reference
1211  $localFile = $this;
1212  Hooks::run( 'LocalFile::getHistory', [ &$localFile, &$tables, &$fields,
1213  &$conds, &$opts, &$join_conds ] );
1214 
1215  $res = $dbr->select( $tables, $fields, $conds, __METHOD__, $opts, $join_conds );
1216  $r = [];
1217 
1218  foreach ( $res as $row ) {
1219  $r[] = $this->repo->newFileFromRow( $row );
1220  }
1221 
1222  if ( $order == 'ASC' ) {
1223  $r = array_reverse( $r ); // make sure it ends up descending
1224  }
1225 
1226  return $r;
1227  }
1228 
1238  public function nextHistoryLine() {
1239  # Polymorphic function name to distinguish foreign and local fetches
1240  $fname = static::class . '::' . __FUNCTION__;
1241 
1242  $dbr = $this->repo->getReplicaDB();
1243 
1244  if ( $this->historyLine == 0 ) { // called for the first time, return line from cur
1245  $fileQuery = self::getQueryInfo();
1246  $this->historyRes = $dbr->select( $fileQuery['tables'],
1247  $fileQuery['fields'] + [
1248  'oi_archive_name' => $dbr->addQuotes( '' ),
1249  'oi_deleted' => 0,
1250  ],
1251  [ 'img_name' => $this->title->getDBkey() ],
1252  $fname,
1253  [],
1254  $fileQuery['joins']
1255  );
1256 
1257  if ( $dbr->numRows( $this->historyRes ) == 0 ) {
1258  $this->historyRes = null;
1259 
1260  return false;
1261  }
1262  } elseif ( $this->historyLine == 1 ) {
1263  $fileQuery = OldLocalFile::getQueryInfo();
1264  $this->historyRes = $dbr->select(
1265  $fileQuery['tables'],
1266  $fileQuery['fields'],
1267  [ 'oi_name' => $this->title->getDBkey() ],
1268  $fname,
1269  [ 'ORDER BY' => 'oi_timestamp DESC' ],
1270  $fileQuery['joins']
1271  );
1272  }
1273  $this->historyLine++;
1274 
1275  return $dbr->fetchObject( $this->historyRes );
1276  }
1277 
1281  public function resetHistory() {
1282  $this->historyLine = 0;
1283 
1284  if ( !is_null( $this->historyRes ) ) {
1285  $this->historyRes = null;
1286  }
1287  }
1288 
1322  function upload( $src, $comment, $pageText, $flags = 0, $props = false,
1323  $timestamp = false, $user = null, $tags = [],
1324  $createNullRevision = true, $revert = false
1325  ) {
1326  if ( $this->getRepo()->getReadOnlyReason() !== false ) {
1327  return $this->readOnlyFatalStatus();
1328  } elseif ( MediaWikiServices::getInstance()->getRevisionStore()->isReadOnly() ) {
1329  // Check this in advance to avoid writing to FileBackend and the file tables,
1330  // only to fail on insert the revision due to the text store being unavailable.
1331  return $this->readOnlyFatalStatus();
1332  }
1333 
1334  $srcPath = ( $src instanceof FSFile ) ? $src->getPath() : $src;
1335  if ( !$props ) {
1336  if ( FileRepo::isVirtualUrl( $srcPath )
1337  || FileBackend::isStoragePath( $srcPath )
1338  ) {
1339  $props = $this->repo->getFileProps( $srcPath );
1340  } else {
1341  $mwProps = new MWFileProps( MediaWikiServices::getInstance()->getMimeAnalyzer() );
1342  $props = $mwProps->getPropsFromPath( $srcPath, true );
1343  }
1344  }
1345 
1346  $options = [];
1347  $handler = MediaHandler::getHandler( $props['mime'] );
1348  if ( $handler ) {
1349  $metadata = AtEase::quietCall( 'unserialize', $props['metadata'] );
1350 
1351  if ( !is_array( $metadata ) ) {
1352  $metadata = [];
1353  }
1354 
1355  $options['headers'] = $handler->getContentHeaders( $metadata );
1356  } else {
1357  $options['headers'] = [];
1358  }
1359 
1360  // Trim spaces on user supplied text
1361  $comment = trim( $comment );
1362 
1363  $this->lock();
1364  $status = $this->publish( $src, $flags, $options );
1365 
1366  if ( $status->successCount >= 2 ) {
1367  // There will be a copy+(one of move,copy,store).
1368  // The first succeeding does not commit us to updating the DB
1369  // since it simply copied the current version to a timestamped file name.
1370  // It is only *preferable* to avoid leaving such files orphaned.
1371  // Once the second operation goes through, then the current version was
1372  // updated and we must therefore update the DB too.
1373  $oldver = $status->value;
1374  $uploadStatus = $this->recordUpload2(
1375  $oldver,
1376  $comment,
1377  $pageText,
1378  $props,
1379  $timestamp,
1380  $user,
1381  $tags,
1382  $createNullRevision,
1383  $revert
1384  );
1385  if ( !$uploadStatus->isOK() ) {
1386  if ( $uploadStatus->hasMessage( 'filenotfound' ) ) {
1387  // update filenotfound error with more specific path
1388  $status->fatal( 'filenotfound', $srcPath );
1389  } else {
1390  $status->merge( $uploadStatus );
1391  }
1392  }
1393  }
1394 
1395  $this->unlock();
1396  return $status;
1397  }
1398 
1411  function recordUpload( $oldver, $desc, $license = '', $copyStatus = '', $source = '',
1412  $watch = false, $timestamp = false, User $user = null ) {
1413  if ( !$user ) {
1414  global $wgUser;
1415  $user = $wgUser;
1416  }
1417 
1418  $pageText = SpecialUpload::getInitialPageText( $desc, $license, $copyStatus, $source );
1419 
1420  if ( !$this->recordUpload2( $oldver, $desc, $pageText, false, $timestamp, $user )->isOK() ) {
1421  return false;
1422  }
1423 
1424  if ( $watch ) {
1425  $user->addWatch( $this->getTitle() );
1426  }
1427 
1428  return true;
1429  }
1430 
1445  function recordUpload2(
1446  $oldver, $comment, $pageText, $props = false, $timestamp = false, $user = null, $tags = [],
1447  $createNullRevision = true, $revert = false
1448  ) {
1450 
1451  if ( is_null( $user ) ) {
1452  global $wgUser;
1453  $user = $wgUser;
1454  }
1455 
1456  $dbw = $this->repo->getMasterDB();
1457 
1458  # Imports or such might force a certain timestamp; otherwise we generate
1459  # it and can fudge it slightly to keep (name,timestamp) unique on re-upload.
1460  if ( $timestamp === false ) {
1461  $timestamp = $dbw->timestamp();
1462  $allowTimeKludge = true;
1463  } else {
1464  $allowTimeKludge = false;
1465  }
1466 
1467  $props = $props ?: $this->repo->getFileProps( $this->getVirtualUrl() );
1468  $props['description'] = $comment;
1469  $props['user'] = $user->getId();
1470  $props['user_text'] = $user->getName();
1471  $props['actor'] = $user->getActorId( $dbw );
1472  $props['timestamp'] = wfTimestamp( TS_MW, $timestamp ); // DB -> TS_MW
1473  $this->setProps( $props );
1474 
1475  # Fail now if the file isn't there
1476  if ( !$this->fileExists ) {
1477  wfDebug( __METHOD__ . ": File " . $this->getRel() . " went missing!\n" );
1478 
1479  return Status::newFatal( 'filenotfound', $this->getRel() );
1480  }
1481 
1482  $dbw->startAtomic( __METHOD__ );
1483 
1484  # Test to see if the row exists using INSERT IGNORE
1485  # This avoids race conditions by locking the row until the commit, and also
1486  # doesn't deadlock. SELECT FOR UPDATE causes a deadlock for every race condition.
1487  $commentStore = MediaWikiServices::getInstance()->getCommentStore();
1488  $commentFields = $commentStore->insert( $dbw, 'img_description', $comment );
1489  $actorMigration = ActorMigration::newMigration();
1490  $actorFields = $actorMigration->getInsertValues( $dbw, 'img_user', $user );
1491  $dbw->insert( 'image',
1492  [
1493  'img_name' => $this->getName(),
1494  'img_size' => $this->size,
1495  'img_width' => intval( $this->width ),
1496  'img_height' => intval( $this->height ),
1497  'img_bits' => $this->bits,
1498  'img_media_type' => $this->media_type,
1499  'img_major_mime' => $this->major_mime,
1500  'img_minor_mime' => $this->minor_mime,
1501  'img_timestamp' => $timestamp,
1502  'img_metadata' => $dbw->encodeBlob( $this->metadata ),
1503  'img_sha1' => $this->sha1
1504  ] + $commentFields + $actorFields,
1505  __METHOD__,
1506  [ 'IGNORE' ]
1507  );
1508  $reupload = ( $dbw->affectedRows() == 0 );
1509 
1510  if ( $reupload ) {
1511  $row = $dbw->selectRow(
1512  'image',
1513  [ 'img_timestamp', 'img_sha1' ],
1514  [ 'img_name' => $this->getName() ],
1515  __METHOD__,
1516  [ 'LOCK IN SHARE MODE' ]
1517  );
1518 
1519  if ( $row && $row->img_sha1 === $this->sha1 ) {
1520  $dbw->endAtomic( __METHOD__ );
1521  wfDebug( __METHOD__ . ": File " . $this->getRel() . " already exists!\n" );
1522  $title = Title::newFromText( $this->getName(), NS_FILE );
1523  return Status::newFatal( 'fileexists-no-change', $title->getPrefixedText() );
1524  }
1525 
1526  if ( $allowTimeKludge ) {
1527  # Use LOCK IN SHARE MODE to ignore any transaction snapshotting
1528  $lUnixtime = $row ? wfTimestamp( TS_UNIX, $row->img_timestamp ) : false;
1529  # Avoid a timestamp that is not newer than the last version
1530  # TODO: the image/oldimage tables should be like page/revision with an ID field
1531  if ( $lUnixtime && wfTimestamp( TS_UNIX, $timestamp ) <= $lUnixtime ) {
1532  sleep( 1 ); // fast enough re-uploads would go far in the future otherwise
1533  $timestamp = $dbw->timestamp( $lUnixtime + 1 );
1534  $this->timestamp = wfTimestamp( TS_MW, $timestamp ); // DB -> TS_MW
1535  }
1536  }
1537 
1538  $tables = [ 'image' ];
1539  $fields = [
1540  'oi_name' => 'img_name',
1541  'oi_archive_name' => $dbw->addQuotes( $oldver ),
1542  'oi_size' => 'img_size',
1543  'oi_width' => 'img_width',
1544  'oi_height' => 'img_height',
1545  'oi_bits' => 'img_bits',
1546  'oi_description_id' => 'img_description_id',
1547  'oi_timestamp' => 'img_timestamp',
1548  'oi_metadata' => 'img_metadata',
1549  'oi_media_type' => 'img_media_type',
1550  'oi_major_mime' => 'img_major_mime',
1551  'oi_minor_mime' => 'img_minor_mime',
1552  'oi_sha1' => 'img_sha1',
1553  ];
1554  $joins = [];
1555 
1556  if ( $wgActorTableSchemaMigrationStage & SCHEMA_COMPAT_WRITE_OLD ) {
1557  $fields['oi_user'] = 'img_user';
1558  $fields['oi_user_text'] = 'img_user_text';
1559  }
1560  if ( $wgActorTableSchemaMigrationStage & SCHEMA_COMPAT_WRITE_NEW ) {
1561  $fields['oi_actor'] = 'img_actor';
1562  }
1563 
1564  if (
1565  ( $wgActorTableSchemaMigrationStage & SCHEMA_COMPAT_WRITE_BOTH ) === SCHEMA_COMPAT_WRITE_BOTH
1566  ) {
1567  // Upgrade any rows that are still old-style. Otherwise an upgrade
1568  // might be missed if a deletion happens while the migration script
1569  // is running.
1570  $res = $dbw->select(
1571  [ 'image' ],
1572  [ 'img_name', 'img_user', 'img_user_text' ],
1573  [ 'img_name' => $this->getName(), 'img_actor' => 0 ],
1574  __METHOD__
1575  );
1576  foreach ( $res as $row ) {
1577  $actorId = User::newFromAnyId( $row->img_user, $row->img_user_text, null )->getActorId( $dbw );
1578  $dbw->update(
1579  'image',
1580  [ 'img_actor' => $actorId ],
1581  [ 'img_name' => $row->img_name, 'img_actor' => 0 ],
1582  __METHOD__
1583  );
1584  }
1585  }
1586 
1587  # (T36993) Note: $oldver can be empty here, if the previous
1588  # version of the file was broken. Allow registration of the new
1589  # version to continue anyway, because that's better than having
1590  # an image that's not fixable by user operations.
1591  # Collision, this is an update of a file
1592  # Insert previous contents into oldimage
1593  $dbw->insertSelect( 'oldimage', $tables, $fields,
1594  [ 'img_name' => $this->getName() ], __METHOD__, [], [], $joins );
1595 
1596  # Update the current image row
1597  $dbw->update( 'image',
1598  [
1599  'img_size' => $this->size,
1600  'img_width' => intval( $this->width ),
1601  'img_height' => intval( $this->height ),
1602  'img_bits' => $this->bits,
1603  'img_media_type' => $this->media_type,
1604  'img_major_mime' => $this->major_mime,
1605  'img_minor_mime' => $this->minor_mime,
1606  'img_timestamp' => $timestamp,
1607  'img_metadata' => $dbw->encodeBlob( $this->metadata ),
1608  'img_sha1' => $this->sha1
1609  ] + $commentFields + $actorFields,
1610  [ 'img_name' => $this->getName() ],
1611  __METHOD__
1612  );
1613  }
1614 
1615  $descTitle = $this->getTitle();
1616  $descId = $descTitle->getArticleID();
1617  $wikiPage = new WikiFilePage( $descTitle );
1618  $wikiPage->setFile( $this );
1619 
1620  // Determine log action. If reupload is done by reverting, use a special log_action.
1621  if ( $revert === true ) {
1622  $logAction = 'revert';
1623  } elseif ( $reupload === true ) {
1624  $logAction = 'overwrite';
1625  } else {
1626  $logAction = 'upload';
1627  }
1628  // Add the log entry...
1629  $logEntry = new ManualLogEntry( 'upload', $logAction );
1630  $logEntry->setTimestamp( $this->timestamp );
1631  $logEntry->setPerformer( $user );
1632  $logEntry->setComment( $comment );
1633  $logEntry->setTarget( $descTitle );
1634  // Allow people using the api to associate log entries with the upload.
1635  // Log has a timestamp, but sometimes different from upload timestamp.
1636  $logEntry->setParameters(
1637  [
1638  'img_sha1' => $this->sha1,
1639  'img_timestamp' => $timestamp,
1640  ]
1641  );
1642  // Note we keep $logId around since during new image
1643  // creation, page doesn't exist yet, so log_page = 0
1644  // but we want it to point to the page we're making,
1645  // so we later modify the log entry.
1646  // For a similar reason, we avoid making an RC entry
1647  // now and wait until the page exists.
1648  $logId = $logEntry->insert();
1649 
1650  if ( $descTitle->exists() ) {
1651  // Use own context to get the action text in content language
1652  $formatter = LogFormatter::newFromEntry( $logEntry );
1653  $formatter->setContext( RequestContext::newExtraneousContext( $descTitle ) );
1654  $editSummary = $formatter->getPlainActionText();
1655 
1656  $nullRevision = $createNullRevision === false ? null : Revision::newNullRevision(
1657  $dbw,
1658  $descId,
1659  $editSummary,
1660  false,
1661  $user
1662  );
1663  if ( $nullRevision ) {
1664  $nullRevision->insertOn( $dbw );
1665  Hooks::run(
1666  'NewRevisionFromEditComplete',
1667  [ $wikiPage, $nullRevision, $nullRevision->getParentId(), $user ]
1668  );
1669  $wikiPage->updateRevisionOn( $dbw, $nullRevision );
1670  // Associate null revision id
1671  $logEntry->setAssociatedRevId( $nullRevision->getId() );
1672  }
1673 
1674  $newPageContent = null;
1675  } else {
1676  // Make the description page and RC log entry post-commit
1677  $newPageContent = ContentHandler::makeContent( $pageText, $descTitle );
1678  }
1679 
1680  # Defer purges, page creation, and link updates in case they error out.
1681  # The most important thing is that files and the DB registry stay synced.
1682  $dbw->endAtomic( __METHOD__ );
1683  $fname = __METHOD__;
1684 
1685  # Do some cache purges after final commit so that:
1686  # a) Changes are more likely to be seen post-purge
1687  # b) They won't cause rollback of the log publish/update above
1689  new AutoCommitUpdate(
1690  $dbw,
1691  __METHOD__,
1692  function () use (
1693  $reupload, $wikiPage, $newPageContent, $comment, $user,
1694  $logEntry, $logId, $descId, $tags, $fname
1695  ) {
1696  # Update memcache after the commit
1697  $this->invalidateCache();
1698 
1699  $updateLogPage = false;
1700  if ( $newPageContent ) {
1701  # New file page; create the description page.
1702  # There's already a log entry, so don't make a second RC entry
1703  # CDN and file cache for the description page are purged by doEditContent.
1704  $status = $wikiPage->doEditContent(
1705  $newPageContent,
1706  $comment,
1708  false,
1709  $user
1710  );
1711 
1712  if ( isset( $status->value['revision'] ) ) {
1714  $rev = $status->value['revision'];
1715  // Associate new page revision id
1716  $logEntry->setAssociatedRevId( $rev->getId() );
1717  }
1718  // This relies on the resetArticleID() call in WikiPage::insertOn(),
1719  // which is triggered on $descTitle by doEditContent() above.
1720  if ( isset( $status->value['revision'] ) ) {
1722  $rev = $status->value['revision'];
1723  $updateLogPage = $rev->getPage();
1724  }
1725  } else {
1726  # Existing file page: invalidate description page cache
1727  $wikiPage->getTitle()->invalidateCache();
1728  $wikiPage->getTitle()->purgeSquid();
1729  # Allow the new file version to be patrolled from the page footer
1731  }
1732 
1733  # Update associated rev id. This should be done by $logEntry->insert() earlier,
1734  # but setAssociatedRevId() wasn't called at that point yet...
1735  $logParams = $logEntry->getParameters();
1736  $logParams['associated_rev_id'] = $logEntry->getAssociatedRevId();
1737  $update = [ 'log_params' => LogEntryBase::makeParamBlob( $logParams ) ];
1738  if ( $updateLogPage ) {
1739  # Also log page, in case where we just created it above
1740  $update['log_page'] = $updateLogPage;
1741  }
1742  $this->getRepo()->getMasterDB()->update(
1743  'logging',
1744  $update,
1745  [ 'log_id' => $logId ],
1746  $fname
1747  );
1748  $this->getRepo()->getMasterDB()->insert(
1749  'log_search',
1750  [
1751  'ls_field' => 'associated_rev_id',
1752  'ls_value' => $logEntry->getAssociatedRevId(),
1753  'ls_log_id' => $logId,
1754  ],
1755  $fname
1756  );
1757 
1758  # Add change tags, if any
1759  if ( $tags ) {
1760  $logEntry->setTags( $tags );
1761  }
1762 
1763  # Uploads can be patrolled
1764  $logEntry->setIsPatrollable( true );
1765 
1766  # Now that the log entry is up-to-date, make an RC entry.
1767  $logEntry->publish( $logId );
1768 
1769  # Run hook for other updates (typically more cache purging)
1770  Hooks::run( 'FileUpload', [ $this, $reupload, !$newPageContent ] );
1771 
1772  if ( $reupload ) {
1773  # Delete old thumbnails
1774  $this->purgeThumbnails();
1775  # Remove the old file from the CDN cache
1777  new CdnCacheUpdate( [ $this->getUrl() ] ),
1779  );
1780  } else {
1781  # Update backlink pages pointing to this title if created
1783  $this->getTitle(),
1784  'imagelinks',
1785  'upload-image',
1786  $user->getName()
1787  );
1788  }
1789 
1790  $this->prerenderThumbnails();
1791  }
1792  ),
1794  );
1795 
1796  if ( !$reupload ) {
1797  # This is a new file, so update the image count
1798  DeferredUpdates::addUpdate( SiteStatsUpdate::factory( [ 'images' => 1 ] ) );
1799  }
1800 
1801  # Invalidate cache for all pages using this file
1803  new HTMLCacheUpdate( $this->getTitle(), 'imagelinks', 'file-upload' )
1804  );
1805 
1806  return Status::newGood();
1807  }
1808 
1824  function publish( $src, $flags = 0, array $options = [] ) {
1825  return $this->publishTo( $src, $this->getRel(), $flags, $options );
1826  }
1827 
1843  function publishTo( $src, $dstRel, $flags = 0, array $options = [] ) {
1844  $srcPath = ( $src instanceof FSFile ) ? $src->getPath() : $src;
1845 
1846  $repo = $this->getRepo();
1847  if ( $repo->getReadOnlyReason() !== false ) {
1848  return $this->readOnlyFatalStatus();
1849  }
1850 
1851  $this->lock();
1852 
1853  if ( $this->isOld() ) {
1854  $archiveRel = $dstRel;
1855  $archiveName = basename( $archiveRel );
1856  } else {
1857  $archiveName = wfTimestamp( TS_MW ) . '!' . $this->getName();
1858  $archiveRel = $this->getArchiveRel( $archiveName );
1859  }
1860 
1861  if ( $repo->hasSha1Storage() ) {
1862  $sha1 = FileRepo::isVirtualUrl( $srcPath )
1863  ? $repo->getFileSha1( $srcPath )
1864  : FSFile::getSha1Base36FromPath( $srcPath );
1866  $wrapperBackend = $repo->getBackend();
1867  $dst = $wrapperBackend->getPathForSHA1( $sha1 );
1868  $status = $repo->quickImport( $src, $dst );
1869  if ( $flags & File::DELETE_SOURCE ) {
1870  unlink( $srcPath );
1871  }
1872 
1873  if ( $this->exists() ) {
1874  $status->value = $archiveName;
1875  }
1876  } else {
1877  $flags = $flags & File::DELETE_SOURCE ? LocalRepo::DELETE_SOURCE : 0;
1878  $status = $repo->publish( $srcPath, $dstRel, $archiveRel, $flags, $options );
1879 
1880  if ( $status->value == 'new' ) {
1881  $status->value = '';
1882  } else {
1883  $status->value = $archiveName;
1884  }
1885  }
1886 
1887  $this->unlock();
1888  return $status;
1889  }
1890 
1908  function move( $target ) {
1909  $localRepo = MediaWikiServices::getInstance()->getRepoGroup();
1910  if ( $this->getRepo()->getReadOnlyReason() !== false ) {
1911  return $this->readOnlyFatalStatus();
1912  }
1913 
1914  wfDebugLog( 'imagemove', "Got request to move {$this->name} to " . $target->getText() );
1915  $batch = new LocalFileMoveBatch( $this, $target );
1916 
1917  $this->lock();
1918  $batch->addCurrent();
1919  $archiveNames = $batch->addOlds();
1920  $status = $batch->execute();
1921  $this->unlock();
1922 
1923  wfDebugLog( 'imagemove', "Finished moving {$this->name}" );
1924 
1925  // Purge the source and target files...
1926  $oldTitleFile = $localRepo->findFile( $this->title );
1927  $newTitleFile = $localRepo->findFile( $target );
1928  // To avoid slow purges in the transaction, move them outside...
1930  new AutoCommitUpdate(
1931  $this->getRepo()->getMasterDB(),
1932  __METHOD__,
1933  function () use ( $oldTitleFile, $newTitleFile, $archiveNames ) {
1934  $oldTitleFile->purgeEverything();
1935  foreach ( $archiveNames as $archiveName ) {
1936  $oldTitleFile->purgeOldThumbnails( $archiveName );
1937  }
1938  $newTitleFile->purgeEverything();
1939  }
1940  ),
1942  );
1943 
1944  if ( $status->isOK() ) {
1945  // Now switch the object
1946  $this->title = $target;
1947  // Force regeneration of the name and hashpath
1948  unset( $this->name );
1949  unset( $this->hashPath );
1950  }
1951 
1952  return $status;
1953  }
1954 
1968  function delete( $reason, $suppress = false, $user = null ) {
1969  if ( $this->getRepo()->getReadOnlyReason() !== false ) {
1970  return $this->readOnlyFatalStatus();
1971  }
1972 
1973  $batch = new LocalFileDeleteBatch( $this, $reason, $suppress, $user );
1974 
1975  $this->lock();
1976  $batch->addCurrent();
1977  // Get old version relative paths
1978  $archiveNames = $batch->addOlds();
1979  $status = $batch->execute();
1980  $this->unlock();
1981 
1982  if ( $status->isOK() ) {
1983  DeferredUpdates::addUpdate( SiteStatsUpdate::factory( [ 'images' => -1 ] ) );
1984  }
1985 
1986  // To avoid slow purges in the transaction, move them outside...
1988  new AutoCommitUpdate(
1989  $this->getRepo()->getMasterDB(),
1990  __METHOD__,
1991  function () use ( $archiveNames ) {
1992  $this->purgeEverything();
1993  foreach ( $archiveNames as $archiveName ) {
1994  $this->purgeOldThumbnails( $archiveName );
1995  }
1996  }
1997  ),
1999  );
2000 
2001  // Purge the CDN
2002  $purgeUrls = [];
2003  foreach ( $archiveNames as $archiveName ) {
2004  $purgeUrls[] = $this->getArchiveUrl( $archiveName );
2005  }
2007 
2008  return $status;
2009  }
2010 
2026  function deleteOld( $archiveName, $reason, $suppress = false, $user = null ) {
2027  if ( $this->getRepo()->getReadOnlyReason() !== false ) {
2028  return $this->readOnlyFatalStatus();
2029  }
2030 
2031  $batch = new LocalFileDeleteBatch( $this, $reason, $suppress, $user );
2032 
2033  $this->lock();
2034  $batch->addOld( $archiveName );
2035  $status = $batch->execute();
2036  $this->unlock();
2037 
2038  $this->purgeOldThumbnails( $archiveName );
2039  if ( $status->isOK() ) {
2040  $this->purgeDescription();
2041  }
2042 
2044  new CdnCacheUpdate( [ $this->getArchiveUrl( $archiveName ) ] ),
2046  );
2047 
2048  return $status;
2049  }
2050 
2062  function restore( $versions = [], $unsuppress = false ) {
2063  if ( $this->getRepo()->getReadOnlyReason() !== false ) {
2064  return $this->readOnlyFatalStatus();
2065  }
2066 
2067  $batch = new LocalFileRestoreBatch( $this, $unsuppress );
2068 
2069  $this->lock();
2070  if ( !$versions ) {
2071  $batch->addAll();
2072  } else {
2073  $batch->addIds( $versions );
2074  }
2075  $status = $batch->execute();
2076  if ( $status->isGood() ) {
2077  $cleanupStatus = $batch->cleanup();
2078  $cleanupStatus->successCount = 0;
2079  $cleanupStatus->failCount = 0;
2080  $status->merge( $cleanupStatus );
2081  }
2082 
2083  $this->unlock();
2084  return $status;
2085  }
2086 
2096  function getDescriptionUrl() {
2097  return $this->title->getLocalURL();
2098  }
2099 
2109  $store = MediaWikiServices::getInstance()->getRevisionStore();
2110  $revision = $store->getRevisionByTitle( $this->title, 0, Revision::READ_NORMAL );
2111  if ( !$revision ) {
2112  return false;
2113  }
2114 
2115  $renderer = MediaWikiServices::getInstance()->getRevisionRenderer();
2116  $rendered = $renderer->getRenderedRevision( $revision, new ParserOptions( null, $lang ) );
2117 
2118  if ( !$rendered ) {
2119  // audience check failed
2120  return false;
2121  }
2122 
2123  $pout = $rendered->getRevisionParserOutput();
2124  return $pout->getText();
2125  }
2126 
2132  function getDescription( $audience = self::FOR_PUBLIC, User $user = null ) {
2133  $this->load();
2134  if ( $audience == self::FOR_PUBLIC && $this->isDeleted( self::DELETED_COMMENT ) ) {
2135  return '';
2136  } elseif ( $audience == self::FOR_THIS_USER
2137  && !$this->userCan( self::DELETED_COMMENT, $user )
2138  ) {
2139  return '';
2140  } else {
2141  return $this->description;
2142  }
2143  }
2144 
2148  function getTimestamp() {
2149  $this->load();
2150 
2151  return $this->timestamp;
2152  }
2153 
2157  public function getDescriptionTouched() {
2158  // The DB lookup might return false, e.g. if the file was just deleted, or the shared DB repo
2159  // itself gets it from elsewhere. To avoid repeating the DB lookups in such a case, we
2160  // need to differentiate between null (uninitialized) and false (failed to load).
2161  if ( $this->descriptionTouched === null ) {
2162  $cond = [
2163  'page_namespace' => $this->title->getNamespace(),
2164  'page_title' => $this->title->getDBkey()
2165  ];
2166  $touched = $this->repo->getReplicaDB()->selectField( 'page', 'page_touched', $cond, __METHOD__ );
2167  $this->descriptionTouched = $touched ? wfTimestamp( TS_MW, $touched ) : false;
2168  }
2169 
2171  }
2172 
2176  function getSha1() {
2177  $this->load();
2178  // Initialise now if necessary
2179  if ( $this->sha1 == '' && $this->fileExists ) {
2180  $this->lock();
2181 
2182  $this->sha1 = $this->repo->getFileSha1( $this->getPath() );
2183  if ( !wfReadOnly() && strval( $this->sha1 ) != '' ) {
2184  $dbw = $this->repo->getMasterDB();
2185  $dbw->update( 'image',
2186  [ 'img_sha1' => $this->sha1 ],
2187  [ 'img_name' => $this->getName() ],
2188  __METHOD__ );
2189  $this->invalidateCache();
2190  }
2191 
2192  $this->unlock();
2193  }
2194 
2195  return $this->sha1;
2196  }
2197 
2201  function isCacheable() {
2202  $this->load();
2203 
2204  // If extra data (metadata) was not loaded then it must have been large
2205  return $this->extraDataLoaded
2206  && strlen( serialize( $this->metadata ) ) <= self::CACHE_FIELD_MAX_LEN;
2207  }
2208 
2213  public function acquireFileLock() {
2214  return Status::wrap( $this->getRepo()->getBackend()->lockFiles(
2215  [ $this->getPath() ], LockManager::LOCK_EX, 10
2216  ) );
2217  }
2218 
2223  public function releaseFileLock() {
2224  return Status::wrap( $this->getRepo()->getBackend()->unlockFiles(
2225  [ $this->getPath() ], LockManager::LOCK_EX
2226  ) );
2227  }
2228 
2238  public function lock() {
2239  if ( !$this->locked ) {
2240  $logger = LoggerFactory::getInstance( 'LocalFile' );
2241 
2242  $dbw = $this->repo->getMasterDB();
2243  $makesTransaction = !$dbw->trxLevel();
2244  $dbw->startAtomic( self::ATOMIC_SECTION_LOCK );
2245  // T56736: use simple lock to handle when the file does not exist.
2246  // SELECT FOR UPDATE prevents changes, not other SELECTs with FOR UPDATE.
2247  // Also, that would cause contention on INSERT of similarly named rows.
2248  $status = $this->acquireFileLock(); // represents all versions of the file
2249  if ( !$status->isGood() ) {
2250  $dbw->endAtomic( self::ATOMIC_SECTION_LOCK );
2251  $logger->warning( "Failed to lock '{file}'", [ 'file' => $this->name ] );
2252 
2253  throw new LocalFileLockError( $status );
2254  }
2255  // Release the lock *after* commit to avoid row-level contention.
2256  // Make sure it triggers on rollback() as well as commit() (T132921).
2257  $dbw->onTransactionResolution(
2258  function () use ( $logger ) {
2259  $status = $this->releaseFileLock();
2260  if ( !$status->isGood() ) {
2261  $logger->error( "Failed to unlock '{file}'", [ 'file' => $this->name ] );
2262  }
2263  },
2264  __METHOD__
2265  );
2266  // Callers might care if the SELECT snapshot is safely fresh
2267  $this->lockedOwnTrx = $makesTransaction;
2268  }
2269 
2270  $this->locked++;
2271 
2272  return $this->lockedOwnTrx;
2273  }
2274 
2283  public function unlock() {
2284  if ( $this->locked ) {
2285  --$this->locked;
2286  if ( !$this->locked ) {
2287  $dbw = $this->repo->getMasterDB();
2288  $dbw->endAtomic( self::ATOMIC_SECTION_LOCK );
2289  $this->lockedOwnTrx = false;
2290  }
2291  }
2292  }
2293 
2297  protected function readOnlyFatalStatus() {
2298  return $this->getRepo()->newFatal( 'filereadonlyerror', $this->getName(),
2299  $this->getRepo()->getName(), $this->getRepo()->getReadOnlyReason() );
2300  }
2301 
2305  function __destruct() {
2306  $this->unlock();
2307  }
2308 }
const SCHEMA_COMPAT_WRITE_OLD
Definition: Defines.php:280
exists()
canRender inherited
Definition: LocalFile.php:985
User $user
Uploader.
Definition: LocalFile.php:115
deferred txt A few of the database updates required by various functions here can be deferred until after the result page is displayed to the user For updating the view updating the linked to tables after a etc PHP does not yet have any way to tell the server to actually return and disconnect while still running these but it might have such a feature in the future We handle these by creating a deferred update object and putting those objects on a global list
Definition: deferred.txt:11
invalidateCache()
Purge the file object/metadata cache.
Definition: LocalFile.php:393
getCacheFields( $prefix='img_')
Returns the list of object properties that are included as-is in the cache.
Definition: LocalFile.php:421
MediaHandler $handler
Definition: File.php:124
addWatch( $title, $checkRights=self::CHECK_USER_RIGHTS)
Watch an article.
Definition: User.php:3808
string $media_type
MEDIATYPE_xxx (bitmap, drawing, audio...)
Definition: LocalFile.php:73
bool $extraDataLoaded
Whether or not lazy-loaded data has been loaded from the database.
Definition: LocalFile.php:91
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:2290
const VERSION
Definition: LocalFile.php:56
div flags Integer display flags(NO_ACTION_LINK, NO_EXTRA_USER_LINKS) 'LogException' returning false will NOT prevent logging a wrapping ErrorException create2 Corresponds to logging log_action database field and which is displayed in the UI & $revert
Definition: hooks.txt:2159
processing should stop and the error should be shown to the user * false
Definition: hooks.txt:187
serialize()
unprefixRow( $row, $prefix='img_')
Definition: LocalFile.php:564
null means default in associative array with keys and values unescaped Should be merged with default with a value of false meaning to suppress the attribute in associative array with keys and values unescaped noclasses & $ret
Definition: hooks.txt:1982
string $minor_mime
Minor MIME type.
Definition: LocalFile.php:109
Apache License January AND DISTRIBUTION Definitions License shall mean the terms and conditions for use
if(PHP_SAPI !='cli-server') if(!isset( $_SERVER['SCRIPT_FILENAME'])) $file
Definition: router.php:42
div flags Integer display flags(NO_ACTION_LINK, NO_EXTRA_USER_LINKS) 'LogException' returning false will NOT prevent logging $e
Definition: hooks.txt:2159
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:1678
purgeMetadataCache()
Refresh metadata in memcached, but don&#39;t touch thumbnails or CDN.
Definition: LocalFile.php:1029
releaseFileLock()
Definition: LocalFile.php:2223
const DELETE_SOURCE
Definition: File.php:76
int $wgActorTableSchemaMigrationStage
Actor table schema migration stage.
if(!isset( $args[0])) $lang
static newFromEntry(LogEntry $entry)
Constructs a new formatter suitable for given entry.
width
const DELETE_SOURCE
Definition: FileRepo.php:40
getSize()
Returns the size of the image file, in bytes.
Definition: LocalFile.php:948
Handles purging appropriate CDN URLs given a title (or titles)
setProps( $info)
Set properties in this object to be equal to those given in the associative array $info...
Definition: LocalFile.php:771
getThumbnails( $archiveName=false)
getTransformScript inherited
Definition: LocalFile.php:1006
Helper class for file undeletion.
unlock()
Decrement the lock reference count and end the atomic section if it reaches zero. ...
Definition: LocalFile.php:2283
string $major_mime
Major MIME type.
Definition: LocalFile.php:106
string $sha1
SHA-1 base 36 content hash.
Definition: LocalFile.php:85
$source
$value
const SCHEMA_COMPAT_READ_NEW
Definition: Defines.php:283
isMissing()
splitMime inherited
Definition: LocalFile.php:813
either a unescaped string or a HtmlArmor object after in associative array form externallinks including delete and has completed for all link tables whether this was an auto creation use $formDescriptor instead default is conds Array Extra conditions for the No matching items in log is displayed if loglist is empty msgKey Array If you want a nice box with a set this to the key of the message First element is the message key
Definition: hooks.txt:2151
injection txt This is an overview of how MediaWiki makes use of dependency injection The design described here grew from the discussion of RFC T384 The term dependency this means that anything an object needs to operate should be injected from the the object itself should only know narrow no concrete implementation of the logic it relies on The requirement to inject everything typically results in an architecture that based on two main types of and essentially stateless service objects that use other service objects to operate on the value objects As of the beginning MediaWiki is only starting to use the DI approach Much of the code still relies on global state or direct resulting in a highly cyclical dependency MediaWikiServices
Definition: injection.txt:23
assertRepoDefined()
Assert that $this->repo is set to a valid FileRepo instance.
Definition: File.php:2280
move( $target)
getLinksTo inherited
Definition: LocalFile.php:1908
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:1875
purgeThumbnails( $options=[])
Delete cached transformed files for the current version only.
Definition: LocalFile.php:1082
getArchiveUrl( $suffix=false)
Get the URL of the archive directory, or a particular file if $suffix is specified.
Definition: File.php:1658
getPrefixedText()
Get the prefixed title with spaces.
Definition: Title.php:1696
quickImport( $src, $dst, $options=null)
Import a file from the local file system into the repo.
Definition: FileRepo.php:977
isOld()
Returns true if the image is an old version STUB.
Definition: File.php:1885
const METADATA_BAD
static isStoragePath( $path)
Check if a given path is a "mwstore://" path.
int $bits
Returned by getimagesize (loadFromXxx)
Definition: LocalFile.php:70
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:1181
loadFromDB( $flags=0)
Load file metadata from the DB.
Definition: LocalFile.php:458
title
const ATOMIC_SECTION_LOCK
Definition: LocalFile.php:141
bool $fileExists
Does the file exist on disk? (loadFromXxx)
Definition: LocalFile.php:61
getTitle()
Return the associated title object.
Definition: File.php:336
Title string bool $title
Definition: File.php:109
this hook is for auditing only RecentChangesLinked and Watchlist Do not use this to implement individual filters if they are compatible with the ChangesListFilter and ChangesListFilterGroup structure use sub classes of those in conjunction with the ChangesListSpecialPageStructuredFilters hook This hook can be used to implement filters that do not implement that or custom behavior that is not an individual filter e g Watchlist & $tables
Definition: hooks.txt:979
getName()
Get the user name, or the IP of an anonymous user.
Definition: User.php:2311
restore( $versions=[], $unsuppress=false)
Restore all or specified deleted revisions to the given file.
Definition: LocalFile.php:2062
purgeEverything()
Purge metadata and all affected pages when the file is created, deleted, or majorly updated...
Definition: File.php:1464
__destruct()
Clean up any dangling locks.
Definition: LocalFile.php:2305
getBackend()
Get the file backend instance.
Definition: FileRepo.php:216
static newFromAnyId( $userId, $userName, $actorId, $wikiId=false)
Static factory method for creation from an ID, name, and/or actor ID.
Definition: User.php:686
static newFromRow( $row, $repo)
Create a LocalFile from a title Do not call this except from inside a repo class. ...
Definition: LocalFile.php:168
and how to run hooks for an and one after Each event has a name
Definition: hooks.txt:6
getThumbnails()
Get all thumbnail names previously generated for this file STUB Overridden by LocalFile.
Definition: File.php:1433
$batch
Definition: linkcache.txt:23
getVirtualUrl( $suffix=false)
Get the public zone virtual URL for a current version source file.
Definition: File.php:1734
getDescriptionShortUrl()
Get short description URL for a file based on the page ID.
Definition: LocalFile.php:914
nextHistoryLine()
Returns the history of this file, line by line.
Definition: LocalFile.php:1238
The User object encapsulates all of the user-specific settings (user_id, name, rights, email address, options, last login time).
Definition: User.php:51
static newFatal( $message)
Factory function for fatal errors.
Definition: StatusValue.php:68
Status::newGood()` to allow deletion, and then `return false` from the hook function. Ensure you consume the 'ChangeTagAfterDelete' hook to carry out custom deletion actions. $tag:name of the tag $user:user initiating the action & $status:Status object. See above. 'ChangeTagsListActive':Allows you to nominate which of the tags your extension uses are in active use. & $tags:list of all active tags. Append to this array. 'ChangeTagsAfterUpdateTags':Called after tags have been updated with the ChangeTags::updateTags function. Params:$addedTags:tags effectively added in the update $removedTags:tags effectively removed in the update $prevTags:tags that were present prior to the update $rc_id:recentchanges table id $rev_id:revision table id $log_id:logging table id $params:tag params $rc:RecentChange being tagged when the tagging accompanies the action, or null $user:User who performed the tagging when the tagging is subsequent to the action, or null 'ChangeTagsAllowedAdd':Called when checking if a user can add tags to a change. & $allowedTags:List of all the tags the user is allowed to add. Any tags the user wants to add( $addTags) that are not in this array will cause it to fail. You may add or remove tags to this array as required. $addTags:List of tags user intends to add. $user:User who is adding the tags. 'ChangeUserGroups':Called before user groups are changed. $performer:The User who will perform the change $user:The User whose groups will be changed & $add:The groups that will be added & $remove:The groups that will be removed 'Collation::factory':Called if $wgCategoryCollation is an unknown collation. $collationName:Name of the collation in question & $collationObject:Null. Replace with a subclass of the Collation class that implements the collation given in $collationName. 'ConfirmEmailComplete':Called after a user 's email has been confirmed successfully. $user:user(object) whose email is being confirmed 'ContentAlterParserOutput':Modify parser output for a given content object. Called by Content::getParserOutput after parsing has finished. Can be used for changes that depend on the result of the parsing but have to be done before LinksUpdate is called(such as adding tracking categories based on the rendered HTML). $content:The Content to render $title:Title of the page, as context $parserOutput:ParserOutput to manipulate 'ContentGetParserOutput':Customize parser output for a given content object, called by AbstractContent::getParserOutput. May be used to override the normal model-specific rendering of page content. $content:The Content to render $title:Title of the page, as context $revId:The revision ID, as context $options:ParserOptions for rendering. To avoid confusing the parser cache, the output can only depend on parameters provided to this hook function, not on global state. $generateHtml:boolean, indicating whether full HTML should be generated. If false, generation of HTML may be skipped, but other information should still be present in the ParserOutput object. & $output:ParserOutput, to manipulate or replace 'ContentHandlerDefaultModelFor':Called when the default content model is determined for a given title. May be used to assign a different model for that title. $title:the Title in question & $model:the model name. Use with CONTENT_MODEL_XXX constants. 'ContentHandlerForModelID':Called when a ContentHandler is requested for a given content model name, but no entry for that model exists in $wgContentHandlers. Note:if your extension implements additional models via this hook, please use GetContentModels hook to make them known to core. $modeName:the requested content model name & $handler:set this to a ContentHandler object, if desired. 'ContentModelCanBeUsedOn':Called to determine whether that content model can be used on a given page. This is especially useful to prevent some content models to be used in some special location. $contentModel:ID of the content model in question $title:the Title in question. & $ok:Output parameter, whether it is OK to use $contentModel on $title. Handler functions that modify $ok should generally return false to prevent further hooks from further modifying $ok. 'ContribsPager::getQueryInfo':Before the contributions query is about to run & $pager:Pager object for contributions & $queryInfo:The query for the contribs Pager 'ContribsPager::reallyDoQuery':Called before really executing the query for My Contributions & $data:an array of results of all contribs queries $pager:The ContribsPager object hooked into $offset:Index offset, inclusive $limit:Exact query limit $descending:Query direction, false for ascending, true for descending 'ContributionsLineEnding':Called before a contributions HTML line is finished $page:SpecialPage object for contributions & $ret:the HTML line $row:the DB row for this line & $classes:the classes to add to the surrounding< li > & $attribs:associative array of other HTML attributes for the< li > element. Currently only data attributes reserved to MediaWiki are allowed(see Sanitizer::isReservedDataAttribute). 'ContributionsToolLinks':Change tool links above Special:Contributions $id:User identifier $title:User page title & $tools:Array of tool links $specialPage:SpecialPage instance for context and services. Can be either SpecialContributions or DeletedContributionsPage. Extensions should type hint against a generic SpecialPage though. 'ConvertContent':Called by AbstractContent::convert when a conversion to another content model is requested. Handler functions that modify $result should generally return false to disable further attempts at conversion. $content:The Content object to be converted. $toModel:The ID of the content model to convert to. $lossy:boolean indicating whether lossy conversion is allowed. & $result:Output parameter, in case the handler function wants to provide a converted Content object. Note that $result->getContentModel() must return $toModel. 'ContentSecurityPolicyDefaultSource':Modify the allowed CSP load sources. This affects all directives except for the script directive. If you want to add a script source, see ContentSecurityPolicyScriptSource hook. & $defaultSrc:Array of Content-Security-Policy allowed sources $policyConfig:Current configuration for the Content-Security-Policy header $mode:ContentSecurityPolicy::REPORT_ONLY_MODE or ContentSecurityPolicy::FULL_MODE depending on type of header 'ContentSecurityPolicyDirectives':Modify the content security policy directives. Use this only if ContentSecurityPolicyDefaultSource and ContentSecurityPolicyScriptSource do not meet your needs. & $directives:Array of CSP directives $policyConfig:Current configuration for the CSP header $mode:ContentSecurityPolicy::REPORT_ONLY_MODE or ContentSecurityPolicy::FULL_MODE depending on type of header 'ContentSecurityPolicyScriptSource':Modify the allowed CSP script sources. Note that you also have to use ContentSecurityPolicyDefaultSource if you want non-script sources to be loaded from whatever you add. & $scriptSrc:Array of CSP directives $policyConfig:Current configuration for the CSP header $mode:ContentSecurityPolicy::REPORT_ONLY_MODE or ContentSecurityPolicy::FULL_MODE depending on type of header 'CustomEditor':When invoking the page editor Return true to allow the normal editor to be used, or false if implementing a custom editor, e.g. for a special namespace, etc. $article:Article being edited $user:User performing the edit 'DeletedContribsPager::reallyDoQuery':Called before really executing the query for Special:DeletedContributions Similar to ContribsPager::reallyDoQuery & $data:an array of results of all contribs queries $pager:The DeletedContribsPager object hooked into $offset:Index offset, inclusive $limit:Exact query limit $descending:Query direction, false for ascending, true for descending 'DeletedContributionsLineEnding':Called before a DeletedContributions HTML line is finished. Similar to ContributionsLineEnding $page:SpecialPage object for DeletedContributions & $ret:the HTML line $row:the DB row for this line & $classes:the classes to add to the surrounding< li > & $attribs:associative array of other HTML attributes for the< li > element. Currently only data attributes reserved to MediaWiki are allowed(see Sanitizer::isReservedDataAttribute). 'DeleteUnknownPreferences':Called by the cleanupPreferences.php maintenance script to build a WHERE clause with which to delete preferences that are not known about. This hook is used by extensions that have dynamically-named preferences that should not be deleted in the usual cleanup process. For example, the Gadgets extension creates preferences prefixed with 'gadget-', and so anything with that prefix is excluded from the deletion. &where:An array that will be passed as the $cond parameter to IDatabase::select() to determine what will be deleted from the user_properties table. $db:The IDatabase object, useful for accessing $db->buildLike() etc. 'DifferenceEngineAfterLoadNewText':called in DifferenceEngine::loadNewText() after the new revision 's content has been loaded into the class member variable $differenceEngine->mNewContent but before returning true from this function. $differenceEngine:DifferenceEngine object 'DifferenceEngineLoadTextAfterNewContentIsLoaded':called in DifferenceEngine::loadText() after the new revision 's content has been loaded into the class member variable $differenceEngine->mNewContent but before checking if the variable 's value is null. This hook can be used to inject content into said class member variable. $differenceEngine:DifferenceEngine object 'DifferenceEngineMarkPatrolledLink':Allows extensions to change the "mark as patrolled" link which is shown both on the diff header as well as on the bottom of a page, usually wrapped in a span element which has class="patrollink". $differenceEngine:DifferenceEngine object & $markAsPatrolledLink:The "mark as patrolled" link HTML(string) $rcid:Recent change ID(rc_id) for this change(int) 'DifferenceEngineMarkPatrolledRCID':Allows extensions to possibly change the rcid parameter. For example the rcid might be set to zero due to the user being the same as the performer of the change but an extension might still want to show it under certain conditions. & $rcid:rc_id(int) of the change or 0 $differenceEngine:DifferenceEngine object $change:RecentChange object $user:User object representing the current user 'DifferenceEngineNewHeader':Allows extensions to change the $newHeader variable, which contains information about the new revision, such as the revision 's author, whether the revision was marked as a minor edit or not, etc. $differenceEngine:DifferenceEngine object & $newHeader:The string containing the various #mw-diff-otitle[1-5] divs, which include things like revision author info, revision comment, RevisionDelete link and more $formattedRevisionTools:Array containing revision tools, some of which may have been injected with the DiffRevisionTools hook $nextlink:String containing the link to the next revision(if any) $status
Definition: hooks.txt:1263
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:1824
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:1182
int $historyRes
Result of the query for the file&#39;s history (nextHistoryLine)
Definition: LocalFile.php:103
bool $locked
True if the image row is locked.
Definition: LocalFile.php:130
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:226
wfReadOnly()
Check whether the wiki is in read-only mode.
string $metadata
Handler-specific metadata.
Definition: LocalFile.php:82
string $descriptionTouched
TS_MW timestamp of the last change of the file description.
Definition: LocalFile.php:121
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:589
string $repoClass
Definition: LocalFile.php:97
getThumbUrl( $suffix=false)
Get the URL of the thumbnail directory, or a particular file if $suffix is specified.
Definition: File.php:1714
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 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:1979
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:443
const LOAD_ALL
Definition: LocalFile.php:139
getHandler()
Get a MediaHandler instance for this file.
Definition: File.php:1388
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:309
static factory(array $deltas)
getDescriptionTouched()
Definition: LocalFile.php:2157
const LOCK_EX
Definition: LockManager.php:69
const SCHEMA_COMPAT_WRITE_NEW
Definition: Defines.php:282
getFileSha1( $virtualUrl)
Get the sha1 (base 36) of a file with a given virtual URL/storage path.
Definition: FileRepo.php:1602
getRel()
Get the path of the file relative to the public zone root.
Definition: File.php:1537
$res
Definition: database.txt:21
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:2168
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:1322
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:1040
$wgUploadThumbnailRenderMap
When defined, is an array of thumbnail widths to be rendered at upload time.
static selectFields()
Fields in the image table.
Definition: LocalFile.php:209
acquireFileLock()
Definition: LocalFile.php:2213
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:1338
const CACHE_FIELD_MAX_LEN
Definition: LocalFile.php:58
This document is intended to provide useful advice for parties seeking to redistribute MediaWiki to end users It s targeted particularly at maintainers for Linux since it s been observed that distribution packages of MediaWiki often break We ve consistently had to recommend that users seeking support use official tarballs instead of their distribution s and this often solves whatever problem the user is having It would be nice if this could such and we might be restricted by PHP settings such as safe mode or open_basedir We cannot assume that the software even has read access anywhere useful Many shared hosts run all users web applications under the same user
Wikitext formatted, in the key only.
Definition: distributors.txt:9
loadFromRow( $row, $prefix='img_')
Load file metadata from a DB result row.
Definition: LocalFile.php:635
static isVirtualUrl( $url)
Determine if a string is an mwrepo:// URL.
Definition: FileRepo.php:255
loadExtraFromDB()
Load lazy file metadata from the DB.
Definition: LocalFile.php:490
$cache
Definition: mcc.php:33
const EDIT_SUPPRESS_RC
Definition: Defines.php:151
null means default in associative array with keys and values unescaped Should be merged with default with a value of false meaning to suppress the attribute in associative array with keys and values unescaped & $options
Definition: hooks.txt:1982
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:185
maybeUpgradeRow()
Upgrade a row if it needs it.
Definition: LocalFile.php:670
readOnlyFatalStatus()
Definition: LocalFile.php:2297
Helper class for file movement.
bool $upgraded
Whether the row was upgraded on load.
Definition: LocalFile.php:124
static newExtraneousContext(Title $title, $request=[])
Create a new extraneous context.
this hook is for auditing only or null if authentication failed before getting that far or null if we can t even determine that When $user is not null
Definition: hooks.txt:780
bool $lockedOwnTrx
True if the image row is locked with a lock initiated transaction.
Definition: LocalFile.php:133
bool $dataLoaded
Whether or not core data has been loaded from the database (loadFromXxx)
Definition: LocalFile.php:88
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:1115
getMetadata()
Get handler-specific metadata.
Definition: LocalFile.php:930
presenting them properly to the user as errors is done by the caller return true use this to change the list i e etc $rev
Definition: hooks.txt:1766
purgeOldThumbnails( $archiveName)
Delete cached transformed files for an archived version only.
Definition: LocalFile.php:1059
string $mime
MIME type, determined by MimeAnalyzer::guessMimeType.
Definition: LocalFile.php:76
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:218
This document is intended to provide useful advice for parties seeking to redistribute MediaWiki to end users It s targeted particularly at maintainers for Linux since it s been observed that distribution packages of MediaWiki often break We ve consistently had to recommend that users seeking support use official tarballs instead of their distribution s and this often solves whatever problem the user is having It would be nice if this could such as
Definition: distributors.txt:9
Special handling for file pages.
Helper class for file deletion.
const SCHEMA_COMPAT_WRITE_BOTH
Definition: Defines.php:284
if(defined( 'MW_SETUP_CALLBACK')) $fname
Customization point after all loading (constants, functions, classes, DefaultSettings, LocalSettings).
Definition: Setup.php:123
purgeThumbList( $dir, $files)
Delete a list of thumbnails visible at urls.
Definition: LocalFile.php:1142
getDescription( $audience=self::FOR_PUBLIC, User $user=null)
Definition: LocalFile.php:2132
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:1548
getDescriptionText(Language $lang=null)
Get the HTML text of the description page This is not used by ImagePage for local files...
Definition: LocalFile.php:2108
static makeTitle( $ns, $title, $fragment='', $interwiki='')
Create a new Title from a namespace index and a DB key.
Definition: Title.php:589
static queueRecursiveJobsForTable(Title $title, $table, $action='unknown', $userName='unknown')
Queue a RefreshLinks job for any table.
injection txt This is an overview of how MediaWiki makes use of dependency injection The design described here grew from the discussion of RFC T384 The term dependency this means that anything an object needs to operate should be injected from the the object itself should only know narrow no concrete implementation of the logic it relies on The requirement to inject everything typically results in an architecture that based on two main types of and essentially stateless service objects that use other service objects to operate on the value objects As of the beginning MediaWiki is only starting to use the DI approach Much of the code still relies on global state or direct resulting in a highly cyclical dependency which acts as the top level factory for services in MediaWiki which can be used to gain access to default instances of various services MediaWikiServices however also allows new services to be defined and default services to be redefined Services are defined or redefined by providing a callback the instantiator that will return a new instance of the service When it will create an instance of MediaWikiServices and populate it with the services defined in the files listed by thereby bootstrapping the DI framework Per $wgServiceWiringFiles lists includes ServiceWiring php
Definition: injection.txt:35
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:1621
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:716
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:2284
Class representing a non-directory file on the file system.
Definition: FSFile.php:29
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:1445
deleteOld( $archiveName, $reason, $suppress=false, $user=null)
Delete an old version of the file.
Definition: LocalFile.php:2026
you have access to all of the normal MediaWiki so you can get a DB use the etc For full docs on the Maintenance class
Definition: maintenance.txt:52
getActorId(IDatabase $dbw=null)
Get the user&#39;s actor ID.
Definition: User.php:2350
const EDIT_NEW
Definition: Defines.php:148
Job for asynchronous rendering of thumbnails.
bool $upgrading
Whether the row was scheduled to upgrade on load.
Definition: LocalFile.php:127
lock()
Start an atomic DB section and lock the image for update or increments a reference counter if the loc...
Definition: LocalFile.php:2238
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:94
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:515
wfDeprecated( $function, $version=false, $component=false, $callerOffset=2)
Throws a warning that $function is deprecated.
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:136
const METADATA_COMPATIBLE
getDescriptionUrl()
isMultipage inherited
Definition: LocalFile.php:2096
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:55
MimeMagic helper wrapper.
Definition: MWFileProps.php:28
MediaWiki Logger LoggerFactory implements a PSR [0] compatible message logging system Named Psr Log LoggerInterface instances can be obtained from the MediaWiki Logger LoggerFactory::getInstance() static method. MediaWiki\Logger\LoggerFactory expects a class implementing the MediaWiki\Logger\Spi interface to act as a factory for new Psr\Log\LoggerInterface instances. The "Spi" in MediaWiki\Logger\Spi stands for "service provider interface". An SPI is an API intended to be implemented or extended by a third party. This software design pattern is intended to enable framework extension and replaceable components. It is specifically used in the MediaWiki\Logger\LoggerFactory service to allow alternate PSR-3 logging implementations to be easily integrated with MediaWiki. The service provider interface allows the backend logging library to be implemented in multiple ways. The $wgMWLoggerDefaultSpi global provides the classname of the default MediaWiki\Logger\Spi implementation to be loaded at runtime. This can either be the name of a class implementing the MediaWiki\Logger\Spi with a zero argument const ructor or a callable that will return an MediaWiki\Logger\Spi instance. Alternately the MediaWiki\Logger\LoggerFactory MediaWiki Logger LoggerFactory
Definition: logger.txt:5
static singleton( $domain=false)
string $timestamp
Upload timestamp.
Definition: LocalFile.php:112
getHeight( $page=1)
Return the height of the image.
Definition: LocalFile.php:860
int $height
Image height.
Definition: LocalFile.php:67
loadFromFile()
Load metadata from the file itself.
Definition: LocalFile.php:410
string $description
Description of current revision of the file.
Definition: LocalFile.php:118
publishTo( $src, $dstRel, $flags=0, array $options=[])
Move or copy a file to a specified location.
Definition: LocalFile.php:1843
static getQueryInfo(array $options=[])
Return the tables, fields, and join conditions to be selected to create a new localfile object...
Definition: LocalFile.php:253
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:969
loadFromCache()
Try to load file metadata from memcached, falling back to the database.
Definition: LocalFile.php:325
getMutableCacheKeys(WANObjectCache $cache)
Definition: LocalFile.php:318
int $width
Image width.
Definition: LocalFile.php:64
getMimeType()
Returns the MIME type of the file.
Definition: LocalFile.php:958
resetHistory()
Reset the history pointer to the first element of the history.
Definition: LocalFile.php:1281
static newNullRevision( $dbw, $pageId, $summary, $minor, $user=null)
Create a new null-revision for insertion into a page&#39;s history.
Definition: Revision.php:1196
int $size
Size in bytes (loadFromXxx)
Definition: LocalFile.php:79
hasSha1Storage()
Returns whether or not storage is SHA-1 based.
Definition: FileRepo.php:1929
isDeleted( $field)
Is this file a "deleted" file in a private archive? STUB.
Definition: File.php:1896
getWidth( $page=1)
Return the width of the image.
Definition: LocalFile.php:828
load( $flags=0)
Load file metadata from cache or DB, unless already loaded.
Definition: LocalFile.php:652
purgeDescription()
Purge the file description page, but don&#39;t go after pages using the file.
Definition: File.php:1452
getThumbPath( $suffix=false)
Get the path of the thumbnail directory, or a particular file if $suffix is specified.
Definition: File.php:1634
$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:155
getUser( $type='text')
Returns user who uploaded the file.
Definition: LocalFile.php:893
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:100
__construct( $title, $repo)
Do not call this except from inside a repo class.
Definition: LocalFile.php:291
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:1411
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