MediaWiki  master
LocalFile.php
Go to the documentation of this file.
1 <?php
28 
46 class LocalFile extends File {
47  const VERSION = 11; // cache version
48 
49  const CACHE_FIELD_MAX_LEN = 1000;
50 
52  protected $fileExists;
53 
55  protected $width;
56 
58  protected $height;
59 
61  protected $bits;
62 
64  protected $media_type;
65 
67  protected $mime;
68 
70  protected $size;
71 
73  protected $metadata;
74 
76  protected $sha1;
77 
79  protected $dataLoaded;
80 
82  protected $extraDataLoaded;
83 
85  protected $deleted;
86 
89 
91  private $historyLine;
92 
94  private $historyRes;
95 
97  private $major_mime;
98 
100  private $minor_mime;
101 
103  private $timestamp;
104 
106  private $user;
107 
109  private $description;
110 
113 
115  private $upgraded;
116 
118  private $upgrading;
119 
121  private $locked;
122 
124  private $lockedOwnTrx;
125 
127  private $missing;
128 
129  // @note: higher than IDBAccessObject constants
130  const LOAD_ALL = 16; // integer; load all the lazy fields too (like metadata)
131 
132  const ATOMIC_SECTION_LOCK = 'LocalFile::lockingTransaction';
133 
146  static function newFromTitle( $title, $repo, $unused = null ) {
147  return new self( $title, $repo );
148  }
149 
159  static function newFromRow( $row, $repo ) {
160  $title = Title::makeTitle( NS_FILE, $row->img_name );
161  $file = new self( $title, $repo );
162  $file->loadFromRow( $row );
163 
164  return $file;
165  }
166 
176  static function newFromKey( $sha1, $repo, $timestamp = false ) {
177  $dbr = $repo->getReplicaDB();
178 
179  $conds = [ 'img_sha1' => $sha1 ];
180  if ( $timestamp ) {
181  $conds['img_timestamp'] = $dbr->timestamp( $timestamp );
182  }
183 
184  $fileQuery = self::getQueryInfo();
185  $row = $dbr->selectRow(
186  $fileQuery['tables'], $fileQuery['fields'], $conds, __METHOD__, [], $fileQuery['joins']
187  );
188  if ( $row ) {
189  return self::newFromRow( $row, $repo );
190  } else {
191  return false;
192  }
193  }
194 
200  static function selectFields() {
202 
203  wfDeprecated( __METHOD__, '1.31' );
204  if ( $wgActorTableSchemaMigrationStage & SCHEMA_COMPAT_READ_NEW ) {
205  // If code is using this instead of self::getQueryInfo(), there's a
206  // decent chance it's going to try to directly access
207  // $row->img_user or $row->img_user_text and we can't give it
208  // useful values here once those aren't being used anymore.
209  throw new BadMethodCallException(
210  'Cannot use ' . __METHOD__
211  . ' when $wgActorTableSchemaMigrationStage has SCHEMA_COMPAT_READ_NEW'
212  );
213  }
214 
215  return [
216  'img_name',
217  'img_size',
218  'img_width',
219  'img_height',
220  'img_metadata',
221  'img_bits',
222  'img_media_type',
223  'img_major_mime',
224  'img_minor_mime',
225  'img_user',
226  'img_user_text',
227  'img_actor' => 'NULL',
228  'img_timestamp',
229  'img_sha1',
230  ] + MediaWikiServices::getInstance()->getCommentStore()->getFields( 'img_description' );
231  }
232 
244  public static function getQueryInfo( array $options = [] ) {
245  $commentQuery = MediaWikiServices::getInstance()->getCommentStore()->getJoin( 'img_description' );
246  $actorQuery = ActorMigration::newMigration()->getJoin( 'img_user' );
247  $ret = [
248  'tables' => [ 'image' ] + $commentQuery['tables'] + $actorQuery['tables'],
249  'fields' => [
250  'img_name',
251  'img_size',
252  'img_width',
253  'img_height',
254  'img_metadata',
255  'img_bits',
256  'img_media_type',
257  'img_major_mime',
258  'img_minor_mime',
259  'img_timestamp',
260  'img_sha1',
261  ] + $commentQuery['fields'] + $actorQuery['fields'],
262  'joins' => $commentQuery['joins'] + $actorQuery['joins'],
263  ];
264 
265  if ( in_array( 'omit-nonlazy', $options, true ) ) {
266  // Internal use only for getting only the lazy fields
267  $ret['fields'] = [];
268  }
269  if ( !in_array( 'omit-lazy', $options, true ) ) {
270  // Note: Keep this in sync with self::getLazyCacheFields()
271  $ret['fields'][] = 'img_metadata';
272  }
273 
274  return $ret;
275  }
276 
282  function __construct( $title, $repo ) {
283  parent::__construct( $title, $repo );
284 
285  $this->metadata = '';
286  $this->historyLine = 0;
287  $this->historyRes = null;
288  $this->dataLoaded = false;
289  $this->extraDataLoaded = false;
290 
291  $this->assertRepoDefined();
292  $this->assertTitleDefined();
293  }
294 
300  function getCacheKey() {
301  return $this->repo->getSharedCacheKey( 'file', sha1( $this->getName() ) );
302  }
303 
310  return [ $this->getCacheKey() ];
311  }
312 
316  private function loadFromCache() {
317  $this->dataLoaded = false;
318  $this->extraDataLoaded = false;
319 
320  $key = $this->getCacheKey();
321  if ( !$key ) {
322  $this->loadFromDB( self::READ_NORMAL );
323 
324  return;
325  }
326 
327  $cache = MediaWikiServices::getInstance()->getMainWANObjectCache();
328  $cachedValues = $cache->getWithSetCallback(
329  $key,
330  $cache::TTL_WEEK,
331  function ( $oldValue, &$ttl, array &$setOpts ) use ( $cache ) {
332  $setOpts += Database::getCacheSetOptions( $this->repo->getReplicaDB() );
333 
334  $this->loadFromDB( self::READ_NORMAL );
335 
336  $fields = $this->getCacheFields( '' );
337  $cacheVal['fileExists'] = $this->fileExists;
338  if ( $this->fileExists ) {
339  foreach ( $fields as $field ) {
340  $cacheVal[$field] = $this->$field;
341  }
342  }
343  $cacheVal['user'] = $this->user ? $this->user->getId() : 0;
344  $cacheVal['user_text'] = $this->user ? $this->user->getName() : '';
345  $cacheVal['actor'] = $this->user ? $this->user->getActorId() : null;
346 
347  // Strip off excessive entries from the subset of fields that can become large.
348  // If the cache value gets to large it will not fit in memcached and nothing will
349  // get cached at all, causing master queries for any file access.
350  foreach ( $this->getLazyCacheFields( '' ) as $field ) {
351  if ( isset( $cacheVal[$field] )
352  && strlen( $cacheVal[$field] ) > 100 * 1024
353  ) {
354  unset( $cacheVal[$field] ); // don't let the value get too big
355  }
356  }
357 
358  if ( $this->fileExists ) {
359  $ttl = $cache->adaptiveTTL( wfTimestamp( TS_UNIX, $this->timestamp ), $ttl );
360  } else {
361  $ttl = $cache::TTL_DAY;
362  }
363 
364  return $cacheVal;
365  },
366  [ 'version' => self::VERSION ]
367  );
368 
369  $this->fileExists = $cachedValues['fileExists'];
370  if ( $this->fileExists ) {
371  $this->setProps( $cachedValues );
372  }
373 
374  $this->dataLoaded = true;
375  $this->extraDataLoaded = true;
376  foreach ( $this->getLazyCacheFields( '' ) as $field ) {
377  $this->extraDataLoaded = $this->extraDataLoaded && isset( $cachedValues[$field] );
378  }
379  }
380 
384  public function invalidateCache() {
385  $key = $this->getCacheKey();
386  if ( !$key ) {
387  return;
388  }
389 
390  $this->repo->getMasterDB()->onTransactionPreCommitOrIdle(
391  function () use ( $key ) {
392  MediaWikiServices::getInstance()->getMainWANObjectCache()->delete( $key );
393  },
394  __METHOD__
395  );
396  }
397 
401  function loadFromFile() {
402  $props = $this->repo->getFileProps( $this->getVirtualUrl() );
403  $this->setProps( $props );
404  }
405 
412  protected function getCacheFields( $prefix = 'img_' ) {
413  if ( $prefix !== '' ) {
414  throw new InvalidArgumentException(
415  __METHOD__ . ' with a non-empty prefix is no longer supported.'
416  );
417  }
418 
419  // See self::getQueryInfo() for the fetching of the data from the DB,
420  // self::loadFromRow() for the loading of the object from the DB row,
421  // and self::loadFromCache() for the caching, and self::setProps() for
422  // populating the object from an array of data.
423  return [ 'size', 'width', 'height', 'bits', 'media_type',
424  'major_mime', 'minor_mime', 'metadata', 'timestamp', 'sha1', 'description' ];
425  }
426 
434  protected function getLazyCacheFields( $prefix = 'img_' ) {
435  if ( $prefix !== '' ) {
436  throw new InvalidArgumentException(
437  __METHOD__ . ' with a non-empty prefix is no longer supported.'
438  );
439  }
440 
441  // Keep this in sync with the omit-lazy option in self::getQueryInfo().
442  return [ 'metadata' ];
443  }
444 
449  function loadFromDB( $flags = 0 ) {
450  $fname = static::class . '::' . __FUNCTION__;
451 
452  # Unconditionally set loaded=true, we don't want the accessors constantly rechecking
453  $this->dataLoaded = true;
454  $this->extraDataLoaded = true;
455 
456  $dbr = ( $flags & self::READ_LATEST )
457  ? $this->repo->getMasterDB()
458  : $this->repo->getReplicaDB();
459 
460  $fileQuery = static::getQueryInfo();
461  $row = $dbr->selectRow(
462  $fileQuery['tables'],
463  $fileQuery['fields'],
464  [ 'img_name' => $this->getName() ],
465  $fname,
466  [],
467  $fileQuery['joins']
468  );
469 
470  if ( $row ) {
471  $this->loadFromRow( $row );
472  } else {
473  $this->fileExists = false;
474  }
475  }
476 
481  protected function loadExtraFromDB() {
482  $fname = static::class . '::' . __FUNCTION__;
483 
484  # Unconditionally set loaded=true, we don't want the accessors constantly rechecking
485  $this->extraDataLoaded = true;
486 
487  $fieldMap = $this->loadExtraFieldsWithTimestamp( $this->repo->getReplicaDB(), $fname );
488  if ( !$fieldMap ) {
489  $fieldMap = $this->loadExtraFieldsWithTimestamp( $this->repo->getMasterDB(), $fname );
490  }
491 
492  if ( $fieldMap ) {
493  foreach ( $fieldMap as $name => $value ) {
494  $this->$name = $value;
495  }
496  } else {
497  throw new MWException( "Could not find data for image '{$this->getName()}'." );
498  }
499  }
500 
506  private function loadExtraFieldsWithTimestamp( $dbr, $fname ) {
507  $fieldMap = false;
508 
509  $fileQuery = self::getQueryInfo( [ 'omit-nonlazy' ] );
510  $row = $dbr->selectRow(
511  $fileQuery['tables'],
512  $fileQuery['fields'],
513  [
514  'img_name' => $this->getName(),
515  'img_timestamp' => $dbr->timestamp( $this->getTimestamp() ),
516  ],
517  $fname,
518  [],
519  $fileQuery['joins']
520  );
521  if ( $row ) {
522  $fieldMap = $this->unprefixRow( $row, 'img_' );
523  } else {
524  # File may have been uploaded over in the meantime; check the old versions
525  $fileQuery = OldLocalFile::getQueryInfo( [ 'omit-nonlazy' ] );
526  $row = $dbr->selectRow(
527  $fileQuery['tables'],
528  $fileQuery['fields'],
529  [
530  'oi_name' => $this->getName(),
531  'oi_timestamp' => $dbr->timestamp( $this->getTimestamp() ),
532  ],
533  $fname,
534  [],
535  $fileQuery['joins']
536  );
537  if ( $row ) {
538  $fieldMap = $this->unprefixRow( $row, 'oi_' );
539  }
540  }
541 
542  if ( isset( $fieldMap['metadata'] ) ) {
543  $fieldMap['metadata'] = $this->repo->getReplicaDB()->decodeBlob( $fieldMap['metadata'] );
544  }
545 
546  return $fieldMap;
547  }
548 
555  protected function unprefixRow( $row, $prefix = 'img_' ) {
556  $array = (array)$row;
557  $prefixLength = strlen( $prefix );
558 
559  // Sanity check prefix once
560  if ( substr( key( $array ), 0, $prefixLength ) !== $prefix ) {
561  throw new MWException( __METHOD__ . ': incorrect $prefix parameter' );
562  }
563 
564  $decoded = [];
565  foreach ( $array as $name => $value ) {
566  $decoded[substr( $name, $prefixLength )] = $value;
567  }
568 
569  return $decoded;
570  }
571 
580  function decodeRow( $row, $prefix = 'img_' ) {
581  $decoded = $this->unprefixRow( $row, $prefix );
582 
583  $decoded['description'] = MediaWikiServices::getInstance()->getCommentStore()
584  ->getComment( 'description', (object)$decoded )->text;
585 
586  $decoded['user'] = User::newFromAnyId(
587  $decoded['user'] ?? null,
588  $decoded['user_text'] ?? null,
589  $decoded['actor'] ?? null
590  );
591  unset( $decoded['user_text'], $decoded['actor'] );
592 
593  $decoded['timestamp'] = wfTimestamp( TS_MW, $decoded['timestamp'] );
594 
595  $decoded['metadata'] = $this->repo->getReplicaDB()->decodeBlob( $decoded['metadata'] );
596 
597  if ( empty( $decoded['major_mime'] ) ) {
598  $decoded['mime'] = 'unknown/unknown';
599  } else {
600  if ( !$decoded['minor_mime'] ) {
601  $decoded['minor_mime'] = 'unknown';
602  }
603  $decoded['mime'] = $decoded['major_mime'] . '/' . $decoded['minor_mime'];
604  }
605 
606  // Trim zero padding from char/binary field
607  $decoded['sha1'] = rtrim( $decoded['sha1'], "\0" );
608 
609  // Normalize some fields to integer type, per their database definition.
610  // Use unary + so that overflows will be upgraded to double instead of
611  // being trucated as with intval(). This is important to allow >2GB
612  // files on 32-bit systems.
613  foreach ( [ 'size', 'width', 'height', 'bits' ] as $field ) {
614  $decoded[$field] = +$decoded[$field];
615  }
616 
617  return $decoded;
618  }
619 
626  function loadFromRow( $row, $prefix = 'img_' ) {
627  $this->dataLoaded = true;
628  $this->extraDataLoaded = true;
629 
630  $array = $this->decodeRow( $row, $prefix );
631 
632  foreach ( $array as $name => $value ) {
633  $this->$name = $value;
634  }
635 
636  $this->fileExists = true;
637  }
638 
643  function load( $flags = 0 ) {
644  if ( !$this->dataLoaded ) {
645  if ( $flags & self::READ_LATEST ) {
646  $this->loadFromDB( $flags );
647  } else {
648  $this->loadFromCache();
649  }
650  }
651 
652  if ( ( $flags & self::LOAD_ALL ) && !$this->extraDataLoaded ) {
653  // @note: loads on name/timestamp to reduce race condition problems
654  $this->loadExtraFromDB();
655  }
656  }
657 
661  protected function maybeUpgradeRow() {
663 
664  if ( wfReadOnly() || $this->upgrading ) {
665  return;
666  }
667 
668  $upgrade = false;
669  if ( is_null( $this->media_type ) || $this->mime == 'image/svg' ) {
670  $upgrade = true;
671  } else {
672  $handler = $this->getHandler();
673  if ( $handler ) {
674  $validity = $handler->isMetadataValid( $this, $this->getMetadata() );
675  if ( $validity === MediaHandler::METADATA_BAD ) {
676  $upgrade = true;
677  } elseif ( $validity === MediaHandler::METADATA_COMPATIBLE ) {
678  $upgrade = $wgUpdateCompatibleMetadata;
679  }
680  }
681  }
682 
683  if ( $upgrade ) {
684  $this->upgrading = true;
685  // Defer updates unless in auto-commit CLI mode
687  $this->upgrading = false; // avoid duplicate updates
688  try {
689  $this->upgradeRow();
690  } catch ( LocalFileLockError $e ) {
691  // let the other process handle it (or do it next time)
692  }
693  } );
694  }
695  }
696 
700  function getUpgraded() {
701  return $this->upgraded;
702  }
703 
707  function upgradeRow() {
708  $this->lock();
709 
710  $this->loadFromFile();
711 
712  # Don't destroy file info of missing files
713  if ( !$this->fileExists ) {
714  $this->unlock();
715  wfDebug( __METHOD__ . ": file does not exist, aborting\n" );
716 
717  return;
718  }
719 
720  $dbw = $this->repo->getMasterDB();
721  list( $major, $minor ) = self::splitMime( $this->mime );
722 
723  if ( wfReadOnly() ) {
724  $this->unlock();
725 
726  return;
727  }
728  wfDebug( __METHOD__ . ': upgrading ' . $this->getName() . " to the current schema\n" );
729 
730  $dbw->update( 'image',
731  [
732  'img_size' => $this->size, // sanity
733  'img_width' => $this->width,
734  'img_height' => $this->height,
735  'img_bits' => $this->bits,
736  'img_media_type' => $this->media_type,
737  'img_major_mime' => $major,
738  'img_minor_mime' => $minor,
739  'img_metadata' => $dbw->encodeBlob( $this->metadata ),
740  'img_sha1' => $this->sha1,
741  ],
742  [ 'img_name' => $this->getName() ],
743  __METHOD__
744  );
745 
746  $this->invalidateCache();
747 
748  $this->unlock();
749  $this->upgraded = true; // avoid rework/retries
750  }
751 
762  function setProps( $info ) {
763  $this->dataLoaded = true;
764  $fields = $this->getCacheFields( '' );
765  $fields[] = 'fileExists';
766 
767  foreach ( $fields as $field ) {
768  if ( isset( $info[$field] ) ) {
769  $this->$field = $info[$field];
770  }
771  }
772 
773  if ( isset( $info['user'] ) || isset( $info['user_text'] ) || isset( $info['actor'] ) ) {
774  $this->user = User::newFromAnyId(
775  $info['user'] ?? null,
776  $info['user_text'] ?? null,
777  $info['actor'] ?? null
778  );
779  }
780 
781  // Fix up mime fields
782  if ( isset( $info['major_mime'] ) ) {
783  $this->mime = "{$info['major_mime']}/{$info['minor_mime']}";
784  } elseif ( isset( $info['mime'] ) ) {
785  $this->mime = $info['mime'];
786  list( $this->major_mime, $this->minor_mime ) = self::splitMime( $this->mime );
787  }
788  }
789 
804  function isMissing() {
805  if ( $this->missing === null ) {
806  $fileExists = $this->repo->fileExists( $this->getVirtualUrl() );
807  $this->missing = !$fileExists;
808  }
809 
810  return $this->missing;
811  }
812 
819  public function getWidth( $page = 1 ) {
820  $page = (int)$page;
821  if ( $page < 1 ) {
822  $page = 1;
823  }
824 
825  $this->load();
826 
827  if ( $this->isMultipage() ) {
828  $handler = $this->getHandler();
829  if ( !$handler ) {
830  return 0;
831  }
832  $dim = $handler->getPageDimensions( $this, $page );
833  if ( $dim ) {
834  return $dim['width'];
835  } else {
836  // For non-paged media, the false goes through an
837  // intval, turning failure into 0, so do same here.
838  return 0;
839  }
840  } else {
841  return $this->width;
842  }
843  }
844 
851  public function getHeight( $page = 1 ) {
852  $page = (int)$page;
853  if ( $page < 1 ) {
854  $page = 1;
855  }
856 
857  $this->load();
858 
859  if ( $this->isMultipage() ) {
860  $handler = $this->getHandler();
861  if ( !$handler ) {
862  return 0;
863  }
864  $dim = $handler->getPageDimensions( $this, $page );
865  if ( $dim ) {
866  return $dim['height'];
867  } else {
868  // For non-paged media, the false goes through an
869  // intval, turning failure into 0, so do same here.
870  return 0;
871  }
872  } else {
873  return $this->height;
874  }
875  }
876 
884  function getUser( $type = 'text' ) {
885  $this->load();
886 
887  if ( $type === 'object' ) {
888  return $this->user;
889  } elseif ( $type === 'text' ) {
890  return $this->user->getName();
891  } elseif ( $type === 'id' ) {
892  return $this->user->getId();
893  }
894 
895  throw new MWException( "Unknown type '$type'." );
896  }
897 
905  public function getDescriptionShortUrl() {
906  $pageId = $this->title->getArticleID();
907 
908  if ( $pageId !== null ) {
909  $url = $this->repo->makeUrl( [ 'curid' => $pageId ] );
910  if ( $url !== false ) {
911  return $url;
912  }
913  }
914  return null;
915  }
916 
921  function getMetadata() {
922  $this->load( self::LOAD_ALL ); // large metadata is loaded in another step
923  return $this->metadata;
924  }
925 
929  function getBitDepth() {
930  $this->load();
931 
932  return (int)$this->bits;
933  }
934 
939  public function getSize() {
940  $this->load();
941 
942  return $this->size;
943  }
944 
949  function getMimeType() {
950  $this->load();
951 
952  return $this->mime;
953  }
954 
960  function getMediaType() {
961  $this->load();
962 
963  return $this->media_type;
964  }
965 
976  public function exists() {
977  $this->load();
978 
979  return $this->fileExists;
980  }
981 
997  function getThumbnails( $archiveName = false ) {
998  if ( $archiveName ) {
999  $dir = $this->getArchiveThumbPath( $archiveName );
1000  } else {
1001  $dir = $this->getThumbPath();
1002  }
1003 
1004  $backend = $this->repo->getBackend();
1005  $files = [ $dir ];
1006  try {
1007  $iterator = $backend->getFileList( [ 'dir' => $dir ] );
1008  foreach ( $iterator as $file ) {
1009  $files[] = $file;
1010  }
1011  } catch ( FileBackendError $e ) {
1012  } // suppress (T56674)
1013 
1014  return $files;
1015  }
1016 
1020  function purgeMetadataCache() {
1021  $this->invalidateCache();
1022  }
1023 
1031  function purgeCache( $options = [] ) {
1032  // Refresh metadata cache
1033  $this->maybeUpgradeRow();
1034  $this->purgeMetadataCache();
1035 
1036  // Delete thumbnails
1037  $this->purgeThumbnails( $options );
1038 
1039  // Purge CDN cache for this file
1041  new CdnCacheUpdate( [ $this->getUrl() ] ),
1043  );
1044  }
1045 
1050  function purgeOldThumbnails( $archiveName ) {
1051  // Get a list of old thumbnails and URLs
1052  $files = $this->getThumbnails( $archiveName );
1053 
1054  // Purge any custom thumbnail caches
1055  Hooks::run( 'LocalFilePurgeThumbnails', [ $this, $archiveName ] );
1056 
1057  // Delete thumbnails
1058  $dir = array_shift( $files );
1059  $this->purgeThumbList( $dir, $files );
1060 
1061  // Purge the CDN
1062  $urls = [];
1063  foreach ( $files as $file ) {
1064  $urls[] = $this->getArchiveThumbUrl( $archiveName, $file );
1065  }
1067  }
1068 
1073  public function purgeThumbnails( $options = [] ) {
1074  $files = $this->getThumbnails();
1075  // Always purge all files from CDN regardless of handler filters
1076  $urls = [];
1077  foreach ( $files as $file ) {
1078  $urls[] = $this->getThumbUrl( $file );
1079  }
1080  array_shift( $urls ); // don't purge directory
1081 
1082  // Give media handler a chance to filter the file purge list
1083  if ( !empty( $options['forThumbRefresh'] ) ) {
1084  $handler = $this->getHandler();
1085  if ( $handler ) {
1087  }
1088  }
1089 
1090  // Purge any custom thumbnail caches
1091  Hooks::run( 'LocalFilePurgeThumbnails', [ $this, false ] );
1092 
1093  // Delete thumbnails
1094  $dir = array_shift( $files );
1095  $this->purgeThumbList( $dir, $files );
1096 
1097  // Purge the CDN
1099  }
1100 
1106  public function prerenderThumbnails() {
1108 
1109  $jobs = [];
1110 
1111  $sizes = $wgUploadThumbnailRenderMap;
1112  rsort( $sizes );
1113 
1114  foreach ( $sizes as $size ) {
1115  if ( $this->isVectorized() || $this->getWidth() > $size ) {
1116  $jobs[] = new ThumbnailRenderJob(
1117  $this->getTitle(),
1118  [ 'transformParams' => [ 'width' => $size ] ]
1119  );
1120  }
1121  }
1122 
1123  if ( $jobs ) {
1124  JobQueueGroup::singleton()->lazyPush( $jobs );
1125  }
1126  }
1127 
1133  protected function purgeThumbList( $dir, $files ) {
1134  $fileListDebug = strtr(
1135  var_export( $files, true ),
1136  [ "\n" => '' ]
1137  );
1138  wfDebug( __METHOD__ . ": $fileListDebug\n" );
1139 
1140  $purgeList = [];
1141  foreach ( $files as $file ) {
1142  if ( $this->repo->supportsSha1URLs() ) {
1143  $reference = $this->getSha1();
1144  } else {
1145  $reference = $this->getName();
1146  }
1147 
1148  # Check that the reference (filename or sha1) is part of the thumb name
1149  # This is a basic sanity check to avoid erasing unrelated directories
1150  if ( strpos( $file, $reference ) !== false
1151  || strpos( $file, "-thumbnail" ) !== false // "short" thumb name
1152  ) {
1153  $purgeList[] = "{$dir}/{$file}";
1154  }
1155  }
1156 
1157  # Delete the thumbnails
1158  $this->repo->quickPurgeBatch( $purgeList );
1159  # Clear out the thumbnail directory if empty
1160  $this->repo->quickCleanDir( $dir );
1161  }
1162 
1173  function getHistory( $limit = null, $start = null, $end = null, $inc = true ) {
1174  $dbr = $this->repo->getReplicaDB();
1175  $oldFileQuery = OldLocalFile::getQueryInfo();
1176 
1177  $tables = $oldFileQuery['tables'];
1178  $fields = $oldFileQuery['fields'];
1179  $join_conds = $oldFileQuery['joins'];
1180  $conds = $opts = [];
1181  $eq = $inc ? '=' : '';
1182  $conds[] = "oi_name = " . $dbr->addQuotes( $this->title->getDBkey() );
1183 
1184  if ( $start ) {
1185  $conds[] = "oi_timestamp <$eq " . $dbr->addQuotes( $dbr->timestamp( $start ) );
1186  }
1187 
1188  if ( $end ) {
1189  $conds[] = "oi_timestamp >$eq " . $dbr->addQuotes( $dbr->timestamp( $end ) );
1190  }
1191 
1192  if ( $limit ) {
1193  $opts['LIMIT'] = $limit;
1194  }
1195 
1196  // Search backwards for time > x queries
1197  $order = ( !$start && $end !== null ) ? 'ASC' : 'DESC';
1198  $opts['ORDER BY'] = "oi_timestamp $order";
1199  $opts['USE INDEX'] = [ 'oldimage' => 'oi_name_timestamp' ];
1200 
1201  // Avoid PHP 7.1 warning from passing $this by reference
1202  $localFile = $this;
1203  Hooks::run( 'LocalFile::getHistory', [ &$localFile, &$tables, &$fields,
1204  &$conds, &$opts, &$join_conds ] );
1205 
1206  $res = $dbr->select( $tables, $fields, $conds, __METHOD__, $opts, $join_conds );
1207  $r = [];
1208 
1209  foreach ( $res as $row ) {
1210  $r[] = $this->repo->newFileFromRow( $row );
1211  }
1212 
1213  if ( $order == 'ASC' ) {
1214  $r = array_reverse( $r ); // make sure it ends up descending
1215  }
1216 
1217  return $r;
1218  }
1219 
1229  public function nextHistoryLine() {
1230  # Polymorphic function name to distinguish foreign and local fetches
1231  $fname = static::class . '::' . __FUNCTION__;
1232 
1233  $dbr = $this->repo->getReplicaDB();
1234 
1235  if ( $this->historyLine == 0 ) { // called for the first time, return line from cur
1236  $fileQuery = self::getQueryInfo();
1237  $this->historyRes = $dbr->select( $fileQuery['tables'],
1238  $fileQuery['fields'] + [
1239  'oi_archive_name' => $dbr->addQuotes( '' ),
1240  'oi_deleted' => 0,
1241  ],
1242  [ 'img_name' => $this->title->getDBkey() ],
1243  $fname,
1244  [],
1245  $fileQuery['joins']
1246  );
1247 
1248  if ( $dbr->numRows( $this->historyRes ) == 0 ) {
1249  $this->historyRes = null;
1250 
1251  return false;
1252  }
1253  } elseif ( $this->historyLine == 1 ) {
1254  $fileQuery = OldLocalFile::getQueryInfo();
1255  $this->historyRes = $dbr->select(
1256  $fileQuery['tables'],
1257  $fileQuery['fields'],
1258  [ 'oi_name' => $this->title->getDBkey() ],
1259  $fname,
1260  [ 'ORDER BY' => 'oi_timestamp DESC' ],
1261  $fileQuery['joins']
1262  );
1263  }
1264  $this->historyLine++;
1265 
1266  return $dbr->fetchObject( $this->historyRes );
1267  }
1268 
1272  public function resetHistory() {
1273  $this->historyLine = 0;
1274 
1275  if ( !is_null( $this->historyRes ) ) {
1276  $this->historyRes = null;
1277  }
1278  }
1279 
1313  function upload( $src, $comment, $pageText, $flags = 0, $props = false,
1314  $timestamp = false, $user = null, $tags = [],
1315  $createNullRevision = true, $revert = false
1316  ) {
1317  if ( $this->getRepo()->getReadOnlyReason() !== false ) {
1318  return $this->readOnlyFatalStatus();
1319  } elseif ( MediaWikiServices::getInstance()->getRevisionStore()->isReadOnly() ) {
1320  // Check this in advance to avoid writing to FileBackend and the file tables,
1321  // only to fail on insert the revision due to the text store being unavailable.
1322  return $this->readOnlyFatalStatus();
1323  }
1324 
1325  $srcPath = ( $src instanceof FSFile ) ? $src->getPath() : $src;
1326  if ( !$props ) {
1327  if ( FileRepo::isVirtualUrl( $srcPath )
1328  || FileBackend::isStoragePath( $srcPath )
1329  ) {
1330  $props = $this->repo->getFileProps( $srcPath );
1331  } else {
1332  $mwProps = new MWFileProps( MediaWikiServices::getInstance()->getMimeAnalyzer() );
1333  $props = $mwProps->getPropsFromPath( $srcPath, true );
1334  }
1335  }
1336 
1337  $options = [];
1338  $handler = MediaHandler::getHandler( $props['mime'] );
1339  if ( $handler ) {
1340  $metadata = Wikimedia\quietCall( 'unserialize', $props['metadata'] );
1341 
1342  if ( !is_array( $metadata ) ) {
1343  $metadata = [];
1344  }
1345 
1346  $options['headers'] = $handler->getContentHeaders( $metadata );
1347  } else {
1348  $options['headers'] = [];
1349  }
1350 
1351  // Trim spaces on user supplied text
1352  $comment = trim( $comment );
1353 
1354  $this->lock();
1355  $status = $this->publish( $src, $flags, $options );
1356 
1357  if ( $status->successCount >= 2 ) {
1358  // There will be a copy+(one of move,copy,store).
1359  // The first succeeding does not commit us to updating the DB
1360  // since it simply copied the current version to a timestamped file name.
1361  // It is only *preferable* to avoid leaving such files orphaned.
1362  // Once the second operation goes through, then the current version was
1363  // updated and we must therefore update the DB too.
1364  $oldver = $status->value;
1365  $uploadStatus = $this->recordUpload2(
1366  $oldver,
1367  $comment,
1368  $pageText,
1369  $props,
1370  $timestamp,
1371  $user,
1372  $tags,
1373  $createNullRevision,
1374  $revert
1375  );
1376  if ( !$uploadStatus->isOK() ) {
1377  if ( $uploadStatus->hasMessage( 'filenotfound' ) ) {
1378  // update filenotfound error with more specific path
1379  $status->fatal( 'filenotfound', $srcPath );
1380  } else {
1381  $status->merge( $uploadStatus );
1382  }
1383  }
1384  }
1385 
1386  $this->unlock();
1387  return $status;
1388  }
1389 
1402  function recordUpload( $oldver, $desc, $license = '', $copyStatus = '', $source = '',
1403  $watch = false, $timestamp = false, User $user = null ) {
1404  if ( !$user ) {
1405  global $wgUser;
1406  $user = $wgUser;
1407  }
1408 
1409  $pageText = SpecialUpload::getInitialPageText( $desc, $license, $copyStatus, $source );
1410 
1411  if ( !$this->recordUpload2( $oldver, $desc, $pageText, false, $timestamp, $user )->isOK() ) {
1412  return false;
1413  }
1414 
1415  if ( $watch ) {
1416  $user->addWatch( $this->getTitle() );
1417  }
1418 
1419  return true;
1420  }
1421 
1436  function recordUpload2(
1437  $oldver, $comment, $pageText, $props = false, $timestamp = false, $user = null, $tags = [],
1438  $createNullRevision = true, $revert = false
1439  ) {
1441 
1442  if ( is_null( $user ) ) {
1443  global $wgUser;
1444  $user = $wgUser;
1445  }
1446 
1447  $dbw = $this->repo->getMasterDB();
1448 
1449  # Imports or such might force a certain timestamp; otherwise we generate
1450  # it and can fudge it slightly to keep (name,timestamp) unique on re-upload.
1451  if ( $timestamp === false ) {
1452  $timestamp = $dbw->timestamp();
1453  $allowTimeKludge = true;
1454  } else {
1455  $allowTimeKludge = false;
1456  }
1457 
1458  $props = $props ?: $this->repo->getFileProps( $this->getVirtualUrl() );
1459  $props['description'] = $comment;
1460  $props['user'] = $user->getId();
1461  $props['user_text'] = $user->getName();
1462  $props['actor'] = $user->getActorId( $dbw );
1463  $props['timestamp'] = wfTimestamp( TS_MW, $timestamp ); // DB -> TS_MW
1464  $this->setProps( $props );
1465 
1466  # Fail now if the file isn't there
1467  if ( !$this->fileExists ) {
1468  wfDebug( __METHOD__ . ": File " . $this->getRel() . " went missing!\n" );
1469 
1470  return Status::newFatal( 'filenotfound', $this->getRel() );
1471  }
1472 
1473  $dbw->startAtomic( __METHOD__ );
1474 
1475  # Test to see if the row exists using INSERT IGNORE
1476  # This avoids race conditions by locking the row until the commit, and also
1477  # doesn't deadlock. SELECT FOR UPDATE causes a deadlock for every race condition.
1478  $commentStore = MediaWikiServices::getInstance()->getCommentStore();
1479  $commentFields = $commentStore->insert( $dbw, 'img_description', $comment );
1480  $actorMigration = ActorMigration::newMigration();
1481  $actorFields = $actorMigration->getInsertValues( $dbw, 'img_user', $user );
1482  $dbw->insert( 'image',
1483  [
1484  'img_name' => $this->getName(),
1485  'img_size' => $this->size,
1486  'img_width' => intval( $this->width ),
1487  'img_height' => intval( $this->height ),
1488  'img_bits' => $this->bits,
1489  'img_media_type' => $this->media_type,
1490  'img_major_mime' => $this->major_mime,
1491  'img_minor_mime' => $this->minor_mime,
1492  'img_timestamp' => $timestamp,
1493  'img_metadata' => $dbw->encodeBlob( $this->metadata ),
1494  'img_sha1' => $this->sha1
1495  ] + $commentFields + $actorFields,
1496  __METHOD__,
1497  'IGNORE'
1498  );
1499  $reupload = ( $dbw->affectedRows() == 0 );
1500 
1501  if ( $reupload ) {
1502  $row = $dbw->selectRow(
1503  'image',
1504  [ 'img_timestamp', 'img_sha1' ],
1505  [ 'img_name' => $this->getName() ],
1506  __METHOD__,
1507  [ 'LOCK IN SHARE MODE' ]
1508  );
1509 
1510  if ( $row && $row->img_sha1 === $this->sha1 ) {
1511  $dbw->endAtomic( __METHOD__ );
1512  wfDebug( __METHOD__ . ": File " . $this->getRel() . " already exists!\n" );
1513  $title = Title::newFromText( $this->getName(), NS_FILE );
1514  return Status::newFatal( 'fileexists-no-change', $title->getPrefixedText() );
1515  }
1516 
1517  if ( $allowTimeKludge ) {
1518  # Use LOCK IN SHARE MODE to ignore any transaction snapshotting
1519  $lUnixtime = $row ? wfTimestamp( TS_UNIX, $row->img_timestamp ) : false;
1520  # Avoid a timestamp that is not newer than the last version
1521  # TODO: the image/oldimage tables should be like page/revision with an ID field
1522  if ( $lUnixtime && wfTimestamp( TS_UNIX, $timestamp ) <= $lUnixtime ) {
1523  sleep( 1 ); // fast enough re-uploads would go far in the future otherwise
1524  $timestamp = $dbw->timestamp( $lUnixtime + 1 );
1525  $this->timestamp = wfTimestamp( TS_MW, $timestamp ); // DB -> TS_MW
1526  }
1527  }
1528 
1529  $tables = [ 'image' ];
1530  $fields = [
1531  'oi_name' => 'img_name',
1532  'oi_archive_name' => $dbw->addQuotes( $oldver ),
1533  'oi_size' => 'img_size',
1534  'oi_width' => 'img_width',
1535  'oi_height' => 'img_height',
1536  'oi_bits' => 'img_bits',
1537  'oi_description_id' => 'img_description_id',
1538  'oi_timestamp' => 'img_timestamp',
1539  'oi_metadata' => 'img_metadata',
1540  'oi_media_type' => 'img_media_type',
1541  'oi_major_mime' => 'img_major_mime',
1542  'oi_minor_mime' => 'img_minor_mime',
1543  'oi_sha1' => 'img_sha1',
1544  ];
1545  $joins = [];
1546 
1547  if ( $wgActorTableSchemaMigrationStage & SCHEMA_COMPAT_WRITE_OLD ) {
1548  $fields['oi_user'] = 'img_user';
1549  $fields['oi_user_text'] = 'img_user_text';
1550  }
1551  if ( $wgActorTableSchemaMigrationStage & SCHEMA_COMPAT_WRITE_NEW ) {
1552  $fields['oi_actor'] = 'img_actor';
1553  }
1554 
1555  if (
1556  ( $wgActorTableSchemaMigrationStage & SCHEMA_COMPAT_WRITE_BOTH ) === SCHEMA_COMPAT_WRITE_BOTH
1557  ) {
1558  // Upgrade any rows that are still old-style. Otherwise an upgrade
1559  // might be missed if a deletion happens while the migration script
1560  // is running.
1561  $res = $dbw->select(
1562  [ 'image' ],
1563  [ 'img_name', 'img_user', 'img_user_text' ],
1564  [ 'img_name' => $this->getName(), 'img_actor' => 0 ],
1565  __METHOD__
1566  );
1567  foreach ( $res as $row ) {
1568  $actorId = User::newFromAnyId( $row->img_user, $row->img_user_text, null )->getActorId( $dbw );
1569  $dbw->update(
1570  'image',
1571  [ 'img_actor' => $actorId ],
1572  [ 'img_name' => $row->img_name, 'img_actor' => 0 ],
1573  __METHOD__
1574  );
1575  }
1576  }
1577 
1578  # (T36993) Note: $oldver can be empty here, if the previous
1579  # version of the file was broken. Allow registration of the new
1580  # version to continue anyway, because that's better than having
1581  # an image that's not fixable by user operations.
1582  # Collision, this is an update of a file
1583  # Insert previous contents into oldimage
1584  $dbw->insertSelect( 'oldimage', $tables, $fields,
1585  [ 'img_name' => $this->getName() ], __METHOD__, [], [], $joins );
1586 
1587  # Update the current image row
1588  $dbw->update( 'image',
1589  [
1590  'img_size' => $this->size,
1591  'img_width' => intval( $this->width ),
1592  'img_height' => intval( $this->height ),
1593  'img_bits' => $this->bits,
1594  'img_media_type' => $this->media_type,
1595  'img_major_mime' => $this->major_mime,
1596  'img_minor_mime' => $this->minor_mime,
1597  'img_timestamp' => $timestamp,
1598  'img_metadata' => $dbw->encodeBlob( $this->metadata ),
1599  'img_sha1' => $this->sha1
1600  ] + $commentFields + $actorFields,
1601  [ 'img_name' => $this->getName() ],
1602  __METHOD__
1603  );
1604  }
1605 
1606  $descTitle = $this->getTitle();
1607  $descId = $descTitle->getArticleID();
1608  $wikiPage = new WikiFilePage( $descTitle );
1609  $wikiPage->setFile( $this );
1610 
1611  // Determine log action. If reupload is done by reverting, use a special log_action.
1612  if ( $revert === true ) {
1613  $logAction = 'revert';
1614  } elseif ( $reupload === true ) {
1615  $logAction = 'overwrite';
1616  } else {
1617  $logAction = 'upload';
1618  }
1619  // Add the log entry...
1620  $logEntry = new ManualLogEntry( 'upload', $logAction );
1621  $logEntry->setTimestamp( $this->timestamp );
1622  $logEntry->setPerformer( $user );
1623  $logEntry->setComment( $comment );
1624  $logEntry->setTarget( $descTitle );
1625  // Allow people using the api to associate log entries with the upload.
1626  // Log has a timestamp, but sometimes different from upload timestamp.
1627  $logEntry->setParameters(
1628  [
1629  'img_sha1' => $this->sha1,
1630  'img_timestamp' => $timestamp,
1631  ]
1632  );
1633  // Note we keep $logId around since during new image
1634  // creation, page doesn't exist yet, so log_page = 0
1635  // but we want it to point to the page we're making,
1636  // so we later modify the log entry.
1637  // For a similar reason, we avoid making an RC entry
1638  // now and wait until the page exists.
1639  $logId = $logEntry->insert();
1640 
1641  if ( $descTitle->exists() ) {
1642  // Use own context to get the action text in content language
1643  $formatter = LogFormatter::newFromEntry( $logEntry );
1644  $formatter->setContext( RequestContext::newExtraneousContext( $descTitle ) );
1645  $editSummary = $formatter->getPlainActionText();
1646 
1647  $nullRevision = $createNullRevision === false ? null : Revision::newNullRevision(
1648  $dbw,
1649  $descId,
1650  $editSummary,
1651  false,
1652  $user
1653  );
1654  if ( $nullRevision ) {
1655  $nullRevision->insertOn( $dbw );
1656  Hooks::run(
1657  'NewRevisionFromEditComplete',
1658  [ $wikiPage, $nullRevision, $nullRevision->getParentId(), $user ]
1659  );
1660  $wikiPage->updateRevisionOn( $dbw, $nullRevision );
1661  // Associate null revision id
1662  $logEntry->setAssociatedRevId( $nullRevision->getId() );
1663  }
1664 
1665  $newPageContent = null;
1666  } else {
1667  // Make the description page and RC log entry post-commit
1668  $newPageContent = ContentHandler::makeContent( $pageText, $descTitle );
1669  }
1670 
1671  # Defer purges, page creation, and link updates in case they error out.
1672  # The most important thing is that files and the DB registry stay synced.
1673  $dbw->endAtomic( __METHOD__ );
1674  $fname = __METHOD__;
1675 
1676  # Do some cache purges after final commit so that:
1677  # a) Changes are more likely to be seen post-purge
1678  # b) They won't cause rollback of the log publish/update above
1680  new AutoCommitUpdate(
1681  $dbw,
1682  __METHOD__,
1683  function () use (
1684  $reupload, $wikiPage, $newPageContent, $comment, $user,
1685  $logEntry, $logId, $descId, $tags, $fname
1686  ) {
1687  # Update memcache after the commit
1688  $this->invalidateCache();
1689 
1690  $updateLogPage = false;
1691  if ( $newPageContent ) {
1692  # New file page; create the description page.
1693  # There's already a log entry, so don't make a second RC entry
1694  # CDN and file cache for the description page are purged by doEditContent.
1695  $status = $wikiPage->doEditContent(
1696  $newPageContent,
1697  $comment,
1699  false,
1700  $user
1701  );
1702 
1703  if ( isset( $status->value['revision'] ) ) {
1705  $rev = $status->value['revision'];
1706  // Associate new page revision id
1707  $logEntry->setAssociatedRevId( $rev->getId() );
1708  }
1709  // This relies on the resetArticleID() call in WikiPage::insertOn(),
1710  // which is triggered on $descTitle by doEditContent() above.
1711  if ( isset( $status->value['revision'] ) ) {
1713  $rev = $status->value['revision'];
1714  $updateLogPage = $rev->getPage();
1715  }
1716  } else {
1717  # Existing file page: invalidate description page cache
1718  $wikiPage->getTitle()->invalidateCache();
1719  $wikiPage->getTitle()->purgeSquid();
1720  # Allow the new file version to be patrolled from the page footer
1722  }
1723 
1724  # Update associated rev id. This should be done by $logEntry->insert() earlier,
1725  # but setAssociatedRevId() wasn't called at that point yet...
1726  $logParams = $logEntry->getParameters();
1727  $logParams['associated_rev_id'] = $logEntry->getAssociatedRevId();
1728  $update = [ 'log_params' => LogEntryBase::makeParamBlob( $logParams ) ];
1729  if ( $updateLogPage ) {
1730  # Also log page, in case where we just created it above
1731  $update['log_page'] = $updateLogPage;
1732  }
1733  $this->getRepo()->getMasterDB()->update(
1734  'logging',
1735  $update,
1736  [ 'log_id' => $logId ],
1737  $fname
1738  );
1739  $this->getRepo()->getMasterDB()->insert(
1740  'log_search',
1741  [
1742  'ls_field' => 'associated_rev_id',
1743  'ls_value' => $logEntry->getAssociatedRevId(),
1744  'ls_log_id' => $logId,
1745  ],
1746  $fname
1747  );
1748 
1749  # Add change tags, if any
1750  if ( $tags ) {
1751  $logEntry->setTags( $tags );
1752  }
1753 
1754  # Uploads can be patrolled
1755  $logEntry->setIsPatrollable( true );
1756 
1757  # Now that the log entry is up-to-date, make an RC entry.
1758  $logEntry->publish( $logId );
1759 
1760  # Run hook for other updates (typically more cache purging)
1761  Hooks::run( 'FileUpload', [ $this, $reupload, !$newPageContent ] );
1762 
1763  if ( $reupload ) {
1764  # Delete old thumbnails
1765  $this->purgeThumbnails();
1766  # Remove the old file from the CDN cache
1768  new CdnCacheUpdate( [ $this->getUrl() ] ),
1770  );
1771  } else {
1772  # Update backlink pages pointing to this title if created
1774  $this->getTitle(),
1775  'imagelinks',
1776  'upload-image',
1777  $user->getName()
1778  );
1779  }
1780 
1781  $this->prerenderThumbnails();
1782  }
1783  ),
1785  );
1786 
1787  if ( !$reupload ) {
1788  # This is a new file, so update the image count
1789  DeferredUpdates::addUpdate( SiteStatsUpdate::factory( [ 'images' => 1 ] ) );
1790  }
1791 
1792  # Invalidate cache for all pages using this file
1794  new HTMLCacheUpdate( $this->getTitle(), 'imagelinks', 'file-upload' )
1795  );
1796 
1797  return Status::newGood();
1798  }
1799 
1815  function publish( $src, $flags = 0, array $options = [] ) {
1816  return $this->publishTo( $src, $this->getRel(), $flags, $options );
1817  }
1818 
1834  function publishTo( $src, $dstRel, $flags = 0, array $options = [] ) {
1835  $srcPath = ( $src instanceof FSFile ) ? $src->getPath() : $src;
1836 
1837  $repo = $this->getRepo();
1838  if ( $repo->getReadOnlyReason() !== false ) {
1839  return $this->readOnlyFatalStatus();
1840  }
1841 
1842  $this->lock();
1843 
1844  if ( $this->isOld() ) {
1845  $archiveRel = $dstRel;
1846  $archiveName = basename( $archiveRel );
1847  } else {
1848  $archiveName = wfTimestamp( TS_MW ) . '!' . $this->getName();
1849  $archiveRel = $this->getArchiveRel( $archiveName );
1850  }
1851 
1852  if ( $repo->hasSha1Storage() ) {
1853  $sha1 = FileRepo::isVirtualUrl( $srcPath )
1854  ? $repo->getFileSha1( $srcPath )
1855  : FSFile::getSha1Base36FromPath( $srcPath );
1857  $wrapperBackend = $repo->getBackend();
1858  $dst = $wrapperBackend->getPathForSHA1( $sha1 );
1859  $status = $repo->quickImport( $src, $dst );
1860  if ( $flags & File::DELETE_SOURCE ) {
1861  unlink( $srcPath );
1862  }
1863 
1864  if ( $this->exists() ) {
1865  $status->value = $archiveName;
1866  }
1867  } else {
1868  $flags = $flags & File::DELETE_SOURCE ? LocalRepo::DELETE_SOURCE : 0;
1869  $status = $repo->publish( $srcPath, $dstRel, $archiveRel, $flags, $options );
1870 
1871  if ( $status->value == 'new' ) {
1872  $status->value = '';
1873  } else {
1874  $status->value = $archiveName;
1875  }
1876  }
1877 
1878  $this->unlock();
1879  return $status;
1880  }
1881 
1899  function move( $target ) {
1900  if ( $this->getRepo()->getReadOnlyReason() !== false ) {
1901  return $this->readOnlyFatalStatus();
1902  }
1903 
1904  wfDebugLog( 'imagemove', "Got request to move {$this->name} to " . $target->getText() );
1905  $batch = new LocalFileMoveBatch( $this, $target );
1906 
1907  $this->lock();
1908  $batch->addCurrent();
1909  $archiveNames = $batch->addOlds();
1910  $status = $batch->execute();
1911  $this->unlock();
1912 
1913  wfDebugLog( 'imagemove', "Finished moving {$this->name}" );
1914 
1915  // Purge the source and target files...
1916  $oldTitleFile = wfLocalFile( $this->title );
1917  $newTitleFile = wfLocalFile( $target );
1918  // To avoid slow purges in the transaction, move them outside...
1920  new AutoCommitUpdate(
1921  $this->getRepo()->getMasterDB(),
1922  __METHOD__,
1923  function () use ( $oldTitleFile, $newTitleFile, $archiveNames ) {
1924  $oldTitleFile->purgeEverything();
1925  foreach ( $archiveNames as $archiveName ) {
1926  $oldTitleFile->purgeOldThumbnails( $archiveName );
1927  }
1928  $newTitleFile->purgeEverything();
1929  }
1930  ),
1932  );
1933 
1934  if ( $status->isOK() ) {
1935  // Now switch the object
1936  $this->title = $target;
1937  // Force regeneration of the name and hashpath
1938  unset( $this->name );
1939  unset( $this->hashPath );
1940  }
1941 
1942  return $status;
1943  }
1944 
1958  function delete( $reason, $suppress = false, $user = null ) {
1959  if ( $this->getRepo()->getReadOnlyReason() !== false ) {
1960  return $this->readOnlyFatalStatus();
1961  }
1962 
1963  $batch = new LocalFileDeleteBatch( $this, $reason, $suppress, $user );
1964 
1965  $this->lock();
1966  $batch->addCurrent();
1967  // Get old version relative paths
1968  $archiveNames = $batch->addOlds();
1969  $status = $batch->execute();
1970  $this->unlock();
1971 
1972  if ( $status->isOK() ) {
1973  DeferredUpdates::addUpdate( SiteStatsUpdate::factory( [ 'images' => -1 ] ) );
1974  }
1975 
1976  // To avoid slow purges in the transaction, move them outside...
1978  new AutoCommitUpdate(
1979  $this->getRepo()->getMasterDB(),
1980  __METHOD__,
1981  function () use ( $archiveNames ) {
1982  $this->purgeEverything();
1983  foreach ( $archiveNames as $archiveName ) {
1984  $this->purgeOldThumbnails( $archiveName );
1985  }
1986  }
1987  ),
1989  );
1990 
1991  // Purge the CDN
1992  $purgeUrls = [];
1993  foreach ( $archiveNames as $archiveName ) {
1994  $purgeUrls[] = $this->getArchiveUrl( $archiveName );
1995  }
1997 
1998  return $status;
1999  }
2000 
2016  function deleteOld( $archiveName, $reason, $suppress = false, $user = null ) {
2017  if ( $this->getRepo()->getReadOnlyReason() !== false ) {
2018  return $this->readOnlyFatalStatus();
2019  }
2020 
2021  $batch = new LocalFileDeleteBatch( $this, $reason, $suppress, $user );
2022 
2023  $this->lock();
2024  $batch->addOld( $archiveName );
2025  $status = $batch->execute();
2026  $this->unlock();
2027 
2028  $this->purgeOldThumbnails( $archiveName );
2029  if ( $status->isOK() ) {
2030  $this->purgeDescription();
2031  }
2032 
2034  new CdnCacheUpdate( [ $this->getArchiveUrl( $archiveName ) ] ),
2036  );
2037 
2038  return $status;
2039  }
2040 
2052  function restore( $versions = [], $unsuppress = false ) {
2053  if ( $this->getRepo()->getReadOnlyReason() !== false ) {
2054  return $this->readOnlyFatalStatus();
2055  }
2056 
2057  $batch = new LocalFileRestoreBatch( $this, $unsuppress );
2058 
2059  $this->lock();
2060  if ( !$versions ) {
2061  $batch->addAll();
2062  } else {
2063  $batch->addIds( $versions );
2064  }
2065  $status = $batch->execute();
2066  if ( $status->isGood() ) {
2067  $cleanupStatus = $batch->cleanup();
2068  $cleanupStatus->successCount = 0;
2069  $cleanupStatus->failCount = 0;
2070  $status->merge( $cleanupStatus );
2071  }
2072 
2073  $this->unlock();
2074  return $status;
2075  }
2076 
2086  function getDescriptionUrl() {
2087  return $this->title->getLocalURL();
2088  }
2089 
2099  $store = MediaWikiServices::getInstance()->getRevisionStore();
2100  $revision = $store->getRevisionByTitle( $this->title, 0, Revision::READ_NORMAL );
2101  if ( !$revision ) {
2102  return false;
2103  }
2104 
2105  $renderer = MediaWikiServices::getInstance()->getRevisionRenderer();
2106  $rendered = $renderer->getRenderedRevision( $revision, new ParserOptions( null, $lang ) );
2107 
2108  if ( !$rendered ) {
2109  // audience check failed
2110  return false;
2111  }
2112 
2113  $pout = $rendered->getRevisionParserOutput();
2114  return $pout->getText();
2115  }
2116 
2122  function getDescription( $audience = self::FOR_PUBLIC, User $user = null ) {
2123  $this->load();
2124  if ( $audience == self::FOR_PUBLIC && $this->isDeleted( self::DELETED_COMMENT ) ) {
2125  return '';
2126  } elseif ( $audience == self::FOR_THIS_USER
2127  && !$this->userCan( self::DELETED_COMMENT, $user )
2128  ) {
2129  return '';
2130  } else {
2131  return $this->description;
2132  }
2133  }
2134 
2138  function getTimestamp() {
2139  $this->load();
2140 
2141  return $this->timestamp;
2142  }
2143 
2147  public function getDescriptionTouched() {
2148  // The DB lookup might return false, e.g. if the file was just deleted, or the shared DB repo
2149  // itself gets it from elsewhere. To avoid repeating the DB lookups in such a case, we
2150  // need to differentiate between null (uninitialized) and false (failed to load).
2151  if ( $this->descriptionTouched === null ) {
2152  $cond = [
2153  'page_namespace' => $this->title->getNamespace(),
2154  'page_title' => $this->title->getDBkey()
2155  ];
2156  $touched = $this->repo->getReplicaDB()->selectField( 'page', 'page_touched', $cond, __METHOD__ );
2157  $this->descriptionTouched = $touched ? wfTimestamp( TS_MW, $touched ) : false;
2158  }
2159 
2161  }
2162 
2166  function getSha1() {
2167  $this->load();
2168  // Initialise now if necessary
2169  if ( $this->sha1 == '' && $this->fileExists ) {
2170  $this->lock();
2171 
2172  $this->sha1 = $this->repo->getFileSha1( $this->getPath() );
2173  if ( !wfReadOnly() && strval( $this->sha1 ) != '' ) {
2174  $dbw = $this->repo->getMasterDB();
2175  $dbw->update( 'image',
2176  [ 'img_sha1' => $this->sha1 ],
2177  [ 'img_name' => $this->getName() ],
2178  __METHOD__ );
2179  $this->invalidateCache();
2180  }
2181 
2182  $this->unlock();
2183  }
2184 
2185  return $this->sha1;
2186  }
2187 
2191  function isCacheable() {
2192  $this->load();
2193 
2194  // If extra data (metadata) was not loaded then it must have been large
2195  return $this->extraDataLoaded
2196  && strlen( serialize( $this->metadata ) ) <= self::CACHE_FIELD_MAX_LEN;
2197  }
2198 
2203  public function acquireFileLock() {
2204  return Status::wrap( $this->getRepo()->getBackend()->lockFiles(
2205  [ $this->getPath() ], LockManager::LOCK_EX, 10
2206  ) );
2207  }
2208 
2213  public function releaseFileLock() {
2214  return Status::wrap( $this->getRepo()->getBackend()->unlockFiles(
2215  [ $this->getPath() ], LockManager::LOCK_EX
2216  ) );
2217  }
2218 
2228  public function lock() {
2229  if ( !$this->locked ) {
2230  $logger = LoggerFactory::getInstance( 'LocalFile' );
2231 
2232  $dbw = $this->repo->getMasterDB();
2233  $makesTransaction = !$dbw->trxLevel();
2234  $dbw->startAtomic( self::ATOMIC_SECTION_LOCK );
2235  // T56736: use simple lock to handle when the file does not exist.
2236  // SELECT FOR UPDATE prevents changes, not other SELECTs with FOR UPDATE.
2237  // Also, that would cause contention on INSERT of similarly named rows.
2238  $status = $this->acquireFileLock(); // represents all versions of the file
2239  if ( !$status->isGood() ) {
2240  $dbw->endAtomic( self::ATOMIC_SECTION_LOCK );
2241  $logger->warning( "Failed to lock '{file}'", [ 'file' => $this->name ] );
2242 
2243  throw new LocalFileLockError( $status );
2244  }
2245  // Release the lock *after* commit to avoid row-level contention.
2246  // Make sure it triggers on rollback() as well as commit() (T132921).
2247  $dbw->onTransactionResolution(
2248  function () use ( $logger ) {
2249  $status = $this->releaseFileLock();
2250  if ( !$status->isGood() ) {
2251  $logger->error( "Failed to unlock '{file}'", [ 'file' => $this->name ] );
2252  }
2253  },
2254  __METHOD__
2255  );
2256  // Callers might care if the SELECT snapshot is safely fresh
2257  $this->lockedOwnTrx = $makesTransaction;
2258  }
2259 
2260  $this->locked++;
2261 
2262  return $this->lockedOwnTrx;
2263  }
2264 
2273  public function unlock() {
2274  if ( $this->locked ) {
2275  --$this->locked;
2276  if ( !$this->locked ) {
2277  $dbw = $this->repo->getMasterDB();
2278  $dbw->endAtomic( self::ATOMIC_SECTION_LOCK );
2279  $this->lockedOwnTrx = false;
2280  }
2281  }
2282  }
2283 
2287  protected function readOnlyFatalStatus() {
2288  return $this->getRepo()->newFatal( 'filereadonlyerror', $this->getName(),
2289  $this->getRepo()->getName(), $this->getRepo()->getReadOnlyReason() );
2290  }
2291 
2295  function __destruct() {
2296  $this->unlock();
2297  }
2298 }
const SCHEMA_COMPAT_WRITE_OLD
Definition: Defines.php:284
The wiki should then use memcached to cache various data To use multiple just add more items to the array To increase the weight of a make its entry a array("192.168.0.1:11211", 2))
exists()
canRender inherited
Definition: LocalFile.php:976
User $user
Uploader.
Definition: LocalFile.php:106
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:384
getCacheFields( $prefix='img_')
Returns the list of object properties that are included as-is in the cache.
Definition: LocalFile.php:412
MediaHandler $handler
Definition: File.php:115
addWatch( $title, $checkRights=self::CHECK_USER_RIGHTS)
Watch an article.
Definition: User.php:3925
string $media_type
MEDIATYPE_xxx (bitmap, drawing, audio...)
Definition: LocalFile.php:64
bool $extraDataLoaded
Whether or not lazy-loaded data has been loaded from the database.
Definition: LocalFile.php:82
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:2281
const VERSION
Definition: LocalFile.php:47
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:555
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:100
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:1669
purgeMetadataCache()
Refresh metadata in memcached, but don&#39;t touch thumbnails or CDN.
Definition: LocalFile.php:1020
releaseFileLock()
Definition: LocalFile.php:2213
const DELETE_SOURCE
Definition: File.php:67
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:939
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:762
getThumbnails( $archiveName=false)
getTransformScript inherited
Definition: LocalFile.php:997
Helper class for file undeletion.
unlock()
Decrement the lock reference count and end the atomic section if it reaches zero. ...
Definition: LocalFile.php:2273
string $major_mime
Major MIME type.
Definition: LocalFile.php:97
string $sha1
SHA-1 base 36 content hash.
Definition: LocalFile.php:76
$source
$value
const SCHEMA_COMPAT_READ_NEW
Definition: Defines.php:287
isMissing()
splitMime inherited
Definition: LocalFile.php:804
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:2271
move( $target)
getLinksTo inherited
Definition: LocalFile.php:1899
getName()
Return the name of this file.
Definition: File.php:298
string $name
The name of a file from its title object.
Definition: File.php:124
getRepo()
Returns the repository.
Definition: File.php:1866
purgeThumbnails( $options=[])
Delete cached transformed files for the current version only.
Definition: LocalFile.php:1073
getArchiveUrl( $suffix=false)
Get the URL of the archive directory, or a particular file if $suffix is specified.
Definition: File.php:1649
getPrefixedText()
Get the prefixed title with spaces.
Definition: Title.php:1691
quickImport( $src, $dst, $options=null)
Import a file from the local file system into the repo.
Definition: FileRepo.php:973
isOld()
Returns true if the image is an old version STUB.
Definition: File.php:1876
const METADATA_BAD
static isStoragePath( $path)
Check if a given path is a "mwstore://" path.
int $bits
Returned by getimagesize (loadFromXxx)
Definition: LocalFile.php:61
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:1177
loadFromDB( $flags=0)
Load file metadata from the DB.
Definition: LocalFile.php:449
title
wfLocalFile( $title)
Get an object referring to a locally registered file.
const ATOMIC_SECTION_LOCK
Definition: LocalFile.php:132
bool $fileExists
Does the file exist on disk? (loadFromXxx)
Definition: LocalFile.php:52
getTitle()
Return the associated title object.
Definition: File.php:327
Title string bool $title
Definition: File.php:100
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:2443
restore( $versions=[], $unsuppress=false)
Restore all or specified deleted revisions to the given file.
Definition: LocalFile.php:2052
purgeEverything()
Purge metadata and all affected pages when the file is created, deleted, or majorly updated...
Definition: File.php:1455
__destruct()
Clean up any dangling locks.
Definition: LocalFile.php:2295
getBackend()
Get the file backend instance.
Definition: FileRepo.php:215
static newFromRow( $row, $repo)
Create a LocalFile from a title Do not call this except from inside a repo class. ...
Definition: LocalFile.php:159
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:1424
$batch
Definition: linkcache.txt:23
getVirtualUrl( $suffix=false)
Get the public zone virtual URL for a current version source file.
Definition: File.php:1725
Deferrable Update for closure/callback updates that should use auto-commit mode.
getDescriptionShortUrl()
Get short description URL for a file based on the page ID.
Definition: LocalFile.php:905
nextHistoryLine()
Returns the history of this file, line by line.
Definition: LocalFile.php:1229
The User object encapsulates all of the user-specific settings (user_id, name, rights, email address, options, last login time).
Definition: User.php:48
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:1815
getPath()
Return the storage path to the file.
Definition: File.php:418
getHistory( $limit=null, $start=null, $end=null, $inc=true)
purgeDescription inherited
Definition: LocalFile.php:1173
int $historyRes
Result of the query for the file&#39;s history (nextHistoryLine)
Definition: LocalFile.php:94
bool $locked
True if the image row is locked.
Definition: LocalFile.php:121
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:225
wfReadOnly()
Check whether the wiki is in read-only mode.
string $metadata
Handler-specific metadata.
Definition: LocalFile.php:73
string $descriptionTouched
TS_MW timestamp of the last change of the file description.
Definition: LocalFile.php:112
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:580
string $repoClass
Definition: LocalFile.php:88
getThumbUrl( $suffix=false)
Get the URL of the thumbnail directory, or a particular file if $suffix is specified.
Definition: File.php:1705
File backend exception for checked exceptions (e.g.
isVectorized()
Return true if the file is vectorized.
Definition: File.php:556
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:1971
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:434
const LOAD_ALL
Definition: LocalFile.php:130
getHandler()
Get a MediaHandler instance for this file.
Definition: File.php:1379
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:300
static factory(array $deltas)
getDescriptionTouched()
Definition: LocalFile.php:2147
const LOCK_EX
Definition: LockManager.php:69
const SCHEMA_COMPAT_WRITE_NEW
Definition: Defines.php:286
getFileSha1( $virtualUrl)
Get the sha1 (base 36) of a file with a given virtual URL/storage path.
Definition: FileRepo.php:1598
getRel()
Get the path of the file relative to the public zone root.
Definition: File.php:1528
$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:2159
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:1313
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:1031
$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:200
acquireFileLock()
Definition: LocalFile.php:2203
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:1337
const CACHE_FIELD_MAX_LEN
Definition: LocalFile.php:49
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:626
static isVirtualUrl( $url)
Determine if a string is an mwrepo:// URL.
Definition: FileRepo.php:254
loadExtraFromDB()
Load lazy file metadata from the DB.
Definition: LocalFile.php:481
$cache
Definition: mcc.php:33
const EDIT_SUPPRESS_RC
Definition: Defines.php:155
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:176
maybeUpgradeRow()
Upgrade a row if it needs it.
Definition: LocalFile.php:661
readOnlyFatalStatus()
Definition: LocalFile.php:2287
Helper class for file movement.
bool $upgraded
Whether the row was upgraded on load.
Definition: LocalFile.php:115
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:124
bool $dataLoaded
Whether or not core data has been loaded from the database (loadFromXxx)
Definition: LocalFile.php:79
const NS_FILE
Definition: Defines.php:70
FileRepo LocalRepo ForeignAPIRepo bool $repo
Some member variables can be lazy-initialised using __get().
Definition: File.php:97
prerenderThumbnails()
Prerenders a configurable set of thumbnails.
Definition: LocalFile.php:1106
getMetadata()
Get handler-specific metadata.
Definition: LocalFile.php:921
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:1050
string $mime
MIME type, determined by MimeAnalyzer::guessMimeType.
Definition: LocalFile.php:67
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:288
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:1133
getDescription( $audience=self::FOR_PUBLIC, User $user=null)
Definition: LocalFile.php:2122
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:1539
getDescriptionText(Language $lang=null)
Get the HTML text of the description page This is not used by ImagePage for local files...
Definition: LocalFile.php:2098
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:1612
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()
static newFromAnyId( $userId, $userName, $actorId)
Static factory method for creation from an ID, name, and/or actor ID.
Definition: User.php:678
upgradeRow()
Fix assorted version-related problems with the image row by reloading it from the file...
Definition: LocalFile.php:707
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:2416
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:1436
deleteOld( $archiveName, $reason, $suppress=false, $user=null)
Delete an old version of the file.
Definition: LocalFile.php:2016
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:2482
const EDIT_NEW
Definition: Defines.php:152
Job for asynchronous rendering of thumbnails.
bool $upgrading
Whether the row was scheduled to upgrade on load.
Definition: LocalFile.php:118
lock()
Start an atomic DB section and lock the image for update or increments a reference counter if the loc...
Definition: LocalFile.php:2228
string $url
The URL corresponding to one of the four basic zones.
Definition: File.php:118
int $deleted
Bitfield akin to rev_deleted.
Definition: LocalFile.php:85
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:506
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:349
bool $missing
True if file is not present in file system.
Definition: LocalFile.php:127
const METADATA_COMPATIBLE
getDescriptionUrl()
isMultipage inherited
Definition: LocalFile.php:2086
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:46
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:103
getHeight( $page=1)
Return the height of the image.
Definition: LocalFile.php:851
int $height
Image height.
Definition: LocalFile.php:58
loadFromFile()
Load metadata from the file itself.
Definition: LocalFile.php:401
string $description
Description of current revision of the file.
Definition: LocalFile.php:109
publishTo( $src, $dstRel, $flags=0, array $options=[])
Move or copy a file to a specified location.
Definition: LocalFile.php:1834
static getQueryInfo(array $options=[])
Return the tables, fields, and join conditions to be selected to create a new localfile object...
Definition: LocalFile.php:244
Implements some public methods and some protected utility functions which are required by multiple ch...
Definition: File.php:52
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:960
loadFromCache()
Try to load file metadata from memcached, falling back to the database.
Definition: LocalFile.php:316
getMutableCacheKeys(WANObjectCache $cache)
Definition: LocalFile.php:309
int $width
Image width.
Definition: LocalFile.php:55
getMimeType()
Returns the MIME type of the file.
Definition: LocalFile.php:949
resetHistory()
Reset the history pointer to the first element of the history.
Definition: LocalFile.php:1272
static newNullRevision( $dbw, $pageId, $summary, $minor, $user=null)
Create a new null-revision for insertion into a page&#39;s history.
Definition: Revision.php:1198
int $size
Size in bytes (loadFromXxx)
Definition: LocalFile.php:70
hasSha1Storage()
Returns whether or not storage is SHA-1 based.
Definition: FileRepo.php:1925
isDeleted( $field)
Is this file a "deleted" file in a private archive? STUB.
Definition: File.php:1887
getWidth( $page=1)
Return the width of the image.
Definition: LocalFile.php:819
load( $flags=0)
Load file metadata from cache or DB, unless already loaded.
Definition: LocalFile.php:643
purgeDescription()
Purge the file description page, but don&#39;t go after pages using the file.
Definition: File.php:1443
getThumbPath( $suffix=false)
Get the path of the thumbnail directory, or a particular file if $suffix is specified.
Definition: File.php:1625
$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:146
getUser( $type='text')
Returns user who uploaded the file.
Definition: LocalFile.php:884
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:91
__construct( $title, $repo)
Do not call this except from inside a repo class.
Definition: LocalFile.php:282
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:1402
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