37ae39995b39a93ee2c7dc6f5333492314b820c6
9 * Base class for all file backend classes (including multi-write backends).
10 * This class defines the methods as abstract that subclasses must implement.
11 * Outside callers can assume that all backends will have these functions.
13 * All "storage paths" are of the format "mwstore://backend/container/path".
14 * The paths use typical file system (FS) notation, though any particular backend may
15 * not actually be using a local filesystem. Therefore, the paths are only virtual.
17 * FS-based backends are somewhat more restrictive due to the existence of real
18 * directory files; a regular file cannot have the same name as a directory. Other
19 * backends with virtual directories may not have this limitation. Callers should
20 * store files in such a way that no files and directories under the same path.
22 * Methods should avoid throwing exceptions at all costs.
23 * As a corollary, external dependencies should be kept to a minimum.
25 * @ingroup FileBackend
28 abstract class FileBackendBase
{
29 protected $name; // unique backend name
30 protected $wikiId; // unique wiki name
31 /** @var LockManager */
32 protected $lockManager;
35 * Build a new object from configuration.
36 * This should only be called from within FileBackendGroup.
39 * 'name' : The name of this backend
40 * 'wikiId' : Prefix to container names that is unique to this wiki
41 * 'lockManager' : Registered name of the file lock manager to use
43 * @param $config Array
45 public function __construct( array $config ) {
46 $this->name
= $config['name'];
47 $this->wikiId
= isset( $config['wikiId'] )
50 $this->lockManager
= LockManagerGroup
::singleton()->get( $config['lockManager'] );
54 * Get the unique backend name.
56 * We may have multiple different backends of the same type.
57 * For example, we can have two Swift backends using different proxies.
61 final public function getName() {
66 * This is the main entry point into the backend for write operations.
67 * Callers supply an ordered list of operations to perform as a transaction.
68 * If any serious errors occur, all attempted operations will be rolled back.
70 * $ops is an array of arrays. The outer array holds a list of operations.
71 * Each inner array is a set of key value pairs that specify an operation.
73 * Supported operations and their parameters:
74 * a) Create a new file in storage with the contents of a string
77 * 'dst' => <storage path>,
78 * 'content' => <string of new file contents>,
79 * 'overwriteDest' => <boolean>,
80 * 'overwriteSame' => <boolean>
82 * b) Copy a file system file into storage
85 * 'src' => <file system path>,
86 * 'dst' => <storage path>,
87 * 'overwriteDest' => <boolean>,
88 * 'overwriteSame' => <boolean>
90 * c) Copy a file within storage
93 * 'src' => <storage path>,
94 * 'dst' => <storage path>,
95 * 'overwriteDest' => <boolean>,
96 * 'overwriteSame' => <boolean>
98 * d) Move a file within storage
101 * 'src' => <storage path>,
102 * 'dst' => <storage path>,
103 * 'overwriteDest' => <boolean>,
104 * 'overwriteSame' => <boolean>
106 * e) Delete a file within storage
109 * 'src' => <storage path>,
110 * 'ignoreMissingSource' => <boolean>
112 * f) Concatenate a list of files within storage into a single temp file
114 * 'op' => 'concatenate',
115 * 'srcs' => <ordered array of storage paths>,
116 * 'dst' => <file system path to 0-byte temp file>
118 * g) Do nothing (no-op)
123 * Boolean flags for operations (operation-specific):
124 * 'ignoreMissingSource' : The operation will simply succeed and do
125 * nothing if the source file does not exist.
126 * 'overwriteDest' : Any destination file will be overwritten.
127 * 'overwriteSame' : An error will not be given if a file already
128 * exists at the destination that has the same
129 * contents as the new contents to be written there.
131 * $opts is an associative of boolean flags, including:
132 * 'ignoreErrors' : Errors that would normally cause a rollback do not.
133 * The remaining operations are still attempted if any fail.
134 * 'nonLocking' : No locks are acquired for the operations.
135 * This can increase performance for non-critical writes.
136 * This has no effect unless the 'ignoreErrors' flag is set.
137 * 'allowStale' : Don't require the latest available data.
138 * This can increase performance for non-critical writes.
139 * This has no effect unless the 'ignoreErrors' flag is set.
142 * This returns a Status, which contains all warnings and fatals that occured
143 * during the operation. The 'failCount', 'successCount', and 'success' members
144 * will reflect each operation attempted. The status will be "OK" unless any
145 * of the operations failed and the 'ignoreErrors' parameter was not set.
147 * @param $ops Array List of operations to execute in order
148 * @param $opts Array Batch operation options
151 final public function doOperations( array $ops, array $opts = array() ) {
152 if ( empty( $opts['ignoreErrors'] ) ) { // sanity
153 unset( $opts['nonLocking'] );
154 unset( $opts['allowStale'] );
156 return $this->doOperationsInternal( $ops, $opts );
160 * @see FileBackendBase::doOperations()
162 abstract protected function doOperationsInternal( array $ops, array $opts );
165 * Same as doOperations() except it takes a single operation.
166 * If you are doing a batch of operations that should either
167 * all succeed or all fail, then use that function instead.
169 * @see FileBackendBase::doOperations()
171 * @param $op Array Operation
172 * @param $opts Array Operation options
175 final public function doOperation( array $op, array $opts = array() ) {
176 return $this->doOperations( array( $op ), $opts );
180 * Performs a single store operation.
181 * This sets $params['op'] to 'store' and passes it to doOperation().
183 * @see FileBackendBase::doOperation()
185 * @param $params Array Operation parameters
186 * @param $opts Array Operation options
189 final public function store( array $params, array $opts = array() ) {
190 $params['op'] = 'store';
191 return $this->doOperation( $params, $opts );
195 * Performs a single copy operation.
196 * This sets $params['op'] to 'copy' and passes it to doOperation().
198 * @see FileBackendBase::doOperation()
200 * @param $params Array Operation parameters
201 * @param $opts Array Operation options
204 final public function copy( array $params, array $opts = array() ) {
205 $params['op'] = 'copy';
206 return $this->doOperation( $params, $opts );
210 * Performs a single move operation.
211 * This sets $params['op'] to 'move' and passes it to doOperation().
213 * @see FileBackendBase::doOperation()
215 * @param $params Array Operation parameters
216 * @param $opts Array Operation options
219 final public function move( array $params, array $opts = array() ) {
220 $params['op'] = 'move';
221 return $this->doOperation( $params, $opts );
225 * Performs a single delete operation.
226 * This sets $params['op'] to 'delete' and passes it to doOperation().
228 * @see FileBackendBase::doOperation()
230 * @param $params Array Operation parameters
231 * @param $opts Array Operation options
234 final public function delete( array $params, array $opts = array() ) {
235 $params['op'] = 'delete';
236 return $this->doOperation( $params, $opts );
240 * Performs a single create operation.
241 * This sets $params['op'] to 'create' and passes it to doOperation().
243 * @see FileBackendBase::doOperation()
245 * @param $params Array Operation parameters
246 * @param $opts Array Operation options
249 final public function create( array $params, array $opts = array() ) {
250 $params['op'] = 'create';
251 return $this->doOperation( $params, $opts );
255 * Performs a single concatenate operation.
256 * This sets $params['op'] to 'concatenate' and passes it to doOperation().
258 * @see FileBackendBase::doOperation()
260 * @param $params Array Operation parameters
261 * @param $opts Array Operation options
264 final public function concatenate( array $params, array $opts = array() ) {
265 $params['op'] = 'concatenate';
266 return $this->doOperation( $params, $opts );
270 * Prepare a storage path for usage. This will create containers
271 * that don't yet exist or, on FS backends, create parent directories.
274 * dir : storage directory
276 * @param $params Array
279 abstract public function prepare( array $params );
282 * Take measures to block web access to a directory and
283 * the container it belongs to. FS backends might add .htaccess
284 * files wheras backends like Swift this might restrict container
285 * access to backend user that represents end-users in web request.
286 * This is not guaranteed to actually do anything.
289 * dir : storage directory
290 * noAccess : try to deny file access
291 * noListing : try to deny file listing
293 * @param $params Array
296 abstract public function secure( array $params );
299 * Clean up an empty storage directory.
300 * On FS backends, the directory will be deleted. Others may do nothing.
303 * dir : storage directory
305 * @param $params Array
308 abstract public function clean( array $params );
311 * Check if a file exists at a storage path in the backend.
314 * src : source storage path
315 * latest : use the latest available data
317 * @param $params Array
318 * @return bool|null Returns null on failure
320 abstract public function fileExists( array $params );
323 * Get a SHA-1 hash of the file at a storage path in the backend.
326 * src : source storage path
327 * latest : use the latest available data
329 * @param $params Array
330 * @return string|false Hash string or false on failure
332 abstract public function getFileSha1Base36( array $params );
335 * Get the last-modified timestamp of the file at a storage path.
338 * src : source storage path
339 * latest : use the latest available data
341 * @param $params Array
342 * @return string|false TS_MW timestamp or false on failure
344 abstract public function getFileTimestamp( array $params );
347 * Get the properties of the file at a storage path in the backend.
348 * Returns FSFile::placeholderProps() on failure.
351 * src : source storage path
352 * latest : use the latest available data
354 * @param $params Array
357 abstract public function getFileProps( array $params );
360 * Stream the file at a storage path in the backend.
361 * Appropriate HTTP headers (Status, Content-Type, Content-Length)
362 * must be sent if streaming began, while none should be sent otherwise.
363 * Implementations should flush the output buffer before sending data.
366 * src : source storage path
367 * headers : additional HTTP headers to send on success
368 * latest : use the latest available data
370 * @param $params Array
373 abstract public function streamFile( array $params );
376 * Returns a file system file, identical to the file at a storage path.
377 * The file returned is either:
378 * a) A local copy of the file at a storage path in the backend.
379 * The temporary copy will have the same extension as the source.
380 * b) An original of the file at a storage path in the backend.
381 * Temporary files may be purged when the file object falls out of scope.
383 * Write operations should *never* be done on this file as some backends
384 * may do internal tracking or may be instances of FileBackendMultiWrite.
385 * In that later case, there are copies of the file that must stay in sync.
388 * src : source storage path
389 * latest : use the latest available data
391 * @param $params Array
392 * @return FSFile|null Returns null on failure
394 abstract public function getLocalReference( array $params );
397 * Get a local copy on disk of the file at a storage path in the backend.
398 * The temporary copy will have the same file extension as the source.
399 * Temporary files may be purged when the file object falls out of scope.
402 * src : source storage path
403 * latest : use the latest available data
405 * @param $params Array
406 * @return TempFSFile|null Returns null on failure
408 abstract public function getLocalCopy( array $params );
411 * Get an iterator to list out all object files under a storage directory.
412 * If the directory is of the form "mwstore://container", then all items in
413 * the container should be listed. If of the form "mwstore://container/dir",
414 * then all items under that container directory should be listed.
415 * Results should be storage paths relative to the given directory.
418 * dir : storage path directory
420 * @return Traversable|Array|null Returns null on failure
422 abstract public function getFileList( array $params );
425 * Lock the files at the given storage paths in the backend.
426 * This will either lock all the files or none (on failure).
428 * Callers should consider using getScopedFileLocks() instead.
430 * @param $paths Array Storage paths
431 * @param $type integer LockManager::LOCK_* constant
434 final public function lockFiles( array $paths, $type ) {
435 return $this->lockManager
->lock( $paths, $type );
439 * Unlock the files at the given storage paths in the backend.
441 * @param $paths Array Storage paths
442 * @param $type integer LockManager::LOCK_* constant
445 final public function unlockFiles( array $paths, $type ) {
446 return $this->lockManager
->unlock( $paths, $type );
450 * Lock the files at the given storage paths in the backend.
451 * This will either lock all the files or none (on failure).
452 * On failure, the status object will be updated with errors.
454 * Once the return value goes out scope, the locks will be released and
455 * the status updated. Unlock fatals will not change the status "OK" value.
457 * @param $paths Array Storage paths
458 * @param $type integer LockManager::LOCK_* constant
459 * @param $status Status Status to update on lock/unlock
460 * @return ScopedLock|null Returns null on failure
462 final public function getScopedFileLocks( array $paths, $type, Status
$status ) {
463 return ScopedLock
::factory( $this->lockManager
, $paths, $type, $status );
468 * Base class for all single-write backends.
469 * This class defines the methods as abstract that subclasses must implement.
471 * @ingroup FileBackend
474 abstract class FileBackend
extends FileBackendBase
{
476 protected $cache = array(); // (storage path => key => value)
477 protected $maxCacheSize = 50; // integer; max paths with entries
480 * Create a file in the backend with the given contents.
481 * Do not call this function from places outside FileBackend and FileOp.
483 * content : the raw file contents
484 * dst : destination storage path
485 * overwriteDest : overwrite any file that exists at the destination
487 * @param $params Array
490 final public function createInternal( array $params ) {
491 $status = $this->doCreateInternal( $params );
492 $this->clearCache( array( $params['dst'] ) );
497 * @see FileBackend::createInternal()
499 abstract protected function doCreateInternal( array $params );
502 * Store a file into the backend from a file on disk.
503 * Do not call this function from places outside FileBackend and FileOp.
505 * src : source path on disk
506 * dst : destination storage path
507 * overwriteDest : overwrite any file that exists at the destination
509 * @param $params Array
512 final public function storeInternal( array $params ) {
513 $status = $this->doStoreInternal( $params );
514 $this->clearCache( array( $params['dst'] ) );
519 * @see FileBackend::storeInternal()
521 abstract protected function doStoreInternal( array $params );
524 * Copy a file from one storage path to another in the backend.
525 * Do not call this function from places outside FileBackend and FileOp.
527 * src : source storage path
528 * dst : destination storage path
529 * overwriteDest : overwrite any file that exists at the destination
531 * @param $params Array
534 final public function copyInternal( array $params ) {
535 $status = $this->doCopyInternal( $params );
536 $this->clearCache( array( $params['dst'] ) );
541 * @see FileBackend::copyInternal()
543 abstract protected function doCopyInternal( array $params );
546 * Delete a file at the storage path.
547 * Do not call this function from places outside FileBackend and FileOp.
549 * src : source storage path
550 * ignoreMissingSource : do nothing if the source file does not exist
552 * @param $params Array
555 final public function deleteInternal( array $params ) {
556 $status = $this->doDeleteInternal( $params );
557 $this->clearCache( array( $params['src'] ) );
562 * @see FileBackend::deleteInternal()
564 abstract protected function doDeleteInternal( array $params );
567 * Move a file from one storage path to another in the backend.
568 * Do not call this function from places outside FileBackend and FileOp.
570 * src : source storage path
571 * dst : destination storage path
572 * overwriteDest : overwrite any file that exists at the destination
574 * @param $params Array
577 final public function moveInternal( array $params ) {
578 $status = $this->doMoveInternal( $params );
579 $this->clearCache( array( $params['src'], $params['dst'] ) );
584 * @see FileBackend::moveInternal()
586 protected function doMoveInternal( array $params ) {
587 // Copy source to dest
588 $status = $this->copy( $params );
589 if ( !$status->isOK() ) {
592 // Delete source (only fails due to races or medium going down)
593 $status->merge( $this->delete( array( 'src' => $params['src'] ) ) );
594 $status->setResult( true, $status->value
); // ignore delete() errors
599 * Combines files from several storage paths into a new file in the backend.
600 * Do not call this function from places outside FileBackend and FileOp.
602 * srcs : ordered source storage paths (e.g. chunk1, chunk2, ...)
603 * dst : destination storage path
604 * overwriteDest : overwrite any file that exists at the destination
606 * @param $params Array
609 final public function concatenateInternal( array $params ) {
610 $status = $this->doConcatenateInternal( $params );
611 $this->clearCache( array( $params['dst'] ) );
616 * @see FileBackend::concatenateInternal()
618 protected function doConcatenateInternal( array $params ) {
619 $status = Status
::newGood();
620 $tmpPath = $params['dst']; // convenience
622 // Check that the specified temp file is valid...
623 wfSuppressWarnings();
624 $ok = ( is_file( $tmpPath ) && !filesize( $tmpPath ) );
626 if ( !$ok ) { // not present or not empty
627 $status->fatal( 'backend-fail-opentemp', $tmpPath );
631 // Build up the temp file using the source chunks (in order)...
632 $tmpHandle = fopen( $tmpPath, 'a' );
633 if ( $tmpHandle === false ) {
634 $status->fatal( 'backend-fail-opentemp', $tmpPath );
637 foreach ( $params['srcs'] as $virtualSource ) {
638 // Get a local FS version of the chunk
639 $tmpFile = $this->getLocalReference( array( 'src' => $virtualSource ) );
641 $status->fatal( 'backend-fail-read', $virtualSource );
644 // Get a handle to the local FS version
645 $sourceHandle = fopen( $tmpFile->getPath(), 'r' );
646 if ( $sourceHandle === false ) {
647 fclose( $tmpHandle );
648 $status->fatal( 'backend-fail-read', $virtualSource );
651 // Append chunk to file (pass chunk size to avoid magic quotes)
652 if ( !stream_copy_to_stream( $sourceHandle, $tmpHandle ) ) {
653 fclose( $sourceHandle );
654 fclose( $tmpHandle );
655 $status->fatal( 'backend-fail-writetemp', $tmpPath );
658 fclose( $sourceHandle );
660 if ( !fclose( $tmpHandle ) ) {
661 $status->fatal( 'backend-fail-closetemp', $tmpPath );
669 * @see FileBackendBase::prepare()
671 public function prepare( array $params ) {
672 return Status
::newGood();
676 * @see FileBackendBase::secure()
678 public function secure( array $params ) {
679 return Status
::newGood();
683 * @see FileBackendBase::clean()
685 public function clean( array $params ) {
686 return Status
::newGood();
690 * @see FileBackendBase::getFileSha1Base36()
692 public function getFileSha1Base36( array $params ) {
693 $path = $params['src'];
694 if ( isset( $this->cache
[$path]['sha1'] ) ) {
695 return $this->cache
[$path]['sha1'];
697 $fsFile = $this->getLocalReference( $params );
701 $sha1 = $fsFile->getSha1Base36();
702 if ( $sha1 !== false ) { // don't cache negatives
703 $this->trimCache(); // limit memory
704 $this->cache
[$path]['sha1'] = $sha1;
711 * @see FileBackendBase::getFileProps()
713 public function getFileProps( array $params ) {
714 $fsFile = $this->getLocalReference( $params );
716 return FSFile
::placeholderProps();
718 return $fsFile->getProps();
723 * @see FileBackendBase::getLocalReference()
725 public function getLocalReference( array $params ) {
726 return $this->getLocalCopy( $params );
730 * @see FileBackendBase::streamFile()
732 public function streamFile( array $params ) {
733 $status = Status
::newGood();
735 $fsFile = $this->getLocalReference( $params );
737 $status->fatal( 'backend-fail-stream', $params['src'] );
741 $extraHeaders = isset( $params['headers'] )
745 $ok = StreamFile
::stream( $fsFile->getPath(), $extraHeaders, false );
747 $status->fatal( 'backend-fail-stream', $params['src'] );
755 * Get the list of supported operations and their corresponding FileOp classes.
759 protected function supportedOperations() {
761 'store' => 'StoreFileOp',
762 'copy' => 'CopyFileOp',
763 'move' => 'MoveFileOp',
764 'delete' => 'DeleteFileOp',
765 'concatenate' => 'ConcatenateFileOp',
766 'create' => 'CreateFileOp',
767 'null' => 'NullFileOp'
772 * Return a list of FileOp objects from a list of operations.
773 * Do not call this function from places outside FileBackend.
775 * The result must have the same number of items as the input.
776 * An exception is thrown if an unsupported operation is requested.
778 * @param $ops Array Same format as doOperations()
779 * @return Array List of FileOp objects
780 * @throws MWException
782 final public function getOperations( array $ops ) {
783 $supportedOps = $this->supportedOperations();
785 $performOps = array(); // array of FileOp objects
786 // Build up ordered array of FileOps...
787 foreach ( $ops as $operation ) {
788 $opName = $operation['op'];
789 if ( isset( $supportedOps[$opName] ) ) {
790 $class = $supportedOps[$opName];
791 // Get params for this operation
792 $params = $operation;
793 // Append the FileOp class
794 $performOps[] = new $class( $this, $params );
796 throw new MWException( "Operation `$opName` is not supported." );
804 * @see FileBackendBase::doOperationsInternal()
806 protected function doOperationsInternal( array $ops, array $opts ) {
807 $status = Status
::newGood();
809 // Build up a list of FileOps...
810 $performOps = $this->getOperations( $ops );
812 // Acquire any locks as needed...
813 if ( empty( $opts['nonLocking'] ) ) {
814 // Build up a list of files to lock...
815 $filesLockEx = $filesLockSh = array();
816 foreach ( $performOps as $fileOp ) {
817 $filesLockSh = array_merge( $filesLockSh, $fileOp->storagePathsRead() );
818 $filesLockEx = array_merge( $filesLockEx, $fileOp->storagePathsChanged() );
820 // Optimization: if doing an EX lock anyway, don't also set an SH one
821 $filesLockSh = array_diff( $filesLockSh, $filesLockEx );
822 // Try to lock those files for the scope of this function...
823 $scopeLockS = $this->getScopedFileLocks( $filesLockSh, LockManager
::LOCK_UW
, $status );
824 $scopeLockE = $this->getScopedFileLocks( $filesLockEx, LockManager
::LOCK_EX
, $status );
825 if ( !$status->isOK() ) {
826 return $status; // abort
830 // Clear any cache entries (after locks acquired)
832 // Actually attempt the operation batch...
833 $status->merge( FileOp
::attemptBatch( $performOps, $opts ) );
839 * Invalidate the file existence and property cache
841 * @param $paths Array Clear cache for specific files
844 final public function clearCache( array $paths = null ) {
845 if ( $paths === null ) {
846 $this->cache
= array();
848 foreach ( $paths as $path ) {
849 unset( $this->cache
[$path] );
855 * Prune the cache if it is too big to add an item
859 protected function trimCache() {
860 if ( count( $this->cache
) >= $this->maxCacheSize
) {
861 reset( $this->cache
);
862 $key = key( $this->cache
);
863 unset( $this->cache
[$key] );
868 * Check if a given path is a mwstore:// path.
869 * This does not do any actual validation or existence checks.
871 * @param $path string
874 final public static function isStoragePath( $path ) {
875 return ( strpos( $path, 'mwstore://' ) === 0 );
879 * Split a storage path (e.g. "mwstore://backend/container/path/to/object")
880 * into a backend name, a container name, and a relative object path.
882 * @param $storagePath string
883 * @return Array (backend, container, rel object) or (null, null, null)
885 final public static function splitStoragePath( $storagePath ) {
886 if ( self
::isStoragePath( $storagePath ) ) {
887 // Note: strlen( 'mwstore://' ) = 10
888 $parts = explode( '/', substr( $storagePath, 10 ), 3 );
889 if ( count( $parts ) == 3 ) {
890 return $parts; // e.g. "backend/container/path"
891 } elseif ( count( $parts ) == 2 ) {
892 return array( $parts[0], $parts[1], '' ); // e.g. "backend/container"
895 return array( null, null, null );
899 * Check if a container name is valid.
900 * This checks for for length and illegal characters.
902 * @param $container string
905 final protected static function isValidContainerName( $container ) {
906 // This accounts for Swift and S3 restrictions. Also note
907 // that these urlencode to the same string, which is useful
908 // since the Swift size limit is *after* URL encoding.
909 return preg_match( '/^[a-zA-Z0-9._-]{1,256}$/u', $container );
913 * Validate and normalize a relative storage path.
914 * Null is returned if the path involves directory traversal.
915 * Traversal is insecure for FS backends and broken for others.
917 * @param $path string
918 * @return string|null
920 final protected static function normalizeStoragePath( $path ) {
921 // Normalize directory separators
922 $path = strtr( $path, '\\', '/' );
923 // Use the same traversal protection as Title::secureAndSplit()
924 if ( strpos( $path, '.' ) !== false ) {
928 strpos( $path, './' ) === 0 ||
929 strpos( $path, '../' ) === 0 ||
930 strpos( $path, '/./' ) !== false ||
931 strpos( $path, '/../' ) !== false
940 * Split a storage path (e.g. "mwstore://backend/container/path/to/object")
941 * into an internal container name and an internal relative object name.
942 * This also checks that the storage path is valid and is within this backend.
944 * @param $storagePath string
945 * @return Array (container, object name) or (null, null) if path is invalid
947 final protected function resolveStoragePath( $storagePath ) {
948 list( $backend, $container, $relPath ) = self
::splitStoragePath( $storagePath );
949 if ( $backend === $this->name
) { // must be for this backend
950 $relPath = self
::normalizeStoragePath( $relPath );
951 if ( $relPath !== null ) {
952 $relPath = $this->resolveContainerPath( $container, $relPath );
953 if ( $relPath !== null ) {
954 $container = $this->fullContainerName( $container );
955 if ( self
::isValidContainerName( $container ) ) {
956 $container = $this->resolveContainerName( $container );
957 if ( $container !== null ) {
958 return array( $container, $relPath );
964 return array( null, null );
968 * Get the full container name, including the wiki ID prefix
970 * @param $container string
973 final protected function fullContainerName( $container ) {
974 if ( $this->wikiId
!= '' ) {
975 return "{$this->wikiId}-$container";
982 * Resolve a container name, checking if it's allowed by the backend.
983 * This is intended for internal use, such as encoding illegal chars.
984 * Subclasses can override this to be more restrictive.
986 * @param $container string
987 * @return string|null
989 protected function resolveContainerName( $container ) {
994 * Resolve a relative storage path, checking if it's allowed by the backend.
995 * This is intended for internal use, such as encoding illegal chars or perhaps
996 * getting absolute paths (e.g. FS based backends). Note that the relative path
997 * may be the empty string (e.g. the path is simply to the container).
999 * @param $container string Container the path is relative to
1000 * @param $relStoragePath string Relative storage path
1001 * @return string|null Path or null if not valid
1003 protected function resolveContainerPath( $container, $relStoragePath ) {
1004 return $relStoragePath;
1008 * Get the final extension from a storage or FS path
1010 * @param $path string
1013 final public static function extensionFromPath( $path ) {
1014 $i = strrpos( $path, '.' );
1015 return strtolower( $i ?
substr( $path, $i +
1 ) : '' );