Let users choose a share_folder

pull/28157/head
Hinrich Mahler 3 years ago committed by John Molakvoæ
parent 33a0b75c83
commit 961f8958c0
No known key found for this signature in database
GPG Key ID: 60C25B8C072916CF

@ -9,6 +9,7 @@
* @author Lukas Reschke <lukas@statuscode.ch>
* @author Maxence Lange <maxence@artificial-owl.com>
* @author Roeland Jago Douma <roeland@famdouma.nl>
* @author Hinrich Mahler <nextcloud@mahlerhome.de>
*
* @license AGPL-3.0
*
@ -82,6 +83,16 @@ return [
'url' => '/settings/defaultAccept',
'verb' => 'PUT',
],
[
'name' => 'Settings#setUserShareFolder',
'url' => '/settings/shareFolder',
'verb' => 'PUT',
],
[
'name' => 'Settings#resetUserShareFolder',
'url' => '/settings/shareFolder',
'verb' => 'DELETE',
],
[
'name' => 'Accept#accept',
'url' => '/accept/{shareId}',

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

@ -1,4 +1,4 @@
!function(e){var n={};function t(r){if(n[r])return n[r].exports;var o=n[r]={i:r,l:!1,exports:{}};return e[r].call(o.exports,o,o.exports,t),o.l=!0,o.exports}t.m=e,t.c=n,t.d=function(e,n,r){t.o(e,n)||Object.defineProperty(e,n,{enumerable:!0,get:r})},t.r=function(e){"undefined"!=typeof Symbol&&Symbol.toStringTag&&Object.defineProperty(e,Symbol.toStringTag,{value:"Module"}),Object.defineProperty(e,"__esModule",{value:!0})},t.t=function(e,n){if(1&n&&(e=t(e)),8&n)return e;if(4&n&&"object"==typeof e&&e&&e.__esModule)return e;var r=Object.create(null);if(t.r(r),Object.defineProperty(r,"default",{enumerable:!0,value:e}),2&n&&"string"!=typeof e)for(var o in e)t.d(r,o,function(n){return e[n]}.bind(null,o));return r},t.n=function(e){var n=e&&e.__esModule?function(){return e.default}:function(){return e};return t.d(n,"a",n),n},t.o=function(e,n){return Object.prototype.hasOwnProperty.call(e,n)},t.p="/js/",t(t.s=154)}({154:function(e,n,r){
!function(e){var n={};function t(r){if(n[r])return n[r].exports;var o=n[r]={i:r,l:!1,exports:{}};return e[r].call(o.exports,o,o.exports,t),o.l=!0,o.exports}t.m=e,t.c=n,t.d=function(e,n,r){t.o(e,n)||Object.defineProperty(e,n,{enumerable:!0,get:r})},t.r=function(e){"undefined"!=typeof Symbol&&Symbol.toStringTag&&Object.defineProperty(e,Symbol.toStringTag,{value:"Module"}),Object.defineProperty(e,"__esModule",{value:!0})},t.t=function(e,n){if(1&n&&(e=t(e)),8&n)return e;if(4&n&&"object"==typeof e&&e&&e.__esModule)return e;var r=Object.create(null);if(t.r(r),Object.defineProperty(r,"default",{enumerable:!0,value:e}),2&n&&"string"!=typeof e)for(var o in e)t.d(r,o,function(n){return e[n]}.bind(null,o));return r},t.n=function(e){var n=e&&e.__esModule?function(){return e.default}:function(){return e};return t.d(n,"a",n),n},t.o=function(e,n){return Object.prototype.hasOwnProperty.call(e,n)},t.p="/js/",t(t.s=155)}({155:function(e,n,r){
/**
* @copyright Copyright (c) 2016 John Molakvoæ <skjnldsv@protonmail.com>
*

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

@ -1,4 +1,4 @@
!function(e){var t={};function r(n){if(t[n])return t[n].exports;var o=t[n]={i:n,l:!1,exports:{}};return e[n].call(o.exports,o,o.exports,r),o.l=!0,o.exports}r.m=e,r.c=t,r.d=function(e,t,n){r.o(e,t)||Object.defineProperty(e,t,{enumerable:!0,get:n})},r.r=function(e){"undefined"!=typeof Symbol&&Symbol.toStringTag&&Object.defineProperty(e,Symbol.toStringTag,{value:"Module"}),Object.defineProperty(e,"__esModule",{value:!0})},r.t=function(e,t){if(1&t&&(e=r(e)),8&t)return e;if(4&t&&"object"==typeof e&&e&&e.__esModule)return e;var n=Object.create(null);if(r.r(n),Object.defineProperty(n,"default",{enumerable:!0,value:e}),2&t&&"string"!=typeof e)for(var o in e)r.d(n,o,function(t){return e[t]}.bind(null,o));return n},r.n=function(e){var t=e&&e.__esModule?function(){return e.default}:function(){return e};return r.d(t,"a",t),t},r.o=function(e,t){return Object.prototype.hasOwnProperty.call(e,t)},r.p="/js/",r(r.s=498)}({498:function(e,t){
!function(e){var t={};function r(n){if(t[n])return t[n].exports;var o=t[n]={i:n,l:!1,exports:{}};return e[n].call(o.exports,o,o.exports,r),o.l=!0,o.exports}r.m=e,r.c=t,r.d=function(e,t,n){r.o(e,t)||Object.defineProperty(e,t,{enumerable:!0,get:n})},r.r=function(e){"undefined"!=typeof Symbol&&Symbol.toStringTag&&Object.defineProperty(e,Symbol.toStringTag,{value:"Module"}),Object.defineProperty(e,"__esModule",{value:!0})},r.t=function(e,t){if(1&t&&(e=r(e)),8&t)return e;if(4&t&&"object"==typeof e&&e&&e.__esModule)return e;var n=Object.create(null);if(r.r(n),Object.defineProperty(n,"default",{enumerable:!0,value:e}),2&t&&"string"!=typeof e)for(var o in e)r.d(n,o,function(t){return e[t]}.bind(null,o));return n},r.n=function(e){var t=e&&e.__esModule?function(){return e.default}:function(){return e};return r.d(t,"a",t),t},r.o=function(e,t){return Object.prototype.hasOwnProperty.call(e,t)},r.p="/js/",r(r.s=502)}({502:function(e,t){
/**
* @copyright Copyright (c) 2019 John Molakvoæ <skjnldsv@protonmail.com>
*

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

@ -6,6 +6,7 @@ declare(strict_types=1);
* @copyright Copyright (c) 2019, Roeland Jago Douma <roeland@famdouma.nl>
*
* @author Roeland Jago Douma <roeland@famdouma.nl>
* @author Hinrich Mahler <nextcloud@mahlerhome.de>
*
* @license GNU AGPL version 3 or any later version
*
@ -35,10 +36,13 @@ class SettingsController extends Controller {
/** @var IConfig */
private $config;
/** @var string */
private $userId;
public function __construct(IRequest $request, IConfig $config, string $userId) {
public function __construct(IRequest $request,
IConfig $config,
string $userId) {
parent::__construct(Application::APP_ID, $request);
$this->config = $config;
@ -52,4 +56,20 @@ class SettingsController extends Controller {
$this->config->setUserValue($this->userId, Application::APP_ID, 'default_accept', $accept ? 'yes' : 'no');
return new JSONResponse();
}
/**
* @NoAdminRequired
*/
public function setUserShareFolder(string $shareFolder): JSONResponse {
$this->config->setUserValue($this->userId, Application::APP_ID, 'share_folder', $shareFolder);
return new JSONResponse();
}
/**
* @NoAdminRequired
*/
public function resetUserShareFolder(): JSONResponse {
$this->config->deleteUserValue($this->userId, Application::APP_ID, 'share_folder');
return new JSONResponse();
}
}

@ -309,7 +309,7 @@ class Manager {
if ($share) {
\OC_Util::setupFS($this->uid);
$shareFolder = Helper::getShareFolder();
$shareFolder = Helper::getShareFolder(null, $this->uid);
$mountPoint = Files::buildNotExistingFileName($shareFolder, $share['name']);
$mountPoint = Filesystem::normalizePath($mountPoint);
$hash = md5($mountPoint);

@ -29,6 +29,7 @@ namespace OCA\Files_Sharing;
use OC\Files\Filesystem;
use OC\Files\View;
use OCA\Files_Sharing\AppInfo\Application;
class Helper {
public static function registerHooks() {
@ -63,16 +64,29 @@ class Helper {
/**
* get default share folder
*
* @param \OC\Files\View $view
* @param \OC\Files\View|null $view
* @param string|null $userId
* @return string
*/
public static function getShareFolder($view = null) {
public static function getShareFolder(View $view = null, string $userId = null): string {
if ($view === null) {
$view = Filesystem::getView();
}
$shareFolder = \OC::$server->getConfig()->getSystemValue('share_folder', '/');
$config = \OC::$server->getConfig();
$systemDefault = $config->getSystemValue('share_folder', '/');
$allowCustomShareFolder = $config->getSystemValueBool('sharing.allow_custom_share_folder', true);
// Init custom shareFolder
$shareFolder = $systemDefault;
if ($userId !== null && $allowCustomShareFolder) {
$shareFolder = $config->getUserValue($userId, Application::APP_ID, 'share_folder', $systemDefault);
}
// Verify and sanitize path
$shareFolder = Filesystem::normalizePath($shareFolder);
// Init path if folder doesn't exists
if (!$view->file_exists($shareFolder)) {
$dir = '';
$subdirs = explode('/', $shareFolder);

@ -7,6 +7,7 @@ declare(strict_types=1);
*
* @author Julius Härtl <jus@bitgrid.net>
* @author Roeland Jago Douma <roeland@famdouma.nl>
* @author Hinrich Mahler <nextcloud@mahlerhome.de>
*
* @license GNU AGPL version 3 or any later version
*
@ -49,10 +50,16 @@ class Personal implements ISettings {
public function getForm(): TemplateResponse {
$defaultAcceptSystemConfig = $this->config->getSystemValueBool('sharing.enable_share_accept', false) ? 'no' : 'yes';
$shareFolderSystemConfig = $this->config->getSystemValue('share_folder', '/');
$acceptDefault = $this->config->getUserValue($this->userId, Application::APP_ID, 'default_accept', $defaultAcceptSystemConfig) === 'yes';
$enforceAccept = $this->config->getSystemValueBool('sharing.force_share_accept', false);
$allowCustomDirectory = $this->config->getSystemValueBool('sharing.allow_custom_share_folder', true);
$shareFolderDefault = $this->config->getUserValue($this->userId, Application::APP_ID, 'share_folder', $shareFolderSystemConfig);
$this->initialState->provideInitialState('accept_default', $acceptDefault);
$this->initialState->provideInitialState('enforce_accept', $enforceAccept);
$this->initialState->provideInitialState('allow_custom_share_folder', $allowCustomDirectory);
$this->initialState->provideInitialState('share_folder', $shareFolderDefault);
$this->initialState->provideInitialState('default_share_folder', $shareFolderSystemConfig);
return new TemplateResponse('files_sharing', 'Settings/personal');
}

@ -105,7 +105,7 @@ class SharedMount extends MountPoint implements MoveableMount {
$folderExistCache->set($parent, $parentExists);
}
if (!$parentExists) {
$parent = Helper::getShareFolder($this->recipientView);
$parent = Helper::getShareFolder($this->recipientView, $this->user);
}
$newMountPoint = $this->generateUniqueTarget(

@ -2,6 +2,7 @@
- @copyright 2019 Roeland Jago Douma <roeland@famdouma.nl>
-
- @author 2019 Roeland Jago Douma <roeland@famdouma.nl>
- @author Hinrich Mahler <nextcloud@mahlerhome.de>
-
- @license GNU AGPL version 3 or any later version
-
@ -20,9 +21,9 @@
-->
<template>
<div v-if="!enforceAcceptShares" id="files-sharing-personal-settings" class="section">
<div v-if="!enforceAcceptShares || allowCustomDirectory" id="files-sharing-personal-settings" class="section">
<h2>{{ t('files_sharing', 'Sharing') }}</h2>
<p>
<p v-if="!enforceAcceptShares">
<input id="files-sharing-personal-settings-accept"
v-model="accepting"
class="checkbox"
@ -30,31 +31,55 @@
@change="toggleEnabled">
<label for="files-sharing-personal-settings-accept">{{ t('files_sharing', 'Accept user and group shares by default') }}</label>
</p>
<p v-if="allowCustomDirectory">
<SelectShareFolderDialogue />
</p>
</div>
</template>
<script>
import axios from '@nextcloud/axios'
import { loadState } from '@nextcloud/initial-state'
import { generateUrl } from '@nextcloud/router'
import { loadState } from '@nextcloud/initial-state'
import { showError } from '@nextcloud/dialogs'
import axios from '@nextcloud/axios'
import SelectShareFolderDialogue from './SelectShareFolderDialogue'
export default {
name: 'PersonalSettings',
components: {
SelectShareFolderDialogue,
},
data() {
return {
// Share acceptance config
accepting: loadState('files_sharing', 'accept_default'),
enforceAcceptShares: loadState('files_sharing', 'enforce_accept'),
// Receiving share folder config
allowCustomDirectory: loadState('files_sharing', 'allow_custom_share_folder'),
}
},
methods: {
toggleEnabled() {
axios.put(
generateUrl('/apps/files_sharing/settings/defaultAccept'),
{
async toggleEnabled() {
try {
await axios.put(generateUrl('/apps/files_sharing/settings/defaultAccept'), {
accept: this.accepting,
}
)
})
} catch (error) {
showError(t('sharing', 'Error while toggling options'))
console.error(error)
}
},
},
}
</script>
<style scoped lang="scss">
p {
margin-top: 12px;
margin-bottom: 12px;
}
</style>

@ -0,0 +1,125 @@
<!--
- @copyright 2021 Hinrich Mahler <nextcloud@mahlerhome.de>
-
- @license GNU AGPL version 3 or any later version
-
- This program is free software: you can redistribute it and/or modify
- it under the terms of the GNU Affero General Public License as
- published by the Free Software Foundation, either version 3 of the
- License, or (at your option) any later version.
-
- This program is distributed in the hope that it will be useful,
- but WITHOUT ANY WARRANTY; without even the implied warranty of
- MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- GNU Affero General Public License for more details.
-
- You should have received a copy of the GNU Affero General Public License
- along with this program. If not, see <http://www.gnu.org/licenses/>.
-->
<template>
<div class="share-folder">
<span>{{ t('files', 'Set default folder for accepted shares') }} </span>
<!-- Folder picking form -->
<form class="share-folder__form" @reset.prevent.stop="resetFolder">
<input class="share-folder__picker"
type="text"
:placeholder="readableDirectory"
@click.prevent="pickFolder">
<!-- Show reset button if folder is different -->
<input v-if="readableDirectory !== defaultDirectory"
class="share-folder__reset"
type="reset"
:value="t('files', 'Reset')"
:aria-label="t('files', 'Reset folder to system default')">
</form>
</div>
</template>
<script>
import axios from '@nextcloud/axios'
import path from 'path'
import { generateUrl } from '@nextcloud/router'
import { getFilePickerBuilder, showError } from '@nextcloud/dialogs'
import { loadState } from '@nextcloud/initial-state'
const defaultDirectory = loadState('files_sharing', 'default_share_folder', '/')
const directory = loadState('files_sharing', 'share_folder', defaultDirectory)
export default {
name: 'SelectShareFolderDialogue',
data() {
return {
directory,
defaultDirectory,
}
},
computed: {
readableDirectory() {
if (!this.directory) {
return '/'
}
return this.directory
},
},
methods: {
async pickFolder() {
// Setup file picker
const picker = getFilePickerBuilder(t('files', 'Choose a default folder for accepted shares'))
.startAt(this.readableDirectory)
.setMultiSelect(false)
.setModal(true)
.setType(1)
.setMimeTypeFilter(['httpd/unix-directory'])
.allowDirectories()
.build()
try {
// Init user folder picking
const dir = await picker.pick() || '/'
if (!dir.startsWith('/')) {
throw new Error(t('files', 'Invalid path selected'))
}
// Fix potential path issues and save results
this.directory = path.normalize(dir)
await axios.put(generateUrl('/apps/files_sharing/settings/shareFolder'), {
shareFolder: this.directory,
})
} catch (error) {
showError(error.message || t('files', 'Unknown error'))
}
},
resetFolder() {
this.directory = this.defaultDirectory
axios.delete(generateUrl('/apps/files_sharing/settings/shareFolder'))
},
},
}
</script>
<style scoped lang="scss">
.share-folder {
&__form {
display: flex;
}
&__picker {
cursor: pointer;
min-width: 266px;
}
// Make the reset button looks like text
&__reset {
background-color: transparent;
border: none;
font-weight: normal;
text-decoration: underline;
font-size: inherit;
}
}
</style>

@ -1485,6 +1485,11 @@ $CONFIG = [
*/
'sharing.force_share_accept' => false,
/**
* Set to false to prevent users from setting a custom share_folder
*/
'sharing.allow_custom_share_folder' => true,
/**
* Set to false to stop sending a mail when users receive a share
*/

@ -44,6 +44,7 @@ namespace OC\Share20;
use OC\Cache\CappedMemoryCache;
use OC\Files\Mount\MoveableMount;
use OC\Share20\Exception\ProviderException;
use OCA\Files_Sharing\AppInfo\Application;
use OCA\Files_Sharing\ISharedStorage;
use OCP\EventDispatcher\IEventDispatcher;
use OCP\Files\File;
@ -788,7 +789,15 @@ class Manager implements IManager {
}
// Generate the target
$target = $this->config->getSystemValue('share_folder', '/') . '/' . $share->getNode()->getName();
$defaultShareFolder = $this->config->getSystemValue('share_folder', '/');
$allowCustomShareFolder = $this->config->getSystemValueBool('sharing.allow_custom_share_folder', true);
if ($allowCustomShareFolder) {
$shareFolder = $this->config->getUserValue($share->getSharedWith(), Application::APP_ID, 'share_folder', $defaultShareFolder);
} else {
$shareFolder = $defaultShareFolder;
}
$target = $shareFolder . '/' . $share->getNode()->getName();
$target = \OC\Files\Filesystem::normalizePath($target);
$share->setTarget($target);

Loading…
Cancel
Save