This commit is contained in:
dartcafe 2020-10-11 19:44:20 +02:00
Родитель a439a12227
Коммит e8cb56e3f5
Не найден ключ, соответствующий данной подписи
Идентификатор ключа GPG: CCE73CEF3035D3C8
16 изменённых файлов: 338 добавлений и 144 удалений

Просмотреть файл

@ -54,7 +54,8 @@ return [
['name' => 'vote#setByToken', 'url' => '/vote/set/s', 'verb' => 'POST'],
['name' => 'vote#delete', 'url' => '/votes/delete', 'verb' => 'POST'],
['name' => 'share#add', 'url' => '/share/add', 'verb' => 'POST'],
['name' => 'share#list', 'url' => '/poll/{pollId}/shares', 'verb' => 'GET'],
['name' => 'share#add', 'url' => '/poll/{pollId}/share', 'verb' => 'POST'],
['name' => 'share#get', 'url' => '/share/{token}', 'verb' => 'GET'],
['name' => 'share#personal', 'url' => '/share/personal', 'verb' => 'POST'],
['name' => 'share#delete', 'url' => '/share/delete/{token}', 'verb' => 'DELETE'],

Просмотреть файл

@ -154,7 +154,7 @@ class PollController extends Controller {
$shares = [];
} else {
$share = null;
$shares = $this->shareService->list($pollId, $token);
$shares = $this->shareService->list($pollId);
}
} catch (Exception $e) {
$share = null;

Просмотреть файл

@ -28,6 +28,7 @@ use OCP\AppFramework\Db\DoesNotExistException;
use OCA\Polls\Exceptions\NotAuthorizedException;
use OCA\Polls\Exceptions\InvalidUsernameException;
use OCA\Polls\Exceptions\InvalidShareType;
use OCA\Polls\Exceptions\ShareAlreadyExists;
use OCP\IRequest;
@ -75,9 +76,24 @@ class ShareController extends Controller {
}
/**
* Add share
* List shares
* @NoAdminRequired
* @param int $pollId
* @return DataResponse
*/
public function list($pollId) {
try {
return new DataResponse(['shares' => $this->shareService->list($pollId)], Http::STATUS_OK);
} catch (NotAuthorizedException $e) {
return new DataResponse(['error' => $e->getMessage()], $e->getStatus());
} catch (\Exception $e) {
return new DataResponse($e, Http::STATUS_CONFLICT);
}
}
/**
* Add share
* @NoAdminRequired
* @param int $pollId
* @param string $type
* @param string $userId
@ -89,8 +105,8 @@ class ShareController extends Controller {
return new DataResponse(['share' => $this->shareService->add($pollId, $type, $userId, $emailAddress)], Http::STATUS_CREATED);
} catch (NotAuthorizedException $e) {
return new DataResponse(['error' => $e->getMessage()], $e->getStatus());
} catch (\Exception $e) {
return new DataResponse($e, Http::STATUS_CONFLICT);
} catch (ShareAlreadyExists $e) {
return new DataResponse(['error' => $e->getMessage()], $e->getStatus());
}
}
@ -201,14 +217,24 @@ class ShareController extends Controller {
$share = $this->shareService->get($token);
if ($share->getType() === Share::TYPE_CIRCLE) {
foreach ((new Circle($share->getUserId()))->getMembers() as $member) {
$shares[] = $this->shareService->add($share->getPollId(), $member->getType(), $member->getId());
try {
$newShare = $this->shareService->add($share->getPollId(), $member->getType(), $member->getId());
$shares[] = $newShare;
} catch (ShareAlreadyExists $e) {
continue;
}
}
} elseif ($share->getType() === Share::TYPE_CONTACTGROUP) {
foreach ((new ContactGroup($share->getUserId()))->getMembers() as $contact) {
$shares[] = $this->shareService->add($share->getPollId(), Share::TYPE_CONTACT, $contact->getId(), $contact->getEmailAddress());
try {
$newShare = $this->shareService->add($share->getPollId(), Share::TYPE_CONTACT, $contact->getId(), $contact->getEmailAddress());
$shares[] = $newShare;
} catch (ShareAlreadyExists $e) {
continue;
}
}
}
$this->shareService->delete($token);
return new DataResponse(['shares' => $shares], Http::STATUS_OK);
} catch (Exception $e) {
return new DataResponse(['error' => $e], Http::STATUS_CONFLICT);

Просмотреть файл

@ -27,6 +27,7 @@ use JsonSerializable;
use OCP\IUser;
use OCP\AppFramework\Db\Entity;
use OCA\Polls\Model\UserGroupClass;
/**
* @method string getId()
@ -87,7 +88,8 @@ class Share extends Entity implements JsonSerializable {
'userEmail' => $this->userEmail,
'invitationSent' => intval($this->invitationSent),
'displayName' => $this->displayName,
'externalUser' => $this->externalUser()
'externalUser' => $this->externalUser(),
'shareeDetail' => UserGroupClass::getUserGroupChild($this->type, $this->userId)
];
}

Просмотреть файл

@ -0,0 +1,39 @@
<?php
/**
* @copyright Copyright (c) 2020 René Gieling <github@dartcafe.de>
*
* @author René Gieling <github@dartcafe.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/>.
*
*/
namespace OCA\Polls\Exceptions;
use OCP\AppFramework\Http;
class ShareAlreadyExists extends \Exception {
/**
* TooShortException Constructor
* @param string $e exception message
*/
public function __construct($e = 'Share already exists') {
parent::__construct($e);
}
public function getStatus() {
return Http::STATUS_OK;
}
}

Просмотреть файл

@ -31,42 +31,76 @@ class Contact extends UserGroupClass {
public const ICON = 'icon-mail';
/** @var Array */
private $contact;
private $contact =[];
/**
* User constructor.
* Contact constructor.
* @param $id
* @param $displayName
*/
public function __construct(
$id,
$displayName = ''
$id
) {
parent::__construct($id, self::TYPE, $displayName);
parent::__construct($id, self::TYPE);
$this->icon = self::ICON;
$this->getContact();
}
/**
* isEnabled
* @NoAdminRequired
* @return Boolean
*/
public static function isEnabled() {
return \OC::$server->getAppManager()->isEnabledForUser('contacts');
}
/**
* resolveContact
* We just need the contact's UID, so make sure, the any prefix is removed
* @NoAdminRequired
*/
private function resolveContactId() {
$parts = explode(":", $this->id);
$this->id = end($parts);
}
/**
* loadContact
* @NoAdminRequired
* The contacts app just provides a search, so we have to load the contact
* after searching via the contact's id and use the first contact.
*
* Currently only the contact's first email address is supported
*
* From Version 1.5 on:
* For compatibility reasons, we have to search for the contacts name too.
* Before this implementation contacts where stored with their FN property.
*
* TODO: Remove FN as search range for loading a contact in a polls version
* later than 1.6.
*/
private function loadContact() {
$contacts = self::listRaw($this->id, ['UID', 'FN']);
if (count($contacts) > 1) {
throw new MultipleContactsFound('Multiple contacts found for id ' . $this->id);
}
$this->contact = $contacts[0];
}
private function getContact() {
if (\OC::$server->getAppManager()->isEnabledForUser('contacts')) {
$this->contact = [];
$parts = explode(":", $this->id);
$this->id = end($parts);
// Search for UID and FN
// Before this implementation contacts where stored with their FN property
// From now on, the contact's UID is used as identifier
// TODO: Remove FN as search range for loading a contact in a polls version later than 1.6
$contacts = \OC::$server->getContactsManager()->search($this->id, ['UID', 'FN']);
if (count($contacts) === 1) {
$this->contact = $contacts[0];
$this->id = $this->contact['UID'];
$this->displayName = isset($this->contact['FN']) ? $this->contact['FN'] : $this->displayName;
$this->emailAddress = isset($this->contact['EMAIL'][0]) ? $this->contact['EMAIL'][0] : $this->emailAddress;
} elseif (count($contacts) > 1) {
throw new MultipleContactsFound('Multiple contacts found for id ' . $this->id);
}
$this->resolveContactId();
$this->loadContact();
$this->id = $this->contact['UID'];
$this->displayName = isset($this->contact['FN']) ? $this->contact['FN'] : $this->displayName;
$this->emailAddress = isset($this->contact['EMAIL'][0]) ? $this->contact['EMAIL'][0] : $this->emailAddress;
$this->organisation = isset($this->contact['ORG']) ? $this->contact['ORG'] : '';
$this->categories = isset($this->contact['CATEGORIES']) ? explode(',', $this->contact['CATEGORIES']) : [];
if (isset($this->contact['CATEGORIES'])) {
$this->categories = explode(',', $this->contact['CATEGORIES']);
@ -88,20 +122,14 @@ class Contact extends UserGroupClass {
} else {
throw new ContactsNotEnabled();
}
}
/**
* isEnabled
* @NoAdminRequired
* @return Boolean
*/
public static function isEnabled() {
return \OC::$server->getAppManager()->isEnabledForUser('contacts');
}
/**
* listRaw
* @NoAdminRequired
* List all contacts with email adresses
* excluding contacts from localSystemBook
* @param string $query
* @return Array
*/

Просмотреть файл

@ -31,17 +31,27 @@ class ContactGroup extends UserGroupClass {
/**
* Group constructor.
* @param $id
* @param $displayName
*/
public function __construct(
$id,
$displayName = ''
$id
) {
parent::__construct($id, self::TYPE, $id);
parent::__construct($id, self::TYPE);
$this->icon = self::ICON;
$this->description = \OC::$server->getL10N('polls')->t('Contact group');
}
/**
* getDisplayName
* @NoAdminRequired
* @return String
*/
public function getDisplayName() {
if (!$this->displayName) {
return $this->id;
}
return $this->displayName;
}
/**
* listRaw
* @NoAdminRequired
@ -99,22 +109,4 @@ class ContactGroup extends UserGroupClass {
return [];
}
/**
* @return array
*/
public function jsonSerialize(): array {
return [
'id' => $this->id,
'user' => $this->id,
'type' => $this->getType(),
'displayName' => $this->getDisplayName(),
'organisation' => $this->getOrganisation(),
'emailAddress' => $this->getEmailAddress(),
'desc' => $this->getDescription(),
'icon' => $this->getIcon(),
'isNoUser' => true,
'isGuest' => true,
];
}
}

Просмотреть файл

@ -34,12 +34,25 @@ class Email extends UserGroupClass {
* @param $displayName
*/
public function __construct(
$id,
$displayName = ''
$id
) {
parent::__construct($id, self::TYPE, $displayName);
parent::__construct($id, self::TYPE);
$this->description = \OC::$server->getL10N('polls')->t('External Email');
$this->icon = self::ICON;
$this->emailAddress = $id;
}
/**
* getDisplayName
* @NoAdminRequired
* @return String
*/
public function getDisplayName() {
if (!$this->displayName) {
return $this->id;
}
return $this->displayName;
}
}

55
lib/Model/GenericUser.php Normal file
Просмотреть файл

@ -0,0 +1,55 @@
<?php
/**
* @copyright Copyright (c) 2017 Vinzenz Rosenkranz <vinzenz.rosenkranz@gmail.com>
*
* @author René Gieling <github@dartcafe.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/>.
*
*/
namespace OCA\Polls\Model;
class GenericUser extends UserGroupClass {
public const TYPE = 'external';
public const ICON_DEFAULT = 'icon-share';
public const ICON_PUBLIC = 'icon-public';
/**
* Email constructor.
* @param $id
* @param $displayName
*/
public function __construct(
$id,
$type = self::TYPE,
$displayName = '',
$emailAddress = ''
) {
parent::__construct($id, $type);
$this->displayName = $displayName;
$this->emailAddress = $emailAddress;
if ($type === UserGroupClass::TYPE_PUBLIC) {
$this->icon = self::ICON_PUBLIC;
$this->description = \OC::$server->getL10N('polls')->t('Public link');
} else {
$this->description = \OC::$server->getL10N('polls')->t('External user');
$this->icon = self::ICON_DEFAULT;
}
}
}

Просмотреть файл

@ -123,10 +123,7 @@ class UserGroupClass implements \JsonSerializable {
* @return String
*/
public function getDisplayName() {
if ($this->displayName) {
return $this->displayName;
}
return $this->id;
return $this->displayName;
}
/**
@ -258,6 +255,37 @@ class UserGroupClass implements \JsonSerializable {
return [];
}
public static function getUserGroupChild($type, $id, $displayName = '', $emailAddress = '') {
switch ($type) {
case Group::TYPE:
return new Group($id);
break;
case Circle::TYPE:
return new Circle($id);
break;
case Contact::TYPE:
return new Contact($id);
break;
case ContactGroup::TYPE:
return new ContactGroup($id);
break;
case User::TYPE:
return new User($id);
break;
case Email::TYPE:
return new Email($id);
break;
case self::TYPE_PUBLIC:
return new GenericUser($id,self::TYPE_PUBLIC);
break;
case self::TYPE_EXTERNAL:
return new GenericUser($id, self::TYPE_EXTERNAL, $displayName, $emailAddress);
break;
default:
throw new InvalidShareType('Invalid share type (' . $type . ')');
}
}
/**
* @return array
*/
@ -265,6 +293,7 @@ class UserGroupClass implements \JsonSerializable {
return [
'id' => $this->getId(),
'user' => $this->getId(),
'userId' => $this->getId(),
'type' => $this->getType(),
'displayName' => $this->getDisplayName(),
'organisation' => $this->getOrganisation(),

Просмотреть файл

@ -40,6 +40,7 @@ use OCA\Polls\Db\PollMapper;
use OCA\Polls\Db\ShareMapper;
use OCA\Polls\Db\Share;
use OCA\Polls\Db\LogMapper;
use OCA\Polls\Db\Log;
use OCA\Polls\Model\Contact;
use OCA\Polls\Model\Email;
use OCA\Polls\Model\Group;

Просмотреть файл

@ -23,8 +23,11 @@
namespace OCA\Polls\Service;
use OCP\AppFramework\Db\DoesNotExistException;
use OCP\AppFramework\Db\MultipleObjectsReturnedException;
use OCA\Polls\Exceptions\NotAuthorizedException;
use OCA\Polls\Exceptions\InvalidShareType;
use OCA\Polls\Exceptions\ShareAlreadyExists;
use OCP\Security\ISecureRandom;
@ -32,12 +35,6 @@ use OCA\Polls\Db\ShareMapper;
use OCA\Polls\Db\Share;
use OCA\Polls\Model\Acl;
use OCA\Polls\Model\UserGroupClass;
use OCA\Polls\Model\Circle;
use OCA\Polls\Model\Contact;
use OCA\Polls\Model\ContactGroup;
use OCA\Polls\Model\Email;
use OCA\Polls\Model\Group;
use OCA\Polls\Model\User;
class ShareService {
@ -85,16 +82,12 @@ class ShareService {
* @return array array of Share
* @throws NotAuthorizedException
*/
public function list($pollId, $token) {
if ($token) {
return [$this->get($token)];
}
public function list($pollId) {
if (!$this->acl->set($pollId)->getAllowEdit()) {
throw new NotAuthorizedException;
}
return $this->shareMapper->findByPoll($pollId);
$shares = $this->shareMapper->findByPoll($pollId);
return $shares;
}
/**
@ -122,6 +115,18 @@ class ShareService {
throw new NotAuthorizedException;
}
if ($type !== UserGroupClass::TYPE_PUBLIC) {
try {
$this->shareMapper->findByPollAndUser($pollId, $userId);
throw new ShareAlreadyExists;
} catch (MultipleObjectsReturnedException $e) {
throw new ShareAlreadyExists;
} catch (DoesNotExistException $e) {
// continue
}
}
$this->share = new Share();
$this->share->setPollId($pollId);
$this->share->setInvitationSent(0);
@ -133,37 +138,11 @@ class ShareService {
));
if ($type === UserGroupClass::TYPE_PUBLIC) {
$this->share->setType(UserGroupClass::TYPE_PUBLIC);
} else {
switch ($type) {
case Group::TYPE:
$share = new Group($userId);
break;
case Circle::TYPE:
$share = new Circle($userId);
break;
case Contact::TYPE:
$share = new Contact($userId);
break;
case ContactGroup::TYPE:
$share = new ContactGroup($userId);
break;
case User::TYPE:
$share = new User($userId);
break;
case Email::TYPE:
$share = new Email($userId, $emailAddress);
break;
default:
throw new InvalidShareType('Invalid share type (' . $type . ')');
}
$this->share->setType($share->getType());
$this->share->setUserId($share->getId());
$this->share->setDisplayName($share->getDisplayName());
$this->share->setUserEmail($share->getEmailAddress());
}
$userGroup = UserGroupClass::getUserGroupChild($type, $userId);
$this->share->setType($userGroup->getType());
$this->share->setUserId($userGroup->getId());
$this->share->setDisplayName($userGroup->getDisplayName());
$this->share->setUserEmail($userGroup->getEmailAddress());
return $this->shareMapper->insert($this->share);
}
@ -219,6 +198,7 @@ class ShareService {
$this->share->setType(Share::TYPE_EXTERNAL);
$this->share->setPollId($pollId);
$this->share->setUserId($userName);
$this->share->setDisplayName($userName);
$this->share->setUserEmail($emailAddress);
$this->share->setInvitationSent(time());
$this->shareMapper->insert($this->share);

Просмотреть файл

@ -26,9 +26,10 @@ namespace OCA\Polls\Service;
use OCP\AppFramework\Db\DoesNotExistException;
use OCA\Polls\Exceptions\NotAuthorizedException;
use OCA\Polls\Db\Log;
use OCA\Polls\Db\OptionMapper;
use OCA\Polls\Db\VoteMapper;
use OCA\Polls\Db\Vote;
use OCA\Polls\Db\OptionMapper;
use OCA\Polls\Model\Acl;
class VoteService {

Просмотреть файл

@ -27,13 +27,13 @@
:menu-position="menuPosition"
:show-user-status="showUserStatus"
:user="userId"
:display-name="displayName"
:display-name="name"
:is-no-user="isNoUser" />
<div v-if="icon" :class="['type-icon', iconClass]" />
<div v-if="!hideNames" class="user-item__name">
{{ displayName }}
{{ name }}
</div>
<slot />
</div>
@ -103,6 +103,14 @@ export default {
return this.type !== 'user' || this.externalUser
},
name() {
if (this.displayName) {
return this.displayName
} else {
return this.userId
}
},
showUserStatus() {
return Boolean(getCurrentUser())
},

Просмотреть файл

@ -180,9 +180,6 @@ export default {
methods: {
resolveGroup(share) {
this.$store.dispatch('poll/shares/resolveGroup', { share: share })
.then((response) => {
this.$store.dispatch('poll/shares/delete', { share: share })
})
},
sendInvitation(share) {
@ -233,6 +230,7 @@ export default {
addShare(payload) {
this.$store
.dispatch('poll/shares/add', {
share: payload,
type: payload.type,
id: payload.id,
userEmail: payload.emailAddress,

Просмотреть файл

@ -87,22 +87,33 @@ const getters = {
}
const actions = {
add(context, payload) {
const endPoint = 'apps/polls/share/add'
return axios.post(generateUrl(endPoint), {
pollId: context.rootState.poll.id,
type: payload.type,
userId: payload.id,
emailAddress: payload.userEmail,
})
list(context) {
const endPoint = 'apps/polls/poll/'.concat(context.rootState.poll.id, '/shares')
return axios.get(generateUrl(endPoint))
.then((response) => {
context.commit('set', response.data)
return response.data
})
.catch((error) => {
console.error('Error loading shares', { error: error.response }, { pollId: context.rootState.poll.id })
throw error
})
},
add(context, payload) {
const endPoint = 'apps/polls/poll/'.concat(context.rootState.poll.id, '/share')
return axios.post(generateUrl(endPoint), payload.share)
.then((response) => {
context.commit('add', response.data.share)
return response.data
})
.catch((error) => {
console.error('Error writing share', { error: error.response }, { payload: payload })
throw error
})
.finally(() => {
context.dispatch('list')
})
},
addPersonal(context, payload) {
@ -120,43 +131,53 @@ const actions = {
},
delete(context, payload) {
const endPoint = 'apps/polls/share/delete'
return axios.delete(generateUrl(endPoint.concat('/', payload.share.token)))
.then(() => {
context.commit('delete', { share: payload.share })
const endPoint = 'apps/polls/share/delete/'.concat(payload.share.token)
context.commit('delete', { share: payload.share })
return axios.delete(generateUrl(endPoint))
.then((response) => {
return response
})
.catch((error) => {
console.error('Error removing share', { error: error.response }, { payload: payload })
throw error
})
.finally(() => {
context.dispatch('list')
})
},
sendInvitation(context, payload) {
const endPoint = 'apps/polls/share/send'
return axios.post(generateUrl(endPoint.concat('/', payload.share.token)))
const endPoint = 'apps/polls/share/send/'.concat(payload.share.token)
return axios.post(generateUrl(endPoint))
.then((response) => {
context.commit('update', { share: response.data.share })
return response
})
.catch((error) => {
console.error('Error sending invitation', { error: error.response }, { payload: payload })
throw error
})
.finally(() => {
context.dispatch('list')
})
},
resolveGroup(context, payload) {
const endPoint = 'apps/polls/share/resolveGroup'
return axios.get(generateUrl(endPoint.concat('/', payload.share.token)))
.then((response) => {
response.data.shares.forEach((item) => {
context.commit('add', item)
})
return response
})
const endPoint = 'apps/polls/share/resolveGroup/'.concat(payload.share.token)
return axios.get(generateUrl(endPoint))
// .then((response) => {
// context.commit('delete', { share: payload.share })
// return response
// })
.catch((error) => {
console.error('Error exploding group', { error: error.response }, { payload: payload })
throw error
})
.finally(() => {
context.dispatch('list')
})
},
}