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 
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 
210  static function selectFields() {
212 
213  wfDeprecated( __METHOD__, '1.31' );
214  if ( $wgActorTableSchemaMigrationStage & SCHEMA_COMPAT_READ_NEW ) {
215  // If code is using this instead of self::getQueryInfo(), there's a
216  // decent chance it's going to try to directly access
217  // $row->img_user or $row->img_user_text and we can't give it
218  // useful values here once those aren't being used anymore.
219  throw new BadMethodCallException(
220  'Cannot use ' . __METHOD__
221  . ' when $wgActorTableSchemaMigrationStage has SCHEMA_COMPAT_READ_NEW'
222  );
223  }
224 
225  return [
226  'img_name',
227  'img_size',
228  'img_width',
229  'img_height',
230  'img_metadata',
231  'img_bits',
232  'img_media_type',
233  'img_major_mime',
234  'img_minor_mime',
235  'img_user',
236  'img_user_text',
237  'img_actor' => 'NULL',
238  'img_timestamp',
239  'img_sha1',
240  ] + MediaWikiServices::getInstance()->getCommentStore()->getFields( 'img_description' );
241  }
242 
254  public static function getQueryInfo( array $options = [] ) {
255  $commentQuery = MediaWikiServices::getInstance()->getCommentStore()->getJoin( 'img_description' );
256  $actorQuery = ActorMigration::newMigration()->getJoin( 'img_user' );
257  $ret = [
258  'tables' => [ 'image' ] + $commentQuery['tables'] + $actorQuery['tables'],
259  'fields' => [
260  'img_name',
261  'img_size',
262  'img_width',
263  'img_height',
264  'img_metadata',
265  'img_bits',
266  'img_media_type',
267  'img_major_mime',
268  'img_minor_mime',
269  'img_timestamp',
270  'img_sha1',
271  ] + $commentQuery['fields'] + $actorQuery['fields'],
272  'joins' => $commentQuery['joins'] + $actorQuery['joins'],
273  ];
274 
275  if ( in_array( 'omit-nonlazy', $options, true ) ) {
276  // Internal use only for getting only the lazy fields
277  $ret['fields'] = [];
278  }
279  if ( !in_array( 'omit-lazy', $options, true ) ) {
280  // Note: Keep this in sync with self::getLazyCacheFields()
281  $ret['fields'][] = 'img_metadata';
282  }
283 
284  return $ret;
285  }
286 
292  function __construct( $title, $repo ) {
293  parent::__construct( $title, $repo );
294 
295  $this->metadata = '';
296  $this->historyLine = 0;
297  $this->historyRes = null;
298  $this->dataLoaded = false;
299  $this->extraDataLoaded = false;
300 
301  $this->assertRepoDefined();
302  $this->assertTitleDefined();
303  }
304 
310  function getCacheKey() {
311  return $this->repo->getSharedCacheKey( 'file', sha1( $this->getName() ) );
312  }
313 
320  return [ $this->getCacheKey() ];
321  }
322 
326  private function loadFromCache() {
327  $this->dataLoaded = false;
328  $this->extraDataLoaded = false;
329 
330  $key = $this->getCacheKey();
331  if ( !$key ) {
332  $this->loadFromDB( self::READ_NORMAL );
333 
334  return;
335  }
336 
337  $cache = MediaWikiServices::getInstance()->getMainWANObjectCache();
338  $cachedValues = $cache->getWithSetCallback(
339  $key,
340  $cache::TTL_WEEK,
341  function ( $oldValue, &$ttl, array &$setOpts ) use ( $cache ) {
342  $setOpts += Database::getCacheSetOptions( $this->repo->getReplicaDB() );
343 
344  $this->loadFromDB( self::READ_NORMAL );
345 
346  $fields = $this->getCacheFields( '' );
347  $cacheVal['fileExists'] = $this->fileExists;
348  if ( $this->fileExists ) {
349  foreach ( $fields as $field ) {
350  $cacheVal[$field] = $this->$field;
351  }
352  }
353  $cacheVal['user'] = $this->user ? $this->user->getId() : 0;
354  $cacheVal['user_text'] = $this->user ? $this->user->getName() : '';
355  $cacheVal['actor'] = $this->user ? $this->user->getActorId() : null;
356 
357  // Strip off excessive entries from the subset of fields that can become large.
358  // If the cache value gets to large it will not fit in memcached and nothing will
359  // get cached at all, causing master queries for any file access.
360  foreach ( $this->getLazyCacheFields( '' ) as $field ) {
361  if ( isset( $cacheVal[$field] )
362  && strlen( $cacheVal[$field] ) > 100 * 1024
363  ) {
364  unset( $cacheVal[$field] ); // don't let the value get too big
365  }
366  }
367 
368  if ( $this->fileExists ) {
369  $ttl = $cache->adaptiveTTL( wfTimestamp( TS_UNIX, $this->timestamp ), $ttl );
370  } else {
371  $ttl = $cache::TTL_DAY;
372  }
373 
374  return $cacheVal;
375  },
376  [ 'version' => self::VERSION ]
377  );
378 
379  $this->fileExists = $cachedValues['fileExists'];
380  if ( $this->fileExists ) {
381  $this->setProps( $cachedValues );
382  }
383 
384  $this->dataLoaded = true;
385  $this->extraDataLoaded = true;
386  foreach ( $this->getLazyCacheFields( '' ) as $field ) {
387  $this->extraDataLoaded = $this->extraDataLoaded && isset( $cachedValues[$field] );
388  }
389  }
390 
394  public function invalidateCache() {
395  $key = $this->getCacheKey();
396  if ( !$key ) {
397  return;
398  }
399 
400  $this->repo->getMasterDB()->onTransactionPreCommitOrIdle(
401  function () use ( $key ) {
402  MediaWikiServices::getInstance()->getMainWANObjectCache()->delete( $key );
403  },
404  __METHOD__
405  );
406  }
407 
411  function loadFromFile() {
412  $props = $this->repo->getFileProps( $this->getVirtualUrl() );
413  $this->setProps( $props );
414  }
415 
422  protected function getCacheFields( $prefix = 'img_' ) {
423  if ( $prefix !== '' ) {
424  throw new InvalidArgumentException(
425  __METHOD__ . ' with a non-empty prefix is no longer supported.'
426  );
427  }
428 
429  // See self::getQueryInfo() for the fetching of the data from the DB,
430  // self::loadFromRow() for the loading of the object from the DB row,
431  // and self::loadFromCache() for the caching, and self::setProps() for
432  // populating the object from an array of data.
433  return [ 'size', 'width', 'height', 'bits', 'media_type',
434  'major_mime', 'minor_mime', 'metadata', 'timestamp', 'sha1', 'description' ];
435  }
436 
444  protected function getLazyCacheFields( $prefix = 'img_' ) {
445  if ( $prefix !== '' ) {
446  throw new InvalidArgumentException(
447  __METHOD__ . ' with a non-empty prefix is no longer supported.'
448  );
449  }
450 
451  // Keep this in sync with the omit-lazy option in self::getQueryInfo().
452  return [ 'metadata' ];
453  }
454 
459  function loadFromDB( $flags = 0 ) {
460  $fname = static::class . '::' . __FUNCTION__;
461 
462  # Unconditionally set loaded=true, we don't want the accessors constantly rechecking
463  $this->dataLoaded = true;
464  $this->extraDataLoaded = true;
465 
466  $dbr = ( $flags & self::READ_LATEST )
467  ? $this->repo->getMasterDB()
468  : $this->repo->getReplicaDB();
469 
470  $fileQuery = static::getQueryInfo();
471  $row = $dbr->selectRow(
472  $fileQuery['tables'],
473  $fileQuery['fields'],
474  [ 'img_name' => $this->getName() ],
475  $fname,
476  [],
477  $fileQuery['joins']
478  );
479 
480  if ( $row ) {
481  $this->loadFromRow( $row );
482  } else {
483  $this->fileExists = false;
484  }
485  }
486 
491  protected function loadExtraFromDB() {
492  $fname = static::class . '::' . __FUNCTION__;
493 
494  # Unconditionally set loaded=true, we don't want the accessors constantly rechecking
495  $this->extraDataLoaded = true;
496 
497  $fieldMap = $this->loadExtraFieldsWithTimestamp( $this->repo->getReplicaDB(), $fname );
498  if ( !$fieldMap ) {
499  $fieldMap = $this->loadExtraFieldsWithTimestamp( $this->repo->getMasterDB(), $fname );
500  }
501 
502  if ( $fieldMap ) {
503  foreach ( $fieldMap as $name => $value ) {
504  $this->$name = $value;
505  }
506  } else {
507  throw new MWException( "Could not find data for image '{$this->getName()}'." );
508  }
509  }
510 
516  private function loadExtraFieldsWithTimestamp( $dbr, $fname ) {
517  $fieldMap = false;
518 
519  $fileQuery = self::getQueryInfo( [ 'omit-nonlazy' ] );
520  $row = $dbr->selectRow(
521  $fileQuery['tables'],
522  $fileQuery['fields'],
523  [
524  'img_name' => $this->getName(),
525  'img_timestamp' => $dbr->timestamp( $this->getTimestamp() ),
526  ],
527  $fname,
528  [],
529  $fileQuery['joins']
530  );
531  if ( $row ) {
532  $fieldMap = $this->unprefixRow( $row, 'img_' );
533  } else {
534  # File may have been uploaded over in the meantime; check the old versions
535  $fileQuery = OldLocalFile::getQueryInfo( [ 'omit-nonlazy' ] );
536  $row = $dbr->selectRow(
537  $fileQuery['tables'],
538  $fileQuery['fields'],
539  [
540  'oi_name' => $this->getName(),
541  'oi_timestamp' => $dbr->timestamp( $this->getTimestamp() ),
542  ],
543  $fname,
544  [],
545  $fileQuery['joins']
546  );
547  if ( $row ) {
548  $fieldMap = $this->unprefixRow( $row, 'oi_' );
549  }
550  }
551 
552  if ( isset( $fieldMap['metadata'] ) ) {
553  $fieldMap['metadata'] = $this->repo->getReplicaDB()->decodeBlob( $fieldMap['metadata'] );
554  }
555 
556  return $fieldMap;
557  }
558 
565  protected function unprefixRow( $row, $prefix = 'img_' ) {
566  $array = (array)$row;
567  $prefixLength = strlen( $prefix );
568 
569  // Sanity check prefix once
570  if ( substr( key( $array ), 0, $prefixLength ) !== $prefix ) {
571  throw new MWException( __METHOD__ . ': incorrect $prefix parameter' );
572  }
573 
574  $decoded = [];
575  foreach ( $array as $name => $value ) {
576  $decoded[substr( $name, $prefixLength )] = $value;
577  }
578 
579  return $decoded;
580  }
581 
590  function decodeRow( $row, $prefix = 'img_' ) {
591  $decoded = $this->unprefixRow( $row, $prefix );
592 
593  $decoded['description'] = MediaWikiServices::getInstance()->getCommentStore()
594  ->getComment( 'description', (object)$decoded )->text;
595 
596  $decoded['user'] = User::newFromAnyId(
597  $decoded['user'] ?? null,
598  $decoded['user_text'] ?? null,
599  $decoded['actor'] ?? null
600  );
601  unset( $decoded['user_text'], $decoded['actor'] );
602 
603  $decoded['timestamp'] = wfTimestamp( TS_MW, $decoded['timestamp'] );
604 
605  $decoded['metadata'] = $this->repo->getReplicaDB()->decodeBlob( $decoded['metadata'] );
606 
607  if ( empty( $decoded['major_mime'] ) ) {
608  $decoded['mime'] = 'unknown/unknown';
609  } else {
610  if ( !$decoded['minor_mime'] ) {
611  $decoded['minor_mime'] = 'unknown';
612  }
613  $decoded['mime'] = $decoded['major_mime'] . '/' . $decoded['minor_mime'];
614  }
615 
616  // Trim zero padding from char/binary field
617  $decoded['sha1'] = rtrim( $decoded['sha1'], "\0" );
618 
619  // Normalize some fields to integer type, per their database definition.
620  // Use unary + so that overflows will be upgraded to double instead of
621  // being trucated as with intval(). This is important to allow >2GB
622  // files on 32-bit systems.
623  foreach ( [ 'size', 'width', 'height', 'bits' ] as $field ) {
624  $decoded[$field] = +$decoded[$field];
625  }
626 
627  return $decoded;
628  }
629 
636  function loadFromRow( $row, $prefix = 'img_' ) {
637  $this->dataLoaded = true;
638  $this->extraDataLoaded = true;
639 
640  $array = $this->decodeRow( $row, $prefix );
641 
642  foreach ( $array as $name => $value ) {
643  $this->$name = $value;
644  }
645 
646  $this->fileExists = true;
647  }
648 
653  function load( $flags = 0 ) {
654  if ( !$this->dataLoaded ) {
655  if ( $flags & self::READ_LATEST ) {
656  $this->loadFromDB( $flags );
657  } else {
658  $this->loadFromCache();
659  }
660  }
661 
662  if ( ( $flags & self::LOAD_ALL ) && !$this->extraDataLoaded ) {
663  // @note: loads on name/timestamp to reduce race condition problems
664  $this->loadExtraFromDB();
665  }
666  }
667 
671  protected function maybeUpgradeRow() {
673 
674  if ( wfReadOnly() || $this->upgrading ) {
675  return;
676  }
677 
678  $upgrade = false;
679  if ( is_null( $this->media_type ) || $this->mime == 'image/svg' ) {
680  $upgrade = true;
681  } else {
682  $handler = $this->getHandler();
683  if ( $handler ) {
684  $validity = $handler->isMetadataValid( $this, $this->getMetadata() );
685  if ( $validity === MediaHandler::METADATA_BAD ) {
686  $upgrade = true;
687  } elseif ( $validity === MediaHandler::METADATA_COMPATIBLE ) {
688  $upgrade = $wgUpdateCompatibleMetadata;
689  }
690  }
691  }
692 
693  if ( $upgrade ) {
694  $this->upgrading = true;
695  // Defer updates unless in auto-commit CLI mode
697  $this->upgrading = false; // avoid duplicate updates
698  try {
699  $this->upgradeRow();
700  } catch ( LocalFileLockError $e ) {
701  // let the other process handle it (or do it next time)
702  }
703  } );
704  }
705  }
706 
710  function getUpgraded() {
711  return $this->upgraded;
712  }
713 
717  function upgradeRow() {
718  $this->lock();
719 
720  $this->loadFromFile();
721 
722  # Don't destroy file info of missing files
723  if ( !$this->fileExists ) {
724  $this->unlock();
725  wfDebug( __METHOD__ . ": file does not exist, aborting\n" );
726 
727  return;
728  }
729 
730  $dbw = $this->repo->getMasterDB();
731  list( $major, $minor ) = self::splitMime( $this->mime );
732 
733  if ( wfReadOnly() ) {
734  $this->unlock();
735 
736  return;
737  }
738  wfDebug( __METHOD__ . ': upgrading ' . $this->getName() . " to the current schema\n" );
739 
740  $dbw->update( 'image',
741  [
742  'img_size' => $this->size, // sanity
743  'img_width' => $this->width,
744  'img_height' => $this->height,
745  'img_bits' => $this->bits,
746  'img_media_type' => $this->media_type,
747  'img_major_mime' => $major,
748  'img_minor_mime' => $minor,
749  'img_metadata' => $dbw->encodeBlob( $this->metadata ),
750  'img_sha1' => $this->sha1,
751  ],
752  [ 'img_name' => $this->getName() ],
753  __METHOD__
754  );
755 
756  $this->invalidateCache();
757 
758  $this->unlock();
759  $this->upgraded = true; // avoid rework/retries
760  }
761 
772  function setProps( $info ) {
773  $this->dataLoaded = true;
774  $fields = $this->getCacheFields( '' );
775  $fields[] = 'fileExists';
776 
777  foreach ( $fields as $field ) {
778  if ( isset( $info[$field] ) ) {
779  $this->$field = $info[$field];
780  }
781  }
782 
783  if ( isset( $info['user'] ) || isset( $info['user_text'] ) || isset( $info['actor'] ) ) {
784  $this->user = User::newFromAnyId(
785  $info['user'] ?? null,
786  $info['user_text'] ?? null,
787  $info['actor'] ?? null
788  );
789  }
790 
791  // Fix up mime fields
792  if ( isset( $info['major_mime'] ) ) {
793  $this->mime = "{$info['major_mime']}/{$info['minor_mime']}";
794  } elseif ( isset( $info['mime'] ) ) {
795  $this->mime = $info['mime'];
796  list( $this->major_mime, $this->minor_mime ) = self::splitMime( $this->mime );
797  }
798  }
799 
814  function isMissing() {
815  if ( $this->missing === null ) {
816  $fileExists = $this->repo->fileExists( $this->getVirtualUrl() );
817  $this->missing = !$fileExists;
818  }
819 
820  return $this->missing;
821  }
822 
829  public function getWidth( $page = 1 ) {
830  $page = (int)$page;
831  if ( $page < 1 ) {
832  $page = 1;
833  }
834 
835  $this->load();
836 
837  if ( $this->isMultipage() ) {
838  $handler = $this->getHandler();
839  if ( !$handler ) {
840  return 0;
841  }
842  $dim = $handler->getPageDimensions( $this, $page );
843  if ( $dim ) {
844  return $dim['width'];
845  } else {
846  // For non-paged media, the false goes through an
847  // intval, turning failure into 0, so do same here.
848  return 0;
849  }
850  } else {
851  return $this->width;
852  }
853  }
854 
861  public function getHeight( $page = 1 ) {
862  $page = (int)$page;
863  if ( $page < 1 ) {
864  $page = 1;
865  }
866 
867  $this->load();
868 
869  if ( $this->isMultipage() ) {
870  $handler = $this->getHandler();
871  if ( !$handler ) {
872  return 0;
873  }
874  $dim = $handler->getPageDimensions( $this, $page );
875  if ( $dim ) {
876  return $dim['height'];
877  } else {
878  // For non-paged media, the false goes through an
879  // intval, turning failure into 0, so do same here.
880  return 0;
881  }
882  } else {
883  return $this->height;
884  }
885  }
886 
894  function getUser( $type = 'text' ) {
895  $this->load();
896 
897  if ( $type === 'object' ) {
898  return $this->user;
899  } elseif ( $type === 'text' ) {
900  return $this->user->getName();
901  } elseif ( $type === 'id' ) {
902  return $this->user->getId();
903  }
904 
905  throw new MWException( "Unknown type '$type'." );
906  }
907 
915  public function getDescriptionShortUrl() {
916  $pageId = $this->title->getArticleID();
917 
918  if ( $pageId !== null ) {
919  $url = $this->repo->makeUrl( [ 'curid' => $pageId ] );
920  if ( $url !== false ) {
921  return $url;
922  }
923  }
924  return null;
925  }
926 
931  function getMetadata() {
932  $this->load( self::LOAD_ALL ); // large metadata is loaded in another step
933  return $this->metadata;
934  }
935 
939  function getBitDepth() {
940  $this->load();
941 
942  return (int)$this->bits;
943  }
944 
949  public function getSize() {
950  $this->load();
951 
952  return $this->size;
953  }
954 
959  function getMimeType() {
960  $this->load();
961 
962  return $this->mime;
963  }
964 
970  function getMediaType() {
971  $this->load();
972 
973  return $this->media_type;
974  }
975 
986  public function exists() {
987  $this->load();
988 
989  return $this->fileExists;
990  }
991 
1007  function getThumbnails( $archiveName = false ) {
1008  if ( $archiveName ) {
1009  $dir = $this->getArchiveThumbPath( $archiveName );
1010  } else {
1011  $dir = $this->getThumbPath();
1012  }
1013 
1014  $backend = $this->repo->getBackend();
1015  $files = [ $dir ];
1016  try {
1017  $iterator = $backend->getFileList( [ 'dir' => $dir ] );
1018  foreach ( $iterator as $file ) {
1019  $files[] = $file;
1020  }
1021  } catch ( FileBackendError $e ) {
1022  } // suppress (T56674)
1023 
1024  return $files;
1025  }
1026 
1030  function purgeMetadataCache() {
1031  $this->invalidateCache();
1032  }
1033 
1041  function purgeCache( $options = [] ) {
1042  // Refresh metadata cache
1043  $this->maybeUpgradeRow();
1044  $this->purgeMetadataCache();
1045 
1046  // Delete thumbnails
1047  $this->purgeThumbnails( $options );
1048 
1049  // Purge CDN cache for this file
1051  new CdnCacheUpdate( [ $this->getUrl() ] ),
1053  );
1054  }
1055 
1060  function purgeOldThumbnails( $archiveName ) {
1061  // Get a list of old thumbnails and URLs
1062  $files = $this->getThumbnails( $archiveName );
1063 
1064  // Purge any custom thumbnail caches
1065  Hooks::run( 'LocalFilePurgeThumbnails', [ $this, $archiveName ] );
1066 
1067  // Delete thumbnails
1068  $dir = array_shift( $files );
1069  $this->purgeThumbList( $dir, $files );
1070 
1071  // Purge the CDN
1072  $urls = [];
1073  foreach ( $files as $file ) {
1074  $urls[] = $this->getArchiveThumbUrl( $archiveName, $file );
1075  }
1077  }
1078 
1083  public function purgeThumbnails( $options = [] ) {
1084  $files = $this->getThumbnails();
1085  // Always purge all files from CDN regardless of handler filters
1086  $urls = [];
1087  foreach ( $files as $file ) {
1088  $urls[] = $this->getThumbUrl( $file );
1089  }
1090  array_shift( $urls ); // don't purge directory
1091 
1092  // Give media handler a chance to filter the file purge list
1093  if ( !empty( $options['forThumbRefresh'] ) ) {
1094  $handler = $this->getHandler();
1095  if ( $handler ) {
1097  }
1098  }
1099 
1100  // Purge any custom thumbnail caches
1101  Hooks::run( 'LocalFilePurgeThumbnails', [ $this, false ] );
1102 
1103  // Delete thumbnails
1104  $dir = array_shift( $files );
1105  $this->purgeThumbList( $dir, $files );
1106 
1107  // Purge the CDN
1109  }
1110 
1116  public function prerenderThumbnails() {
1118 
1119  $jobs = [];
1120 
1121  $sizes = $wgUploadThumbnailRenderMap;
1122  rsort( $sizes );
1123 
1124  foreach ( $sizes as $size ) {
1125  if ( $this->isVectorized() || $this->getWidth() > $size ) {
1126  $jobs[] = new ThumbnailRenderJob(
1127  $this->getTitle(),
1128  [ 'transformParams' => [ 'width' => $size ] ]
1129  );
1130  }
1131  }
1132 
1133  if ( $jobs ) {
1134  JobQueueGroup::singleton()->lazyPush( $jobs );
1135  }
1136  }
1137 
1143  protected function purgeThumbList( $dir, $files ) {
1144  $fileListDebug = strtr(
1145  var_export( $files, true ),
1146  [ "\n" => '' ]
1147  );
1148  wfDebug( __METHOD__ . ": $fileListDebug\n" );
1149 
1150  $purgeList = [];
1151  foreach ( $files as $file ) {
1152  if ( $this->repo->supportsSha1URLs() ) {
1153  $reference = $this->getSha1();
1154  } else {
1155  $reference = $this->getName();
1156  }
1157 
1158  # Check that the reference (filename or sha1) is part of the thumb name
1159  # This is a basic sanity check to avoid erasing unrelated directories
1160  if ( strpos( $file, $reference ) !== false
1161  || strpos( $file, "-thumbnail" ) !== false // "short" thumb name
1162  ) {
1163  $purgeList[] = "{$dir}/{$file}";
1164  }
1165  }
1166 
1167  # Delete the thumbnails
1168  $this->repo->quickPurgeBatch( $purgeList );
1169  # Clear out the thumbnail directory if empty
1170  $this->repo->quickCleanDir( $dir );
1171  }
1172 
1183  function getHistory( $limit = null, $start = null, $end = null, $inc = true ) {
1184  $dbr = $this->repo->getReplicaDB();
1185  $oldFileQuery = OldLocalFile::getQueryInfo();
1186 
1187  $tables = $oldFileQuery['tables'];
1188  $fields = $oldFileQuery['fields'];
1189  $join_conds = $oldFileQuery['joins'];
1190  $conds = $opts = [];
1191  $eq = $inc ? '=' : '';
1192  $conds[] = "oi_name = " . $dbr->addQuotes( $this->title->getDBkey() );
1193 
1194  if ( $start ) {
1195  $conds[] = "oi_timestamp <$eq " . $dbr->addQuotes( $dbr->timestamp( $start ) );
1196  }
1197 
1198  if ( $end ) {
1199  $conds[] = "oi_timestamp >$eq " . $dbr->addQuotes( $dbr->timestamp( $end ) );
1200  }
1201 
1202  if ( $limit ) {
1203  $opts['LIMIT'] = $limit;
1204  }
1205 
1206  // Search backwards for time > x queries
1207  $order = ( !$start && $end !== null ) ? 'ASC' : 'DESC';
1208  $opts['ORDER BY'] = "oi_timestamp $order";
1209  $opts['USE INDEX'] = [ 'oldimage' => 'oi_name_timestamp' ];
1210 
1211  // Avoid PHP 7.1 warning from passing $this by reference
1212  $localFile = $this;
1213  Hooks::run( 'LocalFile::getHistory', [ &$localFile, &$tables, &$fields,
1214  &$conds, &$opts, &$join_conds ] );
1215 
1216  $res = $dbr->select( $tables, $fields, $conds, __METHOD__, $opts, $join_conds );
1217  $r = [];
1218 
1219  foreach ( $res as $row ) {
1220  $r[] = $this->repo->newFileFromRow( $row );
1221  }
1222 
1223  if ( $order == 'ASC' ) {
1224  $r = array_reverse( $r ); // make sure it ends up descending
1225  }
1226 
1227  return $r;
1228  }
1229 
1239  public function nextHistoryLine() {
1240  # Polymorphic function name to distinguish foreign and local fetches
1241  $fname = static::class . '::' . __FUNCTION__;
1242 
1243  $dbr = $this->repo->getReplicaDB();
1244 
1245  if ( $this->historyLine == 0 ) { // called for the first time, return line from cur
1246  $fileQuery = self::getQueryInfo();
1247  $this->historyRes = $dbr->select( $fileQuery['tables'],
1248  $fileQuery['fields'] + [
1249  'oi_archive_name' => $dbr->addQuotes( '' ),
1250  'oi_deleted' => 0,
1251  ],
1252  [ 'img_name' => $this->title->getDBkey() ],
1253  $fname,
1254  [],
1255  $fileQuery['joins']
1256  );
1257 
1258  if ( $dbr->numRows( $this->historyRes ) == 0 ) {
1259  $this->historyRes = null;
1260 
1261  return false;
1262  }
1263  } elseif ( $this->historyLine == 1 ) {
1264  $fileQuery = OldLocalFile::getQueryInfo();
1265  $this->historyRes = $dbr->select(
1266  $fileQuery['tables'],
1267  $fileQuery['fields'],
1268  [ 'oi_name' => $this->title->getDBkey() ],
1269  $fname,
1270  [ 'ORDER BY' => 'oi_timestamp DESC' ],
1271  $fileQuery['joins']
1272  );
1273  }
1274  $this->historyLine++;
1275 
1276  return $dbr->fetchObject( $this->historyRes );
1277  }
1278 
1282  public function resetHistory() {
1283  $this->historyLine = 0;
1284 
1285  if ( !is_null( $this->historyRes ) ) {
1286  $this->historyRes = null;
1287  }
1288  }
1289 
1323  function upload( $src, $comment, $pageText, $flags = 0, $props = false,
1324  $timestamp = false, $user = null, $tags = [],
1325  $createNullRevision = true, $revert = false
1326  ) {
1327  if ( $this->getRepo()->getReadOnlyReason() !== false ) {
1328  return $this->readOnlyFatalStatus();
1329  } elseif ( MediaWikiServices::getInstance()->getRevisionStore()->isReadOnly() ) {
1330  // Check this in advance to avoid writing to FileBackend and the file tables,
1331  // only to fail on insert the revision due to the text store being unavailable.
1332  return $this->readOnlyFatalStatus();
1333  }
1334 
1335  $srcPath = ( $src instanceof FSFile ) ? $src->getPath() : $src;
1336  if ( !$props ) {
1337  if ( FileRepo::isVirtualUrl( $srcPath )
1338  || FileBackend::isStoragePath( $srcPath )
1339  ) {
1340  $props = $this->repo->getFileProps( $srcPath );
1341  } else {
1342  $mwProps = new MWFileProps( MediaWikiServices::getInstance()->getMimeAnalyzer() );
1343  $props = $mwProps->getPropsFromPath( $srcPath, true );
1344  }
1345  }
1346 
1347  $options = [];
1348  $handler = MediaHandler::getHandler( $props['mime'] );
1349  if ( $handler ) {
1350  $metadata = AtEase::quietCall( 'unserialize', $props['metadata'] );
1351 
1352  if ( !is_array( $metadata ) ) {
1353  $metadata = [];
1354  }
1355 
1356  $options['headers'] = $handler->getContentHeaders( $metadata );
1357  } else {
1358  $options['headers'] = [];
1359  }
1360 
1361  // Trim spaces on user supplied text
1362  $comment = trim( $comment );
1363 
1364  $this->lock();
1365  $status = $this->publish( $src, $flags, $options );
1366 
1367  if ( $status->successCount >= 2 ) {
1368  // There will be a copy+(one of move,copy,store).
1369  // The first succeeding does not commit us to updating the DB
1370  // since it simply copied the current version to a timestamped file name.
1371  // It is only *preferable* to avoid leaving such files orphaned.
1372  // Once the second operation goes through, then the current version was
1373  // updated and we must therefore update the DB too.
1374  $oldver = $status->value;
1375  $uploadStatus = $this->recordUpload2(
1376  $oldver,
1377  $comment,
1378  $pageText,
1379  $props,
1380  $timestamp,
1381  $user,
1382  $tags,
1383  $createNullRevision,
1384  $revert
1385  );
1386  if ( !$uploadStatus->isOK() ) {
1387  if ( $uploadStatus->hasMessage( 'filenotfound' ) ) {
1388  // update filenotfound error with more specific path
1389  $status->fatal( 'filenotfound', $srcPath );
1390  } else {
1391  $status->merge( $uploadStatus );
1392  }
1393  }
1394  }
1395 
1396  $this->unlock();
1397  return $status;
1398  }
1399 
1412  function recordUpload( $oldver, $desc, $license = '', $copyStatus = '', $source = '',
1413  $watch = false, $timestamp = false, User $user = null ) {
1414  if ( !$user ) {
1415  global $wgUser;
1416  $user = $wgUser;
1417  }
1418 
1419  $pageText = SpecialUpload::getInitialPageText( $desc, $license, $copyStatus, $source );
1420 
1421  if ( !$this->recordUpload2( $oldver, $desc, $pageText, false, $timestamp, $user )->isOK() ) {
1422  return false;
1423  }
1424 
1425  if ( $watch ) {
1426  $user->addWatch( $this->getTitle() );
1427  }
1428 
1429  return true;
1430  }
1431 
1446  function recordUpload2(
1447  $oldver, $comment, $pageText, $props = false, $timestamp = false, $user = null, $tags = [],
1448  $createNullRevision = true, $revert = false
1449  ) {
1451 
1452  if ( is_null( $user ) ) {
1453  global $wgUser;
1454  $user = $wgUser;
1455  }
1456 
1457  $dbw = $this->repo->getMasterDB();
1458 
1459  # Imports or such might force a certain timestamp; otherwise we generate
1460  # it and can fudge it slightly to keep (name,timestamp) unique on re-upload.
1461  if ( $timestamp === false ) {
1462  $timestamp = $dbw->timestamp();
1463  $allowTimeKludge = true;
1464  } else {
1465  $allowTimeKludge = false;
1466  }
1467 
1468  $props = $props ?: $this->repo->getFileProps( $this->getVirtualUrl() );
1469  $props['description'] = $comment;
1470  $props['user'] = $user->getId();
1471  $props['user_text'] = $user->getName();
1472  $props['actor'] = $user->getActorId( $dbw );
1473  $props['timestamp'] = wfTimestamp( TS_MW, $timestamp ); // DB -> TS_MW
1474  $this->setProps( $props );
1475 
1476  # Fail now if the file isn't there
1477  if ( !$this->fileExists ) {
1478  wfDebug( __METHOD__ . ": File " . $this->getRel() . " went missing!\n" );
1479 
1480  return Status::newFatal( 'filenotfound', $this->getRel() );
1481  }
1482 
1483  $dbw->startAtomic( __METHOD__ );
1484 
1485  # Test to see if the row exists using INSERT IGNORE
1486  # This avoids race conditions by locking the row until the commit, and also
1487  # doesn't deadlock. SELECT FOR UPDATE causes a deadlock for every race condition.
1488  $commentStore = MediaWikiServices::getInstance()->getCommentStore();
1489  $commentFields = $commentStore->insert( $dbw, 'img_description', $comment );
1490  $actorMigration = ActorMigration::newMigration();
1491  $actorFields = $actorMigration->getInsertValues( $dbw, 'img_user', $user );
1492  $dbw->insert( 'image',
1493  [
1494  'img_name' => $this->getName(),
1495  'img_size' => $this->size,
1496  'img_width' => intval( $this->width ),
1497  'img_height' => intval( $this->height ),
1498  'img_bits' => $this->bits,
1499  'img_media_type' => $this->media_type,
1500  'img_major_mime' => $this->major_mime,
1501  'img_minor_mime' => $this->minor_mime,
1502  'img_timestamp' => $timestamp,
1503  'img_metadata' => $dbw->encodeBlob( $this->metadata ),
1504  'img_sha1' => $this->sha1
1505  ] + $commentFields + $actorFields,
1506  __METHOD__,
1507  [ 'IGNORE' ]
1508  );
1509  $reupload = ( $dbw->affectedRows() == 0 );
1510 
1511  if ( $reupload ) {
1512  $row = $dbw->selectRow(
1513  'image',
1514  [ 'img_timestamp', 'img_sha1' ],
1515  [ 'img_name' => $this->getName() ],
1516  __METHOD__,
1517  [ 'LOCK IN SHARE MODE' ]
1518  );
1519 
1520  if ( $row && $row->img_sha1 === $this->sha1 ) {
1521  $dbw->endAtomic( __METHOD__ );
1522  wfDebug( __METHOD__ . ": File " . $this->getRel() . " already exists!\n" );
1523  $title = Title::newFromText( $this->getName(), NS_FILE );
1524  return Status::newFatal( 'fileexists-no-change', $title->getPrefixedText() );
1525  }
1526 
1527  if ( $allowTimeKludge ) {
1528  # Use LOCK IN SHARE MODE to ignore any transaction snapshotting
1529  $lUnixtime = $row ? wfTimestamp( TS_UNIX, $row->img_timestamp ) : false;
1530  # Avoid a timestamp that is not newer than the last version
1531  # TODO: the image/oldimage tables should be like page/revision with an ID field
1532  if ( $lUnixtime && wfTimestamp( TS_UNIX, $timestamp ) <= $lUnixtime ) {
1533  sleep( 1 ); // fast enough re-uploads would go far in the future otherwise
1534  $timestamp = $dbw->timestamp( $lUnixtime + 1 );
1535  $this->timestamp = wfTimestamp( TS_MW, $timestamp ); // DB -> TS_MW
1536  }
1537  }
1538 
1539  $tables = [ 'image' ];
1540  $fields = [
1541  'oi_name' => 'img_name',
1542  'oi_archive_name' => $dbw->addQuotes( $oldver ),
1543  'oi_size' => 'img_size',
1544  'oi_width' => 'img_width',
1545  'oi_height' => 'img_height',
1546  'oi_bits' => 'img_bits',
1547  'oi_description_id' => 'img_description_id',
1548  'oi_timestamp' => 'img_timestamp',
1549  'oi_metadata' => 'img_metadata',
1550  'oi_media_type' => 'img_media_type',
1551  'oi_major_mime' => 'img_major_mime',
1552  'oi_minor_mime' => 'img_minor_mime',
1553  'oi_sha1' => 'img_sha1',
1554  ];
1555  $joins = [];
1556 
1557  if ( $wgActorTableSchemaMigrationStage & SCHEMA_COMPAT_WRITE_OLD ) {
1558  $fields['oi_user'] = 'img_user';
1559  $fields['oi_user_text'] = 'img_user_text';
1560  }
1561  if ( $wgActorTableSchemaMigrationStage & SCHEMA_COMPAT_WRITE_NEW ) {
1562  $fields['oi_actor'] = 'img_actor';
1563  }
1564 
1565  if (
1566  ( $wgActorTableSchemaMigrationStage & SCHEMA_COMPAT_WRITE_BOTH ) === SCHEMA_COMPAT_WRITE_BOTH
1567  ) {
1568  // Upgrade any rows that are still old-style. Otherwise an upgrade
1569  // might be missed if a deletion happens while the migration script
1570  // is running.
1571  $res = $dbw->select(
1572  [ 'image' ],
1573  [ 'img_name', 'img_user', 'img_user_text' ],
1574  [ 'img_name' => $this->getName(), 'img_actor' => 0 ],
1575  __METHOD__
1576  );
1577  foreach ( $res as $row ) {
1578  $actorId = User::newFromAnyId( $row->img_user, $row->img_user_text, null )->getActorId( $dbw );
1579  $dbw->update(
1580  'image',
1581  [ 'img_actor' => $actorId ],
1582  [ 'img_name' => $row->img_name, 'img_actor' => 0 ],
1583  __METHOD__
1584  );
1585  }
1586  }
1587 
1588  # (T36993) Note: $oldver can be empty here, if the previous
1589  # version of the file was broken. Allow registration of the new
1590  # version to continue anyway, because that's better than having
1591  # an image that's not fixable by user operations.
1592  # Collision, this is an update of a file
1593  # Insert previous contents into oldimage
1594  $dbw->insertSelect( 'oldimage', $tables, $fields,
1595  [ 'img_name' => $this->getName() ], __METHOD__, [], [], $joins );
1596 
1597  # Update the current image row
1598  $dbw->update( 'image',
1599  [
1600  'img_size' => $this->size,
1601  'img_width' => intval( $this->width ),
1602  'img_height' => intval( $this->height ),
1603  'img_bits' => $this->bits,
1604  'img_media_type' => $this->media_type,
1605  'img_major_mime' => $this->major_mime,
1606  'img_minor_mime' => $this->minor_mime,
1607  'img_timestamp' => $timestamp,
1608  'img_metadata' => $dbw->encodeBlob( $this->metadata ),
1609  'img_sha1' => $this->sha1
1610  ] + $commentFields + $actorFields,
1611  [ 'img_name' => $this->getName() ],
1612  __METHOD__
1613  );
1614  }
1615 
1616  $descTitle = $this->getTitle();
1617  $descId = $descTitle->getArticleID();
1618  $wikiPage = new WikiFilePage( $descTitle );
1619  $wikiPage->setFile( $this );
1620 
1621  // Determine log action. If reupload is done by reverting, use a special log_action.
1622  if ( $revert === true ) {
1623  $logAction = 'revert';
1624  } elseif ( $reupload === true ) {
1625  $logAction = 'overwrite';
1626  } else {
1627  $logAction = 'upload';
1628  }
1629  // Add the log entry...
1630  $logEntry = new ManualLogEntry( 'upload', $logAction );
1631  $logEntry->setTimestamp( $this->timestamp );
1632  $logEntry->setPerformer( $user );
1633  $logEntry->setComment( $comment );
1634  $logEntry->setTarget( $descTitle );
1635  // Allow people using the api to associate log entries with the upload.
1636  // Log has a timestamp, but sometimes different from upload timestamp.
1637  $logEntry->setParameters(
1638  [
1639  'img_sha1' => $this->sha1,
1640  'img_timestamp' => $timestamp,
1641  ]
1642  );
1643  // Note we keep $logId around since during new image
1644  // creation, page doesn't exist yet, so log_page = 0
1645  // but we want it to point to the page we're making,
1646  // so we later modify the log entry.
1647  // For a similar reason, we avoid making an RC entry
1648  // now and wait until the page exists.
1649  $logId = $logEntry->insert();
1650 
1651  if ( $descTitle->exists() ) {
1652  // Use own context to get the action text in content language
1653  $formatter = LogFormatter::newFromEntry( $logEntry );
1654  $formatter->setContext( RequestContext::newExtraneousContext( $descTitle ) );
1655  $editSummary = $formatter->getPlainActionText();
1656 
1657  $nullRevision = $createNullRevision === false ? null : Revision::newNullRevision(
1658  $dbw,
1659  $descId,
1660  $editSummary,
1661  false,
1662  $user
1663  );
1664  if ( $nullRevision ) {
1665  $nullRevision->insertOn( $dbw );
1666  Hooks::run(
1667  'NewRevisionFromEditComplete',
1668  [ $wikiPage, $nullRevision, $nullRevision->getParentId(), $user ]
1669  );
1670  $wikiPage->updateRevisionOn( $dbw, $nullRevision );
1671  // Associate null revision id
1672  $logEntry->setAssociatedRevId( $nullRevision->getId() );
1673  }
1674 
1675  $newPageContent = null;
1676  } else {
1677  // Make the description page and RC log entry post-commit
1678  $newPageContent = ContentHandler::makeContent( $pageText, $descTitle );
1679  }
1680 
1681  # Defer purges, page creation, and link updates in case they error out.
1682  # The most important thing is that files and the DB registry stay synced.
1683  $dbw->endAtomic( __METHOD__ );
1684  $fname = __METHOD__;
1685 
1686  # Do some cache purges after final commit so that:
1687  # a) Changes are more likely to be seen post-purge
1688  # b) They won't cause rollback of the log publish/update above
1690  new AutoCommitUpdate(
1691  $dbw,
1692  __METHOD__,
1693  function () use (
1694  $reupload, $wikiPage, $newPageContent, $comment, $user,
1695  $logEntry, $logId, $descId, $tags, $fname
1696  ) {
1697  # Update memcache after the commit
1698  $this->invalidateCache();
1699 
1700  $updateLogPage = false;
1701  if ( $newPageContent ) {
1702  # New file page; create the description page.
1703  # There's already a log entry, so don't make a second RC entry
1704  # CDN and file cache for the description page are purged by doEditContent.
1705  $status = $wikiPage->doEditContent(
1706  $newPageContent,
1707  $comment,
1709  false,
1710  $user
1711  );
1712 
1713  if ( isset( $status->value['revision'] ) ) {
1715  $rev = $status->value['revision'];
1716  // Associate new page revision id
1717  $logEntry->setAssociatedRevId( $rev->getId() );
1718  }
1719  // This relies on the resetArticleID() call in WikiPage::insertOn(),
1720  // which is triggered on $descTitle by doEditContent() above.
1721  if ( isset( $status->value['revision'] ) ) {
1723  $rev = $status->value['revision'];
1724  $updateLogPage = $rev->getPage();
1725  }
1726  } else {
1727  # Existing file page: invalidate description page cache
1728  $wikiPage->getTitle()->invalidateCache();
1729  $wikiPage->getTitle()->purgeSquid();
1730  # Allow the new file version to be patrolled from the page footer
1732  }
1733 
1734  # Update associated rev id. This should be done by $logEntry->insert() earlier,
1735  # but setAssociatedRevId() wasn't called at that point yet...
1736  $logParams = $logEntry->getParameters();
1737  $logParams['associated_rev_id'] = $logEntry->getAssociatedRevId();
1738  $update = [ 'log_params' => LogEntryBase::makeParamBlob( $logParams ) ];
1739  if ( $updateLogPage ) {
1740  # Also log page, in case where we just created it above
1741  $update['log_page'] = $updateLogPage;
1742  }
1743  $this->getRepo()->getMasterDB()->update(
1744  'logging',
1745  $update,
1746  [ 'log_id' => $logId ],
1747  $fname
1748  );
1749  $this->getRepo()->getMasterDB()->insert(
1750  'log_search',
1751  [
1752  'ls_field' => 'associated_rev_id',
1753  'ls_value' => $logEntry->getAssociatedRevId(),
1754  'ls_log_id' => $logId,
1755  ],
1756  $fname
1757  );
1758 
1759  # Add change tags, if any
1760  if ( $tags ) {
1761  $logEntry->setTags( $tags );
1762  }
1763 
1764  # Uploads can be patrolled
1765  $logEntry->setIsPatrollable( true );
1766 
1767  # Now that the log entry is up-to-date, make an RC entry.
1768  $logEntry->publish( $logId );
1769 
1770  # Run hook for other updates (typically more cache purging)
1771  Hooks::run( 'FileUpload', [ $this, $reupload, !$newPageContent ] );
1772 
1773  if ( $reupload ) {
1774  # Delete old thumbnails
1775  $this->purgeThumbnails();
1776  # Remove the old file from the CDN cache
1778  new CdnCacheUpdate( [ $this->getUrl() ] ),
1780  );
1781  } else {
1782  # Update backlink pages pointing to this title if created
1784  $this->getTitle(),
1785  'imagelinks',
1786  'upload-image',
1787  $user->getName()
1788  );
1789  }
1790 
1791  $this->prerenderThumbnails();
1792  }
1793  ),
1795  );
1796 
1797  if ( !$reupload ) {
1798  # This is a new file, so update the image count
1799  DeferredUpdates::addUpdate( SiteStatsUpdate::factory( [ 'images' => 1 ] ) );
1800  }
1801 
1802  # Invalidate cache for all pages using this file
1804  new HTMLCacheUpdate( $this->getTitle(), 'imagelinks', 'file-upload' )
1805  );
1806 
1807  return Status::newGood();
1808  }
1809 
1825  function publish( $src, $flags = 0, array $options = [] ) {
1826  return $this->publishTo( $src, $this->getRel(), $flags, $options );
1827  }
1828 
1844  function publishTo( $src, $dstRel, $flags = 0, array $options = [] ) {
1845  $srcPath = ( $src instanceof FSFile ) ? $src->getPath() : $src;
1846 
1847  $repo = $this->getRepo();
1848  if ( $repo->getReadOnlyReason() !== false ) {
1849  return $this->readOnlyFatalStatus();
1850  }
1851 
1852  $this->lock();
1853 
1854  if ( $this->isOld() ) {
1855  $archiveRel = $dstRel;
1856  $archiveName = basename( $archiveRel );
1857  } else {
1858  $archiveName = wfTimestamp( TS_MW ) . '!' . $this->getName();
1859  $archiveRel = $this->getArchiveRel( $archiveName );
1860  }
1861 
1862  if ( $repo->hasSha1Storage() ) {
1863  $sha1 = FileRepo::isVirtualUrl( $srcPath )
1864  ? $repo->getFileSha1( $srcPath )
1865  : FSFile::getSha1Base36FromPath( $srcPath );
1867  $wrapperBackend = $repo->getBackend();
1868  $dst = $wrapperBackend->getPathForSHA1( $sha1 );
1869  $status = $repo->quickImport( $src, $dst );
1870  if ( $flags & File::DELETE_SOURCE ) {
1871  unlink( $srcPath );
1872  }
1873 
1874  if ( $this->exists() ) {
1875  $status->value = $archiveName;
1876  }
1877  } else {
1878  $flags = $flags & File::DELETE_SOURCE ? LocalRepo::DELETE_SOURCE : 0;
1879  $status = $repo->publish( $srcPath, $dstRel, $archiveRel, $flags, $options );
1880 
1881  if ( $status->value == 'new' ) {
1882  $status->value = '';
1883  } else {
1884  $status->value = $archiveName;
1885  }
1886  }
1887 
1888  $this->unlock();
1889  return $status;
1890  }
1891 
1909  function move( $target ) {
1910  $localRepo = MediaWikiServices::getInstance()->getRepoGroup()->getLocalRepo();
1911  if ( $this->getRepo()->getReadOnlyReason() !== false ) {
1912  return $this->readOnlyFatalStatus();
1913  }
1914 
1915  wfDebugLog( 'imagemove', "Got request to move {$this->name} to " . $target->getText() );
1916  $batch = new LocalFileMoveBatch( $this, $target );
1917 
1918  $this->lock();
1919  $batch->addCurrent();
1920  $archiveNames = $batch->addOlds();
1921  $status = $batch->execute();
1922  $this->unlock();
1923 
1924  wfDebugLog( 'imagemove', "Finished moving {$this->name}" );
1925 
1926  // Purge the source and target files...
1927  $oldTitleFile = $localRepo->newFile( $this->title );
1928  $newTitleFile = $localRepo->newFile( $target );
1929  // To avoid slow purges in the transaction, move them outside...
1931  new AutoCommitUpdate(
1932  $this->getRepo()->getMasterDB(),
1933  __METHOD__,
1934  function () use ( $oldTitleFile, $newTitleFile, $archiveNames ) {
1935  $oldTitleFile->purgeEverything();
1936  foreach ( $archiveNames as $archiveName ) {
1937  $oldTitleFile->purgeOldThumbnails( $archiveName );
1938  }
1939  $newTitleFile->purgeEverything();
1940  }
1941  ),
1943  );
1944 
1945  if ( $status->isOK() ) {
1946  // Now switch the object
1947  $this->title = $target;
1948  // Force regeneration of the name and hashpath
1949  unset( $this->name );
1950  unset( $this->hashPath );
1951  }
1952 
1953  return $status;
1954  }
1955 
1969  function delete( $reason, $suppress = false, $user = null ) {
1970  if ( $this->getRepo()->getReadOnlyReason() !== false ) {
1971  return $this->readOnlyFatalStatus();
1972  }
1973 
1974  $batch = new LocalFileDeleteBatch( $this, $reason, $suppress, $user );
1975 
1976  $this->lock();
1977  $batch->addCurrent();
1978  // Get old version relative paths
1979  $archiveNames = $batch->addOlds();
1980  $status = $batch->execute();
1981  $this->unlock();
1982 
1983  if ( $status->isOK() ) {
1984  DeferredUpdates::addUpdate( SiteStatsUpdate::factory( [ 'images' => -1 ] ) );
1985  }
1986 
1987  // To avoid slow purges in the transaction, move them outside...
1989  new AutoCommitUpdate(
1990  $this->getRepo()->getMasterDB(),
1991  __METHOD__,
1992  function () use ( $archiveNames ) {
1993  $this->purgeEverything();
1994  foreach ( $archiveNames as $archiveName ) {
1995  $this->purgeOldThumbnails( $archiveName );
1996  }
1997  }
1998  ),
2000  );
2001 
2002  // Purge the CDN
2003  $purgeUrls = [];
2004  foreach ( $archiveNames as $archiveName ) {
2005  $purgeUrls[] = $this->getArchiveUrl( $archiveName );
2006  }
2008 
2009  return $status;
2010  }
2011 
2027  function deleteOld( $archiveName, $reason, $suppress = false, $user = null ) {
2028  if ( $this->getRepo()->getReadOnlyReason() !== false ) {
2029  return $this->readOnlyFatalStatus();
2030  }
2031 
2032  $batch = new LocalFileDeleteBatch( $this, $reason, $suppress, $user );
2033 
2034  $this->lock();
2035  $batch->addOld( $archiveName );
2036  $status = $batch->execute();
2037  $this->unlock();
2038 
2039  $this->purgeOldThumbnails( $archiveName );
2040  if ( $status->isOK() ) {
2041  $this->purgeDescription();
2042  }
2043 
2045  new CdnCacheUpdate( [ $this->getArchiveUrl( $archiveName ) ] ),
2047  );
2048 
2049  return $status;
2050  }
2051 
2063  function restore( $versions = [], $unsuppress = false ) {
2064  if ( $this->getRepo()->getReadOnlyReason() !== false ) {
2065  return $this->readOnlyFatalStatus();
2066  }
2067 
2068  $batch = new LocalFileRestoreBatch( $this, $unsuppress );
2069 
2070  $this->lock();
2071  if ( !$versions ) {
2072  $batch->addAll();
2073  } else {
2074  $batch->addIds( $versions );
2075  }
2076  $status = $batch->execute();
2077  if ( $status->isGood() ) {
2078  $cleanupStatus = $batch->cleanup();
2079  $cleanupStatus->successCount = 0;
2080  $cleanupStatus->failCount = 0;
2081  $status->merge( $cleanupStatus );
2082  }
2083 
2084  $this->unlock();
2085  return $status;
2086  }
2087 
2097  function getDescriptionUrl() {
2098  return $this->title->getLocalURL();
2099  }
2100 
2110  $store = MediaWikiServices::getInstance()->getRevisionStore();
2111  $revision = $store->getRevisionByTitle( $this->title, 0, Revision::READ_NORMAL );
2112  if ( !$revision ) {
2113  return false;
2114  }
2115 
2116  $renderer = MediaWikiServices::getInstance()->getRevisionRenderer();
2117  $rendered = $renderer->getRenderedRevision( $revision, new ParserOptions( null, $lang ) );
2118 
2119  if ( !$rendered ) {
2120  // audience check failed
2121  return false;
2122  }
2123 
2124  $pout = $rendered->getRevisionParserOutput();
2125  return $pout->getText();
2126  }
2127 
2133  function getDescription( $audience = self::FOR_PUBLIC, User $user = null ) {
2134  $this->load();
2135  if ( $audience == self::FOR_PUBLIC && $this->isDeleted( self::DELETED_COMMENT ) ) {
2136  return '';
2137  } elseif ( $audience == self::FOR_THIS_USER
2138  && !$this->userCan( self::DELETED_COMMENT, $user )
2139  ) {
2140  return '';
2141  } else {
2142  return $this->description;
2143  }
2144  }
2145 
2149  function getTimestamp() {
2150  $this->load();
2151 
2152  return $this->timestamp;
2153  }
2154 
2158  public function getDescriptionTouched() {
2159  // The DB lookup might return false, e.g. if the file was just deleted, or the shared DB repo
2160  // itself gets it from elsewhere. To avoid repeating the DB lookups in such a case, we
2161  // need to differentiate between null (uninitialized) and false (failed to load).
2162  if ( $this->descriptionTouched === null ) {
2163  $cond = [
2164  'page_namespace' => $this->title->getNamespace(),
2165  'page_title' => $this->title->getDBkey()
2166  ];
2167  $touched = $this->repo->getReplicaDB()->selectField( 'page', 'page_touched', $cond, __METHOD__ );
2168  $this->descriptionTouched = $touched ? wfTimestamp( TS_MW, $touched ) : false;
2169  }
2170 
2172  }
2173 
2177  function getSha1() {
2178  $this->load();
2179  // Initialise now if necessary
2180  if ( $this->sha1 == '' && $this->fileExists ) {
2181  $this->lock();
2182 
2183  $this->sha1 = $this->repo->getFileSha1( $this->getPath() );
2184  if ( !wfReadOnly() && strval( $this->sha1 ) != '' ) {
2185  $dbw = $this->repo->getMasterDB();
2186  $dbw->update( 'image',
2187  [ 'img_sha1' => $this->sha1 ],
2188  [ 'img_name' => $this->getName() ],
2189  __METHOD__ );
2190  $this->invalidateCache();
2191  }
2192 
2193  $this->unlock();
2194  }
2195 
2196  return $this->sha1;
2197  }
2198 
2202  function isCacheable() {
2203  $this->load();
2204 
2205  // If extra data (metadata) was not loaded then it must have been large
2206  return $this->extraDataLoaded
2207  && strlen( serialize( $this->metadata ) ) <= self::CACHE_FIELD_MAX_LEN;
2208  }
2209 
2214  public function acquireFileLock() {
2215  return Status::wrap( $this->getRepo()->getBackend()->lockFiles(
2216  [ $this->getPath() ], LockManager::LOCK_EX, 10
2217  ) );
2218  }
2219 
2224  public function releaseFileLock() {
2225  return Status::wrap( $this->getRepo()->getBackend()->unlockFiles(
2226  [ $this->getPath() ], LockManager::LOCK_EX
2227  ) );
2228  }
2229 
2239  public function lock() {
2240  if ( !$this->locked ) {
2241  $logger = LoggerFactory::getInstance( 'LocalFile' );
2242 
2243  $dbw = $this->repo->getMasterDB();
2244  $makesTransaction = !$dbw->trxLevel();
2245  $dbw->startAtomic( self::ATOMIC_SECTION_LOCK );
2246  // T56736: use simple lock to handle when the file does not exist.
2247  // SELECT FOR UPDATE prevents changes, not other SELECTs with FOR UPDATE.
2248  // Also, that would cause contention on INSERT of similarly named rows.
2249  $status = $this->acquireFileLock(); // represents all versions of the file
2250  if ( !$status->isGood() ) {
2251  $dbw->endAtomic( self::ATOMIC_SECTION_LOCK );
2252  $logger->warning( "Failed to lock '{file}'", [ 'file' => $this->name ] );
2253 
2254  throw new LocalFileLockError( $status );
2255  }
2256  // Release the lock *after* commit to avoid row-level contention.
2257  // Make sure it triggers on rollback() as well as commit() (T132921).
2258  $dbw->onTransactionResolution(
2259  function () use ( $logger ) {
2260  $status = $this->releaseFileLock();
2261  if ( !$status->isGood() ) {
2262  $logger->error( "Failed to unlock '{file}'", [ 'file' => $this->name ] );
2263  }
2264  },
2265  __METHOD__
2266  );
2267  // Callers might care if the SELECT snapshot is safely fresh
2268  $this->lockedOwnTrx = $makesTransaction;
2269  }
2270 
2271  $this->locked++;
2272 
2273  return $this->lockedOwnTrx;
2274  }
2275 
2284  public function unlock() {
2285  if ( $this->locked ) {
2286  --$this->locked;
2287  if ( !$this->locked ) {
2288  $dbw = $this->repo->getMasterDB();
2289  $dbw->endAtomic( self::ATOMIC_SECTION_LOCK );
2290  $this->lockedOwnTrx = false;
2291  }
2292  }
2293  }
2294 
2298  protected function readOnlyFatalStatus() {
2299  return $this->getRepo()->newFatal( 'filereadonlyerror', $this->getName(),
2300  $this->getRepo()->getName(), $this->getRepo()->getReadOnlyReason() );
2301  }
2302 
2306  function __destruct() {
2307  $this->unlock();
2308  }
2309 }
const SCHEMA_COMPAT_WRITE_OLD
Definition: Defines.php:264
exists()
canRender inherited
Definition: LocalFile.php:986
User $user
Uploader.
Definition: LocalFile.php:116
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:394
getCacheFields( $prefix='img_')
Returns the list of object properties that are included as-is in the cache.
Definition: LocalFile.php:422
MediaHandler $handler
Definition: File.php:124
addWatch( $title, $checkRights=self::CHECK_USER_RIGHTS)
Watch an article.
Definition: User.php:3716
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:2291
const VERSION
Definition: LocalFile.php:57
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:2158
processing should stop and the error should be shown to the user * false
Definition: hooks.txt:193
serialize()
unprefixRow( $row, $prefix='img_')
Definition: LocalFile.php:565
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:1983
string $minor_mime
Minor MIME type.
Definition: LocalFile.php:110
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:2158
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:1679
purgeMetadataCache()
Refresh metadata in memcached, but don&#39;t touch thumbnails or CDN.
Definition: LocalFile.php:1030
releaseFileLock()
Definition: LocalFile.php:2224
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.
IResultWrapper null $historyRes
Result of the query for the file&#39;s history (nextHistoryLine)
Definition: LocalFile.php:104
width
const DELETE_SOURCE
Definition: FileRepo.php:40
getSize()
Returns the size of the image file, in bytes.
Definition: LocalFile.php:949
Handles purging appropriate CDN URLs given a title (or titles)
static newFromAnyId( $userId, $userName, $actorId, $dbDomain=false)
Static factory method for creation from an ID, name, and/or actor ID.
Definition: User.php:627
setProps( $info)
Set properties in this object to be equal to those given in the associative array $info...
Definition: LocalFile.php:772
getThumbnails( $archiveName=false)
getTransformScript inherited
Definition: LocalFile.php:1007
Helper class for file undeletion.
unlock()
Decrement the lock reference count and end the atomic section if it reaches zero. ...
Definition: LocalFile.php:2284
string $major_mime
Major MIME type.
Definition: LocalFile.php:107
string $sha1
SHA-1 base 36 content hash.
Definition: LocalFile.php:86
$source
$value
const SCHEMA_COMPAT_READ_NEW
Definition: Defines.php:267
isMissing()
splitMime inherited
Definition: LocalFile.php:814
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:2150
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:2281
move( $target)
getLinksTo inherited
Definition: LocalFile.php:1909
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:1876
purgeThumbnails( $options=[])
Delete cached transformed files for the current version only.
Definition: LocalFile.php:1083
getArchiveUrl( $suffix=false)
Get the URL of the archive directory, or a particular file if $suffix is specified.
Definition: File.php:1659
getPrefixedText()
Get the prefixed title with spaces.
Definition: Title.php:1724
quickImport( $src, $dst, $options=null)
Import a file from the local file system into the repo.
Definition: FileRepo.php:979
isOld()
Returns true if the image is an old version STUB.
Definition: File.php:1886
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:1183
loadFromDB( $flags=0)
Load file metadata from the DB.
Definition: LocalFile.php:459
title
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
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:966
getName()
Get the user name, or the IP of an anonymous user.
Definition: User.php:2251
restore( $versions=[], $unsuppress=false)
Restore all or specified deleted revisions to the given file.
Definition: LocalFile.php:2063
purgeEverything()
Purge metadata and all affected pages when the file is created, deleted, or majorly updated...
Definition: File.php:1465
__destruct()
Clean up any dangling locks.
Definition: LocalFile.php:2306
getBackend()
Get the file backend instance.
Definition: FileRepo.php:218
static newFromRow( $row, $repo)
Create a LocalFile from a title Do not call this except from inside a repo class. ...
Definition: LocalFile.php:169
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:1434
$batch
Definition: linkcache.txt:23
getVirtualUrl( $suffix=false)
Get the public zone virtual URL for a current version source file.
Definition: File.php:1735
getDescriptionShortUrl()
Get short description URL for a file based on the page ID.
Definition: LocalFile.php:915
nextHistoryLine()
Returns the history of this file, line by line.
Definition: LocalFile.php:1239
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:1250
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:1825
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:1183
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:228
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:590
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:1715
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:1980
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:444
const LOAD_ALL
Definition: LocalFile.php:140
getHandler()
Get a MediaHandler instance for this file.
Definition: File.php:1389
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:310
static factory(array $deltas)
getDescriptionTouched()
Definition: LocalFile.php:2158
const LOCK_EX
Definition: LockManager.php:70
const SCHEMA_COMPAT_WRITE_NEW
Definition: Defines.php:266
getFileSha1( $virtualUrl)
Get the sha1 (base 36) of a file with a given virtual URL/storage path.
Definition: FileRepo.php:1604
getRel()
Get the path of the file relative to the public zone root.
Definition: File.php:1538
$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:2169
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:1323
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:1041
$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:210
acquireFileLock()
Definition: LocalFile.php:2214
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:1340
const CACHE_FIELD_MAX_LEN
Definition: LocalFile.php:59
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:636
static isVirtualUrl( $url)
Determine if a string is an mwrepo:// URL.
Definition: FileRepo.php:257
loadExtraFromDB()
Load lazy file metadata from the DB.
Definition: LocalFile.php:491
$cache
Definition: mcc.php:33
const EDIT_SUPPRESS_RC
Definition: Defines.php:135
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:1983
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:671
readOnlyFatalStatus()
Definition: LocalFile.php:2298
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.
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:773
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:1116
getMetadata()
Get handler-specific metadata.
Definition: LocalFile.php:931
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:1759
purgeOldThumbnails( $archiveName)
Delete cached transformed files for an archived version only.
Definition: LocalFile.php:1060
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: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:268
if(defined( 'MW_SETUP_CALLBACK')) $fname
Customization point after all loading (constants, functions, classes, DefaultSettings, LocalSettings).
Definition: Setup.php:131
purgeThumbList( $dir, $files)
Delete a list of thumbnails visible at urls.
Definition: LocalFile.php:1143
getDescription( $audience=self::FOR_PUBLIC, User $user=null)
Definition: LocalFile.php:2133
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:1549
getDescriptionText(Language $lang=null)
Get the HTML text of the description page This is not used by ImagePage for local files...
Definition: LocalFile.php:2109
static makeTitle( $ns, $title, $fragment='', $interwiki='')
Create a new Title from a namespace index and a DB key.
Definition: Title.php:592
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:1622
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:717
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:2224
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:1446
deleteOld( $archiveName, $reason, $suppress=false, $user=null)
Delete an old version of the file.
Definition: LocalFile.php:2027
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:2290
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:2239
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:516
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:137
const METADATA_COMPATIBLE
getDescriptionUrl()
isMultipage inherited
Definition: LocalFile.php:2097
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
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:113
getHeight( $page=1)
Return the height of the image.
Definition: LocalFile.php:861
int $height
Image height.
Definition: LocalFile.php:68
loadFromFile()
Load metadata from the file itself.
Definition: LocalFile.php:411
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:1844
static getQueryInfo(array $options=[])
Return the tables, fields, and join conditions to be selected to create a new localfile object...
Definition: LocalFile.php:254
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:970
loadFromCache()
Try to load file metadata from memcached, falling back to the database.
Definition: LocalFile.php:326
getMutableCacheKeys(WANObjectCache $cache)
Definition: LocalFile.php:319
int $width
Image width.
Definition: LocalFile.php:65
getMimeType()
Returns the MIME type of the file.
Definition: LocalFile.php:959
resetHistory()
Reset the history pointer to the first element of the history.
Definition: LocalFile.php:1282
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:80
hasSha1Storage()
Returns whether or not storage is SHA-1 based.
Definition: FileRepo.php:1931
isDeleted( $field)
Is this file a "deleted" file in a private archive? STUB.
Definition: File.php:1897
getWidth( $page=1)
Return the width of the image.
Definition: LocalFile.php:829
load( $flags=0)
Load file metadata from cache or DB, unless already loaded.
Definition: LocalFile.php:653
purgeDescription()
Purge the file description page, but don&#39;t go after pages using the file.
Definition: File.php:1453
getThumbPath( $suffix=false)
Get the path of the thumbnail directory, or a particular file if $suffix is specified.
Definition: File.php:1635
$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:894
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:292
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:1412
static newFromText( $text, $defaultNamespace=NS_MAIN)
Create a new Title from text, such as what one would find in a link.
Definition: Title.php:322