MediaWiki REL1_35
FileBackend.php
Go to the documentation of this file.
1<?php
31use Psr\Log\LoggerAwareInterface;
32use Psr\Log\LoggerInterface;
33use Psr\Log\NullLogger;
34use Wikimedia\ScopedCallback;
35
98abstract class FileBackend implements LoggerAwareInterface {
100 protected $name;
101
103 protected $domainId;
104
106 protected $readOnly;
107
109 protected $parallelize;
110
112 protected $concurrency;
113
116
118 protected $lockManager;
120 protected $fileJournal;
122 protected $logger;
124 protected $profiler;
125
127 protected $obResetFunc;
131 protected $statusWrapper;
132
134 public const ATTR_HEADERS = 1; // files can be tagged with standard HTTP headers
135 public const ATTR_METADATA = 2; // files can be stored with metadata key/values
136 public const ATTR_UNICODE_PATHS = 4; // files can have Unicode paths (not just ASCII)
137
139 protected const STAT_ABSENT = false;
140
142 public const STAT_ERROR = null;
144 public const LIST_ERROR = null;
146 public const TEMPURL_ERROR = null;
148 public const EXISTENCE_ERROR = null;
149
151 public const TIMESTAMP_FAIL = false;
153 public const CONTENT_FAIL = false;
155 public const XATTRS_FAIL = false;
157 public const SIZE_FAIL = false;
159 public const SHA1_FAIL = false;
160
196 public function __construct( array $config ) {
197 if ( !array_key_exists( 'name', $config ) ) {
198 throw new InvalidArgumentException( 'Backend name not specified.' );
199 }
200 $this->name = $config['name'];
201 $this->domainId = $config['domainId'] // e.g. "my_wiki-en_"
202 ?? $config['wikiId'] // b/c alias
203 ?? null;
204 if ( !preg_match( '!^[a-zA-Z0-9-_]{1,255}$!', $this->name ) ) {
205 throw new InvalidArgumentException( "Backend name '{$this->name}' is invalid." );
206 }
207 if ( !is_string( $this->domainId ) ) {
208 throw new InvalidArgumentException(
209 "Backend domain ID not provided for '{$this->name}'." );
210 }
211 $this->lockManager = $config['lockManager'] ?? new NullLockManager( [] );
212 $this->fileJournal = $config['fileJournal'] ?? new NullFileJournal;
213 $this->readOnly = isset( $config['readOnly'] )
214 ? (string)$config['readOnly']
215 : '';
216 $this->parallelize = isset( $config['parallelize'] )
217 ? (string)$config['parallelize']
218 : 'off';
219 $this->concurrency = isset( $config['concurrency'] )
220 ? (int)$config['concurrency']
221 : 50;
222 $this->obResetFunc = $config['obResetFunc'] ?? [ $this, 'resetOutputBuffer' ];
223 $this->streamMimeFunc = $config['streamMimeFunc'] ?? null;
224
225 $this->profiler = $config['profiler'] ?? null;
226 if ( !is_callable( $this->profiler ) ) {
227 $this->profiler = null;
228 }
229 $this->logger = $config['logger'] ?? new NullLogger();
230 $this->statusWrapper = $config['statusWrapper'] ?? null;
231 // tmpDirectory gets precedence for backward compatibility
232 if ( isset( $config['tmpDirectory'] ) ) {
233 $this->tmpFileFactory = new TempFSFileFactory( $config['tmpDirectory'] );
234 } else {
235 $this->tmpFileFactory = $config['tmpFileFactory'] ?? new TempFSFileFactory();
236 }
237 }
238
239 public function setLogger( LoggerInterface $logger ) {
240 $this->logger = $logger;
241 }
242
251 final public function getName() {
252 return $this->name;
253 }
254
261 final public function getDomainId() {
262 return $this->domainId;
263 }
264
272 final public function getWikiId() {
273 return $this->getDomainId();
274 }
275
281 final public function isReadOnly() {
282 return ( $this->readOnly != '' );
283 }
284
290 final public function getReadOnlyReason() {
291 return ( $this->readOnly != '' ) ? $this->readOnly : false;
292 }
293
301 public function getFeatures() {
303 }
304
312 final public function hasFeatures( $bitfield ) {
313 return ( $this->getFeatures() & $bitfield ) === $bitfield;
314 }
315
468 final public function doOperations( array $ops, array $opts = [] ) {
469 if ( empty( $opts['bypassReadOnly'] ) && $this->isReadOnly() ) {
470 return $this->newStatus( 'backend-fail-readonly', $this->name, $this->readOnly );
471 }
472 if ( $ops === [] ) {
473 return $this->newStatus(); // nothing to do
474 }
475
476 $ops = $this->resolveFSFileObjects( $ops );
477 if ( empty( $opts['force'] ) ) { // sanity
478 unset( $opts['nonLocking'] );
479 }
480
482 $scope = ScopedCallback::newScopedIgnoreUserAbort(); // try to ignore client aborts
483
484 return $this->doOperationsInternal( $ops, $opts );
485 }
486
493 abstract protected function doOperationsInternal( array $ops, array $opts );
494
506 final public function doOperation( array $op, array $opts = [] ) {
507 return $this->doOperations( [ $op ], $opts );
508 }
509
520 final public function create( array $params, array $opts = [] ) {
521 return $this->doOperation( [ 'op' => 'create' ] + $params, $opts );
522 }
523
534 final public function store( array $params, array $opts = [] ) {
535 return $this->doOperation( [ 'op' => 'store' ] + $params, $opts );
536 }
537
548 final public function copy( array $params, array $opts = [] ) {
549 return $this->doOperation( [ 'op' => 'copy' ] + $params, $opts );
550 }
551
562 final public function move( array $params, array $opts = [] ) {
563 return $this->doOperation( [ 'op' => 'move' ] + $params, $opts );
564 }
565
576 final public function delete( array $params, array $opts = [] ) {
577 return $this->doOperation( [ 'op' => 'delete' ] + $params, $opts );
578 }
579
591 final public function describe( array $params, array $opts = [] ) {
592 return $this->doOperation( [ 'op' => 'describe' ] + $params, $opts );
593 }
594
711 final public function doQuickOperations( array $ops, array $opts = [] ) {
712 if ( empty( $opts['bypassReadOnly'] ) && $this->isReadOnly() ) {
713 return $this->newStatus( 'backend-fail-readonly', $this->name, $this->readOnly );
714 }
715 if ( $ops === [] ) {
716 return $this->newStatus(); // nothing to do
717 }
718
719 $ops = $this->resolveFSFileObjects( $ops );
720 foreach ( $ops as &$op ) {
721 $op['overwrite'] = true; // avoids RTTs in key/value stores
722 }
723
725 $scope = ScopedCallback::newScopedIgnoreUserAbort(); // try to ignore client aborts
726
727 return $this->doQuickOperationsInternal( $ops, $opts );
728 }
729
737 abstract protected function doQuickOperationsInternal( array $ops, array $opts );
738
750 final public function doQuickOperation( array $op, array $opts = [] ) {
751 return $this->doQuickOperations( [ $op ], $opts );
752 }
753
765 final public function quickCreate( array $params, array $opts = [] ) {
766 return $this->doQuickOperation( [ 'op' => 'create' ] + $params, $opts );
767 }
768
780 final public function quickStore( array $params, array $opts = [] ) {
781 return $this->doQuickOperation( [ 'op' => 'store' ] + $params, $opts );
782 }
783
795 final public function quickCopy( array $params, array $opts = [] ) {
796 return $this->doQuickOperation( [ 'op' => 'copy' ] + $params, $opts );
797 }
798
810 final public function quickMove( array $params, array $opts = [] ) {
811 return $this->doQuickOperation( [ 'op' => 'move' ] + $params, $opts );
812 }
813
825 final public function quickDelete( array $params, array $opts = [] ) {
826 return $this->doQuickOperation( [ 'op' => 'delete' ] + $params, $opts );
827 }
828
840 final public function quickDescribe( array $params, array $opts = [] ) {
841 return $this->doQuickOperation( [ 'op' => 'describe' ] + $params, $opts );
842 }
843
856 abstract public function concatenate( array $params );
857
876 final public function prepare( array $params ) {
877 if ( empty( $params['bypassReadOnly'] ) && $this->isReadOnly() ) {
878 return $this->newStatus( 'backend-fail-readonly', $this->name, $this->readOnly );
879 }
881 $scope = ScopedCallback::newScopedIgnoreUserAbort(); // try to ignore client aborts
882 return $this->doPrepare( $params );
883 }
884
890 abstract protected function doPrepare( array $params );
891
908 final public function secure( array $params ) {
909 if ( empty( $params['bypassReadOnly'] ) && $this->isReadOnly() ) {
910 return $this->newStatus( 'backend-fail-readonly', $this->name, $this->readOnly );
911 }
913 $scope = ScopedCallback::newScopedIgnoreUserAbort(); // try to ignore client aborts
914 return $this->doSecure( $params );
915 }
916
922 abstract protected function doSecure( array $params );
923
942 final public function publish( array $params ) {
943 if ( empty( $params['bypassReadOnly'] ) && $this->isReadOnly() ) {
944 return $this->newStatus( 'backend-fail-readonly', $this->name, $this->readOnly );
945 }
947 $scope = ScopedCallback::newScopedIgnoreUserAbort(); // try to ignore client aborts
948 return $this->doPublish( $params );
949 }
950
956 abstract protected function doPublish( array $params );
957
969 final public function clean( array $params ) {
970 if ( empty( $params['bypassReadOnly'] ) && $this->isReadOnly() ) {
971 return $this->newStatus( 'backend-fail-readonly', $this->name, $this->readOnly );
972 }
974 $scope = ScopedCallback::newScopedIgnoreUserAbort(); // try to ignore client aborts
975 return $this->doClean( $params );
976 }
977
983 abstract protected function doClean( array $params );
984
1001 abstract public function fileExists( array $params );
1002
1013 abstract public function getFileTimestamp( array $params );
1014
1026 final public function getFileContents( array $params ) {
1027 $contents = $this->getFileContentsMulti( [ 'srcs' => [ $params['src'] ] ] + $params );
1028
1029 return $contents[$params['src']];
1030 }
1031
1045 abstract public function getFileContentsMulti( array $params );
1046
1067 abstract public function getFileXAttributes( array $params );
1068
1079 abstract public function getFileSize( array $params );
1080
1097 abstract public function getFileStat( array $params );
1098
1109 abstract public function getFileSha1Base36( array $params );
1110
1120 abstract public function getFileProps( array $params );
1121
1141 abstract public function streamFile( array $params );
1142
1161 final public function getLocalReference( array $params ) {
1162 $fsFiles = $this->getLocalReferenceMulti( [ 'srcs' => [ $params['src'] ] ] + $params );
1163
1164 return $fsFiles[$params['src']];
1165 }
1166
1184 abstract public function getLocalReferenceMulti( array $params );
1185
1198 final public function getLocalCopy( array $params ) {
1199 $tmpFiles = $this->getLocalCopyMulti( [ 'srcs' => [ $params['src'] ] ] + $params );
1200
1201 return $tmpFiles[$params['src']];
1202 }
1203
1219 abstract public function getLocalCopyMulti( array $params );
1220
1239 abstract public function getFileHttpUrl( array $params );
1240
1266 abstract public function directoryExists( array $params );
1267
1290 abstract public function getDirectoryList( array $params );
1291
1308 final public function getTopDirectoryList( array $params ) {
1309 return $this->getDirectoryList( [ 'topOnly' => true ] + $params );
1310 }
1311
1332 abstract public function getFileList( array $params );
1333
1350 final public function getTopFileList( array $params ) {
1351 return $this->getFileList( [ 'topOnly' => true ] + $params );
1352 }
1353
1362 abstract public function preloadCache( array $paths );
1363
1372 abstract public function clearCache( array $paths = null );
1373
1388 abstract public function preloadFileStat( array $params );
1389
1401 final public function lockFiles( array $paths, $type, $timeout = 0 ) {
1402 $paths = array_map( 'FileBackend::normalizeStoragePath', $paths );
1403
1404 return $this->wrapStatus( $this->lockManager->lock( $paths, $type, $timeout ) );
1405 }
1406
1414 final public function unlockFiles( array $paths, $type ) {
1415 $paths = array_map( 'FileBackend::normalizeStoragePath', $paths );
1416
1417 return $this->wrapStatus( $this->lockManager->unlock( $paths, $type ) );
1418 }
1419
1436 final public function getScopedFileLocks(
1437 array $paths, $type, StatusValue $status, $timeout = 0
1438 ) {
1439 if ( $type === 'mixed' ) {
1440 foreach ( $paths as &$typePaths ) {
1441 $typePaths = array_map( 'FileBackend::normalizeStoragePath', $typePaths );
1442 }
1443 } else {
1444 $paths = array_map( 'FileBackend::normalizeStoragePath', $paths );
1445 }
1446
1447 return ScopedLock::factory( $this->lockManager, $paths, $type, $status, $timeout );
1448 }
1449
1466 abstract public function getScopedLocksForOps( array $ops, StatusValue $status );
1467
1475 final public function getRootStoragePath() {
1476 return "mwstore://{$this->name}";
1477 }
1478
1486 final public function getContainerStoragePath( $container ) {
1487 return $this->getRootStoragePath() . "/{$container}";
1488 }
1489
1495 final public function getJournal() {
1496 return $this->fileJournal;
1497 }
1498
1508 protected function resolveFSFileObjects( array $ops ) {
1509 foreach ( $ops as &$op ) {
1510 $src = $op['src'] ?? null;
1511 if ( $src instanceof FSFile ) {
1512 $op['srcRef'] = $src;
1513 $op['src'] = $src->getPath();
1514 }
1515 }
1516 unset( $op );
1517
1518 return $ops;
1519 }
1520
1528 final public static function isStoragePath( $path ) {
1529 return ( strpos( $path ?? '', 'mwstore://' ) === 0 );
1530 }
1531
1540 final public static function splitStoragePath( $storagePath ) {
1541 if ( self::isStoragePath( $storagePath ) ) {
1542 // Remove the "mwstore://" prefix and split the path
1543 $parts = explode( '/', substr( $storagePath, 10 ), 3 );
1544 if ( count( $parts ) >= 2 && $parts[0] != '' && $parts[1] != '' ) {
1545 if ( count( $parts ) == 3 ) {
1546 return $parts; // e.g. "backend/container/path"
1547 } else {
1548 return [ $parts[0], $parts[1], '' ]; // e.g. "backend/container"
1549 }
1550 }
1551 }
1552
1553 return [ null, null, null ];
1554 }
1555
1563 final public static function normalizeStoragePath( $storagePath ) {
1564 list( $backend, $container, $relPath ) = self::splitStoragePath( $storagePath );
1565 if ( $relPath !== null ) { // must be for this backend
1566 $relPath = self::normalizeContainerPath( $relPath );
1567 if ( $relPath !== null ) {
1568 return ( $relPath != '' )
1569 ? "mwstore://{$backend}/{$container}/{$relPath}"
1570 : "mwstore://{$backend}/{$container}";
1571 }
1572 }
1573
1574 return null;
1575 }
1576
1585 final public static function parentStoragePath( $storagePath ) {
1586 // XXX dirname() depends on platform and locale! If nothing enforces that the storage path
1587 // doesn't contain characters like '\', behavior can vary by platform. We should use
1588 // explode() instead.
1589 $storagePath = dirname( $storagePath );
1590 list( , , $rel ) = self::splitStoragePath( $storagePath );
1591
1592 return ( $rel === null ) ? null : $storagePath;
1593 }
1594
1602 final public static function extensionFromPath( $path, $case = 'lowercase' ) {
1603 // This will treat a string starting with . as not having an extension, but store paths have
1604 // to start with 'mwstore://', so "garbage in, garbage out".
1605 $i = strrpos( $path, '.' );
1606 $ext = $i ? substr( $path, $i + 1 ) : '';
1607
1608 if ( $case === 'lowercase' ) {
1609 $ext = strtolower( $ext );
1610 } elseif ( $case === 'uppercase' ) {
1611 $ext = strtoupper( $ext );
1612 }
1613
1614 return $ext;
1615 }
1616
1624 final public static function isPathTraversalFree( $path ) {
1625 return ( self::normalizeContainerPath( $path ) !== null );
1626 }
1627
1637 final public static function makeContentDisposition( $type, $filename = '' ) {
1638 $parts = [];
1639
1640 $type = strtolower( $type );
1641 if ( !in_array( $type, [ 'inline', 'attachment' ] ) ) {
1642 throw new InvalidArgumentException( "Invalid Content-Disposition type '$type'." );
1643 }
1644 $parts[] = $type;
1645
1646 if ( strlen( $filename ) ) {
1647 $parts[] = "filename*=UTF-8''" . rawurlencode( basename( $filename ) );
1648 }
1649
1650 return implode( ';', $parts );
1651 }
1652
1663 final protected static function normalizeContainerPath( $path ) {
1664 // Normalize directory separators
1665 $path = strtr( $path, '\\', '/' );
1666 // Collapse any consecutive directory separators
1667 $path = preg_replace( '![/]{2,}!', '/', $path );
1668 // Remove any leading directory separator
1669 $path = ltrim( $path, '/' );
1670 // Use the same traversal protection as Title::secureAndSplit()
1671 if ( strpos( $path, '.' ) !== false ) {
1672 if (
1673 $path === '.' ||
1674 $path === '..' ||
1675 strpos( $path, './' ) === 0 ||
1676 strpos( $path, '../' ) === 0 ||
1677 strpos( $path, '/./' ) !== false ||
1678 strpos( $path, '/../' ) !== false
1679 ) {
1680 return null;
1681 }
1682 }
1683
1684 return $path;
1685 }
1686
1695 final protected function newStatus( ...$args ) {
1696 if ( count( $args ) ) {
1697 $sv = StatusValue::newFatal( ...$args );
1698 } else {
1699 $sv = StatusValue::newGood();
1700 }
1701
1702 return $this->wrapStatus( $sv );
1703 }
1704
1709 final protected function wrapStatus( StatusValue $sv ) {
1710 return $this->statusWrapper ? call_user_func( $this->statusWrapper, $sv ) : $sv;
1711 }
1712
1717 protected function scopedProfileSection( $section ) {
1718 return $this->profiler ? ( $this->profiler )( $section ) : null;
1719 }
1720
1724 protected function resetOutputBuffer() {
1725 // XXX According to documentation, ob_get_status() always returns a non-empty array and this
1726 // condition will always be true
1727 while ( ob_get_status() ) {
1728 if ( !ob_end_clean() ) {
1729 // Could not remove output buffer handler; abort now
1730 // to avoid getting in some kind of infinite loop.
1731 break;
1732 }
1733 }
1734 }
1735}
Class representing a non-directory file on the file system.
Definition FSFile.php:32
Base class for all file backend classes (including multi-write backends).
getScopedLocksForOps(array $ops, StatusValue $status)
Get an array of scoped locks needed for a batch of file operations.
concatenate(array $params)
Concatenate a list of storage files into a single file system file.
static parentStoragePath( $storagePath)
Get the parent storage directory of a storage path.
doOperation(array $op, array $opts=[])
Same as doOperations() except it takes a single operation.
create(array $params, array $opts=[])
Performs a single create operation.
quickDescribe(array $params, array $opts=[])
Performs a single quick describe operation.
wrapStatus(StatusValue $sv)
static isStoragePath( $path)
Check if a given path is a "mwstore://" path.
hasFeatures( $bitfield)
Check if the backend medium supports a field of extra features.
getFileXAttributes(array $params)
Get metadata about a file at a storage path in the backend.
getLocalCopy(array $params)
Get a local copy on disk of the file at a storage path in the backend.
clean(array $params)
Delete a storage directory if it is empty.
quickCreate(array $params, array $opts=[])
Performs a single quick create operation.
string $name
Unique backend name.
move(array $params, array $opts=[])
Performs a single move operation.
getDirectoryList(array $params)
Get an iterator to list all directories under a storage directory.
getFileList(array $params)
Get an iterator to list all stored files under a storage directory.
callable null $profiler
lockFiles(array $paths, $type, $timeout=0)
Lock the files at the given storage paths in the backend.
preloadFileStat(array $params)
Preload file stat information (concurrently if possible) into in-process cache.
describe(array $params, array $opts=[])
Performs a single describe operation.
getDomainId()
Get the domain identifier used for this backend (possibly empty).
getFeatures()
Get the a bitfield of extra features supported by the backend medium Stable to override.
streamFile(array $params)
Stream the content of the file at a storage path in the backend.
getFileSha1Base36(array $params)
Get a SHA-1 hash of the content of the file at a storage path in the backend.
resetOutputBuffer()
Let's not reset output buffering during tests.
getReadOnlyReason()
Get an explanatory message if this backend is read-only.
doClean(array $params)
publish(array $params)
Remove measures to block web access to a storage directory and the container it belongs to.
quickDelete(array $params, array $opts=[])
Performs a single quick delete operation.
const ATTR_UNICODE_PATHS
callable $obResetFunc
clearCache(array $paths=null)
Invalidate any in-process file stat and property cache.
getFileStat(array $params)
Get quick information about a file at a storage path in the backend.
fileExists(array $params)
Check if a file exists at a storage path in the backend.
static splitStoragePath( $storagePath)
Split a storage path into a backend name, a container name, and a relative file path.
getFileTimestamp(array $params)
Get the last-modified timestamp of the file at a storage path.
LoggerInterface $logger
static extensionFromPath( $path, $case='lowercase')
Get the final extension from a storage or FS path.
static makeContentDisposition( $type, $filename='')
Build a Content-Disposition header value per RFC 6266.
static normalizeContainerPath( $path)
Validate and normalize a relative storage path.
string $readOnly
Read-only explanation message.
preloadCache(array $paths)
Preload persistent file stat cache and property cache into in-process cache.
string $domainId
Unique domain name.
getScopedFileLocks(array $paths, $type, StatusValue $status, $timeout=0)
Lock the files at the given storage paths in the backend.
store(array $params, array $opts=[])
Performs a single store operation.
isReadOnly()
Check if this backend is read-only.
getRootStoragePath()
Get the root storage path of this backend.
getTopDirectoryList(array $params)
Same as FileBackend::getDirectoryList() except only lists directories that are immediately under the ...
getFileProps(array $params)
Get the properties of the content of the file at a storage path in the backend.
getFileSize(array $params)
Get the size (bytes) of a file at a storage path in the backend.
getWikiId()
Alias to getDomainId()
prepare(array $params)
Prepare a storage directory for usage.
getFileContentsMulti(array $params)
Like getFileContents() except it takes an array of storage paths and returns an order preserved map o...
doQuickOperations(array $ops, array $opts=[])
Perform a set of independent file operations on some files.
unlockFiles(array $paths, $type)
Unlock the files at the given storage paths in the backend.
getContainerStoragePath( $container)
Get the storage path for the given container for this backend.
scopedProfileSection( $section)
const ATTR_METADATA
doOperations(array $ops, array $opts=[])
This is the main entry point into the backend for write operations.
newStatus(... $args)
Yields the result of the status wrapper callback on either:
static normalizeStoragePath( $storagePath)
Normalize a storage path by cleaning up directory separators.
directoryExists(array $params)
Check if a directory exists at a given storage path.
callable $streamMimeFunc
resolveFSFileObjects(array $ops)
Convert FSFile 'src' paths to string paths (with an 'srcRef' field set to the FSFile)
quickMove(array $params, array $opts=[])
Performs a single quick move operation.
string $parallelize
When to do operations in parallel.
doPublish(array $params)
LockManager $lockManager
getLocalCopyMulti(array $params)
Like getLocalCopy() except it takes an array of storage paths and yields an order preserved-map of st...
int $concurrency
How many operations can be done in parallel.
getTopFileList(array $params)
Same as FileBackend::getFileList() except only lists files that are immediately under the given direc...
const ATTR_HEADERS
Bitfield flags for supported features.
doPrepare(array $params)
__construct(array $config)
Create a new backend instance from configuration.
quickCopy(array $params, array $opts=[])
Performs a single quick copy operation.
static isPathTraversalFree( $path)
Check if a relative path has no directory traversals.
quickStore(array $params, array $opts=[])
Performs a single quick store operation.
doQuickOperation(array $op, array $opts=[])
Same as doQuickOperations() except it takes a single operation.
callable $statusWrapper
doSecure(array $params)
setLogger(LoggerInterface $logger)
doQuickOperationsInternal(array $ops, array $opts)
getLocalReferenceMulti(array $params)
Like getLocalReference() except it takes an array of storage paths and yields an order-preserved map ...
copy(array $params, array $opts=[])
Performs a single copy operation.
FileJournal $fileJournal
getFileContents(array $params)
Get the contents of a file at a storage path in the backend.
TempFSFileFactory $tmpFileFactory
getLocalReference(array $params)
Returns a file system file, identical in content to the file at a storage path.
getName()
Get the unique backend name.
getJournal()
Get the file journal object for this backend.
getFileHttpUrl(array $params)
Return an HTTP URL to a given file that requires no authentication to use.
doOperationsInternal(array $ops, array $opts)
secure(array $params)
Take measures to block web access to a storage directory and the container it belongs to.
Class for handling file operation journaling.
Class for handling resource locking.
Simple version of FileJournal that does nothing.
Simple version of LockManager that only does lock reference counting.
static factory(LockManager $manager, array $paths, $type, StatusValue $status, $timeout=0)
Get a ScopedLock object representing a lock on resource paths.
Generic operation result class Has warning/error list, boolean status and arbitrary value.
if( $line===false) $args
Definition mcc.php:124
if(!is_readable( $file)) $ext
Definition router.php:48