MediaWiki  1.30.1
LocalFile.php
Go to the documentation of this file.
1 <?php
27 
45 class LocalFile extends File {
46  const VERSION = 10; // cache version
47 
48  const CACHE_FIELD_MAX_LEN = 1000;
49 
51  protected $fileExists;
52 
54  protected $width;
55 
57  protected $height;
58 
60  protected $bits;
61 
63  protected $media_type;
64 
66  protected $mime;
67 
69  protected $size;
70 
72  protected $metadata;
73 
75  protected $sha1;
76 
78  protected $dataLoaded;
79 
81  protected $extraDataLoaded;
82 
84  protected $deleted;
85 
87  protected $repoClass = 'LocalRepo';
88 
90  private $historyLine;
91 
93  private $historyRes;
94 
96  private $major_mime;
97 
99  private $minor_mime;
100 
102  private $timestamp;
103 
105  private $user;
106 
108  private $user_text;
109 
111  private $description;
112 
115 
117  private $upgraded;
118 
120  private $upgrading;
121 
123  private $locked;
124 
126  private $lockedOwnTrx;
127 
129  private $missing;
130 
131  // @note: higher than IDBAccessObject constants
132  const LOAD_ALL = 16; // integer; load all the lazy fields too (like metadata)
133 
134  const ATOMIC_SECTION_LOCK = 'LocalFile::lockingTransaction';
135 
148  static function newFromTitle( $title, $repo, $unused = null ) {
149  return new self( $title, $repo );
150  }
151 
161  static function newFromRow( $row, $repo ) {
162  $title = Title::makeTitle( NS_FILE, $row->img_name );
163  $file = new self( $title, $repo );
164  $file->loadFromRow( $row );
165 
166  return $file;
167  }
168 
178  static function newFromKey( $sha1, $repo, $timestamp = false ) {
179  $dbr = $repo->getReplicaDB();
180 
181  $conds = [ 'img_sha1' => $sha1 ];
182  if ( $timestamp ) {
183  $conds['img_timestamp'] = $dbr->timestamp( $timestamp );
184  }
185 
186  $row = $dbr->selectRow( 'image', self::selectFields(), $conds, __METHOD__ );
187  if ( $row ) {
188  return self::newFromRow( $row, $repo );
189  } else {
190  return false;
191  }
192  }
193 
200  static function selectFields() {
201  return [
202  'img_name',
203  'img_size',
204  'img_width',
205  'img_height',
206  'img_metadata',
207  'img_bits',
208  'img_media_type',
209  'img_major_mime',
210  'img_minor_mime',
211  'img_user',
212  'img_user_text',
213  'img_timestamp',
214  'img_sha1',
215  ] + CommentStore::newKey( 'img_description' )->getFields();
216  }
217 
223  function __construct( $title, $repo ) {
224  parent::__construct( $title, $repo );
225 
226  $this->metadata = '';
227  $this->historyLine = 0;
228  $this->historyRes = null;
229  $this->dataLoaded = false;
230  $this->extraDataLoaded = false;
231 
232  $this->assertRepoDefined();
233  $this->assertTitleDefined();
234  }
235 
241  function getCacheKey() {
242  return $this->repo->getSharedCacheKey( 'file', sha1( $this->getName() ) );
243  }
244 
251  return [ $this->getCacheKey() ];
252  }
253 
257  private function loadFromCache() {
258  $this->dataLoaded = false;
259  $this->extraDataLoaded = false;
260 
261  $key = $this->getCacheKey();
262  if ( !$key ) {
263  $this->loadFromDB( self::READ_NORMAL );
264 
265  return;
266  }
267 
269  $cachedValues = $cache->getWithSetCallback(
270  $key,
271  $cache::TTL_WEEK,
272  function ( $oldValue, &$ttl, array &$setOpts ) use ( $cache ) {
273  $setOpts += Database::getCacheSetOptions( $this->repo->getReplicaDB() );
274 
275  $this->loadFromDB( self::READ_NORMAL );
276 
277  $fields = $this->getCacheFields( '' );
278  $cacheVal['fileExists'] = $this->fileExists;
279  if ( $this->fileExists ) {
280  foreach ( $fields as $field ) {
281  $cacheVal[$field] = $this->$field;
282  }
283  }
284  // Strip off excessive entries from the subset of fields that can become large.
285  // If the cache value gets to large it will not fit in memcached and nothing will
286  // get cached at all, causing master queries for any file access.
287  foreach ( $this->getLazyCacheFields( '' ) as $field ) {
288  if ( isset( $cacheVal[$field] )
289  && strlen( $cacheVal[$field] ) > 100 * 1024
290  ) {
291  unset( $cacheVal[$field] ); // don't let the value get too big
292  }
293  }
294 
295  if ( $this->fileExists ) {
296  $ttl = $cache->adaptiveTTL( wfTimestamp( TS_UNIX, $this->timestamp ), $ttl );
297  } else {
298  $ttl = $cache::TTL_DAY;
299  }
300 
301  return $cacheVal;
302  },
303  [ 'version' => self::VERSION ]
304  );
305 
306  $this->fileExists = $cachedValues['fileExists'];
307  if ( $this->fileExists ) {
308  $this->setProps( $cachedValues );
309  }
310 
311  $this->dataLoaded = true;
312  $this->extraDataLoaded = true;
313  foreach ( $this->getLazyCacheFields( '' ) as $field ) {
314  $this->extraDataLoaded = $this->extraDataLoaded && isset( $cachedValues[$field] );
315  }
316  }
317 
321  public function invalidateCache() {
322  $key = $this->getCacheKey();
323  if ( !$key ) {
324  return;
325  }
326 
327  $this->repo->getMasterDB()->onTransactionPreCommitOrIdle(
328  function () use ( $key ) {
329  ObjectCache::getMainWANInstance()->delete( $key );
330  },
331  __METHOD__
332  );
333  }
334 
338  function loadFromFile() {
339  $props = $this->repo->getFileProps( $this->getVirtualUrl() );
340  $this->setProps( $props );
341  }
342 
347  function getCacheFields( $prefix = 'img_' ) {
348  static $fields = [ 'size', 'width', 'height', 'bits', 'media_type',
349  'major_mime', 'minor_mime', 'metadata', 'timestamp', 'sha1', 'user',
350  'user_text' ];
351  static $results = [];
352 
353  if ( $prefix == '' ) {
354  return array_merge( $fields, [ 'description' ] );
355  }
356  if ( !isset( $results[$prefix] ) ) {
357  $prefixedFields = [];
358  foreach ( $fields as $field ) {
359  $prefixedFields[] = $prefix . $field;
360  }
361  $prefixedFields += CommentStore::newKey( "{$prefix}description" )->getFields();
362  $results[$prefix] = $prefixedFields;
363  }
364 
365  return $results[$prefix];
366  }
367 
372  function getLazyCacheFields( $prefix = 'img_' ) {
373  static $fields = [ 'metadata' ];
374  static $results = [];
375 
376  if ( $prefix == '' ) {
377  return $fields;
378  }
379 
380  if ( !isset( $results[$prefix] ) ) {
381  $prefixedFields = [];
382  foreach ( $fields as $field ) {
383  $prefixedFields[] = $prefix . $field;
384  }
385  $results[$prefix] = $prefixedFields;
386  }
387 
388  return $results[$prefix];
389  }
390 
395  function loadFromDB( $flags = 0 ) {
396  $fname = static::class . '::' . __FUNCTION__;
397 
398  # Unconditionally set loaded=true, we don't want the accessors constantly rechecking
399  $this->dataLoaded = true;
400  $this->extraDataLoaded = true;
401 
402  $dbr = ( $flags & self::READ_LATEST )
403  ? $this->repo->getMasterDB()
404  : $this->repo->getReplicaDB();
405 
406  $row = $dbr->selectRow( 'image', $this->getCacheFields( 'img_' ),
407  [ 'img_name' => $this->getName() ], $fname );
408 
409  if ( $row ) {
410  $this->loadFromRow( $row );
411  } else {
412  $this->fileExists = false;
413  }
414  }
415 
420  protected function loadExtraFromDB() {
421  $fname = static::class . '::' . __FUNCTION__;
422 
423  # Unconditionally set loaded=true, we don't want the accessors constantly rechecking
424  $this->extraDataLoaded = true;
425 
426  $fieldMap = $this->loadFieldsWithTimestamp( $this->repo->getReplicaDB(), $fname );
427  if ( !$fieldMap ) {
428  $fieldMap = $this->loadFieldsWithTimestamp( $this->repo->getMasterDB(), $fname );
429  }
430 
431  if ( $fieldMap ) {
432  foreach ( $fieldMap as $name => $value ) {
433  $this->$name = $value;
434  }
435  } else {
436  throw new MWException( "Could not find data for image '{$this->getName()}'." );
437  }
438  }
439 
445  private function loadFieldsWithTimestamp( $dbr, $fname ) {
446  $fieldMap = false;
447 
448  $row = $dbr->selectRow( 'image', $this->getLazyCacheFields( 'img_' ), [
449  'img_name' => $this->getName(),
450  'img_timestamp' => $dbr->timestamp( $this->getTimestamp() )
451  ], $fname );
452  if ( $row ) {
453  $fieldMap = $this->unprefixRow( $row, 'img_' );
454  } else {
455  # File may have been uploaded over in the meantime; check the old versions
456  $row = $dbr->selectRow( 'oldimage', $this->getLazyCacheFields( 'oi_' ), [
457  'oi_name' => $this->getName(),
458  'oi_timestamp' => $dbr->timestamp( $this->getTimestamp() )
459  ], $fname );
460  if ( $row ) {
461  $fieldMap = $this->unprefixRow( $row, 'oi_' );
462  }
463  }
464 
465  return $fieldMap;
466  }
467 
474  protected function unprefixRow( $row, $prefix = 'img_' ) {
475  $array = (array)$row;
476  $prefixLength = strlen( $prefix );
477 
478  // Sanity check prefix once
479  if ( substr( key( $array ), 0, $prefixLength ) !== $prefix ) {
480  throw new MWException( __METHOD__ . ': incorrect $prefix parameter' );
481  }
482 
483  $decoded = [];
484  foreach ( $array as $name => $value ) {
485  $decoded[substr( $name, $prefixLength )] = $value;
486  }
487 
488  return $decoded;
489  }
490 
499  function decodeRow( $row, $prefix = 'img_' ) {
500  $decoded = $this->unprefixRow( $row, $prefix );
501 
502  $decoded['timestamp'] = wfTimestamp( TS_MW, $decoded['timestamp'] );
503 
504  $decoded['metadata'] = $this->repo->getReplicaDB()->decodeBlob( $decoded['metadata'] );
505 
506  if ( empty( $decoded['major_mime'] ) ) {
507  $decoded['mime'] = 'unknown/unknown';
508  } else {
509  if ( !$decoded['minor_mime'] ) {
510  $decoded['minor_mime'] = 'unknown';
511  }
512  $decoded['mime'] = $decoded['major_mime'] . '/' . $decoded['minor_mime'];
513  }
514 
515  // Trim zero padding from char/binary field
516  $decoded['sha1'] = rtrim( $decoded['sha1'], "\0" );
517 
518  // Normalize some fields to integer type, per their database definition.
519  // Use unary + so that overflows will be upgraded to double instead of
520  // being trucated as with intval(). This is important to allow >2GB
521  // files on 32-bit systems.
522  foreach ( [ 'size', 'width', 'height', 'bits' ] as $field ) {
523  $decoded[$field] = +$decoded[$field];
524  }
525 
526  return $decoded;
527  }
528 
535  function loadFromRow( $row, $prefix = 'img_' ) {
536  $this->dataLoaded = true;
537  $this->extraDataLoaded = true;
538 
539  $this->description = CommentStore::newKey( "{$prefix}description" )
540  // $row is probably using getFields() from self::getCacheFields()
541  ->getCommentLegacy( wfGetDB( DB_REPLICA ), $row )->text;
542 
543  $array = $this->decodeRow( $row, $prefix );
544 
545  foreach ( $array as $name => $value ) {
546  $this->$name = $value;
547  }
548 
549  $this->fileExists = true;
550  $this->maybeUpgradeRow();
551  }
552 
557  function load( $flags = 0 ) {
558  if ( !$this->dataLoaded ) {
559  if ( $flags & self::READ_LATEST ) {
560  $this->loadFromDB( $flags );
561  } else {
562  $this->loadFromCache();
563  }
564  }
565 
566  if ( ( $flags & self::LOAD_ALL ) && !$this->extraDataLoaded ) {
567  // @note: loads on name/timestamp to reduce race condition problems
568  $this->loadExtraFromDB();
569  }
570  }
571 
575  function maybeUpgradeRow() {
577 
578  if ( wfReadOnly() || $this->upgrading ) {
579  return;
580  }
581 
582  $upgrade = false;
583  if ( is_null( $this->media_type ) || $this->mime == 'image/svg' ) {
584  $upgrade = true;
585  } else {
586  $handler = $this->getHandler();
587  if ( $handler ) {
588  $validity = $handler->isMetadataValid( $this, $this->getMetadata() );
589  if ( $validity === MediaHandler::METADATA_BAD ) {
590  $upgrade = true;
591  } elseif ( $validity === MediaHandler::METADATA_COMPATIBLE ) {
592  $upgrade = $wgUpdateCompatibleMetadata;
593  }
594  }
595  }
596 
597  if ( $upgrade ) {
598  $this->upgrading = true;
599  // Defer updates unless in auto-commit CLI mode
601  $this->upgrading = false; // avoid duplicate updates
602  try {
603  $this->upgradeRow();
604  } catch ( LocalFileLockError $e ) {
605  // let the other process handle it (or do it next time)
606  }
607  } );
608  }
609  }
610 
614  function getUpgraded() {
615  return $this->upgraded;
616  }
617 
621  function upgradeRow() {
622  $this->lock(); // begin
623 
624  $this->loadFromFile();
625 
626  # Don't destroy file info of missing files
627  if ( !$this->fileExists ) {
628  $this->unlock();
629  wfDebug( __METHOD__ . ": file does not exist, aborting\n" );
630 
631  return;
632  }
633 
634  $dbw = $this->repo->getMasterDB();
635  list( $major, $minor ) = self::splitMime( $this->mime );
636 
637  if ( wfReadOnly() ) {
638  $this->unlock();
639 
640  return;
641  }
642  wfDebug( __METHOD__ . ': upgrading ' . $this->getName() . " to the current schema\n" );
643 
644  $dbw->update( 'image',
645  [
646  'img_size' => $this->size, // sanity
647  'img_width' => $this->width,
648  'img_height' => $this->height,
649  'img_bits' => $this->bits,
650  'img_media_type' => $this->media_type,
651  'img_major_mime' => $major,
652  'img_minor_mime' => $minor,
653  'img_metadata' => $dbw->encodeBlob( $this->metadata ),
654  'img_sha1' => $this->sha1,
655  ],
656  [ 'img_name' => $this->getName() ],
657  __METHOD__
658  );
659 
660  $this->invalidateCache();
661 
662  $this->unlock(); // done
663  $this->upgraded = true; // avoid rework/retries
664  }
665 
676  function setProps( $info ) {
677  $this->dataLoaded = true;
678  $fields = $this->getCacheFields( '' );
679  $fields[] = 'fileExists';
680 
681  foreach ( $fields as $field ) {
682  if ( isset( $info[$field] ) ) {
683  $this->$field = $info[$field];
684  }
685  }
686 
687  // Fix up mime fields
688  if ( isset( $info['major_mime'] ) ) {
689  $this->mime = "{$info['major_mime']}/{$info['minor_mime']}";
690  } elseif ( isset( $info['mime'] ) ) {
691  $this->mime = $info['mime'];
692  list( $this->major_mime, $this->minor_mime ) = self::splitMime( $this->mime );
693  }
694  }
695 
707  function isMissing() {
708  if ( $this->missing === null ) {
709  list( $fileExists ) = $this->repo->fileExists( $this->getVirtualUrl() );
710  $this->missing = !$fileExists;
711  }
712 
713  return $this->missing;
714  }
715 
722  public function getWidth( $page = 1 ) {
723  $page = (int)$page;
724  if ( $page < 1 ) {
725  $page = 1;
726  }
727 
728  $this->load();
729 
730  if ( $this->isMultipage() ) {
731  $handler = $this->getHandler();
732  if ( !$handler ) {
733  return 0;
734  }
735  $dim = $handler->getPageDimensions( $this, $page );
736  if ( $dim ) {
737  return $dim['width'];
738  } else {
739  // For non-paged media, the false goes through an
740  // intval, turning failure into 0, so do same here.
741  return 0;
742  }
743  } else {
744  return $this->width;
745  }
746  }
747 
754  public function getHeight( $page = 1 ) {
755  $page = (int)$page;
756  if ( $page < 1 ) {
757  $page = 1;
758  }
759 
760  $this->load();
761 
762  if ( $this->isMultipage() ) {
763  $handler = $this->getHandler();
764  if ( !$handler ) {
765  return 0;
766  }
767  $dim = $handler->getPageDimensions( $this, $page );
768  if ( $dim ) {
769  return $dim['height'];
770  } else {
771  // For non-paged media, the false goes through an
772  // intval, turning failure into 0, so do same here.
773  return 0;
774  }
775  } else {
776  return $this->height;
777  }
778  }
779 
786  function getUser( $type = 'text' ) {
787  $this->load();
788 
789  if ( $type == 'text' ) {
790  return $this->user_text;
791  } else { // id
792  return (int)$this->user;
793  }
794  }
795 
803  public function getDescriptionShortUrl() {
804  $pageId = $this->title->getArticleID();
805 
806  if ( $pageId !== null ) {
807  $url = $this->repo->makeUrl( [ 'curid' => $pageId ] );
808  if ( $url !== false ) {
809  return $url;
810  }
811  }
812  return null;
813  }
814 
819  function getMetadata() {
820  $this->load( self::LOAD_ALL ); // large metadata is loaded in another step
821  return $this->metadata;
822  }
823 
827  function getBitDepth() {
828  $this->load();
829 
830  return (int)$this->bits;
831  }
832 
837  public function getSize() {
838  $this->load();
839 
840  return $this->size;
841  }
842 
847  function getMimeType() {
848  $this->load();
849 
850  return $this->mime;
851  }
852 
858  function getMediaType() {
859  $this->load();
860 
861  return $this->media_type;
862  }
863 
874  public function exists() {
875  $this->load();
876 
877  return $this->fileExists;
878  }
879 
895  function getThumbnails( $archiveName = false ) {
896  if ( $archiveName ) {
897  $dir = $this->getArchiveThumbPath( $archiveName );
898  } else {
899  $dir = $this->getThumbPath();
900  }
901 
902  $backend = $this->repo->getBackend();
903  $files = [ $dir ];
904  try {
905  $iterator = $backend->getFileList( [ 'dir' => $dir ] );
906  foreach ( $iterator as $file ) {
907  $files[] = $file;
908  }
909  } catch ( FileBackendError $e ) {
910  } // suppress (T56674)
911 
912  return $files;
913  }
914 
918  function purgeMetadataCache() {
919  $this->invalidateCache();
920  }
921 
929  function purgeCache( $options = [] ) {
930  // Refresh metadata cache
931  $this->purgeMetadataCache();
932 
933  // Delete thumbnails
934  $this->purgeThumbnails( $options );
935 
936  // Purge CDN cache for this file
938  new CdnCacheUpdate( [ $this->getUrl() ] ),
940  );
941  }
942 
947  function purgeOldThumbnails( $archiveName ) {
948  // Get a list of old thumbnails and URLs
949  $files = $this->getThumbnails( $archiveName );
950 
951  // Purge any custom thumbnail caches
952  Hooks::run( 'LocalFilePurgeThumbnails', [ $this, $archiveName ] );
953 
954  // Delete thumbnails
955  $dir = array_shift( $files );
956  $this->purgeThumbList( $dir, $files );
957 
958  // Purge the CDN
959  $urls = [];
960  foreach ( $files as $file ) {
961  $urls[] = $this->getArchiveThumbUrl( $archiveName, $file );
962  }
964  }
965 
970  public function purgeThumbnails( $options = [] ) {
971  $files = $this->getThumbnails();
972  // Always purge all files from CDN regardless of handler filters
973  $urls = [];
974  foreach ( $files as $file ) {
975  $urls[] = $this->getThumbUrl( $file );
976  }
977  array_shift( $urls ); // don't purge directory
978 
979  // Give media handler a chance to filter the file purge list
980  if ( !empty( $options['forThumbRefresh'] ) ) {
981  $handler = $this->getHandler();
982  if ( $handler ) {
984  }
985  }
986 
987  // Purge any custom thumbnail caches
988  Hooks::run( 'LocalFilePurgeThumbnails', [ $this, false ] );
989 
990  // Delete thumbnails
991  $dir = array_shift( $files );
992  $this->purgeThumbList( $dir, $files );
993 
994  // Purge the CDN
996  }
997 
1003  public function prerenderThumbnails() {
1005 
1006  $jobs = [];
1007 
1008  $sizes = $wgUploadThumbnailRenderMap;
1009  rsort( $sizes );
1010 
1011  foreach ( $sizes as $size ) {
1012  if ( $this->isVectorized() || $this->getWidth() > $size ) {
1013  $jobs[] = new ThumbnailRenderJob(
1014  $this->getTitle(),
1015  [ 'transformParams' => [ 'width' => $size ] ]
1016  );
1017  }
1018  }
1019 
1020  if ( $jobs ) {
1021  JobQueueGroup::singleton()->lazyPush( $jobs );
1022  }
1023  }
1024 
1030  protected function purgeThumbList( $dir, $files ) {
1031  $fileListDebug = strtr(
1032  var_export( $files, true ),
1033  [ "\n" => '' ]
1034  );
1035  wfDebug( __METHOD__ . ": $fileListDebug\n" );
1036 
1037  $purgeList = [];
1038  foreach ( $files as $file ) {
1039  if ( $this->repo->supportsSha1URLs() ) {
1040  $reference = $this->getSha1();
1041  } else {
1042  $reference = $this->getName();
1043  }
1044 
1045  # Check that the reference (filename or sha1) is part of the thumb name
1046  # This is a basic sanity check to avoid erasing unrelated directories
1047  if ( strpos( $file, $reference ) !== false
1048  || strpos( $file, "-thumbnail" ) !== false // "short" thumb name
1049  ) {
1050  $purgeList[] = "{$dir}/{$file}";
1051  }
1052  }
1053 
1054  # Delete the thumbnails
1055  $this->repo->quickPurgeBatch( $purgeList );
1056  # Clear out the thumbnail directory if empty
1057  $this->repo->quickCleanDir( $dir );
1058  }
1059 
1070  function getHistory( $limit = null, $start = null, $end = null, $inc = true ) {
1071  $dbr = $this->repo->getReplicaDB();
1072  $tables = [ 'oldimage' ];
1073  $fields = OldLocalFile::selectFields();
1074  $conds = $opts = $join_conds = [];
1075  $eq = $inc ? '=' : '';
1076  $conds[] = "oi_name = " . $dbr->addQuotes( $this->title->getDBkey() );
1077 
1078  if ( $start ) {
1079  $conds[] = "oi_timestamp <$eq " . $dbr->addQuotes( $dbr->timestamp( $start ) );
1080  }
1081 
1082  if ( $end ) {
1083  $conds[] = "oi_timestamp >$eq " . $dbr->addQuotes( $dbr->timestamp( $end ) );
1084  }
1085 
1086  if ( $limit ) {
1087  $opts['LIMIT'] = $limit;
1088  }
1089 
1090  // Search backwards for time > x queries
1091  $order = ( !$start && $end !== null ) ? 'ASC' : 'DESC';
1092  $opts['ORDER BY'] = "oi_timestamp $order";
1093  $opts['USE INDEX'] = [ 'oldimage' => 'oi_name_timestamp' ];
1094 
1095  // Avoid PHP 7.1 warning from passing $this by reference
1096  $localFile = $this;
1097  Hooks::run( 'LocalFile::getHistory', [ &$localFile, &$tables, &$fields,
1098  &$conds, &$opts, &$join_conds ] );
1099 
1100  $res = $dbr->select( $tables, $fields, $conds, __METHOD__, $opts, $join_conds );
1101  $r = [];
1102 
1103  foreach ( $res as $row ) {
1104  $r[] = $this->repo->newFileFromRow( $row );
1105  }
1106 
1107  if ( $order == 'ASC' ) {
1108  $r = array_reverse( $r ); // make sure it ends up descending
1109  }
1110 
1111  return $r;
1112  }
1113 
1123  public function nextHistoryLine() {
1124  # Polymorphic function name to distinguish foreign and local fetches
1125  $fname = static::class . '::' . __FUNCTION__;
1126 
1127  $dbr = $this->repo->getReplicaDB();
1128 
1129  if ( $this->historyLine == 0 ) { // called for the first time, return line from cur
1130  $this->historyRes = $dbr->select( 'image',
1131  self::selectFields() + [
1132  'oi_archive_name' => $dbr->addQuotes( '' ),
1133  'oi_deleted' => 0,
1134  ],
1135  [ 'img_name' => $this->title->getDBkey() ],
1136  $fname
1137  );
1138 
1139  if ( 0 == $dbr->numRows( $this->historyRes ) ) {
1140  $this->historyRes = null;
1141 
1142  return false;
1143  }
1144  } elseif ( $this->historyLine == 1 ) {
1145  $this->historyRes = $dbr->select(
1146  'oldimage',
1148  [ 'oi_name' => $this->title->getDBkey() ],
1149  $fname,
1150  [ 'ORDER BY' => 'oi_timestamp DESC' ]
1151  );
1152  }
1153  $this->historyLine++;
1154 
1155  return $dbr->fetchObject( $this->historyRes );
1156  }
1157 
1161  public function resetHistory() {
1162  $this->historyLine = 0;
1163 
1164  if ( !is_null( $this->historyRes ) ) {
1165  $this->historyRes = null;
1166  }
1167  }
1168 
1199  function upload( $src, $comment, $pageText, $flags = 0, $props = false,
1200  $timestamp = false, $user = null, $tags = []
1201  ) {
1202  if ( $this->getRepo()->getReadOnlyReason() !== false ) {
1203  return $this->readOnlyFatalStatus();
1204  }
1205 
1206  $srcPath = ( $src instanceof FSFile ) ? $src->getPath() : $src;
1207  if ( !$props ) {
1208  if ( $this->repo->isVirtualUrl( $srcPath )
1209  || FileBackend::isStoragePath( $srcPath )
1210  ) {
1211  $props = $this->repo->getFileProps( $srcPath );
1212  } else {
1213  $mwProps = new MWFileProps( MimeMagic::singleton() );
1214  $props = $mwProps->getPropsFromPath( $srcPath, true );
1215  }
1216  }
1217 
1218  $options = [];
1219  $handler = MediaHandler::getHandler( $props['mime'] );
1220  if ( $handler ) {
1221  $metadata = MediaWiki\quietCall( 'unserialize', $props['metadata'] );
1222 
1223  if ( !is_array( $metadata ) ) {
1224  $metadata = [];
1225  }
1226 
1227  $options['headers'] = $handler->getContentHeaders( $metadata );
1228  } else {
1229  $options['headers'] = [];
1230  }
1231 
1232  // Trim spaces on user supplied text
1233  $comment = trim( $comment );
1234 
1235  $this->lock(); // begin
1236  $status = $this->publish( $src, $flags, $options );
1237 
1238  if ( $status->successCount >= 2 ) {
1239  // There will be a copy+(one of move,copy,store).
1240  // The first succeeding does not commit us to updating the DB
1241  // since it simply copied the current version to a timestamped file name.
1242  // It is only *preferable* to avoid leaving such files orphaned.
1243  // Once the second operation goes through, then the current version was
1244  // updated and we must therefore update the DB too.
1245  $oldver = $status->value;
1246  if ( !$this->recordUpload2( $oldver, $comment, $pageText, $props, $timestamp, $user, $tags ) ) {
1247  $status->fatal( 'filenotfound', $srcPath );
1248  }
1249  }
1250 
1251  $this->unlock(); // done
1252 
1253  return $status;
1254  }
1255 
1268  function recordUpload( $oldver, $desc, $license = '', $copyStatus = '', $source = '',
1269  $watch = false, $timestamp = false, User $user = null ) {
1270  if ( !$user ) {
1271  global $wgUser;
1272  $user = $wgUser;
1273  }
1274 
1275  $pageText = SpecialUpload::getInitialPageText( $desc, $license, $copyStatus, $source );
1276 
1277  if ( !$this->recordUpload2( $oldver, $desc, $pageText, false, $timestamp, $user ) ) {
1278  return false;
1279  }
1280 
1281  if ( $watch ) {
1282  $user->addWatch( $this->getTitle() );
1283  }
1284 
1285  return true;
1286  }
1287 
1299  function recordUpload2(
1300  $oldver, $comment, $pageText, $props = false, $timestamp = false, $user = null, $tags = []
1301  ) {
1303 
1304  if ( is_null( $user ) ) {
1305  global $wgUser;
1306  $user = $wgUser;
1307  }
1308 
1309  $dbw = $this->repo->getMasterDB();
1310 
1311  # Imports or such might force a certain timestamp; otherwise we generate
1312  # it and can fudge it slightly to keep (name,timestamp) unique on re-upload.
1313  if ( $timestamp === false ) {
1314  $timestamp = $dbw->timestamp();
1315  $allowTimeKludge = true;
1316  } else {
1317  $allowTimeKludge = false;
1318  }
1319 
1320  $props = $props ?: $this->repo->getFileProps( $this->getVirtualUrl() );
1321  $props['description'] = $comment;
1322  $props['user'] = $user->getId();
1323  $props['user_text'] = $user->getName();
1324  $props['timestamp'] = wfTimestamp( TS_MW, $timestamp ); // DB -> TS_MW
1325  $this->setProps( $props );
1326 
1327  # Fail now if the file isn't there
1328  if ( !$this->fileExists ) {
1329  wfDebug( __METHOD__ . ": File " . $this->getRel() . " went missing!\n" );
1330 
1331  return false;
1332  }
1333 
1334  $dbw->startAtomic( __METHOD__ );
1335 
1336  # Test to see if the row exists using INSERT IGNORE
1337  # This avoids race conditions by locking the row until the commit, and also
1338  # doesn't deadlock. SELECT FOR UPDATE causes a deadlock for every race condition.
1339  $commentStore = new CommentStore( 'img_description' );
1340  list( $commentFields, $commentCallback ) =
1341  $commentStore->insertWithTempTable( $dbw, $comment );
1342  $dbw->insert( 'image',
1343  [
1344  'img_name' => $this->getName(),
1345  'img_size' => $this->size,
1346  'img_width' => intval( $this->width ),
1347  'img_height' => intval( $this->height ),
1348  'img_bits' => $this->bits,
1349  'img_media_type' => $this->media_type,
1350  'img_major_mime' => $this->major_mime,
1351  'img_minor_mime' => $this->minor_mime,
1352  'img_timestamp' => $timestamp,
1353  'img_user' => $user->getId(),
1354  'img_user_text' => $user->getName(),
1355  'img_metadata' => $dbw->encodeBlob( $this->metadata ),
1356  'img_sha1' => $this->sha1
1357  ] + $commentFields,
1358  __METHOD__,
1359  'IGNORE'
1360  );
1361  $reupload = ( $dbw->affectedRows() == 0 );
1362 
1363  if ( $reupload ) {
1364  if ( $allowTimeKludge ) {
1365  # Use LOCK IN SHARE MODE to ignore any transaction snapshotting
1366  $ltimestamp = $dbw->selectField(
1367  'image',
1368  'img_timestamp',
1369  [ 'img_name' => $this->getName() ],
1370  __METHOD__,
1371  [ 'LOCK IN SHARE MODE' ]
1372  );
1373  $lUnixtime = $ltimestamp ? wfTimestamp( TS_UNIX, $ltimestamp ) : false;
1374  # Avoid a timestamp that is not newer than the last version
1375  # TODO: the image/oldimage tables should be like page/revision with an ID field
1376  if ( $lUnixtime && wfTimestamp( TS_UNIX, $timestamp ) <= $lUnixtime ) {
1377  sleep( 1 ); // fast enough re-uploads would go far in the future otherwise
1378  $timestamp = $dbw->timestamp( $lUnixtime + 1 );
1379  $this->timestamp = wfTimestamp( TS_MW, $timestamp ); // DB -> TS_MW
1380  }
1381  }
1382 
1383  $tables = [ 'image' ];
1384  $fields = [
1385  'oi_name' => 'img_name',
1386  'oi_archive_name' => $dbw->addQuotes( $oldver ),
1387  'oi_size' => 'img_size',
1388  'oi_width' => 'img_width',
1389  'oi_height' => 'img_height',
1390  'oi_bits' => 'img_bits',
1391  'oi_timestamp' => 'img_timestamp',
1392  'oi_user' => 'img_user',
1393  'oi_user_text' => 'img_user_text',
1394  'oi_metadata' => 'img_metadata',
1395  'oi_media_type' => 'img_media_type',
1396  'oi_major_mime' => 'img_major_mime',
1397  'oi_minor_mime' => 'img_minor_mime',
1398  'oi_sha1' => 'img_sha1',
1399  ];
1400  $joins = [];
1401 
1403  $fields['oi_description'] = 'img_description';
1404  }
1406  $tables[] = 'image_comment_temp';
1407  $fields['oi_description_id'] = 'imgcomment_description_id';
1408  $joins['image_comment_temp'] = [
1409  $wgCommentTableSchemaMigrationStage === MIGRATION_NEW ? 'JOIN' : 'LEFT JOIN',
1410  [ 'imgcomment_name = img_name' ]
1411  ];
1412  }
1413 
1416  ) {
1417  // Upgrade any rows that are still old-style. Otherwise an upgrade
1418  // might be missed if a deletion happens while the migration script
1419  // is running.
1420  $res = $dbw->select(
1421  [ 'image', 'image_comment_temp' ],
1422  [ 'img_name', 'img_description' ],
1423  [ 'img_name' => $this->getName(), 'imgcomment_name' => null ],
1424  __METHOD__,
1425  [],
1426  [ 'image_comment_temp' => [ 'LEFT JOIN', [ 'imgcomment_name = img_name' ] ] ]
1427  );
1428  foreach ( $res as $row ) {
1429  list( , $callback ) = $commentStore->insertWithTempTable( $dbw, $row->img_description );
1430  $callback( $row->img_name );
1431  }
1432  }
1433 
1434  # (T36993) Note: $oldver can be empty here, if the previous
1435  # version of the file was broken. Allow registration of the new
1436  # version to continue anyway, because that's better than having
1437  # an image that's not fixable by user operations.
1438  # Collision, this is an update of a file
1439  # Insert previous contents into oldimage
1440  $dbw->insertSelect( 'oldimage', $tables, $fields,
1441  [ 'img_name' => $this->getName() ], __METHOD__, [], [], $joins );
1442 
1443  # Update the current image row
1444  $dbw->update( 'image',
1445  [
1446  'img_size' => $this->size,
1447  'img_width' => intval( $this->width ),
1448  'img_height' => intval( $this->height ),
1449  'img_bits' => $this->bits,
1450  'img_media_type' => $this->media_type,
1451  'img_major_mime' => $this->major_mime,
1452  'img_minor_mime' => $this->minor_mime,
1453  'img_timestamp' => $timestamp,
1454  'img_user' => $user->getId(),
1455  'img_user_text' => $user->getName(),
1456  'img_metadata' => $dbw->encodeBlob( $this->metadata ),
1457  'img_sha1' => $this->sha1
1458  ] + $commentFields,
1459  [ 'img_name' => $this->getName() ],
1460  __METHOD__
1461  );
1463  // So $commentCallback can insert the new row
1464  $dbw->delete( 'image_comment_temp', [ 'imgcomment_name' => $this->getName() ], __METHOD__ );
1465  }
1466  }
1467  $commentCallback( $this->getName() );
1468 
1469  $descTitle = $this->getTitle();
1470  $descId = $descTitle->getArticleID();
1471  $wikiPage = new WikiFilePage( $descTitle );
1472  $wikiPage->setFile( $this );
1473 
1474  // Add the log entry...
1475  $logEntry = new ManualLogEntry( 'upload', $reupload ? 'overwrite' : 'upload' );
1476  $logEntry->setTimestamp( $this->timestamp );
1477  $logEntry->setPerformer( $user );
1478  $logEntry->setComment( $comment );
1479  $logEntry->setTarget( $descTitle );
1480  // Allow people using the api to associate log entries with the upload.
1481  // Log has a timestamp, but sometimes different from upload timestamp.
1482  $logEntry->setParameters(
1483  [
1484  'img_sha1' => $this->sha1,
1485  'img_timestamp' => $timestamp,
1486  ]
1487  );
1488  // Note we keep $logId around since during new image
1489  // creation, page doesn't exist yet, so log_page = 0
1490  // but we want it to point to the page we're making,
1491  // so we later modify the log entry.
1492  // For a similar reason, we avoid making an RC entry
1493  // now and wait until the page exists.
1494  $logId = $logEntry->insert();
1495 
1496  if ( $descTitle->exists() ) {
1497  // Use own context to get the action text in content language
1498  $formatter = LogFormatter::newFromEntry( $logEntry );
1499  $formatter->setContext( RequestContext::newExtraneousContext( $descTitle ) );
1500  $editSummary = $formatter->getPlainActionText();
1501 
1502  $nullRevision = Revision::newNullRevision(
1503  $dbw,
1504  $descId,
1505  $editSummary,
1506  false,
1507  $user
1508  );
1509  if ( $nullRevision ) {
1510  $nullRevision->insertOn( $dbw );
1511  Hooks::run(
1512  'NewRevisionFromEditComplete',
1513  [ $wikiPage, $nullRevision, $nullRevision->getParentId(), $user ]
1514  );
1515  $wikiPage->updateRevisionOn( $dbw, $nullRevision );
1516  // Associate null revision id
1517  $logEntry->setAssociatedRevId( $nullRevision->getId() );
1518  }
1519 
1520  $newPageContent = null;
1521  } else {
1522  // Make the description page and RC log entry post-commit
1523  $newPageContent = ContentHandler::makeContent( $pageText, $descTitle );
1524  }
1525 
1526  # Defer purges, page creation, and link updates in case they error out.
1527  # The most important thing is that files and the DB registry stay synced.
1528  $dbw->endAtomic( __METHOD__ );
1529 
1530  # Do some cache purges after final commit so that:
1531  # a) Changes are more likely to be seen post-purge
1532  # b) They won't cause rollback of the log publish/update above
1534  new AutoCommitUpdate(
1535  $dbw,
1536  __METHOD__,
1537  function () use (
1538  $reupload, $wikiPage, $newPageContent, $comment, $user,
1539  $logEntry, $logId, $descId, $tags
1540  ) {
1541  # Update memcache after the commit
1542  $this->invalidateCache();
1543 
1544  $updateLogPage = false;
1545  if ( $newPageContent ) {
1546  # New file page; create the description page.
1547  # There's already a log entry, so don't make a second RC entry
1548  # CDN and file cache for the description page are purged by doEditContent.
1549  $status = $wikiPage->doEditContent(
1550  $newPageContent,
1551  $comment,
1553  false,
1554  $user
1555  );
1556 
1557  if ( isset( $status->value['revision'] ) ) {
1559  $rev = $status->value['revision'];
1560  // Associate new page revision id
1561  $logEntry->setAssociatedRevId( $rev->getId() );
1562  }
1563  // This relies on the resetArticleID() call in WikiPage::insertOn(),
1564  // which is triggered on $descTitle by doEditContent() above.
1565  if ( isset( $status->value['revision'] ) ) {
1567  $rev = $status->value['revision'];
1568  $updateLogPage = $rev->getPage();
1569  }
1570  } else {
1571  # Existing file page: invalidate description page cache
1572  $wikiPage->getTitle()->invalidateCache();
1573  $wikiPage->getTitle()->purgeSquid();
1574  # Allow the new file version to be patrolled from the page footer
1576  }
1577 
1578  # Update associated rev id. This should be done by $logEntry->insert() earlier,
1579  # but setAssociatedRevId() wasn't called at that point yet...
1580  $logParams = $logEntry->getParameters();
1581  $logParams['associated_rev_id'] = $logEntry->getAssociatedRevId();
1582  $update = [ 'log_params' => LogEntryBase::makeParamBlob( $logParams ) ];
1583  if ( $updateLogPage ) {
1584  # Also log page, in case where we just created it above
1585  $update['log_page'] = $updateLogPage;
1586  }
1587  $this->getRepo()->getMasterDB()->update(
1588  'logging',
1589  $update,
1590  [ 'log_id' => $logId ],
1591  __METHOD__
1592  );
1593  $this->getRepo()->getMasterDB()->insert(
1594  'log_search',
1595  [
1596  'ls_field' => 'associated_rev_id',
1597  'ls_value' => $logEntry->getAssociatedRevId(),
1598  'ls_log_id' => $logId,
1599  ],
1600  __METHOD__
1601  );
1602 
1603  # Add change tags, if any
1604  if ( $tags ) {
1605  $logEntry->setTags( $tags );
1606  }
1607 
1608  # Uploads can be patrolled
1609  $logEntry->setIsPatrollable( true );
1610 
1611  # Now that the log entry is up-to-date, make an RC entry.
1612  $logEntry->publish( $logId );
1613 
1614  # Run hook for other updates (typically more cache purging)
1615  Hooks::run( 'FileUpload', [ $this, $reupload, !$newPageContent ] );
1616 
1617  if ( $reupload ) {
1618  # Delete old thumbnails
1619  $this->purgeThumbnails();
1620  # Remove the old file from the CDN cache
1622  new CdnCacheUpdate( [ $this->getUrl() ] ),
1624  );
1625  } else {
1626  # Update backlink pages pointing to this title if created
1627  LinksUpdate::queueRecursiveJobsForTable( $this->getTitle(), 'imagelinks' );
1628  }
1629 
1630  $this->prerenderThumbnails();
1631  }
1632  ),
1634  );
1635 
1636  if ( !$reupload ) {
1637  # This is a new file, so update the image count
1638  DeferredUpdates::addUpdate( SiteStatsUpdate::factory( [ 'images' => 1 ] ) );
1639  }
1640 
1641  # Invalidate cache for all pages using this file
1642  DeferredUpdates::addUpdate( new HTMLCacheUpdate( $this->getTitle(), 'imagelinks' ) );
1643 
1644  return true;
1645  }
1646 
1662  function publish( $src, $flags = 0, array $options = [] ) {
1663  return $this->publishTo( $src, $this->getRel(), $flags, $options );
1664  }
1665 
1681  function publishTo( $src, $dstRel, $flags = 0, array $options = [] ) {
1682  $srcPath = ( $src instanceof FSFile ) ? $src->getPath() : $src;
1683 
1684  $repo = $this->getRepo();
1685  if ( $repo->getReadOnlyReason() !== false ) {
1686  return $this->readOnlyFatalStatus();
1687  }
1688 
1689  $this->lock(); // begin
1690 
1691  $archiveName = wfTimestamp( TS_MW ) . '!' . $this->getName();
1692  $archiveRel = 'archive/' . $this->getHashPath() . $archiveName;
1693 
1694  if ( $repo->hasSha1Storage() ) {
1695  $sha1 = $repo->isVirtualUrl( $srcPath )
1696  ? $repo->getFileSha1( $srcPath )
1697  : FSFile::getSha1Base36FromPath( $srcPath );
1699  $wrapperBackend = $repo->getBackend();
1700  $dst = $wrapperBackend->getPathForSHA1( $sha1 );
1701  $status = $repo->quickImport( $src, $dst );
1702  if ( $flags & File::DELETE_SOURCE ) {
1703  unlink( $srcPath );
1704  }
1705 
1706  if ( $this->exists() ) {
1707  $status->value = $archiveName;
1708  }
1709  } else {
1711  $status = $repo->publish( $srcPath, $dstRel, $archiveRel, $flags, $options );
1712 
1713  if ( $status->value == 'new' ) {
1714  $status->value = '';
1715  } else {
1716  $status->value = $archiveName;
1717  }
1718  }
1719 
1720  $this->unlock(); // done
1721 
1722  return $status;
1723  }
1724 
1742  function move( $target ) {
1743  if ( $this->getRepo()->getReadOnlyReason() !== false ) {
1744  return $this->readOnlyFatalStatus();
1745  }
1746 
1747  wfDebugLog( 'imagemove', "Got request to move {$this->name} to " . $target->getText() );
1748  $batch = new LocalFileMoveBatch( $this, $target );
1749 
1750  $this->lock(); // begin
1751  $batch->addCurrent();
1752  $archiveNames = $batch->addOlds();
1753  $status = $batch->execute();
1754  $this->unlock(); // done
1755 
1756  wfDebugLog( 'imagemove', "Finished moving {$this->name}" );
1757 
1758  // Purge the source and target files...
1759  $oldTitleFile = wfLocalFile( $this->title );
1760  $newTitleFile = wfLocalFile( $target );
1761  // To avoid slow purges in the transaction, move them outside...
1763  new AutoCommitUpdate(
1764  $this->getRepo()->getMasterDB(),
1765  __METHOD__,
1766  function () use ( $oldTitleFile, $newTitleFile, $archiveNames ) {
1767  $oldTitleFile->purgeEverything();
1768  foreach ( $archiveNames as $archiveName ) {
1769  $oldTitleFile->purgeOldThumbnails( $archiveName );
1770  }
1771  $newTitleFile->purgeEverything();
1772  }
1773  ),
1775  );
1776 
1777  if ( $status->isOK() ) {
1778  // Now switch the object
1779  $this->title = $target;
1780  // Force regeneration of the name and hashpath
1781  unset( $this->name );
1782  unset( $this->hashPath );
1783  }
1784 
1785  return $status;
1786  }
1787 
1801  function delete( $reason, $suppress = false, $user = null ) {
1802  if ( $this->getRepo()->getReadOnlyReason() !== false ) {
1803  return $this->readOnlyFatalStatus();
1804  }
1805 
1806  $batch = new LocalFileDeleteBatch( $this, $reason, $suppress, $user );
1807 
1808  $this->lock(); // begin
1809  $batch->addCurrent();
1810  // Get old version relative paths
1811  $archiveNames = $batch->addOlds();
1812  $status = $batch->execute();
1813  $this->unlock(); // done
1814 
1815  if ( $status->isOK() ) {
1816  DeferredUpdates::addUpdate( SiteStatsUpdate::factory( [ 'images' => -1 ] ) );
1817  }
1818 
1819  // To avoid slow purges in the transaction, move them outside...
1821  new AutoCommitUpdate(
1822  $this->getRepo()->getMasterDB(),
1823  __METHOD__,
1824  function () use ( $archiveNames ) {
1825  $this->purgeEverything();
1826  foreach ( $archiveNames as $archiveName ) {
1827  $this->purgeOldThumbnails( $archiveName );
1828  }
1829  }
1830  ),
1832  );
1833 
1834  // Purge the CDN
1835  $purgeUrls = [];
1836  foreach ( $archiveNames as $archiveName ) {
1837  $purgeUrls[] = $this->getArchiveUrl( $archiveName );
1838  }
1840 
1841  return $status;
1842  }
1843 
1859  function deleteOld( $archiveName, $reason, $suppress = false, $user = null ) {
1860  if ( $this->getRepo()->getReadOnlyReason() !== false ) {
1861  return $this->readOnlyFatalStatus();
1862  }
1863 
1864  $batch = new LocalFileDeleteBatch( $this, $reason, $suppress, $user );
1865 
1866  $this->lock(); // begin
1867  $batch->addOld( $archiveName );
1868  $status = $batch->execute();
1869  $this->unlock(); // done
1870 
1871  $this->purgeOldThumbnails( $archiveName );
1872  if ( $status->isOK() ) {
1873  $this->purgeDescription();
1874  }
1875 
1877  new CdnCacheUpdate( [ $this->getArchiveUrl( $archiveName ) ] ),
1879  );
1880 
1881  return $status;
1882  }
1883 
1895  function restore( $versions = [], $unsuppress = false ) {
1896  if ( $this->getRepo()->getReadOnlyReason() !== false ) {
1897  return $this->readOnlyFatalStatus();
1898  }
1899 
1900  $batch = new LocalFileRestoreBatch( $this, $unsuppress );
1901 
1902  $this->lock(); // begin
1903  if ( !$versions ) {
1904  $batch->addAll();
1905  } else {
1906  $batch->addIds( $versions );
1907  }
1908  $status = $batch->execute();
1909  if ( $status->isGood() ) {
1910  $cleanupStatus = $batch->cleanup();
1911  $cleanupStatus->successCount = 0;
1912  $cleanupStatus->failCount = 0;
1913  $status->merge( $cleanupStatus );
1914  }
1915  $this->unlock(); // done
1916 
1917  return $status;
1918  }
1919 
1929  function getDescriptionUrl() {
1930  return $this->title->getLocalURL();
1931  }
1932 
1941  function getDescriptionText( $lang = null ) {
1942  $revision = Revision::newFromTitle( $this->title, false, Revision::READ_NORMAL );
1943  if ( !$revision ) {
1944  return false;
1945  }
1946  $content = $revision->getContent();
1947  if ( !$content ) {
1948  return false;
1949  }
1950  $pout = $content->getParserOutput( $this->title, null, new ParserOptions( null, $lang ) );
1951 
1952  return $pout->getText();
1953  }
1954 
1960  function getDescription( $audience = self::FOR_PUBLIC, User $user = null ) {
1961  $this->load();
1962  if ( $audience == self::FOR_PUBLIC && $this->isDeleted( self::DELETED_COMMENT ) ) {
1963  return '';
1964  } elseif ( $audience == self::FOR_THIS_USER
1965  && !$this->userCan( self::DELETED_COMMENT, $user )
1966  ) {
1967  return '';
1968  } else {
1969  return $this->description;
1970  }
1971  }
1972 
1976  function getTimestamp() {
1977  $this->load();
1978 
1979  return $this->timestamp;
1980  }
1981 
1985  public function getDescriptionTouched() {
1986  // The DB lookup might return false, e.g. if the file was just deleted, or the shared DB repo
1987  // itself gets it from elsewhere. To avoid repeating the DB lookups in such a case, we
1988  // need to differentiate between null (uninitialized) and false (failed to load).
1989  if ( $this->descriptionTouched === null ) {
1990  $cond = [
1991  'page_namespace' => $this->title->getNamespace(),
1992  'page_title' => $this->title->getDBkey()
1993  ];
1994  $touched = $this->repo->getReplicaDB()->selectField( 'page', 'page_touched', $cond, __METHOD__ );
1995  $this->descriptionTouched = $touched ? wfTimestamp( TS_MW, $touched ) : false;
1996  }
1997 
1999  }
2000 
2004  function getSha1() {
2005  $this->load();
2006  // Initialise now if necessary
2007  if ( $this->sha1 == '' && $this->fileExists ) {
2008  $this->lock(); // begin
2009 
2010  $this->sha1 = $this->repo->getFileSha1( $this->getPath() );
2011  if ( !wfReadOnly() && strval( $this->sha1 ) != '' ) {
2012  $dbw = $this->repo->getMasterDB();
2013  $dbw->update( 'image',
2014  [ 'img_sha1' => $this->sha1 ],
2015  [ 'img_name' => $this->getName() ],
2016  __METHOD__ );
2017  $this->invalidateCache();
2018  }
2019 
2020  $this->unlock(); // done
2021  }
2022 
2023  return $this->sha1;
2024  }
2025 
2029  function isCacheable() {
2030  $this->load();
2031 
2032  // If extra data (metadata) was not loaded then it must have been large
2033  return $this->extraDataLoaded
2034  && strlen( serialize( $this->metadata ) ) <= self::CACHE_FIELD_MAX_LEN;
2035  }
2036 
2041  public function acquireFileLock() {
2042  return $this->getRepo()->getBackend()->lockFiles(
2043  [ $this->getPath() ], LockManager::LOCK_EX, 10
2044  );
2045  }
2046 
2051  public function releaseFileLock() {
2052  return $this->getRepo()->getBackend()->unlockFiles(
2053  [ $this->getPath() ], LockManager::LOCK_EX
2054  );
2055  }
2056 
2066  public function lock() {
2067  if ( !$this->locked ) {
2068  $logger = LoggerFactory::getInstance( 'LocalFile' );
2069 
2070  $dbw = $this->repo->getMasterDB();
2071  $makesTransaction = !$dbw->trxLevel();
2072  $dbw->startAtomic( self::ATOMIC_SECTION_LOCK );
2073  // T56736: use simple lock to handle when the file does not exist.
2074  // SELECT FOR UPDATE prevents changes, not other SELECTs with FOR UPDATE.
2075  // Also, that would cause contention on INSERT of similarly named rows.
2076  $status = $this->acquireFileLock(); // represents all versions of the file
2077  if ( !$status->isGood() ) {
2078  $dbw->endAtomic( self::ATOMIC_SECTION_LOCK );
2079  $logger->warning( "Failed to lock '{file}'", [ 'file' => $this->name ] );
2080 
2081  throw new LocalFileLockError( $status );
2082  }
2083  // Release the lock *after* commit to avoid row-level contention.
2084  // Make sure it triggers on rollback() as well as commit() (T132921).
2085  $dbw->onTransactionResolution(
2086  function () use ( $logger ) {
2087  $status = $this->releaseFileLock();
2088  if ( !$status->isGood() ) {
2089  $logger->error( "Failed to unlock '{file}'", [ 'file' => $this->name ] );
2090  }
2091  },
2092  __METHOD__
2093  );
2094  // Callers might care if the SELECT snapshot is safely fresh
2095  $this->lockedOwnTrx = $makesTransaction;
2096  }
2097 
2098  $this->locked++;
2099 
2100  return $this->lockedOwnTrx;
2101  }
2102 
2111  public function unlock() {
2112  if ( $this->locked ) {
2113  --$this->locked;
2114  if ( !$this->locked ) {
2115  $dbw = $this->repo->getMasterDB();
2116  $dbw->endAtomic( self::ATOMIC_SECTION_LOCK );
2117  $this->lockedOwnTrx = false;
2118  }
2119  }
2120  }
2121 
2125  protected function readOnlyFatalStatus() {
2126  return $this->getRepo()->newFatal( 'filereadonlyerror', $this->getName(),
2127  $this->getRepo()->getName(), $this->getRepo()->getReadOnlyReason() );
2128  }
2129 
2133  function __destruct() {
2134  $this->unlock();
2135  }
2136 } // LocalFile class
2137 
2138 # ------------------------------------------------------------------------------
2139 
2146  private $file;
2147 
2149  private $reason;
2150 
2152  private $srcRels = [];
2153 
2155  private $archiveUrls = [];
2156 
2159 
2161  private $suppress;
2162 
2164  private $status;
2165 
2167  private $user;
2168 
2175  function __construct( File $file, $reason = '', $suppress = false, $user = null ) {
2176  $this->file = $file;
2177  $this->reason = $reason;
2178  $this->suppress = $suppress;
2179  if ( $user ) {
2180  $this->user = $user;
2181  } else {
2182  global $wgUser;
2183  $this->user = $wgUser;
2184  }
2185  $this->status = $file->repo->newGood();
2186  }
2187 
2188  public function addCurrent() {
2189  $this->srcRels['.'] = $this->file->getRel();
2190  }
2191 
2195  public function addOld( $oldName ) {
2196  $this->srcRels[$oldName] = $this->file->getArchiveRel( $oldName );
2197  $this->archiveUrls[] = $this->file->getArchiveUrl( $oldName );
2198  }
2199 
2204  public function addOlds() {
2205  $archiveNames = [];
2206 
2207  $dbw = $this->file->repo->getMasterDB();
2208  $result = $dbw->select( 'oldimage',
2209  [ 'oi_archive_name' ],
2210  [ 'oi_name' => $this->file->getName() ],
2211  __METHOD__
2212  );
2213 
2214  foreach ( $result as $row ) {
2215  $this->addOld( $row->oi_archive_name );
2216  $archiveNames[] = $row->oi_archive_name;
2217  }
2218 
2219  return $archiveNames;
2220  }
2221 
2225  protected function getOldRels() {
2226  if ( !isset( $this->srcRels['.'] ) ) {
2227  $oldRels =& $this->srcRels;
2228  $deleteCurrent = false;
2229  } else {
2230  $oldRels = $this->srcRels;
2231  unset( $oldRels['.'] );
2232  $deleteCurrent = true;
2233  }
2234 
2235  return [ $oldRels, $deleteCurrent ];
2236  }
2237 
2241  protected function getHashes() {
2242  $hashes = [];
2243  list( $oldRels, $deleteCurrent ) = $this->getOldRels();
2244 
2245  if ( $deleteCurrent ) {
2246  $hashes['.'] = $this->file->getSha1();
2247  }
2248 
2249  if ( count( $oldRels ) ) {
2250  $dbw = $this->file->repo->getMasterDB();
2251  $res = $dbw->select(
2252  'oldimage',
2253  [ 'oi_archive_name', 'oi_sha1' ],
2254  [ 'oi_archive_name' => array_keys( $oldRels ),
2255  'oi_name' => $this->file->getName() ], // performance
2256  __METHOD__
2257  );
2258 
2259  foreach ( $res as $row ) {
2260  if ( rtrim( $row->oi_sha1, "\0" ) === '' ) {
2261  // Get the hash from the file
2262  $oldUrl = $this->file->getArchiveVirtualUrl( $row->oi_archive_name );
2263  $props = $this->file->repo->getFileProps( $oldUrl );
2264 
2265  if ( $props['fileExists'] ) {
2266  // Upgrade the oldimage row
2267  $dbw->update( 'oldimage',
2268  [ 'oi_sha1' => $props['sha1'] ],
2269  [ 'oi_name' => $this->file->getName(), 'oi_archive_name' => $row->oi_archive_name ],
2270  __METHOD__ );
2271  $hashes[$row->oi_archive_name] = $props['sha1'];
2272  } else {
2273  $hashes[$row->oi_archive_name] = false;
2274  }
2275  } else {
2276  $hashes[$row->oi_archive_name] = $row->oi_sha1;
2277  }
2278  }
2279  }
2280 
2281  $missing = array_diff_key( $this->srcRels, $hashes );
2282 
2283  foreach ( $missing as $name => $rel ) {
2284  $this->status->error( 'filedelete-old-unregistered', $name );
2285  }
2286 
2287  foreach ( $hashes as $name => $hash ) {
2288  if ( !$hash ) {
2289  $this->status->error( 'filedelete-missing', $this->srcRels[$name] );
2290  unset( $hashes[$name] );
2291  }
2292  }
2293 
2294  return $hashes;
2295  }
2296 
2297  protected function doDBInserts() {
2299 
2300  $now = time();
2301  $dbw = $this->file->repo->getMasterDB();
2302 
2303  $commentStoreImgDesc = new CommentStore( 'img_description' );
2304  $commentStoreOiDesc = new CommentStore( 'oi_description' );
2305  $commentStoreFaDesc = new CommentStore( 'fa_description' );
2306  $commentStoreFaReason = new CommentStore( 'fa_deleted_reason' );
2307 
2308  $encTimestamp = $dbw->addQuotes( $dbw->timestamp( $now ) );
2309  $encUserId = $dbw->addQuotes( $this->user->getId() );
2310  $encGroup = $dbw->addQuotes( 'deleted' );
2311  $ext = $this->file->getExtension();
2312  $dotExt = $ext === '' ? '' : ".$ext";
2313  $encExt = $dbw->addQuotes( $dotExt );
2314  list( $oldRels, $deleteCurrent ) = $this->getOldRels();
2315 
2316  // Bitfields to further suppress the content
2317  if ( $this->suppress ) {
2318  $bitfield = Revision::SUPPRESSED_ALL;
2319  } else {
2320  $bitfield = 'oi_deleted';
2321  }
2322 
2323  if ( $deleteCurrent ) {
2324  $tables = [ 'image' ];
2325  $fields = [
2326  'fa_storage_group' => $encGroup,
2327  'fa_storage_key' => $dbw->conditional(
2328  [ 'img_sha1' => '' ],
2329  $dbw->addQuotes( '' ),
2330  $dbw->buildConcat( [ "img_sha1", $encExt ] )
2331  ),
2332  'fa_deleted_user' => $encUserId,
2333  'fa_deleted_timestamp' => $encTimestamp,
2334  'fa_deleted' => $this->suppress ? $bitfield : 0,
2335  'fa_name' => 'img_name',
2336  'fa_archive_name' => 'NULL',
2337  'fa_size' => 'img_size',
2338  'fa_width' => 'img_width',
2339  'fa_height' => 'img_height',
2340  'fa_metadata' => 'img_metadata',
2341  'fa_bits' => 'img_bits',
2342  'fa_media_type' => 'img_media_type',
2343  'fa_major_mime' => 'img_major_mime',
2344  'fa_minor_mime' => 'img_minor_mime',
2345  'fa_user' => 'img_user',
2346  'fa_user_text' => 'img_user_text',
2347  'fa_timestamp' => 'img_timestamp',
2348  'fa_sha1' => 'img_sha1'
2349  ];
2350  $joins = [];
2351 
2352  $fields += array_map(
2353  [ $dbw, 'addQuotes' ],
2354  $commentStoreFaReason->insert( $dbw, $this->reason )
2355  );
2356 
2358  $fields['fa_description'] = 'img_description';
2359  }
2361  $tables[] = 'image_comment_temp';
2362  $fields['fa_description_id'] = 'imgcomment_description_id';
2363  $joins['image_comment_temp'] = [
2364  $wgCommentTableSchemaMigrationStage === MIGRATION_NEW ? 'JOIN' : 'LEFT JOIN',
2365  [ 'imgcomment_name = img_name' ]
2366  ];
2367  }
2368 
2371  ) {
2372  // Upgrade any rows that are still old-style. Otherwise an upgrade
2373  // might be missed if a deletion happens while the migration script
2374  // is running.
2375  $res = $dbw->select(
2376  [ 'image', 'image_comment_temp' ],
2377  [ 'img_name', 'img_description' ],
2378  [ 'img_name' => $this->file->getName(), 'imgcomment_name' => null ],
2379  __METHOD__,
2380  [],
2381  [ 'image_comment_temp' => [ 'LEFT JOIN', [ 'imgcomment_name = img_name' ] ] ]
2382  );
2383  foreach ( $res as $row ) {
2384  list( , $callback ) = $commentStoreImgDesc->insertWithTempTable( $dbw, $row->img_description );
2385  $callback( $row->img_name );
2386  }
2387  }
2388 
2389  $dbw->insertSelect( 'filearchive', $tables, $fields,
2390  [ 'img_name' => $this->file->getName() ], __METHOD__, [], [], $joins );
2391  }
2392 
2393  if ( count( $oldRels ) ) {
2394  $res = $dbw->select(
2395  'oldimage',
2397  [
2398  'oi_name' => $this->file->getName(),
2399  'oi_archive_name' => array_keys( $oldRels )
2400  ],
2401  __METHOD__,
2402  [ 'FOR UPDATE' ]
2403  );
2404  $rowsInsert = [];
2405  if ( $res->numRows() ) {
2406  $reason = $commentStoreFaReason->createComment( $dbw, $this->reason );
2407  foreach ( $res as $row ) {
2408  // Legacy from OldLocalFile::selectFields() just above
2409  $comment = $commentStoreOiDesc->getCommentLegacy( $dbw, $row );
2410  $rowsInsert[] = [
2411  // Deletion-specific fields
2412  'fa_storage_group' => 'deleted',
2413  'fa_storage_key' => ( $row->oi_sha1 === '' )
2414  ? ''
2415  : "{$row->oi_sha1}{$dotExt}",
2416  'fa_deleted_user' => $this->user->getId(),
2417  'fa_deleted_timestamp' => $dbw->timestamp( $now ),
2418  // Counterpart fields
2419  'fa_deleted' => $this->suppress ? $bitfield : $row->oi_deleted,
2420  'fa_name' => $row->oi_name,
2421  'fa_archive_name' => $row->oi_archive_name,
2422  'fa_size' => $row->oi_size,
2423  'fa_width' => $row->oi_width,
2424  'fa_height' => $row->oi_height,
2425  'fa_metadata' => $row->oi_metadata,
2426  'fa_bits' => $row->oi_bits,
2427  'fa_media_type' => $row->oi_media_type,
2428  'fa_major_mime' => $row->oi_major_mime,
2429  'fa_minor_mime' => $row->oi_minor_mime,
2430  'fa_user' => $row->oi_user,
2431  'fa_user_text' => $row->oi_user_text,
2432  'fa_timestamp' => $row->oi_timestamp,
2433  'fa_sha1' => $row->oi_sha1
2434  ] + $commentStoreFaReason->insert( $dbw, $reason )
2435  + $commentStoreFaDesc->insert( $dbw, $comment );
2436  }
2437  }
2438 
2439  $dbw->insert( 'filearchive', $rowsInsert, __METHOD__ );
2440  }
2441  }
2442 
2443  function doDBDeletes() {
2445 
2446  $dbw = $this->file->repo->getMasterDB();
2447  list( $oldRels, $deleteCurrent ) = $this->getOldRels();
2448 
2449  if ( count( $oldRels ) ) {
2450  $dbw->delete( 'oldimage',
2451  [
2452  'oi_name' => $this->file->getName(),
2453  'oi_archive_name' => array_keys( $oldRels )
2454  ], __METHOD__ );
2455  }
2456 
2457  if ( $deleteCurrent ) {
2458  $dbw->delete( 'image', [ 'img_name' => $this->file->getName() ], __METHOD__ );
2460  $dbw->delete(
2461  'image_comment_temp', [ 'imgcomment_name' => $this->file->getName() ], __METHOD__
2462  );
2463  }
2464  }
2465  }
2466 
2471  public function execute() {
2472  $repo = $this->file->getRepo();
2473  $this->file->lock();
2474 
2475  // Prepare deletion batch
2476  $hashes = $this->getHashes();
2477  $this->deletionBatch = [];
2478  $ext = $this->file->getExtension();
2479  $dotExt = $ext === '' ? '' : ".$ext";
2480 
2481  foreach ( $this->srcRels as $name => $srcRel ) {
2482  // Skip files that have no hash (e.g. missing DB record, or sha1 field and file source)
2483  if ( isset( $hashes[$name] ) ) {
2484  $hash = $hashes[$name];
2485  $key = $hash . $dotExt;
2486  $dstRel = $repo->getDeletedHashPath( $key ) . $key;
2487  $this->deletionBatch[$name] = [ $srcRel, $dstRel ];
2488  }
2489  }
2490 
2491  if ( !$repo->hasSha1Storage() ) {
2492  // Removes non-existent file from the batch, so we don't get errors.
2493  // This also handles files in the 'deleted' zone deleted via revision deletion.
2494  $checkStatus = $this->removeNonexistentFiles( $this->deletionBatch );
2495  if ( !$checkStatus->isGood() ) {
2496  $this->status->merge( $checkStatus );
2497  return $this->status;
2498  }
2499  $this->deletionBatch = $checkStatus->value;
2500 
2501  // Execute the file deletion batch
2502  $status = $this->file->repo->deleteBatch( $this->deletionBatch );
2503  if ( !$status->isGood() ) {
2504  $this->status->merge( $status );
2505  }
2506  }
2507 
2508  if ( !$this->status->isOK() ) {
2509  // Critical file deletion error; abort
2510  $this->file->unlock();
2511 
2512  return $this->status;
2513  }
2514 
2515  // Copy the image/oldimage rows to filearchive
2516  $this->doDBInserts();
2517  // Delete image/oldimage rows
2518  $this->doDBDeletes();
2519 
2520  // Commit and return
2521  $this->file->unlock();
2522 
2523  return $this->status;
2524  }
2525 
2531  protected function removeNonexistentFiles( $batch ) {
2532  $files = $newBatch = [];
2533 
2534  foreach ( $batch as $batchItem ) {
2535  list( $src, ) = $batchItem;
2536  $files[$src] = $this->file->repo->getVirtualUrl( 'public' ) . '/' . rawurlencode( $src );
2537  }
2538 
2539  $result = $this->file->repo->fileExistsBatch( $files );
2540  if ( in_array( null, $result, true ) ) {
2541  return Status::newFatal( 'backend-fail-internal',
2542  $this->file->repo->getBackend()->getName() );
2543  }
2544 
2545  foreach ( $batch as $batchItem ) {
2546  if ( $result[$batchItem[0]] ) {
2547  $newBatch[] = $batchItem;
2548  }
2549  }
2550 
2551  return Status::newGood( $newBatch );
2552  }
2553 }
2554 
2555 # ------------------------------------------------------------------------------
2556 
2563  private $file;
2564 
2566  private $cleanupBatch;
2567 
2569  private $ids;
2570 
2572  private $all;
2573 
2575  private $unsuppress = false;
2576 
2581  function __construct( File $file, $unsuppress = false ) {
2582  $this->file = $file;
2583  $this->cleanupBatch = $this->ids = [];
2584  $this->ids = [];
2585  $this->unsuppress = $unsuppress;
2586  }
2587 
2592  public function addId( $fa_id ) {
2593  $this->ids[] = $fa_id;
2594  }
2595 
2600  public function addIds( $ids ) {
2601  $this->ids = array_merge( $this->ids, $ids );
2602  }
2603 
2607  public function addAll() {
2608  $this->all = true;
2609  }
2610 
2619  public function execute() {
2621  global $wgLang;
2622 
2623  $repo = $this->file->getRepo();
2624  if ( !$this->all && !$this->ids ) {
2625  // Do nothing
2626  return $repo->newGood();
2627  }
2628 
2629  $lockOwnsTrx = $this->file->lock();
2630 
2631  $dbw = $this->file->repo->getMasterDB();
2632 
2633  $commentStoreImgDesc = new CommentStore( 'img_description' );
2634  $commentStoreOiDesc = new CommentStore( 'oi_description' );
2635  $commentStoreFaDesc = new CommentStore( 'fa_description' );
2636 
2637  $status = $this->file->repo->newGood();
2638 
2639  $exists = (bool)$dbw->selectField( 'image', '1',
2640  [ 'img_name' => $this->file->getName() ],
2641  __METHOD__,
2642  // The lock() should already prevents changes, but this still may need
2643  // to bypass any transaction snapshot. However, if lock() started the
2644  // trx (which it probably did) then snapshot is post-lock and up-to-date.
2645  $lockOwnsTrx ? [] : [ 'LOCK IN SHARE MODE' ]
2646  );
2647 
2648  // Fetch all or selected archived revisions for the file,
2649  // sorted from the most recent to the oldest.
2650  $conditions = [ 'fa_name' => $this->file->getName() ];
2651 
2652  if ( !$this->all ) {
2653  $conditions['fa_id'] = $this->ids;
2654  }
2655 
2656  $result = $dbw->select(
2657  'filearchive',
2659  $conditions,
2660  __METHOD__,
2661  [ 'ORDER BY' => 'fa_timestamp DESC' ]
2662  );
2663 
2664  $idsPresent = [];
2665  $storeBatch = [];
2666  $insertBatch = [];
2667  $insertCurrent = false;
2668  $deleteIds = [];
2669  $first = true;
2670  $archiveNames = [];
2671 
2672  foreach ( $result as $row ) {
2673  $idsPresent[] = $row->fa_id;
2674 
2675  if ( $row->fa_name != $this->file->getName() ) {
2676  $status->error( 'undelete-filename-mismatch', $wgLang->timeanddate( $row->fa_timestamp ) );
2677  $status->failCount++;
2678  continue;
2679  }
2680 
2681  if ( $row->fa_storage_key == '' ) {
2682  // Revision was missing pre-deletion
2683  $status->error( 'undelete-bad-store-key', $wgLang->timeanddate( $row->fa_timestamp ) );
2684  $status->failCount++;
2685  continue;
2686  }
2687 
2688  $deletedRel = $repo->getDeletedHashPath( $row->fa_storage_key ) .
2689  $row->fa_storage_key;
2690  $deletedUrl = $repo->getVirtualUrl() . '/deleted/' . $deletedRel;
2691 
2692  if ( isset( $row->fa_sha1 ) ) {
2693  $sha1 = $row->fa_sha1;
2694  } else {
2695  // old row, populate from key
2696  $sha1 = LocalRepo::getHashFromKey( $row->fa_storage_key );
2697  }
2698 
2699  # Fix leading zero
2700  if ( strlen( $sha1 ) == 32 && $sha1[0] == '0' ) {
2701  $sha1 = substr( $sha1, 1 );
2702  }
2703 
2704  if ( is_null( $row->fa_major_mime ) || $row->fa_major_mime == 'unknown'
2705  || is_null( $row->fa_minor_mime ) || $row->fa_minor_mime == 'unknown'
2706  || is_null( $row->fa_media_type ) || $row->fa_media_type == 'UNKNOWN'
2707  || is_null( $row->fa_metadata )
2708  ) {
2709  // Refresh our metadata
2710  // Required for a new current revision; nice for older ones too. :)
2711  $props = RepoGroup::singleton()->getFileProps( $deletedUrl );
2712  } else {
2713  $props = [
2714  'minor_mime' => $row->fa_minor_mime,
2715  'major_mime' => $row->fa_major_mime,
2716  'media_type' => $row->fa_media_type,
2717  'metadata' => $row->fa_metadata
2718  ];
2719  }
2720 
2721  // Legacy from ArchivedFile::selectFields() just above
2722  $comment = $commentStoreFaDesc->getCommentLegacy( $dbw, $row );
2723  if ( $first && !$exists ) {
2724  // This revision will be published as the new current version
2725  $destRel = $this->file->getRel();
2726  list( $commentFields, $commentCallback ) =
2727  $commentStoreImgDesc->insertWithTempTable( $dbw, $comment );
2728  $insertCurrent = [
2729  'img_name' => $row->fa_name,
2730  'img_size' => $row->fa_size,
2731  'img_width' => $row->fa_width,
2732  'img_height' => $row->fa_height,
2733  'img_metadata' => $props['metadata'],
2734  'img_bits' => $row->fa_bits,
2735  'img_media_type' => $props['media_type'],
2736  'img_major_mime' => $props['major_mime'],
2737  'img_minor_mime' => $props['minor_mime'],
2738  'img_user' => $row->fa_user,
2739  'img_user_text' => $row->fa_user_text,
2740  'img_timestamp' => $row->fa_timestamp,
2741  'img_sha1' => $sha1
2742  ] + $commentFields;
2743 
2744  // The live (current) version cannot be hidden!
2745  if ( !$this->unsuppress && $row->fa_deleted ) {
2746  $status->fatal( 'undeleterevdel' );
2747  $this->file->unlock();
2748  return $status;
2749  }
2750  } else {
2751  $archiveName = $row->fa_archive_name;
2752 
2753  if ( $archiveName == '' ) {
2754  // This was originally a current version; we
2755  // have to devise a new archive name for it.
2756  // Format is <timestamp of archiving>!<name>
2757  $timestamp = wfTimestamp( TS_UNIX, $row->fa_deleted_timestamp );
2758 
2759  do {
2760  $archiveName = wfTimestamp( TS_MW, $timestamp ) . '!' . $row->fa_name;
2761  $timestamp++;
2762  } while ( isset( $archiveNames[$archiveName] ) );
2763  }
2764 
2765  $archiveNames[$archiveName] = true;
2766  $destRel = $this->file->getArchiveRel( $archiveName );
2767  $insertBatch[] = [
2768  'oi_name' => $row->fa_name,
2769  'oi_archive_name' => $archiveName,
2770  'oi_size' => $row->fa_size,
2771  'oi_width' => $row->fa_width,
2772  'oi_height' => $row->fa_height,
2773  'oi_bits' => $row->fa_bits,
2774  'oi_user' => $row->fa_user,
2775  'oi_user_text' => $row->fa_user_text,
2776  'oi_timestamp' => $row->fa_timestamp,
2777  'oi_metadata' => $props['metadata'],
2778  'oi_media_type' => $props['media_type'],
2779  'oi_major_mime' => $props['major_mime'],
2780  'oi_minor_mime' => $props['minor_mime'],
2781  'oi_deleted' => $this->unsuppress ? 0 : $row->fa_deleted,
2782  'oi_sha1' => $sha1
2783  ] + $commentStoreOiDesc->insert( $dbw, $comment );
2784  }
2785 
2786  $deleteIds[] = $row->fa_id;
2787 
2788  if ( !$this->unsuppress && $row->fa_deleted & File::DELETED_FILE ) {
2789  // private files can stay where they are
2790  $status->successCount++;
2791  } else {
2792  $storeBatch[] = [ $deletedUrl, 'public', $destRel ];
2793  $this->cleanupBatch[] = $row->fa_storage_key;
2794  }
2795 
2796  $first = false;
2797  }
2798 
2799  unset( $result );
2800 
2801  // Add a warning to the status object for missing IDs
2802  $missingIds = array_diff( $this->ids, $idsPresent );
2803 
2804  foreach ( $missingIds as $id ) {
2805  $status->error( 'undelete-missing-filearchive', $id );
2806  }
2807 
2808  if ( !$repo->hasSha1Storage() ) {
2809  // Remove missing files from batch, so we don't get errors when undeleting them
2810  $checkStatus = $this->removeNonexistentFiles( $storeBatch );
2811  if ( !$checkStatus->isGood() ) {
2812  $status->merge( $checkStatus );
2813  return $status;
2814  }
2815  $storeBatch = $checkStatus->value;
2816 
2817  // Run the store batch
2818  // Use the OVERWRITE_SAME flag to smooth over a common error
2819  $storeStatus = $this->file->repo->storeBatch( $storeBatch, FileRepo::OVERWRITE_SAME );
2820  $status->merge( $storeStatus );
2821 
2822  if ( !$status->isGood() ) {
2823  // Even if some files could be copied, fail entirely as that is the
2824  // easiest thing to do without data loss
2825  $this->cleanupFailedBatch( $storeStatus, $storeBatch );
2826  $status->setOK( false );
2827  $this->file->unlock();
2828 
2829  return $status;
2830  }
2831  }
2832 
2833  // Run the DB updates
2834  // Because we have locked the image row, key conflicts should be rare.
2835  // If they do occur, we can roll back the transaction at this time with
2836  // no data loss, but leaving unregistered files scattered throughout the
2837  // public zone.
2838  // This is not ideal, which is why it's important to lock the image row.
2839  if ( $insertCurrent ) {
2840  $dbw->insert( 'image', $insertCurrent, __METHOD__ );
2841  $commentCallback( $insertCurrent['img_name'] );
2842  }
2843 
2844  if ( $insertBatch ) {
2845  $dbw->insert( 'oldimage', $insertBatch, __METHOD__ );
2846  }
2847 
2848  if ( $deleteIds ) {
2849  $dbw->delete( 'filearchive',
2850  [ 'fa_id' => $deleteIds ],
2851  __METHOD__ );
2852  }
2853 
2854  // If store batch is empty (all files are missing), deletion is to be considered successful
2855  if ( $status->successCount > 0 || !$storeBatch || $repo->hasSha1Storage() ) {
2856  if ( !$exists ) {
2857  wfDebug( __METHOD__ . " restored {$status->successCount} items, creating a new current\n" );
2858 
2859  DeferredUpdates::addUpdate( SiteStatsUpdate::factory( [ 'images' => 1 ] ) );
2860 
2861  $this->file->purgeEverything();
2862  } else {
2863  wfDebug( __METHOD__ . " restored {$status->successCount} as archived versions\n" );
2864  $this->file->purgeDescription();
2865  }
2866  }
2867 
2868  $this->file->unlock();
2869 
2870  return $status;
2871  }
2872 
2878  protected function removeNonexistentFiles( $triplets ) {
2879  $files = $filteredTriplets = [];
2880  foreach ( $triplets as $file ) {
2881  $files[$file[0]] = $file[0];
2882  }
2883 
2884  $result = $this->file->repo->fileExistsBatch( $files );
2885  if ( in_array( null, $result, true ) ) {
2886  return Status::newFatal( 'backend-fail-internal',
2887  $this->file->repo->getBackend()->getName() );
2888  }
2889 
2890  foreach ( $triplets as $file ) {
2891  if ( $result[$file[0]] ) {
2892  $filteredTriplets[] = $file;
2893  }
2894  }
2895 
2896  return Status::newGood( $filteredTriplets );
2897  }
2898 
2904  protected function removeNonexistentFromCleanup( $batch ) {
2905  $files = $newBatch = [];
2906  $repo = $this->file->repo;
2907 
2908  foreach ( $batch as $file ) {
2909  $files[$file] = $repo->getVirtualUrl( 'deleted' ) . '/' .
2910  rawurlencode( $repo->getDeletedHashPath( $file ) . $file );
2911  }
2912 
2913  $result = $repo->fileExistsBatch( $files );
2914 
2915  foreach ( $batch as $file ) {
2916  if ( $result[$file] ) {
2917  $newBatch[] = $file;
2918  }
2919  }
2920 
2921  return $newBatch;
2922  }
2923 
2929  public function cleanup() {
2930  if ( !$this->cleanupBatch ) {
2931  return $this->file->repo->newGood();
2932  }
2933 
2934  $this->cleanupBatch = $this->removeNonexistentFromCleanup( $this->cleanupBatch );
2935 
2936  $status = $this->file->repo->cleanupDeletedBatch( $this->cleanupBatch );
2937 
2938  return $status;
2939  }
2940 
2948  protected function cleanupFailedBatch( $storeStatus, $storeBatch ) {
2949  $cleanupBatch = [];
2950 
2951  foreach ( $storeStatus->success as $i => $success ) {
2952  // Check if this item of the batch was successfully copied
2953  if ( $success ) {
2954  // Item was successfully copied and needs to be removed again
2955  // Extract ($dstZone, $dstRel) from the batch
2956  $cleanupBatch[] = [ $storeBatch[$i][1], $storeBatch[$i][2] ];
2957  }
2958  }
2959  $this->file->repo->cleanupBatch( $cleanupBatch );
2960  }
2961 }
2962 
2963 # ------------------------------------------------------------------------------
2964 
2971  protected $file;
2972 
2974  protected $target;
2975 
2976  protected $cur;
2977 
2978  protected $olds;
2979 
2980  protected $oldCount;
2981 
2982  protected $archive;
2983 
2985  protected $db;
2986 
2992  $this->file = $file;
2993  $this->target = $target;
2994  $this->oldHash = $this->file->repo->getHashPath( $this->file->getName() );
2995  $this->newHash = $this->file->repo->getHashPath( $this->target->getDBkey() );
2996  $this->oldName = $this->file->getName();
2997  $this->newName = $this->file->repo->getNameFromTitle( $this->target );
2998  $this->oldRel = $this->oldHash . $this->oldName;
2999  $this->newRel = $this->newHash . $this->newName;
3000  $this->db = $file->getRepo()->getMasterDB();
3001  }
3002 
3006  public function addCurrent() {
3007  $this->cur = [ $this->oldRel, $this->newRel ];
3008  }
3009 
3014  public function addOlds() {
3015  $archiveBase = 'archive';
3016  $this->olds = [];
3017  $this->oldCount = 0;
3018  $archiveNames = [];
3019 
3020  $result = $this->db->select( 'oldimage',
3021  [ 'oi_archive_name', 'oi_deleted' ],
3022  [ 'oi_name' => $this->oldName ],
3023  __METHOD__,
3024  [ 'LOCK IN SHARE MODE' ] // ignore snapshot
3025  );
3026 
3027  foreach ( $result as $row ) {
3028  $archiveNames[] = $row->oi_archive_name;
3029  $oldName = $row->oi_archive_name;
3030  $bits = explode( '!', $oldName, 2 );
3031 
3032  if ( count( $bits ) != 2 ) {
3033  wfDebug( "Old file name missing !: '$oldName' \n" );
3034  continue;
3035  }
3036 
3037  list( $timestamp, $filename ) = $bits;
3038 
3039  if ( $this->oldName != $filename ) {
3040  wfDebug( "Old file name doesn't match: '$oldName' \n" );
3041  continue;
3042  }
3043 
3044  $this->oldCount++;
3045 
3046  // Do we want to add those to oldCount?
3047  if ( $row->oi_deleted & File::DELETED_FILE ) {
3048  continue;
3049  }
3050 
3051  $this->olds[] = [
3052  "{$archiveBase}/{$this->oldHash}{$oldName}",
3053  "{$archiveBase}/{$this->newHash}{$timestamp}!{$this->newName}"
3054  ];
3055  }
3056 
3057  return $archiveNames;
3058  }
3059 
3064  public function execute() {
3065  $repo = $this->file->repo;
3066  $status = $repo->newGood();
3067  $destFile = wfLocalFile( $this->target );
3068 
3069  $this->file->lock(); // begin
3070  $destFile->lock(); // quickly fail if destination is not available
3071 
3072  $triplets = $this->getMoveTriplets();
3073  $checkStatus = $this->removeNonexistentFiles( $triplets );
3074  if ( !$checkStatus->isGood() ) {
3075  $destFile->unlock();
3076  $this->file->unlock();
3077  $status->merge( $checkStatus ); // couldn't talk to file backend
3078  return $status;
3079  }
3080  $triplets = $checkStatus->value;
3081 
3082  // Verify the file versions metadata in the DB.
3083  $statusDb = $this->verifyDBUpdates();
3084  if ( !$statusDb->isGood() ) {
3085  $destFile->unlock();
3086  $this->file->unlock();
3087  $statusDb->setOK( false );
3088 
3089  return $statusDb;
3090  }
3091 
3092  if ( !$repo->hasSha1Storage() ) {
3093  // Copy the files into their new location.
3094  // If a prior process fataled copying or cleaning up files we tolerate any
3095  // of the existing files if they are identical to the ones being stored.
3096  $statusMove = $repo->storeBatch( $triplets, FileRepo::OVERWRITE_SAME );
3097  wfDebugLog( 'imagemove', "Moved files for {$this->file->getName()}: " .
3098  "{$statusMove->successCount} successes, {$statusMove->failCount} failures" );
3099  if ( !$statusMove->isGood() ) {
3100  // Delete any files copied over (while the destination is still locked)
3101  $this->cleanupTarget( $triplets );
3102  $destFile->unlock();
3103  $this->file->unlock();
3104  wfDebugLog( 'imagemove', "Error in moving files: "
3105  . $statusMove->getWikiText( false, false, 'en' ) );
3106  $statusMove->setOK( false );
3107 
3108  return $statusMove;
3109  }
3110  $status->merge( $statusMove );
3111  }
3112 
3113  // Rename the file versions metadata in the DB.
3114  $this->doDBUpdates();
3115 
3116  wfDebugLog( 'imagemove', "Renamed {$this->file->getName()} in database: " .
3117  "{$statusDb->successCount} successes, {$statusDb->failCount} failures" );
3118 
3119  $destFile->unlock();
3120  $this->file->unlock(); // done
3121 
3122  // Everything went ok, remove the source files
3123  $this->cleanupSource( $triplets );
3124 
3125  $status->merge( $statusDb );
3126 
3127  return $status;
3128  }
3129 
3136  protected function verifyDBUpdates() {
3137  $repo = $this->file->repo;
3138  $status = $repo->newGood();
3139  $dbw = $this->db;
3140 
3141  $hasCurrent = $dbw->selectField(
3142  'image',
3143  '1',
3144  [ 'img_name' => $this->oldName ],
3145  __METHOD__,
3146  [ 'FOR UPDATE' ]
3147  );
3148  $oldRowCount = $dbw->selectField(
3149  'oldimage',
3150  'COUNT(*)',
3151  [ 'oi_name' => $this->oldName ],
3152  __METHOD__,
3153  [ 'FOR UPDATE' ]
3154  );
3155 
3156  if ( $hasCurrent ) {
3157  $status->successCount++;
3158  } else {
3159  $status->failCount++;
3160  }
3161  $status->successCount += $oldRowCount;
3162  // T36934: oldCount is based on files that actually exist.
3163  // There may be more DB rows than such files, in which case $affected
3164  // can be greater than $total. We use max() to avoid negatives here.
3165  $status->failCount += max( 0, $this->oldCount - $oldRowCount );
3166  if ( $status->failCount ) {
3167  $status->error( 'imageinvalidfilename' );
3168  }
3169 
3170  return $status;
3171  }
3172 
3177  protected function doDBUpdates() {
3178  $dbw = $this->db;
3179 
3180  // Update current image
3181  $dbw->update(
3182  'image',
3183  [ 'img_name' => $this->newName ],
3184  [ 'img_name' => $this->oldName ],
3185  __METHOD__
3186  );
3187  // Update old images
3188  $dbw->update(
3189  'oldimage',
3190  [
3191  'oi_name' => $this->newName,
3192  'oi_archive_name = ' . $dbw->strreplace( 'oi_archive_name',
3193  $dbw->addQuotes( $this->oldName ), $dbw->addQuotes( $this->newName ) ),
3194  ],
3195  [ 'oi_name' => $this->oldName ],
3196  __METHOD__
3197  );
3198  }
3199 
3204  protected function getMoveTriplets() {
3205  $moves = array_merge( [ $this->cur ], $this->olds );
3206  $triplets = []; // The format is: (srcUrl, destZone, destUrl)
3207 
3208  foreach ( $moves as $move ) {
3209  // $move: (oldRelativePath, newRelativePath)
3210  $srcUrl = $this->file->repo->getVirtualUrl() . '/public/' . rawurlencode( $move[0] );
3211  $triplets[] = [ $srcUrl, 'public', $move[1] ];
3212  wfDebugLog(
3213  'imagemove',
3214  "Generated move triplet for {$this->file->getName()}: {$srcUrl} :: public :: {$move[1]}"
3215  );
3216  }
3217 
3218  return $triplets;
3219  }
3220 
3226  protected function removeNonexistentFiles( $triplets ) {
3227  $files = [];
3228 
3229  foreach ( $triplets as $file ) {
3230  $files[$file[0]] = $file[0];
3231  }
3232 
3233  $result = $this->file->repo->fileExistsBatch( $files );
3234  if ( in_array( null, $result, true ) ) {
3235  return Status::newFatal( 'backend-fail-internal',
3236  $this->file->repo->getBackend()->getName() );
3237  }
3238 
3239  $filteredTriplets = [];
3240  foreach ( $triplets as $file ) {
3241  if ( $result[$file[0]] ) {
3242  $filteredTriplets[] = $file;
3243  } else {
3244  wfDebugLog( 'imagemove', "File {$file[0]} does not exist" );
3245  }
3246  }
3247 
3248  return Status::newGood( $filteredTriplets );
3249  }
3250 
3256  protected function cleanupTarget( $triplets ) {
3257  // Create dest pairs from the triplets
3258  $pairs = [];
3259  foreach ( $triplets as $triplet ) {
3260  // $triplet: (old source virtual URL, dst zone, dest rel)
3261  $pairs[] = [ $triplet[1], $triplet[2] ];
3262  }
3263 
3264  $this->file->repo->cleanupBatch( $pairs );
3265  }
3266 
3272  protected function cleanupSource( $triplets ) {
3273  // Create source file names from the triplets
3274  $files = [];
3275  foreach ( $triplets as $triplet ) {
3276  $files[] = $triplet[0];
3277  }
3278 
3279  $this->file->repo->cleanupBatch( $files );
3280  }
3281 }
3282 
3284  public function __construct( Status $status ) {
3285  parent::__construct(
3286  'actionfailed',
3287  $status->getMessage()
3288  );
3289  }
3290 
3291  public function report() {
3292  global $wgOut;
3293  $wgOut->setStatusCode( 429 );
3294  parent::report();
3295  }
3296 }
LocalFileDeleteBatch\$reason
string $reason
Definition: LocalFile.php:2149
LocalFileMoveBatch\$target
Title $target
Definition: LocalFile.php:2974
LocalFile\$media_type
string $media_type
MEDIATYPE_xxx (bitmap, drawing, audio...)
Definition: LocalFile.php:63
LocalFile\getSha1
getSha1()
Definition: LocalFile.php:2004
ParserOptions
Set options of the Parser.
Definition: ParserOptions.php:40
LocalFile\ATOMIC_SECTION_LOCK
const ATOMIC_SECTION_LOCK
Definition: LocalFile.php:134
LocalFile\$fileExists
bool $fileExists
Does the file exist on disk? (loadFromXxx)
Definition: LocalFile.php:51
File\getPath
getPath()
Return the storage path to the file.
Definition: File.php:417
$wgUpdateCompatibleMetadata
$wgUpdateCompatibleMetadata
If to automatically update the img_metadata field if the metadata field is outdated but compatible wi...
Definition: DefaultSettings.php:676
SpecialUpload\getInitialPageText
static getInitialPageText( $comment='', $license='', $copyStatus='', $source='', Config $config=null)
Get the initial image page text based on a comment and optional file status information.
Definition: SpecialUpload.php:593
LocalFile\maybeUpgradeRow
maybeUpgradeRow()
Upgrade a row if it needs it.
Definition: LocalFile.php:575
Wikimedia\Rdbms\Database
Relational database abstraction object.
Definition: Database.php:45
LocalFileRestoreBatch
Helper class for file undeletion.
Definition: LocalFile.php:2561
LocalFileDeleteBatch\$deletionBatch
array $deletionBatch
Items to be processed in the deletion batch.
Definition: LocalFile.php:2158
$wgUser
$wgUser
Definition: Setup.php:809
LocalFile\getMutableCacheKeys
getMutableCacheKeys(WANObjectCache $cache)
Definition: LocalFile.php:250
FileRepo\getReadOnlyReason
getReadOnlyReason()
Get an explanatory message if this repo is read-only.
Definition: FileRepo.php:225
LocalFile\unprefixRow
unprefixRow( $row, $prefix='img_')
Definition: LocalFile.php:474
File\$repo
FileRepo LocalRepo ForeignAPIRepo bool $repo
Some member variables can be lazy-initialised using __get().
Definition: File.php:96
LocalFile\$width
int $width
Image width.
Definition: LocalFile.php:54
file
We ve cleaned up the code here by removing clumps of infrequently used code and moving them off somewhere else It s much easier for someone working with this code to see what s _really_ going and make changes or fix bugs In we can take all the code that deals with the little used title reversing we can concentrate it all in an extension file
Definition: hooks.txt:91
File\getArchiveThumbPath
getArchiveThumbPath( $archiveName, $suffix=false)
Get the path of an archived file's thumbs, or a particular thumb if $suffix is specified.
Definition: File.php:1597
RepoGroup\singleton
static singleton()
Get a RepoGroup instance.
Definition: RepoGroup.php:59
Revision\SUPPRESSED_ALL
const SUPPRESSED_ALL
Definition: Revision.php:95
false
processing should stop and the error should be shown to the user * false
Definition: hooks.txt:187
LocalFile\__construct
__construct( $title, $repo)
Do not call this except from inside a repo class.
Definition: LocalFile.php:223
LocalFile\unlock
unlock()
Decrement the lock reference count and end the atomic section if it reaches zero.
Definition: LocalFile.php:2111
$tables
this hook is for auditing only RecentChangesLinked and Watchlist 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:988
LocalFile\getTimestamp
getTimestamp()
Definition: LocalFile.php:1976
LocalFileMoveBatch\$file
LocalFile $file
Definition: LocalFile.php:2971
LocalFileDeleteBatch\addOld
addOld( $oldName)
Definition: LocalFile.php:2195
File\isMultipage
isMultipage()
Returns 'true' if this file is a type which supports multiple pages, e.g.
Definition: File.php:1958
LocalFile\getUser
getUser( $type='text')
Returns ID or name of user who uploaded the file.
Definition: LocalFile.php:786
FileRepo\OVERWRITE_SAME
const OVERWRITE_SAME
Definition: FileRepo.php:40
LocalFileDeleteBatch\doDBDeletes
doDBDeletes()
Definition: LocalFile.php:2443
LocalFile\loadFromDB
loadFromDB( $flags=0)
Load file metadata from the DB.
Definition: LocalFile.php:395
$lang
if(!isset( $args[0])) $lang
Definition: testCompression.php:33
LocalFile\purgeThumbList
purgeThumbList( $dir, $files)
Delete a list of thumbnails visible at urls.
Definition: LocalFile.php:1030
File\getRel
getRel()
Get the path of the file relative to the public zone root.
Definition: File.php:1511
LocalFileMoveBatch\cleanupSource
cleanupSource( $triplets)
Cleanup a fully moved array of triplets by deleting the source files.
Definition: LocalFile.php:3272
AutoCommitUpdate
Deferrable Update for closure/callback updates that should use auto-commit mode.
Definition: AutoCommitUpdate.php:9
captcha-old.count
count
Definition: captcha-old.py:249
LocalFile\$missing
bool $missing
True if file is not present in file system.
Definition: LocalFile.php:129
LocalFileDeleteBatch
Helper class for file deletion.
Definition: LocalFile.php:2144
$wgCommentTableSchemaMigrationStage
int $wgCommentTableSchemaMigrationStage
Comment table schema migration stage.
Definition: DefaultSettings.php:8766
MediaHandler\filterThumbnailPurgeList
filterThumbnailPurgeList(&$files, $options)
Remove files from the purge list.
Definition: MediaHandler.php:721
LocalFileRestoreBatch\execute
execute()
Run the transaction, except the cleanup batch.
Definition: LocalFile.php:2619
$result
The index of the header message $result[1]=The index of the body text message $result[2 through n]=Parameters passed to body text message. Please note the header message cannot receive/use parameters. 'ImportHandleLogItemXMLTag':When parsing a XML tag in a log item. Return false to stop further processing of the tag $reader:XMLReader object $logInfo:Array of information 'ImportHandlePageXMLTag':When parsing a XML tag in a page. Return false to stop further processing of the tag $reader:XMLReader object & $pageInfo:Array of information 'ImportHandleRevisionXMLTag':When parsing a XML tag in a page revision. Return false to stop further processing of the tag $reader:XMLReader object $pageInfo:Array of page information $revisionInfo:Array of revision information 'ImportHandleToplevelXMLTag':When parsing a top level XML tag. Return false to stop further processing of the tag $reader:XMLReader object 'ImportHandleUploadXMLTag':When parsing a XML tag in a file upload. Return false to stop further processing of the tag $reader:XMLReader object $revisionInfo:Array of information 'ImportLogInterwikiLink':Hook to change the interwiki link used in log entries and edit summaries for transwiki imports. & $fullInterwikiPrefix:Interwiki prefix, may contain colons. & $pageTitle:String that contains page title. 'ImportSources':Called when reading from the $wgImportSources configuration variable. Can be used to lazy-load the import sources list. & $importSources:The value of $wgImportSources. Modify as necessary. See the comment in DefaultSettings.php for the detail of how to structure this array. 'InfoAction':When building information to display on the action=info page. $context:IContextSource object & $pageInfo:Array of information 'InitializeArticleMaybeRedirect':MediaWiki check to see if title is a redirect. & $title:Title object for the current page & $request:WebRequest & $ignoreRedirect:boolean to skip redirect check & $target:Title/string of redirect target & $article:Article object 'InternalParseBeforeLinks':during Parser 's internalParse method before links but after nowiki/noinclude/includeonly/onlyinclude and other processings. & $parser:Parser object & $text:string containing partially parsed text & $stripState:Parser 's internal StripState object 'InternalParseBeforeSanitize':during Parser 's internalParse method just before the parser removes unwanted/dangerous HTML tags and after nowiki/noinclude/includeonly/onlyinclude and other processings. Ideal for syntax-extensions after template/parser function execution which respect nowiki and HTML-comments. & $parser:Parser object & $text:string containing partially parsed text & $stripState:Parser 's internal StripState object 'InterwikiLoadPrefix':When resolving if a given prefix is an interwiki or not. Return true without providing an interwiki to continue interwiki search. $prefix:interwiki prefix we are looking for. & $iwData:output array describing the interwiki with keys iw_url, iw_local, iw_trans and optionally iw_api and iw_wikiid. 'InvalidateEmailComplete':Called after a user 's email has been invalidated successfully. $user:user(object) whose email is being invalidated 'IRCLineURL':When constructing the URL to use in an IRC notification. Callee may modify $url and $query, URL will be constructed as $url . $query & $url:URL to index.php & $query:Query string $rc:RecentChange object that triggered url generation 'IsFileCacheable':Override the result of Article::isFileCacheable()(if true) & $article:article(object) being checked 'IsTrustedProxy':Override the result of IP::isTrustedProxy() & $ip:IP being check & $result:Change this value to override the result of IP::isTrustedProxy() 'IsUploadAllowedFromUrl':Override the result of UploadFromUrl::isAllowedUrl() $url:URL used to upload from & $allowed:Boolean indicating if uploading is allowed for given URL 'isValidEmailAddr':Override the result of Sanitizer::validateEmail(), for instance to return false if the domain name doesn 't match your organization. $addr:The e-mail address entered by the user & $result:Set this and return false to override the internal checks 'isValidPassword':Override the result of User::isValidPassword() $password:The password entered by the user & $result:Set this and return false to override the internal checks $user:User the password is being validated for 'Language::getMessagesFileName':$code:The language code or the language we 're looking for a messages file for & $file:The messages file path, you can override this to change the location. 'LanguageGetMagic':DEPRECATED! Use $magicWords in a file listed in $wgExtensionMessagesFiles instead. Use this to define synonyms of magic words depending of the language & $magicExtensions:associative array of magic words synonyms $lang:language code(string) 'LanguageGetNamespaces':Provide custom ordering for namespaces or remove namespaces. Do not use this hook to add namespaces. Use CanonicalNamespaces for that. & $namespaces:Array of namespaces indexed by their numbers 'LanguageGetSpecialPageAliases':DEPRECATED! Use $specialPageAliases in a file listed in $wgExtensionMessagesFiles instead. Use to define aliases of special pages names depending of the language & $specialPageAliases:associative array of magic words synonyms $lang:language code(string) 'LanguageGetTranslatedLanguageNames':Provide translated language names. & $names:array of language code=> language name $code:language of the preferred translations 'LanguageLinks':Manipulate a page 's language links. This is called in various places to allow extensions to define the effective language links for a page. $title:The page 's Title. & $links:Array with elements of the form "language:title" in the order that they will be output. & $linkFlags:Associative array mapping prefixed links to arrays of flags. Currently unused, but planned to provide support for marking individual language links in the UI, e.g. for featured articles. 'LanguageSelector':Hook to change the language selector available on a page. $out:The output page. $cssClassName:CSS class name of the language selector. 'LinkBegin':DEPRECATED! Use HtmlPageLinkRendererBegin instead. Used when generating internal and interwiki links in Linker::link(), before processing starts. Return false to skip default processing and return $ret. See documentation for Linker::link() for details on the expected meanings of parameters. $skin:the Skin object $target:the Title that the link is pointing to & $html:the contents that the< a > tag should have(raw HTML) $result
Definition: hooks.txt:1963
LocalFileRestoreBatch\addId
addId( $fa_id)
Add a file by ID.
Definition: LocalFile.php:2592
wfTimestamp
wfTimestamp( $outputtype=TS_UNIX, $ts=0)
Get a timestamp string in one of various formats.
Definition: GlobalFunctions.php:2040
LinksUpdate\queueRecursiveJobsForTable
static queueRecursiveJobsForTable(Title $title, $table)
Queue a RefreshLinks job for any table.
Definition: LinksUpdate.php:334
LocalFileMoveBatch\verifyDBUpdates
verifyDBUpdates()
Verify the database updates and return a new Status indicating how many rows would be updated.
Definition: LocalFile.php:3136
FileBackendError
File backend exception for checked exceptions (e.g.
Definition: FileBackendError.php:8
LocalFile\$upgraded
bool $upgraded
Whether the row was upgraded on load.
Definition: LocalFile.php:117
LocalFile\getDescriptionShortUrl
getDescriptionShortUrl()
Get short description URL for a file based on the page ID.
Definition: LocalFile.php:803
$status
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. '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 'DatabaseOraclePostInit':Called after initialising an Oracle database $db:the DatabaseOracle object '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). '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:1245
LocalFile\getCacheFields
getCacheFields( $prefix='img_')
Definition: LocalFile.php:347
MIGRATION_NEW
const MIGRATION_NEW
Definition: Defines.php:296
LocalFileRestoreBatch\$all
bool $all
Add all revisions of the file.
Definition: LocalFile.php:2572
use
as see the revision history and available at free of to any person obtaining a copy of this software and associated documentation to deal in the Software without including without limitation the rights to use
Definition: MIT-LICENSE.txt:10
LocalFile\$user
int $user
User ID of uploader.
Definition: LocalFile.php:105
LocalFileMoveBatch\__construct
__construct(File $file, Title $target)
Definition: LocalFile.php:2991
DeferredUpdates\addUpdate
static addUpdate(DeferrableUpdate $update, $stage=self::POSTSEND)
Add an update to the deferred list to be run later by execute()
Definition: DeferredUpdates.php:76
LocalFileDeleteBatch\execute
execute()
Run the transaction.
Definition: LocalFile.php:2471
StatusValue\newFatal
static newFatal( $message)
Factory function for fatal errors.
Definition: StatusValue.php:68
LocalFile\getHistory
getHistory( $limit=null, $start=null, $end=null, $inc=true)
purgeDescription inherited
Definition: LocalFile.php:1070
$fname
if(!defined( 'MEDIAWIKI')) $fname
This file is not a valid entry point, perform no further processing unless MEDIAWIKI is defined.
Definition: Setup.php:36
LocalFile\getMediaType
getMediaType()
Returns the type of the media in the file.
Definition: LocalFile.php:858
File\getUrl
getUrl()
Return the URL of the file.
Definition: File.php:348
LocalFile\$dataLoaded
bool $dataLoaded
Whether or not core data has been loaded from the database (loadFromXxx)
Definition: LocalFile.php:78
NS_FILE
const NS_FILE
Definition: Defines.php:71
CommentStore
CommentStore handles storage of comments (edit summaries, log reasons, etc) in the database.
Definition: CommentStore.php:30
MIGRATION_WRITE_BOTH
const MIGRATION_WRITE_BOTH
Definition: Defines.php:294
LocalFile\readOnlyFatalStatus
readOnlyFatalStatus()
Definition: LocalFile.php:2125
LocalFile\newFromTitle
static newFromTitle( $title, $repo, $unused=null)
Create a LocalFile from a title Do not call this except from inside a repo class.
Definition: LocalFile.php:148
serialize
serialize()
Definition: ApiMessage.php:177
wfReadOnly
wfReadOnly()
Check whether the wiki is in read-only mode.
Definition: GlobalFunctions.php:1324
RequestContext\newExtraneousContext
static newExtraneousContext(Title $title, $request=[])
Create a new extraneous context.
Definition: RequestContext.php:640
LocalFile\$upgrading
bool $upgrading
Whether the row was scheduled to upgrade on load.
Definition: LocalFile.php:120
LocalFile\getSize
getSize()
Returns the size of the image file, in bytes.
Definition: LocalFile.php:837
OldLocalFile\selectFields
static selectFields()
Fields in the oldimage table.
Definition: OldLocalFile.php:110
LocalFile\purgeOldThumbnails
purgeOldThumbnails( $archiveName)
Delete cached transformed files for an archived version only.
Definition: LocalFile.php:947
$res
$res
Definition: database.txt:21
LocalFile\$historyRes
int $historyRes
Result of the query for the file's history (nextHistoryLine)
Definition: LocalFile.php:93
$name
Allows to change the fields on the form that will be generated $name
Definition: hooks.txt:302
LocalFile\getThumbnails
getThumbnails( $archiveName=false)
getTransformScript inherited
Definition: LocalFile.php:895
LocalFile\$sha1
string $sha1
SHA-1 base 36 content hash.
Definition: LocalFile.php:75
File\splitMime
static splitMime( $mime)
Split an internet media type into its two components; if not a two-part name, set the minor type to '...
Definition: File.php:273
LocalFileRestoreBatch\$file
LocalFile $file
Definition: LocalFile.php:2563
$success
$success
Definition: NoLocalSettings.php:44
Wikimedia\Rdbms\IDatabase\selectField
selectField( $table, $var, $cond='', $fname=__METHOD__, $options=[], $join_conds=[])
A SELECT wrapper which returns a single field from a single result row.
LocalFileRestoreBatch\__construct
__construct(File $file, $unsuppress=false)
Definition: LocalFile.php:2581
CommentStore\newKey
static newKey( $key)
Static constructor for easier chaining.
Definition: CommentStore.php:114
LocalFile\$minor_mime
string $minor_mime
Minor MIME type.
Definition: LocalFile.php:99
File\isDeleted
isDeleted( $field)
Is this file a "deleted" file in a private archive? STUB.
Definition: File.php:1874
wfDebugLog
wfDebugLog( $logGroup, $text, $dest='all', array $context=[])
Send a line to a supplementary debug log file, if configured, or main debug log if not.
Definition: GlobalFunctions.php:1140
LocalFileDeleteBatch\$status
Status $status
Definition: LocalFile.php:2164
LocalRepo\getHashFromKey
static getHashFromKey( $key)
Gets the SHA1 hash from a storage key.
Definition: LocalRepo.php:181
php
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
LocalFile\acquireFileLock
acquireFileLock()
Definition: LocalFile.php:2041
Wikimedia\Rdbms\IDatabase
Basic database interface for live and lazy-loaded relation database handles.
Definition: IDatabase.php:40
LocalFile\isCacheable
isCacheable()
Definition: LocalFile.php:2029
FileRepo\publish
publish( $src, $dstRel, $archiveRel, $flags=0, array $options=[])
Copy or move a file either from a storage path, virtual URL, or file system path, into this repositor...
Definition: FileRepo.php:1161
Status
Generic operation result class Has warning/error list, boolean status and arbitrary value.
Definition: Status.php:40
LocalFileMoveBatch\$oldCount
$oldCount
Definition: LocalFile.php:2980
LocalFileMoveBatch\addOlds
addOlds()
Add the old versions of the image to the batch.
Definition: LocalFile.php:3014
key
design txt This is a brief overview of the new design More thorough and up to date information is available on the documentation wiki at etc Handles the details of getting and saving to the user table of the and dealing with sessions and cookies OutputPage Encapsulates the entire HTML page that will be sent in response to any server request It is used by calling its functions to add in any and then calling but I prefer the flexibility This should also do the output encoding The system allocates a global one in $wgOut Title Represents the title of an and does all the work of translating among various forms such as plain database key
Definition: design.txt:25
LocalFile\decodeRow
decodeRow( $row, $prefix='img_')
Decode a row from the database (either object or array) to an array with timestamps and MIME types de...
Definition: LocalFile.php:499
LocalFile\$bits
int $bits
Returned by getimagesize (loadFromXxx)
Definition: LocalFile.php:60
title
to move a page</td >< td > &*You are moving the page across *A non empty talk page already exists under the new or *You uncheck the box below In those you will have to move or merge the page manually if desired</td >< td > be sure to &You are responsible for making sure that links continue to point where they are supposed to go Note that the page will &a page at the new title
Definition: All_system_messages.txt:2696
Revision\newFromTitle
static newFromTitle(LinkTarget $linkTarget, $id=0, $flags=0)
Load either the current, or a specified, revision that's attached to a given link target.
Definition: Revision.php:134
LocalFileRestoreBatch\cleanupFailedBatch
cleanupFailedBatch( $storeStatus, $storeBatch)
Cleanup a failed batch.
Definition: LocalFile.php:2948
DeferredUpdates\addCallableUpdate
static addCallableUpdate( $callable, $stage=self::POSTSEND, IDatabase $dbw=null)
Add a callable update.
Definition: DeferredUpdates.php:111
LocalFile\purgeThumbnails
purgeThumbnails( $options=[])
Delete cached transformed files for the current version only.
Definition: LocalFile.php:970
LocalFileDeleteBatch\addCurrent
addCurrent()
Definition: LocalFile.php:2188
FileRepo\hasSha1Storage
hasSha1Storage()
Returns whether or not storage is SHA-1 based.
Definition: FileRepo.php:1925
MediaHandler\METADATA_COMPATIBLE
const METADATA_COMPATIBLE
Definition: MediaHandler.php:34
File
Implements some public methods and some protected utility functions which are required by multiple ch...
Definition: File.php:51
LocalFile\loadExtraFromDB
loadExtraFromDB()
Load lazy file metadata from the DB.
Definition: LocalFile.php:420
LocalFile\publishTo
publishTo( $src, $dstRel, $flags=0, array $options=[])
Move or copy a file to a specified location.
Definition: LocalFile.php:1681
File\$url
string $url
The URL corresponding to one of the four basic zones.
Definition: File.php:117
MWException
MediaWiki exception.
Definition: MWException.php:26
LocalFile\newFromRow
static newFromRow( $row, $repo)
Create a LocalFile from a title Do not call this except from inside a repo class.
Definition: LocalFile.php:161
LocalFile\getDescriptionTouched
getDescriptionTouched()
Definition: LocalFile.php:1985
user
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
Definition: distributors.txt:9
LocalFile\purgeMetadataCache
purgeMetadataCache()
Refresh metadata in memcached, but don't touch thumbnails or CDN.
Definition: LocalFile.php:918
File\getThumbPath
getThumbPath( $suffix=false)
Get the path of the thumbnail directory, or a particular file if $suffix is specified.
Definition: File.php:1610
FileRepo\quickImport
quickImport( $src, $dst, $options=null)
Import a file from the local file system into the repo.
Definition: FileRepo.php:957
LocalFile\getUpgraded
getUpgraded()
Definition: LocalFile.php:614
FileBackend\isStoragePath
static isStoragePath( $path)
Check if a given path is a "mwstore://" path.
Definition: FileBackend.php:1435
LocalFile\deleteOld
deleteOld( $archiveName, $reason, $suppress=false, $user=null)
Delete an old version of the file.
Definition: LocalFile.php:1859
LocalFileLockError\__construct
__construct(Status $status)
Definition: LocalFile.php:3284
LocalFile\CACHE_FIELD_MAX_LEN
const CACHE_FIELD_MAX_LEN
Definition: LocalFile.php:48
LocalFileLockError\report
report()
Output a report about the exception and takes care of formatting.
Definition: LocalFile.php:3291
wfGetDB
wfGetDB( $db, $groups=[], $wiki=false)
Get a Database object.
Definition: GlobalFunctions.php:2856
LocalFile\loadFromRow
loadFromRow( $row, $prefix='img_')
Load file metadata from a DB result row.
Definition: LocalFile.php:535
LocalFile\__destruct
__destruct()
Clean up any dangling locks.
Definition: LocalFile.php:2133
LocalFile\$deleted
int $deleted
Bitfield akin to rev_deleted.
Definition: LocalFile.php:84
$wgUploadThumbnailRenderMap
$wgUploadThumbnailRenderMap
When defined, is an array of thumbnail widths to be rendered at upload time.
Definition: DefaultSettings.php:1422
FSFile\getSha1Base36FromPath
static getSha1Base36FromPath( $path)
Get a SHA-1 hash of a file in the local filesystem, in base-36 lower case encoding,...
Definition: FSFile.php:218
LocalFile\loadFieldsWithTimestamp
loadFieldsWithTimestamp( $dbr, $fname)
Definition: LocalFile.php:445
LocalFileDeleteBatch\$file
LocalFile $file
Definition: LocalFile.php:2146
LocalFileDeleteBatch\$suppress
bool $suppress
Whether to suppress all suppressable fields when deleting.
Definition: LocalFile.php:2161
$wgLang
this class mediates it Skin Encapsulates a look and feel for the wiki All of the functions that render HTML and make choices about how to render it are here and are called from various other places when and is meant to be subclassed with other skins that may override some of its functions The User object contains a reference to a and so rather than having a global skin object we just rely on the global User and get the skin with $wgUser and also has some character encoding functions and other locale stuff The current user interface language is instantiated as $wgLang
Definition: design.txt:56
SiteStatsUpdate\factory
static factory(array $deltas)
Definition: SiteStatsUpdate.php:62
MWFileProps
MimeMagic helper wrapper.
Definition: MWFileProps.php:28
LocalFile\prerenderThumbnails
prerenderThumbnails()
Prerenders a configurable set of thumbnails.
Definition: LocalFile.php:1003
Title\makeTitle
static makeTitle( $ns, $title, $fragment='', $interwiki='')
Create a new Title from a namespace index and a DB key.
Definition: Title.php:529
global
when a variable name is used in a it is silently declared as a new masking the global
Definition: design.txt:93
LocalFileDeleteBatch\$user
User $user
Definition: LocalFile.php:2167
DB_REPLICA
const DB_REPLICA
Definition: defines.php:25
LocalFile\getCacheKey
getCacheKey()
Get the memcached key for the main data for this file, or false if there is no access to the shared c...
Definition: LocalFile.php:241
LocalFileMoveBatch\$db
IDatabase $db
Definition: LocalFile.php:2985
MimeMagic\singleton
static singleton()
Get an instance of this class.
Definition: MimeMagic.php:33
MediaHandler\getPageDimensions
getPageDimensions(File $image, $page)
Get an associative array of page dimensions Currently "width" and "height" are understood,...
Definition: MediaHandler.php:414
LocalFileRestoreBatch\addAll
addAll()
Add all revisions of the file.
Definition: LocalFile.php:2607
LocalFile\$mime
string $mime
MIME type, determined by MimeMagic::guessMimeType.
Definition: LocalFile.php:66
LocalFileRestoreBatch\$unsuppress
bool $unsuppress
Whether to remove all settings for suppressed fields.
Definition: LocalFile.php:2575
wfDebug
wfDebug( $text, $dest='all', array $context=[])
Sends a line to the debug log if enabled or, optionally, to a comment in output.
Definition: GlobalFunctions.php:1047
LocalFile\setProps
setProps( $info)
Set properties in this object to be equal to those given in the associative array $info.
Definition: LocalFile.php:676
FileRepo\getFileSha1
getFileSha1( $virtualUrl)
Get the sha1 (base 36) of a file with a given virtual URL/storage path.
Definition: FileRepo.php:1586
list
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
LocalFile
Class to represent a local file in the wiki's own database.
Definition: LocalFile.php:45
$dir
$dir
Definition: Autoload.php:8
ContentHandler\makeContent
static makeContent( $text, Title $title=null, $modelId=null, $format=null)
Convenience function for creating a Content object from a given textual representation.
Definition: ContentHandler.php:129
LocalFileMoveBatch\$archive
$archive
Definition: LocalFile.php:2982
LocalFile\getMimeType
getMimeType()
Returns the MIME type of the file.
Definition: LocalFile.php:847
LocalFile\getDescription
getDescription( $audience=self::FOR_PUBLIC, User $user=null)
Definition: LocalFile.php:1960
MIGRATION_OLD
const MIGRATION_OLD
Definition: Defines.php:293
LocalFile\$extraDataLoaded
bool $extraDataLoaded
Whether or not lazy-loaded data has been loaded from the database.
Definition: LocalFile.php:81
ThumbnailRenderJob
Job for asynchronous rendering of thumbnails.
Definition: ThumbnailRenderJob.php:29
LocalFile\$size
int $size
Size in bytes (loadFromXxx)
Definition: LocalFile.php:69
HTMLCacheUpdate
Class to invalidate the HTML cache of all the pages linking to a given title.
Definition: HTMLCacheUpdate.php:29
$e
div flags Integer display flags(NO_ACTION_LINK, NO_EXTRA_USER_LINKS) 'LogException' returning false will NOT prevent logging $e
Definition: hooks.txt:2141
File\purgeDescription
purgeDescription()
Purge the file description page, but don't go after pages using the file.
Definition: File.php:1428
LocalFileDeleteBatch\getHashes
getHashes()
Definition: LocalFile.php:2241
LocalFile\LOAD_ALL
const LOAD_ALL
Definition: LocalFile.php:132
$value
$value
Definition: styleTest.css.php:45
LocalFile\lock
lock()
Start an atomic DB section and lock the image for update or increments a reference counter if the loc...
Definition: LocalFile.php:2066
File\$handler
MediaHandler $handler
Definition: File.php:114
LocalFileLockError
Definition: LocalFile.php:3283
CdnCacheUpdate
Handles purging appropriate CDN URLs given a title (or titles)
Definition: CdnCacheUpdate.php:31
File\assertTitleDefined
assertTitleDefined()
Assert that $this->title is set to a Title.
Definition: File.php:2276
StatusValue\newGood
static newGood( $value=null)
Factory function for good results.
Definition: StatusValue.php:81
LocalFile\$historyLine
int $historyLine
Number of line to return by nextHistoryLine() (constructor)
Definition: LocalFile.php:90
LocalFileMoveBatch\getMoveTriplets
getMoveTriplets()
Generate triplets for FileRepo::storeBatch().
Definition: LocalFile.php:3204
LocalFile\releaseFileLock
releaseFileLock()
Definition: LocalFile.php:2051
LocalFile\upgradeRow
upgradeRow()
Fix assorted version-related problems with the image row by reloading it from the file.
Definition: LocalFile.php:621
LocalFile\load
load( $flags=0)
Load file metadata from cache or DB, unless already loaded.
Definition: LocalFile.php:557
WANObjectCache
Multi-datacenter aware caching interface.
Definition: WANObjectCache.php:80
LocalFile\newFromKey
static newFromKey( $sha1, $repo, $timestamp=false)
Create a LocalFile from a SHA-1 key Do not call this except from inside a repo class.
Definition: LocalFile.php:178
LocalFile\nextHistoryLine
nextHistoryLine()
Returns the history of this file, line by line.
Definition: LocalFile.php:1123
LocalFileRestoreBatch\$cleanupBatch
array $cleanupBatch
List of file IDs to restore.
Definition: LocalFile.php:2566
File\$title
Title string bool $title
Definition: File.php:99
LocalFile\$metadata
string $metadata
Handler-specific metadata.
Definition: LocalFile.php:72
LocalFile\getBitDepth
getBitDepth()
Definition: LocalFile.php:827
LocalFile\$height
int $height
Image height.
Definition: LocalFile.php:57
File\getArchiveThumbUrl
getArchiveThumbUrl( $archiveName, $suffix=false)
Get the URL of the archived file's thumbs, or a particular thumb if $suffix is specified.
Definition: File.php:1654
File\getName
getName()
Return the name of this file.
Definition: File.php:297
File\getArchiveUrl
getArchiveUrl( $suffix=false)
Get the URL of the archive directory, or a particular file if $suffix is specified.
Definition: File.php:1634
Wikimedia\Rdbms\IDatabase\update
update( $table, $values, $conds, $fname=__METHOD__, $options=[])
UPDATE wrapper.
FSFile
Class representing a non-directory file on the file system.
Definition: FSFile.php:29
LocalFile\$timestamp
string $timestamp
Upload timestamp.
Definition: LocalFile.php:102
FileRepo\getBackend
getBackend()
Get the file backend instance.
Definition: FileRepo.php:215
LocalFile\$descriptionTouched
string $descriptionTouched
TS_MW timestamp of the last change of the file description.
Definition: LocalFile.php:114
File\DELETE_SOURCE
const DELETE_SOURCE
Definition: File.php:66
LocalFileDeleteBatch\$archiveUrls
array $archiveUrls
Definition: LocalFile.php:2155
LocalFileDeleteBatch\addOlds
addOlds()
Add the old versions of the image to the batch.
Definition: LocalFile.php:2204
LocalFile\recordUpload
recordUpload( $oldver, $desc, $license='', $copyStatus='', $source='', $watch=false, $timestamp=false, User $user=null)
Record a file upload in the upload log and the image table.
Definition: LocalFile.php:1268
LocalFile\recordUpload2
recordUpload2( $oldver, $comment, $pageText, $props=false, $timestamp=false, $user=null, $tags=[])
Record a file upload in the upload log and the image table.
Definition: LocalFile.php:1299
EDIT_NEW
const EDIT_NEW
Definition: Defines.php:153
LocalFileRestoreBatch\$ids
array $ids
List of file IDs to restore.
Definition: LocalFile.php:2569
LocalFile\$locked
bool $locked
True if the image row is locked.
Definition: LocalFile.php:123
LocalFile\$description
string $description
Description of current revision of the file.
Definition: LocalFile.php:111
LocalFile\getLazyCacheFields
getLazyCacheFields( $prefix='img_')
Definition: LocalFile.php:372
LocalFile\selectFields
static selectFields()
Fields in the image table.
Definition: LocalFile.php:200
LocalFile\$user_text
string $user_text
User name of uploader.
Definition: LocalFile.php:108
File\getTitle
getTitle()
Return the associated title object.
Definition: File.php:326
Title
Represents a title within MediaWiki.
Definition: Title.php:39
LocalFile\getMetadata
getMetadata()
Get handler-specific metadata.
Definition: LocalFile.php:819
reason
c Accompany it with the information you received as to the offer to distribute corresponding source complete source code means all the source code for all modules it plus any associated interface definition plus the scripts used to control compilation and installation of the executable as a special the source code distributed need not include anything that is normally and so on of the operating system on which the executable unless that component itself accompanies the executable If distribution of executable or object code is made by offering access to copy from a designated then offering equivalent access to copy the source code from the same place counts as distribution of the source even though third parties are not compelled to copy the source along with the object code You may not or distribute the Program except as expressly provided under this License Any attempt otherwise to sublicense or distribute the Program is and will automatically terminate your rights under this License parties who have received or from you under this License will not have their licenses terminated so long as such parties remain in full compliance You are not required to accept this since you have not signed it nothing else grants you permission to modify or distribute the Program or its derivative works These actions are prohibited by law if you do not accept this License by modifying or distributing the you indicate your acceptance of this License to do and all its terms and conditions for distributing or modifying the Program or works based on it Each time you redistribute the the recipient automatically receives a license from the original licensor to distribute or modify the Program subject to these terms and conditions You may not impose any further restrictions on the recipients exercise of the rights granted herein You are not responsible for enforcing compliance by third parties to this License as a consequence of a court judgment or allegation of patent infringement or for any other reason(not limited to patent issues)
LocalFileRestoreBatch\addIds
addIds( $ids)
Add a whole lot of files by ID.
Definition: LocalFile.php:2600
MediaHandler\getContentHeaders
getContentHeaders( $metadata)
Get useful response headers for GET/HEAD requests for a file with the given metadata.
Definition: MediaHandler.php:923
$dbr
if(! $regexes) $dbr
Definition: cleanup.php:94
$cache
$cache
Definition: mcc.php:33
LocalFile\loadFromFile
loadFromFile()
Load metadata from the file itself.
Definition: LocalFile.php:338
$options
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:1965
File\assertRepoDefined
assertRepoDefined()
Assert that $this->repo is set to a valid FileRepo instance.
Definition: File.php:2266
LocalFileMoveBatch\execute
execute()
Perform the move.
Definition: LocalFile.php:3064
ObjectCache\getMainWANInstance
static getMainWANInstance()
Get the main WAN cache object.
Definition: ObjectCache.php:370
$ext
$ext
Definition: NoLocalSettings.php:25
DeferredUpdates\PRESEND
const PRESEND
Definition: DeferredUpdates.php:60
LocalFile\getDescriptionText
getDescriptionText( $lang=null)
Get the HTML text of the description page This is not used by ImagePage for local files,...
Definition: LocalFile.php:1941
LocalFile\upload
upload( $src, $comment, $pageText, $flags=0, $props=false, $timestamp=false, $user=null, $tags=[])
getHashPath inherited
Definition: LocalFile.php:1199
LocalFile\resetHistory
resetHistory()
Reset the history pointer to the first element of the history.
Definition: LocalFile.php:1161
LocalFileMoveBatch\removeNonexistentFiles
removeNonexistentFiles( $triplets)
Removes non-existent files from move batch.
Definition: LocalFile.php:3226
LogEntryBase\makeParamBlob
static makeParamBlob( $params)
Create a blob from a parameter array.
Definition: LogEntry.php:142
LocalFileRestoreBatch\cleanup
cleanup()
Delete unused files in the deleted zone.
Definition: LocalFile.php:2929
JobQueueGroup\singleton
static singleton( $wiki=false)
Definition: JobQueueGroup.php:72
$rev
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:1750
MediaHandler\getHandler
static getHandler( $type)
Get a MediaHandler for a given MIME type from the instance cache.
Definition: MediaHandler.php:46
LocalFileDeleteBatch\$srcRels
array $srcRels
Definition: LocalFile.php:2152
as
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
LocalFileRestoreBatch\removeNonexistentFiles
removeNonexistentFiles( $triplets)
Removes non-existent files from a store batch.
Definition: LocalFile.php:2878
FileRepo\isVirtualUrl
static isVirtualUrl( $url)
Determine if a string is an mwrepo:// URL.
Definition: FileRepo.php:254
LocalFileMoveBatch
Helper class for file movement.
Definition: LocalFile.php:2969
MediaHandler\METADATA_BAD
const METADATA_BAD
Definition: MediaHandler.php:33
LoggerFactory
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
$source
$source
Definition: mwdoc-filter.php:46
$batch
$batch
Definition: linkcache.txt:23
LocalFileDeleteBatch\getOldRels
getOldRels()
Definition: LocalFile.php:2225
ManualLogEntry
Class for creating log entries manually, to inject them into the database.
Definition: LogEntry.php:400
width
width
Definition: parserTests.txt:163
Revision\newNullRevision
static newNullRevision( $dbw, $pageId, $summary, $minor, $user=null)
Create a new null-revision for insertion into a page's history.
Definition: Revision.php:1715
$hashes
$hashes
Definition: testCompression.php:64
FileRepo\DELETE_SOURCE
const DELETE_SOURCE
Definition: FileRepo.php:38
WikiFilePage
Special handling for file pages.
Definition: WikiFilePage.php:30
File\$name
string $name
The name of a file from its title object.
Definition: File.php:123
File\DELETED_FILE
const DELETED_FILE
Definition: File.php:53
LocalFile\publish
publish( $src, $flags=0, array $options=[])
Move or copy a file to its public location.
Definition: LocalFile.php:1662
name
design txt This is a brief overview of the new design More thorough and up to date information is available on the documentation wiki at name
Definition: design.txt:12
class
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
EDIT_SUPPRESS_RC
const EDIT_SUPPRESS_RC
Definition: Defines.php:156
File\getRepo
getRepo()
Returns the repository.
Definition: File.php:1853
LocalFile\getWidth
getWidth( $page=1)
Return the width of the image.
Definition: LocalFile.php:722
ArchivedFile\selectFields
static selectFields()
Fields in the filearchive table.
Definition: ArchivedFile.php:222
File\getThumbUrl
getThumbUrl( $suffix=false)
Get the URL of the thumbnail directory, or a particular file if $suffix is specified.
Definition: File.php:1692
LocalFile\$major_mime
string $major_mime
Major MIME type.
Definition: LocalFile.php:96
File\isVectorized
isVectorized()
Return true if the file is vectorized.
Definition: File.php:555
File\getHandler
getHandler()
Get a MediaHandler instance for this file.
Definition: File.php:1364
$wgOut
$wgOut
Definition: Setup.php:819
ErrorPageError
An error page which can definitely be safely rendered using the OutputPage.
Definition: ErrorPageError.php:27
LockManager\LOCK_EX
const LOCK_EX
Definition: LockManager.php:69
LocalFile\getHeight
getHeight( $page=1)
Return the height of the image.
Definition: LocalFile.php:754
LocalFile\isMissing
isMissing()
splitMime inherited
Definition: LocalFile.php:707
LocalFile\invalidateCache
invalidateCache()
Purge the file object/metadata cache.
Definition: LocalFile.php:321
File\userCan
userCan( $field, User $user=null)
Determine if the current user is allowed to view a particular field of this file, if it's marked as d...
Definition: File.php:2146
LocalFile\$lockedOwnTrx
bool $lockedOwnTrx
True if the image row is locked with a lock initiated transaction.
Definition: LocalFile.php:126
User
The User object encapsulates all of the user-specific settings (user_id, name, rights,...
Definition: User.php:51
LocalFileDeleteBatch\__construct
__construct(File $file, $reason='', $suppress=false, $user=null)
Definition: LocalFile.php:2175
wfLocalFile
wfLocalFile( $title)
Get an object referring to a locally registered file.
Definition: GlobalFunctions.php:2908
LocalFile\VERSION
const VERSION
Definition: LocalFile.php:46
Hooks\run
static run( $event, array $args=[], $deprecatedVersion=null)
Call hook functions defined in Hooks::register and $wgHooks.
Definition: Hooks.php:203
LocalFile\getDescriptionUrl
getDescriptionUrl()
isMultipage inherited
Definition: LocalFile.php:1929
LocalFile\move
move( $target)
getLinksTo inherited
Definition: LocalFile.php:1742
LocalFileMoveBatch\addCurrent
addCurrent()
Add the current image to the batch.
Definition: LocalFile.php:3006
LocalFile\$repoClass
string $repoClass
Definition: LocalFile.php:87
MediaHandler\isMetadataValid
isMetadataValid( $image, $metadata)
Check if the metadata string is valid for this handler.
Definition: MediaHandler.php:198
LocalFileDeleteBatch\removeNonexistentFiles
removeNonexistentFiles( $batch)
Removes non-existent files from a deletion batch.
Definition: LocalFile.php:2531
LocalFileMoveBatch\doDBUpdates
doDBUpdates()
Do the database updates and return a new Status indicating how many rows where updated.
Definition: LocalFile.php:3177
LocalFile\restore
restore( $versions=[], $unsuppress=false)
Restore all or specified deleted revisions to the given file.
Definition: LocalFile.php:1895
Language
Internationalisation code.
Definition: Language.php:35
File\purgeEverything
purgeEverything()
Purge metadata and all affected pages when the file is created, deleted, or majorly updated.
Definition: File.php:1440
File\getHashPath
getHashPath()
Get the filename hash component of the directory including trailing slash, e.g.
Definition: File.php:1496
LocalFileMoveBatch\$olds
$olds
Definition: LocalFile.php:2978
$flags
it s the revision text itself In either if gzip is the revision text is gzipped $flags
Definition: hooks.txt:2801
LocalFile\exists
exists()
canRender inherited
Definition: LocalFile.php:874
array
the array() calling protocol came about after MediaWiki 1.4rc1.
File\getThumbnails
getThumbnails()
Get all thumbnail names previously generated for this file STUB Overridden by LocalFile.
Definition: File.php:1409
LocalFileMoveBatch\$cur
$cur
Definition: LocalFile.php:2976
LocalFile\loadFromCache
loadFromCache()
Try to load file metadata from memcached, falling back to the database.
Definition: LocalFile.php:257
LocalFile\purgeCache
purgeCache( $options=[])
Delete all previously generated thumbnails, refresh metadata in memcached and purge the CDN.
Definition: LocalFile.php:929
LocalFileMoveBatch\cleanupTarget
cleanupTarget( $triplets)
Cleanup a partially moved array of triplets by deleting the target files.
Definition: LocalFile.php:3256
File\getVirtualUrl
getVirtualUrl( $suffix=false)
Get the public zone virtual URL for a current version source file.
Definition: File.php:1712
LocalFileDeleteBatch\doDBInserts
doDBInserts()
Definition: LocalFile.php:2297
LocalFileRestoreBatch\removeNonexistentFromCleanup
removeNonexistentFromCleanup( $batch)
Removes non-existent files from a cleanup batch.
Definition: LocalFile.php:2904
LogFormatter\newFromEntry
static newFromEntry(LogEntry $entry)
Constructs a new formatter suitable for given entry.
Definition: LogFormatter.php:50
Article\purgePatrolFooterCache
static purgePatrolFooterCache( $articleID)
Purge the cache used to check if it is worth showing the patrol footer For example,...
Definition: Article.php:1130
$type
$type
Definition: testCompression.php:48