<?php
	/**************************************************************************\
	* eGroupWare API - VFS                                                     *
	* This file written by Jason Wies (Zone) <zone@phpgroupware.org>           *
	* This class handles file/dir access for eGroupWare                        *
	* Copyright (C) 2001-2003 Jason Wies, Jonathon Sim		             *
	* -------------------------------------------------------------------------*
	* This library is part of the eGroupWare API                               *
	* http://www.egroupware.org/api                                            * 
	* ------------------------------------------------------------------------ *
	* This library is free software; you can redistribute it and/or modify it  *
	* under the terms of the GNU Lesser General Public License as published by *
	* the Free Software Foundation; either version 2.1 of the License,         *
	* or any later version.                                                    *
	* This library is distributed in the hope that it will be useful, but      *
	* WITHOUT ANY WARRANTY; without even the implied warranty of               *
	* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.                     *
	* See the GNU Lesser General Public License for more details.              *
	* You should have received a copy of the GNU Lesser General Public License *
	* along with this library; if not, write to the Free Software Foundation,  *
	* Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA            *
	\**************************************************************************/


	/*Different aspects of debugging. DEBUG enables debug output for this class
	DEBUG_SQL enables some SQL debugging.  DEBUG_DAV enables (LOTS) of debugging inside
	the HTTP class
	*/
	define ('DEBUG', 0);
	define ('TRACE', 0);//This generates a whole lotta output
	define ('DEBUG_SQL', 0);
	define ('DEBUG_DAV', 0);

	class vfs
	{
		var $basedir;
		var $fakebase;
		var $relative;
		var $working_id;
		var $working_lid;
		var $attributes;
		var $override_acl;
		var $linked_dirs = array();
		var $meta_types;
		var $now;
		var $override_locks;
		var $vfs_table = 'egw_vfs';

		//These are DAV-native properties that have different names in VFS
		var $vfs_property_map = array(
			'creationdate' => 'created',
			'getlastmodified' => 'modified',
			'getcontentlength' => 'size',
			'getcontenttype' => 'mime_type',
			'description' => 'comment',
			'creator_id' => 'createdby_id',
			'contributor_id' => 'modifiedby_id',
			'publisher_id' => 'owner_id'
		);
		
		/**
		 * constructor, sets up variables
		 *
		 */

		function vfs ()
		{
			$this->basedir = $GLOBALS['egw_info']['server']['files_dir'];
			$this->fakebase = '/home';
			$this->working_id = $GLOBALS['egw_info']['user']['account_id'];
			$this->working_lid = $GLOBALS['egw']->accounts->id2name($this->working_id);
			$this->now = date ('Y-m-d');
			$this->override_acl = 0;
			/*
				 File/dir attributes, each corresponding to a database field.  Useful for use in loops
				 If an attribute was added to the table, add it here and possibly add it to
				 set_attributes ()

				 set_attributes now uses this array().   07-Dec-01 skeeter
			*/

			$this->attributes = array(
				'file_id',
				'owner_id',
				'createdby_id',
				'modifiedby_id',
				'created',
				'modified',
				'size',
				'mime_type',
				'deleteable',
				'comment',
				'app',
				'directory',
				'name',
				'link_directory',
				'link_name',
				'version'
			);

			/*
				 These are stored in the MIME-type field and should normally be ignored.
				 Adding a type here will ensure it is normally ignored, but you will have to
				 explicitly add it to acl_check (), and to any other SELECT's in this file
			*/

			$this->meta_types = array ('journal', 'journal-deleted');

			/* We store the linked directories in an array now, so we don't have to make the SQL call again */
/* I dont think this is needed for WebDAV -- RalfBecker 2006/03/27
			switch ($GLOBALS['egw']->db->Type)
			{
				case 'mssql': 
				case 'sybase':
					$where = array(
						"CONVERT(varchar,vfs_link_directory) != ''",
						"CONVERT(varchar,vfs_link_name) != ''",
					);
					break;
				default:
					$where = array(
						"(vfs_link_directory IS NOT NULL OR vfs_link_directory != '')",
						"(vfs_link_name IS NOT NULL OR vfs_link_name != '')",
					);
					break;
			}
			$where[] = $this->extra_sql(array('query_type' => VFS_SQL_SELECT));
			$GLOBALS['egw']->db->select($this->vfs_table,'vfs_directory,vfs_name,vfs_link_directory,vfs_link_name',$where,__LINE__,__FILE__);

			$this->linked_dirs = array ();
			while ($GLOBALS['egw']->db->next_record ())
			{
				$this->linked_dirs[] = array(
					'directory' => $GLOBALS['egw']->db->Record['vfs_directory'],
					'name'      => $GLOBALS['egw']->db->Record['vfs_name'],
					'link_directory' => $GLOBALS['egw']->db->Record['vfs_link_directory'],
					'link_name' => $GLOBALS['egw']->db->Record['vfs_link_name'],
				);
			}
*/
			$this->repository = $GLOBALS['egw_info']['server']['files_dir'];
			$this->dav_user=$GLOBALS['egw_info']['user']['userid'];
			$this->dav_pwd=$GLOBALS['egw_info']['user']['passwd'];
			$parsed_url = parse_url($this->repository);
			$this->dav_host=$parsed_url['host'];
			$this->dav_port=@isset($parsed_url['port']) ? $parsed_url['port'] : ($parsed_url['scheme'] == 'https' ? 443 : 80);

			$this->dav_client =& CreateObject('phpgwapi.http_dav_client');
			$this->dav_client->set_credentials($this->dav_user,$this->dav_pwd);
			$this->dav_client->set_attributes($this->attributes,$this->vfs_property_map);
			$result = $this->dav_client->connect($this->dav_host,$this->dav_port);
			if (DEBUG_DAV) 
			{
				echo '<b>DAV client debugging enabled!</b>';
				$this->dav_client->set_debug(DBGTRACE|DBGINDATA|DBGOUTDATA|DBGSOCK|DBGLOW);
			}
			if (!$result)
			{
				echo '<h2>Cannot connect to the file repository server!</h2>';
				die($this->dav_client->get_body());
			}
			//determine the supported DAV features
/*			$features = $this->dav_client->dav_features('http://'.$this->dav_host);
			if (!$features || ! in_array( '1', $features) )
			{
				die("Error :: The specified file repository: $this->dav_host doesn't appear to support WebDAV! ");
			
			}
*/	
			//Reload the overriden_locks
			$app = $GLOBALS['egw_info']['flags']['currentapp'];
			$session_data = base64_decode($GLOBALS['egw']->session->appsession ('vfs_dav',$app));
			$this->override_locks = array();
			if ($session_data)
			{
				$locks = explode('\n', $session_data);	
				foreach ($locks as $lock)
				{
					$lockdata = explode(';', $lock);
					$name = $lockdata[0]; 
					$token = $lockdata[1];
					$this->override_locks[$name] = $token;
				}
			}

				register_shutdown_function(array(&$this, 'vfs_umount'));
				$this->debug('Constructed with debug enabled');
			
		}

		//TODO:  Get rid of this
		//A quick, temporary debug output function
		function debug($info) {
			if (DEBUG)
			{
				echo '<b> vfs_sql_dav debug:<em> ';
				if (is_array($info))
				{
					print_r($info);
				}
				else
				{
					echo $info;
				}
				echo '</em></b><br>';
			}
		}

		/**
		 * Apaches mod_dav in particular requires that the path sent in a dav request NOT be a URI
		 *
		 */
		function dav_path($uri) {
			//$this->debug('DAV path');
			$parsed = parse_url($uri);
			return $parsed['path'];
		}

		/**
		 * glues a parsed url (ie parsed using PHP's parse_url) back
		 *
		 * 	together
		 * @param $url	The parsed url (its an array)
		 */
		function glue_url ($url){
			if (!is_array($url))
			{
				return false;
			}
			// scheme
			$uri = (!empty($url['scheme'])) ? $url['scheme'].'://' : '';
			// user & pass
			if (!empty($url['user']))
			{
				$uri .= $url['user'];
				if (!empty($url['pass']))
				{
					$uri .=':'.$url['pass'];
				}
				$uri .='@'; 
			}
			// host 
			$uri .= $url['host'];
			// port 
			$port = (!empty($url['port'])) ? ':'.$url['port'] : '';
			$uri .= $port; 
			// path 
			$uri .= $url['path'];
			// fragment or query
			if (isset($url['fragment']))
			{
				$uri .= '#'.$url['fragment'];
			} elseif (isset($url['query']))
			{
				$uri .= '?'.$url['query'];
			}
			return $uri;
		}

		function dav_host($uri) {
			//$this->debug('DAV path');
			$parsed = parse_url($uri);
			$parsed['path'] = '';
			$host = $this->glue_url($parsed);
			return $host;
		}

		function vfs_umount()
		{
			$this->dav_client->disconnect();
		}


		/**
		 * Set path relativity
		 *
		 * @param mask Relative bitmask (see RELATIVE_ defines)
		 */
		function set_relative ($data)
		{
			if (!is_array ($data))
			{
				$data = array ();
			}

			if (!$data['mask'])
			{
				unset ($this->relative);
			}
			else
			{
				$this->relative = $data['mask'];
			}
		}

		/**
		 * Return relativity bitmask
		 *
		 * Returns relativity bitmask, or the default of "completely relative" if unset
		 */
		function get_relative ()
		{
			if (isset ($this->relative) && $this->relative)
			{
				return $this->relative;
			}
			else
			{
				return RELATIVE_ALL;
			}
		}

		/**
		 * Removes leading .'s from 'string'
		 *
		 * You should not pass all filenames through sanitize () unless you plan on rejecting
		 * 		 * .files.  Instead, pass the name through securitycheck () first, and if it fails,
		 * 		 * pass it through sanitize
		 * @param string string to sanitize
		 * @return $string 'string' without it's leading .'s
		 */
		function sanitize ($data)
		{
			if (!is_array ($data))
			{
				$data = array ();
			}

			/* We use path_parts () just to parse the string, not translate paths */
			$p = $this->path_parts (array(
					'string' => $data['string'],
					'relatives' => array (RELATIVE_NONE)
				)
			);

			return (ereg_replace ('^\.+', '', $p->fake_name));
		}

		/**
		 * Security check function
		 *
		 * Checks for basic violations such as ..
		 * 		 * If securitycheck () fails, run your string through vfs->sanitize ()
		 * @param string string to check security of
		 * @return Boolean True/False.  True means secure, False means insecure
		 */
		function securitycheck ($data)
		{
			if (!is_array ($data))
			{
				$data = array ();
			}

			if (substr ($data['string'], 0, 1) == "\\" || strstr ($data['string'], "..") || strstr ($data['string'], "\\..") || strstr ($data['string'], ".\\."))
			{
				return False;
			}
			else
			{
				return True;
			}
		}

		/**
		 * Clean 'string' for use in database queries
		 *
		 * @param string String to clean
		 * @return Cleaned version of 'string'
		 */
		function db_clean ($data)
		{
			if (!is_array ($data))
			{
				$data = array ();
			}

			$string = ereg_replace ("'", "\'", $data['string']);

			return $string;
		}

		/**
		 * Return extra SQL code that should be appended to certain queries
		 *
		 * @param query_type The type of query to get extra SQL code for, in the form of a VFS_SQL define
		 * @return Extra SQL code
		 */
		function extra_sql ($data)
		{ //This is purely for SQL
			return '';
		}

		/**
		 * Add a journal entry after (or before) completing an operation,
		 *
		 * 		 * and increment the version number.  This function should be used internally only
		 * Note that state_one and state_two are ignored for some VFS_OPERATION's, for others
		 * 		 * 	they are required.  They are ignored for any "custom" operation
		 * 		 * 	The two operations that require state_two:
		 * 		 * 	operation		 * 	state_two
		 * 		 * 	VFS_OPERATION_COPIED	fake_full_path of copied to
		 * 		 * 	VFS_OPERATION_MOVED		 * fake_full_path of moved to

		 * 		 * 	If deleting, you must call add_journal () before you delete the entry from the database
		 * @param string File or directory to add entry for
		 * @param relatives Relativity array
		 * @param operation The operation that was performed.  Either a VFS_OPERATION define or
		 * 		 * 	a non-integer descriptive text string
		 * @param state_one The first "state" of the file or directory.  Can be a file name, size,
		 * 		 * 	location, whatever is appropriate for the specific operation
		 * @param state_two The second "state" of the file or directory
		 * @param incversion Boolean True/False.  Increment the version for the file?  Note that this is
		 * 		 * 	 handled automatically for the VFS_OPERATION defines.
		 * 		 * 	 i.e. VFS_OPERATION_EDITED would increment the version, VFS_OPERATION_COPIED
		 * 		 * 	 would not
		 * @return Boolean True/False
		 */
		function add_journal ($data) {
		//The journalling dont work :(  Ideally this will become "versioning"
			return True;
		}


		/**
		 * Flush journal entries for $string.  Used before adding $string
		 *
		 * flush_journal () is an internal function and should be called from add_journal () only
		 * @param string File/directory to flush journal entries of
		 * @param relatives Realtivity array
		 * @param deleteall Delete all types of journal entries, including the active Create entry.
		 * 		 * 	Normally you only want to delete the Create entry when replacing the file
		 * 		 * 	Note that this option does not effect $deleteonly
		 * @param deletedonly Only flush 'journal-deleted' entries (created when $string was deleted)
		 * @return Boolean True/False
		 */
		function flush_journal ($data)
		{
			return True;
		}


		/**
		 * Retrieve journal entries for $string
		 *
		 * @param string File/directory to retrieve journal entries of
		 * @param relatives Relativity array
		 * @param type 0/False = any, 1 = 'journal', 2 = 'journal-deleted'
		 * @return Array of arrays of journal entries
		 */
		function get_journal ($data)
		{
			return array();
		}

		/**
		 * take a real or fake pathname and return an array of its component parts
		 *
		 * @param string full real or fake path
		 * @param relatives Relativity array
		 * @param object True returns an object instead of an array
		 * @param nolinks Don't check for links (made with make_link ()).  Used internally to prevent recursion
		 * @return $rarray/$robject Array or object containing the fake and real component parts of the path
		 * Returned values are:
		 * 		 * mask
		 * 		 * outside
		 * 		 * fake_full_path
		 * 		 * fake_leading_dirs
		 * 		 * fake_extra_path		 * BROKEN
		 * 		 * fake_name
		 * 		 * real_full_path
		 * 		 * real_leading_dirs
		 * 		 * real_extra_path		 * BROKEN
		 * 		 * real_name
		 * 		 * fake_full_path_clean
		 * 		 * fake_leading_dirs_clean
		 * 		 * fake_extra_path_clean	BROKEN
		 * 		 * fake_name_clean
		 * 		 * real_full_path_clean
		 * 		 * real_leading_dirs_clean
		 * 		 * real_extra_path_clean	BROKEN
		 * 		 * real_name_clean
		 * 		 * real_uri
		 * 	"clean" values are run through vfs->db_clean () and
		 * 	are safe for use in SQL queries that use key='value'
		 * 	They should be used ONLY for SQL queries, so are used
		 * 	mostly internally
		 * 	mask is either RELATIVE_NONE or RELATIVE_NONE|VFS_REAL,
		 * 	and is used internally
		 * 	outside is boolean, True if 'relatives' contains VFS_REAL
		 */
		function path_parts ($data)
		{
			$default_values = array
				(
					'relatives'	=> array (RELATIVE_CURRENT),
					'object'	=> True,
					'nolinks'	=> False
				);

			$data = array_merge ($this->default_values ($data, $default_values), $data);

			$sep = SEP;

			$rarray['mask'] = RELATIVE_NONE;

			if (!($data['relatives'][0] & VFS_REAL))
			{
				$rarray['outside'] = False;
				$fake = True;
			}
			else
			{
				$rarray['outside'] = True;
				$rarray['mask'] |= VFS_REAL;
			}

			$string = $this->getabsolutepath (array(
					'string'	=> $data['string'],
					'mask'	=> array ($data['relatives'][0]),
					'fake'	=> $fake
				)
			);

			if ($fake)
			{
				$base_sep = '/';
				$base = '/';

				$opp_base = $this->basedir . $sep;

				$rarray['fake_full_path'] = $string;
			}
			else
			{
				$base_sep = $sep;
				if (ereg ("^$this->basedir" . $sep, $string))
				{
					$base = $this->basedir . $sep;
				}
				else
				{
					$base = $sep;
				}

				$opp_base = '/';
				$rarray['real_full_url'] = $string;
				$rarray['real_full_path'] = $this->dav_path($string);
			}

			/* This is needed because of substr's handling of negative lengths */
			$baselen = strlen ($base);
			$lastslashpos = strrpos ($string, $base_sep);
			$lastslashpos < $baselen ? $length = 0 : $length = $lastslashpos - $baselen;

			$extra_path = $rarray['fake_extra_path'] = $rarray['real_extra_path'] = substr ($string, strlen ($base), $length);
			$name = $rarray['fake_name'] = $rarray['real_name'] = substr ($string, strrpos ($string, $base_sep) + 1);

			if ($fake)
			{
				$rarray['real_extra_path'] ? $dispsep = $sep : $dispsep = '';
				$rarray['real_full_url'] = $opp_base . $rarray['real_extra_path'] . $dispsep . $rarray['real_name'];
				$rarray['real_full_path'] = $this->dav_path($rarray['real_full_url']);
				if ($extra_path)
				{
					$rarray['fake_leading_dirs'] = $base . $extra_path;
					$rarray['real_leading_dirs'] = $this->dav_path($opp_base . $extra_path);
				}
				elseif (strrpos ($rarray['fake_full_path'], $sep) == 0)
				{
					/* If there is only one $sep in the path, we don't want to strip it off */
					$rarray['fake_leading_dirs'] = $sep;
					$rarray['real_leading_dirs'] = $this->dav_path( substr ($opp_base, 0, strlen ($opp_base) - 1));
				}
				else
				{
					/* These strip the ending / */
					$rarray['fake_leading_dirs'] = substr ($base, 0, strlen ($base) - 1);
					$rarray['real_leading_dirs'] = $this->dav_path( substr ($opp_base, 0, strlen ($opp_base) - 1));
				}
			}
			else
			{
				$rarray['fake_full_path'] = $opp_base . $rarray['fake_extra_path'] . '/' . $rarray['fake_name'];
				if ($extra_path)
				{
					$rarray['fake_leading_dirs'] = $opp_base . $extra_path;
					$rarray['real_leading_dirs'] = $this->dav_path($base . $extra_path);
				}
				else
				{
					$rarray['fake_leading_dirs'] = substr ($opp_base, 0, strlen ($opp_base) - 1);
					$rarray['real_leading_dirs'] = $this->dav_path(substr ($base, 0, strlen ($base) - 1));
				}
			}

			/* We check for linked dirs made with make_link ().  This could be better, but it works */
			if (!$data['nolinks'])
			{
				reset ($this->linked_dirs);
				while (list ($num, $link_info) = each ($this->linked_dirs))
				{
					if (ereg ("^$link_info[directory]/$link_info[name](/|$)", $rarray['fake_full_path']))
					{
						$rarray['real_full_path'] = ereg_replace ("^$this->basedir", '', $rarray['real_full_path']);
						$rarray['real_full_path'] = ereg_replace ("^$link_info[directory]" . SEP . "$link_info[name]", $link_info['link_directory'] . SEP . $link_info['link_name'], $rarray['real_full_path']);

						$p = $this->path_parts (array(
								'string'	=> $rarray['real_full_path'],
								'relatives'	=> array (RELATIVE_NONE|VFS_REAL),
								'nolinks'	=> True
							)
						);

						$rarray['real_leading_dirs'] = $this->dav_path($p->real_leading_dirs);
						$rarray['real_extra_path'] = $p->real_extra_path;
						$rarray['real_name'] = $p->real_name;
					}
				}
			}
			
			/*
				Create the 'real_auth_url', which includes the user and
				password (for the view method to redirect you there)
			*/

			$parsed_url = parse_url($rarray['real_full_url']);
			$parsed_url['user'] = $this->dav_user;
//			$parsed_url['pass'] = $this->dav_pwd;
			$rarray['real_full_auth_url'] = $this->glue_url($parsed_url);
					
			$parsed_url = parse_url($rarray['real_full_url']);
			$parsed_url['scheme'] = 'https';
			$parsed_url['user'] = $this->dav_user;
			$rarray['real_full_secure_url'] = $this->glue_url($parsed_url);
			
			
			/*
				 We have to count it before because new keys will be added,
				 which would create an endless loop
			*/
			$count = count ($rarray);
			reset ($rarray);
			for ($i = 0; (list ($key, $value) = each ($rarray)) && $i != $count; $i++)
			{
				$rarray[$key . '_clean'] = $this->db_clean (array ('string' => $value));
			}

			if ($data['object'])
			{
				$robject =& new path_class;

				reset ($rarray);
				while (list ($key, $value) = each ($rarray))
				{
					$robject->$key = $value;
				}
			}

/*
			echo "<br>fake_full_path: $rarray[fake_full_path]
				<br>fake_leading_dirs: $rarray[fake_leading_dirs]
				<br>fake_extra_path: $rarray[fake_extra_path]
				<br>fake_name: $rarray[fake_name]
				<br>real_full_path: $rarray[real_full_path]
				<br>real_full_url: $rarray[real_full_url]
				<br>real_leading_dirs: $rarray[real_leading_dirs]
				<br>real_extra_path: $rarray[real_extra_path]
				<br>real_name: $rarray[real_name]";
*/

			if ($data['object'])
			{
				return ($robject);
			}
			else
			{
				return ($rarray);
			}
		}

		/**
		 * get the absolute path
		 *
		 * @param string defaults to False, directory/file to get path of, relative to relatives[0]
		 * @param mask Relativity bitmask (see RELATIVE_ defines).  RELATIVE_CURRENT means use $this->relative
		 * @param fake Returns the "fake" path, ie /home/user/dir/file (not always possible.  use path_parts () instead)
		 * @return $basedir Full fake or real path
		 */
		function getabsolutepath ($data)
		{
			$default_values = array
				(
					'string'	=> False,
					'mask'	=> array (RELATIVE_CURRENT),
					'fake'	=> True
				);

			$data = array_merge ($this->default_values ($data, $default_values), $data);

			$currentdir = $this->pwd (False);

			/* If they supply just VFS_REAL, we assume they want current relativity */
			if ($data['mask'][0] == VFS_REAL)
			{
				$data['mask'][0] |= RELATIVE_CURRENT;
			}

			if (!$this->securitycheck (array(
					'string'	=> $data['string']
				))
			)
			{
				return False;
			}

			if ($data['mask'][0] & RELATIVE_NONE)
			{
				return $data['string'];
			}

			if ($data['fake'])
			{
				$sep = '/';
			}
			else
			{
				$sep = SEP;
			}

			/* if RELATIVE_CURRENT, retrieve the current mask */
			if ($data['mask'][0] & RELATIVE_CURRENT)
			{
				$mask = $data['mask'][0];
				/* Respect any additional masks by re-adding them after retrieving the current mask*/
				$data['mask'][0] = $this->get_relative () + ($mask - RELATIVE_CURRENT);
			}

			if ($data['fake'])
			{
				$basedir = '/';
			}
			else
			{
				$basedir = $this->basedir . $sep;

				/* This allows all requests to use /'s */
				$data['string'] = preg_replace ("|/|", $sep, $data['string']);
			}

			if (($data['mask'][0] & RELATIVE_PATH) && $currentdir)
			{
				$basedir = $basedir . $currentdir . $sep;
			}
			elseif (($data['mask'][0] & RELATIVE_USER) || ($data['mask'][0] & RELATIVE_USER_APP))
			{
				$basedir = $basedir . $this->fakebase . $sep;
			}

			if ($data['mask'][0] & RELATIVE_CURR_USER)
			{
				$basedir = $basedir . $this->working_lid . $sep;
			}

			if (($data['mask'][0] & RELATIVE_USER) || ($data['mask'][0] & RELATIVE_USER_APP))
			{
				$basedir = $basedir . $GLOBALS['egw_info']['user']['account_lid'] . $sep;
			}

			if ($data['mask'][0] & RELATIVE_USER_APP)
			{
				$basedir = $basedir . "." . $GLOBALS['egw_info']['flags']['currentapp'] . $sep;
			}

			/* Don't add string if it's a /, just for aesthetics */
			if ($data['string'] && $data['string'] != $sep)
			{
				$basedir = $basedir . $data['string'];
			}

			/* Let's not return // */
			while (ereg ($sep . $sep, $basedir))
			{
				$basedir = ereg_replace ($sep . $sep, $sep, $basedir);
			}

			$basedir = ereg_replace ($sep . '$', '', $basedir);

			return $basedir;
		}

		/**
		 * Check ACL access to $file for $GLOBALS['egw_info']["user"]["account_id"];
		 *
		 * @param string File to check access of
		 * To check the access for a file or directory, pass 'string'/'relatives'/'must_exist'.
		 * 		 * To check the access to another user or group, pass 'owner_id'.
		 * 		 * If 'owner_id' is present, we bypass checks on 'string'/'relatives'/'must_exist'
		 * @param relatives Standard relativity array
		 * @param operation Operation to check access to.  In the form of a EGW_ACL defines bitmask.  Default is read
		 * @param owner_id Owner id to check access of (see discussion above)
		 * @param must_exist Boolean.  Set to True if 'string' must exist.  Otherwise, we check the parent directory as well
		 * @return Boolean.  True if access is ok, False otherwise
		 */
		function acl_check ($data)
		{
			return True;
			$default_values = array
				(
					'relatives'	=> array (RELATIVE_CURRENT),
					'operation'	=> EGW_ACL_READ,
					'must_exist'	=> False
				);

			$data = array_merge ($this->default_values ($data, $default_values), $data);

			/* Accommodate special situations */
			if ($this->override_acl || $data['relatives'][0] == RELATIVE_USER_APP)
			{
				return True;
			}

			if (!$data['owner_id'])
			{
				$p = $this->path_parts (array(
						'string'	=> $data['string'],
						'relatives'	=> array ($data['relatives'][0])
					)
				);

				/* Temporary, until we get symlink type files set up */
				if ($p->outside)
				{
					return True;
				}

				/* Read access is always allowed here, but nothing else is */
				if ($data['string'] == '/' || $data['string'] == $this->fakebase)
				{
					if ($data['operation'] == EGW_ACL_READ)
					{
						return True;
					}
					else
					{
						return False;
					}
				}

				/* If the file doesn't exist, we get ownership from the parent directory */
				if (!$this->file_exists (array(
						'string'	=> $p->fake_full_path,
						'relatives'	=> array ($p->mask)
					))
				)
				{
					if ($data['must_exist'])
					{
						return False;
					}

					$data['string'] = $p->fake_leading_dirs;
					$p2 = $this->path_parts (array(
							'string'	=> $data['string'],
							'relatives'	=> array ($p->mask)
						)
					);

					if (!$this->file_exists (array(
							'string'	=> $data['string'],
							'relatives'	=> array ($p->mask)
						))
					)
					{
						return False;
					}
				}
				else
				{
					$p2 = $p;
				}

				$file_info = $this->ls($data);
				$owner_id = $file_info['owner_id'];
			}
			else
			{
				$owner_id = $data['owner_id'];
			}

			/* This is correct.  The ACL currently doesn't handle undefined values correctly */
			if (!$owner_id)
			{
				$owner_id = 0;
			}

			$user_id = $GLOBALS['egw_info']['user']['account_id'];

			/* They always have access to their own files */
			if ($owner_id == $user_id)
			{
				return True;
			}

			/* Check if they're in the group */
			$memberships = $GLOBALS['egw']->accounts->membership ($user_id);

			if (is_array ($memberships))
			{
				reset ($memberships);
				while (list ($num, $group_array) = each ($memberships))
				{
					if ($owner_id == $group_array['account_id'])
					{
						$group_ok = 1;
						break;
					}
				}
			}

			$acl =& CreateObject ('phpgwapi.acl', $owner_id);
			$acl->account_id = $owner_id;
			$acl->read_repository ();

			$rights = $acl->get_rights ($user_id);

			/* Add privileges from the groups this user belongs to */
			if (is_array ($memberships))
			{
				reset ($memberships);
				while (list ($num, $group_array) = each ($memberships))
				{
					$rights |= $acl->get_rights ($group_array['account_id']);
				}
			}

			if ($rights & $data['operation'])
			{
				return True;
			}
			elseif (!$rights && $group_ok)
			{
				$conf =& CreateObject('phpgwapi.config', 'phpgwapi');
				$conf->read_repository();
				if ($conf->config_data['acl_default'] == 'grant')
				{
					return True;
				}
				else
				{
					return False;
				}
			}
			else
			{
				return False;
			}
		}

		/**
		 * Change directory
		 *
		 * To cd to the files root '/', use cd ('/', False, array (RELATIVE_NONE));
		 * @param string default '/'.  directory to cd into.  if "/" and $relative is True, uses "/home/<working_lid>";
		 * @param relative default True/relative means add target to current path, else pass $relative as mask to getabsolutepath()
		 * @param relatives Relativity array
		 */
		function cd ($data = '')
		{
			if (!is_array ($data))
			{
				$noargs = 1;
				$data = array ();
			}

			$default_values = array
				(
					'string'	=> '/',
					'relative'	=> True,
					'relatives'	=> array (RELATIVE_CURRENT)
				);

			$data = array_merge ($this->default_values ($data, $default_values), $data);

			if ($data['relatives'][0] & VFS_REAL)
			{
				$sep = SEP;
			}
			else
			{
				$sep = '/';
			}

			if ($data['relative'] == 'relative' || $data['relative'] == True)
			{
				/* if 'string' is "/" and 'relative' is set, we cd to the user/group home dir */
				if ($data['string'] == '/')
				{
					$data['relatives'][0] = RELATIVE_USER;
					$basedir = $this->getabsolutepath (array(
							'string'	=> False,
							'mask'	=> array ($data['relatives'][0]),
							'fake'	=> True
						)
					);
				}
				else
				{
					$currentdir = $GLOBALS['egw']->session->appsession('vfs','');
					$basedir = $this->getabsolutepath (array(
							'string'	=> $currentdir . $sep . $data['string'],
							'mask'	=> array ($data['relatives'][0]),
							'fake'	=> True
						)
					);
				}
			}
			else
			{
				$basedir = $this->getabsolutepath (array(
						'string'	=> $data['string'],
						'mask'	=> array ($data['relatives'][0])
					)
				);
			}

			$GLOBALS['egw']->session->appsession('vfs','',$basedir);

			return True;
		}

		/**
		 * current working dir
		 *
		 * @param full default True returns full fake path, else just the extra dirs (false strips the leading /)
		 * @return $currentdir currentdir
		 */
		function pwd ($data = '')
		{
			$default_values = array
				(
					'full'	=> True
				);

			$data = array_merge ($this->default_values ($data, $default_values), $data);

			$currentdir = $GLOBALS['egw']->session->appsession('vfs','');

			if (!$data['full'])
			{
				$currentdir = ereg_replace ("^/", '', $currentdir);
			}

			if ($currentdir == '' && $data['full'])
			{
				$currentdir = '/';
			}

			$currentdir = trim ($currentdir);

			return $currentdir;
		}

		/**
		 * return file contents
		 *
		 * @param string filename
		 * @param relatives Relativity array
		 * @return $contents Contents of $file, or False if file cannot be read
		 */
		function read ($data)
		{

			/*If the user really wants to 'view' the file in the browser, it
			is much smarter simply to redirect them to the files web-accessable
			url */
/*			$app = $GLOBALS['egw_info']['flags']['currentapp'];
			if ( ! $data['noview'] && ($app == 'phpwebhosting' || $app = 'filemanager' ))
			{
				$this->view($data);
			}	
*/			
			if (!is_array ($data))
			{
				$data = array ();
			}

			$default_values = array
				(
					'relatives'	=> array (RELATIVE_CURRENT)
				);

			$data = array_merge ($this->default_values ($data, $default_values), $data);

			$p = $this->path_parts (array(
					'string'	=> $data['string'],
					'relatives'	=> array ($data['relatives'][0])
				)
			);

			if (!$this->acl_check (array(
					'string'	=> $p->fake_full_path,
					'relatives'	=> array ($p->mask),
					'operation'	=> EGW_ACL_READ
				))
			)
			{
				return False;
			}
			if ($p->outside)
			{
							
					if (! $fp = fopen ($p->real_full_path, 'r')) 
					{
						return False;
					}
					$size=filesize($p->real_full_path);
					$buffer=fread($fp, $size);
					fclose ($fp);
				return $buffer;
			}
			else
			{
				$status=$this->dav_client->get($p->real_full_path);
	$this->debug($this->dav_client->get_headers());
	
				if($status != 200) return False;
				$contents=$this->dav_client->get_body();
	$this->debug('Read:returning contents.  Status:'.$status);
				return $contents;
			}
		}
		
		/*
		@function view
		@abstract Redirect the users browser to the file
		@param string filename
		@param relatives Relativity array
		@result None (doesnt return)
		@discussion In the case of WebDAV, the file is web-accessible.  So instead
		of reading it into memory and then dumping it back out again when someone
		views a file, it makes much more sense to simply redirect, which is what 
		this method does (its only called when reading from the file in the file manager,
		when the variable "noview" isnt set to "true"
		*/
		function view($data)
		{	
		
			$default_values = array
				(
					'relatives'	=> array (RELATIVE_CURRENT)
				);
			$data = array_merge ($this->default_values ($data, $default_values), $data);
			$p = $this->path_parts (array(
					'string'	=> $data['string'],
					'relatives'	=> array ($data['relatives'][0])
				)
			);
			
			//Determine whether the repository supports SSL
			$parsed_url = parse_url($this->repository);
			if ($parsed_url['scheme']=='https')
			{
				header( 'Location: '.$p->real_full_secure_url, true ); 
			}
			else
			{
				header( 'Location: '.$p->real_full_auth_url, true ); 			
			}
			exit(); 

		}
		
		/*
		@function lock
		@abstract DAV (class 2) locking - sets an exclusive write lock
		@param string filename
		@param relatives Relativity array
		@result True if successfull 
		*/		
		function lock ($data)
		{
			$default_values = array
				(
					'relatives'	=> array (RELATIVE_CURRENT),
					'timeout'	=> 'infinity',
					
				);

			$data = array_merge ($this->default_values ($data, $default_values), $data);

			$p = $this->path_parts (array(
					'string'	=> $data['string'],
					'relatives'	=> array ($data['relatives'][0])
				)
			);		
			return $this->dav_client->lock($p->real_full_url, $this->dav_user, 0, $data['timeout']);

		}
		function lock_token ($data)
		{
			$default_values = array
				(
					'relatives'	=> array (RELATIVE_CURRENT),
					'token' => ''
				);

			$data = array_merge ($this->default_values ($data, $default_values), $data);
			
			$ls_array = $GLOBALS['egw']->vfs->ls (array (
			'string'	=> $data['string'],
			'relatives'	=> $data['relatives']
			)
			);	
			 $lock = @end($ls_array[0]['locks']);
			 $token = @end($lock['lock_tokens']);
			 return $token['full_name'];
		}
		
		
		/*
		@function add_lock_override
		@abstract override a lock
		@param string filename
		@param relatives Relativity array
		@param token (optional) a token for the lock we want to override
		@result None
		@discussion locks are no good unless you can write to a file you yourself locked:
		to do this call add_lock_override with the lock token (or without it - it will
		find it itself, so long as there is only one).  lock_override info is stored in
		the groupware session, so it will persist between page loads, but will be lost 
		when the browser is closed
		*/	
		function add_lock_override($data)
		{
			$default_values = array
			(
				'relatives'	=> array (RELATIVE_CURRENT),
				'token' => ''
				
			);

			$data = array_merge ($this->default_values ($data, $default_values), $data);
			
			if (!strlen($data['token']))
			{
				$ls_array = $GLOBALS['egw']->vfs->ls (array (
				'string'	=> $data['string'],
				'relatives'	=> $data['relatives']
				)
				);	
				 $lock = @end($ls_array[0]['locks']);
				 $token_array = @end($lock['lock_tokens']);
				 $token =  $token_array['full_name'];
			}
			else
			{
				$token = $data['token'];
			}
			 
			$p = $this->path_parts (array(
					'string'	=> $data['string'],
					'relatives'	=> array ($data['relatives'][0])
				)
			);		
			$this->override_locks[$p->real_full_path] = $token;
			$this->save_session();
		}
		
		/*
		@function remove_lock_override
		@abstract stops overriding a lock
		@param string filename
		@param relatives Relativity array
		@result None
		*/	
		function remove_lock_override($data)
		{
			$default_values = array
			(
				'relatives'	=> array (RELATIVE_CURRENT)
				
			);

			$data = array_merge ($this->default_values ($data, $default_values), $data);
			
			if (!strlen($data['token']))
			{
				$ls_array = $GLOBALS['egw']->vfs->ls (array (
				'string'	=> $data['string'],
				'relatives'	=> $data['relatives']
				)
				);	
				 $lock = @end($ls_array[0]['locks']);
				 $token_array = @end($lock['lock_tokens']);
				 $token =  $token_array['full_name'];
			}
			else
			{
				$token = $data['token'];
			}
			 
			$p = $this->path_parts (array(
					'string'	=> $data['string'],
					'relatives'	=> array ($data['relatives'][0])
				)
			);		
			unset($this->override_locks[$p->real_full_path]);
			$this->save_session();
		}	
		
		/*
		@function unlock
		@abstract DAV (class 2) unlocking - unsets the specified lock
		@param string filename
		@param relatives Relativity array
		@param tocken	The token for the lock we wish to remove.
		@result True if successfull
		*/		
		function unlock ($data, $token)
		{
			$default_values = array
				(
					'relatives'	=> array (RELATIVE_CURRENT),
					'content'	=> ''
				);

			$data = array_merge ($this->default_values ($data, $default_values), $data);

			$p = $this->path_parts (array(
					'string'	=> $data['string'],
					'relatives'	=> array ($data['relatives'][0])
				)
			);
			$this->remove_lock_override (array(
					'string'	=> $data['string'],
					'relatives'	=> array ($data['relatives'][0])
				)
			);
			return $this->dav_client->unlock($p->real_full_url, $token);


		}
		
		/*
		@function options
		@abstract Allows querying for optional features - esp optional DAV features 
		like locking
		@param option	The option you want to test for.  Options include 'LOCKING'
			'VIEW', 'VERSION-CONTROL (eventually) etc
		@result true if the specified option is supported
		@discussion This should really check the server.  Unfortunately the overhead of doing this
		in every VFS instance is unacceptable (it essentially doubles the time for any request). Ideally
		we would store these features in the session perhaps?
		*/		
		function options($option)
		{
			switch ($option)
			{
			case 'LOCKING':
				return true;
			case 'VIEW':
				return true;
			default:
				return false;
			}
		}
		
		/**
		 * write to a file
		 *
		 * @param string file name
		 * @param relatives Relativity array
		 * @param content content
		 * @return Boolean True/False
		 */
		function write ($data)
		{
			$default_values = array
				(
					'relatives'	=> array (RELATIVE_CURRENT),
					'content'	=> ''
				);

			$data = array_merge ($this->default_values ($data, $default_values), $data);

			$p = $this->path_parts (array(
					'string'	=> $data['string'],
					'relatives'	=> array ($data['relatives'][0])
				)
			);

			if ($this->file_exists (array (
					'string'	=> $data['string'],
					'relatives'	=> array ($data['relatives'][0])
				))
			)
			{
				$acl_operation = EGW_ACL_EDIT;
				$journal_operation = VFS_OPERATION_EDITED;
			}
			else
			{
				$acl_operation = EGW_ACL_ADD;
			}

			if (!$this->acl_check (array(
					'string'	=> $p->fake_full_path,
					'relatives'	=> array ($p->mask),
					'operation'	=> $acl_operation
				))
			)
			{
				return False;
			}

			//umask(000);

			/*
				 If 'string' doesn't exist, touch () creates both the file and the database entry
				 If 'string' does exist, touch () sets the modification time and modified by
			*/
			/*$this->touch (array(
					'string'	=> $p->fake_full_path,
					'relatives'	=> array ($p->mask)
				)
			);*/
			
			$size=strlen($data['content']);
			if ($p->outside)
			{			    
					if (! $fp = fopen ($p->real_full_path, 'w')) 
					{
						return False;
					}
					$result = fwrite($fp, $data['content']);
					fclose ($fp);
					return $result;
			}
			else
			{
				$token =  $this->override_locks[$p->real_full_path];
				$status=$this->dav_client->put($p->real_full_path,$data['content'],$token);
$this->debug('Put complete,  status: '.$status);
				if($status!=201 && $status!=204) 
				{
					return False;
				}
				else
				{
					return True;
				}
			}
		}

		/**
		 * Create blank file $file or set the modification time and modified by of $file to current time and user
		 *
		 * @param string File to touch or set modifies
		 * @param relatives Relativity array
		 * @return Boolean True/False
		 */
		function touch ($data)
		{
			$default_values = array(
						'relatives'	=> array (RELATIVE_CURRENT)
						);
			$data = array_merge ($this->default_values ($data, $default_values), $data);

			$account_id = $GLOBALS['egw_info']['user']['account_id'];
			$currentapp = $GLOBALS['egw_info']['flags']['currentapp'];

			$p = $this->path_parts (array(
									'string'	=> $data['string'],
									'relatives'	=> array ($data['relatives'][0])
									)
						);
			umask (000);

			/*
				 PHP's touch function will automatically decide whether to
				 create the file or set the modification time
			*/
			if($p->outside)
			{
				return @touch($p->real_full_path);
			}
			elseif ($this->file_exists (array(
					'string'	=> $p->fake_full_path,
					'relatives'	=> array ($p->mask)
				))
			)
			{
				$result =  $this->set_attributes (array(
						'string'	=> $p->fake_full_path,
						'relatives'	=> array ($p->mask),
						'attributes'	=> array(
									'modifiedby_id' => $account_id,
									'modified' => $this->now
						)));
			}
			else
			{
				if (!$this->acl_check (array(
						'string'	=> $p->fake_full_path,
						'relatives'	=> array ($p->mask),
						'operation'	=> EGW_ACL_ADD
					))
				) return False;
				$result = $this->write (array(
									'string'	=> $data['string'],
									'relatives'	=> array ($data['relatives'][0]),
									'content' => ''
									));
				$this->set_attributes(array(
					'string'	=> $p->fake_full_path,
					'relatives'	=> array ($p->mask),
					'attributes'	=> array (
								'createdby_id' => $account_id,
								'created' => $this->now,
								'app' => $currentapp
							)));
			}

			return ($result);
		}

		/**
		 * copy file
		 *
		 * @param from from file/directory
		 * @param to to file/directory
		 * @param relatives Relativity array
		 * @return boolean True/False
		 */
		function cp ($data)
		{
			$default_values = array
				(
					'relatives'	=> array (RELATIVE_CURRENT, RELATIVE_CURRENT)
				);

			$data = array_merge ($this->default_values ($data, $default_values), $data);

			$account_id = $GLOBALS['egw_info']['user']['account_id'];

			$f = $this->path_parts (array(
					'string'	=> $data['from'],
					'relatives'	=> array ($data['relatives'][0])
				)
			);

			$t = $this->path_parts (array(
					'string'	=> $data['to'],
					'relatives'	=> array ($data['relatives'][1])
				)
			);

			if (!$this->acl_check (array(
					'string'	=> $f->fake_full_path,
					'relatives'	=> array ($f->mask),
					'operation'	=> EGW_ACL_READ
				))
			)
			{
				return False;
			}

			if ($this->file_exists (array(
					'string'	=> $t->fake_full_path,
					'relatives'	=> array ($t->mask)
				))
			)
			{
				$remote_operation=EGW_ACL_EDIT;
			}
			else
			{
				$remote_operation=EGW_ACL_ADD;

			}
			if (!$this->acl_check (array(
								 'string'	=> $t->fake_full_path,
								 'relatives'	=> array ($t->mask),
								 'operation'	=> $remote_operation
								 ))
					)
			{
				return False;
			}

			umask(000);

			if ($this->file_type (array(
					'string'	=> $f->fake_full_path,
					'relatives'	=> array ($f->mask)
				)) != 'Directory'
			)
			{
				
				if ($f->outside && $t->outside)
				{
					return copy($f->real_full_path, $t->real_full_url);
				}
				elseif ($f->outside || $t->outside)
				{
							$content = $this->read(array(
						'string'	=> $f->fake_full_path,
						'noview' => true,
						'relatives'	=> array ($f->mask)
						)
					);
					$result = $this->write(array(
						'string'	=> $t->fake_full_path,
						'relatives'	=> array ($t->mask),
						'content' => $content
						)
					);
					}
				else 
				{
						$status=$this->dav_client->copy($f->real_full_path, $t->real_full_url,True, 'Infinity', $this->override_locks[$p->real_full_path]);
						$result = $status == 204 || $status==201;
						if (!$result)
						{
							return False;
						}
			 	 }

				$this->set_attributes(array(
					'string'	=> $t->fake_full_path,
					'relatives'	=> array ($t->mask),
					'attributes' => array (
								'owner_id' => $this->working_id,
								'createdby_id' => $account_id,
							)
						)
					);
				return $result;

			}
			else if (!($f->outside || $t->outside)) 
			{
				//if the files are both on server, its just a depth=infinity copy
				$status=$this->dav_client->copy($f->real_full_path, $t->real_full_url,True, 'infinity', $this->override_locks[$p->real_full_path]);
					if($status != 204 && $status!=201) 
					{
						return False;
					}
					else 
					{
						return True;
					}
			}
			else	/* It's a directory, and one of the files is local */
			{
				/* First, make the initial directory */
				$this->mkdir (array(
						'string'	=> $data['to'],
						'relatives'	=> array ($data['relatives'][1])
					)
				);

				/* Next, we create all the directories below the initial directory */
				$ls = $this->ls (array(
						'string'	=> $f->fake_full_path,
						'relatives'	=> array ($f->mask),
						'checksubdirs'	=> True,
						'mime_type'	=> 'Directory'
					)
				);

				while (list ($num, $entry) = each ($ls))
				{
					$newdir = ereg_replace ("^$f->fake_full_path", "$t->fake_full_path", $entry['directory']);
					$this->mkdir (array(
							'string'	=> $newdir.'/'.$entry['name'],
							'relatives'	=> array ($t->mask)
						)
					);
				}

				/* Lastly, we copy the files over */
				$ls = $this->ls (array(
						'string'	=> $f->fake_full_path,
						'relatives'	=> array ($f->mask)
					)
				);

				while (list ($num, $entry) = each ($ls))
				{
					if ($entry['mime_type'] == 'Directory')
					{
						continue;
					}

					$newdir = ereg_replace ("^$f->fake_full_path", "$t->fake_full_path", $entry['directory']);
					$this->cp (array(
							'from'	=> "$entry[directory]/$entry[name]",
							'to'	=> "$newdir/$entry[name]",
							'relatives'	=> array ($f->mask, $t->mask)
						)
					);
				}
			}

			return True;
		}

		function copy ($data)
		{
			return $this->cp ($data);
		}

		/**
		 * move file/directory
		 *
		 * @param from from file/directory
		 * @param to to file/directory
		 * @param relatives Relativity array
		 * @return boolean True/False
		 */
		function mv ($data)
		{
			$default_values = array
				(
					'relatives'	=> array (RELATIVE_CURRENT, RELATIVE_CURRENT)
				);

			$data = array_merge ($this->default_values ($data, $default_values), $data);

			$account_id = $GLOBALS['egw_info']['user']['account_id'];

			$f = $this->path_parts (array(
					'string'	=> $data['from'],
					'relatives'	=> array ($data['relatives'][0])
				)
			);

			$t = $this->path_parts (array(
					'string'	=> $data['to'],
					'relatives'	=> array ($data['relatives'][1])
				)
			);

			if (!$this->acl_check (array(
					'string'	=> $f->fake_full_path,
					'relatives'	=> array ($f->mask),
					'operation'	=> EGW_ACL_READ
				))
				|| !$this->acl_check (array(
					'string'	=> $f->fake_full_path,
					'relatives'	=> array ($f->mask),
					'operation'	=> EGW_ACL_DELETE
				))
			)
			{
				return False;
			}

			if (!$this->acl_check (array(
					'string'	=> $t->fake_full_path,
					'relatives'	=> array ($t->mask),
					'operation'	=> EGW_ACL_ADD
				))
			)
			{
				return False;
			}

			if ($this->file_exists (array(
					'string'	=> $t->fake_full_path,
					'relatives'	=> array ($t->mask)
				))
			)
			{
				if (!$this->acl_check (array(
						'string'	=> $t->fake_full_path,
						'relatives'	=> array ($t->mask),
						'operation'	=> EGW_ACL_EDIT
					))
				)
				{
					return False;
				}
			}
			umask (000);

			/* We can't move directories into themselves */
			if (($this->file_type (array(
					'string'	=> $f->fake_full_path,
					'relatives'	=> array ($f->mask)
				) == 'Directory'))
				&& ereg ("^$f->fake_full_path", $t->fake_full_path)
			)
			{
				if (($t->fake_full_path == $f->fake_full_path) || substr ($t->fake_full_path, strlen ($f->fake_full_path), 1) == '/')
				{
					return False;
				}
			}

			if ($this->file_exists (array(
					'string'	=> $f->fake_full_path,
					'relatives'	=> array ($f->mask)
				))
			)
			{
				/* We get the listing now, because it will change after we update the database */
				$ls = $this->ls (array(
						'string'	=> $f->fake_full_path,
						'relatives'	=> array ($f->mask)
					)
				);

				if ($this->file_exists (array(
						'string'	=> $t->fake_full_path,
						'relatives'	=> array ($t->mask)
					))
				)
				{
					$this->rm (array(
							'string'	=> $t->fake_full_path,
							'relatives'	=> array ($t->mask)
						)
					);
				}

				$this->correct_attributes (array(
						'string'	=> $t->fake_full_path,
						'relatives'	=> array ($t->mask)
					)
				);
				
				if ($f->outside && $t->outside)
				{
					echo 'local';
					$result = rename ($f->real_full_path, $t->real_full_path);
				}
				else if ($f->outside || $t->outside) //if either file is local, read then write
				{
					$content = $this->read(array(
						'string'	=> $f->fake_full_path,
						'noview' => true,
						'relatives'	=> array ($f->mask)
						)
					);
					$result = $this->write(array(
						'string'	=> $t->fake_full_path,
						'relatives'	=> array ($t->mask),
						'content' => $content
						)
					);
					if ($result)
					{
						$result = $this->rm(array(
							'string'	=> $f->fake_full_path,
							'relatives'	=> array ($f->mask),
							'content' => $content
							)
						);
					}
				}
				else {  //we can do a server-side copy if both files are on the server
					$status=$this->dav_client->move($f->real_full_path, $t->real_full_url,True, 'infinity', $this->override_locks[$p->real_full_path]);
							$result = ($status==201 || $status==204);
				}
				
				if ($result) $this->set_attributes(array(
						'string'	=> $t->fake_full_path,
						'relatives'	=> array ($t->mask),
						'attributes'	=> array (
									'modifiedby_id' => $account_id,
									'modified' => $this->now
								)));
				return $result;
			}
			else
			{
				return False;
			}

			$this->add_journal (array(
					'string'	=> $t->fake_full_path,
					'relatives'	=> array ($t->mask),
					'operation'	=> VFS_OPERATION_MOVED,
					'state_one'	=> $f->fake_full_path,
					'state_two'	=> $t->fake_full_path
				)
			);

			return True;
		}

		/**
		 * shortcut to mv
		 *
		 */
		function move ($data)
		{
			return $this->mv ($data);
		}

		/**
		 * delete file/directory
		 *
		 * @param string file/directory to delete
		 * @param relatives Relativity array
		 * @return boolean True/False
		 */
		function rm ($data)
		{
			$default_values = array
				(
					'relatives'	=> array (RELATIVE_CURRENT)
				);

			$data = array_merge ($this->default_values ($data, $default_values), $data);
			$p = $this->path_parts (array(
					'string'	=> $data['string'],
					'relatives'	=> array ($data['relatives'][0])
				)
			);
			$this->debug("rm: $p->real_full_path");
			if (!$this->acl_check (array(
					'string'	=> $p->fake_full_path,
					'relatives'	=> array ($p->mask),
					'operation'	=> EGW_ACL_DELETE
				))
			)
			{
				return False;
			} 

/*this would become apparent soon enough anyway?
			if (!$this->file_exists (array(
					'string'	=> $data['string'],
					'relatives'	=> array ($data['relatives'][0])
				))
			) return False;
*/
			if ($this->file_type (array(
					'string'	=> $data['string'],
					'relatives'	=> array ($data['relatives'][0])
				)) != 'Directory'
			)
			{
				if ($p->outside)
				{
					return unlink($p->real_full_path);
				}
				else
				{
					$rr=$this->dav_client->delete($p->real_full_path, 0, $this->override_locks[$p->real_full_path]);
					return $rr == 204;	
				}
			}
			else
			{
				$ls = $this->ls (array(
						'string'	=> $p->fake_full_path,
						'relatives'	=> array ($p->mask)
					)
				);

				while (list ($num, $entry) = each ($ls))
				{
					$this->rm (array(
							'string'	=> "$entry[directory]/$entry[name]",
							'relatives'	=> array ($p->mask)
						)
					);
				}

				/* If the directory is linked, we delete the placeholder directory */
				$ls_array = $this->ls (array(
						'string'	=> $p->fake_full_path,
						'relatives'	=> array ($p->mask),
						'checksubdirs'	=> False,
						'mime_type'	=> False,
						'nofiles'	=> True
					)
				);
				$link_info = $ls_array[0];

				if ($link_info['link_directory'] && $link_info['link_name'])
				{
					$path = $this->path_parts (array(
							'string'	=> $link_info['directory'] . '/' . $link_info['name'],
							'relatives'	=> array ($p->mask),
							'nolinks'	=> True
						)
					);
					$this->dav_client->delete($path->real_full_path,0, $this->override_locks[$p->real_full_path]);
				}

				/* Last, we delete the directory itself */
				$this->add_journal (array(
						'string'	=> $p->fake_full_path,
						'relatives'	=> array ($p->mask),
						'operaton'	=> VFS_OPERATION_DELETED
					)
				);

/* I dont think this is needed for WebDAV -- RalfBecker 2006/03/27
				$GLOBALS['egw']->db->delete($this->vfs_table,array(
						'vfs_directory'	=> $p->fake_leading_dirs,
						'vfs_name'		=> $p->fake_name,
						$this->extra_sql(array ('query_type' => VFS_SQL_DELETE))
					), __LINE__, __FILE__);
*/
				//rmdir ($p->real_full_path);
				$this->dav_client->delete($p->real_full_path.'/','Infinity', $this->override_locks[$p->real_full_path]);

				return True;
			}
		}

		/**
		 * shortcut to rm
		 *
		 */
		function delete ($data)
		{
			return $this->rm ($data);
		}

		/**
		 * make a new directory
		 *
		 * @param string Directory name
		 * @param relatives Relativity array
		 * @return boolean True on success
		 */
		function mkdir ($data)
		{
			if (!is_array ($data))
			{
				$data = array ();
			}

			$default_values = array
				(
					'relatives'	=> array (RELATIVE_CURRENT)
				);

			$data = array_merge ($this->default_values ($data, $default_values), $data);

			$account_id = $GLOBALS['egw_info']['user']['account_id'];
			$currentapp = $GLOBALS['egw_info']['flags']['currentapp'];

			$p = $this->path_parts (array(
					'string'	=> $data['string'],
					'relatives'	=> array ($data['relatives'][0])
				)
			);

			if (!$this->acl_check (array(
					'string'	=> $p->fake_full_path,
					'relatives'	=> array ($p->mask),
					'operation'	=> EGW_ACL_ADD)
				)
			)
			{
				return False;
			}

			/* We don't allow /'s in dir names, of course */
			if (ereg ('/', $p->fake_name))
			{
				return False;
			}
			if ($p->outside)
			{
				if (!mkdir($p->real_full_path, 0777))
				{
					return False;
				}
			}
			else if($this->dav_client->mkcol($p->real_full_path, $this->override_locks[$p->real_full_path]) != 201) 
			{
				return False;
			}
			

			if (!$this->file_exists (array(
					'string'	=> $p->fake_full_path.'/'
				))
			)
			{
				/*Now we need to set access control for this dir.  Simply create an .htaccess
				file limiting access to this user, if we are creating this dir in the user's home dir*/
				$homedir = $this->fakebase.'/'.$this->dav_user; 
				if ( substr($p->fake_leading_dirs, 0, strlen($homedir)) == $homedir)
				{ 
					$conf =& CreateObject('phpgwapi.config', 'phpgwapi');
					$conf->read_repository();
					if (!$conf->config_data['acl_default'] == 'grant')
					{
						$htaccess = 'require user '.$GLOBALS['egw_info']['user']['account_lid'];
						if ( ! $this->write(array(
								'string' =>  $p->fake_full_path.'/.htaccess',
								'content' => $htaccess,
								'relatives' => array(RELATIVE_NONE)
							)))
						{
							echo '<p><b>Unable to write .htaccess file</b></p></b>';
						};	
					}
				}
				return True;
			}
			else
			{
				return False;
			}
		}

		/**
		 * Make a link from virtual directory 'vdir' to real directory 'rdir'
		 *
		 * Making a link from 'vdir' to 'rdir' will cause path_parts () to substitute 'rdir' for the real
		 * 		 * path variables when presented with 'vdir'
		 * @param vdir Virtual dir to make link from
		 * @param rdir Real dir to make link to
		 * @param relatives Relativity array
		 * @return Boolean True/False
		 */
		function make_link ($data)
		{
			return False; //This code certainly wont work anymore.  Does anything use it?
		/*
			$default_values = array
				(
					'relatives'	=> array (RELATIVE_CURRENT, RELATIVE_CURRENT)
				);

			$data = array_merge ($this->default_values ($data, $default_values), $data);

			$account_id = $GLOBALS['egw_info']['user']['account_id'];
			$currentapp = $GLOBALS['egw_info']['flags']['currentapp'];

			$vp = $this->path_parts (array(
					'string'	=> $data['vdir'],
					'relatives'	=> array ($data['relatives'][0])
				)
			);

			$rp = $this->path_parts (array(
					'string'	=> $data['rdir'],
					'relatives'	=> array ($data['relatives'][1])
				)
			);

			if (!$this->acl_check (array(
					'string'	=> $vp->fake_full_path,
					'relatives'	=> array ($vp->mask),
					'operation'	=> EGW_ACL_ADD
				))
			) return False;

			if ((!$this->file_exists (array(
					'string'	=> $rp->real_full_path,
					'relatives'	=> array ($rp->mask)
				)))
				&& !mkdir ($rp->real_full_path, 0770)) return False;

			if (!$this->mkdir (array(
					'string'	=> $vp->fake_full_path,
					'relatives'	=> array ($vp->mask)
				))
			)return False;

			$size = $this->get_size (array(
					'string'	=> $rp->real_full_path,
					'relatives'	=> array ($rp->mask)
				)
			);

			$this->set_attributes(array(
					'string'	=> $vp->fake_full_path,
					'relatives'	=> array ($vp->mask),
					'attributes'	=> array (
								'link_directory' => $rp->real_leading_dirs,
								'link_name' => $rp->real_name,
								'size' => $size
							)
				)
			);

			$this->correct_attributes (array(
					'string'	=> $vp->fake_full_path,
					'relatives'	=> array ($vp->mask)
				)
			);

			return True;
	*/
		}

		/**
		 * Update database entry for 'string' with the attributes in 'attributes'
		 *
		 * @param string file/directory to update
		 * @param relatives Relativity array
		 * @param attributes keyed array of attributes.  key is attribute name, value is attribute value
		 * @return Boolean True/False
		 * Valid attributes are:
		 * 		 * owner_id
		 * 		 * createdby_id
		 * 		 * modifiedby_id
		 * 		 * created
		 * 		 * modified
		 * 		 * size
		 * 		 * mime_type
		 * 		 * deleteable
		 * 		 * comment
		 * 		 * app
		 * 		 * link_directory
		 * 		 * link_name
		 * 		 * version
		 * 		 * name
		 * 		 * directory
		 */
		function set_attributes ($data,$operation=EGW_ACL_EDIT)
		{
			/*To get much benefit out of DAV properties we should use 
			some sensible XML namespace.  We will use the Dublin Core 
			metadata specification (http://dublincore.org/) here where 
			we can*/
			$p = $this->path_parts (array(
				'string'	=> $data['string'],
				'relatives'	=> array ($data['relatives'][0])
				));
			$dav_properties = array();
			$lid=''; $fname = ''; $lname='';
			if ($data['attributes']['comment'])
			{
				$dav_properties['dc:description'] = $data['attributes']['comment'];
			}
			if ($id=$data['attributes']['owner_id'])
			{
				$GLOBALS['egw']->accounts->get_account_name($id,&$lid,&$fname,&$lname);
				$dav_properties['dc:publisher'] = $fname .' '. $lname;
				$dav_properties['publisher_id'] = $id;
			}
			if ($id=$data['attributes']['createdby_id'])
			{
				$GLOBALS['egw']->accounts->get_account_name($id,&$lid,&$fname,&$lname);
				$dav_properties['dc:creator'] = $fname .' '. $lname;
				$dav_properties['creator_id'] = $id;
			}
			if ($id=$data['attributes']['modifiedby_id'])
			{
				$GLOBALS['egw']->accounts->get_account_name($id,&$lid,&$fname,&$lname);
				$dav_properties['dc:contributor'] = $fname .' '. $lname;
				$dav_properties['contributor_id'] = $id;
			}

			$xmlns = 'xmlns:dc="http://purl.org/dc/elements/1.0/"';
			$this->dav_client->proppatch($p->real_full_path, $dav_properties, $xmlns, $this->override_locks[$p->real_full_path]);
			return True;
		}

		/**
		 * Set the correct attributes for 'string' (e.g. owner)
		 *
		 * @param string File/directory to correct attributes of
		 * @param relatives Relativity array
		 * @return Boolean True/False
		 */
		function correct_attributes ($data)
		{
			$default_values = array
				(
					'relatives'	=> array (RELATIVE_CURRENT)
				);

			$data = array_merge ($this->default_values ($data, $default_values), $data);
$this->debug('correct_attributes: '.$data['string']);
			$p = $this->path_parts (array(
					'string'	=> $data['string'],
					'relatives'	=> array ($data['relatives'][0])
				)
			);

			if ($p->fake_leading_dirs != $this->fakebase && $p->fake_leading_dirs != '/')
			{
				$ls_array = $this->ls (array(
						'string'	=> $p->fake_leading_dirs,
						'relatives'	=> array ($p->mask),
						'checksubdirs'	=> False,
						'nofiles'	=> True
					)
				);
				$set_attributes_array = Array(
					'owner_id' => $ls_array[0]['owner_id']
				);
			}
			elseif (preg_match ("+^$this->fakebase\/(.*)$+U", $p->fake_full_path, $matches))
			{
				$set_attributes_array = Array(
					'owner_id' => $GLOBALS['egw']->accounts->name2id ($matches[1])
				);
			}
			else
			{
				$set_attributes_array = Array(
					'owner_id' => 0
				);
			}

			$this->set_attributes (array(
					'string'	=> $p->fake_full_name,
					'relatives'	=> array ($p->mask),
					'attributes'	=> $set_attributes_array
				)
			);

			return True;
		}

		/**
		 * return file/dir type (MIME or other)
		 *
		 * @param string File or directory path (/home/user/dir/dir2/dir3, /home/user/dir/dir2/file)
		 * @param relatives Relativity array
		 * @return MIME type, "Directory", or nothing if MIME type is not known
		 */
		function file_type ($data)
		{
$this->debug('file_type');
			$default_values = array
				(
					'relatives'	=> array (RELATIVE_CURRENT)
				);

			$data = array_merge ($this->default_values ($data, $default_values), $data);

			$p = $this->path_parts (array(
					'string'	=> $data['string'],
					'relatives'	=> array ($data['relatives'][0])
				)
			);

			if (!$this->acl_check (array(
					'string'	=> $p->fake_full_path,
					'relatives'	=> array ($p->mask),
					'operation'	=> EGW_ACL_READ,
					'must_exist'	=> True
				))
			) return False;

			if ($p->outside)
			{
				if(is_dir($p->real_full_path)) return ('Directory');
				else return $this->get_ext_mime_type(array('string' => $p->real_full_path));

			}
			$tmp_prop=$this->dav_client->get_properties($p->real_full_path);
$this->debug('tmpprop: '.$p->real_full_path);
$this->debug($tmp_prop);
			$mime_type=$tmp_prop[$p->real_full_path]['mime_type'];
			if ($mime_type == 'httpd/unix-directory' || $tmp_prop[$p->real_full_path]['is_dir']== '1')
			{
				$mime_type='Directory';
			}
$this->debug('file_type: Mime type : '.$mime_type);
			return $mime_type;
		}

		/**
		 * return MIME type based on file extension
		 *
		 * Authors: skeeter
		 * 		 * 	 Internal use only.  Applications should call vfs->file_type ()
		 * @param string File name, with or without leading paths
		 * @return MIME type based on file extension
		 */
		function get_ext_mime_type ($data)
		{
			$file=basename($data['string']);
			$mimefile=EGW_API_INC.'/phpgw_mime.types';
			$fp=fopen($mimefile,'r');
			$contents = explode("\n",fread($fp,filesize($mimefile)));
			fclose($fp);

			$parts=explode('.',strtolower($file));
			$ext=$parts[(sizeof($parts)-1)];

			for($i=0;$i<sizeof($contents);$i++)
			{
				if (!ereg("^#",$contents[$i]))
				{
					$line=split("[[:space:]]+", $contents[$i]);
					if (sizeof($line) >= 2)
					{
						for($j=1;$j<sizeof($line);$j++)
						{
							if($line[$j] == $ext)
							{
								return $line[0];
							}
						}
					}
				}
			}

			return '';
 		}
		

 
		/**
		 * check if file/directory exists
		 *
		 * @param string file/directory to check existance of
		 * @param relatives Relativity array
		 * @return Boolean True/False
		 */
		function file_exists ($data)
		{
			$default_values = array
				(
					'relatives'	=> array (RELATIVE_CURRENT)
				);

			$data = array_merge ($this->default_values ($data, $default_values), $data);

			$p = $this->path_parts (array(
					'string'	=> $data['string'],
					'relatives'	=> array ($data['relatives'][0])
				)
			);
$this->debug('vfs->file_exists() data:'.$data['string']);
$this->debug('vfs->file_exists() full_path:  '.$p->real_full_path);
			if ($p->outside)
			{
				return file_exists($p->real_full_path);
			}
			
			$path = $p->real_full_path;
			
			//Even though this does full XML parsing on the output, because
			// it then caches the result this limits the amount of traffic to
			//the dav server (which makes it faster even over a local connection)
			$props = $this->dav_client->get_properties($path);
			if ($props[$path])
			{
				$this->debug('found');
				return True;
			}
			else
			{
				$this->debug('not found');
				return False;
			}
		}

		/**
		 * Return size of 'string'
		 *
		 * @param string file/directory to get size of
		 * @param relatives Relativity array
		 * @param checksubdirs Boolean, recursively add the size of all sub directories as well?
		 * @return Size of 'string' in bytes
		 */
		function get_size ($data)
		{
			if (!is_array ($data))
			{
				$data = array ();
			}

			$default_values = array
				(
					'relatives'	=> array (RELATIVE_CURRENT),
					'checksubdirs'	=> True
				);

			$data = array_merge ($this->default_values ($data, $default_values), $data);

			$p = $this->path_parts (array(
					'string'	=> $data['string'],
					'relatives'	=> array ($data['relatives'][0])
				)
			);

			if (!$this->acl_check (array(
					'string'	=> $p->fake_full_path,
					'relatives'	=> array ($p->mask),
					'operation'	=> EGW_ACL_READ,
					'must_exist'	=> True
				))
			)
			{
				return False;
			}

			/*
				 WIP - this should run through all of the subfiles/directories in the directory and tally up
				 their sizes.  Should modify ls () to be able to return a list for files outside the virtual root
			*/
			if ($p->outside){
				return filesize($p->real_full_path);
			}

			$ls_array = $this->ls (array(
					'string'	=> $p->fake_full_path,
					'relatives'	=> array ($p->mask),
					'checksubdirs'	=> $data['checksubdirs'],
					'nofiles'	=> !$data['checksubdirs']
				)
			);

			while (list ($num, $file_array) = each ($ls_array))
			{
				/*
					 Make sure the file is in the directory we want, and not
					 some deeper nested directory with a similar name
				*/
/*
				if (@!ereg ('^' . $file_array['directory'], $p->fake_full_path))
				{
					continue;
				}
*/

				$size += $file_array['size'];
$this->debug('size:getting size from fs: '.$size);
			}

			return $size;
		}

		/**
		 * Check if $this->working_id has write access to create files in $dir
		 *
		 * Simple call to acl_check
		 * @param string Directory to check access of
		 * @param relatives Relativity array
		 * @return Boolean True/False
		 */
		function checkperms ($data)
		{
			if (!is_array ($data))
			{
				$data = array ();
			}

			$default_values = array
				(
					'relatives'	=> array (RELATIVE_CURRENT)
				);

			$data = array_merge ($this->default_values ($data, $default_values), $data);

			$p = $this->path_parts (array(
					'string'	=> $data['string'],
					'relatives'	=> array ($data['relatives'][0])
				)
			);

			if (!$this->acl_check (array(
					'string'	=> $p->fake_full_path,
					'relatives'	=> array ($p->mask),
					'operation'	=> EGW_ACL_ADD
				))
			)
			{
				return False;
			}
			else
			{
				return True;
			}
		}

		/**
		 * get directory listing or info about a single file
		 *
		 * Note: The entries are not guaranteed to be returned in any logical order
		 * 		 * 	Note: The size for directories does not include subfiles/subdirectories.
		 * 		 * 	If you need that, use $this->get_size ()
		 * @param string File or Directory
		 * @param relatives Relativity array
		 * @param checksubdirs Boolean, recursively list all sub directories as well?
		 * @param mime_type Only return entries matching MIME-type 'mime_type'.  Can be any MIME-type, "Directory" or "\ " for those without MIME types
		 * @param nofiles Boolean.  True means you want to return just the information about the directory $dir.  If $dir is a file, $nofiles is implied.  This is the equivalent of 'ls -ld $dir'
		 * @param orderby How to order results.  Note that this only works for directories inside the virtual root
		 * @return array of arrays.  Subarrays contain full info for each file/dir.
		 */
		function ls ($data)
		{
			$default_values = array
				(
					'relatives'	=> array (RELATIVE_CURRENT),
					'checksubdirs'	=> True,
					'mime_type'	=> False,
					'nofiles'	=> False,
					'orderby'	=> 'directory'
				);
			$data = array_merge ($this->default_values ($data, $default_values), $data);
			//Stupid "nofiles" fix"
			if ($data['nofiles'])
			{
				$data['relatives'] = array (RELATIVE_NONE);
			}
			$p = $this->path_parts (array(
					'string'	=> $data['string'],
					'relatives'	=> array ($data['relatives'][0])
				)
			);
		
			if ($data['checksubdirs']==False && ereg('.*/$', $data['string']) && $data['nofiles'] )
			{
$this->debug('Returning empty for'.$data['string']);
				return array();
			}
			$dir = $p->fake_full_path;
$this->debug("ls'ing dir: $dir path: ".$p->real_full_path);
			/* If they pass us a file or 'nofiles' is set, return the info for $dir only */
			if (((($type = $this->file_type (array(
					'string'	=> $dir,
					'relatives'	=> array ($p->mask)
				)) != 'Directory'))
				|| ($data['nofiles'])) && !$p->outside
			)
			{
$this->debug('ls branch 1');
			$prop=$this->dav_client->get_properties($p->real_full_path, 1);
			//make the key the 'orderby' attribute
			if (! ($data['orderby'] == 'directory'))
			{
				$tmp_prop = array();
				$id=0;
				foreach ( $prop as $key=>$value)
				{
					$id++;
					$new_key =  substr($value[$data['orderby']].'        ',0, 8);
					$tmp_prop[strtolower($new_key).'_'.$id] = $value;
				}
			}
			else 
			{
				$tmp_prop = $prop;
			}
			ksort($tmp_prop);
			$rarray = array ();
			foreach($tmp_prop as $idx => $value)
			{
				if($value['mime_type']==$data['mime_type'] or $data['mime_type']=='')
				{
					$directory = $this->path_parts($value['directory']);
					$value['directory'] = $directory->fake_full_path;
					if($value['is_dir']) $value['mime_type']='Directory';
					$rarray[] = $value;
				}
			}
$this->debug('ls returning 1:');
				return $rarray;
			}

			//WIP - this should recurse using the same options the virtual part of ls () does
			/* If $dir is outside the virutal root, we have to check the file system manually */
			if ($p->outside)
			{
$this->debug('ls branch 2 (outside)');
				if ($this->file_type (array(
						'string'	=> $p->fake_full_path,
						'relatives'	=> array ($p->mask)
					)) == 'Directory'
					&& !$data['nofiles']
				)
				{
					$dir_handle = opendir ($p->real_full_path);
					while ($filename = readdir ($dir_handle))
					{
						if ($filename == '.' || $filename == '..')
						{
							continue;
						}

						$rarray[] = $this->get_real_info (array(
								'string'	=> $p->real_full_path . SEP . $filename,
								'relatives'	=> array ($p->mask)
							)
						);
					}
				}
				else
				{
					$rarray[] = $this->get_real_info (array(
							'string'	=> $p->real_full_path,
							'relatives'	=> array ($p->mask)
						)
					);
				}
$this->debug('ls returning 2:');
				return $rarray;
			}
$this->debug('ls branch 3');
			/* $dir's not a file, is inside the virtual root, and they want to check subdirs */
			$prop=$this->dav_client->get_properties($p->real_full_path,1);
			unset($prop[$p->real_full_path]);
			//make the key the 'orderby' attribute

			if (! ($data['orderby'] == 'directory'))
			{
				$tmp_prop = array();
				$id=0;
				foreach ( $prop as $key=>$value)
				{
					$id++;
					$new_key =  substr($value[$data['orderby']].'        ',0, 8);
					$tmp_prop[strtolower($new_key).'_'.$id] = $value;
				}
			}
			else 
			{
				$tmp_prop = $prop;
			}
			
			ksort($tmp_prop);

			unset($tmp_prop[ $p->real_full_path]);
			$rarray = array ();
			foreach($tmp_prop as $idx => $value)
			{	
				if($data['mime_type']=='' || $value['mime_type']==$data['mime_type'])
				{
					//$directory = $this->path_parts($value['directory']);
					$value['directory'] = $p->fake_full_path;
					$rarray[] = $value;
				}
			}		
$this->debug('ls:returning 3:');
			return $rarray;
		}

		/**
		 * shortcut to ls
		 *
		 */
		function dir ($data)
		{
			return $this->ls ($data);
		}

		/**
		 * Process and run a Unix-sytle command line
		 *
		 * EXPERIMENTAL.  DANGEROUS.  DO NOT USE THIS UNLESS YOU KNOW WHAT YOU'RE DOING!
		 * 		 * 	This is mostly working, but the command parser needs to be improved to take
		 * 		 * 	files with spaces into consideration (those should be in "").
		 * @param command_line Unix-style command line with one of the commands in the $args array
		 * @return $result The return value of the actual VFS call
		 */
		function command_line ($data)
		{
			if (!is_array ($data))
			{
				$data = array ();
			}

			$args = array
			(
				array ('name'	=> 'mv', 'params'	=> 2),
				array ('name'	=> 'cp', 'params'	=> 2),
				array ('name'	=> 'rm', 'params'	=> 1),
				array ('name'	=> 'ls', 'params'	=> -1),
				array ('name'	=> 'du', 'params'	=> 1, 'func'	=> get_size),
				array ('name'	=> 'cd', 'params'	=> 1),
				array ('name'	=> 'pwd', 'params'	=> 0),
				array ('name'	=> 'cat', 'params'	=> 1, 'func'	=> read),
				array ('name'	=> 'file', 'params'	=> 1, 'func'	=> file_type),
				array ('name'	=> 'mkdir', 'params'	=> 1),
				array ('name'	=> 'touch', 'params'	=> 1)
			);

			if (!$first_space = strpos ($data['command_line'], ' '))
			{
				$first_space = strlen ($data['command_line']);
			}
			if ((!$last_space = strrpos ($data['command_line'], ' ')) || ($last_space == $first_space))
			{
				$last_space = strlen ($data['command_line']) + 1;
			}
			$argv[0] = substr ($data['command_line'], 0, $first_space);
			if (strlen ($argv[0]) != strlen ($data['command_line']))
			{
				$argv[1] = substr ($data['command_line'], $first_space + 1, $last_space - ($first_space + 1));
				if ((strlen ($argv[0]) + 1 + strlen ($argv[1])) != strlen ($data['command_line']))
				{
					$argv[2] = substr ($data['command_line'], $last_space + 1);
				}
			}
			$argc = count ($argv);

			reset ($args);
			while (list (,$arg_info) = each ($args))
			{
				if ($arg_info['name'] == $argv[0])
				{
					$command_ok = 1;
					if (($argc == ($arg_info['params'] + 1)) || ($arg_info['params'] == -1))
					{
						$param_count_ok = 1;
					}
					break;
				}
			}

			if (!$command_ok)
			{
//				return E_VFS_BAD_COMMAND;
				return False;
			}
			if (!$param_count_ok)
			{
//				return E_VFS_BAD_PARAM_COUNT;
				return False;
			}

			for ($i = 1; $i != ($arg_info['params'] + 1); $i++)
			{
				if (substr ($argv[$i], 0, 1) == '/')
				{
					$relatives[] = RELATIVE_NONE;
				}
				else
				{
					$relatives[] = RELATIVE_ALL;
				}
			}

			$func = $arg_info['func'] ? $arg_info['func'] : $arg_info['name'];

			if (!$argv[2])
			{
				$rv = $this->$func (array(
						'string'	=> $argv[1],
						'relatives'	=> $relatives
					)
				);
			}
			else
			{
				$rv = $this->$func (array(
						'from'	=> $argv[1],
						'to'	=> $argv[2],
						'relatives'	=> $relatives
					)
				);
			}

			return ($rv);
		}

		/* Helper functions */

		function default_values ($data, $default_values)
		{
			if(!is_array($data)) $data=array();
			for ($i = 0; list ($key, $value) = each ($default_values); $i++)
			{
				if (!isset ($data[$key]))
				{
					$data[$key] = $value;
				}
			}

			return $data;
		}

		/* Since we are always dealing with real info, this just calls ls */
		function get_real_info ($data){
			if (!is_array ($data))
			{
				$data = array ();
			}

			$default_values = array
				(
					'relatives'	=> array (RELATIVE_CURRENT)
				);

			$data = array_merge ($this->default_values ($data, $default_values), $data);

			$p = $this->path_parts (array(
					'string'	=> $data['string'],
					'relatives'	=> array ($data['relatives'][0])
				)
			);

			if ( $this->file_type(array('string' => $p->real_full_path, 'relatives' => array (RELATIVE_ROOT))) == 'Directory')
			{
				$mime_type = 'Directory';
			}
			else
			{
				$mime_type = $this->get_ext_mime_type (array(
						'string'	=> $p->fake_name
					)
				);

			}

			$size = filesize ($p->real_full_path);
			$rarray = array(
				'directory' => $p->fake_leading_dirs,
				'name' => $p->fake_name,
				'size' => $size,
				'mime_type' => $mime_type
			);

			return ($rarray);
		}
		
		function update_real()
		{ //hmmm. things break without this, but it does nothing in this implementation
			return True;
		}
		
		function save_session()
		{
			//Save the overrided locks in the session
			$app = $GLOBALS['egw_info']['flags']['currentapp'];
			$a = array();
			foreach ($this->override_locks as $name => $token)
			{
				$a[] = $name.';'.$token;	
			}	
			$session_data = implode('\n', $a);
			$this->session = $GLOBALS['egw']->session->appsession ('vfs_dav',$app, base64_encode($session_data));
				
		}	
	}
?>
