4 * A repository for files accessible via the local filesystem. Does not support
5 * database access or registration.
9 const DELETE_SOURCE
= 1;
11 var $directory, $url, $hashLevels, $thumbScriptUrl, $transformVia404;
12 var $descBaseUrl, $scriptDirUrl, $articleUrl, $fetchDescription;
13 var $fileFactory = array( 'UnregisteredLocalFile', 'newFromTitle' );
15 function __construct( $info ) {
17 $this->name
= $info['name'];
18 $this->directory
= $info['directory'];
19 $this->url
= $info['url'];
20 $this->hashLevels
= $info['hashLevels'];
21 $this->transformVia404
= !empty( $info['transformVia404'] );
24 foreach ( array( 'descBaseUrl', 'scriptDirUrl', 'articleUrl', 'fetchDescription',
25 'thumbScriptUrl' ) as $var )
27 if ( isset( $info[$var] ) ) {
28 $this->$var = $info[$var];
34 * Create a new File object from the local repository
35 * @param mixed $title Title object or string
36 * @param mixed $time Time at which the image is supposed to have existed.
37 * If this is specified, the returned object will be an
38 * instance of the repository's old file class instead of
39 * a current file. Repositories not supporting version
40 * control should return false if this parameter is set.
42 function newFile( $title, $time = false ) {
43 if ( !($title instanceof Title
) ) {
44 $title = Title
::makeTitleSafe( NS_IMAGE
, $title );
45 if ( !is_object( $title ) ) {
50 return call_user_func( $this->oldFileFactor
, $title, $this, $time );
52 return call_user_func( $this->fileFactory
, $title, $this );
57 * Find an instance of the named file that existed at the specified time
58 * Returns false if the file did not exist. Repositories not supporting
59 * version control should return false if the time is specified.
61 * @param mixed $time 14-character timestamp, or false for the current version
63 function findFile( $title, $time = false ) {
64 $img = $this->newFile( $title );
68 if ( $img->exists() && $img->getTimestamp() <= $time ) {
71 $img = $this->newFile( $title, $time );
72 if ( $img->exists() ) {
77 function getRootDirectory() {
78 return $this->directory
;
81 function getRootUrl() {
86 return (bool)$this->hashLevels
;
89 function getThumbScriptUrl() {
90 return $this->thumbScriptUrl
;
93 function canTransformVia404() {
94 return $this->transformVia404
;
97 function getZonePath( $zone ) {
100 return $this->directory
;
102 return "{$this->directory}/temp";
104 return $GLOBALS['wgFileStore']['deleted']['directory'];
110 function getZoneUrl( $zone ) {
115 return "{$this->url}/temp";
117 return $GLOBALS['wgFileStore']['deleted']['url'];
124 * Get a URL referring to this repository, with the private mwrepo protocol.
126 function getVirtualUrl( $suffix = false ) {
128 if ( $suffix !== false ) {
129 $path .= '/' . $suffix;
135 * Get the local path corresponding to a virtual URL
137 function resolveVirtualUrl( $url ) {
138 if ( substr( $url, 0, 9 ) != 'mwrepo://' ) {
139 throw new MWException( __METHOD__
.': unknown protoocl' );
142 $bits = explode( '/', substr( $url, 9 ), 3 );
143 if ( count( $bits ) != 3 ) {
144 throw new MWException( __METHOD__
.": invalid mwrepo URL: $url" );
146 list( $host, $zone, $rel ) = $bits;
147 if ( $host !== '' ) {
148 throw new MWException( __METHOD__
.": fetching from a foreign repo is not supported" );
150 $base = $this->getZonePath( $zone );
152 throw new MWException( __METHOD__
.": invalid zone: $zone" );
154 return $base . '/' . urldecode( $rel );
158 * Store a file to a given destination.
160 function store( $srcPath, $dstZone, $dstRel, $flags = 0 ) {
161 $root = $this->getZonePath( $dstZone );
163 throw new MWException( "Invalid zone: $dstZone" );
165 $dstPath = "$root/$dstRel";
167 if ( !is_dir( dirname( $dstPath ) ) ) {
168 wfMkdirParents( dirname( $dstPath ) );
171 if ( substr( $srcPath, 0, 9 ) == 'mwrepo://' ) {
172 $srcPath = $this->resolveVirtualUrl( $srcPath );
175 if ( $flags & self
::DELETE_SOURCE
) {
176 if ( !rename( $srcPath, $dstPath ) ) {
177 return new WikiErrorMsg( 'filerenameerror', wfEscapeWikiText( $srcPath ),
178 wfEscapeWikiText( $dstPath ) );
181 if ( !copy( $srcPath, $dstPath ) ) {
182 return new WikiErrorMsg( 'filecopyerror', wfEscapeWikiText( $srcPath ),
183 wfEscapeWikiText( $dstPath ) );
186 chmod( $dstPath, 0644 );
191 * Pick a random name in the temp zone and store a file to it.
192 * Returns the URL, or a WikiError on failure.
193 * @param string $originalName The base name of the file as specified
194 * by the user. The file extension will be maintained.
195 * @param string $srcPath The current location of the file.
197 function storeTemp( $originalName, $srcPath ) {
198 $dstRel = $this->getHashPath( $originalName ) .
199 gmdate( "YmdHis" ) . '!' . $originalName;
200 $result = $this->store( $srcPath, 'temp', $dstRel );
201 if ( WikiError
::isError( $result ) ) {
204 return $this->getVirtualUrl( "temp/$dstRel" );
208 function publish( $srcPath, $dstPath, $archivePath, $flags = 0 ) {
209 if ( substr( $srcPath, 0, 9 ) == 'mwrepo://' ) {
210 $srcPath = $this->resolveVirtualUrl( $srcPath );
212 $dstDir = dirname( $dstPath );
213 if ( !is_dir( $dstDir ) ) wfMkdirParents( $dstDir );
215 if( is_file( $dstPath ) ) {
216 $archiveDir = dirname( $archivePath );
217 if ( !is_dir( $archiveDir ) ) wfMkdirParents( $archiveDir );
218 wfSuppressWarnings();
219 $success = rename( $dstPath, $archivePath );
223 return new WikiErrorMsg( 'filerenameerror', wfEscapeWikiText( $dstPath ),
224 wfEscapeWikiText( $archivePath ) );
226 else wfDebug(__METHOD__
.": moved file $dstPath to $archivePath\n");
227 $status = 'archived';
234 wfSuppressWarnings();
235 if ( $flags & self
::DELETE_SOURCE
) {
236 if ( !rename( $srcPath, $dstPath ) ) {
237 $error = new WikiErrorMsg( 'filerenameerror', wfEscapeWikiText( $srcPath ),
238 wfEscapeWikiText( $dstPath ) );
241 if ( !copy( $srcPath, $dstPath ) ) {
242 $error = new WikiErrorMsg( 'filerenameerror', wfEscapeWikiText( $srcPath ),
243 wfEscapeWikiText( $dstPath ) );
251 wfDebug(__METHOD__
.": wrote tempfile $srcPath to $dstPath\n");
254 chmod( $dstPath, 0644 );
259 * Get a relative path including trailing slash, e.g. f/fa/
260 * If the repo is not hashed, returns an empty string
262 function getHashPath( $name ) {
263 if ( $this->isHashed() ) {
264 $hash = md5( $name );
266 for ( $i = 1; $i <= $this->hashLevels
; $i++
) {
267 $path .= substr( $hash, 0, $i ) . '/';
280 * Get the file description page base URL, or false if there isn't one.
283 function getDescBaseUrl() {
284 if ( is_null( $this->descBaseUrl
) ) {
285 if ( !is_null( $this->articleUrl
) ) {
286 $this->descBaseUrl
= str_replace( '$1',
287 urlencode( Namespace::getCanonicalName( NS_IMAGE
) ) . ':', $this->articleUrl
);
288 } elseif ( !is_null( $this->scriptDirUrl
) ) {
289 $this->descBaseUrl
= $this->scriptDirUrl
. '/index.php?title=' .
290 urlencode( Namespace::getCanonicalName( NS_IMAGE
) ) . ':';
292 $this->descBaseUrl
= false;
295 return $this->descBaseUrl
;
299 * Get the URL of an image description page. May return false if it is
300 * unknown or not applicable. In general this should only be called by the
301 * File class, since it may return invalid results for certain kinds of
302 * repositories. Use File::getDescriptionUrl() in user code.
304 * In particular, it uses the article paths as specified to the repository
305 * constructor, whereas local repositories use the local Title functions.
307 function getDescriptionUrl( $name ) {
308 $base = $this->getDescBaseUrl();
310 return $base . wfUrlencode( $name );
317 * Get the URL of the content-only fragment of the description page. For
318 * MediaWiki this means action=render. This should only be called by the
319 * repository's file class, since it may return invalid results. User code
320 * should use File::getDescriptionText().
322 function getDescriptionRenderUrl( $name ) {
323 if ( isset( $this->scriptDirUrl
) ) {
324 return $this->scriptDirUrl
. '/index.php?title=' .
325 wfUrlencode( Namespace::getCanonicalName( NS_IMAGE
) . ':' . $name ) .
328 $descBase = $this->getDescBaseUrl();
330 return wfAppendQuery( $descBase . wfUrlencode( $name ), 'action=render' );
338 * Call a callback function for every file in the repository.
339 * Uses the filesystem even in child classes.
341 function enumFilesInFS( $callback ) {
342 $numDirs = 1 << ( $this->hashLevels
* 4 );
343 for ( $flatIndex = 0; $flatIndex < $numDirs; $flatIndex++
) {
344 $hexString = sprintf( "%0{$this->hashLevels}x", $flatIndex );
345 $path = $this->directory
;
346 for ( $hexPos = 0; $hexPos < $this->hashLevels
; $hexPos++
) {
347 $path .= '/' . substr( $hexString, 0, $hexPos +
1 );
349 if ( !file_exists( $path ) ||
!is_dir( $path ) ) {
352 $dir = opendir( $path );
353 while ( false !== ( $name = readdir( $dir ) ) ) {
354 call_user_func( $callback, $path . '/' . $name );
360 * Call a callaback function for every file in the repository
361 * May use either the database or the filesystem
363 function enumFiles( $callback ) {
364 $this->enumFilesInFS( $callback );