MediaWiki REL1_34
FileBackendGroup.php
Go to the documentation of this file.
1<?php
26
35 protected static $instance = null;
36
38 protected $backends = [];
39
40 protected function __construct() {
41 }
42
46 public static function singleton() {
47 if ( self::$instance == null ) {
48 self::$instance = new self();
49 self::$instance->initFromGlobals();
50 }
51
52 return self::$instance;
53 }
54
58 public static function destroySingleton() {
59 self::$instance = null;
60 }
61
65 protected function initFromGlobals() {
67
68 // Register explicitly defined backends
69 $this->register( $wgFileBackends, wfConfiguredReadOnlyReason() );
70
71 $autoBackends = [];
72 // Automatically create b/c backends for file repos...
73 $repos = array_merge( $wgForeignFileRepos, [ $wgLocalFileRepo ] );
74 foreach ( $repos as $info ) {
75 $backendName = $info['backend'];
76 if ( is_object( $backendName ) || isset( $this->backends[$backendName] ) ) {
77 continue; // already defined (or set to the object for some reason)
78 }
79 $repoName = $info['name'];
80 // Local vars that used to be FSRepo members...
81 $directory = $info['directory'];
82 $deletedDir = $info['deletedDir'] ?? false; // deletion disabled
83 $thumbDir = $info['thumbDir'] ?? "{$directory}/thumb";
84 $transcodedDir = $info['transcodedDir'] ?? "{$directory}/transcoded";
85 // Get the FS backend configuration
86 $autoBackends[] = [
87 'name' => $backendName,
88 'class' => FSFileBackend::class,
89 'lockManager' => 'fsLockManager',
90 'containerPaths' => [
91 "{$repoName}-public" => "{$directory}",
92 "{$repoName}-thumb" => $thumbDir,
93 "{$repoName}-transcoded" => $transcodedDir,
94 "{$repoName}-deleted" => $deletedDir,
95 "{$repoName}-temp" => "{$directory}/temp"
96 ],
97 'fileMode' => $info['fileMode'] ?? 0644,
98 'directoryMode' => $wgDirectoryMode,
99 ];
100 }
101
102 // Register implicitly defined backends
103 $this->register( $autoBackends, wfConfiguredReadOnlyReason() );
104 }
105
113 protected function register( array $configs, $readOnlyReason = null ) {
114 foreach ( $configs as $config ) {
115 if ( !isset( $config['name'] ) ) {
116 throw new InvalidArgumentException( "Cannot register a backend with no name." );
117 }
118 $name = $config['name'];
119 if ( isset( $this->backends[$name] ) ) {
120 throw new LogicException( "Backend with name '$name' already registered." );
121 } elseif ( !isset( $config['class'] ) ) {
122 throw new InvalidArgumentException( "Backend with name '$name' has no class." );
123 }
124 $class = $config['class'];
125
126 if ( isset( $config['domainId'] ) ) {
127 $domainId = $config['domainId'];
128 } elseif ( isset( $config['wikiId'] ) ) {
129 $domainId = $config['wikiId']; // b/c
130 } else {
131 // Only use the raw database/prefix for backwards compatibility
132 $ld = WikiMap::getCurrentWikiDbDomain();
133 $domainId = strlen( $ld->getTablePrefix() )
134 ? "{$ld->getDatabase()}-{$ld->getTablePrefix()}"
135 : $ld->getDatabase();
136 // If the local wiki ID and local domain ID do not match, probably due to a
137 // non-default schema, issue a warning. A non-default schema indicates that
138 // it might be used to disambiguate different wikis.
139 $wikiId = WikiMap::getWikiIdFromDbDomain( $ld );
140 if ( $ld->getSchema() !== null && $domainId !== $wikiId ) {
141 wfWarn(
142 "\$wgFileBackend entry '$name' should have 'domainId' set.\n" .
143 "Legacy default 'domainId' is '$domainId' but wiki ID is '$wikiId'."
144 );
145 }
146 }
147 $config['domainId'] = $domainId;
148 $config['readOnly'] = $config['readOnly'] ?? $readOnlyReason;
149
150 unset( $config['class'] ); // backend won't need this
151 $this->backends[$name] = [
152 'class' => $class,
153 'config' => $config,
154 'instance' => null
155 ];
156 }
157 }
158
166 public function get( $name ) {
167 // Lazy-load the actual backend instance
168 if ( !isset( $this->backends[$name]['instance'] ) ) {
169 $config = $this->config( $name );
170
171 $class = $config['class'];
172 if ( $class === FileBackendMultiWrite::class ) {
173 // @todo How can we test this? What's the intended use-case?
174 foreach ( $config['backends'] as $index => $beConfig ) {
175 if ( isset( $beConfig['template'] ) ) {
176 // Config is just a modified version of a registered backend's.
177 // This should only be used when that config is used only by this backend.
178 $config['backends'][$index] += $this->config( $beConfig['template'] );
179 }
180 }
181 }
182
183 $this->backends[$name]['instance'] = new $class( $config );
184 }
185
186 return $this->backends[$name]['instance'];
187 }
188
196 public function config( $name ) {
197 if ( !isset( $this->backends[$name] ) ) {
198 throw new InvalidArgumentException( "No backend defined with the name '$name'." );
199 }
200
201 $config = $this->backends[$name]['config'];
202 $services = MediaWikiServices::getInstance();
203
204 return array_merge(
205 // Default backend parameters
206 [
207 'mimeCallback' => [ $this, 'guessMimeInternal' ],
208 'obResetFunc' => 'wfResetOutputBuffers',
209 'streamMimeFunc' => [ StreamFile::class, 'contentTypeFromPath' ],
210 'tmpFileFactory' => $services->getTempFSFileFactory(),
211 'statusWrapper' => [ Status::class, 'wrap' ],
212 'wanCache' => $services->getMainWANObjectCache(),
213 'srvCache' => ObjectCache::getLocalServerInstance( 'hash' ),
214 'logger' => LoggerFactory::getInstance( 'FileOperation' ),
215 'profiler' => function ( $section ) {
216 return Profiler::instance()->scopedProfileIn( $section );
217 }
218 ],
219 // Configured backend parameters
220 $config,
221 // Resolved backend parameters
222 [
223 'class' => $this->backends[$name]['class'],
224 'lockManager' =>
225 LockManagerGroup::singleton( $config['domainId'] )
226 ->get( $config['lockManager'] ),
227 'fileJournal' => isset( $config['fileJournal'] )
228 ? FileJournal::factory( $config['fileJournal'], $name )
229 : FileJournal::factory( [ 'class' => NullFileJournal::class ], $name )
230 ]
231 );
232 }
233
240 public function backendFromPath( $storagePath ) {
241 list( $backend, , ) = FileBackend::splitStoragePath( $storagePath );
242 if ( $backend !== null && isset( $this->backends[$backend] ) ) {
243 return $this->get( $backend );
244 }
245
246 return null;
247 }
248
256 public function guessMimeInternal( $storagePath, $content, $fsPath ) {
257 $magic = MediaWiki\MediaWikiServices::getInstance()->getMimeAnalyzer();
258 // Trust the extension of the storage path (caller must validate)
259 $ext = FileBackend::extensionFromPath( $storagePath );
260 $type = $magic->guessTypesForExtension( $ext );
261 // For files without a valid extension (or one at all), inspect the contents
262 if ( !$type && $fsPath ) {
263 $type = $magic->guessMimeType( $fsPath, false );
264 } elseif ( !$type && strlen( $content ) ) {
265 $tmpFile = MediaWikiServices::getInstance()->getTempFSFileFactory()
266 ->newTempFSFile( 'mime_', '' );
267 file_put_contents( $tmpFile->getPath(), $content );
268 $type = $magic->guessMimeType( $tmpFile->getPath(), false );
269 }
270 return $type ?: 'unknown/unknown';
271 }
272}
$wgFileBackends
File backend structure configuration.
$wgDirectoryMode
Default value for chmoding of new directories.
$wgLocalFileRepo
File repository structures.
$wgForeignFileRepos
Enable the use of files from one or more other wikis.
wfConfiguredReadOnlyReason()
Get the value of $wgReadOnly or the contents of $wgReadOnlyFile.
wfWarn( $msg, $callerOffset=1, $level=E_USER_NOTICE)
Send a warning either to the debug log or in a PHP error depending on $wgDevelopmentWarnings.
Class to handle file backend registration.
static destroySingleton()
Destroy the singleton instance.
config( $name)
Get the config array for a backend object with a given name.
guessMimeInternal( $storagePath, $content, $fsPath)
initFromGlobals()
Register file backends from the global variables.
static FileBackendGroup $instance
array $backends
(name => ('class' => string, 'config' => array, 'instance' => object))
backendFromPath( $storagePath)
Get an appropriate backend object from a storage path.
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 factory(array $config, $backend)
Create an appropriate FileJournal object from config.
PSR-3 logger instance factory.
MediaWikiServices is the service locator for the application scope of MediaWiki.
$content
Definition router.php:78
if(!is_readable( $file)) $ext
Definition router.php:48