mirror of
https://github.com/EGroupware/egroupware.git
synced 2024-12-25 16:19:00 +01:00
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)
This commit is contained in:
parent
540901e9c7
commit
501df49cbb
@ -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('<h1 style="text-align: center; color: red;">'.lang('Access not permitted')." !!!</h1>\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();
|
||||
}
|
||||
}
|
@ -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 "<p>egw_link::vfs_path('$app','$id','$file') = '$path'</p>\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 "<p>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'</p>\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 "<p>attach_file: ereg('".self::$send_file_ips[$valid]."', '$file[ip]')=".ereg(self::$send_file_ips[$valid],$file['ip'])."</p>\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 "<p>attach_file: full_fname='$file[path]', valid2='$valid2', trans='$trans', check=$check, tfname='$tfname', parts=(x,'${parts[1]}','${parts[2]}')</p>\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 "<p>Can't mkdir($app_dir,0700,true)!</p>\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 "<p>egw_link::delete_attached('$app','$id','$fname') file_id=$file_id</p>\n";
|
||||
echo "<p>egw_link::delete_attached('$app','$id','$fname') url=$url</p>\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 "<p>attached_local(app=$app, id='$id', filename='$filename', ip='$ip', win_user='$win_user', count(send_file_ips)=".count(self::$send_file_ips).")</p>\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 "<p>attached_local: link=$link, valid=$valid, trans='$trans', fname='$fname', parts=(x,'${parts[1]}','${parts[2]}')</p>\n";
|
||||
}
|
||||
}
|
||||
return False;
|
||||
}
|
||||
|
||||
/**
|
||||
* reverse static function of htmlspecialchars()
|
||||
*
|
||||
|
@ -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();
|
||||
|
@ -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
|
||||
{
|
||||
|
147
phpgwapi/inc/class.links_stream_wrapper.inc.php
Normal file
147
phpgwapi/inc/class.links_stream_wrapper.inc.php
Normal file
@ -0,0 +1,147 @@
|
||||
<?php
|
||||
/**
|
||||
* eGroupWare API: VFS - stream wrapper for linked files
|
||||
*
|
||||
* @link http://www.egroupware.org
|
||||
* @license http://opensource.org/licenses/gpl-license.php GPL - GNU General Public License
|
||||
* @package api
|
||||
* @subpackage vfs
|
||||
* @author Ralf Becker <RalfBecker-AT-outdoor-training.de>
|
||||
* @copyright (c) 2008 by Ralf Becker <RalfBecker-AT-outdoor-training.de>
|
||||
* @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');
|
@ -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();
|
||||
|
@ -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'";
|
||||
|
@ -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;
|
||||
|
@ -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
|
||||
|
@ -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';
|
||||
|
@ -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=<pre>\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 "<p>sqlfs_stream_wrapper::eacl('/home/$lid',$rights,$row[acl_location])=$ret</p>\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';
|
||||
}
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user