steffen: server/kolab-horde-framework/kolab-horde-framework/VFS/VFS Browser.php, NONE, 1.1 GC.php, NONE, 1.1 ListItem.php, NONE, 1.1 Object.php, NONE, 1.1 file.php, NONE, 1.1 ftp.php, NONE, 1.1 musql.php, NONE, 1.1 sql.php, NONE, 1.1 sql_file.php, NONE, 1.1

cvs at intevation.de cvs at intevation.de
Fri Oct 14 16:33:17 CEST 2005


Author: steffen

Update of /kolabrepository/server/kolab-horde-framework/kolab-horde-framework/VFS/VFS
In directory doto:/tmp/cvs-serv28903/kolab-horde-framework/kolab-horde-framework/VFS/VFS

Added Files:
	Browser.php GC.php ListItem.php Object.php file.php ftp.php 
	musql.php sql.php sql_file.php 
Log Message:
Separated Horde Framework from kolab-resource-handlers

--- NEW FILE: Browser.php ---
<?php
/**
 * Class for providing a generic UI for any VFS instance.
 *
 * $Horde: framework/VFS/VFS/Browser.php,v 1.8 2004/04/08 18:33:17 slusarz Exp $
 *
 * Copyright 2002-2004 Chuck Hagenbuch <chuck at horde.org>
 *
 * See the enclosed file COPYING for license information (LGPL). If you
 * did not receive this file, see http://www.fsf.org/copyleft/lgpl.html.
 *
 * @author  Chuck Hagenbuch <chuck at horde.org>
 * @version $Revision: 1.1 $
 * @since   Horde 2.2
 * @package VFS
 */
class VFS_Browser {

    /**
     * The VFS instance that we are browsing.
     *
     * @var object VFS $_vfs
     */
    var $_vfs;

    /**
     * The directory where the templates to use are.
     *
     * @var string $_templates
     */
    var $_templates;

    /**
     * Constructor
     *
     * @access public
     *
     * @param object VFS &$vfs   A VFS object.
     * @param string $templates  TODO
     */
    function VFS_Browser(&$vfs, $templates)
    {
        if (isset($vfs)) {
            $this->_vfs = $vfs;
        }
        $this->_templates = $templates;
    }

    /**
     * Set the VFS object in the local object.
     *
     * @access public
     *
     * @param object VFS &$vfs  A VFS object.
     */
    function setVFSObject(&$vfs)
    {
        $this->_vfs = &$vfs;
    }

    /**
     * TODO
     *
     * @access public
     *
     * @param string $path                TODO
     * @param optional boolean $dotfiles  TODO
     * @param optional boolean $dironly   TODO
     */
    function getUI($path, $dotfiles = false, $dironly = false)
    {
        $this->_vfs->listFolder($path, $dotfiles, $dironly);
    }

}

--- NEW FILE: GC.php ---
<?php
/**
 * Class for providing garbage collection for any VFS instance.
 *
 * $Horde: framework/VFS/VFS/GC.php,v 1.4 2004/01/01 15:14:43 jan Exp $
 *
 * Copyright 2003-2004 Michael Slusarz <slusarz at bigworm.colorado.edu>
 *
 * See the enclosed file COPYING for license information (LGPL). If you
 * did not receive this file, see http://www.fsf.org/copyleft/lgpl.html.
 *
 * @author  Michael Slusarz <slusarz at bigworm.colorado.edu>
 * @version $Revision: 1.1 $
 * @since   Horde 3.0
 * @package VFS
 */
class VFS_GC {

    /**
     * Garbage collect files in the VFS storage system.
     *
     * @access public
     *
     * @param object VFS &$vfs        The VFS object to perform
     *                                garbage collection on.
     * @param string $path            The VFS path to clean.
     * @param optional integer $secs  The minimum amount of time (in seconds)
     *                                required before a file is removed.
     */
    function gc(&$vfs, $path, $secs = 345600)
    {
        /* A 1% chance we will run garbage collection during a call. */
        if (rand(0, 99) == 0) {
            $files = $vfs->listFolder($path);
            if (!is_a($files, 'PEAR_Error') && is_array($files)) {
                $modtime = time() - $secs;
                foreach ($files as $val) {
                    if ($val['date'] < $modtime) {
                        $vfs->deleteFile($path, $val['name']);
                    }
                }
            }
        }
    }

}

--- NEW FILE: ListItem.php ---
<?php
/**
 * An item returned from a folder list.
 *
 * $Horde: framework/VFS/VFS/ListItem.php,v 1.11 2004/04/08 18:33:17 slusarz Exp $
 *
 * Copyright 2002-2004 Jon Wood <jon at jellybob.co.uk>
 *
 * See the enclosed file COPYING for license information (LGPL). If you
 * did not receive this file, see http://www.fsf.org/copyleft/lgpl.html.
 *
 * @author  Jon Wood <jon at jellybob.co.uk>
 * @version $Revision: 1.1 $
 * @package VFS
 * @since   Horde 2.2
 */
class VFS_ListItem {

    /**
     * VFS path
     *
     * @var string $_path
     */
    var $_path;

    /**
     * Filename
     *
     * @var string $_name
     */
    var $_name;

    /**
     * File permissions (*nix format: drwxrwxrwx)
     *
     * @var string $_perms
     */
    var $_perms;

    /**
     * Owner user
     *
     * @var string $_owner
     */
    var $_owner;

    /**
     * Owner group
     *
     * @var string $_group
     */
    var $_group;

    /**
     * Size.
     *
     * @var string $_size
     */
    var $_size;

    /**
     * Last modified date.
     *
     * @var string $_date
     */
    var $_date;

    /**
     * Type
     *   .*      --  File extension
     *   **none  --  Unrecognized type
     *   **sym   --  Symlink
     *   **dir   --  Directory
     *
     * @var string $_type
     */
    var $_type;

    /**
     * Type of target if type is '**sym'.
     * NB. Not all backends are capable of distinguishing all of these.
     *   .*        --  File extension
     *   **none    --  Unrecognized type
     *   **sym     --  Symlink to a symlink
     *   **dir     --  Directory
     *   **broken  --  Target not found - broken link
     *
     * @var string $_linktype
     */
    var $_linktype;

    /**
     * Constructor
     *
     * Requires the path to the file, and it's array of properties,
     * returned from a standard VFS::listFolder() call.
     *
     * @access public
     *
     * @param string $path      The path to the file.
     * @param array $fileArray  An array of file properties.
     */
    function VFS_ListItem($path, $fileArray)
    {
        $this->_path = $path . '/' . $fileArray['name'];
        $this->_name = $fileArray['name'];
        $this->_dirname = $path;
        $this->_perms = $fileArray['perms'];
        $this->_owner = $fileArray['owner'];
        $this->_group = $fileArray['group'];
        $this->_size = $fileArray['size'];
        $this->_date = $fileArray['date'];
        $this->_type = $fileArray['type'];
        $this->_linktype = $fileArray['linktype'];
    }

}

--- NEW FILE: Object.php ---
<?php

require_once dirname(__FILE__) . '/../VFS.php';

/**
 * A wrapper for the VFS class to return objects, instead of arrays.
 *
 * $Horde: framework/VFS/VFS/Object.php,v 1.12 2004/04/08 18:33:17 slusarz Exp $
 *
 * Copyright 2002-2004 Jon Wood <jon at jellybob.co.uk>
 *
 * See the enclosed file COPYING for license information (LGPL). If you
 * did not receive this file, see http://www.fsf.org/copyleft/lgpl.html.
 *
 * @author  Jon Wood <jon at jellybob.co.uk>
 * @version $Revision: 1.1 $
 * @package VFS
 * @since   Horde 2.2
 */
class VFS_Object {

    /**
     * The actual vfs that does the work
     *
     * @var object VFS $_vfs
     */
    var $_vfs;

    /**
     * The current path that has been passed to listFolder, if this
     * changes, the list will be rebuilt.
     *
     * @var string $_currentPath
     */
    var $_currentPath;

    /**
     * The return value from a standard VFS listFolder call, to
     * be read with the Object listFolder.
     *
     * @var array $_folderList
     */
    var $_folderList;

    /**
     * Constructor.
     *
     * If you pass in an existing VFS object, it will be used as the VFS
     * object for this object.
     *
     * @access public
     *
     * @param object VFS &$vfs  The VFS object to wrap.
     */
    function VFS_Object(&$vfs)
    {
        if (isset($vfs)) {
            $this->_vfs = $vfs;
        }
    }

    /**
     * Attempts to return a concrete VFS_Object instance based on
     * $driver.
     *
     * @access public
     *
     * @param mixed $driver           The type of concrete VFS subclass to
     *                                return. This is based on the storage
     *                                driver ($driver). The code is
     *                                dynamically included. If $driver is an
     *                                array then we will look in
     *                                $driver[0]/lib/VFS/ for the subclass
     *                                implementation named $driver[1].php.
     * @param optional array $params  A hash containing any additional
     *                                configuration or connection parameters
     *                                a subclass might need.
     *
     * @return object VFS_Object  The newly created concrete VFS_Object
     *                            instance, or false on an error.
     */
    function &factory($driver, $params = array())
    {
        $vfs = &VFS::factory($driver, $params = array());
        return $ret = &new VFS_Object($vfs);
    }

    /**
     * Attempts to return a reference to a concrete VFS instance
     * based on $driver. It will only create a new instance if no
     * VFS instance with the same parameters currently exists.
     *
     * This should be used if multiple types of file backends (and,
     * thus, multiple VFS instances) are required.
     *
     * This method must be invoked as: $var = &VFS::singleton()
     *
     * @access public
     *
     * @param mixed $driver           The type of concrete VFS subclass to
     *                                return. This is based on the storage
     *                                driver ($driver). The code is
     *                                dynamically included. If $driver is an
     *                                array then we will look in
     *                                $driver[0]/lib/VFS/ for the subclass
     *                                implementation named $driver[1].php.
     * @param optional array $params  A hash containing any additional
     *                                configuration or connection parameters
     *                                a subclass might need.
     *
     * @return object VFS_Object  The concrete VFS_Object reference, or false
     *                            on error.
     */
    function &singleton($driver, $params = array())
    {
        $vfs = &VFS::singleton($driver, $params = array());
        return $ret = &new VFS_Object($vfs);
    }

    /**
     * Check the credentials that we have to see if there is a valid login.
     *
     * @access public
     *
     * @return mixed  True on success, PEAR_Error describing the problem
     *                if the credentials are invalid.
     */
    function checkCredentials()
    {
        return $this->_vfs->checkCredentials();
    }

    /**
     * Set configuration parameters.
     *
     * @access public
     *
     * @param optional array $params  An associative array:
     *                                KEY: param name, VAL: param value
     */
    function setParams($params = array())
    {
        $this->_vfs->setParams($params);
    }

    /**
     * Retrieve a file from the VFS.
     *
     * @access public
     *
     * @param string $path  The pathname to the file.
     *
     * @return string  The file data.
     */
    function read($path)
    {
        return $this->_vfs->read(dirname($path), basename($path));
    }

    /**
     * Store a file in the VFS.
     *
     * @access public
     *
     * @param string $path                  The path to store the file in.
     * @param string $tmpFile               The temporary file containing the 
     *                                      data to be stored.
     * @param optional boolean $autocreate  Automatically create directories?
     *
     * @return mixed  True on success or a PEAR_Error object on failure.
     */
    function write($path, $tmpFile, $autocreate = false)
    {
        return $this->_vfs->write(dirname($path), basename($path), $tmpFile, $autocreate = false);
    }

    /**
     * Store a file in the VFS from raw data.
     *
     * @access public
     *
     * @param string $path                  The path to store the file in.
     * @param string $data                  The file data.
     * @param optional boolean $autocreate  Automatically create directories?
     *
     * @return mixed  True on success or a PEAR_Error object on failure.
     */
    function writeData($path, $data, $autocreate = false)
    {
        return $this->_vfs->writeData(dirname($path), basename($path), $data, $autocreate = false);
    }

    /**
     * Delete a file from the VFS.
     *
     * @param string $path  The path to store the file in.
     * @param string $name  The filename to use.
     *
     * @return mixed  True on success or a PEAR_Error object on failure.
     */
    function deleteFile($path)
    {
        return $this->_vfs->deleteFile(dirname($path), basename($path));
    }

    /**
     * Rename a file in the VFS.
     *
     * @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.
     *
     * @return mixed  True on success or a PEAR_Error object on failure.
     */
    function rename($oldpath, $newpath)
    {
        return $this->_vfs->rename(dirname($oldpath), basename($oldpath), dirname($newpath), basename($newpath));
    }

    /**
     * Create a folder in the VFS.
     *
     * @access public
     *
     * @param string $path  The path to the folder.
     *
     * @return mixed  True on success or a PEAR_Error object on failure.
     */
    function createFolder($path)
    {
        return $this->_vfs->createFolder(dirname($path));
    }

    /**
     * Deletes a folder from the VFS.
     *
     * @access public
     *
     * @param string $path The path of the folder to delete.
     *
     * @return mixed  True on success or a PEAR_Error object on failure.
     */
    function deleteFolder($path)
    {
        return $this->_vfs->deleteFolder(dirname($path));
    }

    /**
     * Returns a VFS_ListItem object if the folder can
     * be read, or a PEAR_Error if it can't be. Returns false once
     * the folder has been completely read.
     *
     * @access public
     *
     * @param string $path  The path of the diretory.
     *
     * @return mixed  File list (array) on success, a PEAR_Error
     *                object on failure, or false if the folder is
     *                completely read.
     */
    function listFolder($path)
    {
        if (!($path === $this->_currentPath)) {
            $folderList = $this->_vfs->listFolder($path);
            if ($folderList) {
                $this->_folderList = $folderList;
                $this->_currentPath = $path;
            } else {
                return PEAR::raiseError(sprintf(_("Could not read %s."), $path));
            }
        }

        require_once dirname(__FILE__) . '/ListItem.php';
        if ($file = array_shift($this->_folderList)) {
            $file = &new VFS_ListItem($path, $file);
            return $file;
        } else {
            return false;
        }
    }

    /**
     * Changes permissions for an Item on the VFS.
     *
     * @access public
     *
     * @param string $path        Holds the path of directory of the Item.
     * @param string $permission  TODO
     *
     * @return mixed  True on success or a PEAR_Error object on failure.
     */
    function changePermissions($path, $permission)
    {
        return $this->_vfs->changePermissions(dirname($path), basename($path), $permission);
    }

    /**
     * Return the list of additional credentials required, if any.
     *
     * @access public
     *
     * @return array  Credential list.
     */
    function getRequiredCredentials()
    {
        return $this->_vfs->getRequiredCredentials();
    }

    /**
     * Return the array specificying what permissions are
     * changeable for this implementation.
     *
     * @access public
     *
     * @return array  Changeable permisions.
     */
    function getModifiablePermissions()
    {
        return $this->_vfs->getModifiablePermissions();
    }

    /**
     * Close any resources that need to be closed.
     *
     * @access private
     */
    function _disconnect()
    {
        $this->_vfs->_disconnect();
    }

}

