MediaWiki  REL1_21
FileBackend.php
Go to the documentation of this file.
00001 <?php
00063 abstract class FileBackend {
00064         protected $name; // string; unique backend name
00065         protected $wikiId; // string; unique wiki name
00066         protected $readOnly; // string; read-only explanation message
00067         protected $parallelize; // string; when to do operations in parallel
00068         protected $concurrency; // integer; how many operations can be done in parallel
00069 
00071         protected $lockManager;
00073         protected $fileJournal;
00074 
00101         public function __construct( array $config ) {
00102                 $this->name = $config['name'];
00103                 if ( !preg_match( '!^[a-zA-Z0-9-_]{1,255}$!', $this->name ) ) {
00104                         throw new MWException( "Backend name `{$this->name}` is invalid." );
00105                 }
00106                 $this->wikiId = isset( $config['wikiId'] )
00107                         ? $config['wikiId']
00108                         : wfWikiID(); // e.g. "my_wiki-en_"
00109                 $this->lockManager = ( $config['lockManager'] instanceof LockManager )
00110                         ? $config['lockManager']
00111                         : LockManagerGroup::singleton( $this->wikiId )->get( $config['lockManager'] );
00112                 $this->fileJournal = isset( $config['fileJournal'] )
00113                         ? ( ( $config['fileJournal'] instanceof FileJournal )
00114                                 ? $config['fileJournal']
00115                                 : FileJournal::factory( $config['fileJournal'], $this->name ) )
00116                         : FileJournal::factory( array( 'class' => 'NullFileJournal' ), $this->name );
00117                 $this->readOnly = isset( $config['readOnly'] )
00118                         ? (string)$config['readOnly']
00119                         : '';
00120                 $this->parallelize = isset( $config['parallelize'] )
00121                         ? (string)$config['parallelize']
00122                         : 'off';
00123                 $this->concurrency = isset( $config['concurrency'] )
00124                         ? (int)$config['concurrency']
00125                         : 50;
00126         }
00127 
00135         final public function getName() {
00136                 return $this->name;
00137         }
00138 
00146         final public function getWikiId() {
00147                 return $this->wikiId;
00148         }
00149 
00155         final public function isReadOnly() {
00156                 return ( $this->readOnly != '' );
00157         }
00158 
00164         final public function getReadOnlyReason() {
00165                 return ( $this->readOnly != '' ) ? $this->readOnly : false;
00166         }
00167 
00317         final public function doOperations( array $ops, array $opts = array() ) {
00318                 if ( empty( $opts['bypassReadOnly'] ) && $this->isReadOnly() ) {
00319                         return Status::newFatal( 'backend-fail-readonly', $this->name, $this->readOnly );
00320                 }
00321                 if ( empty( $opts['force'] ) ) { // sanity
00322                         unset( $opts['nonLocking'] );
00323                 }
00324                 $scope = $this->getScopedPHPBehaviorForOps(); // try to ignore client aborts
00325                 return $this->doOperationsInternal( $ops, $opts );
00326         }
00327 
00331         abstract protected function doOperationsInternal( array $ops, array $opts );
00332 
00344         final public function doOperation( array $op, array $opts = array() ) {
00345                 return $this->doOperations( array( $op ), $opts );
00346         }
00347 
00358         final public function create( array $params, array $opts = array() ) {
00359                 return $this->doOperation( array( 'op' => 'create' ) + $params, $opts );
00360         }
00361 
00372         final public function store( array $params, array $opts = array() ) {
00373                 return $this->doOperation( array( 'op' => 'store' ) + $params, $opts );
00374         }
00375 
00386         final public function copy( array $params, array $opts = array() ) {
00387                 return $this->doOperation( array( 'op' => 'copy' ) + $params, $opts );
00388         }
00389 
00400         final public function move( array $params, array $opts = array() ) {
00401                 return $this->doOperation( array( 'op' => 'move' ) + $params, $opts );
00402         }
00403 
00414         final public function delete( array $params, array $opts = array() ) {
00415                 return $this->doOperation( array( 'op' => 'delete' ) + $params, $opts );
00416         }
00417 
00429         final public function describe( array $params, array $opts = array() ) {
00430                 return $this->doOperation( array( 'op' => 'describe' ) + $params, $opts );
00431         }
00432 
00548         final public function doQuickOperations( array $ops, array $opts = array() ) {
00549                 if ( empty( $opts['bypassReadOnly'] ) && $this->isReadOnly() ) {
00550                         return Status::newFatal( 'backend-fail-readonly', $this->name, $this->readOnly );
00551                 }
00552                 foreach ( $ops as &$op ) {
00553                         $op['overwrite'] = true; // avoids RTTs in key/value stores
00554                 }
00555                 $scope = $this->getScopedPHPBehaviorForOps(); // try to ignore client aborts
00556                 return $this->doQuickOperationsInternal( $ops );
00557         }
00558 
00563         abstract protected function doQuickOperationsInternal( array $ops );
00564 
00575         final public function doQuickOperation( array $op ) {
00576                 return $this->doQuickOperations( array( $op ) );
00577         }
00578 
00589         final public function quickCreate( array $params ) {
00590                 return $this->doQuickOperation( array( 'op' => 'create' ) + $params );
00591         }
00592 
00603         final public function quickStore( array $params ) {
00604                 return $this->doQuickOperation( array( 'op' => 'store' ) + $params );
00605         }
00606 
00617         final public function quickCopy( array $params ) {
00618                 return $this->doQuickOperation( array( 'op' => 'copy' ) + $params );
00619         }
00620 
00631         final public function quickMove( array $params ) {
00632                 return $this->doQuickOperation( array( 'op' => 'move' ) + $params );
00633         }
00634 
00645         final public function quickDelete( array $params ) {
00646                 return $this->doQuickOperation( array( 'op' => 'delete' ) + $params );
00647         }
00648 
00659         final public function quickDescribe( array $params ) {
00660                 return $this->doQuickOperation( array( 'op' => 'describe' ) + $params );
00661         }
00662 
00676         abstract public function concatenate( array $params );
00677 
00695         final public function prepare( array $params ) {
00696                 if ( empty( $params['bypassReadOnly'] ) && $this->isReadOnly() ) {
00697                         return Status::newFatal( 'backend-fail-readonly', $this->name, $this->readOnly );
00698                 }
00699                 $scope = $this->getScopedPHPBehaviorForOps(); // try to ignore client aborts
00700                 return $this->doPrepare( $params );
00701         }
00702 
00706         abstract protected function doPrepare( array $params );
00707 
00723         final public function secure( array $params ) {
00724                 if ( empty( $params['bypassReadOnly'] ) && $this->isReadOnly() ) {
00725                         return Status::newFatal( 'backend-fail-readonly', $this->name, $this->readOnly );
00726                 }
00727                 $scope = $this->getScopedPHPBehaviorForOps(); // try to ignore client aborts
00728                 return $this->doSecure( $params );
00729         }
00730 
00734         abstract protected function doSecure( array $params );
00735 
00752         final public function publish( array $params ) {
00753                 if ( empty( $params['bypassReadOnly'] ) && $this->isReadOnly() ) {
00754                         return Status::newFatal( 'backend-fail-readonly', $this->name, $this->readOnly );
00755                 }
00756                 $scope = $this->getScopedPHPBehaviorForOps(); // try to ignore client aborts
00757                 return $this->doPublish( $params );
00758         }
00759 
00763         abstract protected function doPublish( array $params );
00764 
00777         final public function clean( array $params ) {
00778                 if ( empty( $params['bypassReadOnly'] ) && $this->isReadOnly() ) {
00779                         return Status::newFatal( 'backend-fail-readonly', $this->name, $this->readOnly );
00780                 }
00781                 $scope = $this->getScopedPHPBehaviorForOps(); // try to ignore client aborts
00782                 return $this->doClean( $params );
00783         }
00784 
00788         abstract protected function doClean( array $params );
00789 
00797         final protected function getScopedPHPBehaviorForOps() {
00798                 if ( php_sapi_name() != 'cli' ) { // http://bugs.php.net/bug.php?id=47540
00799                         $old = ignore_user_abort( true ); // avoid half-finished operations
00800                         return new ScopedCallback( function() use ( $old ) { ignore_user_abort( $old ); } );
00801                 }
00802                 return null;
00803         }
00804 
00815         abstract public function fileExists( array $params );
00816 
00826         abstract public function getFileTimestamp( array $params );
00827 
00838         final public function getFileContents( array $params ) {
00839                 $contents = $this->getFileContentsMulti(
00840                         array( 'srcs' => array( $params['src'] ) ) + $params );
00841 
00842                 return $contents[$params['src']];
00843         }
00844 
00860         abstract public function getFileContentsMulti( array $params );
00861 
00871         abstract public function getFileSize( array $params );
00872 
00887         abstract public function getFileStat( array $params );
00888 
00898         abstract public function getFileSha1Base36( array $params );
00899 
00910         abstract public function getFileProps( array $params );
00911 
00926         abstract public function streamFile( array $params );
00927 
00947         final public function getLocalReference( array $params ) {
00948                 $fsFiles = $this->getLocalReferenceMulti(
00949                         array( 'srcs' => array( $params['src'] ) ) + $params );
00950 
00951                 return $fsFiles[$params['src']];
00952         }
00953 
00969         abstract public function getLocalReferenceMulti( array $params );
00970 
00982         final public function getLocalCopy( array $params ) {
00983                 $tmpFiles = $this->getLocalCopyMulti(
00984                         array( 'srcs' => array( $params['src'] ) ) + $params );
00985 
00986                 return $tmpFiles[$params['src']];
00987         }
00988 
01004         abstract public function getLocalCopyMulti( array $params );
01005 
01023         abstract public function getFileHttpUrl( array $params );
01024 
01038         abstract public function directoryExists( array $params );
01039 
01057         abstract public function getDirectoryList( array $params );
01058 
01071         final public function getTopDirectoryList( array $params ) {
01072                 return $this->getDirectoryList( array( 'topOnly' => true ) + $params );
01073         }
01074 
01091         abstract public function getFileList( array $params );
01092 
01105         final public function getTopFileList( array $params ) {
01106                 return $this->getFileList( array( 'topOnly' => true ) + $params );
01107         }
01108 
01116         public function preloadCache( array $paths ) {}
01117 
01125         public function clearCache( array $paths = null ) {}
01126 
01137         final public function lockFiles( array $paths, $type ) {
01138                 return $this->lockManager->lock( $paths, $type );
01139         }
01140 
01148         final public function unlockFiles( array $paths, $type ) {
01149                 return $this->lockManager->unlock( $paths, $type );
01150         }
01151 
01165         final public function getScopedFileLocks( array $paths, $type, Status $status ) {
01166                 return ScopedLock::factory( $this->lockManager, $paths, $type, $status );
01167         }
01168 
01185         abstract public function getScopedLocksForOps( array $ops, Status $status );
01186 
01194         final public function getRootStoragePath() {
01195                 return "mwstore://{$this->name}";
01196         }
01197 
01205         final public function getContainerStoragePath( $container ) {
01206                 return $this->getRootStoragePath() . "/{$container}";
01207         }
01208 
01214         final public function getJournal() {
01215                 return $this->fileJournal;
01216         }
01217 
01225         final public static function isStoragePath( $path ) {
01226                 return ( strpos( $path, 'mwstore://' ) === 0 );
01227         }
01228 
01237         final public static function splitStoragePath( $storagePath ) {
01238                 if ( self::isStoragePath( $storagePath ) ) {
01239                         // Remove the "mwstore://" prefix and split the path
01240                         $parts = explode( '/', substr( $storagePath, 10 ), 3 );
01241                         if ( count( $parts ) >= 2 && $parts[0] != '' && $parts[1] != '' ) {
01242                                 if ( count( $parts ) == 3 ) {
01243                                         return $parts; // e.g. "backend/container/path"
01244                                 } else {
01245                                         return array( $parts[0], $parts[1], '' ); // e.g. "backend/container"
01246                                 }
01247                         }
01248                 }
01249                 return array( null, null, null );
01250         }
01251 
01259         final public static function normalizeStoragePath( $storagePath ) {
01260                 list( $backend, $container, $relPath ) = self::splitStoragePath( $storagePath );
01261                 if ( $relPath !== null ) { // must be for this backend
01262                         $relPath = self::normalizeContainerPath( $relPath );
01263                         if ( $relPath !== null ) {
01264                                 return ( $relPath != '' )
01265                                         ? "mwstore://{$backend}/{$container}/{$relPath}"
01266                                         : "mwstore://{$backend}/{$container}";
01267                         }
01268                 }
01269                 return null;
01270         }
01271 
01280         final public static function parentStoragePath( $storagePath ) {
01281                 $storagePath = dirname( $storagePath );
01282                 list( , , $rel ) = self::splitStoragePath( $storagePath );
01283                 return ( $rel === null ) ? null : $storagePath;
01284         }
01285 
01292         final public static function extensionFromPath( $path ) {
01293                 $i = strrpos( $path, '.' );
01294                 return strtolower( $i ? substr( $path, $i + 1 ) : '' );
01295         }
01296 
01304         final public static function isPathTraversalFree( $path ) {
01305                 return ( self::normalizeContainerPath( $path ) !== null );
01306         }
01307 
01317         final public static function makeContentDisposition( $type, $filename = '' ) {
01318                 $parts = array();
01319 
01320                 $type = strtolower( $type );
01321                 if ( !in_array( $type, array( 'inline', 'attachment' ) ) ) {
01322                         throw new MWException( "Invalid Content-Disposition type '$type'." );
01323                 }
01324                 $parts[] = $type;
01325 
01326                 if ( strlen( $filename ) ) {
01327                         $parts[] = "filename*=UTF-8''" . rawurlencode( basename( $filename ) );
01328                 }
01329 
01330                 return implode( ';', $parts );
01331         }
01332 
01343         final protected static function normalizeContainerPath( $path ) {
01344                 // Normalize directory separators
01345                 $path = strtr( $path, '\\', '/' );
01346                 // Collapse any consecutive directory separators
01347                 $path = preg_replace( '![/]{2,}!', '/', $path );
01348                 // Remove any leading directory separator
01349                 $path = ltrim( $path, '/' );
01350                 // Use the same traversal protection as Title::secureAndSplit()
01351                 if ( strpos( $path, '.' ) !== false ) {
01352                         if (
01353                                 $path === '.' ||
01354                                 $path === '..' ||
01355                                 strpos( $path, './' ) === 0 ||
01356                                 strpos( $path, '../' ) === 0 ||
01357                                 strpos( $path, '/./' ) !== false ||
01358                                 strpos( $path, '/../' ) !== false
01359                         ) {
01360                                 return null;
01361                         }
01362                 }
01363                 return $path;
01364         }
01365 }