From 501df49cbb52e0ba49667c245968bc451d9c0ba3 Mon Sep 17 00:00:00 2001 From: Ralf Becker Date: Mon, 14 Apr 2008 05:52:24 +0000 Subject: [PATCH] stream wrapper interface is now eGW VFS interface - if you already run the 1.5.003 update (AND modified anything in the VFS), you have to re-run it, to not loose your modifications or risk an inconsistent VFS (DB does not match filesystem) - to re-run the 1.5.003 update (only if your version is already 1.5.003 or bigger!) run the following sql: UPDATE egw_applications SET app_version=1.5.002 WHERE app_name=phpgwapi - the new vfs supports now an extended ACL, if that is supported by the backend (sqlfs only currently) - eacl allows to set separate recursive acl rights for different users or groups on a directory (and subdirs) - former group grants of group dirs are converted to eacl, thought we only support read or read+write access (no extra add or delete) - attachments via the links class now also use a stream wrapper interface (links_stream_wrapper) and WebDAV as download handler (which requires no longer filemanager run rights) --- phpgwapi/inc/class.bolink.inc.php | 50 +-- phpgwapi/inc/class.egw_link.inc.php | 354 +++++------------- phpgwapi/inc/class.egw_vfs.inc.php | 156 ++++++-- phpgwapi/inc/class.html.inc.php | 9 +- .../inc/class.links_stream_wrapper.inc.php | 147 ++++++++ .../inc/class.oldvfs_stream_wrapper.inc.php | 64 +--- .../inc/class.sqlfs_stream_wrapper.inc.php | 312 +++++++++++++-- phpgwapi/inc/class.vfs_stream_wrapper.inc.php | 68 ++-- phpgwapi/inc/class.vfs_webdav_server.inc.php | 3 +- phpgwapi/setup/setup.inc.php | 10 +- phpgwapi/setup/tables_update.inc.php | 69 +++- 11 files changed, 786 insertions(+), 456 deletions(-) create mode 100644 phpgwapi/inc/class.links_stream_wrapper.inc.php diff --git a/phpgwapi/inc/class.bolink.inc.php b/phpgwapi/inc/class.bolink.inc.php index 29be4bca2b..a7a877407b 100644 --- a/phpgwapi/inc/class.bolink.inc.php +++ b/phpgwapi/inc/class.bolink.inc.php @@ -21,9 +21,6 @@ */ class bolink extends egw_link { - var $public_functions = array( - 'get_file' => true, - ); /** * @deprecated use egw_link::VFS_APPNAME */ @@ -39,51 +36,6 @@ class bolink extends egw_link */ function __construct() { - + error_log('Call to depricated bolink class from '.function_backtrace(1)); } - - /** - * Download an attached file - * - * @todo replace it with egw_vfs::download_url, once egw_vfs/webdav supports the attachments - * @param array $link=null - * @return array with params (eg. menuaction) for download link - */ - function get_file(array $link=null) - { - if (is_array($link)) - { - return array( - 'menuaction' => 'phpgwapi.bolink.get_file', - 'app' => $link['app2'], - 'id' => $link['id2'], - 'filename' => $link['id'] - ); - } - $app = $_GET['app']; - $id = $_GET['id']; - $filename = $_GET['filename']; - - if (empty($app) || empty($id) || empty($filename) || !$this->title($app,$id)) - { - $GLOBALS['egw']->framework->render('

'.lang('Access not permitted')." !!!

\n", - lang('Access not permitted'),true); - $GLOBALS['egw']->common->egw_exit(); - } - $browser = new browser(); - - $local = $this->attached_local($app,$id,$filename,$_SERVER['REMOTE_ADDR'],$browser->is_windows()); - - if ($local) - { - Header('Location: ' . $local); - } - else - { - $info = $this->info_attached($app,$id,$filename); - $browser->content_header($filename,$info['type']); - echo $this->read_attached($app,$id,$filename); - } - $GLOBALS['egw']->common->egw_exit(); - } } \ No newline at end of file diff --git a/phpgwapi/inc/class.egw_link.inc.php b/phpgwapi/inc/class.egw_link.inc.php index e019df5f58..071e82a2da 100644 --- a/phpgwapi/inc/class.egw_link.inc.php +++ b/phpgwapi/inc/class.egw_link.inc.php @@ -46,7 +46,8 @@ * 'add_id' => 'link_id', // --------------------- " ------------------- id * 'add_popup' => '400x300', // size of popup (XxY), if add is in popup * 'notify' => 'app.class.method', // method to be called if an other applications liks or unlinks with app: notify(array $data) - * ); + * 'file_access' => 'app.class.method', // method to be called to check file access rights, see links_stream_wrapper class + * ); // boolean file_access(string $id,int $check,string $rel_path) * } * All entries are optional, thought you only get conected functionality, if you implement them ... * @@ -62,12 +63,6 @@ */ class egw_link extends solink { - /** - * Basepath for attached files - * - * @todo change to '/apps' once the new vfs is complete - */ - const VFS_BASEDIR = '/infolog'; /** * appname used for returned attached files (!= 'filemanager'!) */ @@ -90,14 +85,6 @@ class egw_link extends solink 'add_popup' => '700x750', ), ); - /** - * Instance of the vfs class - * - * @var vfs - */ - private static $vfs; - private static $link_pathes = array(); - private static $send_file_ips = array(); /** * Caches link titles for a better performance * @@ -119,11 +106,6 @@ class egw_link extends solink */ static function init_static( ) { - self::$vfs = new vfs(); - - self::$link_pathes =& $GLOBALS['egw_info']['server']['link_pathes']; - self::$send_file_ips =& $GLOBALS['egw_info']['server']['send_file_ips']; - // other apps can participate in the linking by implementing a search_link hook, which // has to return an array in the format of an app_register entry // for performance reasons, we do it only once / cache it in the session @@ -557,19 +539,7 @@ class egw_link extends solink } if (is_array($link)) { - $size = $link['size']; - if ($size_k = (int)($size / 1024)) - { - if ((int)($size_k / 1024)) - { - $size = sprintf('%3.1dM',doubleval($size_k)/1024.0); - } - else - { - $size = $size_k.'k'; - } - } - $extra = ': '.$link['type'] . ' '.$size; + $extra = ': '.$link['type'] . ' '.egw_vfs::hsize($link['size']); } return self::$title_cache[$app.':'.$id] = $id.$extra; } @@ -662,7 +632,6 @@ class egw_link extends solink /** * view entry $id of $app * - * @ToDo use webdav url of new vfs, once ACL for /apps is ready * @param string $app appname * @param string $id id in $app * @param array $link=null link-data for file-attachments @@ -672,12 +641,7 @@ class egw_link extends solink { if ($app == self::VFS_APPNAME && !empty($id) && is_array($link)) { - return array( - 'menuaction' => 'phpgwapi.bolink.get_file', - 'app' => $link['app2'], - 'id' => $link['id2'], - 'filename' => $link['id'] - ); + return egw_vfs::download_url(parse_url(self::vfs_path($link['app2'],$link['id2'],$link['id']),PHP_URL_PATH)); } if ($app == '' || !is_array($reg = self::$app_register[$app]) || !isset($reg['view']) || !isset($reg['view_id'])) { @@ -710,11 +674,21 @@ class egw_link extends solink */ static function is_popup($app,$action='view') { - if (!($reg = self::$app_register[$app]) || !$reg[$action.'_popup']) - { - return false; - } - return $reg[$action.'_popup']; + return self::get_registry($app,$action.'_popup'); + } + + /** + * Check if $app is in the registry and has an entry for $name + * + * @param string $app app-name + * @param string $name name / key in the registry, eg. 'view' + * @return boolean/string false if $app is not registered, otherwise string with the value for $name + */ + static function get_registry($app,$name) + { + $reg = self::$app_register[$app]; + + return isset($reg) ? $reg[$name] : false; } /** @@ -723,26 +697,30 @@ class egw_link extends solink * All link-files are based in the vfs-subdir '/infolog'. For other apps * separate subdirs with name app are created. * - * @ToDo change to new VFS_BASEDIR /apps (incl. /apps/infolog) * @param string $app appname * @param string $id='' id in $app * @param string $file='' filename - * @param boolean/array $relatives=False return path as array with path in string incl. relatives * @return string/array path or array with path and relatives, depending on $relatives */ - static function vfs_path($app,$id='',$file='',$relatives=False) + static function vfs_path($app,$id='',$file='') { - $path = self::VFS_BASEDIR . ($app == '' || $app == 'infolog' ? '' : '/'.$app) . - ($id != '' ? '/' . $id : '') . ($file != '' ? '/' . $file : ''); - - if (self::DEBUG) + $path = links_stream_wrapper::BASEURL; + if ($app) { - echo "

egw_link::vfs_path('$app','$id','$file') = '$path'

\n"; + $path .= '/'.$app; + + if ($id) + { + $path .= '/'.$id; + + if ($file) + { + $path .= '/'.$file; + } + } } - return $relatives ? array( - 'string' => $path, - 'relatives' => is_array($relatives) ? $relatives : array($relatives) - ) : $path; + //error_log(__METHOD__."($app,$id,$file)=$path"); + return $path; } /** @@ -757,7 +735,8 @@ class egw_link extends solink * $file['path'] path of the file on the client computer * $file['ip'] of the client (path and ip are only needed if u want a symlink (if possible)) * @param string $comment='' comment to add to the link - * @return int negative id of phpgw_vfs table as negative link-id's are for vfs attachments + * @todo remark/comment from the vfs + * @return int negative id of egw_sqlfs table as negative link-id's are for vfs attachments */ static function attach_file($app,$id,$file,$comment='') { @@ -765,73 +744,35 @@ class egw_link extends solink { echo "

attach_file: app='$app', id='$id', tmp_name='$file[tmp_name]', name='$file[name]', size='$file[size]', type='$file[type]', path='$file[path]', ip='$file[ip]', comment='$comment'

\n"; } - // create the root for attached files in infolog, if it does not exists - $vfs_data = array('string'=>self::VFS_BASEDIR,'relatives'=>array(RELATIVE_ROOT)); - if (!(self::$vfs->file_exists($vfs_data))) - { - self::$vfs->override_acl = 1; - self::$vfs->mkdir($vfs_data); - self::$vfs->override_acl = 0; - } + $app_dir = self::vfs_path($app); - $vfs_data = self::vfs_path($app,False,False,RELATIVE_ROOT); - if (!(self::$vfs->file_exists($vfs_data))) - { - self::$vfs->override_acl = 1; - self::$vfs->mkdir($vfs_data); - self::$vfs->override_acl = 0; - } - $vfs_data = self::vfs_path($app,$id,False,RELATIVE_ROOT); - if (!(self::$vfs->file_exists($vfs_data))) - { - self::$vfs->override_acl = 1; - self::$vfs->mkdir($vfs_data); - self::$vfs->override_acl = 0; - } - $fname = self::vfs_path($app,$id,$file['name']); - $tfname = ''; - if (!empty($file['path']) && is_array(self::$link_pathes) && count(self::$link_pathes)) - { - $file['path'] = str_replace('\\\\','/',$file['path']); // vfs uses only '/' - @reset(self::$link_pathes); - while ((list($valid,$trans) = @each(self::$link_pathes)) && !$tfname) - { // check case-insensitive for WIN etc. - $check = $valid[0] == '\\' || strpos(':',$valid) !== false ? 'eregi' : 'ereg'; - $valid2 = str_replace('\\','/',$valid); - //echo "

attach_file: ereg('".self::$send_file_ips[$valid]."', '$file[ip]')=".ereg(self::$send_file_ips[$valid],$file['ip'])."

\n"; - if ($check('^('.$valid2.')(.*)$',$file['path'],$parts) && - ereg(self::$send_file_ips[$valid],$file['ip']) && // right IP - self::$vfs->file_exists(array('string'=>$trans.$parts[2],'relatives'=>array(RELATIVE_NONE|VFS_REAL)))) - { - $tfname = $trans.$parts[2]; - } - //echo "

attach_file: full_fname='$file[path]', valid2='$valid2', trans='$trans', check=$check, tfname='$tfname', parts=(x,'${parts[1]}','${parts[2]}')

\n"; - } - if ($tfname && !self::$vfs->securitycheck(array('string'=>$tfname))) - { - return False; //lang('Invalid filename').': '.$tfname; - } - } - self::$vfs->override_acl = 1; - self::$vfs->cp(array( - 'symlink' => !!$tfname, // try a symlink - 'from' => $tfname ? $tfname : $file['tmp_name'], - 'to' => $fname, - 'relatives' => array(RELATIVE_NONE|VFS_REAL,RELATIVE_ROOT), - )); - self::$vfs->set_attributes(array( - 'string' => $fname, - 'relatives' => array (RELATIVE_ROOT), - 'attributes' => array ( - 'mime_type' => $file['type'], - 'comment' => stripslashes ($comment), - 'app' => $app - ))); - self::$vfs->override_acl = 0; + // we dont want an owner, as this would give rights independent of the apps ACL + $current_user = egw_vfs::$user; egw_vfs::$user = 0; - $link = self::info_attached($app,$id,$file['name']); - - return is_array($link) ? $link['link_id'] : False; + $Ok = true; + if (!file_exists($app_dir)) + { + egw_vfs::$is_root = true; + $Ok = mkdir($app_dir,0700,true); + if (!$Ok) echo "

Can't mkdir($app_dir,0700,true)!

\n"; + egw_vfs::$is_root = false; + } + + $entry_dir = self::vfs_path($app,$id); + if ($Ok && !file_exists($entry_dir)) + { + $Ok = mkdir($entry_dir,0700); + } + egw_vfs::$user = $current_user; + + if ($Ok) + { + $Ok = copy($file['tmp_name'],$fname = $entry_dir.'/'.$file['name']) && + ($stat = links_stream_wrapper::url_stat($fname,0)); + } + // todo: set comment + + return $Ok ? -$stat['ino'] : false; } /** @@ -845,34 +786,24 @@ class egw_link extends solink { if ((int)$app > 0) // is file_id { - $link = self::fileinfo2link($file_id=$app); - $app = $link['app2']; - $id = $link['id2']; - $fname = $link['id']; + $url = links_stream_wrapper::PREFIX.links_stream_wrapper::id2path($app); + } + else + { + if (empty($app) || empty($id)) + { + return False; // dont delete more than all attachments of an entry + } + $url = self::vfs_path($app,$id,$fname); } if (self::DEBUG) { - echo "

egw_link::delete_attached('$app','$id','$fname') file_id=$file_id

\n"; + echo "

egw_link::delete_attached('$app','$id','$fname') url=$url

\n"; } - if (empty($app) || empty($id)) + if (($Ok = egw_vfs::remove($url,true)) && ((int)$app > 0 || $fname)) { - return False; // dont delete more than all attachments of an entry - } - $vfs_data = self::vfs_path($app,$id,$fname,RELATIVE_ROOT); - - $Ok = false; - if (self::$vfs->file_exists($vfs_data)) - { - self::$vfs->override_acl = 1; - $Ok = self::$vfs->delete($vfs_data); - self::$vfs->override_acl = 0; - } - // if filename given (and now deleted) check if dir is empty and remove it in that case - if ($fname && !count(self::$vfs->ls($vfs_data=self::vfs_path($app,$id,'',RELATIVE_ROOT)))) - { - self::$vfs->override_acl = 1; - self::$vfs->delete($vfs_data); - self::$vfs->override_acl = 0; + // try removing the dir, in case it's empty + @rmdir(egw_vfs::dirname($url)); } return $Ok; } @@ -887,52 +818,44 @@ class egw_link extends solink */ static function info_attached($app,$id,$filename) { - self::$vfs->override_acl = 1; - $attachments = self::$vfs->ls(self::vfs_path($app,$id,$filename,RELATIVE_NONE)); - self::$vfs->override_acl = 0; - - if (!count($attachments) || !$attachments[0]['name']) + $url = self::vfs_path($app,$id,$filename); + if (!($stat = links_stream_wrapper::url_stat($url,STREAM_URL_STAT_QUIET))) { - return False; + return false; } - return self::fileinfo2link($attachments[0]); + return self::fileinfo2link($stat,$url); } /** * converts a fileinfo (row in the vfs-db-table) in a link * * @param array/int $fileinfo a row from the vfs-db-table (eg. returned by the vfs ls static function) or a file_id of that table + * @todo remark/comment from the vfs * @return array a 'kind' of link-array */ - static function fileinfo2link($fileinfo) + static function fileinfo2link($fileinfo,$url=null) { if (!is_array($fileinfo)) { - $fileinfo = self::$vfs->ls(array('file_id' => $fileinfo)); - list(,$fileinfo) = each($fileinfo); - - if (!is_array($fileinfo)) + $url = links_stream_wrapper::id2path($fileinfo); + if (!($fileinfo = links_stream_wrapper::url_stat($url,STREAM_URL_STAT_QUIET))) { - return False; + return false; } } - $lastmod = $fileinfo[!empty($fileinfo['modified']) ? 'modified' : 'created']; - list($y,$m,$d) = explode('-',$lastmod); - $lastmod = mktime(0,0,0,$m,$d,$y); - - $dir_parts = array_reverse(explode('/',$fileinfo['directory'])); + list(,,,,$app,$id) = explode('/',$url); // links://apps/$app/$id return array( 'app' => self::VFS_APPNAME, 'id' => $fileinfo['name'], - 'app2' => $dir_parts[1], - 'id2' => $dir_parts[0], - 'remark' => $fileinfo['comment'], - 'owner' => $fileinfo['owner_id'], - 'link_id' => -$fileinfo['file_id'], - 'lastmod' => $lastmod, + 'app2' => $app, + 'id2' => $id, + 'remark' => '', + 'owner' => $fileinfo['uid'], + 'link_id' => -$fileinfo['ino'], + 'lastmod' => $fileinfo['mtime'], 'size' => $fileinfo['size'], - 'type' => $fileinfo['mime_type'] + 'type' => $fileinfo['mime'], ); } @@ -945,97 +868,18 @@ class egw_link extends solink */ static function list_attached($app,$id) { - self::$vfs->override_acl = 1; - $attachments = self::$vfs->ls(self::vfs_path($app,$id,False,RELATIVE_ROOT)); - self::$vfs->override_acl = 0; + $url = self::vfs_path($app,$id); + //error_log(__METHOD__."($app,$id) url=$url"); - if (!count($attachments) || !$attachments[0]['name']) + $attached = array(); + foreach(egw_vfs::find($url,array('url'=>true,'need_mime'=>true,'type'=>'f'),true) as $url => $fileinfo) { - return False; - } - foreach($attachments as $fileinfo) - { - $link = self::fileinfo2link($fileinfo); + $link = self::fileinfo2link($fileinfo,$url); $attached[$link['link_id']] = $link; } return $attached; } - /** - * checks if path starts with a '\\' or has a ':' in it - * - * @param string $path path to check - * @return boolean true if windows path, false otherwise - */ - static function is_win_path($path) - { - return $path{0} == '\\' || $path{1} == ':'; - } - - /** - * reads the attached file and returns the content - * - * @param string $app appname - * @param string $id id in app - * @param string $filename filename - * @return string/boolean content of the attached file, null if $id not found, false if no view perms - */ - static function read_attached($app,$id,$filename) - { - $ret = null; - if (empty($app) || !$id || empty($filename) || !($ret = self::title($app,$id))) - { - return $ret; - } - self::$vfs->override_acl = 1; - $data = self::$vfs->read(self::vfs_path($app,$id,$filename,RELATIVE_ROOT)); - self::$vfs->override_acl = 0; - - return $data; - } - - /** - * Checks if filename should be local availible and if so returns - * - * @param string $app appname - * @param string $id id in app - * @param string $filename filename - * @param string $id ip-address of user - * @param boolean $win_user true if user is on windows, otherwise false - * @return string 'file:/path' for HTTP-redirect else return False - */ - static function attached_local($app,$id,$filename,$ip,$win_user) - { - //echo "

attached_local(app=$app, id='$id', filename='$filename', ip='$ip', win_user='$win_user', count(send_file_ips)=".count(self::$send_file_ips).")

\n"; - - if (!$id || !$filename || /* !self::check_access($info_id,EGW_ACL_READ) || */ - !count(self::$send_file_ips)) - { - return False; - } - $link = self::$vfs->ls(self::vfs_path($app,$id,$filename,RELATIVE_ROOT)+array('readlink'=>True)); - $link = @$link[0]['symlink']; - - if ($link && is_array(self::$link_pathes)) - { - reset(self::$link_pathes); $fname = ''; - while ((list($valid,$trans) = each(self::$link_pathes)) && !$fname) - { - if (!self::is_win_path($valid) == !$win_user && // valid for this OS - $win_user && // only for IE/windows atm - eregi('^'.$trans.'(.*)$',$link,$parts) && // right path - ereg(self::$send_file_ips[$valid],$ip)) // right IP - { - $fname = $valid . $parts[1]; - $fname = !$win_user ? str_replace('\\','/',$fname) : str_replace('/','\\',$fname); - return 'file:'.($win_user ? '//' : '' ).$fname; - } - //echo "

attached_local: link=$link, valid=$valid, trans='$trans', fname='$fname', parts=(x,'${parts[1]}','${parts[2]}')

\n"; - } - } - return False; - } - /** * reverse static function of htmlspecialchars() * diff --git a/phpgwapi/inc/class.egw_vfs.inc.php b/phpgwapi/inc/class.egw_vfs.inc.php index 0ef705816b..7f124a866c 100644 --- a/phpgwapi/inc/class.egw_vfs.inc.php +++ b/phpgwapi/inc/class.egw_vfs.inc.php @@ -84,6 +84,12 @@ class egw_vfs extends vfs_stream_wrapper * @var int */ static $user; + /** + * Current user is an eGW admin + * + * @var boolean + */ + static $is_admin = false; /** * Total of last find call * @@ -322,7 +328,7 @@ class egw_vfs extends vfs_stream_wrapper $is_dir = is_dir($path); if (!isset($options['remove'])) { - $options['remove'] = count($base) == 1 ? ($path == '/' ? 1 : strlen($path)+1) : 0; + $options['remove'] = count($base) == 1 ? strlen($path)+(int)(substr($path,-1)!='/') : 0; } if ((int)$options['mindepth'] == 0 && (!$dirs_last || !$is_dir)) { @@ -332,8 +338,8 @@ class egw_vfs extends vfs_stream_wrapper { while($file = readdir($dir)) { - $file = $path.'/'.$file; - + $file = self::concat($path,$file); + if ((int)$options['mindepth'] <= 1) { self::_check_add($options,$file,$result,1); @@ -416,7 +422,7 @@ class egw_vfs extends vfs_stream_wrapper } return $result; } - //echo "egw_vfs::find($path)="; _debug_array(array_keys($result)); + //error_log("egw_vfs::find($path)=".print_r(array_keys($result),true)); if ($exec !== true) { return array_keys($result); @@ -511,11 +517,13 @@ class egw_vfs extends vfs_stream_wrapper * Recursiv remove all given url's, including it's content if they are files * * @param string/array $urls url or array of url's + * @param boolean $allow_urls=false allow to use url's, default no only pathes (to stay within the vfs) * @return array */ - static function remove($urls) + static function remove($urls,$allow_urls=false) { - return self::find($urls,array('depth'=>true),array(__CLASS__,'_rm_rmdir')); + //error_log(__METHOD__.'('.print_r($urls).')'); + return self::find($urls,array('depth'=>true,'url'=>$allow_urls),array(__CLASS__,'_rm_rmdir')); } /** @@ -543,45 +551,52 @@ class egw_vfs extends vfs_stream_wrapper */ static function is_readable($path,$check = 4) { - if (!($stat = self::stat($path))) - { - return false; - } - return self::check_access($stat,$check); + return self::check_access($path,$check); } /** * The stream_wrapper interface checks is_{readable|writable|executable} against the webservers uid, * which is wrong in case of our vfs, as we use the current users id and memberships * - * @param array $stat + * @param array/string $path stat array or path * @param int $check mode to check: one or more or'ed together of: 4 = read, 2 = write, 1 = executable + * @param array $stat=null stat array, to not query it again * @return boolean */ - static function check_access($stat,$check) + static function check_access($path,$check,$stat=null) { - //error_log(__METHOD__."(stat[name]={$stat['name']},stat[mode]=".sprintf('%o',$stat['mode']).",$check)"); - if (self::$is_root) { return true; } + + // throw exception if stat array is used insead of path, can be removed soon + if (is_array($path)) + { + throw new egw_exception_wrong_parameter('path has to be string, use check_acces($path,$check,$stat=null)!'); + } + // query stat array, if not given + if (is_null($stat)) + { + $stat = self::url_stat($path,0); + } + //error_log(__METHOD__."(path=$path||stat[name]={$stat['name']},stat[mode]=".sprintf('%o',$stat['mode']).",$check)"); if (!$stat) { - //error_log(__METHOD__."(stat[name]={$stat['name']},stat[mode]=".sprintf('%o',$stat['mode']).",$check) no stat array!"); + //error_log(__METHOD__."(path=$path||stat[name]={$stat['name']},stat[mode]=".sprintf('%o',$stat['mode']).",$check) no stat array!"); return false; // file not found } // check if other rights grant access if (($stat['mode'] & $check) == $check) { - //error_log(__METHOD__."(stat[name]={$stat['name']},stat[mode]=".sprintf('%o',$stat['mode']).",$check) access via other rights!"); + //error_log(__METHOD__."(path=$path||stat[name]={$stat['name']},stat[mode]=".sprintf('%o',$stat['mode']).",$check) access via other rights!"); return true; } // check if there's owner access and we are the owner if (($stat['mode'] & ($check << 6)) == ($check << 6) && $stat['uid'] && $stat['uid'] == self::$user) { - //error_log(__METHOD__."(stat[name]={$stat['name']},stat[mode]=".sprintf('%o',$stat['mode']).",$check) access via owner rights!"); + //error_log(__METHOD__."(path=$path||stat[name]={$stat['name']},stat[mode]=".sprintf('%o',$stat['mode']).",$check) access via owner rights!"); return true; } // check if there's a group access and we have the right membership @@ -594,14 +609,15 @@ class egw_vfs extends vfs_stream_wrapper } if (in_array(-abs($stat['gid']),$memberships)) { - //error_log(__METHOD__."(stat[name]={$stat['name']},stat[mode]=".sprintf('%o',$stat['mode']).",$check) access via group rights!"); + //error_log(__METHOD__."(path=$path||stat[name]={$stat['name']},stat[mode]=".sprintf('%o',$stat['mode']).",$check) access via group rights!"); return true; } } - // here could be a check for extended acls ... + // check backend for extended acls (only if path given) + $ret = $path && self::_call_on_backend('check_extended_acl',array($path,$check),true); // true = fail silent if backend does not support - //error_log(__METHOD__."(stat[name]={$stat['name']},stat[mode]=".sprintf('%o',$stat['mode']).",$check) no access!"); - return false; + //error_log(__METHOD__."(path=$path||stat[name]={$stat['name']},stat[mode]=".sprintf('%o',$stat['mode']).",$check) ".($ret ? 'backend extended acl granted access.' : 'no access!!!')); + return $ret; } /** @@ -628,6 +644,38 @@ class egw_vfs extends vfs_stream_wrapper return self::is_readable($path,1); } + /** + * Set or delete extended acl for a given path and owner (or delete them if is_null($rights) + * + * Does NOT check if user has the rights to set the extended acl for the given url/path! + * + * @param string $path string with path + * @param int $rights=null rights to set, or null to delete the entry + * @param int/boolean $owner=null owner for whom to set the rights, null for the current user, or false to delete all rights for $path + * @return boolean true if acl is set/deleted, false on error + */ + static function eacl($url,$rights=null,$owner=null) + { + $params = func_get_args(); + + return self::_call_on_backend('eacl',$params); + } + + /** + * Get all ext. ACL set for a path + * + * Calls itself recursive, to get the parent directories + * + * @param string $path + * @return array/boolean array with array('path'=>$path,'owner'=>$owner,'rights'=>$rights) or false if $path not found + */ + static function get_eacl($path) + { + $params = func_get_args(); + + return self::_call_on_backend('get_eacl',$params); + } + /** * Private constructor to prevent instanciating this class, only it's static methods should be used */ @@ -786,6 +834,59 @@ class egw_vfs extends vfs_stream_wrapper return array_pop($parts); } + + /** + * Get the directory / parent of a given path or url(!), return false for '/'! + * + * @param string $path path or url + * @return string/boolean parent or false if there's none ($path == '/') + */ + static function dirname($url) + { + if ($url == '/' || $url[0] != '/' && parse_url($url,PHP_URL_PATH) == '/') + { + return false; + } + $parts = explode('/',$url); + if (substr($url,-1) == '/') array_pop($parts); + array_pop($parts); + if ($url[0] != '/' && count($parts) == 3) + { + array_push($parts,''); // scheme://host is wrong (no path), has to be scheme://host/ + } + //error_log(__METHOD__."($url)=".implode('/',$parts)); + return implode('/',$parts); + } + + /** + * Check if the current use has owner rights for the given path or stat + * + * We define all eGW admins the owner of the group directories! + * + * @param string $path + * @param array $stat=null stat for path, default queried by this function + * @return boolean + */ + static function has_owner_rights($path,array $stat=null) + { + if (!$stat) $stat = self::url_stat($path,0); + + return $stat['uid'] == self::$user || // user is the owner + self::$is_root || // class runs with root rights + !$stat['uid'] && $stat['gid'] && self::$is_admin; // group directory and user is an eGW admin + } + + /** + * Concat a relative path to an url, taking into account, that the url might already end with a slash + * + * @param string $url base url or path, might end in a / + * @param string $relative relative path to add to $url + * @return string + */ + static function concat($url,$relative) + { + return substr($url,-1) == '/' ? $url.$relative : $url.'/'.$relative; + } /** * URL to download a file @@ -802,6 +903,15 @@ class egw_vfs extends vfs_stream_wrapper { return '/filemanager/webdav.php'.$path; } + + /** + * Initialise our static vars + */ + static function init_static() + { + self::$user = (int) $GLOBALS['egw_info']['user']['account_id']; + self::$is_admin = isset($GLOBALS['egw_info']['user']['apps']['admin']); + } } -egw_vfs::$user = (int) $GLOBALS['egw_info']['user']['account_id']; +egw_vfs::init_static(); diff --git a/phpgwapi/inc/class.html.inc.php b/phpgwapi/inc/class.html.inc.php index 1363e4459d..a57af227e7 100644 --- a/phpgwapi/inc/class.html.inc.php +++ b/phpgwapi/inc/class.html.inc.php @@ -863,14 +863,7 @@ class html { if (substr($name,0,5) == 'vfs:/') // vfs pseudo protocoll { - $parts = explode('/',substr($name,4)); - $file = array_pop($parts); - $path = implode('/',$parts); - $name = array( - 'menuaction' => 'filemanager.uifilemanager.view', - 'path' => rawurlencode(base64_encode($path)), - 'file' => rawurlencode(base64_encode($file)), - ); + $name = egw_vfs::download_url(substr($name,4)); } if (is_array($name)) // menuaction and other get-vars { diff --git a/phpgwapi/inc/class.links_stream_wrapper.inc.php b/phpgwapi/inc/class.links_stream_wrapper.inc.php new file mode 100644 index 0000000000..ffb315883e --- /dev/null +++ b/phpgwapi/inc/class.links_stream_wrapper.inc.php @@ -0,0 +1,147 @@ + + * @copyright (c) 2008 by Ralf Becker + * @version $Id: class.sqlfs_stream_wrapper.inc.php 24997 2008-03-02 21:44:15Z ralfbecker $ + */ + +/** + * eGroupWare API: stream wrapper for linked files + * + * The files stored by the sqlfs_stream_wrapper in a /apps/$app/$id directory + * + * The links stream wrapper extends the sqlfs one, to implement an own ACL based on the access + * of the entry the files are linked to. + * + * Applications can define a 'file_access' method in the link registry with the following signature: + * + * boolean function file_access(string $id,int $check,string $rel_path) + * + * If the do not implement such a function the title function is used to test if the user has + * at least read access to an entry, and if true full (write) access to the files is granted. + * + * The stream wrapper interface is according to the docu on php.net + * + * @link http://de.php.net/manual/de/function.stream-wrapper-register.php + */ +class links_stream_wrapper extends sqlfs_stream_wrapper +{ + /** + * Scheme / protocoll used for this stream-wrapper + */ + const SCHEME = 'links'; + /** + * Prefix to predend to get an url from a path + */ + const PREFIX = 'links://default'; + /** + * Base url to store links + */ + const BASEURL = 'links://default/apps'; + /** + * Enable some debug output to the error_log + */ + const DEBUG = false; + + /** + * Implements ACL based on the access of the user to the entry the files are linked to. + * + * @param string $url url to check + * @param int $check mode to check: one or more or'ed together of: 4 = read, 2 = write, 1 = executable + * @return boolean + */ + static function check_extended_acl($url,$check) + { + $path = parse_url($url,PHP_URL_PATH); + + list(,$apps,$app,$id,$rel_path) = explode('/',$path,5); + + if ($apps != 'apps') + { + $access = false; // no access to anything, but /apps + } + elseif (!$app) + { + $access = !($check & egw_vfs::WRITABLE); // always grant read access to /apps + } + elseif(!isset($GLOBALS['egw_info']['user']['apps'][$app])) + { + $access = false; // user has no access to the $app application + } + elseif (!$id) + { + $access = true; // grant read&write access to /apps/$app + } + // allow applications to implement their own access control to the file storage + elseif(($method = egw_link::get_registry($app,'file_access'))) + { + $access = ExecMethod2($method,$id,$check,$rel_path); + } + // otherwise use the title method to check if user has (at least read access) to the entry + // which gives him then read AND write access to the file store of the entry + else + { + $access = !!egw_link::title($app,$id); + } + if (self::DEBUG) error_log(__METHOD__."($url,$check) ".($access?"access granted ($app:$id:$rel_path)":'no access!!!')); + return $access; + } + + /** + * This method is called in response to stat() calls on the URL paths associated with the wrapper. + * + * Reimplemented from sqlfs, as we have to pass the value of check_extends_acl(), due to the lack of late static binding. + * + * @param string $path + * @param int $flags holds additional flags set by the streams API. It can hold one or more of the following values OR'd together: + * - STREAM_URL_STAT_LINK For resources with the ability to link to other resource (such as an HTTP Location: forward, + * or a filesystem symlink). This flag specified that only information about the link itself should be returned, + * not the resource pointed to by the link. + * This flag is set in response to calls to lstat(), is_link(), or filetype(). + * - STREAM_URL_STAT_QUIET If this flag is set, your wrapper should not raise any errors. If this flag is not set, + * you are responsible for reporting errors using the trigger_error() function during stating of the path. + * stat triggers it's own warning anyway, so it makes no sense to trigger one by our stream-wrapper! + * @return array + */ + static function url_stat ( $url, $flags ) + { + return parent::url_stat($url,$flags,self::check_extended_acl($url,egw_vfs::READABLE)); + } + + /** + * Set or delete extended acl for a given path and owner (or delete them if is_null($rights) + * + * Reimplemented, to NOT call the sqlfs functions, as we dont allow to modify the ACL (defined by the apps) + * + * @param string $path string with path + * @param int $rights=null rights to set, or null to delete the entry + * @param int/boolean $owner=null owner for whom to set the rights, null for the current user, or false to delete all rights for $path + * @param int $fs_id=null fs_id to use, to not query it again (eg. because it's already deleted) + * @return boolean true if acl is set/deleted, false on error + */ + static function eacl($path,$rights=null,$owner=null,$fs_id=null) + { + return false; + } + + /** + * Get all ext. ACL set for a path + * + * Reimplemented, to NOT call the sqlfs functions, as we dont allow to modify the ACL (defined by the apps) + * + * @param string $path + * @return array/boolean array with array('path'=>$path,'owner'=>$owner,'rights'=>$rights) or false if $path not found + */ + function get_eacl($path) + { + return false; + } +} + +stream_register_wrapper(links_stream_wrapper::SCHEME ,'links_stream_wrapper'); diff --git a/phpgwapi/inc/class.oldvfs_stream_wrapper.inc.php b/phpgwapi/inc/class.oldvfs_stream_wrapper.inc.php index 7fc5ec4b1e..433e81b370 100644 --- a/phpgwapi/inc/class.oldvfs_stream_wrapper.inc.php +++ b/phpgwapi/inc/class.oldvfs_stream_wrapper.inc.php @@ -100,21 +100,6 @@ class oldvfs_stream_wrapper implements iface_stream_wrapper */ protected $opened_dir; - /** - * Constructor, only called for the non-static stream_* methods! - * - * @return oldvfs_stream_wrapper - */ - function __construct() - { - if (self::LOG_LEVEL > 1) error_log('oldvfs_stream_wrapper::__construct()'); - - if (!is_object(self::$old_vfs)) - { - self::$old_vfs =& new vfs_home(); - } - } - /** * This method is called immediately after your stream object is created. * @@ -447,10 +432,6 @@ class oldvfs_stream_wrapper implements iface_stream_wrapper $path = parse_url($url,PHP_URL_PATH); - if (!is_object(self::$old_vfs)) - { - self::$old_vfs =& new vfs_home(); - } $data=array( 'string' => $path, 'relatives' => array(RELATIVE_ROOT), // filename is relative to the vfs-root @@ -488,10 +469,6 @@ class oldvfs_stream_wrapper implements iface_stream_wrapper $path_from = parse_url($url_from,PHP_URL_PATH); $path_to = parse_url($url_to,PHP_URL_PATH); - if (!is_object(self::$old_vfs)) - { - self::$old_vfs =& new vfs_home(); - } $data_from = array( 'string' => $path_from, 'relatives' => array(RELATIVE_ROOT), // filename is relative to the vfs-root @@ -554,10 +531,6 @@ class oldvfs_stream_wrapper implements iface_stream_wrapper $path = parse_url($url,PHP_URL_PATH); - if (!is_object(self::$old_vfs)) - { - self::$old_vfs =& new vfs_home(); - } // check if we should also create all non-existing path components and our parent does not exist, // if yes call ourself recursive with the parent directory if (($options & STREAM_MKDIR_RECURSIVE) && $path != '/' && !self::$old_vfs->file_exists(array( @@ -606,10 +579,6 @@ class oldvfs_stream_wrapper implements iface_stream_wrapper $path = parse_url($url,PHP_URL_PATH); - if (!is_object(self::$old_vfs)) - { - self::$old_vfs =& new vfs_home(); - } $data=array( 'string' => $path, 'relatives' => array(RELATIVE_ROOT), // filename is relative to the vfs-root @@ -657,10 +626,6 @@ class oldvfs_stream_wrapper implements iface_stream_wrapper $path = parse_url($url,PHP_URL_PATH); - if (!is_object(self::$old_vfs)) - { - self::$old_vfs =& new vfs_home(); - } $data=array( 'string' => $path, 'relatives' => array(RELATIVE_ROOT), // filename is relative to the vfs-root @@ -683,10 +648,6 @@ class oldvfs_stream_wrapper implements iface_stream_wrapper $this->opened_dir = null; - if (!is_object(self::$old_vfs)) - { - self::$old_vfs =& new vfs_home(); - } $path = parse_url($url,PHP_URL_PATH); $files = self::$old_vfs->ls(array( @@ -699,7 +660,7 @@ class oldvfs_stream_wrapper implements iface_stream_wrapper )); if (!is_array($files) || // we also need to return false, if $url is not a directory! - count($files) == 1 && $path == $files[0]['directory'].'/'.$files[0]['name'] && + count($files) == 1 && $path == egw_vfs::concat($files[0]['directory'],$files[0]['name']) && $files[0]['mime_type'] != self::DIR_MIME_TYPE) { self::_remove_password($url); @@ -711,7 +672,7 @@ class oldvfs_stream_wrapper implements iface_stream_wrapper foreach($files as $file) { $this->opened_dir[] = $file['name']; - self::$stat_cache[$file['directory'].'/'.$file['name']] = $file; + self::$stat_cache[egw_vfs::concat($file['directory'],$file['name'])] = $file; } //print_r($this->opened_dir); reset($this->opened_dir); @@ -757,10 +718,6 @@ class oldvfs_stream_wrapper implements iface_stream_wrapper return self::_vfsinfo2stat(self::$stat_cache[$path]); } - if (!is_object(self::$old_vfs)) - { - self::$old_vfs =& new vfs_home(); - } list($info) = self::$old_vfs->ls(array( 'string' => $path, 'relatives' => array(RELATIVE_ROOT), // filename is relative to the vfs-root @@ -894,6 +851,21 @@ class oldvfs_stream_wrapper implements iface_stream_wrapper $parts['host'].$parts['path']; } } + + /** + * Initialise our static vars + * + */ + function init_static() + { + stream_register_wrapper(self::SCHEME ,__CLASS__); + + if (!is_object(self::$old_vfs)) + { + self::$old_vfs =& new vfs_home(); + self::$old_vfs->override_acl =& egw_vfs::$is_root; + } + } } -stream_register_wrapper(oldvfs_stream_wrapper::SCHEME ,'oldvfs_stream_wrapper'); +oldvfs_stream_wrapper::init_static(); diff --git a/phpgwapi/inc/class.sqlfs_stream_wrapper.inc.php b/phpgwapi/inc/class.sqlfs_stream_wrapper.inc.php index 55fd995c4b..6dafcadfc5 100644 --- a/phpgwapi/inc/class.sqlfs_stream_wrapper.inc.php +++ b/phpgwapi/inc/class.sqlfs_stream_wrapper.inc.php @@ -22,7 +22,7 @@ * * I use the PDO DB interface, as it allows to access BLOB's as streams (avoiding to hold them complete in memory). * - * The interface is according to the docu on php.net + * The stream wrapper interface is according to the docu on php.net * * @link http://de.php.net/manual/de/function.stream-wrapper-register.php * @ToDo compare (and optimize) performance with old vfs system (eg. via webdav) @@ -152,18 +152,17 @@ class sqlfs_stream_wrapper implements iface_stream_wrapper if (self::LOG_LEVEL > 1) error_log(__METHOD__."($url,$mode,$options)"); $path = parse_url($url,PHP_URL_PATH); + $dir = egw_vfs::dirname($url); $this->opened_path = $path; $this->opened_mode = $mode; $this->opened_stream = null; - $stat = self::url_stat($url,0); - if (!($stat = self::url_stat($path,0)) || $mode[0] == 'x') // file not found or file should NOT exist { if ($mode[0] == 'r' || // does $mode require the file to exist (r,r+) $mode[0] == 'x' || // or file should not exist, but does - !egw_vfs::check_access(($dir_stat = self::url_stat(dirname($path),0)),egw_vfs::WRITABLE)) // or we are not allowed to create it + !egw_vfs::check_access($dir,egw_vfs::WRITABLE,$dir_stat=self::url_stat($dir,0))) // or we are not allowed to create it { self::_remove_password($url); if (self::LOG_LEVEL) error_log(__METHOD__."($url,$mode,$options) file does not exist or can not be created!"); @@ -212,7 +211,7 @@ class sqlfs_stream_wrapper implements iface_stream_wrapper } else { - if ($mode != 'r' && !egw_vfs::check_access($stat,egw_vfs::WRITABLE)) // we are not allowed to edit it + if ($mode != 'r' && !egw_vfs::check_access($url,egw_vfs::WRITABLE,$stat)) // we are not allowed to edit it { self::_remove_password($url); if (self::LOG_LEVEL) error_log(__METHOD__."($url,$mode,$options) file can not be edited!"); @@ -440,8 +439,7 @@ class sqlfs_stream_wrapper implements iface_stream_wrapper $path = parse_url($url,PHP_URL_PATH); - if (!($stat = self::url_stat($path,0)) || !($dir_stat = self::url_stat(dirname($path),0)) || - !egw_vfs::check_access($dir_stat,egw_vfs::WRITABLE)) + if (!($stat = self::url_stat($path,0)) || !egw_vfs::check_access(dirname($path),egw_vfs::WRITABLE)) { self::_remove_password($url); if (self::LOG_LEVEL) error_log(__METHOD__."($url) (type=$type) permission denied!"); @@ -475,16 +473,16 @@ class sqlfs_stream_wrapper implements iface_stream_wrapper $path_from = parse_url($url_from,PHP_URL_PATH); $path_to = parse_url($url_to,PHP_URL_PATH); + $to_dir = dirname($path_to); - if (!($from_stat = self::url_stat($path_from,0)) || - !($from_dir_stat = self::url_stat(dirname($path_from),0)) || !egw_vfs::check_access($from_dir_stat,egw_vfs::WRITABLE)) + if (!($from_stat = self::url_stat($path_from,0)) || !egw_vfs::check_access(dirname($path_from),egw_vfs::WRITABLE)) { self::_remove_password($url_from); self::_remove_password($url_to); if (self::LOG_LEVEL) error_log(__METHOD__."($url_from,$url_to): $path_from permission denied!"); return false; // no permission or file does not exist } - if (!($to_dir_stat = self::url_stat(dirname($path_to),0)) || !egw_vfs::check_access($to_dir_stat,egw_vfs::WRITABLE)) + if (!egw_vfs::check_access($to_dir,egw_vfs::WRITABLE,$to_dir_stat = self::url_stat($to_dir,0))) { self::_remove_password($url_from); self::_remove_password($url_to); @@ -544,19 +542,19 @@ class sqlfs_stream_wrapper implements iface_stream_wrapper return false; } - $parent = self::url_stat(dirname($path),STREAM_URL_STAT_QUIET); + $parent = self::url_stat($parent_path=dirname($path),STREAM_URL_STAT_QUIET); // check if we should also create all non-existing path components and our parent does not exist, // if yes call ourself recursive with the parent directory if (($options & STREAM_MKDIR_RECURSIVE) && $path != '/' && !$parent) { - if (!self::mkdir(dirname($path),$mode,$options)) + if (!self::mkdir($parent_path,$mode,$options)) { return false; } - $parent = self::url_stat(dirname($path),0); + $parent = self::url_stat($parent_path=dirname($path),0); } - if (!$parent || !egw_vfs::check_access($parent,egw_vfs::WRITABLE)) + if (!$parent || !egw_vfs::check_access($parent_path,egw_vfs::WRITABLE,$parent)) { self::_remove_password($url); if (self::LOG_LEVEL) error_log(__METHOD__."('$url',$mode,$options) permission denied!"); @@ -601,9 +599,10 @@ class sqlfs_stream_wrapper implements iface_stream_wrapper if (self::LOG_LEVEL > 1) error_log(__METHOD__."($url)"); $path = parse_url($url,PHP_URL_PATH); + $parent = dirname($path); if (!($stat = self::url_stat($path,0)) || $stat['mime'] != self::DIR_MIME_TYPE || - !($parent = self::url_stat(dirname($path),0)) || !egw_vfs::check_access($parent,egw_vfs::WRITABLE)) + !egw_vfs::check_access($parent,egw_vfs::WRITABLE)) { self::_remove_password($url); if (self::LOG_LEVEL) error_log(__METHOD__."($url,$options) (type=$type) permission denied!"); @@ -630,6 +629,7 @@ class sqlfs_stream_wrapper implements iface_stream_wrapper $stmt = self::$pdo->prepare('DELETE FROM '.self::TABLE.' WHERE fs_id=?'); if (($ret = $stmt->execute(array($stat['ino']))) && $operation == self::STORE2FS) { + self::eacl($path,null,false,$stat['ino']); // remove all (=false) evtl. existing extended acl for that dir rmdir(self::_fs_path($path)); } return $ret; @@ -651,17 +651,15 @@ class sqlfs_stream_wrapper implements iface_stream_wrapper if (!($stat = self::url_stat($path,STREAM_URL_STAT_QUIET))) { // file does not exist --> create an empty one - if (($f = fopen(self::SCHEME.'://default'.$path,'w')) && fclose($f)) - { - if (!is_null($time)) - { - $stat = self::url_stat($path,0); - } - } - else + if (!($f = fopen(self::SCHEME.'://default'.$path,'w')) || !fclose($f)) { return false; } + if (is_null($time)) + { + return true; // new (empty) file created with current mod time + } + $stat = self::url_stat($path,0); } $stmt = self::$pdo->prepare('UPDATE '.self::TABLE.' SET fs_modified=:fs_modified WHERE fs_id=:fs_id'); @@ -801,15 +799,13 @@ class sqlfs_stream_wrapper implements iface_stream_wrapper */ function dir_opendir ( $url, $options ) { - if (self::LOG_LEVEL > 1) error_log(__METHOD__."('$url',$options)"); - $this->opened_dir = null; $path = parse_url($url,PHP_URL_PATH); if (!($stat = self::url_stat($url,0)) || // dir not found $stat['mime'] != self::DIR_MIME_TYPE || // no dir - !egw_vfs::check_access($stat,egw_vfs::EXECUTABLE|egw_vfs::READABLE)) // no access + !egw_vfs::check_access($url,egw_vfs::EXECUTABLE|egw_vfs::READABLE,$stat)) // no access { self::_remove_password($url); $msg = $stat['mime'] != self::DIR_MIME_TYPE ? "$url is no directory" : 'permission denied'; @@ -819,12 +815,14 @@ class sqlfs_stream_wrapper implements iface_stream_wrapper } $this->opened_dir = array(); $query = 'SELECT fs_id,fs_name,fs_mode,fs_uid,fs_gid,fs_size,fs_mime,fs_created,fs_modified FROM '.self::TABLE.' WHERE fs_dir=?'; + // only return readable files, if dir is not writable by user - if (!egw_vfs::check_access($stat,egw_vfs::WRITABLE)) + /* to value/check the extended acl later on, we have to return all files! + if (egw_vfs::HIDE_UNREADABLES && !egw_vfs::check_access($url,egw_vfs::WRITABLE,$stat)) { $query .= ' AND '.self::_sql_readable(); - } - $query = "/* sqlfs::dir_opendir($path) */ ".$query; + }*/ + //$query = "/* sqlfs::dir_opendir($path) */ ".$query; $stmt = self::$pdo->prepare($query); $stmt->setFetchMode(PDO::FETCH_ASSOC); @@ -833,9 +831,10 @@ class sqlfs_stream_wrapper implements iface_stream_wrapper foreach($stmt as $file) { $this->opened_dir[] = $file['fs_name']; - self::$stat_cache[$path.'/'.$file['fs_name']] = $file; + self::$stat_cache[egw_vfs::concat($path,$file['fs_name'])] = $file; } } + if (self::LOG_LEVEL > 1) error_log(__METHOD__."($url,$options): ".implode(', ',$this->opened_dir)); //print_r($this->opened_dir); reset($this->opened_dir); @@ -866,12 +865,13 @@ class sqlfs_stream_wrapper implements iface_stream_wrapper * - STREAM_URL_STAT_QUIET If this flag is set, your wrapper should not raise any errors. If this flag is not set, * you are responsible for reporting errors using the trigger_error() function during stating of the path. * stat triggers it's own warning anyway, so it makes no sense to trigger one by our stream-wrapper! + * @param boolean $eacl_access=null allows extending classes to pass the value of their check_extended_acl() method (no lsb!) * @return array */ - static function url_stat ( $url, $flags ) + static function url_stat ( $url, $flags, $eacl_access=null ) { if (self::LOG_LEVEL > 1) error_log(__METHOD__."('$url',$flags)"); - + $path = parse_url($url,PHP_URL_PATH); // webdav adds a trailing slash to dirs, which causes url_stat to NOT find the file otherwise @@ -891,6 +891,12 @@ class sqlfs_stream_wrapper implements iface_stream_wrapper } $base_query = 'SELECT fs_id,fs_name,fs_mode,fs_uid,fs_gid,fs_size,fs_mime,fs_created,fs_modified FROM '.self::TABLE.' WHERE fs_name=? AND fs_dir='; $parts = explode('/',$path); + + // if we have extendes acl access to the url, we dont need and can NOT include the sql for the readable check + if (is_null($eacl_access)) + { + $eacl_access = self::check_extended_acl($path,egw_vfs::READABLE); // should be static::check_extended_acl, but no lsb! + } foreach($parts as $n => $name) { if ($n == 0) @@ -901,9 +907,15 @@ class sqlfs_stream_wrapper implements iface_stream_wrapper { $query = 'SELECT fs_id FROM '.self::TABLE.' WHERE fs_dir=('.$query.') AND fs_name='.self::$pdo->quote($name); - // if we are not root, we need to make sure the user has the right to tranverse all partent directories (read-rights) - if (!egw_vfs::$is_root) + // if we are not root AND have no extended acl access, we need to make sure the user has the right to tranverse all parent directories (read-rights) + if (!egw_vfs::$is_root && !$eacl_access) { + if (!egw_vfs::$user) + { + self::_remove_password($url); + if (self::LOG_LEVEL > 1) error_log(__METHOD__."('$url',$flags) permission denied, no user-id and not root!"); + return false; + } $query .= ' AND '.self::_sql_readable(); } } @@ -912,7 +924,7 @@ class sqlfs_stream_wrapper implements iface_stream_wrapper $query = str_replace('fs_name=?','fs_name='.self::$pdo->quote($name),$base_query).'('.$query.')'; } } - $query = "/* sqlfs::url_stat($path) */ ".$query; + //$query = "/* sqlfs::url_stat($path) */ ".$query; //echo "query=$query\n"; if (!($result = self::$pdo->query($query)) || !($info = $result->fetch(PDO::FETCH_ASSOC))) @@ -923,6 +935,7 @@ class sqlfs_stream_wrapper implements iface_stream_wrapper } self::$stat_cache[$path] = $info; + if (self::LOG_LEVEL > 1) error_log(__METHOD__."($url,$flags)=".str_replace("\n",'',print_r($info,true))); return self::_vfsinfo2stat($info); } @@ -964,7 +977,7 @@ class sqlfs_stream_wrapper implements iface_stream_wrapper return $file; } - + /** * This method is called in response to rewinddir(). * @@ -1002,6 +1015,223 @@ class sqlfs_stream_wrapper implements iface_stream_wrapper return true; } + private static $extended_acl; + + /** + * Check if extendes ACL (stored in eGW's ACL table) grants access + * + * The extended ACL is inherited, so it's valid for all subdirs and the included files! + * The used algorithm break on the first match. It could be used, to disallow further access. + * + * @param string $url url to check + * @param int $check mode to check: one or more or'ed together of: 4 = read, 2 = write, 1 = executable + * @return boolean + */ + static function check_extended_acl($url,$check) + { + $url_path = parse_url($url,PHP_URL_PATH); + + if (is_null(self::$extended_acl)) + { + self::_read_extended_acl(); + } + $access = false; + foreach(self::$extended_acl as $path => $rights) + { + if ($path == $url_path || substr($url_path,0,strlen($path)+1) == $path.'/') + { + $access = ($rights & $check) == $check; + break; + } + } + //error_log(__METHOD__."($url,$check) ".($access?"access granted by $path=$rights":'no access!!!')); + return $access; + } + + /** + * Read the extended acl via acl::get_grants('sqlfs') + * + */ + private static function _read_extended_acl() + { + if ((self::$extended_acl = $GLOBALS['egw']->session->appsession('extended_acl',self::EACL_APPNAME)) != false) + { + return; // ext. ACL read from session. + } + self::$extended_acl = array(); + if (($rights = $GLOBALS['egw']->acl->get_all_location_rights(egw_vfs::$user,self::EACL_APPNAME))) + { + $pathes = self::id2path(array_keys($rights)); + } + foreach($rights as $fs_id => $right) + { + $path = $pathes[$fs_id]; + if (isset($path)) + { + self::$extended_acl[$path] = (int)$right; + } + } + // sort by length descending, to allow more specific pathes to have precedence + uksort(self::$extended_acl,create_function('$a,$b','return strlen($b)-strlen($a);')); + $GLOBALS['egw']->session->appsession('extended_acl',self::EACL_APPNAME,self::$extended_acl); + //echo __METHOD__; print_r(self::$extended_acl); + } + + /** + * Appname used with the acl class to store the extended acl + */ + const EACL_APPNAME = 'sqlfs'; + + /** + * Set or delete extended acl for a given path and owner (or delete them if is_null($rights) + * + * Only root, the owner of the path or an eGW admin (only if there's no owner but a group) are allowd to set eACL's! + * + * @param string $path string with path + * @param int $rights=null rights to set, or null to delete the entry + * @param int/boolean $owner=null owner for whom to set the rights, null for the current user, or false to delete all rights for $path + * @param int $fs_id=null fs_id to use, to not query it again (eg. because it's already deleted) + * @return boolean true if acl is set/deleted, false on error + */ + static function eacl($path,$rights=null,$owner=null,$fs_id=null) + { + if ($path[0] != '/') + { + $path = parse_url($path,PHP_URL_PATH); + } + if (is_null($fs_id)) + { + if (!($stat = self::url_stat($path,0))) + { + error_log(__METHOD__."($path,$rights,$owner,$fs_id) no such file or directory!"); + return false; // $path not found + } + if (!egw_vfs::has_owner_rights($path,$stat)) // not group dir and user is eGW admin + { + error_log(__METHOD__."($path,$rights,$owner,$fs_id) permission denied!"); + return false; // permission denied + } + $fs_id = $stat['ino']; + } + if (is_null($owner)) + { + $owner = egw_vfs::$user; + } + if (is_null($rights) || $owner === false) + { + // delete eacl + if (is_null($owner) || $owner == egw_vfs::$user) + { + unset(self::$extended_acl[$path]); + } + $ret = $GLOBALS['egw']->acl->delete_repository(self::EACL_APPNAME,$fs_id,(int)$owner); + } + else + { + if (isset(self::$extended_acl) && ($owner == egw_vfs::$user || + $owner < 0 && egw_vfs::$user && in_array($owner,$GLOBALS['egw']->accounts->memberships(egw_vfs::$user,true)))) + { + // set rights for this class, if applicable + self::$extended_acl[$path] = $rights; + } + $ret = $GLOBALS['egw']->acl->add_repository(self::EACL_APPNAME,$fs_id,$owner,$rights); + } + if ($ret) + { + $GLOBALS['egw']->session->appsession('extended_acl',self::EACL_APPNAME,self::$extended_acl); + } + //error_log(__METHOD__."($path,$rights,$owner,$fs_id)=".(int)$ret); + return $ret; + } + + /** + * Get all ext. ACL set for a path + * + * Calls itself recursive, to get the parent directories + * + * @param string $path + * @return array/boolean array with array('path'=>$path,'owner'=>$owner,'rights'=>$rights) or false if $path not found + */ + function get_eacl($path) + { + if (!($stat = self::url_stat($path,STREAM_URL_STAT_QUIET))) + { + return false; // not found + } + $eacls = array(); + foreach($GLOBALS['egw']->acl->get_all_rights($stat['ino'],self::EACL_APPNAME) as $owner => $rights) + { + $eacls[] = array( + 'path' => $path, + 'owner' => $owner, + 'rights' => $rights, + 'ino' => $stat['ino'], + ); + } + if (($path = egw_vfs::dirname($path))) + { + return array_merge(self::get_eacl($path),$eacls); + } + // sort by length descending, to show precedence + usort($eacls,create_function('$a,$b','return strlen($b["path"])-strlen($a["path"]);')); + + return $eacls; + } + + /** + * Return the path of given fs_id(s) + * + * Calls itself recursive to to determine the path of the parent/directory + * + * @param int/array $fs_ids integer fs_id or array of them + * @return string/array path or array or pathes indexed by fs_id + */ + static function id2path($fs_ids) + { + //error_log(__METHOD__.'('.print_r($fs_id,true).')'); + + $ids = (array)$fs_ids; + if (count($ids) > 1) array_map(create_function('&$v','$v = (int)$v;'),$ids); + $query = 'SELECT fs_id,fs_dir,fs_name FROM '.self::TABLE.' WHERE fs_id'. + (count($ids) == 1 ? '='.(int)$ids[0] : ' IN ('.implode(',',$ids).')'); + + if (!is_object(self::$pdo)) + { + self::_pdo(); + } + //$query = '/* sqlfs::id2path('.implode(',',$ids).') */ '.$query; + $stmt = self::$pdo->prepare($query); + $stmt->setFetchMode(PDO::FETCH_ASSOC); + if (!$stmt->execute()) + { + return false; // not found + } + $parents = array(); + foreach($stmt as $row) + { + if ($row['fs_dir'] > 1 && !in_array($row['fs_dir'],$parents)) + { + $parents[] = $row['fs_dir']; + } + $rows[$row['fs_id']] = $row; + } + unset($stmt); + + if ($parents && !($parents = self::id2path($parents))) + { + return false; // parent not found, should never happen ... + } + $pathes = array(); + foreach($rows as $fs_id => $row) + { + $parent = $row['fs_dir'] > 1 ? $parents[$row['fs_dir']] : ''; + + $pathes[$fs_id] = $parent . '/' . $row['fs_name']; + } + //error_log(__METHOD__.'('.print_r($fs_ids,true).')='.print_r($pathes,true)); + return is_array($fs_ids) ? $pathes : array_shift($pathes); + } + /** * Convert a sqlfs-file-info into a stat array * @@ -1035,22 +1265,22 @@ class sqlfs_stream_wrapper implements iface_stream_wrapper */ static private function _pdo() { - $server =& $GLOBALS['egw_info']['server']; + $egw_db = isset($GLOBALS['egw_setup']) ? $GLOBALS['egw_setup']->db : $GLOBALS['egw']->db; - switch($server['db_type']) + switch($egw_db->Type) { default: - $dsn = $server['db_type'].':host='.$server['db_host'].';dbname='.$server['db_name']; + $dsn = $egw_db->Type.':host='.$egw_db->Host.';dbname='.$egw_db->Database; break; } - self::$pdo = new PDO($dsn,$server['db_user'],$server['db_pass']); + self::$pdo = new PDO($dsn,$egw_db->User,$egw_db->Password); // set client charset of the connection $charset = $GLOBALS['egw']->translation->charset(); switch($server['db_type']) { case 'mysql': - if (isset($GLOBALS['egw']->db->Link_ID->charset2mysql[$charset])) $charset = $GLOBALS['egw']->db->Link_ID->charset2mysql[$charset]; + if (isset($egw_db->Link_ID->charset2mysql[$charset])) $charset = $egw_db->Link_ID->charset2mysql[$charset]; // fall throught case 'pgsql': $query = "SET NAMES '$charset'"; diff --git a/phpgwapi/inc/class.vfs_stream_wrapper.inc.php b/phpgwapi/inc/class.vfs_stream_wrapper.inc.php index 70e8a74515..5fc8f563d0 100644 --- a/phpgwapi/inc/class.vfs_stream_wrapper.inc.php +++ b/phpgwapi/inc/class.vfs_stream_wrapper.inc.php @@ -38,6 +38,11 @@ class vfs_stream_wrapper implements iface_stream_wrapper * Mime type of directories, the old vfs used 'Directory', while eg. WebDAV uses 'httpd/unix-directory' */ const DIR_MIME_TYPE = 'httpd/unix-directory'; + /** + * Should unreadable entries in a not writable directory be hidden, default yes + */ + const HIDE_UNREADABLES = true; + /** * optional context param when opening the stream, null if no context passed * @@ -53,8 +58,9 @@ class vfs_stream_wrapper implements iface_stream_wrapper * @var array */ protected static $fstab = array( -// '/' => 'sqlfs://$user:$pass@$host/', - '/' => 'oldvfs://$user:$pass@$host/', + '/' => 'sqlfs://$user:$pass@$host/', + '/apps' => 'links://$user:$pass@$host/apps', +// '/' => 'oldvfs://$user:$pass@$host/', // '/files' => 'oldvfs://$user:$pass@$host/home/Default', // '/images' => 'http://localhost/egroupware/phpgwapi/templates/idots/images', // '/home/ralf/linux' => '/home/ralf', // we probably need to forbid direct filesystem access for security reasons! @@ -126,11 +132,15 @@ class vfs_stream_wrapper implements iface_stream_wrapper } $parts = array_merge(parse_url($path),$defaults); + if (!empty($parts['scheme']) && $parts['scheme'] != self::SCHEME) + { + return $path; // path is already a non-vfs url --> nothing to do + } if (empty($parts['path'])) $parts['path'] = '/'; foreach(array_reverse(self::$fstab) as $mounted => $url) { - if ($mounted == substr($parts['path'],0,strlen($mounted))) + if ($mounted == '/' || $mounted == $parts['path'] || $mounted.'/' == substr($parts['path'],0,strlen($mounted)+1)) { $scheme = parse_url($url,PHP_URL_SCHEME); if (is_null(self::$wrappers) || !in_array($scheme,self::$wrappers)) @@ -141,6 +151,9 @@ class vfs_stream_wrapper implements iface_stream_wrapper $url = str_replace(array('$user','$pass','$host'),array($parts['user'],$parts['pass'],$parts['host']),$url); + if ($parts['query']) $url .= '?'.$parts['query']; + if ($parts['fragment']) $url .= '#'.$parts['fragment']; + //error_log(__METHOD__."($path) = $url"); return $cache[$path] = $url; } @@ -384,8 +397,11 @@ class vfs_stream_wrapper implements iface_stream_wrapper * * @param string $name * @param array $params first param has to be the path, otherwise we can not determine the correct wrapper + * @param boolean $fail_silent=false should only false be returned if function is not supported by the backend, + * or should an E_USER_WARNING error be triggered (default) + * @return mixed return value of backend or false if function does not exist on backend */ - static private function _call_on_backend($name,$params) + static protected function _call_on_backend($name,$params,$fail_silent=false) { $path = $params[0]; @@ -397,7 +413,7 @@ class vfs_stream_wrapper implements iface_stream_wrapper { if (!class_exists($class = $scheme.'_stream_wrapper') || !method_exists($class,$name)) { - trigger_error("Can't $name for scheme $scheme!\n",E_USER_WARNING); + if (!$fail_silent) trigger_error("Can't $name for scheme $scheme!\n",E_USER_WARNING); return false; } $params[0] = $url; @@ -540,7 +556,7 @@ class vfs_stream_wrapper implements iface_stream_wrapper { return false; } - $this->opened_dir_writable = ($stat = @stat($this->opened_dir_url)) && egw_vfs::check_access($stat,egw_vfs::WRITABLE); + $this->opened_dir_writable = egw_vfs::check_access($this->opened_dir_url,egw_vfs::WRITABLE); // check our fstab if we need to add some of the mountpoints $basepath = parse_url($this->opened_dir_url,PHP_URL_PATH); @@ -589,7 +605,16 @@ class vfs_stream_wrapper implements iface_stream_wrapper return false; } //error_log(__METHOD__."('$path',$flags) calling stat($url)"); - return @stat($url); // suppressed the stat failed warnings + return $stat = @stat($url); // suppressed the stat failed warnings + + // Todo: if we hide non readables, we should return false on url_stat for consitency (if dir is not writabel) + // Problem: this does NOT stop (calles itself infinit recursive)! + if (self::HIDE_UNREADABLES && !egw_vfs::check_access($path,egw_vfs::READABLE,$stat) && + !egw_vfs::check_access(dirname($path,egw_vfs::WRITABLE))) + { + return false; + } + return $stat; } /** @@ -610,14 +635,10 @@ class vfs_stream_wrapper implements iface_stream_wrapper } // only return children readable by the user, if dir is not writable do { - if (($file = readdir($this->opened_dir)) !== false && !$this->opened_dir_writable) - { - $stat = stat($this->opened_dir_url.'/'.$file); - } - //echo __METHOD__."() opened_dir_writable=$this->opened_dir_writable, file=$file, readable=".(int)egw_vfs::check_access($stat,egw_vfs::READABLE).", loop=". - // (int)($file !== false && $stat && !egw_vfs::check_access($stat,egw_vfs::READABLE))."\n"; + $file = readdir($this->opened_dir); } - while($file !== false && $stat && !egw_vfs::check_access($stat,egw_vfs::READABLE)); + while($file !== false && self::HIDE_UNREADABLES && !$this->opened_dir_writable && + !egw_vfs::check_access(egw_vfs::concat($this->opened_dir_url,$file),egw_vfs::READABLE)); return $file; } @@ -669,16 +690,19 @@ class vfs_stream_wrapper implements iface_stream_wrapper require_once('HTTP/WebDAV/Client.php'); self::$wrappers[] = 'webdav'; break; - case 'oldvfs': - case 'sqlfs': - require_once(EGW_API_INC.'/class.'.$scheme.'_stream_wrapper.inc.php'); - self::$wrappers[] = $scheme; - break; case '': - return true; // default file, always loaded + break; // default file, always loaded default: - trigger_error("Can't load stream-wrapper for scheme '$scheme'!",E_USER_WARNING); - return false; + // check if scheme is buildin in php or one of our own stream wrappers + if (in_array($scheme,stream_get_wrappers()) || class_exists($scheme.'_stream_wrapper')) + { + self::$wrappers[] = $scheme; + } + else + { + trigger_error("Can't load stream-wrapper for scheme '$scheme'!",E_USER_WARNING); + return false; + } } } return true; diff --git a/phpgwapi/inc/class.vfs_webdav_server.inc.php b/phpgwapi/inc/class.vfs_webdav_server.inc.php index 913ebbedd3..47bd81eb3d 100644 --- a/phpgwapi/inc/class.vfs_webdav_server.inc.php +++ b/phpgwapi/inc/class.vfs_webdav_server.inc.php @@ -14,7 +14,6 @@ */ require_once('HTTP/WebDAV/Server/Filesystem.php'); -require_once(EGW_API_INC.'/class.egw_vfs.inc.php'); /** * FileManger - WebDAV access using the new stream wrapper VFS interface @@ -33,7 +32,7 @@ class vfs_webdav_server extends HTTP_WebDAV_Server_Filesystem * * @var string */ - var $base = 'vfs://default'; + var $base = egw_vfs::PREFIX; /** * Debug level: 0 = nothing, 1 = function calls, 2 = more info, eg. complete $_SERVER array diff --git a/phpgwapi/setup/setup.inc.php b/phpgwapi/setup/setup.inc.php index 1f57c9be89..b6add51d28 100755 --- a/phpgwapi/setup/setup.inc.php +++ b/phpgwapi/setup/setup.inc.php @@ -14,7 +14,7 @@ /* Basic information about this app */ $setup_info['phpgwapi']['name'] = 'phpgwapi'; $setup_info['phpgwapi']['title'] = 'eGroupWare API'; - $setup_info['phpgwapi']['version'] = '1.5.004'; + $setup_info['phpgwapi']['version'] = '1.5.006'; $setup_info['phpgwapi']['versions']['current_header'] = '1.28'; $setup_info['phpgwapi']['enable'] = 3; $setup_info['phpgwapi']['app_order'] = 1; @@ -55,14 +55,6 @@ $setup_info['phpgwapi']['tables'][] = 'egw_addressbook2list'; $setup_info['phpgwapi']['tables'][] = 'egw_sqlfs'; - // hooks used by vfs_home to manage user- and group-directories - $setup_info['phpgwapi']['hooks']['addaccount'] = 'phpgwapi.vfs_home.addAccount'; - $setup_info['phpgwapi']['hooks']['deleteaccount'] = 'phpgwapi.vfs_home.deleteAccount'; - $setup_info['phpgwapi']['hooks']['editaccount'] = 'phpgwapi.vfs_home.editAccount'; - $setup_info['phpgwapi']['hooks']['addgroup'] = 'phpgwapi.vfs_home.addGroup'; - $setup_info['phpgwapi']['hooks']['deletegroup'] = 'phpgwapi.vfs_home.deleteGroup'; - $setup_info['phpgwapi']['hooks']['editgroup'] = 'phpgwapi.vfs_home.editGroup'; - // hooks used by vfs_home_hooks to manage user- and group-directories for the new stream based VFS $setup_info['phpgwapi']['hooks']['addaccount'] = 'phpgwapi.vfs_home_hooks.addAccount'; $setup_info['phpgwapi']['hooks']['deleteaccount'] = 'phpgwapi.vfs_home_hooks.deleteAccount'; diff --git a/phpgwapi/setup/tables_update.inc.php b/phpgwapi/setup/tables_update.inc.php index 59adc76bb6..4c28442fbd 100644 --- a/phpgwapi/setup/tables_update.inc.php +++ b/phpgwapi/setup/tables_update.inc.php @@ -95,7 +95,10 @@ { // import the current egw_vfs into egw_sqlfs // ToDo: moving /infolog and /infolog/$app to /apps in the files dir!!! - $debug = true; + $debug = $GLOBALS['DEBUG']; + + // delete the table in case this update runs multiple times + $GLOBALS['egw_setup']->db->delete('egw_vfs',false,__LINE__,__FILE__); $query = $GLOBALS['egw_setup']->db->select('egw_vfs','*',"vfs_mime_type != 'journal' AND vfs_mime_type != 'journal-deleted'",__LINE__,__FILE__,false,'ORDER BY length(vfs_directory) ASC','phpgwapi'); if ($debug) echo "rows=
\n";
@@ -184,3 +187,67 @@
 		}
 		return $GLOBALS['setup_info']['phpgwapi']['currentver'] = '1.5.004';
 	}
+
+	$test[] = '1.5.004';
+	function phpgwapi_upgrade1_5_004()
+	{
+		// convert the filemanager group grants into extended ACL
+		
+		// delete all sqlfs entries from the ACL table, in case we run multiple times
+		$GLOBALS['egw_setup']->db->delete('egw_acl',array('acl_appname' => sqlfs_stream_wrapper::EACL_APPNAME),__LINE__,__FILE__);
+		
+		$GLOBALS['egw_setup']->setup_account_object();
+		$accounts = $GLOBALS['egw_setup']->accounts;
+		$accounts = new accounts();
+		
+		egw_vfs::$is_root = true;	// we need root rights to set the extended acl, without being the owner
+
+		foreach($GLOBALS['egw_setup']->db->select('egw_acl','*',array(
+			'acl_appname' => 'filemanager',
+			"acl_location != 'run'",
+		),__LINE__,__FILE__) as $row)
+		{
+			$rights = egw_vfs::READABLE | egw_vfs::EXECUTABLE; 
+			if($row['acl_rights'] > 1) $rights |= egw_vfs::WRITABLE;
+			
+			if (($lid = $accounts->id2name($row['acl_account'])) && $accounts->exists($row['acl_location']))
+			{
+				$ret = sqlfs_stream_wrapper::eacl('/home/'.$lid,$rights,(int)$row['acl_location']);
+				//echo "

sqlfs_stream_wrapper::eacl('/home/$lid',$rights,$row[acl_location])=$ret

\n"; + } + } + egw_vfs::$is_root = false; + + return $GLOBALS['setup_info']['phpgwapi']['currentver'] = '1.5.005'; + } + + $test[] = '1.5.005'; + function phpgwapi_upgrade1_5_005() + { + // move /infolog/$app to /apps/$app and /infolog to /apps/infolog + + $files_dir = $GLOBALS['egw_setup']->db->select('egw_config','config_value',array( + 'config_name' => 'files_dir', + 'config_app' => 'phpgwapi', + ),__LINE__,__FILE__)->fetchSingle(); + + if ($files_dir && file_exists($files_dir) && file_exists($files_dir.'/infolog')) + { + mkdir($files_dir.'/apps',0700,true); + if (($dir = opendir($files_dir.'/infolog'))) + { + while(($app = readdir($dir))) + { + if (!is_numeric($app) && $app[0] != '.') // ingore infolog entries and . or .. + { + rename($files_dir.'/infolog/'.$app,$files_dir.'/apps/'.$app); + } + } + closedir($dir); + rename($files_dir.'/infolog',$files_dir.'/apps/infolog'); + } + } + + return $GLOBALS['setup_info']['phpgwapi']['currentver'] = '1.5.006'; + } +