--- NEW FILE: file.php ---
<?php
/**
 * VFS implementation for a standard filesystem.
 *
 * <pre>
 * Required values for $params:
 *      'vfsroot'       The root path
 * </pre>
 *
 * Note: The user that your webserver runs as (commonly 'nobody',
 * 'apache', or 'www-data') MUST have read/write permission to the
 * directory you specific as the 'vfsroot'.
 *
 * $Horde: framework/VFS/VFS/file.php,v 1.57 2004/05/27 22:06:27 jan Exp $
 *
 * Copyright 2002-2004 Chuck Hagenbuch <chuck at horde.org>
 *
 * See the enclosed file COPYING for license information (LGPL). If you
 * did not receive this file, see http://www.fsf.org/copyleft/lgpl.html.
 *
 * @author  Chuck Hagenbuch
 * @version $Revision: 1.1 $
 * @since   Horde 2.2
 * @package VFS
 */
class VFS_file extends VFS {

    /**
     * List of permissions and if they can be changed in this VFS
     * backend.
     *
     * @var array $_permissions
     */
    var $_permissions = array(
        'owner' => array('read' => true, 'write' => true, 'execute' => true),
        'group' => array('read' => true, 'write' => true, 'execute' => true),
        'all'   => array('read' => true, 'write' => true, 'execute' => true)
    );

    /**
     * Constructs a new Filesystem based VFS object.
     *
     * @access public
     *
     * @param optional array $params  A hash containing connection parameters.
     */
    function VFS_file($params = array())
    {
        parent::VFS($params);

        if (substr($this->_params['vfsroot'], -1) == '/' ||
            substr($this->_params['vfsroot'], -1) == '\\') {
            $this->_params['vfsroot'] = substr($this->_params['vfsroot'], 0, strlen($this->_params['vfsroot']) - 1);
        }
    }

    /**
     * Retrieve a file from the VFS.
     *
     * @access public
     *
     * @param string $path  The pathname to the file.
     * @param string $name  The filename to retrieve.
     *
     * @return string  The file data.
     */
    function read($path, $name)
    {
        $file = $this->_getNativePath($path, $name);
        $fp = @fopen($file, 'rb');
        if (!$fp) {
            return PEAR::raiseError(_("Unable to open VFS file."));
        }
        $data = fread($fp, filesize($file));
        fclose($fp);

        return $data;
    }

    /**
     * Store a file in the VFS, with the data copied from a temporary
     * file.
     *
     * @access public
     *
     * @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 optional boolean $autocreate  Automatically create directories?
     *
     * @return mixed  True on success or a PEAR_Error object on failure.
     */
    function write($path, $name, $tmpFile, $autocreate = true)
    {
        if (!@is_dir($this->_getNativePath($path))) {
            if ($autocreate) {
                $res = $this->autocreatePath($path);
                if (is_a($res, 'PEAR_Error')) {
                    return $res;
                }
            } else {
                return PEAR::raiseError(_("VFS directory does not exist."));
            }
        }

        $fp = @fopen($this->_getNativePath($path, $name), 'w');
        if (!$fp) {
            return PEAR::raiseError(_("Unable to open VFS file for writing."));
        }

        $dataFP = @fopen($tmpFile, 'rb');
        $data = @fread($dataFP, filesize($tmpFile));
        fclose($dataFP);

        if (!@fwrite($fp, $data)) {
            return PEAR::raiseError(_("Unable to write VFS file data."));
        }

        return true;
    }

    /**
     * Moves a file in the database and the file system.
     *
     * @access public
     *
     * @param string $path  The path to store the file in.
     * @param string $name  The filename to use.
     *
     * @return mixed  True on success or a PEAR_Error object on failure.
     */
    function move($path, $name, $dest)
    {
        $fileCheck = $this->listFolder($dest, false);
        foreach ($fileCheck as $file) {
            if ($file['name'] == $name) {
                return PEAR::raiseError(_("Unable to move VFS file."));
            }
        }

        if (!@rename($this->_getNativePath($path, $name), $this->_getNativePath($dest, $name))) {
            return PEAR::raiseError(_("Unable to move VFS file."));
        }

        return true;
    }

    /**
     * Copies a file through the backend.
     *
     * @access public
     *
     * @param string $path  The path to store the file in.
     * @param string $name  The filename to use.
     * @param string $dest  The destination of the file.
     *
     * @return mixed  True on success or a PEAR_Error object on failure.
     */
    function copy($path, $name, $dest)
    {
        $fileCheck = $this->listFolder($dest, false);
        if ($path == $dest) {
            return PEAR::raiseError(_("The file can not be copied onto itself."));
        }
        foreach ($fileCheck as $file) {
            if ($file['name'] == $name) {
                return PEAR::raiseError(_("Unable to copy VFS file."));
            }
        }
        if (!@copy($this->_getNativePath($path, $name), $this->_getNativePath($dest, $name))) {
            return PEAR::raiseError(_("Unable to copy VFS file."));
        }

        return true;
    }

    /**
     * Store a file in the VFS from raw data.
     *
     * @access public
     *
     * @param string $path                  The path to store the file in.
     * @param string $name                  The filename to use.
     * @param string $data                  The file data.
     * @param optional boolean $autocreate  Automatically create directories?
     *
     * @return mixed  True on success or a PEAR_Error object on failure.
     */
    function writeData($path, $name, $data, $autocreate = true)
    {
        if (!@is_dir($this->_getNativePath($path))) {
            if ($autocreate) {
                $res = $this->autocreatePath($path);
                if (is_a($res, 'PEAR_Error')) {
                    return $res;
                }
            } else {
                return PEAR::raiseError(_("VFS directory does not exist."));
            }
        }

        $fp = @fopen($this->_getNativePath($path, $name), 'w');
        if (!$fp) {
            return PEAR::raiseError(_("Unable to open VFS file for writing."));
        }

        if (!@fwrite($fp, $data)) {
            return PEAR::raiseError(_("Unable to write VFS file data."));
        }

        return true;
    }

    /**
     * Delete a file from the VFS.
     *
     * @access public
     *
     * @param string $path  The path to store the file in.
     * @param string $name  The filename to use.
     *
     * @return mixed  True on success or a PEAR_Error object on failure.
     */
    function deleteFile($path, $name)
    {
        if (!@unlink($this->_getNativePath($path, $name))) {
            return PEAR::raiseError(_("Unable to delete VFS file."));
        }
    }

    /**
     * Delete a folder from the VFS.
     *
     * @access public
     *
     * @param string $path                 The path to delete the folder from.
     * @param string $name                 The foldername to use.
     * @param optional boolean $recursive  Force a recursive delete?
     *
     * @return mixed True on success or a PEAR_Error object on failure.
     */
    function deleteFolder($path, $name, $recursive = false)
    {
        if ($recursive) {
            $result = $this->emptyFolder($path . '/' . $name);
            if (is_a($result, 'PEAR_Error')) {
                return $result;
            }
        } else {
            $list = $this->listFolder($path . '/' . $name);
            if (is_a($list, 'PEAR_Error')) {
                return $list;
            }
            if (count($list)) {
                return PEAR::raiseError(sprintf(_("Unable to delete %s, the directory is not empty"),
                                                $path . '/' . $name));
            }
        }

        if (!@rmdir($this->_getNativePath($path, $name))) {
            return PEAR::raiseError(_("Unable to delete VFS directory."));
        }

        return true;
    }

    /**
     * Creates a folder on the VFS.
     *
     * @access public
     *
     * @param string $path  The path to delete the folder from.
     * @param string $name  The foldername to use.
     *
     * @return mixed  True on success or a PEAR_Error object on failure.
     */
    function createFolder($path, $name)
    {
        if (!@mkdir($this->_getNativePath($path, $name))) {
            return PEAR::raiseError(_("Unable to create VFS directory."));
        }

        return true;
    }

    /**
     * Check if a given pathname is a folder.
     *
     * @access public
     *
     * @param string $path  The path to the folder.
     * @param string $name  The file/folder name.
     *
     * @return boolean  True if it is a folder, false otherwise.
     */
    function isFolder($path, $name)
    {
        return @is_dir($this->_getNativePath($path, $name));
    }

    /**
     * Changes permissions for an item in the VFS.
     *
     * @access public
     *
     * @param string $path         The path of directory of the item.
     * @param string $name         The name of the item.
     * @param integer $permission  The octal value of the new permission.
     *
     * @return mixed  True on success or a PEAR_Error object on failure.
     */
    function changePermissions($path, $name, $permission)
    {
        if (!@chmod($this->_getNativePath($path, $name), $permission)) {
            return PEAR::raiseError(sprintf(_("Unable to change permission for VFS file %s/%s."), $path, $name));
        }

        return true;
    }

    /**
     * Return a list of the contents of a folder.
     *
     * @access public
     *
     * @param string $path                The path of the directory.
     * @param optional mixed $filter      String/hash to filter file/dirname
     *                                    on.
     * @param optional boolean $dotfiles  Show dotfiles?
     * @param optional boolean $dironly   Show only directories?
     *
     * @return array  File list on success, PEAR_Error on error.
     */
    function listFolder($path, $filter = null, $dotfiles = true,
                        $dironly = false)
    {
        $files = array();
        $path = isset($path) ? $this->_getNativePath($path) : $this->_getNativePath();

        if (!@is_dir($path)) {
            return PEAR::raiseError(_("Not a directory"));
        }

        if (!@chdir($path)) {
            return PEAR::raiseError(_("Unable to access VFS directory."));
        }

        $handle = opendir($path);
        while (($entry = readdir($handle)) !== false) {
            // Filter out '.' and '..' entries.
            if ($entry == '.' || $entry == '..') {
                continue;
            }

            // Filter out dotfiles if they aren't wanted.
            if (!$dotfiles && substr($entry, 0, 1) == '.') {
                continue;
            }

            // File name
            $file['name'] = $entry;

            // Unix style file permissions
            $file['perms'] = $this->_getUnixPerms(fileperms($entry));

            // Owner
            $file['owner'] = fileowner($entry);
            if (function_exists('posix_getpwuid')) {
                $owner = posix_getpwuid($file['owner']);
                $file['owner'] = $owner['name'];
            }

            // Group
            $file['group'] = filegroup($entry);
            if (function_exists('posix_getgrgid')) {
                $group = posix_getgrgid($file['group']);
                $file['group'] = $group['name'];
            }

            // Size
            $file['size'] = filesize($entry);

            // Date
            $file['date'] = filemtime($entry);

            // Type
            if (@is_dir($entry) && !is_link($entry)) {
                $file['perms'] = 'd' . $file['perms'];
                $file['type'] = '**dir';
                $file['size'] = -1;
            } elseif (is_link($entry)) {
                $file['perms'] = 'l' . $file['perms'];
                $file['type'] = '**sym';
                $file['link'] = readlink($entry);
                $file['linktype'] = '**none';
                if (file_exists($file['link'])) {
                    if (is_dir($file['link'])) {
                        $file['linktype'] = '**dir';
                    } elseif (is_link($file['link'])) {
                        $file['linktype'] = '**sym';
                    } elseif (is_file($file['link'])) {
                        $ext = explode('.', $file['link']);
                        if (!(count($ext) == 1 || ($ext[0] === '' && count($ext) == 2))) {
                            $file['linktype'] = VFS::strtolower($ext[count($ext) - 1]);
                        }
                    }
                } else {
                    $file['linktype'] = '**broken';
                }
            } elseif (is_file($entry)) {
                $file['perms'] = '-' . $file['perms'];
                $ext = explode('.', $entry);

                if (count($ext) == 1 || (substr($file['name'], 0, 1) === '.' && count($ext) == 2)) {
                    $file['type'] = '**none';
                } else {
                    $file['type'] = VFS::strtolower($ext[count($ext) - 1]);
                }
            } else {
                $file['type'] = '**none';
                if ((fileperms($entry) & 0xC000) == 0xC000) {
                    $file['perms'] = 's' . $file['perms'];
                } elseif ((fileperms($entry) & 0x6000) == 0x6000) {
                    $file['perms'] = 'b' . $file['perms'];
                } elseif ((fileperms($entry) & 0x2000) == 0x2000) {
                    $file['perms'] = 'c' . $file['perms'];
                } elseif ((fileperms($entry) & 0x1000) == 0x1000) {
                    $file['perms'] = 'p' . $file['perms'];
                } else {
                    $file['perms'] = '?' . $file['perms'];
                }
            }

            // Filtering.
            if ($this->_filterMatch($filter, $file['name'])) {
                unset($file);
                continue;
            }
            if ($dironly && $file['type'] !== '**dir') {
                unset($file);
                continue;
            }

            $files[$file['name']] = $file;
            unset($file);
        }

        return $files;
    }

    /**
     * Returns a sorted list of folders in specified directory.
     *
     * @access public
     *
     * @param optional string $path         The path of the directory to get
     *                                      the directory list for.
     * @param optional mixed $filter        Hash of items to filter based on
     *                                      folderlist.
     * @param optional boolean $dotfolders  Include dotfolders?
     *
     * @return mixed  Folder list on success or a PEAR_Error object on failure.
     */
    function listFolders($path = '', $filter = null, $dotfolders = true)
    {
        $conn = $this->_connect();
        if (is_a($conn, 'PEAR_Error')) {
            return $conn;
        }

        $folders = array();
        $folder = array();

        $folderList = $this->listFolder($path, null, $dotfolders, true);

        $folder['val'] = dirname($path);
        $folder['abbrev'] = '..';
        $folder['label'] = '..';

        $folders[$folder['val']] = $folder;

        foreach ($folderList as $files) {
            $folder['val'] = $path . '/' . $files['name'];
            $folder['abbrev'] = $files['name'];
            $folder['label'] = $folder['val'];

            $folders[$folder['val']] = $folder;
        }

        ksort($folders);

        return $folders;
    }

    /**
     * Return Unix style perms.
     *
     * @access private
     *
     * @param integer $perms  The permissions to set.
     *
     * @return string  Unix style perms.
     */
    function _getUnixPerms($perms)
    {
        // Determine permissions
        $owner['read']    = ($perms & 00400) ? 'r' : '-';
        $owner['write']   = ($perms & 00200) ? 'w' : '-';
        $owner['execute'] = ($perms & 00100) ? 'x' : '-';
        $group['read']    = ($perms & 00040) ? 'r' : '-';
        $group['write']   = ($perms & 00020) ? 'w' : '-';
        $group['execute'] = ($perms & 00010) ? 'x' : '-';
        $world['read']    = ($perms & 00004) ? 'r' : '-';
        $world['write']   = ($perms & 00002) ? 'w' : '-';
        $world['execute'] = ($perms & 00001) ? 'x' : '-';

        // Adjust for SUID, SGID and sticky bit
        if ($perms & 0x800) {
            $owner['execute'] = ($owner['execute'] == 'x') ? 's' : 'S';
        }
        if ($perms & 0x400) {
            $group['execute'] = ($group['execute'] == 'x') ? 's' : 'S';
        }
        if ($perms & 0x200) {
            $world['execute'] = ($world['execute'] == 'x') ? 't' : 'T';
        }

        $unixPerms = $owner['read'] . $owner['write'] . $owner['execute'] .
                     $group['read'] . $group['write'] . $group['execute'] .
                     $world['read'] . $world['write'] . $world['execute'];

        return $unixPerms;
    }

    /**
     * Rename a file or folder in the VFS.
     *
     * @access public
     *
     * @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.
     *
     * @return mixed  True on success or a PEAR_Error object on failure.
     */
    function rename($oldpath, $oldname, $newpath, $newname)
    {
        if (!@rename($this->_getNativePath($oldpath, $oldname),
                     $this->_getNativePath($newpath, $newname))) {
            return PEAR::raiseError(sprintf(_("Unable to rename VFS file %s/%s."), $oldpath, $oldname));
        }

        return true;
    }

