diff --git a/filemanager/tests/benchmark_dav.php b/filemanager/tests/benchmark_dav.php
new file mode 100644
index 0000000000..a76caf910f
--- /dev/null
+++ b/filemanager/tests/benchmark_dav.php
@@ -0,0 +1,61 @@
+ "phpwebhosting",
+ "noheader" => False,
+ "noappheader" => False,
+ "enable_vfs_class" => True);
+
+include("../../header.inc.php");
+
+ function getmicrotime()
+ {
+ list($usec, $sec) = explode(" ",microtime());
+ return ((float)$usec + (float)$sec);
+ }
+
+ function stats($array)
+ {
+ $mean = array_sum($array)/count($array);
+ $a = 0;
+ foreach ($array as $value)
+ {
+ $a += ($value - $mean)*($value - $mean);
+ }
+ $std = sqrt($a/count($array));
+ $error = $std/sqrt(count($array));
+ echo "mean time: $mean error: +-$error";
+ }
+ echo 'Benchmarking vfs::ls
';
+ $times = array();
+ $phpgw->vfs->cd();
+ for ($i=0;$i<20; $i++)
+ {
+ $phpgw->vfs->dav_client->cached_props = array();
+ $time1 = getmicrotime();
+ $result = $phpgw->vfs->ls (array ('string' => ''));
+ $time = getmicrotime() - $time1;
+ $times[] = $time;
+ echo "run $i: $time
";
+ //sleep(1);
+ flush();
+ }
+ stats($times);
+
+ echo '
Benchmarking dav_client::get_properties
';
+ $times = array();
+ $phpgw->vfs->cd();
+ for ($i=0;$i<20; $i++)
+ {
+ $phpgw->vfs->dav_client->cached_props = array();
+ $time1 = getmicrotime();
+ $result = $phpgw->vfs->dav_client->get_properties('/home/sim');
+ $time = getmicrotime() - $time1;
+ $times[] = $time;
+ echo "run $i: $time
";
+ flush();
+ }
+ stats($times);
+
+
+?>
diff --git a/filemanager/tests/test_dav.php b/filemanager/tests/test_dav.php
new file mode 100644
index 0000000000..f3f17e49fc
--- /dev/null
+++ b/filemanager/tests/test_dav.php
@@ -0,0 +1,531 @@
+$string ";
+/* html_table_col_end();
+ html_table_col_begin();*/
+ echo " OK";
+/* html_table_col_end();
+ html_table_row_end();*/
+}
+function fail($string){
+/* html_table_row_begin();
+ html_table_col_begin();*/
+ echo "
$string ";
+/* html_table_col_end();
+ html_table_col_begin();*/
+ echo " FAILED!
";
+/* html_table_col_end();
+ html_table_row_end(); */
+}
+
+$phpgw_info["flags"] = array("currentapp" => "filemanager",
+ "noheader" => False,
+ "noappheader" => False,
+ "enable_vfs_class" => True);
+
+include("../../header.inc.php");
+
+html_text('VFS_DAV tests:');
+html_break (1);
+html_text_italic (PHP_OS . " - " . $phpgw_info["server"]["db_type"] . " - " . PHP_VERSION . " - " . $phpgw->vfs->basedir);
+html_break (1);
+//html_table_begin();
+
+
+$sep = SEP;
+$user = $phpgw->vfs->working_lid;
+$homedir = $phpgw->vfs->fakebase . "/" . $user;
+$realhomedir = preg_replace ("|/|", $sep, $homedir);
+$filesdir = $phpgw->vfs->basedir;
+$currentapp = $phpgw_info["flags"]["currentapp"];
+$time1 = time();
+
+echo ' override locks : ';print_r($phpgw->vfs->override_locks);
+ ###
+ # write test
+
+ $phpgw->vfs->cd ();
+ $testfile = 'sdhdsjjkldggfsbhgbnirooaqojsdkljajklvagbytoi-test';
+ $teststring = 'delete me' ;
+ if (!$result = $phpgw->vfs->write (array ('string' => $testfile,
+ 'content' => $teststring
+ )))
+ {
+ fail( __LINE__." failed writing file!");
+ }
+ else
+ {
+ ok("write");
+ }
+
+#read
+ $phpgw->vfs->cd ();
+ $result = $phpgw->vfs->read (array ('string' => $testfile, 'noview' => true ));
+ if (!$result==$teststring)
+ {
+ fail( __LINE__." failed reading file!");
+ }
+ else
+ {
+ ok(" read");
+ }
+
+ ###
+ # ls test
+
+ $result1 = $phpgw->vfs->ls (array ('string' => $testfile ));
+
+ if (!count($result1))
+ {
+ fail(__LINE__." failed listing file!");
+ }
+ else
+ {
+ ok(" ls : known file");
+ }
+//list the parent dir
+
+ $result = $phpgw->vfs->ls (array ('string' => ''));
+ foreach ($result as $file)
+ {
+ if ($testfile == $file['name'])
+ {
+ $found = true;
+ break;
+ }
+ }
+ if (!$found)
+ {
+ fail(__LINE__." failed listing file!");
+ }
+ else
+ {
+ ok(" ls : parent");
+ }
+ $found = false;
+ foreach ($result as $file)
+ {
+ if ($result1[0]['directory'] == $file['full_name'])
+ {
+ $found = true;
+ break;
+ }
+ }
+ if ($found)
+ {
+ fail(__LINE__." parent is present in its own listing!");
+ }
+ else
+ {
+ ok(" ls : parent self reference");
+ }
+# getsize
+ $phpgw->vfs->cd ();
+ $result = $phpgw->vfs->get_size (array ('string' => $testfile ));
+ $len = strlen($teststring);
+ if (!($result== $len))
+ {
+ fail(__LINE__." failed getting size of file result $result strlen $len");
+ }
+ else
+ {
+ ok("get_size");
+ }
+
+#filetype
+ $phpgw->vfs->cd ();
+ $result = $phpgw->vfs->file_type(array ('string' => $testfile ));
+ $len = strlen($teststring);
+ if (!($result== 'application/octet-stream'))
+ {
+ fail(__LINE__." failed getting file type $result");
+ }
+ else
+ {
+ ok("file_type");
+ }
+
+
+#file_exists
+ $phpgw->vfs->cd ();
+ $result = $phpgw->vfs->file_exists(array ('string' => $testfile ));
+ if (!$result)
+ {
+ fail(__LINE__." file_exist failed: $result");
+ }
+ else
+ {
+ ok("file_exists");
+ }
+
+#lock
+ $phpgw->vfs->cd ();
+ $result = $phpgw->vfs->lock (array ('string' => $testfile ));
+ if (!$result)
+ {
+ fail(__LINE__."failed locking file!");
+ }
+ else
+ {
+ ok(" lock");
+ }
+
+
+ $ls_array = $GLOBALS['phpgw']->vfs->ls (array (
+ 'string' => $testfile,
+ 'relatives' => array (RELATIVE_ALL),
+ 'checksubdirs' => False
+ )
+ );
+ if (!count($ls_array[0]['locks']))
+ {
+ fail(__LINE__."after locking file no locks exist!");
+ }
+ else
+ {
+ ok(" lock: after locking lock exists.");
+ }
+ $lock = end($ls_array[0]['locks']);
+ $tokens = end($lock['lock_tokens']);
+ $token = $tokens['name'];
+ //write should now fail
+ $result = $phpgw->vfs->write (array ('string' => $testfile,
+ 'content' => 'delete me'
+ ));
+ if ($result)
+ {
+ fail(__LINE__."I can write a supposidly locked file!");
+ }
+ else
+ {
+ ok("lock: after locking write fails");
+ }
+
+ $phpgw->vfs->add_lock_override(array ('string' => $testfile));
+ $result = $phpgw->vfs->write (array ('string' => $testfile,
+ 'content' => 'delete me'
+ ));
+ if (!$result)
+ {
+ fail(__LINE__."I cant write a locked file after overriding the lock!");
+ }
+ else
+ {
+ ok("lock: after lock override write succeeds");
+ }
+###
+# unlock test
+
+ $phpgw->vfs->cd ();
+ $result = $phpgw->vfs->unlock (array ('string' => $testfile ), $token);
+ if (!$result)
+ {
+ fail( __LINE__."failed unlocking file!");
+ }
+ else
+ {
+ OK("unlock");
+ }
+
+#server side copy
+
+ $phpgw->vfs->cd ();
+ $result = $phpgw->vfs->cp(array ('from' => $testfile,
+ 'to' => $testfile.'2',
+ 'relatives' => array (RELATIVE_ALL, RELATIVE_ALL)
+ ));
+ if (!$result)
+ {
+ fail(__LINE__." failed copying! returned: $result");
+ }
+ else
+ {
+ ok("server-side copy");
+ }
+ $result = $phpgw->vfs->file_exists(array ('string' => $testfile.'2'
+ ));
+ if (!$result)
+ {
+ fail(__LINE__." after copy, target doesnt exist!");
+ }
+ else
+ {
+ ok("server-side copy : test for target");
+ }
+ $result = $phpgw->vfs->read (array ('string' => "$testfile".'2',
+ 'noview' => true,
+ 'relatives' => array (RELATIVE_ALL)
+ ));
+ if (!$result==$teststring)
+ {
+ fail( __LINE__."after copy, read returned '$result' not '$teststring' ");
+ }
+ else
+ {
+ ok(" server-side copy: read target");
+ }
+ $result = $phpgw->vfs->delete(array ('string' => $testfile.'2'
+ ));
+
+ if (!$result)
+ {
+ fail(__LINE__." failed copying! delete copied file returned: $result");
+ }
+ else
+ {
+ ok("server-side copy : delete target");
+ }
+
+#remote -> local copy
+ $phpgw->vfs->cd ();
+ echo "";
+ $result = $phpgw->vfs->cp(array ('from' => $testfile,
+ 'to' => "/tmp/$testfile".'2',
+ 'relatives' => array (RELATIVE_ALL, RELATIVE_NONE | VFS_REAL)
+ ));
+ echo "
";
+ if (!$result)
+ {
+ fail(__LINE__." failed remote->local copying! returned: $result");
+ }
+ else
+ {
+ ok("remote->local copy");
+ }
+ echo "";
+ $result = $phpgw->vfs->file_exists(array ('string' => "/tmp/$testfile".'2',
+ 'relatives' => array (RELATIVE_NONE | VFS_REAL)
+ ));
+ echo "
";
+ if (!$result)
+ {
+ fail(__LINE__." after remote->local copy, target doesnt exist!");
+ }
+ else
+ {
+ ok("remote->local copy : test for target");
+ }
+ $phpgw->vfs->cd ();
+ echo "";
+ $result = $phpgw->vfs->read (array ('string' => "/tmp/$testfile".'2',
+ 'noview' => true,
+ 'relatives' => array (RELATIVE_NONE | VFS_REAL)
+ ));
+ echo "
";
+ if (!$result==$teststring)
+ {
+ fail( __LINE__."after remote->local copy, returned $result");
+ }
+ else
+ {
+ ok(" remote->local copy: read target");
+ }
+ echo "";
+ $result = $phpgw->vfs->delete(array ('string' => "/tmp/$testfile".'2',
+ 'relatives' => array (RELATIVE_NONE | VFS_REAL)
+ ));
+ echo "
";
+ if (!$result)
+ {
+ fail(__LINE__." failed copying! delete copied file returned: $result");
+ }
+ else
+ {
+ ok("remote->local copy : delete target");
+ }
+
+#move
+ $phpgw->vfs->cd ();
+ echo "";
+ $result = $phpgw->vfs->mv(array ('from' => $testfile,
+ 'to' => $testfile.'2',
+ 'relatives' => array (RELATIVE_ALL, RELATIVE_ALL)
+ ));
+ echo "
";
+ if (!$result)
+ {
+ fail(__LINE__." failed moving! returned: $result");
+ }
+ else
+ {
+ ok("server-side move");
+ }
+ $result = $phpgw->vfs->file_exists(array ('string' => $testfile,
+ ));
+ if ($result)
+ {
+ fail(__LINE__." failed moving! returned: $result");
+ }
+ else
+ {
+ ok("server-side move : test for source");
+ }
+ $result = $phpgw->vfs->file_exists(array ('string' => $testfile.'2',
+ ));
+ if (!$result)
+ {
+ fail(__LINE__." failed moving! returned: $result");
+ }
+ else
+ {
+ ok("server-side move : test for target");
+ }
+ echo "";
+ $result = $phpgw->vfs->read (array ('string' => $testfile.'2',
+ 'noview' => true,
+ 'relatives' => array (RELATIVE_ALL)
+ ));
+ echo "
";
+ if (!$result==$teststring)
+ {
+ fail( __LINE__."after move, read returned '$result' not '$teststring' ");
+ }
+ else
+ {
+ ok(" server-side move: read target");
+ }
+
+ echo "";
+ $result = $phpgw->vfs->mv(array ('from' => $testfile.'2',
+ 'to' => $testfile,
+ 'relatives' => array (RELATIVE_ALL, RELATIVE_ALL)
+ ));
+ echo "
";
+ if (!$result)
+ {
+ fail(__LINE__." failed moving! returned: $result");
+ }
+ else
+ {
+ ok("server-side move : move back");
+ }
+
+#remote->local move
+ echo "";
+ $phpgw->vfs->cd ();
+ $result = $phpgw->vfs->mv(array ('from' => $testfile,
+ 'to' => '/tmp/'.$testfile.'2',
+ 'relatives' => array (RELATIVE_ALL, RELATIVE_NONE | VFS_REAL)
+ ));
+ echo "
";
+ if (!$result)
+ {
+ fail(__LINE__." failed moving! returned: $result");
+ }
+ else
+ {
+ ok("remote->local move");
+ }
+ $result = $phpgw->vfs->file_exists(array ('string' => $testfile,
+ ));
+ if ($result)
+ {
+ fail(__LINE__." failed moving! returned: $result");
+ }
+ else
+ {
+ ok("remote->local move : test for source");
+ }
+ $result = $phpgw->vfs->file_exists(array ('string' => '/tmp/'.$testfile.'2',
+ 'relatives' => array(RELATIVE_NONE | VFS_REAL)
+ ));
+ if (!$result)
+ {
+ fail(__LINE__." failed moving! returned: $result");
+ }
+ else
+ {
+ ok("remote->local move : test for target");
+ }
+
+ $result = $phpgw->vfs->read (array ('string' => '/tmp/'.$testfile.'2',
+ 'noview' => true,
+ 'relatives' => array(RELATIVE_NONE | VFS_REAL)
+ ));
+ if (!$result==$teststring)
+ {
+ fail( __LINE__."after move, read returned '$result' not '$teststring' ");
+ }
+ else
+ {
+ ok("remote->local move: read target");
+ }
+
+ echo "";
+ $result = $phpgw->vfs->mv(array ('from' => '/tmp/'.$testfile.'2',
+ 'to' => $testfile,
+ 'relatives' => array (RELATIVE_NONE | VFS_REAL, RELATIVE_ALL)
+ ));
+ echo "
";
+ if (!$result)
+ {
+ fail(__LINE__." failed moving! returned: $result");
+ }
+ else
+ {
+ ok("server-side move : move back");
+ }
+
+
+###
+# delete test
+
+ $phpgw->vfs->cd ();
+ $result = $phpgw->vfs->delete(array ('string' => $testfile ));
+ if (!$result)
+ {
+ fail(__LINE__."failed deleting file! returned: $result");
+ }
+ else
+ {
+ ok("delete");
+ }
+
+###
+# mkdir test
+
+ $phpgw->vfs->cd ();
+ $result = $phpgw->vfs->mkdir(array ('string' => $testfile ));
+ if (!$result)
+ {
+ fail(__LINE__."failed creating collection! returned: $result");
+ }
+ else
+ {
+ ok("mkdir");
+ }
+
+ $result = $phpgw->vfs->write (array ('string' => $testfile.'/'.$testfile.'2',
+ 'content' => $teststring
+ ));
+
+ if (!$result)
+ {
+ fail( __LINE__." failed writing file into new dir!");
+ }
+ else
+ {
+ ok("mkdir : write into dir");
+ }
+###
+# rm dir test
+
+ $phpgw->vfs->cd ();
+ $result = $phpgw->vfs->rm(array ('string' => $testfile ));
+ if (!$result)
+ {
+ fail(__LINE__." failed deleting collection! returned: $result");
+ }
+ else
+ {
+ ok("delete dir");
+ }
+
+ //html_table_end();
+ $time = time() - $time1;
+ html_text("Done in $time s");
+ html_page_close ();
+
+?>
diff --git a/phpgwapi/doc/vfs/vfs_dav/README b/phpgwapi/doc/vfs/vfs_dav/README
new file mode 100644
index 0000000000..5b3de61473
--- /dev/null
+++ b/phpgwapi/doc/vfs/vfs_dav/README
@@ -0,0 +1,122 @@
+vfs_dav: WebDAV support for phpGroupWare
+----------------------------------------
+
+WARNING::This is still alpha-quality code, and may delete all your files,
+corrupt all your data, and run away with your daughter. Dont use it unless
+you know what your doing.
+
+
+This package contains classes and patches implementing a virtual filesystem
+for phpGroupWare (0.9.14) that uses a WebDAV file repository as a
+storage area. This allows you to store your files online in phpgroupware,
+in a way that cooperates well with other web applications (for instance,
+in Windows you can then access your files as a "web folder", and similarly
+KDE, Gnome, MacOSX, and a multitude of applications all include some way of
+browsing files on a WebDAV share)
+
+Features
+--------
+- (should be) fully compatible with the WebDAV standard, as specified in
+ RFC2518 http://www.ietf.org/rfc/rfc2518.txt (NB : this doesn't imply that it
+ is :) only that I consider it a bug if it isn't )
+- Maps vfs properties (eg creator_id etc) to the Dublin Core metadata
+ specification (http://dublincore.org/) (eg "author") where possible, and
+ stores them as DAV properties
+- Limited Access control support - when you create a dir in groupware, it
+ puts a .htaccess file in it with "limit user " in it. Combined
+ with an Apache module authenticating against your phpgroupware account
+ database, this provides reasonable user-security (proper ACL support is
+ trickier to implement though :(
+
+TODO:
+-----
+- DELTA-V versioning extensions, using the subversion DELTA-V server
+- Journalling -> Versioning. Journalling is currently unimplemented
+- Locking (phpGroupWare doesn't really have any locking mechanism yet)
+- ACL support for users accessing through an external webdav client.
+
+Installation
+------------
+To install:
+
+1/ Setup a WebDAV server - currently this code has only been well tested using
+ Apache's mod_dav (http://www.webdav.org/mod_dav/). To setup mod_dav ensure
+ that you have the module installed correctly ( RTFM :) and create a virtual
+ host (eg files.yourdomain.com) something like this:
+
+
+ AccessFileName .htaccess
+ ServerAdmin webmaster@yourdomain.com
+ DocumentRoot /var/files
+
+ AllowOverride All
+ Options +Indexes
+ DAV on
+ DirectoryIndex /
+ RemoveHandler .php .php4 .php3 .phtml
+
+
+ #This ensures phpgroupware can modify .htaccess files
+ order deny,allow
+ deny from all
+ #make sure your phpgroupware server is included here.
+ allow from localhost .localdomain 192.168.
+
+ ServerName files.yourdomain.com
+ ErrorLog logs/dav_err
+ CustomLog logs/dav_acc combined
+
+
+2/ On the setup page (phpgroupware/setup/config.php) specify
+ the WebDAV server URL (eg http://files.yourdomain.com ) in the: "Full path
+ for users and groups files" text area, and select DAV in:
+ "Select where you want to store/retrieve filesystem information"
+ combo. If your file repository supports SSL you might want to enter
+ 'https://files.yourdomain.com' instead - note that phpGroupWare itself wont
+ use SSL to access the repository, but when it redirects the users browser to
+ the repository it will use the secure https url.
+
+3/ Make sure your WebDAV repository contains a "home" directory (important!)
+ So if your WebDAV directory is /var/files, you would need:
+ /var/files/
+ /var/files/home/
+
+4/ To enable authentication you must use a third-party Apache authentication
+ module. Which you use depends on how you have setup authentication in
+ phpGroupWare - for instance if you use an SQL DB (the default) then set up
+ mod_auth_pgsql (http://www.giuseppetanzilli.it/mod_auth_pgsql/) or
+ mod_auth_mysql (http://modauthmysql.sourceforge.net/)
+ An example .htaccess file is included for postgresql - mysql would be
+ similar. Your file repository also needs to be configured to allow
+ phpGroupWare to write .htaccess files (the setup in (3) will allow this)
+
+ Note that using an Apache module for authentication is not strictly
+ required in order to use WebDAV within phpGroupWare.
+
+Authors
+-------
+Jonathon Sim for Zeald Ltd (http://zeald.com), Paolo Andreetto
+. Also contains code from the Net_HTTP_Client PHP class
+(http://lwest.free.fr/doc/php/lib/net_http_client-en.html) by Leo West
+.
+
+License
+-------
+By using this software you agree to the terms of the GNU LGPL: this is the same
+license that the phpGroupWare API is distributed under:
+
+Copyright (C) 2002 Zeald Ltd.
+
+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
diff --git a/phpgwapi/doc/vfs/vfs_dav/htaccess.pgsql_example b/phpgwapi/doc/vfs/vfs_dav/htaccess.pgsql_example
new file mode 100644
index 0000000000..15e3b016e2
--- /dev/null
+++ b/phpgwapi/doc/vfs/vfs_dav/htaccess.pgsql_example
@@ -0,0 +1,21 @@
+ php_flag engine off
+ RemoveHandler cgi-script .cgi .pl
+ RemoveType application/x-httpd-php .php .php3
+ RemoveType application/x-httpd-php-source .phps
+
+AuthName "Groupware File Repository"
+AuthType basic
+Auth_PG_cache_passwords on
+Auth_PG_host orion.zeald.com
+Auth_PG_database gw_zeald
+Auth_PG_user zeald
+Auth_PG_pwd password
+Auth_PG_pwd_table phpgw_accounts
+Auth_PG_grp_table phpgw_accounts
+Auth_PG_uid_field account_lid
+Auth_PG_pwd_field account_pwd
+Auth_PG_gid_field account_groups
+Auth_PG_hash_type MD5
+Auth_PG_pwd_whereclause " AND account_status='A' AND account_type='u' AND account_expires *
+ * Provides methods for manipulating an RFC 2518 DAV repository *
+ * Copyright (C) 2002 Zeald Ltd *
+ * -------------------------------------------------------------------------*
+ * This library is part of the phpGroupWare API *
+ * http://www.phpgroupware.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 *
+ \**************************************************************************/
+
+ /* $Id$ */
+
+ /*At the moment much of this is simply a wrapper around the NET_HTTP_Client class,
+ with some other methods for parsing the returned XML etc
+ Ideally this will eventually use groupware's inbuilt HTTP class
+ */
+
+ define ('DEBUG_DAV_CLIENT', 0);
+ define ('DEBUG_DAV_XML', 0);
+ define ('DEBUG_CACHE', 0);
+
+
+##############################################################
+# 'Private' classes - these are only used internally and should
+# not be used by external code
+##############################################################
+
+ /*
+ PHP STILL doesnt have any sort of stable DOM parser. So lets make our
+ own XML parser, that parses XML into a tree of arrays (I know, it could do
+ something resembling DOM, but it doesnt!)
+ */
+ class xml_tree_parser
+ {
+ var $namespaces;
+ var $current_element;
+ var $num = 1;
+ var $tree = NULL;
+
+ /*
+ This is the only end-user function in the class. Call parse with an XML string, and
+ you will get back the tree of 'element' arrays
+ */
+ function parse($xml_string)
+ {
+ $this->xml_parser = xml_parser_create();
+ xml_set_element_handler($this->xml_parser,array(&$this,"start_element"),array(&$this,"end_element"));
+ xml_set_character_data_handler($this->xml_parser,array(&$this,"parse_data"));
+
+ $this->parser_result=array();
+ $this->xml = $xml_string;
+ xml_parse($this->xml_parser,$xml_string);
+ xml_parser_free($this->xml_parser);
+ if (DEBUG_DAV_XML)
+ {
+ echo ''.htmlentities($xml_string).'
';
+ echo 'parsed to:' ; $this->print_tree();
+ }
+ return $this->tree;
+ }
+
+ //a useful function for debug output - will print the tree after parsing
+ function print_tree($element=NULL, $prefix='|', $processed=array())
+ {
+
+ if ($processed[$element['id']])
+ {
+ echo '***RECURSION!!***';
+ die();
+ }
+ else
+ {
+ $processed[$element['id']] = true;
+ }
+
+ if ($element == NULL)
+ {
+ $element = $this->tree;
+ }
+ echo $prefix.$element['namespace'].':'.$element['name'].' '.$element['start'].'->'.$element['end'].'
';
+ $prefix .= '-->';
+ if ($element['data'])
+ {
+ echo $prefix.$element['data'].'
';
+ }
+
+ foreach ($element['children'] as $id=>$child)
+ {
+ $this->print_tree($child, $prefix, &$processed);
+ }
+
+ }
+
+ //called by the xml parser at the start of an element, it creates that elements node in the tree
+ function start_element($parser,$name,$attr)
+ {
+
+ if (preg_match('/(.*):(.*)/', $name, $matches))
+ {
+ $ns = $this->namespaces[$matches[1]];
+ $element = $matches[2];
+ }
+ else
+ {
+ $element = $name;
+ }
+
+ if ($this->tree == NULL)
+ {
+ $this->tree = array(
+ 'namespace'=>$ns,
+ 'name' => $element,
+ 'attributes' => $attr,
+ 'data' => '',
+ 'children' => array(),
+ 'parent' => NULL,
+ 'type' => 'xml_element',
+ 'id' => $this->num
+ );
+ $this->current_element = &$this->tree;
+ }
+ else
+ {
+ $parent = &$this->current_element;
+ $parent['children'][$this->num]=array(
+ 'namespace'=>$ns,
+ 'name' => $element,
+ 'attributes' => $attr,
+ 'data' => '',
+ 'children' => array(),
+ 'parent' => &$parent,
+ 'type' => 'xml_element',
+ 'id' => $this->num
+ );
+ $this->current_element = &$parent['children'][$this->num];
+ }
+ $this->num++;
+ $this->current_element['start'] = xml_get_current_byte_index($parser);
+ foreach ($attr as $name => $value)
+ {
+ if (ereg('^XMLNS:(.*)', $name, $matches) )
+ {
+ $this->namespaces[$matches[1]] = $value;
+ }
+ }
+ }
+
+ //at the end of an element, stores the start and end positions in the xml stream, and moves up the tree
+ function end_element($parser,$name)
+ {
+ $curr = xml_get_current_byte_index($parser);
+ $this->current_element['end'] =strpos($this->xml, '>', $curr);
+ $this->current_element = &$this->current_element['parent'];
+
+ }
+
+ //if there is a CDATA element, puts it into the parent elements node
+ function parse_data($parser,$data)
+ {
+ $this->current_element['data']=$data;
+ }
+
+ }
+
+ /*This class uses a bunch of recursive functions to process the DAV XML tree
+ digging out the relevent information and putting it into an array
+ */
+ class dav_processor
+ {
+ function dav_processor($xml_string)
+ {
+ $this->xml = $xml_string;
+ $this->dav_parser = new xml_tree_parser();
+ $this->tree = $this->dav_parser->parse($xml_string);
+
+ }
+
+ function process_tree(&$element, &$result_array)
+ {
+
+ //This lets us mark a node as 'done' and provides protection against infinite loops
+ if ($this->processed[$element['id']])
+ {
+ return $result_array;
+ }
+ else
+ {
+ $this->processed[$element['id']] = true;
+ }
+
+ if ( $element['namespace'] == 'DAV:')
+ {
+ if ($element['name'] == 'RESPONSE')
+ {
+ $result = array(
+ 'size' => 0,
+ 'getcontenttype' => 'application/octet-stream',
+ 'is_dir' => 0
+ );
+ foreach ($element['children'] as $id=>$child)
+ {
+ $this->process_properties($child, $result);
+ }
+ $result_array[$result['full_name']] = $result;
+
+ }
+ }
+ // ->recursion
+ foreach ($element['children'] as $id=>$child)
+ {
+ $this->process_tree($child, $result_array);
+ }
+ return $result_array;
+ }
+
+ function process_properties($element, &$result_array)
+ {
+ if ($this->processed[$element['id']])
+ {
+ return $result_array;
+ }
+ else
+ {
+ $this->processed[$element['id']] = true;
+ }
+
+ if ( $element['namespace'] == 'DAV:')
+ {
+ switch ($element['name'])
+ {
+ case 'HREF':
+ $string = $element['data'];
+ $idx=strrpos($string,SEP);
+ if($idx && $idx==strlen($string)-1)
+ {
+ $this->current_ref=substr($string,0,$idx);
+ }
+ else
+ {
+ $this->current_ref=$string;
+ }
+ $result_array['name']=basename($string);
+ $result_array['directory']=dirname($string);
+ $result_array['full_name'] = $this->current_ref;
+ break;
+ case 'SUPPORTEDLOCK':
+ if (count($element['children'])) //There are active locks
+ {
+ $result_array['supported_locks'] = array();
+ foreach ($element['children'] as $id=>$child)
+ {
+ $this->process_properties($child, $result_array['supported_locks']);
+ }
+ }
+ break;
+ case 'LOCKDISCOVERY':
+ if (count($element['children'])) //There are active locks
+ {
+ $result_array['locks'] = array();
+ foreach ($element['children'] as $id=>$child)
+ {
+ $this->process_properties($child, $result_array['locks']);
+ }
+ }
+ break;
+ case 'LOCKENTRY':
+ if (count($element['children']))
+ {
+ $result_array[$element['id']] = array();
+ foreach ($element['children'] as $id=>$child)
+ {
+ $this->process_properties($child, $result_array[$element['id']] );
+ }
+ }
+ break;
+ case 'ACTIVELOCK':
+ if (count($element['children']))
+ {
+ $result_array[$element['id']] = array();
+ foreach ($element['children'] as $id=>$child)
+ {
+ $this->process_properties($child, $result_array[$element['id']] );
+ }
+ }
+ break;
+ case 'OWNER':
+ $result_array['owner'] = array();
+
+ foreach ($element['children'] as $child)
+ {
+ $this->process_verbatim($child, &$result_array['owner'][]);
+ }
+
+ //print_r($element);die();
+ //die();
+ $result_array['owner_xml'] = substr($this->xml, $element['start'], $element['end']-$element['start']+1);
+ return $result_array; //No need to process this branch further
+ break;
+ case 'LOCKTOKEN':
+ if (count($element['children']))
+ {
+
+ foreach ($element['children'] as $id=>$child)
+ {
+ $this->process_properties($child, $tmp_result , $processed);
+ $result_array['lock_tokens'][$tmp_result['full_name']] = $tmp_result;
+ }
+ }
+ break;
+ case 'LOCKTYPE':
+ $child = end($element['children']);
+ if ($child)
+ {
+ $this->processed[$child['id']] = true;
+ $result_array['locktype'] = $child['name'];
+ }
+ break;
+ case 'LOCKSCOPE':
+ $child = end($element['children']);
+ if ($child)
+ {
+ $this->processed[$child['id']] = true;
+ $result_array['lockscope'] = $child['name'];
+ }
+ break;
+ default:
+ if (trim($element['data']))
+ {
+ $result_array[strtolower($element['name'])] = $element['data'];
+ }
+ }
+ }
+ else
+ {
+ if (trim($element['data']))
+ {
+ $result_array[strtolower($element['name'])] = $element['data'];
+ }
+ }
+
+ foreach ($element['children'] as $id=>$child)
+ {
+ $this->process_properties($child, $result_array);
+ }
+
+ return $result_array;
+ }
+
+ function process_verbatim($element, &$result_array)
+ {
+ if ($this->processed[$element['id']])
+ {
+ return $result_array;
+ }
+ else
+ {
+ $this->processed[$element['id']] = true;
+ }
+
+ foreach ( $element as $key => $value)
+ {
+ //The parent link is death to naive programmers (eg me) :)
+ if (!( $key == 'children' || $key == 'parent') )
+ {
+ $result_array[$key] = $value;
+ }
+ }
+ $result_array['children'] = array();
+ foreach ($element['children'] as $id=>$child)
+ {
+ echo 'processing child:';
+ $this->process_verbatim($child, $result_array['children']);
+ }
+ return $result_array;
+ }
+ }
+
+#####################################################
+#This is the actual public interface of this class
+#####################################################
+ class http_dav_client
+ {
+ var $attributes=array();
+ var $vfs_property_map = array();
+ var $cached_props = array();
+ function http_dav_client()
+ {
+ $this->http_client = CreateObject('phpgwapi.net_http_client');
+ $this->set_debug(0);
+ }
+
+ //TODO: Get rid of this
+ //A quick, temporary debug output function
+ function debug($info) {
+
+ if (DEBUG_DAV_CLIENT)
+ {
+ echo ' http_dav_client debug: ';
+ if (is_array($info))
+ {
+ print_r($info);
+ }
+ else
+ {
+ echo $info;
+ }
+ echo '
';
+ }
+ }
+ /*!
+ @function glue_url
+ @abstract 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 encodeurl
+ @abstract encodes a url from its "display name" to something the dav server will accept
+ @param uri The unencoded uri
+ @discussion
+ Deals with "url"s which may contain spaces and other unsavoury characters,
+ by using appropriate %20s
+ */
+ function encodeurl($uri)
+ {
+ $parsed_uri = parse_url($uri);
+ if (empty($parsed_uri['scheme']))
+ {
+ $path = $uri;
+ }
+ else
+ {
+ $path = $parsed_uri['path'];
+ }
+ $fixed_array = array();
+ foreach (explode('/', $path) as $name)
+ {
+ $fixed_array[] = rawurlencode($name);
+ }
+ $fixed_path = implode('/', $fixed_array);
+ if (empty($parsed_uri['scheme']))
+ {
+ $parsed_uri['path'] = $fixed_path;
+ $newuri = $this->glue_url($parsed_uri);
+ }
+ else
+ {
+ $newuri = $fixed_path;
+ }
+ return $newuri;
+
+ }
+ /*!
+ @function decodeurl
+ @abstract decodes a url to its "display name"
+ @param uri The encoded uri
+ @discussion
+ Deals with "url"s which may contain spaces and other unsavoury characters,
+ by using appropriate %20s
+ */
+ function decodeurl($uri)
+ {
+ $parsed_uri = parse_url($uri);
+ if (empty($parsed_uri['scheme']))
+ {
+ $path = $uri;
+ }
+ else
+ {
+ $path = $parsed_uri['path'];
+ }
+ $fixed_array = array();
+ foreach (explode('/', $path) as $name)
+ {
+ $fixed_array[] = rawurldecode($name);
+ }
+ $fixed_path = implode('/', $fixed_array);
+ if (empty($parsed_uri['scheme']))
+ {
+ $parsed_uri['path'] = $fixed_path;
+ $newuri = $this->glue_url($parsed_uri);
+ }
+ else
+ {
+ $newuri = $fixed_path;
+ }
+ return $newuri;
+
+ }
+ /*!
+ @function set_attributes
+ @abstract Sets the "attribute map"
+ @param attributes Attributes to extract "as-is" from the DAV properties
+ @param dav_map A mapping of dav_property_name => attribute_name for attributes
+ with different names in DAV and the desired name space.
+ @discussion
+ This is mainly for use by VFS, where the VFS attributes (eg size) differ
+ from the corresponding DAV ones ("getcontentlength")
+ */
+ function set_attributes($attributes, $dav_map)
+ {
+ $this->vfs_property_map = $dav_map;
+ $this->attributes = $attributes;
+ }
+
+ /*!
+ @function set_credentials
+ @abstract Sets authentication credentials for HTTP AUTH
+ @param username The username to connect with
+ @param password The password to connect with
+ @discussion
+ The only supported authentication type is "basic"
+ */
+ function set_credentials( $username, $password )
+ {
+ $this->http_client->setCredentials($username, $password );
+ }
+
+ /*!
+ @function connect
+ @abstract connects to the server
+ @param dav_host The host to connect to
+ @param dav_port The port to connect to
+ @discussion
+ If the server requires authentication you will need to set credentials
+ with set_credentials first
+ */
+
+ function connect($dav_host,$dav_port)
+ {
+ $this->dav_host = $dav_host;
+ $this->dav_port = $dav_port;
+ $this->http_client->addHeader('Host',$this->dav_host);
+ $this->http_client->addHeader('Connection','close');
+ //$this->http_client->addHeader('transfer-encoding','identity');
+ // $this->http_client->addHeader('Connection','keep-alive');
+ // $this->http_client->addHeader('Keep-Alive','timeout=20, state="Accept,Accept-Language"');
+ $this->http_client->addHeader('Accept-Encoding','chunked');
+ $this->http_client->setProtocolVersion( '1.1' );
+ $this->http_client->addHeader( 'user-agent', 'Mozilla/5.0 (compatible; PHPGroupware dav_client/1; Linux)');
+ return $this->http_client->Connect($dav_host,$dav_port);
+ }
+ function set_debug($debug)
+ {
+ $this->http_client->setDebug($debug);
+ }
+
+ /*!
+ @function disconnect
+ @abstract disconnect from the server
+ @discussion
+ When doing HTTP 1.1 we frequently close/reopen the connection
+ anyway, so this function needs to be called after any other DAV calls
+ (since if they find the connection closed, they just reopen it)
+ */
+
+ function disconnect()
+ {
+ $this->http_client->Disconnect();
+ }
+
+ /*!
+ @function get_properties
+ @abstract a high-level method of getting DAV properties
+ @param url The URL to get properties for
+ @param scope the 'depth' to recuse subdirectories (default 1)
+ @param sorted whether we should sort the rsulting array (default True)
+ @result array of file->property arra
+ @discussion
+ This function performs all the necessary XML parsing etc to convert DAV properties (ie XML nodes)
+ into associative arrays of properties - including doing mappings
+ from DAV property names to any desired property name format (eg the VFS one)
+ This is controlled by the attribute arrays set in the set_attributes function.
+ */
+ function get_properties($url,$scope=1){
+ $request_id = $url.'//'.$scope.'//'.$sorted; //A unique id for this request (for caching)
+ if ($this->cached_props[$request_id])
+ {
+if (DEBUG_CACHE) echo'Cache hit : cache id:'.$request_id;
+ return $this->cached_props[$request_id];
+ }
+ else if (! $sorted && $this->cached_props[$url.'//'.$scope.'//1'])
+ {
+if (DEBUG_CACHE) echo ' Cache hit : cache id: '.$request_id;
+ return $this->cached_props[$url.'//'.$scope.'//1'];
+ }
+if (DEBUG_CACHE)
+{
+ echo ' Cache miss : cache id: '.$request_id;
+/* echo " cache:";
+ print_r($this->cached_props);
+ echo '
';*/
+}
+
+
+ if($this->propfind($url,$scope) != 207)
+ {
+ if($this->propfind($url.'/',$scope) != 207)
+ {
+ return array();
+ }
+ }
+ $xml_result=$this->http_client->getBody();
+ $result_array = array();
+ $dav_processor = new dav_processor($xml_result);
+ $tmp_list = $dav_processor->process_tree($dav_processor->tree, $result_array);
+
+ foreach($tmp_list as $name=>$item) {
+ $fixed_name = $this->decodeurl($name);
+ $newitem = $item;
+ $newitem['is_dir']= ($item['getcontenttype'] =='httpd/unix-directory' ? 1 : 0);
+ $item['directory'] = $this->decodeurl($item['directory']);
+ //Since above we sawed off the protocol and host portions of the url, lets readd them.
+ if (strlen($item['directory'])) {
+ $path = $item['directory'];
+ $host = $this->dav_host;
+ $newitem['directory'] = $host.$path;
+ }
+
+ //Get any extra properties that may share the vfs name
+ foreach ($this->attributes as $num=>$vfs_name)
+ {
+ if ($item[$vfs_name])
+ {
+ $newitem[$vfs_name] = $item[$vfs_name];
+ }
+ }
+
+ //Map some DAV properties onto VFS ones.
+ foreach ($this->vfs_property_map as $dav_name=>$vfs_name)
+ {
+ if ($item[$dav_name])
+ {
+ $newitem[$vfs_name] = $item[$dav_name];
+ }
+ }
+
+ if ($newitem['is_dir'] == 1)
+ {
+ $newitem['mime_type']='Directory';
+ }
+
+ $this->debug('
properties:
');
+ $this->debug($newitem);
+ $newitem['name'] = $this->decodeurl($newitem['name']);
+ $result[$fixed_name]=$newitem;
+ $this->cached_props[$name.'//1//1'] = array($fixed_name=>$newitem);
+ }
+ if ($sorted)
+ {
+ ksort($result);
+ }
+ $this->cached_props[$request_id] = $result;
+ return $result;
+ }
+
+ function get($uri)
+ {
+ $uri = $this->encodeurl($uri);
+ return $this->http_client->Get($uri);
+ }
+
+ /*!
+ @function get_body
+ @abstract return the response body
+ @result string body content
+ @discussion
+ invoke it after a Get() call for instance, to retrieve the response
+ */
+ function get_body()
+ {
+ return $this->http_client->getBody();
+ }
+
+ /*!
+ @function get_headers
+ @abstract return the response headers
+ @result array headers received from server in the form headername => value
+ @discussion
+ to be called after a Get() or Head() call
+ */
+ function get_headers()
+ {
+ return $this->http_client->getHeaders();
+ }
+
+ /*!
+ @function copy
+ @abstract PUT is the method to sending a file on the server.
+ @param uri the location of the file on the server. dont forget the heading "/"
+ @param data the content of the file. binary content accepted
+ @result string response status code 201 (Created) if ok
+ */
+ function put($uri, $data, $token='')
+ {
+ $uri = $this->encodeurl($uri);
+if (DEBUG_CACHE) echo 'cache cleared';
+ if (strlen($token))
+ {
+ $this->http_client->addHeader('If', '<'.$uri.'>'.' (<'.$token.'>)');
+ }
+
+ $this->cached_props = array();
+ $result = $this->http_client->Put($uri, $data);
+ $this->http_client->removeHeader('If');
+ return $result;
+ }
+
+ /*!
+ @function copy
+ @abstract Copy a file -allready on the server- into a new location
+ @param srcUri the current file location on the server. dont forget the heading "/"
+ @param destUri the destination location on the server. this is *not* a full URL
+ @param overwrite boolean - true to overwrite an existing destination - overwrite by default
+ @result Returns the HTTP status code
+ @discussion
+ returns response status code 204 (Unchanged) if ok
+ */
+ function copy( $srcUri, $destUri, $overwrite=true, $scope=0, $token='')
+ {
+ $srcUri = $this->encodeurl($srcUri);
+ $destUri = $this->encodeurl($destUri);
+if (DEBUG_CACHE) echo 'cache cleared';
+ if (strlen($token))
+ {
+ $this->http_client->addHeader('If', '<'.$uri.'>'.' (<'.$token.'>)');
+ }
+ $this->cached_props = array();
+ $result = $this->http_client->Copy( $srcUri, $destUri, $overwrite, $scope);
+ $this->http_client->removeHeader('If');
+ return $result;
+ }
+
+ /*!
+ @function move
+ @abstract Moves a WEBDAV resource on the server
+ @param srcUri the current file location on the server. dont forget the heading "/"
+ @param destUri the destination location on the server. this is *not* a full URL
+ @param overwrite boolean - true to overwrite an existing destination (default is yes)
+ @result Returns the HTTP status code
+ @discussion
+ returns response status code 204 (Unchanged) if ok
+ */
+ function move( $srcUri, $destUri, $overwrite=true, $scope=0, $token='' )
+ {
+ $srcUri = $this->encodeurl($srcUri);
+ $destUri = $this->encodeurl($destUri);
+if (DEBUG_CACHE) echo 'cache cleared';
+ if (strlen($token))
+ {
+ $this->http_client->addHeader('If', '<'.$uri.'>'.' (<'.$token.'>)');
+ }
+ $this->cached_props = array();
+ $result = $this->http_client->Move( $srcUri, $destUri, $overwrite, $scope);
+ $this->http_client->removeHeader('If');
+ return $result;
+ }
+
+ /*!
+ @function delete
+ @abstract Deletes a WEBDAV resource
+ @param uri The URI we are deleting
+ @result Returns the HTTP status code
+ @discussion
+ returns response status code 204 (Unchanged) if ok
+ */
+ function delete( $uri, $scope=0, $token='')
+ {
+ $uri = $this->encodeurl($uri);
+if (DEBUG_CACHE) echo 'cache cleared';
+ if (strlen($token))
+ {
+ $this->http_client->addHeader('If', '<'.$uri.'>'.' (<'.$token.'>)');
+ }
+
+ $this->cached_props = array();
+ $result = $this->http_client->Delete( $uri, $scope);
+ $this->http_client->removeHeader('If');
+ return $result;
+ }
+
+ /*!
+ @function mkcol
+ @abstract Creates a WEBDAV collection (AKA a directory)
+ @param uri The URI to create
+ @result Returns the HTTP status code
+ */
+ function mkcol( $uri, $token='' )
+ {
+ $uri = $this->encodeurl($uri);
+if (DEBUG_CACHE) echo 'cache cleared';
+ if (strlen($token))
+ {
+ $this->http_client->addHeader('If', '<'.$uri.'>'.' (<'.$token.'>)');
+ }
+ $this->cached_props = array();
+ return $this->http_client->MkCol( $uri );
+ $this->http_client->removeHeader('If');
+ }
+
+ /*!
+ @function propfind
+ @abstract Queries WEBDAV properties
+ @param uri uri of resource whose properties we are changing
+ @param scope Specifies how "deep" to search (0=just this file/dir 1=subfiles/dirs etc)
+ @result Returns the HTTP status code
+ @discussion
+ to get the result XML call get_body()
+ */
+ function propfind( $uri, $scope=0 )
+ {
+ $uri = $this->encodeurl($uri);
+ return $this->http_client->PropFind( $uri, $scope);
+ }
+ /*!
+ @function proppatch
+ @abstract Sets DAV properties
+ @param uri uri of resource whose properties we are changing
+ @param attributes An array of attributes and values.
+ @param namespaces Extra namespace definitions that apply to the properties
+ @result Returns the HTTP status code
+ @discussion
+ To make DAV properties useful it helps to use a well established XML dialect
+ such as the "Dublin Core"
+
+ */
+ function proppatch($uri, $attributes, $namespaces='', $token='')
+ {
+ $uri = $this->encodeurl($uri);
+if (DEBUG_CACHE) echo 'cache cleared';
+ if (strlen($token))
+ {
+ $this->http_client->addHeader('If', '<'.$uri.'>'.' (<'.$token.'>)');
+ }
+ $this->cached_props = array();
+ //Begin evil nastiness
+ $davxml = '
+ $value)
+ {
+ $davxml .= '
+
+
+ <'.$name.'>'.utf8_encode(htmlspecialchars($value)).''.$name.'>
+
+
+';
+ }
+ $davxml .= '
+';
+
+ if (DEBUG_DAV_XML) {
+ echo 'send'.htmlentities($davxml).'
';
+ }
+ $this->http_client->requestBody = $davxml;
+ if( $this->http_client->sendCommand( 'PROPPATCH '.$uri.' HTTP/1.1' ) )
+ {
+ $this->http_client->processReply();
+ }
+
+ if (DEBUG_DAV_XML) {
+ echo 'Recieve'.htmlentities($this->http_client->getBody()).'
';
+ }
+ $this->http_client->removeHeader('If');
+ return $this->http_client->reply;
+ }
+
+ /*!
+ @function unlock
+ @abstract unlocks a locked resource on the DAV server
+ @param uri uri of the resource we are unlocking
+ @param a 'token' for the lock (to get the token, do a propfind)
+ @result true if successfull
+ @discussion
+ Not all DAV servers support locking (its in the RFC, but many common
+ DAV servers only implement "DAV class 1" (no locking)
+ */
+
+ function unlock($uri, $token)
+ {
+ $uri = $this->encodeurl($uri);
+if (DEBUG_CACHE) echo 'cache cleared';
+ $this->cached_props = array();
+ $this->http_client->addHeader('Lock-Token', '<'.$token.'>');
+ $this->http_client->sendCommand( 'UNLOCK '.$uri.' HTTP/1.1');
+ $this->http_client->removeHeader('Lock-Token');
+ $this->http_client->processReply();
+ if ( $this->http_client->reply == '204')
+ {
+ return true;
+ }
+ else
+ {
+ $headers = $this->http_client->getHeaders();
+ echo $this->http_client->getBody();
+ if ($headers['Content-Type'] == 'text/html')
+ {
+ echo $this->http_client->getBody();
+ die();
+ }
+ else
+ {
+ return false;
+ }
+ }
+ }
+
+ /*!
+ @function lock
+ @abstract locks a resource on the DAV server
+ @param uri uri of the resource we are locking
+ @param owner the 'owner' information for the lock (purely informative)
+ @param depth the depth to which we lock collections
+ @result true if successfull
+ @discussion
+ Not all DAV servers support locking (its in the RFC, but many common
+ DAV servers only implement "DAV class 1" (no locking)
+ */
+ function lock($uri, $owner, $depth=0, $timeout='infinity')
+ {
+ $uri = $this->encodeurl($uri);
+if (DEBUG_CACHE) echo 'cache cleared';
+ $this->cached_props = array();
+ $body = "
+
+\n
+ $owner
+\n";
+
+ $this->http_client->requestBody = utf8_encode( $body );
+ $this->http_client->addHeader('Depth', $depth);
+ if (! (strtolower(trim($timeout)) == 'infinite'))
+ {
+ $timeout = 'Second-'.$timeout;
+ }
+ $this->http_client->addHeader('Timeout', $timeout);
+
+ if( $this->http_client->sendCommand( "LOCK $uri HTTP/1.1" ) )
+ $this->http_client->processReply();
+ $this->http_client->removeHeader('timeout');
+ if ( $this->http_client->reply == '200')
+ {
+ return true;
+ }
+ else
+ {
+ $headers = $this->http_client->getHeaders();
+ echo $this->http_client->getBody();
+ return false;
+
+ }
+
+ }
+ /*!
+ @function options
+ @abstract determines the optional HTTP features supported by a server
+ @param uri uri of the resource we are seeking options for (or * for the whole server)
+ @result Returns an array of option values
+ @discussion
+ Interesting options include "ACCESS" (whether you can read a file) and
+ DAV (DAV features)
+
+ */
+ function options($uri)
+ {
+ $uri = $this->encodeurl($uri);
+ if( $this->http_client->sendCommand( 'OPTIONS '.$uri.' HTTP/1.1' ) == '200' )
+ {
+ $this->http_client->processReply();
+ $headers = $this->http_client->getHeaders();
+ return $headers;
+ }
+ else
+ {
+ return False;
+ }
+ }
+ /*!
+ @function dav_features
+ @abstract determines the features of a DAV server
+ @param uri uri of resource whose properties we are changing
+ @result Returns an array of option values
+ @discussion
+ Likely return codes include NULL (this isnt a dav server!), 1
+ (This is a dav server, supporting all standard DAV features except locking)
+ 2, (additionally supports locking (should also return 1)) and
+ 'version-control' (this server supports versioning extensions for this resource)
+ */
+ function dav_features($uri)
+ {
+ $uri = $this->encodeurl($uri);
+ $options = $this->options($uri);
+ $dav_options = $options['DAV'];
+ if ($dav_options)
+ {
+ $features=explode(',', $dav_options);
+ }
+ else
+ {
+ $features = NULL;
+ }
+ return $features;
+ }
+/**************************************************************
+ RFC 3253 DeltaV versioning extensions
+ **************************************************************
+ These are 100% untested, and almost certainly dont work yet...
+ eventually they will be made to work with subversion...
+ */
+
+ /*!
+ @function report
+ @abstract Report is a kind of extended PROPFIND - it queries properties accros versions etc
+ @param uri uri of resource whose properties we are changing
+ @param report the type of report desired eg DAV:version-tree, DAV:expand-property etc (see http://greenbytes.de/tech/webdav/rfc3253.html#METHOD_REPORT)
+ @param namespace any extra XML namespaces needed for the specified properties
+ @result Returns an array of option values
+ @discussion
+ From the relevent RFC:
+ "A REPORT request is an extensible mechanism for obtaining information about
+ a resource. Unlike a resource property, which has a single value, the value
+ of a report can depend on additional information specified in the REPORT
+ request body and in the REPORT request headers."
+ */
+ function report($uri, $report, $properties, $namespaces='')
+ {
+ $uri = $this->encodeurl($uri);
+ $davxml = '
+\n';
+ }
+ $davxml .= '\t\n';
+ if (DEBUG_DAV_XML) {
+ echo 'send'.htmlentities($davxml).'
';
+ }
+ $this->http_client->requestBody = $davxml;
+ if( $this->http_client->sendCommand( 'REPORT '.$uri.' HTTP/1.1' ) )
+ {
+ $this->http_client->processReply();
+ }
+
+ if (DEBUG_DAV_XML) {
+ echo 'Recieve'.htmlentities($this->http_client->getBody()).'
';
+ }
+ return $this->http_client->reply;
+ }
+
+ }
+
diff --git a/phpgwapi/inc/class.vfs_dav.inc.php b/phpgwapi/inc/class.vfs_dav.inc.php
new file mode 100644
index 0000000000..c0028ac508
--- /dev/null
+++ b/phpgwapi/inc/class.vfs_dav.inc.php
@@ -0,0 +1,2978 @@
+ *
+ * This class handles file/dir access for phpGroupWare *
+ * Copyright (C) 2001-2003 Jason Wies, Jonathon Sim *
+ * -------------------------------------------------------------------------*
+ * This library is part of the phpGroupWare API *
+ * http://www.phpgroupware.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 *
+ \**************************************************************************/
+
+
+ /* Relative defines. Used mainly by getabsolutepath () */
+ define ('RELATIVE_ROOT', 1);
+ define ('RELATIVE_USER', 2);
+ define ('RELATIVE_CURR_USER', 4);
+ define ('RELATIVE_USER_APP', 8);
+ define ('RELATIVE_PATH', 16);
+ define ('RELATIVE_NONE', 32);
+ define ('RELATIVE_CURRENT', 64);
+ define ('VFS_REAL', 1024);
+ define ('RELATIVE_ALL', RELATIVE_PATH);
+
+ /* These are used in calls to extra_sql () */
+ define ('VFS_SQL_SELECT', 1);
+ define ('VFS_SQL_DELETE', 2);
+ define ('VFS_SQL_UPDATE', 4);
+
+ /* These are used in calls to add_journal (), and allow journal messages to be more standard */
+ define ('VFS_OPERATION_CREATED', 1);
+ define ('VFS_OPERATION_EDITED', 2);
+ define ('VFS_OPERATION_EDITED_COMMENT', 4);
+ define ('VFS_OPERATION_COPIED', 8);
+ define ('VFS_OPERATION_MOVED', 16);
+ define ('VFS_OPERATION_DELETED', 32);
+
+ /*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 path_class
+ @abstract helper class for path_parts
+ */
+
+ class path_class
+ {
+ var $mask;
+ var $outside;
+ var $fake_full_path;
+ var $fake_leading_dirs;
+ var $fake_extra_path;
+ var $fake_name;
+ var $real_full_url;
+ var $real_full_auth_url;
+ var $real_full_secure_url;
+ var $real_full_path;
+ var $real_leading_dirs;
+ var $real_extra_path;
+ var $real_name;
+ var $fake_full_path_clean;
+ var $fake_leading_dirs_clean;
+ var $fake_extra_path_clean;
+ var $fake_name_clean;
+ var $real_full_url_clean;
+ var $real_full_auth_url_clean;
+ var $real_full_secure_url_clean;
+ var $real_full_path_clean;
+ var $real_leading_dirs_clean;
+ var $real_extra_path_clean;
+ var $real_name_clean;
+ }
+
+ class vfs
+ {
+ var $basedir;
+ var $fakebase;
+ var $relative;
+ var $working_id;
+ var $working_lid;
+ var $attributes;
+ var $override_acl;
+ var $linked_dirs;
+ var $meta_types;
+ var $now;
+ var $override_locks;
+ //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'
+ );
+
+ /*!
+ @function vfs
+ @abstract constructor, sets up variables
+ */
+
+ function vfs ()
+ {
+ $this->basedir = $GLOBALS['phpgw_info']['server']['files_dir'];
+ $this->fakebase = '/home';
+ $this->working_id = $GLOBALS['phpgw_info']['user']['account_id'];
+ $this->working_lid = $GLOBALS['phpgw']->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 */
+ if ($GLOBALS['phpgw_info']['server']['db_type']=='mssql'
+ || $GLOBALS['phpgw_info']['server']['db_type']=='sybase')
+ {
+ $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__);
+ }
+ else
+ {
+ $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__);
+ }
+
+ $this->linked_dirs = array ();
+ while ($GLOBALS['phpgw']->db->next_record ())
+ {
+ $this->linked_dirs[] = $GLOBALS['phpgw']->db->Record;
+ }
+
+
+ $this->repository = $GLOBALS['phpgw_info']['server']['files_dir'];
+ $this->dav_user=$GLOBALS['phpgw_info']['user']['userid'];
+ $this->dav_pwd=$GLOBALS['phpgw_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'] : 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 'DAV client debugging enabled!';
+ $this->dav_client->set_debug(DBGTRACE|DBGINDATA|DBGOUTDATA|DBGSOCK|DBGLOW);
+ }
+ if (!$result)
+ {
+ echo 'Cannot connect to the file repository server!
';
+ 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['phpgw_info']['flags']['currentapp'];
+ $session_data = base64_decode($GLOBALS['phpgw']->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 ' vfs_sql_dav debug: ';
+ if (is_array($info))
+ {
+ print_r($info);
+ }
+ else
+ {
+ echo $info;
+ }
+ echo '
';
+ }
+ }
+
+ /*!
+ @function dav_path
+ @abstract 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'];
+ }
+
+ /*!
+ @function glue_url
+ @abstract 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();
+ }
+
+
+ /*!
+ @function set_relative
+ @abstract 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'];
+ }
+ }
+
+ /*!
+ @function get_relative
+ @abstract Return relativity bitmask
+ @discussion 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;
+ }
+ }
+
+ /*!
+ @function sanitize
+ @abstract Removes leading .'s from 'string'
+ @discussion 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
+ @result $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));
+ }
+
+ /*!
+ @function securitycheck
+ @abstract Security check function
+ @discussion Checks for basic violations such as ..
+ If securitycheck () fails, run your string through vfs->sanitize ()
+ @param string string to check security of
+ @result 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;
+ }
+ }
+
+ /*!
+ @function db_clean
+ @abstract Clean 'string' for use in database queries
+ @param string String to clean
+ @result Cleaned version of 'string'
+ */
+ function db_clean ($data)
+ {
+ if (!is_array ($data))
+ {
+ $data = array ();
+ }
+
+ $string = ereg_replace ("'", "\'", $data['string']);
+
+ return $string;
+ }
+
+ /*!
+ @function extra_sql
+ @abstract 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
+ @result Extra SQL code
+ */
+ function extra_sql ($data)
+ { //This is purely for SQL
+ return '';
+ }
+
+ /*!
+ @function add_journal
+ @abstract Add a journal entry after (or before) completing an operation,
+ and increment the version number. This function should be used internally only
+ @discussion 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
+ @result Boolean True/False
+ */
+ function add_journal ($data) {
+ //The journalling dont work :( Ideally this will become "versioning"
+ return True;
+ }
+
+
+ /*!
+ @function flush_journal
+ @abstract Flush journal entries for $string. Used before adding $string
+ @discussion 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)
+ @result Boolean True/False
+ */
+ function flush_journal ($data)
+ {
+ return True;
+ }
+
+
+ /*!
+ @function get_journal
+ @abstract 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'
+ @result Array of arrays of journal entries
+ */
+ function get_journal ($data)
+ {
+ return array();
+ }
+
+ /*!
+ @function path_parts
+ @abstract 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
+ @result $rarray/$robject Array or object containing the fake and real component parts of the path
+ @discussion 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 "
fake_full_path: $rarray[fake_full_path]
+
fake_leading_dirs: $rarray[fake_leading_dirs]
+
fake_extra_path: $rarray[fake_extra_path]
+
fake_name: $rarray[fake_name]
+
real_full_path: $rarray[real_full_path]
+
real_full_url: $rarray[real_full_url]
+
real_leading_dirs: $rarray[real_leading_dirs]
+
real_extra_path: $rarray[real_extra_path]
+
real_name: $rarray[real_name]";
+*/
+
+ if ($data['object'])
+ {
+ return ($robject);
+ }
+ else
+ {
+ return ($rarray);
+ }
+ }
+
+ /*!
+ @function getabsolutepath
+ @abstract 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)
+ @result $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['phpgw_info']['user']['account_lid'] . $sep;
+ }
+
+ if ($data['mask'][0] & RELATIVE_USER_APP)
+ {
+ $basedir = $basedir . "." . $GLOBALS['phpgw_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;
+ }
+
+ /*!
+ @function acl_check
+ @abstract Check ACL access to $file for $GLOBALS['phpgw_info']["user"]["account_id"];
+ @param string File to check access of
+ @discussion 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 PHPGW_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
+ @result Boolean. True if access is ok, False otherwise
+ */
+ function acl_check ($data)
+ {
+ return True;
+ $default_values = array
+ (
+ 'relatives' => array (RELATIVE_CURRENT),
+ 'operation' => PHPGW_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'] == PHPGW_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['phpgw_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['phpgw']->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;
+ }
+ }
+
+ /*!
+ @function cd
+ @abstract Change directory
+ @discussion 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/";
+ @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['phpgw']->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['phpgw']->session->appsession('vfs','',$basedir);
+
+ return True;
+ }
+
+ /*!
+ @function pwd
+ @abstract current working dir
+ @param full default True returns full fake path, else just the extra dirs (false strips the leading /)
+ @result $currentdir currentdir
+ */
+ function pwd ($data = '')
+ {
+ $default_values = array
+ (
+ 'full' => True
+ );
+
+ $data = array_merge ($this->default_values ($data, $default_values), $data);
+
+ $currentdir = $GLOBALS['phpgw']->session->appsession('vfs','');
+
+ if (!$data['full'])
+ {
+ $currentdir = ereg_replace ("^/", '', $currentdir);
+ }
+
+ if ($currentdir == '' && $data['full'])
+ {
+ $currentdir = '/';
+ }
+
+ $currentdir = trim ($currentdir);
+
+ return $currentdir;
+ }
+
+ /*!
+ @function read
+ @abstract return file contents
+ @param string filename
+ @param relatives Relativity array
+ @result $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['phpgw_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' => PHPGW_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['phpgw']->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['phpgw']->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['phpgw']->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;
+ }
+ }
+
+ /*!
+ @function write
+ @abstract write to a file
+ @param string file name
+ @param relatives Relativity array
+ @param content content
+ @result 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 = PHPGW_ACL_EDIT;
+ $journal_operation = VFS_OPERATION_EDITED;
+ }
+ else
+ {
+ $acl_operation = PHPGW_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;
+ }
+ }
+ }
+
+ /*!
+ @function touch
+ @abstract 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
+ @result 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['phpgw_info']['user']['account_id'];
+ $currentapp = $GLOBALS['phpgw_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' => PHPGW_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);
+ }
+
+ /*!
+ @function cp
+ @abstract copy file
+ @param from from file/directory
+ @param to to file/directory
+ @param relatives Relativity array
+ @result 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['phpgw_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' => PHPGW_ACL_READ
+ ))
+ )
+ {
+ return False;
+ }
+
+ if ($this->file_exists (array(
+ 'string' => $t->fake_full_path,
+ 'relatives' => array ($t->mask)
+ ))
+ )
+ {
+ $remote_operation=PHPGW_ACL_EDIT;
+ }
+ else
+ {
+ $remote_operation=PHPGW_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);
+ }
+
+ /*!
+ @function mv
+ @abstract move file/directory
+ @param from from file/directory
+ @param to to file/directory
+ @param relatives Relativity array
+ @result 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['phpgw_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' => PHPGW_ACL_READ
+ ))
+ || !$this->acl_check (array(
+ 'string' => $f->fake_full_path,
+ 'relatives' => array ($f->mask),
+ 'operation' => PHPGW_ACL_DELETE
+ ))
+ )
+ {
+ return False;
+ }
+
+ if (!$this->acl_check (array(
+ 'string' => $t->fake_full_path,
+ 'relatives' => array ($t->mask),
+ 'operation' => PHPGW_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' => PHPGW_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;
+ }
+
+ /*!
+ @function move
+ @abstract shortcut to mv
+ */
+ function move ($data)
+ {
+ return $this->mv ($data);
+ }
+
+ /*!
+ @function rm
+ @abstract delete file/directory
+ @param string file/directory to delete
+ @param relatives Relativity array
+ @result 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' => PHPGW_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
+ )
+ );
+
+ $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__);
+
+ //rmdir ($p->real_full_path);
+ $this->dav_client->delete($p->real_full_path.'/','Infinity', $this->override_locks[$p->real_full_path]);
+
+ return True;
+ }
+ }
+
+ /*!
+ @function delete
+ @abstract shortcut to rm
+ */
+ function delete ($data)
+ {
+ return $this->rm ($data);
+ }
+
+ /*!
+ @function mkdir
+ @abstract make a new directory
+ @param string Directory name
+ @param relatives Relativity array
+ @result 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['phpgw_info']['user']['account_id'];
+ $currentapp = $GLOBALS['phpgw_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' => PHPGW_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['phpgw_info']['user']['account_lid'];
+ if ( ! $this->write(array(
+ 'string' => $p->fake_full_path.'/.htaccess',
+ 'content' => $htaccess,
+ 'relatives' => array(RELATIVE_NONE)
+ )))
+ {
+ echo 'Unable to write .htaccess file
';
+ };
+ }
+ }
+ return True;
+ }
+ else
+ {
+ return False;
+ }
+ }
+
+ /*!
+ @function make_link
+ @abstract Make a link from virtual directory 'vdir' to real directory 'rdir'
+ @discussion 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
+ @result 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['phpgw_info']['user']['account_id'];
+ $currentapp = $GLOBALS['phpgw_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' => PHPGW_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;
+ */
+ }
+
+ /*!
+ @function set_attributes
+ @abstract 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
+ @result Boolean True/False
+ @discussion 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=PHPGW_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['phpgw']->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['phpgw']->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['phpgw']->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;
+ }
+
+ /*!
+ @function correct_attributes
+ @abstract Set the correct attributes for 'string' (e.g. owner)
+ @param string File/directory to correct attributes of
+ @param relatives Relativity array
+ @result 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['phpgw']->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;
+ }
+
+ /*!
+ @function file_type
+ @abstract 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
+ @result 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' => PHPGW_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;
+ }
+
+ /*!
+ @function get_ext_mime_type
+ @abstract return MIME type based on file extension
+ @description Authors: skeeter
+ Internal use only. Applications should call vfs->file_type ()
+ @param string File name, with or without leading paths
+ @result MIME type based on file extension
+ */
+ function get_ext_mime_type ($data)
+ {
+ $file=basename($data['string']);
+ $mimefile=PHPGW_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= 2)
+ {
+ for($j=1;$j 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;
+ }
+ }
+
+ /*!
+ @function get_size
+ @abstract 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?
+ @result 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' => PHPGW_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;
+ }
+
+ /*!
+ @function checkperms
+ @abstract Check if $this->working_id has write access to create files in $dir
+ @discussion Simple call to acl_check
+ @param string Directory to check access of
+ @param relatives Relativity array
+ @result 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' => PHPGW_ACL_ADD
+ ))
+ )
+ {
+ return False;
+ }
+ else
+ {
+ return True;
+ }
+ }
+
+ /*!
+ @function ls
+ @abstract get directory listing or info about a single file
+ @discussion 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
+ @result 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;
+ }
+
+ /*!
+ @function dir
+ @abstract shortcut to ls
+ */
+ function dir ($data)
+ {
+ return $this->ls ($data);
+ }
+
+ /*!
+ @function command_line
+ @abstract Process and run a Unix-sytle command line
+ @discussion 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
+ @result $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 (is_dir ($p->real_full_path))
+ {
+ $mime_type = 'Directory';
+ }
+ else
+ {
+ $mime_type = $this->get_ext_mime_type (array(
+ 'string' => $p->fake_name
+ )
+ );
+
+ if($mime_type)
+ {
+ $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__);
+ }
+ }
+
+ $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['phpgw_info']['flags']['currentapp'];
+ $a = array();
+ foreach ($this->override_locks as $name => $token)
+ {
+ $a[] = $name.';'.$token;
+ }
+ $session_data = implode('\n', $a);
+ $this->session = $GLOBALS['phpgw']->session->appsession ('vfs_dav',$app, base64_encode($session_data));
+
+ }
+ }
+?>