name = $info['name']; $this->directory = $info['directory']; $this->url = $info['url']; $this->hashLevels = $info['hashLevels']; $this->transformVia404 = !empty( $info['transformVia404'] ); // Optional settings foreach ( array( 'descBaseUrl', 'scriptDirUrl', 'articleUrl', 'fetchDescription', 'thumbScriptUrl' ) as $var ) { if ( isset( $info[$var] ) ) { $this->$var = $info[$var]; } } } /** * Create a new File object from the local repository * @param mixed $title Title object or string * @param mixed $time Time at which the image is supposed to have existed. * If this is specified, the returned object will be an * instance of the repository's old file class instead of * a current file. Repositories not supporting version * control should return false if this parameter is set. */ function newFile( $title, $time = false ) { if ( !($title instanceof Title) ) { $title = Title::makeTitleSafe( NS_IMAGE, $title ); if ( !is_object( $title ) ) { return null; } } if ( $time ) { return call_user_func( $this->oldFileFactor, $title, $this, $time ); } else { return call_user_func( $this->fileFactory, $title, $this ); } } /** * Find an instance of the named file that existed at the specified time * Returns false if the file did not exist. Repositories not supporting * version control should return false if the time is specified. * * @param mixed $time 14-character timestamp, or false for the current version */ function findFile( $title, $time = false ) { $img = $this->newFile( $title ); if ( !$img ) { return false; } if ( $img->exists() && $img->getTimestamp() <= $time ) { return $img; } $img = $this->newFile( $title, $time ); if ( $img->exists() ) { return $img; } } function getRootDirectory() { return $this->directory; } function getRootUrl() { return $this->url; } function isHashed() { return (bool)$this->hashLevels; } function getThumbScriptUrl() { return $this->thumbScriptUrl; } function canTransformVia404() { return $this->transformVia404; } function getZonePath( $zone ) { switch ( $zone ) { case 'public': return $this->directory; case 'temp': return "{$this->directory}/temp"; case 'deleted': return $GLOBALS['wgFileStore']['deleted']['directory']; default: return false; } } function getZoneUrl( $zone ) { switch ( $zone ) { case 'public': return $this->url; case 'temp': return "{$this->url}/temp"; case 'deleted': return $GLOBALS['wgFileStore']['deleted']['url']; default: return false; } } /** * Get a URL referring to this repository, with the private mwrepo protocol. */ function getVirtualUrl( $suffix = false ) { $path = 'mwrepo://'; if ( $suffix !== false ) { $path .= '/' . $suffix; } return $path; } /** * Get the local path corresponding to a virtual URL */ function resolveVirtualUrl( $url ) { if ( substr( $url, 0, 9 ) != 'mwrepo://' ) { throw new MWException( __METHOD__.': unknown protoocl' ); } $bits = explode( '/', substr( $url, 9 ), 3 ); if ( count( $bits ) != 3 ) { throw new MWException( __METHOD__.": invalid mwrepo URL: $url" ); } list( $host, $zone, $rel ) = $bits; if ( $host !== '' ) { throw new MWException( __METHOD__.": fetching from a foreign repo is not supported" ); } $base = $this->getZonePath( $zone ); if ( !$base ) { throw new MWException( __METHOD__.": invalid zone: $zone" ); } return $base . '/' . urldecode( $rel ); } /** * Store a file to a given destination. */ function store( $srcPath, $dstZone, $dstRel, $flags = 0 ) { $root = $this->getZonePath( $dstZone ); if ( !$root ) { throw new MWException( "Invalid zone: $dstZone" ); } $dstPath = "$root/$dstRel"; if ( !is_dir( dirname( $dstPath ) ) ) { wfMkdirParents( dirname( $dstPath ) ); } if ( substr( $srcPath, 0, 9 ) == 'mwrepo://' ) { $srcPath = $this->resolveVirtualUrl( $srcPath ); } if ( $flags & self::DELETE_SOURCE ) { if ( !rename( $srcPath, $dstPath ) ) { return new WikiErrorMsg( 'filerenameerror', wfEscapeWikiText( $srcPath ), wfEscapeWikiText( $dstPath ) ); } } else { if ( !copy( $srcPath, $dstPath ) ) { return new WikiErrorMsg( 'filecopyerror', wfEscapeWikiText( $srcPath ), wfEscapeWikiText( $dstPath ) ); } } chmod( $dstPath, 0644 ); return true; } /** * Pick a random name in the temp zone and store a file to it. * Returns the URL, or a WikiError on failure. * @param string $originalName The base name of the file as specified * by the user. The file extension will be maintained. * @param string $srcPath The current location of the file. */ function storeTemp( $originalName, $srcPath ) { $dstRel = $this->getHashPath( $originalName ) . gmdate( "YmdHis" ) . '!' . $originalName; $result = $this->store( $srcPath, 'temp', $dstRel ); if ( WikiError::isError( $result ) ) { return $result; } else { return $this->getVirtualUrl( "temp/$dstRel" ); } } function publish( $srcPath, $dstPath, $archivePath, $flags = 0 ) { if ( substr( $srcPath, 0, 9 ) == 'mwrepo://' ) { $srcPath = $this->resolveVirtualUrl( $srcPath ); } $dstDir = dirname( $dstPath ); if ( !is_dir( $dstDir ) ) wfMkdirParents( $dstDir ); if( is_file( $dstPath ) ) { $archiveDir = dirname( $archivePath ); if ( !is_dir( $archiveDir ) ) wfMkdirParents( $archiveDir ); wfSuppressWarnings(); $success = rename( $dstPath, $archivePath ); wfRestoreWarnings(); if( ! $success ) { return new WikiErrorMsg( 'filerenameerror', wfEscapeWikiText( $dstPath ), wfEscapeWikiText( $archivePath ) ); } else wfDebug(__METHOD__.": moved file $dstPath to $archivePath\n"); $status = 'archived'; } else { $status = 'new'; } $error = false; wfSuppressWarnings(); if ( $flags & self::DELETE_SOURCE ) { if ( !rename( $srcPath, $dstPath ) ) { $error = new WikiErrorMsg( 'filerenameerror', wfEscapeWikiText( $srcPath ), wfEscapeWikiText( $dstPath ) ); } } else { if ( !copy( $srcPath, $dstPath ) ) { $error = new WikiErrorMsg( 'filerenameerror', wfEscapeWikiText( $srcPath ), wfEscapeWikiText( $dstPath ) ); } } wfRestoreWarnings(); if( $error ) { return $error; } else { wfDebug(__METHOD__.": wrote tempfile $srcPath to $dstPath\n"); } chmod( $dstPath, 0644 ); return $status; } /** * Get a relative path including trailing slash, e.g. f/fa/ * If the repo is not hashed, returns an empty string */ function getHashPath( $name ) { if ( $this->isHashed() ) { $hash = md5( $name ); $path = ''; for ( $i = 1; $i <= $this->hashLevels; $i++ ) { $path .= substr( $hash, 0, $i ) . '/'; } return $path; } else { return ''; } } function getName() { return $this->name; } /** * Get the file description page base URL, or false if there isn't one. * @private */ function getDescBaseUrl() { if ( is_null( $this->descBaseUrl ) ) { if ( !is_null( $this->articleUrl ) ) { $this->descBaseUrl = str_replace( '$1', urlencode( Namespace::getCanonicalName( NS_IMAGE ) ) . ':', $this->articleUrl ); } elseif ( !is_null( $this->scriptDirUrl ) ) { $this->descBaseUrl = $this->scriptDirUrl . '/index.php?title=' . urlencode( Namespace::getCanonicalName( NS_IMAGE ) ) . ':'; } else { $this->descBaseUrl = false; } } return $this->descBaseUrl; } /** * Get the URL of an image description page. May return false if it is * unknown or not applicable. In general this should only be called by the * File class, since it may return invalid results for certain kinds of * repositories. Use File::getDescriptionUrl() in user code. * * In particular, it uses the article paths as specified to the repository * constructor, whereas local repositories use the local Title functions. */ function getDescriptionUrl( $name ) { $base = $this->getDescBaseUrl(); if ( $base ) { return $base . wfUrlencode( $name ); } else { return false; } } /** * Get the URL of the content-only fragment of the description page. For * MediaWiki this means action=render. This should only be called by the * repository's file class, since it may return invalid results. User code * should use File::getDescriptionText(). */ function getDescriptionRenderUrl( $name ) { if ( isset( $this->scriptDirUrl ) ) { return $this->scriptDirUrl . '/index.php?title=' . wfUrlencode( Namespace::getCanonicalName( NS_IMAGE ) . ':' . $name ) . '&action=render'; } else { $descBase = $this->getDescBaseUrl(); if ( $descBase ) { return wfAppendQuery( $descBase . wfUrlencode( $name ), 'action=render' ); } else { return false; } } } /** * Call a callback function for every file in the repository. * Uses the filesystem even in child classes. */ function enumFilesInFS( $callback ) { $numDirs = 1 << ( $this->hashLevels * 4 ); for ( $flatIndex = 0; $flatIndex < $numDirs; $flatIndex++ ) { $hexString = sprintf( "%0{$this->hashLevels}x", $flatIndex ); $path = $this->directory; for ( $hexPos = 0; $hexPos < $this->hashLevels; $hexPos++ ) { $path .= '/' . substr( $hexString, 0, $hexPos + 1 ); } if ( !file_exists( $path ) || !is_dir( $path ) ) { continue; } $dir = opendir( $path ); while ( false !== ( $name = readdir( $dir ) ) ) { call_user_func( $callback, $path . '/' . $name ); } } } /** * Call a callaback function for every file in the repository * May use either the database or the filesystem */ function enumFiles( $callback ) { $this->enumFilesInFS( $callback ); } } ?>