    /**
     * Return a full filename on the native filesystem, from a VFS
     * path and name.
     *
     * @access private
     *
     * @param optional string $path  The VFS file path.
     * @param optional string $name  The VFS filename.
     *
     * @return string  The full native filename.
     */
    function _getNativePath($path = '', $name = '')
    {
        $name = basename($name);
        if (!empty($name)) {
            if ($name == '..') {
                $name = '';
            } else {
                if (substr($name, 0, 1) != '/') {
                    $name = '/' . $name;
                }
            }
        }

        if (!empty($path)) {
            if (isset($this->_params['home']) &&
                preg_match('|^~/?(.*)$|', $path, $matches)) {
                $path = $this->_params['home'] . '/' . $matches[1];
            }

            $path = str_replace('..', '', $path);
            if (substr($path, 0, 1) == '/') {
                return $this->_params['vfsroot'] . $path . $name;
            } else {
                return $this->_params['vfsroot'] . '/' . $path . $name;
            }
        } else {
            return $this->_params['vfsroot'] . $name;
        }
    }

    /**
     * Stub to check if we have a valid connection. Makes sure that
     * the vfsroot is readable.
     *
     * @access private
     *
     * @return mixed  True if vfsroot is readable, PEAR_Error if it isn't.
     */
    function _connect()
    {
        if ((@is_dir($this->_params['vfsroot']) &&
             @is_readable($this->_params['vfsroot'])) ||
            @mkdir($this->_params['vfsroot'])) {
            return true;
        } else {
            return PEAR::raiseError(_("Unable to read the vfsroot directory."));
        }
    }

}

--- NEW FILE: ftp.php ---
<?php
/**
 * VFS implementation for an FTP server.
 *
 * <pre>
 * Required values for $params:
 *      'username'       The username with which to connect to the ftp server.
 *      'password'       The password with which to connect to the ftp server.
 *      'hostspec'       The ftp server to connect to.
 * Optional values for $params:
 *      'pasv'           If true, connection will be set to passive mode.
 *      'port'           The port used to connect to the ftp server if other
 *                       than 21.
 *      'ssl'            If true, and PHP had been compiled with OpenSSL
 *                       support, TLS transport-level encryption will be
 *                       negotiated with the server.
 * </pre>
 *
 * $Horde: framework/VFS/VFS/ftp.php,v 1.70 2004/04/18 13:46:15 jan Exp $
 *
 * Copyright 2002-2004 Chuck Hagenbuch <chuck at horde.org>
 * Copyright 2002-2004 Michael Varghese <mike.varghese at ascellatech.com>
 *
 * See the enclosed file COPYING for license information (LGPL). If you
 * did not receive this file, see http://www.fsf.org/copyleft/lgpl.html.
 *
 * @author  Chuck Hagenbuch <chuck at horde.org>
 * @author  Michael Varghese <mike.varghese at ascellatech.com>
 * @version $Revision: 1.1 $
 * @since   Horde 2.2
 * @package VFS
 */
class VFS_ftp extends VFS {

    /**
     * List of additional credentials required for this VFS backend.
     *
     * @var array $_credentials
     */
    var $_credentials = array('username', 'password');

    /**
     * List of permissions and if they can be changed in this VFS
     * backend.
     *
     * @var array $_permissions
     */
    var $_permissions = array(
        'owner' => array('read' => true, 'write' => true, 'execute' => true),
        'group' => array('read' => true, 'write' => true, 'execute' => true),
        'all'   => array('read' => true, 'write' => true, 'execute' => true));

    /**
     * Variable holding the connection to the ftp server.
     *
     * @var resource $_stream
     */
    var $_stream = false;

    /**
     * Retrieve a file from the VFS.
     *
     * @access public
     *
     * @param string $path  The pathname to the file.
     * @param string $name  The filename to retrieve.
     *
     * @return string  The file data.
     */
    function read($path, $name)
    {
        $conn = $this->_connect();
        if (is_a($conn, 'PEAR_Error')) {
            return $conn;
        }

        $tmpFile = $this->_getTempFile();
        $fetch = @ftp_get($this->_stream, $tmpFile,
                          $this->_getPath($path, $name), FTP_BINARY);
        if ($fetch === false) {
            return PEAR::raiseError(sprintf(_("Unable to open VFS file \"%s\"."), $this->_getPath($path, $name)));
        }

        if (OS_WINDOWS) {
            $mode = 'rb';
        } else {
            $mode = 'r';
        }
        $fp = fopen($tmpFile, $mode);
        $data = fread($fp, filesize($tmpFile));
        fclose($fp);
        unlink($tmpFile);
        return $data;
    }

    /**
     * Store a file in the VFS.
     *
     * @access public
     *
     * @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 optional boolean $autocreate  Automatically create directories?
     *
     * @return mixed  True on success or a PEAR_Error object on failure.
     */
    function write($path, $name, $tmpFile, $autocreate = false)
    {
        $conn = $this->_connect();
        if (is_a($conn, 'PEAR_Error')) {
            return $conn;
        }

        if (!@ftp_put($this->_stream, $this->_getPath($path, $name), $tmpFile, FTP_BINARY)) {
            if ($autocreate) {
                $result = $this->autocreatePath($path);
                if (is_a($result, 'PEAR_Error')) {
                    return $result;
                }
                if (!@ftp_put($this->_stream, $this->_getPath($path, $name), $tmpFile, FTP_BINARY)) {
                    return PEAR::raiseError(sprintf(_("Unable to write VFS file \"%s\"."), $this->_getPath($path, $name)));
                }
            } else {
                return PEAR::raiseError(sprintf(_("Unable to write VFS file \"%s\"."), $this->_getPath($path, $name)));
            }
        }

        return true;
    }

    /**
     * Store a file in the VFS from raw data.
     *
     * @access public
     *
     * @param string $path                  The path to store the file in.
     * @param string $name                  The filename to use.
     * @param string $data                  The file data.
     * @param optional boolean $autocreate  Automatically create directories?
     *
     * @return mixed  True on success or a PEAR_Error object on failure.
     */
    function writeData($path, $name, $data, $autocreate = false)
    {
        $tmpFile = $this->_getTempFile();
        $fp = fopen($tmpFile, 'wb');
        fwrite($fp, $data);
        fclose($fp);

        $result = $this->write($path, $name, $tmpFile, $autocreate);
        unlink($tmpFile);
        return $result;
    }

    /**
     * Delete a file from the VFS.
     *
     * @access public
     *
     * @param string $path  The path to delete the file from.
     * @param string $name  The filename to use.
     *
     * @return mixed  True on success or a PEAR_Error object on failure.
     */
    function deleteFile($path, $name)
    {
        $conn = $this->_connect();
        if (is_a($conn, 'PEAR_Error')) {
            return $conn;
        }

        if (!@ftp_delete($this->_stream, $this->_getPath($path, $name))) {
            return PEAR::raiseError(sprintf(_("Unable to delete VFS file \"%s\"."), $this->_getPath($path, $name)));
        }

        return true;
    }

    /**
     * Check if a given pathname is a folder.
     *
     * @access public
     *
     * @param string $path  The path to the folder.
     * @param string $name  The file/folder name.
     *
     * @return boolean  True if it is a folder, false otherwise.
     */
    function isFolder($path, $name)
    {
        $conn = $this->_connect();
        if (is_a($conn, 'PEAR_Error')) {
            return $conn;
        }

        $result = false;
        $olddir = $this->getCurrentDirectory();

        /* See if we can change to the given path. */
        if (@ftp_chdir($this->_stream, $this->_getPath($path, $name))) {
            $result = true;
        }

        $this->_setPath($olddir);

        return $result;
    }

    /**
     * Delete a folder from the VFS.
     *
     * @access public
     *
     * @param string $path                 The path to delete the folder from.
     * @param string $name                 The name of the folder to delete.
     * @param optional boolean $recursive  Force a recursive delete?
     *
     * @return mixed  True on success or a PEAR_Error object on failure.
     */
    function deleteFolder($path, $name, $recursive = false)
    {
        $conn = $this->_connect();
        if (is_a($conn, 'PEAR_Error')) {
            return $conn;
        }

        $isDir = false;
        $dirCheck = $this->listFolder($path);
        foreach ($dirCheck as $file) {
            if ($file['name'] == $name && $file['type'] == '**dir') {
                $isDir = true;
                break;
            }
        }

        if ($isDir) {
            $file_list = $this->listFolder($this->_getPath($path, $name));
            if (is_a($file_list, 'PEAR_Error')) {
                return $file_list;
            }

            if (count($file_list) && !$recursive) {
                return PEAR::raiseError(sprintf(_("Unable to delete \"%s\", the directory is not empty."),
                                                $this->_getPath($path, $name)));
            }

            foreach ($file_list as $file) {
                if ($file['type'] == '**dir') {
                    $result = $this->deleteFolder($this->_getPath($path, $name), $file['name'], $recursive);
                } else {
                    $result = $this->deleteFile($this->_getPath($path, $name), $file['name']);
                }
                if (is_a($result, 'PEAR_Error')) {
                    return $result;
                }
            }

            if (!@ftp_rmdir($this->_stream, $this->_getPath($path, $name))) {
                return PEAR::raiseError(sprintf(_("Cannot remove directory \"%s\"."), $this->_getPath($path, $name)));
            }
        } else {
            if (!@ftp_delete($this->_stream, $this->_getPath($path, $name))) {
                return PEAR::raiseError(sprintf(_("Cannot delete file \"%s\"."), $this->_getPath($path, $name)));
            }
        }

        return true;
    }

    /**
     * Rename a file in the VFS.
     *
     * @access public
     *
     * @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.
     *
     * @return mixed  True on success or a PEAR_Error object on failure.
     */
    function rename($oldpath, $oldname, $newpath, $newname)
    {
        $conn = $this->_connect();
        if (is_a($conn, 'PEAR_Error')) {
            return $conn;
        }

        if (!@ftp_rename($this->_stream, $this->_getPath($oldpath, $oldname), $this->_getPath($newpath, $newname))) {
            return PEAR::raiseError(sprintf(_("Unable to rename VFS file \"%s\"."), $this->_getPath($oldpath, $oldname)));
        }

        return true;
    }

    /**
     * Creates a folder on the VFS.
     *
     * @access public
     *
     * @param string $path  Holds the path of directory to create folder.
     * @param string $name  Holds the name of the new folder.
     *
     * @return mixed  True on success or a PEAR_Error object on failure.
     */
    function createFolder($path, $name)
    {
        $conn = $this->_connect();
        if (is_a($conn, 'PEAR_Error')) {
            return $conn;
        }

        if (!@ftp_mkdir($this->_stream, $this->_getPath($path, $name))) {
            return PEAR::raiseError(sprintf(_("Unable to create VFS directory \"%s\"."), $this->_getPath($path, $name)));
        }

        return true;
    }

    /**
     * Changes permissions for an Item on the VFS.
     *
     * @access public
     *
     * @param string $path        Holds the path of directory of the Item.
     * @param string $name        Holds the name of the Item.
     * @param string $permission  TODO
     *
     * @return mixed  True on success or a PEAR_Error object on failure.
     */
    function changePermissions($path, $name, $permission)
    {
        $conn = $this->_connect();
        if (is_a($conn, 'PEAR_Error')) {
            return $conn;
        }

        if (!@ftp_site($this->_stream, 'CHMOD ' . $permission . ' ' . $this->_getPath($path, $name))) {
            return PEAR::raiseError(sprintf(_("Unable to change permission for VFS file \"%s\"."), $this->_getPath($path, $name)));
        }

        return true;
    }

    /**
     * Returns an unsorted file list.
     *
     * @access public
     *
     * @param optional string $path       The path of the directory to get the
     *                                    file list for.
     * @param optional mixed $filter      Hash of items to filter based on
     *                                    filename.
     * @param optional boolean $dotfiles  Show dotfiles?
     * @param optional boolean $dironly   Show directories only?
     *
     * @return mixed  File list on success or a PEAR_Error object on failure.
     */
    function listFolder($path = '', $filter = null, $dotfiles = true,
                        $dironly = false)
    {
        $conn = $this->_connect();
        if (is_a($conn, 'PEAR_Error')) {
            return $conn;
        }

        $files = array();
        $type = @ftp_systype($this->_stream);
        if ($type == 'UNKNOWN') {
            // Go with unix-style listings by default.
            $type = 'UNIX';
        }

        $olddir = $this->getCurrentDirectory();
        if (!empty($path)) {
            $res = $this->_setPath($path);
            if (is_a($res, 'PEAR_Error')) {
                return $res;
            }
        }

        if ($type == 'UNIX') {
            // If we don't want dotfiles, We can save work here by not
            // doing an ls -a and then not doing the check later (by
            // setting $dotfiles to true, the if is short-circuited).
            if ($dotfiles) {
                $list = ftp_rawlist($this->_stream, '-al');
                $dotfiles = true;
            } else {
                $list = ftp_rawlist($this->_stream, '-l');
            }
        } else {
           $list = ftp_rawlist($this->_stream, '');
        }

        if (!is_array($list)) {
            if (isset($olddir)) {
                $res = $this->_setPath($olddir);
                if (is_a($res, 'PEAR_Error')) {
                    return $res;
                }
            }
            return array();
        }

        foreach ($list as $line) {
            $file = array();
            $item = preg_split('/\s+/', $line);
            if ($type == 'UNIX' || (stristr($type, 'win') && !preg_match('|\d\d-\d\d-\d\d|', $item[0]))) {
                if (count($item) < 8 || substr($line, 0, 5) == 'total') {
                    continue;
                }
                $file['perms'] = $item[0];
                $file['owner'] = $item[2];
                $file['group'] = $item[3];
                $file['name'] = substr($line, strpos($line, sprintf("%s %2s %5s", $item[5], $item[6], $item[7])) + 13);

                // Filter out '.' and '..' entries.
                if (preg_match('/^\.\.?\/?$/', $file['name'])) {
                    continue;
                }

                // Filter out dotfiles if they aren't wanted.
                if (!$dotfiles && substr($file['name'], 0, 1) == '.') {
                    continue;
                }

                $p1 = substr($file['perms'], 0, 1);
                if ($p1 === 'l') {
                    $file['link'] = substr($file['name'], strpos($file['name'], '->') + 3);
                    $file['name'] = substr($file['name'], 0, strpos($file['name'], '->') - 1);
                    $file['type'] = '**sym';

                   if ($this->isFolder('', $file['link'])) {
                              $file['linktype'] = '**dir';
                                                    } else {
                                                    $parts = explode('/', $file['link']);
                                                    $name = explode('.', array_pop($parts));
                                                    if (count($name) == 1 || ($name[0] === '' && count($name) == 2)) {
                                                        $file['linktype'] = '**none';
                                                        } else {
                                                            $file['linktype'] = VFS::strtolower(array_pop($name));
                                                            }
                                                                   }
                } elseif ($p1 === 'd') {
                    $file['type'] = '**dir';
                } else {
                    $name = explode('.', $file['name']);
                    if (count($name) == 1 || (substr($file['name'], 0, 1) === '.' && count($name) == 2)) {
                        $file['type'] = '**none';
                    } else {
                        $file['type'] = VFS::strtolower($name[count($name) - 1]);
                    }
                }
                if ($file['type'] == '**dir') {
                    $file['size'] = -1;
                } else {
                    $file['size'] = $item[4];
                }
                if (strstr($item[7], ':')) {
                    $file['date'] = strtotime($item[7] . ':00' . $item[5] . ' ' . $item[6] . ' ' . date('Y', time()));
                    if ($file['date'] > time()) {
                        $file['date'] = strtotime($item[7] . ':00' . $item[5] . ' ' . $item[6] . ' ' . (date('Y', time()) - 1));
                    }
                } else {
                    $file['date'] = strtotime('00:00:00' . $item[5] . ' ' . $item[6] . ' ' . $item[7]);
                }
            } else {
                /* Handle Windows FTP servers returning DOS-style file
                 * listings. */
                $file['perms'] = '';
                $file['owner'] = '';
                $file['group'] = '';
                $file['name'] = $item[3];
                $index = 4;
                while ($index < count($item)) {
                    $file['name'] .= ' ' . $item[$index];
                    $index++;
                }
                $file['date'] = strtotime($item[0] . ' ' . $item[1]);
                if ($item[2] == '<DIR>') {
                    $file['type'] = '**dir';
                    $file['size'] = -1;
                } else {
                    $file['size'] = $item[2];
                    $name = explode('.', $file['name']);
                    if (count($name) == 1 || (substr($file['name'], 0, 1) === '.' && count($name) == 2)) {
                        $file['type'] = '**none';
                    } else {
                        $file['type'] = VFS::strtolower($name[count($name) - 1]);
                    }
                }
            }

            // Filtering.
            if ($this->_filterMatch($filter, $file['name'])) {
                unset($file);
                continue;
            }
            if ($dironly && $file['type'] !== '**dir') {
                unset($file);
                continue;
            }

            $files[$file['name']] = $file;
            unset($file);
        }

        if (isset($olddir)) {
            $res = $this->_setPath($olddir);
            if (is_a($res, 'PEAR_Error')) {
                return $res;
            }
        }
        return $files;
    }

