MediaWiki  REL1_20
FileBackend.php
Go to the documentation of this file.
00001 <?php
00059 abstract class FileBackend {
00060         protected $name; // string; unique backend name
00061         protected $wikiId; // string; unique wiki name
00062         protected $readOnly; // string; read-only explanation message
00063         protected $parallelize; // string; when to do operations in parallel
00064         protected $concurrency; // integer; how many operations can be done in parallel
00065 
00067         protected $lockManager;
00069         protected $fileJournal;
00070 
00093         public function __construct( array $config ) {
00094                 $this->name = $config['name'];
00095                 if ( !preg_match( '!^[a-zA-Z0-9-_]{1,255}$!', $this->name ) ) {
00096                         throw new MWException( "Backend name `{$this->name}` is invalid." );
00097                 }
00098                 $this->wikiId = isset( $config['wikiId'] )
00099                         ? $config['wikiId']
00100                         : wfWikiID(); // e.g. "my_wiki-en_"
00101                 $this->lockManager = ( $config['lockManager'] instanceof LockManager )
00102                         ? $config['lockManager']
00103                         : LockManagerGroup::singleton()->get( $config['lockManager'] );
00104                 $this->fileJournal = isset( $config['fileJournal'] )
00105                         ? ( ( $config['fileJournal'] instanceof FileJournal )
00106                                 ? $config['fileJournal']
00107                                 : FileJournal::factory( $config['fileJournal'], $this->name ) )
00108                         : FileJournal::factory( array( 'class' => 'NullFileJournal' ), $this->name );
00109                 $this->readOnly = isset( $config['readOnly'] )
00110                         ? (string)$config['readOnly']
00111                         : '';
00112                 $this->parallelize = isset( $config['parallelize'] )
00113                         ? (string)$config['parallelize']
00114                         : 'off';
00115                 $this->concurrency = isset( $config['concurrency'] )
00116                         ? (int)$config['concurrency']
00117                         : 50;
00118         }
00119 
00127         final public function getName() {
00128                 return $this->name;
00129         }
00130 
00137         final public function getWikiId() {
00138                 return $this->wikiId;
00139         }
00140 
00146         final public function isReadOnly() {
00147                 return ( $this->readOnly != '' );
00148         }
00149 
00155         final public function getReadOnlyReason() {
00156                 return ( $this->readOnly != '' ) ? $this->readOnly : false;
00157         }
00158 
00289         final public function doOperations( array $ops, array $opts = array() ) {
00290                 if ( empty( $opts['bypassReadOnly'] ) && $this->isReadOnly() ) {
00291                         return Status::newFatal( 'backend-fail-readonly', $this->name, $this->readOnly );
00292                 }
00293                 if ( empty( $opts['force'] ) ) { // sanity
00294                         unset( $opts['nonLocking'] );
00295                         unset( $opts['allowStale'] );
00296                 }
00297                 $opts['concurrency'] = 1; // off
00298                 if ( $this->parallelize === 'implicit' ) {
00299                         if ( !isset( $opts['parallelize'] ) || $opts['parallelize'] ) {
00300                                 $opts['concurrency'] = $this->concurrency;
00301                         }
00302                 } elseif ( $this->parallelize === 'explicit' ) {
00303                         if ( !empty( $opts['parallelize'] ) ) {
00304                                 $opts['concurrency'] = $this->concurrency;
00305                         }
00306                 }
00307                 return $this->doOperationsInternal( $ops, $opts );
00308         }
00309 
00313         abstract protected function doOperationsInternal( array $ops, array $opts );
00314 
00326         final public function doOperation( array $op, array $opts = array() ) {
00327                 return $this->doOperations( array( $op ), $opts );
00328         }
00329 
00340         final public function create( array $params, array $opts = array() ) {
00341                 return $this->doOperation( array( 'op' => 'create' ) + $params, $opts );
00342         }
00343 
00354         final public function store( array $params, array $opts = array() ) {
00355                 return $this->doOperation( array( 'op' => 'store' ) + $params, $opts );
00356         }
00357 
00368         final public function copy( array $params, array $opts = array() ) {
00369                 return $this->doOperation( array( 'op' => 'copy' ) + $params, $opts );
00370         }
00371 
00382         final public function move( array $params, array $opts = array() ) {
00383                 return $this->doOperation( array( 'op' => 'move' ) + $params, $opts );
00384         }
00385 
00396         final public function delete( array $params, array $opts = array() ) {
00397                 return $this->doOperation( array( 'op' => 'delete' ) + $params, $opts );
00398         }
00399 
00488         final public function doQuickOperations( array $ops, array $opts = array() ) {
00489                 if ( empty( $opts['bypassReadOnly'] ) && $this->isReadOnly() ) {
00490                         return Status::newFatal( 'backend-fail-readonly', $this->name, $this->readOnly );
00491                 }
00492                 foreach ( $ops as &$op ) {
00493                         $op['overwrite'] = true; // avoids RTTs in key/value stores
00494                 }
00495                 return $this->doQuickOperationsInternal( $ops );
00496         }
00497 
00502         abstract protected function doQuickOperationsInternal( array $ops );
00503 
00514         final public function doQuickOperation( array $op ) {
00515                 return $this->doQuickOperations( array( $op ) );
00516         }
00517 
00528         final public function quickCreate( array $params ) {
00529                 return $this->doQuickOperation( array( 'op' => 'create' ) + $params );
00530         }
00531 
00542         final public function quickStore( array $params ) {
00543                 return $this->doQuickOperation( array( 'op' => 'store' ) + $params );
00544         }
00545 
00556         final public function quickCopy( array $params ) {
00557                 return $this->doQuickOperation( array( 'op' => 'copy' ) + $params );
00558         }
00559 
00570         final public function quickMove( array $params ) {
00571                 return $this->doQuickOperation( array( 'op' => 'move' ) + $params );
00572         }
00573 
00584         final public function quickDelete( array $params ) {
00585                 return $this->doQuickOperation( array( 'op' => 'delete' ) + $params );
00586         }
00587 
00600         abstract public function concatenate( array $params );
00601 
00619         final public function prepare( array $params ) {
00620                 if ( empty( $params['bypassReadOnly'] ) && $this->isReadOnly() ) {
00621                         return Status::newFatal( 'backend-fail-readonly', $this->name, $this->readOnly );
00622                 }
00623                 return $this->doPrepare( $params );
00624         }
00625 
00629         abstract protected function doPrepare( array $params );
00630 
00646         final public function secure( array $params ) {
00647                 if ( empty( $params['bypassReadOnly'] ) && $this->isReadOnly() ) {
00648                         return Status::newFatal( 'backend-fail-readonly', $this->name, $this->readOnly );
00649                 }
00650                 return $this->doSecure( $params );
00651         }
00652 
00656         abstract protected function doSecure( array $params );
00657 
00674         final public function publish( array $params ) {
00675                 if ( empty( $params['bypassReadOnly'] ) && $this->isReadOnly() ) {
00676                         return Status::newFatal( 'backend-fail-readonly', $this->name, $this->readOnly );
00677                 }
00678                 return $this->doPublish( $params );
00679         }
00680 
00684         abstract protected function doPublish( array $params );
00685 
00698         final public function clean( array $params ) {
00699                 if ( empty( $params['bypassReadOnly'] ) && $this->isReadOnly() ) {
00700                         return Status::newFatal( 'backend-fail-readonly', $this->name, $this->readOnly );
00701                 }
00702                 return $this->doClean( $params );
00703         }
00704 
00708         abstract protected function doClean( array $params );
00709 
00720         abstract public function fileExists( array $params );
00721 
00731         abstract public function getFileTimestamp( array $params );
00732 
00743         abstract public function getFileContents( array $params );
00744 
00754         abstract public function getFileSize( array $params );
00755 
00770         abstract public function getFileStat( array $params );
00771 
00781         abstract public function getFileSha1Base36( array $params );
00782 
00793         abstract public function getFileProps( array $params );
00794 
00809         abstract public function streamFile( array $params );
00810 
00830         abstract public function getLocalReference( array $params );
00831 
00843         abstract public function getLocalCopy( array $params );
00844 
00858         abstract public function directoryExists( array $params );
00859 
00877         abstract public function getDirectoryList( array $params );
00878 
00891         final public function getTopDirectoryList( array $params ) {
00892                 return $this->getDirectoryList( array( 'topOnly' => true ) + $params );
00893         }
00894 
00911         abstract public function getFileList( array $params );
00912 
00925         final public function getTopFileList( array $params ) {
00926                 return $this->getFileList( array( 'topOnly' => true ) + $params );
00927         }
00928 
00936         public function preloadCache( array $paths ) {}
00937 
00945         public function clearCache( array $paths = null ) {}
00946 
00957         final public function lockFiles( array $paths, $type ) {
00958                 return $this->lockManager->lock( $paths, $type );
00959         }
00960 
00968         final public function unlockFiles( array $paths, $type ) {
00969                 return $this->lockManager->unlock( $paths, $type );
00970         }
00971 
00985         final public function getScopedFileLocks( array $paths, $type, Status $status ) {
00986                 return ScopedLock::factory( $this->lockManager, $paths, $type, $status );
00987         }
00988 
01005         abstract public function getScopedLocksForOps( array $ops, Status $status );
01006 
01014         final public function getRootStoragePath() {
01015                 return "mwstore://{$this->name}";
01016         }
01017 
01023         final public function getJournal() {
01024                 return $this->fileJournal;
01025         }
01026 
01034         final public static function isStoragePath( $path ) {
01035                 return ( strpos( $path, 'mwstore://' ) === 0 );
01036         }
01037 
01046         final public static function splitStoragePath( $storagePath ) {
01047                 if ( self::isStoragePath( $storagePath ) ) {
01048                         // Remove the "mwstore://" prefix and split the path
01049                         $parts = explode( '/', substr( $storagePath, 10 ), 3 );
01050                         if ( count( $parts ) >= 2 && $parts[0] != '' && $parts[1] != '' ) {
01051                                 if ( count( $parts ) == 3 ) {
01052                                         return $parts; // e.g. "backend/container/path"
01053                                 } else {
01054                                         return array( $parts[0], $parts[1], '' ); // e.g. "backend/container"
01055                                 }
01056                         }
01057                 }
01058                 return array( null, null, null );
01059         }
01060 
01068         final public static function normalizeStoragePath( $storagePath ) {
01069                 list( $backend, $container, $relPath ) = self::splitStoragePath( $storagePath );
01070                 if ( $relPath !== null ) { // must be for this backend
01071                         $relPath = self::normalizeContainerPath( $relPath );
01072                         if ( $relPath !== null ) {
01073                                 return ( $relPath != '' )
01074                                         ? "mwstore://{$backend}/{$container}/{$relPath}"
01075                                         : "mwstore://{$backend}/{$container}";
01076                         }
01077                 }
01078                 return null;
01079         }
01080 
01089         final public static function parentStoragePath( $storagePath ) {
01090                 $storagePath = dirname( $storagePath );
01091                 list( $b, $cont, $rel ) = self::splitStoragePath( $storagePath );
01092                 return ( $rel === null ) ? null : $storagePath;
01093         }
01094 
01101         final public static function extensionFromPath( $path ) {
01102                 $i = strrpos( $path, '.' );
01103                 return strtolower( $i ? substr( $path, $i + 1 ) : '' );
01104         }
01105 
01113         final public static function isPathTraversalFree( $path ) {
01114                 return ( self::normalizeContainerPath( $path ) !== null );
01115         }
01116 
01125         final public static function makeContentDisposition( $type, $filename = '' ) {
01126                 $parts = array();
01127 
01128                 $type = strtolower( $type );
01129                 if ( !in_array( $type, array( 'inline', 'attachment' ) ) ) {
01130                         throw new MWException( "Invalid Content-Disposition type '$type'." );
01131                 }
01132                 $parts[] = $type;
01133 
01134                 if ( strlen( $filename ) ) {
01135                         $parts[] = "filename*=UTF-8''" . rawurlencode( basename( $filename ) );
01136                 }
01137 
01138                 return implode( ';', $parts );
01139         }
01140 
01151         final protected static function normalizeContainerPath( $path ) {
01152                 // Normalize directory separators
01153                 $path = strtr( $path, '\\', '/' );
01154                 // Collapse any consecutive directory separators
01155                 $path = preg_replace( '![/]{2,}!', '/', $path );
01156                 // Remove any leading directory separator
01157                 $path = ltrim( $path, '/' );
01158                 // Use the same traversal protection as Title::secureAndSplit()
01159                 if ( strpos( $path, '.' ) !== false ) {
01160                         if (
01161                                 $path === '.' ||
01162                                 $path === '..' ||
01163                                 strpos( $path, './' ) === 0 ||
01164                                 strpos( $path, '../' ) === 0 ||
01165                                 strpos( $path, '/./' ) !== false ||
01166                                 strpos( $path, '/../' ) !== false
01167                         ) {
01168                                 return null;
01169                         }
01170                 }
01171                 return $path;
01172         }
01173 }