Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 10 additions & 2 deletions lib/Db/Poll.php
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,8 @@
use OCA\Polls\Helper\Container;
use OCA\Polls\Model\Settings\AppSettings;
use OCA\Polls\Model\Settings\SystemSettings;
use OCA\Polls\Model\User\User;
use OCA\Polls\Model\UserBase;
use OCA\Polls\UserSession;
use OCP\IURLGenerator;

Expand Down Expand Up @@ -785,6 +787,11 @@ private function getAllowAddOptions(): bool {
return false;
}

// deny, if user is not allowed to vote
if (!$this->getAllowVote()) {
return false;
}

// public shares are not allowed to add options
if ($this->userSession->getShare()->getType() === Share::TYPE_PUBLIC) {
return false;
Expand Down Expand Up @@ -912,8 +919,9 @@ private function getAllowVote(): bool {
return false;
}

// public shares are not allowed to vote
if ($this->userSession->getShare()->getType() === Share::TYPE_PUBLIC) {
// Only external, user or admin shares are allowed to vote
// All other external types have to register first
if (!in_array($this->userSession->getCurrentUser()->getType(), [UserBase::TYPE_EXTERNAL, UserBase::TYPE_USER, UserBase::TYPE_ADMIN])) {
return false;
}

Expand Down
1 change: 1 addition & 0 deletions src/Types/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,7 @@ export type SignalingType =
| 'invalid'
| 'success'
| 'checking'
| 'missing'

export type UserType =
| 'email'
Expand Down
5 changes: 4 additions & 1 deletion src/components/Base/modules/InputDiv.vue
Original file line number Diff line number Diff line change
Expand Up @@ -78,6 +78,9 @@ const computedSignalingClass = computed(() => {
if (signalingClass === 'invalid') {
return 'error'
}
if (signalingClass === 'missing') {
return 'error'
}
return signalingClass
})

Expand Down Expand Up @@ -307,7 +310,7 @@ const inputClass = computed(() => [
opacity: 0.8;
&.error {
opacity: 1;
color: var(--color-error);
color: var(--color-text-error);
}
}

Expand Down
222 changes: 109 additions & 113 deletions src/components/Public/PublicRegisterModal.vue
Original file line number Diff line number Diff line change
Expand Up @@ -33,39 +33,10 @@ const sessionStore = useSessionStore()
const pollStore = usePollStore()

const COOKIE_LIFETIME = 30
const checkStatus = ref<{
email: SignalingType
userName: SignalingType
}>({
email: 'empty',
userName: 'empty',
})

const sendRegistration = ref(false)
const userName = ref('')
const emailAddress = ref('')
const saveCookie = ref(true)

const registrationIsValid = computed(
() =>
checkStatus.value.userName === 'valid'
&& (checkStatus.value.email === 'valid'
|| (emailAddress.value.length === 0
&& sessionStore.share.publicPollEmail !== 'mandatory')),
)
const disableSubmit = computed(
() =>
!registrationIsValid.value
|| checkStatus.value.userName === 'checking'
|| sendRegistration.value,
)
const emailGeneratedStatus = computed(() =>
checkStatus.value.email === 'empty'
? sessionStore.share.publicPollEmail
: checkStatus.value.email,
)
const offerCookies = computed(() => sessionStore.share.type === 'public')

const loginLink = computed(() => {
const redirectUrl = router.resolve({
name: 'publicVote',
Expand All @@ -75,85 +46,8 @@ const loginLink = computed(() => {
return `${generateUrl('/login')}?redirect_url=${redirectUrl}`
})

const userNameHint = computed(() => {
if (checkStatus.value.userName === 'checking') {
return t('polls', 'Checking name …')
}
if (checkStatus.value.userName === 'empty') {
return t('polls', 'A name is required.')
}
if (checkStatus.value.userName === 'invalid') {
return t('polls', 'The name {username} is invalid or reserved.', {
username: userName.value,
})
}
return ''
})

const emailAddressHint = computed(() => {
if (emailGeneratedStatus.value === 'checking') {
return t('polls', 'Checking email address …')
}
if (emailGeneratedStatus.value === 'mandatory') {
return t('polls', 'An email address is required.')
}
if (emailGeneratedStatus.value === 'invalid') {
return t('polls', 'Invalid email address.')
}
if (sessionStore.share.type === 'public') {
if (emailGeneratedStatus.value === 'valid') {
return t(
'polls',
'You will receive your personal link after clicking "OK".',
)
}
return t(
'polls',
'Enter your email address to get your personal access link.',
)
}
return ''
})

onMounted(() => {
if (route.name === 'publicVote' && route.query.name) {
userName.value = route.query.name.toString()
} else {
userName.value = sessionStore.currentUser.displayName
}
if (route.name === 'publicVote' && route.query.email) {
emailAddress.value = route.query.email.toString()
} else {
emailAddress.value = sessionStore.currentUser.emailAddress
}
})

/**
*
* @param token
*/
function routeToPersonalShare(token: string): void {
if (route.params.token === token) {
// if share was not a public share, but a personal share
// (i.e. email shares allow to change personal data by fist entering of the poll),
// just load the poll
pollStore.load()
closeModal()
} else {
// in case of a public share, redirect to the generated share
router.push({
name: 'publicVote',
params: { token },
replace: true,
})
closeModal()
}
}
const offerCookies = computed(() => sessionStore.share.type === 'public')

/**
*
* @param value - value to be stored in the cookie
*/
function updateCookie(value: string): void {
const cookieExpiration = COOKIE_LIFETIME * 24 * 60 * 1000
setCookie(route.params.token.toString(), value, cookieExpiration)
Expand All @@ -175,35 +69,90 @@ function login(): void {
)
}

const checkStatus = ref<{
email: SignalingType
userName: SignalingType
}>({
email: 'empty',
userName: 'missing',
})

const userName = ref('')
const userNameHint = computed(() => {
if (checkStatus.value.userName === 'checking') {
return t('polls', 'Checking name …')
}
if (checkStatus.value.userName === 'missing') {
return t('polls', 'A name is required.')
}
if (checkStatus.value.userName === 'invalid') {
return t('polls', 'The name {username} is invalid or reserved.', {
username: userName.value,
})
}
return ''
})

const validatePublicUsername = debounce(async function (): Promise<void> {
if (userName.value.length < 1) {
checkStatus.value.userName = 'empty'
checkStatus.value.userName = 'missing'
return
}

checkStatus.value.userName = 'checking'

try {
await ValidatorAPI.validateName(route.params.token, userName.value)
checkStatus.value.userName = 'valid'
} catch (error) {
if ((error as AxiosError).code === 'ERR_CANCELED') {
return
}

checkStatus.value.userName = 'invalid'

if ((error as AxiosError).code === 'ERR_BAD_REQUEST') {
checkStatus.value.userName = 'invalid'
return
}
throw error
}
}, 500)

const emailAddress = ref('')

const emailAddressHint = computed(() => {
if (checkStatus.value.email === 'checking') {
return t('polls', 'Checking email address …')
}
if (checkStatus.value.email === 'missing') {
return t('polls', 'An email address is required.')
}
if (checkStatus.value.email === 'invalid') {
return t('polls', 'Invalid email address.')
}
if (sessionStore.share.type === 'public') {
if (checkStatus.value.email === 'valid') {
return t(
'polls',
'You will receive your personal link after clicking "OK".',
)
}
return t(
'polls',
'Enter your email address to get your personal access link.',
)
}
return ''
})

const validateEmailAddress = debounce(async function (): Promise<void> {
if (emailAddress.value.length < 1) {
checkStatus.value.email = 'empty'
checkStatus.value.email = sessionStore.share.publicPollEmail === 'mandatory' ? 'missing': 'empty'
return
}

checkStatus.value.email = 'checking'

try {
await ValidatorAPI.validateEmailAddress(emailAddress.value)
checkStatus.value.email = 'valid'
Expand All @@ -219,9 +168,32 @@ const validateEmailAddress = debounce(async function (): Promise<void> {
}
}, 500)

/**
*
*/
const registrationIsValid = computed(
() =>
checkStatus.value.userName === 'valid'
&& (checkStatus.value.email === 'valid'
|| (emailAddress.value.length === 0
&& sessionStore.share.publicPollEmail !== 'mandatory')),
)

function routeToPersonalShare(token: string): void {
if (route.params.token === token) {
// if share was not a public share, but a personal share
// (i.e. email shares allow to change personal data by fist entering of the poll),
// just refresh the session and load the poll
sessionStore.load()
pollStore.load()
} else {
// in case of a public share, redirect to the generated share
router.push({
name: 'publicVote',
params: { token },
replace: true,
})
}
closeModal()
}

async function submitRegistration(): Promise<void> {
if (!registrationIsValid.value || sendRegistration.value) {
return
Expand Down Expand Up @@ -262,6 +234,30 @@ async function submitRegistration(): Promise<void> {
sendRegistration.value = false
}
}

const disableSubmit = computed(
() =>
!registrationIsValid.value
|| checkStatus.value.userName === 'checking'
|| sendRegistration.value,
)

onMounted(() => {
if (route.name === 'publicVote') {
// TODO: remove displayName at controller side
// this ist just a quick fix
sessionStore.currentUser.displayName = route.query.displayName as string || ''
sessionStore.currentUser.emailAddress = route.query.emailAddress as string || ''
}

// pre-fill input field with existing data
userName.value = sessionStore.currentUser.displayName
validatePublicUsername()

// pre-fill input field with existing data
emailAddress.value = sessionStore.currentUser.emailAddress
validateEmailAddress()
})
</script>

<template>
Expand Down
4 changes: 2 additions & 2 deletions src/components/User/ActionInputDisplayName.vue
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,7 @@ function setStatus(status: StatusResults) {
inputProps.value.showTrailingButton = status === 'success'
}

const validate = debounce(async function () {
const validatePublicUsername = debounce(async function () {
if (sessionStore.share.user.displayName.length < 1) {
setStatus('unchanged')
return
Expand Down Expand Up @@ -90,7 +90,7 @@ async function submit() {
v-if="sessionStore.route.name === 'publicVote'"
v-bind="inputProps"
v-model="sessionStore.share.user.displayName"
@update:value-value="validate"
@update:value="validatePublicUsername"
@submit="submit">
<template #icon>
<EditAccountIcon />
Expand Down
2 changes: 1 addition & 1 deletion src/components/User/UserMenu.vue
Original file line number Diff line number Diff line change
Expand Up @@ -375,7 +375,7 @@ async function submitEmail() {
v-if="sessionStore.share?.type === 'external'"
v-bind="displayNameInputProps"
v-model="sessionStore.share.user.displayName"
@update:value-value="validateDisplayName"
@update:model-value="validateDisplayName"
@submit="submitDisplayName">
<template #icon>
<EditAccountIcon />
Expand Down
2 changes: 1 addition & 1 deletion src/components/VoteTable/VoteMenu.vue
Original file line number Diff line number Diff line change
Expand Up @@ -288,7 +288,7 @@ async function submitEmail() {
v-if="sessionStore.share?.type === 'external'"
v-bind="displayNameInputProps"
v-model="sessionStore.share.user.displayName"
@update:value-value="validateDisplayName"
@update:model-value="validateDisplayName"
@submit="submitDisplayName">
<template #icon>
<EditAccountIcon />
Expand Down
Loading