Fix users & groups sharing
Signed-off-by: John Molakvoæ (skjnldsv) <skjnldsv@protonmail.com>
This commit is contained in:
parent
c6b515fa3d
commit
e060cfdfc7
|
@ -60,7 +60,5 @@ return [
|
||||||
|
|
||||||
['name' => 'api#insertSubmission', 'url' => '/api/v1/submission/insert', 'verb' => 'POST'],
|
['name' => 'api#insertSubmission', 'url' => '/api/v1/submission/insert', 'verb' => 'POST'],
|
||||||
['name' => 'api#deleteSubmission', 'url' => '/api/v1/submission/{id}', 'verb' => 'DELETE'],
|
['name' => 'api#deleteSubmission', 'url' => '/api/v1/submission/{id}', 'verb' => 'DELETE'],
|
||||||
|
|
||||||
['name' => 'system#get_site_users_and_groups', 'url' => '/get/siteusers', 'verb' => 'POST'],
|
|
||||||
]
|
]
|
||||||
];
|
];
|
||||||
|
|
|
@ -26,6 +26,7 @@
|
||||||
|
|
||||||
namespace OCA\Forms\Controller;
|
namespace OCA\Forms\Controller;
|
||||||
|
|
||||||
|
use Exception;
|
||||||
use OCA\Forms\Db\Answer;
|
use OCA\Forms\Db\Answer;
|
||||||
use OCA\Forms\Db\AnswerMapper;
|
use OCA\Forms\Db\AnswerMapper;
|
||||||
use OCA\Forms\Db\Form;
|
use OCA\Forms\Db\Form;
|
||||||
|
@ -127,6 +128,7 @@ class ApiController extends Controller {
|
||||||
'hash' => $form->getHash(),
|
'hash' => $form->getHash(),
|
||||||
'title' => $form->getTitle(),
|
'title' => $form->getTitle(),
|
||||||
'expires' => $form->getExpires(),
|
'expires' => $form->getExpires(),
|
||||||
|
'partial' => true
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -210,6 +212,21 @@ class ApiController extends Controller {
|
||||||
return new Http\JSONResponse([], Http::STATUS_FORBIDDEN);
|
return new Http\JSONResponse([], Http::STATUS_FORBIDDEN);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Make sure we only store id
|
||||||
|
try {
|
||||||
|
if ($keyValuePairs['access']) {
|
||||||
|
$keyValuePairs['access']['users'] = array_map(function (array $user): string {
|
||||||
|
return $user['id'];
|
||||||
|
}, $keyValuePairs['access']['users']);
|
||||||
|
$keyValuePairs['access']['groups'] = array_map(function (array $group): string {
|
||||||
|
return $group['id'];
|
||||||
|
}, $keyValuePairs['access']['groups']);
|
||||||
|
}
|
||||||
|
} catch (Exception $e) {
|
||||||
|
$this->logger->debug('Malformed access');
|
||||||
|
return new Http\JSONResponse(['message' => 'Malformed access'], Http::STATUS_BAD_REQUEST);
|
||||||
|
}
|
||||||
|
|
||||||
// Create FormEntity with given Params & Id.
|
// Create FormEntity with given Params & Id.
|
||||||
$form = Form::fromParams($keyValuePairs);
|
$form = Form::fromParams($keyValuePairs);
|
||||||
$form->setId($id);
|
$form->setId($id);
|
||||||
|
|
|
@ -1,95 +0,0 @@
|
||||||
<?php
|
|
||||||
/**
|
|
||||||
* @copyright Copyright (c) 2017 Vinzenz Rosenkranz <vinzenz.rosenkranz@gmail.com>
|
|
||||||
*
|
|
||||||
* @author affan98 <affan98@gmail.com>
|
|
||||||
* @author John Molakvoæ (skjnldsv) <skjnldsv@protonmail.com>
|
|
||||||
* @author Roeland Jago Douma <roeland@famdouma.nl>
|
|
||||||
*
|
|
||||||
* @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/>.
|
|
||||||
*
|
|
||||||
*/
|
|
||||||
|
|
||||||
namespace OCA\Forms\Controller;
|
|
||||||
|
|
||||||
use OCP\AppFramework\Controller;
|
|
||||||
use OCP\AppFramework\Http;
|
|
||||||
use OCP\AppFramework\Http\DataResponse;
|
|
||||||
|
|
||||||
use OCP\IGroupManager;
|
|
||||||
use OCP\IUserManager;
|
|
||||||
use OCP\IRequest;
|
|
||||||
|
|
||||||
class SystemController extends Controller {
|
|
||||||
public function __construct(
|
|
||||||
string $appName,
|
|
||||||
IGroupManager $groupManager,
|
|
||||||
IUserManager $userManager,
|
|
||||||
IRequest $request
|
|
||||||
) {
|
|
||||||
parent::__construct($appName, $request);
|
|
||||||
$this->groupManager = $groupManager;
|
|
||||||
$this->userManager = $userManager;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Get a list of NC users and groups
|
|
||||||
* @NoAdminRequired
|
|
||||||
* @return DataResponse
|
|
||||||
*/
|
|
||||||
public function getSiteUsersAndGroups($query = '', $getGroups = true, $getUsers = true, $skipGroups = [], $skipUsers = []) {
|
|
||||||
$list = [];
|
|
||||||
$data = [];
|
|
||||||
if ($getGroups) {
|
|
||||||
$groups = $this->groupManager->search($query);
|
|
||||||
foreach ($groups as $group) {
|
|
||||||
if (!in_array($group->getGID(), $skipGroups)) {
|
|
||||||
$list[] = [
|
|
||||||
'id' => $group->getGID(),
|
|
||||||
'user' => $group->getGID(),
|
|
||||||
'type' => 'group',
|
|
||||||
'desc' => 'group',
|
|
||||||
'icon' => 'icon-group',
|
|
||||||
'displayName' => $group->getGID(),
|
|
||||||
'avatarURL' => ''
|
|
||||||
];
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if ($getUsers) {
|
|
||||||
$users = $this->userManager->searchDisplayName($query);
|
|
||||||
foreach ($users as $user) {
|
|
||||||
if (!in_array($user->getUID(), $skipUsers)) {
|
|
||||||
$list[] = [
|
|
||||||
'id' => $user->getUID(),
|
|
||||||
'user' => $user->getUID(),
|
|
||||||
'type' => 'user',
|
|
||||||
'desc' => 'user',
|
|
||||||
'icon' => 'icon-user',
|
|
||||||
'displayName' => $user->getDisplayName(),
|
|
||||||
'avatarURL' => '',
|
|
||||||
'lastLogin' => $user->getLastLogin(),
|
|
||||||
'cloudId' => $user->getCloudId()
|
|
||||||
];
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
$data['siteusers'] = $list;
|
|
||||||
return new DataResponse($data, Http::STATUS_OK);
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -29,8 +29,12 @@ use OCA\Forms\Db\QuestionMapper;
|
||||||
use OCA\Forms\Db\SubmissionMapper;
|
use OCA\Forms\Db\SubmissionMapper;
|
||||||
use OCP\AppFramework\Db\DoesNotExistException;
|
use OCP\AppFramework\Db\DoesNotExistException;
|
||||||
use OCP\AppFramework\Db\IMapperException;
|
use OCP\AppFramework\Db\IMapperException;
|
||||||
|
use OCP\IGroup;
|
||||||
use OCP\IGroupManager;
|
use OCP\IGroupManager;
|
||||||
|
use OCP\IUser;
|
||||||
|
use OCP\IUserManager;
|
||||||
use OCP\IUserSession;
|
use OCP\IUserSession;
|
||||||
|
use OCP\Share\IShare;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Trait for getting forms information in a service
|
* Trait for getting forms information in a service
|
||||||
|
@ -51,6 +55,9 @@ class FormsService {
|
||||||
|
|
||||||
/** @var IGroupManager */
|
/** @var IGroupManager */
|
||||||
private $groupManager;
|
private $groupManager;
|
||||||
|
|
||||||
|
/** @var IUserManager */
|
||||||
|
private $userManager;
|
||||||
|
|
||||||
/** @var IUserSession */
|
/** @var IUserSession */
|
||||||
private $userSession;
|
private $userSession;
|
||||||
|
@ -60,12 +67,14 @@ class FormsService {
|
||||||
OptionMapper $optionMapper,
|
OptionMapper $optionMapper,
|
||||||
SubmissionMapper $submissionMapper,
|
SubmissionMapper $submissionMapper,
|
||||||
IGroupManager $groupManager,
|
IGroupManager $groupManager,
|
||||||
|
IUserManager $userManager,
|
||||||
IUserSession $userSession) {
|
IUserSession $userSession) {
|
||||||
$this->formMapper = $formMapper;
|
$this->formMapper = $formMapper;
|
||||||
$this->questionMapper = $questionMapper;
|
$this->questionMapper = $questionMapper;
|
||||||
$this->optionMapper = $optionMapper;
|
$this->optionMapper = $optionMapper;
|
||||||
$this->submissionMapper = $submissionMapper;
|
$this->submissionMapper = $submissionMapper;
|
||||||
$this->groupManager = $groupManager;
|
$this->groupManager = $groupManager;
|
||||||
|
$this->userManager = $userManager;
|
||||||
$this->userSession = $userSession;
|
$this->userSession = $userSession;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -112,6 +121,15 @@ class FormsService {
|
||||||
$result = $form->read();
|
$result = $form->read();
|
||||||
$result['questions'] = $this->getQuestions($id);
|
$result['questions'] = $this->getQuestions($id);
|
||||||
|
|
||||||
|
// Set proper user/groups properties
|
||||||
|
|
||||||
|
// Make sure we have the bare minimum
|
||||||
|
$result['access'] = array_merge(['users' => [], 'groups' => []], $result['access']);
|
||||||
|
|
||||||
|
// Properly format users & groups
|
||||||
|
$result['access']['users'] = array_map([$this, 'formatUsers'], $result['access']['users']);
|
||||||
|
$result['access']['groups'] = array_map([$this, 'formatGroups'], $result['access']['groups']);
|
||||||
|
|
||||||
return $result;
|
return $result;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -205,4 +223,40 @@ class FormsService {
|
||||||
// None of the possible access-options matched.
|
// None of the possible access-options matched.
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Format users access
|
||||||
|
*
|
||||||
|
* @param string $userId
|
||||||
|
* @return array
|
||||||
|
*/
|
||||||
|
private function formatUsers(string $userId): array {
|
||||||
|
$user = $this->userManager->get($userId);
|
||||||
|
if ($user instanceof IUser) {
|
||||||
|
return [
|
||||||
|
'id' => $userId,
|
||||||
|
'displayName' => $user->getDisplayName(),
|
||||||
|
'shareType' => IShare::TYPE_USER
|
||||||
|
];
|
||||||
|
}
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Format groups access
|
||||||
|
*
|
||||||
|
* @param string $groupId
|
||||||
|
* @return array
|
||||||
|
*/
|
||||||
|
private function formatGroups(string $groupId): array {
|
||||||
|
$group = $this->groupManager->get($groupId);
|
||||||
|
if ($group instanceof IGroup) {
|
||||||
|
return [
|
||||||
|
'id' => $groupId,
|
||||||
|
'displayName' => $group->getDisplayName(),
|
||||||
|
'shareType' => IShare::TYPE_GROUP
|
||||||
|
];
|
||||||
|
}
|
||||||
|
return [];
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -58,7 +58,9 @@
|
||||||
<!-- No errors show router content -->
|
<!-- No errors show router content -->
|
||||||
<template v-else>
|
<template v-else>
|
||||||
<router-view :form.sync="selectedForm" />
|
<router-view :form.sync="selectedForm" />
|
||||||
<router-view :form="selectedForm" name="sidebar" />
|
<router-view v-if="!selectedForm.partial"
|
||||||
|
:form="selectedForm"
|
||||||
|
name="sidebar" />
|
||||||
</template>
|
</template>
|
||||||
</Content>
|
</Content>
|
||||||
</template>
|
</template>
|
||||||
|
|
|
@ -20,79 +20,47 @@
|
||||||
-
|
-
|
||||||
-->
|
-->
|
||||||
|
|
||||||
/* global Vue, oc_userconfig */
|
|
||||||
<template>
|
<template>
|
||||||
<div :class="type" class="user-row">
|
<div class="user-row">
|
||||||
<div v-if="description" class="description">
|
<Avatar :user="id" :display-name="computedDisplayName" :is-no-user="isNoUser" />
|
||||||
{{ description }}
|
|
||||||
</div>
|
|
||||||
<Avatar :user="userId" :display-name="computedDisplayName" :is-no-user="isNoUser" />
|
|
||||||
<div v-if="!hideNames" class="user-name">
|
|
||||||
{{ computedDisplayName }}
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
import Avatar from '@nextcloud/vue/dist/Components/Avatar'
|
import Avatar from '@nextcloud/vue/dist/Components/Avatar'
|
||||||
import { getCurrentUser } from '@nextcloud/auth'
|
import ShareTypes from '../mixins/ShareTypes'
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
components: {
|
components: {
|
||||||
Avatar,
|
Avatar,
|
||||||
},
|
},
|
||||||
|
mixins: [ShareTypes],
|
||||||
|
|
||||||
props: {
|
props: {
|
||||||
hideNames: {
|
id: {
|
||||||
type: Boolean,
|
|
||||||
default: false,
|
|
||||||
},
|
|
||||||
userId: {
|
|
||||||
type: String,
|
type: String,
|
||||||
default: undefined,
|
required: true,
|
||||||
},
|
},
|
||||||
displayName: {
|
displayName: {
|
||||||
type: String,
|
type: String,
|
||||||
default: '',
|
required: true,
|
||||||
},
|
},
|
||||||
size: {
|
shareType: {
|
||||||
type: Number,
|
type: Number,
|
||||||
default: 32,
|
required: true,
|
||||||
},
|
|
||||||
type: {
|
|
||||||
type: String,
|
|
||||||
default: 'user',
|
|
||||||
},
|
|
||||||
description: {
|
|
||||||
type: String,
|
|
||||||
default: '',
|
|
||||||
},
|
},
|
||||||
|
|
||||||
},
|
},
|
||||||
|
|
||||||
data() {
|
|
||||||
return {
|
|
||||||
nothidden: false,
|
|
||||||
}
|
|
||||||
},
|
|
||||||
|
|
||||||
computed: {
|
computed: {
|
||||||
isNoUser() {
|
isNoUser() {
|
||||||
return this.type !== 'user'
|
return this.shareType !== this.SHARE_TYPES.SHARE_TYPE_USER
|
||||||
},
|
},
|
||||||
computedDisplayName() {
|
computedDisplayName() {
|
||||||
let value = this.displayName
|
if (this.shareType === this.SHARE_TYPES.SHARE_TYPE_GROUP) {
|
||||||
|
return `${this.displayName} (${t('forms', 'Group')})`
|
||||||
if (this.userId === getCurrentUser().uid) {
|
|
||||||
value = getCurrentUser().displayName
|
|
||||||
} else {
|
|
||||||
if (!this.displayName) {
|
|
||||||
value = this.userId
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
if (this.type === 'group') {
|
return this.displayName
|
||||||
value = value + ' (' + t('forms', 'Group') + ')'
|
|
||||||
}
|
|
||||||
return value
|
|
||||||
},
|
},
|
||||||
|
|
||||||
},
|
},
|
|
@ -23,38 +23,35 @@
|
||||||
<template>
|
<template>
|
||||||
<div class="sharing">
|
<div class="sharing">
|
||||||
<Multiselect id="ajax"
|
<Multiselect id="ajax"
|
||||||
v-model="shares"
|
|
||||||
:options="users"
|
|
||||||
:multiple="true"
|
|
||||||
:user-select="true"
|
|
||||||
:tag-width="80"
|
|
||||||
:clear-on-select="false"
|
:clear-on-select="false"
|
||||||
:preserve-search="true"
|
:hide-selected="true"
|
||||||
:options-limit="20"
|
|
||||||
:loading="isLoading"
|
|
||||||
:internal-search="false"
|
:internal-search="false"
|
||||||
:searchable="true"
|
:loading="loading"
|
||||||
|
:multiple="true"
|
||||||
|
:options="options"
|
||||||
|
:placeholder="t('forms', 'User or group name …')"
|
||||||
:preselect-first="true"
|
:preselect-first="true"
|
||||||
:placeholder="placeholder"
|
:preserve-search="true"
|
||||||
|
:searchable="true"
|
||||||
|
:user-select="true"
|
||||||
label="displayName"
|
label="displayName"
|
||||||
track-by="user"
|
track-by="id"
|
||||||
@search-change="loadUsersAsync"
|
@search-change="asyncFind"
|
||||||
@close="updateShares">
|
@select="addShare">
|
||||||
<template slot="selection" slot-scope="{ values, search, isOpen }">
|
<template #noOptions>
|
||||||
<span v-if="values.length && !isOpen" class="multiselect__single">
|
{{ t('forms', 'No recommendations. Start typing.') }}
|
||||||
{{ values.length }} users selected
|
</template>
|
||||||
</span>
|
<template #noResult>
|
||||||
|
{{ noResultText }}
|
||||||
</template>
|
</template>
|
||||||
</Multiselect>
|
</Multiselect>
|
||||||
|
|
||||||
<TransitionGroup :css="false" tag="ul" class="shared-list">
|
<TransitionGroup :css="false" tag="ul" class="shared-list">
|
||||||
<li v-for="(item, index) in sortedShares" :key="item.displayName" :data-index="index">
|
<!-- TODO: Iterate two times, will be cleaner, one for users, one for groups -->
|
||||||
<UserDiv :user-id="item.user"
|
<li v-for="(item, index) in sortedShares" :key="item.id + '-' + item.shareType" :data-index="index">
|
||||||
:display-name="item.displayName"
|
<UserDiv v-bind="item" />
|
||||||
:type="item.type"
|
|
||||||
:hide-names="hideNames" />
|
|
||||||
<div class="options">
|
<div class="options">
|
||||||
<a class="icon icon-delete svg delete-form" @click="removeShare(index, item)" />
|
<a class="icon icon-delete svg delete-form" @click="removeShare(item)" />
|
||||||
</div>
|
</div>
|
||||||
</li>
|
</li>
|
||||||
</TransitionGroup>
|
</TransitionGroup>
|
||||||
|
@ -62,12 +59,15 @@
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
import { generateUrl } from '@nextcloud/router'
|
import { generateOcsUrl } from '@nextcloud/router'
|
||||||
|
import { getCurrentUser } from '@nextcloud/auth'
|
||||||
import axios from '@nextcloud/axios'
|
import axios from '@nextcloud/axios'
|
||||||
|
import debounce from 'debounce'
|
||||||
import Multiselect from '@nextcloud/vue/dist/Components/Multiselect'
|
import Multiselect from '@nextcloud/vue/dist/Components/Multiselect'
|
||||||
|
import ShareTypes from '../mixins/ShareTypes'
|
||||||
|
|
||||||
// TODO: replace with same design as core sharing
|
// TODO: replace with same design as core sharing
|
||||||
import UserDiv from './_base-UserDiv'
|
import UserDiv from './UserDiv'
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
components: {
|
components: {
|
||||||
|
@ -75,70 +75,101 @@ export default {
|
||||||
UserDiv,
|
UserDiv,
|
||||||
},
|
},
|
||||||
|
|
||||||
|
mixins: [ShareTypes],
|
||||||
|
|
||||||
props: {
|
props: {
|
||||||
placeholder: {
|
groupShares: {
|
||||||
type: String,
|
|
||||||
default: '',
|
|
||||||
},
|
|
||||||
|
|
||||||
activeShares: {
|
|
||||||
type: Array,
|
type: Array,
|
||||||
default: function() {
|
default: () => ([]),
|
||||||
return []
|
|
||||||
},
|
|
||||||
},
|
},
|
||||||
|
userShares: {
|
||||||
hideNames: {
|
type: Array,
|
||||||
type: Boolean,
|
default: () => ([]),
|
||||||
default: false,
|
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|
||||||
data() {
|
data() {
|
||||||
return {
|
return {
|
||||||
shares: [],
|
query: '',
|
||||||
users: [],
|
loading: false,
|
||||||
isLoading: false,
|
|
||||||
siteUsersListOptions: {
|
// TODO: have a global mixin for this, shared with server?
|
||||||
getUsers: true,
|
minSearchStringLength: parseInt(OC.config['sharing.minSearchStringLength'], 10) || 0,
|
||||||
getGroups: true,
|
maxAutocompleteResults: parseInt(OC.config['sharing.maxAutocompleteResults'], 10) || 200,
|
||||||
query: '',
|
|
||||||
},
|
// Search data
|
||||||
|
recommendations: [],
|
||||||
|
suggestions: [],
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
computed: {
|
computed: {
|
||||||
sortedShares() {
|
sortedShares() {
|
||||||
return this.shares.slice(0).sort(this.sortByDisplayname)
|
return [...this.userShares, ...this.groupShares].slice()
|
||||||
|
.sort(this.sortByDisplayname)
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Is the search valid ?
|
||||||
|
* @returns {boolean}
|
||||||
|
*/
|
||||||
|
isValidQuery() {
|
||||||
|
return this.query && this.query.trim() !== '' && this.query.length > this.minSearchStringLength
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Multiseelct options. Recommendations by default,
|
||||||
|
* direct search when search query is valid.
|
||||||
|
* @returns {Array}
|
||||||
|
*/
|
||||||
|
options() {
|
||||||
|
if (this.isValidQuery) {
|
||||||
|
return this.suggestions
|
||||||
|
}
|
||||||
|
return this.recommendations
|
||||||
|
},
|
||||||
|
|
||||||
|
noResultText() {
|
||||||
|
if (this.loading) {
|
||||||
|
return t('forms', 'Searching …')
|
||||||
|
}
|
||||||
|
return t('forms', 'No elements found.')
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|
||||||
watch: {
|
mounted() {
|
||||||
activeShares(value) {
|
this.getRecommendations()
|
||||||
this.shares = value.slice(0)
|
|
||||||
},
|
|
||||||
},
|
},
|
||||||
|
|
||||||
methods: {
|
methods: {
|
||||||
removeShare(index, item) {
|
removeShare(item) {
|
||||||
this.$emit('remove-share', item)
|
// Filter out the removed item
|
||||||
|
const users = this.userShares.filter(user => !(user.id === item.id && !item.isGroup))
|
||||||
|
const groups = this.groupShares.filter(group => !(group.id === item.id && item.isGroup))
|
||||||
|
this.$emit('update:shares', { users, groups })
|
||||||
},
|
},
|
||||||
|
|
||||||
updateShares() {
|
/**
|
||||||
this.$emit('update-shares', this.shares)
|
* Add a new share and dispatch the change to the parent
|
||||||
},
|
* @param {Object} share the new share
|
||||||
|
*/
|
||||||
|
addShare(share) {
|
||||||
|
const users = this.userShares.slice()
|
||||||
|
const groups = this.groupShares.slice()
|
||||||
|
const newShare = {
|
||||||
|
id: share.shareWith,
|
||||||
|
displayName: share.displayName,
|
||||||
|
shareType: share.shareType,
|
||||||
|
}
|
||||||
|
|
||||||
loadUsersAsync(query) {
|
if (share.shareType === this.SHARE_TYPES.SHARE_TYPE_USER) {
|
||||||
this.isLoading = false
|
users.push(newShare)
|
||||||
this.siteUsersListOptions.query = query
|
} else if (share.shareType === this.SHARE_TYPES.SHARE_TYPE_GROUP) {
|
||||||
axios.post(generateUrl('apps/forms/get/siteusers'), this.siteUsersListOptions)
|
groups.push(newShare)
|
||||||
.then((response) => {
|
}
|
||||||
this.users = response.data.siteusers
|
|
||||||
this.isLoading = false
|
console.debug('Adding new share', share, users, groups)
|
||||||
}, (error) => {
|
this.$emit('update:shares', { users, groups })
|
||||||
/* eslint-disable-next-line no-console */
|
|
||||||
console.log(error.response)
|
|
||||||
})
|
|
||||||
},
|
},
|
||||||
|
|
||||||
sortByDisplayname(a, b) {
|
sortByDisplayname(a, b) {
|
||||||
|
@ -147,6 +178,196 @@ export default {
|
||||||
return 0
|
return 0
|
||||||
},
|
},
|
||||||
|
|
||||||
|
async asyncFind(query, id) {
|
||||||
|
// save current query to check if we display
|
||||||
|
// recommendations or search results
|
||||||
|
this.query = query.trim()
|
||||||
|
if (this.isValidQuery) {
|
||||||
|
// start loading now to have proper ux feedback
|
||||||
|
// during the debounce
|
||||||
|
this.loading = true
|
||||||
|
await this.debounceGetSuggestions(query)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get suggestions
|
||||||
|
*
|
||||||
|
* @param {string} search the search query
|
||||||
|
*/
|
||||||
|
async getSuggestions(search) {
|
||||||
|
this.loading = true
|
||||||
|
|
||||||
|
const shareType = [
|
||||||
|
this.SHARE_TYPES.SHARE_TYPE_USER,
|
||||||
|
this.SHARE_TYPES.SHARE_TYPE_GROUP,
|
||||||
|
]
|
||||||
|
|
||||||
|
const request = await axios.get(generateOcsUrl('apps/files_sharing/api/v1') + 'sharees', {
|
||||||
|
params: {
|
||||||
|
format: 'json',
|
||||||
|
itemType: 'file',
|
||||||
|
search,
|
||||||
|
perPage: this.maxAutocompleteResults,
|
||||||
|
shareType,
|
||||||
|
},
|
||||||
|
})
|
||||||
|
|
||||||
|
if (request.data.ocs.meta.statuscode !== 100) {
|
||||||
|
console.error('Error fetching suggestions', request)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
const data = request.data.ocs.data
|
||||||
|
const exact = request.data.ocs.data.exact
|
||||||
|
data.exact = [] // removing exact from general results
|
||||||
|
|
||||||
|
// flatten array of arrays
|
||||||
|
const rawExactSuggestions = Object.values(exact).reduce((arr, elem) => arr.concat(elem), [])
|
||||||
|
const rawSuggestions = Object.values(data).reduce((arr, elem) => arr.concat(elem), [])
|
||||||
|
|
||||||
|
// remove invalid data and format to user-select layout
|
||||||
|
const exactSuggestions = this.filterOutExistingShares(rawExactSuggestions)
|
||||||
|
.map(share => this.formatForMultiselect(share))
|
||||||
|
// sort by type so we can get user&groups first...
|
||||||
|
.sort((a, b) => a.shareType - b.shareType)
|
||||||
|
const suggestions = this.filterOutExistingShares(rawSuggestions)
|
||||||
|
.map(share => this.formatForMultiselect(share))
|
||||||
|
// sort by type so we can get user&groups first...
|
||||||
|
.sort((a, b) => a.shareType - b.shareType)
|
||||||
|
|
||||||
|
this.suggestions = exactSuggestions.concat(suggestions)
|
||||||
|
|
||||||
|
this.loading = false
|
||||||
|
console.info('suggestions', this.suggestions)
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Debounce getSuggestions
|
||||||
|
*
|
||||||
|
* @param {...*} args the arguments
|
||||||
|
*/
|
||||||
|
debounceGetSuggestions: debounce(function(...args) {
|
||||||
|
this.getSuggestions(...args)
|
||||||
|
}, 300),
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the sharing recommendations
|
||||||
|
*/
|
||||||
|
async getRecommendations() {
|
||||||
|
this.loading = true
|
||||||
|
|
||||||
|
const request = await axios.get(generateOcsUrl('apps/files_sharing/api/v1') + 'sharees_recommended', {
|
||||||
|
params: {
|
||||||
|
format: 'json',
|
||||||
|
itemType: 'file',
|
||||||
|
},
|
||||||
|
})
|
||||||
|
|
||||||
|
if (request.data.ocs.meta.statuscode !== 100) {
|
||||||
|
console.error('Error fetching recommendations', request)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
const exact = request.data.ocs.data.exact
|
||||||
|
|
||||||
|
// flatten array of arrays
|
||||||
|
const rawRecommendations = Object.values(exact).reduce((arr, elem) => arr.concat(elem), [])
|
||||||
|
|
||||||
|
// remove invalid data and format to user-select layout
|
||||||
|
this.recommendations = this.filterOutExistingShares(rawRecommendations)
|
||||||
|
.map(share => this.formatForMultiselect(share))
|
||||||
|
|
||||||
|
this.loading = false
|
||||||
|
console.info('recommendations', this.recommendations)
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Filter out existing shares from
|
||||||
|
* the provided shares search results
|
||||||
|
*
|
||||||
|
* @param {Object[]} shares the array of shares object
|
||||||
|
* @returns {Object[]}
|
||||||
|
*/
|
||||||
|
filterOutExistingShares(shares) {
|
||||||
|
return shares.reduce((arr, share) => {
|
||||||
|
// only check proper objects
|
||||||
|
if (typeof share !== 'object') {
|
||||||
|
return arr
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
// filter out current user
|
||||||
|
if (share.value.shareType === this.SHARE_TYPES.SHARE_TYPE_USER
|
||||||
|
&& share.value.shareWith === getCurrentUser().uid) {
|
||||||
|
return arr
|
||||||
|
}
|
||||||
|
|
||||||
|
// Filter out existing shares
|
||||||
|
if (share.value.shareType === this.SHARE_TYPES.SHARE_TYPE_USER) {
|
||||||
|
if (this.userShares.find(user => user.id === share.value.shareWith)) {
|
||||||
|
return arr
|
||||||
|
}
|
||||||
|
} else if (share.value.shareType === this.SHARE_TYPES.SHARE_TYPE_GROUP) {
|
||||||
|
if (this.groupShares.find(group => group.id === share.value.shareWith)) {
|
||||||
|
return arr
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// ALL GOOD
|
||||||
|
// let's add the suggestion
|
||||||
|
arr.push(share)
|
||||||
|
} catch {
|
||||||
|
return arr
|
||||||
|
}
|
||||||
|
return arr
|
||||||
|
}, [])
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Format shares for the multiselect options
|
||||||
|
* @param {Object} result select entry item
|
||||||
|
* @returns {Object}
|
||||||
|
*/
|
||||||
|
formatForMultiselect(result) {
|
||||||
|
return {
|
||||||
|
shareWith: result.value.shareWith,
|
||||||
|
shareType: result.value.shareType,
|
||||||
|
user: result.uuid || result.value.shareWith,
|
||||||
|
isNoUser: result.value.shareType !== this.SHARE_TYPES.SHARE_TYPE_USER,
|
||||||
|
displayName: result.name || result.label,
|
||||||
|
icon: this.shareTypeToIcon(result.value.shareType),
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the icon based on the share type
|
||||||
|
* @param {number} type the share type
|
||||||
|
* @returns {string} the icon class
|
||||||
|
*/
|
||||||
|
shareTypeToIcon(type) {
|
||||||
|
switch (type) {
|
||||||
|
case this.SHARE_TYPES.SHARE_TYPE_GUEST:
|
||||||
|
// default is a user, other icons are here to differenciate
|
||||||
|
// themselves from it, so let's not display the user icon
|
||||||
|
// case this.SHARE_TYPES.SHARE_TYPE_REMOTE:
|
||||||
|
// case this.SHARE_TYPES.SHARE_TYPE_USER:
|
||||||
|
return 'icon-user'
|
||||||
|
case this.SHARE_TYPES.SHARE_TYPE_REMOTE_GROUP:
|
||||||
|
case this.SHARE_TYPES.SHARE_TYPE_GROUP:
|
||||||
|
return 'icon-group'
|
||||||
|
case this.SHARE_TYPES.SHARE_TYPE_EMAIL:
|
||||||
|
return 'icon-mail'
|
||||||
|
case this.SHARE_TYPES.SHARE_TYPE_CIRCLE:
|
||||||
|
return 'icon-circle'
|
||||||
|
case this.SHARE_TYPES.SHARE_TYPE_ROOM:
|
||||||
|
return 'icon-room'
|
||||||
|
|
||||||
|
default:
|
||||||
|
return ''
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
39
src/mixins/ShareTypes.js
Normal file
39
src/mixins/ShareTypes.js
Normal file
|
@ -0,0 +1,39 @@
|
||||||
|
/**
|
||||||
|
* @copyright Copyright (c) 2019 John Molakvoæ <skjnldsv@protonmail.com>
|
||||||
|
*
|
||||||
|
* @author John Molakvoæ <skjnldsv@protonmail.com>
|
||||||
|
*
|
||||||
|
* @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/>.
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
|
||||||
|
export default {
|
||||||
|
data() {
|
||||||
|
return {
|
||||||
|
SHARE_TYPES: {
|
||||||
|
SHARE_TYPE_USER: OC.Share.SHARE_TYPE_USER,
|
||||||
|
SHARE_TYPE_GROUP: OC.Share.SHARE_TYPE_GROUP,
|
||||||
|
SHARE_TYPE_LINK: OC.Share.SHARE_TYPE_LINK,
|
||||||
|
SHARE_TYPE_EMAIL: OC.Share.SHARE_TYPE_EMAIL,
|
||||||
|
SHARE_TYPE_REMOTE: OC.Share.SHARE_TYPE_REMOTE,
|
||||||
|
SHARE_TYPE_CIRCLE: OC.Share.SHARE_TYPE_CIRCLE,
|
||||||
|
SHARE_TYPE_GUEST: OC.Share.SHARE_TYPE_GUEST,
|
||||||
|
SHARE_TYPE_REMOTE_GROUP: OC.Share.SHARE_TYPE_REMOTE_GROUP,
|
||||||
|
SHARE_TYPE_ROOM: OC.Share.SHARE_TYPE_ROOM,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
},
|
||||||
|
}
|
|
@ -21,7 +21,7 @@
|
||||||
-->
|
-->
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<AppSidebar v-if="form"
|
<AppSidebar
|
||||||
v-show="opened"
|
v-show="opened"
|
||||||
:title="form.title"
|
:title="form.title"
|
||||||
@close="onClose">
|
@close="onClose">
|
||||||
|
@ -44,7 +44,7 @@
|
||||||
:disabled="isPublic || form.isAnonymous"
|
:disabled="isPublic || form.isAnonymous"
|
||||||
type="checkbox"
|
type="checkbox"
|
||||||
class="checkbox"
|
class="checkbox"
|
||||||
@change="onSubmOnceChange">
|
@change="onSubmitOnceChange">
|
||||||
<label for="submitOnce">
|
<label for="submitOnce">
|
||||||
{{ t('forms', 'Only allow one response per user') }}
|
{{ t('forms', 'Only allow one response per user') }}
|
||||||
</label>
|
</label>
|
||||||
|
@ -113,11 +113,9 @@
|
||||||
</span>
|
</span>
|
||||||
</label>
|
</label>
|
||||||
<ShareDiv v-show="form.access.type === 'selected'"
|
<ShareDiv v-show="form.access.type === 'selected'"
|
||||||
:active-shares="form.shares"
|
:user-shares="userShares"
|
||||||
:placeholder="t('forms', 'Name of user or group')"
|
:group-shares="groupShares"
|
||||||
:hide-names="true"
|
@update:shares="onSharingChange" />
|
||||||
@update-shares="updateShares"
|
|
||||||
@remove-share="removeShare" />
|
|
||||||
</li>
|
</li>
|
||||||
</ul>
|
</ul>
|
||||||
</AppSidebar>
|
</AppSidebar>
|
||||||
|
@ -182,10 +180,16 @@ export default {
|
||||||
expirationDate() {
|
expirationDate() {
|
||||||
return moment(this.form.expires, 'X').toDate()
|
return moment(this.form.expires, 'X').toDate()
|
||||||
},
|
},
|
||||||
|
|
||||||
isExpired() {
|
isExpired() {
|
||||||
return this.form.expires && moment().unix() > this.form.expires
|
return this.form.expires && moment().unix() > this.form.expires
|
||||||
},
|
},
|
||||||
|
|
||||||
|
userShares() {
|
||||||
|
return [...this.form?.access?.users || []]
|
||||||
|
},
|
||||||
|
groupShares() {
|
||||||
|
return [...this.form?.access?.groups || []]
|
||||||
|
},
|
||||||
},
|
},
|
||||||
|
|
||||||
async created() {
|
async created() {
|
||||||
|
@ -224,18 +228,6 @@ export default {
|
||||||
},
|
},
|
||||||
|
|
||||||
methods: {
|
methods: {
|
||||||
addShare(item) {
|
|
||||||
this.form.shares.push(item)
|
|
||||||
},
|
|
||||||
|
|
||||||
updateShares(share) {
|
|
||||||
this.form.shares = share.slice(0)
|
|
||||||
},
|
|
||||||
|
|
||||||
removeShare(item) {
|
|
||||||
this.form.shares.splice(this.form.shares.indexOf(item), 1)
|
|
||||||
},
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Sidebar state methods
|
* Sidebar state methods
|
||||||
*/
|
*/
|
||||||
|
@ -252,12 +244,17 @@ export default {
|
||||||
onAnonChange() {
|
onAnonChange() {
|
||||||
this.saveFormProperty('isAnonymous')
|
this.saveFormProperty('isAnonymous')
|
||||||
},
|
},
|
||||||
onSubmOnceChange() {
|
onSubmitOnceChange() {
|
||||||
this.saveFormProperty('submitOnce')
|
this.saveFormProperty('submitOnce')
|
||||||
},
|
},
|
||||||
onAccessChange() {
|
onAccessChange() {
|
||||||
this.saveFormProperty('access')
|
this.saveFormProperty('access')
|
||||||
},
|
},
|
||||||
|
onSharingChange({ groups, users }) {
|
||||||
|
this.$set(this.form.access, 'groups', groups)
|
||||||
|
this.$set(this.form.access, 'users', users)
|
||||||
|
this.onAccessChange()
|
||||||
|
},
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* On date picker change
|
* On date picker change
|
||||||
|
|
Loading…
Reference in a new issue