00001 <?php
00002
00008 abstract class FileRepo {
00009 const FILES_ONLY = 1;
00010 const DELETE_SOURCE = 1;
00011 const OVERWRITE = 2;
00012 const OVERWRITE_SAME = 4;
00013
00014 var $thumbScriptUrl, $transformVia404;
00015 var $descBaseUrl, $scriptDirUrl, $articleUrl, $fetchDescription, $initialCapital;
00016 var $pathDisclosureProtection = 'paranoid';
00017 var $descriptionCacheExpiry, $hashLevels, $url, $thumbUrl;
00018
00023 var $fileFactory = false, $oldFileFactory = false;
00024 var $fileFactoryKey = false, $oldFileFactoryKey = false;
00025
00026 function __construct( $info ) {
00027
00028 $this->name = $info['name'];
00029
00030
00031 $this->initialCapital = MWNamespace::isCapitalized( NS_FILE );
00032 foreach ( array( 'descBaseUrl', 'scriptDirUrl', 'articleUrl', 'fetchDescription',
00033 'thumbScriptUrl', 'initialCapital', 'pathDisclosureProtection',
00034 'descriptionCacheExpiry', 'hashLevels', 'url', 'thumbUrl' ) as $var )
00035 {
00036 if ( isset( $info[$var] ) ) {
00037 $this->$var = $info[$var];
00038 }
00039 }
00040 $this->transformVia404 = !empty( $info['transformVia404'] );
00041 }
00042
00046 static function isVirtualUrl( $url ) {
00047 return substr( $url, 0, 9 ) == 'mwrepo://';
00048 }
00049
00059 function newFile( $title, $time = false ) {
00060 if ( !($title instanceof Title) ) {
00061 $title = Title::makeTitleSafe( NS_FILE, $title );
00062 if ( !is_object( $title ) ) {
00063 return null;
00064 }
00065 }
00066 if ( $time ) {
00067 if ( $this->oldFileFactory ) {
00068 return call_user_func( $this->oldFileFactory, $title, $this, $time );
00069 } else {
00070 return false;
00071 }
00072 } else {
00073 return call_user_func( $this->fileFactory, $title, $this );
00074 }
00075 }
00076
00094 function findFile( $title, $options = array() ) {
00095 if ( !is_array( $options ) ) {
00096
00097 $time = $options;
00098 } else {
00099 $time = isset( $options['time'] ) ? $options['time'] : false;
00100 }
00101 if ( !($title instanceof Title) ) {
00102 $title = Title::makeTitleSafe( NS_FILE, $title );
00103 if ( !is_object( $title ) ) {
00104 return false;
00105 }
00106 }
00107 # First try the current version of the file to see if it precedes the timestamp
00108 $img = $this->newFile( $title );
00109 if ( !$img ) {
00110 return false;
00111 }
00112 if ( $img->exists() && ( !$time || $img->getTimestamp() == $time ) ) {
00113 return $img;
00114 }
00115 # Now try an old version of the file
00116 if ( $time !== false ) {
00117 $img = $this->newFile( $title, $time );
00118 if ( $img && $img->exists() ) {
00119 if ( !$img->isDeleted(File::DELETED_FILE) ) {
00120 return $img;
00121 } else if ( !empty( $options['private'] ) && $img->userCan(File::DELETED_FILE) ) {
00122 return $img;
00123 }
00124 }
00125 }
00126
00127 # Now try redirects
00128 if ( !empty( $options['ignoreRedirect'] ) ) {
00129 return false;
00130 }
00131 $redir = $this->checkRedirect( $title );
00132 if( $redir && $redir->getNamespace() == NS_FILE) {
00133 $img = $this->newFile( $redir );
00134 if( !$img ) {
00135 return false;
00136 }
00137 if( $img->exists() ) {
00138 $img->redirectedFrom( $title->getDBkey() );
00139 return $img;
00140 }
00141 }
00142 return false;
00143 }
00144
00145
00146
00147
00148
00149
00150
00151
00152
00153
00154 function findFiles( $items ) {
00155 $result = array();
00156 foreach ( $items as $index => $item ) {
00157 if ( is_array( $item ) ) {
00158 $title = $item['title'];
00159 $options = $item;
00160 unset( $options['title'] );
00161 } else {
00162 $title = $item;
00163 $options = array();
00164 }
00165 $file = $this->findFile( $title, $options );
00166 if ( $file )
00167 $result[$file->getTitle()->getDBkey()] = $file;
00168 }
00169 return $result;
00170 }
00171
00181 function newFileFromKey( $sha1, $time = false ) {
00182 if ( $time ) {
00183 if ( $this->oldFileFactoryKey ) {
00184 return call_user_func( $this->oldFileFactoryKey, $sha1, $this, $time );
00185 } else {
00186 return false;
00187 }
00188 } else {
00189 return call_user_func( $this->fileFactoryKey, $sha1, $this );
00190 }
00191 }
00192
00201 function findFileFromKey( $sha1, $options = array() ) {
00202 if ( !is_array( $options ) ) {
00203 # MW 1.15 compat
00204 $time = $options;
00205 } else {
00206 $time = isset( $options['time'] ) ? $options['time'] : false;
00207 }
00208
00209 # First try the current version of the file to see if it precedes the timestamp
00210 $img = $this->newFileFromKey( $sha1 );
00211 if ( !$img ) {
00212 return false;
00213 }
00214 if ( $img->exists() && ( !$time || $img->getTimestamp() == $time ) ) {
00215 return $img;
00216 }
00217 # Now try an old version of the file
00218 if ( $time !== false ) {
00219 $img = $this->newFileFromKey( $sha1, $time );
00220 if ( $img->exists() ) {
00221 if ( !$img->isDeleted(File::DELETED_FILE) ) {
00222 return $img;
00223 } else if ( !empty( $options['private'] ) && $img->userCan(File::DELETED_FILE) ) {
00224 return $img;
00225 }
00226 }
00227 }
00228 return false;
00229 }
00230
00234 function getThumbScriptUrl() {
00235 return $this->thumbScriptUrl;
00236 }
00237
00243 function getZoneUrl( $zone ) {
00244 return false;
00245 }
00246
00250 function canTransformVia404() {
00251 return $this->transformVia404;
00252 }
00253
00257 function getNameFromTitle( $title ) {
00258 global $wgCapitalLinks;
00259 if ( $this->initialCapital != MWNamespace::isCapitalized( NS_FILE ) ) {
00260 global $wgContLang;
00261 $name = $title->getUserCaseDBKey();
00262 if ( $this->initialCapital ) {
00263 $name = $wgContLang->ucfirst( $name );
00264 }
00265 } else {
00266 $name = $title->getDBkey();
00267 }
00268 return $name;
00269 }
00270
00271 static function getHashPathForLevel( $name, $levels ) {
00272 if ( $levels == 0 ) {
00273 return '';
00274 } else {
00275 $hash = md5( $name );
00276 $path = '';
00277 for ( $i = 1; $i <= $levels; $i++ ) {
00278 $path .= substr( $hash, 0, $i ) . '/';
00279 }
00280 return $path;
00281 }
00282 }
00283
00288 function getHashPath( $name ) {
00289 return self::getHashPathForLevel( $name, $this->hashLevels );
00290 }
00291
00295 function getName() {
00296 return $this->name;
00297 }
00298
00308 function getDescriptionUrl( $name ) {
00309 $encName = wfUrlencode( $name );
00310 if ( !is_null( $this->descBaseUrl ) ) {
00311 # "http://example.com/wiki/Image:"
00312 return $this->descBaseUrl . $encName;
00313 }
00314 if ( !is_null( $this->articleUrl ) ) {
00315 # "http://example.com/wiki/$1"
00316 #
00317 # We use "Image:" as the canonical namespace for
00318 # compatibility across all MediaWiki versions.
00319 return str_replace( '$1',
00320 "Image:$encName", $this->articleUrl );
00321 }
00322 if ( !is_null( $this->scriptDirUrl ) ) {
00323 # "http://example.com/w"
00324 #
00325 # We use "Image:" as the canonical namespace for
00326 # compatibility across all MediaWiki versions,
00327 # and just sort of hope index.php is right. ;)
00328 return $this->scriptDirUrl .
00329 "/index.php?title=Image:$encName";
00330 }
00331 return false;
00332 }
00333
00342 function getDescriptionRenderUrl( $name, $lang = null ) {
00343 $query = 'action=render';
00344 if ( !is_null( $lang ) ) {
00345 $query .= '&uselang=' . $lang;
00346 }
00347 if ( isset( $this->scriptDirUrl ) ) {
00348 return $this->scriptDirUrl . '/index.php?title=' .
00349 wfUrlencode( 'Image:' . $name ) .
00350 "&$query";
00351 } else {
00352 $descUrl = $this->getDescriptionUrl( $name );
00353 if ( $descUrl ) {
00354 return wfAppendQuery( $descUrl, $query );
00355 } else {
00356 return false;
00357 }
00358 }
00359 }
00360
00374 function store( $srcPath, $dstZone, $dstRel, $flags = 0 ) {
00375 $status = $this->storeBatch( array( array( $srcPath, $dstZone, $dstRel ) ), $flags );
00376 if ( $status->successCount == 0 ) {
00377 $status->ok = false;
00378 }
00379 return $status;
00380 }
00381
00388 abstract function storeBatch( $triplets, $flags = 0 );
00389
00398 abstract function storeTemp( $originalName, $srcPath );
00399
00400
00409 abstract function append( $srcPath, $toAppendPath, $flags = 0 );
00410
00417 function freeTemp( $virtualUrl ) {
00418 return true;
00419 }
00420
00435 function publish( $srcPath, $dstRel, $archiveRel, $flags = 0 ) {
00436 $status = $this->publishBatch( array( array( $srcPath, $dstRel, $archiveRel ) ), $flags );
00437 if ( $status->successCount == 0 ) {
00438 $status->ok = false;
00439 }
00440 if ( isset( $status->value[0] ) ) {
00441 $status->value = $status->value[0];
00442 } else {
00443 $status->value = false;
00444 }
00445 return $status;
00446 }
00447
00454 abstract function publishBatch( $triplets, $flags = 0 );
00455
00456 function fileExists( $file, $flags = 0 ) {
00457 $result = $this->fileExistsBatch( array( $file ), $flags );
00458 return $result[0];
00459 }
00460
00469 abstract function fileExistsBatch( $files, $flags = 0 );
00470
00487 abstract function deleteBatch( $sourceDestPairs );
00488
00498 function delete( $srcRel, $archiveRel ) {
00499 return $this->deleteBatch( array( array( $srcRel, $archiveRel ) ) );
00500 }
00501
00507 abstract function getFileProps( $virtualUrl );
00508
00514 function enumFiles( $callback ) {
00515 throw new MWException( 'enumFiles is not supported by ' . get_class( $this ) );
00516 }
00517
00521 function validateFilename( $filename ) {
00522 if ( strval( $filename ) == '' ) {
00523 return false;
00524 }
00525 if ( wfIsWindows() ) {
00526 $filename = strtr( $filename, '\\', '/' );
00527 }
00531 if ( strpos( $filename, '.' ) !== false &&
00532 ( $filename === '.' || $filename === '..' ||
00533 strpos( $filename, './' ) === 0 ||
00534 strpos( $filename, '../' ) === 0 ||
00535 strpos( $filename, '/./' ) !== false ||
00536 strpos( $filename, '/../' ) !== false ) )
00537 {
00538 return false;
00539 } else {
00540 return true;
00541 }
00542 }
00543
00547 function paranoidClean( $param ) { return '[hidden]'; }
00548 function passThrough( $param ) { return $param; }
00549
00553 function getErrorCleanupFunction() {
00554 switch ( $this->pathDisclosureProtection ) {
00555 case 'none':
00556 $callback = array( $this, 'passThrough' );
00557 break;
00558 default:
00559 $callback = array( $this, 'paranoidClean' );
00560 }
00561 return $callback;
00562 }
00568 function newFatal( $message ) {
00569 $params = func_get_args();
00570 array_unshift( $params, $this );
00571 return call_user_func_array( array( 'FileRepoStatus', 'newFatal' ), $params );
00572 }
00573
00577 function newGood( $value = null ) {
00578 return FileRepoStatus::newGood( $this, $value );
00579 }
00580
00585 function cleanupDeletedBatch( $storageKeys ) {}
00586
00594 function checkRedirect( $title ) {
00595 return false;
00596 }
00597
00605 function invalidateImageRedirect( $title ) {}
00606
00613 function findBySha1( $hash ) {
00614 return array();
00615 }
00616
00621 public function getDisplayName() {
00622
00623 if ( $this->name == 'local' ) {
00624 return null;
00625 }
00626
00627 $repoName = wfMsg( 'shared-repo-name-' . $this->name );
00628 if ( !wfEmptyMsg( 'shared-repo-name-' . $this->name, $repoName ) ) {
00629 return $repoName;
00630 }
00631 return wfMsg( 'shared-repo' );
00632 }
00633
00641 function getSharedCacheKey( ) {
00642 return false;
00643 }
00644
00650 function getLocalCacheKey( ) {
00651 $args = func_get_args();
00652 array_unshift( $args, 'filerepo', $this->getName() );
00653 return call_user_func_array( 'wfMemcKey', $args );
00654 }
00655 }