1<?php
2	/**
3	* Virtual File System
4	* @author Jason Wies <zone@phpgroupware.org>
5	* @copyright Copyright (C) 2001-2003 Jason Wies, Johnathan Sim
6	* @copyright Portions Copyright (C) 2003,2004 Free Software Foundation, Inc. http://www.fsf.org/
7	* @license http://www.fsf.org/licenses/lgpl.html GNU Lesser General Public License
8	* @package phpgwapi
9	* @subpackage vfs
10	* @version $Id: class.vfs_dav.inc.php 15462 2004-11-06 15:34:27Z powerstat $
11	*/
12
13	/**
14	* Enables debug output for this class
15	*/
16	define ('DEBUG', 0);
17	/**
18	* This generates a whole lotta output
19	*/
20	define ('TRACE', 0);
21	/**
22	* Enables some SQL debugging
23	*/
24	define ('DEBUG_SQL', 0);
25	/**
26	* Enables (LOTS) of debugging inside the HTTP class
27	*/
28	define ('DEBUG_DAV', 0);
29
30
31	/**
32	* Virtual File System
33	*
34	* @package phpgwapi
35	* @subpackage vfs
36	*/
37	class vfs
38	{
39		var $basedir;
40		var $fakebase;
41		var $relative;
42		var $working_id;
43		var $working_lid;
44		var $attributes;
45		var $override_acl;
46		var $linked_dirs;
47		var $meta_types;
48		var $now;
49		var $override_locks;
50
51		//These are DAV-native properties that have different names in VFS
52		var $vfs_property_map = array(
53			'creationdate' => 'created',
54			'getlastmodified' => 'modified',
55			'getcontentlength' => 'size',
56			'getcontenttype' => 'mime_type',
57			'description' => 'comment',
58			'creator_id' => 'createdby_id',
59			'contributor_id' => 'modifiedby_id',
60			'publisher_id' => 'owner_id'
61		);
62
63		/*!
64		@function vfs
65		@abstract constructor, sets up variables
66		*/
67
68		function vfs ()
69		{
70			$this->basedir = $GLOBALS['phpgw_info']['server']['files_dir'];
71			$this->fakebase = '/home';
72			$this->working_id = $GLOBALS['phpgw_info']['user']['account_id'];
73			$this->working_lid = $GLOBALS['phpgw']->accounts->id2name($this->working_id);
74			$this->now = date ('Y-m-d');
75			$this->override_acl = 0;
76			/*
77			   File/dir attributes, each corresponding to a database field.  Useful for use in loops
78			   If an attribute was added to the table, add it here and possibly add it to
79			   set_attributes ()
80
81			   set_attributes now uses this array().   07-Dec-01 skeeter
82			*/
83
84			$this->attributes = array(
85				'file_id',
86				'owner_id',
87				'createdby_id',
88				'modifiedby_id',
89				'created',
90				'modified',
91				'size',
92				'mime_type',
93				'deleteable',
94				'comment',
95				'app',
96				'directory',
97				'name',
98				'link_directory',
99				'link_name',
100				'version'
101			);
102
103			/*
104			   These are stored in the MIME-type field and should normally be ignored.
105			   Adding a type here will ensure it is normally ignored, but you will have to
106			   explicitly add it to acl_check (), and to any other SELECT's in this file
107			*/
108
109			$this->meta_types = array ('journal', 'journal-deleted');
110
111			/* We store the linked directories in an array now, so we don't have to make the SQL call again */
112			if ($GLOBALS['phpgw_info']['server']['db_type']=='mssql'
113				|| $GLOBALS['phpgw_info']['server']['db_type']=='sybase')
114			{
115				$query = $GLOBALS['phpgw']->db->query ("SELECT directory, name, link_directory, link_name FROM phpgw_vfs WHERE CONVERT(varchar,link_directory) != '' AND CONVERT(varchar,link_name) != ''" . $this->extra_sql (array ('query_type' => VFS_SQL_SELECT)), __LINE__,__FILE__);
116			}
117			else
118			{
119				$query = $GLOBALS['phpgw']->db->query ("SELECT directory, name, link_directory, link_name FROM phpgw_vfs WHERE (link_directory IS NOT NULL or link_directory != '') AND (link_name IS NOT NULL or link_name != '')" . $this->extra_sql (array ('query_type' => VFS_SQL_SELECT)), __LINE__,__FILE__);
120			}
121
122			$this->linked_dirs = array ();
123			while ($GLOBALS['phpgw']->db->next_record ())
124			{
125				$this->linked_dirs[] = $GLOBALS['phpgw']->db->Record;
126			}
127
128
129			$this->repository = $GLOBALS['phpgw_info']['server']['files_dir'];
130			$this->dav_user=$GLOBALS['phpgw_info']['user']['userid'];
131			$this->dav_pwd=$GLOBALS['phpgw_info']['user']['passwd'];
132			$parsed_url = parse_url($this->repository);
133			$this->dav_host=$parsed_url['host'];
134			$this->dav_port=@isset($parsed_url['port']) ? $parsed_url['port'] : 80;
135
136			$this->dav_client = CreateObject('phpgwapi.http_dav_client');
137			$this->dav_client->set_credentials($this->dav_user,$this->dav_pwd);
138			$this->dav_client->set_attributes($this->attributes,$this->vfs_property_map);
139			$result = $this->dav_client->connect($this->dav_host,$this->dav_port);
140			if (DEBUG_DAV)
141			{
142				echo '<b>DAV client debugging enabled!</b>';
143				$this->dav_client->set_debug(DBGTRACE|DBGINDATA|DBGOUTDATA|DBGSOCK|DBGLOW);
144			}
145			if (!$result)
146			{
147				echo '<h2>Cannot connect to the file repository server!</h2>';
148				die($this->dav_client->get_body());
149			}
150			//determine the supported DAV features
151/*			$features = $this->dav_client->dav_features('http://'.$this->dav_host);
152			if (!$features || ! in_array( '1', $features) )
153			{
154				die("Error :: The specified file repository: $this->dav_host doesn't appear to support WebDAV! ");
155
156			}
157*/
158			//Reload the overriden_locks
159			$app = $GLOBALS['phpgw_info']['flags']['currentapp'];
160			$session_data = base64_decode($GLOBALS['phpgw']->session->appsession ('vfs_dav',$app));
161			$this->override_locks = array();
162			if ($session_data)
163			{
164				$locks = explode('\n', $session_data);
165				foreach ($locks as $lock)
166				{
167					$lockdata = explode(';', $lock);
168					$name = $lockdata[0];
169					$token = $lockdata[1];
170					$this->override_locks[$name] = $token;
171				}
172			}
173
174				register_shutdown_function(array(&$this, 'vfs_umount'));
175				$this->debug('Constructed with debug enabled');
176
177		}
178
179		//TODO:  Get rid of this
180		//A quick, temporary debug output function
181		function debug($info) {
182			if (DEBUG)
183			{
184				echo '<b> vfs_sql_dav debug:<em> ';
185				if (is_array($info))
186				{
187					print_r($info);
188				}
189				else
190				{
191					echo $info;
192				}
193				echo '</em></b><br />';
194			}
195		}
196
197		/*!
198		@function dav_path
199		@abstract Apaches mod_dav in particular requires that the path sent in a dav request NOT be a URI
200		*/
201		function dav_path($uri) {
202			//$this->debug('DAV path');
203			$parsed = parse_url($uri);
204			return $parsed['path'];
205		}
206
207		/*!
208		@function glue_url
209		@abstract glues a parsed url (ie parsed using PHP's parse_url) back
210			together
211		@param $url	The parsed url (its an array)
212		*/
213		function glue_url ($url){
214			if (!is_array($url))
215			{
216				return false;
217			}
218			// scheme
219			$uri = (!empty($url['scheme'])) ? $url['scheme'].'://' : '';
220			// user & pass
221			if (!empty($url['user']))
222			{
223				$uri .= $url['user'];
224				if (!empty($url['pass']))
225				{
226					$uri .=':'.$url['pass'];
227				}
228				$uri .='@';
229			}
230			// host
231			$uri .= $url['host'];
232			// port
233			$port = (!empty($url['port'])) ? ':'.$url['port'] : '';
234			$uri .= $port;
235			// path
236			$uri .= $url['path'];
237			// fragment or query
238			if (isset($url['fragment']))
239			{
240				$uri .= '#'.$url['fragment'];
241			} elseif (isset($url['query']))
242			{
243				$uri .= '?'.$url['query'];
244			}
245			return $uri;
246		}
247
248		function dav_host($uri) {
249			//$this->debug('DAV path');
250			$parsed = parse_url($uri);
251			$parsed['path'] = '';
252			$host = $this->glue_url($parsed);
253			return $host;
254		}
255
256		function vfs_umount()
257		{
258			$this->dav_client->disconnect();
259		}
260
261
262		/*!
263		@function set_relative
264		@abstract Set path relativity
265		@param mask Relative bitmask (see RELATIVE_ defines)
266		*/
267		function set_relative ($data)
268		{
269			if (!is_array ($data))
270			{
271				$data = array ();
272			}
273
274			if (!$data['mask'])
275			{
276				unset ($this->relative);
277			}
278			else
279			{
280				$this->relative = $data['mask'];
281			}
282		}
283
284		/*!
285		@function get_relative
286		@abstract Return relativity bitmask
287		@discussion Returns relativity bitmask, or the default of "completely relative" if unset
288		*/
289		function get_relative ()
290		{
291			if (isset ($this->relative) && $this->relative)
292			{
293				return $this->relative;
294			}
295			else
296			{
297				return RELATIVE_ALL;
298			}
299		}
300
301		/*!
302		@function sanitize
303		@abstract Removes leading .'s from 'string'
304		@discussion You should not pass all filenames through sanitize () unless you plan on rejecting
305				.files.  Instead, pass the name through securitycheck () first, and if it fails,
306				pass it through sanitize
307		@param string string to sanitize
308		@result $string 'string' without it's leading .'s
309		*/
310		function sanitize ($data)
311		{
312			if (!is_array ($data))
313			{
314				$data = array ();
315			}
316
317			/* We use path_parts () just to parse the string, not translate paths */
318			$p = $this->path_parts (array(
319					'string' => $data['string'],
320					'relatives' => array (RELATIVE_NONE)
321				)
322			);
323
324			return (ereg_replace ('^\.+', '', $p->fake_name));
325		}
326
327		/*!
328		@function securitycheck
329		@abstract Security check function
330		@discussion Checks for basic violations such as ..
331				If securitycheck () fails, run your string through vfs->sanitize ()
332		@param string string to check security of
333		@result Boolean True/False.  True means secure, False means insecure
334		*/
335		function securitycheck ($data)
336		{
337			if (!is_array ($data))
338			{
339				$data = array ();
340			}
341
342			if (substr ($data['string'], 0, 1) == "\\" || strstr ($data['string'], "..") || strstr ($data['string'], "\\..") || strstr ($data['string'], ".\\."))
343			{
344				return False;
345			}
346			else
347			{
348				return True;
349			}
350		}
351
352		/*!
353		@function db_clean
354		@abstract Clean 'string' for use in database queries
355		@param string String to clean
356		@result Cleaned version of 'string'
357		*/
358		function db_clean ($data)
359		{
360			if (!is_array ($data))
361			{
362				$data = array ();
363			}
364
365			$string = ereg_replace ("'", "\'", $data['string']);
366
367			return $string;
368		}
369
370		/*!
371		@function extra_sql
372		@abstract Return extra SQL code that should be appended to certain queries
373		@param query_type The type of query to get extra SQL code for, in the form of a VFS_SQL define
374		@result Extra SQL code
375		*/
376		function extra_sql ($data)
377		{ //This is purely for SQL
378			return '';
379		}
380
381		/*!
382		@function add_journal
383		@abstract Add a journal entry after (or before) completing an operation,
384			  and increment the version number.  This function should be used internally only
385		@discussion Note that state_one and state_two are ignored for some VFS_OPERATION's, for others
386			    they are required.  They are ignored for any "custom" operation
387			    The two operations that require state_two:
388			    operation			state_two
389			    VFS_OPERATION_COPIED	fake_full_path of copied to
390			    VFS_OPERATION_MOVED		fake_full_path of moved to
391
392			    If deleting, you must call add_journal () before you delete the entry from the database
393		@param string File or directory to add entry for
394		@param relatives Relativity array
395		@param operation The operation that was performed.  Either a VFS_OPERATION define or
396				  a non-integer descriptive text string
397		@param state_one The first "state" of the file or directory.  Can be a file name, size,
398				  location, whatever is appropriate for the specific operation
399		@param state_two The second "state" of the file or directory
400		@param incversion Boolean True/False.  Increment the version for the file?  Note that this is
401				   handled automatically for the VFS_OPERATION defines.
402				   i.e. VFS_OPERATION_EDITED would increment the version, VFS_OPERATION_COPIED
403				   would not
404		@result Boolean True/False
405		*/
406		function add_journal ($data) {
407		//The journalling dont work :(  Ideally this will become "versioning"
408			return True;
409		}
410
411
412		/*!
413		@function flush_journal
414		@abstract Flush journal entries for $string.  Used before adding $string
415		@discussion flush_journal () is an internal function and should be called from add_journal () only
416		@param string File/directory to flush journal entries of
417		@param relatives Realtivity array
418		@param deleteall Delete all types of journal entries, including the active Create entry.
419				  Normally you only want to delete the Create entry when replacing the file
420				  Note that this option does not effect $deleteonly
421		@param deletedonly Only flush 'journal-deleted' entries (created when $string was deleted)
422		@result Boolean True/False
423		*/
424		function flush_journal ($data)
425		{
426			return True;
427		}
428
429
430		/*!
431		@function get_journal
432		@abstract Retrieve journal entries for $string
433		@param string File/directory to retrieve journal entries of
434		@param relatives Relativity array
435		@param type 0/False = any, 1 = 'journal', 2 = 'journal-deleted'
436		@result Array of arrays of journal entries
437		*/
438		function get_journal ($data)
439		{
440			return array();
441		}
442
443		/*!
444		@function path_parts
445		@abstract take a real or fake pathname and return an array of its component parts
446		@param string full real or fake path
447		@param relatives Relativity array
448		@param object True returns an object instead of an array
449		@param nolinks Don't check for links (made with make_link ()).  Used internally to prevent recursion
450		@result $rarray/$robject Array or object containing the fake and real component parts of the path
451		@discussion Returned values are:
452				mask
453				outside
454				fake_full_path
455				fake_leading_dirs
456				fake_extra_path		BROKEN
457				fake_name
458				real_full_path
459				real_leading_dirs
460				real_extra_path		BROKEN
461				real_name
462				fake_full_path_clean
463				fake_leading_dirs_clean
464				fake_extra_path_clean	BROKEN
465				fake_name_clean
466				real_full_path_clean
467				real_leading_dirs_clean
468				real_extra_path_clean	BROKEN
469				real_name_clean
470				real_uri
471			"clean" values are run through vfs->db_clean () and
472			are safe for use in SQL queries that use key='value'
473			They should be used ONLY for SQL queries, so are used
474			mostly internally
475			mask is either RELATIVE_NONE or RELATIVE_NONE|VFS_REAL,
476			and is used internally
477			outside is boolean, True if 'relatives' contains VFS_REAL
478		*/
479		function path_parts ($data)
480		{
481			$default_values = array
482				(
483					'relatives'	=> array (RELATIVE_CURRENT),
484					'object'	=> True,
485					'nolinks'	=> False
486				);
487
488			$data = array_merge ($this->default_values ($data, $default_values), $data);
489
490			$sep = SEP;
491
492			$rarray['mask'] = RELATIVE_NONE;
493
494			if (!($data['relatives'][0] & VFS_REAL))
495			{
496				$rarray['outside'] = False;
497				$fake = True;
498			}
499			else
500			{
501				$rarray['outside'] = True;
502				$rarray['mask'] |= VFS_REAL;
503			}
504
505			$string = $this->getabsolutepath (array(
506					'string'	=> $data['string'],
507					'mask'	=> array ($data['relatives'][0]),
508					'fake'	=> $fake
509				)
510			);
511
512			if ($fake)
513			{
514				$base_sep = '/';
515				$base = '/';
516
517				$opp_base = $this->basedir . $sep;
518
519				$rarray['fake_full_path'] = $string;
520			}
521			else
522			{
523				$base_sep = $sep;
524				if (ereg ("^$this->basedir" . $sep, $string))
525				{
526					$base = $this->basedir . $sep;
527				}
528				else
529				{
530					$base = $sep;
531				}
532
533				$opp_base = '/';
534				$rarray['real_full_url'] = $string;
535				$rarray['real_full_path'] = $this->dav_path($string);
536			}
537
538			/* This is needed because of substr's handling of negative lengths */
539			$baselen = strlen ($base);
540			$lastslashpos = strrpos ($string, $base_sep);
541			$length = (($lastslashpos < $baselen) ? 0 : ($lastslashpos - $baselen));
542
543			$extra_path = $rarray['fake_extra_path'] = $rarray['real_extra_path'] = substr ($string, strlen ($base), $length);
544			$name = $rarray['fake_name'] = $rarray['real_name'] = substr ($string, strrpos ($string, $base_sep) + 1);
545
546			if ($fake)
547			{
548				$dispsep = ($rarray['real_extra_path'] ? $sep : '');
549				$rarray['real_full_url'] = $opp_base . $rarray['real_extra_path'] . $dispsep . $rarray['real_name'];
550				$rarray['real_full_path'] = $this->dav_path($rarray['real_full_url']);
551				if ($extra_path)
552				{
553					$rarray['fake_leading_dirs'] = $base . $extra_path;
554					$rarray['real_leading_dirs'] = $this->dav_path($opp_base . $extra_path);
555				}
556				elseif (strrpos ($rarray['fake_full_path'], $sep) == 0)
557				{
558					/* If there is only one $sep in the path, we don't want to strip it off */
559					$rarray['fake_leading_dirs'] = $sep;
560					$rarray['real_leading_dirs'] = $this->dav_path( substr ($opp_base, 0, strlen ($opp_base) - 1));
561				}
562				else
563				{
564					/* These strip the ending / */
565					$rarray['fake_leading_dirs'] = substr ($base, 0, strlen ($base) - 1);
566					$rarray['real_leading_dirs'] = $this->dav_path( substr ($opp_base, 0, strlen ($opp_base) - 1));
567				}
568			}
569			else
570			{
571				$rarray['fake_full_path'] = $opp_base . $rarray['fake_extra_path'] . '/' . $rarray['fake_name'];
572				if ($extra_path)
573				{
574					$rarray['fake_leading_dirs'] = $opp_base . $extra_path;
575					$rarray['real_leading_dirs'] = $this->dav_path($base . $extra_path);
576				}
577				else
578				{
579					$rarray['fake_leading_dirs'] = substr ($opp_base, 0, strlen ($opp_base) - 1);
580					$rarray['real_leading_dirs'] = $this->dav_path(substr ($base, 0, strlen ($base) - 1));
581				}
582			}
583
584			/* We check for linked dirs made with make_link ().  This could be better, but it works */
585			if (!$data['nolinks'])
586			{
587				reset ($this->linked_dirs);
588				while (list ($num, $link_info) = each ($this->linked_dirs))
589				{
590					if (ereg ("^$link_info[directory]/$link_info[name](/|$)", $rarray['fake_full_path']))
591					{
592						$rarray['real_full_path'] = ereg_replace ("^$this->basedir", '', $rarray['real_full_path']);
593						$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']);
594
595						$p = $this->path_parts (array(
596								'string'	=> $rarray['real_full_path'],
597								'relatives'	=> array (RELATIVE_NONE|VFS_REAL),
598								'nolinks'	=> True
599							)
600						);
601
602						$rarray['real_leading_dirs'] = $this->dav_path($p->real_leading_dirs);
603						$rarray['real_extra_path'] = $p->real_extra_path;
604						$rarray['real_name'] = $p->real_name;
605					}
606				}
607			}
608
609			/*
610				Create the 'real_auth_url', which includes the user and
611				password (for the view method to redirect you there)
612			*/
613
614			$parsed_url = parse_url($rarray['real_full_url']);
615			$parsed_url['user'] = $this->dav_user;
616//			$parsed_url['pass'] = $this->dav_pwd;
617			$rarray['real_full_auth_url'] = $this->glue_url($parsed_url);
618
619			$parsed_url = parse_url($rarray['real_full_url']);
620			$parsed_url['scheme'] = 'https';
621			$parsed_url['user'] = $this->dav_user;
622			$rarray['real_full_secure_url'] = $this->glue_url($parsed_url);
623
624
625			/*
626			   We have to count it before because new keys will be added,
627			   which would create an endless loop
628			*/
629			$count = count ($rarray);
630			reset ($rarray);
631			for ($i = 0; (list ($key, $value) = each ($rarray)) && $i != $count; $i++)
632			{
633				$rarray[$key . '_clean'] = $this->db_clean (array ('string' => $value));
634			}
635
636			if ($data['object'])
637			{
638				$robject = new path_class;
639
640				reset ($rarray);
641				while (list ($key, $value) = each ($rarray))
642				{
643					$robject->$key = $value;
644				}
645			}
646
647/*
648			echo "<br />fake_full_path: $rarray[fake_full_path]
649				<br />fake_leading_dirs: $rarray[fake_leading_dirs]
650				<br />fake_extra_path: $rarray[fake_extra_path]
651				<br />fake_name: $rarray[fake_name]
652				<br />real_full_path: $rarray[real_full_path]
653				<br />real_full_url: $rarray[real_full_url]
654				<br />real_leading_dirs: $rarray[real_leading_dirs]
655				<br />real_extra_path: $rarray[real_extra_path]
656				<br />real_name: $rarray[real_name]";
657*/
658
659			if ($data['object'])
660			{
661				return ($robject);
662			}
663			else
664			{
665				return ($rarray);
666			}
667		}
668
669		/*!
670		@function getabsolutepath
671		@abstract get the absolute path
672		@param string defaults to False, directory/file to get path of, relative to relatives[0]
673		@param mask Relativity bitmask (see RELATIVE_ defines).  RELATIVE_CURRENT means use $this->relative
674		@param fake Returns the "fake" path, ie /home/user/dir/file (not always possible.  use path_parts () instead)
675		@result $basedir Full fake or real path
676		*/
677		function getabsolutepath ($data)
678		{
679			$default_values = array
680				(
681					'string'	=> False,
682					'mask'	=> array (RELATIVE_CURRENT),
683					'fake'	=> True
684				);
685
686			$data = array_merge ($this->default_values ($data, $default_values), $data);
687
688			$currentdir = $this->pwd (False);
689
690			/* If they supply just VFS_REAL, we assume they want current relativity */
691			if ($data['mask'][0] == VFS_REAL)
692			{
693				$data['mask'][0] |= RELATIVE_CURRENT;
694			}
695
696			if (!$this->securitycheck (array(
697					'string'	=> $data['string']
698				))
699			)
700			{
701				return False;
702			}
703
704			if ($data['mask'][0] & RELATIVE_NONE)
705			{
706				return $data['string'];
707			}
708
709			if ($data['fake'])
710			{
711				$sep = '/';
712			}
713			else
714			{
715				$sep = SEP;
716			}
717
718			/* if RELATIVE_CURRENT, retrieve the current mask */
719			if ($data['mask'][0] & RELATIVE_CURRENT)
720			{
721				$mask = $data['mask'][0];
722				/* Respect any additional masks by re-adding them after retrieving the current mask*/
723				$data['mask'][0] = $this->get_relative () + ($mask - RELATIVE_CURRENT);
724			}
725
726			if ($data['fake'])
727			{
728				$basedir = '/';
729			}
730			else
731			{
732				$basedir = $this->basedir . $sep;
733
734				/* This allows all requests to use /'s */
735				$data['string'] = preg_replace ("|/|", $sep, $data['string']);
736			}
737
738			if (($data['mask'][0] & RELATIVE_PATH) && $currentdir)
739			{
740				$basedir = $basedir . $currentdir . $sep;
741			}
742			elseif (($data['mask'][0] & RELATIVE_USER) || ($data['mask'][0] & RELATIVE_USER_APP))
743			{
744				$basedir = $basedir . $this->fakebase . $sep;
745			}
746
747			if ($data['mask'][0] & RELATIVE_CURR_USER)
748			{
749				$basedir = $basedir . $this->working_lid . $sep;
750			}
751
752			if (($data['mask'][0] & RELATIVE_USER) || ($data['mask'][0] & RELATIVE_USER_APP))
753			{
754				$basedir = $basedir . $GLOBALS['phpgw_info']['user']['account_lid'] . $sep;
755			}
756
757			if ($data['mask'][0] & RELATIVE_USER_APP)
758			{
759				$basedir = $basedir . "." . $GLOBALS['phpgw_info']['flags']['currentapp'] . $sep;
760			}
761
762			/* Don't add string if it's a /, just for aesthetics */
763			if ($data['string'] && $data['string'] != $sep)
764			{
765				$basedir = $basedir . $data['string'];
766			}
767
768			/* Let's not return // */
769			while (ereg ($sep . $sep, $basedir))
770			{
771				$basedir = ereg_replace ($sep . $sep, $sep, $basedir);
772			}
773
774			$basedir = ereg_replace ($sep . '$', '', $basedir);
775
776			return $basedir;
777		}
778
779		/*!
780		@function acl_check
781		@abstract Check ACL access to $file for $GLOBALS['phpgw_info']["user"]["account_id"];
782		@param string File to check access of
783		@discussion To check the access for a file or directory, pass 'string'/'relatives'/'must_exist'.
784				To check the access to another user or group, pass 'owner_id'.
785				If 'owner_id' is present, we bypass checks on 'string'/'relatives'/'must_exist'
786		@param relatives Standard relativity array
787		@param operation Operation to check access to.  In the form of a PHPGW_ACL defines bitmask.  Default is read
788		@param owner_id Owner id to check access of (see discussion above)
789		@param must_exist Boolean.  Set to True if 'string' must exist.  Otherwise, we check the parent directory as well
790		@result Boolean.  True if access is ok, False otherwise
791		*/
792		function acl_check ($data)
793		{
794//			return True;
795			$default_values = array
796				(
797					'relatives'	=> array (RELATIVE_CURRENT),
798					'operation'	=> PHPGW_ACL_READ,
799					'must_exist'	=> False
800				);
801
802			$data = array_merge ($this->default_values ($data, $default_values), $data);
803
804			/* Accommodate special situations */
805			if ($this->override_acl || $data['relatives'][0] == RELATIVE_USER_APP)
806			{
807				return True;
808			}
809
810			if (!$data['owner_id'])
811			{
812				$p = $this->path_parts (array(
813						'string'	=> $data['string'],
814						'relatives'	=> array ($data['relatives'][0])
815					)
816				);
817
818				/* Temporary, until we get symlink type files set up */
819				if ($p->outside)
820				{
821					return True;
822				}
823
824				/* Read access is always allowed here, but nothing else is */
825				if ($data['string'] == '/' || $data['string'] == $this->fakebase)
826				{
827					if ($data['operation'] == PHPGW_ACL_READ)
828					{
829						return True;
830					}
831					else
832					{
833						return False;
834					}
835				}
836
837				/* If the file doesn't exist, we get ownership from the parent directory */
838				if (!$this->file_exists (array(
839						'string'	=> $p->fake_full_path,
840						'relatives'	=> array ($p->mask)
841					))
842				)
843				{
844					if ($data['must_exist'])
845					{
846						return False;
847					}
848
849					$data['string'] = $p->fake_leading_dirs;
850					$p2 = $this->path_parts (array(
851							'string'	=> $data['string'],
852							'relatives'	=> array ($p->mask)
853						)
854					);
855
856					if (!$this->file_exists (array(
857							'string'	=> $data['string'],
858							'relatives'	=> array ($p->mask)
859						))
860					)
861					{
862						return False;
863					}
864				}
865				else
866				{
867					$p2 = $p;
868				}
869				 /*
870				 We don't use ls () to get owner_id as we normally would,
871				 because ls () calls acl_check (), which would create an infinite loop
872				 */
873				$this->override_acl=1; /* To avoid a loop */
874				$file_info = $this->ls($data);
875				$this->override_acl=0;
876				$owner_id = $file_info['owner_id'];
877			}
878			else
879			{
880				$owner_id = $data['owner_id'];
881			}
882
883			/* This is correct.  The ACL currently doesn't handle undefined values correctly */
884			if (!$owner_id)
885			{
886				$owner_id = 0;
887			}
888
889			$user_id = $GLOBALS['phpgw_info']['user']['account_id'];
890
891			/* They always have access to their own files */
892			if ($owner_id == $user_id)
893			{
894				return True;
895			}
896
897			/* Check if they're in the group */
898			$memberships = $GLOBALS['phpgw']->accounts->membership ($user_id);
899
900			if (is_array ($memberships))
901			{
902				reset ($memberships);
903				while (list ($num, $group_array) = each ($memberships))
904				{
905					if ($owner_id == $group_array['account_id'])
906					{
907						$group_ok = 1;
908						break;
909					}
910				}
911			}
912
913			$acl = CreateObject ('phpgwapi.acl', $owner_id);
914			$acl->account_id = $owner_id;
915			$acl->read_repository ();
916
917			$rights = $acl->get_rights ($user_id);
918
919			/* Add privileges from the groups this user belongs to */
920			if (is_array ($memberships))
921			{
922				reset ($memberships);
923				while (list ($num, $group_array) = each ($memberships))
924				{
925					$rights |= $acl->get_rights ($group_array['account_id']);
926				}
927			}
928
929			if ($rights & $data['operation'])
930			{
931				return True;
932			}
933			elseif (!$rights && $group_ok)
934			{
935				$conf = CreateObject('phpgwapi.config', 'phpgwapi');
936				$conf->read_repository();
937				if ($conf->config_data['acl_default'] == 'grant')
938				{
939					return True;
940				}
941				else
942				{
943					return False;
944				}
945			}
946			else
947			{
948				return False;
949			}
950		}
951
952		/*!
953		@function cd
954		@abstract Change directory
955		@discussion To cd to the files root '/', use cd ('/', False, array (RELATIVE_NONE));
956		@param string default '/'.  directory to cd into.  if "/" and $relative is True, uses "/home/<working_lid>";
957		@param relative default True/relative means add target to current path, else pass $relative as mask to getabsolutepath()
958		@param relatives Relativity array
959		*/
960		function cd ($data = '')
961		{
962			if (!is_array ($data))
963			{
964				$noargs = 1;
965				$data = array ();
966			}
967
968			$default_values = array
969				(
970					'string'	=> '/',
971					'relative'	=> True,
972					'relatives'	=> array (RELATIVE_CURRENT)
973				);
974
975			$data = array_merge ($this->default_values ($data, $default_values), $data);
976
977			if ($data['relatives'][0] & VFS_REAL)
978			{
979				$sep = SEP;
980			}
981			else
982			{
983				$sep = '/';
984			}
985
986			if ($data['relative'] == 'relative' || $data['relative'] == True)
987			{
988				/* if 'string' is "/" and 'relative' is set, we cd to the user/group home dir */
989				if ($data['string'] == '/')
990				{
991					$data['relatives'][0] = RELATIVE_USER;
992					$basedir = $this->getabsolutepath (array(
993							'string'	=> False,
994							'mask'	=> array ($data['relatives'][0]),
995							'fake'	=> True
996						)
997					);
998				}
999				else
1000				{
1001					$currentdir = $GLOBALS['phpgw']->session->appsession('vfs','');
1002					$basedir = $this->getabsolutepath (array(
1003							'string'	=> $currentdir . $sep . $data['string'],
1004							'mask'	=> array ($data['relatives'][0]),
1005							'fake'	=> True
1006						)
1007					);
1008				}
1009			}
1010			else
1011			{
1012				$basedir = $this->getabsolutepath (array(
1013						'string'	=> $data['string'],
1014						'mask'	=> array ($data['relatives'][0])
1015					)
1016				);
1017			}
1018
1019			$GLOBALS['phpgw']->session->appsession('vfs','',$basedir);
1020
1021			return True;
1022		}
1023
1024		/*!
1025		@function pwd
1026		@abstract current working dir
1027		@param full default True returns full fake path, else just the extra dirs (false strips the leading /)
1028		@result $currentdir currentdir
1029		*/
1030		function pwd ($data = '')
1031		{
1032			$default_values = array
1033				(
1034					'full'	=> True
1035				);
1036
1037			$data = array_merge ($this->default_values ($data, $default_values), $data);
1038
1039			$currentdir = $GLOBALS['phpgw']->session->appsession('vfs','');
1040
1041			if (!$data['full'])
1042			{
1043				$currentdir = ereg_replace ("^/", '', $currentdir);
1044			}
1045
1046			if ($currentdir == '' && $data['full'])
1047			{
1048				$currentdir = '/';
1049			}
1050
1051			$currentdir = trim ($currentdir);
1052
1053			return $currentdir;
1054		}
1055
1056		/*!
1057		@function read
1058		@abstract return file contents
1059		@param string filename
1060		@param relatives Relativity array
1061		@result $contents Contents of $file, or False if file cannot be read
1062		*/
1063		function read ($data)
1064		{
1065
1066			/*If the user really wants to 'view' the file in the browser, it
1067			is much smarter simply to redirect them to the files web-accessable
1068			url */
1069/*			$app = $GLOBALS['phpgw_info']['flags']['currentapp'];
1070			if ( ! $data['noview'] && ($app == 'phpwebhosting' || $app = 'filemanager' ))
1071			{
1072				$this->view($data);
1073			}
1074*/
1075			if (!is_array ($data))
1076			{
1077				$data = array ();
1078			}
1079
1080			$default_values = array
1081				(
1082					'relatives'	=> array (RELATIVE_CURRENT)
1083				);
1084
1085			$data = array_merge ($this->default_values ($data, $default_values), $data);
1086
1087			$p = $this->path_parts (array(
1088					'string'	=> $data['string'],
1089					'relatives'	=> array ($data['relatives'][0])
1090				)
1091			);
1092
1093			if (!$this->acl_check (array(
1094					'string'	=> $p->fake_full_path,
1095					'relatives'	=> array ($p->mask),
1096					'operation'	=> PHPGW_ACL_READ
1097				))
1098			)
1099			{
1100				return False;
1101			}
1102			if ($p->outside)
1103			{
1104
1105			    if (! $fp = fopen ($p->real_full_path, 'r'))
1106			    {
1107			    	return False;
1108			    }
1109			    $size=filesize($p->real_full_path);
1110			    $buffer=fread($fp, $size);
1111			    fclose ($fp);
1112				return $buffer;
1113			}
1114			else
1115			{
1116				$status=$this->dav_client->get($p->real_full_path);
1117	$this->debug($this->dav_client->get_headers());
1118
1119				if($status != 200) return False;
1120				$contents=$this->dav_client->get_body();
1121	$this->debug('Read:returning contents.  Status:'.$status);
1122				return $contents;
1123			}
1124		}
1125
1126		/*
1127		@function view
1128		@abstract Redirect the users browser to the file
1129		@param string filename
1130		@param relatives Relativity array
1131		@result None (doesnt return)
1132		@discussion In the case of WebDAV, the file is web-accessible.  So instead
1133		of reading it into memory and then dumping it back out again when someone
1134		views a file, it makes much more sense to simply redirect, which is what
1135		this method does (its only called when reading from the file in the file manager,
1136		when the variable "noview" isnt set to "true"
1137		*/
1138		function view($data)
1139		{
1140
1141			$default_values = array
1142				(
1143					'relatives'	=> array (RELATIVE_CURRENT)
1144				);
1145			$data = array_merge ($this->default_values ($data, $default_values), $data);
1146			$p = $this->path_parts (array(
1147					'string'	=> $data['string'],
1148					'relatives'	=> array ($data['relatives'][0])
1149				)
1150			);
1151
1152			//Determine whether the repository supports SSL
1153			$parsed_url = parse_url($this->repository);
1154			if ($parsed_url['scheme']=='https')
1155			{
1156				header( 'Location: '.$p->real_full_secure_url, true );
1157			}
1158			else
1159			{
1160				header( 'Location: '.$p->real_full_auth_url, true );
1161			}
1162			exit();
1163
1164		}
1165
1166		/*
1167		@function lock
1168		@abstract DAV (class 2) locking - sets an exclusive write lock
1169		@param string filename
1170		@param relatives Relativity array
1171		@result True if successfull
1172		*/
1173		function lock ($data)
1174		{
1175			$default_values = array
1176				(
1177					'relatives'	=> array (RELATIVE_CURRENT),
1178					'timeout'	=> 'infinity'
1179
1180				);
1181
1182			$data = array_merge($this->default_values($data,$default_values),$data);
1183
1184			$p = $this->path_parts (array(
1185					'string'	=> $data['string'],
1186					'relatives'	=> array ($data['relatives'][0])
1187				)
1188			);
1189			return $this->dav_client->lock($p->real_full_url, $this->dav_user, 0, $data['timeout']);
1190
1191		}
1192		function lock_token ($data)
1193		{
1194			$default_values = array
1195				(
1196					'relatives'	=> array (RELATIVE_CURRENT),
1197					'token' => ''
1198				);
1199
1200			$data = array_merge ($this->default_values ($data, $default_values), $data);
1201
1202			$ls_array = $GLOBALS['phpgw']->vfs->ls (array (
1203			'string'	=> $data['string'],
1204			'relatives'	=> $data['relatives']
1205			)
1206			);
1207			 $lock = @end($ls_array[0]['locks']);
1208			 $token = @end($lock['lock_tokens']);
1209			 return $token['full_name'];
1210		}
1211
1212
1213		/*
1214		@function add_lock_override
1215		@abstract override a lock
1216		@param string filename
1217		@param relatives Relativity array
1218		@param token (optional) a token for the lock we want to override
1219		@result None
1220		@discussion locks are no good unless you can write to a file you yourself locked:
1221		to do this call add_lock_override with the lock token (or without it - it will
1222		find it itself, so long as there is only one).  lock_override info is stored in
1223		the groupware session, so it will persist between page loads, but will be lost
1224		when the browser is closed
1225		*/
1226		function add_lock_override($data)
1227		{
1228			$default_values = array
1229			(
1230				'relatives'	=> array (RELATIVE_CURRENT),
1231				'token' => ''
1232
1233			);
1234
1235			$data = array_merge ($this->default_values ($data, $default_values), $data);
1236
1237			if (!strlen($data['token']))
1238			{
1239				$ls_array = $GLOBALS['phpgw']->vfs->ls (array (
1240				'string'	=> $data['string'],
1241				'relatives'	=> $data['relatives']
1242				)
1243				);
1244				 $lock = @end($ls_array[0]['locks']);
1245				 $token_array = @end($lock['lock_tokens']);
1246				 $token =  $token_array['full_name'];
1247			}
1248			else
1249			{
1250				$token = $data['token'];
1251			}
1252
1253			$p = $this->path_parts (array(
1254					'string'	=> $data['string'],
1255					'relatives'	=> array ($data['relatives'][0])
1256				)
1257			);
1258			$this->override_locks[$p->real_full_path] = $token;
1259			$this->save_session();
1260		}
1261
1262		/*
1263		@function remove_lock_override
1264		@abstract stops overriding a lock
1265		@param string filename
1266		@param relatives Relativity array
1267		@result None
1268		*/
1269		function remove_lock_override($data)
1270		{
1271			$default_values = array
1272			(
1273				'relatives'	=> array (RELATIVE_CURRENT)
1274
1275			);
1276
1277			$data = array_merge ($this->default_values ($data, $default_values), $data);
1278
1279			if (!strlen($data['token']))
1280			{
1281				$ls_array = $GLOBALS['phpgw']->vfs->ls (array (
1282				'string'	=> $data['string'],
1283				'relatives'	=> $data['relatives']
1284				)
1285				);
1286				 $lock = @end($ls_array[0]['locks']);
1287				 $token_array = @end($lock['lock_tokens']);
1288				 $token =  $token_array['full_name'];
1289			}
1290			else
1291			{
1292				$token = $data['token'];
1293			}
1294
1295			$p = $this->path_parts (array(
1296					'string'	=> $data['string'],
1297					'relatives'	=> array ($data['relatives'][0])
1298				)
1299			);
1300			unset($this->override_locks[$p->real_full_path]);
1301			$this->save_session();
1302		}
1303
1304		/*
1305		@function unlock
1306		@abstract DAV (class 2) unlocking - unsets the specified lock
1307		@param string filename
1308		@param relatives Relativity array
1309		@param tocken	The token for the lock we wish to remove.
1310		@result True if successfull
1311		*/
1312		function unlock ($data, $token)
1313		{
1314			$default_values = array
1315				(
1316					'relatives'	=> array (RELATIVE_CURRENT),
1317					'content'	=> ''
1318				);
1319
1320			$data = array_merge ($this->default_values ($data, $default_values), $data);
1321
1322			$p = $this->path_parts (array(
1323					'string'	=> $data['string'],
1324					'relatives'	=> array ($data['relatives'][0])
1325				)
1326			);
1327			$this->remove_lock_override (array(
1328					'string'	=> $data['string'],
1329					'relatives'	=> array ($data['relatives'][0])
1330				)
1331			);
1332			return $this->dav_client->unlock($p->real_full_url, $token);
1333
1334
1335		}
1336
1337		/*
1338		@function options
1339		@abstract Allows querying for optional features - esp optional DAV features
1340		like locking
1341		@param option	The option you want to test for.  Options include 'LOCKING'
1342			'VIEW', 'VERSION-CONTROL (eventually) etc
1343		@result true if the specified option is supported
1344		@discussion This should really check the server.  Unfortunately the overhead of doing this
1345		in every VFS instance is unacceptable (it essentially doubles the time for any request). Ideally
1346		we would store these features in the session perhaps?
1347		*/
1348		function options($option)
1349		{
1350			switch ($option)
1351			{
1352			case 'LOCKING':
1353				return true;
1354			case 'VIEW':
1355				return true;
1356			default:
1357				return false;
1358			}
1359		}
1360
1361		/*!
1362		@function write
1363		@abstract write to a file
1364		@param string file name
1365		@param relatives Relativity array
1366		@param content content
1367		@result Boolean True/False
1368		*/
1369		function write ($data)
1370		{
1371			$default_values = array
1372				(
1373					'relatives'	=> array (RELATIVE_CURRENT),
1374					'content'	=> ''
1375				);
1376
1377			$data = array_merge ($this->default_values ($data, $default_values), $data);
1378
1379			$p = $this->path_parts (array(
1380					'string'	=> $data['string'],
1381					'relatives'	=> array ($data['relatives'][0])
1382				)
1383			);
1384
1385			if ($this->file_exists (array (
1386					'string'	=> $data['string'],
1387					'relatives'	=> array ($data['relatives'][0])
1388				))
1389			)
1390			{
1391				$acl_operation = PHPGW_ACL_EDIT;
1392				$journal_operation = VFS_OPERATION_EDITED;
1393			}
1394			else
1395			{
1396				$acl_operation = PHPGW_ACL_ADD;
1397			}
1398
1399			if (!$this->acl_check (array(
1400					'string'	=> $p->fake_full_path,
1401					'relatives'	=> array ($p->mask),
1402					'operation'	=> $acl_operation
1403				))
1404			)
1405			{
1406				return False;
1407			}
1408
1409			//umask(000);
1410
1411			/*
1412			   If 'string' doesn't exist, touch () creates both the file and the database entry
1413			   If 'string' does exist, touch () sets the modification time and modified by
1414			*/
1415			/*$this->touch (array(
1416					'string'	=> $p->fake_full_path,
1417					'relatives'	=> array ($p->mask)
1418				)
1419			);*/
1420
1421			$size=strlen($data['content']);
1422			if ($p->outside)
1423			{
1424			    if (! $fp = fopen ($p->real_full_path, 'w'))
1425			    {
1426			    	return False;
1427			    }
1428			    $result = fwrite($fp, $data['content']);
1429			    fclose ($fp);
1430			    return $result;
1431			}
1432			else
1433			{
1434				$token =  $this->override_locks[$p->real_full_path];
1435				$status=$this->dav_client->put($p->real_full_path,$data['content'],$token);
1436$this->debug('Put complete,  status: '.$status);
1437				if($status!=201 && $status!=204)
1438				{
1439					return False;
1440				}
1441				else
1442				{
1443					return True;
1444				}
1445			}
1446		}
1447
1448		/*!
1449		@function touch
1450		@abstract Create blank file $file or set the modification time and modified by of $file to current time and user
1451		@param string File to touch or set modifies
1452		@param relatives Relativity array
1453		@result Boolean True/False
1454		*/
1455		function touch ($data)
1456		{
1457			$default_values = array(
1458						'relatives'	=> array (RELATIVE_CURRENT)
1459						);
1460			$data = array_merge ($this->default_values ($data, $default_values), $data);
1461
1462			$account_id = $GLOBALS['phpgw_info']['user']['account_id'];
1463			$currentapp = $GLOBALS['phpgw_info']['flags']['currentapp'];
1464
1465			$p = $this->path_parts (array(
1466						      'string'	=> $data['string'],
1467						      'relatives'	=> array ($data['relatives'][0])
1468						      )
1469						);
1470			umask (000);
1471
1472			/*
1473			   PHP's touch function will automatically decide whether to
1474			   create the file or set the modification time
1475			*/
1476			if($p->outside)
1477			{
1478			  return @touch($p->real_full_path);
1479			}
1480			elseif ($this->file_exists (array(
1481					'string'	=> $p->fake_full_path,
1482					'relatives'	=> array ($p->mask)
1483				))
1484			)
1485			{
1486				$result =  $this->set_attributes (array(
1487						'string'	=> $p->fake_full_path,
1488						'relatives'	=> array ($p->mask),
1489						'attributes'	=> array(
1490									'modifiedby_id' => $account_id,
1491									'modified' => $this->now
1492						)));
1493			}
1494			else
1495			{
1496				if (!$this->acl_check (array(
1497						'string'	=> $p->fake_full_path,
1498						'relatives'	=> array ($p->mask),
1499						'operation'	=> PHPGW_ACL_ADD
1500					))
1501				) return False;
1502				$result = $this->write (array(
1503						      'string'	=> $data['string'],
1504						      'relatives'	=> array ($data['relatives'][0]),
1505						      'content' => ''
1506						      ));
1507				$this->set_attributes(array(
1508					'string'	=> $p->fake_full_path,
1509					'relatives'	=> array ($p->mask),
1510					'attributes'	=> array (
1511								'createdby_id' => $account_id,
1512								'created' => $this->now,
1513								'app' => $currentapp
1514							)));
1515			}
1516
1517			return ($result);
1518		}
1519
1520		/*!
1521		@function cp
1522		@abstract copy file
1523		@param from from file/directory
1524		@param to to file/directory
1525		@param relatives Relativity array
1526		@result boolean True/False
1527		*/
1528		function cp ($data)
1529		{
1530			$default_values = array
1531				(
1532					'relatives'	=> array (RELATIVE_CURRENT, RELATIVE_CURRENT)
1533				);
1534
1535			$data = array_merge ($this->default_values ($data, $default_values), $data);
1536
1537			$account_id = $GLOBALS['phpgw_info']['user']['account_id'];
1538
1539			$f = $this->path_parts (array(
1540					'string'	=> $data['from'],
1541					'relatives'	=> array ($data['relatives'][0])
1542				)
1543			);
1544
1545			$t = $this->path_parts (array(
1546					'string'	=> $data['to'],
1547					'relatives'	=> array ($data['relatives'][1])
1548				)
1549			);
1550
1551			if (!$this->acl_check (array(
1552					'string'	=> $f->fake_full_path,
1553					'relatives'	=> array ($f->mask),
1554					'operation'	=> PHPGW_ACL_READ
1555				))
1556			)
1557			{
1558				return False;
1559			}
1560
1561			if ($this->file_exists (array(
1562					'string'	=> $t->fake_full_path,
1563					'relatives'	=> array ($t->mask)
1564				))
1565			)
1566			{
1567				$remote_operation=PHPGW_ACL_EDIT;
1568			}
1569			else
1570			{
1571				$remote_operation=PHPGW_ACL_ADD;
1572
1573			}
1574			if (!$this->acl_check (array(
1575						     'string'	=> $t->fake_full_path,
1576						     'relatives'	=> array ($t->mask),
1577						     'operation'	=> $remote_operation
1578						     ))
1579			    )
1580			{
1581				return False;
1582			}
1583
1584			umask(000);
1585
1586			if ($this->file_type (array(
1587					'string'	=> $f->fake_full_path,
1588					'relatives'	=> array ($f->mask)
1589				)) != 'Directory'
1590			)
1591			{
1592
1593				if ($f->outside && $t->outside)
1594				{
1595					return copy($f->real_full_path, $t->real_full_url);
1596				}
1597				elseif ($f->outside || $t->outside)
1598				{
1599			      	$content = $this->read(array(
1600						'string'	=> $f->fake_full_path,
1601						'noview' => true,
1602						'relatives'	=> array ($f->mask)
1603						)
1604					);
1605					$result = $this->write(array(
1606						'string'	=> $t->fake_full_path,
1607						'relatives'	=> array ($t->mask),
1608						'content' => $content
1609						)
1610					);
1611			    }
1612				else
1613				{
1614				    $status=$this->dav_client->copy($f->real_full_path, $t->real_full_url,True, 'Infinity', $this->override_locks[$p->real_full_path]);
1615				    $result = $status == 204 || $status==201;
1616				    if (!$result)
1617				    {
1618				    	return False;
1619				    }
1620			 	 }
1621
1622				$this->set_attributes(array(
1623					'string'	=> $t->fake_full_path,
1624					'relatives'	=> array ($t->mask),
1625					'attributes' => array (
1626								'owner_id' => $this->working_id,
1627								'createdby_id' => $account_id
1628							)
1629						)
1630					);
1631				return $result;
1632
1633			}
1634			else if (!($f->outside || $t->outside))
1635			{
1636				//if the files are both on server, its just a depth=infinity copy
1637				$status=$this->dav_client->copy($f->real_full_path, $t->real_full_url,True, 'infinity', $this->override_locks[$p->real_full_path]);
1638			    if($status != 204 && $status!=201)
1639			    {
1640			    	return False;
1641			    }
1642			    else
1643			    {
1644			    	return True;
1645			    }
1646			}
1647			else	/* It's a directory, and one of the files is local */
1648			{
1649				/* First, make the initial directory */
1650				if ($this->mkdir (array(
1651						'string'	=> $data['to'],
1652						'relatives'	=> array ($data['relatives'][1])
1653					)) === False
1654				)
1655				{
1656					return False;
1657				}
1658
1659				/* Next, we create all the directories below the initial directory */
1660				$ls = $this->ls (array(
1661						'string'	=> $f->fake_full_path,
1662						'relatives'	=> array ($f->mask),
1663						'checksubdirs'	=> True,
1664						'mime_type'	=> 'Directory'
1665					)
1666				);
1667
1668				while (list ($num, $entry) = each ($ls))
1669				{
1670					$newdir = ereg_replace ("^$f->fake_full_path", "$t->fake_full_path", $entry['directory']);
1671					if ($this->mkdir (array(
1672							'string'	=> $newdir.'/'.$entry['name'],
1673							'relatives'	=> array ($t->mask)
1674						)) === False
1675					)
1676					{
1677						return False;
1678					}
1679				}
1680
1681				/* Lastly, we copy the files over */
1682				$ls = $this->ls (array(
1683						'string'	=> $f->fake_full_path,
1684						'relatives'	=> array ($f->mask)
1685					)
1686				);
1687
1688				while (list ($num, $entry) = each ($ls))
1689				{
1690					if ($entry['mime_type'] == 'Directory')
1691					{
1692						continue;
1693					}
1694
1695					$newdir = ereg_replace ("^$f->fake_full_path", "$t->fake_full_path", $entry['directory']);
1696					$this->cp (array(
1697							'from'	=> "$entry[directory]/$entry[name]",
1698							'to'	=> "$newdir/$entry[name]",
1699							'relatives'	=> array ($f->mask, $t->mask)
1700						)
1701					);
1702				}
1703			}
1704
1705			return True;
1706		}
1707
1708		function copy ($data)
1709		{
1710			return $this->cp ($data);
1711		}
1712
1713		/*!
1714		@function mv
1715		@abstract move file/directory
1716		@param from from file/directory
1717		@param to to file/directory
1718		@param relatives Relativity array
1719		@result boolean True/False
1720		*/
1721		function mv ($data)
1722		{
1723			$default_values = array
1724				(
1725					'relatives'	=> array (RELATIVE_CURRENT, RELATIVE_CURRENT)
1726				);
1727
1728			$data = array_merge ($this->default_values ($data, $default_values), $data);
1729
1730			$account_id = $GLOBALS['phpgw_info']['user']['account_id'];
1731
1732			$f = $this->path_parts (array(
1733					'string'	=> $data['from'],
1734					'relatives'	=> array ($data['relatives'][0])
1735				)
1736			);
1737
1738			$t = $this->path_parts (array(
1739					'string'	=> $data['to'],
1740					'relatives'	=> array ($data['relatives'][1])
1741				)
1742			);
1743
1744			if (!$this->acl_check (array(
1745					'string'	=> $f->fake_full_path,
1746					'relatives'	=> array ($f->mask),
1747					'operation'	=> PHPGW_ACL_READ
1748				))
1749				|| !$this->acl_check (array(
1750					'string'	=> $f->fake_full_path,
1751					'relatives'	=> array ($f->mask),
1752					'operation'	=> PHPGW_ACL_DELETE
1753				))
1754			)
1755			{
1756				return False;
1757			}
1758
1759			if (!$this->acl_check (array(
1760					'string'	=> $t->fake_full_path,
1761					'relatives'	=> array ($t->mask),
1762					'operation'	=> PHPGW_ACL_ADD
1763				))
1764			)
1765			{
1766				return False;
1767			}
1768
1769			if ($this->file_exists (array(
1770					'string'	=> $t->fake_full_path,
1771					'relatives'	=> array ($t->mask)
1772				))
1773			)
1774			{
1775				if (!$this->acl_check (array(
1776						'string'	=> $t->fake_full_path,
1777						'relatives'	=> array ($t->mask),
1778						'operation'	=> PHPGW_ACL_EDIT
1779					))
1780				)
1781				{
1782					return False;
1783				}
1784			}
1785			umask (000);
1786
1787			/* We can't move directories into themselves */
1788			if (($this->file_type (array(
1789					'string'	=> $f->fake_full_path,
1790					'relatives'	=> array ($f->mask)
1791				) == 'Directory'))
1792				&& ereg ("^$f->fake_full_path", $t->fake_full_path)
1793			)
1794			{
1795				if (($t->fake_full_path == $f->fake_full_path) || substr ($t->fake_full_path, strlen ($f->fake_full_path), 1) == '/')
1796				{
1797					return False;
1798				}
1799			}
1800
1801			if ($this->file_exists (array(
1802					'string'	=> $f->fake_full_path,
1803					'relatives'	=> array ($f->mask)
1804				))
1805			)
1806			{
1807				/* We get the listing now, because it will change after we update the database */
1808				$ls = $this->ls (array(
1809						'string'	=> $f->fake_full_path,
1810						'relatives'	=> array ($f->mask)
1811					)
1812				);
1813
1814				if ($this->file_exists (array(
1815						'string'	=> $t->fake_full_path,
1816						'relatives'	=> array ($t->mask)
1817					))
1818				)
1819				{
1820					$this->rm (array(
1821							'string'	=> $t->fake_full_path,
1822							'relatives'	=> array ($t->mask)
1823						)
1824					);
1825				}
1826
1827				$this->correct_attributes (array(
1828						'string'	=> $t->fake_full_path,
1829						'relatives'	=> array ($t->mask)
1830					)
1831				);
1832
1833				if ($f->outside && $t->outside)
1834				{
1835					echo 'local';
1836					$result = rename ($f->real_full_path, $t->real_full_path);
1837				}
1838				else if ($f->outside || $t->outside) //if either file is local, read then write
1839				{
1840					$content = $this->read(array(
1841						'string'	=> $f->fake_full_path,
1842						'noview' => true,
1843						'relatives'	=> array ($f->mask)
1844						)
1845					);
1846					$result = $this->write(array(
1847						'string'	=> $t->fake_full_path,
1848						'relatives'	=> array ($t->mask),
1849						'content' => $content
1850						)
1851					);
1852					if ($result)
1853					{
1854						$result = $this->rm(array(
1855							'string'	=> $f->fake_full_path,
1856							'relatives'	=> array ($f->mask),
1857							'content' => $content
1858							)
1859						);
1860					}
1861				}
1862				else {  //we can do a server-side copy if both files are on the server
1863					$status=$this->dav_client->move($f->real_full_path, $t->real_full_url,True, 'infinity', $this->override_locks[$p->real_full_path]);
1864			        $result = ($status==201 || $status==204);
1865				}
1866
1867				if ($result) $this->set_attributes(array(
1868						'string'	=> $t->fake_full_path,
1869						'relatives'	=> array ($t->mask),
1870						'attributes'	=> array (
1871									'modifiedby_id' => $account_id,
1872									'modified' => $this->now
1873								)));
1874				return $result;
1875			}
1876			else
1877			{
1878				return False;
1879			}
1880
1881			$this->add_journal (array(
1882					'string'	=> $t->fake_full_path,
1883					'relatives'	=> array ($t->mask),
1884					'operation'	=> VFS_OPERATION_MOVED,
1885					'state_one'	=> $f->fake_full_path,
1886					'state_two'	=> $t->fake_full_path
1887				)
1888			);
1889
1890			return True;
1891		}
1892
1893		/*!
1894		@function move
1895		@abstract shortcut to mv
1896		*/
1897		function move ($data)
1898		{
1899			return $this->mv ($data);
1900		}
1901
1902		/*!
1903		@function rm
1904		@abstract delete file/directory
1905		@param string file/directory to delete
1906		@param relatives Relativity array
1907		@result boolean True/False
1908		*/
1909		function rm ($data)
1910		{
1911			$default_values = array
1912				(
1913					'relatives'	=> array (RELATIVE_CURRENT)
1914				);
1915
1916			$data = array_merge ($this->default_values ($data, $default_values), $data);
1917			$p = $this->path_parts (array(
1918					'string'	=> $data['string'],
1919					'relatives'	=> array ($data['relatives'][0])
1920				)
1921			);
1922			$this->debug("rm: $p->real_full_path");
1923			if (!$this->acl_check (array(
1924					'string'	=> $p->fake_full_path,
1925					'relatives'	=> array ($p->mask),
1926					'operation'	=> PHPGW_ACL_DELETE
1927				))
1928			)
1929			{
1930				return False;
1931			}
1932
1933/*this would become apparent soon enough anyway?
1934			if (!$this->file_exists (array(
1935					'string'	=> $data['string'],
1936					'relatives'	=> array ($data['relatives'][0])
1937				))
1938			) return False;
1939*/
1940			if ($this->file_type (array(
1941					'string'	=> $data['string'],
1942					'relatives'	=> array ($data['relatives'][0])
1943				)) != 'Directory'
1944			)
1945			{
1946				if ($p->outside)
1947				{
1948					return unlink($p->real_full_path);
1949				}
1950				else
1951				{
1952					$rr=$this->dav_client->delete($p->real_full_path, 0, $this->override_locks[$p->real_full_path]);
1953					return $rr == 204;
1954				}
1955			}
1956			else
1957			{
1958				$ls = $this->ls (array(
1959						'string'	=> $p->fake_full_path,
1960						'relatives'	=> array ($p->mask)
1961					)
1962				);
1963
1964				while (list ($num, $entry) = each ($ls))
1965				{
1966					$this->rm (array(
1967							'string'	=> "$entry[directory]/$entry[name]",
1968							'relatives'	=> array ($p->mask)
1969						)
1970					);
1971				}
1972
1973				/* If the directory is linked, we delete the placeholder directory */
1974				$ls_array = $this->ls (array(
1975						'string'	=> $p->fake_full_path,
1976						'relatives'	=> array ($p->mask),
1977						'checksubdirs'	=> False,
1978						'mime_type'	=> False,
1979						'nofiles'	=> True
1980					)
1981				);
1982				$link_info = $ls_array[0];
1983
1984				if ($link_info['link_directory'] && $link_info['link_name'])
1985				{
1986					$path = $this->path_parts (array(
1987							'string'	=> $link_info['directory'] . '/' . $link_info['name'],
1988							'relatives'	=> array ($p->mask),
1989							'nolinks'	=> True
1990						)
1991					);
1992					$this->dav_client->delete($path->real_full_path,0, $this->override_locks[$p->real_full_path]);
1993				}
1994
1995				/* Last, we delete the directory itself */
1996				$this->add_journal (array(
1997						'string'	=> $p->fake_full_path,
1998						'relatives'	=> array ($p->mask),
1999						'operaton'	=> VFS_OPERATION_DELETED
2000					)
2001				);
2002
2003				$query = $GLOBALS['phpgw']->db->query ("DELETE FROM phpgw_vfs WHERE directory='$p->fake_leading_dirs_clean' AND name='$p->fake_name_clean'" . $this->extra_sql (array ('query_type' => VFS_SQL_DELETE)), __LINE__, __FILE__);
2004
2005				//rmdir ($p->real_full_path);
2006				$this->dav_client->delete($p->real_full_path.'/','Infinity', $this->override_locks[$p->real_full_path]);
2007
2008				return True;
2009			}
2010		}
2011
2012		/*!
2013		@function delete
2014		@abstract shortcut to rm
2015		*/
2016		function delete ($data)
2017		{
2018			return $this->rm ($data);
2019		}
2020
2021		/*!
2022		@function mkdir
2023		@abstract make a new directory
2024		@param string Directory name
2025		@param relatives Relativity array
2026		@result boolean True on success
2027		*/
2028		function mkdir ($data)
2029		{
2030			if (!is_array ($data))
2031			{
2032				$data = array ();
2033			}
2034
2035			$default_values = array
2036				(
2037					'relatives'	=> array (RELATIVE_CURRENT)
2038				);
2039
2040			$data = array_merge ($this->default_values ($data, $default_values), $data);
2041
2042			$account_id = $GLOBALS['phpgw_info']['user']['account_id'];
2043			$currentapp = $GLOBALS['phpgw_info']['flags']['currentapp'];
2044
2045			$p = $this->path_parts (array(
2046					'string'	=> $data['string'],
2047					'relatives'	=> array ($data['relatives'][0])
2048				)
2049			);
2050
2051			if (!$this->acl_check (array(
2052					'string'	=> $p->fake_full_path,
2053					'relatives'	=> array ($p->mask),
2054					'operation'	=> PHPGW_ACL_ADD)
2055				)
2056			)
2057			{
2058				return False;
2059			}
2060
2061			/* We don't allow /'s in dir names, of course */
2062			if (ereg ('/', $p->fake_name))
2063			{
2064				return False;
2065			}
2066			if ($p->outside)
2067			{
2068				if (file_exists($p->real_full_path))
2069				{
2070					if (!is_dir($p->real_full_path))
2071					{
2072						return False;
2073					}
2074				}
2075				elseif (!mkdir($p->real_full_path, 0777))
2076				{
2077					return False;
2078				}
2079			}
2080			else if($this->dav_client->mkcol($p->real_full_path, $this->override_locks[$p->real_full_path]) != 201)
2081			{
2082				return False;
2083			}
2084
2085
2086			if ($this->file_exists (array(
2087					'string'	=> $p->fake_full_path
2088				))
2089			)
2090			{
2091				/*Now we need to set access control for this dir.  Simply create an .htaccess
2092				file limiting access to this user, if we are creating this dir in the user's home dir*/
2093				$homedir = $this->fakebase.'/'.$this->dav_user;
2094				if ( substr($p->fake_leading_dirs, 0, strlen($homedir)) == $homedir)
2095				{
2096					$conf = CreateObject('phpgwapi.config', 'phpgwapi');
2097					$conf->read_repository();
2098					if ($conf->config_data['acl_default'] != 'grant')
2099					{
2100						$htaccess = 'require user '.$GLOBALS['phpgw_info']['user']['account_lid'];
2101						if ( ! $this->write(array(
2102								'string' =>  $p->fake_full_path.'/.htaccess',
2103								'content' => $htaccess,
2104								'relatives' => array(RELATIVE_NONE)
2105							)))
2106						{
2107							echo '<p><b>Unable to write .htaccess file</b></p></b>';
2108						};
2109					}
2110				}
2111				return True;
2112			}
2113			else
2114			{
2115				return False;
2116			}
2117		}
2118
2119		/*!
2120		@function make_link
2121		@abstract Make a link from virtual directory 'vdir' to real directory 'rdir'
2122		@discussion Making a link from 'vdir' to 'rdir' will cause path_parts () to substitute 'rdir' for the real
2123				path variables when presented with 'vdir'
2124		@param vdir Virtual dir to make link from
2125		@param rdir Real dir to make link to
2126		@param relatives Relativity array
2127		@result Boolean True/False
2128		*/
2129		function make_link ($data)
2130		{
2131			return False; //This code certainly wont work anymore.  Does anything use it?
2132		/*
2133			$default_values = array
2134				(
2135					'relatives'	=> array (RELATIVE_CURRENT, RELATIVE_CURRENT)
2136				);
2137
2138			$data = array_merge ($this->default_values ($data, $default_values), $data);
2139
2140			$account_id = $GLOBALS['phpgw_info']['user']['account_id'];
2141			$currentapp = $GLOBALS['phpgw_info']['flags']['currentapp'];
2142
2143			$vp = $this->path_parts (array(
2144					'string'	=> $data['vdir'],
2145					'relatives'	=> array ($data['relatives'][0])
2146				)
2147			);
2148
2149			$rp = $this->path_parts (array(
2150					'string'	=> $data['rdir'],
2151					'relatives'	=> array ($data['relatives'][1])
2152				)
2153			);
2154
2155			if (!$this->acl_check (array(
2156					'string'	=> $vp->fake_full_path,
2157					'relatives'	=> array ($vp->mask),
2158					'operation'	=> PHPGW_ACL_ADD
2159				))
2160			) return False;
2161
2162			if ($this->file_exists (array(
2163					'string'	=> $rp->real_full_path,
2164					'relatives'	=> array ($rp->mask)
2165			)))
2166			{
2167				if (!is_dir($rp->real_full_path))
2168				{
2169					return False;
2170				}
2171			}
2172			elseif (!mkdir ($rp->real_full_path, 0770))
2173			{
2174				return False;
2175			}
2176
2177			if (!$this->mkdir (array(
2178					'string'	=> $vp->fake_full_path,
2179					'relatives'	=> array ($vp->mask)
2180				))
2181			)return False;
2182
2183			$size = $this->get_size (array(
2184					'string'	=> $rp->real_full_path,
2185					'relatives'	=> array ($rp->mask)
2186				)
2187			);
2188
2189			$this->set_attributes(array(
2190					'string'	=> $vp->fake_full_path,
2191					'relatives'	=> array ($vp->mask),
2192					'attributes'	=> array (
2193								'link_directory' => $rp->real_leading_dirs,
2194								'link_name' => $rp->real_name,
2195								'size' => $size
2196							)
2197				)
2198			);
2199
2200			$this->correct_attributes (array(
2201					'string'	=> $vp->fake_full_path,
2202					'relatives'	=> array ($vp->mask)
2203				)
2204			);
2205
2206			return True;
2207	*/
2208		}
2209
2210		/*!
2211		@function set_attributes
2212		@abstract Update database entry for 'string' with the attributes in 'attributes'
2213		@param string file/directory to update
2214		@param relatives Relativity array
2215		@param attributes keyed array of attributes.  key is attribute name, value is attribute value
2216		@result Boolean True/False
2217		@discussion Valid attributes are:
2218				owner_id
2219				createdby_id
2220				modifiedby_id
2221				created
2222				modified
2223				size
2224				mime_type
2225				deleteable
2226				comment
2227				app
2228				link_directory
2229				link_name
2230				version
2231				name
2232				directory
2233		*/
2234		function set_attributes ($data,$operation=PHPGW_ACL_EDIT)
2235		{
2236			/*To get much benefit out of DAV properties we should use
2237			some sensible XML namespace.  We will use the Dublin Core
2238			metadata specification (http://dublincore.org/) here where
2239			we can*/
2240			$p = $this->path_parts (array(
2241				'string'	=> $data['string'],
2242				'relatives'	=> array ($data['relatives'][0])
2243				));
2244			$dav_properties = array();
2245			$lid=''; $fname = ''; $lname='';
2246			if ($data['attributes']['comment'])
2247			{
2248				$dav_properties['dc:description'] = $data['attributes']['comment'];
2249			}
2250			if ($id=$data['attributes']['owner_id'])
2251			{
2252				$GLOBALS['phpgw']->accounts->get_account_name($id,&$lid,&$fname,&$lname);
2253				$dav_properties['dc:publisher'] = $fname .' '. $lname;
2254				$dav_properties['publisher_id'] = $id;
2255			}
2256			if ($id=$data['attributes']['createdby_id'])
2257			{
2258				$GLOBALS['phpgw']->accounts->get_account_name($id,&$lid,&$fname,&$lname);
2259				$dav_properties['dc:creator'] = $fname .' '. $lname;
2260				$dav_properties['creator_id'] = $id;
2261			}
2262			if ($id=$data['attributes']['modifiedby_id'])
2263			{
2264				$GLOBALS['phpgw']->accounts->get_account_name($id,&$lid,&$fname,&$lname);
2265				$dav_properties['dc:contributor'] = $fname .' '. $lname;
2266				$dav_properties['contributor_id'] = $id;
2267			}
2268
2269			$xmlns = 'xmlns:dc="http://purl.org/dc/elements/1.0/"';
2270			$this->dav_client->proppatch($p->real_full_path, $dav_properties, $xmlns, $this->override_locks[$p->real_full_path]);
2271			return True;
2272		}
2273
2274		/*!
2275		@function correct_attributes
2276		@abstract Set the correct attributes for 'string' (e.g. owner)
2277		@param string File/directory to correct attributes of
2278		@param relatives Relativity array
2279		@result Boolean True/False
2280		*/
2281		function correct_attributes ($data)
2282		{
2283			$default_values = array
2284				(
2285					'relatives'	=> array (RELATIVE_CURRENT)
2286				);
2287
2288			$data = array_merge ($this->default_values ($data, $default_values), $data);
2289$this->debug('correct_attributes: '.$data['string']);
2290			$p = $this->path_parts (array(
2291					'string'	=> $data['string'],
2292					'relatives'	=> array ($data['relatives'][0])
2293				)
2294			);
2295
2296			if ($p->fake_leading_dirs != $this->fakebase && $p->fake_leading_dirs != '/')
2297			{
2298				$ls_array = $this->ls (array(
2299						'string'	=> $p->fake_leading_dirs,
2300						'relatives'	=> array ($p->mask),
2301						'checksubdirs'	=> False,
2302						'nofiles'	=> True
2303					)
2304				);
2305				$set_attributes_array = Array(
2306					'owner_id' => $ls_array[0]['owner_id']
2307				);
2308			}
2309			elseif (preg_match ("+^$this->fakebase\/(.*)$+U", $p->fake_full_path, $matches))
2310			{
2311				$set_attributes_array = Array(
2312					'owner_id' => $GLOBALS['phpgw']->accounts->name2id ($matches[1])
2313				);
2314			}
2315			else
2316			{
2317				$set_attributes_array = Array(
2318					'owner_id' => 0
2319				);
2320			}
2321
2322			$this->set_attributes (array(
2323					'string'	=> $p->fake_full_name,
2324					'relatives'	=> array ($p->mask),
2325					'attributes'	=> $set_attributes_array
2326				)
2327			);
2328
2329			return True;
2330		}
2331
2332		/*!
2333		@function file_type
2334		@abstract return file/dir type (MIME or other)
2335		@param string File or directory path (/home/user/dir/dir2/dir3, /home/user/dir/dir2/file)
2336		@param relatives Relativity array
2337		@result MIME type, "Directory", or nothing if MIME type is not known
2338		*/
2339		function file_type ($data)
2340		{
2341$this->debug('file_type');
2342			$default_values = array
2343				(
2344					'relatives'	=> array (RELATIVE_CURRENT)
2345				);
2346
2347			$data = array_merge ($this->default_values ($data, $default_values), $data);
2348
2349			$p = $this->path_parts (array(
2350					'string'	=> $data['string'],
2351					'relatives'	=> array ($data['relatives'][0])
2352				)
2353			);
2354
2355			if (!$this->acl_check (array(
2356					'string'	=> $p->fake_full_path,
2357					'relatives'	=> array ($p->mask),
2358					'operation'	=> PHPGW_ACL_READ,
2359					'must_exist'	=> True
2360				))
2361			) return False;
2362
2363			if ($p->outside)
2364			{
2365			  if(is_dir($p->real_full_path)) return ('Directory');
2366			  else return $this->get_ext_mime_type(array('string' => $p->real_full_path));
2367
2368			}
2369			$tmp_prop=$this->dav_client->get_properties($p->real_full_path);
2370$this->debug('tmpprop: '.$p->real_full_path);
2371$this->debug($tmp_prop);
2372			$mime_type=$tmp_prop[$p->real_full_path]['mime_type'];
2373			if ($mime_type == 'httpd/unix-directory' || $tmp_prop[$p->real_full_path]['is_dir']== '1')
2374			{
2375				$mime_type='Directory';
2376			}
2377$this->debug('file_type: Mime type : '.$mime_type);
2378			return $mime_type;
2379		}
2380
2381		/*!
2382		@function get_ext_mime_type
2383		@abstract return MIME type based on file extension
2384		@description Authors: skeeter
2385			     Internal use only.  Applications should call vfs->file_type ()
2386		@param string File name, with or without leading paths
2387		@result MIME type based on file extension
2388		*/
2389		function get_ext_mime_type ($data)
2390		{
2391			$file=basename($data['string']);
2392			$mimefile=PHPGW_API_INC.'/phpgw_mime.types';
2393			$fp=fopen($mimefile,'r');
2394			$contents = explode("\n",fread($fp,filesize($mimefile)));
2395			fclose($fp);
2396
2397			$parts=explode('.',strtolower($file));
2398			$ext=$parts[(sizeof($parts)-1)];
2399
2400			for($i=0;$i<sizeof($contents);$i++)
2401			{
2402				if (!ereg("^#",$contents[$i]))
2403				{
2404					$line=split("[[:space:]]+", $contents[$i]);
2405					if (sizeof($line) >= 2)
2406					{
2407						for($j=1;$j<sizeof($line);$j++)
2408						{
2409							if($line[$j] == $ext)
2410							{
2411								return $line[0];
2412							}
2413						}
2414					}
2415				}
2416			}
2417
2418			return '';
2419 		}
2420
2421
2422
2423		/*!
2424		@function file_exists
2425		@abstract check if file/directory exists
2426		@param string file/directory to check existance of
2427		@param relatives Relativity array
2428		@result Boolean True/False
2429		*/
2430		function file_exists ($data)
2431		{
2432			$default_values = array
2433				(
2434					'relatives'	=> array (RELATIVE_CURRENT)
2435				);
2436
2437			$data = array_merge ($this->default_values ($data, $default_values), $data);
2438
2439			$p = $this->path_parts (array(
2440					'string'	=> $data['string'],
2441					'relatives'	=> array ($data['relatives'][0])
2442				)
2443			);
2444			$this->debug('vfs->file_exists() data:'.$data['string']);
2445			$this->debug('vfs->file_exists() full_path:  '.$p->real_full_path);
2446			if ($p->outside)
2447			{
2448			  return file_exists($p->real_full_path);
2449			}
2450
2451			$path = $p->real_full_path;
2452
2453			//Even though this does full XML parsing on the output, because
2454			// it then caches the result this limits the amount of traffic to
2455			//the dav server (which makes it faster even over a local connection)
2456			$props = $this->dav_client->get_properties($path);
2457			if ($props[$path])
2458			{
2459				$this->debug('found');
2460				return True;
2461			}
2462			else
2463			{
2464				$this->debug('not found');
2465				return False;
2466			}
2467		}
2468
2469		/*!
2470		@function get_size
2471		@abstract Return size of 'string'
2472		@param string file/directory to get size of
2473		@param relatives Relativity array
2474		@param checksubdirs Boolean, recursively add the size of all sub directories as well?
2475		@result Size of 'string' in bytes
2476		*/
2477		function get_size ($data)
2478		{
2479			if (!is_array ($data))
2480			{
2481				$data = array ();
2482			}
2483
2484			$default_values = array
2485				(
2486					'relatives'	=> array (RELATIVE_CURRENT),
2487					'checksubdirs'	=> True
2488				);
2489
2490			$data = array_merge ($this->default_values ($data, $default_values), $data);
2491
2492			$p = $this->path_parts (array(
2493					'string'	=> $data['string'],
2494					'relatives'	=> array ($data['relatives'][0])
2495				)
2496			);
2497
2498			if (!$this->acl_check (array(
2499					'string'	=> $p->fake_full_path,
2500					'relatives'	=> array ($p->mask),
2501					'operation'	=> PHPGW_ACL_READ,
2502					'must_exist'	=> True
2503				))
2504			)
2505			{
2506				return False;
2507			}
2508
2509			/*
2510			   WIP - this should run through all of the subfiles/directories in the directory and tally up
2511			   their sizes.  Should modify ls () to be able to return a list for files outside the virtual root
2512			*/
2513			if ($p->outside){
2514			  return filesize($p->real_full_path);
2515			}
2516
2517			$ls_array = $this->ls (array(
2518					'string'	=> $p->fake_full_path,
2519					'relatives'	=> array ($p->mask),
2520					'checksubdirs'	=> $data['checksubdirs'],
2521					'nofiles'	=> !$data['checksubdirs']
2522				)
2523			);
2524
2525			while (list ($num, $file_array) = each ($ls_array))
2526			{
2527				/*
2528				   Make sure the file is in the directory we want, and not
2529				   some deeper nested directory with a similar name
2530				*/
2531/*
2532				if (@!ereg ('^' . $file_array['directory'], $p->fake_full_path))
2533				{
2534					continue;
2535				}
2536*/
2537
2538				$size += $file_array['size'];
2539$this->debug('size:getting size from fs: '.$size);
2540			}
2541
2542			return $size;
2543		}
2544
2545		/*!
2546		@function checkperms
2547		@abstract Check if $this->working_id has write access to create files in $dir
2548		@discussion Simple call to acl_check
2549		@param string Directory to check access of
2550		@param relatives Relativity array
2551		@result Boolean True/False
2552		*/
2553		function checkperms ($data)
2554		{
2555			if (!is_array ($data))
2556			{
2557				$data = array ();
2558			}
2559
2560			$default_values = array
2561				(
2562					'relatives'	=> array (RELATIVE_CURRENT)
2563				);
2564
2565			$data = array_merge ($this->default_values ($data, $default_values), $data);
2566
2567			$p = $this->path_parts (array(
2568					'string'	=> $data['string'],
2569					'relatives'	=> array ($data['relatives'][0])
2570				)
2571			);
2572
2573			if (!$this->acl_check (array(
2574					'string'	=> $p->fake_full_path,
2575					'relatives'	=> array ($p->mask),
2576					'operation'	=> PHPGW_ACL_ADD
2577				))
2578			)
2579			{
2580				return False;
2581			}
2582			else
2583			{
2584				return True;
2585			}
2586		}
2587
2588		/*!
2589		@function ls
2590		@abstract get directory listing or info about a single file
2591		@discussion Note: The entries are not guaranteed to be returned in any logical order
2592			    Note: The size for directories does not include subfiles/subdirectories.
2593				  If you need that, use $this->get_size ()
2594		@param string File or Directory
2595		@param relatives Relativity array
2596		@param checksubdirs Boolean, recursively list all sub directories as well?
2597		@param mime_type Only return entries matching MIME-type 'mime_type'.  Can be any MIME-type, "Directory" or "\ " for those without MIME types
2598		@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'
2599		@param orderby How to order results.  Note that this only works for directories inside the virtual root
2600		@result array of arrays.  Subarrays contain full info for each file/dir.
2601		*/
2602		function ls ($data)
2603		{
2604			$default_values = array
2605				(
2606					'relatives'	=> array (RELATIVE_CURRENT),
2607					'checksubdirs'	=> True,
2608					'mime_type'	=> False,
2609					'nofiles'	=> False,
2610					'orderby'	=> 'directory'
2611				);
2612			$data = array_merge ($this->default_values ($data, $default_values), $data);
2613			//Stupid "nofiles" fix"
2614			if ($data['nofiles'])
2615			{
2616				$data['relatives'] = array (RELATIVE_NONE);
2617			}
2618			$p = $this->path_parts (array(
2619					'string'	=> $data['string'],
2620					'relatives'	=> array ($data['relatives'][0])
2621				)
2622			);
2623
2624			if ($data['checksubdirs']==False && ereg('.*/$', $data['string']) && $data['nofiles'] )
2625			{
2626$this->debug('Returning empty for'.$data['string']);
2627				return array();
2628			}
2629			$dir = $p->fake_full_path;
2630$this->debug("ls'ing dir: $dir path: ".$p->real_full_path);
2631			/* If they pass us a file or 'nofiles' is set, return the info for $dir only */
2632			if (((($type = $this->file_type (array(
2633					'string'	=> $dir,
2634					'relatives'	=> array ($p->mask)
2635				)) != 'Directory'))
2636				|| ($data['nofiles'])) && !$p->outside
2637			)
2638			{
2639$this->debug('ls branch 1');
2640			$prop=$this->dav_client->get_properties($p->real_full_path, 1);
2641			//make the key the 'orderby' attribute
2642			if (! ($data['orderby'] == 'directory'))
2643			{
2644				$tmp_prop = array();
2645				$id=0;
2646				foreach ( $prop as $key=>$value)
2647				{
2648					$id++;
2649					$new_key =  substr($value[$data['orderby']].'        ',0, 8);
2650					$tmp_prop[strtolower($new_key).'_'.$id] = $value;
2651				}
2652			}
2653			else
2654			{
2655				$tmp_prop = $prop;
2656			}
2657			ksort($tmp_prop);
2658			$rarray = array ();
2659			foreach($tmp_prop as $idx => $value)
2660			{
2661				if($value['mime_type']==$data['mime_type'] or $data['mime_type']=='')
2662				{
2663					$directory = $this->path_parts($value['directory']);
2664					$value['directory'] = $directory->fake_full_path;
2665					if($value['is_dir']) $value['mime_type']='Directory';
2666					$rarray[] = $value;
2667				}
2668			}
2669$this->debug('ls returning 1:');
2670				return $rarray;
2671			}
2672
2673			//WIP - this should recurse using the same options the virtual part of ls () does
2674			/* If $dir is outside the virutal root, we have to check the file system manually */
2675			if ($p->outside)
2676			{
2677$this->debug('ls branch 2 (outside)');
2678				if ($this->file_type (array(
2679						'string'	=> $p->fake_full_path,
2680						'relatives'	=> array ($p->mask)
2681					)) == 'Directory'
2682					&& !$data['nofiles']
2683				)
2684				{
2685					$dir_handle = opendir ($p->real_full_path);
2686					while ($filename = readdir ($dir_handle))
2687					{
2688						if ($filename == '.' || $filename == '..')
2689						{
2690							continue;
2691						}
2692
2693						$rarray[] = $this->get_real_info (array(
2694								'string'	=> $p->real_full_path . SEP . $filename,
2695								'relatives'	=> array ($p->mask)
2696							)
2697						);
2698					}
2699				}
2700				else
2701				{
2702					$rarray[] = $this->get_real_info (array(
2703							'string'	=> $p->real_full_path,
2704							'relatives'	=> array ($p->mask)
2705						)
2706					);
2707				}
2708$this->debug('ls returning 2:');
2709				return $rarray;
2710			}
2711$this->debug('ls branch 3');
2712			/* $dir's not a file, is inside the virtual root, and they want to check subdirs */
2713			$prop=$this->dav_client->get_properties($p->real_full_path,1);
2714			unset($prop[$p->real_full_path]);
2715			//make the key the 'orderby' attribute
2716
2717			if (! ($data['orderby'] == 'directory'))
2718			{
2719				$tmp_prop = array();
2720				$id=0;
2721				foreach ( $prop as $key=>$value)
2722				{
2723					$id++;
2724					$new_key =  substr($value[$data['orderby']].'        ',0, 8);
2725					$tmp_prop[strtolower($new_key).'_'.$id] = $value;
2726				}
2727			}
2728			else
2729			{
2730				$tmp_prop = $prop;
2731			}
2732
2733			ksort($tmp_prop);
2734
2735			unset($tmp_prop[ $p->real_full_path]);
2736			$rarray = array ();
2737			foreach($tmp_prop as $idx => $value)
2738			{
2739				if($data['mime_type']=='' || $value['mime_type']==$data['mime_type'])
2740				{
2741					//$directory = $this->path_parts($value['directory']);
2742					$value['directory'] = $p->fake_full_path;
2743					$rarray[] = $value;
2744				}
2745			}
2746$this->debug('ls:returning 3:');
2747			return $rarray;
2748		}
2749
2750		/*!
2751		@function dir
2752		@abstract shortcut to ls
2753		*/
2754		function dir ($data)
2755		{
2756			return $this->ls ($data);
2757		}
2758
2759		/*!
2760		@function command_line
2761		@abstract Process and run a Unix-sytle command line
2762		@discussion EXPERIMENTAL.  DANGEROUS.  DO NOT USE THIS UNLESS YOU KNOW WHAT YOU'RE DOING!
2763			    This is mostly working, but the command parser needs to be improved to take
2764			    files with spaces into consideration (those should be in "").
2765		@param command_line Unix-style command line with one of the commands in the $args array
2766		@result $result The return value of the actual VFS call
2767		*/
2768		function command_line ($data)
2769		{
2770			if (!is_array ($data))
2771			{
2772				$data = array ();
2773			}
2774
2775			$args = array
2776			(
2777				array ('name'	=> 'mv', 'params'	=> 2),
2778				array ('name'	=> 'cp', 'params'	=> 2),
2779				array ('name'	=> 'rm', 'params'	=> 1),
2780				array ('name'	=> 'ls', 'params'	=> -1),
2781				array ('name'	=> 'du', 'params'	=> 1, 'func'	=> get_size),
2782				array ('name'	=> 'cd', 'params'	=> 1),
2783				array ('name'	=> 'pwd', 'params'	=> 0),
2784				array ('name'	=> 'cat', 'params'	=> 1, 'func'	=> read),
2785				array ('name'	=> 'file', 'params'	=> 1, 'func'	=> file_type),
2786				array ('name'	=> 'mkdir', 'params'	=> 1),
2787				array ('name'	=> 'touch', 'params'	=> 1)
2788			);
2789
2790			if (!$first_space = strpos ($data['command_line'], ' '))
2791			{
2792				$first_space = strlen ($data['command_line']);
2793			}
2794			if ((!$last_space = strrpos ($data['command_line'], ' ')) || ($last_space == $first_space))
2795			{
2796				$last_space = strlen ($data['command_line']) + 1;
2797			}
2798			$argv[0] = substr ($data['command_line'], 0, $first_space);
2799			if (strlen ($argv[0]) != strlen ($data['command_line']))
2800			{
2801				$argv[1] = substr ($data['command_line'], $first_space + 1, $last_space - ($first_space + 1));
2802				if ((strlen ($argv[0]) + 1 + strlen ($argv[1])) != strlen ($data['command_line']))
2803				{
2804					$argv[2] = substr ($data['command_line'], $last_space + 1);
2805				}
2806			}
2807			$argc = count ($argv);
2808
2809			reset ($args);
2810			while (list (,$arg_info) = each ($args))
2811			{
2812				if ($arg_info['name'] == $argv[0])
2813				{
2814					$command_ok = 1;
2815					if (($argc == ($arg_info['params'] + 1)) || ($arg_info['params'] == -1))
2816					{
2817						$param_count_ok = 1;
2818					}
2819					break;
2820				}
2821			}
2822
2823			if (!$command_ok)
2824			{
2825//				return E_VFS_BAD_COMMAND;
2826				return False;
2827			}
2828			if (!$param_count_ok)
2829			{
2830//				return E_VFS_BAD_PARAM_COUNT;
2831				return False;
2832			}
2833
2834			for ($i = 1; $i != ($arg_info['params'] + 1); $i++)
2835			{
2836				if (substr ($argv[$i], 0, 1) == '/')
2837				{
2838					$relatives[] = RELATIVE_NONE;
2839				}
2840				else
2841				{
2842					$relatives[] = RELATIVE_ALL;
2843				}
2844			}
2845
2846			$func = $arg_info['func'] ? $arg_info['func'] : $arg_info['name'];
2847
2848			if (!$argv[2])
2849			{
2850				$rv = $this->$func (array(
2851						'string'	=> $argv[1],
2852						'relatives'	=> $relatives
2853					)
2854				);
2855			}
2856			else
2857			{
2858				$rv = $this->$func (array(
2859						'from'	=> $argv[1],
2860						'to'	=> $argv[2],
2861						'relatives'	=> $relatives
2862					)
2863				);
2864			}
2865
2866			return ($rv);
2867		}
2868
2869		/* Helper functions */
2870
2871		function default_values ($data, $default_values)
2872		{
2873		  if(!is_array($data)) $data=array();
2874			for ($i = 0; list ($key, $value) = each ($default_values); $i++)
2875			{
2876				if (!isset ($data[$key]))
2877				{
2878					$data[$key] = $value;
2879				}
2880			}
2881
2882			return $data;
2883		}
2884
2885		/* Since we are always dealing with real info, this just calls ls */
2886		function get_real_info ($data){
2887			if (!is_array ($data))
2888			{
2889				$data = array ();
2890			}
2891
2892			$default_values = array
2893				(
2894					'relatives'	=> array (RELATIVE_CURRENT)
2895				);
2896
2897			$data = array_merge ($this->default_values ($data, $default_values), $data);
2898
2899			$p = $this->path_parts (array(
2900					'string'	=> $data['string'],
2901					'relatives'	=> array ($data['relatives'][0])
2902				)
2903			);
2904
2905			if (is_dir ($p->real_full_path))
2906			{
2907				$mime_type = 'Directory';
2908			}
2909			else
2910			{
2911				$mime_type = $this->get_ext_mime_type (array(
2912						'string'	=> $p->fake_name
2913					)
2914				);
2915
2916				if($mime_type)
2917				{
2918					$GLOBALS['phpgw']->db->query ("UPDATE phpgw_vfs SET mime_type='".$mime_type."' WHERE directory='".$p->fake_leading_dirs_clean."' AND name='".$p->fake_name_clean."'" . $this->extra_sql (array ('query_type' => VFS_SQL_SELECT)), __LINE__, __FILE__);
2919				}
2920			}
2921
2922			$size = filesize ($p->real_full_path);
2923			$rarray = array(
2924				'directory' => $p->fake_leading_dirs,
2925				'name' => $p->fake_name,
2926				'size' => $size,
2927				'mime_type' => $mime_type
2928			);
2929
2930			return ($rarray);
2931		}
2932
2933		function update_real()
2934		{ //hmmm. things break without this, but it does nothing in this implementation
2935			return True;
2936		}
2937
2938		function save_session()
2939		{
2940			//Save the overrided locks in the session
2941			$app = $GLOBALS['phpgw_info']['flags']['currentapp'];
2942			$a = array();
2943			foreach ($this->override_locks as $name => $token)
2944			{
2945				$a[] = $name.';'.$token;
2946			}
2947			$session_data = implode('\n', $a);
2948			$this->session = $GLOBALS['phpgw']->session->appsession ('vfs_dav',$app, base64_encode($session_data));
2949
2950		}
2951	}
2952?>
2953