diff --git a/api/js/etemplate/et2_widget_file.js b/api/js/etemplate/et2_widget_file.js index 78b5254108..4c9da56aaa 100644 --- a/api/js/etemplate/et2_widget_file.js +++ b/api/js/etemplate/et2_widget_file.js @@ -478,8 +478,11 @@ var et2_file = /** @class */ (function (_super) { var event = jQuery.Event('upload'); event.data = this; // Callback - if (this.options.onFinishOne) { - return et2_call(this.options.onFinishOne, event, response, name); + if (typeof this.onFinishOne == 'function') { + this.onFinishOne(event, response, name); + } + else if (this.options.onFinishOne) { + et2_call(this.options.onFinishOne, event, response, name); } return true; }; @@ -596,7 +599,7 @@ var et2_file = /** @class */ (function (_super) { }, onFinishOne: { "name": "Finish event handler for each one", - "type": "any", + "type": "js", "default": et2_no_init, "description": "A (js) function called when a file to be uploaded is finished." }, diff --git a/api/js/etemplate/et2_widget_file.ts b/api/js/etemplate/et2_widget_file.ts index 29bd61fe31..18dff6cf6a 100644 --- a/api/js/etemplate/et2_widget_file.ts +++ b/api/js/etemplate/et2_widget_file.ts @@ -89,7 +89,7 @@ export class et2_file extends et2_inputWidget }, onFinishOne: { "name": "Finish event handler for each one", - "type": "any", + "type": "js", "default": et2_no_init, "description": "A (js) function called when a file to be uploaded is finished." }, @@ -665,9 +665,13 @@ export class et2_file extends et2_inputWidget event.data = this; // Callback - if(this.options.onFinishOne) + if(typeof this.onFinishOne == 'function') { - return et2_call(this.options.onFinishOne,event,response,name); + this.onFinishOne(event, response, name); + } + else if (this.options.onFinishOne) + { + et2_call(this.options.onFinishOne,event,response,name); } return true; } diff --git a/api/js/jsapi/egw_app.js b/api/js/jsapi/egw_app.js index 36a2bc5aeb..a69c5c0007 100644 --- a/api/js/jsapi/egw_app.js +++ b/api/js/jsapi/egw_app.js @@ -1587,9 +1587,10 @@ var EgwApp = /** @class */ (function () { * @param {Boolean} _writable Allow edit access from the share. * @param {Boolean} _files Allow access to files from the share. * @param {Function} _callback Callback with results + * @param {Object} _extra Additional (app-specific or special) parameters * @returns {Boolean} returns false if not successful */ - EgwApp.prototype.share_link = function (_action, _senders, _target, _writable, _files, _callback) { + EgwApp.prototype.share_link = function (_action, _senders, _target, _writable, _files, _callback, _extra) { var path = _senders[0].id; if (!path) { return this.egw.message(this.egw.lang('Missing share path. Unable to create share.'), 'error'); @@ -1606,7 +1607,7 @@ var EgwApp = /** @class */ (function () { if (typeof _files === 'undefined' && _action.parent && _action.parent.getActionById('shareFiles')) { _files = _action.parent.getActionById('shareFiles').checked || false; } - return egw.json('EGroupware\\Api\\Sharing::ajax_create', [_action.id, path, _writable, _files], _callback ? _callback : this._share_link_callback, this, true, this).sendRequest(); + return egw.json('EGroupware\\Api\\Sharing::ajax_create', [_action.id, path, _writable, _files, _extra], _callback ? _callback : this._share_link_callback, this, true, this).sendRequest(); }; EgwApp.prototype.share_merge = function (_action, _senders, _target) { var parent = _action.parent.parent; diff --git a/api/js/jsapi/egw_app.ts b/api/js/jsapi/egw_app.ts index afd2b50147..031d8cff15 100644 --- a/api/js/jsapi/egw_app.ts +++ b/api/js/jsapi/egw_app.ts @@ -1987,9 +1987,10 @@ export abstract class EgwApp * @param {Boolean} _writable Allow edit access from the share. * @param {Boolean} _files Allow access to files from the share. * @param {Function} _callback Callback with results + * @param {Object} _extra Additional (app-specific or special) parameters * @returns {Boolean} returns false if not successful */ - share_link(_action, _senders, _target, _writable?, _files?, _callback?){ + share_link(_action, _senders, _target, _writable?, _files?, _callback?, _extra?){ var path = _senders[0].id; if(!path) { @@ -2011,7 +2012,7 @@ export abstract class EgwApp _files = _action.parent.getActionById('shareFiles').checked || false; } - return egw.json('EGroupware\\Api\\Sharing::ajax_create', [_action.id, path, _writable, _files], + return egw.json('EGroupware\\Api\\Sharing::ajax_create', [_action.id, path, _writable, _files, _extra], _callback ? _callback : this._share_link_callback, this, true, this).sendRequest(); } diff --git a/api/src/Contacts/Photo.php b/api/src/Contacts/Photo.php index 7f4b36a6fd..116c02c3c7 100644 --- a/api/src/Contacts/Photo.php +++ b/api/src/Contacts/Photo.php @@ -114,10 +114,7 @@ class Photo $path = $tmp; } return Api\Vfs\Sharing::share2link(Api\Vfs\Sharing::create( - $path, - Api\Vfs\Sharing::READONLY, - basename($path), - array() + '', $path, Api\Vfs\Sharing::READONLY, basename($path), array() )); } } \ No newline at end of file diff --git a/api/src/Sharing.php b/api/src/Sharing.php index 8b842de6c5..caa968e59c 100644 --- a/api/src/Sharing.php +++ b/api/src/Sharing.php @@ -552,17 +552,18 @@ class Sharing /** * Create a new share * + * @param string $action_id Specific type of share being created, default '' * @param string $path either path in temp_dir or vfs with optional vfs scheme * @param string $mode self::LINK: copy file in users tmp-dir or self::READABLE share given vfs file, - * if no vfs behave as self::LINK + * if no vfs behave as self::LINK * @param string $name filename to use for $mode==self::LINK, default basename of $path * @param string|array $recipients one or more recipient email addresses * @param array $extra =array() extra data to store + * @return array with share data, eg. value for key 'share_token' * @throw Api\Exception\NotFound if $path not found * @throw Api\Exception\AssertionFailed if user temp. directory does not exist and can not be created - * @return array with share data, eg. value for key 'share_token' */ - public static function create($path, $mode, $name, $recipients, $extra=array()) + public static function create(string $action_id, $path, $mode, $name, $recipients, $extra = array()) { if (!isset(static::$db)) static::$db = $GLOBALS['egw']->db; @@ -646,8 +647,9 @@ class Sharing * * @param String $action * @param String $path - * @param boolean $writable - * @param boolean $files + * @param boolean $writable Allow editing the shared entry / folder / file + * @param boolean $files For sharing an application entry, allow access to the linked files + * @param $extra Additional extra parameters */ public static function ajax_create($action, $path, $writable = false, $files = false, $extra = array()) { @@ -661,6 +663,7 @@ class Sharing 'include_files' => $files ); $share = $class::create( + $action, $path, $writable ? Sharing::WRITABLE : Sharing::READONLY, basename($path), diff --git a/api/src/Storage/Merge.php b/api/src/Storage/Merge.php index 488d56e376..480a07c8e2 100644 --- a/api/src/Storage/Merge.php +++ b/api/src/Storage/Merge.php @@ -13,16 +13,15 @@ namespace EGroupware\Api\Storage; +use DOMDocument; use EGroupware\Api; use EGroupware\Stylite; - -use DOMDocument; -use XSLTProcessor; use tidy; +use uiaccountsel; +use XSLTProcessor; use ZipArchive; // explicit import old, non-namespaced phpgwapi classes -use uiaccountsel; /** * Document merge print @@ -519,7 +518,7 @@ abstract class Merge //$extra['share_writable'] |= ($mode == Sharing::WRITABLE ? 1 : 0); - return \EGroupware\Stylite\Link\Sharing::create($path, $mode, NULL, $recipients, $extra); + return \EGroupware\Stylite\Link\Sharing::create('', $path, $mode, NULL, $recipients, $extra); } /** diff --git a/api/src/Vfs/Sharing.php b/api/src/Vfs/Sharing.php index 8be78589c0..17d6fdf753 100644 --- a/api/src/Vfs/Sharing.php +++ b/api/src/Vfs/Sharing.php @@ -44,6 +44,9 @@ class Sharing extends \EGroupware\Api\Sharing const READONLY = 'share_ro'; const WRITABLE = 'share_rw'; + const HIDDEN_UPLOAD = 9; // 8 is the next bitwise flag + 1 for writable + const HIDDEN_UPLOAD_DIR = '/Upload'; + /** * Modes for sharing files * @@ -185,17 +188,18 @@ class Sharing extends \EGroupware\Api\Sharing /** * Create a new share * + * @param string $action_id Name of the action used to create the share. Allows for customization. * @param string $path either path in temp_dir or vfs with optional vfs scheme * @param string $mode self::LINK: copy file in users tmp-dir or self::READABLE share given vfs file, - * if no vfs behave as self::LINK + * if no vfs behave as self::LINK * @param string $name filename to use for $mode==self::LINK, default basename of $path * @param string|array $recipients one or more recipient email addresses * @param array $extra =array() extra data to store + * @return array with share data, eg. value for key 'share_token' * @throw Api\Exception\NotFound if $path not found * @throw Api\Exception\AssertionFailed if user temp. directory does not exist and can not be created - * @return array with share data, eg. value for key 'share_token' */ - public static function create($path, $mode, $name, $recipients, $extra=array()) + public static function create(string $action_id, $path, $mode, $name, $recipients, $extra = array()) { if (!isset(self::$db)) self::$db = $GLOBALS['egw']->db; @@ -245,10 +249,17 @@ class Sharing extends \EGroupware\Api\Sharing { throw new Api\Exception\NotFound("'$path' NOT found!"); } + + // Set up anonymous upload directory + if($action_id == 'shareUploadDir') + { + static::create_hidden_upload($path, $mode, $name, $recipients, $extra); + } + // check if file has been shared before, with identical attributes if (($mode != self::LINK )) { - return parent::create($vfs_path ? $vfs_path : $path, $mode, $name, $recipients, $extra); + return parent::create($action_id, $vfs_path ? $vfs_path : $path, $mode, $name, $recipients, $extra); } else { @@ -286,10 +297,54 @@ class Sharing extends \EGroupware\Api\Sharing $vfs_path = $tmp_file; } - return parent::create($vfs_path, $mode, $name, $recipients, $extra); + return parent::create($action_id, $vfs_path, $mode, $name, $recipients, $extra); } } + /** + * Check the given path for an anonymous upload directory, and create it if it does not + * exist yet. Anon upload directory is not visible over the share, and any files uploaded + * to the share are placed inside it instead. + * + * @param $path + * @param $mode + * @param $name + * @param $recipients + * @param $extra + * + * @throws Api\Exception\AssertionFailed + * @throws Api\Exception\NoPermission + * @throws Api\Exception\WrongParameter + */ + protected static function create_hidden_upload($path, $mode, $name, $recipients, &$extra) + { + $upload_dir = Vfs::concat($path, self::HIDDEN_UPLOAD_DIR); + + if(($stat = Vfs::stat($upload_dir)) && !Vfs::check_access($upload_dir, Vfs::WRITABLE, $stat)) + { + throw new Api\Exception\NoPermission("Upload directory exists, but you have no write permission"); + } + if (!($stat = Vfs::stat($upload_dir))) + { + // Directory is not there, create it + if (!mkdir($upload_dir)) + { + throw new Api\Exception\NoPermission("Could not make upload directory"); + } + } + + // Set flags so things work + $extra['share_writable'] = self::HIDDEN_UPLOAD; + } + + /** + * Does this share have a hidden upload directory + */ + public function has_hidden_upload() + { + return (int)$this->share['share_writable'] == self::HIDDEN_UPLOAD; + } + /** * Delete specified shares and unlink temp. files * @@ -364,6 +419,17 @@ class Sharing extends \EGroupware\Api\Sharing $actions['share']['children']['shareReadonlyLink']['order'] = 22; $actions['share']['children']['shareWritable']['group'] = 3; + // Add in a hidden upload directory + $actions['share']['children']['shareUploadDir'] = array( + 'caption' => 'Hidden uploads', + 'group' => 1, + 'order' => 30, + 'enabled' => 'javaScript:app.filemanager.hidden_upload_enabled', + 'onExecute' => 'javaScript:app.filemanager.share_link', + 'icon' => 'upload', + 'hideOnDisabled' => true + ); + // Add in merge to document if (class_exists($appname.'_merge')) { @@ -451,6 +517,43 @@ if (file_exists(__DIR__.'/../../../filemanager/inc/class.filemanager_ui.inc.php' class SharingUi extends filemanager_ui { + /** + * Get active view - override so it points to this class + * + * @return string + */ + 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 = new Api\Etemplate(static::LIST_TEMPLATE); + + // Override and take over get_rows so we can filter out upload directory, or other customisations + $content['nm']['get_rows'] = '.' . __CLASS__ . '.get_rows'; + + if (isset($GLOBALS['egw']->sharing) && $GLOBALS['egw']->sharing->has_hidden_upload()) + { + // 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(static::get_home_dir(), Vfs\Sharing::HIDDEN_UPLOAD_DIR); + $this->etemplate->setElementAttribute('nm[upload]', 'onFinishOne', "app.filemanager.upload(ev, 1, '$path', 'rename')"); + } + + return parent::listview($content, $msg); + } + /** * Get the configured start directory for the current user * @@ -472,7 +575,7 @@ if (file_exists(__DIR__.'/../../../filemanager/inc/class.filemanager_ui.inc.php' $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())) + if (Vfs::is_writable($GLOBALS['egw']->sharing->get_root())) { return $actions; } @@ -501,9 +604,69 @@ if (file_exists(__DIR__.'/../../../filemanager/inc/class.filemanager_ui.inc.php' $options = parent::get_vfs_options($query); // Hide symlinks - $options['type'] = '!l'; + // TODO: This hides everything, see Vfs::_check_add() line 648 + //$options['type'] = '!l'; return $options; } + + protected function is_hidden_upload_dir($directory) + { + if (!isset($GLOBALS['egw']->sharing)) return false; + return Vfs::is_dir($directory) && $directory == Vfs::concat( $GLOBALS['egw']->sharing->get_root(), Vfs\Sharing::HIDDEN_UPLOAD_DIR ); + } + + /** + * Callback to fetch the rows for the nextmatch widget + * + * @param array $query + * @param array &$rows + * @return int + */ + function get_rows(&$query, &$rows) + { + $hidden_upload = (isset($GLOBALS['egw']->sharing) && $GLOBALS['egw']->sharing->has_hidden_upload()); + + // Check for navigating outside share, redirect back to share + if (!Vfs::stat($query['path'],false) || !Vfs::is_dir($query['path']) || !Vfs::check_access($query['path'],Vfs::READABLE) || + + // Not allowed in hidden upload dir + $hidden_upload && strpos($query['path'], Sharing::HIDDEN_UPLOAD_DIR) === 0) + { + // 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 if directory is writeable or not + $response = Api\Json\Response::get(); + $response->call('app.filemanager.set_readonly', $query['path'], true); + + // 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/tests/Vfs/SharingBase.php b/api/tests/Vfs/SharingBase.php index 44094ce86e..469c09ffd0 100644 --- a/api/tests/Vfs/SharingBase.php +++ b/api/tests/Vfs/SharingBase.php @@ -416,7 +416,7 @@ class SharingBase extends LoggedInTest } // Create share - $this->shares[] = $share = TestSharing::create($path, $mode, $name, $recipients, $extra); + $this->shares[] = $share = TestSharing::create('', $path, $mode, $name, $recipients, $extra); return $share; } diff --git a/api/tests/Vfs/SharingHooksTest.php b/api/tests/Vfs/SharingHooksTest.php index 525e4549f8..926e3342c9 100644 --- a/api/tests/Vfs/SharingHooksTest.php +++ b/api/tests/Vfs/SharingHooksTest.php @@ -36,7 +36,7 @@ class SharingHooksTest extends SharingBase Sharing::delete(array('share_path' => $test_file)); // Create share - $this->shares[] = $created_share = Sharing::create($test_file, Sharing::READONLY, '', ''); + $this->shares[] = $created_share = Sharing::create('', $test_file, Sharing::READONLY, '', ''); $this->assertEquals(Vfs::PREFIX . $test_file, $created_share['share_path']); @@ -64,7 +64,7 @@ class SharingHooksTest extends SharingBase Sharing::delete(array('share_path' => $test_file)); // Create share - $this->shares[] = $created_share = Sharing::create($test_file, Sharing::READONLY, '', ''); + $this->shares[] = $created_share = Sharing::create('', $test_file, Sharing::READONLY, '', ''); $this->assertEquals(Vfs::PREFIX . $test_file, $created_share['share_path']); @@ -91,7 +91,7 @@ class SharingHooksTest extends SharingBase Sharing::delete(array('share_path' => $test_file)); // Create share - $this->shares[] = $created_share = Sharing::create($test_file, Sharing::READONLY, '', ''); + $this->shares[] = $created_share = Sharing::create('', $test_file, Sharing::READONLY, '', ''); $this->assertEquals(Vfs::PREFIX . $test_file, $created_share['share_path']); diff --git a/api/tests/Vfs/SharingTest.php b/api/tests/Vfs/SharingTest.php index ccc0a30019..8c2287e1de 100644 --- a/api/tests/Vfs/SharingTest.php +++ b/api/tests/Vfs/SharingTest.php @@ -20,7 +20,6 @@ namespace EGroupware\Api\Vfs; require_once __DIR__ . '/SharingBase.php'; use EGroupware\Api\Vfs; -use EGroupware\Api\LoggedInTest as LoggedInTest; class SharingTest extends SharingBase @@ -231,7 +230,7 @@ class SharingTest extends SharingBase ); // Create share - $this->shares[] = $created_share = Sharing::create($symlink, Sharing::READONLY, '', ''); + $this->shares[] = $created_share = Sharing::create('', $symlink, Sharing::READONLY, '', ''); $this->assertEquals($file, $created_share['share_path']); } @@ -257,7 +256,7 @@ class SharingTest extends SharingBase ); // Create share - $this->shares[] = $created_share = Sharing::create($symlink, Sharing::READONLY, '', ''); + $this->shares[] = $created_share = Sharing::create('', $symlink, Sharing::READONLY, '', ''); $this->assertEquals($file, $created_share['share_path']); } diff --git a/filemanager/inc/class.filemanager_merge.inc.php b/filemanager/inc/class.filemanager_merge.inc.php index f984ce9f3c..7c0e27d306 100644 --- a/filemanager/inc/class.filemanager_merge.inc.php +++ b/filemanager/inc/class.filemanager_merge.inc.php @@ -249,7 +249,7 @@ class filemanager_merge extends Api\Storage\Merge $recipients = array(); $extra = array(); - return \EGroupware\Api\Vfs\Sharing::create($path, $mode, NULL, $recipients, $extra); + return \EGroupware\Api\Vfs\Sharing::create('', $path, $mode, NULL, $recipients, $extra); } /** diff --git a/filemanager/inc/class.filemanager_ui.inc.php b/filemanager/inc/class.filemanager_ui.inc.php index 4de5318329..82518c6f9f 100644 --- a/filemanager/inc/class.filemanager_ui.inc.php +++ b/filemanager/inc/class.filemanager_ui.inc.php @@ -48,6 +48,8 @@ class filemanager_ui * */ public static $merge_prop_namespace = ''; + protected $etemplate; + const LIST_TEMPLATE = 'filemanager.index'; /** * Constructor @@ -466,7 +468,7 @@ class filemanager_ui */ function listview(array $content=null,$msg=null) { - $tpl = new Etemplate('filemanager.index'); + $tpl = $this->etemplate ? $this->etemplate : new Etemplate(static::LIST_TEMPLATE); if($msg) Framework::message($msg); @@ -569,7 +571,7 @@ class filemanager_ui // sharing has no divAppbox, we need to set popupMainDiv instead, to be able to drop files everywhere if (substr($_SERVER['SCRIPT_FILENAME'], -10) == '/share.php') { - $tpl->setElementAttribute('nm[buttons][upload]', 'drop_target', 'popupMainDiv'); + $tpl->setElementAttribute('nm[upload]', 'drop_target', 'popupMainDiv'); } // Set view button to match current settings if($content['nm']['view'] == 'tile') @@ -1483,82 +1485,20 @@ class filemanager_ui switch($action) { case 'upload': - $script_error = 0; - foreach($selected as $tmp_name => &$data) - { - $path = Vfs::concat($dir, Vfs::encodePathComponent($data['name'])); - - if(Vfs::deny_script($path)) - { - if (!isset($script_error)) - { - $arr['msg'] .= ($arr['msg'] ? "\n" : '').lang('You are NOT allowed to upload a script!'); - } - ++$script_error; - ++$arr['errs']; - unset($selected[$tmp_name]); - } - elseif (Vfs::is_dir($path)) - { - $data['confirm'] = 'is_dir'; - } - elseif (!$data['confirmed'] && Vfs::stat($path)) - { - $data['confirm'] = true; - } - else - { - if (is_dir($GLOBALS['egw_info']['server']['temp_dir']) && is_writable($GLOBALS['egw_info']['server']['temp_dir'])) - { - $tmp_path = $GLOBALS['egw_info']['server']['temp_dir'] . '/' . basename($tmp_name); - } - else - { - $tmp_path = ini_get('upload_tmp_dir').'/'.basename($tmp_name); - } - - if (Vfs::copy_uploaded($tmp_path, $path, $props, false)) - { - ++$arr['files']; - $uploaded[] = $data['name']; - } - else - { - ++$arr['errs']; - } - } - } - if ($arr['errs'] > $script_error) - { - $arr['msg'] .= ($arr['msg'] ? "\n" : '').lang('Error uploading file!'); - } - if ($arr['files']) - { - $arr['msg'] .= ($arr['msg'] ? "\n" : '').lang('%1 successful uploaded.', implode(', ', $uploaded)); - } - $arr['uploaded'] = $selected; - $arr['path'] = $dir; - $arr['props'] = $props; + static::handle_upload_action($action, $selected, $dir, $props, $arr); break; case 'shareWritableLink': case 'shareReadonlyLink': if ($action === 'shareWritableLink') { $share = Vfs\Sharing::create( - $selected, - Vfs\Sharing::WRITABLE, - basename($selected), - array(), - array('share_writable' => true) + '', $selected, Vfs\Sharing::WRITABLE, basename($selected), array(), array('share_writable' => true) ); } else { $share = Vfs\Sharing::create( - $selected, - Vfs\Sharing::READONLY, - basename($selected), - array() + '', $selected, Vfs\Sharing::READONLY, basename($selected), array() ); } $arr["share_link"] = $link = Vfs\Sharing::share2link($share); @@ -1592,6 +1532,102 @@ class filemanager_ui return $arr; } + /** + * Deal with an uploaded file + * + * @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) + { + $script_error = 0; + $conflict = $selected['conflict']; + unset($selected['conflict']); + + foreach($selected as $tmp_name => &$data) + { + $path = Vfs::concat($dir, Vfs::encodePathComponent($data['name'])); + + if(Vfs::deny_script($path)) + { + if (!isset($script_error)) + { + $arr['msg'] .= ($arr['msg'] ? "\n" : '').lang('You are NOT allowed to upload a script!'); + } + ++$script_error; + ++$arr['errs']; + unset($selected[$tmp_name]); + continue; + } + elseif (Vfs::is_dir($path)) + { + $data['confirm'] = 'is_dir'; + continue; + } + elseif (!$data['confirmed'] && Vfs::stat($path)) + { + // File exists, what to do? + switch($conflict) + { + case 'overwrite': + unset($data['confirm']); + $data['confirmed'] = true; + break; + case 'rename': + // Find a unique name + $i = 1; + $info = pathinfo($path); + while(Vfs::file_exists($path)) + { + $path = $info['dirname'] . '/'. $info['filename'] . " ($i)." . $info['extension']; + $i++; + } + break; + case 'ask': + default: + $data['confirm'] = true; + } + } + if(!$data['confirm']) + { + if (is_dir($GLOBALS['egw_info']['server']['temp_dir']) && is_writable($GLOBALS['egw_info']['server']['temp_dir'])) + { + $tmp_path = $GLOBALS['egw_info']['server']['temp_dir'] . '/' . basename($tmp_name); + } + else + { + $tmp_path = ini_get('upload_tmp_dir') . '/' . basename($tmp_name); + } + + if (Vfs::copy_uploaded($tmp_path, $path, $props, false)) + { + ++$arr['files']; + $uploaded[] = $data['name']; + } + else + { + ++$arr['errs']; + } + } + } + if ($arr['errs'] > $script_error) + { + $arr['msg'] .= ($arr['msg'] ? "\n" : '').lang('Error uploading file!'); + } + if ($arr['files']) + { + $arr['msg'] .= ($arr['msg'] ? "\n" : '').lang('%1 successful uploaded.', implode(', ', $uploaded)); + } + $arr['uploaded'] = $selected; + $arr['path'] = $dir; + $arr['props'] = $props; + } + /** * Convert perms array back to integer mode * diff --git a/filemanager/js/app.js b/filemanager/js/app.js index 7c56a6799f..ef39adf63b 100644 --- a/filemanager/js/app.js +++ b/filemanager/js/app.js @@ -254,14 +254,18 @@ var filemanagerAPP = /** @class */ (function (_super) { * @param {event} _event * @param {number} _file_count * @param {string=} _path where the file is uploaded to, default current directory + * @param {string} _conflict What to do if the file conflicts with one on the server */ - filemanagerAPP.prototype.upload = function (_event, _file_count, _path) { + filemanagerAPP.prototype.upload = function (_event, _file_count, _path, _conflict) { + if (_conflict === void 0) { _conflict = "ask"; } if (typeof _path == 'undefined') { _path = this.get_path(); } if (_file_count && !jQuery.isEmptyObject(_event.data.getValue())) { var widget = _event.data; - egw.json('filemanager_ui::ajax_action', ['upload', widget.getValue(), _path], this._upload_callback, this, true, this).sendRequest(); + var value = widget.getValue(); + value.conflict = _conflict; + egw.json('filemanager_ui::ajax_action', ['upload', value, _path, _conflict], this._upload_callback, this, true, this).sendRequest(); widget.set_value(''); } }; @@ -974,7 +978,8 @@ var filemanagerAPP = /** @class */ (function (_super) { if (!path) { _senders[0] = { id: this.get_path() }; } - _super.prototype.share_link.call(this, _action, _senders, _target, _writable, _files, _callback); + var _extra = {}; + _super.prototype.share_link.call(this, _action, _senders, _target, _writable, _files, _callback, _extra); }; /** * Share-link callback @@ -1011,6 +1016,16 @@ var filemanagerAPP = /** @class */ (function (_super) { value: { content: { "share_link": _data.share_link } } }); }; + /** + * Check if a row can have the Hidden Uploads action + * Needs to be a directory + */ + filemanagerAPP.prototype.hidden_upload_enabled = function (_action, _senders) { + var data = egw.dataGetUIDdata(_senders[0].id); + var readonly = (data.data.class || '').split(/ +/).indexOf('noEdit') >= 0; + // symlinks dont have mime 'http/unix-directory', but server marks all directories with class 'isDir' + return (data.data.is_dir && !readonly); + }; /** * View the link from an existing share * (EPL only) diff --git a/filemanager/js/app.ts b/filemanager/js/app.ts index 43e80aa3f3..95d4c2e89f 100644 --- a/filemanager/js/app.ts +++ b/filemanager/js/app.ts @@ -290,8 +290,9 @@ export class filemanagerAPP extends EgwApp * @param {event} _event * @param {number} _file_count * @param {string=} _path where the file is uploaded to, default current directory + * @param {string} _conflict What to do if the file conflicts with one on the server */ - upload(_event, _file_count : number, _path? : string) + upload(_event, _file_count : number, _path? : string, _conflict = "ask") { if(typeof _path == 'undefined') { @@ -300,7 +301,9 @@ export class filemanagerAPP extends EgwApp if (_file_count && !jQuery.isEmptyObject(_event.data.getValue())) { let widget = _event.data; - egw.json('filemanager_ui::ajax_action', ['upload', widget.getValue(), _path], + let value = widget.getValue(); + value.conflict = _conflict; + egw.json('filemanager_ui::ajax_action', ['upload', value, _path, _conflict], this._upload_callback, this, true, this ).sendRequest(); widget.set_value(''); @@ -1179,7 +1182,8 @@ export class filemanagerAPP extends EgwApp { _senders[0] = {id: this.get_path()}; } - super.share_link(_action, _senders, _target, _writable, _files, _callback); + let _extra = {}; + super.share_link(_action, _senders, _target, _writable, _files, _callback, _extra); } /** @@ -1223,6 +1227,19 @@ export class filemanagerAPP extends EgwApp }); } + /** + * Check if a row can have the Hidden Uploads action + * Needs to be a directory + */ + hidden_upload_enabled(_action: egwAction, _senders: egwActionObject[]) + { + let data = egw.dataGetUIDdata(_senders[0].id); + let readonly = (data.data.class || '').split(/ +/).indexOf('noEdit') >= 0; + + // symlinks dont have mime 'http/unix-directory', but server marks all directories with class 'isDir' + return (data.data.is_dir && !readonly); + } + /** * View the link from an existing share * (EPL only) diff --git a/mail/inc/class.mail_compose.inc.php b/mail/inc/class.mail_compose.inc.php index 0d8a3e6f99..3fe9fb1a43 100644 --- a/mail/inc/class.mail_compose.inc.php +++ b/mail/inc/class.mail_compose.inc.php @@ -11,13 +11,13 @@ */ use EGroupware\Api; -use EGroupware\Api\Link; -use EGroupware\Api\Framework; -use EGroupware\Api\Egw; use EGroupware\Api\Acl; +use EGroupware\Api\Egw; use EGroupware\Api\Etemplate; -use EGroupware\Api\Vfs; +use EGroupware\Api\Framework; +use EGroupware\Api\Link; use EGroupware\Api\Mail; +use EGroupware\Api\Vfs; /** * Mail interface class for compose mails in popup @@ -2664,11 +2664,11 @@ class mail_compose // create share if ($filemode == Vfs\Sharing::WRITABLE || $expiration || $password) { - $share = stylite_sharing::create($path, $filemode, $attachment['name'], $recipients, $expiration, $password); + $share = stylite_sharing::create('', $path, $filemode, $attachment['name'], $recipients, $expiration, $password); } else { - $share = Vfs\Sharing::create($path, $filemode, $attachment['name'], $recipients); + $share = Vfs\Sharing::create('', $path, $filemode, $attachment['name'], $recipients); } $link = Vfs\Sharing::share2link($share);