    /**
     * Returns a sorted list of folders in specified directory.
     *
     * @access public
     *
     * @param optional string $path         The path of the directory to get
     *                                      the directory list for.
     * @param optional mixed $filter        Hash of items to filter based on
     *                                      folderlist.
     * @param optional boolean $dotfolders  Include dotfolders?
     *
     * @return mixed  Folder list on success or a PEAR_Error object on failure.
     */
    function listFolders($path = '', $filter = null, $dotfolders = true)
    {
        $conn = $this->_connect();
        if (is_a($conn, 'PEAR_Error')) {
            return $conn;
        }

        $folders = array();
        $folder = array();

        $folderList = $this->listFolder($path, null, $dotfolders, true);
        if (is_a($folderList, 'PEAR_Error')) {
            return $folderList;
        }

        $folder['val'] = $this->_parentDir($path);
        $folder['abbrev'] = '..';
        $folder['label'] = '..';

        $folders[$folder['val']] = $folder;

        foreach ($folderList as $files) {
            $folder['val'] = $this->_getPath($path, $files['name']);
            $folder['abbrev'] = $files['name'];
            $folder['label'] = $folder['val'];

            $folders[$folder['val']] = $folder;
        }

        ksort($folders);
        return $folders;
    }

    /**
     * Copies a file through the backend.
     *
     * @access public
     *
     * @param string $path  The path to store the file in.
     * @param string $name  The filename to use.
     * @param string $dest  The destination of the file.
     *
     * @return mixed  True on success or a PEAR_Error object on failure.
     */
    function copy($path, $name, $dest)
    {
        $conn = $this->_connect();
        if (is_a($conn, 'PEAR_Error')) {
            return $conn;
        }

        $fileCheck = $this->listFolder($dest, null, true);
        foreach ($fileCheck as $file) {
            if ($file['name'] == $name) {
                return PEAR::raiseError(sprintf(_("%s already exists."), $this->_getPath($dest, $name)));
            }
        }

        $isDir = false;
        $dirCheck = $this->listFolder($path, null, false);
        foreach ($dirCheck as $file) {
            if ($file['name'] == $name && $file['type'] == '**dir') {
                $isDir = true;
                break;
            }
        }

        if ($isDir) {
            $result = $this->createFolder($dest, $name);

            if (is_a($result, 'PEAR_Error')) {
                return $result;
            }

            $file_list = $this->listFolder($this->_getPath($path, $name));
            foreach ($file_list as $file) {
                $result = $this->copy($this->_getPath($path, $name), $file['name'], $this->_getPath($dest, $name));
                if (is_a($result, 'PEAR_Error')) {
                    return $result;
                }
            }
        } else {
            $tmpFile = $this->_getTempFile();
            $fetch = @ftp_get($this->_stream, $tmpFile, $this->_getPath($path, $name), FTP_BINARY);
            if (!$fetch) {
                unlink($tmpFile);
                return PEAR::raiseError(sprintf(_("Failed to copy from \"%s\"."), $this->_getPath($path, $name)));
            }

            if (!@ftp_put($this->_stream, $this->_getPath($dest, $name), $tmpFile, FTP_BINARY)) {
                unlink($tmpFile);
                return PEAR::raiseError(sprintf(_("Failed to copy to \"%s\"."), $this->_getPath($dest, $name)));
            }

            unlink($tmpFile);
        }

        return true;
    }

    /**
     * Moves a file through the backend.
     *
     * @access public
     *
     * @param string $path  The path to store the file in.
     * @param string $name  The filename to use.
     * @param string $dest  The destination of the file.
     *
     * @return mixed  True on success or a PEAR_Error object on failure.
     */
    function move($path, $name, $dest)
    {
        $conn = $this->_connect();
        if (is_a($conn, 'PEAR_Error')) {
            return $conn;
        }

        $fileCheck = $this->listFolder($dest, null, true);
        foreach ($fileCheck as $file) {
            if ($file['name'] == $name) {
                return PEAR::raiseError(sprintf(_("%s already exists."), $this->_getPath($dest, $name)));
            }
        }

        if (!@ftp_rename($this->_stream, $this->_getPath($path, $name), $this->_getPath($dest, $name))) {
            return PEAR::raiseError(sprintf(_("Failed to move to \"%s\"."), $this->_getPath($dest, $name)));
        }

        return true;
    }

    /**
     * Return the current working directory on the FTP server.
     *
     * @access public
     *
     * @return string  The current working directory.
     */
    function getCurrentDirectory()
    {
        $this->_connect();
        return ftp_pwd($this->_stream);
    }

    /**
     * Change directories on the server.
     *
     * @access private
     *
     * @param string $path  The path to change to.
     *
     * @return mixed  True on success, or a PEAR_Error on failure.
     */
    function _setPath($path)
    {
        if (!@ftp_chdir($this->_stream, $path)) {
            return PEAR::raiseError(sprintf(_("Unable to change to %s."), $path));
        }
        return true;
    }

    /**
     * Returns the full path of an item.
     *
     * @access private
     *
     * @param string $path  Holds the path of directory of the Item.
     * @param string $name  Holds the name of the Item.
     *
     * @return mixed  Full path when $path isset and just $name when not set.
     */
    function _getPath($path, $name)
    {
        if ($path !== '') {
             return ($path . '/' . $name);
        }
        return ($name);
    }

    /**
     * Returns the parent directory of specified path.
     *
     * @access private
     *
     * @param string $path  The path to get the parent of.
     *
     * @return mixed  The parent directory (string) on success
     *                or a PEAR_Error object on failure.
     */
    function _parentDir($path)
    {
        $conn = $this->_connect();
        if (is_a($conn, 'PEAR_Error')) {
            return $conn;
        }

        $olddir = $this->getCurrentDirectory();
        @ftp_cdup($this->_stream);

        $parent = $this->getCurrentDirectory();
        $this->_setPath($olddir);

        if (!$parent) {
            return PEAR::raiseError(_("Unable to determine current directory."));
        }

        return $parent;
    }

    /**
     * Attempts to open a connection to the FTP server.
     *
     * @access private
     *
     * @return mixed  True on success or a PEAR_Error object on failure.
     */
    function _connect()
    {
        if ($this->_stream === false) {
            if (!extension_loaded('ftp')) {
                return PEAR::raiseError(_("The FTP extension is not available."));
            }

            if (!is_array($this->_params)) {
                return PEAR::raiseError(_("No configuration information specified for FTP VFS."));
            }

            $required = array('hostspec', 'username', 'password');
            foreach ($required as $val) {
                if (!isset($this->_params[$val])) {
                    return PEAR::raiseError(sprintf(_("Required '%s' not specified in VFS configuration."), $val));
                }
            }

            /* Connect to the ftp server using the supplied parameters. */
            if (!empty($this->_params['ssl'])) {
                if (function_exists('ftp_ssl_connect')) {
                    $this->_stream = @ftp_ssl_connect($this->_params['hostspec'], $this->_params['port']);
                } else {
                    return PEAR::raiseError(_("Unable to connect with SSL."));
                }
            } else {
                $this->_stream = @ftp_connect($this->_params['hostspec'], $this->_params['port']);
            }
            if (!$this->_stream) {
                return PEAR::raiseError(_("Connection to FTP server failed."));
            }

            $connected = @ftp_login($this->_stream, $this->_params['username'], $this->_params['password']);
            if (!$connected) {
                return PEAR::raiseError(_("Authentication to FTP server failed."));
                $this->_disconnect();
            }

            if (!empty($this->_params['pasv'])) {
                @ftp_pasv($this->_stream, true);
            }
        }

        return true;
    }

    /**
     * Disconnect from the FTP server and clean up the connection.
     *
     * @access private
     */
    function _disconnect()
    {
        @ftp_quit($this->_stream);
        $this->_stream = false;
    }

    /**
     * Determine the location of the system temporary directory.  If a
     * specific setting cannot be found, it defaults to /tmp
     *
     * @access private
     *
     * @return string  A directory name which can be used for temp files.
     *                 Returns false if one could not be found.
     */
    function _getTempDir()
    {
        $tmp_locations = array('/tmp', '/var/tmp', 'c:\WUTemp', 'c:\temp', 'c:\windows\temp', 'c:\winnt\temp');

        /* Try PHP's upload_tmp_dir directive. */
        $tmp = ini_get('upload_tmp_dir');

        /* Otherwise, try to determine the TMPDIR environment
           variable. */
        if (empty($tmp)) {
            $tmp = getenv('TMPDIR');
        }

        /* If we still cannot determine a value, then cycle through a
         * list of preset possibilities. */
        while (empty($tmp) && sizeof($tmp_locations)) {
            $tmp_check = array_shift($tmp_locations);
            if (@is_dir($tmp_check)) {
                $tmp = $tmp_check;
            }
        }

        /* If it is still empty, we have failed, so return false;
         * otherwise return the directory determined. */
        return empty($tmp) ? false : $tmp;
    }

    /**
     * Create a temporary file.
     *
     * @access private
     *
     * @return string  Returns the full path-name to the temporary file.
     *                 Returns false if a temp file could not be created.
     */
    function _getTempFile()
    {
        $tmp_dir = $this->_getTempDir();
        if (empty($tmp_dir)) {
            return false;
        }

        $tmp_file = tempnam($tmp_dir, 'vfs');

        /* If the file was created, then register it for deletion and return */
        if (empty($tmp_file)) {
            return false;
        } else {
            return $tmp_file;
        }
    }

}

--- NEW FILE: musql.php ---
<?php

require_once dirname(__FILE__) . '/sql.php';

/** @constant integer VFS_FLAG_READ  Permission for read access. */
define('VFS_FLAG_READ', 1);

/** @constant integer VFS_FLAG_WRITE  Permission for read access. */
define('VFS_FLAG_WRITE', 2);

/**
 * Multi User VFS implementation for PHP's PEAR database
 * abstraction layer.
 *
 * <pre>
 * Required values for $params:
 *      'phptype'       The database type (ie. 'pgsql', 'mysql, etc.).
 *      'hostspec'      The hostname of the database server.
 *      'protocol'      The communication protocol ('tcp', 'unix', etc.).
 *      'username'      The username with which to connect to the database.
 *      'password'      The password associated with 'username'.
 *      'database'      The name of the database.
 *
 * Optional values:
 *      'table'         The name of the vfs table in 'database'. Defaults to
 *                      'horde_muvfs'.
 *
 * Required by some database implementations:
 *      'options'       Additional options to pass to the database.
 *      'tty'           The TTY on which to connect to the database.
 *      'port'          The port on which to connect to the database.
 * </pre>
 *
 * Known Issues:
 * Delete is not recusive, so file and folders that used to be in a folder that
 * gets deleted life forever in the database, or re-appear when the folder is
 * recreated.
 * Rename has the same issue, if files are lost if a folder is renamed.
 *
 * The table structure for the VFS can be found in
 * horde/scripts/db/muvfs.sql.
 *
 * Database specific notes:
 *
 * MSSQL:
 * - The vfs_data field must be of type IMAGE.
 * - You need the following php.ini settings:
 * <code>
 *    ; Valid range 0 - 2147483647. Default = 4096.
 *    mssql.textlimit = 0 ; zero to pass through
 *
 *    ; Valid range 0 - 2147483647. Default = 4096.
 *    mssql.textsize = 0 ; zero to pass through
 * </code>
 *
 * $Horde: framework/VFS/VFS/musql.php,v 1.29 2004/04/08 18:33:17 slusarz Exp $
 *
 * Copyright 2002-2004 Chuck Hagenbuch <chuck at horde.org>
 *
 * See the enclosed file COPYING for license information (LGPL). If you
 * did not receive this file, see http://www.fsf.org/copyleft/lgpl.html.
 *
 * @author  Chuck Hagenbuch <chuck at horde.org>
 * @author  Mike Cochrane <mike at graftonhall.co.nz>
 * @version $Revision: 1.1 $
 * @since   Horde 2.2
 * @package VFS
 */
class VFS_musql extends VFS_sql {

    /**
     * List of permissions and if they can be changed in this VFS
     *
     * @var array $_permissions
     */
    var $_permissions = array(
        'owner' => array('read' => false, 'write' => false, 'execute' => false),
        'group' => array('read' => false, 'write' => false, 'execute' => false),
        'all'   => array('read' => true,  'write' => true,  'execute' => false)
    );

    /**
     * Store a file in the VFS from raw data.
     *
     * @access public
     *
     * @param string $path                  The path to store the file in.
     * @param string $name                  The filename to use.
     * @param string $data                  The file data.
     * @param optional boolean $autocreate  Automatically create directories?
     *
     * @return mixed  True on success or a PEAR_Error object on failure.
     */
    function writeData($path, $name, $data, $autocreate = false)
    {
        $conn = $this->_connect();
        if (PEAR::isError($conn)) {
            return $conn;
        }

        /* Make sure we have write access to this and all parent
         * paths. */
        if ($path != '') {
            $paths = explode('/', $path);
            $path_name = array_pop($paths);
            if (!$this->isFolder(implode('/', $paths), $path_name)) {
                if (!$autocreate) {
                    return PEAR::raiseError(sprintf(_("Folder %s does not exist"), $path), 'horde.error');
                } else {
                    $result = $this->autocreatePath($path);
                    if (is_a($result, 'PEAR_Error')) {
                        return $result;
                    }
                }
            }
            $paths[] = $path_name;
            $previous = '';

            foreach ($paths as $thispath) {
                $results = $this->_db->getAll(sprintf('SELECT vfs_owner, vfs_perms FROM %s
                                                       WHERE vfs_path = %s AND vfs_name= %s',
                                                      $this->_params['table'],
                                                      $this->_db->quote($previous),
                                                      $this->_db->quote($thispath)));
                if (!is_array($results) || count($results) < 1) {
                    return PEAR::raiseError(_("Unable to create VFS file."));
                }

                $allowed = false;
                foreach ($results as $result) {
                    if ($result[0] == $this->_params['user'] || $result[1] & VFS_FLAG_WRITE) {
                        $allowed = true;
                        break;
                    }
                }

                if (!$allowed) {
                    return PEAR::raiseError(_("Access denied creating VFS file."));
                }

                $previous = $thispath;
            }
        }

        return parent::writeData($path, $name, $data, $autocreate);
    }

