Welcome to mirror list, hosted at ThFree Co, Russian Federation.

github.com/nextcloud/polls.git - Unnamed repository; edit this file 'description' to name the repository.
summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorRené Gieling <github@dartcafe.de>2020-01-28 21:26:42 +0300
committerGitHub <noreply@github.com>2020-01-28 21:26:42 +0300
commitff0571f7ffb5c5098233890609a8024deddb84db (patch)
treecd37430594905a7fb7955cd8604a98a1034abcc7
parentee078a071ce8af18d59e2034b66ee383c4bb67eb (diff)
parent0bd70b482a01de419c13cf4b4aca2645aab1e93a (diff)
Merge pull request #791 from nextcloud/fixAcl
Optimization access to shared polls
-rw-r--r--appinfo/routes.php1
-rw-r--r--lib/Controller/PageController.php10
-rw-r--r--lib/Controller/PollController.php28
-rw-r--r--lib/Db/VoteMapper.php20
-rw-r--r--lib/Model/Acl.php39
-rw-r--r--src/js/App.vue21
-rw-r--r--src/js/components/Navigation/Navigation.vue9
-rw-r--r--src/js/router.js11
-rw-r--r--src/js/store/modules/poll.js6
-rw-r--r--src/js/store/modules/polls.js3
-rw-r--r--src/js/views/NotFound.vue44
-rw-r--r--src/js/views/PollList.vue3
-rw-r--r--src/js/views/PublicVote.vue172
-rw-r--r--src/js/views/Vote.vue36
14 files changed, 185 insertions, 218 deletions
diff --git a/appinfo/routes.php b/appinfo/routes.php
index 47b5c6d9..f6533215 100644
--- a/appinfo/routes.php
+++ b/appinfo/routes.php
@@ -24,6 +24,7 @@
return [
'routes' => [
['name' => 'page#index', 'url' => '/', 'verb' => 'GET'],
+ ['name' => 'page#index', 'url' => '/not-found', 'verb' => 'GET', 'postfix' => 'notfound'],
['name' => 'page#index', 'url' => '/list/{id}', 'verb' => 'GET', 'postfix' => 'list'],
['name' => 'page#index', 'url' => '/vote/{id}', 'verb' => 'GET', 'postfix' => 'vote'],
['name' => 'page#vote_public', 'url' => '/s/{token}', 'verb' => 'GET', 'postfix' => 'public'],
diff --git a/lib/Controller/PageController.php b/lib/Controller/PageController.php
index 82932a44..71e0a7cd 100644
--- a/lib/Controller/PageController.php
+++ b/lib/Controller/PageController.php
@@ -71,8 +71,14 @@ class PageController extends Controller {
* @return PublicTemplateResponse
*/
public function votePublic() {
- return new PublicTemplateResponse('polls', 'polls.tmpl', [
- 'urlGenerator' => $this->urlGenerator]);
+ if (\OC::$server->getUserSession()->isLoggedIn()) {
+ return new TemplateResponse('polls', 'polls.tmpl', [
+ 'urlGenerator' => $this->urlGenerator]);
+ } else {
+ return new PublicTemplateResponse('polls', 'polls.tmpl', [
+ 'urlGenerator' => $this->urlGenerator]);
+ }
+
}
}
diff --git a/lib/Controller/PollController.php b/lib/Controller/PollController.php
index c3805ecf..99917de3 100644
--- a/lib/Controller/PollController.php
+++ b/lib/Controller/PollController.php
@@ -115,14 +115,28 @@ class PollController extends Controller {
public function list() {
if (\OC::$server->getUserSession()->isLoggedIn()) {
try {
- $polls = array_values(array_filter($this->pollMapper->findAll(), function($item) {
- return $this->acl->setPollId($item->getId())->getAllowView();
- }));
- return new DataResponse($polls, Http::STATUS_OK);
+ // $polls = array_values(array_filter($this->pollMapper->findAll(), function($item) {
+ // return $this->acl->setPollId($item->getId())->getAllowView();
+ // }));
+
+ $polls = $this->pollMapper->findAll();
+
+ foreach ($polls as $poll) {
+ $combinedPoll = (object) array_merge(
+ (array) json_decode(json_encode($poll)), (array) json_decode(json_encode($this->acl->setPollId($poll->getId()))));
+ if ($combinedPoll->allowView) {
+ $pollList[] = $combinedPoll;
+ }
+ }
+
+ return new DataResponse($pollList, Http::STATUS_OK);
} catch (DoesNotExistException $e) {
return new DataResponse($e, Http::STATUS_NOT_FOUND);
}
+ } else {
+ return new DataResponse([], Http::STATUS_OK);
}
+
}
/**
@@ -139,7 +153,9 @@ class PollController extends Controller {
$this->acl->setPollId($pollId);
}
$this->poll = $this->pollMapper->find($pollId);
-
+ if (!$this->acl->getAllowView()) {
+ return new DataResponse(null, Http::STATUS_UNAUTHORIZED);
+ }
return new DataResponse([
'poll' => $this->poll,
'acl' => $this->acl
@@ -147,7 +163,7 @@ class PollController extends Controller {
} catch (DoesNotExistException $e) {
$this->logger->info('Poll ' . $pollId . ' not found!', ['app' => 'polls']);
- return new DataResponse($e, Http::STATUS_NOT_FOUND);
+ return new DataResponse(null, Http::STATUS_NOT_FOUND);
}
}
diff --git a/lib/Db/VoteMapper.php b/lib/Db/VoteMapper.php
index 2e759d8c..244eaf26 100644
--- a/lib/Db/VoteMapper.php
+++ b/lib/Db/VoteMapper.php
@@ -98,6 +98,26 @@ class VoteMapper extends QBMapper {
/**
* @param int $pollId
+ * @throws \OCP\AppFramework\Db\DoesNotExistException if not found
+ * @return array
+ */
+ public function findParticipantsVotes($pollId, $userId) {
+ $qb = $this->db->getQueryBuilder();
+
+ $qb->select('*')
+ ->from($this->getTableName())
+ ->where(
+ $qb->expr()->eq('poll_id', $qb->createNamedParameter($pollId, IQueryBuilder::PARAM_INT))
+ )
+ ->andWhere(
+ $qb->expr()->eq('user_id', $qb->createNamedParameter($userId, IQueryBuilder::PARAM_STR))
+ );
+
+ return $this->findEntities($qb);
+ }
+
+ /**
+ * @param int $pollId
* @param string $userId
*/
public function deleteByPollAndUser($pollId, $userId) {
diff --git a/lib/Model/Acl.php b/lib/Model/Acl.php
index 949bbbef..68096490 100644
--- a/lib/Model/Acl.php
+++ b/lib/Model/Acl.php
@@ -33,6 +33,7 @@ use OCP\ILogger;
use OCA\Polls\Db\Poll;
use OCA\Polls\Db\Share;
use OCA\Polls\Db\PollMapper;
+use OCA\Polls\Db\VoteMapper;
use OCA\Polls\Db\ShareMapper;
/**
@@ -80,6 +81,7 @@ class Acl implements JsonSerializable {
* @param ILogger $logger
* @param IGroupManager $groupManager
* @param PollMapper $pollMapper
+ * @param VoteMapper $voteMapper
* @param ShareMapper $shareMapper
* @param Poll $pollMapper
*
@@ -89,6 +91,7 @@ class Acl implements JsonSerializable {
ILogger $logger,
IGroupManager $groupManager,
PollMapper $pollMapper,
+ VoteMapper $voteMapper,
ShareMapper $shareMapper,
Poll $poll
) {
@@ -96,6 +99,7 @@ class Acl implements JsonSerializable {
$this->logger = $logger;
$this->groupManager = $groupManager;
$this->pollMapper = $pollMapper;
+ $this->voteMapper = $voteMapper;
$this->shareMapper = $shareMapper;
$this->poll = $poll;
}
@@ -177,10 +181,13 @@ class Acl implements JsonSerializable {
public function getAllowView(): bool {
return (
$this->getIsOwner()
- || $this->getIsAdmin()
+ || $this->getUserHasVoted()
+ || ($this->getIsAdmin() && $this->poll->getAdminAccess())
|| ($this->getGroupShare() && !$this->poll->getDeleted())
|| ($this->getPersonalShare() && !$this->poll->getDeleted())
- || $this->poll->getAccess() !== 'hidden'
+ || ($this->getPublicShare() && !$this->poll->getDeleted())
+ || ($this->poll->getAccess() !== 'hidden' && !$this->getPublicShare())
+
);
}
@@ -202,6 +209,16 @@ class Acl implements JsonSerializable {
* @NoAdminRequired
* @return bool
*/
+ public function getUserHasVoted(): bool {
+ return count(
+ $this->voteMapper->findParticipantsVotes($this->getPollId(), $this->getUserId())
+ );
+ }
+
+ /**
+ * @NoAdminRequired
+ * @return bool
+ */
public function getPersonalShare(): bool {
return count(
@@ -217,6 +234,21 @@ class Acl implements JsonSerializable {
* @NoAdminRequired
* @return bool
*/
+ public function getPublicShare(): bool {
+
+ return count(
+ array_filter($this->shareMapper->findByPoll($this->getPollId()), function($item) {
+ if ($item->getType() === 'public' && $item->getToken() === $this->getToken()) {
+ return true;
+ }
+ })
+ );
+ }
+
+ /**
+ * @NoAdminRequired
+ * @return bool
+ */
public function getExpired(): bool {
return (
$this->poll->getExpire() > 0
@@ -233,6 +265,7 @@ class Acl implements JsonSerializable {
($this->getAllowView() || $this->getFoundByToken())
&& !$this->getExpired()
&& !$this->poll->getDeleted()
+ && $this->userId
) {
return true;
@@ -366,8 +399,10 @@ class Acl implements JsonSerializable {
'allowEdit' => $this->getAllowEdit(),
'allowSeeUsernames' => $this->getAllowSeeUsernames(),
'allowSeeAllVotes' => $this->getAllowSeeAllVotes(),
+ 'userHasVoted' => $this->getUserHasVoted(),
'groupShare' => $this->getGroupShare(),
'personalShare' => $this->getPersonalShare(),
+ 'publicShare' => $this->getPublicShare(),
'foundByToken' => $this->getFoundByToken(),
'accessLevel' => $this->getAccessLevel()
];
diff --git a/src/js/App.vue b/src/js/App.vue
index ec588070..cca48a0e 100644
--- a/src/js/App.vue
+++ b/src/js/App.vue
@@ -22,7 +22,7 @@
<template>
<div id="app-polls">
- <Navigation v-if="loadNavigation" />
+ <Navigation v-if="OC.currentUser" />
<router-view />
</div>
</template>
@@ -34,25 +34,8 @@ export default {
name: 'App',
components: {
Navigation
- },
-
- data() {
- return {
- loadNavigation: false
- }
- },
-
- computed: {
- isPublic() {
- return (this.$route.name === 'publicVote')
- }
- },
-
- watch: {
- $route() {
- this.loadNavigation = (this.$route.name !== 'publicVote')
- }
}
+
}
</script>
diff --git a/src/js/components/Navigation/Navigation.vue b/src/js/components/Navigation/Navigation.vue
index be07f1d3..9bd9c5af 100644
--- a/src/js/components/Navigation/Navigation.vue
+++ b/src/js/components/Navigation/Navigation.vue
@@ -41,6 +41,14 @@
</ul>
</AppNavigationItem>
+ <AppNavigationItem :title="t('polls', 'Participated')" :allow-collapse="true"
+ icon="icon-user" :to="{ name: 'list', params: {type: 'participated'}}" :open="false">
+ <ul>
+ <PollNavigationItems v-for="(poll) in participatedPolls" :key="poll.id" :poll="poll"
+ @switchDeleted="switchDeleted(poll.id)" @clonePoll="clonePoll(poll.id)" />
+ </ul>
+ </AppNavigationItem>
+
<AppNavigationItem :title="t('polls', 'Public polls')" :allow-collapse="true"
icon="icon-link" :to="{ name: 'list', params: {type: 'public'}}" :open="false">
<ul>
@@ -97,6 +105,7 @@ export default {
'myPolls',
'publicPolls',
'hiddenPolls',
+ 'participatedPolls',
'deletedPolls'
]),
diff --git a/src/js/router.js b/src/js/router.js
index d11cbc71..de609ba8 100644
--- a/src/js/router.js
+++ b/src/js/router.js
@@ -29,7 +29,7 @@ import { generateUrl } from '@nextcloud/router'
// Dynamic loading
const List = () => import('./views/PollList')
const Vote = () => import('./views/Vote')
-const PublicVote = () => import('./views/PublicVote')
+const NotFound = () => import('./views/NotFound')
Vue.use(Router)
@@ -56,6 +56,13 @@ export default new Router({
name: 'list'
},
{
+ path: '/not-found',
+ components: {
+ default: NotFound
+ },
+ name: 'notfound'
+ },
+ {
path: '/vote/:id',
components: {
default: Vote
@@ -70,7 +77,7 @@ export default new Router({
{
path: '/s/:token',
components: {
- default: PublicVote
+ default: Vote
},
props: true,
name: 'publicVote'
diff --git a/src/js/store/modules/poll.js b/src/js/store/modules/poll.js
index 384738f4..65edae5c 100644
--- a/src/js/store/modules/poll.js
+++ b/src/js/store/modules/poll.js
@@ -100,9 +100,11 @@ const actions = {
.then((response) => {
context.commit('setPoll', { poll: response.data.poll })
context.commit('acl/setAcl', { acl: response.data.acl })
+ return response
}, (error) => {
- if (error.response.status !== '404') {
- console.error('Error loading poll', { error: error.response }, { payload: payload })
+ if (error.response.status !== '404' && error.response.status !== '401') {
+ console.debug('Error loading poll', { error: error.response }, { payload: payload })
+ return error.response
}
throw error
})
diff --git a/src/js/store/modules/polls.js b/src/js/store/modules/polls.js
index 9618d44d..fd91da7d 100644
--- a/src/js/store/modules/polls.js
+++ b/src/js/store/modules/polls.js
@@ -51,6 +51,9 @@ const getters = {
},
deletedPolls: (state) => {
return state.list.filter(poll => (poll.deleted))
+ },
+ participatedPolls: (state) => {
+ return state.list.filter(poll => (poll.userHasVoted))
}
}
diff --git a/src/js/views/NotFound.vue b/src/js/views/NotFound.vue
new file mode 100644
index 00000000..60a4d899
--- /dev/null
+++ b/src/js/views/NotFound.vue
@@ -0,0 +1,44 @@
+<!--
+ - @copyright Copyright (c) 2018 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/>.
+ -
+ -->
+
+<template>
+ <AppContent>
+ <div id="emptycontent">
+ <div id="emptycontent-icon" class="icon-search" />
+ <h2>{{ t('polls', 'The poll does not exist') }}</h2>
+ <p class="emptycontent-additional">
+ {{ t('polls', 'Enter a poll or start a new one.') }}
+ </p>
+ </div>
+ </AppContent>
+</template>
+
+<script>
+import { AppContent } from '@nextcloud/vue'
+
+export default {
+ name: 'NotFound',
+ components: {
+ AppContent
+ }
+}
+</script>
diff --git a/src/js/views/PollList.vue b/src/js/views/PollList.vue
index 9bba0b68..91640262 100644
--- a/src/js/views/PollList.vue
+++ b/src/js/views/PollList.vue
@@ -75,6 +75,7 @@ export default {
'myPolls',
'publicPolls',
'hiddenPolls',
+ 'participatedPolls',
'deletedPolls'
]),
@@ -87,6 +88,8 @@ export default {
return this.hiddenPolls
} else if (this.$route.params.type === 'deleted') {
return this.deletedPolls
+ } else if (this.$route.params.type === 'participated') {
+ return this.participatedPolls
} else {
return this.allPolls
}
diff --git a/src/js/views/PublicVote.vue b/src/js/views/PublicVote.vue
deleted file mode 100644
index fcc0b448..00000000
--- a/src/js/views/PublicVote.vue
+++ /dev/null
@@ -1,172 +0,0 @@
-<!--
- - @copyright Copyright (c) 2018 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/>.
- -
- -->
-
-<template>
- <AppContent>
- <div v-if="poll.id > 0" v-show="!isLoading" class="main-container">
- <div class="header-actions">
- <button class="button btn primary" @click="tableMode = !tableMode">
- <span>{{ t('polls', 'Switch view') }}</span>
- </button>
- <a href="#" class="icon icon-settings active"
- :title="t('polls', 'Open Sidebar')" @click="toggleSideBar()" />
- </div>
- <PollTitle />
- <PollInformation />
- <VoteHeaderPublic v-if="!acl.loggedIn" />
- <PollDescription />
- <VoteList v-show="!tableMode" />
- <VoteTable v-show="tableMode" />
-
- <div class="additional">
- <ParticipantsList v-if="acl.allowSeeUsernames" />
- <!-- <Comments /> -->
- </div>
- </div>
-
- <SideBar v-if="sideBarOpen" @closeSideBar="toggleSideBar" />
- <LoadingOverlay v-if="isLoading" />
- </AppContent>
-</template>
-
-<script>
-// import Comments from '../components/Comments/Comments'
-import { AppContent } from '@nextcloud/vue'
-import ParticipantsList from '../components/Base/ParticipantsList'
-import PollDescription from '../components/Base/PollDescription'
-import PollInformation from '../components/Base/PollInformation'
-import PollTitle from '../components/Base/PollTitle'
-import LoadingOverlay from '../components/Base/LoadingOverlay'
-import VoteHeaderPublic from '../components/VoteTable/VoteHeaderPublic'
-import SideBar from '../components/SideBar/SideBar'
-import VoteList from '../components/VoteTable/VoteList'
-import VoteTable from '../components/VoteTable/VoteTable'
-import { mapState } from 'vuex'
-
-export default {
- name: 'Vote',
- components: {
- AppContent,
- ParticipantsList,
- PollDescription,
- PollInformation,
- PollTitle,
- LoadingOverlay,
- VoteHeaderPublic,
- // Comments,
- SideBar,
- VoteTable,
- VoteList
- },
-
- data() {
- return {
- voteSaved: false,
- delay: 50,
- sideBarOpen: false,
- isLoading: false,
- initialTab: 'comments',
- tableMode: true
- }
- },
-
- computed: {
- ...mapState({
- poll: state => state.poll,
- acl: state => state.acl
- }),
-
- windowTitle: function() {
- return t('polls', 'Polls') + ' - ' + this.poll.title
- }
-
- },
-
- watch: {
- $route() {
- this.loadPoll()
- }
- },
-
- mounted() {
- this.loadPoll()
- },
-
- methods: {
- loadPoll() {
- this.isLoading = true
- this.$store.dispatch('loadPollMain', { token: this.$route.params.token })
- .then(() => {
- this.$store.dispatch('loadPoll', { token: this.$route.params.token })
- .then(() => {
- this.isLoading = false
- })
- })
- .catch((error) => {
- console.error(error)
- this.isLoading = false
- })
- },
-
- toggleSideBar() {
- this.sideBarOpen = !this.sideBarOpen
- }
-
- }
-}
-</script>
-
-<style lang="scss" scoped>
-.additional {
- display: flex;
- flex-wrap: wrap;
- .participants {
- flex: 1;
- }
- .comments {
- flex: 3;
- }
-}
-
-.main-container {
- position: relative;
- flex: 1;
- padding: 8px 24px;
- margin: 0;
- flex-direction: column;
- flex-wrap: nowrap;
- overflow-x: scroll;
-}
-
-.header-actions {
- right: 0;
- position: absolute;
- display: flex;
-}
-
-.icon.icon-settings.active {
- display: block;
- width: 44px;
- height: 44px;
-}
-
-</style>
diff --git a/src/js/views/Vote.vue b/src/js/views/Vote.vue
index 6c98e636..8cd15a92 100644
--- a/src/js/views/Vote.vue
+++ b/src/js/views/Vote.vue
@@ -32,17 +32,21 @@
</div>
<PollTitle />
<PollInformation />
+ <VoteHeaderPublic v-if="!OC.currentUser" />
<PollDescription />
<VoteList v-show="!tableMode && options.list.length" />
<VoteTable v-show="tableMode && options.list.length" />
<div v-if="!options.list.length" class="emptycontent">
<div class="icon-toggle-filelist" />
- <button @click="openOptions">
+ <button v-if="acl.getAllowEdit" @click="openOptions">
{{ t('polls', 'There are no vote options, add some in the options section of the right side bar.') }}
</button>
+ <div v-if="!acl.getAllowEdit">
+ {{ t('polls', 'There are no vote options. Maybe the owner did not provide some until now.') }}
+ </div>
</div>
- <Subscription />
+ <Subscription v-if="OC.currentUser" />
<div class="additional">
<ParticipantsList v-if="acl.allowSeeUsernames" />
</div>
@@ -61,6 +65,7 @@ import PollDescription from '../components/Base/PollDescription'
import PollInformation from '../components/Base/PollInformation'
import PollTitle from '../components/Base/PollTitle'
import LoadingOverlay from '../components/Base/LoadingOverlay'
+import VoteHeaderPublic from '../components/VoteTable/VoteHeaderPublic'
import SideBar from '../components/SideBar/SideBar'
import VoteList from '../components/VoteTable/VoteList'
import VoteTable from '../components/VoteTable/VoteTable'
@@ -76,6 +81,7 @@ export default {
PollInformation,
PollTitle,
LoadingOverlay,
+ VoteHeaderPublic,
SideBar,
VoteTable,
VoteList
@@ -85,10 +91,9 @@ export default {
return {
voteSaved: false,
delay: 50,
- sideBarOpen: false,
+ sideBarOpen: true,
isLoading: false,
initialTab: 'comments',
- newName: '',
tableMode: true,
activeTab: t('polls', 'Comments').toLowerCase()
}
@@ -130,19 +135,24 @@ export default {
loadPoll() {
this.isLoading = true
- this.$store.dispatch({ type: 'loadPollMain', pollId: this.$route.params.id })
- .then(() => {
- this.$store.dispatch({ type: 'loadPoll', pollId: this.$route.params.id })
- .then(() => {
- if (this.acl.allowEdit && moment.unix(this.poll.created).diff() > -10000) {
- this.openConfiguration()
- }
- this.isLoading = false
- })
+ this.$store.dispatch({ type: 'loadPollMain', pollId: this.$route.params.id, token: this.$route.params.token })
+ .then((response) => {
+ if (response.status === 200) {
+ this.$store.dispatch({ type: 'loadPoll', pollId: this.$route.params.id, token: this.$route.params.token })
+ .then(() => {
+ if (this.acl.allowEdit && moment.unix(this.poll.created).diff() > -10000) {
+ this.openConfiguration()
+ }
+ this.isLoading = false
+ })
+ } else {
+ this.$router.replace({ name: 'notfound' })
+ }
})
.catch((error) => {
console.error(error)
this.isLoading = false
+ this.$router.replace({ name: 'notfound' })
})
},