egroupware_official/filemanager/inc/class.filemanager_ui.inc.php
Ralf Becker 0218ffb751 - new vfs-widget to encapsulate common vfs/file related stuff
+ path with clickable components
  + human readable size, mode, ...
  + mime icon with integrated thumbnail creation
- link widget uses now vfs-mime for it's icons
- thumbnail creation is now switched on with size 32px by default, it can
  be switched of by the admin or user, in doing so explicitly
- mime-icons are moved from filemanager to etemplate, as not everyone
  installs filemanager
- filemanager has now 3 display modi:
  + Current directory (with subdirs always on top)
  + Subdirs sorted in
  + Files from subdirs (shows recursive all files and you
    can click on the path components thanks to new vfs widget)
2008-10-06 17:43:42 +00:00

721 lines
23 KiB
PHP

<?php
/**
* eGroupWare - Filemanager - user interface
*
* @link http://www.egroupware.org
* @package filemanager
* @author Ralf Becker <RalfBecker-AT-outdoor-training.de>
* @copyright (c) 2008 by Ralf Becker <RalfBecker-AT-outdoor-training.de>
* @license http://opensource.org/licenses/gpl-license.php GPL - GNU General Public License
* @version $Id$
*/
class filemanager_ui
{
/**
* Methods callable via menuaction
*
* @var array
*/
var $public_functions = array(
'index' => true,
'file' => true,
);
/**
* Constructor
*
*/
function __construct()
{
// strip slashes from _GET parameters, if someone still has magic_quotes_gpc on
if (get_magic_quotes_gpc() && $_GET)
{
$_GET = etemplate::array_stripslashes($_GET);
}
}
/**
* Main filemanager page
*
* @param array $content=null
* @param string $msg=null
*/
function index(array $content=null,$msg=null)
{
$tpl = new etemplate('filemanager.index');
//_debug_array($content);
if (!is_array($content))
{
$content = array(
'nm' => $GLOBALS['egw']->session->appsession('index','filemanager'),
);
if (!is_array($content['nm']))
{
$content['nm'] = array(
'get_rows' => 'filemanager.filemanager_ui.get_rows', // I method/callback to request the data for the rows eg. 'notes.bo.get_rows'
'filter' => '1', // current dir only
'no_filter2' => True, // I disable the 2. filter (params are the same as for filter)
'no_cat' => True, // I disable the cat-selectbox
'lettersearch' => True, // I show a lettersearch
'searchletter' => false, // I0 active letter of the lettersearch or false for [all]
'start' => 0, // IO position in list
'order' => 'name', // IO name of the column to sort after (optional for the sortheaders)
'sort' => 'ASC', // IO direction of the sort: 'ASC' or 'DESC'
'default_cols' => '!comment,ctime', // I columns to use if there's no user or default pref (! as first char uses all but the named columns), default all columns
'csv_fields' => false, // I false=disable csv export, true or unset=enable it with auto-detected fieldnames,
//or array with name=>label or name=>array('label'=>label,'type'=>type) pairs (type is a eT widget-type)
'path' => '/home/'.$GLOBALS['egw_info']['user']['account_lid'],
);
// check if user specified a valid startpath in his prefs --> use it
if (($path = $GLOBALS['egw_info']['user']['preferences']['filemanager']['startfolder']) &&
$path[0] == '/' && egw_vfs::is_dir($path) && egw_vfs::check_access($path, egw_vfs::READABLE))
{
$content['nm']['path'] = $path;
}
}
if (isset($_GET['msg'])) $msg = $_GET['msg'];
if (isset($_GET['path']) && ($path = $_GET['path']))
{
if ($path[0] == '/' && egw_vfs::is_dir($path) && egw_vfs::check_access($path,egw_vfs::READABLE))
{
$content['nm']['path'] = $path;
}
else
{
$msg .= lang('The requested path %1 is not available.',$path);
}
}
}
// check if we have a failed upload AND upload_max_filesize >= post_max_size --> no $_POST array
if ($_GET['post_empty'] && self::km2int(ini_get('upload_max_filesize')) >= self::km2int(ini_get('post_max_size')) ||
// or size bigger as upload_max_filesize
$content['button']['upload'] && (!is_array($content['upload']) || !is_uploaded_file($content['upload']['tmp_name'])))
{
$msg = lang('Error uploading file!')."\n".self::max_upload_size_message();
unset($content['button']);
}
$content['nm']['msg'] = $msg;
if ($content['action'] || $content['nm']['rows'])
{
if ($content['action'])
{
$content['nm']['msg'] = self::action($content['action'],$content['nm']['rows']['checked'],$content['nm']['path']);
unset($content['action']);
}
elseif($content['nm']['rows']['delete'])
{
$content['nm']['msg'] = self::action('delete',array_keys($content['nm']['rows']['delete']),$content['nm']['path']);
}
unset($content['nm']['rows']);
}
$clipboard_files = $GLOBALS['egw']->session->appsession('clipboard_files','filemanager');
$clipboard_type = $GLOBALS['egw']->session->appsession('clipboard_type','filemanager');
if ($content['button'])
{
if ($content['button'])
{
list($button) = each($content['button']);
unset($content['button']);
}
switch($button)
{
case 'up':
if ($content['nm']['path'] != '/')
{
$content['nm']['path'] = dirname($content['nm']['path']);
}
break;
case 'home':
$content['nm']['path'] = '/home/'.$GLOBALS['egw_info']['user']['account_lid'];
break;
case 'createdir':
if ($content['nm']['path'][0] != '/')
{
$ses = $GLOBALS['egw']->session->appsession('index','filemanager');
$old_path = $ses['path'];
$content['nm']['path'] = egw_vfs::concat($old_path,$content['nm']['path']);
}
if (!@egw_vfs::mkdir($content['nm']['path'],null,STREAM_MKDIR_RECURSIVE))
{
$content['nm']['msg'] = !egw_vfs::is_writable(dirname($content['nm']['path'])) ?
lang('Permission denied!') : lang('Failed to create directory!');
if (!$old_path)
{
$ses = $GLOBALS['egw']->session->appsession('index','filemanager');
$old_path = $ses['path'];
}
$content['nm']['path'] = $old_path;
}
break;
case 'paste':
$content['nm']['msg'] = self::action($clipboard_type.'_paste',$clipboard_files,$content['nm']['path']);
break;
case 'upload':
// strip '?', '/' and '#' from filenames, as they are forbidden for sqlfs / make problems
$to = egw_vfs::concat($content['nm']['path'],str_replace(array('?','/','#'),'',$content['upload']['name']));
if ($content['upload'] && is_uploaded_file($content['upload']['tmp_name']) &&
(egw_vfs::is_writable($content['nm']['path']) || egw_vfs::is_writable($to)) &&
copy($content['upload']['tmp_name'],egw_vfs::PREFIX.$to))
{
$content['nm']['msg'] = lang('File successful uploaded.');
}
else
{
$content['nm']['msg'] = lang('Error uploading file!');
}
break;
}
}
if (!egw_vfs::is_dir($content['nm']['path']))
{
$content['nm']['msg'] .= ' '.lang('Directory not found or no permission to access it!');
}
else
{
$dir_is_writable = egw_vfs::is_writable($content['nm']['path']);
}
$content['paste_tooltip'] = $clipboard_files ? '<p><b>'.lang('%1 the following files into current directory',
$clipboard_type=='copy'?lang('Copy'):lang('Move')).':</b><br />'.implode('<br />',$clipboard_files).'</p>' : '';
$content['upload_size'] = self::max_upload_size_message();
//_debug_array($content);
$readonlys['button[paste]'] = !$clipboard_files;
$readonlys['button[createdir]'] = !$dir_is_writable;
$readonlys['button[upload]'] = $readonlys['upload'] = !$dir_is_writable;
if ($dir_is_writable || !$content['nm']['filter']) $sel_options['action']['delete'] = lang('Delete');
$sel_options['action']['copy'] = lang('Copy to clipboard');
if ($dir_is_writable || !$content['nm']['filter']) $sel_options['action']['cut'] = lang('Cut to clipboard');
$sel_options['filter'] = array(
'1' => 'Current directory',
'2' => 'Directories sorted in',
'' => 'Files from subdirectories',
);
$tpl->exec('filemanager.filemanager_ui.index',$content,$sel_options,$readonlys,array('nm' => $content['nm']));
}
/**
* Convert numbers like '32M' or '512k' to integers
*
* @param string $size
* @return int
*/
private static function km2int($size)
{
if (!is_numeric($size))
{
switch(strtolower(substr($size,-1)))
{
case 'm':
$size = 1024*1024*(int)$size;
break;
case 'k':
$size = 1024*(int)$size;
break;
}
}
return (int)$size;
}
/**
* Message containing the max Upload size from the current php.ini settings
*
* We have to take the smaler one of upload_max_filesize AND post_max_size-2800 into account.
* memory_limit does NOT matter any more, because of the stream-interface of the vfs.
*
* @return string
*/
static function max_upload_size_message()
{
$upload_max_filesize = ini_get('upload_max_filesize');
$post_max_size = ini_get('post_max_size');
$max_upload = min(self::km2int($upload_max_filesize),self::km2int($post_max_size)-2800);
return lang('Maximum size for uploads').': '.egw_vfs::hsize($max_upload).
" (php.ini: upload_max_filesize=$upload_max_filesize, post_max_size=$post_max_size)";
}
/**
* Run a certain action with the selected file
*
* @param string $action
* @param array $selected selected pathes
* @param mixed $dir=null current directory
* @return string success or failure message displayed to the user
*/
static private function action($action,$selected,$dir=null)
{
//echo '<p>'.__METHOD__."($action,array(".implode(', ',$selected).",$dir)</p>\n";
if (!count($selected))
{
return lang('You need to select some files first!');
}
$errs = $dirs = $files = 0;
switch($action)
{
case 'delete':
$dirs = $files = $errs = 0;
foreach(egw_vfs::find($selected,array('depth'=>true)) as $path)
{
if (($is_dir = egw_vfs::is_dir($path)) && egw_vfs::rmdir($path,0))
{
++$dirs;
}
elseif (!$is_dir && egw_vfs::unlink($path))
{
++$files;
}
else
{
++$errs;
}
}
if ($errs)
{
return lang('%1 errors deleteting (%2 directories and %3 files deleted)!',$errs,$dirs,$files);
}
if ($dirs)
{
return lang('%1 directories and %2 files deleted.',$dirs,$files);
}
return $files == 1 ? lang('File deleted.') : lang('%1 files deleted.',$files);
case 'copy':
case 'cut':
$GLOBALS['egw']->session->appsession('clipboard_files','filemanager',$selected);
$GLOBALS['egw']->session->appsession('clipboard_type','filemanager',$action);
return lang('%1 URLs %2 to clipboard.',count($selected),$action=='copy'?lang('copied'):lang('cut'));
case 'copy_paste':
foreach($selected as $path)
{
if (!egw_vfs::is_dir($path))
{
$to = egw_vfs::concat($dir,egw_vfs::basename($path));
if ($path != $to && egw_vfs::copy($path,$to))
{
++$files;
}
else
{
++$errs;
}
}
else
{
$len = strlen(dirname($path));
foreach(egw_vfs::find($path) as $p)
{
$to = $dir.substr($p,$len);
if ($to == $p) // cant copy into itself!
{
++$errs;
continue;
}
if (($is_dir = egw_vfs::is_dir($p)) && egw_vfs::mkdir($to,null,STREAM_MKDIR_RECURSIVE))
{
++$dirs;
}
elseif(!$is_dir && egw_vfs::copy($p,$to))
{
++$files;
}
else
{
++$errs;
}
}
}
}
if ($errs)
{
return lang('%1 errors copying (%2 diretories and %3 files copied)!',$errs,$dirs,$files);
}
return $dirs ? lang('%1 directories and %2 files copied.',$dirs,$files) : lang('%1 files copied.',$files);
case 'cut_paste':
foreach($selected as $path)
{
$to = egw_vfs::concat($dir,egw_vfs::basename($path));
if ($path != $to && egw_vfs::rename($path,$to))
{
++$files;
}
else
{
++$errs;
}
}
$GLOBALS['egw']->session->appsession('clipboard_files','filemanager',false); // cant move again
if ($errs)
{
return lang('%1 errors moving (%2 files moved)!',$errs,$files);
}
return lang('%1 files moved.',$files);
}
return "Unknown action '$action'!";
}
/**
* Callback to fetch the rows for the nextmatch widget
*
* @param array $query
* @param array &$rows
* @param array &$readonlys
*/
function get_rows($query,&$rows,&$readonlys)
{
$GLOBALS['egw']->session->appsession('index','filemanager',$query);
if (!egw_vfs::is_dir($query['path'])
|| !egw_vfs::check_access($query['path'],egw_vfs::READABLE))
{
$rows = array();
$query['total'] = 0;
// we will leave here, since we are not allowed, or the location does not exist. Index must handle that, and give
// an appropriate message
$GLOBALS['egw']->redirect($GLOBALS['egw']->link('/index.php',array('menuaction'=>'filemanager.filemanager_ui.index',
'path'=>$query['path'],
'msg' => lang('Directory not found or no permission to access it!'))));
}
$rows = $dir_is_writable = array();
if($query['searchletter'] && !empty($query['search']))
{
$namefilter = '/^'.$query['searchletter'].'.*'.str_replace(array('\\?','\\*'),array('.{1}','.*'),preg_quote($query['search'])).'/i';
if ($query['searchletter'] == strtolower($query['search'][0]))
{
$namefilter = '/^('.$query['searchletter'].'.*'.str_replace(array('\\?','\\*'),array('.{1}','.*'),preg_quote($query['search'])).'|'.
str_replace(array('\\?','\\*'),array('.{1}','.*'),preg_quote($query['search'])).')/i';
}
}
elseif ($query['searchletter'])
{
$namefilter = '/^'.$query['searchletter'].'/i';
}
elseif(!empty($query['search']))
{
$namefilter = '/'.str_replace(array('\\?','\\*'),array('.{1}','.*'),preg_quote($query['search'])).'/i';
}
foreach(egw_vfs::find($query['path'],array(
'mindepth' => 1,
'maxdepth' => $query['filter'] ? (int)(boolean)$query['filter'] : null,
'dirsontop' => $query['filter'] <= 1,
'type' => $query['filter'] ? null : 'f',
'order' => $query['order'], 'sort' => $query['sort'],
'limit' => (int)$query['num_rows'].','.(int)$query['start'],
'need_mime' => true,
'name_preg' => $namefilter,
),true) as $path => $row)
{
$row['icon'] = egw_vfs::mime_icon($row['mime']);
//echo $path; _debug_array($row);
$rows[++$n] = $row;
$path2n[$path] = $n;
$dir = dirname($path);
if (!isset($dir_is_writable[$dir]))
{
$dir_is_writable[$dir] = egw_vfs::is_writable($dir);
}
if (!$dir_is_writable[$dir])
{
$readonlys["delete[$path]"] = true; // no rights to delete the file
}
}
// query comments and cf's for the displayed rows
$cols_to_show = explode(',',$GLOBALS['egw_info']['user']['preferences']['filemanager']['nextmatch-filemanager.index.rows']);
$cfs = config::get_customfields('filemanager');
$all_cfs = in_array('customfields',$cols_to_show) && $cols_to_show[count($cols_to_show)-1][0] != '#';
if ($path2n && (in_array('comment',$cols_to_show) || in_array('customfields',$cols_to_show)) &&
($path2props = egw_vfs::propfind(array_keys($path2n))))
{
foreach($path2props as $path => $props)
{
$row =& $rows[$path2n[$path]];
foreach($props as $prop)
{
if (!$all_cfs && $prop['name'][0] == '#' && !in_array($prop['name'],$cols_to_show)) continue;
$row[$prop['name']] = strlen($prop['val']) < 64 ? $prop['val'] : substr($prop['val'],0,64).' ...';
}
}
}
//_debug_array($readonlys);
return egw_vfs::$find_total;
}
/**
* Preferences of a file/directory
*
* @param array $content=null
* @param string $msg=''
*/
function file(array $content=null,$msg='')
{
static $tabs = 'general|perms|eacl|preview|custom';
$tpl = new etemplate('filemanager.file');
if (!is_array($content))
{
if (!($stat = egw_vfs::stat($path = $_GET['path'])))
{
$content['msg'] = lang('File or directory not found!');
}
else
{
$content = $stat;
$content['name'] = egw_vfs::basename($path);
$content['dir'] = dirname($path);
$content['path'] = $path;
$content['hsize'] = egw_vfs::hsize($stat['size']);
$content['mime'] = egw_vfs::mime_content_type($path);
$content['icon'] = egw_vfs::mime_icon($content['mime']);
$content['gid'] *= -1; // our widgets use negative gid's
if (($props = egw_vfs::propfind($path)))
{
foreach($props as $prop) $content[$prop['name']] = $prop['val'];
}
}
$content[$tabs] = $_GET['tabs'];
if (!($content['is_dir'] = egw_vfs::is_dir($path)))
{
$content['perms']['executable'] = (int)!!($content['mode'] & 0111);
$mask = 6;
if (preg_match('/^text/',$content['mime']) && $content['size'] < 100000)
{
$content['text_content'] = file_get_contents(egw_vfs::PREFIX.$path);
}
}
else
{
//currently not implemented in backend $content['perms']['sticky'] = (int)!!($content['mode'] & 0x201);
$mask = 7;
}
foreach(array('owner' => 6,'group' => 3,'other' => 0) as $name => $shift)
{
$content['perms'][$name] = ($content['mode'] >> $shift) & $mask;
}
$content['is_owner'] = egw_vfs::has_owner_rights($path,$content);
}
else
{
//_debug_array($content);
$path =& $content['path'];
list($button) = @each($content['button']); unset($content['button']);
if (in_array($button,array('save','apply')))
{
$props = array();
foreach($content['old'] as $name => $old_value)
{
if (isset($content[$name]) && $old_value != $content[$name])
{
if ($name == 'name')
{
if (egw_vfs::rename($path,$to = egw_vfs::concat($content['dir'],$content['name'])))
{
$msg .= lang('Renamed %1 to %2.',$path,$to).' ';
$content['old']['name'] = $content[$name];
$path = $to;
}
else
{
$msg .= lang('Rename of %1 to %2 failed!',$path,$to).' ';
}
}
elseif ($name[0] == '#' || $name == 'comment')
{
$props[] = array('name' => $name, 'val' => $content[$name] ? $content[$name] : null);
}
else
{
static $name2cmd = array('uid' => 'chown','gid' => 'chgrp','perms' => 'chmod');
$cmd = array('egw_vfs',$name2cmd[$name]);
$value = $name == 'perms' ? self::perms2mode($content['perms']) : $content[$name];
if ($content['modify_subs'] && $name == 'perms')
{
egw_vfs::find($path,array('type'=>'d'),$cmd,array($value));
egw_vfs::find($path,array('type'=>'f'),$cmd,array($value & 0666)); // no execute for files
}
elseif ($content['modify_subs'])
{
egw_vfs::find($path,null,$cmd,array($value));
}
else
{
call_user_func_array($cmd,array($path,$value));
}
$msg .= lang('Permissions changed for %1.',$path.($content['modify_subs']?' '.lang('and all it\'s childeren'):''));
}
}
}
if ($props)
{
if (egw_vfs::proppatch($path,$props))
{
foreach($props as $prop)
{
$content['old'][$prop['name']] = $prop['val'];
}
$msg .= lang('Properties saved.');
}
else
{
$msg .= lang('Saving properties failed!');
}
}
}
elseif ($content['eacl'] && $content['is_owner'])
{
if ($content['eacl']['delete'])
{
list($ino_owner) = each($content['eacl']['delete']);
list($ino,$owner) = explode('-',$ino_owner);
$msg .= egw_vfs::eacl($path,null,$owner) ? lang('ACL deleted.') : lang('Error deleting the ACL entry!');
}
elseif ($button == 'eacl')
{
if (!$content['eacl']['owner'])
{
$msg .= lang('You need to select an owner!');
}
else
{
$msg .= egw_vfs::eacl($path,$content['eacl']['rights'],$content['eacl']['owner']) ?
lang('ACL added.') : lang('Error adding the ACL!');
}
}
}
// refresh opender and close our window
$link = egw::link('/index.php',array(
'menuaction' => 'filemanager.filemanager_ui.index',
'msg' => $msg,
));
$js = "opener.location.href='".addslashes($link)."'; ";
if ($button == 'save') $js .= "window.close();";
echo "<html>\n<body>\n<script>\n$js\n</script>\n</body>\n</html>\n";
if ($button == 'save')$GLOBALS['egw']->common->egw_exit();
}
$content['link'] = $GLOBALS['egw']->link(egw_vfs::download_url($path));
$content['msg'] = $msg;
if (($readonlys['uid'] = !egw_vfs::$is_root) && !$content['uid']) $content['ro_uid_root'] = 'root';
// only owner can change group & perms
if (($readonlys['gid'] = !$content['is_owner'] ||
parse_url(egw_vfs::resolve_url($content['path']),PHP_URL_SCHEME) == 'oldvfs')) // no uid, gid or perms in oldvfs
{
if (!$content['gid']) $content['ro_gid_root'] = 'root';
foreach($content['perms'] as $name => $value)
{
$readonlys['perms['.$name.']'] = true;
}
}
$readonlys['name'] = !egw_vfs::is_writable($content['dir']);
$readonlys['comment'] = !egw_vfs::is_writable($path);
if (!($cfs = config::get_customfields('filemanager')))
{
$readonlys[$tabs]['custom'] = true;
}
elseif (!egw_vfs::is_writable($path))
{
foreach($cfs as $name => $data)
{
$readonlys['#'.$name] = true;
}
}
$readonlys[$tabs]['eacl'] = true; // eacl off by default
if ($content['is_dir'])
{
$readonlys[$tabs]['preview'] = true; // no preview tab for dirs
$sel_options['rights']=$sel_options['owner']=$sel_options['group']=$sel_options['other'] = array(
7 => lang('Display and modification of content'),
5 => lang('Display of content'),
0 => lang('No access'),
);
if(($content['eacl'] = egw_vfs::get_eacl($content['path'])) !== false) // backend supports eacl
{
unset($readonlys[$tabs]['eacl']); // --> switch the tab on again
foreach($content['eacl'] as &$eacl)
{
$eacl['path'] = parse_url($eacl['path'],PHP_URL_PATH);
$readonlys['delete['.$eacl['ino'].'-'.$eacl['owner'].']'] = $eacl['ino'] != $content['ino'] ||
$eacl['path'] != $content['path'] || !$content['is_owner'];
}
array_unshift($content['eacl'],false); // make the keys start with 1, not 0
$content['eacl']['rights'] = $content['eacl']['owner'] = 0;
}
}
else
{
$sel_options['owner']=$sel_options['group']=$sel_options['other'] = array(
6 => lang('Read & write access'),
4 => lang('Read access only'),
0 => lang('No access'),
);
}
$preserve = $content;
if (!isset($preserve['old']))
{
$preserve['old'] = array(
'perms' => $content['perms'],
'name' => $content['name'],
'uid' => $content['uid'],
'gid' => $content['gid'],
'comment' => (string)$content['comment'],
);
if ($cfs) foreach($cfs as $name => $data)
{
$preserve['old']['#'.$name] = (string)$content['#'.$name];
}
}
$GLOBALS['egw_info']['flags']['java_script'] = "<script>window.focus();</script>\n";
$GLOBALS['egw_info']['flags']['app_header'] = lang('Preferences').' '.$path;
$tpl->exec('filemanager.filemanager_ui.file',$content,$sel_options,$readonlys,$preserve,2);
}
/**
* Check if the rename target exists and would be overwritten
*
* @param string $source
* @param string $target
* @return string
*/
static public function ajax_check_rename_target($source,$target)
{
$response = new xajaxResponse();
$response->addAlert("source='$source' --> target='$target'");
return $response->getXML();
}
/**
* Convert perms array back to integer mode
*
* @param array $perms with keys owner, group, other, executable, sticky
* @return int
*/
private function perms2mode(array $perms)
{
$mode = $perms['owner'] << 6 | $perms['group'] << 3 | $prems['other'];
if ($mode['executable'])
{
$mode |= 0111;
}
if ($mode['sticky'])
{
$mode |= 0x201;
}
return $mode;
}
}