    /**
     * Delete a file from the VFS.
     *
     * @access public
     *
     * @param string $path  The path to store the file in.
     * @param string $name  The filename to use.
     *
     * @return mixed  True on success or a PEAR_Error object on failure.
     */
    function deleteFile($path, $name)
    {
        $conn = $this->_connect();
        if (PEAR::isError($conn)) {
            return $conn;
        }

        $fileList = $this->_db->getAll(sprintf('SELECT vfs_id, vfs_owner, vfs_perms FROM %s
                                                WHERE vfs_path = %s AND vfs_name= %s AND vfs_type = %s',
                                               $this->_params['table'],
                                               $this->_db->quote($path),
                                               $this->_db->quote($name),
                                               $this->_db->quote(VFS_FILE)));

        if (!is_array($fileList) || count($fileList) < 1) {
            return PEAR::raiseError(_("Unable to delete VFS file."));
        }

        /* There may be one or more files with the same name but the
           user may not have read access to them, so doesn't see
           them. So we have to delete the one they have access to. */
        foreach ($fileList as $file) {
            if ($file[1] == $this->_params['user'] || $file[2] & VFS_FLAG_WRITE) {
                $result = $this->_db->query(sprintf('DELETE FROM %s WHERE vfs_id = %s',
                                                    $this->_params['table'],
                                                    $this->_db->quote($file[0])));

                if ($this->_db->affectedRows() == 0) {
                    return PEAR::raiseError(_("Unable to delete VFS file."));
                }
                return $result;
            }
        }

        // FIXME: 'Access Denied deleting file %s/%s'
        return PEAR::raiseError(_("Unable to delete VFS file."));
    }

    /**
     * Rename a file or folder in the VFS.
     *
     * @access public
     *
     * @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.
     *
     * @return mixed  True on success or a PEAR_Error object on failure.
     */
    function rename($oldpath, $oldname, $newpath, $newname)
    {
        $conn = $this->_connect();
        if (PEAR::isError($conn)) {
            return $conn;
        }

        $fileList = $this->_db->getAll(sprintf('SELECT vfs_id, vfs_owner, vfs_perms FROM %s
                                                       WHERE vfs_path = %s AND vfs_name= %s',
                                                       $this->_params['table'],
                                                       $this->_db->quote($oldpath),
                                                       $this->_db->quote($oldname)));

        if (!is_array($fileList) || count($fileList) < 1) {
            return PEAR::raiseError(_("Unable to rename VFS file."));
        }

        /* There may be one or more files with the same name but the user may
	 * not have read access to them, so doesn't see them. So we have to
	 * rename the one they have access to. */
        foreach ($fileList as $file) {
            if ($file[1] == $this->_params['user'] || $file[2] & VFS_FLAG_WRITE) {
                $result = $this->_db->query(sprintf('UPDATE %s SET vfs_path = %s, vfs_name = %s, vfs_modified = %s
                                                    WHERE vfs_id = %s',
                                                    $this->_params['table'],
                                                    $this->_db->quote($newpath),
                                                    $this->_db->quote($newname),
                                                    $this->_db->quote(time()),
                                                    $this->_db->quote($file[0])));
                return $result;
            }
        }

        return PEAR::raiseError(sprintf(_("Unable to rename VFS file %s/%s."), $oldpath, $oldname));
    }

    /**
     * Creates a folder on the VFS.
     *
     * @access public
     *
     * @param string $path  Holds the path of directory to create folder.
     * @param string $name  Holds the name of the new folder.
     *
     * @return mixed  True on success or a PEAR_Error object on failure.
     */
    function createFolder($path, $name)
    {
        $conn = $this->_connect();
        if (PEAR::isError($conn)) {
            return $conn;
        }

        /* make sure we have write access to this and all parent paths */
        if ($path != "") {
            $paths = explode('/', $path);
            $previous = '';

            foreach ($paths as $thispath) {
                $results = $this->_db->getAll(sprintf('SELECT vfs_owner, vfs_perms FROM %s
                                                               WHERE vfs_path = %s AND vfs_name= %s',
                                                               $this->_params['table'],
                                                               $this->_db->quote($previous),
                                                               $this->_db->quote($thispath)));
                if (!is_array($results) || count($results) < 1) {
                    return PEAR::raiseError(_("Unable to create VFS directory."));
                }

                $allowed = false;
                foreach ($results as $result) {
                    if ($result[0] == $this->_params['user'] || $result[1] & VFS_FLAG_WRITE) {
                        $allowed = true;
                        break;
                    }
                }

                if (!$allowed) {
                    return PEAR::raiseError(_("Access denied creating VFS directory."));
                }

                $previous = $thispath;
            }
        }

        $id = $this->_db->nextId($this->_params['table']);
        return $this->_db->query(sprintf('INSERT INTO %s (vfs_id, vfs_type, vfs_path, vfs_name, vfs_modified, vfs_owner, vfs_perms)
                                         VALUES (%s, %s, %s, %s, %s, %s, %s)',
                                         $this->_params['table'],
                                         $this->_db->quote($id),
                                         $this->_db->quote(VFS_FOLDER),
                                         $this->_db->quote($path),
                                         $this->_db->quote($name),
                                         $this->_db->quote(time()),
                                         $this->_db->quote($this->_params['user']),
                                         $this->_db->quote(0)
                                         ));
    }

    /**
     * Delete a file from the VFS.
     *
     * @access public
     *
     * @param string $path                 The path to delete the folder from.
     * @param string $name                 The foldername to use.
     * @param optional boolean $recursive  Force a recursive delete?
     *
     * @return mixed  True on success or a PEAR_Error object on failure.
     */
    function deleteFolder($path, $name, $recursive = false)
    {
        $conn = $this->_connect();
        if (PEAR::isError($conn)) {
            return $conn;
        }

        if ($recursive) {
            $result = $this->emptyFolder($path . '/' . $name);
            if (is_a($result, 'PEAR_Error')) {
                return $result;
            }
        } else {
            $list = $this->listFolder($path . '/' . $name);
            if (is_a($list, 'PEAR_Error')) {
                return $list;
            }
            if (count($list)) {
                return PEAR::raiseError(sprintf(_("Unable to delete %s, the directory is not empty"),
                                                $path . '/' . $name));
            }
        }

        $fileList = $this->_db->getAll(sprintf('SELECT vfs_id, vfs_owner, vfs_perms FROM %s
                                                       WHERE vfs_path = %s AND vfs_name= %s AND vfs_type = %s',
                                                       $this->_params['table'],
                                                       $this->_db->quote($path),
                                                       $this->_db->quote($name),
                                                       $this->_db->quote(VFS_FOLDER)));

        if (!is_array($fileList) || count($fileList) < 1) {
            return PEAR::raiseError(_("Unable to delete VFS directory."));
        }

        /* There may be one or more folders with the same name but as the user
	 * may not have read access to them, they don't see them. So we have to
	 * delete the one they have access to */
        foreach ($fileList as $file) {
            if ($file[1] == $this->_params['user'] || $file[2] & VFS_FLAG_WRITE) {
                $result = $this->_db->query(sprintf('DELETE FROM %s WHERE vfs_id = %s',
                                                    $this->_params['table'],
                                                    $this->_db->quote($file[0])));

                if ($this->_db->affectedRows() == 0) {
                    return PEAR::raiseError(_("Unable to delete VFS directory."));
                }

                return $result;
            }
        }

        // FIXME: 'Access Denied deleting folder %s/%s'
        return PEAR::raiseError(_("Unable to delete VFS directory."));
    }

    /**
     * Return a list of the contents of a folder.
     *
     * @param string $path                The path of the directory.
     * @param optional mixed $filter      String/hash to filter file/dirname
     *                                    on.
     * @param optional boolean $dotfiles  Show dotfiles?
     * @param optional boolean $dironly   Show only directories?
     *
     * @return mixed  File list on success or false on failure.
     */
    function listFolder($path, $filter = null, $dotfiles = true,
                        $dironly = false)
    {
        $conn = $this->_connect();
        if (is_a($conn, 'PEAR_Error')) {
            return $conn;
        }

        $files = array();
        $fileList = array();

        $fileList = $this->_db->getAll(sprintf('SELECT vfs_name, vfs_type, vfs_modified, vfs_owner, vfs_perms, vfs_data FROM %s
                                               WHERE vfs_path = %s AND (vfs_owner = %s or vfs_perms && %s)',
                                               $this->_params['table'],
                                               $this->_db->quote($path),
                                               $this->_db->quote($this->_params['user']),
                                               $this->_db->quote(VFS_FLAG_READ)
                                               ));
        if (is_a($fileList, 'PEAR_Error')) {
            return $fileList;
        }

        foreach ($fileList as $line) {
            // Filter out dotfiles if they aren't wanted.
            if (!$dotfiles && substr($line[0], 0, 1) == '.') {
                continue;
            }

            $file['name'] = stripslashes($line[0]);

            if ($line[1] == VFS_FILE) {
                $name = explode('.', $line[0]);

                if (count($name) == 1) {
                    $file['type'] = '**none';
                } else {
                    $file['type'] = VFS::strtolower($name[count($name) - 1]);
                }

                $file['size'] = VFS::strlen($line[5]);
            } elseif ($line[1] == VFS_FOLDER) {
                $file['type'] = '**dir';
                $file['size'] = -1;
            }

            $file['date'] = $line[2];
            $file['owner'] = $line[3];

            $line[4] = intval($line[4]);
            $file['perms']  = ($line[1] == VFS_FOLDER) ? 'd' : '-';
            $file['perms'] .= 'rw-';
            $file['perms'] .= ($line[4] & VFS_FLAG_READ) ? 'r' : '-';
            $file['perms'] .= ($line[4] & VFS_FLAG_WRITE) ? 'w' : '-';
            $file['perms'] .= '-';
            $file['group'] = '-';

            // filtering
            if ($this->_filterMatch($filter, $file['name'])) {
                unset($file);
                continue;
            }
            if ($dironly && $file['type'] !== '**dir') {
                unset($file);
                continue;
            }

            $files[$file['name']] = $file;
            unset($file);
        }

        return $files;
    }

    /**
     * Changes permissions for an Item on the VFS.
     *
     * @access public
     *
     * @param string $path  Holds the path of directory of the Item.
     * @param string $name  Holds the name of the Item.
     *
     * @return mixed  True on success or a PEAR_Error object on failure.
     */
    function changePermissions($path, $name, $permission)
    {
        $conn = $this->_connect();
        if (PEAR::isError($conn)) {
            return $conn;
        }

        $val = intval(substr($permission, -1));
        $perm = 0;
        $perm |= ($val & 4) ? VFS_FLAG_READ : 0;
        $perm |= ($val & 2) ? VFS_FLAG_WRITE : 0;

        $fileList = $this->_db->getAll(sprintf('SELECT vfs_id, vfs_owner, vfs_perms FROM %s
                                                       WHERE vfs_path = %s AND vfs_name= %s',
                                                       $this->_params['table'],
                                                       $this->_db->quote($path),
                                                       $this->_db->quote($name)));

        if (!is_array($fileList) || count($fileList) < 1) {
            return PEAR::raiseError(_("Unable to rename VFS file."));
        }

        /* There may be one or more files with the same name but the user may
	 * not have read access to them, so doesn't see them. So we have to
	 * chmod the one they have access to. */
        foreach ($fileList as $file) {
            if ($file[1] == $this->_params['user'] || $file[2] & VFS_FLAG_WRITE) {
                $result = $this->_db->query(sprintf('UPDATE %s SET vfs_perms = %s
                                                    WHERE vfs_id = %s',
                                                    $this->_params['table'],
                                                    $this->_db->quote($perm),
                                                    $this->_db->quote($file[0])));
                return $result;
            }
        }

        return PEAR::raiseError(sprintf(_("Unable to change permission for VFS file %s/%s."), $path, $name));
    }

}

--- NEW FILE: sql.php ---
<?php

/** @constant integer VFS_FILE  File value for vfs_type column. */
define('VFS_FILE', 1);

/** @constant integer VFS_FOLDER  Folder value for vfs_type column. */
define('VFS_FOLDER', 2);

/**
 * VFS implementation for PHP's PEAR database abstraction layer.
 *
 * <pre>
 * Required values for $params:
 *      'phptype'       The database type (ie. 'pgsql', 'mysql, etc.).
 *      'hostspec'      The hostname of the database server.
 *      'protocol'      The communication protocol ('tcp', 'unix', etc.).
 *      'username'      The username with which to connect to the database.
 *      'password'      The password associated with 'username'.
 *      'database'      The name of the database.
 *
 * Optional values:
 *      'table'         The name of the vfs table in 'database'. Defaults to
 *                      'horde_vfs'.
 *
 * Required by some database implementations:
 *      'options'       Additional options to pass to the database.
 *      'tty'           The TTY on which to connect to the database.
 *      'port'          The port on which to connect to the database.
 * </pre>
 *
 * The table structure for the VFS can be found in
 * horde/scripts/db/vfs.sql.
 *
 * Database specific notes:
 *
 * MSSQL:
 * <pre>
 * - The vfs_data field must be of type IMAGE.
 * - You need the following php.ini settings:
 *    ; Valid range 0 - 2147483647. Default = 4096.
 *    mssql.textlimit = 0 ; zero to pass through
 *
 *    ; Valid range 0 - 2147483647. Default = 4096.
 *    mssql.textsize = 0 ; zero to pass through
 * </pre>
 *
 * $Horde: framework/VFS/VFS/sql.php,v 1.79 2004/05/25 14:39:19 jan Exp $
 *
 * Copyright 2002-2004 Chuck Hagenbuch <chuck at horde.org>
 *
 * See the enclosed file COPYING for license information (LGPL). If you
 * did not receive this file, see http://www.fsf.org/copyleft/lgpl.html.
 *
 * @author  Chuck Hagenbuch <chuck at horde.org>
 * @version $Revision: 1.1 $
 * @since   Horde 2.2
 * @package VFS
 */
class VFS_sql extends VFS {

    /**
     * Handle for the current database connection.
     *
     * @var object DB $_db
     */
    var $_db = false;

    /**
     * Retrieve a file from the VFS.
     *
     * @access public
     *
     * @param string $path  The pathname to the file.
     * @param string $name  The filename to retrieve.
     *
     * @return string  The file data.
     */
    function read($path, $name)
    {
        $conn = $this->_connect();
        if (is_a($conn, 'PEAR_Error')) {
            return $conn;
        }

        return $this->_readBlob($this->_params['table'], 'vfs_data',
                                array('vfs_path' => $path,
                                      'vfs_name' => $name));
    }

    /**
     * Store a file in the VFS.
     *
     * @access public
     *
     * @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 optional boolean $autocreate  Automatically create directories?
     *
     * @return mixed  True on success or a PEAR_Error object on failure.

     */
    function write($path, $name, $tmpFile, $autocreate = false)
    {
        $dataFP = @fopen($tmpFile, 'rb');
        $data = @fread($dataFP, filesize($tmpFile));
        fclose($dataFP);
        return $this->writeData($path, $name, $data, $autocreate);
    }

