MediaWiki  1.29.2
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 
198  static function selectFields() {
199  return [
200  'img_name',
201  'img_size',
202  'img_width',
203  'img_height',
204  'img_metadata',
205  'img_bits',
206  'img_media_type',
207  'img_major_mime',
208  'img_minor_mime',
209  'img_description',
210  'img_user',
211  'img_user_text',
212  'img_timestamp',
213  'img_sha1',
214  ];
215  }
216 
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', 'description' ];
351  static $results = [];
352 
353  if ( $prefix == '' ) {
354  return $fields;
355  }
356 
357  if ( !isset( $results[$prefix] ) ) {
358  $prefixedFields = [];
359  foreach ( $fields as $field ) {
360  $prefixedFields[] = $prefix . $field;
361  }
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  $array = $this->decodeRow( $row, $prefix );
540 
541  foreach ( $array as $name => $value ) {
542  $this->$name = $value;
543  }
544 
545  $this->fileExists = true;
546  $this->maybeUpgradeRow();
547  }
548 
553  function load( $flags = 0 ) {
554  if ( !$this->dataLoaded ) {
555  if ( $flags & self::READ_LATEST ) {
556  $this->loadFromDB( $flags );
557  } else {
558  $this->loadFromCache();
559  }
560  }
561 
562  if ( ( $flags & self::LOAD_ALL ) && !$this->extraDataLoaded ) {
563  // @note: loads on name/timestamp to reduce race condition problems
564  $this->loadExtraFromDB();
565  }
566  }
567 
571  function maybeUpgradeRow() {
573 
574  if ( wfReadOnly() || $this->upgrading ) {
575  return;
576  }
577 
578  $upgrade = false;
579  if ( is_null( $this->media_type ) || $this->mime == 'image/svg' ) {
580  $upgrade = true;
581  } else {
582  $handler = $this->getHandler();
583  if ( $handler ) {
584  $validity = $handler->isMetadataValid( $this, $this->getMetadata() );
585  if ( $validity === MediaHandler::METADATA_BAD ) {
586  $upgrade = true;
587  } elseif ( $validity === MediaHandler::METADATA_COMPATIBLE ) {
588  $upgrade = $wgUpdateCompatibleMetadata;
589  }
590  }
591  }
592 
593  if ( $upgrade ) {
594  $this->upgrading = true;
595  // Defer updates unless in auto-commit CLI mode
597  $this->upgrading = false; // avoid duplicate updates
598  try {
599  $this->upgradeRow();
600  } catch ( LocalFileLockError $e ) {
601  // let the other process handle it (or do it next time)
602  }
603  } );
604  }
605  }
606 
610  function getUpgraded() {
611  return $this->upgraded;
612  }
613 
617  function upgradeRow() {
618  $this->lock(); // begin
619 
620  $this->loadFromFile();
621 
622  # Don't destroy file info of missing files
623  if ( !$this->fileExists ) {
624  $this->unlock();
625  wfDebug( __METHOD__ . ": file does not exist, aborting\n" );
626 
627  return;
628  }
629 
630  $dbw = $this->repo->getMasterDB();
631  list( $major, $minor ) = self::splitMime( $this->mime );
632 
633  if ( wfReadOnly() ) {
634  $this->unlock();
635 
636  return;
637  }
638  wfDebug( __METHOD__ . ': upgrading ' . $this->getName() . " to the current schema\n" );
639 
640  $dbw->update( 'image',
641  [
642  'img_size' => $this->size, // sanity
643  'img_width' => $this->width,
644  'img_height' => $this->height,
645  'img_bits' => $this->bits,
646  'img_media_type' => $this->media_type,
647  'img_major_mime' => $major,
648  'img_minor_mime' => $minor,
649  'img_metadata' => $dbw->encodeBlob( $this->metadata ),
650  'img_sha1' => $this->sha1,
651  ],
652  [ 'img_name' => $this->getName() ],
653  __METHOD__
654  );
655 
656  $this->invalidateCache();
657 
658  $this->unlock(); // done
659  $this->upgraded = true; // avoid rework/retries
660  }
661 
672  function setProps( $info ) {
673  $this->dataLoaded = true;
674  $fields = $this->getCacheFields( '' );
675  $fields[] = 'fileExists';
676 
677  foreach ( $fields as $field ) {
678  if ( isset( $info[$field] ) ) {
679  $this->$field = $info[$field];
680  }
681  }
682 
683  // Fix up mime fields
684  if ( isset( $info['major_mime'] ) ) {
685  $this->mime = "{$info['major_mime']}/{$info['minor_mime']}";
686  } elseif ( isset( $info['mime'] ) ) {
687  $this->mime = $info['mime'];
688  list( $this->major_mime, $this->minor_mime ) = self::splitMime( $this->mime );
689  }
690  }
691 
703  function isMissing() {
704  if ( $this->missing === null ) {
705  list( $fileExists ) = $this->repo->fileExists( $this->getVirtualUrl() );
706  $this->missing = !$fileExists;
707  }
708 
709  return $this->missing;
710  }
711 
718  public function getWidth( $page = 1 ) {
719  $this->load();
720 
721  if ( $this->isMultipage() ) {
722  $handler = $this->getHandler();
723  if ( !$handler ) {
724  return 0;
725  }
726  $dim = $handler->getPageDimensions( $this, $page );
727  if ( $dim ) {
728  return $dim['width'];
729  } else {
730  // For non-paged media, the false goes through an
731  // intval, turning failure into 0, so do same here.
732  return 0;
733  }
734  } else {
735  return $this->width;
736  }
737  }
738 
745  public function getHeight( $page = 1 ) {
746  $this->load();
747 
748  if ( $this->isMultipage() ) {
749  $handler = $this->getHandler();
750  if ( !$handler ) {
751  return 0;
752  }
753  $dim = $handler->getPageDimensions( $this, $page );
754  if ( $dim ) {
755  return $dim['height'];
756  } else {
757  // For non-paged media, the false goes through an
758  // intval, turning failure into 0, so do same here.
759  return 0;
760  }
761  } else {
762  return $this->height;
763  }
764  }
765 
772  function getUser( $type = 'text' ) {
773  $this->load();
774 
775  if ( $type == 'text' ) {
776  return $this->user_text;
777  } else { // id
778  return (int)$this->user;
779  }
780  }
781 
789  public function getDescriptionShortUrl() {
790  $pageId = $this->title->getArticleID();
791 
792  if ( $pageId !== null ) {
793  $url = $this->repo->makeUrl( [ 'curid' => $pageId ] );
794  if ( $url !== false ) {
795  return $url;
796  }
797  }
798  return null;
799  }
800 
805  function getMetadata() {
806  $this->load( self::LOAD_ALL ); // large metadata is loaded in another step
807  return $this->metadata;
808  }
809 
813  function getBitDepth() {
814  $this->load();
815 
816  return (int)$this->bits;
817  }
818 
823  public function getSize() {
824  $this->load();
825 
826  return $this->size;
827  }
828 
833  function getMimeType() {
834  $this->load();
835 
836  return $this->mime;
837  }
838 
844  function getMediaType() {
845  $this->load();
846 
847  return $this->media_type;
848  }
849 
860  public function exists() {
861  $this->load();
862 
863  return $this->fileExists;
864  }
865 
881  function getThumbnails( $archiveName = false ) {
882  if ( $archiveName ) {
883  $dir = $this->getArchiveThumbPath( $archiveName );
884  } else {
885  $dir = $this->getThumbPath();
886  }
887 
888  $backend = $this->repo->getBackend();
889  $files = [ $dir ];
890  try {
891  $iterator = $backend->getFileList( [ 'dir' => $dir ] );
892  foreach ( $iterator as $file ) {
893  $files[] = $file;
894  }
895  } catch ( FileBackendError $e ) {
896  } // suppress (T56674)
897 
898  return $files;
899  }
900 
904  function purgeMetadataCache() {
905  $this->invalidateCache();
906  }
907 
915  function purgeCache( $options = [] ) {
916  // Refresh metadata cache
917  $this->purgeMetadataCache();
918 
919  // Delete thumbnails
920  $this->purgeThumbnails( $options );
921 
922  // Purge CDN cache for this file
924  new CdnCacheUpdate( [ $this->getUrl() ] ),
926  );
927  }
928 
933  function purgeOldThumbnails( $archiveName ) {
934  // Get a list of old thumbnails and URLs
935  $files = $this->getThumbnails( $archiveName );
936 
937  // Purge any custom thumbnail caches
938  Hooks::run( 'LocalFilePurgeThumbnails', [ $this, $archiveName ] );
939 
940  // Delete thumbnails
941  $dir = array_shift( $files );
942  $this->purgeThumbList( $dir, $files );
943 
944  // Purge the CDN
945  $urls = [];
946  foreach ( $files as $file ) {
947  $urls[] = $this->getArchiveThumbUrl( $archiveName, $file );
948  }
950  }
951 
956  public function purgeThumbnails( $options = [] ) {
957  $files = $this->getThumbnails();
958  // Always purge all files from CDN regardless of handler filters
959  $urls = [];
960  foreach ( $files as $file ) {
961  $urls[] = $this->getThumbUrl( $file );
962  }
963  array_shift( $urls ); // don't purge directory
964 
965  // Give media handler a chance to filter the file purge list
966  if ( !empty( $options['forThumbRefresh'] ) ) {
967  $handler = $this->getHandler();
968  if ( $handler ) {
970  }
971  }
972 
973  // Purge any custom thumbnail caches
974  Hooks::run( 'LocalFilePurgeThumbnails', [ $this, false ] );
975 
976  // Delete thumbnails
977  $dir = array_shift( $files );
978  $this->purgeThumbList( $dir, $files );
979 
980  // Purge the CDN
982  }
983 
989  public function prerenderThumbnails() {
991 
992  $jobs = [];
993 
995  rsort( $sizes );
996 
997  foreach ( $sizes as $size ) {
998  if ( $this->isVectorized() || $this->getWidth() > $size ) {
999  $jobs[] = new ThumbnailRenderJob(
1000  $this->getTitle(),
1001  [ 'transformParams' => [ 'width' => $size ] ]
1002  );
1003  }
1004  }
1005 
1006  if ( $jobs ) {
1007  JobQueueGroup::singleton()->lazyPush( $jobs );
1008  }
1009  }
1010 
1016  protected function purgeThumbList( $dir, $files ) {
1017  $fileListDebug = strtr(
1018  var_export( $files, true ),
1019  [ "\n" => '' ]
1020  );
1021  wfDebug( __METHOD__ . ": $fileListDebug\n" );
1022 
1023  $purgeList = [];
1024  foreach ( $files as $file ) {
1025  # Check that the base file name is part of the thumb name
1026  # This is a basic sanity check to avoid erasing unrelated directories
1027  if ( strpos( $file, $this->getName() ) !== false
1028  || strpos( $file, "-thumbnail" ) !== false // "short" thumb name
1029  ) {
1030  $purgeList[] = "{$dir}/{$file}";
1031  }
1032  }
1033 
1034  # Delete the thumbnails
1035  $this->repo->quickPurgeBatch( $purgeList );
1036  # Clear out the thumbnail directory if empty
1037  $this->repo->quickCleanDir( $dir );
1038  }
1039 
1050  function getHistory( $limit = null, $start = null, $end = null, $inc = true ) {
1051  $dbr = $this->repo->getReplicaDB();
1052  $tables = [ 'oldimage' ];
1053  $fields = OldLocalFile::selectFields();
1054  $conds = $opts = $join_conds = [];
1055  $eq = $inc ? '=' : '';
1056  $conds[] = "oi_name = " . $dbr->addQuotes( $this->title->getDBkey() );
1057 
1058  if ( $start ) {
1059  $conds[] = "oi_timestamp <$eq " . $dbr->addQuotes( $dbr->timestamp( $start ) );
1060  }
1061 
1062  if ( $end ) {
1063  $conds[] = "oi_timestamp >$eq " . $dbr->addQuotes( $dbr->timestamp( $end ) );
1064  }
1065 
1066  if ( $limit ) {
1067  $opts['LIMIT'] = $limit;
1068  }
1069 
1070  // Search backwards for time > x queries
1071  $order = ( !$start && $end !== null ) ? 'ASC' : 'DESC';
1072  $opts['ORDER BY'] = "oi_timestamp $order";
1073  $opts['USE INDEX'] = [ 'oldimage' => 'oi_name_timestamp' ];
1074 
1075  // Avoid PHP 7.1 warning from passing $this by reference
1076  $localFile = $this;
1077  Hooks::run( 'LocalFile::getHistory', [ &$localFile, &$tables, &$fields,
1078  &$conds, &$opts, &$join_conds ] );
1079 
1080  $res = $dbr->select( $tables, $fields, $conds, __METHOD__, $opts, $join_conds );
1081  $r = [];
1082 
1083  foreach ( $res as $row ) {
1084  $r[] = $this->repo->newFileFromRow( $row );
1085  }
1086 
1087  if ( $order == 'ASC' ) {
1088  $r = array_reverse( $r ); // make sure it ends up descending
1089  }
1090 
1091  return $r;
1092  }
1093 
1103  public function nextHistoryLine() {
1104  # Polymorphic function name to distinguish foreign and local fetches
1105  $fname = static::class . '::' . __FUNCTION__;
1106 
1107  $dbr = $this->repo->getReplicaDB();
1108 
1109  if ( $this->historyLine == 0 ) { // called for the first time, return line from cur
1110  $this->historyRes = $dbr->select( 'image',
1111  [
1112  '*',
1113  "'' AS oi_archive_name",
1114  '0 as oi_deleted',
1115  'img_sha1'
1116  ],
1117  [ 'img_name' => $this->title->getDBkey() ],
1118  $fname
1119  );
1120 
1121  if ( 0 == $dbr->numRows( $this->historyRes ) ) {
1122  $this->historyRes = null;
1123 
1124  return false;
1125  }
1126  } elseif ( $this->historyLine == 1 ) {
1127  $this->historyRes = $dbr->select( 'oldimage', '*',
1128  [ 'oi_name' => $this->title->getDBkey() ],
1129  $fname,
1130  [ 'ORDER BY' => 'oi_timestamp DESC' ]
1131  );
1132  }
1133  $this->historyLine++;
1134 
1135  return $dbr->fetchObject( $this->historyRes );
1136  }
1137 
1141  public function resetHistory() {
1142  $this->historyLine = 0;
1143 
1144  if ( !is_null( $this->historyRes ) ) {
1145  $this->historyRes = null;
1146  }
1147  }
1148 
1179  function upload( $src, $comment, $pageText, $flags = 0, $props = false,
1180  $timestamp = false, $user = null, $tags = []
1181  ) {
1183 
1184  if ( $this->getRepo()->getReadOnlyReason() !== false ) {
1185  return $this->readOnlyFatalStatus();
1186  }
1187 
1188  $srcPath = ( $src instanceof FSFile ) ? $src->getPath() : $src;
1189  if ( !$props ) {
1190  if ( $this->repo->isVirtualUrl( $srcPath )
1191  || FileBackend::isStoragePath( $srcPath )
1192  ) {
1193  $props = $this->repo->getFileProps( $srcPath );
1194  } else {
1195  $mwProps = new MWFileProps( MimeMagic::singleton() );
1196  $props = $mwProps->getPropsFromPath( $srcPath, true );
1197  }
1198  }
1199 
1200  $options = [];
1201  $handler = MediaHandler::getHandler( $props['mime'] );
1202  if ( $handler ) {
1203  $options['headers'] = $handler->getStreamHeaders( $props['metadata'] );
1204  } else {
1205  $options['headers'] = [];
1206  }
1207 
1208  // Trim spaces on user supplied text
1209  $comment = trim( $comment );
1210 
1211  // Truncate nicely or the DB will do it for us
1212  // non-nicely (dangling multi-byte chars, non-truncated version in cache).
1213  $comment = $wgContLang->truncate( $comment, 255 );
1214  $this->lock(); // begin
1215  $status = $this->publish( $src, $flags, $options );
1216 
1217  if ( $status->successCount >= 2 ) {
1218  // There will be a copy+(one of move,copy,store).
1219  // The first succeeding does not commit us to updating the DB
1220  // since it simply copied the current version to a timestamped file name.
1221  // It is only *preferable* to avoid leaving such files orphaned.
1222  // Once the second operation goes through, then the current version was
1223  // updated and we must therefore update the DB too.
1224  $oldver = $status->value;
1225  if ( !$this->recordUpload2( $oldver, $comment, $pageText, $props, $timestamp, $user, $tags ) ) {
1226  $status->fatal( 'filenotfound', $srcPath );
1227  }
1228  }
1229 
1230  $this->unlock(); // done
1231 
1232  return $status;
1233  }
1234 
1247  function recordUpload( $oldver, $desc, $license = '', $copyStatus = '', $source = '',
1248  $watch = false, $timestamp = false, User $user = null ) {
1249  if ( !$user ) {
1250  global $wgUser;
1251  $user = $wgUser;
1252  }
1253 
1254  $pageText = SpecialUpload::getInitialPageText( $desc, $license, $copyStatus, $source );
1255 
1256  if ( !$this->recordUpload2( $oldver, $desc, $pageText, false, $timestamp, $user ) ) {
1257  return false;
1258  }
1259 
1260  if ( $watch ) {
1261  $user->addWatch( $this->getTitle() );
1262  }
1263 
1264  return true;
1265  }
1266 
1278  function recordUpload2(
1279  $oldver, $comment, $pageText, $props = false, $timestamp = false, $user = null, $tags = []
1280  ) {
1281  if ( is_null( $user ) ) {
1282  global $wgUser;
1283  $user = $wgUser;
1284  }
1285 
1286  $dbw = $this->repo->getMasterDB();
1287 
1288  # Imports or such might force a certain timestamp; otherwise we generate
1289  # it and can fudge it slightly to keep (name,timestamp) unique on re-upload.
1290  if ( $timestamp === false ) {
1291  $timestamp = $dbw->timestamp();
1292  $allowTimeKludge = true;
1293  } else {
1294  $allowTimeKludge = false;
1295  }
1296 
1297  $props = $props ?: $this->repo->getFileProps( $this->getVirtualUrl() );
1298  $props['description'] = $comment;
1299  $props['user'] = $user->getId();
1300  $props['user_text'] = $user->getName();
1301  $props['timestamp'] = wfTimestamp( TS_MW, $timestamp ); // DB -> TS_MW
1302  $this->setProps( $props );
1303 
1304  # Fail now if the file isn't there
1305  if ( !$this->fileExists ) {
1306  wfDebug( __METHOD__ . ": File " . $this->getRel() . " went missing!\n" );
1307 
1308  return false;
1309  }
1310 
1311  $dbw->startAtomic( __METHOD__ );
1312 
1313  # Test to see if the row exists using INSERT IGNORE
1314  # This avoids race conditions by locking the row until the commit, and also
1315  # doesn't deadlock. SELECT FOR UPDATE causes a deadlock for every race condition.
1316  $dbw->insert( 'image',
1317  [
1318  'img_name' => $this->getName(),
1319  'img_size' => $this->size,
1320  'img_width' => intval( $this->width ),
1321  'img_height' => intval( $this->height ),
1322  'img_bits' => $this->bits,
1323  'img_media_type' => $this->media_type,
1324  'img_major_mime' => $this->major_mime,
1325  'img_minor_mime' => $this->minor_mime,
1326  'img_timestamp' => $timestamp,
1327  'img_description' => $comment,
1328  'img_user' => $user->getId(),
1329  'img_user_text' => $user->getName(),
1330  'img_metadata' => $dbw->encodeBlob( $this->metadata ),
1331  'img_sha1' => $this->sha1
1332  ],
1333  __METHOD__,
1334  'IGNORE'
1335  );
1336 
1337  $reupload = ( $dbw->affectedRows() == 0 );
1338  if ( $reupload ) {
1339  if ( $allowTimeKludge ) {
1340  # Use LOCK IN SHARE MODE to ignore any transaction snapshotting
1341  $ltimestamp = $dbw->selectField(
1342  'image',
1343  'img_timestamp',
1344  [ 'img_name' => $this->getName() ],
1345  __METHOD__,
1346  [ 'LOCK IN SHARE MODE' ]
1347  );
1348  $lUnixtime = $ltimestamp ? wfTimestamp( TS_UNIX, $ltimestamp ) : false;
1349  # Avoid a timestamp that is not newer than the last version
1350  # TODO: the image/oldimage tables should be like page/revision with an ID field
1351  if ( $lUnixtime && wfTimestamp( TS_UNIX, $timestamp ) <= $lUnixtime ) {
1352  sleep( 1 ); // fast enough re-uploads would go far in the future otherwise
1353  $timestamp = $dbw->timestamp( $lUnixtime + 1 );
1354  $this->timestamp = wfTimestamp( TS_MW, $timestamp ); // DB -> TS_MW
1355  }
1356  }
1357 
1358  # (T36993) Note: $oldver can be empty here, if the previous
1359  # version of the file was broken. Allow registration of the new
1360  # version to continue anyway, because that's better than having
1361  # an image that's not fixable by user operations.
1362  # Collision, this is an update of a file
1363  # Insert previous contents into oldimage
1364  $dbw->insertSelect( 'oldimage', 'image',
1365  [
1366  'oi_name' => 'img_name',
1367  'oi_archive_name' => $dbw->addQuotes( $oldver ),
1368  'oi_size' => 'img_size',
1369  'oi_width' => 'img_width',
1370  'oi_height' => 'img_height',
1371  'oi_bits' => 'img_bits',
1372  'oi_timestamp' => 'img_timestamp',
1373  'oi_description' => 'img_description',
1374  'oi_user' => 'img_user',
1375  'oi_user_text' => 'img_user_text',
1376  'oi_metadata' => 'img_metadata',
1377  'oi_media_type' => 'img_media_type',
1378  'oi_major_mime' => 'img_major_mime',
1379  'oi_minor_mime' => 'img_minor_mime',
1380  'oi_sha1' => 'img_sha1'
1381  ],
1382  [ 'img_name' => $this->getName() ],
1383  __METHOD__
1384  );
1385 
1386  # Update the current image row
1387  $dbw->update( 'image',
1388  [
1389  'img_size' => $this->size,
1390  'img_width' => intval( $this->width ),
1391  'img_height' => intval( $this->height ),
1392  'img_bits' => $this->bits,
1393  'img_media_type' => $this->media_type,
1394  'img_major_mime' => $this->major_mime,
1395  'img_minor_mime' => $this->minor_mime,
1396  'img_timestamp' => $timestamp,
1397  'img_description' => $comment,
1398  'img_user' => $user->getId(),
1399  'img_user_text' => $user->getName(),
1400  'img_metadata' => $dbw->encodeBlob( $this->metadata ),
1401  'img_sha1' => $this->sha1
1402  ],
1403  [ 'img_name' => $this->getName() ],
1404  __METHOD__
1405  );
1406  }
1407 
1408  $descTitle = $this->getTitle();
1409  $descId = $descTitle->getArticleID();
1410  $wikiPage = new WikiFilePage( $descTitle );
1411  $wikiPage->setFile( $this );
1412 
1413  // Add the log entry...
1414  $logEntry = new ManualLogEntry( 'upload', $reupload ? 'overwrite' : 'upload' );
1415  $logEntry->setTimestamp( $this->timestamp );
1416  $logEntry->setPerformer( $user );
1417  $logEntry->setComment( $comment );
1418  $logEntry->setTarget( $descTitle );
1419  // Allow people using the api to associate log entries with the upload.
1420  // Log has a timestamp, but sometimes different from upload timestamp.
1421  $logEntry->setParameters(
1422  [
1423  'img_sha1' => $this->sha1,
1424  'img_timestamp' => $timestamp,
1425  ]
1426  );
1427  // Note we keep $logId around since during new image
1428  // creation, page doesn't exist yet, so log_page = 0
1429  // but we want it to point to the page we're making,
1430  // so we later modify the log entry.
1431  // For a similar reason, we avoid making an RC entry
1432  // now and wait until the page exists.
1433  $logId = $logEntry->insert();
1434 
1435  if ( $descTitle->exists() ) {
1436  // Use own context to get the action text in content language
1437  $formatter = LogFormatter::newFromEntry( $logEntry );
1438  $formatter->setContext( RequestContext::newExtraneousContext( $descTitle ) );
1439  $editSummary = $formatter->getPlainActionText();
1440 
1441  $nullRevision = Revision::newNullRevision(
1442  $dbw,
1443  $descId,
1444  $editSummary,
1445  false,
1446  $user
1447  );
1448  if ( $nullRevision ) {
1449  $nullRevision->insertOn( $dbw );
1450  Hooks::run(
1451  'NewRevisionFromEditComplete',
1452  [ $wikiPage, $nullRevision, $nullRevision->getParentId(), $user ]
1453  );
1454  $wikiPage->updateRevisionOn( $dbw, $nullRevision );
1455  // Associate null revision id
1456  $logEntry->setAssociatedRevId( $nullRevision->getId() );
1457  }
1458 
1459  $newPageContent = null;
1460  } else {
1461  // Make the description page and RC log entry post-commit
1462  $newPageContent = ContentHandler::makeContent( $pageText, $descTitle );
1463  }
1464 
1465  # Defer purges, page creation, and link updates in case they error out.
1466  # The most important thing is that files and the DB registry stay synced.
1467  $dbw->endAtomic( __METHOD__ );
1468 
1469  # Do some cache purges after final commit so that:
1470  # a) Changes are more likely to be seen post-purge
1471  # b) They won't cause rollback of the log publish/update above
1473  new AutoCommitUpdate(
1474  $dbw,
1475  __METHOD__,
1476  function () use (
1477  $reupload, $wikiPage, $newPageContent, $comment, $user,
1478  $logEntry, $logId, $descId, $tags
1479  ) {
1480  # Update memcache after the commit
1481  $this->invalidateCache();
1482 
1483  $updateLogPage = false;
1484  if ( $newPageContent ) {
1485  # New file page; create the description page.
1486  # There's already a log entry, so don't make a second RC entry
1487  # CDN and file cache for the description page are purged by doEditContent.
1488  $status = $wikiPage->doEditContent(
1489  $newPageContent,
1490  $comment,
1492  false,
1493  $user
1494  );
1495 
1496  if ( isset( $status->value['revision'] ) ) {
1498  $rev = $status->value['revision'];
1499  // Associate new page revision id
1500  $logEntry->setAssociatedRevId( $rev->getId() );
1501  }
1502  // This relies on the resetArticleID() call in WikiPage::insertOn(),
1503  // which is triggered on $descTitle by doEditContent() above.
1504  if ( isset( $status->value['revision'] ) ) {
1506  $rev = $status->value['revision'];
1507  $updateLogPage = $rev->getPage();
1508  }
1509  } else {
1510  # Existing file page: invalidate description page cache
1511  $wikiPage->getTitle()->invalidateCache();
1512  $wikiPage->getTitle()->purgeSquid();
1513  # Allow the new file version to be patrolled from the page footer
1515  }
1516 
1517  # Update associated rev id. This should be done by $logEntry->insert() earlier,
1518  # but setAssociatedRevId() wasn't called at that point yet...
1519  $logParams = $logEntry->getParameters();
1520  $logParams['associated_rev_id'] = $logEntry->getAssociatedRevId();
1521  $update = [ 'log_params' => LogEntryBase::makeParamBlob( $logParams ) ];
1522  if ( $updateLogPage ) {
1523  # Also log page, in case where we just created it above
1524  $update['log_page'] = $updateLogPage;
1525  }
1526  $this->getRepo()->getMasterDB()->update(
1527  'logging',
1528  $update,
1529  [ 'log_id' => $logId ],
1530  __METHOD__
1531  );
1532  $this->getRepo()->getMasterDB()->insert(
1533  'log_search',
1534  [
1535  'ls_field' => 'associated_rev_id',
1536  'ls_value' => $logEntry->getAssociatedRevId(),
1537  'ls_log_id' => $logId,
1538  ],
1539  __METHOD__
1540  );
1541 
1542  # Add change tags, if any
1543  if ( $tags ) {
1544  $logEntry->setTags( $tags );
1545  }
1546 
1547  # Uploads can be patrolled
1548  $logEntry->setIsPatrollable( true );
1549 
1550  # Now that the log entry is up-to-date, make an RC entry.
1551  $logEntry->publish( $logId );
1552 
1553  # Run hook for other updates (typically more cache purging)
1554  Hooks::run( 'FileUpload', [ $this, $reupload, !$newPageContent ] );
1555 
1556  if ( $reupload ) {
1557  # Delete old thumbnails
1558  $this->purgeThumbnails();
1559  # Remove the old file from the CDN cache
1561  new CdnCacheUpdate( [ $this->getUrl() ] ),
1563  );
1564  } else {
1565  # Update backlink pages pointing to this title if created
1566  LinksUpdate::queueRecursiveJobsForTable( $this->getTitle(), 'imagelinks' );
1567  }
1568 
1569  $this->prerenderThumbnails();
1570  }
1571  ),
1573  );
1574 
1575  if ( !$reupload ) {
1576  # This is a new file, so update the image count
1577  DeferredUpdates::addUpdate( SiteStatsUpdate::factory( [ 'images' => 1 ] ) );
1578  }
1579 
1580  # Invalidate cache for all pages using this file
1581  DeferredUpdates::addUpdate( new HTMLCacheUpdate( $this->getTitle(), 'imagelinks' ) );
1582 
1583  return true;
1584  }
1585 
1601  function publish( $src, $flags = 0, array $options = [] ) {
1602  return $this->publishTo( $src, $this->getRel(), $flags, $options );
1603  }
1604 
1620  function publishTo( $src, $dstRel, $flags = 0, array $options = [] ) {
1621  $srcPath = ( $src instanceof FSFile ) ? $src->getPath() : $src;
1622 
1623  $repo = $this->getRepo();
1624  if ( $repo->getReadOnlyReason() !== false ) {
1625  return $this->readOnlyFatalStatus();
1626  }
1627 
1628  $this->lock(); // begin
1629 
1630  $archiveName = wfTimestamp( TS_MW ) . '!' . $this->getName();
1631  $archiveRel = 'archive/' . $this->getHashPath() . $archiveName;
1632 
1633  if ( $repo->hasSha1Storage() ) {
1634  $sha1 = $repo->isVirtualUrl( $srcPath )
1635  ? $repo->getFileSha1( $srcPath )
1636  : FSFile::getSha1Base36FromPath( $srcPath );
1638  $wrapperBackend = $repo->getBackend();
1639  $dst = $wrapperBackend->getPathForSHA1( $sha1 );
1640  $status = $repo->quickImport( $src, $dst );
1641  if ( $flags & File::DELETE_SOURCE ) {
1642  unlink( $srcPath );
1643  }
1644 
1645  if ( $this->exists() ) {
1646  $status->value = $archiveName;
1647  }
1648  } else {
1650  $status = $repo->publish( $srcPath, $dstRel, $archiveRel, $flags, $options );
1651 
1652  if ( $status->value == 'new' ) {
1653  $status->value = '';
1654  } else {
1655  $status->value = $archiveName;
1656  }
1657  }
1658 
1659  $this->unlock(); // done
1660 
1661  return $status;
1662  }
1663 
1681  function move( $target ) {
1682  if ( $this->getRepo()->getReadOnlyReason() !== false ) {
1683  return $this->readOnlyFatalStatus();
1684  }
1685 
1686  wfDebugLog( 'imagemove', "Got request to move {$this->name} to " . $target->getText() );
1687  $batch = new LocalFileMoveBatch( $this, $target );
1688 
1689  $this->lock(); // begin
1690  $batch->addCurrent();
1691  $archiveNames = $batch->addOlds();
1692  $status = $batch->execute();
1693  $this->unlock(); // done
1694 
1695  wfDebugLog( 'imagemove', "Finished moving {$this->name}" );
1696 
1697  // Purge the source and target files...
1698  $oldTitleFile = wfLocalFile( $this->title );
1699  $newTitleFile = wfLocalFile( $target );
1700  // To avoid slow purges in the transaction, move them outside...
1702  new AutoCommitUpdate(
1703  $this->getRepo()->getMasterDB(),
1704  __METHOD__,
1705  function () use ( $oldTitleFile, $newTitleFile, $archiveNames ) {
1706  $oldTitleFile->purgeEverything();
1707  foreach ( $archiveNames as $archiveName ) {
1708  $oldTitleFile->purgeOldThumbnails( $archiveName );
1709  }
1710  $newTitleFile->purgeEverything();
1711  }
1712  ),
1714  );
1715 
1716  if ( $status->isOK() ) {
1717  // Now switch the object
1718  $this->title = $target;
1719  // Force regeneration of the name and hashpath
1720  unset( $this->name );
1721  unset( $this->hashPath );
1722  }
1723 
1724  return $status;
1725  }
1726 
1740  function delete( $reason, $suppress = false, $user = null ) {
1741  if ( $this->getRepo()->getReadOnlyReason() !== false ) {
1742  return $this->readOnlyFatalStatus();
1743  }
1744 
1745  $batch = new LocalFileDeleteBatch( $this, $reason, $suppress, $user );
1746 
1747  $this->lock(); // begin
1748  $batch->addCurrent();
1749  // Get old version relative paths
1750  $archiveNames = $batch->addOlds();
1751  $status = $batch->execute();
1752  $this->unlock(); // done
1753 
1754  if ( $status->isOK() ) {
1755  DeferredUpdates::addUpdate( SiteStatsUpdate::factory( [ 'images' => -1 ] ) );
1756  }
1757 
1758  // To avoid slow purges in the transaction, move them outside...
1760  new AutoCommitUpdate(
1761  $this->getRepo()->getMasterDB(),
1762  __METHOD__,
1763  function () use ( $archiveNames ) {
1764  $this->purgeEverything();
1765  foreach ( $archiveNames as $archiveName ) {
1766  $this->purgeOldThumbnails( $archiveName );
1767  }
1768  }
1769  ),
1771  );
1772 
1773  // Purge the CDN
1774  $purgeUrls = [];
1775  foreach ( $archiveNames as $archiveName ) {
1776  $purgeUrls[] = $this->getArchiveUrl( $archiveName );
1777  }
1779 
1780  return $status;
1781  }
1782 
1798  function deleteOld( $archiveName, $reason, $suppress = false, $user = null ) {
1799  if ( $this->getRepo()->getReadOnlyReason() !== false ) {
1800  return $this->readOnlyFatalStatus();
1801  }
1802 
1803  $batch = new LocalFileDeleteBatch( $this, $reason, $suppress, $user );
1804 
1805  $this->lock(); // begin
1806  $batch->addOld( $archiveName );
1807  $status = $batch->execute();
1808  $this->unlock(); // done
1809 
1810  $this->purgeOldThumbnails( $archiveName );
1811  if ( $status->isOK() ) {
1812  $this->purgeDescription();
1813  }
1814 
1816  new CdnCacheUpdate( [ $this->getArchiveUrl( $archiveName ) ] ),
1818  );
1819 
1820  return $status;
1821  }
1822 
1834  function restore( $versions = [], $unsuppress = false ) {
1835  if ( $this->getRepo()->getReadOnlyReason() !== false ) {
1836  return $this->readOnlyFatalStatus();
1837  }
1838 
1839  $batch = new LocalFileRestoreBatch( $this, $unsuppress );
1840 
1841  $this->lock(); // begin
1842  if ( !$versions ) {
1843  $batch->addAll();
1844  } else {
1845  $batch->addIds( $versions );
1846  }
1847  $status = $batch->execute();
1848  if ( $status->isGood() ) {
1849  $cleanupStatus = $batch->cleanup();
1850  $cleanupStatus->successCount = 0;
1851  $cleanupStatus->failCount = 0;
1852  $status->merge( $cleanupStatus );
1853  }
1854  $this->unlock(); // done
1855 
1856  return $status;
1857  }
1858 
1868  function getDescriptionUrl() {
1869  return $this->title->getLocalURL();
1870  }
1871 
1880  function getDescriptionText( $lang = null ) {
1881  $revision = Revision::newFromTitle( $this->title, false, Revision::READ_NORMAL );
1882  if ( !$revision ) {
1883  return false;
1884  }
1885  $content = $revision->getContent();
1886  if ( !$content ) {
1887  return false;
1888  }
1889  $pout = $content->getParserOutput( $this->title, null, new ParserOptions( null, $lang ) );
1890 
1891  return $pout->getText();
1892  }
1893 
1899  function getDescription( $audience = self::FOR_PUBLIC, User $user = null ) {
1900  $this->load();
1901  if ( $audience == self::FOR_PUBLIC && $this->isDeleted( self::DELETED_COMMENT ) ) {
1902  return '';
1903  } elseif ( $audience == self::FOR_THIS_USER
1904  && !$this->userCan( self::DELETED_COMMENT, $user )
1905  ) {
1906  return '';
1907  } else {
1908  return $this->description;
1909  }
1910  }
1911 
1915  function getTimestamp() {
1916  $this->load();
1917 
1918  return $this->timestamp;
1919  }
1920 
1924  public function getDescriptionTouched() {
1925  // The DB lookup might return false, e.g. if the file was just deleted, or the shared DB repo
1926  // itself gets it from elsewhere. To avoid repeating the DB lookups in such a case, we
1927  // need to differentiate between null (uninitialized) and false (failed to load).
1928  if ( $this->descriptionTouched === null ) {
1929  $cond = [
1930  'page_namespace' => $this->title->getNamespace(),
1931  'page_title' => $this->title->getDBkey()
1932  ];
1933  $touched = $this->repo->getReplicaDB()->selectField( 'page', 'page_touched', $cond, __METHOD__ );
1934  $this->descriptionTouched = $touched ? wfTimestamp( TS_MW, $touched ) : false;
1935  }
1936 
1938  }
1939 
1943  function getSha1() {
1944  $this->load();
1945  // Initialise now if necessary
1946  if ( $this->sha1 == '' && $this->fileExists ) {
1947  $this->lock(); // begin
1948 
1949  $this->sha1 = $this->repo->getFileSha1( $this->getPath() );
1950  if ( !wfReadOnly() && strval( $this->sha1 ) != '' ) {
1951  $dbw = $this->repo->getMasterDB();
1952  $dbw->update( 'image',
1953  [ 'img_sha1' => $this->sha1 ],
1954  [ 'img_name' => $this->getName() ],
1955  __METHOD__ );
1956  $this->invalidateCache();
1957  }
1958 
1959  $this->unlock(); // done
1960  }
1961 
1962  return $this->sha1;
1963  }
1964 
1968  function isCacheable() {
1969  $this->load();
1970 
1971  // If extra data (metadata) was not loaded then it must have been large
1972  return $this->extraDataLoaded
1973  && strlen( serialize( $this->metadata ) ) <= self::CACHE_FIELD_MAX_LEN;
1974  }
1975 
1980  public function acquireFileLock() {
1981  return $this->getRepo()->getBackend()->lockFiles(
1982  [ $this->getPath() ], LockManager::LOCK_EX, 10
1983  );
1984  }
1985 
1990  public function releaseFileLock() {
1991  return $this->getRepo()->getBackend()->unlockFiles(
1992  [ $this->getPath() ], LockManager::LOCK_EX
1993  );
1994  }
1995 
2005  public function lock() {
2006  if ( !$this->locked ) {
2007  $logger = LoggerFactory::getInstance( 'LocalFile' );
2008 
2009  $dbw = $this->repo->getMasterDB();
2010  $makesTransaction = !$dbw->trxLevel();
2011  $dbw->startAtomic( self::ATOMIC_SECTION_LOCK );
2012  // T56736: use simple lock to handle when the file does not exist.
2013  // SELECT FOR UPDATE prevents changes, not other SELECTs with FOR UPDATE.
2014  // Also, that would cause contention on INSERT of similarly named rows.
2015  $status = $this->acquireFileLock(); // represents all versions of the file
2016  if ( !$status->isGood() ) {
2017  $dbw->endAtomic( self::ATOMIC_SECTION_LOCK );
2018  $logger->warning( "Failed to lock '{file}'", [ 'file' => $this->name ] );
2019 
2020  throw new LocalFileLockError( $status );
2021  }
2022  // Release the lock *after* commit to avoid row-level contention.
2023  // Make sure it triggers on rollback() as well as commit() (T132921).
2024  $dbw->onTransactionResolution(
2025  function () use ( $logger ) {
2026  $status = $this->releaseFileLock();
2027  if ( !$status->isGood() ) {
2028  $logger->error( "Failed to unlock '{file}'", [ 'file' => $this->name ] );
2029  }
2030  },
2031  __METHOD__
2032  );
2033  // Callers might care if the SELECT snapshot is safely fresh
2034  $this->lockedOwnTrx = $makesTransaction;
2035  }
2036 
2037  $this->locked++;
2038 
2039  return $this->lockedOwnTrx;
2040  }
2041 
2050  public function unlock() {
2051  if ( $this->locked ) {
2052  --$this->locked;
2053  if ( !$this->locked ) {
2054  $dbw = $this->repo->getMasterDB();
2055  $dbw->endAtomic( self::ATOMIC_SECTION_LOCK );
2056  $this->lockedOwnTrx = false;
2057  }
2058  }
2059  }
2060 
2064  protected function readOnlyFatalStatus() {
2065  return $this->getRepo()->newFatal( 'filereadonlyerror', $this->getName(),
2066  $this->getRepo()->getName(), $this->getRepo()->getReadOnlyReason() );
2067  }
2068 
2072  function __destruct() {
2073  $this->unlock();
2074  }
2075 } // LocalFile class
2076 
2077 # ------------------------------------------------------------------------------
2078 
2085  private $file;
2086 
2088  private $reason;
2089 
2091  private $srcRels = [];
2092 
2094  private $archiveUrls = [];
2095 
2098 
2100  private $suppress;
2101 
2103  private $status;
2104 
2106  private $user;
2107 
2114  function __construct( File $file, $reason = '', $suppress = false, $user = null ) {
2115  $this->file = $file;
2116  $this->reason = $reason;
2117  $this->suppress = $suppress;
2118  if ( $user ) {
2119  $this->user = $user;
2120  } else {
2121  global $wgUser;
2122  $this->user = $wgUser;
2123  }
2124  $this->status = $file->repo->newGood();
2125  }
2126 
2127  public function addCurrent() {
2128  $this->srcRels['.'] = $this->file->getRel();
2129  }
2130 
2134  public function addOld( $oldName ) {
2135  $this->srcRels[$oldName] = $this->file->getArchiveRel( $oldName );
2136  $this->archiveUrls[] = $this->file->getArchiveUrl( $oldName );
2137  }
2138 
2143  public function addOlds() {
2144  $archiveNames = [];
2145 
2146  $dbw = $this->file->repo->getMasterDB();
2147  $result = $dbw->select( 'oldimage',
2148  [ 'oi_archive_name' ],
2149  [ 'oi_name' => $this->file->getName() ],
2150  __METHOD__
2151  );
2152 
2153  foreach ( $result as $row ) {
2154  $this->addOld( $row->oi_archive_name );
2155  $archiveNames[] = $row->oi_archive_name;
2156  }
2157 
2158  return $archiveNames;
2159  }
2160 
2164  protected function getOldRels() {
2165  if ( !isset( $this->srcRels['.'] ) ) {
2166  $oldRels =& $this->srcRels;
2167  $deleteCurrent = false;
2168  } else {
2169  $oldRels = $this->srcRels;
2170  unset( $oldRels['.'] );
2171  $deleteCurrent = true;
2172  }
2173 
2174  return [ $oldRels, $deleteCurrent ];
2175  }
2176 
2180  protected function getHashes() {
2181  $hashes = [];
2182  list( $oldRels, $deleteCurrent ) = $this->getOldRels();
2183 
2184  if ( $deleteCurrent ) {
2185  $hashes['.'] = $this->file->getSha1();
2186  }
2187 
2188  if ( count( $oldRels ) ) {
2189  $dbw = $this->file->repo->getMasterDB();
2190  $res = $dbw->select(
2191  'oldimage',
2192  [ 'oi_archive_name', 'oi_sha1' ],
2193  [ 'oi_archive_name' => array_keys( $oldRels ),
2194  'oi_name' => $this->file->getName() ], // performance
2195  __METHOD__
2196  );
2197 
2198  foreach ( $res as $row ) {
2199  if ( rtrim( $row->oi_sha1, "\0" ) === '' ) {
2200  // Get the hash from the file
2201  $oldUrl = $this->file->getArchiveVirtualUrl( $row->oi_archive_name );
2202  $props = $this->file->repo->getFileProps( $oldUrl );
2203 
2204  if ( $props['fileExists'] ) {
2205  // Upgrade the oldimage row
2206  $dbw->update( 'oldimage',
2207  [ 'oi_sha1' => $props['sha1'] ],
2208  [ 'oi_name' => $this->file->getName(), 'oi_archive_name' => $row->oi_archive_name ],
2209  __METHOD__ );
2210  $hashes[$row->oi_archive_name] = $props['sha1'];
2211  } else {
2212  $hashes[$row->oi_archive_name] = false;
2213  }
2214  } else {
2215  $hashes[$row->oi_archive_name] = $row->oi_sha1;
2216  }
2217  }
2218  }
2219 
2220  $missing = array_diff_key( $this->srcRels, $hashes );
2221 
2222  foreach ( $missing as $name => $rel ) {
2223  $this->status->error( 'filedelete-old-unregistered', $name );
2224  }
2225 
2226  foreach ( $hashes as $name => $hash ) {
2227  if ( !$hash ) {
2228  $this->status->error( 'filedelete-missing', $this->srcRels[$name] );
2229  unset( $hashes[$name] );
2230  }
2231  }
2232 
2233  return $hashes;
2234  }
2235 
2236  protected function doDBInserts() {
2237  $now = time();
2238  $dbw = $this->file->repo->getMasterDB();
2239  $encTimestamp = $dbw->addQuotes( $dbw->timestamp( $now ) );
2240  $encUserId = $dbw->addQuotes( $this->user->getId() );
2241  $encReason = $dbw->addQuotes( $this->reason );
2242  $encGroup = $dbw->addQuotes( 'deleted' );
2243  $ext = $this->file->getExtension();
2244  $dotExt = $ext === '' ? '' : ".$ext";
2245  $encExt = $dbw->addQuotes( $dotExt );
2246  list( $oldRels, $deleteCurrent ) = $this->getOldRels();
2247 
2248  // Bitfields to further suppress the content
2249  if ( $this->suppress ) {
2250  $bitfield = Revision::SUPPRESSED_ALL;
2251  } else {
2252  $bitfield = 'oi_deleted';
2253  }
2254 
2255  if ( $deleteCurrent ) {
2256  $dbw->insertSelect(
2257  'filearchive',
2258  'image',
2259  [
2260  'fa_storage_group' => $encGroup,
2261  'fa_storage_key' => $dbw->conditional(
2262  [ 'img_sha1' => '' ],
2263  $dbw->addQuotes( '' ),
2264  $dbw->buildConcat( [ "img_sha1", $encExt ] )
2265  ),
2266  'fa_deleted_user' => $encUserId,
2267  'fa_deleted_timestamp' => $encTimestamp,
2268  'fa_deleted_reason' => $encReason,
2269  'fa_deleted' => $this->suppress ? $bitfield : 0,
2270  'fa_name' => 'img_name',
2271  'fa_archive_name' => 'NULL',
2272  'fa_size' => 'img_size',
2273  'fa_width' => 'img_width',
2274  'fa_height' => 'img_height',
2275  'fa_metadata' => 'img_metadata',
2276  'fa_bits' => 'img_bits',
2277  'fa_media_type' => 'img_media_type',
2278  'fa_major_mime' => 'img_major_mime',
2279  'fa_minor_mime' => 'img_minor_mime',
2280  'fa_description' => 'img_description',
2281  'fa_user' => 'img_user',
2282  'fa_user_text' => 'img_user_text',
2283  'fa_timestamp' => 'img_timestamp',
2284  'fa_sha1' => 'img_sha1'
2285  ],
2286  [ 'img_name' => $this->file->getName() ],
2287  __METHOD__
2288  );
2289  }
2290 
2291  if ( count( $oldRels ) ) {
2292  $res = $dbw->select(
2293  'oldimage',
2295  [
2296  'oi_name' => $this->file->getName(),
2297  'oi_archive_name' => array_keys( $oldRels )
2298  ],
2299  __METHOD__,
2300  [ 'FOR UPDATE' ]
2301  );
2302  $rowsInsert = [];
2303  foreach ( $res as $row ) {
2304  $rowsInsert[] = [
2305  // Deletion-specific fields
2306  'fa_storage_group' => 'deleted',
2307  'fa_storage_key' => ( $row->oi_sha1 === '' )
2308  ? ''
2309  : "{$row->oi_sha1}{$dotExt}",
2310  'fa_deleted_user' => $this->user->getId(),
2311  'fa_deleted_timestamp' => $dbw->timestamp( $now ),
2312  'fa_deleted_reason' => $this->reason,
2313  // Counterpart fields
2314  'fa_deleted' => $this->suppress ? $bitfield : $row->oi_deleted,
2315  'fa_name' => $row->oi_name,
2316  'fa_archive_name' => $row->oi_archive_name,
2317  'fa_size' => $row->oi_size,
2318  'fa_width' => $row->oi_width,
2319  'fa_height' => $row->oi_height,
2320  'fa_metadata' => $row->oi_metadata,
2321  'fa_bits' => $row->oi_bits,
2322  'fa_media_type' => $row->oi_media_type,
2323  'fa_major_mime' => $row->oi_major_mime,
2324  'fa_minor_mime' => $row->oi_minor_mime,
2325  'fa_description' => $row->oi_description,
2326  'fa_user' => $row->oi_user,
2327  'fa_user_text' => $row->oi_user_text,
2328  'fa_timestamp' => $row->oi_timestamp,
2329  'fa_sha1' => $row->oi_sha1
2330  ];
2331  }
2332 
2333  $dbw->insert( 'filearchive', $rowsInsert, __METHOD__ );
2334  }
2335  }
2336 
2337  function doDBDeletes() {
2338  $dbw = $this->file->repo->getMasterDB();
2339  list( $oldRels, $deleteCurrent ) = $this->getOldRels();
2340 
2341  if ( count( $oldRels ) ) {
2342  $dbw->delete( 'oldimage',
2343  [
2344  'oi_name' => $this->file->getName(),
2345  'oi_archive_name' => array_keys( $oldRels )
2346  ], __METHOD__ );
2347  }
2348 
2349  if ( $deleteCurrent ) {
2350  $dbw->delete( 'image', [ 'img_name' => $this->file->getName() ], __METHOD__ );
2351  }
2352  }
2353 
2358  public function execute() {
2359  $repo = $this->file->getRepo();
2360  $this->file->lock();
2361 
2362  // Prepare deletion batch
2363  $hashes = $this->getHashes();
2364  $this->deletionBatch = [];
2365  $ext = $this->file->getExtension();
2366  $dotExt = $ext === '' ? '' : ".$ext";
2367 
2368  foreach ( $this->srcRels as $name => $srcRel ) {
2369  // Skip files that have no hash (e.g. missing DB record, or sha1 field and file source)
2370  if ( isset( $hashes[$name] ) ) {
2371  $hash = $hashes[$name];
2372  $key = $hash . $dotExt;
2373  $dstRel = $repo->getDeletedHashPath( $key ) . $key;
2374  $this->deletionBatch[$name] = [ $srcRel, $dstRel ];
2375  }
2376  }
2377 
2378  if ( !$repo->hasSha1Storage() ) {
2379  // Removes non-existent file from the batch, so we don't get errors.
2380  // This also handles files in the 'deleted' zone deleted via revision deletion.
2381  $checkStatus = $this->removeNonexistentFiles( $this->deletionBatch );
2382  if ( !$checkStatus->isGood() ) {
2383  $this->status->merge( $checkStatus );
2384  return $this->status;
2385  }
2386  $this->deletionBatch = $checkStatus->value;
2387 
2388  // Execute the file deletion batch
2389  $status = $this->file->repo->deleteBatch( $this->deletionBatch );
2390  if ( !$status->isGood() ) {
2391  $this->status->merge( $status );
2392  }
2393  }
2394 
2395  if ( !$this->status->isOK() ) {
2396  // Critical file deletion error; abort
2397  $this->file->unlock();
2398 
2399  return $this->status;
2400  }
2401 
2402  // Copy the image/oldimage rows to filearchive
2403  $this->doDBInserts();
2404  // Delete image/oldimage rows
2405  $this->doDBDeletes();
2406 
2407  // Commit and return
2408  $this->file->unlock();
2409 
2410  return $this->status;
2411  }
2412 
2418  protected function removeNonexistentFiles( $batch ) {
2419  $files = $newBatch = [];
2420 
2421  foreach ( $batch as $batchItem ) {
2422  list( $src, ) = $batchItem;
2423  $files[$src] = $this->file->repo->getVirtualUrl( 'public' ) . '/' . rawurlencode( $src );
2424  }
2425 
2426  $result = $this->file->repo->fileExistsBatch( $files );
2427  if ( in_array( null, $result, true ) ) {
2428  return Status::newFatal( 'backend-fail-internal',
2429  $this->file->repo->getBackend()->getName() );
2430  }
2431 
2432  foreach ( $batch as $batchItem ) {
2433  if ( $result[$batchItem[0]] ) {
2434  $newBatch[] = $batchItem;
2435  }
2436  }
2437 
2438  return Status::newGood( $newBatch );
2439  }
2440 }
2441 
2442 # ------------------------------------------------------------------------------
2443 
2450  private $file;
2451 
2453  private $cleanupBatch;
2454 
2456  private $ids;
2457 
2459  private $all;
2460 
2462  private $unsuppress = false;
2463 
2468  function __construct( File $file, $unsuppress = false ) {
2469  $this->file = $file;
2470  $this->cleanupBatch = $this->ids = [];
2471  $this->ids = [];
2472  $this->unsuppress = $unsuppress;
2473  }
2474 
2479  public function addId( $fa_id ) {
2480  $this->ids[] = $fa_id;
2481  }
2482 
2487  public function addIds( $ids ) {
2488  $this->ids = array_merge( $this->ids, $ids );
2489  }
2490 
2494  public function addAll() {
2495  $this->all = true;
2496  }
2497 
2506  public function execute() {
2508  global $wgLang;
2509 
2510  $repo = $this->file->getRepo();
2511  if ( !$this->all && !$this->ids ) {
2512  // Do nothing
2513  return $repo->newGood();
2514  }
2515 
2516  $lockOwnsTrx = $this->file->lock();
2517 
2518  $dbw = $this->file->repo->getMasterDB();
2519  $status = $this->file->repo->newGood();
2520 
2521  $exists = (bool)$dbw->selectField( 'image', '1',
2522  [ 'img_name' => $this->file->getName() ],
2523  __METHOD__,
2524  // The lock() should already prevents changes, but this still may need
2525  // to bypass any transaction snapshot. However, if lock() started the
2526  // trx (which it probably did) then snapshot is post-lock and up-to-date.
2527  $lockOwnsTrx ? [] : [ 'LOCK IN SHARE MODE' ]
2528  );
2529 
2530  // Fetch all or selected archived revisions for the file,
2531  // sorted from the most recent to the oldest.
2532  $conditions = [ 'fa_name' => $this->file->getName() ];
2533 
2534  if ( !$this->all ) {
2535  $conditions['fa_id'] = $this->ids;
2536  }
2537 
2538  $result = $dbw->select(
2539  'filearchive',
2541  $conditions,
2542  __METHOD__,
2543  [ 'ORDER BY' => 'fa_timestamp DESC' ]
2544  );
2545 
2546  $idsPresent = [];
2547  $storeBatch = [];
2548  $insertBatch = [];
2549  $insertCurrent = false;
2550  $deleteIds = [];
2551  $first = true;
2552  $archiveNames = [];
2553 
2554  foreach ( $result as $row ) {
2555  $idsPresent[] = $row->fa_id;
2556 
2557  if ( $row->fa_name != $this->file->getName() ) {
2558  $status->error( 'undelete-filename-mismatch', $wgLang->timeanddate( $row->fa_timestamp ) );
2559  $status->failCount++;
2560  continue;
2561  }
2562 
2563  if ( $row->fa_storage_key == '' ) {
2564  // Revision was missing pre-deletion
2565  $status->error( 'undelete-bad-store-key', $wgLang->timeanddate( $row->fa_timestamp ) );
2566  $status->failCount++;
2567  continue;
2568  }
2569 
2570  $deletedRel = $repo->getDeletedHashPath( $row->fa_storage_key ) .
2571  $row->fa_storage_key;
2572  $deletedUrl = $repo->getVirtualUrl() . '/deleted/' . $deletedRel;
2573 
2574  if ( isset( $row->fa_sha1 ) ) {
2575  $sha1 = $row->fa_sha1;
2576  } else {
2577  // old row, populate from key
2578  $sha1 = LocalRepo::getHashFromKey( $row->fa_storage_key );
2579  }
2580 
2581  # Fix leading zero
2582  if ( strlen( $sha1 ) == 32 && $sha1[0] == '0' ) {
2583  $sha1 = substr( $sha1, 1 );
2584  }
2585 
2586  if ( is_null( $row->fa_major_mime ) || $row->fa_major_mime == 'unknown'
2587  || is_null( $row->fa_minor_mime ) || $row->fa_minor_mime == 'unknown'
2588  || is_null( $row->fa_media_type ) || $row->fa_media_type == 'UNKNOWN'
2589  || is_null( $row->fa_metadata )
2590  ) {
2591  // Refresh our metadata
2592  // Required for a new current revision; nice for older ones too. :)
2593  $props = RepoGroup::singleton()->getFileProps( $deletedUrl );
2594  } else {
2595  $props = [
2596  'minor_mime' => $row->fa_minor_mime,
2597  'major_mime' => $row->fa_major_mime,
2598  'media_type' => $row->fa_media_type,
2599  'metadata' => $row->fa_metadata
2600  ];
2601  }
2602 
2603  if ( $first && !$exists ) {
2604  // This revision will be published as the new current version
2605  $destRel = $this->file->getRel();
2606  $insertCurrent = [
2607  'img_name' => $row->fa_name,
2608  'img_size' => $row->fa_size,
2609  'img_width' => $row->fa_width,
2610  'img_height' => $row->fa_height,
2611  'img_metadata' => $props['metadata'],
2612  'img_bits' => $row->fa_bits,
2613  'img_media_type' => $props['media_type'],
2614  'img_major_mime' => $props['major_mime'],
2615  'img_minor_mime' => $props['minor_mime'],
2616  'img_description' => $row->fa_description,
2617  'img_user' => $row->fa_user,
2618  'img_user_text' => $row->fa_user_text,
2619  'img_timestamp' => $row->fa_timestamp,
2620  'img_sha1' => $sha1
2621  ];
2622 
2623  // The live (current) version cannot be hidden!
2624  if ( !$this->unsuppress && $row->fa_deleted ) {
2625  $status->fatal( 'undeleterevdel' );
2626  $this->file->unlock();
2627  return $status;
2628  }
2629  } else {
2630  $archiveName = $row->fa_archive_name;
2631 
2632  if ( $archiveName == '' ) {
2633  // This was originally a current version; we
2634  // have to devise a new archive name for it.
2635  // Format is <timestamp of archiving>!<name>
2636  $timestamp = wfTimestamp( TS_UNIX, $row->fa_deleted_timestamp );
2637 
2638  do {
2639  $archiveName = wfTimestamp( TS_MW, $timestamp ) . '!' . $row->fa_name;
2640  $timestamp++;
2641  } while ( isset( $archiveNames[$archiveName] ) );
2642  }
2643 
2644  $archiveNames[$archiveName] = true;
2645  $destRel = $this->file->getArchiveRel( $archiveName );
2646  $insertBatch[] = [
2647  'oi_name' => $row->fa_name,
2648  'oi_archive_name' => $archiveName,
2649  'oi_size' => $row->fa_size,
2650  'oi_width' => $row->fa_width,
2651  'oi_height' => $row->fa_height,
2652  'oi_bits' => $row->fa_bits,
2653  'oi_description' => $row->fa_description,
2654  'oi_user' => $row->fa_user,
2655  'oi_user_text' => $row->fa_user_text,
2656  'oi_timestamp' => $row->fa_timestamp,
2657  'oi_metadata' => $props['metadata'],
2658  'oi_media_type' => $props['media_type'],
2659  'oi_major_mime' => $props['major_mime'],
2660  'oi_minor_mime' => $props['minor_mime'],
2661  'oi_deleted' => $this->unsuppress ? 0 : $row->fa_deleted,
2662  'oi_sha1' => $sha1 ];
2663  }
2664 
2665  $deleteIds[] = $row->fa_id;
2666 
2667  if ( !$this->unsuppress && $row->fa_deleted & File::DELETED_FILE ) {
2668  // private files can stay where they are
2669  $status->successCount++;
2670  } else {
2671  $storeBatch[] = [ $deletedUrl, 'public', $destRel ];
2672  $this->cleanupBatch[] = $row->fa_storage_key;
2673  }
2674 
2675  $first = false;
2676  }
2677 
2678  unset( $result );
2679 
2680  // Add a warning to the status object for missing IDs
2681  $missingIds = array_diff( $this->ids, $idsPresent );
2682 
2683  foreach ( $missingIds as $id ) {
2684  $status->error( 'undelete-missing-filearchive', $id );
2685  }
2686 
2687  if ( !$repo->hasSha1Storage() ) {
2688  // Remove missing files from batch, so we don't get errors when undeleting them
2689  $checkStatus = $this->removeNonexistentFiles( $storeBatch );
2690  if ( !$checkStatus->isGood() ) {
2691  $status->merge( $checkStatus );
2692  return $status;
2693  }
2694  $storeBatch = $checkStatus->value;
2695 
2696  // Run the store batch
2697  // Use the OVERWRITE_SAME flag to smooth over a common error
2698  $storeStatus = $this->file->repo->storeBatch( $storeBatch, FileRepo::OVERWRITE_SAME );
2699  $status->merge( $storeStatus );
2700 
2701  if ( !$status->isGood() ) {
2702  // Even if some files could be copied, fail entirely as that is the
2703  // easiest thing to do without data loss
2704  $this->cleanupFailedBatch( $storeStatus, $storeBatch );
2705  $status->setOK( false );
2706  $this->file->unlock();
2707 
2708  return $status;
2709  }
2710  }
2711 
2712  // Run the DB updates
2713  // Because we have locked the image row, key conflicts should be rare.
2714  // If they do occur, we can roll back the transaction at this time with
2715  // no data loss, but leaving unregistered files scattered throughout the
2716  // public zone.
2717  // This is not ideal, which is why it's important to lock the image row.
2718  if ( $insertCurrent ) {
2719  $dbw->insert( 'image', $insertCurrent, __METHOD__ );
2720  }
2721 
2722  if ( $insertBatch ) {
2723  $dbw->insert( 'oldimage', $insertBatch, __METHOD__ );
2724  }
2725 
2726  if ( $deleteIds ) {
2727  $dbw->delete( 'filearchive',
2728  [ 'fa_id' => $deleteIds ],
2729  __METHOD__ );
2730  }
2731 
2732  // If store batch is empty (all files are missing), deletion is to be considered successful
2733  if ( $status->successCount > 0 || !$storeBatch || $repo->hasSha1Storage() ) {
2734  if ( !$exists ) {
2735  wfDebug( __METHOD__ . " restored {$status->successCount} items, creating a new current\n" );
2736 
2737  DeferredUpdates::addUpdate( SiteStatsUpdate::factory( [ 'images' => 1 ] ) );
2738 
2739  $this->file->purgeEverything();
2740  } else {
2741  wfDebug( __METHOD__ . " restored {$status->successCount} as archived versions\n" );
2742  $this->file->purgeDescription();
2743  }
2744  }
2745 
2746  $this->file->unlock();
2747 
2748  return $status;
2749  }
2750 
2756  protected function removeNonexistentFiles( $triplets ) {
2757  $files = $filteredTriplets = [];
2758  foreach ( $triplets as $file ) {
2759  $files[$file[0]] = $file[0];
2760  }
2761 
2762  $result = $this->file->repo->fileExistsBatch( $files );
2763  if ( in_array( null, $result, true ) ) {
2764  return Status::newFatal( 'backend-fail-internal',
2765  $this->file->repo->getBackend()->getName() );
2766  }
2767 
2768  foreach ( $triplets as $file ) {
2769  if ( $result[$file[0]] ) {
2770  $filteredTriplets[] = $file;
2771  }
2772  }
2773 
2774  return Status::newGood( $filteredTriplets );
2775  }
2776 
2782  protected function removeNonexistentFromCleanup( $batch ) {
2783  $files = $newBatch = [];
2784  $repo = $this->file->repo;
2785 
2786  foreach ( $batch as $file ) {
2787  $files[$file] = $repo->getVirtualUrl( 'deleted' ) . '/' .
2788  rawurlencode( $repo->getDeletedHashPath( $file ) . $file );
2789  }
2790 
2791  $result = $repo->fileExistsBatch( $files );
2792 
2793  foreach ( $batch as $file ) {
2794  if ( $result[$file] ) {
2795  $newBatch[] = $file;
2796  }
2797  }
2798 
2799  return $newBatch;
2800  }
2801 
2807  public function cleanup() {
2808  if ( !$this->cleanupBatch ) {
2809  return $this->file->repo->newGood();
2810  }
2811 
2812  $this->cleanupBatch = $this->removeNonexistentFromCleanup( $this->cleanupBatch );
2813 
2814  $status = $this->file->repo->cleanupDeletedBatch( $this->cleanupBatch );
2815 
2816  return $status;
2817  }
2818 
2826  protected function cleanupFailedBatch( $storeStatus, $storeBatch ) {
2827  $cleanupBatch = [];
2828 
2829  foreach ( $storeStatus->success as $i => $success ) {
2830  // Check if this item of the batch was successfully copied
2831  if ( $success ) {
2832  // Item was successfully copied and needs to be removed again
2833  // Extract ($dstZone, $dstRel) from the batch
2834  $cleanupBatch[] = [ $storeBatch[$i][1], $storeBatch[$i][2] ];
2835  }
2836  }
2837  $this->file->repo->cleanupBatch( $cleanupBatch );
2838  }
2839 }
2840 
2841 # ------------------------------------------------------------------------------
2842 
2849  protected $file;
2850 
2852  protected $target;
2853 
2854  protected $cur;
2855 
2856  protected $olds;
2857 
2858  protected $oldCount;
2859 
2860  protected $archive;
2861 
2863  protected $db;
2864 
2870  $this->file = $file;
2871  $this->target = $target;
2872  $this->oldHash = $this->file->repo->getHashPath( $this->file->getName() );
2873  $this->newHash = $this->file->repo->getHashPath( $this->target->getDBkey() );
2874  $this->oldName = $this->file->getName();
2875  $this->newName = $this->file->repo->getNameFromTitle( $this->target );
2876  $this->oldRel = $this->oldHash . $this->oldName;
2877  $this->newRel = $this->newHash . $this->newName;
2878  $this->db = $file->getRepo()->getMasterDB();
2879  }
2880 
2884  public function addCurrent() {
2885  $this->cur = [ $this->oldRel, $this->newRel ];
2886  }
2887 
2892  public function addOlds() {
2893  $archiveBase = 'archive';
2894  $this->olds = [];
2895  $this->oldCount = 0;
2896  $archiveNames = [];
2897 
2898  $result = $this->db->select( 'oldimage',
2899  [ 'oi_archive_name', 'oi_deleted' ],
2900  [ 'oi_name' => $this->oldName ],
2901  __METHOD__,
2902  [ 'LOCK IN SHARE MODE' ] // ignore snapshot
2903  );
2904 
2905  foreach ( $result as $row ) {
2906  $archiveNames[] = $row->oi_archive_name;
2907  $oldName = $row->oi_archive_name;
2908  $bits = explode( '!', $oldName, 2 );
2909 
2910  if ( count( $bits ) != 2 ) {
2911  wfDebug( "Old file name missing !: '$oldName' \n" );
2912  continue;
2913  }
2914 
2915  list( $timestamp, $filename ) = $bits;
2916 
2917  if ( $this->oldName != $filename ) {
2918  wfDebug( "Old file name doesn't match: '$oldName' \n" );
2919  continue;
2920  }
2921 
2922  $this->oldCount++;
2923 
2924  // Do we want to add those to oldCount?
2925  if ( $row->oi_deleted & File::DELETED_FILE ) {
2926  continue;
2927  }
2928 
2929  $this->olds[] = [
2930  "{$archiveBase}/{$this->oldHash}{$oldName}",
2931  "{$archiveBase}/{$this->newHash}{$timestamp}!{$this->newName}"
2932  ];
2933  }
2934 
2935  return $archiveNames;
2936  }
2937 
2942  public function execute() {
2943  $repo = $this->file->repo;
2944  $status = $repo->newGood();
2945  $destFile = wfLocalFile( $this->target );
2946 
2947  $this->file->lock(); // begin
2948  $destFile->lock(); // quickly fail if destination is not available
2949 
2950  $triplets = $this->getMoveTriplets();
2951  $checkStatus = $this->removeNonexistentFiles( $triplets );
2952  if ( !$checkStatus->isGood() ) {
2953  $destFile->unlock();
2954  $this->file->unlock();
2955  $status->merge( $checkStatus ); // couldn't talk to file backend
2956  return $status;
2957  }
2958  $triplets = $checkStatus->value;
2959 
2960  // Verify the file versions metadata in the DB.
2961  $statusDb = $this->verifyDBUpdates();
2962  if ( !$statusDb->isGood() ) {
2963  $destFile->unlock();
2964  $this->file->unlock();
2965  $statusDb->setOK( false );
2966 
2967  return $statusDb;
2968  }
2969 
2970  if ( !$repo->hasSha1Storage() ) {
2971  // Copy the files into their new location.
2972  // If a prior process fataled copying or cleaning up files we tolerate any
2973  // of the existing files if they are identical to the ones being stored.
2974  $statusMove = $repo->storeBatch( $triplets, FileRepo::OVERWRITE_SAME );
2975  wfDebugLog( 'imagemove', "Moved files for {$this->file->getName()}: " .
2976  "{$statusMove->successCount} successes, {$statusMove->failCount} failures" );
2977  if ( !$statusMove->isGood() ) {
2978  // Delete any files copied over (while the destination is still locked)
2979  $this->cleanupTarget( $triplets );
2980  $destFile->unlock();
2981  $this->file->unlock();
2982  wfDebugLog( 'imagemove', "Error in moving files: "
2983  . $statusMove->getWikiText( false, false, 'en' ) );
2984  $statusMove->setOK( false );
2985 
2986  return $statusMove;
2987  }
2988  $status->merge( $statusMove );
2989  }
2990 
2991  // Rename the file versions metadata in the DB.
2992  $this->doDBUpdates();
2993 
2994  wfDebugLog( 'imagemove', "Renamed {$this->file->getName()} in database: " .
2995  "{$statusDb->successCount} successes, {$statusDb->failCount} failures" );
2996 
2997  $destFile->unlock();
2998  $this->file->unlock(); // done
2999 
3000  // Everything went ok, remove the source files
3001  $this->cleanupSource( $triplets );
3002 
3003  $status->merge( $statusDb );
3004 
3005  return $status;
3006  }
3007 
3014  protected function verifyDBUpdates() {
3015  $repo = $this->file->repo;
3016  $status = $repo->newGood();
3017  $dbw = $this->db;
3018 
3019  $hasCurrent = $dbw->selectField(
3020  'image',
3021  '1',
3022  [ 'img_name' => $this->oldName ],
3023  __METHOD__,
3024  [ 'FOR UPDATE' ]
3025  );
3026  $oldRowCount = $dbw->selectField(
3027  'oldimage',
3028  'COUNT(*)',
3029  [ 'oi_name' => $this->oldName ],
3030  __METHOD__,
3031  [ 'FOR UPDATE' ]
3032  );
3033 
3034  if ( $hasCurrent ) {
3035  $status->successCount++;
3036  } else {
3037  $status->failCount++;
3038  }
3039  $status->successCount += $oldRowCount;
3040  // T36934: oldCount is based on files that actually exist.
3041  // There may be more DB rows than such files, in which case $affected
3042  // can be greater than $total. We use max() to avoid negatives here.
3043  $status->failCount += max( 0, $this->oldCount - $oldRowCount );
3044  if ( $status->failCount ) {
3045  $status->error( 'imageinvalidfilename' );
3046  }
3047 
3048  return $status;
3049  }
3050 
3055  protected function doDBUpdates() {
3056  $dbw = $this->db;
3057 
3058  // Update current image
3059  $dbw->update(
3060  'image',
3061  [ 'img_name' => $this->newName ],
3062  [ 'img_name' => $this->oldName ],
3063  __METHOD__
3064  );
3065  // Update old images
3066  $dbw->update(
3067  'oldimage',
3068  [
3069  'oi_name' => $this->newName,
3070  'oi_archive_name = ' . $dbw->strreplace( 'oi_archive_name',
3071  $dbw->addQuotes( $this->oldName ), $dbw->addQuotes( $this->newName ) ),
3072  ],
3073  [ 'oi_name' => $this->oldName ],
3074  __METHOD__
3075  );
3076  }
3077 
3082  protected function getMoveTriplets() {
3083  $moves = array_merge( [ $this->cur ], $this->olds );
3084  $triplets = []; // The format is: (srcUrl, destZone, destUrl)
3085 
3086  foreach ( $moves as $move ) {
3087  // $move: (oldRelativePath, newRelativePath)
3088  $srcUrl = $this->file->repo->getVirtualUrl() . '/public/' . rawurlencode( $move[0] );
3089  $triplets[] = [ $srcUrl, 'public', $move[1] ];
3090  wfDebugLog(
3091  'imagemove',
3092  "Generated move triplet for {$this->file->getName()}: {$srcUrl} :: public :: {$move[1]}"
3093  );
3094  }
3095 
3096  return $triplets;
3097  }
3098 
3104  protected function removeNonexistentFiles( $triplets ) {
3105  $files = [];
3106 
3107  foreach ( $triplets as $file ) {
3108  $files[$file[0]] = $file[0];
3109  }
3110 
3111  $result = $this->file->repo->fileExistsBatch( $files );
3112  if ( in_array( null, $result, true ) ) {
3113  return Status::newFatal( 'backend-fail-internal',
3114  $this->file->repo->getBackend()->getName() );
3115  }
3116 
3117  $filteredTriplets = [];
3118  foreach ( $triplets as $file ) {
3119  if ( $result[$file[0]] ) {
3120  $filteredTriplets[] = $file;
3121  } else {
3122  wfDebugLog( 'imagemove', "File {$file[0]} does not exist" );
3123  }
3124  }
3125 
3126  return Status::newGood( $filteredTriplets );
3127  }
3128 
3134  protected function cleanupTarget( $triplets ) {
3135  // Create dest pairs from the triplets
3136  $pairs = [];
3137  foreach ( $triplets as $triplet ) {
3138  // $triplet: (old source virtual URL, dst zone, dest rel)
3139  $pairs[] = [ $triplet[1], $triplet[2] ];
3140  }
3141 
3142  $this->file->repo->cleanupBatch( $pairs );
3143  }
3144 
3150  protected function cleanupSource( $triplets ) {
3151  // Create source file names from the triplets
3152  $files = [];
3153  foreach ( $triplets as $triplet ) {
3154  $files[] = $triplet[0];
3155  }
3156 
3157  $this->file->repo->cleanupBatch( $files );
3158  }
3159 }
3160 
3162  public function __construct( Status $status ) {
3163  parent::__construct(
3164  'actionfailed',
3165  $status->getMessage()
3166  );
3167  }
3168 
3169  public function report() {
3170  global $wgOut;
3171  $wgOut->setStatusCode( 429 );
3172  parent::report();
3173  }
3174 }
LocalFileDeleteBatch\$reason
string $reason
Definition: LocalFile.php:2088
LocalFileMoveBatch\$target
Title $target
Definition: LocalFile.php:2852
LocalFile\$media_type
string $media_type
MEDIATYPE_xxx (bitmap, drawing, audio...)
Definition: LocalFile.php:63
LocalFile\getSha1
getSha1()
Definition: LocalFile.php:1943
ParserOptions
Set options of the Parser.
Definition: ParserOptions.php:33
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:571
Wikimedia\Rdbms\Database
Relational database abstraction object.
Definition: Database.php:45
LocalFileRestoreBatch
Helper class for file undeletion.
Definition: LocalFile.php:2448
LocalFileDeleteBatch\$deletionBatch
array $deletionBatch
Items to be processed in the deletion batch.
Definition: LocalFile.php:2097
$wgUser
$wgUser
Definition: Setup.php:781
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:93
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:1598
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:189
LocalFile\__construct
__construct( $title, $repo)
Constructor.
Definition: LocalFile.php:223
LocalFile\unlock
unlock()
Decrement the lock reference count and end the atomic section if it reaches zero.
Definition: LocalFile.php:2050
$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:990
LocalFile\getTimestamp
getTimestamp()
Definition: LocalFile.php:1915
LocalFileMoveBatch\$file
LocalFile $file
Definition: LocalFile.php:2849
LocalFileDeleteBatch\addOld
addOld( $oldName)
Definition: LocalFile.php:2134
File\isMultipage
isMultipage()
Returns 'true' if this file is a type which supports multiple pages, e.g.
Definition: File.php:1959
LocalFile\getUser
getUser( $type='text')
Returns ID or name of user who uploaded the file.
Definition: LocalFile.php:772
FileRepo\OVERWRITE_SAME
const OVERWRITE_SAME
Definition: FileRepo.php:40
LocalFileDeleteBatch\doDBDeletes
doDBDeletes()
Definition: LocalFile.php:2337
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:1016
File\getRel
getRel()
Get the path of the file relative to the public zone root.
Definition: File.php:1512
LocalFileMoveBatch\cleanupSource
cleanupSource( $triplets)
Cleanup a fully moved array of triplets by deleting the source files.
Definition: LocalFile.php:3150
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:225
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:2083
MediaHandler\filterThumbnailPurgeList
filterThumbnailPurgeList(&$files, $options)
Remove files from the purge list.
Definition: MediaHandler.php:722
LocalFileRestoreBatch\execute
execute()
Run the transaction, except the cleanup batch.
Definition: LocalFile.php:2506
$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:1954
LocalFileRestoreBatch\addId
addId( $fa_id)
Add a file by ID.
Definition: LocalFile.php:2479
wfTimestamp
wfTimestamp( $outputtype=TS_UNIX, $ts=0)
Get a timestamp string in one of various formats.
Definition: GlobalFunctions.php:1994
LinksUpdate\queueRecursiveJobsForTable
static queueRecursiveJobsForTable(Title $title, $table)
Queue a RefreshLinks job for any table.
Definition: LinksUpdate.php:336
LocalFileMoveBatch\verifyDBUpdates
verifyDBUpdates()
Verify the database updates and return a new Status indicating how many rows would be updated.
Definition: LocalFile.php:3014
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:789
$status
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 and Watchlist you will want to construct new ChangesListBooleanFilter or ChangesListStringOptionsFilter objects When constructing you specify which group they belong to You can reuse existing or create your you must register them with $special registerFilterGroup removed from all revisions and log entries to which it was applied This gives extensions a chance to take it off their books as the deletion has already been partly carried out by this point or something similar the user will be unable to create the tag set $status
Definition: hooks.txt:1049
LocalFile\getCacheFields
getCacheFields( $prefix='img_')
Definition: LocalFile.php:347
LocalFileRestoreBatch\$all
bool $all
Add all revisions of the file.
Definition: LocalFile.php:2459
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:2869
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:2358
StatusValue\newFatal
static newFatal( $message)
Factory function for fatal errors.
Definition: StatusValue.php:63
LocalFile\getHistory
getHistory( $limit=null, $start=null, $end=null, $inc=true)
purgeDescription inherited
Definition: LocalFile.php:1050
$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:844
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:68
LocalFile\readOnlyFatalStatus
readOnlyFatalStatus()
Definition: LocalFile.php:2064
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:1277
RequestContext\newExtraneousContext
static newExtraneousContext(Title $title, $request=[])
Create a new extraneous context.
Definition: RequestContext.php:638
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:823
OldLocalFile\selectFields
static selectFields()
Fields in the oldimage table.
Definition: OldLocalFile.php:108
LocalFile\purgeOldThumbnails
purgeOldThumbnails( $archiveName)
Delete cached transformed files for an archived version only.
Definition: LocalFile.php:933
$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:304
LocalFile\getThumbnails
getThumbnails( $archiveName=false)
getTransformScript inherited
Definition: LocalFile.php:881
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:2450
$success
$success
Definition: NoLocalSettings.php:44
LocalFileRestoreBatch\__construct
__construct(File $file, $unsuppress=false)
Definition: LocalFile.php:2468
$type
do that in ParserLimitReportFormat instead use this to modify the parameters of the image and a DIV can begin in one section and end in another Make sure your code can handle that case gracefully See the EditSectionClearerLink extension for an example zero but section is usually empty its values are the globals values before the output is cached my talk my contributions etc etc otherwise the built in rate limiting checks are if enabled allows for interception of redirect as a string mapping parameter names to values & $type
Definition: hooks.txt:2536
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:1875
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:1092
LocalFileDeleteBatch\$status
Status $status
Definition: LocalFile.php:2103
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:1980
Wikimedia\Rdbms\IDatabase
Basic database interface for live and lazy-loaded relation database handles.
Definition: IDatabase.php:40
LocalFile\isCacheable
isCacheable()
Definition: LocalFile.php:1968
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:2858
LocalFileMoveBatch\addOlds
addOlds()
Add the old versions of the image to the batch.
Definition: LocalFile.php:2892
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
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:2826
DeferredUpdates\addCallableUpdate
static addCallableUpdate( $callable, $stage=self::POSTSEND, IDatabase $dbw=null)
Add a callable update.
Definition: DeferredUpdates.php:111
StatusValue\isGood
isGood()
Returns whether the operation completed and didn't have any error or warnings.
Definition: StatusValue.php:116
LocalFile\purgeThumbnails
purgeThumbnails( $options=[])
Delete cached transformed files for the current version only.
Definition: LocalFile.php:956
LocalFileDeleteBatch\addCurrent
addCurrent()
Definition: LocalFile.php:2127
FileRepo\hasSha1Storage
hasSha1Storage()
Returns whether or not storage is SHA-1 based.
Definition: FileRepo.php:1915
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:1620
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:1924
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:904
File\getThumbPath
getThumbPath( $suffix=false)
Get the path of the thumbnail directory, or a particular file if $suffix is specified.
Definition: File.php:1611
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:610
FileBackend\isStoragePath
static isStoragePath( $path)
Check if a given path is a "mwstore://" path.
Definition: FileBackend.php:1436
LocalFile\deleteOld
deleteOld( $archiveName, $reason, $suppress=false, $user=null)
Delete an old version of the file.
Definition: LocalFile.php:1798
LocalFileLockError\__construct
__construct(Status $status)
Definition: LocalFile.php:3162
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:3169
$content
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 and Watchlist you will want to construct new ChangesListBooleanFilter or ChangesListStringOptionsFilter objects When constructing you specify which group they belong to You can reuse existing or create your you must register them with $special registerFilterGroup removed from all revisions and log entries to which it was applied This gives extensions a chance to take it off their books as the deletion has already been partly carried out by this point or something similar the user will be unable to create the tag set and then return false from the hook function Ensure you consume the ChangeTagAfterDelete hook to carry out custom deletion actions as context called by AbstractContent::getParserOutput May be used to override the normal model specific rendering of page content $content
Definition: hooks.txt:1049
$page
do that in ParserLimitReportFormat instead use this to modify the parameters of the image and a DIV can begin in one section and end in another Make sure your code can handle that case gracefully See the EditSectionClearerLink extension for an example zero but section is usually empty its values are the globals values before the output is cached $page
Definition: hooks.txt:2536
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:2072
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:1403
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
$limit
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 and Watchlist you will want to construct new ChangesListBooleanFilter or ChangesListStringOptionsFilter objects When constructing you specify which group they belong to You can reuse existing or create your you must register them with $special registerFilterGroup removed from all revisions and log entries to which it was applied This gives extensions a chance to take it off their books as the deletion has already been partly carried out by this point or something similar the user will be unable to create the tag set and then return false from the hook function Ensure you consume the ChangeTagAfterDelete hook to carry out custom deletion actions as context called by AbstractContent::getParserOutput May be used to override the normal model specific rendering of page content as context as context the output can only depend on parameters provided to this hook not on global state indicating whether full HTML should be generated If generation of HTML may be but other information should still be present in the ParserOutput object to manipulate or replace but no entry for that model exists in $wgContentHandlers please use GetContentModels hook to make them known to core if desired 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 inclusive $limit
Definition: hooks.txt:1049
LocalFileDeleteBatch\$file
LocalFile $file
Definition: LocalFile.php:2085
LocalFileDeleteBatch\$suppress
bool $suppress
Whether to suppress all suppressable fields when deleting.
Definition: LocalFile.php:2100
$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:989
Title\makeTitle
static makeTitle( $ns, $title, $fragment='', $interwiki='')
Create a new Title from a namespace index and a DB key.
Definition: Title.php:514
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:2106
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:2863
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:415
LocalFileRestoreBatch\addAll
addAll()
Add all revisions of the file.
Definition: LocalFile.php:2494
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:2462
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:999
LocalFile\setProps
setProps( $info)
Set properties in this object to be equal to those given in the associative array $info.
Definition: LocalFile.php:672
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:2860
LocalFile\getMimeType
getMimeType()
Returns the MIME type of the file.
Definition: LocalFile.php:833
LocalFile\getDescription
getDescription( $audience=self::FOR_PUBLIC, User $user=null)
Definition: LocalFile.php:1899
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:2122
File\purgeDescription
purgeDescription()
Purge the file description page, but don't go after pages using the file.
Definition: File.php:1429
LocalFileDeleteBatch\getHashes
getHashes()
Definition: LocalFile.php:2180
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:2005
File\$handler
MediaHandler $handler
Definition: File.php:114
LocalFileLockError
Definition: LocalFile.php:3161
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:2258
StatusValue\newGood
static newGood( $value=null)
Factory function for good results.
Definition: StatusValue.php:76
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:3082
LocalFile\releaseFileLock
releaseFileLock()
Definition: LocalFile.php:1990
title
title
Definition: parserTests.txt:211
LocalFile\upgradeRow
upgradeRow()
Fix assorted version-related problems with the image row by reloading it from the file.
Definition: LocalFile.php:617
LocalFile\load
load( $flags=0)
Load file metadata from cache or DB, unless already loaded.
Definition: LocalFile.php:553
WANObjectCache
Multi-datacenter aware caching interface.
Definition: WANObjectCache.php:81
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:1103
LocalFileRestoreBatch\$cleanupBatch
array $cleanupBatch
List of file IDs to restore.
Definition: LocalFile.php:2453
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:813
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:1655
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:1635
Wikimedia\Rdbms\IDatabase\update
update( $table, $values, $conds, $fname=__METHOD__, $options=[])
UPDATE wrapper.
MediaHandler\getStreamHeaders
getStreamHeaders( $metadata)
Get useful response headers for GET/HEAD requests for a file with the given metadata.
Definition: MediaHandler.php:313
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:2094
LocalFileDeleteBatch\addOlds
addOlds()
Add the old versions of the image to the batch.
Definition: LocalFile.php:2143
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:1247
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:1278
EDIT_NEW
const EDIT_NEW
Definition: Defines.php:150
LocalFileRestoreBatch\$ids
array $ids
List of file IDs to restore.
Definition: LocalFile.php:2456
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:198
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
Wikimedia\Rdbms\IDatabase\selectField
selectField( $table, $var, $cond='', $fname=__METHOD__, $options=[])
A SELECT wrapper which returns a single field from a single result row.
Title
Represents a title within MediaWiki.
Definition: Title.php:39
LocalFile\getMetadata
getMetadata()
Get handler-specific metadata.
Definition: LocalFile.php:805
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:2487
$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
File\assertRepoDefined
assertRepoDefined()
Assert that $this->repo is set to a valid FileRepo instance.
Definition: File.php:2248
LocalFileMoveBatch\execute
execute()
Perform the move.
Definition: LocalFile.php:2942
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:1880
LocalFile\upload
upload( $src, $comment, $pageText, $flags=0, $props=false, $timestamp=false, $user=null, $tags=[])
getHashPath inherited
Definition: LocalFile.php:1179
LocalFile\resetHistory
resetHistory()
Reset the history pointer to the first element of the history.
Definition: LocalFile.php:1141
LocalFileMoveBatch\removeNonexistentFiles
removeNonexistentFiles( $triplets)
Removes non-existent files from move batch.
Definition: LocalFile.php:3104
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:2807
JobQueueGroup\singleton
static singleton( $wiki=false)
Definition: JobQueueGroup.php:71
$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:1741
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:2091
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:2756
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:2847
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:45
$batch
$batch
Definition: linkcache.txt:23
LocalFileDeleteBatch\getOldRels
getOldRels()
Definition: LocalFile.php:2164
ManualLogEntry
Class for creating log entries manually, to inject them into the database.
Definition: LogEntry.php:396
width
width
Definition: parserTests.txt:155
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:1693
$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:1601
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:153
File\getRepo
getRepo()
Returns the repository.
Definition: File.php:1854
LocalFile\getWidth
getWidth( $page=1)
Return the width of the image.
Definition: LocalFile.php:718
ArchivedFile\selectFields
static selectFields()
Fields in the filearchive table.
Definition: ArchivedFile.php:220
File\getThumbUrl
getThumbUrl( $suffix=false)
Get the URL of the thumbnail directory, or a particular file if $suffix is specified.
Definition: File.php:1693
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:1365
$wgOut
$wgOut
Definition: Setup.php:791
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:70
LocalFile\getHeight
getHeight( $page=1)
Return the height of the image.
Definition: LocalFile.php:745
LocalFile\isMissing
isMissing()
splitMime inherited
Definition: LocalFile.php:703
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:2147
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:50
LocalFileDeleteBatch\__construct
__construct(File $file, $reason='', $suppress=false, $user=null)
Definition: LocalFile.php:2114
wfLocalFile
wfLocalFile( $title)
Get an object referring to a locally registered file.
Definition: GlobalFunctions.php:3112
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:131
LocalFile\getDescriptionUrl
getDescriptionUrl()
isMultipage inherited
Definition: LocalFile.php:1868
LocalFile\move
move( $target)
getLinksTo inherited
Definition: LocalFile.php:1681
LocalFileMoveBatch\addCurrent
addCurrent()
Add the current image to the batch.
Definition: LocalFile.php:2884
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:199
LocalFileDeleteBatch\removeNonexistentFiles
removeNonexistentFiles( $batch)
Removes non-existent files from a deletion batch.
Definition: LocalFile.php:2418
LocalFileMoveBatch\doDBUpdates
doDBUpdates()
Do the database updates and return a new Status indicating how many rows where updated.
Definition: LocalFile.php:3055
$options
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 and Watchlist you will want to construct new ChangesListBooleanFilter or ChangesListStringOptionsFilter objects When constructing you specify which group they belong to You can reuse existing or create your you must register them with $special registerFilterGroup removed from all revisions and log entries to which it was applied This gives extensions a chance to take it off their books as the deletion has already been partly carried out by this point or something similar the user will be unable to create the tag set and then return false from the hook function Ensure you consume the ChangeTagAfterDelete hook to carry out custom deletion actions as context called by AbstractContent::getParserOutput May be used to override the normal model specific rendering of page content as context as context $options
Definition: hooks.txt:1049
LocalFile\restore
restore( $versions=[], $unsuppress=false)
Restore all or specified deleted revisions to the given file.
Definition: LocalFile.php:1834
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:1441
File\getHashPath
getHashPath()
Get the filename hash component of the directory including trailing slash, e.g.
Definition: File.php:1497
LocalFileMoveBatch\$olds
$olds
Definition: LocalFile.php:2856
$flags
it s the revision text itself In either if gzip is the revision text is gzipped $flags
Definition: hooks.txt:2749
LocalFile\exists
exists()
canRender inherited
Definition: LocalFile.php:860
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:1410
LocalFileMoveBatch\$cur
$cur
Definition: LocalFile.php:2854
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:915
LocalFileMoveBatch\cleanupTarget
cleanupTarget( $triplets)
Cleanup a partially moved array of triplets by deleting the target files.
Definition: LocalFile.php:3134
File\getVirtualUrl
getVirtualUrl( $suffix=false)
Get the public zone virtual URL for a current version source file.
Definition: File.php:1713
LocalFileDeleteBatch\doDBInserts
doDBInserts()
Definition: LocalFile.php:2236
LocalFileRestoreBatch\removeNonexistentFromCleanup
removeNonexistentFromCleanup( $batch)
Removes non-existent files from a cleanup batch.
Definition: LocalFile.php:2782
$wgContLang
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 and the content language as $wgContLang
Definition: design.txt:56
LogFormatter\newFromEntry
static newFromEntry(LogEntry $entry)
Constructs a new formatter suitable for given entry.
Definition: LogFormatter.php:48
Article\purgePatrolFooterCache
static purgePatrolFooterCache( $articleID)
Purge the cache used to check if it is worth showing the patrol footer For example,...
Definition: Article.php:1113