]> scripts.mit.edu Git - autoinstallsdev/mediawiki.git/blob - includes/filebackend/FileBackendGroup.php
MediaWiki 1.30.2
[autoinstallsdev/mediawiki.git] / includes / filebackend / FileBackendGroup.php
1 <?php
2 /**
3  * File backend registration handling.
4  *
5  * This program is free software; you can redistribute it and/or modify
6  * it under the terms of the GNU General Public License as published by
7  * the Free Software Foundation; either version 2 of the License, or
8  * (at your option) any later version.
9  *
10  * This program is distributed in the hope that it will be useful,
11  * but WITHOUT ANY WARRANTY; without even the implied warranty of
12  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13  * GNU General Public License for more details.
14  *
15  * You should have received a copy of the GNU General Public License along
16  * with this program; if not, write to the Free Software Foundation, Inc.,
17  * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
18  * http://www.gnu.org/copyleft/gpl.html
19  *
20  * @file
21  * @ingroup FileBackend
22  */
23 use \MediaWiki\Logger\LoggerFactory;
24 use MediaWiki\MediaWikiServices;
25
26 /**
27  * Class to handle file backend registration
28  *
29  * @ingroup FileBackend
30  * @since 1.19
31  */
32 class FileBackendGroup {
33         /** @var FileBackendGroup */
34         protected static $instance = null;
35
36         /** @var array (name => ('class' => string, 'config' => array, 'instance' => object)) */
37         protected $backends = [];
38
39         protected function __construct() {
40         }
41
42         /**
43          * @return FileBackendGroup
44          */
45         public static function singleton() {
46                 if ( self::$instance == null ) {
47                         self::$instance = new self();
48                         self::$instance->initFromGlobals();
49                 }
50
51                 return self::$instance;
52         }
53
54         /**
55          * Destroy the singleton instance
56          */
57         public static function destroySingleton() {
58                 self::$instance = null;
59         }
60
61         /**
62          * Register file backends from the global variables
63          */
64         protected function initFromGlobals() {
65                 global $wgLocalFileRepo, $wgForeignFileRepos, $wgFileBackends, $wgDirectoryMode;
66
67                 // Register explicitly defined backends
68                 $this->register( $wgFileBackends, wfConfiguredReadOnlyReason() );
69
70                 $autoBackends = [];
71                 // Automatically create b/c backends for file repos...
72                 $repos = array_merge( $wgForeignFileRepos, [ $wgLocalFileRepo ] );
73                 foreach ( $repos as $info ) {
74                         $backendName = $info['backend'];
75                         if ( is_object( $backendName ) || isset( $this->backends[$backendName] ) ) {
76                                 continue; // already defined (or set to the object for some reason)
77                         }
78                         $repoName = $info['name'];
79                         // Local vars that used to be FSRepo members...
80                         $directory = $info['directory'];
81                         $deletedDir = isset( $info['deletedDir'] )
82                                 ? $info['deletedDir']
83                                 : false; // deletion disabled
84                         $thumbDir = isset( $info['thumbDir'] )
85                                 ? $info['thumbDir']
86                                 : "{$directory}/thumb";
87                         $transcodedDir = isset( $info['transcodedDir'] )
88                                 ? $info['transcodedDir']
89                                 : "{$directory}/transcoded";
90                         // Get the FS backend configuration
91                         $autoBackends[] = [
92                                 'name' => $backendName,
93                                 'class' => 'FSFileBackend',
94                                 'lockManager' => 'fsLockManager',
95                                 'containerPaths' => [
96                                         "{$repoName}-public" => "{$directory}",
97                                         "{$repoName}-thumb" => $thumbDir,
98                                         "{$repoName}-transcoded" => $transcodedDir,
99                                         "{$repoName}-deleted" => $deletedDir,
100                                         "{$repoName}-temp" => "{$directory}/temp"
101                                 ],
102                                 'fileMode' => isset( $info['fileMode'] ) ? $info['fileMode'] : 0644,
103                                 'directoryMode' => $wgDirectoryMode,
104                         ];
105                 }
106
107                 // Register implicitly defined backends
108                 $this->register( $autoBackends, wfConfiguredReadOnlyReason() );
109         }
110
111         /**
112          * Register an array of file backend configurations
113          *
114          * @param array $configs
115          * @param string|null $readOnlyReason
116          * @throws InvalidArgumentException
117          */
118         protected function register( array $configs, $readOnlyReason = null ) {
119                 foreach ( $configs as $config ) {
120                         if ( !isset( $config['name'] ) ) {
121                                 throw new InvalidArgumentException( "Cannot register a backend with no name." );
122                         }
123                         $name = $config['name'];
124                         if ( isset( $this->backends[$name] ) ) {
125                                 throw new LogicException( "Backend with name `{$name}` already registered." );
126                         } elseif ( !isset( $config['class'] ) ) {
127                                 throw new InvalidArgumentException( "Backend with name `{$name}` has no class." );
128                         }
129                         $class = $config['class'];
130
131                         $config['readOnly'] = !empty( $config['readOnly'] )
132                                 ? $config['readOnly']
133                                 : $readOnlyReason;
134
135                         unset( $config['class'] ); // backend won't need this
136                         $this->backends[$name] = [
137                                 'class' => $class,
138                                 'config' => $config,
139                                 'instance' => null
140                         ];
141                 }
142         }
143
144         /**
145          * Get the backend object with a given name
146          *
147          * @param string $name
148          * @return FileBackend
149          * @throws InvalidArgumentException
150          */
151         public function get( $name ) {
152                 // Lazy-load the actual backend instance
153                 if ( !isset( $this->backends[$name]['instance'] ) ) {
154                         $config = $this->config( $name );
155
156                         $class = $config['class'];
157                         if ( $class === 'FileBackendMultiWrite' ) {
158                                 foreach ( $config['backends'] as $index => $beConfig ) {
159                                         if ( isset( $beConfig['template'] ) ) {
160                                                 // Config is just a modified version of a registered backend's.
161                                                 // This should only be used when that config is used only by this backend.
162                                                 $config['backends'][$index] += $this->config( $beConfig['template'] );
163                                         }
164                                 }
165                         }
166
167                         $this->backends[$name]['instance'] = new $class( $config );
168                 }
169
170                 return $this->backends[$name]['instance'];
171         }
172
173         /**
174          * Get the config array for a backend object with a given name
175          *
176          * @param string $name
177          * @return array Parameters to FileBackend::__construct()
178          * @throws InvalidArgumentException
179          */
180         public function config( $name ) {
181                 if ( !isset( $this->backends[$name] ) ) {
182                         throw new InvalidArgumentException( "No backend defined with the name `$name`." );
183                 }
184                 $class = $this->backends[$name]['class'];
185
186                 $config = $this->backends[$name]['config'];
187                 $config['class'] = $class;
188                 $config += [ // set defaults
189                         'wikiId' => wfWikiID(), // e.g. "my_wiki-en_"
190                         'mimeCallback' => [ $this, 'guessMimeInternal' ],
191                         'obResetFunc' => 'wfResetOutputBuffers',
192                         'streamMimeFunc' => [ 'StreamFile', 'contentTypeFromPath' ],
193                         'tmpDirectory' => wfTempDir(),
194                         'statusWrapper' => [ 'Status', 'wrap' ],
195                         'wanCache' => MediaWikiServices::getInstance()->getMainWANObjectCache(),
196                         'srvCache' => ObjectCache::getLocalServerInstance( 'hash' ),
197                         'logger' => LoggerFactory::getInstance( 'FileOperation' ),
198                         'profiler' => Profiler::instance()
199                 ];
200                 $config['lockManager'] =
201                         LockManagerGroup::singleton( $config['wikiId'] )->get( $config['lockManager'] );
202                 $config['fileJournal'] = isset( $config['fileJournal'] )
203                         ? FileJournal::factory( $config['fileJournal'], $name )
204                         : FileJournal::factory( [ 'class' => 'NullFileJournal' ], $name );
205
206                 return $config;
207         }
208
209         /**
210          * Get an appropriate backend object from a storage path
211          *
212          * @param string $storagePath
213          * @return FileBackend|null Backend or null on failure
214          */
215         public function backendFromPath( $storagePath ) {
216                 list( $backend, , ) = FileBackend::splitStoragePath( $storagePath );
217                 if ( $backend !== null && isset( $this->backends[$backend] ) ) {
218                         return $this->get( $backend );
219                 }
220
221                 return null;
222         }
223
224         /**
225          * @param string $storagePath
226          * @param string|null $content
227          * @param string|null $fsPath
228          * @return string
229          * @since 1.27
230          */
231         public function guessMimeInternal( $storagePath, $content, $fsPath ) {
232                 $magic = MimeMagic::singleton();
233                 // Trust the extension of the storage path (caller must validate)
234                 $ext = FileBackend::extensionFromPath( $storagePath );
235                 $type = $magic->guessTypesForExtension( $ext );
236                 // For files without a valid extension (or one at all), inspect the contents
237                 if ( !$type && $fsPath ) {
238                         $type = $magic->guessMimeType( $fsPath, false );
239                 } elseif ( !$type && strlen( $content ) ) {
240                         $tmpFile = TempFSFile::factory( 'mime_', '', wfTempDir() );
241                         file_put_contents( $tmpFile->getPath(), $content );
242                         $type = $magic->guessMimeType( $tmpFile->getPath(), false );
243                 }
244                 return $type ?: 'unknown/unknown';
245         }
246 }