MediaWiki 1.40.4
FileRepo.php
Go to the documentation of this file.
1<?php
17use Wikimedia\AtEase\AtEase;
18
48class FileRepo {
49 public const DELETE_SOURCE = 1;
50 public const OVERWRITE = 2;
51 public const OVERWRITE_SAME = 4;
52 public const SKIP_LOCKING = 8;
53
54 public const NAME_AND_TIME_ONLY = 1;
55
60
63
65 protected $hasSha1Storage = false;
66
68 protected $supportsSha1URLs = false;
69
71 protected $backend;
72
74 protected $zones = [];
75
77 protected $thumbScriptUrl;
78
83
87 protected $descBaseUrl;
88
92 protected $scriptDirUrl;
93
95 protected $articleUrl;
96
103
109 protected $pathDisclosureProtection = 'simple';
110
112 protected $url;
113
115 protected $thumbUrl;
116
118 protected $hashLevels;
119
122
128
130 protected $favicon = null;
131
133 protected $isPrivate;
134
136 protected $fileFactory = [ UnregisteredLocalFile::class, 'newFromTitle' ];
138 protected $oldFileFactory = false;
140 protected $fileFactoryKey = false;
142 protected $oldFileFactoryKey = false;
143
147 protected $thumbProxyUrl;
150
152 protected $disableLocalTransform = false;
153
155 protected $wanCache;
156
162 public $name;
163
170 public function __construct( array $info = null ) {
171 // Verify required settings presence
172 if (
173 $info === null
174 || !array_key_exists( 'name', $info )
175 || !array_key_exists( 'backend', $info )
176 ) {
177 throw new MWException( __CLASS__ .
178 " requires an array of options having both 'name' and 'backend' keys.\n" );
179 }
180
181 // Required settings
182 $this->name = $info['name'];
183 if ( $info['backend'] instanceof FileBackend ) {
184 $this->backend = $info['backend']; // useful for testing
185 } else {
186 $this->backend =
187 MediaWikiServices::getInstance()->getFileBackendGroup()->get( $info['backend'] );
188 }
189
190 // Optional settings that can have no value
191 $optionalSettings = [
192 'descBaseUrl', 'scriptDirUrl', 'articleUrl', 'fetchDescription',
193 'thumbScriptUrl', 'pathDisclosureProtection', 'descriptionCacheExpiry',
194 'favicon', 'thumbProxyUrl', 'thumbProxySecret', 'disableLocalTransform'
195 ];
196 foreach ( $optionalSettings as $var ) {
197 if ( isset( $info[$var] ) ) {
198 $this->$var = $info[$var];
199 }
200 }
201
202 // Optional settings that have a default
203 $localCapitalLinks =
204 MediaWikiServices::getInstance()->getNamespaceInfo()->isCapitalized( NS_FILE );
205 $this->initialCapital = $info['initialCapital'] ?? $localCapitalLinks;
206 if ( $localCapitalLinks && !$this->initialCapital ) {
207 // If the local wiki's file namespace requires an initial capital, but a foreign file
208 // repo doesn't, complications will result. Linker code will want to auto-capitalize the
209 // first letter of links to files, but those links might actually point to files on
210 // foreign wikis with initial-lowercase names. This combination is not likely to be
211 // used by anyone anyway, so we just outlaw it to save ourselves the bugs. If you want
212 // to include a foreign file repo with initialCapital false, set your local file
213 // namespace to not be capitalized either.
214 throw new InvalidArgumentException(
215 'File repos with initial capital false are not allowed on wikis where the File ' .
216 'namespace has initial capital true' );
217 }
218
219 $this->url = $info['url'] ?? false; // a subclass may set the URL (e.g. ForeignAPIRepo)
220 $defaultThumbUrl = $this->url ? $this->url . '/thumb' : false;
221 $this->thumbUrl = $info['thumbUrl'] ?? $defaultThumbUrl;
222 $this->hashLevels = $info['hashLevels'] ?? 2;
223 $this->deletedHashLevels = $info['deletedHashLevels'] ?? $this->hashLevels;
224 $this->transformVia404 = !empty( $info['transformVia404'] );
225 $this->abbrvThreshold = $info['abbrvThreshold'] ?? 255;
226 $this->isPrivate = !empty( $info['isPrivate'] );
227 // Give defaults for the basic zones...
228 $this->zones = $info['zones'] ?? [];
229 foreach ( [ 'public', 'thumb', 'transcoded', 'temp', 'deleted' ] as $zone ) {
230 if ( !isset( $this->zones[$zone]['container'] ) ) {
231 $this->zones[$zone]['container'] = "{$this->name}-{$zone}";
232 }
233 if ( !isset( $this->zones[$zone]['directory'] ) ) {
234 $this->zones[$zone]['directory'] = '';
235 }
236 if ( !isset( $this->zones[$zone]['urlsByExt'] ) ) {
237 $this->zones[$zone]['urlsByExt'] = [];
238 }
239 }
240
241 $this->supportsSha1URLs = !empty( $info['supportsSha1URLs'] );
242
243 $this->wanCache = $info['wanCache'] ?? WANObjectCache::newEmpty();
244 }
245
251 public function getBackend() {
252 return $this->backend;
253 }
254
261 public function getReadOnlyReason() {
262 return $this->backend->getReadOnlyReason();
263 }
264
271 protected function initZones( $doZones = [] ): void {
272 foreach ( (array)$doZones as $zone ) {
273 $root = $this->getZonePath( $zone );
274 if ( $root === null ) {
275 throw new MWException( "No '$zone' zone defined in the {$this->name} repo." );
276 }
277 }
278 }
279
286 public static function isVirtualUrl( $url ) {
287 return str_starts_with( $url, 'mwrepo://' );
288 }
289
298 public function getVirtualUrl( $suffix = false ) {
299 $path = 'mwrepo://' . $this->name;
300 if ( $suffix !== false ) {
301 $path .= '/' . rawurlencode( $suffix );
302 }
303
304 return $path;
305 }
306
314 public function getZoneUrl( $zone, $ext = null ) {
315 if ( in_array( $zone, [ 'public', 'thumb', 'transcoded' ] ) ) {
316 // standard public zones
317 if ( $ext !== null && isset( $this->zones[$zone]['urlsByExt'][$ext] ) ) {
318 // custom URL for extension/zone
319 // @phan-suppress-next-line PhanTypeArraySuspiciousNullable
320 return $this->zones[$zone]['urlsByExt'][$ext];
321 } elseif ( isset( $this->zones[$zone]['url'] ) ) {
322 // custom URL for zone
323 return $this->zones[$zone]['url'];
324 }
325 }
326 switch ( $zone ) {
327 case 'public':
328 return $this->url;
329 case 'temp':
330 case 'deleted':
331 return false; // no public URL
332 case 'thumb':
333 return $this->thumbUrl;
334 case 'transcoded':
335 return "{$this->url}/transcoded";
336 default:
337 return false;
338 }
339 }
340
344 public function backendSupportsUnicodePaths() {
345 return (bool)( $this->getBackend()->getFeatures() & FileBackend::ATTR_UNICODE_PATHS );
346 }
347
356 public function resolveVirtualUrl( $url ) {
357 if ( !str_starts_with( $url, 'mwrepo://' ) ) {
358 throw new MWException( __METHOD__ . ': unknown protocol' );
359 }
360 $bits = explode( '/', substr( $url, 9 ), 3 );
361 if ( count( $bits ) != 3 ) {
362 throw new MWException( __METHOD__ . ": invalid mwrepo URL: $url" );
363 }
364 [ $repo, $zone, $rel ] = $bits;
365 if ( $repo !== $this->name ) {
366 throw new MWException( __METHOD__ . ": fetching from a foreign repo is not supported" );
367 }
368 $base = $this->getZonePath( $zone );
369 if ( !$base ) {
370 throw new MWException( __METHOD__ . ": invalid zone: $zone" );
371 }
372
373 return $base . '/' . rawurldecode( $rel );
374 }
375
382 protected function getZoneLocation( $zone ) {
383 if ( !isset( $this->zones[$zone] ) ) {
384 return [ null, null ]; // bogus
385 }
386
387 return [ $this->zones[$zone]['container'], $this->zones[$zone]['directory'] ];
388 }
389
396 public function getZonePath( $zone ) {
397 [ $container, $base ] = $this->getZoneLocation( $zone );
398 if ( $container === null || $base === null ) {
399 return null;
400 }
401 $backendName = $this->backend->getName();
402 if ( $base != '' ) { // may not be set
403 $base = "/{$base}";
404 }
405
406 return "mwstore://$backendName/{$container}{$base}";
407 }
408
420 public function newFile( $title, $time = false ) {
421 $title = File::normalizeTitle( $title );
422 if ( !$title ) {
423 return null;
424 }
425 if ( $time ) {
426 if ( $this->oldFileFactory ) {
427 return call_user_func( $this->oldFileFactory, $title, $this, $time );
428 } else {
429 return null;
430 }
431 } else {
432 return call_user_func( $this->fileFactory, $title, $this );
433 }
434 }
435
455 public function findFile( $title, $options = [] ) {
456 if ( !empty( $options['private'] ) && !( $options['private'] instanceof Authority ) ) {
457 throw new InvalidArgumentException(
458 __METHOD__ . ' called with the `private` option set to something ' .
459 'other than an Authority object'
460 );
461 }
462
463 $title = File::normalizeTitle( $title );
464 if ( !$title ) {
465 return false;
466 }
467 if ( isset( $options['bypassCache'] ) ) {
468 $options['latest'] = $options['bypassCache']; // b/c
469 }
470 $time = $options['time'] ?? false;
471 $flags = !empty( $options['latest'] ) ? File::READ_LATEST : 0;
472 # First try the current version of the file to see if it precedes the timestamp
473 $img = $this->newFile( $title );
474 if ( !$img ) {
475 return false;
476 }
477 $img->load( $flags );
478 if ( $img->exists() && ( !$time || $img->getTimestamp() == $time ) ) {
479 return $img;
480 }
481 # Now try an old version of the file
482 if ( $time !== false ) {
483 $img = $this->newFile( $title, $time );
484 if ( $img ) {
485 $img->load( $flags );
486 if ( $img->exists() ) {
487 if ( !$img->isDeleted( File::DELETED_FILE ) ) {
488 return $img; // always OK
489 } elseif (
490 // If its not empty, its an Authority object
491 !empty( $options['private'] ) &&
492 $img->userCan( File::DELETED_FILE, $options['private'] )
493 ) {
494 return $img;
495 }
496 }
497 }
498 }
499
500 # Now try redirects
501 if ( !empty( $options['ignoreRedirect'] ) ) {
502 return false;
503 }
504 $redir = $this->checkRedirect( $title );
505 if ( $redir && $title->getNamespace() === NS_FILE ) {
506 $img = $this->newFile( $redir );
507 if ( !$img ) {
508 return false;
509 }
510 $img->load( $flags );
511 if ( $img->exists() ) {
512 $img->redirectedFrom( $title->getDBkey() );
513
514 return $img;
515 }
516 }
517
518 return false;
519 }
520
538 public function findFiles( array $items, $flags = 0 ) {
539 $result = [];
540 foreach ( $items as $item ) {
541 if ( is_array( $item ) ) {
542 $title = $item['title'];
543 $options = $item;
544 unset( $options['title'] );
545
546 if (
547 !empty( $options['private'] ) &&
548 !( $options['private'] instanceof Authority )
549 ) {
550 $options['private'] = RequestContext::getMain()->getAuthority();
551 }
552 } else {
553 $title = $item;
554 $options = [];
555 }
556 $file = $this->findFile( $title, $options );
557 if ( $file ) {
558 $searchName = File::normalizeTitle( $title )->getDBkey(); // must be valid
559 if ( $flags & self::NAME_AND_TIME_ONLY ) {
560 $result[$searchName] = [
561 'title' => $file->getTitle()->getDBkey(),
562 'timestamp' => $file->getTimestamp()
563 ];
564 } else {
565 $result[$searchName] = $file;
566 }
567 }
568 }
569
570 return $result;
571 }
572
583 public function findFileFromKey( $sha1, $options = [] ) {
584 if ( !empty( $options['private'] ) && !( $options['private'] instanceof Authority ) ) {
585 throw new InvalidArgumentException(
586 __METHOD__ . ' called with the `private` option set to something ' .
587 'other than an Authority object'
588 );
589 }
590
591 $time = $options['time'] ?? false;
592 # First try to find a matching current version of a file...
593 if ( !$this->fileFactoryKey ) {
594 return false; // find-by-sha1 not supported
595 }
596 $img = call_user_func( $this->fileFactoryKey, $sha1, $this, $time );
597 if ( $img && $img->exists() ) {
598 return $img;
599 }
600 # Now try to find a matching old version of a file...
601 if ( $time !== false && $this->oldFileFactoryKey ) { // find-by-sha1 supported?
602 $img = call_user_func( $this->oldFileFactoryKey, $sha1, $this, $time );
603 if ( $img && $img->exists() ) {
604 if ( !$img->isDeleted( File::DELETED_FILE ) ) {
605 return $img; // always OK
606 } elseif (
607 // If its not empty, its an Authority object
608 !empty( $options['private'] ) &&
609 $img->userCan( File::DELETED_FILE, $options['private'] )
610 ) {
611 return $img;
612 }
613 }
614 }
615
616 return false;
617 }
618
627 public function findBySha1( $hash ) {
628 return [];
629 }
630
638 public function findBySha1s( array $hashes ) {
639 $result = [];
640 foreach ( $hashes as $hash ) {
641 $files = $this->findBySha1( $hash );
642 if ( count( $files ) ) {
643 $result[$hash] = $files;
644 }
645 }
646
647 return $result;
648 }
649
658 public function findFilesByPrefix( $prefix, $limit ) {
659 return [];
660 }
661
667 public function getThumbScriptUrl() {
668 return $this->thumbScriptUrl;
669 }
670
676 public function getThumbProxyUrl() {
677 return $this->thumbProxyUrl;
678 }
679
685 public function getThumbProxySecret() {
686 return $this->thumbProxySecret;
687 }
688
694 public function canTransformVia404() {
695 return $this->transformVia404;
696 }
697
704 public function canTransformLocally() {
705 return !$this->disableLocalTransform;
706 }
707
714 public function getNameFromTitle( $title ) {
715 if (
716 $this->initialCapital !=
717 MediaWikiServices::getInstance()->getNamespaceInfo()->isCapitalized( NS_FILE )
718 ) {
719 $name = $title->getDBkey();
720 if ( $this->initialCapital ) {
721 $name = MediaWikiServices::getInstance()->getContentLanguage()->ucfirst( $name );
722 }
723 } else {
724 $name = $title->getDBkey();
725 }
726
727 return $name;
728 }
729
735 public function getRootDirectory() {
736 return $this->getZonePath( 'public' );
737 }
738
746 public function getHashPath( $name ) {
747 return self::getHashPathForLevel( $name, $this->hashLevels );
748 }
749
757 public function getTempHashPath( $suffix ) {
758 $parts = explode( '!', $suffix, 2 ); // format is <timestamp>!<name> or just <name>
759 $name = $parts[1] ?? $suffix; // hash path is not based on timestamp
760 return self::getHashPathForLevel( $name, $this->hashLevels );
761 }
762
768 protected static function getHashPathForLevel( $name, $levels ) {
769 if ( $levels == 0 ) {
770 return '';
771 } else {
772 $hash = md5( $name );
773 $path = '';
774 for ( $i = 1; $i <= $levels; $i++ ) {
775 $path .= substr( $hash, 0, $i ) . '/';
776 }
777
778 return $path;
779 }
780 }
781
787 public function getHashLevels() {
788 return $this->hashLevels;
789 }
790
796 public function getName() {
797 return $this->name;
798 }
799
807 public function makeUrl( $query = '', $entry = 'index' ) {
808 if ( isset( $this->scriptDirUrl ) ) {
809 return wfAppendQuery( "{$this->scriptDirUrl}/{$entry}.php", $query );
810 }
811
812 return false;
813 }
814
827 public function getDescriptionUrl( $name ) {
828 $encName = wfUrlencode( $name );
829 if ( $this->descBaseUrl !== null ) {
830 # "http://example.com/wiki/File:"
831 return $this->descBaseUrl . $encName;
832 }
833 if ( $this->articleUrl !== null ) {
834 # "http://example.com/wiki/$1"
835 # We use "Image:" as the canonical namespace for
836 # compatibility across all MediaWiki versions.
837 return str_replace( '$1',
838 "Image:$encName", $this->articleUrl );
839 }
840 if ( $this->scriptDirUrl !== null ) {
841 # "http://example.com/w"
842 # We use "Image:" as the canonical namespace for
843 # compatibility across all MediaWiki versions,
844 # and just sort of hope index.php is right. ;)
845 return $this->makeUrl( "title=Image:$encName" );
846 }
847
848 return false;
849 }
850
861 public function getDescriptionRenderUrl( $name, $lang = null ) {
862 $query = 'action=render';
863 if ( $lang !== null ) {
864 $query .= '&uselang=' . urlencode( $lang );
865 }
866 if ( isset( $this->scriptDirUrl ) ) {
867 return $this->makeUrl(
868 'title=' .
869 wfUrlencode( 'Image:' . $name ) .
870 "&$query" );
871 } else {
872 $descUrl = $this->getDescriptionUrl( $name );
873 if ( $descUrl ) {
874 return wfAppendQuery( $descUrl, $query );
875 } else {
876 return false;
877 }
878 }
879 }
880
886 public function getDescriptionStylesheetUrl() {
887 if ( isset( $this->scriptDirUrl ) ) {
888 // Must match canonical query parameter order for optimum caching
889 // See HtmlCacheUpdater::getUrls
890 return $this->makeUrl( 'title=MediaWiki:Filepage.css&action=raw&ctype=text/css' );
891 }
892
893 return false;
894 }
895
913 public function store( $srcPath, $dstZone, $dstRel, $flags = 0 ) {
914 $this->assertWritableRepo(); // fail out if read-only
915
916 $status = $this->storeBatch( [ [ $srcPath, $dstZone, $dstRel ] ], $flags );
917 if ( $status->successCount == 0 ) {
918 $status->setOK( false );
919 }
920
921 return $status;
922 }
923
938 public function storeBatch( array $triplets, $flags = 0 ) {
939 $this->assertWritableRepo(); // fail out if read-only
940
941 if ( $flags & self::DELETE_SOURCE ) {
942 throw new InvalidArgumentException( "DELETE_SOURCE not supported in " . __METHOD__ );
943 }
944
945 $status = $this->newGood();
946 $backend = $this->backend; // convenience
947
948 $operations = [];
949 // Validate each triplet and get the store operation...
950 foreach ( $triplets as $triplet ) {
951 [ $src, $dstZone, $dstRel ] = $triplet;
952 $srcPath = ( $src instanceof FSFile ) ? $src->getPath() : $src;
953 wfDebug( __METHOD__
954 . "( \$src='$srcPath', \$dstZone='$dstZone', \$dstRel='$dstRel' )"
955 );
956 // Resolve source path
957 if ( $src instanceof FSFile ) {
958 $op = 'store';
959 } else {
960 $src = $this->resolveToStoragePathIfVirtual( $src );
961 $op = FileBackend::isStoragePath( $src ) ? 'copy' : 'store';
962 }
963 // Resolve destination path
964 $root = $this->getZonePath( $dstZone );
965 if ( !$root ) {
966 throw new MWException( "Invalid zone: $dstZone" );
967 }
968 if ( !$this->validateFilename( $dstRel ) ) {
969 throw new MWException( 'Validation error in $dstRel' );
970 }
971 $dstPath = "$root/$dstRel";
972 $dstDir = dirname( $dstPath );
973 // Create destination directories for this triplet
974 if ( !$this->initDirectory( $dstDir )->isOK() ) {
975 return $this->newFatal( 'directorycreateerror', $dstDir );
976 }
977
978 // Copy the source file to the destination
979 $operations[] = [
980 'op' => $op,
981 'src' => $src, // storage path (copy) or local file path (store)
982 'dst' => $dstPath,
983 'overwrite' => ( $flags & self::OVERWRITE ) ? true : false,
984 'overwriteSame' => ( $flags & self::OVERWRITE_SAME ) ? true : false,
985 ];
986 }
987
988 // Execute the store operation for each triplet
989 $opts = [ 'force' => true ];
990 if ( $flags & self::SKIP_LOCKING ) {
991 $opts['nonLocking'] = true;
992 }
993
994 return $status->merge( $backend->doOperations( $operations, $opts ) );
995 }
996
1007 public function cleanupBatch( array $files, $flags = 0 ) {
1008 $this->assertWritableRepo(); // fail out if read-only
1009
1010 $status = $this->newGood();
1011
1012 $operations = [];
1013 foreach ( $files as $path ) {
1014 if ( is_array( $path ) ) {
1015 // This is a pair, extract it
1016 [ $zone, $rel ] = $path;
1017 $path = $this->getZonePath( $zone ) . "/$rel";
1018 } else {
1019 // Resolve source to a storage path if virtual
1020 $path = $this->resolveToStoragePathIfVirtual( $path );
1021 }
1022 $operations[] = [ 'op' => 'delete', 'src' => $path ];
1023 }
1024 // Actually delete files from storage...
1025 $opts = [ 'force' => true ];
1026 if ( $flags & self::SKIP_LOCKING ) {
1027 $opts['nonLocking'] = true;
1028 }
1029
1030 return $status->merge( $this->backend->doOperations( $operations, $opts ) );
1031 }
1032
1050 final public function quickImport( $src, $dst, $options = null ) {
1051 return $this->quickImportBatch( [ [ $src, $dst, $options ] ] );
1052 }
1053
1068 public function quickImportBatch( array $triples ) {
1069 $status = $this->newGood();
1070 $operations = [];
1071 foreach ( $triples as $triple ) {
1072 [ $src, $dst ] = $triple;
1073 if ( $src instanceof FSFile ) {
1074 $op = 'store';
1075 } else {
1076 $src = $this->resolveToStoragePathIfVirtual( $src );
1077 $op = FileBackend::isStoragePath( $src ) ? 'copy' : 'store';
1078 }
1079 $dst = $this->resolveToStoragePathIfVirtual( $dst );
1080
1081 if ( !isset( $triple[2] ) ) {
1082 $headers = [];
1083 } elseif ( is_string( $triple[2] ) ) {
1084 // back-compat
1085 $headers = [ 'Content-Disposition' => $triple[2] ];
1086 } elseif ( is_array( $triple[2] ) && isset( $triple[2]['headers'] ) ) {
1087 $headers = $triple[2]['headers'];
1088 } else {
1089 $headers = [];
1090 }
1091
1092 $operations[] = [
1093 'op' => $op,
1094 'src' => $src, // storage path (copy) or local path/FSFile (store)
1095 'dst' => $dst,
1096 'headers' => $headers
1097 ];
1098 $status->merge( $this->initDirectory( dirname( $dst ) ) );
1099 }
1100
1101 return $status->merge( $this->backend->doQuickOperations( $operations ) );
1102 }
1103
1112 final public function quickPurge( $path ) {
1113 return $this->quickPurgeBatch( [ $path ] );
1114 }
1115
1123 public function quickCleanDir( $dir ) {
1124 return $this->newGood()->merge(
1125 $this->backend->clean(
1126 [ 'dir' => $this->resolveToStoragePathIfVirtual( $dir ) ]
1127 )
1128 );
1129 }
1130
1139 public function quickPurgeBatch( array $paths ) {
1140 $status = $this->newGood();
1141 $operations = [];
1142 foreach ( $paths as $path ) {
1143 $operations[] = [
1144 'op' => 'delete',
1145 'src' => $this->resolveToStoragePathIfVirtual( $path ),
1146 'ignoreMissingSource' => true
1147 ];
1148 }
1149 $status->merge( $this->backend->doQuickOperations( $operations ) );
1150
1151 return $status;
1152 }
1153
1164 public function storeTemp( $originalName, $srcPath ) {
1165 $this->assertWritableRepo(); // fail out if read-only
1166
1167 $date = MWTimestamp::getInstance()->format( 'YmdHis' );
1168 $hashPath = $this->getHashPath( $originalName );
1169 $dstUrlRel = $hashPath . $date . '!' . rawurlencode( $originalName );
1170 $virtualUrl = $this->getVirtualUrl( 'temp' ) . '/' . $dstUrlRel;
1171
1172 $result = $this->quickImport( $srcPath, $virtualUrl );
1173 $result->value = $virtualUrl;
1174
1175 return $result;
1176 }
1177
1184 public function freeTemp( $virtualUrl ) {
1185 $this->assertWritableRepo(); // fail out if read-only
1186
1187 $temp = $this->getVirtualUrl( 'temp' );
1188 if ( !str_starts_with( $virtualUrl, $temp ) ) {
1189 wfDebug( __METHOD__ . ": Invalid temp virtual URL" );
1190
1191 return false;
1192 }
1193
1194 return $this->quickPurge( $virtualUrl )->isOK();
1195 }
1196
1206 public function concatenate( array $srcPaths, $dstPath, $flags = 0 ) {
1207 $this->assertWritableRepo(); // fail out if read-only
1208
1209 $status = $this->newGood();
1210
1211 $sources = [];
1212 foreach ( $srcPaths as $srcPath ) {
1213 // Resolve source to a storage path if virtual
1214 $source = $this->resolveToStoragePathIfVirtual( $srcPath );
1215 $sources[] = $source; // chunk to merge
1216 }
1217
1218 // Concatenate the chunks into one FS file
1219 $params = [ 'srcs' => $sources, 'dst' => $dstPath ];
1220 $status->merge( $this->backend->concatenate( $params ) );
1221 if ( !$status->isOK() ) {
1222 return $status;
1223 }
1224
1225 // Delete the sources if required
1226 if ( $flags & self::DELETE_SOURCE ) {
1227 $status->merge( $this->quickPurgeBatch( $srcPaths ) );
1228 }
1229
1230 // Make sure status is OK, despite any quickPurgeBatch() fatals
1231 $status->setResult( true );
1232
1233 return $status;
1234 }
1235
1259 public function publish(
1260 $src, $dstRel, $archiveRel, $flags = 0, array $options = []
1261 ) {
1262 $this->assertWritableRepo(); // fail out if read-only
1263
1264 $status = $this->publishBatch(
1265 [ [ $src, $dstRel, $archiveRel, $options ] ], $flags );
1266 if ( $status->successCount == 0 ) {
1267 $status->setOK( false );
1268 }
1269 $status->value = $status->value[0] ?? false;
1270
1271 return $status;
1272 }
1273
1286 public function publishBatch( array $ntuples, $flags = 0 ) {
1287 $this->assertWritableRepo(); // fail out if read-only
1288
1289 $backend = $this->backend; // convenience
1290 // Try creating directories
1291 $this->initZones( 'public' );
1292
1293 $status = $this->newGood( [] );
1294
1295 $operations = [];
1296 $sourceFSFilesToDelete = []; // cleanup for disk source files
1297 // Validate each triplet and get the store operation...
1298 foreach ( $ntuples as $ntuple ) {
1299 [ $src, $dstRel, $archiveRel ] = $ntuple;
1300 $srcPath = ( $src instanceof FSFile ) ? $src->getPath() : $src;
1301
1302 $options = $ntuple[3] ?? [];
1303 // Resolve source to a storage path if virtual
1304 $srcPath = $this->resolveToStoragePathIfVirtual( $srcPath );
1305 if ( !$this->validateFilename( $dstRel ) ) {
1306 throw new MWException( 'Validation error in $dstRel' );
1307 }
1308 if ( !$this->validateFilename( $archiveRel ) ) {
1309 throw new MWException( 'Validation error in $archiveRel' );
1310 }
1311
1312 $publicRoot = $this->getZonePath( 'public' );
1313 $dstPath = "$publicRoot/$dstRel";
1314 $archivePath = "$publicRoot/$archiveRel";
1315
1316 $dstDir = dirname( $dstPath );
1317 $archiveDir = dirname( $archivePath );
1318 // Abort immediately on directory creation errors since they're likely to be repetitive
1319 if ( !$this->initDirectory( $dstDir )->isOK() ) {
1320 return $this->newFatal( 'directorycreateerror', $dstDir );
1321 }
1322 if ( !$this->initDirectory( $archiveDir )->isOK() ) {
1323 return $this->newFatal( 'directorycreateerror', $archiveDir );
1324 }
1325
1326 // Set any desired headers to be use in GET/HEAD responses
1327 $headers = $options['headers'] ?? [];
1328
1329 // Archive destination file if it exists.
1330 // This will check if the archive file also exists and fail if does.
1331 // This is a check to avoid data loss. On Windows and Linux,
1332 // copy() will overwrite, so the existence check is vulnerable to
1333 // race conditions unless a functioning LockManager is used.
1334 // LocalFile also uses SELECT FOR UPDATE for synchronization.
1335 $operations[] = [
1336 'op' => 'copy',
1337 'src' => $dstPath,
1338 'dst' => $archivePath,
1339 'ignoreMissingSource' => true
1340 ];
1341
1342 // Copy (or move) the source file to the destination
1343 if ( FileBackend::isStoragePath( $srcPath ) ) {
1344 $operations[] = [
1345 'op' => ( $flags & self::DELETE_SOURCE ) ? 'move' : 'copy',
1346 'src' => $srcPath,
1347 'dst' => $dstPath,
1348 'overwrite' => true, // replace current
1349 'headers' => $headers
1350 ];
1351 } else {
1352 $operations[] = [
1353 'op' => 'store',
1354 'src' => $src, // storage path (copy) or local path/FSFile (store)
1355 'dst' => $dstPath,
1356 'overwrite' => true, // replace current
1357 'headers' => $headers
1358 ];
1359 if ( $flags & self::DELETE_SOURCE ) {
1360 $sourceFSFilesToDelete[] = $srcPath;
1361 }
1362 }
1363 }
1364
1365 // Execute the operations for each triplet
1366 $status->merge( $backend->doOperations( $operations ) );
1367 // Find out which files were archived...
1368 foreach ( $ntuples as $i => $ntuple ) {
1369 [ , , $archiveRel ] = $ntuple;
1370 $archivePath = $this->getZonePath( 'public' ) . "/$archiveRel";
1371 if ( $this->fileExists( $archivePath ) ) {
1372 $status->value[$i] = 'archived';
1373 } else {
1374 $status->value[$i] = 'new';
1375 }
1376 }
1377 // Cleanup for disk source files...
1378 foreach ( $sourceFSFilesToDelete as $file ) {
1379 AtEase::suppressWarnings();
1380 unlink( $file ); // FS cleanup
1381 AtEase::restoreWarnings();
1382 }
1383
1384 return $status;
1385 }
1386
1394 protected function initDirectory( $dir ) {
1395 $path = $this->resolveToStoragePathIfVirtual( $dir );
1396 [ , $container, ] = FileBackend::splitStoragePath( $path );
1397
1398 $params = [ 'dir' => $path ];
1399 if ( $this->isPrivate
1400 || $container === $this->zones['deleted']['container']
1401 || $container === $this->zones['temp']['container']
1402 ) {
1403 # Take all available measures to prevent web accessibility of new deleted
1404 # directories, in case the user has not configured offline storage
1405 $params = [ 'noAccess' => true, 'noListing' => true ] + $params;
1406 }
1407
1408 return $this->newGood()->merge( $this->backend->prepare( $params ) );
1409 }
1410
1417 public function cleanDir( $dir ) {
1418 $this->assertWritableRepo(); // fail out if read-only
1419
1420 return $this->newGood()->merge(
1421 $this->backend->clean(
1422 [ 'dir' => $this->resolveToStoragePathIfVirtual( $dir ) ]
1423 )
1424 );
1425 }
1426
1433 public function fileExists( $file ) {
1434 $result = $this->fileExistsBatch( [ $file ] );
1435
1436 return $result[0];
1437 }
1438
1446 public function fileExistsBatch( array $files ) {
1447 $paths = array_map( [ $this, 'resolveToStoragePathIfVirtual' ], $files );
1448 $this->backend->preloadFileStat( [ 'srcs' => $paths ] );
1449
1450 $result = [];
1451 foreach ( $files as $key => $file ) {
1452 $path = $this->resolveToStoragePathIfVirtual( $file );
1453 $result[$key] = $this->backend->fileExists( [ 'src' => $path ] );
1454 }
1455
1456 return $result;
1457 }
1458
1469 public function delete( $srcRel, $archiveRel ) {
1470 $this->assertWritableRepo(); // fail out if read-only
1471
1472 return $this->deleteBatch( [ [ $srcRel, $archiveRel ] ] );
1473 }
1474
1492 public function deleteBatch( array $sourceDestPairs ) {
1493 $this->assertWritableRepo(); // fail out if read-only
1494
1495 // Try creating directories
1496 $this->initZones( [ 'public', 'deleted' ] );
1497
1498 $status = $this->newGood();
1499
1500 $backend = $this->backend; // convenience
1501 $operations = [];
1502 // Validate filenames and create archive directories
1503 foreach ( $sourceDestPairs as [ $srcRel, $archiveRel ] ) {
1504 if ( !$this->validateFilename( $srcRel ) ) {
1505 throw new MWException( __METHOD__ . ':Validation error in $srcRel' );
1506 } elseif ( !$this->validateFilename( $archiveRel ) ) {
1507 throw new MWException( __METHOD__ . ':Validation error in $archiveRel' );
1508 }
1509
1510 $publicRoot = $this->getZonePath( 'public' );
1511 $srcPath = "{$publicRoot}/$srcRel";
1512
1513 $deletedRoot = $this->getZonePath( 'deleted' );
1514 $archivePath = "{$deletedRoot}/{$archiveRel}";
1515 $archiveDir = dirname( $archivePath ); // does not touch FS
1516
1517 // Create destination directories
1518 if ( !$this->initDirectory( $archiveDir )->isGood() ) {
1519 return $this->newFatal( 'directorycreateerror', $archiveDir );
1520 }
1521
1522 $operations[] = [
1523 'op' => 'move',
1524 'src' => $srcPath,
1525 'dst' => $archivePath,
1526 // We may have 2+ identical files being deleted,
1527 // all of which will map to the same destination file
1528 'overwriteSame' => true // also see T33792
1529 ];
1530 }
1531
1532 // Move the files by execute the operations for each pair.
1533 // We're now committed to returning an OK result, which will
1534 // lead to the files being moved in the DB also.
1535 $opts = [ 'force' => true ];
1536 return $status->merge( $backend->doOperations( $operations, $opts ) );
1537 }
1538
1545 public function cleanupDeletedBatch( array $storageKeys ) {
1546 $this->assertWritableRepo();
1547 }
1548
1557 public function getDeletedHashPath( $key ) {
1558 if ( strlen( $key ) < 31 ) {
1559 throw new MWException( "Invalid storage key '$key'." );
1560 }
1561 $path = '';
1562 for ( $i = 0; $i < $this->deletedHashLevels; $i++ ) {
1563 $path .= $key[$i] . '/';
1564 }
1565
1566 return $path;
1567 }
1568
1577 protected function resolveToStoragePathIfVirtual( $path ) {
1578 if ( self::isVirtualUrl( $path ) ) {
1579 return $this->resolveVirtualUrl( $path );
1580 }
1581
1582 return $path;
1583 }
1584
1592 public function getLocalCopy( $virtualUrl ) {
1593 $path = $this->resolveToStoragePathIfVirtual( $virtualUrl );
1594
1595 return $this->backend->getLocalCopy( [ 'src' => $path ] );
1596 }
1597
1606 public function getLocalReference( $virtualUrl ) {
1607 $path = $this->resolveToStoragePathIfVirtual( $virtualUrl );
1608
1609 return $this->backend->getLocalReference( [ 'src' => $path ] );
1610 }
1611
1619 public function getFileProps( $virtualUrl ) {
1620 $fsFile = $this->getLocalReference( $virtualUrl );
1621 $mwProps = new MWFileProps( MediaWikiServices::getInstance()->getMimeAnalyzer() );
1622 if ( $fsFile ) {
1623 $props = $mwProps->getPropsFromPath( $fsFile->getPath(), true );
1624 } else {
1625 $props = $mwProps->newPlaceholderProps();
1626 }
1627
1628 return $props;
1629 }
1630
1637 public function getFileTimestamp( $virtualUrl ) {
1638 $path = $this->resolveToStoragePathIfVirtual( $virtualUrl );
1639
1640 return $this->backend->getFileTimestamp( [ 'src' => $path ] );
1641 }
1642
1649 public function getFileSize( $virtualUrl ) {
1650 $path = $this->resolveToStoragePathIfVirtual( $virtualUrl );
1651
1652 return $this->backend->getFileSize( [ 'src' => $path ] );
1653 }
1654
1661 public function getFileSha1( $virtualUrl ) {
1662 $path = $this->resolveToStoragePathIfVirtual( $virtualUrl );
1663
1664 return $this->backend->getFileSha1Base36( [ 'src' => $path ] );
1665 }
1666
1676 public function streamFileWithStatus( $virtualUrl, $headers = [], $optHeaders = [] ) {
1677 $path = $this->resolveToStoragePathIfVirtual( $virtualUrl );
1678 $params = [ 'src' => $path, 'headers' => $headers, 'options' => $optHeaders ];
1679
1680 // T172851: HHVM does not flush the output properly, causing OOM
1681 ob_start( null, 1048576 );
1682 ob_implicit_flush( true );
1683
1684 $status = $this->newGood()->merge( $this->backend->streamFile( $params ) );
1685
1686 // T186565: Close the buffer, unless it has already been closed
1687 // in HTTPFileStreamer::resetOutputBuffers().
1688 if ( ob_get_status() ) {
1689 ob_end_flush();
1690 }
1691
1692 return $status;
1693 }
1694
1703 public function enumFiles( $callback ) {
1704 $this->enumFilesInStorage( $callback );
1705 }
1706
1714 protected function enumFilesInStorage( $callback ) {
1715 $publicRoot = $this->getZonePath( 'public' );
1716 $numDirs = 1 << ( $this->hashLevels * 4 );
1717 // Use a priori assumptions about directory structure
1718 // to reduce the tree height of the scanning process.
1719 for ( $flatIndex = 0; $flatIndex < $numDirs; $flatIndex++ ) {
1720 $hexString = sprintf( "%0{$this->hashLevels}x", $flatIndex );
1721 $path = $publicRoot;
1722 for ( $hexPos = 0; $hexPos < $this->hashLevels; $hexPos++ ) {
1723 $path .= '/' . substr( $hexString, 0, $hexPos + 1 );
1724 }
1725 $iterator = $this->backend->getFileList( [ 'dir' => $path ] );
1726 if ( $iterator === null ) {
1727 throw new MWException( __METHOD__ . ': could not get file listing for ' . $path );
1728 }
1729 foreach ( $iterator as $name ) {
1730 // Each item returned is a public file
1731 call_user_func( $callback, "{$path}/{$name}" );
1732 }
1733 }
1734 }
1735
1742 public function validateFilename( $filename ) {
1743 if ( strval( $filename ) == '' ) {
1744 return false;
1745 }
1746
1747 return FileBackend::isPathTraversalFree( $filename );
1748 }
1749
1755 private function getErrorCleanupFunction() {
1756 switch ( $this->pathDisclosureProtection ) {
1757 case 'none':
1758 case 'simple': // b/c
1759 $callback = [ $this, 'passThrough' ];
1760 break;
1761 default: // 'paranoid'
1762 $callback = [ $this, 'paranoidClean' ];
1763 }
1764 return $callback;
1765 }
1766
1773 public function paranoidClean( $param ) {
1774 return '[hidden]';
1775 }
1776
1783 public function passThrough( $param ) {
1784 return $param;
1785 }
1786
1794 public function newFatal( $message, ...$parameters ) {
1795 $status = Status::newFatal( $message, ...$parameters );
1796 $status->cleanCallback = $this->getErrorCleanupFunction();
1797
1798 return $status;
1799 }
1800
1807 public function newGood( $value = null ) {
1808 $status = Status::newGood( $value );
1809 $status->cleanCallback = $this->getErrorCleanupFunction();
1810
1811 return $status;
1812 }
1813
1822 public function checkRedirect( $title ) {
1823 return false;
1824 }
1825
1833 public function invalidateImageRedirect( $title ) {
1834 }
1835
1841 public function getDisplayName() {
1842 $sitename = MediaWikiServices::getInstance()->getMainConfig()->get( MainConfigNames::Sitename );
1843
1844 if ( $this->isLocal() ) {
1845 return $sitename;
1846 }
1847
1848 // 'shared-repo-name-wikimediacommons' is used when $wgUseInstantCommons = true
1849 return wfMessageFallback( 'shared-repo-name-' . $this->name, 'shared-repo' )->text();
1850 }
1851
1859 public function nameForThumb( $name ) {
1860 if ( strlen( $name ) > $this->abbrvThreshold ) {
1862 $name = ( $ext == '' ) ? 'thumbnail' : "thumbnail.$ext";
1863 }
1864
1865 return $name;
1866 }
1867
1873 public function isLocal() {
1874 return $this->getName() == 'local';
1875 }
1876
1888 public function getSharedCacheKey( $kClassSuffix, ...$components ) {
1889 return false;
1890 }
1891
1903 public function getLocalCacheKey( $kClassSuffix, ...$components ) {
1904 return $this->wanCache->makeKey(
1905 'filerepo-' . $kClassSuffix,
1906 $this->getName(),
1907 ...$components
1908 );
1909 }
1910
1919 public function getTempRepo() {
1920 return new TempFileRepo( [
1921 'name' => "{$this->name}-temp",
1922 'backend' => $this->backend,
1923 'zones' => [
1924 'public' => [
1925 // Same place storeTemp() uses in the base repo, though
1926 // the path hashing is mismatched, which is annoying.
1927 'container' => $this->zones['temp']['container'],
1928 'directory' => $this->zones['temp']['directory']
1929 ],
1930 'thumb' => [
1931 'container' => $this->zones['temp']['container'],
1932 'directory' => $this->zones['temp']['directory'] == ''
1933 ? 'thumb'
1934 : $this->zones['temp']['directory'] . '/thumb'
1935 ],
1936 'transcoded' => [
1937 'container' => $this->zones['temp']['container'],
1938 'directory' => $this->zones['temp']['directory'] == ''
1939 ? 'transcoded'
1940 : $this->zones['temp']['directory'] . '/transcoded'
1941 ]
1942 ],
1943 'hashLevels' => $this->hashLevels, // performance
1944 'isPrivate' => true // all in temp zone
1945 ] );
1946 }
1947
1954 public function getUploadStash( UserIdentity $user = null ) {
1955 return new UploadStash( $this, $user );
1956 }
1957
1965 protected function assertWritableRepo() {
1966 }
1967
1974 public function getInfo() {
1975 $ret = [
1976 'name' => $this->getName(),
1977 'displayname' => $this->getDisplayName(),
1978 'rootUrl' => $this->getZoneUrl( 'public' ),
1979 'local' => $this->isLocal(),
1980 ];
1981
1982 $optionalSettings = [
1983 'url',
1984 'thumbUrl',
1985 'initialCapital',
1986 'descBaseUrl',
1987 'scriptDirUrl',
1988 'articleUrl',
1989 'fetchDescription',
1990 'descriptionCacheExpiry',
1991 ];
1992 foreach ( $optionalSettings as $k ) {
1993 if ( isset( $this->$k ) ) {
1994 $ret[$k] = $this->$k;
1995 }
1996 }
1997 if ( isset( $this->favicon ) ) {
1998 // Expand any local path to full URL to improve API usability (T77093).
1999 $ret['favicon'] = MediaWikiServices::getInstance()->getUrlUtils()
2000 ->expand( $this->favicon );
2001 }
2002
2003 return $ret;
2004 }
2005
2010 public function hasSha1Storage() {
2011 return $this->hasSha1Storage;
2012 }
2013
2018 public function supportsSha1URLs() {
2019 return $this->supportsSha1URLs;
2020 }
2021}
const NS_FILE
Definition Defines.php:70
wfDebug( $text, $dest='all', array $context=[])
Sends a line to the debug log if enabled or, optionally, to a comment in output.
wfUrlencode( $s)
We want some things to be included as literal characters in our title URLs for prettiness,...
wfMessageFallback(... $keys)
This function accepts multiple message keys and returns a message instance for the first message whic...
wfAppendQuery( $url, $query)
Append a query string to an existing URL, which may or may not already have query string parameters a...
Class representing a non-directory file on the file system.
Definition FSFile.php:32
Base class for all file backend classes (including multi-write backends).
static isStoragePath( $path)
Check if a given path is a "mwstore://" path.
const ATTR_UNICODE_PATHS
static splitStoragePath( $storagePath)
Split a storage path into a backend name, a container name, and a relative file path.
static extensionFromPath( $path, $case='lowercase')
Get the final extension from a storage or FS path.
static isPathTraversalFree( $path)
Check if a relative path has no directory traversals.
Base class for file repositories.
Definition FileRepo.php:48
string $pathDisclosureProtection
May be 'paranoid' to remove all parameters from error messages, 'none' to leave the paths in unchange...
Definition FileRepo.php:109
getTempHashPath( $suffix)
Get a relative path including trailing slash, e.g.
Definition FileRepo.php:757
int $hashLevels
The number of directory levels for hash-based division of files.
Definition FileRepo.php:118
getTempRepo()
Get a temporary private FileRepo associated with this repo.
cleanupDeletedBatch(array $storageKeys)
Delete files in the deleted directory if they are not referenced in the filearchive table.
const OVERWRITE_SAME
Definition FileRepo.php:51
resolveVirtualUrl( $url)
Get the backend storage path corresponding to a virtual URL.
Definition FileRepo.php:356
nameForThumb( $name)
Get the portion of the file that contains the origin file name.
publishBatch(array $ntuples, $flags=0)
Publish a batch of files.
findFiles(array $items, $flags=0)
Find many files at once.
Definition FileRepo.php:538
newFatal( $message,... $parameters)
Create a new fatal error.
getThumbProxyUrl()
Get the URL thumb.php requests are being proxied to.
Definition FileRepo.php:676
getZoneLocation( $zone)
The storage container and base path of a zone.
Definition FileRepo.php:382
fileExists( $file)
Checks existence of a file.
getFileSha1( $virtualUrl)
Get the sha1 (base 36) of a file with a given virtual URL/storage path.
bool $supportsSha1URLs
Definition FileRepo.php:68
quickImportBatch(array $triples)
Import a batch of files from the local file system into the repo.
assertWritableRepo()
Throw an exception if this repo is read-only by design.
getRootDirectory()
Get the public zone root storage directory of the repository.
Definition FileRepo.php:735
supportsSha1URLs()
Returns whether or not repo supports having originals SHA-1s in the thumb URLs.
newGood( $value=null)
Create a new good result.
findFilesByPrefix( $prefix, $limit)
Return an array of files where the name starts with $prefix.
Definition FileRepo.php:658
getHashLevels()
Get the number of hash directory levels.
Definition FileRepo.php:787
string $thumbProxySecret
Secret key to pass as an X-Swift-Secret header to the proxied thumb service.
Definition FileRepo.php:149
streamFileWithStatus( $virtualUrl, $headers=[], $optHeaders=[])
Attempt to stream a file with the given virtual URL/storage path.
getName()
Get the name of this repository, as specified by $info['name]' to the constructor.
Definition FileRepo.php:796
store( $srcPath, $dstZone, $dstRel, $flags=0)
Store a file to a given destination.
Definition FileRepo.php:913
findFile( $title, $options=[])
Find an instance of the named file created at the specified time Returns false if the file does not e...
Definition FileRepo.php:455
callable false $oldFileFactoryKey
Override these in the base class.
Definition FileRepo.php:142
getVirtualUrl( $suffix=false)
Get a URL referring to this repository, with the private mwrepo protocol.
Definition FileRepo.php:298
const NAME_AND_TIME_ONLY
Definition FileRepo.php:54
quickPurge( $path)
Purge a file from the repo.
quickPurgeBatch(array $paths)
Purge a batch of files from the repo.
passThrough( $param)
Path disclosure protection function.
static getHashPathForLevel( $name, $levels)
Definition FileRepo.php:768
array $zones
Map of zones to config.
Definition FileRepo.php:74
callable false $fileFactoryKey
Override these in the base class.
Definition FileRepo.php:140
checkRedirect( $title)
Checks if there is a redirect named as $title.
getDisplayName()
Get the human-readable name of the repo.
getSharedCacheKey( $kClassSuffix,... $components)
Get a global, repository-qualified, WAN cache key.
getLocalCacheKey( $kClassSuffix,... $components)
Get a site-local, repository-qualified, WAN cache key.
bool $disableLocalTransform
Disable local image scaling.
Definition FileRepo.php:152
storeBatch(array $triplets, $flags=0)
Store a batch of files.
Definition FileRepo.php:938
enumFiles( $callback)
Call a callback function for every public regular file in the repository.
canTransformLocally()
Returns true if the repository can transform files locally.
Definition FileRepo.php:704
hasSha1Storage()
Returns whether or not storage is SHA-1 based.
cleanupBatch(array $files, $flags=0)
Deletes a batch of files.
publish( $src, $dstRel, $archiveRel, $flags=0, array $options=[])
Copy or move a file either from a storage path, virtual URL, or file system path, into this repositor...
initDirectory( $dir)
Creates a directory with the appropriate zone permissions.
int $abbrvThreshold
File names over this size will use the short form of thumbnail names.
Definition FileRepo.php:127
makeUrl( $query='', $entry='index')
Make an url to this repo.
Definition FileRepo.php:807
findBySha1s(array $hashes)
Get an array of arrays or iterators of file objects for files that have the given SHA-1 content hashe...
Definition FileRepo.php:638
string $thumbProxyUrl
URL of where to proxy thumb.php requests to.
Definition FileRepo.php:147
concatenate(array $srcPaths, $dstPath, $flags=0)
Concatenate a list of temporary files into a target file location.
FileBackend $backend
Definition FileRepo.php:71
null string $favicon
The URL to a favicon (optional, may be a server-local path URL).
Definition FileRepo.php:130
fileExistsBatch(array $files)
Checks existence of an array of files.
int $descriptionCacheExpiry
Definition FileRepo.php:62
paranoidClean( $param)
Path disclosure protection function.
WANObjectCache $wanCache
Definition FileRepo.php:155
const SKIP_LOCKING
Definition FileRepo.php:52
initZones( $doZones=[])
Ensure that a single zone or list of zones is defined for usage.
Definition FileRepo.php:271
getFileProps( $virtualUrl)
Get properties of a file with a given virtual URL/storage path.
const OVERWRITE
Definition FileRepo.php:50
isLocal()
Returns true if this the local file repository.
getZonePath( $zone)
Get the storage path corresponding to one of the zones.
Definition FileRepo.php:396
getUploadStash(UserIdentity $user=null)
Get an UploadStash associated with this repo.
getDescriptionUrl( $name)
Get the URL of an image description page.
Definition FileRepo.php:827
cleanDir( $dir)
Deletes a directory if empty.
resolveToStoragePathIfVirtual( $path)
If a path is a virtual URL, resolve it to a storage path.
getDeletedHashPath( $key)
Get a relative path for a deletion archive key, e.g.
bool $hasSha1Storage
Definition FileRepo.php:65
const DELETE_SOURCE
Definition FileRepo.php:49
getNameFromTitle( $title)
Get the name of a file from its title.
Definition FileRepo.php:714
invalidateImageRedirect( $title)
Invalidates image redirect cache related to that image Doesn't do anything for repositories that don'...
getFileSize( $virtualUrl)
Get the size of a file with a given virtual URL/storage path.
getThumbProxySecret()
Get the secret key for the proxied thumb service.
Definition FileRepo.php:685
bool $fetchDescription
Whether to fetch commons image description pages and display them on the local wiki.
Definition FileRepo.php:59
string false $url
Public zone URL.
Definition FileRepo.php:112
callable $fileFactory
Override these in the base class.
Definition FileRepo.php:136
static isVirtualUrl( $url)
Determine if a string is an mwrepo:// URL.
Definition FileRepo.php:286
getDescriptionStylesheetUrl()
Get the URL of the stylesheet to apply to description pages.
Definition FileRepo.php:886
bool $transformVia404
Whether to skip media file transformation on parse and rely on a 404 handler instead.
Definition FileRepo.php:82
getFileTimestamp( $virtualUrl)
Get the timestamp of a file with a given virtual URL/storage path.
bool $isPrivate
Whether all zones should be private (e.g.
Definition FileRepo.php:133
string $scriptDirUrl
URL of the MediaWiki installation, equivalent to $wgScriptPath, e.g.
Definition FileRepo.php:92
string $descBaseUrl
URL of image description pages, e.g.
Definition FileRepo.php:87
getZoneUrl( $zone, $ext=null)
Get the URL corresponding to one of the four basic zones.
Definition FileRepo.php:314
getReadOnlyReason()
Get an explanatory message if this repo is read-only.
Definition FileRepo.php:261
newFile( $title, $time=false)
Create a new File object from the local repository.
Definition FileRepo.php:420
storeTemp( $originalName, $srcPath)
Pick a random name in the temp zone and store a file to it.
quickCleanDir( $dir)
Deletes a directory if empty.
canTransformVia404()
Returns true if the repository can transform files via a 404 handler.
Definition FileRepo.php:694
enumFilesInStorage( $callback)
Call a callback function for every public file in the repository.
validateFilename( $filename)
Determine if a relative path is valid, i.e.
findFileFromKey( $sha1, $options=[])
Find an instance of the file with this key, created at the specified time Returns false if the file d...
Definition FileRepo.php:583
int $deletedHashLevels
The number of directory levels for hash-based division of deleted files.
Definition FileRepo.php:121
string $thumbScriptUrl
URL of thumb.php.
Definition FileRepo.php:77
backendSupportsUnicodePaths()
Definition FileRepo.php:344
__construct(array $info=null)
Definition FileRepo.php:170
string $name
Definition FileRepo.php:162
string false $thumbUrl
The base thumbnail URL.
Definition FileRepo.php:115
bool $initialCapital
Equivalent to $wgCapitalLinks (or $wgCapitalLinkOverrides[NS_FILE], determines whether filenames impl...
Definition FileRepo.php:102
getLocalCopy( $virtualUrl)
Get a local FS copy of a file with a given virtual URL/storage path.
deleteBatch(array $sourceDestPairs)
Move a group of files to the deletion archive.
getHashPath( $name)
Get a relative path including trailing slash, e.g.
Definition FileRepo.php:746
callable false $oldFileFactory
Override these in the base class.
Definition FileRepo.php:138
quickImport( $src, $dst, $options=null)
Import a file from the local file system into the repo.
string $articleUrl
Equivalent to $wgArticlePath, e.g.
Definition FileRepo.php:95
getDescriptionRenderUrl( $name, $lang=null)
Get the URL of the content-only fragment of the description page.
Definition FileRepo.php:861
freeTemp( $virtualUrl)
Remove a temporary file or mark it for garbage collection.
findBySha1( $hash)
Get an array or iterator of file objects for files that have a given SHA-1 content hash.
Definition FileRepo.php:627
getBackend()
Get the file backend instance.
Definition FileRepo.php:251
getInfo()
Return information about the repository.
getThumbScriptUrl()
Get the URL of thumb.php.
Definition FileRepo.php:667
getLocalReference( $virtualUrl)
Get a local FS file with a given virtual URL/storage path.
MediaWiki exception.
MimeMagic helper wrapper.
A class containing constants representing the names of configuration variables.
Service locator for MediaWiki core services.
Represents a title within MediaWiki.
Definition Title.php:82
FileRepo for temporary files created by FileRepo::getTempRepo()
UploadStash is intended to accomplish a few things:
Multi-datacenter aware caching interface.
Interface for objects (potentially) representing an editable wiki page.
This interface represents the authority associated the current execution context, such as a web reque...
Definition Authority.php:37
Interface for objects representing user identity.
$source
return true
Definition router.php:92
if(PHP_SAPI !='cli-server') if(!isset( $_SERVER['SCRIPT_FILENAME'])) $file
Item class for a filearchive table row.
Definition router.php:42
if(!is_readable( $file)) $ext
Definition router.php:48
if(!isset( $args[0])) $lang