    /**
     * Store a file in the VFS from raw data.
     *
     * @access public
     *
     * @param string $path                  The path to store the file in.
     * @param string $name                  The filename to use.
     * @param string $data                  The file data.
     * @param optional boolean $autocreate  Automatically create directories?
     *
     * @return mixed  True on success or a PEAR_Error object on failure.
     */
    function writeData($path, $name, $data, $autocreate = false)
    {
        $conn = $this->_connect();
        if (is_a($conn, 'PEAR_Error')) {
            return $conn;
        }

        /* Check to see if the data already exists. */
        $sql = sprintf('SELECT vfs_id FROM %s WHERE vfs_path %s AND vfs_name = %s',
                       $this->_params['table'],
                       (empty($path) && $this->_db->dbsyntax == 'oci8') ? ' IS NULL' : ' = ' . $this->_db->quote($path),
                       $this->_db->quote($name));
        $id = $this->_db->getOne($sql);

        if (is_a($id, 'PEAR_Error')) {
            return $id;
        }

        if (!is_null($id)) {
            return $this->_updateBlob($this->_params['table'], 'vfs_data',
                                      $data, array('vfs_id' => $id),
                                      array('vfs_modified' => time()));
        } else {
            /* Check to see if the folder already exists. */
            $dirs = explode('/', $path);
            $path_name = array_pop($dirs);
            $parent = implode('/', $dirs);
            if (!$this->isFolder($parent, $path_name)) {
                if (!$autocreate) {
                    return PEAR::raiseError(sprintf(_("Folder %s does not exist"), $path), 'horde.error');
                } else {
                    $result = $this->autocreatePath($path);
                    if (is_a($result, 'PEAR_Error')) {
                        return $result;
                    }
                }
            }

            $id = $this->_db->nextId($this->_params['table']);
            if (is_a($id, 'PEAR_Error')) {
                return $id;
            }
            return $this->_insertBlob($this->_params['table'], 'vfs_data',
                                      $data, array('vfs_id' => $id,
                                                   'vfs_type' => VFS_FILE,
                                                   'vfs_path' => $path,
                                                   'vfs_name' => $name,
                                                   'vfs_modified' => time(),
                                                   'vfs_owner' => $this->_params['user']));
        }
    }

    /**
     * Delete a file from the VFS.
     *
     * @access public
     *
     * @param string $path  The path to store the file in.
     * @param string $name  The filename to use.
     *
     * @return mixed  True on success or a PEAR_Error object on failure.
     */
    function deleteFile($path, $name)
    {
        $conn = $this->_connect();
        if (is_a($conn, 'PEAR_Error')) {
            return $conn;
        }

        $sql = sprintf('DELETE FROM %s WHERE vfs_type = %s AND vfs_path %s AND vfs_name = %s',
                       $this->_params['table'],
                       $this->_db->quote(VFS_FILE),
                       (empty($path) && $this->_db->dbsyntax == 'oci8') ? ' IS NULL' : ' = ' . $this->_db->quote($path),
                       $this->_db->quote($name));
        $result = $this->_db->query($sql);

        if ($this->_db->affectedRows() == 0) {
            return PEAR::raiseError(_("Unable to delete VFS file."));
        }

        return $result;
    }

    /**
     * Rename a file or folder in the VFS.
     *
     * @access public
     *
     * @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.
     *
     * @return mixed  True on success or a PEAR_Error object on failure.
     */
    function rename($oldpath, $oldname, $newpath, $newname)
    {
        $conn = $this->_connect();
        if (is_a($conn, 'PEAR_Error')) {
            return $conn;
        }

        $sql = sprintf('UPDATE %s SET vfs_path = %s, vfs_name = %s, vfs_modified = %s WHERE vfs_path = %s AND vfs_name = %s',
                       $this->_params['table'],
                       $this->_db->quote($newpath),
                       $this->_db->quote($newname),
                       $this->_db->quote(time()),
                       $this->_db->quote($oldpath),
                       $this->_db->quote($oldname));
        $result = $this->_db->query($sql);

        if ($this->_db->affectedRows() == 0) {
            return PEAR::raiseError(_("Unable to rename VFS file."));
        }

        $rename = $this->_recursiveRename($oldpath, $oldname, $newpath, $newname);
        if (is_a($rename, 'PEAR_Error')) {
            return PEAR::raiseError(sprintf(_("Unable to rename VFS directory: %s."), $rename->getMessage()));
        }

        return $result;
    }

    /**
     * Creates a folder on the VFS.
     *
     * @access public
     *
     * @param string $path  Holds the path of directory to create folder.
     * @param string $name  Holds the name of the new folder.
     *
     * @return mixed  True on success or a PEAR_Error object on failure.
     */
    function createFolder($path, $name)
    {
        $conn = $this->_connect();
        if (is_a($conn, 'PEAR_Error')) {
            return $conn;
        }

        $id = $this->_db->nextId($this->_params['table']);
        if (is_a($id, 'PEAR_Error')) {
            return $id;
        }

        $sql = sprintf('INSERT INTO %s (vfs_id, vfs_type, vfs_path, vfs_name, vfs_modified, vfs_owner) VALUES (%s, %s, %s, %s, %s, %s)',
                       $this->_params['table'],
                       $this->_db->quote($id),
                       $this->_db->quote(VFS_FOLDER),
                       $this->_db->quote($path),
                       $this->_db->quote($name),
                       $this->_db->quote(time()),
                       $this->_db->quote($this->_params['user']));
        return $this->_db->query($sql);
    }

    /**
     * Delete a folder from the VFS.
     *
     * @access public
     *
     * @param string $path                 The path of the folder.
     * @param string $name                 The folder name to use.
     * @param optional boolean $recursive  Force a recursive delete?
     *
     * @return mixed  True on success or a PEAR_Error object on failure.
     */
    function deleteFolder($path, $name, $recursive = false)
    {
        $conn = $this->_connect();
        if (is_a($conn, 'PEAR_Error')) {
            return $conn;
        }

        $folderPath = $this->_getNativePath($path, $name);

        /* Check if not recursive and fail if directory not empty */
        if (!$recursive) {
            $folderList = $this->listFolder($folderPath, null, true);
            if (is_a($folderList, 'PEAR_Error')) {
                return $folderList;
            } elseif (!empty($folderList)) {
                return PEAR::raiseError(sprintf(_("Unable to delete %s, the directory is not empty"),
                                                $path . '/' . $name));
            }
        }

        /* First delete everything below the folder, so if error we get
         * no orphans */
        $sql = sprintf('DELETE FROM %s WHERE vfs_path %s',
                       $this->_params['table'],
                       (empty($folderPath) && $this->_db->dbsyntax == 'oci8') ? ' IS NULL' : ' LIKE ' . $this->_db->quote($this->_getNativePath($folderPath, '%')));
        $deleteContents = $this->_db->query($sql);
        if (is_a($deleteContents, 'PEAR_Error')) {
            return PEAR::raiseError(sprintf(_("Unable to delete VFS recursively: %s."), $deleteContents->getMessage()));
        }

        /* Now delete everything inside the folder. */
        $sql = sprintf('DELETE FROM %s WHERE vfs_path %s',
                       $this->_params['table'],
                       (empty($path) && $this->_db->dbsyntax == 'oci8') ? ' IS NULL' : ' = ' . $this->_db->quote($folderPath));
        $delete = $this->_db->query($sql);
        if (is_a($delete, 'PEAR_Error')) {
            return PEAR::raiseError(sprintf(_("Unable to delete VFS directory: %s."), $delete->getMessage()));
        }

        /* All ok now delete the actual folder */
        $sql = sprintf('DELETE FROM %s WHERE vfs_path %s AND vfs_name = %s',
                       $this->_params['table'],
                       (empty($path) && $this->_db->dbsyntax == 'oci8') ? ' IS NULL' : ' = ' . $this->_db->quote($path),
                       $this->_db->quote($name));
        $delete = $this->_db->query($sql);
        if (is_a($delete, 'PEAR_Error')) {
            return PEAR::raiseError(sprintf(_("Unable to delete VFS directory: %s."), $delete->getMessage()));
        }

        return $delete;
    }

    /**
     * Return a list of the contents of a folder.
     *
     * @access public
     *
     * @param string $path                The directory path.
     * @param optional mixed $filter      String/hash of items to filter based
     *                                    on filename.
     * @param optional boolean $dotfiles  Show dotfiles?
     * @param optional boolean $dironly   Show directories only?
     *
     * @return mixed  File list on success or false on failure.
     */
    function listFolder($path, $filter = null, $dotfiles = true,
                        $dironly = false)
    {
        $conn = $this->_connect();
        if (is_a($conn, 'PEAR_Error')) {
            return $conn;
        }

        $files = array();
        $fileList = array();

        // Fix for an ODD Oracle quirk.
        if (empty($path) && $this->_db->dbsyntax == 'oci8') {
            $where = 'vfs_path IS NULL';
        } else {
            $where = 'vfs_path = ' . $this->_db->quote($path);
        }

        $sql = sprintf('SELECT vfs_name, vfs_type, vfs_data, vfs_modified, vfs_owner FROM %s WHERE %s',
                            $this->_params['table'],
                            $where);
        $fileList = $this->_db->getAll($sql);
        if (is_a($fileList, 'PEAR_Error')) {
            return $fileList;
        }

        foreach ($fileList as $line) {
            // Filter out dotfiles if they aren't wanted.
            if (!$dotfiles && substr($line[0], 0, 1) == '.') {
                continue;
            }

            $file['name'] = $line[0];

            if ($line[1] == VFS_FILE) {
                $name = explode('.', $line[0]);

                if (count($name) == 1) {
                    $file['type'] = '**none';
                } else {
                    $file['type'] = VFS::strtolower($name[count($name) - 1]);
                }

                $file['size'] = VFS::strlen($line[2]);
            } elseif ($line[1] == VFS_FOLDER) {
                $file['type'] = '**dir';
                $file['size'] = -1;
            }

            $file['date'] = $line[3];
            $file['owner'] = $line[4];
            $file['perms'] = '-';
            $file['group'] = '-';

            // filtering
            if ($this->_filterMatch($filter, $file['name'])) {
                unset($file);
                continue;
            }
            if ($dironly && $file['type'] !== '**dir') {
                unset($file);
                continue;
            }

            $files[$file['name']] = $file;
            unset($file);
       }

        return $files;
    }

    /**
     * Returns a sorted list of folders in specified directory.
     *
     * @access public
     *
     * @param optional string $path         The path of the directory to get
     *                                      the directory list for.
     * @param optional mixed $filter        String/hash of items to filter
     *                                      based on folderlist.
     * @param optional boolean $dotfolders  Include dotfolders?
     *
     * @return mixed  Folder list on success or PEAR_Error object on failure.
     */
    function listFolders($path = '', $filter = null, $dotfolders = true)
    {
        $sql = sprintf('SELECT vfs_name, vfs_path FROM %s WHERE vfs_path = %s AND vfs_type = %s',
                       $this->_params['table'],
                       $path,
                       VFS_FOLDER);
        $folderList = $this->_db->getAll($sql);
        if (is_a($folderList, 'PEAR_Error')) {
            return $folderList;
        }

        $folders = array();
        foreach ($folderList as $line) {
            $folder['val'] = $this->_getSQLNativePath($line[1], $line[0]);
            $folder['abbrev'] = '';
            $folder['label'] = '';

            $count = substr_count($folder['val'], '/');

            $x = 0;
            while ($x < $count) {
                $folder['abbrev'] .= '    ';
                $folder['label'] .= '    ';
                $x++;
            }

            $folder['abbrev'] .= $line[0];
            $folder['label'] .= $line[0];

            $strlen = VFS::strlen($folder['label']);
            if ($strlen > 26) {
                $folder['abbrev'] = substr($folder['label'], 0, ($count * 4));
                $length = (29 - ($count * 4)) / 2;
                $folder['abbrev'] .= substr($folder['label'], ($count * 4), $length);
                $folder['abbrev'] .= '...';
                $folder['abbrev'] .= substr($folder['label'], -1 * $length, $length);
            }

            $found = false;
            foreach ($filter as $fltr) {
                if ($folder['val'] == $fltr) {
                    $found = true;
                }
            }

            if (!$found) {
                $folders[$folder['val']] = $folder;
            }
        }

        ksort($folders);
        return $folders;
    }

    /**
     * Renames all child paths.
     *
     * @access private
     *
     * @param string $path  The path of the folder to rename.
     * @param string $name  The foldername to use.
     *
     * @return mixed  True on success or a PEAR_Error object on failure.
     */
    function _recursiveRename($oldpath, $oldname, $newpath, $newname)
    {
        $sql = sprintf('SELECT vfs_name FROM %s WHERE vfs_type = %s AND vfs_path = %s',
                       $this->_params['table'],
                       $this->_db->quote(VFS_FOLDER),
                       $this->_db->quote($this->_getNativePath($oldpath, $oldname)));
        $folderList = $this->_db->getCol($sql);

        foreach ($folderList as $folder) {
            $this->_recursiveRename($this->_getNativePath($oldpath, $oldname), $folder, $this->_getNativePath($newpath, $newname), $folder);
        }

        $sql = sprintf('UPDATE %s SET vfs_path = %s WHERE vfs_path = %s',
                       $this->_params['table'],
                       $this->_db->quote($this->_getNativePath($newpath, $newname)),
                       $this->_db->quote($this->_getNativePath($oldpath, $oldname)));
        $result = $this->_db->query($sql);

        return $result;
    }

    /**
     * Return a full filename on the native filesystem, from a VFS
     * path and name.
     *
     * @access private
     *
     * @param string $path  The VFS file path.
     * @param string $name  The VFS filename.
     *
     * @return string  The full native filename.
     */
    function _getNativePath($path, $name)
    {
        if (empty($path)) {
            return $name;
        }

        if (!empty($path)) {
            if (isset($this->_params['home']) &&
                preg_match('|^~/?(.*)$|', $path, $matches)) {
                $path = $this->_params['home'] . '/' . $matches[1];
            }
        }

        return $path . '/' . $name;
    }

    /**
     * Attempts to open a persistent connection to the SQL server.
     *
     * @access private
     *
     * @return mixed  True on success or a PEAR_Error object on failure.
     */
    function _connect()
    {
        if ($this->_db === false) {
            if (!is_array($this->_params)) {
                return PEAR::raiseError(_("No configuration information specified for SQL VFS."));
            }

            $required = array('phptype', 'hostspec', 'username', 'password', 'database');
            foreach ($required as $val) {
                if (!isset($this->_params[$val])) {
                    return PEAR::raiseError(sprintf(_("Required '%s' not specified in VFS configuration."), $val));
                }
            }

            if (!isset($this->_params['table'])) {
                $this->_params['table'] = 'horde_vfs';
            }

            /* Connect to the SQL server using the supplied parameters. */
            require_once 'DB.php';
            $this->_db = &DB::connect($this->_params,
                                      array('persistent' => !empty($this->_params['persistent'])));
            if (is_a($this->_db, 'PEAR_Error')) {
                $error = $this->_db;
                $this->_db = false;
                return $error;
            }

            /* Enable the "portability" option. */
            $this->_db->setOption('optimize', 'portability');
        }

        return true;
    }

    /**
     * Disconnect from the SQL server and clean up the connection.
     *
     * @access private
     */
    function _disconnect()
    {
        if ($this->_db) {
            $this->_db->disconnect();
            $this->_db = false;
        }
    }

