diff --git a/filemanager/inc/class.filemanager_ui.inc.php b/filemanager/inc/class.filemanager_ui.inc.php index 04a3f678ed..7e36be962b 100644 --- a/filemanager/inc/class.filemanager_ui.inc.php +++ b/filemanager/inc/class.filemanager_ui.inc.php @@ -1095,18 +1095,26 @@ class filemanager_ui 'align' => 'right', )); } - if (($extra_tab = egw_vfs::getExtraInfo($path,$content))) + if (($extra_tabs = egw_vfs::getExtraInfo($path,$content))) { $tabs =& $tpl->get_widget_by_name('tabs=general|perms|eacl|preview|custom'); - $tabs['name'] .= '|'.$extra_tab['name']; - $tabs['label'] .= '|'.$extra_tab['label']; - if ($extra_tab['data'] && is_array($extra_tab['data'])) + foreach(isset($extra_tabs[0]) ? $extra_tabs : array($extra_tabs) as $extra_tab) { - $content += $extra_tab['data']; - } - if ($extra_tab['readonlys'] && is_array($extra_tab['readonlys'])) - { - $readonlys += $extra_tab['readonlys']; + $tabs['name'] .= '|'.$extra_tab['name']; + $tabs['label'] .= '|'.$extra_tab['label']; + $tabs['help'] .= '|'.$extra_tab['help']; + if ($extra_tab['data'] && is_array($extra_tab['data'])) + { + $content = array_merge($content, $extra_tab['data']); + } + if ($extra_tab['preserve'] && is_array($extra_tab['preserve'])) + { + $preserve = array_merge($preserve, $extra_tab['preserve']); + } + if ($extra_tab['readonlys'] && is_array($extra_tab['readonlys'])) + { + $readonlys = array_merge($content, $extra_tab['readonlys']); + } } } $GLOBALS['egw_info']['flags']['java_script'] = "\n"; diff --git a/phpgwapi/inc/class.egw_link.inc.php b/phpgwapi/inc/class.egw_link.inc.php index 3afafa1f74..49061e9546 100644 --- a/phpgwapi/inc/class.egw_link.inc.php +++ b/phpgwapi/inc/class.egw_link.inc.php @@ -1229,11 +1229,19 @@ class egw_link extends solink /** * Delete the diverse caches for $app/$id * - * @param string $app - * @param int|string $id + * @param string $app app-name or null to delete the whole cache + * @param int|string $id id or null to delete only file_access cache of given app (keeps title cache, if app implements file_access!) */ - private static function delete_cache($app,$id) + public static function delete_cache($app,$id) { + if (empty($app) || empty($id)) + { + self::$file_access_cache = array(); + if (empty($app) || !self::get_registry($app, 'file_access')) + { + self::$title_cache = array(); + } + } unset(self::$title_cache[$app.':'.$id]); unset(self::$file_access_cache[$app.':'.$id]); } diff --git a/phpgwapi/inc/class.egw_vfs.inc.php b/phpgwapi/inc/class.egw_vfs.inc.php index bfd50857bb..7b6c253510 100644 --- a/phpgwapi/inc/class.egw_vfs.inc.php +++ b/phpgwapi/inc/class.egw_vfs.inc.php @@ -760,14 +760,50 @@ class egw_vfs extends vfs_stream_wrapper * 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|string $path stat array or path + * @param string $path path * @param int $check mode to check: one or more or'ed together of: 4 = egw_vfs::READABLE, * 2 = egw_vfs::WRITABLE, 1 = egw_vfs::EXECUTABLE * @param array $stat=null stat array, to not query it again + * @param int $user=null user used for check, if not current user (egw_vfs::$user) * @return boolean */ - static function check_access($path,$check,$stat=null) + static function check_access($path, $check, array $stat=null, $user=null) { + if (!$stat && $user && $user != self::$user) + { + static $path_user_stat = array(); + + $backup_user = self::$user; + self::$user = $user; + + if (!isset($path_user_stat[$path]) || !isset($path_user_stat[$path][$user])) + { + self::clearstatcache($path); + + $path_user_stat[$path][$user] = self::url_stat($path, 0); + } + if (($stat = $path_user_stat[$path][$user])) + { + // some backend mounts use $user:$pass in their url, for them we have to deny access! + if (strpos(self::resolve_url($path, false, false, false), '$user') !== false) + { + $ret = false; + } + else + { + $ret = self::check_access($path, $check, $stat); + } + } + else + { + $ret = false; // no access, if we can not stat the file + } + self::$user = $backup_user; + + //error_log(__METHOD__."(path=$path||stat[name]={$stat['name']},stat[mode]=".sprintf('%o',$stat['mode']).",$check,$user) ".array2string($ret)); + return $ret; + } + if (self::$is_root) { return true; @@ -819,12 +855,7 @@ class egw_vfs extends vfs_stream_wrapper // check if there's a group access and we have the right membership if (($stat['mode'] & ($check << 3)) == ($check << 3) && $stat['gid']) { - static $memberships; - if (is_null($memberships)) - { - $memberships = $GLOBALS['egw']->accounts->memberships(self::$user,true); - } - if (in_array(-abs($stat['gid']),$memberships)) + if (in_array(-abs($stat['gid']), $GLOBALS['egw']->accounts->memberships(self::$user, true))) { //error_log(__METHOD__."(path=$path||stat[name]={$stat['name']},stat[mode]=".sprintf('%o',$stat['mode']).",$check) access via group rights!"); return true; @@ -1420,7 +1451,24 @@ class egw_vfs extends vfs_stream_wrapper */ static function getExtraInfo($path,array $content=null) { - return self::_call_on_backend('extra_info',array($path,$content),true); // true = fail silent if backend does NOT support it + $extra = array(); + if (($extra_info = self::_call_on_backend('extra_info',array($path,$content),true))) // true = fail silent if backend does NOT support it + { + $extra[] = $extra_info; + } + + if (($vfs_extra = $GLOBALS['egw']->hooks->process(array( + 'location' => 'vfs_extra', + 'path' => $path, + 'content' => $content, + )))) + { + foreach($vfs_extra as $app => $data) + { + $extra = $extra ? array_merge($extra, $data) : $data; + } + } + return $extra; } /** diff --git a/phpgwapi/inc/class.links_stream_wrapper.inc.php b/phpgwapi/inc/class.links_stream_wrapper.inc.php index 1581b90a93..2f503b9378 100644 --- a/phpgwapi/inc/class.links_stream_wrapper.inc.php +++ b/phpgwapi/inc/class.links_stream_wrapper.inc.php @@ -59,6 +59,26 @@ class links_stream_wrapper extends links_stream_wrapper_parent */ const DEBUG = false; + /** + * Clears our stat-cache + * + * Normaly not necessary, as it is automatically cleared/updated, UNLESS egw_vfs::$user changes! + * + * We have to clear file_access cache of egw_link, as our ACL depends on it! + * + * @param string $path='/apps' to be able to clear only file_access cache of a certain app + */ + public static function clearstatcache($path='/apps') + { + //error_log(__METHOD__."('$path')"); + if ($path[0] != '/') $path = parse_url($path, PHP_URL_PATH); + list(,,$app) = explode('/',$path); + egw_link::delete_cache($app, null); // null = delete only file_access cache, if app implements it, otherwise title cache is deleted too + + // need to call sqlfs clearstatcache too + parent::clearstatcache($path); + } + /** * Implements ACL based on the access of the user to the entry the files are linked to. * diff --git a/phpgwapi/inc/class.sqlfs_stream_wrapper.inc.php b/phpgwapi/inc/class.sqlfs_stream_wrapper.inc.php index 5a0f7f34b0..14eee2075a 100644 --- a/phpgwapi/inc/class.sqlfs_stream_wrapper.inc.php +++ b/phpgwapi/inc/class.sqlfs_stream_wrapper.inc.php @@ -160,6 +160,21 @@ class sqlfs_stream_wrapper implements iface_stream_wrapper */ static public $extra_columns = ',fs_link'; + /** + * Clears our stat-cache + * + * Normaly not necessary, as it is automatically cleared/updated, UNLESS egw_vfs::$user changes! + * + * @param string $path='/' + */ + public static function clearstatcache($path='/') + { + //error_log(__METHOD__."('$path')"); + self::$stat_cache = array(); + + $GLOBALS['egw']->session->appsession('extended_acl',self::EACL_APPNAME,self::$extended_acl = array()); + } + /** * This method is called immediately after your stream object is created. * diff --git a/phpgwapi/inc/class.vfs_stream_wrapper.inc.php b/phpgwapi/inc/class.vfs_stream_wrapper.inc.php index 5fc9799cba..c8da76f722 100644 --- a/phpgwapi/inc/class.vfs_stream_wrapper.inc.php +++ b/phpgwapi/inc/class.vfs_stream_wrapper.inc.php @@ -72,6 +72,30 @@ class vfs_stream_wrapper implements iface_stream_wrapper * @var ressource */ private $opened_stream; + /** + * Mode of opened stream, eg. "r" or "w" + * + * @var string + */ + private $opened_stream_mode; + /** + * Path of opened stream + * + * @var string + */ + private $opened_stream_path; + /** + * URL of opened stream + * + * @var string + */ + private $opened_stream_url; + /** + * Opened stream is a new file, false for existing files + * + * @var boolean + */ + private $opened_stream_is_new; /** * directory-ressouce this class is opened for by dir_open * @@ -110,16 +134,18 @@ class vfs_stream_wrapper implements iface_stream_wrapper * * @param string $path * @param boolean $file_exists=true true if file needs to exists, false if not + * @param boolean $resolve_last_symlink=true + * @param array|boolean &$stat=null on return: stat of existing file or false for non-existing files * @return string|boolean false if the url cant be resolved, should not happen if fstab has a root entry */ - static function resolve_url_symlinks($path,$file_exists=true,$resolve_last_symlink=true) + static function resolve_url_symlinks($path,$file_exists=true,$resolve_last_symlink=true,&$stat=null) { $path = self::get_path($path); if (!($stat = self::url_stat($path,$resolve_last_symlink?0:STREAM_URL_STAT_LINK)) && !$file_exists) { - $ret = self::check_symlink_components($path,0,$url); - if (self::LOG_LEVEL > 1) $log = " (check_symlink_components('$path',0,'$url') = $ret)"; + $stat = self::check_symlink_components($path,0,$url); + if (self::LOG_LEVEL > 1) $log = " (check_symlink_components('$path',0,'$url') = $stat)"; } else { @@ -139,24 +165,27 @@ class vfs_stream_wrapper implements iface_stream_wrapper * * @param string $path * @param boolean $do_symlink=true is a direct match allowed, default yes (must be false for a lstat or readlink!) + * @param boolean $use_symlinkcache=true + * @param boolean $replace_user_pass_host=true replace $user,$pass,$host in url, default true, if false result is not cached * @return string|boolean false if the url cant be resolved, should not happen if fstab has a root entry */ - static function resolve_url($path,$do_symlink=true,$use_symlinkcache=true) + static function resolve_url($path,$do_symlink=true,$use_symlinkcache=true,$replace_user_pass_host=true) { static $cache = array(); $path = self::get_path($path); // we do some caching here - if (isset($cache[$path])) + if (isset($cache[$path]) && $replace_user_pass_host) { if (self::LOG_LEVEL > 1) error_log(__METHOD__."('$path') = '{$cache[$path]}' (from cache)"); return $cache[$path]; } // check if we can already resolve path (or a part of it) with a known symlinks if ($use_symlinkcache) + { $path = self::symlinkCache_resolve($path,$do_symlink); - + } // setting default user, passwd and domain, if it's not contained int the url static $defaults; if (is_null($defaults)) @@ -188,13 +217,18 @@ class vfs_stream_wrapper implements iface_stream_wrapper } $url = egw_vfs::concat($url,substr($parts['path'],strlen($mounted))); - $url = str_replace(array('$user','$pass','$host'),array($parts['user'],$parts['pass'],$parts['host']),$url); - + if ($replace_user_pass_host) + { + $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']; if (self::LOG_LEVEL > 1) error_log(__METHOD__."('$path') = '$url'"); - return $cache[$path] = $url; + + if ($replace_user_pass_host) $cache[$path] = $url; + + return $url; } } if (self::LOG_LEVEL > 0) error_log(__METHOD__."('$path') can't resolve path!\n"); @@ -237,7 +271,7 @@ class vfs_stream_wrapper implements iface_stream_wrapper { $this->opened_stream = null; - if (!($url = self::resolve_url_symlinks($path,$mode[0]=='r'))) + if (!($url = self::resolve_url_symlinks($path,$mode[0]=='r',true,$stat))) { return false; } @@ -245,6 +279,11 @@ class vfs_stream_wrapper implements iface_stream_wrapper { return false; } + $this->opened_stream_mode = $mode; + $this->opened_stream_path = $path[0] == '/' ? $path : parse_url($path, PHP_URL_PATH); + $this->opened_stream_url = $url; + $this->opened_stream_is_new = !$stat; + return true; } @@ -252,12 +291,24 @@ class vfs_stream_wrapper implements iface_stream_wrapper * This method is called when the stream is closed, using fclose(). * * You must release any resources that were locked or allocated by the stream. + * + * VFS calls either "vfs_read", "vfs_added" or "vfs_modified" hook */ function stream_close ( ) { $ret = fclose($this->opened_stream); - $this->opened_stream = null; + if (isset($GLOBALS['egw']) && isset($GLOBALS['egw']->hooks)) + { + $GLOBALS['egw']->hooks->process(array( + 'location' => str_replace('b','',$this->opened_stream_mode) == 'r' ? 'vfs_read' : + ($this->opened_stream_is_new ? 'vfs_added' : 'vfs_modified'), + 'path' => $this->opened_stream_path, + 'mode' => $this->opened_stream_mode, + 'url' => $this->opened_stream_url, + ),'',true); + } + $this->opened_stream = $this->opened_stream_mode = $this->opened_stream_path = $this->opened_stream_url = $this->opened_stream_is_new = null; return $ret; } @@ -384,6 +435,15 @@ class vfs_stream_wrapper implements iface_stream_wrapper { return false; } + // call "vfs_unlink" hook, before unlink + if (isset($GLOBALS['egw']) && isset($GLOBALS['egw']->hooks)) + { + $GLOBALS['egw']->hooks->process(array( + 'location' => 'vfs_unlink', + 'path' => $path[0] == '/' ? $path : parse_url($path, PHP_URL_PATH), + 'url' => $url, + ),'',true); + } self::symlinkCache_remove($path); return unlink($url); } @@ -428,6 +488,17 @@ class vfs_stream_wrapper implements iface_stream_wrapper { error_log(__METHOD__."('$path_from','$path_to') url_from='$url_from', url_to='$url_to' returning ".array2string($ret)); } + // call "vfs_rename" hook + if ($ret && isset($GLOBALS['egw']) && isset($GLOBALS['egw']->hooks)) + { + $GLOBALS['egw']->hooks->process(array( + 'location' => 'vfs_rename', + 'from' => $path_from[0] == '/' ? $path_from : parse_url($path_from, PHP_URL_PATH), + 'to' => $path_to[0] == '/' ? $path_to : parse_url($path_to, PHP_URL_PATH), + 'url_from' => $url_from, + 'url_to' => $url_to, + ),'',true); + } return $ret; } @@ -448,7 +519,18 @@ class vfs_stream_wrapper implements iface_stream_wrapper { return false; } - return mkdir($url,$mode,$options); + $ret = mkdir($url,$mode,$options); + + // call "vfs_mkdir" hook + if ($ret && isset($GLOBALS['egw']) && isset($GLOBALS['egw']->hooks)) + { + $GLOBALS['egw']->hooks->process(array( + 'location' => 'vfs_mkdir', + 'path' => $path[0] == '/' ? $path : parse_url($path, PHP_URL_PATH), + 'url' => $url, + ),'',true); + } + return $ret; } /** @@ -467,6 +549,15 @@ class vfs_stream_wrapper implements iface_stream_wrapper { return false; } + // call "vfs_rmdir" hook, before removing + if (isset($GLOBALS['egw']) && isset($GLOBALS['egw']->hooks)) + { + $GLOBALS['egw']->hooks->process(array( + 'location' => 'vfs_rmdir', + 'path' => $path[0] == '/' ? $path : parse_url($path, PHP_URL_PATH), + 'url' => $url, + ),'',true); + } self::symlinkCache_remove($path); return rmdir($url); } @@ -932,6 +1023,24 @@ class vfs_stream_wrapper implements iface_stream_wrapper return isset($target) ? $target : $path; } + /** + * Clears our internal stat and symlink cache + * + * Normaly not necessary, as it is automatically cleared/updated, UNLESS egw_vfs::$user changes! + * + * We have to clear the symlink cache before AND after calling the backend, + * because auf traversal rights may be different when egw_vfs::$user changes! + * + * @param string $path='/' path of backend, whos cache to clear + */ + static function clearstatcache($path='/') + { + //error_log(__METHOD__."('$path')"); + self::$symlink_cache = array(); + self::_call_on_backend('clearstatcache', array($path), true, 0); + self::$symlink_cache = array(); + } + /** * This method is called in response to readdir(). *