* 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. * * * The table structure for the VFS can be found in * data/vfs.sql. * * Database specific notes: * * MSSQL: *
 * - 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
 * 
* * $Horde: framework/VFS/VFS/sql.php,v 1.92 2005/04/07 13:24:23 jan Exp $ * * Copyright 2002-2005 Chuck Hagenbuch * * 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 * @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)); } /** * Stores 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 boolean $autocreate Automatically create directories? * * @return mixed True on success or a PEAR_Error object on failure. */ function write($path, $name, $tmpFile, $autocreate = false) { $size = filesize($tmpFile); if ($size === 0) { $data = ''; } else { $dataFP = @fopen($tmpFile, 'rb'); if (!$dataFP) { return PEAR::raiseError(sprintf(_("The temporary file \"%s\" does not exist."), $tmpFile)); } $data = @fread($dataFP, $size); @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)); $this->log($sql, PEAR_LOG_DEBUG); $id = $this->_db->getOne($sql); if (is_a($id, 'PEAR_Error')) { $this->log($id, PEAR_LOG_ERR); 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')) { $this->log($id, PEAR_LOG_ERR); 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)); $this->log($sql, PEAR_LOG_DEBUG); $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; } $values = array(); $sql = 'UPDATE ' . $this->_params['table']; $sql .= ' SET vfs_path = ?, vfs_name = ?, vfs_modified = ? WHERE vfs_path = ? AND vfs_name = ?'; $this->log($sql, PEAR_LOG_DEBUG); array_push($values, $newpath, $newname, time(), $oldpath, $oldname); $sth = $this->_db->prepare($sql); $result = $this->_db->execute($sth, $values); 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')) { $this->log($rename, PEAR_LOG_ERR); 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')) { $this->log($id, PEAR_LOG_ERR); return $id; } $values = array(); $sql = 'INSERT INTO ' . $this->_params['table']; $sql .= ' (vfs_id, vfs_type, vfs_path, vfs_name, vfs_modified, vfs_owner) VALUES (?, ?, ?, ?, ?, ?)'; $this->log($sql, PEAR_LOG_DEBUG); array_push($values, $id, VFS_FOLDER, $path, $name, time(), $this->_params['user']); $sth = $this->_db->prepare($sql); return $this->_db->execute($sth, $values); } /** * 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')) { $this->log($folderList, PEAR_LOG_ERR); 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, '%'))); $this->log($sql, PEAR_LOG_DEBUG); $deleteContents = $this->_db->query($sql); if (is_a($deleteContents, 'PEAR_Error')) { $this->log($deleteContents, PEAR_LOG_ERR); 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)); $this->log($sql, PEAR_LOG_DEBUG); $delete = $this->_db->query($sql); if (is_a($delete, 'PEAR_Error')) { $this->log($delete, PEAR_LOG_ERR); 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)); $this->log($sql, PEAR_LOG_DEBUG); $delete = $this->_db->query($sql); if (is_a($delete, 'PEAR_Error')) { $this->log($delete, PEAR_LOG_ERR); 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 Oracle not differentiating between '' and NULL. 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); $this->log($sql, PEAR_LOG_DEBUG); $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 = array(), $dotfolders = true) { $conn = $this->_connect(); if (is_a($conn, 'PEAR_Error')) { return $conn; } $values = array(); $sql = 'SELECT vfs_name, vfs_path FROM ' . $this->_params['table']; $sql .= ' WHERE vfs_path = ? AND vfs_type = ?'; $this->log($sql, PEAR_LOG_DEBUG); array_push($values, $path, VFS_FOLDER); $folderList = $this->_db->getAll($sql, $values); if (is_a($folderList, 'PEAR_Error')) { return $folderList; } $folders = array(); foreach ($folderList as $line) { $folder['val'] = $this->_getNativePath($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) { $values = array(); $sql = 'SELECT vfs_name FROM ' . $this->_params['table']; $sql .= ' WHERE vfs_type = ? AND vfs_path = ?'; $this->log($sql, PEAR_LOG_DEBUG); array_push($values, VFS_FOLDER, $this->_getNativePath($oldpath, $oldname)); $folderList = $this->_db->getCol($sql, 0, $values); foreach ($folderList as $folder) { $this->_recursiveRename($this->_getNativePath($oldpath, $oldname), $folder, $this->_getNativePath($newpath, $newname), $folder); } $values = array(); $sql = 'UPDATE ' . $this->_params['table'] . ' SET vfs_path = ? WHERE vfs_path = ?'; $this->log($sql, PEAR_LOG_DEBUG); array_push($values, $this->_getNativePath($newpath, $newname), $this->_getNativePath($oldpath, $oldname)); $sth = $this->_db->prepare($sql); $result = $this->_db->execute($sth, $values); 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')) { $this->log($this->_db, PEAR_LOG_ERR); $error = $this->_db; $this->_db = false; return $error; } $this->_db->setOption('portability', DB_PORTABILITY_LOWERCASE | DB_PORTABILITY_ERRORS); } 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); $this->log($sql, PEAR_LOG_DEBUG); $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->quoteSmart($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. */ $this->log($query, PEAR_LOG_DEBUG); $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 WHERE %s FOR UPDATE', $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. */ $this->log($query, PEAR_LOG_DEBUG); $stmt = $this->_db->prepare($query); return $this->_db->execute($stmt, $values); } }