    /**
     * TODO
     *
     * @access private
     *
     * @param string $table     TODO
     * @param string $field     TODO
     * @param string $criteria  TODO
     *
     * @return mixed  TODO
     */
    function _readBlob($table, $field, $criteria)
    {
        if (!count($criteria)) {
            return PEAR::raiseError('You must specify the fetch criteria');
        }

        $where = '';

        switch ($this->_db->dbsyntax) {
        case 'oci8':
            foreach ($criteria as $key => $value) {
                if (!empty($where)) {
                    $where .= ' AND ';
                }
                if (empty($value)) {
                    $where .= $key . ' IS NULL';
                } else {
                    $where .= $key . ' = ' . $this->_db->quote($value);
                }
            }

            $statement = OCIParse($this->_db->connection,
                                  sprintf('SELECT %s FROM %s WHERE %s',
                                          $field, $table, $where));
            OCIExecute($statement);
            if (OCIFetchInto($statement, $lob)) {
                $result = $lob[0]->load();
                if (is_null($result)) {
                    $result = PEAR::raiseError('Unable to load SQL data.');
                }
            } else {
                $result = PEAR::raiseError('Unable to load SQL data.');
            }
            OCIFreeStatement($statement);
            break;

        default:
            foreach ($criteria as $key => $value) {
                if (!empty($where)) {
                    $where .= ' AND ';
                }
                $where .= $key . ' = ' . $this->_db->quote($value);
            }

            $sql = sprintf('SELECT %s FROM %s WHERE %s',
                           $field, $table, $where);
            $result = $this->_db->getOne($sql);

            if (is_null($result)) {
                $result = PEAR::raiseError('Unable to load SQL data.');
            } else {
                switch ($this->_db->dbsyntax) {
                case 'pgsql':
                    $result = pack('H' . strlen($result), $result);
                    break;
                }
            }
        }

        return $result;
    }

    /**
     * TODO
     *
     * @access private
     *
     * @param string $table       TODO
     * @param string $field       TODO
     * @param string $data        TODO
     * @param string $attributes  TODO
     *
     * @return mixed  TODO
     */
    function _insertBlob($table, $field, $data, $attributes)
    {
        $fields = array();
        $values = array();

        switch ($this->_db->dbsyntax) {
        case 'oci8':
            foreach ($attributes as $key => $value) {
                $fields[] = $key;
                $values[] = $this->_db->quote($value);
            }

            $statement = OCIParse($this->_db->connection,
                                  sprintf('INSERT INTO %s (%s, %s)' .
                                          ' VALUES (%s, EMPTY_BLOB()) RETURNING %s INTO :blob',
                                          $table,
                                          implode(', ', $fields),
                                          $field,
                                          implode(', ', $values),
                                          $field));

            $lob = OCINewDescriptor($this->_db->connection);
            OCIBindByName($statement, ':blob', $lob, -1, SQLT_BLOB);
            OCIExecute($statement, OCI_DEFAULT);
            $lob->save($data);
            $result = OCICommit($this->_db->connection);
            $lob->free();
            OCIFreeStatement($statement);
            return $result ? true : PEAR::raiseError('Unknown Error');

        default:
            foreach ($attributes as $key => $value) {
                $fields[] = $key;
                $values[] = $value;
            }

            $query = sprintf('INSERT INTO %s (%s, %s) VALUES (%s)',
                             $table,
                             implode(', ', $fields),
                             $field,
                             '?' . str_repeat(', ?', count($values)));
            break;
        }

        switch ($this->_db->dbsyntax) {
        case 'mssql':
        case 'pgsql':
            $values[] = bin2hex($data);
            break;

        default:
            $values[] = $data;
        }

        /* Execute the query. */
        $stmt = $this->_db->prepare($query);
        return $this->_db->execute($stmt, $values);
    }

    /**
     * TODO
     *
     * @access private
     *
     * @param string $table      TODO
     * @param string $field      TODO
     * @param string $data       TODO
     * @param string $where      TODO
     * @param array $alsoupdate  TODO
     *
     * @return mixed  TODO
     */
    function _updateBlob($table, $field, $data, $where, $alsoupdate)
    {
        $fields = array();
        $values = array();

        switch ($this->_db->dbsyntax) {
        case 'oci8':
            $wherestring = '';
            foreach ($where as $key => $value) {
                if (!empty($wherestring)) {
                    $wherestring .= ' AND ';
                }
                $wherestring .= $key . ' = ' . $this->_db->quote($value);
            }

            $statement = OCIParse($this->_db->connection,
                                  sprintf('SELECT %s FROM %s FOR UPDATE WHERE %s',
                                          $field,
                                          $table,
                                          $wherestring));

            OCIExecute($statement, OCI_DEFAULT);
            OCIFetchInto($statement, $lob);
            $lob[0]->save($data);
            $result = OCICommit($this->_db->connection);
            $lob[0]->free();
            OCIFreeStatement($statement);
            return $result ? true : PEAR::raiseError('Unknown Error');

        default:
            $updatestring = '';
            $values = array();
            foreach ($alsoupdate as $key => $value) {
                $updatestring .= $key . ' = ?, ';
                $values[] = $value;
            }
            $updatestring .= $field . ' = ?';
            switch ($this->_db->dbsyntax) {
            case 'mssql':
            case 'pgsql':
                $values[] = bin2hex($data);
                break;

            default:
                $values[] = $data;
            }

            $wherestring = '';
            foreach ($where as $key => $value) {
                if (!empty($wherestring)) {
                    $wherestring .= ' AND ';
                }
                $wherestring .= $key . ' = ?';
                $values[] = $value;
            }

            $query = sprintf('UPDATE %s SET %s WHERE %s',
                             $table,
                             $updatestring,
                             $wherestring);
            break;
        }

        /* Execute the query. */
        $stmt = $this->_db->prepare($query);
        return $this->_db->execute($stmt, $values);
    }

}

--- NEW FILE: sql_file.php ---
<?php

/** @constant integer VFS_FILE  File value for vfs_type column. */
define('VFS_FILE', 1);

/** @constant integer VFS_FOLDER  Folder value for vfs_type column. */
define('VFS_FOLDER', 2);

/**
 * VFS:: implementation using PHP's PEAR database abstraction
 * layer and local file system for file storage.
 *
 * <pre>
 * Required values for $params:
 *      'phptype'       The database type (ie. 'pgsql', 'mysql, etc.).
 *      'hostspec'      The hostname of the database server.
 *      'protocol'      The communication protocol ('tcp', 'unix', etc.).
 *      'username'      The username with which to connect to the database.
 *      'password'      The password associated with 'username'.
 *      'database'      The name of the database.
 *      'vfsroot'       The root directory of where the files should be
 *                      actually stored.
 *
 * Optional values:
 *      'table'         The name of the vfs table in 'database'. Defaults to
 *                      'horde_vfs'.
 *
 * Required by some database implementations:
 *      'options'       Additional options to pass to the database.
 *      'tty'           The TTY on which to connect to the database.
 *      'port'          The port on which to connect to the database.
 * </pre>
 *
 * The table structure for the VFS can be found in
 * horde/scripts/db/vfs.sql.
 *
 * $Horde: framework/VFS/VFS/sql_file.php,v 1.40 2004/04/08 18:33:17 slusarz Exp $
 *
 * @author  Michael Varghese <mike.varghese at ascellatech.com>
 * @version $Revision: 1.1 $
 * @since   Horde 2.2
 * @package VFS
 */
class VFS_sql_file extends VFS {

    /**
     * Handle for the current database connection.
     *
     * @var object DB $_db
     */
    var $_db = false;

    /**
     * Retrieve a file from the VFS.
     *
     * @access public
     *
     * @param string $path  The pathname to the file.
     * @param string $name  The filename to retrieve.
     *
     * @return string  The file data.
     */
    function read($path, $name)
    {
        $conn = $this->_connect();
        if (is_a($conn, 'PEAR_Error')) {
            return $conn;
        }

        $file = $this->_getNativePath($path, $name);
        $fp = @fopen($file, 'rb');
        if (!$fp) {
            return PEAR::raiseError(_("Unable to open VFS file."));
        }

        $data = fread($fp, filesize($file));
        fclose($fp);

        return $data;
    }

    /**
     * Store a file in the VFS, with the data copied from a temporary
     * file.
     *
     * @access public
     *
     * @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 optional boolean $autocreate  Automatically create directories?
     *
     * @return mixed  True on success or a PEAR_Error object on failure.
     */
    function write($path, $name, $tmpFile, $autocreate = false)
    {
        $dataFP = @fopen($tmpFile, 'rb');
        $data = @fread($dataFP, filesize($tmpFile));
        fclose($dataFP);
        return $this->writeData($path, $name, $data, $autocreate);
    }

    /**
     * Store a file in the VFS from raw data.
     *
     * @access public
     *
     * @param string $path                  The path to store the file in.
     * @param string $name                  The filename to use.
     * @param string $data                  The file data.
     * @param optional boolean $autocreate  Automatically create directories?
     *
     * @return mixed  True on success or a PEAR_Error object on failure.
     */
    function writeData($path, $name, $data, $autocreate = false)
    {
        $fp = @fopen($this->_getNativePath($path, $name), 'w');
        if (!$fp) {
            if ($autocreate) {
                $result = $this->autocreatePath($path);
                if (is_a($result, 'PEAR_Error')) {
                    return $result;
                }
                $fp = @fopen($this->_getNativePath($path, $name), 'w');
                if (!$fp) {
                    return PEAR::raiseError(_("Unable to open VFS file for writing."));
                }
            }
            return PEAR::raiseError(_("Unable to open VFS file for writing."));
        }

        if (!@fwrite($fp, $data)) {
            return PEAR::raiseError(_("Unable to write VFS file data."));
        }

        if (is_a($this->_writeSQLData($path, $name, $autocreate), 'PEAR_Error')) {
            @unlink($this->_getNativePath($path, $name));
            return PEAR::raiseError(_("Unable to write VFS file data."));
        }
    }

    /**
     * Moves a file in the database and the file system.
     *
     * @access public
     *
     * @param string $path  The path to store the file in.
     * @param string $name  The old filename.
     * @param string $dest  The new filename.
     *
     * @return mixed  True on success or a PEAR_Error object on failure.
     */
    function move($path, $name, $dest)
    {
        $conn = $this->_connect();
        if (is_a($conn, 'PEAR_Error')) {
            return $conn;
        }

        $fileCheck = $this->listFolder($dest, null, false);
        foreach ($fileCheck as $file) {
            if ($file['name'] == $name) {
                return PEAR::raiseError(_("Unable to move VFS file."));
            }
        }

        if (strpos($dest, $this->_getSQLNativePath($path, $name)) !== false) {
            return PEAR::raiseError(_("Unable to move VFS file."));
        }

        return $this->rename($path, $name, $dest, $name);
    }

    /**
     * Copies a file through the backend.
     *
     * @access public
     *
     * @param string $path  The path to store the file in.
     * @param string $name  The filename to use.
     * @param string $dest  The destination of the file.
     *
     * @return mixed  True on success or a PEAR_Error object on failure.
     */
    function copy($path, $name, $dest)
    {
        $conn = $this->_connect();
        if (is_a($conn, 'PEAR_Error')) {
            return $conn;
        }

        $fileCheck = $this->listFolder($dest, null, false);
        foreach ($fileCheck as $file) {
            if ($file['name'] == $name) {
                return PEAR::raiseError(_("Unable to copy VFS file."));
            }
        }

        if (strpos($dest, $this->_getSQLNativePath($path, $name)) !== false) {
            return PEAR::raiseError(_("Unable to copy VFS file."));
        }

        if (is_dir($this->_getNativePath($path, $name))) {
            return $this->_recursiveCopy($path, $name, $dest);
        }

        if (!@copy($this->_getNativePath($path, $name), $this->_getNativePath($dest, $name))) {
            return PEAR::raiseError(_("Unable to copy VFS file."));
        }

        $id = $this->_db->nextId($this->_params['table']);

        $query = sprintf('INSERT INTO %s (vfs_id, vfs_type, vfs_path, vfs_name, vfs_modified, vfs_owner) VALUES (%s, %s, %s, %s, %s, %s)',
                         $this->_params['table'],
                         $this->_db->quote($id),
                         $this->_db->quote(VFS_FILE),
                         $this->_db->quote($dest),
                         $this->_db->quote($name),
                         $this->_db->quote(time()),
                         $this->_db->quote($this->_params['user'])
                         );

        $result = $this->_db->query($query);

        if (is_a($result, 'PEAR_Error')) {
            unlink($this->_getNativePath($dest, $name));
            return $result;
        }

        return true;
    }

    /**
     * Creates a folder on the VFS.
     *
     * @access public
     *
     * @param string $path  Holds the path of directory to create folder.
     * @param string $name  Holds the name of the new folder.
     *
     * @return mixed  True on success or a PEAR_Error object on failure.
     */
    function createFolder($path, $name)
    {
        $conn = $this->_connect();
        if (is_a($conn, 'PEAR_Error')) {
            return $conn;
        }

        $id = $this->_db->nextId($this->_params['table']);
        $result = $this->_db->query(sprintf('INSERT INTO %s (vfs_id, vfs_type, vfs_path, vfs_name, vfs_modified, vfs_owner)
                                         VALUES (%s, %s, %s, %s, %s, %s)',
                                         $this->_params['table'],
                                         $this->_db->quote($id),
                                         $this->_db->quote(VFS_FOLDER),
                                         $this->_db->quote($path),
                                         $this->_db->quote($name),
                                         $this->_db->quote(time()),
                                         $this->_db->quote($this->_params['user'])));
        if (is_a($result, 'PEAR_Error')) {
            return $result;
        }

        if (!@mkdir($this->_getNativePath($path, $name))) {
            $result = $this->_db->query(sprintf('DELETE FROM %s WHERE vfs_id = %s',
                                                $this->_params['table'],
                                                $this->_db->quote($id)));
            return PEAR::raiseError(_("Unable to create VFS directory."));
        }

        return true;
    }

    /**
     * Rename a file or folder in the VFS.
     *
     * @access public
     *
     * @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.
     *
     * @return mixed  True on success or a PEAR_Error object on failure.
     */
    function rename($oldpath, $oldname, $newpath, $newname)
    {
        $conn = $this->_connect();
        if (is_a($conn, 'PEAR_Error')) {
            return $conn;
        }

        $result = $this->_db->query(sprintf('UPDATE %s SET vfs_path = %s, vfs_name = %s, vfs_modified = %s
                                            WHERE vfs_path = %s AND vfs_name = %s',
                                            $this->_params['table'],
                                            $this->_db->quote($newpath),
                                            $this->_db->quote($newname),
                                            $this->_db->quote(time()),
                                            $this->_db->quote($oldpath),
                                            $this->_db->quote($oldname)));

        if ($this->_db->affectedRows() == 0) {
            return PEAR::raiseError(_("Unable to rename VFS file."));
        }

        if (is_a($this->_recursiveSQLRename($oldpath, $oldname, $newpath, $newname), 'PEAR_Error')) {
            $result = $this->_db->query(sprintf('UPDATE %s SET vfs_path = %s, vfs_name = %s
                                                WHERE vfs_path = %s AND vfs_name = %s',
                                                $this->_params['table'],
                                                $this->_db->quote($oldpath),
                                                $this->_db->quote($oldname),
                                                $this->_db->quote($newpath),
                                                $this->_db->quote($newname)));
            return PEAR::raiseError(_("Unable to rename VFS directory."));
        }

        if (!@rename($this->_getNativePath($oldpath, $oldname), $this->_getNativePath($newpath, $newname))) {
            $result = $this->_db->query(sprintf('UPDATE %s SET vfs_path = %s, vfs_name = %s
                                                WHERE vfs_path = %s AND vfs_name = %s',
                                                $this->_params['table'],
                                                $this->_db->quote($oldpath),
                                                $this->_db->quote($oldname),
                                                $this->_db->quote($newpath),
                                                $this->_db->quote($newname)));
            return PEAR::raiseError(_("Unable to rename VFS file."));
        }

        return true;
    }

