2007-06-16 02:55:25 +00:00
|
|
|
<?php
|
2012-02-08 15:51:16 +00:00
|
|
|
/**
|
|
|
|
|
* @defgroup FileRepo File Repository
|
|
|
|
|
*
|
|
|
|
|
* @brief This module handles how MediaWiki interacts with filesystems.
|
|
|
|
|
*
|
|
|
|
|
* @details
|
|
|
|
|
*/
|
|
|
|
|
|
2010-09-04 18:13:18 +00:00
|
|
|
/**
|
|
|
|
|
* Base code for file repositories.
|
|
|
|
|
*
|
2012-05-07 07:11:33 +00:00
|
|
|
* This program is free software; you can redistribute it and/or modify
|
|
|
|
|
* it under the terms of the GNU General Public License as published by
|
|
|
|
|
* the Free Software Foundation; either version 2 of the License, or
|
|
|
|
|
* (at your option) any later version.
|
|
|
|
|
*
|
|
|
|
|
* This program is distributed in the hope that it will be useful,
|
|
|
|
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
|
|
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
|
|
|
* GNU General Public License for more details.
|
|
|
|
|
*
|
|
|
|
|
* You should have received a copy of the GNU General Public License along
|
|
|
|
|
* with this program; if not, write to the Free Software Foundation, Inc.,
|
|
|
|
|
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
|
|
|
|
|
* http://www.gnu.org/copyleft/gpl.html
|
|
|
|
|
*
|
2010-09-04 18:13:18 +00:00
|
|
|
* @file
|
|
|
|
|
* @ingroup FileRepo
|
|
|
|
|
*/
|
2007-06-16 02:55:25 +00:00
|
|
|
|
|
|
|
|
/**
|
2011-12-20 03:52:06 +00:00
|
|
|
* Base class for file repositories
|
2010-09-04 18:13:18 +00:00
|
|
|
*
|
WARNING: HUGE COMMIT
Doxygen documentation update:
* Changed alls @addtogroup to @ingroup. @addtogroup adds the comment to the group description, but doesn't add the file, class, function, ... to the group like @ingroup does. See for example http://svn.wikimedia.org/doc/group__SpecialPage.html where it's impossible to see related files, classes, ... that should belong to that group.
* Added @file to file description, it seems that it should be explicitely decalred for file descriptions, otherwise doxygen will think that the comment document the first class, variabled, function, ... that is in that file.
* Removed some empty comments
* Removed some ?>
Added following groups:
* ExternalStorage
* JobQueue
* MaintenanceLanguage
One more thing: there are still a lot of warnings when generating the doc.
2008-05-20 17:13:28 +00:00
|
|
|
* @ingroup FileRepo
|
2007-06-16 02:55:25 +00:00
|
|
|
*/
|
2011-12-20 03:52:06 +00:00
|
|
|
class FileRepo {
|
2007-06-16 02:55:25 +00:00
|
|
|
const DELETE_SOURCE = 1;
|
2007-07-22 14:45:12 +00:00
|
|
|
const OVERWRITE = 2;
|
|
|
|
|
const OVERWRITE_SAME = 4;
|
2011-12-21 20:39:50 +00:00
|
|
|
const SKIP_LOCKING = 8;
|
2007-06-16 02:55:25 +00:00
|
|
|
|
2012-01-29 22:22:28 +00:00
|
|
|
/** @var FileBackend */
|
2011-12-20 03:52:06 +00:00
|
|
|
protected $backend;
|
|
|
|
|
/** @var Array Map of zones to config */
|
|
|
|
|
protected $zones = array();
|
|
|
|
|
|
2007-06-16 02:55:25 +00:00
|
|
|
var $thumbScriptUrl, $transformVia404;
|
2010-07-02 18:37:06 +00:00
|
|
|
var $descBaseUrl, $scriptDirUrl, $scriptExtension, $articleUrl;
|
|
|
|
|
var $fetchDescription, $initialCapital;
|
2011-12-20 03:52:06 +00:00
|
|
|
var $pathDisclosureProtection = 'simple'; // 'paranoid'
|
|
|
|
|
var $descriptionCacheExpiry, $url, $thumbUrl;
|
|
|
|
|
var $hashLevels, $deletedHashLevels;
|
2012-08-31 21:21:39 +00:00
|
|
|
protected $abbrvThreshold;
|
2007-06-16 02:55:25 +00:00
|
|
|
|
2008-04-14 07:45:50 +00:00
|
|
|
/**
|
2007-06-16 02:55:25 +00:00
|
|
|
* Factory functions for creating new files
|
|
|
|
|
* Override these in the base class
|
|
|
|
|
*/
|
2011-12-20 03:52:06 +00:00
|
|
|
var $fileFactory = array( 'UnregisteredLocalFile', 'newFromTitle' );
|
|
|
|
|
var $oldFileFactory = false;
|
2011-03-28 21:40:50 +00:00
|
|
|
var $fileFactoryKey = false, $oldFileFactoryKey = false;
|
2007-06-16 02:55:25 +00:00
|
|
|
|
2012-05-18 02:58:15 +00:00
|
|
|
/**
|
|
|
|
|
* @param $info array|null
|
|
|
|
|
* @throws MWException
|
|
|
|
|
*/
|
2012-05-10 18:15:29 +00:00
|
|
|
public function __construct( array $info = null ) {
|
2012-01-23 14:50:54 +00:00
|
|
|
// Verify required settings presence
|
2013-04-20 17:18:13 +00:00
|
|
|
if (
|
2012-01-23 14:50:54 +00:00
|
|
|
$info === null
|
|
|
|
|
|| !array_key_exists( 'name', $info )
|
|
|
|
|
|| !array_key_exists( 'backend', $info )
|
|
|
|
|
) {
|
|
|
|
|
throw new MWException( __CLASS__ . " requires an array of options having both 'name' and 'backend' keys.\n" );
|
|
|
|
|
}
|
|
|
|
|
|
2007-06-16 02:55:25 +00:00
|
|
|
// Required settings
|
|
|
|
|
$this->name = $info['name'];
|
2012-01-29 22:22:28 +00:00
|
|
|
if ( $info['backend'] instanceof FileBackend ) {
|
2011-12-20 03:52:06 +00:00
|
|
|
$this->backend = $info['backend']; // useful for testing
|
|
|
|
|
} else {
|
|
|
|
|
$this->backend = FileBackendGroup::singleton()->get( $info['backend'] );
|
|
|
|
|
}
|
2008-04-14 07:45:50 +00:00
|
|
|
|
2011-12-20 03:52:06 +00:00
|
|
|
// Optional settings that can have no value
|
|
|
|
|
$optionalSettings = array(
|
|
|
|
|
'descBaseUrl', 'scriptDirUrl', 'articleUrl', 'fetchDescription',
|
|
|
|
|
'thumbScriptUrl', 'pathDisclosureProtection', 'descriptionCacheExpiry',
|
|
|
|
|
'scriptExtension'
|
|
|
|
|
);
|
|
|
|
|
foreach ( $optionalSettings as $var ) {
|
2007-06-16 02:55:25 +00:00
|
|
|
if ( isset( $info[$var] ) ) {
|
|
|
|
|
$this->$var = $info[$var];
|
|
|
|
|
}
|
|
|
|
|
}
|
2011-12-20 03:52:06 +00:00
|
|
|
|
|
|
|
|
// Optional settings that have a default
|
|
|
|
|
$this->initialCapital = isset( $info['initialCapital'] )
|
|
|
|
|
? $info['initialCapital']
|
|
|
|
|
: MWNamespace::isCapitalized( NS_FILE );
|
2011-12-21 21:29:16 +00:00
|
|
|
$this->url = isset( $info['url'] )
|
|
|
|
|
? $info['url']
|
|
|
|
|
: false; // a subclass may set the URL (e.g. ForeignAPIRepo)
|
|
|
|
|
if ( isset( $info['thumbUrl'] ) ) {
|
|
|
|
|
$this->thumbUrl = $info['thumbUrl'];
|
|
|
|
|
} else {
|
|
|
|
|
$this->thumbUrl = $this->url ? "{$this->url}/thumb" : false;
|
|
|
|
|
}
|
2011-12-20 03:52:06 +00:00
|
|
|
$this->hashLevels = isset( $info['hashLevels'] )
|
|
|
|
|
? $info['hashLevels']
|
|
|
|
|
: 2;
|
|
|
|
|
$this->deletedHashLevels = isset( $info['deletedHashLevels'] )
|
|
|
|
|
? $info['deletedHashLevels']
|
|
|
|
|
: $this->hashLevels;
|
2007-06-16 02:55:25 +00:00
|
|
|
$this->transformVia404 = !empty( $info['transformVia404'] );
|
2012-08-31 21:21:39 +00:00
|
|
|
$this->abbrvThreshold = isset( $info['abbrvThreshold'] )
|
|
|
|
|
? $info['abbrvThreshold']
|
|
|
|
|
: 255;
|
2012-08-20 23:39:55 +00:00
|
|
|
$this->isPrivate = !empty( $info['isPrivate'] );
|
2011-12-20 03:52:06 +00:00
|
|
|
// Give defaults for the basic zones...
|
2012-08-20 23:39:55 +00:00
|
|
|
$this->zones = isset( $info['zones'] ) ? $info['zones'] : array();
|
2013-01-15 23:30:41 +00:00
|
|
|
foreach ( array( 'public', 'thumb', 'transcoded', 'temp', 'deleted' ) as $zone ) {
|
2012-05-10 18:15:29 +00:00
|
|
|
if ( !isset( $this->zones[$zone]['container'] ) ) {
|
|
|
|
|
$this->zones[$zone]['container'] = "{$this->name}-{$zone}";
|
|
|
|
|
}
|
|
|
|
|
if ( !isset( $this->zones[$zone]['directory'] ) ) {
|
|
|
|
|
$this->zones[$zone]['directory'] = '';
|
2011-12-20 03:52:06 +00:00
|
|
|
}
|
2012-10-13 00:24:31 +00:00
|
|
|
if ( !isset( $this->zones[$zone]['urlsByExt'] ) ) {
|
|
|
|
|
$this->zones[$zone]['urlsByExt'] = array();
|
|
|
|
|
}
|
2011-12-20 03:52:06 +00:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
2012-04-05 04:10:50 +00:00
|
|
|
* Get the file backend instance. Use this function wisely.
|
2011-12-20 03:52:06 +00:00
|
|
|
*
|
2012-01-29 22:22:28 +00:00
|
|
|
* @return FileBackend
|
2011-12-20 03:52:06 +00:00
|
|
|
*/
|
|
|
|
|
public function getBackend() {
|
|
|
|
|
return $this->backend;
|
|
|
|
|
}
|
|
|
|
|
|
2012-03-14 21:30:26 +00:00
|
|
|
/**
|
2012-04-05 04:10:50 +00:00
|
|
|
* Get an explanatory message if this repo is read-only.
|
|
|
|
|
* This checks if an administrator disabled writes to the backend.
|
2012-03-14 21:30:26 +00:00
|
|
|
*
|
|
|
|
|
* @return string|bool Returns false if the repo is not read-only
|
|
|
|
|
*/
|
|
|
|
|
public function getReadOnlyReason() {
|
|
|
|
|
return $this->backend->getReadOnlyReason();
|
|
|
|
|
}
|
|
|
|
|
|
2011-12-20 03:52:06 +00:00
|
|
|
/**
|
2012-04-05 04:10:50 +00:00
|
|
|
* Check if a single zone or list of zones is defined for usage
|
2012-03-14 21:30:26 +00:00
|
|
|
*
|
2013-03-11 17:15:01 +00:00
|
|
|
* @param array $doZones Only do a particular zones
|
2012-05-18 02:58:15 +00:00
|
|
|
* @throws MWException
|
2011-12-20 03:52:06 +00:00
|
|
|
* @return Status
|
|
|
|
|
*/
|
|
|
|
|
protected function initZones( $doZones = array() ) {
|
|
|
|
|
$status = $this->newGood();
|
2011-12-22 01:06:19 +00:00
|
|
|
foreach ( (array)$doZones as $zone ) {
|
2011-12-20 03:52:06 +00:00
|
|
|
$root = $this->getZonePath( $zone );
|
2011-12-22 01:06:19 +00:00
|
|
|
if ( $root === null ) {
|
2012-01-03 19:28:04 +00:00
|
|
|
throw new MWException( "No '$zone' zone defined in the {$this->name} repo." );
|
2011-12-20 03:52:06 +00:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
return $status;
|
|
|
|
|
}
|
|
|
|
|
|
2007-06-16 02:55:25 +00:00
|
|
|
/**
|
|
|
|
|
* Determine if a string is an mwrepo:// URL
|
2011-05-28 18:58:51 +00:00
|
|
|
*
|
|
|
|
|
* @param $url string
|
|
|
|
|
* @return bool
|
2007-06-16 02:55:25 +00:00
|
|
|
*/
|
2011-12-20 03:52:06 +00:00
|
|
|
public static function isVirtualUrl( $url ) {
|
2007-06-16 02:55:25 +00:00
|
|
|
return substr( $url, 0, 9 ) == 'mwrepo://';
|
|
|
|
|
}
|
|
|
|
|
|
2011-12-20 03:52:06 +00:00
|
|
|
/**
|
|
|
|
|
* Get a URL referring to this repository, with the private mwrepo protocol.
|
|
|
|
|
* The suffix, if supplied, is considered to be unencoded, and will be
|
|
|
|
|
* URL-encoded before being returned.
|
|
|
|
|
*
|
2012-05-18 02:58:15 +00:00
|
|
|
* @param $suffix string|bool
|
2011-12-20 03:52:06 +00:00
|
|
|
* @return string
|
|
|
|
|
*/
|
|
|
|
|
public function getVirtualUrl( $suffix = false ) {
|
|
|
|
|
$path = 'mwrepo://' . $this->name;
|
|
|
|
|
if ( $suffix !== false ) {
|
|
|
|
|
$path .= '/' . rawurlencode( $suffix );
|
|
|
|
|
}
|
|
|
|
|
return $path;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Get the URL corresponding to one of the four basic zones
|
|
|
|
|
*
|
2013-03-11 17:15:01 +00:00
|
|
|
* @param string $zone One of: public, deleted, temp, thumb
|
|
|
|
|
* @param string|null $ext Optional file extension
|
2011-12-20 03:52:06 +00:00
|
|
|
* @return String or false
|
|
|
|
|
*/
|
2012-10-13 00:24:31 +00:00
|
|
|
public function getZoneUrl( $zone, $ext = null ) {
|
2013-01-15 23:30:41 +00:00
|
|
|
if ( in_array( $zone, array( 'public', 'temp', 'thumb', 'transcoded' ) ) ) { // standard public zones
|
2012-10-13 00:24:31 +00:00
|
|
|
if ( $ext !== null && isset( $this->zones[$zone]['urlsByExt'][$ext] ) ) {
|
|
|
|
|
return $this->zones[$zone]['urlsByExt'][$ext]; // custom URL for extension/zone
|
|
|
|
|
} elseif ( isset( $this->zones[$zone]['url'] ) ) {
|
|
|
|
|
return $this->zones[$zone]['url']; // custom URL for zone
|
|
|
|
|
}
|
2012-05-10 18:15:29 +00:00
|
|
|
}
|
2011-12-20 03:52:06 +00:00
|
|
|
switch ( $zone ) {
|
|
|
|
|
case 'public':
|
|
|
|
|
return $this->url;
|
|
|
|
|
case 'temp':
|
|
|
|
|
return "{$this->url}/temp";
|
|
|
|
|
case 'deleted':
|
|
|
|
|
return false; // no public URL
|
|
|
|
|
case 'thumb':
|
|
|
|
|
return $this->thumbUrl;
|
2013-01-15 23:30:41 +00:00
|
|
|
case 'transcoded':
|
|
|
|
|
return "{$this->url}/transcoded";
|
2011-12-20 03:52:06 +00:00
|
|
|
default:
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2012-07-26 19:00:22 +00:00
|
|
|
/**
|
|
|
|
|
* Get the thumb zone URL configured to be handled by scripts like thumb_handler.php.
|
|
|
|
|
* This is probably only useful for internal requests, such as from a fast frontend server
|
|
|
|
|
* to a slower backend server.
|
|
|
|
|
*
|
|
|
|
|
* Large sites may use a different host name for uploads than for wikis. In any case, the
|
|
|
|
|
* wiki configuration is needed in order to use thumb.php. To avoid extracting the wiki ID
|
|
|
|
|
* from the URL path, one can configure thumb_handler.php to recognize a special path on the
|
|
|
|
|
* same host name as the wiki that is used for viewing thumbnails.
|
|
|
|
|
*
|
2013-03-11 17:15:01 +00:00
|
|
|
* @param string $zone one of: public, deleted, temp, thumb
|
2012-07-26 19:00:22 +00:00
|
|
|
* @return String or false
|
|
|
|
|
*/
|
|
|
|
|
public function getZoneHandlerUrl( $zone ) {
|
|
|
|
|
if ( isset( $this->zones[$zone]['handlerUrl'] )
|
2013-01-15 23:30:41 +00:00
|
|
|
&& in_array( $zone, array( 'public', 'temp', 'thumb', 'transcoded' ) ) )
|
2012-07-26 19:00:22 +00:00
|
|
|
{
|
|
|
|
|
return $this->zones[$zone]['handlerUrl'];
|
|
|
|
|
}
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
|
2011-12-20 03:52:06 +00:00
|
|
|
/**
|
2012-04-05 04:10:50 +00:00
|
|
|
* Get the backend storage path corresponding to a virtual URL.
|
|
|
|
|
* Use this function wisely.
|
2011-12-20 03:52:06 +00:00
|
|
|
*
|
|
|
|
|
* @param $url string
|
2012-05-18 02:58:15 +00:00
|
|
|
* @throws MWException
|
2011-12-20 03:52:06 +00:00
|
|
|
* @return string
|
|
|
|
|
*/
|
2012-04-05 04:10:50 +00:00
|
|
|
public function resolveVirtualUrl( $url ) {
|
2011-12-20 03:52:06 +00:00
|
|
|
if ( substr( $url, 0, 9 ) != 'mwrepo://' ) {
|
2013-04-13 11:36:24 +00:00
|
|
|
throw new MWException( __METHOD__ . ': unknown protocol' );
|
2011-12-20 03:52:06 +00:00
|
|
|
}
|
|
|
|
|
$bits = explode( '/', substr( $url, 9 ), 3 );
|
|
|
|
|
if ( count( $bits ) != 3 ) {
|
2013-04-13 11:36:24 +00:00
|
|
|
throw new MWException( __METHOD__ . ": invalid mwrepo URL: $url" );
|
2011-12-20 03:52:06 +00:00
|
|
|
}
|
|
|
|
|
list( $repo, $zone, $rel ) = $bits;
|
|
|
|
|
if ( $repo !== $this->name ) {
|
2013-04-13 11:36:24 +00:00
|
|
|
throw new MWException( __METHOD__ . ": fetching from a foreign repo is not supported" );
|
2011-12-20 03:52:06 +00:00
|
|
|
}
|
|
|
|
|
$base = $this->getZonePath( $zone );
|
|
|
|
|
if ( !$base ) {
|
2013-04-13 11:36:24 +00:00
|
|
|
throw new MWException( __METHOD__ . ": invalid zone: $zone" );
|
2011-12-20 03:52:06 +00:00
|
|
|
}
|
|
|
|
|
return $base . '/' . rawurldecode( $rel );
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* The the storage container and base path of a zone
|
2012-04-05 04:10:50 +00:00
|
|
|
*
|
2011-12-20 03:52:06 +00:00
|
|
|
* @param $zone string
|
|
|
|
|
* @return Array (container, base path) or (null, null)
|
|
|
|
|
*/
|
|
|
|
|
protected function getZoneLocation( $zone ) {
|
|
|
|
|
if ( !isset( $this->zones[$zone] ) ) {
|
|
|
|
|
return array( null, null ); // bogus
|
|
|
|
|
}
|
|
|
|
|
return array( $this->zones[$zone]['container'], $this->zones[$zone]['directory'] );
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Get the storage path corresponding to one of the zones
|
|
|
|
|
*
|
|
|
|
|
* @param $zone string
|
2012-04-05 04:10:50 +00:00
|
|
|
* @return string|null Returns null if the zone is not defined
|
2011-12-20 03:52:06 +00:00
|
|
|
*/
|
|
|
|
|
public function getZonePath( $zone ) {
|
|
|
|
|
list( $container, $base ) = $this->getZoneLocation( $zone );
|
|
|
|
|
if ( $container === null || $base === null ) {
|
|
|
|
|
return null;
|
|
|
|
|
}
|
|
|
|
|
$backendName = $this->backend->getName();
|
|
|
|
|
if ( $base != '' ) { // may not be set
|
|
|
|
|
$base = "/{$base}";
|
|
|
|
|
}
|
|
|
|
|
return "mwstore://$backendName/{$container}{$base}";
|
|
|
|
|
}
|
|
|
|
|
|
2007-06-16 02:55:25 +00:00
|
|
|
/**
|
|
|
|
|
* Create a new File object from the local repository
|
2010-03-26 21:55:13 +00:00
|
|
|
*
|
|
|
|
|
* @param $title Mixed: Title object or string
|
|
|
|
|
* @param $time Mixed: Time at which the image was uploaded.
|
|
|
|
|
* 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.
|
2011-11-11 22:14:21 +00:00
|
|
|
* @return File|null A File, or null if passed an invalid Title
|
2007-06-16 02:55:25 +00:00
|
|
|
*/
|
2011-12-20 03:52:06 +00:00
|
|
|
public function newFile( $title, $time = false ) {
|
2011-11-11 22:14:21 +00:00
|
|
|
$title = File::normalizeTitle( $title );
|
|
|
|
|
if ( !$title ) {
|
|
|
|
|
return null;
|
2007-06-16 02:55:25 +00:00
|
|
|
}
|
|
|
|
|
if ( $time ) {
|
|
|
|
|
if ( $this->oldFileFactory ) {
|
|
|
|
|
return call_user_func( $this->oldFileFactory, $title, $this, $time );
|
|
|
|
|
} else {
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
} else {
|
|
|
|
|
return call_user_func( $this->fileFactory, $title, $this );
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
2008-04-05 15:38:08 +00:00
|
|
|
* Find an instance of the named file created at the specified time
|
2008-04-14 07:45:50 +00:00
|
|
|
* Returns false if the file does not exist. Repositories not supporting
|
2007-06-16 02:55:25 +00:00
|
|
|
* version control should return false if the time is specified.
|
|
|
|
|
*
|
2010-03-26 21:55:13 +00:00
|
|
|
* @param $title Mixed: Title object or string
|
2013-03-11 17:15:01 +00:00
|
|
|
* @param array $options Associative array of options:
|
2012-04-05 04:10:50 +00:00
|
|
|
* time: requested time for a specific file version, or false for the
|
2009-08-15 09:59:59 +00:00
|
|
|
* current version. An image object will be returned which was
|
2012-04-05 04:10:50 +00:00
|
|
|
* created at the specified time (which may be archived or current).
|
2009-08-15 09:59:59 +00:00
|
|
|
*
|
|
|
|
|
* ignoreRedirect: If true, do not follow file redirects
|
|
|
|
|
*
|
2010-02-10 10:36:11 +00:00
|
|
|
* private: If true, return restricted (deleted) files if the current
|
2009-08-15 09:59:59 +00:00
|
|
|
* user is allowed to view them. Otherwise, such files will not
|
|
|
|
|
* be found.
|
2012-02-10 15:37:33 +00:00
|
|
|
* @return File|bool False on failure
|
2007-06-16 02:55:25 +00:00
|
|
|
*/
|
2011-12-20 03:52:06 +00:00
|
|
|
public function findFile( $title, $options = array() ) {
|
2011-11-11 22:14:21 +00:00
|
|
|
$title = File::normalizeTitle( $title );
|
|
|
|
|
if ( !$title ) {
|
|
|
|
|
return false;
|
2008-04-06 10:18:47 +00:00
|
|
|
}
|
2011-11-11 22:14:21 +00:00
|
|
|
$time = isset( $options['time'] ) ? $options['time'] : false;
|
2007-06-16 02:55:25 +00:00
|
|
|
# First try the current version of the file to see if it precedes the timestamp
|
|
|
|
|
$img = $this->newFile( $title );
|
2007-10-01 19:50:25 +00:00
|
|
|
if ( !$img ) {
|
|
|
|
|
return false;
|
|
|
|
|
}
|
2008-02-04 08:18:55 +00:00
|
|
|
if ( $img->exists() && ( !$time || $img->getTimestamp() == $time ) ) {
|
2007-06-16 02:55:25 +00:00
|
|
|
return $img;
|
|
|
|
|
}
|
|
|
|
|
# Now try an old version of the file
|
2008-05-07 03:39:35 +00:00
|
|
|
if ( $time !== false ) {
|
|
|
|
|
$img = $this->newFile( $title, $time );
|
2008-09-07 00:38:57 +00:00
|
|
|
if ( $img && $img->exists() ) {
|
2011-10-19 04:06:16 +00:00
|
|
|
if ( !$img->isDeleted( File::DELETED_FILE ) ) {
|
|
|
|
|
return $img; // always OK
|
|
|
|
|
} elseif ( !empty( $options['private'] ) && $img->userCan( File::DELETED_FILE ) ) {
|
2008-05-07 03:39:35 +00:00
|
|
|
return $img;
|
|
|
|
|
}
|
2008-03-09 03:10:28 +00:00
|
|
|
}
|
2007-06-16 02:55:25 +00:00
|
|
|
}
|
2010-02-10 10:36:11 +00:00
|
|
|
|
2008-01-16 18:27:43 +00:00
|
|
|
# Now try redirects
|
2009-08-15 09:59:59 +00:00
|
|
|
if ( !empty( $options['ignoreRedirect'] ) ) {
|
2008-05-20 02:58:40 +00:00
|
|
|
return false;
|
|
|
|
|
}
|
2010-02-10 10:36:11 +00:00
|
|
|
$redir = $this->checkRedirect( $title );
|
2013-04-27 12:02:08 +00:00
|
|
|
if ( $redir && $title->getNamespace() == NS_FILE ) {
|
2008-01-16 18:27:43 +00:00
|
|
|
$img = $this->newFile( $redir );
|
2011-12-20 03:52:06 +00:00
|
|
|
if ( !$img ) {
|
2008-01-16 18:27:43 +00:00
|
|
|
return false;
|
|
|
|
|
}
|
2011-12-20 03:52:06 +00:00
|
|
|
if ( $img->exists() ) {
|
2008-05-08 20:55:13 +00:00
|
|
|
$img->redirectedFrom( $title->getDBkey() );
|
2008-01-16 18:27:43 +00:00
|
|
|
return $img;
|
|
|
|
|
}
|
|
|
|
|
}
|
2008-03-09 03:10:28 +00:00
|
|
|
return false;
|
2007-06-16 02:55:25 +00:00
|
|
|
}
|
2010-02-10 10:36:11 +00:00
|
|
|
|
2011-05-21 19:35:16 +00:00
|
|
|
/**
|
2010-02-10 10:36:11 +00:00
|
|
|
* Find many files at once.
|
2011-12-20 03:52:06 +00:00
|
|
|
*
|
2013-03-11 17:15:01 +00:00
|
|
|
* @param array $items An array of titles, or an array of findFile() options with
|
2009-08-15 09:59:59 +00:00
|
|
|
* the "title" option giving the title. Example:
|
|
|
|
|
*
|
|
|
|
|
* $findItem = array( 'title' => $title, 'private' => true );
|
|
|
|
|
* $findBatch = array( $findItem );
|
|
|
|
|
* $repo->findFiles( $findBatch );
|
2011-09-07 12:00:58 +00:00
|
|
|
* @return array
|
2008-05-20 17:05:57 +00:00
|
|
|
*/
|
2012-04-05 04:10:50 +00:00
|
|
|
public function findFiles( array $items ) {
|
2008-05-20 17:05:57 +00:00
|
|
|
$result = array();
|
2010-10-12 22:48:22 +00:00
|
|
|
foreach ( $items as $item ) {
|
2009-08-15 09:59:59 +00:00
|
|
|
if ( is_array( $item ) ) {
|
|
|
|
|
$title = $item['title'];
|
|
|
|
|
$options = $item;
|
|
|
|
|
unset( $options['title'] );
|
|
|
|
|
} else {
|
|
|
|
|
$title = $item;
|
|
|
|
|
$options = array();
|
|
|
|
|
}
|
|
|
|
|
$file = $this->findFile( $title, $options );
|
2010-10-12 22:48:22 +00:00
|
|
|
if ( $file ) {
|
2008-05-20 17:05:57 +00:00
|
|
|
$result[$file->getTitle()->getDBkey()] = $file;
|
2010-10-12 22:48:22 +00:00
|
|
|
}
|
2008-05-20 17:05:57 +00:00
|
|
|
}
|
|
|
|
|
return $result;
|
|
|
|
|
}
|
2010-02-10 10:36:11 +00:00
|
|
|
|
2011-03-28 21:40:50 +00:00
|
|
|
/**
|
|
|
|
|
* Find an instance of the file with this key, created at the specified time
|
|
|
|
|
* Returns false if the file does not exist. Repositories not supporting
|
|
|
|
|
* version control should return false if the time is specified.
|
|
|
|
|
*
|
2013-03-11 17:15:01 +00:00
|
|
|
* @param string $sha1 base 36 SHA-1 hash
|
|
|
|
|
* @param array $options Option array, same as findFile().
|
2012-02-10 15:37:33 +00:00
|
|
|
* @return File|bool False on failure
|
2011-03-28 21:40:50 +00:00
|
|
|
*/
|
2011-12-20 03:52:06 +00:00
|
|
|
public function findFileFromKey( $sha1, $options = array() ) {
|
2011-03-28 21:40:50 +00:00
|
|
|
$time = isset( $options['time'] ) ? $options['time'] : false;
|
2011-10-19 04:06:16 +00:00
|
|
|
# First try to find a matching current version of a file...
|
|
|
|
|
if ( $this->fileFactoryKey ) {
|
|
|
|
|
$img = call_user_func( $this->fileFactoryKey, $sha1, $this, $time );
|
|
|
|
|
} else {
|
|
|
|
|
return false; // find-by-sha1 not supported
|
2011-03-28 21:40:50 +00:00
|
|
|
}
|
2011-10-19 04:06:16 +00:00
|
|
|
if ( $img && $img->exists() ) {
|
2011-03-28 21:40:50 +00:00
|
|
|
return $img;
|
|
|
|
|
}
|
2011-10-19 04:06:16 +00:00
|
|
|
# Now try to find a matching old version of a file...
|
|
|
|
|
if ( $time !== false && $this->oldFileFactoryKey ) { // find-by-sha1 supported?
|
|
|
|
|
$img = call_user_func( $this->oldFileFactoryKey, $sha1, $this, $time );
|
2011-03-28 21:40:50 +00:00
|
|
|
if ( $img && $img->exists() ) {
|
2011-10-19 04:06:16 +00:00
|
|
|
if ( !$img->isDeleted( File::DELETED_FILE ) ) {
|
|
|
|
|
return $img; // always OK
|
|
|
|
|
} elseif ( !empty( $options['private'] ) && $img->userCan( File::DELETED_FILE ) ) {
|
2011-03-28 21:40:50 +00:00
|
|
|
return $img;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
|
2007-06-16 02:55:25 +00:00
|
|
|
/**
|
2011-12-20 03:52:06 +00:00
|
|
|
* Get an array or iterator of file objects for files that have a given
|
|
|
|
|
* SHA-1 content hash.
|
|
|
|
|
*
|
|
|
|
|
* STUB
|
2012-05-18 02:58:15 +00:00
|
|
|
* @param $hash
|
2012-02-09 21:33:27 +00:00
|
|
|
* @return array
|
2007-06-16 02:55:25 +00:00
|
|
|
*/
|
2011-12-20 03:52:06 +00:00
|
|
|
public function findBySha1( $hash ) {
|
|
|
|
|
return array();
|
2007-06-16 02:55:25 +00:00
|
|
|
}
|
2010-02-10 10:36:11 +00:00
|
|
|
|
2012-07-23 16:45:38 +00:00
|
|
|
/**
|
|
|
|
|
* Get an array of arrays or iterators of file objects for files that
|
|
|
|
|
* have the given SHA-1 content hashes.
|
|
|
|
|
*
|
2013-03-11 17:15:01 +00:00
|
|
|
* @param array $hashes An array of hashes
|
2012-07-23 16:45:38 +00:00
|
|
|
* @return array An Array of arrays or iterators of file objects and the hash as key
|
|
|
|
|
*/
|
|
|
|
|
public function findBySha1s( array $hashes ) {
|
|
|
|
|
$result = array();
|
|
|
|
|
foreach ( $hashes as $hash ) {
|
|
|
|
|
$files = $this->findBySha1( $hash );
|
|
|
|
|
if ( count( $files ) ) {
|
|
|
|
|
$result[$hash] = $files;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
return $result;
|
|
|
|
|
}
|
|
|
|
|
|
2013-02-06 20:15:07 +00:00
|
|
|
/**
|
|
|
|
|
* Return an array of files where the name starts with $prefix.
|
|
|
|
|
*
|
|
|
|
|
* STUB
|
|
|
|
|
* @param string $prefix The prefix to search for
|
|
|
|
|
* @param int $limit The maximum amount of files to return
|
|
|
|
|
* @return array
|
|
|
|
|
*/
|
|
|
|
|
public function findFilesByPrefix( $prefix, $limit ) {
|
|
|
|
|
return array();
|
|
|
|
|
}
|
|
|
|
|
|
2009-10-09 11:41:38 +00:00
|
|
|
/**
|
2011-12-20 03:52:06 +00:00
|
|
|
* Get the public root URL of the repository
|
|
|
|
|
*
|
2012-05-10 18:15:29 +00:00
|
|
|
* @deprecated since 1.20
|
2012-02-10 15:37:33 +00:00
|
|
|
* @return string
|
2009-10-09 11:41:38 +00:00
|
|
|
*/
|
2011-12-20 03:52:06 +00:00
|
|
|
public function getRootUrl() {
|
2012-05-10 18:15:29 +00:00
|
|
|
return $this->getZoneUrl( 'public' );
|
2011-12-20 03:52:06 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Get the URL of thumb.php
|
|
|
|
|
*
|
|
|
|
|
* @return string
|
|
|
|
|
*/
|
|
|
|
|
public function getThumbScriptUrl() {
|
|
|
|
|
return $this->thumbScriptUrl;
|
2009-10-09 11:41:38 +00:00
|
|
|
}
|
2007-06-16 02:55:25 +00:00
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Returns true if the repository can transform files via a 404 handler
|
2011-09-07 12:00:58 +00:00
|
|
|
*
|
|
|
|
|
* @return bool
|
2007-06-16 02:55:25 +00:00
|
|
|
*/
|
2011-12-20 03:52:06 +00:00
|
|
|
public function canTransformVia404() {
|
2007-06-16 02:55:25 +00:00
|
|
|
return $this->transformVia404;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Get the name of an image from its title object
|
2011-12-20 03:52:06 +00:00
|
|
|
*
|
2011-02-18 23:56:08 +00:00
|
|
|
* @param $title Title
|
2012-02-09 21:33:27 +00:00
|
|
|
* @return String
|
2007-06-16 02:55:25 +00:00
|
|
|
*/
|
2011-12-20 03:52:06 +00:00
|
|
|
public function getNameFromTitle( Title $title ) {
|
|
|
|
|
global $wgContLang;
|
2009-10-09 12:52:16 +00:00
|
|
|
if ( $this->initialCapital != MWNamespace::isCapitalized( NS_FILE ) ) {
|
2007-06-16 02:55:25 +00:00
|
|
|
$name = $title->getUserCaseDBKey();
|
|
|
|
|
if ( $this->initialCapital ) {
|
|
|
|
|
$name = $wgContLang->ucfirst( $name );
|
|
|
|
|
}
|
|
|
|
|
} else {
|
|
|
|
|
$name = $title->getDBkey();
|
|
|
|
|
}
|
|
|
|
|
return $name;
|
|
|
|
|
}
|
|
|
|
|
|
2011-12-20 03:52:06 +00:00
|
|
|
/**
|
|
|
|
|
* Get the public zone root storage directory of the repository
|
|
|
|
|
*
|
|
|
|
|
* @return string
|
|
|
|
|
*/
|
|
|
|
|
public function getRootDirectory() {
|
|
|
|
|
return $this->getZonePath( 'public' );
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Get a relative path including trailing slash, e.g. f/fa/
|
|
|
|
|
* If the repo is not hashed, returns an empty string
|
|
|
|
|
*
|
2013-03-11 17:15:01 +00:00
|
|
|
* @param string $name Name of file
|
2011-12-20 03:52:06 +00:00
|
|
|
* @return string
|
|
|
|
|
*/
|
|
|
|
|
public function getHashPath( $name ) {
|
|
|
|
|
return self::getHashPathForLevel( $name, $this->hashLevels );
|
|
|
|
|
}
|
|
|
|
|
|
2012-04-06 17:11:13 +00:00
|
|
|
/**
|
|
|
|
|
* Get a relative path including trailing slash, e.g. f/fa/
|
|
|
|
|
* If the repo is not hashed, returns an empty string
|
|
|
|
|
*
|
2013-03-11 17:15:01 +00:00
|
|
|
* @param string $suffix Basename of file from FileRepo::storeTemp()
|
2012-04-06 17:11:13 +00:00
|
|
|
* @return string
|
|
|
|
|
*/
|
|
|
|
|
public function getTempHashPath( $suffix ) {
|
|
|
|
|
$parts = explode( '!', $suffix, 2 ); // format is <timestamp>!<name> or just <name>
|
|
|
|
|
$name = isset( $parts[1] ) ? $parts[1] : $suffix; // hash path is not based on timestamp
|
|
|
|
|
return self::getHashPathForLevel( $name, $this->hashLevels );
|
|
|
|
|
}
|
|
|
|
|
|
2011-09-07 12:00:58 +00:00
|
|
|
/**
|
|
|
|
|
* @param $name
|
|
|
|
|
* @param $levels
|
|
|
|
|
* @return string
|
|
|
|
|
*/
|
2012-04-05 04:10:50 +00:00
|
|
|
protected static function getHashPathForLevel( $name, $levels ) {
|
2007-06-16 02:55:25 +00:00
|
|
|
if ( $levels == 0 ) {
|
|
|
|
|
return '';
|
|
|
|
|
} else {
|
|
|
|
|
$hash = md5( $name );
|
|
|
|
|
$path = '';
|
|
|
|
|
for ( $i = 1; $i <= $levels; $i++ ) {
|
|
|
|
|
$path .= substr( $hash, 0, $i ) . '/';
|
|
|
|
|
}
|
|
|
|
|
return $path;
|
|
|
|
|
}
|
|
|
|
|
}
|
2010-02-10 10:36:11 +00:00
|
|
|
|
2011-12-08 03:43:07 +00:00
|
|
|
/**
|
|
|
|
|
* Get the number of hash directory levels
|
|
|
|
|
*
|
|
|
|
|
* @return integer
|
|
|
|
|
*/
|
2011-12-20 03:52:06 +00:00
|
|
|
public function getHashLevels() {
|
2011-12-08 03:43:07 +00:00
|
|
|
return $this->hashLevels;
|
|
|
|
|
}
|
|
|
|
|
|
2008-11-08 22:20:23 +00:00
|
|
|
/**
|
2011-12-20 03:52:06 +00:00
|
|
|
* Get the name of this repository, as specified by $info['name]' to the constructor
|
2011-09-07 12:00:58 +00:00
|
|
|
*
|
|
|
|
|
* @return string
|
2008-11-08 22:20:23 +00:00
|
|
|
*/
|
2011-12-20 03:52:06 +00:00
|
|
|
public function getName() {
|
2007-06-16 02:55:25 +00:00
|
|
|
return $this->name;
|
|
|
|
|
}
|
2011-06-17 16:03:52 +00:00
|
|
|
|
2010-07-02 18:37:06 +00:00
|
|
|
/**
|
|
|
|
|
* Make an url to this repo
|
2011-06-17 16:03:52 +00:00
|
|
|
*
|
2010-07-02 18:37:06 +00:00
|
|
|
* @param $query mixed Query string to append
|
2013-03-11 17:15:01 +00:00
|
|
|
* @param string $entry Entry point; defaults to index
|
2012-02-10 15:37:33 +00:00
|
|
|
* @return string|bool False on failure
|
2010-07-02 18:37:06 +00:00
|
|
|
*/
|
2011-12-20 03:52:06 +00:00
|
|
|
public function makeUrl( $query = '', $entry = 'index' ) {
|
2011-12-21 21:29:16 +00:00
|
|
|
if ( isset( $this->scriptDirUrl ) ) {
|
|
|
|
|
$ext = isset( $this->scriptExtension ) ? $this->scriptExtension : '.php';
|
|
|
|
|
return wfAppendQuery( "{$this->scriptDirUrl}/{$entry}{$ext}", $query );
|
|
|
|
|
}
|
|
|
|
|
return false;
|
2011-06-17 16:03:52 +00:00
|
|
|
}
|
2007-06-16 02:55:25 +00:00
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Get the URL of an image description page. May return false if it is
|
2008-04-14 07:45:50 +00:00
|
|
|
* 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
|
2007-06-16 02:55:25 +00:00
|
|
|
* 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.
|
2011-12-20 03:52:06 +00:00
|
|
|
*
|
|
|
|
|
* @param $name string
|
|
|
|
|
* @return string
|
2007-06-16 02:55:25 +00:00
|
|
|
*/
|
2011-12-20 03:52:06 +00:00
|
|
|
public function getDescriptionUrl( $name ) {
|
2008-12-10 22:07:04 +00:00
|
|
|
$encName = wfUrlencode( $name );
|
|
|
|
|
if ( !is_null( $this->descBaseUrl ) ) {
|
|
|
|
|
# "http://example.com/wiki/Image:"
|
|
|
|
|
return $this->descBaseUrl . $encName;
|
2007-06-16 02:55:25 +00:00
|
|
|
}
|
2008-12-10 22:07:04 +00:00
|
|
|
if ( !is_null( $this->articleUrl ) ) {
|
|
|
|
|
# "http://example.com/wiki/$1"
|
|
|
|
|
#
|
|
|
|
|
# We use "Image:" as the canonical namespace for
|
|
|
|
|
# compatibility across all MediaWiki versions.
|
|
|
|
|
return str_replace( '$1',
|
|
|
|
|
"Image:$encName", $this->articleUrl );
|
|
|
|
|
}
|
|
|
|
|
if ( !is_null( $this->scriptDirUrl ) ) {
|
|
|
|
|
# "http://example.com/w"
|
|
|
|
|
#
|
|
|
|
|
# We use "Image:" as the canonical namespace for
|
|
|
|
|
# compatibility across all MediaWiki versions,
|
|
|
|
|
# and just sort of hope index.php is right. ;)
|
2010-07-02 18:37:06 +00:00
|
|
|
return $this->makeUrl( "title=Image:$encName" );
|
2008-12-10 22:07:04 +00:00
|
|
|
}
|
|
|
|
|
return false;
|
2007-06-16 02:55:25 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
2008-04-14 07:45:50 +00:00
|
|
|
* 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
|
2007-06-16 02:55:25 +00:00
|
|
|
* should use File::getDescriptionText().
|
2011-12-20 03:52:06 +00:00
|
|
|
*
|
2013-03-11 17:15:01 +00:00
|
|
|
* @param string $name name of image to fetch
|
|
|
|
|
* @param string $lang language to fetch it in, if any.
|
2011-12-20 03:52:06 +00:00
|
|
|
* @return string
|
2007-06-16 02:55:25 +00:00
|
|
|
*/
|
2011-12-20 03:52:06 +00:00
|
|
|
public function getDescriptionRenderUrl( $name, $lang = null ) {
|
2009-01-27 19:34:21 +00:00
|
|
|
$query = 'action=render';
|
|
|
|
|
if ( !is_null( $lang ) ) {
|
|
|
|
|
$query .= '&uselang=' . $lang;
|
|
|
|
|
}
|
2007-06-16 02:55:25 +00:00
|
|
|
if ( isset( $this->scriptDirUrl ) ) {
|
2011-06-17 16:03:52 +00:00
|
|
|
return $this->makeUrl(
|
2010-07-02 18:37:06 +00:00
|
|
|
'title=' .
|
2008-12-04 18:23:52 +00:00
|
|
|
wfUrlencode( 'Image:' . $name ) .
|
2010-07-02 18:37:06 +00:00
|
|
|
"&$query" );
|
2007-06-16 02:55:25 +00:00
|
|
|
} else {
|
2008-12-10 22:07:04 +00:00
|
|
|
$descUrl = $this->getDescriptionUrl( $name );
|
|
|
|
|
if ( $descUrl ) {
|
2009-01-27 19:34:21 +00:00
|
|
|
return wfAppendQuery( $descUrl, $query );
|
2007-06-16 02:55:25 +00:00
|
|
|
} else {
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
2011-06-17 16:03:52 +00:00
|
|
|
|
2010-07-02 19:54:46 +00:00
|
|
|
/**
|
|
|
|
|
* Get the URL of the stylesheet to apply to description pages
|
2011-12-20 03:52:06 +00:00
|
|
|
*
|
2012-02-10 15:37:33 +00:00
|
|
|
* @return string|bool False on failure
|
2010-07-02 19:54:46 +00:00
|
|
|
*/
|
2011-12-20 03:52:06 +00:00
|
|
|
public function getDescriptionStylesheetUrl() {
|
2011-12-21 21:29:16 +00:00
|
|
|
if ( isset( $this->scriptDirUrl ) ) {
|
2010-07-25 21:15:27 +00:00
|
|
|
return $this->makeUrl( 'title=MediaWiki:Filepage.css&' .
|
2013-01-28 18:04:20 +00:00
|
|
|
wfArrayToCgi( Skin::getDynamicStylesheetQuery() ) );
|
2010-07-02 19:54:46 +00:00
|
|
|
}
|
2011-12-21 21:29:16 +00:00
|
|
|
return false;
|
2010-07-02 19:54:46 +00:00
|
|
|
}
|
2007-06-16 02:55:25 +00:00
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Store a file to a given destination.
|
2007-07-22 14:45:12 +00:00
|
|
|
*
|
2013-03-11 17:15:01 +00:00
|
|
|
* @param string $srcPath source file system path, storage path, or virtual URL
|
|
|
|
|
* @param string $dstZone destination zone
|
|
|
|
|
* @param string $dstRel destination relative path
|
2010-03-26 21:55:13 +00:00
|
|
|
* @param $flags Integer: bitwise combination of the following flags:
|
2007-07-22 14:45:12 +00:00
|
|
|
* self::DELETE_SOURCE Delete the source file after upload
|
|
|
|
|
* self::OVERWRITE Overwrite an existing destination file instead of failing
|
2008-04-14 07:45:50 +00:00
|
|
|
* self::OVERWRITE_SAME Overwrite the file if the destination exists and has the
|
2007-07-22 14:45:12 +00:00
|
|
|
* same contents as the source
|
2011-12-21 20:39:50 +00:00
|
|
|
* self::SKIP_LOCKING Skip any file locking when doing the store
|
2007-07-22 14:45:12 +00:00
|
|
|
* @return FileRepoStatus
|
|
|
|
|
*/
|
2011-12-20 03:52:06 +00:00
|
|
|
public function store( $srcPath, $dstZone, $dstRel, $flags = 0 ) {
|
2012-04-05 04:10:50 +00:00
|
|
|
$this->assertWritableRepo(); // fail out if read-only
|
|
|
|
|
|
2007-07-22 14:45:12 +00:00
|
|
|
$status = $this->storeBatch( array( array( $srcPath, $dstZone, $dstRel ) ), $flags );
|
|
|
|
|
if ( $status->successCount == 0 ) {
|
|
|
|
|
$status->ok = false;
|
|
|
|
|
}
|
2012-04-05 04:10:50 +00:00
|
|
|
|
2007-07-22 14:45:12 +00:00
|
|
|
return $status;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Store a batch of files
|
|
|
|
|
*
|
2013-03-11 17:15:01 +00:00
|
|
|
* @param array $triplets (src, dest zone, dest rel) triplets as per store()
|
2011-12-20 03:52:06 +00:00
|
|
|
* @param $flags Integer: bitwise combination of the following flags:
|
|
|
|
|
* self::DELETE_SOURCE Delete the source file after upload
|
|
|
|
|
* self::OVERWRITE Overwrite an existing destination file instead of failing
|
|
|
|
|
* self::OVERWRITE_SAME Overwrite the file if the destination exists and has the
|
|
|
|
|
* same contents as the source
|
2011-12-21 20:39:50 +00:00
|
|
|
* self::SKIP_LOCKING Skip any file locking when doing the store
|
2012-05-18 02:58:15 +00:00
|
|
|
* @throws MWException
|
2011-12-20 03:52:06 +00:00
|
|
|
* @return FileRepoStatus
|
2007-06-16 02:55:25 +00:00
|
|
|
*/
|
2012-04-05 04:10:50 +00:00
|
|
|
public function storeBatch( array $triplets, $flags = 0 ) {
|
|
|
|
|
$this->assertWritableRepo(); // fail out if read-only
|
2011-12-20 03:52:06 +00:00
|
|
|
|
|
|
|
|
$status = $this->newGood();
|
2012-04-05 04:10:50 +00:00
|
|
|
$backend = $this->backend; // convenience
|
2011-12-20 03:52:06 +00:00
|
|
|
|
|
|
|
|
$operations = array();
|
|
|
|
|
$sourceFSFilesToDelete = array(); // cleanup for disk source files
|
|
|
|
|
// Validate each triplet and get the store operation...
|
2012-01-01 23:33:33 +00:00
|
|
|
foreach ( $triplets as $triplet ) {
|
2011-12-20 03:52:06 +00:00
|
|
|
list( $srcPath, $dstZone, $dstRel ) = $triplet;
|
2012-02-08 10:00:02 +00:00
|
|
|
wfDebug( __METHOD__
|
|
|
|
|
. "( \$src='$srcPath', \$dstZone='$dstZone', \$dstRel='$dstRel' )\n"
|
|
|
|
|
);
|
2011-12-20 03:52:06 +00:00
|
|
|
|
|
|
|
|
// Resolve destination path
|
|
|
|
|
$root = $this->getZonePath( $dstZone );
|
|
|
|
|
if ( !$root ) {
|
|
|
|
|
throw new MWException( "Invalid zone: $dstZone" );
|
|
|
|
|
}
|
|
|
|
|
if ( !$this->validateFilename( $dstRel ) ) {
|
|
|
|
|
throw new MWException( 'Validation error in $dstRel' );
|
|
|
|
|
}
|
|
|
|
|
$dstPath = "$root/$dstRel";
|
2013-03-07 16:50:43 +00:00
|
|
|
$dstDir = dirname( $dstPath );
|
2011-12-20 03:52:06 +00:00
|
|
|
// Create destination directories for this triplet
|
2012-05-21 22:19:06 +00:00
|
|
|
if ( !$this->initDirectory( $dstDir )->isOK() ) {
|
2011-12-20 03:52:06 +00:00
|
|
|
return $this->newFatal( 'directorycreateerror', $dstDir );
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Resolve source to a storage path if virtual
|
2012-04-17 17:56:35 +00:00
|
|
|
$srcPath = $this->resolveToStoragePath( $srcPath );
|
2011-12-20 03:52:06 +00:00
|
|
|
|
|
|
|
|
// Get the appropriate file operation
|
|
|
|
|
if ( FileBackend::isStoragePath( $srcPath ) ) {
|
|
|
|
|
$opName = ( $flags & self::DELETE_SOURCE ) ? 'move' : 'copy';
|
|
|
|
|
} else {
|
|
|
|
|
$opName = 'store';
|
|
|
|
|
if ( $flags & self::DELETE_SOURCE ) {
|
|
|
|
|
$sourceFSFilesToDelete[] = $srcPath;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
$operations[] = array(
|
2013-04-20 17:18:13 +00:00
|
|
|
'op' => $opName,
|
|
|
|
|
'src' => $srcPath,
|
|
|
|
|
'dst' => $dstPath,
|
|
|
|
|
'overwrite' => $flags & self::OVERWRITE,
|
2011-12-20 03:52:06 +00:00
|
|
|
'overwriteSame' => $flags & self::OVERWRITE_SAME,
|
|
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Execute the store operation for each triplet
|
2012-01-08 08:40:00 +00:00
|
|
|
$opts = array( 'force' => true );
|
2011-12-21 20:39:50 +00:00
|
|
|
if ( $flags & self::SKIP_LOCKING ) {
|
|
|
|
|
$opts['nonLocking'] = true;
|
|
|
|
|
}
|
2011-12-20 03:52:06 +00:00
|
|
|
$status->merge( $backend->doOperations( $operations, $opts ) );
|
|
|
|
|
// Cleanup for disk source files...
|
|
|
|
|
foreach ( $sourceFSFilesToDelete as $file ) {
|
|
|
|
|
wfSuppressWarnings();
|
|
|
|
|
unlink( $file ); // FS cleanup
|
|
|
|
|
wfRestoreWarnings();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return $status;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Deletes a batch of files.
|
2012-04-05 04:10:50 +00:00
|
|
|
* Each file can be a (zone, rel) pair, virtual url, storage path.
|
2011-12-20 03:52:06 +00:00
|
|
|
* It will try to delete each file, but ignores any errors that may occur.
|
|
|
|
|
*
|
2013-03-11 17:15:01 +00:00
|
|
|
* @param array $files List of files to delete
|
2012-03-09 01:18:32 +00:00
|
|
|
* @param $flags Integer: bitwise combination of the following flags:
|
|
|
|
|
* self::SKIP_LOCKING Skip any file locking when doing the deletions
|
2012-04-05 04:10:50 +00:00
|
|
|
* @return FileRepoStatus
|
2011-12-20 03:52:06 +00:00
|
|
|
*/
|
2012-04-05 04:10:50 +00:00
|
|
|
public function cleanupBatch( array $files, $flags = 0 ) {
|
|
|
|
|
$this->assertWritableRepo(); // fail out if read-only
|
|
|
|
|
|
|
|
|
|
$status = $this->newGood();
|
|
|
|
|
|
2011-12-20 03:52:06 +00:00
|
|
|
$operations = array();
|
2012-04-05 04:10:50 +00:00
|
|
|
foreach ( $files as $path ) {
|
|
|
|
|
if ( is_array( $path ) ) {
|
2011-12-20 03:52:06 +00:00
|
|
|
// This is a pair, extract it
|
2012-04-05 04:10:50 +00:00
|
|
|
list( $zone, $rel ) = $path;
|
|
|
|
|
$path = $this->getZonePath( $zone ) . "/$rel";
|
2011-12-20 03:52:06 +00:00
|
|
|
} else {
|
2012-04-05 04:10:50 +00:00
|
|
|
// Resolve source to a storage path if virtual
|
2012-04-17 17:56:35 +00:00
|
|
|
$path = $this->resolveToStoragePath( $path );
|
2011-12-20 03:52:06 +00:00
|
|
|
}
|
2012-04-05 04:10:50 +00:00
|
|
|
$operations[] = array( 'op' => 'delete', 'src' => $path );
|
2011-12-20 03:52:06 +00:00
|
|
|
}
|
|
|
|
|
// Actually delete files from storage...
|
2012-01-08 08:40:00 +00:00
|
|
|
$opts = array( 'force' => true );
|
2012-03-09 01:18:32 +00:00
|
|
|
if ( $flags & self::SKIP_LOCKING ) {
|
|
|
|
|
$opts['nonLocking'] = true;
|
|
|
|
|
}
|
2012-04-05 04:10:50 +00:00
|
|
|
$status->merge( $this->backend->doOperations( $operations, $opts ) );
|
|
|
|
|
|
|
|
|
|
return $status;
|
2011-12-20 03:52:06 +00:00
|
|
|
}
|
2007-06-16 02:55:25 +00:00
|
|
|
|
2012-04-16 23:51:55 +00:00
|
|
|
/**
|
|
|
|
|
* Import a file from the local file system into the repo.
|
|
|
|
|
* This does no locking nor journaling and overrides existing files.
|
2012-04-24 00:30:38 +00:00
|
|
|
* This function can be used to write to otherwise read-only foreign repos.
|
2012-04-16 23:51:55 +00:00
|
|
|
* This is intended for copying generated thumbnails into the repo.
|
|
|
|
|
*
|
2013-03-11 17:15:01 +00:00
|
|
|
* @param string $src Source file system path, storage path, or virtual URL
|
|
|
|
|
* @param string $dst Virtual URL or storage path
|
|
|
|
|
* @param string|null $disposition Content-Disposition if given and supported
|
2012-04-16 23:51:55 +00:00
|
|
|
* @return FileRepoStatus
|
|
|
|
|
*/
|
2012-08-31 21:21:39 +00:00
|
|
|
final public function quickImport( $src, $dst, $disposition = null ) {
|
|
|
|
|
return $this->quickImportBatch( array( array( $src, $dst, $disposition ) ) );
|
2012-04-16 23:51:55 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Purge a file from the repo. This does no locking nor journaling.
|
2012-04-24 00:30:38 +00:00
|
|
|
* This function can be used to write to otherwise read-only foreign repos.
|
|
|
|
|
* This is intended for purging thumbnails.
|
2012-04-16 23:51:55 +00:00
|
|
|
*
|
2013-03-11 17:15:01 +00:00
|
|
|
* @param string $path Virtual URL or storage path
|
2012-04-16 23:51:55 +00:00
|
|
|
* @return FileRepoStatus
|
|
|
|
|
*/
|
|
|
|
|
final public function quickPurge( $path ) {
|
|
|
|
|
return $this->quickPurgeBatch( array( $path ) );
|
|
|
|
|
}
|
|
|
|
|
|
2012-04-25 17:47:59 +00:00
|
|
|
/**
|
|
|
|
|
* Deletes a directory if empty.
|
|
|
|
|
* This function can be used to write to otherwise read-only foreign repos.
|
|
|
|
|
*
|
2013-03-11 17:15:01 +00:00
|
|
|
* @param string $dir Virtual URL (or storage path) of directory to clean
|
2012-04-25 17:47:59 +00:00
|
|
|
* @return Status
|
|
|
|
|
*/
|
|
|
|
|
public function quickCleanDir( $dir ) {
|
|
|
|
|
$status = $this->newGood();
|
|
|
|
|
$status->merge( $this->backend->clean(
|
|
|
|
|
array( 'dir' => $this->resolveToStoragePath( $dir ) ) ) );
|
|
|
|
|
|
|
|
|
|
return $status;
|
|
|
|
|
}
|
|
|
|
|
|
2012-04-16 23:51:55 +00:00
|
|
|
/**
|
|
|
|
|
* Import a batch of files from the local file system into the repo.
|
|
|
|
|
* This does no locking nor journaling and overrides existing files.
|
2012-04-24 00:30:38 +00:00
|
|
|
* This function can be used to write to otherwise read-only foreign repos.
|
2012-04-16 23:51:55 +00:00
|
|
|
* This is intended for copying generated thumbnails into the repo.
|
|
|
|
|
*
|
2012-09-09 04:40:17 +00:00
|
|
|
* All path parameters may be a file system path, storage path, or virtual URL.
|
2012-08-31 21:21:39 +00:00
|
|
|
* When "dispositions" are given they are used as Content-Disposition if supported.
|
|
|
|
|
*
|
2013-03-11 17:15:01 +00:00
|
|
|
* @param array $triples List of (source path, destination path, disposition)
|
2012-04-16 23:51:55 +00:00
|
|
|
* @return FileRepoStatus
|
|
|
|
|
*/
|
2012-09-09 02:15:27 +00:00
|
|
|
public function quickImportBatch( array $triples ) {
|
2012-04-16 23:51:55 +00:00
|
|
|
$status = $this->newGood();
|
|
|
|
|
$operations = array();
|
2012-09-09 02:15:27 +00:00
|
|
|
foreach ( $triples as $triple ) {
|
|
|
|
|
list( $src, $dst ) = $triple;
|
2012-09-09 04:40:17 +00:00
|
|
|
$src = $this->resolveToStoragePath( $src );
|
2012-05-21 22:19:06 +00:00
|
|
|
$dst = $this->resolveToStoragePath( $dst );
|
2012-04-16 23:51:55 +00:00
|
|
|
$operations[] = array(
|
2013-04-20 17:18:13 +00:00
|
|
|
'op' => FileBackend::isStoragePath( $src ) ? 'copy' : 'store',
|
|
|
|
|
'src' => $src,
|
|
|
|
|
'dst' => $dst,
|
2012-09-09 02:15:27 +00:00
|
|
|
'disposition' => isset( $triple[2] ) ? $triple[2] : null
|
2012-04-16 23:51:55 +00:00
|
|
|
);
|
2012-05-21 22:19:06 +00:00
|
|
|
$status->merge( $this->initDirectory( dirname( $dst ) ) );
|
2012-04-16 23:51:55 +00:00
|
|
|
}
|
2012-05-16 20:45:31 +00:00
|
|
|
$status->merge( $this->backend->doQuickOperations( $operations ) );
|
2012-04-16 23:51:55 +00:00
|
|
|
|
|
|
|
|
return $status;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
2012-04-24 00:30:38 +00:00
|
|
|
* Purge a batch of files from the repo.
|
|
|
|
|
* This function can be used to write to otherwise read-only foreign repos.
|
|
|
|
|
* This does no locking nor journaling and is intended for purging thumbnails.
|
2012-04-16 23:51:55 +00:00
|
|
|
*
|
2013-03-11 17:15:01 +00:00
|
|
|
* @param array $paths List of virtual URLs or storage paths
|
2012-04-16 23:51:55 +00:00
|
|
|
* @return FileRepoStatus
|
|
|
|
|
*/
|
|
|
|
|
public function quickPurgeBatch( array $paths ) {
|
|
|
|
|
$status = $this->newGood();
|
|
|
|
|
$operations = array();
|
|
|
|
|
foreach ( $paths as $path ) {
|
|
|
|
|
$operations[] = array(
|
2013-04-20 17:18:13 +00:00
|
|
|
'op' => 'delete',
|
|
|
|
|
'src' => $this->resolveToStoragePath( $path ),
|
2012-04-16 23:51:55 +00:00
|
|
|
'ignoreMissingSource' => true
|
|
|
|
|
);
|
|
|
|
|
}
|
2012-05-16 20:45:31 +00:00
|
|
|
$status->merge( $this->backend->doQuickOperations( $operations ) );
|
2012-04-16 23:51:55 +00:00
|
|
|
|
|
|
|
|
return $status;
|
|
|
|
|
}
|
|
|
|
|
|
2007-06-16 02:55:25 +00:00
|
|
|
/**
|
|
|
|
|
* Pick a random name in the temp zone and store a file to it.
|
2012-02-08 12:16:32 +00:00
|
|
|
* Returns a FileRepoStatus object with the file Virtual URL in the value,
|
|
|
|
|
* file can later be disposed using FileRepo::freeTemp().
|
|
|
|
|
*
|
2013-03-11 17:15:01 +00:00
|
|
|
* @param string $originalName the base name of the file as specified
|
2007-06-16 02:55:25 +00:00
|
|
|
* by the user. The file extension will be maintained.
|
2013-03-11 17:15:01 +00:00
|
|
|
* @param string $srcPath the current location of the file.
|
2011-12-20 03:52:06 +00:00
|
|
|
* @return FileRepoStatus object with the URL in the value.
|
2007-06-16 02:55:25 +00:00
|
|
|
*/
|
2011-12-20 03:52:06 +00:00
|
|
|
public function storeTemp( $originalName, $srcPath ) {
|
2012-04-05 04:10:50 +00:00
|
|
|
$this->assertWritableRepo(); // fail out if read-only
|
|
|
|
|
|
2013-03-07 16:50:43 +00:00
|
|
|
$date = gmdate( "YmdHis" );
|
|
|
|
|
$hashPath = $this->getHashPath( $originalName );
|
|
|
|
|
$dstUrlRel = $hashPath . $date . '!' . rawurlencode( $originalName );
|
2013-02-03 19:42:08 +00:00
|
|
|
$virtualUrl = $this->getVirtualUrl( 'temp' ) . '/' . $dstUrlRel;
|
2007-06-16 02:55:25 +00:00
|
|
|
|
2012-08-30 23:03:36 +00:00
|
|
|
$result = $this->quickImport( $srcPath, $virtualUrl );
|
|
|
|
|
$result->value = $virtualUrl;
|
2012-04-05 04:10:50 +00:00
|
|
|
|
2011-12-20 03:52:06 +00:00
|
|
|
return $result;
|
|
|
|
|
}
|
2010-02-10 11:08:39 +00:00
|
|
|
|
2011-11-30 14:56:40 +00:00
|
|
|
/**
|
2012-08-30 23:03:36 +00:00
|
|
|
* Remove a temporary file or mark it for garbage collection
|
|
|
|
|
*
|
2013-03-11 17:15:01 +00:00
|
|
|
* @param string $virtualUrl the virtual URL returned by FileRepo::storeTemp()
|
2012-08-30 23:03:36 +00:00
|
|
|
* @return Boolean: true on success, false on failure
|
|
|
|
|
*/
|
|
|
|
|
public function freeTemp( $virtualUrl ) {
|
|
|
|
|
$this->assertWritableRepo(); // fail out if read-only
|
|
|
|
|
|
|
|
|
|
$temp = $this->getVirtualUrl( 'temp' );
|
|
|
|
|
if ( substr( $virtualUrl, 0, strlen( $temp ) ) != $temp ) {
|
2013-04-13 11:36:24 +00:00
|
|
|
wfDebug( __METHOD__ . ": Invalid temp virtual URL\n" );
|
2012-08-30 23:03:36 +00:00
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return $this->quickPurge( $virtualUrl )->isOK();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Concatenate a list of temporary files into a target file location.
|
2012-04-05 04:10:50 +00:00
|
|
|
*
|
2013-03-11 17:15:01 +00:00
|
|
|
* @param array $srcPaths Ordered list of source virtual URLs/storage paths
|
|
|
|
|
* @param string $dstPath Target file system path
|
2011-12-20 03:52:06 +00:00
|
|
|
* @param $flags Integer: bitwise combination of the following flags:
|
|
|
|
|
* self::DELETE_SOURCE Delete the source files
|
|
|
|
|
* @return FileRepoStatus
|
2010-02-10 11:08:39 +00:00
|
|
|
*/
|
2012-11-25 00:59:16 +00:00
|
|
|
public function concatenate( array $srcPaths, $dstPath, $flags = 0 ) {
|
2012-04-05 04:10:50 +00:00
|
|
|
$this->assertWritableRepo(); // fail out if read-only
|
|
|
|
|
|
2011-12-20 03:52:06 +00:00
|
|
|
$status = $this->newGood();
|
2010-02-10 10:36:11 +00:00
|
|
|
|
2011-12-20 03:52:06 +00:00
|
|
|
$sources = array();
|
|
|
|
|
foreach ( $srcPaths as $srcPath ) {
|
|
|
|
|
// Resolve source to a storage path if virtual
|
|
|
|
|
$source = $this->resolveToStoragePath( $srcPath );
|
|
|
|
|
$sources[] = $source; // chunk to merge
|
|
|
|
|
}
|
|
|
|
|
|
2012-01-08 09:25:15 +00:00
|
|
|
// Concatenate the chunks into one FS file
|
2012-11-25 00:59:16 +00:00
|
|
|
$params = array( 'srcs' => $sources, 'dst' => $dstPath );
|
2012-01-08 09:25:15 +00:00
|
|
|
$status->merge( $this->backend->concatenate( $params ) );
|
2011-12-20 03:52:06 +00:00
|
|
|
if ( !$status->isOK() ) {
|
|
|
|
|
return $status;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Delete the sources if required
|
2012-08-30 23:03:36 +00:00
|
|
|
if ( $flags & self::DELETE_SOURCE ) {
|
|
|
|
|
$status->merge( $this->quickPurgeBatch( $srcPaths ) );
|
2011-12-20 03:52:06 +00:00
|
|
|
}
|
|
|
|
|
|
2012-08-30 23:03:36 +00:00
|
|
|
// Make sure status is OK, despite any quickPurgeBatch() fatals
|
2011-12-20 03:52:06 +00:00
|
|
|
$status->setResult( true );
|
|
|
|
|
|
|
|
|
|
return $status;
|
|
|
|
|
}
|
2011-05-23 01:18:06 +00:00
|
|
|
|
2007-06-16 02:55:25 +00:00
|
|
|
/**
|
2011-12-20 03:52:06 +00:00
|
|
|
* Copy or move a file either from a storage path, virtual URL,
|
2012-09-09 04:40:17 +00:00
|
|
|
* or file system path, into this repository at the specified destination location.
|
2007-06-16 02:55:25 +00:00
|
|
|
*
|
2007-07-22 14:45:12 +00:00
|
|
|
* Returns a FileRepoStatus object. On success, the value contains "new" or
|
2008-04-14 07:45:50 +00:00
|
|
|
* "archived", to indicate whether the file was new with that name.
|
2007-07-22 14:45:12 +00:00
|
|
|
*
|
2012-11-20 01:38:17 +00:00
|
|
|
* Options to $options include:
|
|
|
|
|
* - headers : name/value map of HTTP headers to use in response to GET/HEAD requests
|
|
|
|
|
*
|
2013-03-11 17:15:01 +00:00
|
|
|
* @param string $srcPath the source file system path, storage path, or URL
|
|
|
|
|
* @param string $dstRel the destination relative path
|
|
|
|
|
* @param string $archiveRel the relative path where the existing file is to
|
2007-06-16 02:55:25 +00:00
|
|
|
* be archived, if there is one. Relative to the public zone root.
|
2010-03-26 21:55:13 +00:00
|
|
|
* @param $flags Integer: bitfield, may be FileRepo::DELETE_SOURCE to indicate
|
2007-06-16 02:55:25 +00:00
|
|
|
* that the source file should be deleted if possible
|
2013-03-11 17:15:01 +00:00
|
|
|
* @param array $options Optional additional parameters
|
2012-02-09 21:33:27 +00:00
|
|
|
* @return FileRepoStatus
|
2007-06-16 02:55:25 +00:00
|
|
|
*/
|
2012-11-20 01:38:17 +00:00
|
|
|
public function publish(
|
|
|
|
|
$srcPath, $dstRel, $archiveRel, $flags = 0, array $options = array()
|
|
|
|
|
) {
|
2012-04-05 04:10:50 +00:00
|
|
|
$this->assertWritableRepo(); // fail out if read-only
|
|
|
|
|
|
2012-11-20 01:38:17 +00:00
|
|
|
$status = $this->publishBatch(
|
|
|
|
|
array( array( $srcPath, $dstRel, $archiveRel, $options ) ), $flags );
|
2007-07-22 14:45:12 +00:00
|
|
|
if ( $status->successCount == 0 ) {
|
|
|
|
|
$status->ok = false;
|
|
|
|
|
}
|
|
|
|
|
if ( isset( $status->value[0] ) ) {
|
|
|
|
|
$status->value = $status->value[0];
|
|
|
|
|
} else {
|
|
|
|
|
$status->value = false;
|
|
|
|
|
}
|
2012-04-05 04:10:50 +00:00
|
|
|
|
2007-07-22 14:45:12 +00:00
|
|
|
return $status;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Publish a batch of files
|
2011-12-20 03:52:06 +00:00
|
|
|
*
|
2013-03-11 17:15:01 +00:00
|
|
|
* @param array $ntuples (source, dest, archive) triplets or
|
2012-11-20 22:37:58 +00:00
|
|
|
* (source, dest, archive, options) 4-tuples as per publish().
|
2010-03-26 21:55:13 +00:00
|
|
|
* @param $flags Integer: bitfield, may be FileRepo::DELETE_SOURCE to indicate
|
2007-07-22 14:45:12 +00:00
|
|
|
* that the source files should be deleted if possible
|
2012-05-18 02:58:15 +00:00
|
|
|
* @throws MWException
|
2011-12-20 03:52:06 +00:00
|
|
|
* @return FileRepoStatus
|
2007-07-22 14:45:12 +00:00
|
|
|
*/
|
2012-11-20 22:37:58 +00:00
|
|
|
public function publishBatch( array $ntuples, $flags = 0 ) {
|
2012-04-05 04:10:50 +00:00
|
|
|
$this->assertWritableRepo(); // fail out if read-only
|
2011-12-20 03:52:06 +00:00
|
|
|
|
2012-04-05 04:10:50 +00:00
|
|
|
$backend = $this->backend; // convenience
|
2011-12-20 03:52:06 +00:00
|
|
|
// Try creating directories
|
|
|
|
|
$status = $this->initZones( 'public' );
|
|
|
|
|
if ( !$status->isOK() ) {
|
|
|
|
|
return $status;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
$status = $this->newGood( array() );
|
|
|
|
|
|
|
|
|
|
$operations = array();
|
|
|
|
|
$sourceFSFilesToDelete = array(); // cleanup for disk source files
|
|
|
|
|
// Validate each triplet and get the store operation...
|
2012-12-09 03:27:02 +00:00
|
|
|
foreach ( $ntuples as $ntuple ) {
|
2012-11-20 22:37:58 +00:00
|
|
|
list( $srcPath, $dstRel, $archiveRel ) = $ntuple;
|
|
|
|
|
$options = isset( $ntuple[3] ) ? $ntuple[3] : array();
|
2011-12-20 03:52:06 +00:00
|
|
|
// Resolve source to a storage path if virtual
|
2012-04-17 17:56:35 +00:00
|
|
|
$srcPath = $this->resolveToStoragePath( $srcPath );
|
2011-12-20 03:52:06 +00:00
|
|
|
if ( !$this->validateFilename( $dstRel ) ) {
|
|
|
|
|
throw new MWException( 'Validation error in $dstRel' );
|
|
|
|
|
}
|
|
|
|
|
if ( !$this->validateFilename( $archiveRel ) ) {
|
|
|
|
|
throw new MWException( 'Validation error in $archiveRel' );
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
$publicRoot = $this->getZonePath( 'public' );
|
|
|
|
|
$dstPath = "$publicRoot/$dstRel";
|
|
|
|
|
$archivePath = "$publicRoot/$archiveRel";
|
|
|
|
|
|
|
|
|
|
$dstDir = dirname( $dstPath );
|
|
|
|
|
$archiveDir = dirname( $archivePath );
|
|
|
|
|
// Abort immediately on directory creation errors since they're likely to be repetitive
|
2012-05-21 22:19:06 +00:00
|
|
|
if ( !$this->initDirectory( $dstDir )->isOK() ) {
|
2011-12-20 03:52:06 +00:00
|
|
|
return $this->newFatal( 'directorycreateerror', $dstDir );
|
|
|
|
|
}
|
2013-02-03 19:42:08 +00:00
|
|
|
if ( !$this->initDirectory( $archiveDir )->isOK() ) {
|
2011-12-20 03:52:06 +00:00
|
|
|
return $this->newFatal( 'directorycreateerror', $archiveDir );
|
|
|
|
|
}
|
|
|
|
|
|
2012-11-20 01:38:17 +00:00
|
|
|
// Set any desired headers to be use in GET/HEAD responses
|
|
|
|
|
$headers = isset( $options['headers'] ) ? $options['headers'] : array();
|
|
|
|
|
|
2012-10-30 02:04:03 +00:00
|
|
|
// Archive destination file if it exists.
|
|
|
|
|
// This will check if the archive file also exists and fail if does.
|
|
|
|
|
// This is a sanity check to avoid data loss. On Windows and Linux,
|
|
|
|
|
// copy() will overwrite, so the existence check is vulnerable to
|
|
|
|
|
// race conditions unless an functioning LockManager is used.
|
|
|
|
|
// LocalFile also uses SELECT FOR UPDATE for synchronization.
|
|
|
|
|
$operations[] = array(
|
2013-04-20 17:18:13 +00:00
|
|
|
'op' => 'copy',
|
|
|
|
|
'src' => $dstPath,
|
|
|
|
|
'dst' => $archivePath,
|
2012-10-30 02:04:03 +00:00
|
|
|
'ignoreMissingSource' => true
|
|
|
|
|
);
|
|
|
|
|
|
2011-12-20 03:52:06 +00:00
|
|
|
// Copy (or move) the source file to the destination
|
|
|
|
|
if ( FileBackend::isStoragePath( $srcPath ) ) {
|
|
|
|
|
if ( $flags & self::DELETE_SOURCE ) {
|
|
|
|
|
$operations[] = array(
|
2013-04-20 17:18:13 +00:00
|
|
|
'op' => 'move',
|
|
|
|
|
'src' => $srcPath,
|
|
|
|
|
'dst' => $dstPath,
|
2012-11-20 01:38:17 +00:00
|
|
|
'overwrite' => true, // replace current
|
2013-04-20 17:18:13 +00:00
|
|
|
'headers' => $headers
|
2011-12-20 03:52:06 +00:00
|
|
|
);
|
|
|
|
|
} else {
|
|
|
|
|
$operations[] = array(
|
2013-04-20 17:18:13 +00:00
|
|
|
'op' => 'copy',
|
|
|
|
|
'src' => $srcPath,
|
|
|
|
|
'dst' => $dstPath,
|
2012-11-20 01:38:17 +00:00
|
|
|
'overwrite' => true, // replace current
|
2013-04-20 17:18:13 +00:00
|
|
|
'headers' => $headers
|
2011-12-20 03:52:06 +00:00
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
} else { // FS source path
|
|
|
|
|
$operations[] = array(
|
2013-04-20 17:18:13 +00:00
|
|
|
'op' => 'store',
|
|
|
|
|
'src' => $srcPath,
|
|
|
|
|
'dst' => $dstPath,
|
2012-11-20 01:38:17 +00:00
|
|
|
'overwrite' => true, // replace current
|
2013-04-20 17:18:13 +00:00
|
|
|
'headers' => $headers
|
2011-12-20 03:52:06 +00:00
|
|
|
);
|
|
|
|
|
if ( $flags & self::DELETE_SOURCE ) {
|
|
|
|
|
$sourceFSFilesToDelete[] = $srcPath;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Execute the operations for each triplet
|
2012-10-30 02:04:03 +00:00
|
|
|
$status->merge( $backend->doOperations( $operations ) );
|
|
|
|
|
// Find out which files were archived...
|
2012-11-20 22:37:58 +00:00
|
|
|
foreach ( $ntuples as $i => $ntuple ) {
|
2012-12-09 03:27:02 +00:00
|
|
|
list( , , $archiveRel ) = $ntuple;
|
2012-10-30 02:04:03 +00:00
|
|
|
$archivePath = $this->getZonePath( 'public' ) . "/$archiveRel";
|
|
|
|
|
if ( $this->fileExists( $archivePath ) ) {
|
|
|
|
|
$status->value[$i] = 'archived';
|
|
|
|
|
} else {
|
|
|
|
|
$status->value[$i] = 'new';
|
|
|
|
|
}
|
|
|
|
|
}
|
2011-12-20 03:52:06 +00:00
|
|
|
// Cleanup for disk source files...
|
|
|
|
|
foreach ( $sourceFSFilesToDelete as $file ) {
|
|
|
|
|
wfSuppressWarnings();
|
|
|
|
|
unlink( $file ); // FS cleanup
|
|
|
|
|
wfRestoreWarnings();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return $status;
|
|
|
|
|
}
|
2007-07-22 14:45:12 +00:00
|
|
|
|
2012-05-21 22:19:06 +00:00
|
|
|
/**
|
|
|
|
|
* Creates a directory with the appropriate zone permissions.
|
|
|
|
|
* Callers are responsible for doing read-only and "writable repo" checks.
|
|
|
|
|
*
|
2013-03-11 17:15:01 +00:00
|
|
|
* @param string $dir Virtual URL (or storage path) of directory to clean
|
2012-05-21 22:19:06 +00:00
|
|
|
* @return Status
|
|
|
|
|
*/
|
|
|
|
|
protected function initDirectory( $dir ) {
|
|
|
|
|
$path = $this->resolveToStoragePath( $dir );
|
2012-12-09 03:27:02 +00:00
|
|
|
list( , $container, ) = FileBackend::splitStoragePath( $path );
|
2012-05-21 22:19:06 +00:00
|
|
|
|
|
|
|
|
$params = array( 'dir' => $path );
|
2012-08-20 23:39:55 +00:00
|
|
|
if ( $this->isPrivate || $container === $this->zones['deleted']['container'] ) {
|
2012-05-21 22:19:06 +00:00
|
|
|
# Take all available measures to prevent web accessibility of new deleted
|
|
|
|
|
# directories, in case the user has not configured offline storage
|
|
|
|
|
$params = array( 'noAccess' => true, 'noListing' => true ) + $params;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return $this->backend->prepare( $params );
|
|
|
|
|
}
|
|
|
|
|
|
2012-04-16 23:51:55 +00:00
|
|
|
/**
|
2012-04-24 00:30:38 +00:00
|
|
|
* Deletes a directory if empty.
|
2012-04-16 23:51:55 +00:00
|
|
|
*
|
2013-03-11 17:15:01 +00:00
|
|
|
* @param string $dir Virtual URL (or storage path) of directory to clean
|
2012-04-16 23:51:55 +00:00
|
|
|
* @return Status
|
|
|
|
|
*/
|
|
|
|
|
public function cleanDir( $dir ) {
|
2012-04-25 17:47:59 +00:00
|
|
|
$this->assertWritableRepo(); // fail out if read-only
|
|
|
|
|
|
2012-04-16 23:51:55 +00:00
|
|
|
$status = $this->newGood();
|
|
|
|
|
$status->merge( $this->backend->clean(
|
|
|
|
|
array( 'dir' => $this->resolveToStoragePath( $dir ) ) ) );
|
|
|
|
|
|
|
|
|
|
return $status;
|
|
|
|
|
}
|
|
|
|
|
|
2011-09-07 12:00:58 +00:00
|
|
|
/**
|
2011-12-20 03:52:06 +00:00
|
|
|
* Checks existence of a a file
|
|
|
|
|
*
|
2013-03-11 17:15:01 +00:00
|
|
|
* @param string $file Virtual URL (or storage path) of file to check
|
2011-09-07 12:00:58 +00:00
|
|
|
* @return bool
|
|
|
|
|
*/
|
2012-04-05 04:10:50 +00:00
|
|
|
public function fileExists( $file ) {
|
|
|
|
|
$result = $this->fileExistsBatch( array( $file ) );
|
2009-06-10 01:47:58 +00:00
|
|
|
return $result[0];
|
2009-06-08 15:10:08 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Checks existence of an array of files.
|
|
|
|
|
*
|
2013-03-11 17:15:01 +00:00
|
|
|
* @param array $files Virtual URLs (or storage paths) of files to check
|
2012-02-09 19:30:01 +00:00
|
|
|
* @return array|bool Either array of files and existence flags, or false
|
2009-06-08 15:10:08 +00:00
|
|
|
*/
|
2012-04-05 04:10:50 +00:00
|
|
|
public function fileExistsBatch( array $files ) {
|
2011-12-20 03:52:06 +00:00
|
|
|
$result = array();
|
|
|
|
|
foreach ( $files as $key => $file ) {
|
2012-04-17 17:56:35 +00:00
|
|
|
$file = $this->resolveToStoragePath( $file );
|
2012-04-05 04:10:50 +00:00
|
|
|
$result[$key] = $this->backend->fileExists( array( 'src' => $file ) );
|
2011-12-20 03:52:06 +00:00
|
|
|
}
|
|
|
|
|
return $result;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Move a file to the deletion archive.
|
|
|
|
|
* If no valid deletion archive exists, this may either delete the file
|
|
|
|
|
* or throw an exception, depending on the preference of the repository
|
|
|
|
|
*
|
|
|
|
|
* @param $srcRel Mixed: relative path for the file to be deleted
|
|
|
|
|
* @param $archiveRel Mixed: relative path for the archive location.
|
|
|
|
|
* Relative to a private archive directory.
|
|
|
|
|
* @return FileRepoStatus object
|
|
|
|
|
*/
|
|
|
|
|
public function delete( $srcRel, $archiveRel ) {
|
2012-04-05 04:10:50 +00:00
|
|
|
$this->assertWritableRepo(); // fail out if read-only
|
|
|
|
|
|
2011-12-20 03:52:06 +00:00
|
|
|
return $this->deleteBatch( array( array( $srcRel, $archiveRel ) ) );
|
|
|
|
|
}
|
2009-06-08 15:10:08 +00:00
|
|
|
|
2007-07-22 14:45:12 +00:00
|
|
|
/**
|
|
|
|
|
* Move a group of files to the deletion archive.
|
|
|
|
|
*
|
2008-04-14 07:45:50 +00:00
|
|
|
* If no valid deletion archive is configured, this may either delete the
|
2007-07-22 14:45:12 +00:00
|
|
|
* file or throw an exception, depending on the preference of the repository.
|
|
|
|
|
*
|
2011-12-20 19:25:23 +00:00
|
|
|
* The overwrite policy is determined by the repository -- currently LocalRepo
|
2008-04-14 07:45:50 +00:00
|
|
|
* assumes a naming scheme in the deleted zone based on content hash, as
|
2007-07-22 14:45:12 +00:00
|
|
|
* opposed to the public zone which is assumed to be unique.
|
|
|
|
|
*
|
2013-03-11 17:15:01 +00:00
|
|
|
* @param array $sourceDestPairs of source/destination pairs. Each element
|
2007-07-22 14:45:12 +00:00
|
|
|
* is a two-element array containing the source file path relative to the
|
2008-04-14 07:45:50 +00:00
|
|
|
* public root in the first element, and the archive file path relative
|
2007-07-22 14:45:12 +00:00
|
|
|
* to the deleted zone root in the second element.
|
2012-05-18 02:58:15 +00:00
|
|
|
* @throws MWException
|
2007-07-22 14:45:12 +00:00
|
|
|
* @return FileRepoStatus
|
|
|
|
|
*/
|
2012-04-05 04:10:50 +00:00
|
|
|
public function deleteBatch( array $sourceDestPairs ) {
|
|
|
|
|
$this->assertWritableRepo(); // fail out if read-only
|
2011-12-20 03:52:06 +00:00
|
|
|
|
|
|
|
|
// Try creating directories
|
|
|
|
|
$status = $this->initZones( array( 'public', 'deleted' ) );
|
|
|
|
|
if ( !$status->isOK() ) {
|
|
|
|
|
return $status;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
$status = $this->newGood();
|
|
|
|
|
|
2012-04-05 04:10:50 +00:00
|
|
|
$backend = $this->backend; // convenience
|
2011-12-20 03:52:06 +00:00
|
|
|
$operations = array();
|
|
|
|
|
// Validate filenames and create archive directories
|
|
|
|
|
foreach ( $sourceDestPairs as $pair ) {
|
|
|
|
|
list( $srcRel, $archiveRel ) = $pair;
|
|
|
|
|
if ( !$this->validateFilename( $srcRel ) ) {
|
2013-04-13 11:36:24 +00:00
|
|
|
throw new MWException( __METHOD__ . ':Validation error in $srcRel' );
|
2012-04-05 04:10:50 +00:00
|
|
|
} elseif ( !$this->validateFilename( $archiveRel ) ) {
|
2013-04-13 11:36:24 +00:00
|
|
|
throw new MWException( __METHOD__ . ':Validation error in $archiveRel' );
|
2011-12-20 03:52:06 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
$publicRoot = $this->getZonePath( 'public' );
|
|
|
|
|
$srcPath = "{$publicRoot}/$srcRel";
|
|
|
|
|
|
|
|
|
|
$deletedRoot = $this->getZonePath( 'deleted' );
|
|
|
|
|
$archivePath = "{$deletedRoot}/{$archiveRel}";
|
|
|
|
|
$archiveDir = dirname( $archivePath ); // does not touch FS
|
|
|
|
|
|
|
|
|
|
// Create destination directories
|
2012-05-21 22:19:06 +00:00
|
|
|
if ( !$this->initDirectory( $archiveDir )->isOK() ) {
|
2011-12-20 03:52:06 +00:00
|
|
|
return $this->newFatal( 'directorycreateerror', $archiveDir );
|
|
|
|
|
}
|
|
|
|
|
|
2012-01-14 00:50:18 +00:00
|
|
|
$operations[] = array(
|
2013-04-20 17:18:13 +00:00
|
|
|
'op' => 'move',
|
|
|
|
|
'src' => $srcPath,
|
|
|
|
|
'dst' => $archivePath,
|
2012-01-14 00:50:18 +00:00
|
|
|
// We may have 2+ identical files being deleted,
|
|
|
|
|
// all of which will map to the same destination file
|
2012-01-22 00:06:18 +00:00
|
|
|
'overwriteSame' => true // also see bug 31792
|
2012-01-14 00:50:18 +00:00
|
|
|
);
|
2011-12-20 03:52:06 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Move the files by execute the operations for each pair.
|
|
|
|
|
// We're now committed to returning an OK result, which will
|
|
|
|
|
// lead to the files being moved in the DB also.
|
2012-01-08 08:40:00 +00:00
|
|
|
$opts = array( 'force' => true );
|
2011-12-20 03:52:06 +00:00
|
|
|
$status->merge( $backend->doOperations( $operations, $opts ) );
|
|
|
|
|
|
|
|
|
|
return $status;
|
|
|
|
|
}
|
2007-07-22 14:45:12 +00:00
|
|
|
|
2012-04-05 04:10:50 +00:00
|
|
|
/**
|
|
|
|
|
* Delete files in the deleted directory if they are not referenced in the filearchive table
|
|
|
|
|
*
|
|
|
|
|
* STUB
|
|
|
|
|
*/
|
|
|
|
|
public function cleanupDeletedBatch( array $storageKeys ) {
|
|
|
|
|
$this->assertWritableRepo();
|
|
|
|
|
}
|
|
|
|
|
|
2007-07-22 14:45:12 +00:00
|
|
|
/**
|
2011-12-20 03:52:06 +00:00
|
|
|
* Get a relative path for a deletion archive key,
|
|
|
|
|
* e.g. s/z/a/ for sza251lrxrc1jad41h5mgilp8nysje52.jpg
|
|
|
|
|
*
|
2012-05-18 02:58:15 +00:00
|
|
|
* @param $key string
|
2012-10-07 23:35:26 +00:00
|
|
|
* @throws MWException
|
2011-12-20 03:52:06 +00:00
|
|
|
* @return string
|
2007-07-22 14:45:12 +00:00
|
|
|
*/
|
2011-12-20 03:52:06 +00:00
|
|
|
public function getDeletedHashPath( $key ) {
|
2012-09-20 20:47:15 +00:00
|
|
|
if ( strlen( $key ) < 31 ) {
|
|
|
|
|
throw new MWException( "Invalid storage key '$key'." );
|
|
|
|
|
}
|
2011-12-20 03:52:06 +00:00
|
|
|
$path = '';
|
|
|
|
|
for ( $i = 0; $i < $this->deletedHashLevels; $i++ ) {
|
|
|
|
|
$path .= $key[$i] . '/';
|
|
|
|
|
}
|
|
|
|
|
return $path;
|
2007-07-22 14:45:12 +00:00
|
|
|
}
|
2007-06-16 02:55:25 +00:00
|
|
|
|
|
|
|
|
/**
|
2011-12-20 03:52:06 +00:00
|
|
|
* If a path is a virtual URL, resolve it to a storage path.
|
|
|
|
|
* Otherwise, just return the path as it is.
|
2011-09-07 12:00:58 +00:00
|
|
|
*
|
2011-12-20 03:52:06 +00:00
|
|
|
* @param $path string
|
|
|
|
|
* @return string
|
|
|
|
|
* @throws MWException
|
|
|
|
|
*/
|
|
|
|
|
protected function resolveToStoragePath( $path ) {
|
|
|
|
|
if ( $this->isVirtualUrl( $path ) ) {
|
|
|
|
|
return $this->resolveVirtualUrl( $path );
|
|
|
|
|
}
|
|
|
|
|
return $path;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Get a local FS copy of a file with a given virtual URL/storage path.
|
|
|
|
|
* Temporary files may be purged when the file object falls out of scope.
|
2012-04-05 04:10:50 +00:00
|
|
|
*
|
2011-12-20 03:52:06 +00:00
|
|
|
* @param $virtualUrl string
|
|
|
|
|
* @return TempFSFile|null Returns null on failure
|
|
|
|
|
*/
|
|
|
|
|
public function getLocalCopy( $virtualUrl ) {
|
|
|
|
|
$path = $this->resolveToStoragePath( $virtualUrl );
|
|
|
|
|
return $this->backend->getLocalCopy( array( 'src' => $path ) );
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Get a local FS file with a given virtual URL/storage path.
|
|
|
|
|
* The file is either an original or a copy. It should not be changed.
|
|
|
|
|
* Temporary files may be purged when the file object falls out of scope.
|
2012-04-05 04:10:50 +00:00
|
|
|
*
|
2011-09-07 12:00:58 +00:00
|
|
|
* @param $virtualUrl string
|
2011-12-20 03:52:06 +00:00
|
|
|
* @return FSFile|null Returns null on failure.
|
2007-06-16 02:55:25 +00:00
|
|
|
*/
|
2011-12-20 03:52:06 +00:00
|
|
|
public function getLocalReference( $virtualUrl ) {
|
|
|
|
|
$path = $this->resolveToStoragePath( $virtualUrl );
|
|
|
|
|
return $this->backend->getLocalReference( array( 'src' => $path ) );
|
|
|
|
|
}
|
2007-06-16 02:55:25 +00:00
|
|
|
|
|
|
|
|
/**
|
2011-12-20 03:52:06 +00:00
|
|
|
* Get properties of a file with a given virtual URL/storage path.
|
|
|
|
|
* Properties should ultimately be obtained via FSFile::getProps().
|
|
|
|
|
*
|
|
|
|
|
* @param $virtualUrl string
|
|
|
|
|
* @return Array
|
2007-06-16 02:55:25 +00:00
|
|
|
*/
|
2011-12-20 03:52:06 +00:00
|
|
|
public function getFileProps( $virtualUrl ) {
|
|
|
|
|
$path = $this->resolveToStoragePath( $virtualUrl );
|
|
|
|
|
return $this->backend->getFileProps( array( 'src' => $path ) );
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Get the timestamp of a file with a given virtual URL/storage path
|
|
|
|
|
*
|
|
|
|
|
* @param $virtualUrl string
|
2012-02-10 15:37:33 +00:00
|
|
|
* @return string|bool False on failure
|
2011-12-20 03:52:06 +00:00
|
|
|
*/
|
|
|
|
|
public function getFileTimestamp( $virtualUrl ) {
|
|
|
|
|
$path = $this->resolveToStoragePath( $virtualUrl );
|
|
|
|
|
return $this->backend->getFileTimestamp( array( 'src' => $path ) );
|
|
|
|
|
}
|
|
|
|
|
|
2012-11-18 10:32:50 +00:00
|
|
|
/**
|
|
|
|
|
* Get the size of a file with a given virtual URL/storage path
|
|
|
|
|
*
|
|
|
|
|
* @param $virtualUrl string
|
|
|
|
|
* @return integer|bool False on failure
|
|
|
|
|
*/
|
|
|
|
|
public function getFileSize( $virtualUrl ) {
|
|
|
|
|
$path = $this->resolveToStoragePath( $virtualUrl );
|
|
|
|
|
return $this->backend->getFileSize( array( 'src' => $path ) );
|
|
|
|
|
}
|
|
|
|
|
|
2011-12-20 03:52:06 +00:00
|
|
|
/**
|
2012-08-17 03:00:24 +00:00
|
|
|
* Get the sha1 (base 36) of a file with a given virtual URL/storage path
|
2011-12-20 03:52:06 +00:00
|
|
|
*
|
|
|
|
|
* @param $virtualUrl string
|
2012-02-09 18:01:54 +00:00
|
|
|
* @return string|bool
|
2011-12-20 03:52:06 +00:00
|
|
|
*/
|
|
|
|
|
public function getFileSha1( $virtualUrl ) {
|
|
|
|
|
$path = $this->resolveToStoragePath( $virtualUrl );
|
2012-08-17 03:00:24 +00:00
|
|
|
return $this->backend->getFileSha1Base36( array( 'src' => $path ) );
|
2011-12-20 03:52:06 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Attempt to stream a file with the given virtual URL/storage path
|
|
|
|
|
*
|
|
|
|
|
* @param $virtualUrl string
|
2013-03-11 17:15:01 +00:00
|
|
|
* @param array $headers Additional HTTP headers to send on success
|
2011-12-20 03:52:06 +00:00
|
|
|
* @return bool Success
|
|
|
|
|
*/
|
|
|
|
|
public function streamFile( $virtualUrl, $headers = array() ) {
|
|
|
|
|
$path = $this->resolveToStoragePath( $virtualUrl );
|
|
|
|
|
$params = array( 'src' => $path, 'headers' => $headers );
|
|
|
|
|
return $this->backend->streamFile( $params )->isOK();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
2012-01-04 08:07:52 +00:00
|
|
|
* Call a callback function for every public regular file in the repository.
|
|
|
|
|
* This only acts on the current version of files, not any old versions.
|
2011-12-20 03:52:06 +00:00
|
|
|
* May use either the database or the filesystem.
|
|
|
|
|
*
|
|
|
|
|
* @param $callback Array|string
|
2012-01-12 19:41:18 +00:00
|
|
|
* @return void
|
2011-12-20 03:52:06 +00:00
|
|
|
*/
|
|
|
|
|
public function enumFiles( $callback ) {
|
2012-01-03 15:36:46 +00:00
|
|
|
$this->enumFilesInStorage( $callback );
|
2011-12-20 03:52:06 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Call a callback function for every public file in the repository.
|
|
|
|
|
* May use either the database or the filesystem.
|
|
|
|
|
*
|
|
|
|
|
* @param $callback Array|string
|
2012-01-12 19:41:18 +00:00
|
|
|
* @return void
|
2011-12-20 03:52:06 +00:00
|
|
|
*/
|
|
|
|
|
protected function enumFilesInStorage( $callback ) {
|
|
|
|
|
$publicRoot = $this->getZonePath( 'public' );
|
|
|
|
|
$numDirs = 1 << ( $this->hashLevels * 4 );
|
|
|
|
|
// Use a priori assumptions about directory structure
|
|
|
|
|
// to reduce the tree height of the scanning process.
|
|
|
|
|
for ( $flatIndex = 0; $flatIndex < $numDirs; $flatIndex++ ) {
|
|
|
|
|
$hexString = sprintf( "%0{$this->hashLevels}x", $flatIndex );
|
|
|
|
|
$path = $publicRoot;
|
|
|
|
|
for ( $hexPos = 0; $hexPos < $this->hashLevels; $hexPos++ ) {
|
|
|
|
|
$path .= '/' . substr( $hexString, 0, $hexPos + 1 );
|
|
|
|
|
}
|
|
|
|
|
$iterator = $this->backend->getFileList( array( 'dir' => $path ) );
|
|
|
|
|
foreach ( $iterator as $name ) {
|
|
|
|
|
// Each item returned is a public file
|
|
|
|
|
call_user_func( $callback, "{$path}/{$name}" );
|
|
|
|
|
}
|
|
|
|
|
}
|
2007-06-16 02:55:25 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Determine if a relative path is valid, i.e. not blank or involving directory traveral
|
2011-09-07 12:00:58 +00:00
|
|
|
*
|
|
|
|
|
* @param $filename string
|
|
|
|
|
* @return bool
|
2007-06-16 02:55:25 +00:00
|
|
|
*/
|
2011-12-20 03:52:06 +00:00
|
|
|
public function validateFilename( $filename ) {
|
2007-06-16 02:55:25 +00:00
|
|
|
if ( strval( $filename ) == '' ) {
|
|
|
|
|
return false;
|
|
|
|
|
}
|
2012-04-05 04:10:50 +00:00
|
|
|
return FileBackend::isPathTraversalFree( $filename );
|
2007-06-16 02:55:25 +00:00
|
|
|
}
|
2007-07-22 14:45:12 +00:00
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Get a callback function to use for cleaning error message parameters
|
2011-12-20 03:52:06 +00:00
|
|
|
*
|
|
|
|
|
* @return Array
|
2007-07-22 14:45:12 +00:00
|
|
|
*/
|
|
|
|
|
function getErrorCleanupFunction() {
|
|
|
|
|
switch ( $this->pathDisclosureProtection ) {
|
|
|
|
|
case 'none':
|
2012-04-05 04:10:50 +00:00
|
|
|
case 'simple': // b/c
|
2007-07-22 14:45:12 +00:00
|
|
|
$callback = array( $this, 'passThrough' );
|
|
|
|
|
break;
|
|
|
|
|
default: // 'paranoid'
|
|
|
|
|
$callback = array( $this, 'paranoidClean' );
|
|
|
|
|
}
|
|
|
|
|
return $callback;
|
|
|
|
|
}
|
2011-12-20 03:52:06 +00:00
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Path disclosure protection function
|
|
|
|
|
*
|
|
|
|
|
* @param $param string
|
|
|
|
|
* @return string
|
|
|
|
|
*/
|
|
|
|
|
function paranoidClean( $param ) {
|
|
|
|
|
return '[hidden]';
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Path disclosure protection function
|
|
|
|
|
*
|
|
|
|
|
* @param $param string
|
|
|
|
|
* @return string
|
|
|
|
|
*/
|
|
|
|
|
function passThrough( $param ) {
|
|
|
|
|
return $param;
|
|
|
|
|
}
|
2007-07-22 14:45:12 +00:00
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Create a new fatal error
|
2011-12-20 03:52:06 +00:00
|
|
|
*
|
|
|
|
|
* @return FileRepoStatus
|
2007-07-22 14:45:12 +00:00
|
|
|
*/
|
2012-04-05 04:10:50 +00:00
|
|
|
public function newFatal( $message /*, parameters...*/ ) {
|
2007-07-22 14:45:12 +00:00
|
|
|
$params = func_get_args();
|
|
|
|
|
array_unshift( $params, $this );
|
2013-05-08 06:48:56 +00:00
|
|
|
return call_user_func_array( array( 'FileRepoStatus', 'newFatal' ), $params );
|
2007-07-22 14:45:12 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Create a new good result
|
2011-09-07 12:00:58 +00:00
|
|
|
*
|
2012-05-18 02:58:15 +00:00
|
|
|
* @param $value null|string
|
2011-09-07 12:00:58 +00:00
|
|
|
* @return FileRepoStatus
|
2007-07-22 14:45:12 +00:00
|
|
|
*/
|
2012-04-05 04:10:50 +00:00
|
|
|
public function newGood( $value = null ) {
|
2007-07-22 14:45:12 +00:00
|
|
|
return FileRepoStatus::newGood( $this, $value );
|
|
|
|
|
}
|
|
|
|
|
|
2008-01-16 18:27:43 +00:00
|
|
|
/**
|
2009-04-20 03:06:49 +00:00
|
|
|
* Checks if there is a redirect named as $title. If there is, return the
|
|
|
|
|
* title object. If not, return false.
|
|
|
|
|
* STUB
|
2008-01-16 18:27:43 +00:00
|
|
|
*
|
2010-03-26 21:55:13 +00:00
|
|
|
* @param $title Title of image
|
2011-02-18 23:56:08 +00:00
|
|
|
* @return Bool
|
2008-01-16 18:27:43 +00:00
|
|
|
*/
|
2011-12-20 03:52:06 +00:00
|
|
|
public function checkRedirect( Title $title ) {
|
2009-04-20 03:06:49 +00:00
|
|
|
return false;
|
2008-01-16 18:27:43 +00:00
|
|
|
}
|
2008-04-12 18:04:46 +00:00
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Invalidates image redirect cache related to that image
|
2009-06-17 07:31:00 +00:00
|
|
|
* Doesn't do anything for repositories that don't support image redirects.
|
2010-02-10 10:36:11 +00:00
|
|
|
*
|
2009-06-17 07:31:00 +00:00
|
|
|
* STUB
|
2010-03-26 21:55:13 +00:00
|
|
|
* @param $title Title of image
|
2010-02-10 10:36:11 +00:00
|
|
|
*/
|
2011-12-20 03:52:06 +00:00
|
|
|
public function invalidateImageRedirect( Title $title ) {}
|
2010-02-10 10:36:11 +00:00
|
|
|
|
2009-06-17 07:31:00 +00:00
|
|
|
/**
|
2011-12-20 03:52:06 +00:00
|
|
|
* Get the human-readable name of the repo
|
2009-06-17 07:31:00 +00:00
|
|
|
*
|
2009-02-17 22:27:10 +00:00
|
|
|
* @return string
|
|
|
|
|
*/
|
|
|
|
|
public function getDisplayName() {
|
|
|
|
|
// We don't name our own repo, return nothing
|
2010-06-18 20:00:27 +00:00
|
|
|
if ( $this->isLocal() ) {
|
2009-02-17 22:27:10 +00:00
|
|
|
return null;
|
|
|
|
|
}
|
2010-03-06 16:06:43 +00:00
|
|
|
// 'shared-repo-name-wikimediacommons' is used when $wgUseInstantCommons = true
|
2011-01-14 10:51:05 +00:00
|
|
|
return wfMessageFallback( 'shared-repo-name-' . $this->name, 'shared-repo' )->text();
|
2009-02-17 22:27:10 +00:00
|
|
|
}
|
2010-02-10 10:36:11 +00:00
|
|
|
|
2012-08-31 21:21:39 +00:00
|
|
|
/**
|
|
|
|
|
* Get the portion of the file that contains the origin file name.
|
|
|
|
|
* If that name is too long, then the name "thumbnail.<ext>" will be given.
|
|
|
|
|
*
|
|
|
|
|
* @param $name string
|
|
|
|
|
* @return string
|
|
|
|
|
*/
|
|
|
|
|
public function nameForThumb( $name ) {
|
|
|
|
|
if ( strlen( $name ) > $this->abbrvThreshold ) {
|
2013-03-07 16:50:43 +00:00
|
|
|
$ext = FileBackend::extensionFromPath( $name );
|
2012-08-31 21:21:39 +00:00
|
|
|
$name = ( $ext == '' ) ? 'thumbnail' : "thumbnail.$ext";
|
|
|
|
|
}
|
|
|
|
|
return $name;
|
|
|
|
|
}
|
|
|
|
|
|
2010-06-18 20:00:27 +00:00
|
|
|
/**
|
|
|
|
|
* Returns true if this the local file repository.
|
|
|
|
|
*
|
|
|
|
|
* @return bool
|
|
|
|
|
*/
|
2011-12-20 03:52:06 +00:00
|
|
|
public function isLocal() {
|
2010-06-18 20:00:27 +00:00
|
|
|
return $this->getName() == 'local';
|
|
|
|
|
}
|
|
|
|
|
|
2009-06-17 07:31:00 +00:00
|
|
|
/**
|
|
|
|
|
* Get a key on the primary cache for this repository.
|
2010-02-10 10:36:11 +00:00
|
|
|
* Returns false if the repository's cache is not accessible at this site.
|
2009-06-17 07:31:00 +00:00
|
|
|
* The parameters are the parts of the key, as for wfMemcKey().
|
|
|
|
|
*
|
|
|
|
|
* STUB
|
2012-02-09 21:33:27 +00:00
|
|
|
* @return bool
|
2009-06-17 07:31:00 +00:00
|
|
|
*/
|
2012-04-05 04:10:50 +00:00
|
|
|
public function getSharedCacheKey( /*...*/ ) {
|
2009-06-17 07:31:00 +00:00
|
|
|
return false;
|
2009-03-18 05:17:49 +00:00
|
|
|
}
|
2009-04-16 20:16:21 +00:00
|
|
|
|
2009-06-17 07:31:00 +00:00
|
|
|
/**
|
2010-02-10 10:36:11 +00:00
|
|
|
* Get a key for this repo in the local cache domain. These cache keys are
|
2009-06-17 07:31:00 +00:00
|
|
|
* not shared with remote instances of the repo.
|
|
|
|
|
* The parameters are the parts of the key, as for wfMemcKey().
|
2011-12-20 03:52:06 +00:00
|
|
|
*
|
|
|
|
|
* @return string
|
2009-06-17 07:31:00 +00:00
|
|
|
*/
|
2012-04-05 04:10:50 +00:00
|
|
|
public function getLocalCacheKey( /*...*/ ) {
|
2009-06-17 07:31:00 +00:00
|
|
|
$args = func_get_args();
|
|
|
|
|
array_unshift( $args, 'filerepo', $this->getName() );
|
|
|
|
|
return call_user_func_array( 'wfMemcKey', $args );
|
2009-04-16 20:16:21 +00:00
|
|
|
}
|
2011-06-17 16:03:52 +00:00
|
|
|
|
2012-04-06 17:11:13 +00:00
|
|
|
/**
|
|
|
|
|
* Get an temporary FileRepo associated with this repo.
|
|
|
|
|
* Files will be created in the temp zone of this repo and
|
|
|
|
|
* thumbnails in a /temp subdirectory in thumb zone of this repo.
|
|
|
|
|
* It will have the same backend as this repo.
|
|
|
|
|
*
|
|
|
|
|
* @return TempFileRepo
|
|
|
|
|
*/
|
|
|
|
|
public function getTempRepo() {
|
|
|
|
|
return new TempFileRepo( array(
|
2013-04-20 17:18:13 +00:00
|
|
|
'name' => "{$this->name}-temp",
|
|
|
|
|
'backend' => $this->backend,
|
|
|
|
|
'zones' => array(
|
2012-04-06 17:11:13 +00:00
|
|
|
'public' => array(
|
|
|
|
|
'container' => $this->zones['temp']['container'],
|
|
|
|
|
'directory' => $this->zones['temp']['directory']
|
|
|
|
|
),
|
2013-04-20 17:18:13 +00:00
|
|
|
'thumb' => array(
|
2012-04-06 17:11:13 +00:00
|
|
|
'container' => $this->zones['thumb']['container'],
|
|
|
|
|
'directory' => ( $this->zones['thumb']['directory'] == '' )
|
|
|
|
|
? 'temp'
|
|
|
|
|
: $this->zones['thumb']['directory'] . '/temp'
|
2013-01-15 23:30:41 +00:00
|
|
|
),
|
2013-04-20 17:18:13 +00:00
|
|
|
'transcoded' => array(
|
2013-01-15 23:30:41 +00:00
|
|
|
'container' => $this->zones['transcoded']['container'],
|
|
|
|
|
'directory' => ( $this->zones['transcoded']['directory'] == '' )
|
|
|
|
|
? 'temp'
|
|
|
|
|
: $this->zones['transcoded']['directory'] . '/temp'
|
2012-04-06 17:11:13 +00:00
|
|
|
)
|
|
|
|
|
),
|
2013-04-20 17:18:13 +00:00
|
|
|
'url' => $this->getZoneUrl( 'temp' ),
|
|
|
|
|
'thumbUrl' => $this->getZoneUrl( 'thumb' ) . '/temp',
|
|
|
|
|
'transcodedUrl' => $this->getZoneUrl( 'transcoded' ) . '/temp',
|
2012-04-06 17:11:13 +00:00
|
|
|
'hashLevels' => $this->hashLevels // performance
|
|
|
|
|
) );
|
|
|
|
|
}
|
|
|
|
|
|
2011-01-25 21:26:53 +00:00
|
|
|
/**
|
|
|
|
|
* Get an UploadStash associated with this repo.
|
2011-02-27 21:11:45 +00:00
|
|
|
*
|
2012-11-19 08:07:50 +00:00
|
|
|
* @param $user User
|
2011-02-27 21:11:45 +00:00
|
|
|
* @return UploadStash
|
2011-01-25 21:26:53 +00:00
|
|
|
*/
|
2012-11-19 08:07:50 +00:00
|
|
|
public function getUploadStash( User $user = null ) {
|
|
|
|
|
return new UploadStash( $this, $user );
|
2011-01-25 21:26:53 +00:00
|
|
|
}
|
2012-04-05 04:10:50 +00:00
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Throw an exception if this repo is read-only by design.
|
|
|
|
|
* This does not and should not check getReadOnlyReason().
|
|
|
|
|
*
|
|
|
|
|
* @return void
|
|
|
|
|
* @throws MWException
|
|
|
|
|
*/
|
|
|
|
|
protected function assertWritableRepo() {}
|
2007-06-16 02:55:25 +00:00
|
|
|
}
|
2012-04-06 17:11:13 +00:00
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* FileRepo for temporary files created via FileRepo::getTempRepo()
|
|
|
|
|
*/
|
|
|
|
|
class TempFileRepo extends FileRepo {
|
|
|
|
|
public function getTempRepo() {
|
|
|
|
|
throw new MWException( "Cannot get a temp repo from a temp repo." );
|
|
|
|
|
}
|
|
|
|
|
}
|