diff --git a/api/js/jsapi/egw.js b/api/js/jsapi/egw.js index a379929df0..ae26c8823f 100644 --- a/api/js/jsapi/egw.js +++ b/api/js/jsapi/egw.js @@ -334,7 +334,7 @@ et2.load(data.name,data.url,data.data); if (typeof data.response != 'undefined') { - var json_request = egw(window).json(); + var json_request = egw(window).json(""); json_request.handleResponse({response: data.response}); } }); diff --git a/api/src/Vfs/Base.php b/api/src/Vfs/Base.php index b77e2ce216..b88171bf86 100644 --- a/api/src/Vfs/Base.php +++ b/api/src/Vfs/Base.php @@ -180,6 +180,14 @@ class Base } unset(self::$fstab[$path], $GLOBALS['egw_info']['server']['vfs_fstab'][$path]); Config::save_value('vfs_fstab', $GLOBALS['egw_info']['server']['vfs_fstab'],'phpgwapi'); + + unset($GLOBALS['egw_info']['user']['preferences']['common']['vfs_fstab'][$path]); + unset($_SESSION[Api\Session::EGW_INFO_CACHE]['user']['preferences']['common']['vfs_fstab'][$path]); + $prefs = new Api\Preferences(); + $prefs->read_repository(); + unset($prefs->user['common']['vfs_fstab'][$path]); + $prefs->save_repository(); + // invalidate session cache if (method_exists($GLOBALS['egw'],'invalidate_session_cache')) // egw object in setup is limited { diff --git a/api/src/Vfs/HiddenUploadSharing.php b/api/src/Vfs/HiddenUploadSharing.php index 75810f5825..7d1ff77c05 100644 --- a/api/src/Vfs/HiddenUploadSharing.php +++ b/api/src/Vfs/HiddenUploadSharing.php @@ -14,6 +14,7 @@ namespace EGroupware\Api\Vfs; use EGroupware\Api; use EGroupware\Api\Vfs; +use EGroupware\Filemanager\Sharing\HiddenUpload; /** * VFS sharing for a folder, but always read-only. A /Upload directory is used to receive uploads without allowing any @@ -187,7 +188,7 @@ class HiddenUploadSharing extends Sharing $GLOBALS['egw_info']['flags']['currentapp'] = 'filemanager'; Api\Framework::includeCSS('filemanager', 'sharing'); - $ui = new UploadSharingUi(); + $ui = new HiddenUpload(); $ui->index(); } @@ -203,139 +204,4 @@ class HiddenUploadSharing extends Sharing { static::setup_share(true, $this->share); } -} - -if (file_exists(__DIR__.'/../../../filemanager/inc/class.filemanager_ui.inc.php')) -{ - require_once __DIR__.'/../../../filemanager/inc/class.filemanager_ui.inc.php'; - - class UploadSharingUi extends SharingUi - { - - /** - * Get active view - override so it points to this class - * - * @return string - */ - public static function get_view() - { - return array(new UploadSharingUi(), 'listview'); - } - - /** - * Filemanager listview - * - * Override to customize for sharing with a hidden upload directory. - * Everything not in the upload directory is readonly, but we make it look like you can upload. - * The upload directory is not shown. - * - * @param array $content - * @param string $msg - */ - function listview(array $content=null,$msg=null) - { - $this->etemplate = $this->etemplate ? $this->etemplate : new Api\Etemplate(static::LIST_TEMPLATE); - - if (isset($GLOBALS['egw']->sharing) && $GLOBALS['egw']->sharing->has_hidden_upload()) - { - // Tell client side that the path is actually writable - $content['initial_path_readonly'] = false; - - // No new anything - $this->etemplate->disableElement('nm[new]'); - $this->etemplate->setElementAttribute('nm[button][createdir]', 'readonly', true); - - // Take over upload, change target and conflict strategy - $path = Vfs::concat(self::get_home_dir(), Sharing::HIDDEN_UPLOAD_DIR); - $target = str_replace('\\', '\\\\', __CLASS__); - $this->etemplate->setElementAttribute('nm[upload]', 'onFinishOne', "app.filemanager.upload(ev, 1, '$path', 'rename', '{$target}::ajax_action')"); - } - - return parent::listview($content, $msg); - } - - /** - * Deal with an uploaded file. - * Overridden from the parent to change the message and message type - * - * @param string $action Should be 'upload' - * @param $selected Array of file information - * @param string $dir Target directory - * @param $props - * @param string[] $arr Result - * - * @throws Api\Exception\AssertionFailed - */ - protected static function handle_upload_action(string $action, $selected, $dir, $props, &$arr) - { - Api\Translation::add_app('filemanager'); - $vfs = Vfs::mount(); - $GLOBALS['egw']->sharing->redo(); - parent::handle_upload_action($action, $selected, $dir, $props, $arr); - $arr['msg'] .= "\n" . lang("The uploaded file is only visible to the person sharing these files with you, not to yourself or other people knowing this sharing link."); - $arr['type'] = 'notice'; - } - - protected function is_hidden_upload_dir($directory) - { - if (!isset($GLOBALS['egw']->sharing)) return false; - // Just hide anything that is 'Upload' mounted where we expect, not just this share, to avoid exposing when - // more than one share is used - $mounts = Vfs::mount(); - return Vfs::is_dir($directory) && '/'.Vfs::basename($directory) == Sharing::HIDDEN_UPLOAD_DIR && $mounts[$directory]; - } - - /** - * Callback to fetch the rows for the nextmatch widget - * - * @param array $query - * @param array &$rows - * @return int - * - * @throws Api\Json\Exception - */ - function get_rows(&$query, &$rows) - { - $hidden_upload = (isset($GLOBALS['egw']->sharing) && $GLOBALS['egw']->sharing->has_hidden_upload()); - - // Not allowed in hidden upload dir - $check_path = Sharing::HIDDEN_UPLOAD_DIR . (substr($query['path'], -1) == '/' ? '/' : ''); - if(($length = strlen($check_path)) && (substr($query['path'], -$length) === $check_path)) - { - // only redirect, if it would be to some other location, gives redirect-loop otherwise - if ($query['path'] != ($path = static::get_home_dir())) - { - // we will leave here, since we are not allowed, go back to root - // TODO: Give message about it, redirect to home dir - } - $rows = array(); - return 0; - } - - // Get file list from parent - $total = parent::get_rows($query, $rows); - - if(! $hidden_upload ) - { - return $total; - } - - // tell client-side that this directory is writeable - allows upload + button - $response = Api\Json\Response::get(); - $response->call('app.filemanager.set_readonly', $query['path'], false); - - // Hide the hidden upload directory, mark everything else as readonly - foreach($rows as $key => &$row) - { - if($this->is_hidden_upload_dir($row['path'])) - { - unset($rows[$key]); - $total--; - continue; - } - $row['class'] .= 'noEdit noDelete '; - } - return $total; - } - } } \ No newline at end of file diff --git a/api/src/Vfs/Sharing.php b/api/src/Vfs/Sharing.php index 7d4511a960..edb3297c0a 100644 --- a/api/src/Vfs/Sharing.php +++ b/api/src/Vfs/Sharing.php @@ -15,8 +15,9 @@ namespace EGroupware\Api\Vfs; use EGroupware\Api; use EGroupware\Api\Vfs; -use EGroupware\Collabora\Wopi; -use filemanager_ui; +use EGroupware\Filemanager\Sharing\AnonymousList; +use EGroupware\Filemanager\Sharing\Ui; +use EGroupware\Filemanager\Sharing\Ui as ShareUi; /** * VFS sharing @@ -119,7 +120,7 @@ class Sharing extends \EGroupware\Api\Sharing // ToDo: handle there's already something there with that name (incl. maybe the same share!) Vfs::$is_root = true; - if (!Vfs::mount(Vfs\Sharing\StreamWrapper::share2url($share), $share['share_root'], false, false, $clear_fstab)) + if (!Vfs::mount(Vfs\Sharing\StreamWrapper::share2url($share), $share['share_root'], false, false, false)) { sleep(1); return static::share_fail( @@ -129,8 +130,7 @@ class Sharing extends \EGroupware\Api\Sharing } Vfs::$is_root = false; - Api\Framework::message(lang('Share has been mounted into you shares directory').': '.$share['share_root'], 'success'); - // ToDo: ask user if he want's the share permanently mounted + // get_ui() will ask user if he wants the share permanently mounted return; } @@ -233,6 +233,38 @@ class Sharing extends \EGroupware\Api\Sharing */ public function get_ui() { + // Ask about the share, if the user is not anonymous. + // * Link has already been opened in a new tab by this point * + if($GLOBALS['egw_info']['user']['account_lid'] !== 'anonymous' && $GLOBALS['egw_info']['user']['account_id'] !== $this->share['owner']) + { + // Check to see if this is already permanent mounted share + $mount_target = Vfs\Sharing\StreamWrapper::share2url($this->share); + if(($mounted = array_search($mount_target,$GLOBALS['egw_info']['user']['preferences']['common']['vfs_fstab']))) + { + Api\Json\Response::get()->apply('window.opener.egw.open_link',[ + Api\Framework::link('/index.php',[ + 'menuaction' => 'filemanager.filemanager_ui.index', + 'path' => $mounted + ]),'filemanager',false,'filemanager' + ]); + } + else + { + // New share, ask about it + Api\Json\Response::get()->apply('window.opener.egw.open_link',[ + Api\Framework::link('/index.php',[ + 'menuaction' => 'filemanager.EGroupware\\Filemanager\\Sharing\\Ui.share_received', + 'token' => $this->share['share_token'] + ]),'filemanager','600x150','filemanager' + ]); + } + + Api\Json\Response::get()->apply('window.close'); + // Still need to load the list after though, since loading it processes what we just added + $ui = new \filemanager_ui(); + return $ui->index(); + } + // run full eTemplate2 UI for directories $_GET['path'] = $this->share['share_root']; $GLOBALS['egw_info']['user']['preferences']['filemanager']['nm_view'] = 'tile'; @@ -240,7 +272,7 @@ class Sharing extends \EGroupware\Api\Sharing $GLOBALS['egw_info']['flags']['js_link_registry'] = true; $GLOBALS['egw_info']['flags']['currentapp'] = 'filemanager'; Api\Framework::includeCSS('filemanager', 'sharing'); - $ui = new SharingUi(); + $ui = new AnonymousList(); $ui->index(); } @@ -546,134 +578,4 @@ class Sharing extends \EGroupware\Api\Sharing } } } -} - -if (file_exists(__DIR__.'/../../../filemanager/inc/class.filemanager_ui.inc.php')) -{ - require_once __DIR__.'/../../../filemanager/inc/class.filemanager_ui.inc.php'; - - class SharingUi extends filemanager_ui - { - /** - * Constructor - * - * Reimplemented to load filemanager translations - */ - function __construct() - { - parent::__construct(); - - Api\Translation::add_app('filemanager'); - } - - /** - * Get active view - override so it points to this class - * - * @return callable - */ - public static function get_view() - { - return array(new SharingUi(), 'listview'); - } - - /** - * Filemanager listview - * - * @param array $content - * @param string $msg - */ - function listview(array $content=null,$msg=null) - { - $this->etemplate = $this->etemplate ? $this->etemplate : new Api\Etemplate(static::LIST_TEMPLATE); - - // Override and take over get_rows so we can customize - $content['nm']['get_rows'] = '.' . get_class($this) . '.get_rows'; - - return parent::listview($content, $msg); - } - - /** - * Get the configured start directory for the current user - * - * @return string - */ - static function get_home_dir() - { - return $GLOBALS['egw']->sharing->get_root(); - } - - /** - * Context menu - * - * @return array - */ - public static function get_actions() - { - $actions = parent::get_actions(); - $group = 1; - // do not add edit setting action when we are in sharing - unset($actions['edit']); - if (Vfs::is_writable($GLOBALS['egw']->sharing->get_root())) - { - return $actions; - } - $actions+= array( - 'egw_copy' => array( - 'enabled' => false, - 'group' => $group + 0.5, - 'hideOnDisabled' => true - ), - 'egw_copy_add' => array( - 'enabled' => false, - 'group' => $group + 0.5, - 'hideOnDisabled' => true - ), - 'paste' => array( - 'enabled' => false, - 'group' => $group + 0.5, - 'hideOnDisabled' => true - ), - ); - return $actions; - } - - protected function get_vfs_options($query) - { - $options = parent::get_vfs_options($query); - - // Hide symlinks - // TODO: This hides everything, see Vfs::_check_add() line 648 - //$options['type'] = '!l'; - - return $options; - } - - /** - * Callback to fetch the rows for the nextmatch widget - * - * @param array $query - * @param array &$rows - * @return int - */ - function get_rows(&$query, &$rows) - { - // Check for navigating outside share, redirect back to share - if (!empty($query['path']) && (!Vfs::stat($query['path'],false) || !Vfs::is_dir($query['path']) || !Vfs::check_access($query['path'],Vfs::READABLE))) - { - // only redirect, if it would be to some other location, gives redirect-loop otherwise - if ($query['path'] != ($path = static::get_home_dir())) - { - // we will leave here, since we are not allowed, go back to root - // TODO: Give message about it, redirect to home dir - } - $rows = array(); - return 0; - } - - // Get file list from parent - $total = parent::get_rows($query, $rows); - - return $total; - } - } } \ No newline at end of file diff --git a/filemanager/inc/class.filemanager_ui.inc.php b/filemanager/inc/class.filemanager_ui.inc.php index 9d7b8b727a..158a71d644 100644 --- a/filemanager/inc/class.filemanager_ui.inc.php +++ b/filemanager/inc/class.filemanager_ui.inc.php @@ -842,7 +842,17 @@ class filemanager_ui // feeding the links to dirs to Vfs::find() deletes the content of the dirs, not just the link! foreach($selected as $key => $path) { - if (!Vfs::is_dir($path) || Vfs::is_link($path)) + if (Vfs::is_dir($path) && $GLOBALS['egw_info']['user']['preferences']['common']['vfs_fstab'][$path]) + { + // Trying to delete a share. Unmount. + $root = Vfs::$is_root; + Vfs::$is_root = true; + Vfs::umount($path); + Vfs::$is_root = $root; + ++$dirs; + unset($selected[$key]); + } + else if (!Vfs::is_dir($path) || Vfs::is_link($path)) { if (Vfs::unlink($path)) { @@ -1705,4 +1715,24 @@ class filemanager_ui } return $mode; } + + + /** + * User just got a new share. Ask the questions, let them customize + * + * @param array $content + */ + public function share_received($content = array()) + { + // Deal with returning data & changes + + // Read info for display + $content['mount_location'] = $content['share_path']; + $sel_options = array(); + $readonlys = array(); + $preserve = $content; + $template = new Api\Etemplate("api.file_share_received"); + + $template->exec(__CLASS__ . '::' . __METHOD__, $content, $sel_options, $readonlys, $preserve, 2); + } } diff --git a/filemanager/src/Sharing/AnonymousList.php b/filemanager/src/Sharing/AnonymousList.php new file mode 100644 index 0000000000..c4a1bc17be --- /dev/null +++ b/filemanager/src/Sharing/AnonymousList.php @@ -0,0 +1,144 @@ +etemplate = $this->etemplate ? $this->etemplate : new Api\Etemplate(static::LIST_TEMPLATE); + + // Override and take over get_rows so we can customize + $content['nm']['get_rows'] = '.' . get_class($this) . '.get_rows'; + + return parent::listview($content, $msg); + } + + /** + * Get the configured start directory for the current user + * + * @return string + */ + static function get_home_dir() + { + return $GLOBALS['egw']->sharing->get_root(); + } + + /** + * Context menu + * + * @return array + */ + public static function get_actions() + { + $actions = parent::get_actions(); + $group = 1; + // do not add edit setting action when we are in sharing + unset($actions['edit']); + if (Vfs::is_writable($GLOBALS['egw']->sharing->get_root())) + { + return $actions; + } + $actions += array( + 'egw_copy' => array( + 'enabled' => false, + 'group' => $group + 0.5, + 'hideOnDisabled' => true + ), + 'egw_copy_add' => array( + 'enabled' => false, + 'group' => $group + 0.5, + 'hideOnDisabled' => true + ), + 'paste' => array( + 'enabled' => false, + 'group' => $group + 0.5, + 'hideOnDisabled' => true + ), + ); + return $actions; + } + + protected function get_vfs_options($query) + { + $options = parent::get_vfs_options($query); + + // Hide symlinks + // TODO: This hides everything, see Vfs::_check_add() line 648 + //$options['type'] = '!l'; + + return $options; + } + + /** + * Callback to fetch the rows for the nextmatch widget + * + * @param array $query + * @param array &$rows + * @return int + */ + function get_rows(&$query, &$rows) + { + // Check for navigating outside share, redirect back to share + if (!empty($query['path']) && (!Vfs::stat($query['path'], false) || !Vfs::is_dir($query['path']) || !Vfs::check_access($query['path'], Vfs::READABLE))) + { + // only redirect, if it would be to some other location, gives redirect-loop otherwise + if ($query['path'] != ($path = static::get_home_dir())) + { + // we will leave here, since we are not allowed, go back to root + // TODO: Give message about it, redirect to home dir + } + $rows = array(); + return 0; + } + + // Get file list from parent + $total = parent::get_rows($query, $rows); + + return $total; + } +} \ No newline at end of file diff --git a/filemanager/src/Sharing/HiddenUpload.php b/filemanager/src/Sharing/HiddenUpload.php new file mode 100644 index 0000000000..76d0a133d9 --- /dev/null +++ b/filemanager/src/Sharing/HiddenUpload.php @@ -0,0 +1,139 @@ +etemplate = $this->etemplate ? $this->etemplate : new Api\Etemplate(static::LIST_TEMPLATE); + + if (isset($GLOBALS['egw']->sharing) && $GLOBALS['egw']->sharing->has_hidden_upload()) + { + // Tell client side that the path is actually writable + $content['initial_path_readonly'] = false; + + // No new anything + $this->etemplate->disableElement('nm[new]'); + $this->etemplate->setElementAttribute('nm[button][createdir]', 'readonly', true); + + // Take over upload, change target and conflict strategy + $path = Vfs::concat(self::get_home_dir(), Sharing::HIDDEN_UPLOAD_DIR); + $target = str_replace('\\', '\\\\', __CLASS__); + $this->etemplate->setElementAttribute('nm[upload]', 'onFinishOne', "app.filemanager.upload(ev, 1, '$path', 'rename', '{$target}::ajax_action')"); + } + + return parent::listview($content, $msg); + } + + /** + * Deal with an uploaded file. + * Overridden from the parent to change the message and message type + * + * @param string $action Should be 'upload' + * @param $selected Array of file information + * @param string $dir Target directory + * @param $props + * @param string[] $arr Result + * + * @throws Api\Exception\AssertionFailed + */ + protected static function handle_upload_action(string $action, $selected, $dir, $props, &$arr) + { + Api\Translation::add_app('filemanager'); + $vfs = Vfs::mount(); + $GLOBALS['egw']->sharing->redo(); + parent::handle_upload_action($action, $selected, $dir, $props, $arr); + $arr['msg'] .= "\n" . lang("The uploaded file is only visible to the person sharing these files with you, not to yourself or other people knowing this sharing link."); + $arr['type'] = 'notice'; + } + + protected function is_hidden_upload_dir($directory) + { + if (!isset($GLOBALS['egw']->sharing)) return false; + // Just hide anything that is 'Upload' mounted where we expect, not just this share, to avoid exposing when + // more than one share is used + $mounts = Vfs::mount(); + return Vfs::is_dir($directory) && '/'.Vfs::basename($directory) == Sharing::HIDDEN_UPLOAD_DIR && $mounts[$directory]; + } + + /** + * Callback to fetch the rows for the nextmatch widget + * + * @param array $query + * @param array &$rows + * @return int + * + * @throws Api\Json\Exception + */ + function get_rows(&$query, &$rows) + { + $hidden_upload = (isset($GLOBALS['egw']->sharing) && $GLOBALS['egw']->sharing->has_hidden_upload()); + + // Not allowed in hidden upload dir + $check_path = Sharing::HIDDEN_UPLOAD_DIR . (substr($query['path'], -1) == '/' ? '/' : ''); + if(($length = strlen($check_path)) && (substr($query['path'], -$length) === $check_path)) + { + // only redirect, if it would be to some other location, gives redirect-loop otherwise + if ($query['path'] != ($path = static::get_home_dir())) + { + // we will leave here, since we are not allowed, go back to root + // TODO: Give message about it, redirect to home dir + } + $rows = array(); + return 0; + } + + // Get file list from parent + $total = parent::get_rows($query, $rows); + + if(! $hidden_upload ) + { + return $total; + } + + // tell client-side that this directory is writeable - allows upload + button + $response = Api\Json\Response::get(); + $response->call('app.filemanager.set_readonly', $query['path'], false); + + // Hide the hidden upload directory, mark everything else as readonly + foreach($rows as $key => &$row) + { + if($this->is_hidden_upload_dir($row['path'])) + { + unset($rows[$key]); + $total--; + continue; + } + $row['class'] .= 'noEdit noDelete '; + } + return $total; + } +} \ No newline at end of file diff --git a/filemanager/src/Sharing/Ui.php b/filemanager/src/Sharing/Ui.php new file mode 100644 index 0000000000..d5572269e9 --- /dev/null +++ b/filemanager/src/Sharing/Ui.php @@ -0,0 +1,105 @@ + true + ); + + /** + * The user has been given a new share. Ask what they want to do with it. + * + * This allows changing the name / mountpoint, and if they want to only + * mount it temporarily. + * + * @param array $content + */ + public function share_received($content = array()) + { + // Deal with response + $this->handle_share_received($content); + + // Set up for display + $template = new Api\Etemplate('filemanager.file_share_received'); + + $token = $content['token'] ?: $_GET['token']; + + $share = Sharing::so()->read(['share_token' => $token]); + + // This should already have been done, but we want the correct share root + Sharing::setup_share(true,$share); + + $content = $share; + $content['share_passwd'] = !!$content['share_passwd']; + unset($content['share_id']); + $content['mount_location'] = Vfs::basename($content['share_root']); + $content['permanent'] = true; + + $sel_options = array(); + $readonlys = array(); + $preserve = $content; + $preserve['url'] = Vfs\Sharing\StreamWrapper::share2url($share); + + $template->exec('filemanager.'.__CLASS__.'.'.__FUNCTION__, $content, $sel_options, $readonlys, $preserve); + } + + /** + * User submitted the share_received dialog, update the share appropriately + * + * @param array $content + */ + protected function handle_share_received($content = array()) + { + if(!$content) return; + + // Set persistent + $persistent_mount = $content['permanent'] ? $GLOBALS['egw_info']['user']['account_id'] : false; + + $new_mountpoint = Vfs::dirname($content['share_root']) . '/' . $content['mount_location']; + Vfs::$is_root = true; + Vfs::umount($content['share_root']); + if(Vfs::mount($content['url'], $new_mountpoint, false, $persistent_mount)) + { + $content['share_root'] = $new_mountpoint; + } + Vfs::$is_root = false; + + // also save for current session + $GLOBALS['egw_info']['user']['preferences']['common']['vfs_fstab'][$new_mountpoint] = + $_SESSION[Api\Session::EGW_INFO_CACHE]['user']['preferences']['common']['vfs_fstab'][$new_mountpoint] = $content['url']; + + $GLOBALS['egw_info']['server']['vfs_fstab'] = Vfs::mount(); + + // Go to new share + Api\Json\Response::get()->apply('window.opener.egw.open_link',[ + Api\Framework::link('/index.php',[ + 'menuaction' => "filemanager.filemanager_ui.index", + 'path' => $content['share_root'] + ]), 'filemanager',false,'filemanager' + ]); + Api\Framework::window_close(); + } +} \ No newline at end of file diff --git a/filemanager/templates/default/file_share_received.xet b/filemanager/templates/default/file_share_received.xet new file mode 100644 index 0000000000..2459a717a9 --- /dev/null +++ b/filemanager/templates/default/file_share_received.xet @@ -0,0 +1,32 @@ + + + + + + \ No newline at end of file