From 447afed3a7d6b0315d05716e4b78dddc2140d7ee Mon Sep 17 00:00:00 2001 From: dartcafe Date: Tue, 16 Dec 2025 23:16:00 +0100 Subject: [PATCH] testing, fixing, testing, fixing, ... Signed-off-by: dartcafe --- lib/Db/Poll.php | 12 +- src/Types/index.ts | 1 + src/components/Base/modules/InputDiv.vue | 5 +- src/components/Public/PublicRegisterModal.vue | 222 +++++++++--------- .../User/ActionInputDisplayName.vue | 4 +- src/components/User/UserMenu.vue | 2 +- src/components/VoteTable/VoteMenu.vue | 2 +- src/stores/votes.ts | 4 +- 8 files changed, 130 insertions(+), 122 deletions(-) diff --git a/lib/Db/Poll.php b/lib/Db/Poll.php index 13137f6f32..61f2d29949 100644 --- a/lib/Db/Poll.php +++ b/lib/Db/Poll.php @@ -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; @@ -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; @@ -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; } diff --git a/src/Types/index.ts b/src/Types/index.ts index a47f061f13..bcf67b2201 100644 --- a/src/Types/index.ts +++ b/src/Types/index.ts @@ -43,6 +43,7 @@ export type SignalingType = | 'invalid' | 'success' | 'checking' + | 'missing' export type UserType = | 'email' diff --git a/src/components/Base/modules/InputDiv.vue b/src/components/Base/modules/InputDiv.vue index 2a3400efbc..cc474c20dc 100644 --- a/src/components/Base/modules/InputDiv.vue +++ b/src/components/Base/modules/InputDiv.vue @@ -78,6 +78,9 @@ const computedSignalingClass = computed(() => { if (signalingClass === 'invalid') { return 'error' } + if (signalingClass === 'missing') { + return 'error' + } return signalingClass }) @@ -307,7 +310,7 @@ const inputClass = computed(() => [ opacity: 0.8; &.error { opacity: 1; - color: var(--color-error); + color: var(--color-text-error); } } diff --git a/src/components/Public/PublicRegisterModal.vue b/src/components/Public/PublicRegisterModal.vue index 7b4f591a73..07d4ed31a7 100644 --- a/src/components/Public/PublicRegisterModal.vue +++ b/src/components/Public/PublicRegisterModal.vue @@ -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', @@ -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) @@ -175,13 +69,38 @@ 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 { 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' @@ -189,21 +108,51 @@ const validatePublicUsername = debounce(async function (): Promise { 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 { 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' @@ -219,9 +168,32 @@ const validateEmailAddress = debounce(async function (): Promise { } }, 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 { if (!registrationIsValid.value || sendRegistration.value) { return @@ -262,6 +234,30 @@ async function submitRegistration(): Promise { 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() +})