* @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 cannot 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. *
* 'limit' = Maximum quota allowed
* 'usage' = Currently used portion of quota (in bytes)
*
* @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;
}
}