MediaWiki  1.23.8
LocalFile.php
Go to the documentation of this file.
1 <?php
27 define( 'MW_FILE_VERSION', 9 );
28 
46 class LocalFile extends File {
47  const CACHE_FIELD_MAX_LEN = 1000;
48 
50  protected $fileExists;
51 
53  protected $width;
54 
56  protected $height;
57 
59  protected $bits;
60 
62  protected $media_type;
63 
65  protected $mime;
66 
68  protected $size;
69 
71  protected $metadata;
72 
74  protected $sha1;
75 
77  protected $dataLoaded;
78 
80  protected $extraDataLoaded;
81 
83  protected $deleted;
84 
86  protected $repoClass = 'LocalRepo';
87 
89  private $historyLine;
90 
92  private $historyRes;
93 
95  private $major_mime;
96 
98  private $minor_mime;
99 
101  private $timestamp;
102 
104  private $user;
105 
107  private $user_text;
108 
110  private $description;
111 
113  private $upgraded;
114 
116  private $locked;
117 
119  private $lockedOwnTrx;
120 
122  private $missing;
123 
124  const LOAD_ALL = 1; // integer; load all the lazy fields too (like metadata)
125 
138  static function newFromTitle( $title, $repo, $unused = null ) {
139  return new self( $title, $repo );
140  }
141 
151  static function newFromRow( $row, $repo ) {
152  $title = Title::makeTitle( NS_FILE, $row->img_name );
153  $file = new self( $title, $repo );
154  $file->loadFromRow( $row );
155 
156  return $file;
157  }
158 
168  static function newFromKey( $sha1, $repo, $timestamp = false ) {
169  $dbr = $repo->getSlaveDB();
170 
171  $conds = array( 'img_sha1' => $sha1 );
172  if ( $timestamp ) {
173  $conds['img_timestamp'] = $dbr->timestamp( $timestamp );
174  }
175 
176  $row = $dbr->selectRow( 'image', self::selectFields(), $conds, __METHOD__ );
177  if ( $row ) {
178  return self::newFromRow( $row, $repo );
179  } else {
180  return false;
181  }
182  }
183 
188  static function selectFields() {
189  return array(
190  'img_name',
191  'img_size',
192  'img_width',
193  'img_height',
194  'img_metadata',
195  'img_bits',
196  'img_media_type',
197  'img_major_mime',
198  'img_minor_mime',
199  'img_description',
200  'img_user',
201  'img_user_text',
202  'img_timestamp',
203  'img_sha1',
204  );
205  }
206 
211  function __construct( $title, $repo ) {
212  parent::__construct( $title, $repo );
213 
214  $this->metadata = '';
215  $this->historyLine = 0;
216  $this->historyRes = null;
217  $this->dataLoaded = false;
218  $this->extraDataLoaded = false;
219 
220  $this->assertRepoDefined();
221  $this->assertTitleDefined();
222  }
223 
229  function getCacheKey() {
230  $hashedName = md5( $this->getName() );
231 
232  return $this->repo->getSharedCacheKey( 'file', $hashedName );
233  }
234 
239  function loadFromCache() {
240  global $wgMemc;
241 
242  wfProfileIn( __METHOD__ );
243  $this->dataLoaded = false;
244  $this->extraDataLoaded = false;
245  $key = $this->getCacheKey();
246 
247  if ( !$key ) {
248  wfProfileOut( __METHOD__ );
249 
250  return false;
251  }
252 
253  $cachedValues = $wgMemc->get( $key );
254 
255  // Check if the key existed and belongs to this version of MediaWiki
256  if ( isset( $cachedValues['version'] ) && $cachedValues['version'] == MW_FILE_VERSION ) {
257  wfDebug( "Pulling file metadata from cache key $key\n" );
258  $this->fileExists = $cachedValues['fileExists'];
259  if ( $this->fileExists ) {
260  $this->setProps( $cachedValues );
261  }
262  $this->dataLoaded = true;
263  $this->extraDataLoaded = true;
264  foreach ( $this->getLazyCacheFields( '' ) as $field ) {
265  $this->extraDataLoaded = $this->extraDataLoaded && isset( $cachedValues[$field] );
266  }
267  }
268 
269  if ( $this->dataLoaded ) {
270  wfIncrStats( 'image_cache_hit' );
271  } else {
272  wfIncrStats( 'image_cache_miss' );
273  }
274 
275  wfProfileOut( __METHOD__ );
276 
277  return $this->dataLoaded;
278  }
279 
283  function saveToCache() {
284  global $wgMemc;
285 
286  $this->load();
287  $key = $this->getCacheKey();
288 
289  if ( !$key ) {
290  return;
291  }
292 
293  $fields = $this->getCacheFields( '' );
294  $cache = array( 'version' => MW_FILE_VERSION );
295  $cache['fileExists'] = $this->fileExists;
296 
297  if ( $this->fileExists ) {
298  foreach ( $fields as $field ) {
299  $cache[$field] = $this->$field;
300  }
301  }
302 
303  // Strip off excessive entries from the subset of fields that can become large.
304  // If the cache value gets to large it will not fit in memcached and nothing will
305  // get cached at all, causing master queries for any file access.
306  foreach ( $this->getLazyCacheFields( '' ) as $field ) {
307  if ( isset( $cache[$field] ) && strlen( $cache[$field] ) > 100 * 1024 ) {
308  unset( $cache[$field] ); // don't let the value get too big
309  }
310  }
311 
312  // Cache presence for 1 week and negatives for 1 day
313  $wgMemc->set( $key, $cache, $this->fileExists ? 86400 * 7 : 86400 );
314  }
315 
319  function loadFromFile() {
320  $props = $this->repo->getFileProps( $this->getVirtualUrl() );
321  $this->setProps( $props );
322  }
323 
328  function getCacheFields( $prefix = 'img_' ) {
329  static $fields = array( 'size', 'width', 'height', 'bits', 'media_type',
330  'major_mime', 'minor_mime', 'metadata', 'timestamp', 'sha1', 'user',
331  'user_text', 'description' );
332  static $results = array();
333 
334  if ( $prefix == '' ) {
335  return $fields;
336  }
337 
338  if ( !isset( $results[$prefix] ) ) {
339  $prefixedFields = array();
340  foreach ( $fields as $field ) {
341  $prefixedFields[] = $prefix . $field;
342  }
343  $results[$prefix] = $prefixedFields;
344  }
345 
346  return $results[$prefix];
347  }
348 
353  function getLazyCacheFields( $prefix = 'img_' ) {
354  static $fields = array( 'metadata' );
355  static $results = array();
356 
357  if ( $prefix == '' ) {
358  return $fields;
359  }
360 
361  if ( !isset( $results[$prefix] ) ) {
362  $prefixedFields = array();
363  foreach ( $fields as $field ) {
364  $prefixedFields[] = $prefix . $field;
365  }
366  $results[$prefix] = $prefixedFields;
367  }
368 
369  return $results[$prefix];
370  }
371 
375  function loadFromDB() {
376  # Polymorphic function name to distinguish foreign and local fetches
377  $fname = get_class( $this ) . '::' . __FUNCTION__;
378  wfProfileIn( $fname );
379 
380  # Unconditionally set loaded=true, we don't want the accessors constantly rechecking
381  $this->dataLoaded = true;
382  $this->extraDataLoaded = true;
383 
384  $dbr = $this->repo->getMasterDB();
385  $row = $dbr->selectRow( 'image', $this->getCacheFields( 'img_' ),
386  array( 'img_name' => $this->getName() ), $fname );
387 
388  if ( $row ) {
389  $this->loadFromRow( $row );
390  } else {
391  $this->fileExists = false;
392  }
393 
394  wfProfileOut( $fname );
395  }
396 
401  protected function loadExtraFromDB() {
402  # Polymorphic function name to distinguish foreign and local fetches
403  $fname = get_class( $this ) . '::' . __FUNCTION__;
404  wfProfileIn( $fname );
405 
406  # Unconditionally set loaded=true, we don't want the accessors constantly rechecking
407  $this->extraDataLoaded = true;
408 
409  $dbr = $this->repo->getSlaveDB();
410  // In theory the file could have just been renamed/deleted...oh well
411  $row = $dbr->selectRow( 'image', $this->getLazyCacheFields( 'img_' ),
412  array( 'img_name' => $this->getName() ), $fname );
413 
414  if ( !$row ) { // fallback to master
415  $dbr = $this->repo->getMasterDB();
416  $row = $dbr->selectRow( 'image', $this->getLazyCacheFields( 'img_' ),
417  array( 'img_name' => $this->getName() ), $fname );
418  }
419 
420  if ( $row ) {
421  foreach ( $this->unprefixRow( $row, 'img_' ) as $name => $value ) {
422  $this->$name = $value;
423  }
424  } else {
425  wfProfileOut( $fname );
426  throw new MWException( "Could not find data for image '{$this->getName()}'." );
427  }
428 
429  wfProfileOut( $fname );
430  }
431 
438  protected function unprefixRow( $row, $prefix = 'img_' ) {
439  $array = (array)$row;
440  $prefixLength = strlen( $prefix );
441 
442  // Sanity check prefix once
443  if ( substr( key( $array ), 0, $prefixLength ) !== $prefix ) {
444  throw new MWException( __METHOD__ . ': incorrect $prefix parameter' );
445  }
446 
447  $decoded = array();
448  foreach ( $array as $name => $value ) {
449  $decoded[substr( $name, $prefixLength )] = $value;
450  }
451 
452  return $decoded;
453  }
454 
463  function decodeRow( $row, $prefix = 'img_' ) {
464  $decoded = $this->unprefixRow( $row, $prefix );
465 
466  $decoded['timestamp'] = wfTimestamp( TS_MW, $decoded['timestamp'] );
467 
468  $decoded['metadata'] = $this->repo->getSlaveDB()->decodeBlob( $decoded['metadata'] );
469 
470  if ( empty( $decoded['major_mime'] ) ) {
471  $decoded['mime'] = 'unknown/unknown';
472  } else {
473  if ( !$decoded['minor_mime'] ) {
474  $decoded['minor_mime'] = 'unknown';
475  }
476  $decoded['mime'] = $decoded['major_mime'] . '/' . $decoded['minor_mime'];
477  }
478 
479  # Trim zero padding from char/binary field
480  $decoded['sha1'] = rtrim( $decoded['sha1'], "\0" );
481 
482  return $decoded;
483  }
484 
488  function loadFromRow( $row, $prefix = 'img_' ) {
489  $this->dataLoaded = true;
490  $this->extraDataLoaded = true;
491 
492  $array = $this->decodeRow( $row, $prefix );
493 
494  foreach ( $array as $name => $value ) {
495  $this->$name = $value;
496  }
497 
498  $this->fileExists = true;
499  $this->maybeUpgradeRow();
500  }
501 
506  function load( $flags = 0 ) {
507  if ( !$this->dataLoaded ) {
508  if ( !$this->loadFromCache() ) {
509  $this->loadFromDB();
510  $this->saveToCache();
511  }
512  $this->dataLoaded = true;
513  }
514  if ( ( $flags & self::LOAD_ALL ) && !$this->extraDataLoaded ) {
515  $this->loadExtraFromDB();
516  }
517  }
518 
522  function maybeUpgradeRow() {
523  global $wgUpdateCompatibleMetadata;
524  if ( wfReadOnly() ) {
525  return;
526  }
527 
528  if ( is_null( $this->media_type ) ||
529  $this->mime == 'image/svg'
530  ) {
531  $this->upgradeRow();
532  $this->upgraded = true;
533  } else {
534  $handler = $this->getHandler();
535  if ( $handler ) {
536  $validity = $handler->isMetadataValid( $this, $this->getMetadata() );
537  if ( $validity === MediaHandler::METADATA_BAD
538  || ( $validity === MediaHandler::METADATA_COMPATIBLE && $wgUpdateCompatibleMetadata )
539  ) {
540  $this->upgradeRow();
541  $this->upgraded = true;
542  }
543  }
544  }
545  }
546 
547  function getUpgraded() {
548  return $this->upgraded;
549  }
550 
554  function upgradeRow() {
555  wfProfileIn( __METHOD__ );
556 
557  $this->lock(); // begin
558 
559  $this->loadFromFile();
560 
561  # Don't destroy file info of missing files
562  if ( !$this->fileExists ) {
563  wfDebug( __METHOD__ . ": file does not exist, aborting\n" );
564  wfProfileOut( __METHOD__ );
565 
566  return;
567  }
568 
569  $dbw = $this->repo->getMasterDB();
570  list( $major, $minor ) = self::splitMime( $this->mime );
571 
572  if ( wfReadOnly() ) {
573  wfProfileOut( __METHOD__ );
574 
575  return;
576  }
577  wfDebug( __METHOD__ . ': upgrading ' . $this->getName() . " to the current schema\n" );
578 
579  $dbw->update( 'image',
580  array(
581  'img_size' => $this->size, // sanity
582  'img_width' => $this->width,
583  'img_height' => $this->height,
584  'img_bits' => $this->bits,
585  'img_media_type' => $this->media_type,
586  'img_major_mime' => $major,
587  'img_minor_mime' => $minor,
588  'img_metadata' => $dbw->encodeBlob( $this->metadata ),
589  'img_sha1' => $this->sha1,
590  ),
591  array( 'img_name' => $this->getName() ),
592  __METHOD__
593  );
594 
595  $this->saveToCache();
596 
597  $this->unlock(); // done
598 
599  wfProfileOut( __METHOD__ );
600  }
601 
610  function setProps( $info ) {
611  $this->dataLoaded = true;
612  $fields = $this->getCacheFields( '' );
613  $fields[] = 'fileExists';
614 
615  foreach ( $fields as $field ) {
616  if ( isset( $info[$field] ) ) {
617  $this->$field = $info[$field];
618  }
619  }
620 
621  // Fix up mime fields
622  if ( isset( $info['major_mime'] ) ) {
623  $this->mime = "{$info['major_mime']}/{$info['minor_mime']}";
624  } elseif ( isset( $info['mime'] ) ) {
625  $this->mime = $info['mime'];
626  list( $this->major_mime, $this->minor_mime ) = self::splitMime( $this->mime );
627  }
628  }
629 
641  function isMissing() {
642  if ( $this->missing === null ) {
643  list( $fileExists ) = $this->repo->fileExists( $this->getVirtualUrl() );
644  $this->missing = !$fileExists;
645  }
646 
647  return $this->missing;
648  }
649 
656  public function getWidth( $page = 1 ) {
657  $this->load();
658 
659  if ( $this->isMultipage() ) {
660  $handler = $this->getHandler();
661  if ( !$handler ) {
662  return 0;
663  }
664  $dim = $handler->getPageDimensions( $this, $page );
665  if ( $dim ) {
666  return $dim['width'];
667  } else {
668  // For non-paged media, the false goes through an
669  // intval, turning failure into 0, so do same here.
670  return 0;
671  }
672  } else {
673  return $this->width;
674  }
675  }
676 
683  public function getHeight( $page = 1 ) {
684  $this->load();
685 
686  if ( $this->isMultipage() ) {
687  $handler = $this->getHandler();
688  if ( !$handler ) {
689  return 0;
690  }
691  $dim = $handler->getPageDimensions( $this, $page );
692  if ( $dim ) {
693  return $dim['height'];
694  } else {
695  // For non-paged media, the false goes through an
696  // intval, turning failure into 0, so do same here.
697  return 0;
698  }
699  } else {
700  return $this->height;
701  }
702  }
703 
710  function getUser( $type = 'text' ) {
711  $this->load();
712 
713  if ( $type == 'text' ) {
714  return $this->user_text;
715  } elseif ( $type == 'id' ) {
716  return $this->user;
717  }
718  }
719 
724  function getMetadata() {
725  $this->load( self::LOAD_ALL ); // large metadata is loaded in another step
726  return $this->metadata;
727  }
728 
732  function getBitDepth() {
733  $this->load();
734 
735  return $this->bits;
736  }
737 
742  public function getSize() {
743  $this->load();
744 
745  return $this->size;
746  }
747 
752  function getMimeType() {
753  $this->load();
754 
755  return $this->mime;
756  }
757 
763  function getMediaType() {
764  $this->load();
765 
766  return $this->media_type;
767  }
768 
779  public function exists() {
780  $this->load();
781 
782  return $this->fileExists;
783  }
784 
797  function migrateThumbFile( $thumbName ) {
798  /* Old code for bug 2532
799  $thumbDir = $this->getThumbPath();
800  $thumbPath = "$thumbDir/$thumbName";
801  if ( is_dir( $thumbPath ) ) {
802  // Directory where file should be
803  // This happened occasionally due to broken migration code in 1.5
804  // Rename to broken-*
805  for ( $i = 0; $i < 100; $i++ ) {
806  $broken = $this->repo->getZonePath( 'public' ) . "/broken-$i-$thumbName";
807  if ( !file_exists( $broken ) ) {
808  rename( $thumbPath, $broken );
809  break;
810  }
811  }
812  // Doesn't exist anymore
813  clearstatcache();
814  }
815  */
816  /*
817  if ( $this->repo->fileExists( $thumbDir ) ) {
818  // Delete file where directory should be
819  $this->repo->cleanupBatch( array( $thumbDir ) );
820  }
821  */
822  }
823 
833  function getThumbnails( $archiveName = false ) {
834  if ( $archiveName ) {
835  $dir = $this->getArchiveThumbPath( $archiveName );
836  } else {
837  $dir = $this->getThumbPath();
838  }
839 
840  $backend = $this->repo->getBackend();
841  $files = array( $dir );
842  try {
843  $iterator = $backend->getFileList( array( 'dir' => $dir ) );
844  foreach ( $iterator as $file ) {
845  $files[] = $file;
846  }
847  } catch ( FileBackendError $e ) {
848  } // suppress (bug 54674)
849 
850  return $files;
851  }
852 
856  function purgeMetadataCache() {
857  $this->loadFromDB();
858  $this->saveToCache();
859  $this->purgeHistory();
860  }
861 
867  function purgeHistory() {
868  global $wgMemc;
869 
870  $hashedName = md5( $this->getName() );
871  $oldKey = $this->repo->getSharedCacheKey( 'oldfile', $hashedName );
872 
873  if ( $oldKey ) {
874  $wgMemc->delete( $oldKey );
875  }
876  }
877 
885  function purgeCache( $options = array() ) {
886  wfProfileIn( __METHOD__ );
887  // Refresh metadata cache
888  $this->purgeMetadataCache();
889 
890  // Delete thumbnails
891  $this->purgeThumbnails( $options );
892 
893  // Purge squid cache for this file
894  SquidUpdate::purge( array( $this->getURL() ) );
895  wfProfileOut( __METHOD__ );
896  }
897 
902  function purgeOldThumbnails( $archiveName ) {
903  global $wgUseSquid;
904  wfProfileIn( __METHOD__ );
905 
906  // Get a list of old thumbnails and URLs
907  $files = $this->getThumbnails( $archiveName );
908 
909  // Purge any custom thumbnail caches
910  wfRunHooks( 'LocalFilePurgeThumbnails', array( $this, $archiveName ) );
911 
912  $dir = array_shift( $files );
913  $this->purgeThumbList( $dir, $files );
914 
915  // Purge the squid
916  if ( $wgUseSquid ) {
917  $urls = array();
918  foreach ( $files as $file ) {
919  $urls[] = $this->getArchiveThumbUrl( $archiveName, $file );
920  }
921  SquidUpdate::purge( $urls );
922  }
923 
924  wfProfileOut( __METHOD__ );
925  }
926 
930  function purgeThumbnails( $options = array() ) {
931  global $wgUseSquid;
932  wfProfileIn( __METHOD__ );
933 
934  // Delete thumbnails
935  $files = $this->getThumbnails();
936  // Always purge all files from squid regardless of handler filters
937  $urls = array();
938  if ( $wgUseSquid ) {
939  foreach ( $files as $file ) {
940  $urls[] = $this->getThumbUrl( $file );
941  }
942  array_shift( $urls ); // don't purge directory
943  }
944 
945  // Give media handler a chance to filter the file purge list
946  if ( !empty( $options['forThumbRefresh'] ) ) {
947  $handler = $this->getHandler();
948  if ( $handler ) {
950  }
951  }
952 
953  // Purge any custom thumbnail caches
954  wfRunHooks( 'LocalFilePurgeThumbnails', array( $this, false ) );
955 
956  $dir = array_shift( $files );
957  $this->purgeThumbList( $dir, $files );
958 
959  // Purge the squid
960  if ( $wgUseSquid ) {
961  SquidUpdate::purge( $urls );
962  }
963 
964  wfProfileOut( __METHOD__ );
965  }
966 
972  protected function purgeThumbList( $dir, $files ) {
973  $fileListDebug = strtr(
974  var_export( $files, true ),
975  array( "\n" => '' )
976  );
977  wfDebug( __METHOD__ . ": $fileListDebug\n" );
978 
979  $purgeList = array();
980  foreach ( $files as $file ) {
981  # Check that the base file name is part of the thumb name
982  # This is a basic sanity check to avoid erasing unrelated directories
983  if ( strpos( $file, $this->getName() ) !== false
984  || strpos( $file, "-thumbnail" ) !== false // "short" thumb name
985  ) {
986  $purgeList[] = "{$dir}/{$file}";
987  }
988  }
989 
990  # Delete the thumbnails
991  $this->repo->quickPurgeBatch( $purgeList );
992  # Clear out the thumbnail directory if empty
993  $this->repo->quickCleanDir( $dir );
994  }
995 
1006  function getHistory( $limit = null, $start = null, $end = null, $inc = true ) {
1007  $dbr = $this->repo->getSlaveDB();
1008  $tables = array( 'oldimage' );
1009  $fields = OldLocalFile::selectFields();
1010  $conds = $opts = $join_conds = array();
1011  $eq = $inc ? '=' : '';
1012  $conds[] = "oi_name = " . $dbr->addQuotes( $this->title->getDBkey() );
1013 
1014  if ( $start ) {
1015  $conds[] = "oi_timestamp <$eq " . $dbr->addQuotes( $dbr->timestamp( $start ) );
1016  }
1017 
1018  if ( $end ) {
1019  $conds[] = "oi_timestamp >$eq " . $dbr->addQuotes( $dbr->timestamp( $end ) );
1020  }
1021 
1022  if ( $limit ) {
1023  $opts['LIMIT'] = $limit;
1024  }
1025 
1026  // Search backwards for time > x queries
1027  $order = ( !$start && $end !== null ) ? 'ASC' : 'DESC';
1028  $opts['ORDER BY'] = "oi_timestamp $order";
1029  $opts['USE INDEX'] = array( 'oldimage' => 'oi_name_timestamp' );
1030 
1031  wfRunHooks( 'LocalFile::getHistory', array( &$this, &$tables, &$fields,
1032  &$conds, &$opts, &$join_conds ) );
1033 
1034  $res = $dbr->select( $tables, $fields, $conds, __METHOD__, $opts, $join_conds );
1035  $r = array();
1036 
1037  foreach ( $res as $row ) {
1038  $r[] = $this->repo->newFileFromRow( $row );
1039  }
1040 
1041  if ( $order == 'ASC' ) {
1042  $r = array_reverse( $r ); // make sure it ends up descending
1043  }
1044 
1045  return $r;
1046  }
1047 
1057  public function nextHistoryLine() {
1058  # Polymorphic function name to distinguish foreign and local fetches
1059  $fname = get_class( $this ) . '::' . __FUNCTION__;
1060 
1061  $dbr = $this->repo->getSlaveDB();
1062 
1063  if ( $this->historyLine == 0 ) { // called for the first time, return line from cur
1064  $this->historyRes = $dbr->select( 'image',
1065  array(
1066  '*',
1067  "'' AS oi_archive_name",
1068  '0 as oi_deleted',
1069  'img_sha1'
1070  ),
1071  array( 'img_name' => $this->title->getDBkey() ),
1072  $fname
1073  );
1074 
1075  if ( 0 == $dbr->numRows( $this->historyRes ) ) {
1076  $this->historyRes = null;
1077 
1078  return false;
1079  }
1080  } elseif ( $this->historyLine == 1 ) {
1081  $this->historyRes = $dbr->select( 'oldimage', '*',
1082  array( 'oi_name' => $this->title->getDBkey() ),
1083  $fname,
1084  array( 'ORDER BY' => 'oi_timestamp DESC' )
1085  );
1086  }
1087  $this->historyLine++;
1088 
1089  return $dbr->fetchObject( $this->historyRes );
1090  }
1091 
1095  public function resetHistory() {
1096  $this->historyLine = 0;
1097 
1098  if ( !is_null( $this->historyRes ) ) {
1099  $this->historyRes = null;
1100  }
1101  }
1102 
1132  function upload( $srcPath, $comment, $pageText, $flags = 0, $props = false,
1133  $timestamp = false, $user = null
1134  ) {
1136 
1137  if ( $this->getRepo()->getReadOnlyReason() !== false ) {
1138  return $this->readOnlyFatalStatus();
1139  }
1140 
1141  if ( !$props ) {
1142  wfProfileIn( __METHOD__ . '-getProps' );
1143  if ( $this->repo->isVirtualUrl( $srcPath )
1144  || FileBackend::isStoragePath( $srcPath )
1145  ) {
1146  $props = $this->repo->getFileProps( $srcPath );
1147  } else {
1148  $props = FSFile::getPropsFromPath( $srcPath );
1149  }
1150  wfProfileOut( __METHOD__ . '-getProps' );
1151  }
1152 
1153  $options = array();
1154  $handler = MediaHandler::getHandler( $props['mime'] );
1155  if ( $handler ) {
1156  $options['headers'] = $handler->getStreamHeaders( $props['metadata'] );
1157  } else {
1158  $options['headers'] = array();
1159  }
1160 
1161  // Trim spaces on user supplied text
1162  $comment = trim( $comment );
1163 
1164  // truncate nicely or the DB will do it for us
1165  // non-nicely (dangling multi-byte chars, non-truncated version in cache).
1166  $comment = $wgContLang->truncate( $comment, 255 );
1167  $this->lock(); // begin
1168  $status = $this->publish( $srcPath, $flags, $options );
1169 
1170  if ( $status->successCount > 0 ) {
1171  # Essentially we are displacing any existing current file and saving
1172  # a new current file at the old location. If just the first succeeded,
1173  # we still need to displace the current DB entry and put in a new one.
1174  if ( !$this->recordUpload2( $status->value, $comment, $pageText, $props, $timestamp, $user ) ) {
1175  $status->fatal( 'filenotfound', $srcPath );
1176  }
1177  }
1178 
1179  $this->unlock(); // done
1180 
1181  return $status;
1182  }
1183 
1196  function recordUpload( $oldver, $desc, $license = '', $copyStatus = '', $source = '',
1197  $watch = false, $timestamp = false, User $user = null ) {
1198  if ( !$user ) {
1199  global $wgUser;
1200  $user = $wgUser;
1201  }
1202 
1203  $pageText = SpecialUpload::getInitialPageText( $desc, $license, $copyStatus, $source );
1204 
1205  if ( !$this->recordUpload2( $oldver, $desc, $pageText, false, $timestamp, $user ) ) {
1206  return false;
1207  }
1208 
1209  if ( $watch ) {
1210  $user->addWatch( $this->getTitle() );
1211  }
1212 
1213  return true;
1214  }
1215 
1226  function recordUpload2( $oldver, $comment, $pageText, $props = false, $timestamp = false,
1227  $user = null
1228  ) {
1229  wfProfileIn( __METHOD__ );
1230 
1231  if ( is_null( $user ) ) {
1232  global $wgUser;
1233  $user = $wgUser;
1234  }
1235 
1236  $dbw = $this->repo->getMasterDB();
1237  $dbw->begin( __METHOD__ );
1238 
1239  if ( !$props ) {
1240  wfProfileIn( __METHOD__ . '-getProps' );
1241  $props = $this->repo->getFileProps( $this->getVirtualUrl() );
1242  wfProfileOut( __METHOD__ . '-getProps' );
1243  }
1244 
1245  if ( $timestamp === false ) {
1246  $timestamp = $dbw->timestamp();
1247  }
1248 
1249  $props['description'] = $comment;
1250  $props['user'] = $user->getId();
1251  $props['user_text'] = $user->getName();
1252  $props['timestamp'] = wfTimestamp( TS_MW, $timestamp ); // DB -> TS_MW
1253  $this->setProps( $props );
1254 
1255  # Fail now if the file isn't there
1256  if ( !$this->fileExists ) {
1257  wfDebug( __METHOD__ . ": File " . $this->getRel() . " went missing!\n" );
1258  wfProfileOut( __METHOD__ );
1259 
1260  return false;
1261  }
1262 
1263  $reupload = false;
1264 
1265  # Test to see if the row exists using INSERT IGNORE
1266  # This avoids race conditions by locking the row until the commit, and also
1267  # doesn't deadlock. SELECT FOR UPDATE causes a deadlock for every race condition.
1268  $dbw->insert( 'image',
1269  array(
1270  'img_name' => $this->getName(),
1271  'img_size' => $this->size,
1272  'img_width' => intval( $this->width ),
1273  'img_height' => intval( $this->height ),
1274  'img_bits' => $this->bits,
1275  'img_media_type' => $this->media_type,
1276  'img_major_mime' => $this->major_mime,
1277  'img_minor_mime' => $this->minor_mime,
1278  'img_timestamp' => $timestamp,
1279  'img_description' => $comment,
1280  'img_user' => $user->getId(),
1281  'img_user_text' => $user->getName(),
1282  'img_metadata' => $dbw->encodeBlob( $this->metadata ),
1283  'img_sha1' => $this->sha1
1284  ),
1285  __METHOD__,
1286  'IGNORE'
1287  );
1288  if ( $dbw->affectedRows() == 0 ) {
1289  # (bug 34993) Note: $oldver can be empty here, if the previous
1290  # version of the file was broken. Allow registration of the new
1291  # version to continue anyway, because that's better than having
1292  # an image that's not fixable by user operations.
1293 
1294  $reupload = true;
1295  # Collision, this is an update of a file
1296  # Insert previous contents into oldimage
1297  $dbw->insertSelect( 'oldimage', 'image',
1298  array(
1299  'oi_name' => 'img_name',
1300  'oi_archive_name' => $dbw->addQuotes( $oldver ),
1301  'oi_size' => 'img_size',
1302  'oi_width' => 'img_width',
1303  'oi_height' => 'img_height',
1304  'oi_bits' => 'img_bits',
1305  'oi_timestamp' => 'img_timestamp',
1306  'oi_description' => 'img_description',
1307  'oi_user' => 'img_user',
1308  'oi_user_text' => 'img_user_text',
1309  'oi_metadata' => 'img_metadata',
1310  'oi_media_type' => 'img_media_type',
1311  'oi_major_mime' => 'img_major_mime',
1312  'oi_minor_mime' => 'img_minor_mime',
1313  'oi_sha1' => 'img_sha1'
1314  ),
1315  array( 'img_name' => $this->getName() ),
1316  __METHOD__
1317  );
1318 
1319  # Update the current image row
1320  $dbw->update( 'image',
1321  array( /* SET */
1322  'img_size' => $this->size,
1323  'img_width' => intval( $this->width ),
1324  'img_height' => intval( $this->height ),
1325  'img_bits' => $this->bits,
1326  'img_media_type' => $this->media_type,
1327  'img_major_mime' => $this->major_mime,
1328  'img_minor_mime' => $this->minor_mime,
1329  'img_timestamp' => $timestamp,
1330  'img_description' => $comment,
1331  'img_user' => $user->getId(),
1332  'img_user_text' => $user->getName(),
1333  'img_metadata' => $dbw->encodeBlob( $this->metadata ),
1334  'img_sha1' => $this->sha1
1335  ),
1336  array( 'img_name' => $this->getName() ),
1337  __METHOD__
1338  );
1339  } else {
1340  # This is a new file, so update the image count
1342  }
1343 
1344  $descTitle = $this->getTitle();
1345  $wikiPage = new WikiFilePage( $descTitle );
1346  $wikiPage->setFile( $this );
1347 
1348  # Add the log entry
1349  $action = $reupload ? 'overwrite' : 'upload';
1350 
1351  $logEntry = new ManualLogEntry( 'upload', $action );
1352  $logEntry->setPerformer( $user );
1353  $logEntry->setComment( $comment );
1354  $logEntry->setTarget( $descTitle );
1355 
1356  // Allow people using the api to associate log entries with the upload.
1357  // Log has a timestamp, but sometimes different from upload timestamp.
1358  $logEntry->setParameters(
1359  array(
1360  'img_sha1' => $this->sha1,
1361  'img_timestamp' => $timestamp,
1362  )
1363  );
1364  // Note we keep $logId around since during new image
1365  // creation, page doesn't exist yet, so log_page = 0
1366  // but we want it to point to the page we're making,
1367  // so we later modify the log entry.
1368  // For a similar reason, we avoid making an RC entry
1369  // now and wait until the page exists.
1370  $logId = $logEntry->insert();
1371 
1372  $exists = $descTitle->exists();
1373  if ( $exists ) {
1374  // Page exists, do RC entry now (otherwise we wait for later).
1375  $logEntry->publish( $logId );
1376  }
1377  wfProfileIn( __METHOD__ . '-edit' );
1378 
1379  if ( $exists ) {
1380  # Create a null revision
1381  $latest = $descTitle->getLatestRevID();
1382  $editSummary = LogFormatter::newFromEntry( $logEntry )->getPlainActionText();
1383 
1384  $nullRevision = Revision::newNullRevision(
1385  $dbw,
1386  $descTitle->getArticleID(),
1387  $editSummary,
1388  false
1389  );
1390  if ( !is_null( $nullRevision ) ) {
1391  $nullRevision->insertOn( $dbw );
1392 
1393  wfRunHooks( 'NewRevisionFromEditComplete', array( $wikiPage, $nullRevision, $latest, $user ) );
1394  $wikiPage->updateRevisionOn( $dbw, $nullRevision );
1395  }
1396  }
1397 
1398  # Commit the transaction now, in case something goes wrong later
1399  # The most important thing is that files don't get lost, especially archives
1400  # NOTE: once we have support for nested transactions, the commit may be moved
1401  # to after $wikiPage->doEdit has been called.
1402  $dbw->commit( __METHOD__ );
1403 
1404  # Save to memcache.
1405  # We shall not saveToCache before the commit since otherwise
1406  # in case of a rollback there is an usable file from memcached
1407  # which in fact doesn't really exist (bug 24978)
1408  $this->saveToCache();
1409 
1410  if ( $exists ) {
1411  # Invalidate the cache for the description page
1412  $descTitle->invalidateCache();
1413  $descTitle->purgeSquid();
1414  } else {
1415  # New file; create the description page.
1416  # There's already a log entry, so don't make a second RC entry
1417  # Squid and file cache for the description page are purged by doEditContent.
1418  $content = ContentHandler::makeContent( $pageText, $descTitle );
1419  $status = $wikiPage->doEditContent(
1420  $content,
1421  $comment,
1423  false,
1424  $user
1425  );
1426 
1427  $dbw->begin( __METHOD__ ); // XXX; doEdit() uses a transaction
1428  // Now that the page exists, make an RC entry.
1429  $logEntry->publish( $logId );
1430  if ( isset( $status->value['revision'] ) ) {
1431  $dbw->update( 'logging',
1432  array( 'log_page' => $status->value['revision']->getPage() ),
1433  array( 'log_id' => $logId ),
1434  __METHOD__
1435  );
1436  }
1437  $dbw->commit( __METHOD__ ); // commit before anything bad can happen
1438  }
1439 
1440  wfProfileOut( __METHOD__ . '-edit' );
1441 
1442 
1443  if ( $reupload ) {
1444  # Delete old thumbnails
1445  wfProfileIn( __METHOD__ . '-purge' );
1446  $this->purgeThumbnails();
1447  wfProfileOut( __METHOD__ . '-purge' );
1448 
1449  # Remove the old file from the squid cache
1450  SquidUpdate::purge( array( $this->getURL() ) );
1451  }
1452 
1453  # Hooks, hooks, the magic of hooks...
1454  wfProfileIn( __METHOD__ . '-hooks' );
1455  wfRunHooks( 'FileUpload', array( $this, $reupload, $descTitle->exists() ) );
1456  wfProfileOut( __METHOD__ . '-hooks' );
1457 
1458  # Invalidate cache for all pages using this file
1459  $update = new HTMLCacheUpdate( $this->getTitle(), 'imagelinks' );
1460  $update->doUpdate();
1461  if ( !$reupload ) {
1462  LinksUpdate::queueRecursiveJobsForTable( $this->getTitle(), 'imagelinks' );
1463  }
1464 
1465  wfProfileOut( __METHOD__ );
1466 
1467  return true;
1468  }
1469 
1485  function publish( $srcPath, $flags = 0, array $options = array() ) {
1486  return $this->publishTo( $srcPath, $this->getRel(), $flags, $options );
1487  }
1488 
1504  function publishTo( $srcPath, $dstRel, $flags = 0, array $options = array() ) {
1505  if ( $this->getRepo()->getReadOnlyReason() !== false ) {
1506  return $this->readOnlyFatalStatus();
1507  }
1508 
1509  $this->lock(); // begin
1510 
1511  $archiveName = wfTimestamp( TS_MW ) . '!' . $this->getName();
1512  $archiveRel = 'archive/' . $this->getHashPath() . $archiveName;
1514  $status = $this->repo->publish( $srcPath, $dstRel, $archiveRel, $flags, $options );
1515 
1516  if ( $status->value == 'new' ) {
1517  $status->value = '';
1518  } else {
1519  $status->value = $archiveName;
1520  }
1521 
1522  $this->unlock(); // done
1523 
1524  return $status;
1525  }
1526 
1544  function move( $target ) {
1545  if ( $this->getRepo()->getReadOnlyReason() !== false ) {
1546  return $this->readOnlyFatalStatus();
1547  }
1548 
1549  wfDebugLog( 'imagemove', "Got request to move {$this->name} to " . $target->getText() );
1550  $batch = new LocalFileMoveBatch( $this, $target );
1551 
1552  $this->lock(); // begin
1553  $batch->addCurrent();
1554  $archiveNames = $batch->addOlds();
1555  $status = $batch->execute();
1556  $this->unlock(); // done
1557 
1558  wfDebugLog( 'imagemove', "Finished moving {$this->name}" );
1559 
1560  // Purge the source and target files...
1561  $oldTitleFile = wfLocalFile( $this->title );
1562  $newTitleFile = wfLocalFile( $target );
1563  // Hack: the lock()/unlock() pair is nested in a transaction so the locking is not
1564  // tied to BEGIN/COMMIT. To avoid slow purges in the transaction, move them outside.
1565  $this->getRepo()->getMasterDB()->onTransactionIdle(
1566  function () use ( $oldTitleFile, $newTitleFile, $archiveNames ) {
1567  $oldTitleFile->purgeEverything();
1568  foreach ( $archiveNames as $archiveName ) {
1569  $oldTitleFile->purgeOldThumbnails( $archiveName );
1570  }
1571  $newTitleFile->purgeEverything();
1572  }
1573  );
1574 
1575  if ( $status->isOK() ) {
1576  // Now switch the object
1577  $this->title = $target;
1578  // Force regeneration of the name and hashpath
1579  unset( $this->name );
1580  unset( $this->hashPath );
1581  }
1582 
1583  return $status;
1584  }
1585 
1598  function delete( $reason, $suppress = false ) {
1599  if ( $this->getRepo()->getReadOnlyReason() !== false ) {
1600  return $this->readOnlyFatalStatus();
1601  }
1602 
1603  $batch = new LocalFileDeleteBatch( $this, $reason, $suppress );
1604 
1605  $this->lock(); // begin
1606  $batch->addCurrent();
1607  # Get old version relative paths
1608  $archiveNames = $batch->addOlds();
1609  $status = $batch->execute();
1610  $this->unlock(); // done
1611 
1612  if ( $status->isOK() ) {
1614  }
1615 
1616  // Hack: the lock()/unlock() pair is nested in a transaction so the locking is not
1617  // tied to BEGIN/COMMIT. To avoid slow purges in the transaction, move them outside.
1618  $file = $this;
1619  $this->getRepo()->getMasterDB()->onTransactionIdle(
1620  function () use ( $file, $archiveNames ) {
1621  global $wgUseSquid;
1622 
1623  $file->purgeEverything();
1624  foreach ( $archiveNames as $archiveName ) {
1625  $file->purgeOldThumbnails( $archiveName );
1626  }
1627 
1628  if ( $wgUseSquid ) {
1629  // Purge the squid
1630  $purgeUrls = array();
1631  foreach ( $archiveNames as $archiveName ) {
1632  $purgeUrls[] = $file->getArchiveUrl( $archiveName );
1633  }
1634  SquidUpdate::purge( $purgeUrls );
1635  }
1636  }
1637  );
1638 
1639  return $status;
1640  }
1641 
1656  function deleteOld( $archiveName, $reason, $suppress = false ) {
1657  global $wgUseSquid;
1658  if ( $this->getRepo()->getReadOnlyReason() !== false ) {
1659  return $this->readOnlyFatalStatus();
1660  }
1661 
1662  $batch = new LocalFileDeleteBatch( $this, $reason, $suppress );
1663 
1664  $this->lock(); // begin
1665  $batch->addOld( $archiveName );
1666  $status = $batch->execute();
1667  $this->unlock(); // done
1668 
1669  $this->purgeOldThumbnails( $archiveName );
1670  if ( $status->isOK() ) {
1671  $this->purgeDescription();
1672  $this->purgeHistory();
1673  }
1674 
1675  if ( $wgUseSquid ) {
1676  // Purge the squid
1677  SquidUpdate::purge( array( $this->getArchiveUrl( $archiveName ) ) );
1678  }
1679 
1680  return $status;
1681  }
1682 
1694  function restore( $versions = array(), $unsuppress = false ) {
1695  if ( $this->getRepo()->getReadOnlyReason() !== false ) {
1696  return $this->readOnlyFatalStatus();
1697  }
1698 
1699  $batch = new LocalFileRestoreBatch( $this, $unsuppress );
1700 
1701  $this->lock(); // begin
1702  if ( !$versions ) {
1703  $batch->addAll();
1704  } else {
1705  $batch->addIds( $versions );
1706  }
1707  $status = $batch->execute();
1708  if ( $status->isGood() ) {
1709  $cleanupStatus = $batch->cleanup();
1710  $cleanupStatus->successCount = 0;
1711  $cleanupStatus->failCount = 0;
1712  $status->merge( $cleanupStatus );
1713  }
1714  $this->unlock(); // done
1716  return $status;
1717  }
1718 
1728  function getDescriptionUrl() {
1729  return $this->title->getLocalURL();
1730  }
1731 
1740  function getDescriptionText( $lang = null ) {
1741  $revision = Revision::newFromTitle( $this->title, false, Revision::READ_NORMAL );
1742  if ( !$revision ) {
1743  return false;
1744  }
1745  $content = $revision->getContent();
1746  if ( !$content ) {
1747  return false;
1748  }
1749  $pout = $content->getParserOutput( $this->title, null, new ParserOptions( null, $lang ) );
1751  return $pout->getText();
1752  }
1753 
1759  function getDescription( $audience = self::FOR_PUBLIC, User $user = null ) {
1760  $this->load();
1761  if ( $audience == self::FOR_PUBLIC && $this->isDeleted( self::DELETED_COMMENT ) ) {
1762  return '';
1763  } elseif ( $audience == self::FOR_THIS_USER
1764  && !$this->userCan( self::DELETED_COMMENT, $user )
1765  ) {
1766  return '';
1767  } else {
1768  return $this->description;
1769  }
1770  }
1771 
1775  function getTimestamp() {
1776  $this->load();
1777 
1778  return $this->timestamp;
1779  }
1780 
1784  function getSha1() {
1785  $this->load();
1786  // Initialise now if necessary
1787  if ( $this->sha1 == '' && $this->fileExists ) {
1788  $this->lock(); // begin
1789 
1790  $this->sha1 = $this->repo->getFileSha1( $this->getPath() );
1791  if ( !wfReadOnly() && strval( $this->sha1 ) != '' ) {
1792  $dbw = $this->repo->getMasterDB();
1793  $dbw->update( 'image',
1794  array( 'img_sha1' => $this->sha1 ),
1795  array( 'img_name' => $this->getName() ),
1796  __METHOD__ );
1797  $this->saveToCache();
1798  }
1799 
1800  $this->unlock(); // done
1801  }
1802 
1803  return $this->sha1;
1804  }
1805 
1809  function isCacheable() {
1810  $this->load();
1811 
1812  // If extra data (metadata) was not loaded then it must have been large
1813  return $this->extraDataLoaded
1814  && strlen( serialize( $this->metadata ) ) <= self::CACHE_FIELD_MAX_LEN;
1815  }
1816 
1823  function lock() {
1824  $dbw = $this->repo->getMasterDB();
1825 
1826  if ( !$this->locked ) {
1827  if ( !$dbw->trxLevel() ) {
1828  $dbw->begin( __METHOD__ );
1829  $this->lockedOwnTrx = true;
1830  }
1831  $this->locked++;
1832  // Bug 54736: use simple lock to handle when the file does not exist.
1833  // SELECT FOR UPDATE only locks records not the gaps where there are none.
1834  $cache = wfGetMainCache();
1835  $key = $this->getCacheKey();
1836  if ( !$cache->lock( $key, 5 ) ) {
1837  throw new MWException( "Could not acquire lock for '{$this->getName()}.'" );
1838  }
1839  $dbw->onTransactionIdle( function () use ( $cache, $key ) {
1840  $cache->unlock( $key ); // release on commit
1841  } );
1842  }
1843 
1844  return $dbw->selectField( 'image', '1',
1845  array( 'img_name' => $this->getName() ), __METHOD__, array( 'FOR UPDATE' ) );
1846  }
1847 
1852  function unlock() {
1853  if ( $this->locked ) {
1854  --$this->locked;
1855  if ( !$this->locked && $this->lockedOwnTrx ) {
1856  $dbw = $this->repo->getMasterDB();
1857  $dbw->commit( __METHOD__ );
1858  $this->lockedOwnTrx = false;
1859  }
1860  }
1861  }
1862 
1866  function unlockAndRollback() {
1867  $this->locked = false;
1868  $dbw = $this->repo->getMasterDB();
1869  $dbw->rollback( __METHOD__ );
1870  $this->lockedOwnTrx = false;
1871  }
1872 
1876  protected function readOnlyFatalStatus() {
1877  return $this->getRepo()->newFatal( 'filereadonlyerror', $this->getName(),
1878  $this->getRepo()->getName(), $this->getRepo()->getReadOnlyReason() );
1879  }
1880 
1884  function __destruct() {
1885  $this->unlock();
1886  }
1887 } // LocalFile class
1888 
1889 # ------------------------------------------------------------------------------
1895 class LocalFileDeleteBatch {
1897  private $file;
1898 
1900  private $reason;
1901 
1903  private $srcRels = array();
1906  private $archiveUrls = array();
1907 
1909  private $deletionBatch;
1910 
1912  private $suppress;
1915  private $status;
1916 
1922  function __construct( File $file, $reason = '', $suppress = false ) {
1923  $this->file = $file;
1924  $this->reason = $reason;
1925  $this->suppress = $suppress;
1926  $this->status = $file->repo->newGood();
1927  }
1928 
1929  function addCurrent() {
1930  $this->srcRels['.'] = $this->file->getRel();
1931  }
1932 
1936  function addOld( $oldName ) {
1937  $this->srcRels[$oldName] = $this->file->getArchiveRel( $oldName );
1938  $this->archiveUrls[] = $this->file->getArchiveUrl( $oldName );
1939  }
1940 
1945  function addOlds() {
1946  $archiveNames = array();
1947 
1948  $dbw = $this->file->repo->getMasterDB();
1949  $result = $dbw->select( 'oldimage',
1950  array( 'oi_archive_name' ),
1951  array( 'oi_name' => $this->file->getName() ),
1952  __METHOD__
1953  );
1954 
1955  foreach ( $result as $row ) {
1956  $this->addOld( $row->oi_archive_name );
1957  $archiveNames[] = $row->oi_archive_name;
1958  }
1959 
1960  return $archiveNames;
1961  }
1962 
1966  function getOldRels() {
1967  if ( !isset( $this->srcRels['.'] ) ) {
1968  $oldRels =& $this->srcRels;
1969  $deleteCurrent = false;
1970  } else {
1971  $oldRels = $this->srcRels;
1972  unset( $oldRels['.'] );
1973  $deleteCurrent = true;
1974  }
1975 
1976  return array( $oldRels, $deleteCurrent );
1977  }
1978 
1982  protected function getHashes() {
1983  $hashes = array();
1984  list( $oldRels, $deleteCurrent ) = $this->getOldRels();
1985 
1986  if ( $deleteCurrent ) {
1987  $hashes['.'] = $this->file->getSha1();
1988  }
1989 
1990  if ( count( $oldRels ) ) {
1991  $dbw = $this->file->repo->getMasterDB();
1992  $res = $dbw->select(
1993  'oldimage',
1994  array( 'oi_archive_name', 'oi_sha1' ),
1995  array( 'oi_archive_name' => array_keys( $oldRels ) ),
1996  __METHOD__
1997  );
1998 
1999  foreach ( $res as $row ) {
2000  if ( rtrim( $row->oi_sha1, "\0" ) === '' ) {
2001  // Get the hash from the file
2002  $oldUrl = $this->file->getArchiveVirtualUrl( $row->oi_archive_name );
2003  $props = $this->file->repo->getFileProps( $oldUrl );
2004 
2005  if ( $props['fileExists'] ) {
2006  // Upgrade the oldimage row
2007  $dbw->update( 'oldimage',
2008  array( 'oi_sha1' => $props['sha1'] ),
2009  array( 'oi_name' => $this->file->getName(), 'oi_archive_name' => $row->oi_archive_name ),
2010  __METHOD__ );
2011  $hashes[$row->oi_archive_name] = $props['sha1'];
2012  } else {
2013  $hashes[$row->oi_archive_name] = false;
2014  }
2015  } else {
2016  $hashes[$row->oi_archive_name] = $row->oi_sha1;
2017  }
2018  }
2019  }
2020 
2021  $missing = array_diff_key( $this->srcRels, $hashes );
2022 
2023  foreach ( $missing as $name => $rel ) {
2024  $this->status->error( 'filedelete-old-unregistered', $name );
2025  }
2026 
2027  foreach ( $hashes as $name => $hash ) {
2028  if ( !$hash ) {
2029  $this->status->error( 'filedelete-missing', $this->srcRels[$name] );
2030  unset( $hashes[$name] );
2031  }
2032  }
2033 
2034  return $hashes;
2035  }
2036 
2037  function doDBInserts() {
2038  global $wgUser;
2039 
2040  $dbw = $this->file->repo->getMasterDB();
2041  $encTimestamp = $dbw->addQuotes( $dbw->timestamp() );
2042  $encUserId = $dbw->addQuotes( $wgUser->getId() );
2043  $encReason = $dbw->addQuotes( $this->reason );
2044  $encGroup = $dbw->addQuotes( 'deleted' );
2045  $ext = $this->file->getExtension();
2046  $dotExt = $ext === '' ? '' : ".$ext";
2047  $encExt = $dbw->addQuotes( $dotExt );
2048  list( $oldRels, $deleteCurrent ) = $this->getOldRels();
2049 
2050  // Bitfields to further suppress the content
2051  if ( $this->suppress ) {
2052  $bitfield = 0;
2053  // This should be 15...
2054  $bitfield |= Revision::DELETED_TEXT;
2055  $bitfield |= Revision::DELETED_COMMENT;
2056  $bitfield |= Revision::DELETED_USER;
2057  $bitfield |= Revision::DELETED_RESTRICTED;
2058  } else {
2059  $bitfield = 'oi_deleted';
2060  }
2061 
2062  if ( $deleteCurrent ) {
2063  $concat = $dbw->buildConcat( array( "img_sha1", $encExt ) );
2064  $where = array( 'img_name' => $this->file->getName() );
2065  $dbw->insertSelect( 'filearchive', 'image',
2066  array(
2067  'fa_storage_group' => $encGroup,
2068  'fa_storage_key' => "CASE WHEN img_sha1='' THEN '' ELSE $concat END",
2069  'fa_deleted_user' => $encUserId,
2070  'fa_deleted_timestamp' => $encTimestamp,
2071  'fa_deleted_reason' => $encReason,
2072  'fa_deleted' => $this->suppress ? $bitfield : 0,
2073 
2074  'fa_name' => 'img_name',
2075  'fa_archive_name' => 'NULL',
2076  'fa_size' => 'img_size',
2077  'fa_width' => 'img_width',
2078  'fa_height' => 'img_height',
2079  'fa_metadata' => 'img_metadata',
2080  'fa_bits' => 'img_bits',
2081  'fa_media_type' => 'img_media_type',
2082  'fa_major_mime' => 'img_major_mime',
2083  'fa_minor_mime' => 'img_minor_mime',
2084  'fa_description' => 'img_description',
2085  'fa_user' => 'img_user',
2086  'fa_user_text' => 'img_user_text',
2087  'fa_timestamp' => 'img_timestamp',
2088  'fa_sha1' => 'img_sha1',
2089  ), $where, __METHOD__ );
2090  }
2091 
2092  if ( count( $oldRels ) ) {
2093  $concat = $dbw->buildConcat( array( "oi_sha1", $encExt ) );
2094  $where = array(
2095  'oi_name' => $this->file->getName(),
2096  'oi_archive_name' => array_keys( $oldRels ) );
2097  $dbw->insertSelect( 'filearchive', 'oldimage',
2098  array(
2099  'fa_storage_group' => $encGroup,
2100  'fa_storage_key' => "CASE WHEN oi_sha1='' THEN '' ELSE $concat END",
2101  'fa_deleted_user' => $encUserId,
2102  'fa_deleted_timestamp' => $encTimestamp,
2103  'fa_deleted_reason' => $encReason,
2104  'fa_deleted' => $this->suppress ? $bitfield : 'oi_deleted',
2105 
2106  'fa_name' => 'oi_name',
2107  'fa_archive_name' => 'oi_archive_name',
2108  'fa_size' => 'oi_size',
2109  'fa_width' => 'oi_width',
2110  'fa_height' => 'oi_height',
2111  'fa_metadata' => 'oi_metadata',
2112  'fa_bits' => 'oi_bits',
2113  'fa_media_type' => 'oi_media_type',
2114  'fa_major_mime' => 'oi_major_mime',
2115  'fa_minor_mime' => 'oi_minor_mime',
2116  'fa_description' => 'oi_description',
2117  'fa_user' => 'oi_user',
2118  'fa_user_text' => 'oi_user_text',
2119  'fa_timestamp' => 'oi_timestamp',
2120  'fa_sha1' => 'oi_sha1',
2121  ), $where, __METHOD__ );
2122  }
2123  }
2124 
2125  function doDBDeletes() {
2126  $dbw = $this->file->repo->getMasterDB();
2127  list( $oldRels, $deleteCurrent ) = $this->getOldRels();
2128 
2129  if ( count( $oldRels ) ) {
2130  $dbw->delete( 'oldimage',
2131  array(
2132  'oi_name' => $this->file->getName(),
2133  'oi_archive_name' => array_keys( $oldRels )
2134  ), __METHOD__ );
2135  }
2136 
2137  if ( $deleteCurrent ) {
2138  $dbw->delete( 'image', array( 'img_name' => $this->file->getName() ), __METHOD__ );
2139  }
2140  }
2141 
2146  function execute() {
2147  wfProfileIn( __METHOD__ );
2148 
2149  $this->file->lock();
2150  // Leave private files alone
2151  $privateFiles = array();
2152  list( $oldRels, ) = $this->getOldRels();
2153  $dbw = $this->file->repo->getMasterDB();
2154 
2155  if ( !empty( $oldRels ) ) {
2156  $res = $dbw->select( 'oldimage',
2157  array( 'oi_archive_name' ),
2158  array( 'oi_name' => $this->file->getName(),
2159  'oi_archive_name' => array_keys( $oldRels ),
2160  $dbw->bitAnd( 'oi_deleted', File::DELETED_FILE ) => File::DELETED_FILE ),
2161  __METHOD__ );
2162 
2163  foreach ( $res as $row ) {
2164  $privateFiles[$row->oi_archive_name] = 1;
2165  }
2166  }
2167  // Prepare deletion batch
2168  $hashes = $this->getHashes();
2169  $this->deletionBatch = array();
2170  $ext = $this->file->getExtension();
2171  $dotExt = $ext === '' ? '' : ".$ext";
2172 
2173  foreach ( $this->srcRels as $name => $srcRel ) {
2174  // Skip files that have no hash (missing source).
2175  // Keep private files where they are.
2176  if ( isset( $hashes[$name] ) && !array_key_exists( $name, $privateFiles ) ) {
2177  $hash = $hashes[$name];
2178  $key = $hash . $dotExt;
2179  $dstRel = $this->file->repo->getDeletedHashPath( $key ) . $key;
2180  $this->deletionBatch[$name] = array( $srcRel, $dstRel );
2181  }
2182  }
2183 
2184  // Lock the filearchive rows so that the files don't get deleted by a cleanup operation
2185  // We acquire this lock by running the inserts now, before the file operations.
2186  //
2187  // This potentially has poor lock contention characteristics -- an alternative
2188  // scheme would be to insert stub filearchive entries with no fa_name and commit
2189  // them in a separate transaction, then run the file ops, then update the fa_name fields.
2190  $this->doDBInserts();
2191 
2192  // Removes non-existent file from the batch, so we don't get errors.
2193  $this->deletionBatch = $this->removeNonexistentFiles( $this->deletionBatch );
2194 
2195  // Execute the file deletion batch
2196  $status = $this->file->repo->deleteBatch( $this->deletionBatch );
2197 
2198  if ( !$status->isGood() ) {
2199  $this->status->merge( $status );
2200  }
2201 
2202  if ( !$this->status->isOK() ) {
2203  // Critical file deletion error
2204  // Roll back inserts, release lock and abort
2205  // TODO: delete the defunct filearchive rows if we are using a non-transactional DB
2206  $this->file->unlockAndRollback();
2207  wfProfileOut( __METHOD__ );
2208 
2209  return $this->status;
2210  }
2211 
2212  // Delete image/oldimage rows
2213  $this->doDBDeletes();
2214 
2215  // Commit and return
2216  $this->file->unlock();
2217  wfProfileOut( __METHOD__ );
2218 
2219  return $this->status;
2220  }
2227  function removeNonexistentFiles( $batch ) {
2228  $files = $newBatch = array();
2229 
2230  foreach ( $batch as $batchItem ) {
2231  list( $src, ) = $batchItem;
2232  $files[$src] = $this->file->repo->getVirtualUrl( 'public' ) . '/' . rawurlencode( $src );
2233  }
2234 
2235  $result = $this->file->repo->fileExistsBatch( $files );
2237  foreach ( $batch as $batchItem ) {
2238  if ( $result[$batchItem[0]] ) {
2239  $newBatch[] = $batchItem;
2240  }
2241  }
2242 
2243  return $newBatch;
2244  }
2245 }
2247 # ------------------------------------------------------------------------------
2248 
2255  private $file;
2256 
2258  private $cleanupBatch;
2259 
2261  private $ids;
2262 
2264  private $all;
2265 
2267  private $unsuppress = false;
2268 
2273  function __construct( File $file, $unsuppress = false ) {
2274  $this->file = $file;
2275  $this->cleanupBatch = $this->ids = array();
2276  $this->ids = array();
2277  $this->unsuppress = $unsuppress;
2278  }
2279 
2283  function addId( $fa_id ) {
2284  $this->ids[] = $fa_id;
2285  }
2286 
2290  function addIds( $ids ) {
2291  $this->ids = array_merge( $this->ids, $ids );
2292  }
2293 
2297  function addAll() {
2298  $this->all = true;
2299  }
2300 
2309  function execute() {
2310  global $wgLang;
2311 
2312  if ( !$this->all && !$this->ids ) {
2313  // Do nothing
2314  return $this->file->repo->newGood();
2315  }
2316 
2317  $exists = $this->file->lock();
2318  $dbw = $this->file->repo->getMasterDB();
2319  $status = $this->file->repo->newGood();
2320 
2321  // Fetch all or selected archived revisions for the file,
2322  // sorted from the most recent to the oldest.
2323  $conditions = array( 'fa_name' => $this->file->getName() );
2324 
2325  if ( !$this->all ) {
2326  $conditions['fa_id'] = $this->ids;
2327  }
2328 
2329  $result = $dbw->select(
2330  'filearchive',
2332  $conditions,
2333  __METHOD__,
2334  array( 'ORDER BY' => 'fa_timestamp DESC' )
2335  );
2336 
2337  $idsPresent = array();
2338  $storeBatch = array();
2339  $insertBatch = array();
2340  $insertCurrent = false;
2341  $deleteIds = array();
2342  $first = true;
2343  $archiveNames = array();
2344 
2345  foreach ( $result as $row ) {
2346  $idsPresent[] = $row->fa_id;
2347 
2348  if ( $row->fa_name != $this->file->getName() ) {
2349  $status->error( 'undelete-filename-mismatch', $wgLang->timeanddate( $row->fa_timestamp ) );
2350  $status->failCount++;
2351  continue;
2352  }
2353 
2354  if ( $row->fa_storage_key == '' ) {
2355  // Revision was missing pre-deletion
2356  $status->error( 'undelete-bad-store-key', $wgLang->timeanddate( $row->fa_timestamp ) );
2357  $status->failCount++;
2358  continue;
2359  }
2360 
2361  $deletedRel = $this->file->repo->getDeletedHashPath( $row->fa_storage_key ) .
2362  $row->fa_storage_key;
2363  $deletedUrl = $this->file->repo->getVirtualUrl() . '/deleted/' . $deletedRel;
2364 
2365  if ( isset( $row->fa_sha1 ) ) {
2366  $sha1 = $row->fa_sha1;
2367  } else {
2368  // old row, populate from key
2369  $sha1 = LocalRepo::getHashFromKey( $row->fa_storage_key );
2370  }
2371 
2372  # Fix leading zero
2373  if ( strlen( $sha1 ) == 32 && $sha1[0] == '0' ) {
2374  $sha1 = substr( $sha1, 1 );
2375  }
2376 
2377  if ( is_null( $row->fa_major_mime ) || $row->fa_major_mime == 'unknown'
2378  || is_null( $row->fa_minor_mime ) || $row->fa_minor_mime == 'unknown'
2379  || is_null( $row->fa_media_type ) || $row->fa_media_type == 'UNKNOWN'
2380  || is_null( $row->fa_metadata )
2381  ) {
2382  // Refresh our metadata
2383  // Required for a new current revision; nice for older ones too. :)
2384  $props = RepoGroup::singleton()->getFileProps( $deletedUrl );
2385  } else {
2386  $props = array(
2387  'minor_mime' => $row->fa_minor_mime,
2388  'major_mime' => $row->fa_major_mime,
2389  'media_type' => $row->fa_media_type,
2390  'metadata' => $row->fa_metadata
2391  );
2392  }
2393 
2394  if ( $first && !$exists ) {
2395  // This revision will be published as the new current version
2396  $destRel = $this->file->getRel();
2397  $insertCurrent = array(
2398  'img_name' => $row->fa_name,
2399  'img_size' => $row->fa_size,
2400  'img_width' => $row->fa_width,
2401  'img_height' => $row->fa_height,
2402  'img_metadata' => $props['metadata'],
2403  'img_bits' => $row->fa_bits,
2404  'img_media_type' => $props['media_type'],
2405  'img_major_mime' => $props['major_mime'],
2406  'img_minor_mime' => $props['minor_mime'],
2407  'img_description' => $row->fa_description,
2408  'img_user' => $row->fa_user,
2409  'img_user_text' => $row->fa_user_text,
2410  'img_timestamp' => $row->fa_timestamp,
2411  'img_sha1' => $sha1
2412  );
2413 
2414  // The live (current) version cannot be hidden!
2415  if ( !$this->unsuppress && $row->fa_deleted ) {
2416  $storeBatch[] = array( $deletedUrl, 'public', $destRel );
2417  $this->cleanupBatch[] = $row->fa_storage_key;
2418  }
2419  } else {
2420  $archiveName = $row->fa_archive_name;
2421 
2422  if ( $archiveName == '' ) {
2423  // This was originally a current version; we
2424  // have to devise a new archive name for it.
2425  // Format is <timestamp of archiving>!<name>
2426  $timestamp = wfTimestamp( TS_UNIX, $row->fa_deleted_timestamp );
2427 
2428  do {
2429  $archiveName = wfTimestamp( TS_MW, $timestamp ) . '!' . $row->fa_name;
2430  $timestamp++;
2431  } while ( isset( $archiveNames[$archiveName] ) );
2432  }
2433 
2434  $archiveNames[$archiveName] = true;
2435  $destRel = $this->file->getArchiveRel( $archiveName );
2436  $insertBatch[] = array(
2437  'oi_name' => $row->fa_name,
2438  'oi_archive_name' => $archiveName,
2439  'oi_size' => $row->fa_size,
2440  'oi_width' => $row->fa_width,
2441  'oi_height' => $row->fa_height,
2442  'oi_bits' => $row->fa_bits,
2443  'oi_description' => $row->fa_description,
2444  'oi_user' => $row->fa_user,
2445  'oi_user_text' => $row->fa_user_text,
2446  'oi_timestamp' => $row->fa_timestamp,
2447  'oi_metadata' => $props['metadata'],
2448  'oi_media_type' => $props['media_type'],
2449  'oi_major_mime' => $props['major_mime'],
2450  'oi_minor_mime' => $props['minor_mime'],
2451  'oi_deleted' => $this->unsuppress ? 0 : $row->fa_deleted,
2452  'oi_sha1' => $sha1 );
2453  }
2454 
2455  $deleteIds[] = $row->fa_id;
2456 
2457  if ( !$this->unsuppress && $row->fa_deleted & File::DELETED_FILE ) {
2458  // private files can stay where they are
2459  $status->successCount++;
2460  } else {
2461  $storeBatch[] = array( $deletedUrl, 'public', $destRel );
2462  $this->cleanupBatch[] = $row->fa_storage_key;
2463  }
2464 
2465  $first = false;
2466  }
2467 
2468  unset( $result );
2469 
2470  // Add a warning to the status object for missing IDs
2471  $missingIds = array_diff( $this->ids, $idsPresent );
2472 
2473  foreach ( $missingIds as $id ) {
2474  $status->error( 'undelete-missing-filearchive', $id );
2475  }
2476 
2477  // Remove missing files from batch, so we don't get errors when undeleting them
2478  $storeBatch = $this->removeNonexistentFiles( $storeBatch );
2479 
2480  // Run the store batch
2481  // Use the OVERWRITE_SAME flag to smooth over a common error
2482  $storeStatus = $this->file->repo->storeBatch( $storeBatch, FileRepo::OVERWRITE_SAME );
2483  $status->merge( $storeStatus );
2484 
2485  if ( !$status->isGood() ) {
2486  // Even if some files could be copied, fail entirely as that is the
2487  // easiest thing to do without data loss
2488  $this->cleanupFailedBatch( $storeStatus, $storeBatch );
2489  $status->ok = false;
2490  $this->file->unlock();
2491 
2492  return $status;
2493  }
2494 
2495  // Run the DB updates
2496  // Because we have locked the image row, key conflicts should be rare.
2497  // If they do occur, we can roll back the transaction at this time with
2498  // no data loss, but leaving unregistered files scattered throughout the
2499  // public zone.
2500  // This is not ideal, which is why it's important to lock the image row.
2501  if ( $insertCurrent ) {
2502  $dbw->insert( 'image', $insertCurrent, __METHOD__ );
2503  }
2504 
2505  if ( $insertBatch ) {
2506  $dbw->insert( 'oldimage', $insertBatch, __METHOD__ );
2507  }
2508 
2509  if ( $deleteIds ) {
2510  $dbw->delete( 'filearchive',
2511  array( 'fa_id' => $deleteIds ),
2512  __METHOD__ );
2513  }
2514 
2515  // If store batch is empty (all files are missing), deletion is to be considered successful
2516  if ( $status->successCount > 0 || !$storeBatch ) {
2517  if ( !$exists ) {
2518  wfDebug( __METHOD__ . " restored {$status->successCount} items, creating a new current\n" );
2519 
2521 
2522  $this->file->purgeEverything();
2523  } else {
2524  wfDebug( __METHOD__ . " restored {$status->successCount} as archived versions\n" );
2525  $this->file->purgeDescription();
2526  $this->file->purgeHistory();
2527  }
2528  }
2529 
2530  $this->file->unlock();
2531 
2532  return $status;
2533  }
2534 
2540  function removeNonexistentFiles( $triplets ) {
2541  $files = $filteredTriplets = array();
2542  foreach ( $triplets as $file ) {
2543  $files[$file[0]] = $file[0];
2544  }
2545 
2546  $result = $this->file->repo->fileExistsBatch( $files );
2547 
2548  foreach ( $triplets as $file ) {
2549  if ( $result[$file[0]] ) {
2550  $filteredTriplets[] = $file;
2551  }
2552  }
2553 
2554  return $filteredTriplets;
2555  }
2556 
2562  function removeNonexistentFromCleanup( $batch ) {
2563  $files = $newBatch = array();
2564  $repo = $this->file->repo;
2565 
2566  foreach ( $batch as $file ) {
2567  $files[$file] = $repo->getVirtualUrl( 'deleted' ) . '/' .
2568  rawurlencode( $repo->getDeletedHashPath( $file ) . $file );
2569  }
2570 
2571  $result = $repo->fileExistsBatch( $files );
2572 
2573  foreach ( $batch as $file ) {
2574  if ( $result[$file] ) {
2575  $newBatch[] = $file;
2576  }
2577  }
2578 
2579  return $newBatch;
2580  }
2581 
2587  function cleanup() {
2588  if ( !$this->cleanupBatch ) {
2589  return $this->file->repo->newGood();
2590  }
2592  $this->cleanupBatch = $this->removeNonexistentFromCleanup( $this->cleanupBatch );
2594  $status = $this->file->repo->cleanupDeletedBatch( $this->cleanupBatch );
2596  return $status;
2597  }
2598 
2606  function cleanupFailedBatch( $storeStatus, $storeBatch ) {
2607  $cleanupBatch = array();
2608 
2609  foreach ( $storeStatus->success as $i => $success ) {
2610  // Check if this item of the batch was successfully copied
2611  if ( $success ) {
2612  // Item was successfully copied and needs to be removed again
2613  // Extract ($dstZone, $dstRel) from the batch
2614  $cleanupBatch[] = array( $storeBatch[$i][1], $storeBatch[$i][2] );
2615  }
2616  }
2617  $this->file->repo->cleanupBatch( $cleanupBatch );
2618  }
2619 }
2620 
2621 # ------------------------------------------------------------------------------
2622 
2627 class LocalFileMoveBatch {
2629  protected $file;
2630 
2632  protected $target;
2633 
2634  protected $cur;
2635 
2636  protected $olds;
2637 
2638  protected $oldCount;
2639 
2640  protected $archive;
2641 
2643  protected $db;
2644 
2649  function __construct( File $file, Title $target ) {
2650  $this->file = $file;
2651  $this->target = $target;
2652  $this->oldHash = $this->file->repo->getHashPath( $this->file->getName() );
2653  $this->newHash = $this->file->repo->getHashPath( $this->target->getDBkey() );
2654  $this->oldName = $this->file->getName();
2655  $this->newName = $this->file->repo->getNameFromTitle( $this->target );
2656  $this->oldRel = $this->oldHash . $this->oldName;
2657  $this->newRel = $this->newHash . $this->newName;
2658  $this->db = $file->getRepo()->getMasterDb();
2659  }
2660 
2664  function addCurrent() {
2665  $this->cur = array( $this->oldRel, $this->newRel );
2666  }
2667 
2672  function addOlds() {
2673  $archiveBase = 'archive';
2674  $this->olds = array();
2675  $this->oldCount = 0;
2676  $archiveNames = array();
2677 
2678  $result = $this->db->select( 'oldimage',
2679  array( 'oi_archive_name', 'oi_deleted' ),
2680  array( 'oi_name' => $this->oldName ),
2681  __METHOD__
2682  );
2683 
2684  foreach ( $result as $row ) {
2685  $archiveNames[] = $row->oi_archive_name;
2686  $oldName = $row->oi_archive_name;
2687  $bits = explode( '!', $oldName, 2 );
2688 
2689  if ( count( $bits ) != 2 ) {
2690  wfDebug( "Old file name missing !: '$oldName' \n" );
2691  continue;
2692  }
2693 
2694  list( $timestamp, $filename ) = $bits;
2695 
2696  if ( $this->oldName != $filename ) {
2697  wfDebug( "Old file name doesn't match: '$oldName' \n" );
2698  continue;
2699  }
2700 
2701  $this->oldCount++;
2702 
2703  // Do we want to add those to oldCount?
2704  if ( $row->oi_deleted & File::DELETED_FILE ) {
2705  continue;
2706  }
2707 
2708  $this->olds[] = array(
2709  "{$archiveBase}/{$this->oldHash}{$oldName}",
2710  "{$archiveBase}/{$this->newHash}{$timestamp}!{$this->newName}"
2711  );
2712  }
2713 
2714  return $archiveNames;
2715  }
2716 
2721  function execute() {
2722  $repo = $this->file->repo;
2723  $status = $repo->newGood();
2724 
2725  $triplets = $this->getMoveTriplets();
2726  $triplets = $this->removeNonexistentFiles( $triplets );
2727  $destFile = wfLocalFile( $this->target );
2728 
2729  $this->file->lock(); // begin
2730  $destFile->lock(); // quickly fail if destination is not available
2731  // Rename the file versions metadata in the DB.
2732  // This implicitly locks the destination file, which avoids race conditions.
2733  // If we moved the files from A -> C before DB updates, another process could
2734  // move files from B -> C at this point, causing storeBatch() to fail and thus
2735  // cleanupTarget() to trigger. It would delete the C files and cause data loss.
2736  $statusDb = $this->doDBUpdates();
2737  if ( !$statusDb->isGood() ) {
2738  $this->file->unlockAndRollback();
2739  $statusDb->ok = false;
2740 
2741  return $statusDb;
2742  }
2743  wfDebugLog( 'imagemove', "Renamed {$this->file->getName()} in database: " .
2744  "{$statusDb->successCount} successes, {$statusDb->failCount} failures" );
2745 
2746  // Copy the files into their new location.
2747  // If a prior process fataled copying or cleaning up files we tolerate any
2748  // of the existing files if they are identical to the ones being stored.
2749  $statusMove = $repo->storeBatch( $triplets, FileRepo::OVERWRITE_SAME );
2750  wfDebugLog( 'imagemove', "Moved files for {$this->file->getName()}: " .
2751  "{$statusMove->successCount} successes, {$statusMove->failCount} failures" );
2752  if ( !$statusMove->isGood() ) {
2753  // Delete any files copied over (while the destination is still locked)
2754  $this->cleanupTarget( $triplets );
2755  $this->file->unlockAndRollback(); // unlocks the destination
2756  wfDebugLog( 'imagemove', "Error in moving files: " . $statusMove->getWikiText() );
2757  $statusMove->ok = false;
2758 
2759  return $statusMove;
2760  }
2761  $destFile->unlock();
2762  $this->file->unlock(); // done
2763 
2764  // Everything went ok, remove the source files
2765  $this->cleanupSource( $triplets );
2766 
2767  $status->merge( $statusDb );
2768  $status->merge( $statusMove );
2769 
2770  return $status;
2771  }
2772 
2779  function doDBUpdates() {
2780  $repo = $this->file->repo;
2781  $status = $repo->newGood();
2782  $dbw = $this->db;
2783 
2784  // Update current image
2785  $dbw->update(
2786  'image',
2787  array( 'img_name' => $this->newName ),
2788  array( 'img_name' => $this->oldName ),
2789  __METHOD__
2790  );
2792  if ( $dbw->affectedRows() ) {
2793  $status->successCount++;
2794  } else {
2795  $status->failCount++;
2796  $status->fatal( 'imageinvalidfilename' );
2797 
2798  return $status;
2799  }
2800 
2801  // Update old images
2802  $dbw->update(
2803  'oldimage',
2804  array(
2805  'oi_name' => $this->newName,
2806  'oi_archive_name = ' . $dbw->strreplace( 'oi_archive_name',
2807  $dbw->addQuotes( $this->oldName ), $dbw->addQuotes( $this->newName ) ),
2808  ),
2809  array( 'oi_name' => $this->oldName ),
2810  __METHOD__
2811  );
2812 
2813  $affected = $dbw->affectedRows();
2815  $status->successCount += $affected;
2816  // Bug 34934: $total is based on files that actually exist.
2817  // There may be more DB rows than such files, in which case $affected
2818  // can be greater than $total. We use max() to avoid negatives here.
2819  $status->failCount += max( 0, $total - $affected );
2820  if ( $status->failCount ) {
2821  $status->error( 'imageinvalidfilename' );
2822  }
2823 
2824  return $status;
2825  }
2826 
2831  function getMoveTriplets() {
2832  $moves = array_merge( array( $this->cur ), $this->olds );
2833  $triplets = array(); // The format is: (srcUrl, destZone, destUrl)
2834 
2835  foreach ( $moves as $move ) {
2836  // $move: (oldRelativePath, newRelativePath)
2837  $srcUrl = $this->file->repo->getVirtualUrl() . '/public/' . rawurlencode( $move[0] );
2838  $triplets[] = array( $srcUrl, 'public', $move[1] );
2839  wfDebugLog(
2840  'imagemove',
2841  "Generated move triplet for {$this->file->getName()}: {$srcUrl} :: public :: {$move[1]}"
2842  );
2843  }
2844 
2845  return $triplets;
2846  }
2847 
2853  function removeNonexistentFiles( $triplets ) {
2854  $files = array();
2855 
2856  foreach ( $triplets as $file ) {
2857  $files[$file[0]] = $file[0];
2858  }
2859 
2860  $result = $this->file->repo->fileExistsBatch( $files );
2861  $filteredTriplets = array();
2862 
2863  foreach ( $triplets as $file ) {
2864  if ( $result[$file[0]] ) {
2865  $filteredTriplets[] = $file;
2866  } else {
2867  wfDebugLog( 'imagemove', "File {$file[0]} does not exist" );
2868  }
2869  }
2870 
2871  return $filteredTriplets;
2872  }
2873 
2878  function cleanupTarget( $triplets ) {
2879  // Create dest pairs from the triplets
2880  $pairs = array();
2881  foreach ( $triplets as $triplet ) {
2882  // $triplet: (old source virtual URL, dst zone, dest rel)
2883  $pairs[] = array( $triplet[1], $triplet[2] );
2884  }
2885 
2886  $this->file->repo->cleanupBatch( $pairs );
2887  }
2888 
2893  function cleanupSource( $triplets ) {
2894  // Create source file names from the triplets
2895  $files = array();
2896  foreach ( $triplets as $triplet ) {
2897  $files[] = $triplet[0];
2898  }
2899 
2900  $this->file->repo->cleanupBatch( $files );
2901  }
2902 }
LocalFileDeleteBatch\$reason
string $reason
Definition: LocalFile.php:1873
LocalFileMoveBatch\$target
Title $target
Definition: LocalFile.php:2593
LocalFile\$media_type
string $media_type
MEDIATYPE_xxx (bitmap, drawing, audio...) *.
Definition: LocalFile.php:57
LocalFile\getSha1
getSha1()
Definition: LocalFile.php:1759
Revision\DELETED_USER
const DELETED_USER
Definition: Revision.php:67
ParserOptions
Set options of the Parser.
Definition: ParserOptions.php:31
LocalFile\$fileExists
bool $fileExists
Does the file exist on disk? (loadFromXxx) *.
Definition: LocalFile.php:49
File\getPath
getPath()
Return the storage path to the file.
Definition: File.php:383
Title\makeTitle
static & makeTitle( $ns, $title, $fragment='', $interwiki='')
Create a new Title from a namespace index and a DB key.
Definition: Title.php:398
LocalFile\maybeUpgradeRow
maybeUpgradeRow()
Upgrade a row if it needs it.
Definition: LocalFile.php:497
LocalFileRestoreBatch
Helper class for file undeletion.
Definition: LocalFile.php:2221
$wgUser
$wgUser
Definition: Setup.php:552
LocalFileDeleteBatch\$deletionBatch
array $deletionBatch
Items to be processed in the deletion batch *.
Definition: LocalFile.php:1879
Revision\DELETED_RESTRICTED
const DELETED_RESTRICTED
Definition: Revision.php:68
$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. $reader:XMLReader object $logInfo:Array of information Return false to stop further processing of the tag 'ImportHandlePageXMLTag':When parsing a XML tag in a page. $reader:XMLReader object $pageInfo:Array of information Return false to stop further processing of the tag 'ImportHandleRevisionXMLTag':When parsing a XML tag in a page revision. $reader:XMLReader object $pageInfo:Array of page information $revisionInfo:Array of revision information Return false to stop further processing of the tag 'ImportHandleToplevelXMLTag':When parsing a top level XML tag. $reader:XMLReader object Return false to stop further processing of the tag 'ImportHandleUploadXMLTag':When parsing a XML tag in a file upload. $reader:XMLReader object $revisionInfo:Array of information Return false to stop further processing of the tag '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 '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. '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 '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 '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 wfIsTrustedProxy() $ip:IP being check $result:Change this value to override the result of wfIsTrustedProxy() '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 User::isValidEmailAddr(), 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. '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 '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) '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:Associative array mapping language codes to prefixed links of the form "language:title". & $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. 'LinkBegin':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:1528
LocalFile\unprefixRow
unprefixRow( $row, $prefix='img_')
Definition: LocalFile.php:413
File\$repo
FileRepo LocalRepo ForeignAPIRepo bool $repo
Some member variables can be lazy-initialised using __get().
Definition: File.php:94
LocalFile\$width
int $width
image width *
Definition: LocalFile.php:51
LocalFile\deleteOld
deleteOld( $archiveName, $reason, $suppress=false)
Delete an old version of the file.
Definition: LocalFile.php:1631
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:1311
RepoGroup\singleton
static singleton()
Get a RepoGroup instance.
Definition: RepoGroup.php:53
Revision\DELETED_COMMENT
const DELETED_COMMENT
Definition: Revision.php:66
LocalFile\__construct
__construct( $title, $repo)
Constructor.
Definition: LocalFile.php:186
LocalFile\unlock
unlock()
Decrement the lock reference count.
Definition: LocalFile.php:1827
php
skin txt MediaWiki includes four core it has been set as the default in MediaWiki since the replacing Monobook it had been been the default skin since before being replaced by Vector largely rewritten in while keeping its appearance Several legacy skins were removed in the as the burden of supporting them became too heavy to bear Those in etc for skin dependent CSS etc for skin dependent JavaScript These can also be customised on a per user by etc This feature has led to a wide variety of user styles becoming that gallery is a good place to ending in php
Definition: skin.txt:62
$files
$files
Definition: importImages.php:67
LocalFile\getTimestamp
getTimestamp()
Definition: LocalFile.php:1750
LocalFileMoveBatch\$file
LocalFile $file
Definition: LocalFile.php:2591
LocalFileDeleteBatch\addOld
addOld( $oldName)
Definition: LocalFile.php:1904
File\isMultipage
isMultipage()
Returns 'true' if this file is a type which supports multiple pages, e.g.
Definition: File.php:1670
Revision\newNullRevision
static newNullRevision( $dbw, $pageId, $summary, $minor)
Create a new null-revision for insertion into a page's history.
Definition: Revision.php:1567
LocalFile\getUser
getUser( $type='text')
Returns ID or name of user who uploaded the file.
Definition: LocalFile.php:685
$tables
namespace and then decline to actually register it RecentChangesLinked and Watchlist RecentChangesLinked and Watchlist e g Watchlist & $tables
Definition: hooks.txt:815
FileRepo\OVERWRITE_SAME
const OVERWRITE_SAME
Definition: FileRepo.php:40
LocalFile\unlockAndRollback
unlockAndRollback()
Roll back the DB transaction and mark the image unlocked.
Definition: LocalFile.php:1841
LocalFileDeleteBatch\doDBDeletes
doDBDeletes()
Definition: LocalFile.php:2093
LocalFile\purgeCache
purgeCache( $options=array())
Delete all previously generated thumbnails, refresh metadata in memcached and purge the squid.
Definition: LocalFile.php:860
LocalFile\purgeThumbList
purgeThumbList( $dir, $files)
Delete a list of thumbnails visible at urls.
Definition: LocalFile.php:947
File\getRel
getRel()
Get the path of the file relative to the public zone root.
Definition: File.php:1225
LocalFileMoveBatch\cleanupSource
cleanupSource( $triplets)
Cleanup a fully moved array of triplets by deleting the source files.
Definition: LocalFile.php:2853
FSFile\getPropsFromPath
static getPropsFromPath( $path, $ext=true)
Get an associative array containing information about a file in the local filesystem.
Definition: FSFile.php:243
$wgMemc
globals will be eliminated from MediaWiki replaced by an application object which would be passed to constructors Whether that would be an convenient solution remains to be but certainly PHP makes such object oriented programming models easier than they were in previous versions For the time being MediaWiki programmers will have to work in an environment with some global context At the time of globals were initialised on startup by MediaWiki of these were configuration which are documented in DefaultSettings php There is no comprehensive documentation for the remaining however some of the most important ones are listed below They are typically initialised either in index php or in Setup php For a description of the see design txt $wgTitle Title object created from the request URL $wgOut OutputPage object for HTTP response $wgUser User object for the user associated with the current request $wgLang Language object selected by user preferences $wgContLang Language object associated with the wiki being viewed $wgParser Parser object Parser extensions register their hooks here $wgRequest WebRequest to get request data $wgMemc
Definition: globals.txt:25
LocalFile\$missing
bool $missing
True if file is not present in file system.
Definition: LocalFile.php:97
LocalFileDeleteBatch
Helper class for file deletion.
Definition: LocalFile.php:1870
$timestamp
if( $limit) $timestamp
Definition: importImages.php:104
MediaHandler\filterThumbnailPurgeList
filterThumbnailPurgeList(&$files, $options)
Remove files from the purge list.
Definition: MediaHandler.php:724
LocalFileRestoreBatch\execute
execute()
Run the transaction, except the cleanup batch.
Definition: LocalFile.php:2272
LocalFileRestoreBatch\addId
addId( $fa_id)
Add a file by ID.
Definition: LocalFile.php:2246
wfTimestamp
wfTimestamp( $outputtype=TS_UNIX, $ts=0)
Get a timestamp string in one of various formats.
Definition: GlobalFunctions.php:2483
LinksUpdate\queueRecursiveJobsForTable
static queueRecursiveJobsForTable(Title $title, $table)
Queue a RefreshLinks job for any table.
Definition: LinksUpdate.php:238
FileBackendError
File backend exception for checked exceptions (e.g.
Definition: FileBackend.php:1492
LocalFile\$upgraded
bool $upgraded
Whether the row was upgraded on load *.
Definition: LocalFile.php:91
wfDebugLog
wfDebugLog( $logGroup, $text, $dest='all')
Send a line to a supplementary debug log file, if configured, or main debug log if not.
Definition: GlobalFunctions.php:1040
wfProfileIn
wfProfileIn( $functionname)
Begin profiling of a function.
Definition: Profiler.php:33
LocalFile\getCacheFields
getCacheFields( $prefix='img_')
Definition: LocalFile.php:303
LocalFileRestoreBatch\$all
bool $all
Add all revisions of the file *.
Definition: LocalFile.php:2228
LocalFile\$user
int $user
User ID of uploader *.
Definition: LocalFile.php:85
LocalFileMoveBatch\__construct
__construct(File $file, Title $target)
Definition: LocalFile.php:2609
LocalFileDeleteBatch\execute
execute()
Run the transaction.
Definition: LocalFile.php:2114
LocalFile\getHistory
getHistory( $limit=null, $start=null, $end=null, $inc=true)
purgeDescription inherited
Definition: LocalFile.php:981
$fname
if(!defined( 'MEDIAWIKI')) $fname
This file is not a valid entry point, perform no further processing unless MEDIAWIKI is defined.
Definition: Setup.php:35
LocalFile\getMediaType
getMediaType()
Returns the type of the media in the file.
Definition: LocalFile.php:738
LocalFile\$dataLoaded
bool $dataLoaded
Whether or not core data has been loaded from the database (loadFromXxx) *.
Definition: LocalFile.php:67
NS_FILE
const NS_FILE
Definition: Defines.php:85
$limit
if( $sleep) $limit
Definition: importImages.php:99
LocalFile\readOnlyFatalStatus
readOnlyFatalStatus()
Definition: LocalFile.php:1851
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:113
wfReadOnly
wfReadOnly()
Check whether the wiki is in read-only mode.
Definition: GlobalFunctions.php:1313
LocalFile\getSize
getSize()
Returns the size of the image file, in bytes.
Definition: LocalFile.php:717
OldLocalFile\selectFields
static selectFields()
Fields in the oldimage table.
Definition: OldLocalFile.php:106
LocalFile\purgeOldThumbnails
purgeOldThumbnails( $archiveName)
Delete cached transformed files for an archived version only.
Definition: LocalFile.php:877
LocalFile\saveToCache
saveToCache()
Save the file metadata to memcached.
Definition: LocalFile.php:258
LocalFile\purgeThumbnails
purgeThumbnails( $options=array())
Delete cached transformed files for the current version only.
Definition: LocalFile.php:905
LocalFile\$historyRes
int $historyRes
Result of the query for the file's history (nextHistoryLine) *.
Definition: LocalFile.php:77
LocalFile\getThumbnails
getThumbnails( $archiveName=false)
getHandler inherited
Definition: LocalFile.php:808
LocalFile\$sha1
string $sha1
SHA-1 base 36 content hash *.
Definition: LocalFile.php:65
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:249
$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
LocalFileRestoreBatch\$file
LocalFile $file
Definition: LocalFile.php:2222
LocalFileRestoreBatch\__construct
__construct(File $file, $unsuppress=false)
Definition: LocalFile.php:2236
$flags
it s the revision text itself In either if gzip is the revision text is gzipped $flags
Definition: hooks.txt:2113
LocalFile\$minor_mime
string $minor_mime
Minor mime type *.
Definition: LocalFile.php:81
File\isDeleted
isDeleted( $field)
Is this file a "deleted" file in a private archive? STUB.
Definition: File.php:1587
LocalRepo\getHashFromKey
static getHashFromKey( $key)
Gets the SHA1 hash from a storage key.
Definition: LocalRepo.php:156
MediaHandler\getPageDimensions
getPageDimensions( $image, $page)
Get an associative array of page dimensions Currently "width" and "height" are understood,...
Definition: MediaHandler.php:412
Status\isGood
isGood()
Returns whether the operation completed and didn't have any error or warnings.
Definition: Status.php:100
$dbr
$dbr
Definition: testCompression.php:48
LocalFile\isCacheable
isCacheable()
Definition: LocalFile.php:1784
LocalFileMoveBatch\$oldCount
$oldCount
Definition: LocalFile.php:2599
LocalFileMoveBatch\addOlds
addOlds()
Add the old versions of the image to the batch.
Definition: LocalFile.php:2632
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:438
LocalFile\$bits
int $bits
Returned by getimagesize (loadFromXxx) *.
Definition: LocalFile.php:55
title
to move a page</td >< td > &*You are moving the page across *A non empty talk page already exists under the new or *You uncheck the box below In those you will have to move or merge the page manually if desired</td >< td > be sure to &You are responsible for making sure that links continue to point where they are supposed to go Note that the page will &a page at the new title
Definition: All_system_messages.txt:2703
DatabaseBase\update
update( $table, $values, $conds, $fname=__METHOD__, $options=array())
UPDATE wrapper.
Definition: Database.php:1966
IDBAccessObject\READ_NORMAL
const READ_NORMAL
Definition: IDBAccessObject.php:53
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
LocalFileRestoreBatch\cleanupFailedBatch
cleanupFailedBatch( $storeStatus, $storeBatch)
Cleanup a failed batch.
Definition: LocalFile.php:2569
LocalFileDeleteBatch\addCurrent
addCurrent()
Definition: LocalFile.php:1897
MediaHandler\METADATA_COMPATIBLE
const METADATA_COMPATIBLE
Definition: MediaHandler.php:33
$success
$success
Definition: Utf8Test.php:91
File
Implements some public methods and some protected utility functions which are required by multiple ch...
Definition: File.php:50
LocalFile\loadExtraFromDB
loadExtraFromDB()
Load lazy file metadata from the DB.
Definition: LocalFile.php:376
wfGetMainCache
wfGetMainCache()
Get the main cache object.
Definition: GlobalFunctions.php:3966
$total
$total
Definition: Utf8Test.php:92
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:126
LocalFile\purgeMetadataCache
purgeMetadataCache()
Refresh metadata in memcached, but don't touch thumbnails or squid.
Definition: LocalFile.php:831
File\getThumbPath
getThumbPath( $suffix=false)
Get the path of the thumbnail directory, or a particular file if $suffix is specified.
Definition: File.php:1324
LocalFile\getUpgraded
getUpgraded()
Definition: LocalFile.php:522
wfIncrStats
wfIncrStats( $key, $count=1)
Increment a statistics counter.
Definition: GlobalFunctions.php:1304
FileBackend\isStoragePath
static isStoragePath( $path)
Check if a given path is a "mwstore://" path.
Definition: FileBackend.php:1330
LocalFile\CACHE_FIELD_MAX_LEN
const CACHE_FIELD_MAX_LEN
Definition: LocalFile.php:47
LocalFile\publishTo
publishTo( $srcPath, $dstRel, $flags=0, array $options=array())
Move or copy a file to a specified location.
Definition: LocalFile.php:1479
LocalFile\loadFromRow
loadFromRow( $row, $prefix='img_')
Load file metadata from a DB result row.
Definition: LocalFile.php:463
MW_FILE_VERSION
const MW_FILE_VERSION
Bump this number when serialized cache records may be incompatible.
Definition: LocalFile.php:27
LocalFile\__destruct
__destruct()
Clean up any dangling locks.
Definition: LocalFile.php:1859
LocalFile\$deleted
int $deleted
Bitfield akin to rev_deleted *.
Definition: LocalFile.php:71
SquidUpdate\purge
static purge( $urlArr)
Purges a list of Squids defined in $wgSquidServers.
Definition: SquidUpdate.php:134
DeferredUpdates\addUpdate
static addUpdate(DeferrableUpdate $update)
Add an update to the deferred list.
Definition: DeferredUpdates.php:51
wfProfileOut
wfProfileOut( $functionname='missing')
Stop profiling of a function.
Definition: Profiler.php:46
LocalFile\recordUpload2
recordUpload2( $oldver, $comment, $pageText, $props=false, $timestamp=false, $user=null)
Record a file upload in the upload log and the image table.
Definition: LocalFile.php:1201
LocalFileDeleteBatch\$file
LocalFile $file
Definition: LocalFile.php:1871
wfRunHooks
wfRunHooks( $event, array $args=array(), $deprecatedVersion=null)
Call hook functions defined in $wgHooks.
Definition: GlobalFunctions.php:4010
LocalFileDeleteBatch\$suppress
bool $suppress
Wether to suppress all suppressable fields when deleting *.
Definition: LocalFile.php:1881
LocalFile\migrateThumbFile
migrateThumbFile( $thumbName)
getTransformScript inherited
Definition: LocalFile.php:772
LocalFileDeleteBatch\__construct
__construct(File $file, $reason='', $suppress=false)
Definition: LocalFile.php:1890
array
the array() calling protocol came about after MediaWiki 1.4rc1.
List of Api Query prop modules.
SiteStatsUpdate\factory
static factory(array $deltas)
Definition: SiteStatsUpdate.php:50
global
when a variable name is used in a it is silently declared as a new masking the global
Definition: design.txt:93
$comment
$comment
Definition: importImages.php:107
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:204
LocalFileRestoreBatch\addAll
addAll()
Add all revisions of the file.
Definition: LocalFile.php:2260
LocalFile\$mime
string $mime
MIME type, determined by MimeMagic::guessMimeType *.
Definition: LocalFile.php:59
LocalFileRestoreBatch\$unsuppress
bool $unsuppress
Wether to remove all settings for suppressed fields *.
Definition: LocalFile.php:2230
LocalFile\setProps
setProps( $info)
Set properties in this object to be equal to those given in the associative array $info.
Definition: LocalFile.php:585
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:46
false
processing should stop and the error should be shown to the user * false
Definition: hooks.txt:188
LocalFile\upload
upload( $srcPath, $comment, $pageText, $flags=0, $props=false, $timestamp=false, $user=null)
getHashPath inherited
Definition: LocalFile.php:1107
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:144
LocalFileMoveBatch\$archive
$archive
Definition: LocalFile.php:2601
LocalFile\getMimeType
getMimeType()
Returns the mime type of the file.
Definition: LocalFile.php:727
LocalFile\getDescription
getDescription( $audience=self::FOR_PUBLIC, User $user=null)
Definition: LocalFile.php:1734
LocalFile\purgeHistory
purgeHistory()
Purge the shared history (OldLocalFile) cache.
Definition: LocalFile.php:842
$options
null means default in associative array with keys and values unescaped Should be merged with default with a value of false meaning to suppress the attribute in associative array with keys and values unescaped & $options
Definition: hooks.txt:1530
LocalFile\$extraDataLoaded
bool $extraDataLoaded
Whether or not lazy-loaded data has been loaded from the database *.
Definition: LocalFile.php:69
LocalFile\$size
int $size
Size in bytes (loadFromXxx) *.
Definition: LocalFile.php:61
LocalFile\restore
restore( $versions=array(), $unsuppress=false)
Restore all or specified deleted revisions to the given file.
Definition: LocalFile.php:1669
TS_MW
const TS_MW
MediaWiki concatenated string timestamp (YYYYMMDDHHMMSS)
Definition: GlobalFunctions.php:2431
Revision\newFromTitle
static newFromTitle( $title, $id=0, $flags=0)
Load either the current, or a specified, revision that's attached to a given title.
Definition: Revision.php:106
wfDebug
wfDebug( $text, $dest='all')
Sends a line to the debug log if enabled or, optionally, to a comment in output.
Definition: GlobalFunctions.php:933
HTMLCacheUpdate
Class to invalidate the HTML cache of all the pages linking to a given title.
Definition: HTMLCacheUpdate.php:29
File\purgeDescription
purgeDescription()
Purge the file description page, but don't go after pages using the file.
Definition: File.php:1141
$name
Allows to change the fields on the form that will be generated $name
Definition: hooks.txt:336
LocalFileDeleteBatch\getHashes
getHashes()
Definition: LocalFile.php:1950
LocalFile\loadFromDB
loadFromDB()
Load file metadata from the DB.
Definition: LocalFile.php:350
LocalFile\LOAD_ALL
const LOAD_ALL
Definition: LocalFile.php:99
$value
$value
Definition: styleTest.css.php:45
LocalFile\lock
lock()
Start a transaction and lock the image for update Increments a reference counter if the lock is alrea...
Definition: LocalFile.php:1798
File\$handler
MediaHandler $handler
Definition: File.php:106
File\assertTitleDefined
assertTitleDefined()
Assert that $this->title is set to a Title.
Definition: File.php:1994
LocalFile\$historyLine
int $historyLine
Number of line to return by nextHistoryLine() (constructor) *.
Definition: LocalFile.php:75
LocalFileMoveBatch\getMoveTriplets
getMoveTriplets()
Generate triplets for FileRepo::storeBatch().
Definition: LocalFile.php:2791
DatabaseBase
Database abstraction object.
Definition: Database.php:219
LocalFile\upgradeRow
upgradeRow()
Fix assorted version-related problems with the image row by reloading it from the file.
Definition: LocalFile.php:529
LocalFile\load
load( $flags=0)
Load file metadata from cache or DB, unless already loaded.
Definition: LocalFile.php:481
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:143
LocalFile\nextHistoryLine
nextHistoryLine()
Returns the history of this file, line by line.
Definition: LocalFile.php:1032
LocalFileRestoreBatch\$cleanupBatch
array $cleanupBatch
List of file IDs to restore *.
Definition: LocalFile.php:2224
File\$title
Title string bool $title
Definition: File.php:96
LocalFile\$metadata
string $metadata
Handler-specific metadata *.
Definition: LocalFile.php:63
SpecialUpload\getInitialPageText
static getInitialPageText( $comment='', $license='', $copyStatus='', $source='')
Get the initial image page text based on a comment and optional file status information.
Definition: SpecialUpload.php:481
LocalFile\getBitDepth
getBitDepth()
Definition: LocalFile.php:707
LocalFile\$height
int $height
image height *
Definition: LocalFile.php:53
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:1368
File\getName
getName()
Return the name of this file.
Definition: File.php:273
File\getArchiveUrl
getArchiveUrl( $suffix=false)
Get the URL of the archive directory, or a particular file if $suffix is specified.
Definition: File.php:1348
MediaHandler\getStreamHeaders
getStreamHeaders( $metadata)
Get useful response headers for GET/HEAD requests for a file with the given metadata.
Definition: MediaHandler.php:310
LocalFile\$timestamp
string $timestamp
Upload timestamp *.
Definition: LocalFile.php:83
File\DELETE_SOURCE
const DELETE_SOURCE
Definition: File.php:65
LocalFileDeleteBatch\$archiveUrls
array $archiveUrls
Definition: LocalFile.php:1877
$hash
return false to override stock group addition can be modified try getUserPermissionsErrors userCan checks are continued by internal code can override on output return false to not delete it return false to override the default password checks & $hash
Definition: hooks.txt:2697
LocalFileDeleteBatch\addOlds
addOlds()
Add the old versions of the image to the batch.
Definition: LocalFile.php:1913
LocalFileMoveBatch\$db
DatabaseBase $db
Definition: LocalFile.php:2603
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:1171
$file
if(PHP_SAPI !='cli') $file
Definition: UtfNormalTest2.php:30
EDIT_NEW
const EDIT_NEW
Definition: Defines.php:189
LocalFileRestoreBatch\$ids
array $ids
List of file IDs to restore *.
Definition: LocalFile.php:2226
LocalFile\$locked
bool $locked
True if the image row is locked *.
Definition: LocalFile.php:93
LocalFile\$description
string $description
Description of current revision of the file *.
Definition: LocalFile.php:89
LocalFile\getLazyCacheFields
getLazyCacheFields( $prefix='img_')
Definition: LocalFile.php:328
LocalFile\selectFields
static selectFields()
Fields in the image table.
Definition: LocalFile.php:163
LocalFile\$user_text
string $user_text
User name of uploader *.
Definition: LocalFile.php:87
File\getTitle
getTitle()
Return the associated title object.
Definition: File.php:302
Title
Represents a title within MediaWiki.
Definition: Title.php:35
LocalFile\getMetadata
getMetadata()
Get handler-specific metadata.
Definition: LocalFile.php:699
$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
LocalFileRestoreBatch\addIds
addIds( $ids)
Add a whole lot of files by ID.
Definition: LocalFile.php:2253
$cache
$cache
Definition: mcc.php:32
LocalFile\loadFromFile
loadFromFile()
Load metadata from the file itself.
Definition: LocalFile.php:294
File\assertRepoDefined
assertRepoDefined()
Assert that $this->repo is set to a valid FileRepo instance.
Definition: File.php:1984
LocalFileMoveBatch\execute
execute()
Perform the move.
Definition: LocalFile.php:2681
$dir
if(count( $args)==0) $dir
Definition: importImages.php:49
$ext
$ext
Definition: NoLocalSettings.php:34
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:1715
TS_UNIX
const TS_UNIX
Unix time - the number of seconds since 1970-01-01 00:00:00 UTC.
Definition: GlobalFunctions.php:2426
LocalFile\resetHistory
resetHistory()
Reset the history pointer to the first element of the history.
Definition: LocalFile.php:1070
LocalFileMoveBatch\removeNonexistentFiles
removeNonexistentFiles( $triplets)
Removes non-existent files from move batch.
Definition: LocalFile.php:2813
LocalFileRestoreBatch\cleanup
cleanup()
Delete unused files in the deleted zone.
Definition: LocalFile.php:2550
MediaHandler\getHandler
static getHandler( $type)
Get a MediaHandler for a given MIME type from the instance cache.
Definition: MediaHandler.php:48
LocalFileDeleteBatch\$srcRels
array $srcRels
Definition: LocalFile.php:1875
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:2503
LocalFileMoveBatch
Helper class for file movement.
Definition: LocalFile.php:2590
MediaHandler\METADATA_BAD
const METADATA_BAD
Definition: MediaHandler.php:32
$source
if(PHP_SAPI !='cli') $source
Definition: mwdoc-filter.php:18
$batch
$batch
Definition: linkcache.txt:23
LocalFileDeleteBatch\getOldRels
getOldRels()
Definition: LocalFile.php:1934
ManualLogEntry
Class for creating log entries manually, for example to inject them into the database.
Definition: LogEntry.php:339
FileRepoStatus
Generic operation result class for FileRepo-related operations.
Definition: FileRepoStatus.php:28
$hashes
$hashes
Definition: testCompression.php:62
FileRepo\DELETE_SOURCE
const DELETE_SOURCE
Definition: FileRepo.php:38
WikiFilePage
Special handling for file pages.
Definition: WikiFilePage.php:28
File\$name
string $name
The name of a file from its title object *.
Definition: File.php:112
File\DELETED_FILE
const DELETED_FILE
Definition: File.php:52
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
EDIT_SUPPRESS_RC
const EDIT_SUPPRESS_RC
Definition: Defines.php:192
File\getRepo
getRepo()
Returns the repository.
Definition: File.php:1566
LocalFile\getWidth
getWidth( $page=1)
Return the width of the image.
Definition: LocalFile.php:631
ArchivedFile\selectFields
static selectFields()
Fields in the filearchive table.
Definition: ArchivedFile.php:190
File\getThumbUrl
getThumbUrl( $suffix=false)
Get the URL of the thumbnail directory, or a particular file if $suffix is specified.
Definition: File.php:1406
LocalFile\$major_mime
string $major_mime
Major mime type *.
Definition: LocalFile.php:79
File\getHandler
getHandler()
Get a MediaHandler instance for this file.
Definition: File.php:1078
$e
if( $useReadline) $e
Definition: eval.php:66
LocalFile\getHeight
getHeight( $page=1)
Return the height of the image.
Definition: LocalFile.php:658
LocalFile\isMissing
isMissing()
splitMime inherited
Definition: LocalFile.php:616
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:1845
LocalFile\$lockedOwnTrx
bool $lockedOwnTrx
True if the image row is locked with a lock initiated transaction *.
Definition: LocalFile.php:95
User
The User object encapsulates all of the user-specific settings (user_id, name, rights,...
Definition: User.php:59
wfLocalFile
wfLocalFile( $title)
Get an object referring to a locally registered file.
Definition: GlobalFunctions.php:3713
LocalFile\getDescriptionUrl
getDescriptionUrl()
isMultipage inherited
Definition: LocalFile.php:1703
LocalFile\move
move( $target)
getLinksTo inherited
Definition: LocalFile.php:1519
$res
$res
Definition: database.txt:21
LocalFileMoveBatch\addCurrent
addCurrent()
Add the current image to the batch.
Definition: LocalFile.php:2624
LocalFile\$repoClass
string $repoClass
Definition: LocalFile.php:73
MediaHandler\isMetadataValid
isMetadataValid( $image, $metadata)
Check if the metadata string is valid for this handler.
Definition: MediaHandler.php:196
LocalFileDeleteBatch\removeNonexistentFiles
removeNonexistentFiles( $batch)
Removes non-existent files from a deletion batch.
Definition: LocalFile.php:2195
LocalFileMoveBatch\doDBUpdates
doDBUpdates()
Do the database updates and return a new FileRepoStatus indicating how many rows where updated.
Definition: LocalFile.php:2739
LocalFileDeleteBatch\$status
FileRepoStatus $status
Definition: LocalFile.php:1883
Revision\DELETED_TEXT
const DELETED_TEXT
Definition: Revision.php:65
File\getHashPath
getHashPath()
Get the filename hash component of the directory including trailing slash, e.g.
Definition: File.php:1210
LocalFileMoveBatch\$olds
$olds
Definition: LocalFile.php:2597
LocalFile\exists
exists()
canRender inherited
Definition: LocalFile.php:754
File\getThumbnails
getThumbnails()
Get all thumbnail names previously generated for this file STUB Overridden by LocalFile.
Definition: File.php:1122
$license
$license
Definition: importImages.php:123
LocalFileMoveBatch\$cur
$cur
Definition: LocalFile.php:2595
LocalFile\publish
publish( $srcPath, $flags=0, array $options=array())
Move or copy a file to its public location.
Definition: LocalFile.php:1460
LocalFile\loadFromCache
loadFromCache()
Try to load file metadata from memcached.
Definition: LocalFile.php:214
LocalFileMoveBatch\cleanupTarget
cleanupTarget( $triplets)
Cleanup a partially moved array of triplets by deleting the target files.
Definition: LocalFile.php:2838
File\getVirtualUrl
getVirtualUrl( $suffix=false)
Get the public zone virtual URL for a current version source file.
Definition: File.php:1426
LocalFileDeleteBatch\doDBInserts
doDBInserts()
Definition: LocalFile.php:2005
LocalFileRestoreBatch\removeNonexistentFromCleanup
removeNonexistentFromCleanup( $batch)
Removes non-existent files from a cleanup batch.
Definition: LocalFile.php:2525
LogFormatter\newFromEntry
static newFromEntry(LogEntry $entry)
Constructs a new formatter suitable for given entry.
Definition: LogFormatter.php:45
$type
$type
Definition: testCompression.php:46