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