diff --git a/l10n/bg.js b/l10n/bg.js index cf4379aaf7..291d07a2bb 100644 --- a/l10n/bg.js +++ b/l10n/bg.js @@ -182,6 +182,9 @@ OC.L10N.register( "Next day" : "Утре", "Add" : "Добавяне", "Propose an option" : "Предложете опция", + "This poll is unpublished." : "Тази анкета не е публикувана.", + "Only visible to me" : "Видимо само за мен", + "Only visible to {displayName}" : "Видимо само за {displayName}", "Error while saving comment" : "Грешка при запазване на коментара", "New comment …" : "Нов коментар...", "Confidential" : "Поверителен", @@ -212,6 +215,8 @@ OC.L10N.register( "Restore poll" : "Възстановяване на анкета", "Archive poll" : "Архивна анкета", "Delete poll" : "Изтриване на анкета", + "Force confidential comments (only visible to you and the author)" : "Наложи конфиденциални коментари (видими само за теб и автора)", + "Force confidential comments (only visible to {displayName} and the author)" : "Наложи конфиденциални коментари (видими само за {displayName} и автора)", "Limit \"Yes\" votes per option" : "Ограничение на гласовете „Да“ за опция", "Hide not available Options" : "Скриване на недостъпните опции", "Allow Proposals" : "Разрешаване на предложения", @@ -266,6 +271,7 @@ OC.L10N.register( "Each line creates a new option. Duplicates will get skipped without warning." : "Всеки ред създава нова опция. Дубликатите ще бъдат пропускани без предупреждение.", "Poll informations" : "Информация за анкетата", "Archived" : "Архивирано", + "Unpublished" : "Непубликувано", "A private poll from {name}" : "Частна анкета от {name}", "An openly accessible poll from {name}" : "Свободно достъпна анкета от {name}", "Closing {relativeExpirationTime}" : "Затваряне {relativeExpirationTime}", @@ -295,6 +301,8 @@ OC.L10N.register( "Error cloning poll." : "Грешка при клониране на анкета.", "Take over" : "Вземане на управление", "Sort by title" : "Сортиране по заглавие", + "Descending" : "Низходящо", + "Ascending" : "Възходящо", "Checking name …" : "Името се проверява …", "A name is required." : "Нужно е име.", "The name {username} is invalid or reserved." : "Името {username} е невалидно или е запазено.", diff --git a/l10n/bg.json b/l10n/bg.json index 653adc6ec8..323b9251c9 100644 --- a/l10n/bg.json +++ b/l10n/bg.json @@ -180,6 +180,9 @@ "Next day" : "Утре", "Add" : "Добавяне", "Propose an option" : "Предложете опция", + "This poll is unpublished." : "Тази анкета не е публикувана.", + "Only visible to me" : "Видимо само за мен", + "Only visible to {displayName}" : "Видимо само за {displayName}", "Error while saving comment" : "Грешка при запазване на коментара", "New comment …" : "Нов коментар...", "Confidential" : "Поверителен", @@ -210,6 +213,8 @@ "Restore poll" : "Възстановяване на анкета", "Archive poll" : "Архивна анкета", "Delete poll" : "Изтриване на анкета", + "Force confidential comments (only visible to you and the author)" : "Наложи конфиденциални коментари (видими само за теб и автора)", + "Force confidential comments (only visible to {displayName} and the author)" : "Наложи конфиденциални коментари (видими само за {displayName} и автора)", "Limit \"Yes\" votes per option" : "Ограничение на гласовете „Да“ за опция", "Hide not available Options" : "Скриване на недостъпните опции", "Allow Proposals" : "Разрешаване на предложения", @@ -264,6 +269,7 @@ "Each line creates a new option. Duplicates will get skipped without warning." : "Всеки ред създава нова опция. Дубликатите ще бъдат пропускани без предупреждение.", "Poll informations" : "Информация за анкетата", "Archived" : "Архивирано", + "Unpublished" : "Непубликувано", "A private poll from {name}" : "Частна анкета от {name}", "An openly accessible poll from {name}" : "Свободно достъпна анкета от {name}", "Closing {relativeExpirationTime}" : "Затваряне {relativeExpirationTime}", @@ -293,6 +299,8 @@ "Error cloning poll." : "Грешка при клониране на анкета.", "Take over" : "Вземане на управление", "Sort by title" : "Сортиране по заглавие", + "Descending" : "Низходящо", + "Ascending" : "Възходящо", "Checking name …" : "Името се проверява …", "A name is required." : "Нужно е име.", "The name {username} is invalid or reserved." : "Името {username} е невалидно или е запазено.", diff --git a/l10n/ca.js b/l10n/ca.js index 09decaacc5..6fd6d4450c 100644 --- a/l10n/ca.js +++ b/l10n/ca.js @@ -377,6 +377,7 @@ OC.L10N.register( "Error cloning poll." : "Error clonant l’enquesta.", "Take over" : "Fer-se càrrec", "Sort by title" : "Ordenar per títol", + "Ascending" : "Ascendent", "Checking name …" : "S'està comprovant el nom …", "A name is required." : "Cal un nom.", "The name {username} is invalid or reserved." : "El nom {username} no és vàlid o reservat.", diff --git a/l10n/ca.json b/l10n/ca.json index 4ebe5c2f52..4a320ec064 100644 --- a/l10n/ca.json +++ b/l10n/ca.json @@ -375,6 +375,7 @@ "Error cloning poll." : "Error clonant l’enquesta.", "Take over" : "Fer-se càrrec", "Sort by title" : "Ordenar per títol", + "Ascending" : "Ascendent", "Checking name …" : "S'està comprovant el nom …", "A name is required." : "Cal un nom.", "The name {username} is invalid or reserved." : "El nom {username} no és vàlid o reservat.", diff --git a/l10n/cs.js b/l10n/cs.js index c524ce3e9c..12fef26b16 100644 --- a/l10n/cs.js +++ b/l10n/cs.js @@ -243,6 +243,7 @@ OC.L10N.register( "Add some!" : "Přidejte nějaký!", "Edit access" : "Upravit přístup", "Register" : "Registrovat", + "Send confirmation mails" : "Odeslat potvrzovací e-maily", "See result" : "Zobrazit výsledek", "Result of sent confirmation mails" : "Výsledek zaslaných potvrzovacích e-mailů", "No valid email address" : "Žádná platná e-mailová adresa", @@ -269,6 +270,11 @@ OC.L10N.register( "No further action is possible." : "Není možná žádná další akce.", "Due to possible performance issues {countHiddenParticipants} voters are hidden." : "Kvůli možným problémům s výkonem je skryto {countHiddenParticipants} hlasujících.", "You can reveal them, but you may expect an unwanted long loading time." : "Je možné si je nechat zobrazit, ale očekávejte neúměrně dlouhou dobu načítání.", + "_%n orphaned vote reduces your vote quota._::_%n orphaned votes reduces your vote quota._" : ["%n osiřelá volba snižuje vaší kvótu voleb.","%n osiřelé volby snižují vaší kvótu voleb.","%n osiřelých voleb snižuje vaší kvótu voleb.","%n osiřelé volby snižují vaší kvótu voleb."], + "No more voting options are available." : "Nejsou k dispozici žádné další možnosti voleb.", + "_%n voting option is available._::_%n voting options are available._" : ["K dispozici %n možnost volby.","K dispozici %n možnosti volby.","K dispozici %n možností voleb.","K dispozici %n možnosti volby."], + "You have no votes left." : "Už nemáte žádné hlasy.", + "_You have %n vote left out of {maxVotes}._::_You have %n votes left out of {maxVotes}._" : ["K dispozici ještě %n hlas z {maxVotes}.","K dispozici ještě %n hlasy z {maxVotes}.","K dispozici ještě %n hlasů z {maxVotes}.","K dispozici ještě %n hlasů z {maxVotes}."], "Limited votes." : "Omezené hlasy.", "This share is locked and allows only read access. Registering is not possible." : "Sdílení je uzamčeno a umožňuje přístup pouze pro čtení. Registrace není možná", "Voting is locked and you have just read access to this poll." : "Hlasování je uzamčeno. Máte přístup jen ke čtení.", diff --git a/l10n/cs.json b/l10n/cs.json index d0cdcfc781..1473760c3c 100644 --- a/l10n/cs.json +++ b/l10n/cs.json @@ -241,6 +241,7 @@ "Add some!" : "Přidejte nějaký!", "Edit access" : "Upravit přístup", "Register" : "Registrovat", + "Send confirmation mails" : "Odeslat potvrzovací e-maily", "See result" : "Zobrazit výsledek", "Result of sent confirmation mails" : "Výsledek zaslaných potvrzovacích e-mailů", "No valid email address" : "Žádná platná e-mailová adresa", @@ -267,6 +268,11 @@ "No further action is possible." : "Není možná žádná další akce.", "Due to possible performance issues {countHiddenParticipants} voters are hidden." : "Kvůli možným problémům s výkonem je skryto {countHiddenParticipants} hlasujících.", "You can reveal them, but you may expect an unwanted long loading time." : "Je možné si je nechat zobrazit, ale očekávejte neúměrně dlouhou dobu načítání.", + "_%n orphaned vote reduces your vote quota._::_%n orphaned votes reduces your vote quota._" : ["%n osiřelá volba snižuje vaší kvótu voleb.","%n osiřelé volby snižují vaší kvótu voleb.","%n osiřelých voleb snižuje vaší kvótu voleb.","%n osiřelé volby snižují vaší kvótu voleb."], + "No more voting options are available." : "Nejsou k dispozici žádné další možnosti voleb.", + "_%n voting option is available._::_%n voting options are available._" : ["K dispozici %n možnost volby.","K dispozici %n možnosti volby.","K dispozici %n možností voleb.","K dispozici %n možnosti volby."], + "You have no votes left." : "Už nemáte žádné hlasy.", + "_You have %n vote left out of {maxVotes}._::_You have %n votes left out of {maxVotes}._" : ["K dispozici ještě %n hlas z {maxVotes}.","K dispozici ještě %n hlasy z {maxVotes}.","K dispozici ještě %n hlasů z {maxVotes}.","K dispozici ještě %n hlasů z {maxVotes}."], "Limited votes." : "Omezené hlasy.", "This share is locked and allows only read access. Registering is not possible." : "Sdílení je uzamčeno a umožňuje přístup pouze pro čtení. Registrace není možná", "Voting is locked and you have just read access to this poll." : "Hlasování je uzamčeno. Máte přístup jen ke čtení.", diff --git a/l10n/de.js b/l10n/de.js index 19779f27f5..af20fe4541 100644 --- a/l10n/de.js +++ b/l10n/de.js @@ -243,6 +243,7 @@ OC.L10N.register( "Add some!" : "Füge einige hinzu!", "Edit access" : "Zugriff bearbeiten", "Register" : "Registrieren", + "Send confirmation mails" : "Bestätigungs-E-Mails senden", "See result" : "Ergebnis anzeigen", "Result of sent confirmation mails" : "Ergebnis der versendeten Bestätigungs-E-Mails", "No valid email address" : "Keine gültige E-Mail-Adresse", @@ -262,13 +263,18 @@ OC.L10N.register( "The proposal period ends {timeRelative}." : "Die Vorschlagsfrist endet {timeRelative}.", "Propose an option" : "Eine Option vorschlagen", "This poll is an anonymous poll for everyone, including the owner. Deanonymizing is disabled for this poll." : "Diese Umfrage ist für alle, einschließlich des Eigentümers, anonym. Die Deanonymisierung ist für diese Umfrage deaktiviert.", - "But be aware that your name is not stored in an encrypted or obfuscated way." : "Beachte bitte, dass Dein Name weder verschlüsselt noch pseudomisiert gespeichert wird.", + "But be aware that your name is not stored in an encrypted or obfuscated way." : "Beachte bitte, dass dein Name weder verschlüsselt noch pseudomisiert gespeichert wird.", "This poll is an anonymous poll for everyone, except for the owner and delegated poll administration." : "Diese Umfrage ist anonym für alle, außer dem Eigentümer und der delegierten Umfrageadministration.", "Anonymization of this poll can be removed at any time by the owner and delegated poll administration." : "Die Anonymisierung dieser Umfrage kann jederzeit vom Eigentümer und der beauftragten Umfrageadministration aufgehoben werden.", "This poll is closed." : "Diese Umfrage ist geschlossen.", "No further action is possible." : "Keine weitere Aktion möglich.", "Due to possible performance issues {countHiddenParticipants} voters are hidden." : "Aufgrund möglicher Leistungsprobleme werden {countHiddenParticipants} Abstimmende ausgeblendet.", "You can reveal them, but you may expect an unwanted long loading time." : "Du kannst diese zwar anzeigen, musst aber mit einer ungewollt langen Ladezeit rechnen.", + "_%n orphaned vote reduces your vote quota._::_%n orphaned votes reduces your vote quota._" : ["%n verwaiste Stimme reduziert deine Stimmenquote.","%n verwaiste Stimmen reduzieren Deine Stimmenquote."], + "No more voting options are available." : "Keine weiteren Abstimmungsmöglichkeiten vorhanden.", + "_%n voting option is available._::_%n voting options are available._" : ["%n Abstimmungsmöglichkeit vorhanden","%n Abstimmungsmöglichkeiten vorhanden"], + "You have no votes left." : "Keine Stimmen mehr übrig.", + "_You have %n vote left out of {maxVotes}._::_You have %n votes left out of {maxVotes}._" : ["Es ist noch %n von {maxVotes} Stimmen übrig.","Es sind noch %n von {maxVotes} Stimmen übrig."], "Limited votes." : "Begrenzte Stimmen.", "This share is locked and allows only read access. Registering is not possible." : "Diese Freigabe ist gesperrt und erlaubt nur den Lesezugriff. Eine Registrierung ist nicht möglich.", "Voting is locked and you have just read access to this poll." : "Die Abstimmung ist gesperrt und Du hast lediglich Lesezugriff auf diese Umfrage.", diff --git a/l10n/de.json b/l10n/de.json index e40e4427a9..1c0cd83f91 100644 --- a/l10n/de.json +++ b/l10n/de.json @@ -241,6 +241,7 @@ "Add some!" : "Füge einige hinzu!", "Edit access" : "Zugriff bearbeiten", "Register" : "Registrieren", + "Send confirmation mails" : "Bestätigungs-E-Mails senden", "See result" : "Ergebnis anzeigen", "Result of sent confirmation mails" : "Ergebnis der versendeten Bestätigungs-E-Mails", "No valid email address" : "Keine gültige E-Mail-Adresse", @@ -260,13 +261,18 @@ "The proposal period ends {timeRelative}." : "Die Vorschlagsfrist endet {timeRelative}.", "Propose an option" : "Eine Option vorschlagen", "This poll is an anonymous poll for everyone, including the owner. Deanonymizing is disabled for this poll." : "Diese Umfrage ist für alle, einschließlich des Eigentümers, anonym. Die Deanonymisierung ist für diese Umfrage deaktiviert.", - "But be aware that your name is not stored in an encrypted or obfuscated way." : "Beachte bitte, dass Dein Name weder verschlüsselt noch pseudomisiert gespeichert wird.", + "But be aware that your name is not stored in an encrypted or obfuscated way." : "Beachte bitte, dass dein Name weder verschlüsselt noch pseudomisiert gespeichert wird.", "This poll is an anonymous poll for everyone, except for the owner and delegated poll administration." : "Diese Umfrage ist anonym für alle, außer dem Eigentümer und der delegierten Umfrageadministration.", "Anonymization of this poll can be removed at any time by the owner and delegated poll administration." : "Die Anonymisierung dieser Umfrage kann jederzeit vom Eigentümer und der beauftragten Umfrageadministration aufgehoben werden.", "This poll is closed." : "Diese Umfrage ist geschlossen.", "No further action is possible." : "Keine weitere Aktion möglich.", "Due to possible performance issues {countHiddenParticipants} voters are hidden." : "Aufgrund möglicher Leistungsprobleme werden {countHiddenParticipants} Abstimmende ausgeblendet.", "You can reveal them, but you may expect an unwanted long loading time." : "Du kannst diese zwar anzeigen, musst aber mit einer ungewollt langen Ladezeit rechnen.", + "_%n orphaned vote reduces your vote quota._::_%n orphaned votes reduces your vote quota._" : ["%n verwaiste Stimme reduziert deine Stimmenquote.","%n verwaiste Stimmen reduzieren Deine Stimmenquote."], + "No more voting options are available." : "Keine weiteren Abstimmungsmöglichkeiten vorhanden.", + "_%n voting option is available._::_%n voting options are available._" : ["%n Abstimmungsmöglichkeit vorhanden","%n Abstimmungsmöglichkeiten vorhanden"], + "You have no votes left." : "Keine Stimmen mehr übrig.", + "_You have %n vote left out of {maxVotes}._::_You have %n votes left out of {maxVotes}._" : ["Es ist noch %n von {maxVotes} Stimmen übrig.","Es sind noch %n von {maxVotes} Stimmen übrig."], "Limited votes." : "Begrenzte Stimmen.", "This share is locked and allows only read access. Registering is not possible." : "Diese Freigabe ist gesperrt und erlaubt nur den Lesezugriff. Eine Registrierung ist nicht möglich.", "Voting is locked and you have just read access to this poll." : "Die Abstimmung ist gesperrt und Du hast lediglich Lesezugriff auf diese Umfrage.", diff --git a/l10n/de_DE.js b/l10n/de_DE.js index a37dc90f1f..1ed917e680 100644 --- a/l10n/de_DE.js +++ b/l10n/de_DE.js @@ -243,6 +243,7 @@ OC.L10N.register( "Add some!" : "Fügen Sie einige hinzu!", "Edit access" : "Zugriff bearbeiten", "Register" : "Registrieren", + "Send confirmation mails" : "Bestätigungs-E-Mails senden", "See result" : "Ergebnis anzeigen", "Result of sent confirmation mails" : "Ergebnis der versendeten Bestätigungs-E-Mails", "No valid email address" : "Keine gültige E-Mail-Adresse", @@ -269,6 +270,11 @@ OC.L10N.register( "No further action is possible." : "Keine weitere Aktion möglich.", "Due to possible performance issues {countHiddenParticipants} voters are hidden." : "Aufgrund möglicher Leistungsprobleme werden {countHiddenParticipants} Abstimmende ausgeblendet.", "You can reveal them, but you may expect an unwanted long loading time." : "Sie können diese zwar anzeigen, müssen aber mit einer ungewollt langen Ladezeit rechnen.", + "_%n orphaned vote reduces your vote quota._::_%n orphaned votes reduces your vote quota._" : ["%n verwaiste Stimme reduziert Ihre Stimmenquote.","%n verwaiste Stimmen reduzieren Ihre Stimmenquote."], + "No more voting options are available." : "Keine weiteren Abstimmungsmöglichkeiten mehr vorhanden.", + "_%n voting option is available._::_%n voting options are available._" : ["%n Abstimmungsmöglichkeit vorhanden","%n Abstimmungsmöglichkeiten vorhanden"], + "You have no votes left." : "Keine Stimmen mehr übrig.", + "_You have %n vote left out of {maxVotes}._::_You have %n votes left out of {maxVotes}._" : ["Es ist noch %n von {maxVotes} Stimme übrig.","Es sind noch %n von {maxVotes} Stimmen übrig."], "Limited votes." : "Begrenzte Stimmen.", "This share is locked and allows only read access. Registering is not possible." : "Diese Freigabe ist gesperrt und erlaubt nur den Lesezugriff. Eine Registrierung ist nicht möglich.", "Voting is locked and you have just read access to this poll." : "Die Abstimmung ist gesperrt und Sie haben lediglich Lesezugriff auf diese Umfrage.", diff --git a/l10n/de_DE.json b/l10n/de_DE.json index fb9aaa79bf..81c24aa428 100644 --- a/l10n/de_DE.json +++ b/l10n/de_DE.json @@ -241,6 +241,7 @@ "Add some!" : "Fügen Sie einige hinzu!", "Edit access" : "Zugriff bearbeiten", "Register" : "Registrieren", + "Send confirmation mails" : "Bestätigungs-E-Mails senden", "See result" : "Ergebnis anzeigen", "Result of sent confirmation mails" : "Ergebnis der versendeten Bestätigungs-E-Mails", "No valid email address" : "Keine gültige E-Mail-Adresse", @@ -267,6 +268,11 @@ "No further action is possible." : "Keine weitere Aktion möglich.", "Due to possible performance issues {countHiddenParticipants} voters are hidden." : "Aufgrund möglicher Leistungsprobleme werden {countHiddenParticipants} Abstimmende ausgeblendet.", "You can reveal them, but you may expect an unwanted long loading time." : "Sie können diese zwar anzeigen, müssen aber mit einer ungewollt langen Ladezeit rechnen.", + "_%n orphaned vote reduces your vote quota._::_%n orphaned votes reduces your vote quota._" : ["%n verwaiste Stimme reduziert Ihre Stimmenquote.","%n verwaiste Stimmen reduzieren Ihre Stimmenquote."], + "No more voting options are available." : "Keine weiteren Abstimmungsmöglichkeiten mehr vorhanden.", + "_%n voting option is available._::_%n voting options are available._" : ["%n Abstimmungsmöglichkeit vorhanden","%n Abstimmungsmöglichkeiten vorhanden"], + "You have no votes left." : "Keine Stimmen mehr übrig.", + "_You have %n vote left out of {maxVotes}._::_You have %n votes left out of {maxVotes}._" : ["Es ist noch %n von {maxVotes} Stimme übrig.","Es sind noch %n von {maxVotes} Stimmen übrig."], "Limited votes." : "Begrenzte Stimmen.", "This share is locked and allows only read access. Registering is not possible." : "Diese Freigabe ist gesperrt und erlaubt nur den Lesezugriff. Eine Registrierung ist nicht möglich.", "Voting is locked and you have just read access to this poll." : "Die Abstimmung ist gesperrt und Sie haben lediglich Lesezugriff auf diese Umfrage.", diff --git a/l10n/et_EE.js b/l10n/et_EE.js index 14ccc35f55..7a755abc29 100644 --- a/l10n/et_EE.js +++ b/l10n/et_EE.js @@ -209,6 +209,7 @@ OC.L10N.register( "Add some!" : "Lisa mõni!", "Edit access" : "Muuda ligipääsu", "Register" : "Registreeru", + "Send confirmation mails" : "Saada kinnitused e-kirjaga", "See result" : "Vaata tulemust", "Result of sent confirmation mails" : "Saadetud kinnituskirjade kokkuvõte", "No valid email address" : "Pole korrektne e-posti aadress", @@ -235,6 +236,10 @@ OC.L10N.register( "No further action is possible." : "Täiendavaid toiminguid ei saa teha", "Due to possible performance issues {countHiddenParticipants} voters are hidden." : "Jõudlusprobleemide vältimiseks on {countHiddenParticipants} hääletajat peidetud.", "You can reveal them, but you may expect an unwanted long loading time." : "Sa võid paluda neid näidata, aga ole valmis üsna pikaks laadimisajaks.", + "No more voting options are available." : "Sul pole enam ühtegi hääletusvõimalust.", + "_%n voting option is available._::_%n voting options are available._" : ["Saadaval on %n hääletusvõimalus","Saadaval on %n hääletusvõimalust"], + "You have no votes left." : "Sul pole enam ühtegi häält.", + "_You have %n vote left out of {maxVotes}._::_You have %n votes left out of {maxVotes}._" : ["Kokku on sul {maxVotes} häält ning neist saad kasutada veel %n.","Kokku on sul {maxVotes} häält ning neist saad kasutada veel %n."], "Limited votes." : "Piiratud hääletamine.", "To participate, register with your email address and a name." : "Registreerimiseks sisesta oma e-posti aadress ja nimi.", "To participate, register a name and optionally with your email address." : "Registreerimiseks sisesta oma nimi ja soovi korral ka e-posti aadress.", diff --git a/l10n/et_EE.json b/l10n/et_EE.json index fd618810e7..0ead35932c 100644 --- a/l10n/et_EE.json +++ b/l10n/et_EE.json @@ -207,6 +207,7 @@ "Add some!" : "Lisa mõni!", "Edit access" : "Muuda ligipääsu", "Register" : "Registreeru", + "Send confirmation mails" : "Saada kinnitused e-kirjaga", "See result" : "Vaata tulemust", "Result of sent confirmation mails" : "Saadetud kinnituskirjade kokkuvõte", "No valid email address" : "Pole korrektne e-posti aadress", @@ -233,6 +234,10 @@ "No further action is possible." : "Täiendavaid toiminguid ei saa teha", "Due to possible performance issues {countHiddenParticipants} voters are hidden." : "Jõudlusprobleemide vältimiseks on {countHiddenParticipants} hääletajat peidetud.", "You can reveal them, but you may expect an unwanted long loading time." : "Sa võid paluda neid näidata, aga ole valmis üsna pikaks laadimisajaks.", + "No more voting options are available." : "Sul pole enam ühtegi hääletusvõimalust.", + "_%n voting option is available._::_%n voting options are available._" : ["Saadaval on %n hääletusvõimalus","Saadaval on %n hääletusvõimalust"], + "You have no votes left." : "Sul pole enam ühtegi häält.", + "_You have %n vote left out of {maxVotes}._::_You have %n votes left out of {maxVotes}._" : ["Kokku on sul {maxVotes} häält ning neist saad kasutada veel %n.","Kokku on sul {maxVotes} häält ning neist saad kasutada veel %n."], "Limited votes." : "Piiratud hääletamine.", "To participate, register with your email address and a name." : "Registreerimiseks sisesta oma e-posti aadress ja nimi.", "To participate, register a name and optionally with your email address." : "Registreerimiseks sisesta oma nimi ja soovi korral ka e-posti aadress.", diff --git a/l10n/ga.js b/l10n/ga.js index 8fcc59b913..6614e81a05 100644 --- a/l10n/ga.js +++ b/l10n/ga.js @@ -243,6 +243,7 @@ OC.L10N.register( "Add some!" : "Cuir roinnt!", "Edit access" : "Cuir rochtain in eagar", "Register" : "Clár", + "Send confirmation mails" : "Seol ríomhphoist deimhnithe", "See result" : "Féach toradh", "Result of sent confirmation mails" : "Toradh na dteachtaireachtaí deimhnithe seolta", "No valid email address" : "Níl seoladh ríomhphoist bailí ann", @@ -269,6 +270,11 @@ OC.L10N.register( "No further action is possible." : "Ní féidir a thuilleadh a dhéanamh.", "Due to possible performance issues {countHiddenParticipants} voters are hidden." : "De bharr fadhbanna feidhmíochta féideartha {countHiddenParticipants} tá na vótálaithe i bhfolach.", "You can reveal them, but you may expect an unwanted long loading time." : "Is féidir leat iad a nochtadh, ach b'fhéidir go mbeifeá ag súil le ham lódála fada nach dteastaíonn.", + "_%n orphaned vote reduces your vote quota._::_%n orphaned votes reduces your vote quota._" : ["Laghdaíonn %n vóta dílleachta do chuóta vótaí.","Laghdaíonn %n vótaí dílleachta do chuóta vótaí.","Laghdaíonn %n vótaí dílleachta do chuóta vótaí.","Laghdaíonn %n vótaí dílleachta do chuóta vótaí.","Laghdaíonn %n vótaí dílleachta do chuóta vótaí."], + "No more voting options are available." : "Níl aon roghanna vótála eile ar fáil.", + "_%n voting option is available._::_%n voting options are available._" : ["Tá %n rogha vótála ar fáil.","Tá %n roghanna vótála ar fáil.","Tá %n roghanna vótála ar fáil.","Tá %n roghanna vótála ar fáil.","Tá %n roghanna vótála ar fáil."], + "You have no votes left." : "Níl aon vótaí fágtha agat.", + "_You have %n vote left out of {maxVotes}._::_You have %n votes left out of {maxVotes}._" : ["Tá %n vóta fágtha agat as {maxVotes}.","Tá %n vóta fágtha agat as {maxVotes}.","Tá %n vóta fágtha agat as {maxVotes}.","Tá %n vóta fágtha agat as {maxVotes}.","Tá %n vóta fágtha agat as {maxVotes}."], "Limited votes." : "Vótaí teoranta.", "This share is locked and allows only read access. Registering is not possible." : "Tá an sciar seo faoi ghlas agus ní cheadaíonn sé ach rochtain léite. Ní féidir clárú.", "Voting is locked and you have just read access to this poll." : "Tá an vótáil faoi ghlas agus tá rochtain ar an vótaíocht seo díreach léite agat.", diff --git a/l10n/ga.json b/l10n/ga.json index fb800a7ecc..407c0aa219 100644 --- a/l10n/ga.json +++ b/l10n/ga.json @@ -241,6 +241,7 @@ "Add some!" : "Cuir roinnt!", "Edit access" : "Cuir rochtain in eagar", "Register" : "Clár", + "Send confirmation mails" : "Seol ríomhphoist deimhnithe", "See result" : "Féach toradh", "Result of sent confirmation mails" : "Toradh na dteachtaireachtaí deimhnithe seolta", "No valid email address" : "Níl seoladh ríomhphoist bailí ann", @@ -267,6 +268,11 @@ "No further action is possible." : "Ní féidir a thuilleadh a dhéanamh.", "Due to possible performance issues {countHiddenParticipants} voters are hidden." : "De bharr fadhbanna feidhmíochta féideartha {countHiddenParticipants} tá na vótálaithe i bhfolach.", "You can reveal them, but you may expect an unwanted long loading time." : "Is féidir leat iad a nochtadh, ach b'fhéidir go mbeifeá ag súil le ham lódála fada nach dteastaíonn.", + "_%n orphaned vote reduces your vote quota._::_%n orphaned votes reduces your vote quota._" : ["Laghdaíonn %n vóta dílleachta do chuóta vótaí.","Laghdaíonn %n vótaí dílleachta do chuóta vótaí.","Laghdaíonn %n vótaí dílleachta do chuóta vótaí.","Laghdaíonn %n vótaí dílleachta do chuóta vótaí.","Laghdaíonn %n vótaí dílleachta do chuóta vótaí."], + "No more voting options are available." : "Níl aon roghanna vótála eile ar fáil.", + "_%n voting option is available._::_%n voting options are available._" : ["Tá %n rogha vótála ar fáil.","Tá %n roghanna vótála ar fáil.","Tá %n roghanna vótála ar fáil.","Tá %n roghanna vótála ar fáil.","Tá %n roghanna vótála ar fáil."], + "You have no votes left." : "Níl aon vótaí fágtha agat.", + "_You have %n vote left out of {maxVotes}._::_You have %n votes left out of {maxVotes}._" : ["Tá %n vóta fágtha agat as {maxVotes}.","Tá %n vóta fágtha agat as {maxVotes}.","Tá %n vóta fágtha agat as {maxVotes}.","Tá %n vóta fágtha agat as {maxVotes}.","Tá %n vóta fágtha agat as {maxVotes}."], "Limited votes." : "Vótaí teoranta.", "This share is locked and allows only read access. Registering is not possible." : "Tá an sciar seo faoi ghlas agus ní cheadaíonn sé ach rochtain léite. Ní féidir clárú.", "Voting is locked and you have just read access to this poll." : "Tá an vótáil faoi ghlas agus tá rochtain ar an vótaíocht seo díreach léite agat.", diff --git a/l10n/hu.js b/l10n/hu.js index 9faad6f93b..75d2dea8d4 100644 --- a/l10n/hu.js +++ b/l10n/hu.js @@ -320,6 +320,7 @@ OC.L10N.register( "_%n No vote_::_%n \"No\" votes_" : ["%n „Nem” szavazat","%n „Nem” szavazat"], "_%n \"Maybe\" vote_::_%n \"Maybe\" votes_" : ["%n „Talán” szavazat","%n „Talán” szavazat"], "No description provided" : "Nincs leírás megadva", + "Archived {relativeTime}" : "Archiválva: {relativeTime}", "Expiration" : "Lejárat", "Error archiving/restoring poll." : "Hiba a szavazás archiválása/helyreállítása során.", "Error cloning poll." : "Hiba a szavazás klónozása során.", diff --git a/l10n/hu.json b/l10n/hu.json index 8d4b50ef2c..f459a805f8 100644 --- a/l10n/hu.json +++ b/l10n/hu.json @@ -318,6 +318,7 @@ "_%n No vote_::_%n \"No\" votes_" : ["%n „Nem” szavazat","%n „Nem” szavazat"], "_%n \"Maybe\" vote_::_%n \"Maybe\" votes_" : ["%n „Talán” szavazat","%n „Talán” szavazat"], "No description provided" : "Nincs leírás megadva", + "Archived {relativeTime}" : "Archiválva: {relativeTime}", "Expiration" : "Lejárat", "Error archiving/restoring poll." : "Hiba a szavazás archiválása/helyreállítása során.", "Error cloning poll." : "Hiba a szavazás klónozása során.", diff --git a/l10n/it.js b/l10n/it.js index 4f7b7b1e12..8cd4e3b48b 100644 --- a/l10n/it.js +++ b/l10n/it.js @@ -243,6 +243,7 @@ OC.L10N.register( "Add some!" : "Aggiungine qualcuna!", "Edit access" : "Modifica accesso", "Register" : "Registra", + "Send confirmation mails" : "Invia email di conferma", "See result" : "Vedi risultato", "Result of sent confirmation mails" : "Risultato delle mail di conferma inviate", "No valid email address" : "Nessun indirizzo email valido", @@ -269,6 +270,11 @@ OC.L10N.register( "No further action is possible." : "Non è possibile intraprendere ulteriori azioni.", "Due to possible performance issues {countHiddenParticipants} voters are hidden." : "A causa di possibili problemi di prestazioni {countHiddenParticipants} i votanti sono nascosti.", "You can reveal them, but you may expect an unwanted long loading time." : "È possibile visualizzarli, ma si rischia di ottenere un tempo di caricamento lungo e indesiderato.", + "_%n orphaned vote reduces your vote quota._::_%n orphaned votes reduces your vote quota._" : ["%n voto orfano riduce la tua quota di voto.","%n voti orfani riduce la tua quota di voti.","%n orphaned votes reduces your vote quota."], + "No more voting options are available." : "Non sono più disponibili opzioni di voto.", + "_%n voting option is available._::_%n voting options are available._" : ["%n è disponibile l'opzione di voto.","%nsono disponibili opzioni di voto.","%n sono disponibili opzioni di voto."], + "You have no votes left." : "Non hai ancora voti.", + "_You have %n vote left out of {maxVotes}._::_You have %n votes left out of {maxVotes}._" : ["Tu hai %n voto rimasto di {maxVotes}.","Tu hai %n voti rimasti di {maxVotes}.","You have %n votes left out of {maxVotes}."], "Limited votes." : "Voti limitati.", "This share is locked and allows only read access. Registering is not possible." : "Questa condivisione è bloccata e consente solo l'accesso in lettura. La registrazione non è possibile.", "Voting is locked and you have just read access to this poll." : "La votazione è bloccata e hai solo accesso in lettura a questo sondaggio.", diff --git a/l10n/it.json b/l10n/it.json index c550af487d..83ad1eeb85 100644 --- a/l10n/it.json +++ b/l10n/it.json @@ -241,6 +241,7 @@ "Add some!" : "Aggiungine qualcuna!", "Edit access" : "Modifica accesso", "Register" : "Registra", + "Send confirmation mails" : "Invia email di conferma", "See result" : "Vedi risultato", "Result of sent confirmation mails" : "Risultato delle mail di conferma inviate", "No valid email address" : "Nessun indirizzo email valido", @@ -267,6 +268,11 @@ "No further action is possible." : "Non è possibile intraprendere ulteriori azioni.", "Due to possible performance issues {countHiddenParticipants} voters are hidden." : "A causa di possibili problemi di prestazioni {countHiddenParticipants} i votanti sono nascosti.", "You can reveal them, but you may expect an unwanted long loading time." : "È possibile visualizzarli, ma si rischia di ottenere un tempo di caricamento lungo e indesiderato.", + "_%n orphaned vote reduces your vote quota._::_%n orphaned votes reduces your vote quota._" : ["%n voto orfano riduce la tua quota di voto.","%n voti orfani riduce la tua quota di voti.","%n orphaned votes reduces your vote quota."], + "No more voting options are available." : "Non sono più disponibili opzioni di voto.", + "_%n voting option is available._::_%n voting options are available._" : ["%n è disponibile l'opzione di voto.","%nsono disponibili opzioni di voto.","%n sono disponibili opzioni di voto."], + "You have no votes left." : "Non hai ancora voti.", + "_You have %n vote left out of {maxVotes}._::_You have %n votes left out of {maxVotes}._" : ["Tu hai %n voto rimasto di {maxVotes}.","Tu hai %n voti rimasti di {maxVotes}.","You have %n votes left out of {maxVotes}."], "Limited votes." : "Voti limitati.", "This share is locked and allows only read access. Registering is not possible." : "Questa condivisione è bloccata e consente solo l'accesso in lettura. La registrazione non è possibile.", "Voting is locked and you have just read access to this poll." : "La votazione è bloccata e hai solo accesso in lettura a questo sondaggio.", diff --git a/l10n/lv.js b/l10n/lv.js index 4d6bfca0c2..6d6ab0188d 100644 --- a/l10n/lv.js +++ b/l10n/lv.js @@ -45,7 +45,7 @@ OC.L10N.register( "No description provided" : "Apraksts nav sniegts", "Expiration" : "Termiņš", "Privacy policy" : "Privātuma politika", - "Login" : "Pieteikumvārds", + "Login" : "Pieteikties", "Days after which polls should be archived after closing" : "Dienu skaits pēc aizvēršanas, pēc kurām aptaujas vajadzētu arhivēt", "Enable the automatic deletion of archived polls" : "Iespējot arhivētu aptauju automātisku izdzēšanu", "Privacy policy link:" : "Privātuma politikas saite:", diff --git a/l10n/lv.json b/l10n/lv.json index 45f0db2595..3c9454de57 100644 --- a/l10n/lv.json +++ b/l10n/lv.json @@ -43,7 +43,7 @@ "No description provided" : "Apraksts nav sniegts", "Expiration" : "Termiņš", "Privacy policy" : "Privātuma politika", - "Login" : "Pieteikumvārds", + "Login" : "Pieteikties", "Days after which polls should be archived after closing" : "Dienu skaits pēc aizvēršanas, pēc kurām aptaujas vajadzētu arhivēt", "Enable the automatic deletion of archived polls" : "Iespējot arhivētu aptauju automātisku izdzēšanu", "Privacy policy link:" : "Privātuma politikas saite:", diff --git a/l10n/pt_BR.js b/l10n/pt_BR.js index c95d29a28d..a22b7c4d6d 100644 --- a/l10n/pt_BR.js +++ b/l10n/pt_BR.js @@ -243,6 +243,7 @@ OC.L10N.register( "Add some!" : "Adicione alguma!", "Edit access" : "Editar acesso", "Register" : "Registrar-se", + "Send confirmation mails" : "Enviar e-mails de confirmação", "See result" : "Ver resultado", "Result of sent confirmation mails" : "Resultado dos e-mails de confirmação enviados", "No valid email address" : "Nenhum endereço de e-mail válido", @@ -269,6 +270,11 @@ OC.L10N.register( "No further action is possible." : "Nenhuma ação adicional é possível.", "Due to possible performance issues {countHiddenParticipants} voters are hidden." : "Devido a possíveis problemas de desempenho, {countHiddenParticipants} eleitores estão ocultos.", "You can reveal them, but you may expect an unwanted long loading time." : "Você pode revelá-los, mas pode esperar um longo tempo de carregamento indesejado.", + "_%n orphaned vote reduces your vote quota._::_%n orphaned votes reduces your vote quota._" : ["%n voto órfão reduz sua cota de votos.","%n de votos órfãos reduzem sua cota de votos.","%n votos órfãos reduzem sua cota de votos."], + "No more voting options are available." : "Não há mais opções de voto disponíveis.", + "_%n voting option is available._::_%n voting options are available._" : ["Está disponível %n opção de voto.","Estão disponíveis %n de opções de voto.","Estão disponíveis %n opções de voto."], + "You have no votes left." : "Você não tem mais votos.", + "_You have %n vote left out of {maxVotes}._::_You have %n votes left out of {maxVotes}._" : ["Você tem %n voto restante de {maxVotes}.","Você tem %n de votos restantes de {maxVotes}.","Você tem %n votos restantes de {maxVotes}."], "Limited votes." : "Votos limitados.", "This share is locked and allows only read access. Registering is not possible." : "Este compartilhamento está bloqueado e permite apenas acesso de leitura. O registro não é possível.", "Voting is locked and you have just read access to this poll." : "A votação está bloqueada e você só tem acesso de leitura a esta enquete.", @@ -681,7 +687,7 @@ OC.L10N.register( "Details" : "Detalhes", "Configuration" : "Configuração", "Options" : "Opções", - "Sharing" : "Compartilhando", + "Sharing" : "Compartilhamento", "Activity" : "Atividade", "Select polls to combine" : "Selecione as enquetes para unir", "Search for conflicting calendar entries" : "Pesquisar entradas de calendário conflitantes", diff --git a/l10n/pt_BR.json b/l10n/pt_BR.json index 0b1721dd66..ddf0c0eaf5 100644 --- a/l10n/pt_BR.json +++ b/l10n/pt_BR.json @@ -241,6 +241,7 @@ "Add some!" : "Adicione alguma!", "Edit access" : "Editar acesso", "Register" : "Registrar-se", + "Send confirmation mails" : "Enviar e-mails de confirmação", "See result" : "Ver resultado", "Result of sent confirmation mails" : "Resultado dos e-mails de confirmação enviados", "No valid email address" : "Nenhum endereço de e-mail válido", @@ -267,6 +268,11 @@ "No further action is possible." : "Nenhuma ação adicional é possível.", "Due to possible performance issues {countHiddenParticipants} voters are hidden." : "Devido a possíveis problemas de desempenho, {countHiddenParticipants} eleitores estão ocultos.", "You can reveal them, but you may expect an unwanted long loading time." : "Você pode revelá-los, mas pode esperar um longo tempo de carregamento indesejado.", + "_%n orphaned vote reduces your vote quota._::_%n orphaned votes reduces your vote quota._" : ["%n voto órfão reduz sua cota de votos.","%n de votos órfãos reduzem sua cota de votos.","%n votos órfãos reduzem sua cota de votos."], + "No more voting options are available." : "Não há mais opções de voto disponíveis.", + "_%n voting option is available._::_%n voting options are available._" : ["Está disponível %n opção de voto.","Estão disponíveis %n de opções de voto.","Estão disponíveis %n opções de voto."], + "You have no votes left." : "Você não tem mais votos.", + "_You have %n vote left out of {maxVotes}._::_You have %n votes left out of {maxVotes}._" : ["Você tem %n voto restante de {maxVotes}.","Você tem %n de votos restantes de {maxVotes}.","Você tem %n votos restantes de {maxVotes}."], "Limited votes." : "Votos limitados.", "This share is locked and allows only read access. Registering is not possible." : "Este compartilhamento está bloqueado e permite apenas acesso de leitura. O registro não é possível.", "Voting is locked and you have just read access to this poll." : "A votação está bloqueada e você só tem acesso de leitura a esta enquete.", @@ -679,7 +685,7 @@ "Details" : "Detalhes", "Configuration" : "Configuração", "Options" : "Opções", - "Sharing" : "Compartilhando", + "Sharing" : "Compartilhamento", "Activity" : "Atividade", "Select polls to combine" : "Selecione as enquetes para unir", "Search for conflicting calendar entries" : "Pesquisar entradas de calendário conflitantes", diff --git a/l10n/sr.js b/l10n/sr.js index 67080bf1d0..778da5da85 100644 --- a/l10n/sr.js +++ b/l10n/sr.js @@ -243,6 +243,7 @@ OC.L10N.register( "Add some!" : "Додајте неку!", "Edit access" : "Уреди приступ", "Register" : "Региструј се", + "Send confirmation mails" : "Пошаљи мејлове потврде", "See result" : "Погледајте резултат", "Result of sent confirmation mails" : "Резултат послатих мејлова потврде", "No valid email address" : "Нема исправне и-мејл адресе", @@ -269,6 +270,11 @@ OC.L10N.register( "No further action is possible." : "Нису могуће накнадне акције.", "Due to possible performance issues {countHiddenParticipants} voters are hidden." : "{countHiddenParticipants} гласача је сакривено због забринутости о могућим проблемима са перформансама.", "You can reveal them, but you may expect an unwanted long loading time." : "Можете да их откријете, али можете да очекујете непожељна дугачка времена учитавања.", + "_%n orphaned vote reduces your vote quota._::_%n orphaned votes reduces your vote quota._" : ["%n глас сироче умањује вашу квоту гласова.","%n гласа сирочета умањује вашу квоту гласова.","%n гласова сирочета умањује вашу квоту гласова."], + "No more voting options are available." : "Нема више доступних опција гласања.", + "_%n voting option is available._::_%n voting options are available._" : ["Доступна је %n опција гласања.","Доступне су %n опције гласања.","Доступно је %n опција гласања."], + "You have no votes left." : "Немате преосталих гласова.", + "_You have %n vote left out of {maxVotes}._::_You have %n votes left out of {maxVotes}._" : ["Преостао вам је %n глас од {maxVotes} укупно.","Преостала су вам %n гласа од {maxVotes} укупно.","Преостало вам је %n гласова од {maxVotes} укупно."], "Limited votes." : "Ограничени гласови.", "This share is locked and allows only read access. Registering is not possible." : "Ово дељење је закључано и можете само да га читате. Регистрација није могућа.", "Voting is locked and you have just read access to this poll." : "Гласање је закључано и имате право само да читате ово гласање.", @@ -691,7 +697,7 @@ OC.L10N.register( "Some visual styling options." : "Неке опције за промену визуелног стила.", "We are sorry, but there are no more vote options available" : "Жао нам је, али више нема доступних опција за гласање", "All options are booked up." : "Све опције су заузете.", - "No vote options available" : "Нема достиупних опција гласања", + "No vote options available" : "Нема доступних опција гласања", "Maybe the owner did not provide some until now." : "Можда их власник још увек није понудио.", "Add options" : "Додај опције", "Minute" : "Минут", diff --git a/l10n/sr.json b/l10n/sr.json index d8e324ded6..c856685e2c 100644 --- a/l10n/sr.json +++ b/l10n/sr.json @@ -241,6 +241,7 @@ "Add some!" : "Додајте неку!", "Edit access" : "Уреди приступ", "Register" : "Региструј се", + "Send confirmation mails" : "Пошаљи мејлове потврде", "See result" : "Погледајте резултат", "Result of sent confirmation mails" : "Резултат послатих мејлова потврде", "No valid email address" : "Нема исправне и-мејл адресе", @@ -267,6 +268,11 @@ "No further action is possible." : "Нису могуће накнадне акције.", "Due to possible performance issues {countHiddenParticipants} voters are hidden." : "{countHiddenParticipants} гласача је сакривено због забринутости о могућим проблемима са перформансама.", "You can reveal them, but you may expect an unwanted long loading time." : "Можете да их откријете, али можете да очекујете непожељна дугачка времена учитавања.", + "_%n orphaned vote reduces your vote quota._::_%n orphaned votes reduces your vote quota._" : ["%n глас сироче умањује вашу квоту гласова.","%n гласа сирочета умањује вашу квоту гласова.","%n гласова сирочета умањује вашу квоту гласова."], + "No more voting options are available." : "Нема више доступних опција гласања.", + "_%n voting option is available._::_%n voting options are available._" : ["Доступна је %n опција гласања.","Доступне су %n опције гласања.","Доступно је %n опција гласања."], + "You have no votes left." : "Немате преосталих гласова.", + "_You have %n vote left out of {maxVotes}._::_You have %n votes left out of {maxVotes}._" : ["Преостао вам је %n глас од {maxVotes} укупно.","Преостала су вам %n гласа од {maxVotes} укупно.","Преостало вам је %n гласова од {maxVotes} укупно."], "Limited votes." : "Ограничени гласови.", "This share is locked and allows only read access. Registering is not possible." : "Ово дељење је закључано и можете само да га читате. Регистрација није могућа.", "Voting is locked and you have just read access to this poll." : "Гласање је закључано и имате право само да читате ово гласање.", @@ -689,7 +695,7 @@ "Some visual styling options." : "Неке опције за промену визуелног стила.", "We are sorry, but there are no more vote options available" : "Жао нам је, али више нема доступних опција за гласање", "All options are booked up." : "Све опције су заузете.", - "No vote options available" : "Нема достиупних опција гласања", + "No vote options available" : "Нема доступних опција гласања", "Maybe the owner did not provide some until now." : "Можда их власник још увек није понудио.", "Add options" : "Додај опције", "Minute" : "Минут", diff --git a/l10n/sw.js b/l10n/sw.js new file mode 100644 index 0000000000..16bc2c7351 --- /dev/null +++ b/l10n/sw.js @@ -0,0 +1,42 @@ +OC.L10N.register( + "polls", + { + "Team" : "Timu", + "Group" : "Kundi", + "Delete" : "Futa", + "Unknown error" : "Hitilafu isiyojulikana", + "Previous day" : "Siku iliyopita", + "Next day" : "Siku inayofuata", + "Add" : "Ongeza", + "Cancel" : "Cancel", + "Close" : "Funga", + "Yes" : "Ndiyo", + "No" : "Hapana", + "From" : "Tangu/ kutoka", + "To" : "Mpaka/ hadi", + "Email address" : "Anwani ya barua pepe", + "OK" : "OK", + "All day" : "Siku yote", + "Duration" : "Muda", + "Preview" : "Mwonekano wa awali", + "Submit" : "Wasilisha", + "{optionText} already exists" : "{optionText} already exists", + "never" : "kamwe", + "Login" : "Ingia", + "Show QR code" : "Onesha msimbo wa QR", + "Description" : "Maelezo", + "Switch to list view" : "Switch to list view", + "Comments" : "Maoni", + "Details" : "Maelezo ya kina", + "Options" : "Chaguzi", + "Sharing" : "inashirikisha", + "Activity" : "Shughuli", + "Add options" : "Ongeza chaguzi", + "Day" : "Siku", + "Week" : "Wiki", + "Month" : "Mwezi", + "Year" : "Mwaka", + "Created" : "Imetengenezwa", + "Owner" : "Mmiliki" +}, +"nplurals=2; plural=(n != 1);"); diff --git a/l10n/sw.json b/l10n/sw.json new file mode 100644 index 0000000000..3af17fa297 --- /dev/null +++ b/l10n/sw.json @@ -0,0 +1,40 @@ +{ "translations": { + "Team" : "Timu", + "Group" : "Kundi", + "Delete" : "Futa", + "Unknown error" : "Hitilafu isiyojulikana", + "Previous day" : "Siku iliyopita", + "Next day" : "Siku inayofuata", + "Add" : "Ongeza", + "Cancel" : "Cancel", + "Close" : "Funga", + "Yes" : "Ndiyo", + "No" : "Hapana", + "From" : "Tangu/ kutoka", + "To" : "Mpaka/ hadi", + "Email address" : "Anwani ya barua pepe", + "OK" : "OK", + "All day" : "Siku yote", + "Duration" : "Muda", + "Preview" : "Mwonekano wa awali", + "Submit" : "Wasilisha", + "{optionText} already exists" : "{optionText} already exists", + "never" : "kamwe", + "Login" : "Ingia", + "Show QR code" : "Onesha msimbo wa QR", + "Description" : "Maelezo", + "Switch to list view" : "Switch to list view", + "Comments" : "Maoni", + "Details" : "Maelezo ya kina", + "Options" : "Chaguzi", + "Sharing" : "inashirikisha", + "Activity" : "Shughuli", + "Add options" : "Ongeza chaguzi", + "Day" : "Siku", + "Week" : "Wiki", + "Month" : "Mwezi", + "Year" : "Mwaka", + "Created" : "Imetengenezwa", + "Owner" : "Mmiliki" +},"pluralForm" :"nplurals=2; plural=(n != 1);" +} \ No newline at end of file diff --git a/l10n/zh_TW.js b/l10n/zh_TW.js index b3d1b9512c..5dec7c58e2 100644 --- a/l10n/zh_TW.js +++ b/l10n/zh_TW.js @@ -243,6 +243,7 @@ OC.L10N.register( "Add some!" : "新增一些!", "Edit access" : "編輯存取權", "Register" : "註冊", + "Send confirmation mails" : "傳送確認郵件", "See result" : "查看結果", "Result of sent confirmation mails" : "傳送確認電子郵件的結果", "No valid email address" : "無有效的電子郵件地址", @@ -269,6 +270,11 @@ OC.L10N.register( "No further action is possible." : "無法採取進一步行動。", "Due to possible performance issues {countHiddenParticipants} voters are hidden." : "由於效能問題,隱藏了 {countHiddenParticipants} 個投票者。", "You can reveal them, but you may expect an unwanted long loading time." : "您可以顯示它們,但您可能會遇到不必要的長載入時間。", + "_%n orphaned vote reduces your vote quota._::_%n orphaned votes reduces your vote quota._" : ["%n 個無主投票會減少您的投票配額。"], + "No more voting options are available." : "沒有更多投票選項。", + "_%n voting option is available._::_%n voting options are available._" : ["有 %n 個投票選項。"], + "You have no votes left." : "您已經沒有選票了。", + "_You have %n vote left out of {maxVotes}._::_You have %n votes left out of {maxVotes}._" : ["您共有 {maxVotes} 票,還剩 %n 票。"], "Limited votes." : "已限制投票。", "This share is locked and allows only read access. Registering is not possible." : "此分享已被鎖定並且僅允許唯讀存取。無法註冊。", "Voting is locked and you have just read access to this poll." : "投票已鎖定,只有您可以讀取此投票。", diff --git a/l10n/zh_TW.json b/l10n/zh_TW.json index 5cfab6e0e4..87e76a2ae4 100644 --- a/l10n/zh_TW.json +++ b/l10n/zh_TW.json @@ -241,6 +241,7 @@ "Add some!" : "新增一些!", "Edit access" : "編輯存取權", "Register" : "註冊", + "Send confirmation mails" : "傳送確認郵件", "See result" : "查看結果", "Result of sent confirmation mails" : "傳送確認電子郵件的結果", "No valid email address" : "無有效的電子郵件地址", @@ -267,6 +268,11 @@ "No further action is possible." : "無法採取進一步行動。", "Due to possible performance issues {countHiddenParticipants} voters are hidden." : "由於效能問題,隱藏了 {countHiddenParticipants} 個投票者。", "You can reveal them, but you may expect an unwanted long loading time." : "您可以顯示它們,但您可能會遇到不必要的長載入時間。", + "_%n orphaned vote reduces your vote quota._::_%n orphaned votes reduces your vote quota._" : ["%n 個無主投票會減少您的投票配額。"], + "No more voting options are available." : "沒有更多投票選項。", + "_%n voting option is available._::_%n voting options are available._" : ["有 %n 個投票選項。"], + "You have no votes left." : "您已經沒有選票了。", + "_You have %n vote left out of {maxVotes}._::_You have %n votes left out of {maxVotes}._" : ["您共有 {maxVotes} 票,還剩 %n 票。"], "Limited votes." : "已限制投票。", "This share is locked and allows only read access. Registering is not possible." : "此分享已被鎖定並且僅允許唯讀存取。無法註冊。", "Voting is locked and you have just read access to this poll." : "投票已鎖定,只有您可以讀取此投票。", diff --git a/lib/Command/Command.php b/lib/Command/Command.php index 2cd1928ca3..06ca6cd277 100644 --- a/lib/Command/Command.php +++ b/lib/Command/Command.php @@ -28,7 +28,7 @@ class Command extends \Symfony\Component\Console\Command\Command { protected InputInterface $input; protected OutputInterface $output; protected ConfirmationQuestion $question; - + public function __construct( ) { parent::__construct(); diff --git a/lib/Command/Db/CleanMigrations.php b/lib/Command/Db/CleanMigrations.php index 2cefe2fea5..95fedcc261 100644 --- a/lib/Command/Db/CleanMigrations.php +++ b/lib/Command/Db/CleanMigrations.php @@ -39,7 +39,7 @@ protected function runCommands(): int { $this->printComment('Remove migration entries from migration table'); $this->connection->migrateToSchema($this->schema); - + return 0; } } diff --git a/lib/Command/Share/Add.php b/lib/Command/Share/Add.php index a9bcd70d9e..913cd5eda3 100644 --- a/lib/Command/Share/Add.php +++ b/lib/Command/Share/Add.php @@ -93,7 +93,7 @@ private function inviteUsers(Poll $poll, array $userIds): void { } } } - + /** * @param Poll $poll * @param string[] $groupIds diff --git a/lib/Controller/PageController.php b/lib/Controller/PageController.php index dbd1ad1aa3..a7b6fc229f 100644 --- a/lib/Controller/PageController.php +++ b/lib/Controller/PageController.php @@ -44,6 +44,7 @@ public function __construct( #[FrontpageRoute(verb: 'GET', url: '/combo', postfix: 'combo')] #[FrontpageRoute(verb: 'GET', url: '/not-found', postfix: 'notFound')] #[FrontpageRoute(verb: 'GET', url: '/list/{category}', postfix: 'list')] + #[FrontpageRoute(verb: 'GET', url: '/group/{slug}', postfix: 'group')] public function index(): TemplateResponse { Util::addScript(AppConstants::APP_ID, 'polls-main'); $this->eventDispatcher->dispatchTyped(new LoadAdditionalScriptsEvent()); diff --git a/lib/Controller/PollApiController.php b/lib/Controller/PollApiController.php index eefd704ed0..4c7b4ed90c 100644 --- a/lib/Controller/PollApiController.php +++ b/lib/Controller/PollApiController.php @@ -43,13 +43,83 @@ public function __construct( /** * Get list of polls + * + * psalm-return DataResponse */ #[CORS] #[NoAdminRequired] #[NoCSRFRequired] #[ApiRoute(verb: 'GET', url: '/api/v1.0/polls', requirements: ['apiVersion' => '(v2)'])] - public function list(): DataResponse { - return $this->response(fn () => ['polls' => $this->pollService->list()]); + public function listPolls(): DataResponse { + return $this->response(fn () => ['polls' => $this->pollService->listPolls()]); + } + + /** + * Get list of pollgroups + * + * psalm-return DataResponse }> + */ + #[CORS] + #[NoAdminRequired] + #[NoCSRFRequired] + #[ApiRoute(verb: 'GET', url: '/api/v1.0/pollgroups', requirements: ['apiVersion' => '(v2)'])] + public function listPollGroups(): DataResponse { + return $this->response(fn () => ['pollGroups' => $this->pollService->listPollGroups()]); + } + + /** + * Create a new pollgroup with its title and add a poll to it + * + * @param int $pollId Poll id to add to the new pollgroup + * @param string $newPollGroupName Name of the new pollgroup + * + * psalm-return JSONResponse + */ + #[CORS] + #[NoAdminRequired] + #[NoCSRFRequired] + #[ApiRoute(verb: 'POST', url: '/api/v1.0/pollgroup/new/poll/{pollId}', requirements: ['apiVersion' => '(v2)'])] + public function addPollToNewPollGroup(int $pollId, string $newPollGroupName = ''): DataResponse { + return $this->response(fn () => [ + 'pollGroup' => $this->pollService->addPollToPollGroup($pollId, newPollGroupName: $newPollGroupName), + 'poll' => $this->pollService->get($pollId), + ]); + } + + /** + * Add poll to a group + * @param int $pollGroupId Pollgroup id + * @param int $pollId Poll id + * + * psalm-return DataResponse + */ + #[CORS] + #[NoAdminRequired] + #[NoCSRFRequired] + #[ApiRoute(verb: 'PUT', url: '/api/v1.0/pollgroup/{pollGroupId}/poll/{pollId}', requirements: ['apiVersion' => '(v2)'])] + public function addPollToPollGroup(int $pollId, int $pollGroupId): DataResponse { + return $this->response(fn () => [ + 'pollGroup' => $this->pollService->addPollToPollGroup($pollId, $pollGroupId), + 'poll' => $this->pollService->get($pollId), + ]); + } + + /** + * Remove poll from a group + * @param int $pollGroupId Pollgroup id + * @param int $pollId Poll id + * + * psalm-return DataResponse + */ + #[CORS] + #[NoAdminRequired] + #[NoCSRFRequired] + #[ApiRoute(verb: 'DELETE', url: '/api/v1.0/pollgroup/{pollGroupId}/poll/{pollId}', requirements: ['apiVersion' => '(v2)'])] + public function removePollFromPollGroup(int $pollId, int $pollGroupId): DataResponse { + return $this->response(fn () => [ + 'pollgroup' => $this->pollService->removePollFromPollGroup($pollId, $pollGroupId), + 'poll' => $this->pollService->get($pollId), + ]); } /** diff --git a/lib/Controller/PollController.php b/lib/Controller/PollController.php index 766bb0eab9..d3a29969a7 100644 --- a/lib/Controller/PollController.php +++ b/lib/Controller/PollController.php @@ -44,18 +44,27 @@ public function __construct( /** * Get list of polls + * psalm-return JSONResponse, + * permissions: array{ + * pollCreationAllowed: bool, + * comboAllowed: bool + * }, + * pollGroups: array + * }> */ #[NoAdminRequired] #[FrontpageRoute(verb: 'GET', url: '/polls')] - public function list(): JSONResponse { + public function listPolls(): JSONResponse { return $this->response(function () { $appSettings = Container::queryClass(AppSettings::class); return [ - 'list' => $this->pollService->list(), + 'polls' => $this->pollService->listPolls(), 'permissions' => [ 'pollCreationAllowed' => $appSettings->getPollCreationAllowed(), 'comboAllowed' => $appSettings->getComboAllowed(), ], + 'pollGroups' => $this->pollService->listPollGroups(), ]; }); } @@ -63,6 +72,8 @@ public function list(): JSONResponse { /** * get poll * @param int $pollId Poll id + * + * psalm-return JSONResponse */ #[NoAdminRequired] #[FrontpageRoute(verb: 'GET', url: '/poll/{pollId}/poll')] @@ -75,6 +86,16 @@ public function get(int $pollId): JSONResponse { /** * get complete poll * @param int $pollId Poll id + * + * psalm-return JSONResponse, + * votes: array, + * comments: array, + * shares: array, + * subscribed: Subscription|null + * }> + * */ #[NoAdminRequired] #[FrontpageRoute(verb: 'GET', url: '/poll/{pollId}')] @@ -93,6 +114,9 @@ public function getFull(int $pollId): JSONResponse { * Add poll * @param string $title Poll title * @param string $type Poll type ('datePoll', 'textPoll') + * @param string $votingVariant Voting variant (default: Poll::VARIANT_SIMPLE) + * + * psalm-return JSONResponse */ #[NoAdminRequired] #[FrontpageRoute(verb: 'POST', url: '/poll/add')] @@ -109,6 +133,8 @@ public function add(string $type, string $title, string $votingVariant = Poll::V * Update poll configuration * @param int $pollId Poll id * @param array $poll poll config + * + * psalm-return JSONResponse */ #[NoAdminRequired] #[FrontpageRoute(verb: 'PUT', url: '/poll/{pollId}')] @@ -238,4 +264,84 @@ public function changeOwner(int $pollId, string $targetUserId): JSONResponse { public function getParticipantsEmailAddresses(int $pollId): JSONResponse { return $this->response(fn () => $this->pollService->getParticipantsEmailAddresses($pollId)); } + + /** + * Get list of pollgroups + * + * psalm-return JSONResponse}> + */ + #[NoAdminRequired] + #[FrontpageRoute(verb: 'GET', url: '/pollgroups')] + public function listPollGroups(): JSONResponse { + return $this->response(function () { + return [ + 'pollGroups' => $this->pollService->listPollGroups(), + ]; + }); + } + + /** + * Create a new pollgroup with its title and add a poll to it + * + * @param int $pollId Poll id to add to the new pollgroup + * @param string $newPollGroupName Name of the new pollgroup + * + * psalm-return JSONResponse + */ + #[NoAdminRequired] + #[FrontpageRoute(verb: 'POST', url: '/pollgroup/new/poll/{pollId}')] + public function addPollToNewPollGroup(int $pollId, string $newPollGroupName = ''): JSONResponse { + return $this->response(fn () => [ + 'pollGroup' => $this->pollService->addPollToPollGroup($pollId, newPollGroupName: $newPollGroupName), + 'poll' => $this->pollService->get($pollId), + ]); + } + + /** + * Add poll to pollgroup + * @param int $pollId Poll id + * @param int $pollGroupId Poll group id + * + * psalm-return JSONResponse + */ + #[NoAdminRequired] + #[FrontpageRoute(verb: 'PUT', url: '/pollgroup/{pollGroupId}/poll/{pollId}')] + public function addPollToPollGroup(int $pollId, int $pollGroupId): JSONResponse { + return $this->response(fn () => [ + 'pollGroup' => $this->pollService->addPollToPollGroup($pollId, $pollGroupId), + 'poll' => $this->pollService->get($pollId), + ]); + } + + /** + * Update Pollgroup + */ + #[NoAdminRequired] + #[FrontpageRoute(verb: 'PUT', url: '/pollgroup/{pollGroupId}/update')] + public function updatePollGroup( + int $pollGroupId, + string $title, + string $titleExt, + string $description, + ): JSONResponse { + return $this->response(fn () => [ + 'pollGroup' => $this->pollService->updatePollGroup($pollGroupId, $title, $titleExt, $description), + ]); + } + /** + * Remove poll from pollgroup + * @param int $pollId Poll id + * @param int $pollGroupId Poll group id + * + * psalm-return JSONResponse + */ + #[NoAdminRequired] + #[FrontpageRoute(verb: 'DELETE', url: '/pollgroup/{pollGroupId}/poll/{pollId}')] + public function removePollFromPollGroup(int $pollId, int $pollGroupId): JSONResponse { + return $this->response(fn () => [ + 'pollGroup' => $this->pollService->removePollFromPollGroup($pollId, $pollGroupId), + 'poll' => $this->pollService->get($pollId), + ]); + } + } diff --git a/lib/Cron/UserDeletedJob.php b/lib/Cron/UserDeletedJob.php index 4b83cb236c..3777d0dbef 100644 --- a/lib/Cron/UserDeletedJob.php +++ b/lib/Cron/UserDeletedJob.php @@ -59,9 +59,9 @@ protected function run($argument) { $replacementName = 'deleted_' . $this->secureRandom->generate( 8, - ISecureRandom::CHAR_DIGITS . - ISecureRandom::CHAR_LOWER . - ISecureRandom::CHAR_UPPER + ISecureRandom::CHAR_DIGITS + . ISecureRandom::CHAR_LOWER + . ISecureRandom::CHAR_UPPER ); $this->pollMapper->deleteByUserId($userId); diff --git a/lib/Db/EntityWithUser.php b/lib/Db/EntityWithUser.php index bfdbeb19b4..c1e3aed2f9 100644 --- a/lib/Db/EntityWithUser.php +++ b/lib/Db/EntityWithUser.php @@ -8,6 +8,7 @@ namespace OCA\Polls\Db; +use Exception; use OCA\Polls\Helper\Container; use OCA\Polls\Model\User\Anon; use OCA\Polls\Model\UserBase; @@ -101,7 +102,14 @@ public function getUser(): UserBase { $userMapper = (Container::queryClass(UserMapper::class)); - $user = $userMapper->getParticipant($this->getUserId(), $this->getPollId()); + try { + $pollId = $this->getPollId(); + $user = $userMapper->getParticipant($this->getUserId(), $pollId); + // Get user from userbase + } catch (Exception $e) { + // If pollId is not set, we assume that the user is not a participant of a poll + $user = $userMapper->getUserFromUserBase($this->getUserId()); + } return $user; } } diff --git a/lib/Db/Poll.php b/lib/Db/Poll.php index b4b0a22405..928e1fd95e 100644 --- a/lib/Db/Poll.php +++ b/lib/Db/Poll.php @@ -172,6 +172,7 @@ class Poll extends EntityWithUser implements JsonSerializable { protected ?string $groupShares = ''; protected int $optionsCount = 0; protected int $proposalsCount = 0; + protected ?string $pollGroups = ''; // subqueried columns protected int $currentUserOrphanedVotes = 0; @@ -230,6 +231,7 @@ public function jsonSerialize(): array { 'status' => $this->getStatusArray(), 'currentUserStatus' => $this->getCurrentUserStatus(), 'permissions' => $this->getPermissionsArray(), + 'pollGroups' => $this->getPollGroups(), ]; } @@ -437,6 +439,18 @@ private function getGroupShares(): array { return []; } + /** + * @return int[] + * + * @psalm-return list + */ + public function getPollGroups(): array { + if (!$this->pollGroups) { + return []; + } + return array_map('intval', explode(PollGroup::CONCAT_SEPARATOR, $this->pollGroups)); + } + private function getAccess(): string { if ($this->access === self::ACCESS_PUBLIC) { return self::ACCESS_OPEN; diff --git a/lib/Db/PollGroup.php b/lib/Db/PollGroup.php new file mode 100644 index 0000000000..392e997f25 --- /dev/null +++ b/lib/Db/PollGroup.php @@ -0,0 +1,109 @@ + + */ + public function getPollIds(): array { + if (!$this->pollIds) { + return []; + } + return array_map('intval', explode(self::CONCAT_SEPARATOR, $this->pollIds)); + } + + public function setPollIds(array $pollIds): void { + $this->pollIds = implode(self::CONCAT_SEPARATOR, $pollIds); + } + + public function hasPoll(int $pollId): bool { + $polls = $this->getPollIds(); + return in_array($pollId, $polls, true); + } + + public function getSlug(): string { + // sanitize the title to remove any unwanted characters + $slug = preg_replace('/[^a-zA-Z0-9\s]/', '', $this->getTitle()); + // in case the title is empty, use a default slug + if ($slug === '') { + $slug = 'group'; + } + return strtolower(str_replace(' ', '-', $slug)) . '-' . $this->getId(); + } + + // alias of getOwner() + public function getUserId(): string { + return $this->getOwner(); + } + + // alias of setOwner($value) + public function setUserId(string $userId): void { + $this->setOwner($userId); + } + + /** + * @return array + * + * @psalm-suppress PossiblyUnusedMethod + */ + public function jsonSerialize(): array { + return [ + 'id' => $this->getId(), + 'created' => $this->getCreated(), + 'deleted' => $this->getDeleted(), + 'description' => $this->getDescription(), + 'owner' => $this->getUser(), + 'title' => $this->getTitle(), + 'titleExt' => $this->getTitleExt(), + 'pollIds' => $this->getPollIds(), + 'slug' => $this->getSlug(), + ]; + } +} diff --git a/lib/Db/PollGroupMapper.php b/lib/Db/PollGroupMapper.php new file mode 100644 index 0000000000..7b838f12ea --- /dev/null +++ b/lib/Db/PollGroupMapper.php @@ -0,0 +1,165 @@ + + */ +class PollGroupMapper extends QBMapper { + public const TABLE = PollGroup::TABLE; + public const CONCAT_SEPARATOR = ','; + + /** @psalm-suppress PossiblyUnusedMethod */ + public function __construct( + IDBConnection $db, + private UserSession $userSession, + ) { + parent::__construct($db, PollGroup::TABLE, PollGroup::class); + } + + /** + * List all PollGroups + * + * @return PollGroup[] + */ + public function list(): array { + $qb = $this->buildQuery(); + $qb->orderBy('title', 'ASC'); + return $this->findEntities($qb); + } + + /** + * Find a PollGroup by its ID + * + * @param int $id id off poll group + * @return PollGroup + */ + public function find(int $id): PollGroup { + $qb = $this->buildQuery(); + + $qb->where($qb->expr()->eq(self::TABLE . '.id', $qb->createNamedParameter($id))); + + return $this->findEntity($qb); + } + + public function addPollToGroup(int $pollId, int $groupId): void { + $qb = $this->db->getQueryBuilder(); + $qb->insert(PollGroup::RELATION_TABLE) + ->setValue('poll_id', $qb->createNamedParameter($pollId)) + ->setValue('group_id', $qb->createNamedParameter($groupId)); + $qb->executeStatement(); + } + + /** + * Remove a Poll from a PollGroup + * + * @param int $pollId id of poll + * @param int $groupId id of group + * @throws Exception + */ + public function removePollFromGroup(int $pollId, int $groupId): void { + $qb = $this->db->getQueryBuilder(); + $qb->delete(PollGroup::RELATION_TABLE) + ->where( + $qb->expr()->andX( + $qb->expr()->eq('poll_id', $qb->createNamedParameter($pollId)), + $qb->expr()->eq('group_id', $qb->createNamedParameter($groupId)) + ) + ); + $qb->executeStatement(); + } + + public function addGroup(string $PollGroupName): PollGroup { + $pollGroup = new PollGroup(); + $pollGroup->setTitle($PollGroupName); + $pollGroup->setTitleExt($PollGroupName); + $pollGroup->setDescription($PollGroupName); + $pollGroup->setCreated(time()); + $pollGroup->setOwner($this->userSession->getCurrentUserId()); + return $this->insert($pollGroup); + + } + + public function tidyPollGroups(): void { + $qb = $this->db->getQueryBuilder(); + + // This is, what we wanna do + // + // DELETE FROM oc_polls_groups + // WHERE `id` not IN ( + // SELECT `group_id` + // FROM oc_polls_groups_polls) + // + // should result in + // + // $qb->delete(PollGroup::TABLE) + // ->where($qb->expr()->notIn( + // 'id', + // $qb->selectDistinct('group_id') + // ->from(PollGroup::RELATION_TABLE) + // ->getSQL(), + // IQueryBuilder::PARAM_INT_ARRAY + // ) + // ); + // + // But we have to use a subquery, otherwise, we get 'InvalidArgumentException Only strings, Literals and Parameters are allowed' + + $subquery = $this->db->getQueryBuilder(); + $subquery->selectDistinct('group_id')->from(PollGroup::RELATION_TABLE); + + $qb->delete(PollGroup::TABLE) + ->where($qb->expr()->notIn( + 'id', + $qb->createFunction($subquery->getSQL()), + IQueryBuilder::PARAM_INT_ARRAY + ) + ); + $qb->executeStatement(); + } + + /** + * Build the enhanced query with joined tables + */ + protected function buildQuery(): IQueryBuilder { + $qb = $this->db->getQueryBuilder(); + $qb->select(self::TABLE . '.*') + ->from($this->getTableName(), self::TABLE) + ->groupBy(self::TABLE . '.id'); + + // Join polls + $this->joinPolls($qb); + + return $qb; + } + + protected function joinPolls(IQueryBuilder $qb): void { + $joinPollsAlias = 'polls'; + TableManager::getConcatenatedArray( + qb: $qb, + concatColumn: $joinPollsAlias . '.poll_id', + asColumn: 'poll_ids', + dbProvider: $this->db->getDatabaseProvider(), + ); + + $qb->leftJoin( + self::TABLE, + PollGroup::RELATION_TABLE, + $joinPollsAlias, + $qb->expr()->andX( + $qb->expr()->eq(self::TABLE . '.id', $joinPollsAlias . '.group_id'), + ) + ); + } +} diff --git a/lib/Db/PollMapper.php b/lib/Db/PollMapper.php index 8fc8a35d33..4c50328bcd 100644 --- a/lib/Db/PollMapper.php +++ b/lib/Db/PollMapper.php @@ -8,10 +8,6 @@ namespace OCA\Polls\Db; -use Doctrine\DBAL\Platforms\MySQLPlatform; -use Doctrine\DBAL\Platforms\OraclePlatform; -use Doctrine\DBAL\Platforms\PostgreSQLPlatform; -use Doctrine\DBAL\Platforms\SqlitePlatform; use OCA\Polls\UserSession; use OCP\AppFramework\Db\QBMapper; use OCP\DB\QueryBuilder\IParameter; @@ -205,6 +201,7 @@ protected function buildQuery(): IQueryBuilder { $this->joinOptions($qb, self::TABLE); $this->joinUserRole($qb, self::TABLE, $currentUserId); $this->joinGroupShares($qb, self::TABLE); + $this->joinPollGroups($qb, self::TABLE); return $qb; } @@ -242,22 +239,12 @@ protected function joinUserRole(IQueryBuilder &$qb, string $fromAlias, string $c */ protected function joinGroupShares(IQueryBuilder &$qb, string $fromAlias): void { $joinAlias = 'group_shares'; - - if ($this->db->getDatabasePlatform() instanceof PostgreSQLPlatform) { - $qb->addSelect($qb->createFunction('string_agg(distinct ' . $joinAlias . '.user_id, \'' . self::CONCAT_SEPARATOR . '\') AS group_shares')); - - } elseif ($this->db->getDatabasePlatform() instanceof OraclePlatform) { - $qb->addSelect($qb->createFunction('listagg(distinct ' . $joinAlias . '.user_id, \'' . self::CONCAT_SEPARATOR . '\') WITHIN GROUP (ORDER BY ' . $joinAlias . '.user_id) AS group_shares')); - - } elseif ($this->db->getDatabasePlatform() instanceof SqlitePlatform) { - $qb->addSelect($qb->createFunction('group_concat(replace(distinct ' . $joinAlias . '.user_id ,\'\',\'\'), \'' . self::CONCAT_SEPARATOR . '\') AS group_shares')); - - } elseif ($this->db->getDatabasePlatform() instanceof MySQLPlatform) { - $qb->addSelect($qb->createFunction('group_concat(distinct ' . $joinAlias . '.user_id SEPARATOR "' . self::CONCAT_SEPARATOR . '") AS group_shares')); - - } else { - $qb->addSelect($qb->createFunction('group_concat(distinct ' . $joinAlias . '.user_id SEPARATOR "' . self::CONCAT_SEPARATOR . '") AS group_shares')); - } + TableManager::getConcatenatedArray( + qb: $qb, + concatColumn: $joinAlias . '.user_id', + asColumn: 'group_shares', + dbProvider: $this->db->getDatabaseProvider(), + ); $qb->leftJoin( $fromAlias, @@ -271,6 +258,26 @@ protected function joinGroupShares(IQueryBuilder &$qb, string $fromAlias): void ); } + + protected function joinPollGroups(IQueryBuilder $qb, string $fromAlias): void { + $joinPollsGroupsAlias = 'groups'; + TableManager::getConcatenatedArray( + qb: $qb, + concatColumn: $joinPollsGroupsAlias . '.group_id', + asColumn: 'poll_groups', + dbProvider: $this->db->getDatabaseProvider(), + ); + + $qb->leftJoin( + $fromAlias, + PollGroup::RELATION_TABLE, + $joinPollsGroupsAlias, + $qb->expr()->andX( + $qb->expr()->eq(self::TABLE . '.id', $joinPollsGroupsAlias . '.poll_id'), + ) + ); + } + /** * Joins options to evaluate min and max option date for date polls * if text poll or no options are set, diff --git a/lib/Db/TableManager.php b/lib/Db/TableManager.php index 5a7ac8082c..ae5a160390 100644 --- a/lib/Db/TableManager.php +++ b/lib/Db/TableManager.php @@ -428,4 +428,32 @@ public function migrateOptionsToHash(): array { } return $messages; } + + /** + * Get a concatenated array of values from a column in the query builder. + * + * @param IQueryBuilder $qb The query builder instance per reference + * @param string $concatColumn The column to concatenate + * @param string $asColumn The alias for the concatenated column + * @param string $dbProvider The database provider (default: IDBConnection::PLATFORM_MYSQL) + * @param string $separator The separator for concatenation (default: ',') + * + * @psalm-param IDBConnection::PLATFORM_* $dbProvider + * + */ + public static function getConcatenatedArray( + IQueryBuilder &$qb, + string $concatColumn, + string $asColumn, + string $dbProvider, + string $separator = ',', + ): void { + $qb->addSelect(match ($dbProvider) { + IDBConnection::PLATFORM_POSTGRES => $qb->createFunction('string_agg(distinct ' . $concatColumn . '::varchar, \'' . $separator . '\') AS ' . $asColumn), + IDBConnection::PLATFORM_ORACLE => $qb->createFunction('listagg(distinct ' . $concatColumn . ', \'' . $separator . '\') WITHIN GROUP (ORDER BY ' . $concatColumn . ') AS ' . $asColumn), + IDBConnection::PLATFORM_SQLITE => $qb->createFunction('group_concat(replace(distinct ' . $concatColumn . ' ,\'\',\'\'), \'' . $separator . '\') AS ' . $asColumn), + default => $qb->createFunction('group_concat(distinct ' . $concatColumn . ' SEPARATOR "' . $separator . '") AS ' . $asColumn), + }); + } + } diff --git a/lib/Migration/RepairSteps/CreateTables.php b/lib/Migration/RepairSteps/CreateTables.php index 3d19353bcf..2b5a1f2bf9 100644 --- a/lib/Migration/RepairSteps/CreateTables.php +++ b/lib/Migration/RepairSteps/CreateTables.php @@ -43,7 +43,7 @@ public function run(IOutput $output): void { $messages = $this->tableManager->createTables(); $this->connection->migrateToSchema($this->schema); - + foreach ($messages as $message) { $output->info($message); } diff --git a/lib/Migration/RepairSteps/DropOrphanedColumns.php b/lib/Migration/RepairSteps/DropOrphanedColumns.php index 4ede8f2c07..f647aca75f 100644 --- a/lib/Migration/RepairSteps/DropOrphanedColumns.php +++ b/lib/Migration/RepairSteps/DropOrphanedColumns.php @@ -36,7 +36,7 @@ public function run(IOutput $output): void { $this->tableManager->setSchema($this->schema); $messages = $this->tableManager->removeObsoleteColumns(); $this->connection->migrateToSchema($this->schema); - + foreach ($messages as $message) { $output->info($message); } diff --git a/lib/Migration/RepairSteps/DropOrphanedTables.php b/lib/Migration/RepairSteps/DropOrphanedTables.php index 04be78bd20..12aefdbf6e 100644 --- a/lib/Migration/RepairSteps/DropOrphanedTables.php +++ b/lib/Migration/RepairSteps/DropOrphanedTables.php @@ -38,7 +38,7 @@ public function run(IOutput $output): void { $messages = $this->tableManager->removeObsoleteTables(); $this->connection->migrateToSchema($this->schema); - + foreach ($messages as $message) { $output->info($message); } diff --git a/lib/Migration/TableSchema.php b/lib/Migration/TableSchema.php index 9334bf40ec..995562b40c 100644 --- a/lib/Migration/TableSchema.php +++ b/lib/Migration/TableSchema.php @@ -13,6 +13,7 @@ use OCA\Polls\Db\Log; use OCA\Polls\Db\Option; use OCA\Polls\Db\Poll; +use OCA\Polls\Db\PollGroup; use OCA\Polls\Db\Preferences; use OCA\Polls\Db\Share; use OCA\Polls\Db\Subscription; @@ -56,6 +57,7 @@ abstract class TableSchema { Vote::TABLE => ['name' => 'UNIQ_votes', 'unique' => true, 'columns' => ['poll_id', 'user_id', 'vote_option_hash']], Preferences::TABLE => ['name' => 'UNIQ_preferences', 'unique' => true, 'columns' => ['user_id']], Watch::TABLE => ['name' => 'UNIQ_watch', 'unique' => true, 'columns' => ['poll_id', 'table', 'session_id']], + PollGroup::RELATION_TABLE => ['name' => 'UNIQ_poll_group_relation', 'unique' => true, 'columns' => ['poll_id', 'group_id']], ]; /** @@ -148,6 +150,20 @@ abstract class TableSchema { * */ public const TABLES = [ + PollGroup::TABLE => [ + 'id' => ['type' => Types::BIGINT, 'options' => ['autoincrement' => true, 'notnull' => true, 'length' => 20]], + 'created' => ['type' => Types::BIGINT, 'options' => ['notnull' => true, 'default' => 0, 'length' => 20]], + 'deleted' => ['type' => Types::BIGINT, 'options' => ['notnull' => true, 'default' => 0, 'length' => 20]], + 'description' => ['type' => Types::TEXT, 'options' => ['notnull' => false, 'default' => null, 'length' => 65535]], + 'owner' => ['type' => Types::STRING, 'options' => ['notnull' => false, 'default' => null, 'length' => 256]], + 'title' => ['type' => Types::STRING, 'options' => ['notnull' => true, 'default' => '', 'length' => 128]], + 'title_ext' => ['type' => Types::STRING, 'options' => ['notnull' => true, 'default' => '', 'length' => 128]], + ], + PollGroup::RELATION_TABLE => [ + 'id' => ['type' => Types::BIGINT, 'options' => ['autoincrement' => true, 'notnull' => true, 'length' => 20]], + 'poll_id' => ['type' => Types::BIGINT, 'options' => ['notnull' => true, 'default' => 0, 'length' => 20]], + 'group_id' => ['type' => Types::BIGINT, 'options' => ['notnull' => true, 'default' => 0, 'length' => 20]], + ], Poll::TABLE => [ 'id' => ['type' => Types::BIGINT, 'options' => ['autoincrement' => true, 'notnull' => true, 'length' => 20]], 'type' => ['type' => Types::STRING, 'options' => ['notnull' => true, 'default' => 'datePoll', 'length' => 64]], diff --git a/lib/Service/PollService.php b/lib/Service/PollService.php index c8d0e3ddac..2db1c161d0 100644 --- a/lib/Service/PollService.php +++ b/lib/Service/PollService.php @@ -9,6 +9,8 @@ namespace OCA\Polls\Service; use OCA\Polls\Db\Poll; +use OCA\Polls\Db\PollGroup; +use OCA\Polls\Db\PollGroupMapper; use OCA\Polls\Db\PollMapper; use OCA\Polls\Db\UserMapper; use OCA\Polls\Db\VoteMapper; @@ -33,6 +35,7 @@ use OCA\Polls\Model\UserBase; use OCA\Polls\UserSession; use OCP\AppFramework\Db\DoesNotExistException; +use OCP\DB\Exception; use OCP\EventDispatcher\IEventDispatcher; use OCP\Search\ISearchQuery; @@ -47,13 +50,14 @@ public function __construct( private UserMapper $userMapper, private UserSession $userSession, private VoteMapper $voteMapper, + private PollGroupMapper $pollGroupMapper, ) { } /** * Get list of polls including Threshold for "relevant polls" */ - public function list(): array { + public function listPolls(): array { $pollList = $this->pollMapper->findForMe($this->userSession->getCurrentUserId()); if ($this->userSession->getCurrentUser()->getIsAdmin()) { return $pollList; @@ -64,6 +68,93 @@ public function list(): array { })); } + public function listPollGroups(): array { + return $this->pollGroupMapper->list(); + } + + public function updatePollGroup( + int $pollGroupId, + string $title, + string $titleExt, + string $description, + ): PollGroup { + try { + $pollGroup = $this->pollGroupMapper->find($pollGroupId); + if ($pollGroup->getOwner() !== $this->userSession->getCurrentUserId()) { + throw new ForbiddenException('You do not have permission to edit this poll group'); + } + $pollGroup->setTitle($title); + $pollGroup->setTitleExt($titleExt); + $pollGroup->setDescription($description); + + $pollGroup = $this->pollGroupMapper->update($pollGroup); + return $pollGroup; + } catch (DoesNotExistException $e) { + throw new NotFoundException('Poll group not found'); + } + } + public function addPollToPollGroup( + int $pollId, + ?int $pollGroupId = null, + ?string $newPollGroupName = null, + ): PollGroup { + $poll = $this->pollMapper->find($pollId); + $poll->request(Poll::PERMISSION_POLL_EDIT); + + if ($pollGroupId === null && $newPollGroupName) { + if (!$this->appSettings->getPollCreationAllowed()) { + // If poll creation is disabled, creating a poll group is also disabled + throw new ForbiddenException('Poll group creation is disabled'); + } + // Create new poll group + $pollGroup = $this->pollGroupMapper->addGroup($newPollGroupName); + } else { + $pollGroup = $this->pollGroupMapper->find($pollGroupId); + } + + if (!$pollGroup->hasPoll($pollId)) { + try { + $this->pollGroupMapper->addPollToGroup($pollId, $pollGroup->getId()); + } catch (Exception $e) { + if ($e->getReason() === Exception::REASON_UNIQUE_CONSTRAINT_VIOLATION) { + // Poll is already member of this group + } else { + throw $e; + } + } + + $this->eventDispatcher->dispatchTyped(new PollUpdatedEvent($poll)); + } + + return $this->pollGroupMapper->find($pollGroup->getId()); + } + + public function removePollFromPollGroup( + int $pollId, + int $pollGroupId, + ): ?PollGroup { + $poll = $this->pollMapper->find($pollId); + $poll->request(Poll::PERMISSION_POLL_EDIT); + + $pollGroup = $this->pollGroupMapper->find($pollGroupId); + + if ($pollGroup->hasPoll($pollId)) { + $this->pollGroupMapper->removePollFromGroup($pollId, $pollGroupId); + $this->eventDispatcher->dispatchTyped(new PollUpdatedEvent($poll)); + } else { + throw new NotFoundException('Poll not found in group'); + } + + $this->pollGroupMapper->tidyPollGroups(); + try { + $pollGroup = $this->pollGroupMapper->find($pollGroupId); + } catch (DoesNotExistException $e) { + // Poll group was deleted, return null + return null; + } + return $pollGroup; + } + /** * Get list of polls */ diff --git a/lib/Service/ShareService.php b/lib/Service/ShareService.php index aca4d7e25b..22a71d75c0 100644 --- a/lib/Service/ShareService.php +++ b/lib/Service/ShareService.php @@ -538,9 +538,9 @@ private function generatePublicUserId(string $prefix = 'ex_'): string { while ($publicUserId === '') { $publicUserId = $prefix . $this->secureRandom->generate( 8, - ISecureRandom::CHAR_DIGITS . - ISecureRandom::CHAR_LOWER . - ISecureRandom::CHAR_UPPER + ISecureRandom::CHAR_DIGITS + . ISecureRandom::CHAR_LOWER + . ISecureRandom::CHAR_UPPER ); try { @@ -629,9 +629,9 @@ private function generateToken(): string { $loopCounter++; $token = $this->secureRandom->generate( 8, - ISecureRandom::CHAR_DIGITS . - ISecureRandom::CHAR_LOWER . - ISecureRandom::CHAR_UPPER + ISecureRandom::CHAR_DIGITS + . ISecureRandom::CHAR_LOWER + . ISecureRandom::CHAR_UPPER ); try { $this->shareMapper->findByToken($token, true); diff --git a/package-lock.json b/package-lock.json index 599bb58722..216b72ce65 100644 --- a/package-lock.json +++ b/package-lock.json @@ -19,9 +19,9 @@ "@nextcloud/moment": "^1.3.4", "@nextcloud/router": "^3.0.1", "@nextcloud/vue": "^9.0.0-rc.2", - "@vueuse/core": "^13.3.0", - "@vueuse/integrations": "^13.3.0", - "axios": "^1.9.0", + "@vueuse/core": "^13.4.0", + "@vueuse/integrations": "^13.4.0", + "axios": "^1.10.0", "core-js": "^3.43.0", "dompurify": "^3.2.6", "file-saver": "^2.0.5", @@ -33,7 +33,7 @@ "marked-gfm-heading-id": "^4.1.0", "pinia": "^3.0.3", "qrcode": "^1.5.3", - "vue": "^3.5.16", + "vue": "^3.5.17", "vue-material-design-icons": "^5.3.0", "vue-router": "^4.5.1", "xlsx": "https://cdn.sheetjs.com/xlsx-0.20.3/xlsx-0.20.3.tgz" @@ -45,12 +45,12 @@ "@nextcloud/stylelint-config": "^3.1.0", "@nextcloud/vite-config": "^2.3.5", "@types/file-saver": "^2.0.7", - "@types/lodash": "^4.17.17", + "@types/lodash": "^4.17.18", "@types/luxon": "^3.6.2", "@types/qrcode": "^1.5.5", "@vue/tsconfig": "^0.7.0", "eslint-config-prettier": "^10.1.5", - "eslint-plugin-prettier": "^5.4.1", + "eslint-plugin-prettier": "^5.5.0", "prettier": "^3.2.5", "vite": "^6.3.5", "vite-plugin-node-polyfills": "^0.23.0", @@ -270,12 +270,12 @@ } }, "node_modules/@babel/parser": { - "version": "7.27.2", - "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.27.2.tgz", - "integrity": "sha512-QYLs8299NA7WM/bZAdp+CviYYkVoYXlDW2rzliy3chxd1PQjej7JORuMJDJXJUb9g0TT+B99EwaVLKmX+sPXWw==", + "version": "7.27.5", + "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.27.5.tgz", + "integrity": "sha512-OsQd175SxWkGlzbny8J3K8TnnDD0N3lrIUtB92xwyRpzaenGZhxDvxN/JgU00U3CDZNj9tPuDJ5H0WS4Nt3vKg==", "license": "MIT", "dependencies": { - "@babel/types": "^7.27.1" + "@babel/types": "^7.27.3" }, "bin": { "parser": "bin/babel-parser.js" @@ -333,9 +333,9 @@ } }, "node_modules/@babel/types": { - "version": "7.27.1", - "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.27.1.tgz", - "integrity": "sha512-+EzkxvLNfiUeKMgy/3luqfsCWFRXLb7U6wNQTk60tovuckwB15B191tJWvpp4HjiQWdJkCxO3Wbvc6jlk3Xb2Q==", + "version": "7.27.6", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.27.6.tgz", + "integrity": "sha512-ETyHEk2VHHvl9b9jZP5IHPavHYk57EhanlRRuae9XCpb/j5bDCbPPMOBfCWhnl/7EDJz0jEMCi/RhccCE8r1+Q==", "license": "MIT", "dependencies": { "@babel/helper-string-parser": "^7.27.1", @@ -2868,9 +2868,9 @@ "peer": true }, "node_modules/@types/lodash": { - "version": "4.17.17", - "resolved": "https://registry.npmjs.org/@types/lodash/-/lodash-4.17.17.tgz", - "integrity": "sha512-RRVJ+J3J+WmyOTqnz3PiBLA501eKwXl2noseKOrNo/6+XEHjTAxO4xHvxQB6QuNm+s4WRbn6rSiap8+EA+ykFQ==", + "version": "4.17.18", + "resolved": "https://registry.npmjs.org/@types/lodash/-/lodash-4.17.18.tgz", + "integrity": "sha512-KJ65INaxqxmU6EoCiJmRPZC9H9RVWCRd349tXM2M3O5NA7cY6YL7c0bHAHQ93NOfTObEQ004kd2QVHs/r0+m4g==", "dev": true, "license": "MIT" }, @@ -3460,53 +3460,53 @@ } }, "node_modules/@vue/compiler-core": { - "version": "3.5.16", - "resolved": "https://registry.npmjs.org/@vue/compiler-core/-/compiler-core-3.5.16.tgz", - "integrity": "sha512-AOQS2eaQOaaZQoL1u+2rCJIKDruNXVBZSiUD3chnUrsoX5ZTQMaCvXlWNIfxBJuU15r1o7+mpo5223KVtIhAgQ==", + "version": "3.5.17", + "resolved": "https://registry.npmjs.org/@vue/compiler-core/-/compiler-core-3.5.17.tgz", + "integrity": "sha512-Xe+AittLbAyV0pabcN7cP7/BenRBNcteM4aSDCtRvGw0d9OL+HG1u/XHLY/kt1q4fyMeZYXyIYrsHuPSiDPosA==", "license": "MIT", "dependencies": { - "@babel/parser": "^7.27.2", - "@vue/shared": "3.5.16", + "@babel/parser": "^7.27.5", + "@vue/shared": "3.5.17", "entities": "^4.5.0", "estree-walker": "^2.0.2", "source-map-js": "^1.2.1" } }, "node_modules/@vue/compiler-dom": { - "version": "3.5.16", - "resolved": "https://registry.npmjs.org/@vue/compiler-dom/-/compiler-dom-3.5.16.tgz", - "integrity": "sha512-SSJIhBr/teipXiXjmWOVWLnxjNGo65Oj/8wTEQz0nqwQeP75jWZ0n4sF24Zxoht1cuJoWopwj0J0exYwCJ0dCQ==", + "version": "3.5.17", + "resolved": "https://registry.npmjs.org/@vue/compiler-dom/-/compiler-dom-3.5.17.tgz", + "integrity": "sha512-+2UgfLKoaNLhgfhV5Ihnk6wB4ljyW1/7wUIog2puUqajiC29Lp5R/IKDdkebh9jTbTogTbsgB+OY9cEWzG95JQ==", "license": "MIT", "dependencies": { - "@vue/compiler-core": "3.5.16", - "@vue/shared": "3.5.16" + "@vue/compiler-core": "3.5.17", + "@vue/shared": "3.5.17" } }, "node_modules/@vue/compiler-sfc": { - "version": "3.5.16", - "resolved": "https://registry.npmjs.org/@vue/compiler-sfc/-/compiler-sfc-3.5.16.tgz", - "integrity": "sha512-rQR6VSFNpiinDy/DVUE0vHoIDUF++6p910cgcZoaAUm3POxgNOOdS/xgoll3rNdKYTYPnnbARDCZOyZ+QSe6Pw==", + "version": "3.5.17", + "resolved": "https://registry.npmjs.org/@vue/compiler-sfc/-/compiler-sfc-3.5.17.tgz", + "integrity": "sha512-rQQxbRJMgTqwRugtjw0cnyQv9cP4/4BxWfTdRBkqsTfLOHWykLzbOc3C4GGzAmdMDxhzU/1Ija5bTjMVrddqww==", "license": "MIT", "dependencies": { - "@babel/parser": "^7.27.2", - "@vue/compiler-core": "3.5.16", - "@vue/compiler-dom": "3.5.16", - "@vue/compiler-ssr": "3.5.16", - "@vue/shared": "3.5.16", + "@babel/parser": "^7.27.5", + "@vue/compiler-core": "3.5.17", + "@vue/compiler-dom": "3.5.17", + "@vue/compiler-ssr": "3.5.17", + "@vue/shared": "3.5.17", "estree-walker": "^2.0.2", "magic-string": "^0.30.17", - "postcss": "^8.5.3", + "postcss": "^8.5.6", "source-map-js": "^1.2.1" } }, "node_modules/@vue/compiler-ssr": { - "version": "3.5.16", - "resolved": "https://registry.npmjs.org/@vue/compiler-ssr/-/compiler-ssr-3.5.16.tgz", - "integrity": "sha512-d2V7kfxbdsjrDSGlJE7my1ZzCXViEcqN6w14DOsDrUCHEA6vbnVCpRFfrc4ryCP/lCKzX2eS1YtnLE/BuC9f/A==", + "version": "3.5.17", + "resolved": "https://registry.npmjs.org/@vue/compiler-ssr/-/compiler-ssr-3.5.17.tgz", + "integrity": "sha512-hkDbA0Q20ZzGgpj5uZjb9rBzQtIHLS78mMilwrlpWk2Ep37DYntUz0PonQ6kr113vfOEdM+zTBuJDaceNIW0tQ==", "license": "MIT", "dependencies": { - "@vue/compiler-dom": "3.5.16", - "@vue/shared": "3.5.16" + "@vue/compiler-dom": "3.5.17", + "@vue/shared": "3.5.17" } }, "node_modules/@vue/compiler-vue2": { @@ -3605,53 +3605,53 @@ } }, "node_modules/@vue/reactivity": { - "version": "3.5.16", - "resolved": "https://registry.npmjs.org/@vue/reactivity/-/reactivity-3.5.16.tgz", - "integrity": "sha512-FG5Q5ee/kxhIm1p2bykPpPwqiUBV3kFySsHEQha5BJvjXdZTUfmya7wP7zC39dFuZAcf/PD5S4Lni55vGLMhvA==", + "version": "3.5.17", + "resolved": "https://registry.npmjs.org/@vue/reactivity/-/reactivity-3.5.17.tgz", + "integrity": "sha512-l/rmw2STIscWi7SNJp708FK4Kofs97zc/5aEPQh4bOsReD/8ICuBcEmS7KGwDj5ODQLYWVN2lNibKJL1z5b+Lw==", "license": "MIT", "dependencies": { - "@vue/shared": "3.5.16" + "@vue/shared": "3.5.17" } }, "node_modules/@vue/runtime-core": { - "version": "3.5.16", - "resolved": "https://registry.npmjs.org/@vue/runtime-core/-/runtime-core-3.5.16.tgz", - "integrity": "sha512-bw5Ykq6+JFHYxrQa7Tjr+VSzw7Dj4ldR/udyBZbq73fCdJmyy5MPIFR9IX/M5Qs+TtTjuyUTCnmK3lWWwpAcFQ==", + "version": "3.5.17", + "resolved": "https://registry.npmjs.org/@vue/runtime-core/-/runtime-core-3.5.17.tgz", + "integrity": "sha512-QQLXa20dHg1R0ri4bjKeGFKEkJA7MMBxrKo2G+gJikmumRS7PTD4BOU9FKrDQWMKowz7frJJGqBffYMgQYS96Q==", "license": "MIT", "dependencies": { - "@vue/reactivity": "3.5.16", - "@vue/shared": "3.5.16" + "@vue/reactivity": "3.5.17", + "@vue/shared": "3.5.17" } }, "node_modules/@vue/runtime-dom": { - "version": "3.5.16", - "resolved": "https://registry.npmjs.org/@vue/runtime-dom/-/runtime-dom-3.5.16.tgz", - "integrity": "sha512-T1qqYJsG2xMGhImRUV9y/RseB9d0eCYZQ4CWca9ztCuiPj/XWNNN+lkNBuzVbia5z4/cgxdL28NoQCvC0Xcfww==", + "version": "3.5.17", + "resolved": "https://registry.npmjs.org/@vue/runtime-dom/-/runtime-dom-3.5.17.tgz", + "integrity": "sha512-8El0M60TcwZ1QMz4/os2MdlQECgGoVHPuLnQBU3m9h3gdNRW9xRmI8iLS4t/22OQlOE6aJvNNlBiCzPHur4H9g==", "license": "MIT", "dependencies": { - "@vue/reactivity": "3.5.16", - "@vue/runtime-core": "3.5.16", - "@vue/shared": "3.5.16", + "@vue/reactivity": "3.5.17", + "@vue/runtime-core": "3.5.17", + "@vue/shared": "3.5.17", "csstype": "^3.1.3" } }, "node_modules/@vue/server-renderer": { - "version": "3.5.16", - "resolved": "https://registry.npmjs.org/@vue/server-renderer/-/server-renderer-3.5.16.tgz", - "integrity": "sha512-BrX0qLiv/WugguGsnQUJiYOE0Fe5mZTwi6b7X/ybGB0vfrPH9z0gD/Y6WOR1sGCgX4gc25L1RYS5eYQKDMoNIg==", + "version": "3.5.17", + "resolved": "https://registry.npmjs.org/@vue/server-renderer/-/server-renderer-3.5.17.tgz", + "integrity": "sha512-BOHhm8HalujY6lmC3DbqF6uXN/K00uWiEeF22LfEsm9Q93XeJ/plHTepGwf6tqFcF7GA5oGSSAAUock3VvzaCA==", "license": "MIT", "dependencies": { - "@vue/compiler-ssr": "3.5.16", - "@vue/shared": "3.5.16" + "@vue/compiler-ssr": "3.5.17", + "@vue/shared": "3.5.17" }, "peerDependencies": { - "vue": "3.5.16" + "vue": "3.5.17" } }, "node_modules/@vue/shared": { - "version": "3.5.16", - "resolved": "https://registry.npmjs.org/@vue/shared/-/shared-3.5.16.tgz", - "integrity": "sha512-c/0fWy3Jw6Z8L9FmTyYfkpM5zklnqqa9+a6dz3DvONRKW2NEbh46BP0FHuLFSWi2TnQEtp91Z6zOWNrU6QiyPg==", + "version": "3.5.17", + "resolved": "https://registry.npmjs.org/@vue/shared/-/shared-3.5.17.tgz", + "integrity": "sha512-CabR+UN630VnsJO/jHWYBC1YVXyMq94KKp6iF5MQgZJs5I8cmjw6oVMO1oDbtBkENSHSSn/UadWlW/OAgdmKrg==", "license": "MIT" }, "node_modules/@vue/tsconfig": { @@ -3728,14 +3728,14 @@ } }, "node_modules/@vueuse/core": { - "version": "13.3.0", - "resolved": "https://registry.npmjs.org/@vueuse/core/-/core-13.3.0.tgz", - "integrity": "sha512-uYRz5oEfebHCoRhK4moXFM3NSCd5vu2XMLOq/Riz5FdqZMy2RvBtazdtL3gEcmDyqkztDe9ZP/zymObMIbiYSg==", + "version": "13.4.0", + "resolved": "https://registry.npmjs.org/@vueuse/core/-/core-13.4.0.tgz", + "integrity": "sha512-OnK7zW3bTq/QclEk17+vDFN3tuAm8ONb9zQUIHrYQkkFesu3WeGUx/3YzpEp+ly53IfDAT9rsYXgGW6piNZC5w==", "license": "MIT", "dependencies": { "@types/web-bluetooth": "^0.0.21", - "@vueuse/metadata": "13.3.0", - "@vueuse/shared": "13.3.0" + "@vueuse/metadata": "13.4.0", + "@vueuse/shared": "13.4.0" }, "funding": { "url": "https://github.com/sponsors/antfu" @@ -3745,9 +3745,9 @@ } }, "node_modules/@vueuse/core/node_modules/@vueuse/shared": { - "version": "13.3.0", - "resolved": "https://registry.npmjs.org/@vueuse/shared/-/shared-13.3.0.tgz", - "integrity": "sha512-L1QKsF0Eg9tiZSFXTgodYnu0Rsa2P0En2LuLrIs/jgrkyiDuJSsPZK+tx+wU0mMsYHUYEjNsuE41uqqkuR8VhA==", + "version": "13.4.0", + "resolved": "https://registry.npmjs.org/@vueuse/shared/-/shared-13.4.0.tgz", + "integrity": "sha512-+AxuKbw8R1gYy5T21V5yhadeNM7rJqb4cPaRI9DdGnnNl3uqXh+unvQ3uCaA2DjYLbNr1+l7ht/B4qEsRegX6A==", "license": "MIT", "funding": { "url": "https://github.com/sponsors/antfu" @@ -3757,13 +3757,13 @@ } }, "node_modules/@vueuse/integrations": { - "version": "13.3.0", - "resolved": "https://registry.npmjs.org/@vueuse/integrations/-/integrations-13.3.0.tgz", - "integrity": "sha512-h5mGRYPbiTZTFP/AKELLPGnUDBly7z7Qd1pgEQlT3ItQ0NlZM0vB+8SOQycpSBOBlgg72Zgw+mi2r+4O/G8RuQ==", + "version": "13.4.0", + "resolved": "https://registry.npmjs.org/@vueuse/integrations/-/integrations-13.4.0.tgz", + "integrity": "sha512-rwNoE0MNJBUuSzTZcUVrkovtHvpWIySOcC6XpcS33ZarHDNhd9CPvCD4eNl3N0Phz1he1JV0iYULRyPQ5HCbFA==", "license": "MIT", "dependencies": { - "@vueuse/core": "13.3.0", - "@vueuse/shared": "13.3.0" + "@vueuse/core": "13.4.0", + "@vueuse/shared": "13.4.0" }, "funding": { "url": "https://github.com/sponsors/antfu" @@ -3823,9 +3823,9 @@ } }, "node_modules/@vueuse/integrations/node_modules/@vueuse/shared": { - "version": "13.3.0", - "resolved": "https://registry.npmjs.org/@vueuse/shared/-/shared-13.3.0.tgz", - "integrity": "sha512-L1QKsF0Eg9tiZSFXTgodYnu0Rsa2P0En2LuLrIs/jgrkyiDuJSsPZK+tx+wU0mMsYHUYEjNsuE41uqqkuR8VhA==", + "version": "13.4.0", + "resolved": "https://registry.npmjs.org/@vueuse/shared/-/shared-13.4.0.tgz", + "integrity": "sha512-+AxuKbw8R1gYy5T21V5yhadeNM7rJqb4cPaRI9DdGnnNl3uqXh+unvQ3uCaA2DjYLbNr1+l7ht/B4qEsRegX6A==", "license": "MIT", "funding": { "url": "https://github.com/sponsors/antfu" @@ -3835,9 +3835,9 @@ } }, "node_modules/@vueuse/metadata": { - "version": "13.3.0", - "resolved": "https://registry.npmjs.org/@vueuse/metadata/-/metadata-13.3.0.tgz", - "integrity": "sha512-42IzJIOYCKIb0Yjv1JfaKpx8JlCiTmtCWrPxt7Ja6Wzoq0h79+YVXmBV03N966KEmDEESTbp5R/qO3AB5BDnGw==", + "version": "13.4.0", + "resolved": "https://registry.npmjs.org/@vueuse/metadata/-/metadata-13.4.0.tgz", + "integrity": "sha512-CPDQ/IgOeWbqItg1c/pS+Ulum63MNbpJ4eecjFJqgD/JUCJ822zLfpw6M9HzSvL6wbzMieOtIAW/H8deQASKHg==", "license": "MIT", "funding": { "url": "https://github.com/sponsors/antfu" @@ -4214,9 +4214,9 @@ } }, "node_modules/axios": { - "version": "1.9.0", - "resolved": "https://registry.npmjs.org/axios/-/axios-1.9.0.tgz", - "integrity": "sha512-re4CqKTJaURpzbLHtIi6XpDv20/CnpXOtjRY5/CU32L8gU8ek9UIivcfvSWvmKEngmVbrUtPpdDwWDWL7DNHvg==", + "version": "1.10.0", + "resolved": "https://registry.npmjs.org/axios/-/axios-1.10.0.tgz", + "integrity": "sha512-/1xYAC4MP/HEG+3duIhFr4ZQXR4sQXOIe+o6sdqzeykGLx6Upp/1p8MHqhINOvGeP7xyNHe7tsiJByc4SSVUxw==", "license": "MIT", "dependencies": { "follow-redirects": "^1.15.6", @@ -6417,9 +6417,9 @@ } }, "node_modules/eslint-plugin-prettier": { - "version": "5.4.1", - "resolved": "https://registry.npmjs.org/eslint-plugin-prettier/-/eslint-plugin-prettier-5.4.1.tgz", - "integrity": "sha512-9dF+KuU/Ilkq27A8idRP7N2DH8iUR6qXcjF3FR2wETY21PZdBrIjwCau8oboyGj9b7etWmTGEeM8e7oOed6ZWg==", + "version": "5.5.0", + "resolved": "https://registry.npmjs.org/eslint-plugin-prettier/-/eslint-plugin-prettier-5.5.0.tgz", + "integrity": "sha512-8qsOYwkkGrahrgoUv76NZi23koqXOGiiEzXMrT8Q7VcYaUISR+5MorIUxfWqYXN0fN/31WbSrxCxFkVQ43wwrA==", "dev": true, "license": "MIT", "dependencies": { @@ -10498,9 +10498,9 @@ } }, "node_modules/postcss": { - "version": "8.5.3", - "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.3.tgz", - "integrity": "sha512-dle9A3yYxlBSrt8Fu+IpjGT8SY8hN0mlaA6GY8t0P5PjIOZemULz/E2Bnm/2dcUOena75OTNkHI76uZBNUUq3A==", + "version": "8.5.6", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.6.tgz", + "integrity": "sha512-3Ybi1tAuwAP9s0r1UQ2J4n5Y0G05bJkpUIO0/bI9MhwmD70S5aTWbXGBwxHrelT+XM1k6dM0pk+SwNkpTRN7Pg==", "funding": [ { "type": "opencollective", @@ -10517,7 +10517,7 @@ ], "license": "MIT", "dependencies": { - "nanoid": "^3.3.8", + "nanoid": "^3.3.11", "picocolors": "^1.1.1", "source-map-js": "^1.2.1" }, @@ -13381,16 +13381,16 @@ "license": "MIT" }, "node_modules/vue": { - "version": "3.5.16", - "resolved": "https://registry.npmjs.org/vue/-/vue-3.5.16.tgz", - "integrity": "sha512-rjOV2ecxMd5SiAmof2xzh2WxntRcigkX/He4YFJ6WdRvVUrbt6DxC1Iujh10XLl8xCDRDtGKMeO3D+pRQ1PP9w==", + "version": "3.5.17", + "resolved": "https://registry.npmjs.org/vue/-/vue-3.5.17.tgz", + "integrity": "sha512-LbHV3xPN9BeljML+Xctq4lbz2lVHCR6DtbpTf5XIO6gugpXUN49j2QQPcMj086r9+AkJ0FfUT8xjulKKBkkr9g==", "license": "MIT", "dependencies": { - "@vue/compiler-dom": "3.5.16", - "@vue/compiler-sfc": "3.5.16", - "@vue/runtime-dom": "3.5.16", - "@vue/server-renderer": "3.5.16", - "@vue/shared": "3.5.16" + "@vue/compiler-dom": "3.5.17", + "@vue/compiler-sfc": "3.5.17", + "@vue/runtime-dom": "3.5.17", + "@vue/server-renderer": "3.5.17", + "@vue/shared": "3.5.17" }, "peerDependencies": { "typescript": "*" diff --git a/package.json b/package.json index 51d97de23a..5b1092f8bc 100644 --- a/package.json +++ b/package.json @@ -41,9 +41,9 @@ "@nextcloud/moment": "^1.3.4", "@nextcloud/router": "^3.0.1", "@nextcloud/vue": "^9.0.0-rc.2", - "@vueuse/core": "^13.3.0", - "@vueuse/integrations": "^13.3.0", - "axios": "^1.9.0", + "@vueuse/core": "^13.4.0", + "@vueuse/integrations": "^13.4.0", + "axios": "^1.10.0", "core-js": "^3.43.0", "dompurify": "^3.2.6", "file-saver": "^2.0.5", @@ -55,7 +55,7 @@ "marked-gfm-heading-id": "^4.1.0", "pinia": "^3.0.3", "qrcode": "^1.5.3", - "vue": "^3.5.16", + "vue": "^3.5.17", "vue-material-design-icons": "^5.3.0", "vue-router": "^4.5.1", "xlsx": "https://cdn.sheetjs.com/xlsx-0.20.3/xlsx-0.20.3.tgz" @@ -67,12 +67,12 @@ "@nextcloud/stylelint-config": "^3.1.0", "@nextcloud/vite-config": "^2.3.5", "@types/file-saver": "^2.0.7", - "@types/lodash": "^4.17.17", + "@types/lodash": "^4.17.18", "@types/luxon": "^3.6.2", "@types/qrcode": "^1.5.5", "@vue/tsconfig": "^0.7.0", "eslint-config-prettier": "^10.1.5", - "eslint-plugin-prettier": "^5.4.1", + "eslint-plugin-prettier": "^5.5.0", "prettier": "^3.2.5", "vite": "^6.3.5", "vite-plugin-node-polyfills": "^0.23.0", diff --git a/src/Api/modules/polls.ts b/src/Api/modules/polls.ts index 1a20edb190..9558c18c5b 100644 --- a/src/Api/modules/polls.ts +++ b/src/Api/modules/polls.ts @@ -2,13 +2,14 @@ * SPDX-FileCopyrightText: 2022 Nextcloud contributors * SPDX-License-Identifier: AGPL-3.0-or-later */ -import { Poll, PollConfiguration, PollType } from '../../stores/poll.js' +import { Poll, PollConfiguration, PollType } from '../../stores/poll.ts' import { AxiosResponse } from '@nextcloud/axios' import { httpInstance, createCancelTokenHandler } from './HttpApi.js' -import { Option } from '../../stores/options.js' -import { Vote } from '../../stores/votes.js' -import { Share } from '../../stores/shares.js' -import { ApiEmailAdressList, Comment } from '../../Types/index.js' +import { Option } from '../../stores/options.ts' +import { Vote } from '../../stores/votes.ts' +import { Share } from '../../stores/shares.ts' +import { ApiEmailAdressList, Comment } from '../../Types/index.ts' +import { PollGroup } from '../../stores/polls.ts' export type Confirmations = { sentMails: { emailAddress: string; displayName: string }[] @@ -18,7 +19,94 @@ export type Confirmations = { } const polls = { - getPolls(): Promise> { + getPollGroups(): Promise> { + return httpInstance.request({ + method: 'GET', + url: 'pollgroups', + params: { time: +new Date() }, + cancelToken: + cancelTokenHandlerObject[ + this.getPollGroups.name + ].handleRequestCancellation().token, + }) + }, + + createPollGroupForPoll( + newPollGroupName: string, + pollId: number, + ): Promise> { + return httpInstance.request({ + method: 'POST', + url: `pollgroup/new/poll/${pollId}`, + data: { + newPollGroupName, + }, + cancelToken: + cancelTokenHandlerObject[ + this.createPollGroupForPoll.name + ].handleRequestCancellation().token, + }) + }, + + addPollToGroup( + pollGroupId: number, + pollId: number, + ): Promise> { + return httpInstance.request({ + method: 'PUT', + url: `pollgroup/${pollGroupId}/poll/${pollId}`, + cancelToken: + cancelTokenHandlerObject[ + this.addPollToGroup.name + ].handleRequestCancellation().token, + }) + }, + + removePollFromGroup( + pollGroupId: number, + pollId: number, + ): Promise> { + return httpInstance.request({ + method: 'DELETE', + url: `pollgroup/${pollGroupId}/poll/${pollId}`, + cancelToken: + cancelTokenHandlerObject[ + this.removePollFromGroup.name + ].handleRequestCancellation().token, + }) + }, + + updatePollGroup( + pollGroupId: number, + title: string, + titleExt: string, + description: string, + ): Promise> { + return httpInstance.request({ + method: 'PUT', + url: `pollgroup/${pollGroupId}/update`, + data: { + title, + titleExt, + description, + }, + cancelToken: + cancelTokenHandlerObject[ + this.updatePollGroup.name + ].handleRequestCancellation().token, + }) + }, + + getPolls(): Promise< + AxiosResponse<{ + polls: Poll[] + permissions: { + pollCreationAllowed: boolean + comboAllowed: true + } + pollGroups: PollGroup[] + }> + > { return httpInstance.request({ method: 'GET', url: 'polls', diff --git a/src/components/Actions/modules/ActionEditGroup.vue b/src/components/Actions/modules/ActionEditGroup.vue new file mode 100644 index 0000000000..ee4bf934e3 --- /dev/null +++ b/src/components/Actions/modules/ActionEditGroup.vue @@ -0,0 +1,57 @@ + + + + + diff --git a/src/components/PollGroup/PollGroupEditDlg.vue b/src/components/PollGroup/PollGroupEditDlg.vue new file mode 100644 index 0000000000..b93cdd9b16 --- /dev/null +++ b/src/components/PollGroup/PollGroupEditDlg.vue @@ -0,0 +1,200 @@ + + + + +