Files
server/usr/share/psa-pear/pear/php/Horde/Vfs/Base.php
2026-01-07 20:52:11 +01:00

850 lines
24 KiB
PHP

<?php
/**
* VFS API for abstracted file storage and access.
*
* Copyright 2002-2017 Horde LLC (http://www.horde.org/)
*
* See the enclosed file COPYING for license information (LGPL). If you
* did not receive this file, see http://www.horde.org/licenses/lgpl21.
*
* @author Chuck Hagenbuch <chuck@horde.org>
* @package Vfs
*/
abstract class Horde_Vfs_Base
{
/**
* Hash containing connection parameters.
*
* @var array
*/
protected $_params = array();
/**
* List of additional credentials required for this VFS backend (example:
* For FTP, we need a username and password to log in to the server with).
*
* @var array
*/
protected $_credentials = array();
/**
* List of permissions and if they can be changed in this VFS backend.
*
* @var array
*/
protected $_permissions = array(
'owner' => array(
'read' => false,
'write' => false,
'execute' => false
),
'group' => array(
'read' => false,
'write' => false,
'execute' => false
),
'all' => array(
'read' => false,
'write' => false,
'execute' => false
)
);
/**
* List of features that the VFS driver supports.
*
* @var array
*/
protected $_features = array(
'readByteRange' => false,
);
/**
* The current size, in bytes, of the VFS tree.
*
* @var integer
*/
protected $_vfsSize = null;
/**
* Constructor.
*
* @param array $params A hash containing connection parameters.
*/
public function __construct($params = array())
{
$this->setParams(array(
'user' => '',
'vfs_quotalimit' => -1,
'vfs_quotaroot' => ''
));
$this->setParams($params);
}
/**
* Returns whether the drivers supports a certain feature.
*
* @param string $feature A feature name. See {@link $_features} for a
* list of possible features.
*
* @return boolean True if the feature is supported.
*/
public function hasFeature($feature)
{
return !empty($this->_features[$feature]);
}
/**
* Checks the credentials that we have by calling _connect(), to see if
* there is a valid login.
*
* @throws Horde_Vfs_Exception
*/
public function checkCredentials()
{
$this->_connect();
}
/**
* TODO
*
* @throws Horde_Vfs_Exception
*/
protected function _connect()
{
}
/**
* Sets configuration parameters.
*
* @param array $params An associative array with parameter names as
* keys.
*/
public function setParams($params = array())
{
$this->_params = array_merge($this->_params, $params);
}
/**
* Returns configuration parameters.
*
* @param string $name The parameter to return.
*
* @return mixed The parameter value or null if it doesn't exist.
*/
public function getParam($name)
{
return isset($this->_params[$name])
? $this->_params[$name]
: null;
}
/**
* Retrieves the size of a file from the VFS.
*
* @abstract
*
* @param string $path The pathname to the file.
* @param string $name The filename to retrieve.
*
* @return integer The file size.
* @throws Horde_Vfs_Exception
*/
public function size($path, $name)
{
throw new Horde_Vfs_Exception('Not supported.');
}
/**
* Returns the size of a folder.
*
* @param string $path The path of the folder.
*
* @return integer The size of the folder, in bytes.
* @throws Horde_Vfs_Exception
*/
public function getFolderSize($path = null)
{
$size = 0;
$root = !is_null($path) ? $path . '/' : '';
$object_list = $this->listFolder($root, null, true, false, true);
foreach ($object_list as $key => $val) {
$size += isset($val['subdirs'])
? $this->getFolderSize($root . '/' . $key)
: $this->size($root, $key);
}
return $size;
}
/**
* Retrieves a file from the VFS.
*
* @abstract
*
* @param string $path The pathname to the file.
* @param string $name The filename to retrieve.
*
* @return string The file data.
* @throws Horde_Vfs_Exception
*/
public function read($path, $name)
{
throw new Horde_Vfs_Exception('Not supported.');
}
/**
* Retrieves a file from the VFS as an on-disk local file.
*
* This function provides a file on local disk with the data of a VFS file
* in it. This file <em>cannot</em> be modified! The behavior if you do
* modify it is undefined. It will be removed at the end of the request.
*
* @param string $path The pathname to the file.
* @param string $name The filename to retrieve.
*
* @return string A local filename.
* @throws Horde_Vfs_Exception
*/
public function readFile($path, $name)
{
// Create a temporary file and register it for deletion at the
// end of this request.
if (!($localFile = Horde_Util::getTempFile('vfs'))) {
throw new Horde_Vfs_Exception('Unable to create temporary file.');
}
if (is_callable(array($this, 'readStream'))) {
// Use a stream from the VFS if possible, to avoid reading all data
// into memory.
$stream = $this->readStream($path, $name);
if (!($localStream = fopen($localFile, 'w'))) {
throw new Horde_Vfs_Exception('Unable to open temporary file.');
}
stream_copy_to_stream($stream, $localStream);
fclose($localStream);
} else {
// We have to read all of the data in one shot.
$data = $this->read($path, $name);
file_put_contents($localFile, $data);
}
// $localFile now has $path/$name's data in it.
return $localFile;
}
/**
* Retrieves a part of a file from the VFS. Particularly useful when
* reading large files which would exceed the PHP memory limits if they
* were stored in a string.
*
* @abstract
*
* @param string $path The pathname to the file.
* @param string $name The filename to retrieve.
* @param integer $offset The offset of the part. (The new offset will
* be stored in here).
* @param integer $length The length of the part. If the length = -1,
* the whole part after the offset is retrieved.
* If more bytes are given as exists after the
* given offset. Only the available bytes are
* read.
* @param integer $remaining The bytes that are left, after the part that
* is retrieved.
*
* @return string The file data.
* @throws Horde_Vfs_Exception
*/
public function readByteRange($path, $name, &$offset, $length, &$remaining)
{
throw new Horde_Vfs_Exception('Not supported.');
}
/**
* Stores a file in the VFS.
*
* @abstract
*
* @param string $path The path to store the file in.
* @param string $name The filename to use.
* @param string $tmpFile The temporary file containing the data to
* be stored.
* @param boolean $autocreate Automatically create directories?
*
* @throws Horde_Vfs_Exception
*/
public function write($path, $name, $tmpFile, $autocreate = false)
{
throw new Horde_Vfs_Exception('Not supported.');
}
/**
* Stores a file in the VFS from raw data.
*
* @abstract
*
* @param string $path The path to store the file in.
* @param string $name The filename to use.
* @param string|resource $data The data as a string or stream resource.
* Resources allowed @since 2.4.0
* @param boolean $autocreate Automatically create directories?
*
* @throws Horde_Vfs_Exception
*/
public function writeData($path, $name, $data, $autocreate = false)
{
throw new Horde_Vfs_Exception('Not supported.');
}
/**
* Moves a file through the backend.
*
* @param string $path The path of the original file.
* @param string $name The name of the original file.
* @param string $dest The destination file name.
* @param boolean $autocreate Automatically create directories?
*
* @throws Horde_Vfs_Exception
*/
public function move($path, $name, $dest, $autocreate = false)
{
$this->copy($path, $name, $dest, $autocreate);
if ($this->isFolder($path, $name)) {
$this->deleteFolder($path, $name, true);
} else {
$this->deleteFile($path, $name);
}
}
/**
* Copies a file through the backend.
*
* @param string $path The path of the original file.
* @param string $name The name of the original file.
* @param string $dest The name of the destination directory.
* @param boolean $autocreate Automatically create directories?
*
* @throws Horde_Vfs_Exception
*/
public function copy($path, $name, $dest, $autocreate = false)
{
$this->_checkDestination($path, $dest);
if ($autocreate) {
$this->autocreatePath($dest);
}
if ($this->isFolder($path, $name)) {
$this->_copyRecursive($path, $name, $dest);
} else {
$this->writeData($dest, $name, $this->read($path, $name), $autocreate);
}
}
/**
* Checks whether a source and destination directory are the same.
*
* @param string $path A source path.
* @param string $dest A destination path.
*
* @throws Horce_Vfs_Exception of both paths are the same.
*/
protected function _checkDestination($path, $dest)
{
if (preg_match('|^' . preg_quote(rtrim($path, '/'), '|') . '/?$|', $dest)) {
throw new Horde_Vfs_Exception('Cannot copy file(s) - source and destination are the same.');
}
}
/**
* Recursively copies a directory through the backend.
*
* @param string $path The path of the original file.
* @param string $name The name of the original file.
* @param string $dest The name of the destination directory.
*
* @throws Horde_Vfs_Exception
*/
protected function _copyRecursive($path, $name, $dest)
{
$this->createFolder($dest, $name);
$file_list = $this->listFolder($this->_getPath($path, $name));
foreach ($file_list as $file) {
$this->copy($this->_getPath($path, $name), $file['name'], $this->_getPath($dest, $name));
}
}
/**
* Alias to deleteFile()
*/
public function delete($path, $name)
{
$this->deleteFile($path, $name);
}
/**
* Deletes a file from the VFS.
*
* @abstract
*
* @param string $path The path to delete the file from.
* @param string $name The filename to delete.
*
* @throws Horde_Vfs_Exception
*/
public function deleteFile($path, $name)
{
throw new Horde_Vfs_Exception('Not supported.');
}
/**
* Renames a file in the VFS.
*
* @abstract
*
* @param string $oldpath The old path to the file.
* @param string $oldname The old filename.
* @param string $newpath The new path of the file.
* @param string $newname The new filename.
*
* @throws Horde_Vfs_Exception
*/
public function rename($oldpath, $oldname, $newpath, $newname)
{
throw new Horde_Vfs_Exception('Not supported.');
}
/**
* Returns if a given file or folder exists in a folder.
*
* @param string $path The path to the folder.
* @param string $name The file or folder name.
*
* @return boolean True if it exists, false otherwise.
*/
public function exists($path, $name)
{
try {
$list = $this->listFolder($path);
return isset($list[$name]);
} catch (Horde_Vfs_Exception $e) {
return false;
}
}
/**
* Creates a folder in the VFS.
*
* @abstract
*
* @param string $path The parent folder.
* @param string $name The name of the new folder.
*
* @throws Horde_Vfs_Exception
*/
public function createFolder($path, $name)
{
throw new Horde_Vfs_Exception('Not supported.');
}
/**
* Automatically creates any necessary parent directories in the specified
* $path.
*
* @param string $path The VFS path to autocreate.
*
* @throws Horde_Vfs_Exception
*/
public function autocreatePath($path)
{
$dirs = explode('/', $path);
$cur = '/';
foreach ($dirs as $dir) {
if (!strlen($dir)) {
continue;
}
if (!$this->isFolder($cur, $dir)) {
$this->createFolder($cur, $dir);
}
if ($cur != '/') {
$cur .= '/';
}
$cur .= $dir;
}
}
/**
* Checks if a given item is a folder.
*
* @param string $path The parent folder.
* @param string $name The item name.
*
* @return boolean True if it is a folder, false otherwise.
*/
public function isFolder($path, $name)
{
try {
$folderList = $this->listFolder($path, null, true, true);
return isset($folderList[$name]);
} catch (Horde_Vfs_Exception $e) {
return false;
}
}
/**
* Deletes a folder from the VFS.
*
* @abstract
*
* @param string $path The parent folder.
* @param string $name The name of the folder to delete.
* @param boolean $recursive Force a recursive delete?
*
* @throws Horde_Vfs_Exception
*/
public function deleteFolder($path, $name, $recursive = false)
{
throw new Horde_Vfs_Exception('Not supported.');
}
/**
* Recursively remove all files and subfolders from the given
* folder.
*
* @param string $path The path of the folder to empty.
*
* @throws Horde_Vfs_Exception
*/
public function emptyFolder($path)
{
// Get and delete the subfolders.
foreach ($this->listFolder($path, null, true, true) as $folder) {
$this->deleteFolder($path, $folder['name'], true);
}
// Only files are left, get and delete them.
foreach ($this->listFolder($path) as $file) {
$this->deleteFile($path, $file['name']);
}
}
/**
* Returns a file list of the directory passed in.
*
* @todo Move all but $path to an $opts argument.
*
* @param string $path The path of the directory.
* @param string|array $filter Regular expression(s) to filter
* file/directory name on.
* @param boolean $dotfiles Show dotfiles?
* @param boolean $dironly Show only directories?
* @param boolean $recursive Return all directory levels recursively?
*
* @return array File list.
* @throws Horde_Vfs_Exception
*/
public function listFolder($path, $filter = null, $dotfiles = true,
$dironly = false, $recursive = false)
{
$list = $this->_listFolder($path, $filter, $dotfiles, $dironly);
if (!$recursive) {
return $list;
}
if (strlen($path)) {
$path .= '/';
}
foreach ($list as $name => $values) {
if ($values['type'] == '**dir') {
$list[$name]['subdirs'] = $this->listFolder($path . $name, $filter, $dotfiles, $dironly, $recursive);
}
}
return $list;
}
/**
* Returns an an unsorted file list of the specified directory.
*
* @abstract
*
* @param string $path The path of the directory.
* @param string|array $filter Regular expression(s) to filter
* file/directory name on.
* @param boolean $dotfiles Show dotfiles?
* @param boolean $dironly Show only directories?
*
* @return array File list.
* @throws Horde_Vfs_Exception
*/
protected function _listFolder($path, $filter = null, $dotfiles = true,
$dironly = false)
{
throw new Horde_Vfs_Exception('Not supported.');
}
/**
* Returns the current working directory of the VFS backend.
*
* @return string The current working directory.
*/
public function getCurrentDirectory()
{
return '';
}
/**
* Returns whether or not a file or directory name matches an filter
* element.
*
* @param string|array $filter Regular expression(s) to build the filter
* from.
* @param string $filename String containing the file/directory name
* to match.
*
* @return boolean True on match, false on no match.
*/
protected function _filterMatch($filter, $filename)
{
if (is_array($filter)) {
$filter = implode('|', $filter);
}
if (!strlen($filter)) {
return false;
}
return preg_match('/' . $filter . '/', $filename);
}
/**
* Changes permissions for an item on the VFS.
*
* @abstract
*
* @param string $path The parent folder of the item.
* @param string $name The name of the item.
* @param string $permission The permission to set.
*
* @throws Horde_Vfs_Exception
*/
public function changePermissions($path, $name, $permission)
{
throw new Horde_Vfs_Exception('Not supported.');
}
/**
* Returns the list of additional credentials required, if any.
*
* @return array Credential list.
*/
public function getRequiredCredentials()
{
return array_diff($this->_credentials, array_keys($this->_params));
}
/**
* Returns an array specifying what permissions are changeable for this
* VFS implementation.
*
* @return array Changeable permisions.
*/
public function getModifiablePermissions()
{
return $this->_permissions;
}
/**
* Returns the size of the VFS item.
*
* @return integer The size, in bytes, of the VFS item.
*/
public function getVFSSize()
{
if (is_null($this->_vfsSize)) {
$this->_vfsSize = $this->getFolderSize($this->_params['vfs_quotaroot']);
}
return $this->_vfsSize;
}
/**
* Sets the VFS quota limit.
*
* @param integer $quota The limit to apply.
* @param integer $metric The metric to multiply the quota into.
*/
public function setQuota($quota, $metric = Horde_Vfs::QUOTA_METRIC_BYTE)
{
switch ($metric) {
case Horde_Vfs::QUOTA_METRIC_KB:
$quota *= pow(2, 10);
break;
case Horde_Vfs::QUOTA_METRIC_MB:
$quota *= pow(2, 20);
break;
case Horde_Vfs::QUOTA_METRIC_GB:
$quota *= pow(2, 30);
break;
}
$this->_params['vfs_quotalimit'] = $quota;
}
/**
* Sets the VFS quota root.
*
* @param string $dir The root directory for the quota determination.
*/
public function setQuotaRoot($dir)
{
$this->_params['vfs_quotaroot'] = $dir;
}
/**
* Get quota information (used/allocated), in bytes.
*
* @return mixed An associative array.
* <pre>
* 'limit' = Maximum quota allowed
* 'usage' = Currently used portion of quota (in bytes)
* </pre>
* @throws Horde_Vfs_Exception
*/
public function getQuota()
{
if ($this->_params['vfs_quotalimit'] == -1) {
throw new Horde_Vfs_Exception('No quota set.');
}
return array(
'limit' => $this->_params['vfs_quotalimit'],
'usage' => $this->getVFSSize()
);
}
/**
* Checks the quota when preparing to write data.
*
* @param string $mode Either 'string' or 'file'. If 'string', $data is
* the data to be written. If 'file', $data is the
* filename containing the data to be written.
* @param string $data Either the data or the filename to the data.
* @param string $path The path the file is located in.
* @param string $name The filename.
*
* @throws Horde_Vfs_Exception
*/
protected function _checkQuotaWrite($mode, $data, $path = null, $name = null)
{
if ($this->_params['vfs_quotalimit'] == -1 &&
is_null($this->_vfsSize)) {
return;
}
if ($mode == 'file') {
$filesize = filesize($data);
if ($filesize === false) {
throw new Horde_Vfs_Exception('Unable to read VFS file (filesize() failed).');
}
} else {
$filesize = $this->_getDataSize($data);
}
$oldsize = 0;
if ($name) {
try {
$oldsize = $this->size($path, $name);
} catch (Horde_Vfs_Exception $e) {
}
}
$vfssize = $this->getVFSSize();
if ($this->_params['vfs_quotalimit'] > -1 &&
($vfssize + $filesize - $oldsize) > $this->_params['vfs_quotalimit']) {
throw new Horde_Vfs_Exception('Unable to write VFS file, quota will be exceeded.');
}
if (!is_null($this->_vfsSize)) {
$this->_vfsSize += $filesize - $oldsize;
}
}
/**
* Return the size of $data.
*
* @param string|resource $data The data.
*
* @return integer The data length.
*/
protected function _getDataSize($data)
{
if (is_resource($data)) {
$pos = fseek($data, 0, SEEK_END);
$size = ftell($data);
fseek($data, $pos);
return $size;
}
return strlen($data);
}
protected function _ensureSeekable($stream)
{
if (!is_resource($stream)) {
return $stream;
}
$meta = stream_get_meta_data($stream);
if (!$meta['seekable']) {
$temp = @fopen('php://temp', 'r+');
while (!feof($stream)) {
fwrite($temp, fread($stream, 8192));
}
return $temp;
}
rewind($stream);
return $stream;
}
/**
* Checks the quota when preparing to delete data.
*
* @param string $path The path the file is located in.
* @param string $name The filename.
*
* @throws Horde_Vfs_Exception
*/
protected function _checkQuotaDelete($path, $name)
{
if (!is_null($this->_vfsSize)) {
$this->_vfsSize -= $this->size($path, $name);
}
}
/**
* Returns the full path of an item.
*
* @param string $path The path of directory of the item.
* @param string $name The name of the item.
*
* @return mixed Full path when $path isset and just $name when not set.
*/
protected function _getPath($path, $name)
{
if (strlen($path) > 0) {
if (substr($path, -1) == '/') {
return $path . $name;
}
return $path . '/' . $name;
}
return $name;
}
}