    /**
     * Delete a folder from the VFS.
     *
     * @access public
     *
     * @param string $path                 The path to delete the folder from.
     * @param string $name                 The foldername to use.
     * @param optional boolean $recursive  Force a recursive delete?
     *
     * @return mixed  True on success or a PEAR_Error object on failure.
     */
    function deleteFolder($path, $name, $recursive = false)
    {
        $conn = $this->_connect();
        if (is_a($conn, 'PEAR_Error')) {
            return $conn;
        }

        if ($recursive) {
            $result = $this->emptyFolder($path . '/' . $name);
            if (is_a($result, 'PEAR_Error')) {
                return $result;
            }
        } else {
            $list = $this->listFolder($path . '/' . $name);
            if (is_a($list, 'PEAR_Error')) {
                return $list;
            }
            if (count($list)) {
                return PEAR::raiseError(sprintf(_("Unable to delete %s, the directory is not empty"),
                                                $path . '/' . $name));
            }
        }

        $result = $this->_db->query(sprintf('DELETE FROM %s WHERE vfs_type = %s AND vfs_path = %s AND vfs_name = %s',
                                            $this->_params['table'],
                                            $this->_db->quote(VFS_FOLDER),
                                            $this->_db->quote($path),
                                            $this->_db->quote($name)));

        if ($this->_db->affectedRows() == 0 || is_a($result, 'PEAR_Error')) {
            return PEAR::raiseError(_("Unable to delete VFS directory."));
        }

        if (is_a($this->_recursiveSQLDelete($path, $name), 'PEAR_Error')) {
            return PEAR::raiseError(_("Unable to delete VFS directory recursively."));
        }

        if (is_a($this->_recursiveLFSDelete($path, $name), 'PEAR_Error')) {
            return PEAR::raiseError(_("Unable to delete VFS directory recursively."));
        }

        return $result;
    }

    /**
     * Delete a file from the VFS.
     *
     * @access public
     *
     * @param string $path  The path to store the file in.
     * @param string $name  The filename to use.
     *
     * @return mixed  True on success or a PEAR_Error object on failure.
     */
    function deleteFile($path, $name)
    {
        $conn = $this->_connect();
        if (is_a($conn, 'PEAR_Error')) {
            return $conn;
        }

        $result = $this->_db->query(sprintf('DELETE FROM %s WHERE vfs_type = %s AND vfs_path = %s AND vfs_name = %s',
                                            $this->_params['table'],
                                            $this->_db->quote(VFS_FILE),
                                            $this->_db->quote($path),
                                            $this->_db->quote($name)));

        if ($this->_db->affectedRows() == 0) {
            return PEAR::raiseError(_("Unable to delete VFS file."));
        }

        if (is_a($result, 'PEAR_Error')) {
            return $result;
        }

        if (!@unlink($this->_getNativePath($path, $name))) {
            return PEAR::raiseError(_("Unable to delete VFS file."));
        }
    }

    /**
     * Return a list of the contents of a folder.
     *
     * @access public
     *
     * @param string $path                The directory path.
     * @param optional mixed $filter      String/hash of items to filter based
     *                                    on filename.
     * @param optional boolean $dotfiles  Show dotfiles?
     * @param optional boolean $dironly   Show directories only?
     *
     * @return mixed  File list on success or false on failure.
     */
    function listFolder($path, $filter = null, $dotfiles = true,
                        $dironly = false)
    {
        $conn = $this->_connect();
        if (is_a($conn, 'PEAR_Error')) {
            return $conn;
        }

        $files = array();
        $fileList = array();

        $fileList = $this->_db->getAll(sprintf('SELECT vfs_name, vfs_type, vfs_modified, vfs_owner FROM %s
                                               WHERE vfs_path = %s',
                                               $this->_params['table'],
                                               $this->_db->quote($path)));
        if (is_a($fileList, 'PEAR_Error')) {
            return $fileList;
        }

        foreach ($fileList as $line) {
            // Filter out dotfiles if they aren't wanted.
            if (!$dotfiles && substr($line[0], 0, 1) == '.') {
                continue;
            }

            $file['name'] = $line[0];

            if ($line[1] == VFS_FILE) {
                $name = explode('.', $line[0]);

                if (count($name) == 1) {
                    $file['type'] = '**none';
                } else {
                    $file['type'] = VFS::strtolower($name[count($name) - 1]);
                }

                $file['size'] = filesize($this->_getNativePath($path, $line[0]));
            } elseif ($line[1] == VFS_FOLDER) {
                $file['type'] = '**dir';
                $file['size'] = -1;
            }

            $file['date'] = $line[2];
            $file['owner'] = $line[3];
            $file['perms'] = '-';
            $file['group'] = '-';

            // Filtering.
            if ($this->_filterMatch($filter, $file['name'])) {
                unset($file);
                continue;
            }
            if ($dironly && $file['type'] !== '**dir') {
                unset($file);
                continue;
            }

            $files[$file['name']] = $file;
            unset($file);
        }

        return $files;
    }

    /**
     * Returns a sorted list of folders in specified directory.
     *
     * @access public
     *
     * @param optional string $path         The path of the directory to get
     *                                      the directory list for.
     * @param optional mixed $filter        String/hash of items to filter
     *                                      based on folderlist.
     * @param optional boolean $dotfolders  Include dotfolders?
     *
     * @return mixed  Folder list on success or a PEAR_Error object on failure.
     */
    function listFolders($path = '', $filter = null, $dotfolders = true)
    {
        $sql = sprintf('SELECT vfs_name, vfs_path FROM %s WHERE vfs_path = %s AND vfs_type = %s',
                       $this->_params['table'],
                       $path,
                       VFS_FOLDER);

        $folderList = $this->_db->getAll($sql);
        if (is_a($folderList, 'PEAR_Error')) {
            return $folderList;
        }

        $folders = array();
        foreach ($folderList as $line) {
            $folder['val'] = $this->_getSQLNativePath($line[1], $line[0]);
            $folder['abbrev'] = '';
            $folder['label'] = '';

            $count = substr_count($folder['val'], '/');

            $x = 0;
            while ($x < $count) {
                $folder['abbrev'] .= '    ';
                $folder['label'] .= '    ';
                $x++;
            }

            $folder['abbrev'] .= $line[0];
            $folder['label'] .= $line[0];

            $strlen = VFS::strlen($folder['label']);
            if ($strlen > 26) {
                $folder['abbrev'] = substr($folder['label'], 0, ($count * 4));
                $length = (29 - ($count * 4)) / 2;
                $folder['abbrev'] .= substr($folder['label'], ($count * 4), $length);
                $folder['abbrev'] .= '...';
                $folder['abbrev'] .= substr($folder['label'], -1 * $length, $length);
            }

            $found = false;
            foreach ($filter as $fltr) {
                if ($folder['val'] == $fltr) {
                    $found = true;
                }
            }

            if (!$found) {
                $folders[$folder['val']] = $folder;
            }
        }

        ksort($folders);
        return $folders;
    }

    /**
     * Recursively copies the contents of a folder to a destination.
     *
     * @access private
     *
     * @param string $path  The path to store the directory in.
     * @param string $name  The name of the directory.
     * @param string $dest  The destination of the directory.
     *
     * @return mixed  True on success or a PEAR_Error object on failure.
     */
    function _recursiveCopy($path, $name, $dest)
    {
        $result = $this->createFolder($dest, $name);

        if (is_a($result, 'PEAR_Error')) {
            return $result;
        }

        $file_list = $this->listFolder($this->_getSQLNativePath($path, $name));

        foreach ($file_list as $file) {
            $result = $this->copy($this->_getSQLNativePath($path, $name), $file['name'], $this->_getSQLNativePath($dest, $name));

            if (is_a($result, 'PEAR_Error')) {
                return $result;
            }
        }
        return true;
     }

    /**
     * Store a files information within the database.
     *
     * @access private
     *
     * @param string $path                  The path to store the file in.
     * @param string $name                  The filename to use.
     * @param optional boolean $autocreate  Automatically create directories?
     *
     * @return mixed  True on success or a PEAR_Error object on failure.
     */
    function _writeSQLData($path, $name, $autocreate = false)
    {
        $conn = $this->_connect();
        if (is_a($conn, 'PEAR_Error')) {
            return $conn;
        }

        $id = $this->_db->nextId($this->_params['table']);

        $query = sprintf('INSERT INTO %s (vfs_id, vfs_type, vfs_path, vfs_name, vfs_modified,' .
                         ' vfs_owner) VALUES (%s, %s, %s, %s, %s, %s)',
                         $this->_params['table'],
                         $this->_db->quote($id),
                         $this->_db->quote(VFS_FILE),
                         $this->_db->quote($path),
                         $this->_db->quote($name),
                         $this->_db->quote(time()),
                         $this->_db->quote($this->_params['user']));

        return $this->_db->query($query);
    }

    /**
     * Renames all child paths.
     *
     * @access private
     *
     * @param string $oldpath  The old path of the folder to rename.
     * @param string $oldname  The old name.
     * @param string $newpath  The new path of the folder to rename.
     * @param string $newname  The new name.
     *
     * @return mixed  True on success or a PEAR_Error object on failure.
     */
    function _recursiveSQLRename($oldpath, $oldname, $newpath, $newname)
    {
        $folderList = $this->_db->getCol(sprintf('SELECT vfs_name FROM %s WHERE vfs_type = %s AND vfs_path = %s',
                                                 $this->_params['table'],
                                                 $this->_db->quote(VFS_FOLDER),
                                                 $this->_db->quote($this->_getSQLNativePath($oldpath, $oldname))));

        foreach ($folderList as $folder) {
            $this->_recursiveSQLRename($this->_getSQLNativePath($oldpath, $oldname), $folder, $this->_getSQLNativePath($newpath, $newname), $folder);
        }

        $result = $this->_db->query(sprintf('UPDATE %s SET vfs_path = %s WHERE vfs_path = %s',
                                            $this->_params['table'],
                                            $this->_db->quote($this->_getSQLNativePath($newpath, $newname)),
                                            $this->_db->quote($this->_getSQLNativePath($oldpath, $oldname))));

        if (is_a($result, 'PEAR_Error')) {
            return $result;
        }
    }

    /**
     * Delete a folders contents from the VFS in the SQL database,
     * recursively.
     *
     * @access private
     *
     * @param string $path  The path of the folder.
     * @param string $name  The foldername to use.
     *
     * @return mixed  True on success or a PEAR_Error object on failure.
     */
    function _recursiveSQLDelete($path, $name)
    {
        $result = $this->_db->query(sprintf('DELETE FROM %s WHERE vfs_type = %s AND vfs_path = %s',
                                            $this->_params['table'],
                                            $this->_db->quote(VFS_FILE),
                                            $this->_db->quote($this->_getSQLNativePath($path, $name))));
        if (is_a($result, 'PEAR_Error')) {
            return $result;
        }

        $folderList = $this->_db->getCol(sprintf('SELECT vfs_name FROM %s WHERE vfs_type = %s AND vfs_path = %s',
                                                 $this->_params['table'],
                                                 $this->_db->quote(VFS_FOLDER),
                                                 $this->_db->quote($this->_getSQLNativePath($path, $name))));

        foreach ($folderList as $folder) {
            $this->_recursiveSQLDelete($this->_getSQLNativePath($path, $name), $folder);
        }

        $result = $this->_db->query(sprintf('DELETE FROM %s WHERE vfs_type = %s AND vfs_name = %s AND vfs_path = %s',
                                            $this->_params['table'],
                                            $this->_db->quote(VFS_FOLDER),
                                            $this->_db->quote($name),
                                            $this->_db->quote($path)));

        return $result;
    }

    /**
     * Delete a folders contents from the VFS, recursively.
     *
     * @access private
     *
     * @param string $path  The path of the folder.
     * @param string $name  The foldername to use.
     *
     * @return mixed  True on success or a PEAR_Error object on failure.
     */
    function _recursiveLFSDelete($path, $name)
    {
        $dir = $this->_getNativePath($path, $name);
        $dh = @opendir($dir);

        while (false !== ($file = readdir($dh))) {
            if ($file != '.' && $file != '..') {
                if (is_dir($dir . '/' . $file)) {
                    $this->_recursiveLFSDelete(empty($path) ? $name : $path . '/' . $name, $file);
                } else {
                    @unlink($dir . '/' . $file);
                }
            }
        }
        @closedir($dh);

        return rmdir($dir);
    }

    /**
     * Attempts to open a persistent connection to the SQL server.
     *
     * @access private
     *
     * @return mixed  True on success or a PEAR_Error object on failure.
     */
    function _connect()
    {
        if ($this->_db === false) {
            if (!is_array($this->_params)) {
                return PEAR::raiseError(_("No configuration information specified for SQL-File VFS."));
            }

            $required = array('phptype', 'hostspec', 'username', 'password', 'database', 'vfsroot');
            foreach ($required as $val) {
                if (!isset($this->_params[$val])) {
                    return PEAR::raiseError(sprintf(_("Required '%s' not specified in VFS configuration."), $val));
                }
            }

            if (!isset($this->_params['table'])) {
                $this->_params['table'] = 'horde_vfs';
            }

            /* Connect to the SQL server using the supplied parameters. */
            require_once 'DB.php';
            $this->_db = &DB::connect($this->_params,
                                      array('persistent' => !empty($this->_params['persistent'])));
            if (DB::isError($this->_db)) {
                $error = $this->_db;
                $this->_db = false;
                return $error;
            }

            /* Enable the "portability" option. */
            $this->_db->setOption('optimize', 'portability');
        }

        return true;
    }

    /**
     * Disconnect from the SQL server and clean up the connection.
     *
     * @access private
     */
    function _disconnect()
    {
        if ($this->_db) {
            $this->_db->disconnect();
            $this->_db = false;
        }
    }

    /**
     * Return a full filename on the native filesystem, from a VFS
     * path and name.
     *
     * @access private
     *
     * @param string $path  The VFS file path.
     * @param string $name  The VFS filename.
     *
     * @return string  The full native filename.
     */
    function _getNativePath($path, $name)
    {
        if (!empty($name)) {
            $name = '/' . $name;
        }
        if (isset($path)) {
            if (isset($this->_params['home']) &&
                preg_match('|^~/?(.*)$|', $path, $matches)) {
                $path = $this->_params['home']  . '/' . $matches[1];
            }

            return $this->_params['vfsroot'] . '/' . $path . $name;
        } else {
            return $this->_params['vfsroot'] . $name;
        }
    }

    /**
     * Return a full SQL filename on the native filesystem, from a VFS
     * path and name.
     *
     * @access private
     *
     * @param string $path  The VFS file path.
     * @param string $name  The VFS filename.
     *
     * @return string  The full native filename.
     */
    function _getSQLNativePath($path, $name)
    {
        if (empty($path)) {
            return $name;
        }

        return $path . '/' . $name;
    }

}





More information about the commits mailing list