summaryrefslogtreecommitdiffstats
path: root/apps/dav
diff options
context:
space:
mode:
Diffstat (limited to 'apps/dav')
-rw-r--r--apps/dav/appinfo/info.xml4
-rw-r--r--apps/dav/composer/composer/autoload_classmap.php3
-rw-r--r--apps/dav/composer/composer/autoload_static.php3
-rw-r--r--apps/dav/img/calendar.svg1
-rw-r--r--apps/dav/l10n/bg.js2
-rw-r--r--apps/dav/l10n/bg.json2
-rw-r--r--apps/dav/l10n/ca.js5
-rw-r--r--apps/dav/l10n/ca.json5
-rw-r--r--apps/dav/l10n/cs.js4
-rw-r--r--apps/dav/l10n/cs.json4
-rw-r--r--apps/dav/l10n/de.js2
-rw-r--r--apps/dav/l10n/de.json2
-rw-r--r--apps/dav/l10n/de_DE.js4
-rw-r--r--apps/dav/l10n/de_DE.json4
-rw-r--r--apps/dav/l10n/en_GB.js2
-rw-r--r--apps/dav/l10n/en_GB.json2
-rw-r--r--apps/dav/l10n/es.js2
-rw-r--r--apps/dav/l10n/es.json2
-rw-r--r--apps/dav/l10n/eu.js3
-rw-r--r--apps/dav/l10n/eu.json3
-rw-r--r--apps/dav/l10n/fr.js6
-rw-r--r--apps/dav/l10n/fr.json6
-rw-r--r--apps/dav/l10n/gl.js86
-rw-r--r--apps/dav/l10n/gl.json86
-rw-r--r--apps/dav/l10n/hu.js3
-rw-r--r--apps/dav/l10n/hu.json3
-rw-r--r--apps/dav/l10n/mk.js1
-rw-r--r--apps/dav/l10n/mk.json1
-rw-r--r--apps/dav/l10n/pl.js5
-rw-r--r--apps/dav/l10n/pl.json5
-rw-r--r--apps/dav/l10n/pt_BR.js2
-rw-r--r--apps/dav/l10n/pt_BR.json2
-rw-r--r--apps/dav/l10n/ru.js20
-rw-r--r--apps/dav/l10n/ru.json20
-rw-r--r--apps/dav/l10n/sl.js20
-rw-r--r--apps/dav/l10n/sl.json20
-rw-r--r--apps/dav/l10n/sr.js86
-rw-r--r--apps/dav/l10n/sr.json86
-rw-r--r--apps/dav/l10n/sv.js16
-rw-r--r--apps/dav/l10n/sv.json16
-rw-r--r--apps/dav/l10n/tr.js8
-rw-r--r--apps/dav/l10n/tr.json8
-rw-r--r--apps/dav/l10n/uk.js6
-rw-r--r--apps/dav/l10n/uk.json6
-rw-r--r--apps/dav/l10n/zh_HK.js2
-rw-r--r--apps/dav/l10n/zh_HK.json2
-rw-r--r--apps/dav/l10n/zh_TW.js2
-rw-r--r--apps/dav/l10n/zh_TW.json2
-rw-r--r--apps/dav/lib/BackgroundJob/UserStatusAutomation.php24
-rw-r--r--apps/dav/lib/CalDAV/CachedSubscription.php5
-rw-r--r--apps/dav/lib/CalDAV/EventComparisonService.php2
-rw-r--r--apps/dav/lib/CalDAV/Proxy/ProxyMapper.php2
-rw-r--r--apps/dav/lib/CalDAV/Schedule/IMipPlugin.php26
-rw-r--r--apps/dav/lib/CalDAV/Schedule/IMipService.php23
-rw-r--r--apps/dav/lib/CalDAV/Schedule/Plugin.php37
-rw-r--r--apps/dav/lib/CardDAV/AddressBook.php2
-rw-r--r--apps/dav/lib/CardDAV/CardDavBackend.php3
-rw-r--r--apps/dav/lib/Connector/Sabre/AppleQuirksPlugin.php133
-rw-r--r--apps/dav/lib/Connector/Sabre/Directory.php12
-rw-r--r--apps/dav/lib/Connector/Sabre/FilesPlugin.php2
-rw-r--r--apps/dav/lib/Connector/Sabre/Node.php4
-rw-r--r--apps/dav/lib/Connector/Sabre/SharesPlugin.php1
-rw-r--r--apps/dav/lib/DAV/CustomPropertiesBackend.php2
-rw-r--r--apps/dav/lib/Events/CalendarShareUpdatedEvent.php6
-rw-r--r--apps/dav/lib/Migration/RemoveObjectProperties.php2
-rw-r--r--apps/dav/lib/Server.php5
-rw-r--r--apps/dav/lib/Upload/ChunkingV2Plugin.php392
-rw-r--r--apps/dav/lib/Upload/FutureFile.php5
-rw-r--r--apps/dav/lib/Upload/PartFile.php111
-rw-r--r--apps/dav/lib/Upload/UploadFile.php16
-rw-r--r--apps/dav/lib/Upload/UploadFolder.php30
-rw-r--r--apps/dav/lib/Upload/UploadHome.php21
-rw-r--r--apps/dav/src/dav/client.js4
-rw-r--r--apps/dav/src/service/CalendarService.js6
-rw-r--r--apps/dav/src/settings-personal-availability.js2
-rw-r--r--apps/dav/src/settings.js2
-rw-r--r--apps/dav/src/views/Availability.vue12
-rw-r--r--apps/dav/src/views/CalDavSettings.spec.js2
-rw-r--r--apps/dav/src/views/CalDavSettings.vue4
-rw-r--r--apps/dav/tests/unit/BackgroundJob/UserStatusAutomationTest.php204
-rw-r--r--apps/dav/tests/unit/CalDAV/Schedule/IMipPluginTest.php10
-rw-r--r--apps/dav/tests/unit/CalDAV/Schedule/PluginTest.php29
-rw-r--r--apps/dav/tests/unit/Connector/Sabre/DirectoryTest.php15
-rw-r--r--apps/dav/tests/unit/Connector/Sabre/ObjectTreeTest.php3
-rw-r--r--apps/dav/tests/unit/Connector/Sabre/SharesPluginTest.php1
85 files changed, 1627 insertions, 118 deletions
diff --git a/apps/dav/appinfo/info.xml b/apps/dav/appinfo/info.xml
index dd657564ea9..b37e73fa5b6 100644
--- a/apps/dav/appinfo/info.xml
+++ b/apps/dav/appinfo/info.xml
@@ -5,7 +5,7 @@
<name>WebDAV</name>
<summary>WebDAV endpoint</summary>
<description>WebDAV endpoint</description>
- <version>1.25.0</version>
+ <version>1.26.0</version>
<licence>agpl</licence>
<author>owncloud.org</author>
<namespace>DAV</namespace>
@@ -15,7 +15,7 @@
<category>integration</category>
<bugs>https://github.com/nextcloud/server/issues</bugs>
<dependencies>
- <nextcloud min-version="26" max-version="26"/>
+ <nextcloud min-version="27" max-version="27"/>
</dependencies>
<background-jobs>
diff --git a/apps/dav/composer/composer/autoload_classmap.php b/apps/dav/composer/composer/autoload_classmap.php
index a100dac1d85..a9bf60698fd 100644
--- a/apps/dav/composer/composer/autoload_classmap.php
+++ b/apps/dav/composer/composer/autoload_classmap.php
@@ -143,6 +143,7 @@ return array(
'OCA\\DAV\\Connector\\LegacyDAVACL' => $baseDir . '/../lib/Connector/LegacyDAVACL.php',
'OCA\\DAV\\Connector\\PublicAuth' => $baseDir . '/../lib/Connector/PublicAuth.php',
'OCA\\DAV\\Connector\\Sabre\\AnonymousOptionsPlugin' => $baseDir . '/../lib/Connector/Sabre/AnonymousOptionsPlugin.php',
+ 'OCA\\DAV\\Connector\\Sabre\\AppleQuirksPlugin' => $baseDir . '/../lib/Connector/Sabre/AppleQuirksPlugin.php',
'OCA\\DAV\\Connector\\Sabre\\Auth' => $baseDir . '/../lib/Connector/Sabre/Auth.php',
'OCA\\DAV\\Connector\\Sabre\\BearerAuth' => $baseDir . '/../lib/Connector/Sabre/BearerAuth.php',
'OCA\\DAV\\Connector\\Sabre\\BlockLegacyClientPlugin' => $baseDir . '/../lib/Connector/Sabre/BlockLegacyClientPlugin.php',
@@ -310,8 +311,10 @@ return array(
'OCA\\DAV\\Traits\\PrincipalProxyTrait' => $baseDir . '/../lib/Traits/PrincipalProxyTrait.php',
'OCA\\DAV\\Upload\\AssemblyStream' => $baseDir . '/../lib/Upload/AssemblyStream.php',
'OCA\\DAV\\Upload\\ChunkingPlugin' => $baseDir . '/../lib/Upload/ChunkingPlugin.php',
+ 'OCA\\DAV\\Upload\\ChunkingV2Plugin' => $baseDir . '/../lib/Upload/ChunkingV2Plugin.php',
'OCA\\DAV\\Upload\\CleanupService' => $baseDir . '/../lib/Upload/CleanupService.php',
'OCA\\DAV\\Upload\\FutureFile' => $baseDir . '/../lib/Upload/FutureFile.php',
+ 'OCA\\DAV\\Upload\\PartFile' => $baseDir . '/../lib/Upload/PartFile.php',
'OCA\\DAV\\Upload\\RootCollection' => $baseDir . '/../lib/Upload/RootCollection.php',
'OCA\\DAV\\Upload\\UploadFile' => $baseDir . '/../lib/Upload/UploadFile.php',
'OCA\\DAV\\Upload\\UploadFolder' => $baseDir . '/../lib/Upload/UploadFolder.php',
diff --git a/apps/dav/composer/composer/autoload_static.php b/apps/dav/composer/composer/autoload_static.php
index 4187bb6c6f3..48104281cd4 100644
--- a/apps/dav/composer/composer/autoload_static.php
+++ b/apps/dav/composer/composer/autoload_static.php
@@ -158,6 +158,7 @@ class ComposerStaticInitDAV
'OCA\\DAV\\Connector\\LegacyDAVACL' => __DIR__ . '/..' . '/../lib/Connector/LegacyDAVACL.php',
'OCA\\DAV\\Connector\\PublicAuth' => __DIR__ . '/..' . '/../lib/Connector/PublicAuth.php',
'OCA\\DAV\\Connector\\Sabre\\AnonymousOptionsPlugin' => __DIR__ . '/..' . '/../lib/Connector/Sabre/AnonymousOptionsPlugin.php',
+ 'OCA\\DAV\\Connector\\Sabre\\AppleQuirksPlugin' => __DIR__ . '/..' . '/../lib/Connector/Sabre/AppleQuirksPlugin.php',
'OCA\\DAV\\Connector\\Sabre\\Auth' => __DIR__ . '/..' . '/../lib/Connector/Sabre/Auth.php',
'OCA\\DAV\\Connector\\Sabre\\BearerAuth' => __DIR__ . '/..' . '/../lib/Connector/Sabre/BearerAuth.php',
'OCA\\DAV\\Connector\\Sabre\\BlockLegacyClientPlugin' => __DIR__ . '/..' . '/../lib/Connector/Sabre/BlockLegacyClientPlugin.php',
@@ -325,8 +326,10 @@ class ComposerStaticInitDAV
'OCA\\DAV\\Traits\\PrincipalProxyTrait' => __DIR__ . '/..' . '/../lib/Traits/PrincipalProxyTrait.php',
'OCA\\DAV\\Upload\\AssemblyStream' => __DIR__ . '/..' . '/../lib/Upload/AssemblyStream.php',
'OCA\\DAV\\Upload\\ChunkingPlugin' => __DIR__ . '/..' . '/../lib/Upload/ChunkingPlugin.php',
+ 'OCA\\DAV\\Upload\\ChunkingV2Plugin' => __DIR__ . '/..' . '/../lib/Upload/ChunkingV2Plugin.php',
'OCA\\DAV\\Upload\\CleanupService' => __DIR__ . '/..' . '/../lib/Upload/CleanupService.php',
'OCA\\DAV\\Upload\\FutureFile' => __DIR__ . '/..' . '/../lib/Upload/FutureFile.php',
+ 'OCA\\DAV\\Upload\\PartFile' => __DIR__ . '/..' . '/../lib/Upload/PartFile.php',
'OCA\\DAV\\Upload\\RootCollection' => __DIR__ . '/..' . '/../lib/Upload/RootCollection.php',
'OCA\\DAV\\Upload\\UploadFile' => __DIR__ . '/..' . '/../lib/Upload/UploadFile.php',
'OCA\\DAV\\Upload\\UploadFolder' => __DIR__ . '/..' . '/../lib/Upload/UploadFolder.php',
diff --git a/apps/dav/img/calendar.svg b/apps/dav/img/calendar.svg
new file mode 100644
index 00000000000..fed04535dab
--- /dev/null
+++ b/apps/dav/img/calendar.svg
@@ -0,0 +1 @@
+<svg height="16" width="16" xmlns="http://www.w3.org/2000/svg" version="1.1" viewbox="0 0 16 16"><path fill="#000" d="m4 1c-0.5 0-1 0.5-1 1v2c0 0.5 0.5 1 1 1s1-0.5 1-1v-2c0-0.5-0.5-1-1-1zm8 0c-0.5 0-1 0.5-1 1v2c0 0.5 0.5 1 1 1s1-0.5 1-1v-2c0-0.5-0.5-1-1-1zm-6.5 2v1c0 0.831-0.5 1.5-1.5 1.5s-1.5-0.5-1.5-1.5v-0.9375c-0.8841 0.227-1.5 1.0247-1.5 1.9375v8c0 1.108 0.892 2 2 2h10c1.108 0 2-0.892 2-2v-8c0-0.9128-0.61588-1.7105-1.5-1.9375v0.9375c0 0.831-0.5 1.5-1.5 1.5s-1.5-0.5-1.5-1.5v-1zm7.5 5v5h-10v-5z"/></svg>
diff --git a/apps/dav/l10n/bg.js b/apps/dav/l10n/bg.js
index e43c36c2e1a..a5931ec3bbb 100644
--- a/apps/dav/l10n/bg.js
+++ b/apps/dav/l10n/bg.js
@@ -75,6 +75,8 @@ OC.L10N.register(
"\"%1$s\" has been canceled" : "„%1$s“ е отказано",
"Re: %1$s" : "Re: %1$s",
"%1$s has responded to your invitation" : "%1$s отговори/ха на вашата покана",
+ "Invitation updated: %1$s" : "Поканата е актуализирана: %1$s",
+ "%1$s updated the event \"%2$s\"" : "%1$s актуализира събитието „%2$s“",
"Invitation: %1$s" : "Покана: %1$s",
"%1$s would like to invite you to \"%2$s\"" : "%1$s желае да ви покани на „%2$s“",
"Organizer:" : "Organizer/организатор/:",
diff --git a/apps/dav/l10n/bg.json b/apps/dav/l10n/bg.json
index 284f165abd9..c8d8d6e3ae7 100644
--- a/apps/dav/l10n/bg.json
+++ b/apps/dav/l10n/bg.json
@@ -73,6 +73,8 @@
"\"%1$s\" has been canceled" : "„%1$s“ е отказано",
"Re: %1$s" : "Re: %1$s",
"%1$s has responded to your invitation" : "%1$s отговори/ха на вашата покана",
+ "Invitation updated: %1$s" : "Поканата е актуализирана: %1$s",
+ "%1$s updated the event \"%2$s\"" : "%1$s актуализира събитието „%2$s“",
"Invitation: %1$s" : "Покана: %1$s",
"%1$s would like to invite you to \"%2$s\"" : "%1$s желае да ви покани на „%2$s“",
"Organizer:" : "Organizer/организатор/:",
diff --git a/apps/dav/l10n/ca.js b/apps/dav/l10n/ca.js
index 1174802208c..7a4a3c89a0f 100644
--- a/apps/dav/l10n/ca.js
+++ b/apps/dav/l10n/ca.js
@@ -72,8 +72,13 @@ OC.L10N.register(
"Where: %s" : "On: %s",
"%1$s via %2$s" : "%1$s mitjançant %2$s",
"Cancelled: %1$s" : "Cancel·lat: %1$s",
+ "\"%1$s\" has been canceled" : "\"%1$s\" s'ha cancel·lat",
"Re: %1$s" : "Re: %1$s",
+ "%1$s has responded to your invitation" : "%1$s ha respost a la teva invitació",
+ "Invitation updated: %1$s" : "Invitació actualitzada: %1$s",
+ "%1$s updated the event \"%2$s\"" : "%1$s ha actualitzat l'esdeveniment \"%2$s\"",
"Invitation: %1$s" : "Invitació: %1$s",
+ "%1$s would like to invite you to \"%2$s\"" : "%1$s vol convidar-vos a \"%2$s\"",
"Organizer:" : "Organitzador:",
"Attendees:" : "Assistents:",
"Title:" : "Títol:",
diff --git a/apps/dav/l10n/ca.json b/apps/dav/l10n/ca.json
index 0a6f4402503..20db175d8c4 100644
--- a/apps/dav/l10n/ca.json
+++ b/apps/dav/l10n/ca.json
@@ -70,8 +70,13 @@
"Where: %s" : "On: %s",
"%1$s via %2$s" : "%1$s mitjançant %2$s",
"Cancelled: %1$s" : "Cancel·lat: %1$s",
+ "\"%1$s\" has been canceled" : "\"%1$s\" s'ha cancel·lat",
"Re: %1$s" : "Re: %1$s",
+ "%1$s has responded to your invitation" : "%1$s ha respost a la teva invitació",
+ "Invitation updated: %1$s" : "Invitació actualitzada: %1$s",
+ "%1$s updated the event \"%2$s\"" : "%1$s ha actualitzat l'esdeveniment \"%2$s\"",
"Invitation: %1$s" : "Invitació: %1$s",
+ "%1$s would like to invite you to \"%2$s\"" : "%1$s vol convidar-vos a \"%2$s\"",
"Organizer:" : "Organitzador:",
"Attendees:" : "Assistents:",
"Title:" : "Títol:",
diff --git a/apps/dav/l10n/cs.js b/apps/dav/l10n/cs.js
index fa814d575e0..ce1e7d55b54 100644
--- a/apps/dav/l10n/cs.js
+++ b/apps/dav/l10n/cs.js
@@ -75,6 +75,8 @@ OC.L10N.register(
"\"%1$s\" has been canceled" : "„%1$s“ bylo zrušeno",
"Re: %1$s" : "Odp.: %1$s",
"%1$s has responded to your invitation" : "%1$s odpověděl(a) na vaši pozvánku",
+ "Invitation updated: %1$s" : "Pozvánka aktualizována: %1$s",
+ "%1$s updated the event \"%2$s\"" : "%1$s zaktualizoval(a) událost „%2$s",
"Invitation: %1$s" : "Pozvánka: %1$s",
"%1$s would like to invite you to \"%2$s\"" : "%1$s by vás ráda pozval(a) na „%2$s“",
"Organizer:" : "Organizátor:",
@@ -129,7 +131,7 @@ OC.L10N.register(
"Could not rename part file assembled from chunks" : "Nedaří se přejmenovat částečný soubor složený ze shluků",
"Failed to write file contents: %1$s" : "Nepodařilo se zapsat obsahy souborů: %1$s",
"File not found: %1$s" : "Soubor nenalezen: %1$s",
- "System is in maintenance mode." : "Na systému právě probíhá údržba.",
+ "System is in maintenance mode." : "Systém se právě nachází v režimu údržby.",
"Upgrade needed" : "Je třeba přejít na novější verzi",
"Your %s needs to be configured to use HTTPS in order to use CalDAV and CardDAV with iOS/macOS." : "Váš %s potřebuje být nastavený aby používal HTTPS, aby bylo možné používat CalDAV a CardDAV s iOS/macOS.",
"Configures a CalDAV account" : "Nastaví CalDAV účet",
diff --git a/apps/dav/l10n/cs.json b/apps/dav/l10n/cs.json
index 9234e5b9d99..4ecd597e1bd 100644
--- a/apps/dav/l10n/cs.json
+++ b/apps/dav/l10n/cs.json
@@ -73,6 +73,8 @@
"\"%1$s\" has been canceled" : "„%1$s“ bylo zrušeno",
"Re: %1$s" : "Odp.: %1$s",
"%1$s has responded to your invitation" : "%1$s odpověděl(a) na vaši pozvánku",
+ "Invitation updated: %1$s" : "Pozvánka aktualizována: %1$s",
+ "%1$s updated the event \"%2$s\"" : "%1$s zaktualizoval(a) událost „%2$s",
"Invitation: %1$s" : "Pozvánka: %1$s",
"%1$s would like to invite you to \"%2$s\"" : "%1$s by vás ráda pozval(a) na „%2$s“",
"Organizer:" : "Organizátor:",
@@ -127,7 +129,7 @@
"Could not rename part file assembled from chunks" : "Nedaří se přejmenovat částečný soubor složený ze shluků",
"Failed to write file contents: %1$s" : "Nepodařilo se zapsat obsahy souborů: %1$s",
"File not found: %1$s" : "Soubor nenalezen: %1$s",
- "System is in maintenance mode." : "Na systému právě probíhá údržba.",
+ "System is in maintenance mode." : "Systém se právě nachází v režimu údržby.",
"Upgrade needed" : "Je třeba přejít na novější verzi",
"Your %s needs to be configured to use HTTPS in order to use CalDAV and CardDAV with iOS/macOS." : "Váš %s potřebuje být nastavený aby používal HTTPS, aby bylo možné používat CalDAV a CardDAV s iOS/macOS.",
"Configures a CalDAV account" : "Nastaví CalDAV účet",
diff --git a/apps/dav/l10n/de.js b/apps/dav/l10n/de.js
index 0765e4406e5..adff7fabf43 100644
--- a/apps/dav/l10n/de.js
+++ b/apps/dav/l10n/de.js
@@ -75,6 +75,8 @@ OC.L10N.register(
"\"%1$s\" has been canceled" : "\"%1$s\" wurde abgebrochen.",
"Re: %1$s" : "Re: %1$s",
"%1$s has responded to your invitation" : "%1$s hat auf deine Einladung geantwortet.",
+ "Invitation updated: %1$s" : "Einladung aktualisiert: %1$s",
+ "%1$s updated the event \"%2$s\"" : "%1$s hat die Veranstaltung \"%2$s\" aktualisiert",
"Invitation: %1$s" : "Einladung: %1$s",
"%1$s would like to invite you to \"%2$s\"" : "%1$s möchte dich zu \"%2$s\" einladen.",
"Organizer:" : "Organisator:",
diff --git a/apps/dav/l10n/de.json b/apps/dav/l10n/de.json
index 368950ad5d1..3aecada1d1f 100644
--- a/apps/dav/l10n/de.json
+++ b/apps/dav/l10n/de.json
@@ -73,6 +73,8 @@
"\"%1$s\" has been canceled" : "\"%1$s\" wurde abgebrochen.",
"Re: %1$s" : "Re: %1$s",
"%1$s has responded to your invitation" : "%1$s hat auf deine Einladung geantwortet.",
+ "Invitation updated: %1$s" : "Einladung aktualisiert: %1$s",
+ "%1$s updated the event \"%2$s\"" : "%1$s hat die Veranstaltung \"%2$s\" aktualisiert",
"Invitation: %1$s" : "Einladung: %1$s",
"%1$s would like to invite you to \"%2$s\"" : "%1$s möchte dich zu \"%2$s\" einladen.",
"Organizer:" : "Organisator:",
diff --git a/apps/dav/l10n/de_DE.js b/apps/dav/l10n/de_DE.js
index 1868f3a9190..ab170d444a3 100644
--- a/apps/dav/l10n/de_DE.js
+++ b/apps/dav/l10n/de_DE.js
@@ -72,9 +72,11 @@ OC.L10N.register(
"Where: %s" : "Ort: %s",
"%1$s via %2$s" : "%1$s über %2$s",
"Cancelled: %1$s" : "Abgesagt: %1$s",
- "\"%1$s\" has been canceled" : "\"%1$s\" wurde abgebrochen",
+ "\"%1$s\" has been canceled" : "\"%1$s“ wurde abgesagt.",
"Re: %1$s" : "Re: %1$s",
"%1$s has responded to your invitation" : "%1$s hat auf Ihre Einladung geantwortet",
+ "Invitation updated: %1$s" : "Einladung aktualisiert: %1$s",
+ "%1$s updated the event \"%2$s\"" : "%1$s hat die Veranstaltung \"%2$s\" aktualisiert",
"Invitation: %1$s" : "Einladung: %1$s",
"%1$s would like to invite you to \"%2$s\"" : "%1$s möchte Sie zu \"%2$s\" einladen",
"Organizer:" : "Organisator:",
diff --git a/apps/dav/l10n/de_DE.json b/apps/dav/l10n/de_DE.json
index 09d29f0ce62..ab756378088 100644
--- a/apps/dav/l10n/de_DE.json
+++ b/apps/dav/l10n/de_DE.json
@@ -70,9 +70,11 @@
"Where: %s" : "Ort: %s",
"%1$s via %2$s" : "%1$s über %2$s",
"Cancelled: %1$s" : "Abgesagt: %1$s",
- "\"%1$s\" has been canceled" : "\"%1$s\" wurde abgebrochen",
+ "\"%1$s\" has been canceled" : "\"%1$s“ wurde abgesagt.",
"Re: %1$s" : "Re: %1$s",
"%1$s has responded to your invitation" : "%1$s hat auf Ihre Einladung geantwortet",
+ "Invitation updated: %1$s" : "Einladung aktualisiert: %1$s",
+ "%1$s updated the event \"%2$s\"" : "%1$s hat die Veranstaltung \"%2$s\" aktualisiert",
"Invitation: %1$s" : "Einladung: %1$s",
"%1$s would like to invite you to \"%2$s\"" : "%1$s möchte Sie zu \"%2$s\" einladen",
"Organizer:" : "Organisator:",
diff --git a/apps/dav/l10n/en_GB.js b/apps/dav/l10n/en_GB.js
index ac17c21b6d4..4c8ab334d66 100644
--- a/apps/dav/l10n/en_GB.js
+++ b/apps/dav/l10n/en_GB.js
@@ -75,6 +75,8 @@ OC.L10N.register(
"\"%1$s\" has been canceled" : "\"%1$s\" has been cancelled",
"Re: %1$s" : "Re: %1$s",
"%1$s has responded to your invitation" : "%1$s has responded to your invitation",
+ "Invitation updated: %1$s" : "Invitation updated: %1$s",
+ "%1$s updated the event \"%2$s\"" : "%1$s updated the event \"%2$s\"",
"Invitation: %1$s" : "Invitation: %1$s",
"%1$s would like to invite you to \"%2$s\"" : "%1$s would like to invite you to \"%2$s\"",
"Organizer:" : "Organiser:",
diff --git a/apps/dav/l10n/en_GB.json b/apps/dav/l10n/en_GB.json
index ee386676c4a..1a9010fe006 100644
--- a/apps/dav/l10n/en_GB.json
+++ b/apps/dav/l10n/en_GB.json
@@ -73,6 +73,8 @@
"\"%1$s\" has been canceled" : "\"%1$s\" has been cancelled",
"Re: %1$s" : "Re: %1$s",
"%1$s has responded to your invitation" : "%1$s has responded to your invitation",
+ "Invitation updated: %1$s" : "Invitation updated: %1$s",
+ "%1$s updated the event \"%2$s\"" : "%1$s updated the event \"%2$s\"",
"Invitation: %1$s" : "Invitation: %1$s",
"%1$s would like to invite you to \"%2$s\"" : "%1$s would like to invite you to \"%2$s\"",
"Organizer:" : "Organiser:",
diff --git a/apps/dav/l10n/es.js b/apps/dav/l10n/es.js
index 507b822cb20..2ae42f1c897 100644
--- a/apps/dav/l10n/es.js
+++ b/apps/dav/l10n/es.js
@@ -75,6 +75,8 @@ OC.L10N.register(
"\"%1$s\" has been canceled" : "\"%1$s\" ha sido cancelada",
"Re: %1$s" : "Re: %1$s",
"%1$s has responded to your invitation" : "%1$s ha respondido a su invitación",
+ "Invitation updated: %1$s" : "Invitación actualizada: %1$s",
+ "%1$s updated the event \"%2$s\"" : "%1$s actualizó el evento \"%2$s\"",
"Invitation: %1$s" : "Invitación: %1$s",
"%1$s would like to invite you to \"%2$s\"" : "%1$s desea invitarle a \"%2$s\"",
"Organizer:" : "Organizador:",
diff --git a/apps/dav/l10n/es.json b/apps/dav/l10n/es.json
index d0b55436281..203c52acb65 100644
--- a/apps/dav/l10n/es.json
+++ b/apps/dav/l10n/es.json
@@ -73,6 +73,8 @@
"\"%1$s\" has been canceled" : "\"%1$s\" ha sido cancelada",
"Re: %1$s" : "Re: %1$s",
"%1$s has responded to your invitation" : "%1$s ha respondido a su invitación",
+ "Invitation updated: %1$s" : "Invitación actualizada: %1$s",
+ "%1$s updated the event \"%2$s\"" : "%1$s actualizó el evento \"%2$s\"",
"Invitation: %1$s" : "Invitación: %1$s",
"%1$s would like to invite you to \"%2$s\"" : "%1$s desea invitarle a \"%2$s\"",
"Organizer:" : "Organizador:",
diff --git a/apps/dav/l10n/eu.js b/apps/dav/l10n/eu.js
index fbc0f4f196f..6e02ca53f49 100644
--- a/apps/dav/l10n/eu.js
+++ b/apps/dav/l10n/eu.js
@@ -74,6 +74,9 @@ OC.L10N.register(
"Cancelled: %1$s" : "Utzita: %1$s",
"\"%1$s\" has been canceled" : "\"%1$s\" bertan behera utzi da",
"Re: %1$s" : "Er: %1$s",
+ "%1$s has responded to your invitation" : "%1$s -(e)k zure gonbidapena erantzun du",
+ "Invitation updated: %1$s" : "Gonbidapena eguneratuta: %1$s",
+ "%1$s updated the event \"%2$s\"" : "%1$s-k \"%2$s\" ekitaldia eguneratu du",
"Invitation: %1$s" : "Gonbidapena: %1$s",
"%1$s would like to invite you to \"%2$s\"" : "%1$s-k \"%2$s\"-ra gonbidatu nahi zaitu",
"Organizer:" : "Antolatzailea:",
diff --git a/apps/dav/l10n/eu.json b/apps/dav/l10n/eu.json
index 88f2cfd40d2..14f1f3de4a5 100644
--- a/apps/dav/l10n/eu.json
+++ b/apps/dav/l10n/eu.json
@@ -72,6 +72,9 @@
"Cancelled: %1$s" : "Utzita: %1$s",
"\"%1$s\" has been canceled" : "\"%1$s\" bertan behera utzi da",
"Re: %1$s" : "Er: %1$s",
+ "%1$s has responded to your invitation" : "%1$s -(e)k zure gonbidapena erantzun du",
+ "Invitation updated: %1$s" : "Gonbidapena eguneratuta: %1$s",
+ "%1$s updated the event \"%2$s\"" : "%1$s-k \"%2$s\" ekitaldia eguneratu du",
"Invitation: %1$s" : "Gonbidapena: %1$s",
"%1$s would like to invite you to \"%2$s\"" : "%1$s-k \"%2$s\"-ra gonbidatu nahi zaitu",
"Organizer:" : "Antolatzailea:",
diff --git a/apps/dav/l10n/fr.js b/apps/dav/l10n/fr.js
index 97a417a8609..92a95ba9995 100644
--- a/apps/dav/l10n/fr.js
+++ b/apps/dav/l10n/fr.js
@@ -37,7 +37,7 @@ OC.L10N.register(
"{actor} restored event {event} of calendar {calendar}" : "{actor} a restauré l'événement {event} dans l'agenda {calendar}",
"You restored event {event} of calendar {calendar}" : "Vous avez restauré l'événement {event} dans l'agenda {calendar}",
"Busy" : "Occupé",
- "{actor} created to-do {todo} in list {calendar}" : "{actor} a créé le pense-bête {todo} dans la liste {calendar}",
+ "{actor} created to-do {todo} in list {calendar}" : "{actor} a créé la tâche {todo} dans la liste {calendar}",
"You created to-do {todo} in list {calendar}" : "Vous avez créé un pense-bête {todo} dans la liste {calendar}",
"{actor} deleted to-do {todo} from list {calendar}" : "{actor} a supprimé un pense-bête {todo} de la liste {calendar}",
"You deleted to-do {todo} from list {calendar}" : "Vous avez supprimé le pense-bête {todo} de la liste {calendar}",
@@ -75,6 +75,8 @@ OC.L10N.register(
"\"%1$s\" has been canceled" : "\"%1$s\" a été annulé(e)",
"Re: %1$s" : "Re : %1$s",
"%1$s has responded to your invitation" : "%1$s a répondu à votre invitation",
+ "Invitation updated: %1$s" : "Invitation mise à jour : %1$s",
+ "%1$s updated the event \"%2$s\"" : "%1$s a mis à jour l'évènement %2$s",
"Invitation: %1$s" : "Invitation : %1$s",
"%1$s would like to invite you to \"%2$s\"" : "%1$s souhaite vous inviter à \"%2$s\"",
"Organizer:" : "Organisateur :",
@@ -85,7 +87,7 @@ OC.L10N.register(
"Link:" : "Lien :",
"Accept" : "Accepter",
"Decline" : "Refuser",
- "More options …" : "Plus d'options …",
+ "More options …" : "Plus d'options…",
"More options at %s" : "Plus d'options à %s",
"Contacts" : "Contacts",
"{actor} created address book {addressbook}" : "{actor} a créé le carnet d'adresses {addressbook}",
diff --git a/apps/dav/l10n/fr.json b/apps/dav/l10n/fr.json
index f07134e100f..b266d7c3dff 100644
--- a/apps/dav/l10n/fr.json
+++ b/apps/dav/l10n/fr.json
@@ -35,7 +35,7 @@
"{actor} restored event {event} of calendar {calendar}" : "{actor} a restauré l'événement {event} dans l'agenda {calendar}",
"You restored event {event} of calendar {calendar}" : "Vous avez restauré l'événement {event} dans l'agenda {calendar}",
"Busy" : "Occupé",
- "{actor} created to-do {todo} in list {calendar}" : "{actor} a créé le pense-bête {todo} dans la liste {calendar}",
+ "{actor} created to-do {todo} in list {calendar}" : "{actor} a créé la tâche {todo} dans la liste {calendar}",
"You created to-do {todo} in list {calendar}" : "Vous avez créé un pense-bête {todo} dans la liste {calendar}",
"{actor} deleted to-do {todo} from list {calendar}" : "{actor} a supprimé un pense-bête {todo} de la liste {calendar}",
"You deleted to-do {todo} from list {calendar}" : "Vous avez supprimé le pense-bête {todo} de la liste {calendar}",
@@ -73,6 +73,8 @@
"\"%1$s\" has been canceled" : "\"%1$s\" a été annulé(e)",
"Re: %1$s" : "Re : %1$s",
"%1$s has responded to your invitation" : "%1$s a répondu à votre invitation",
+ "Invitation updated: %1$s" : "Invitation mise à jour : %1$s",
+ "%1$s updated the event \"%2$s\"" : "%1$s a mis à jour l'évènement %2$s",
"Invitation: %1$s" : "Invitation : %1$s",
"%1$s would like to invite you to \"%2$s\"" : "%1$s souhaite vous inviter à \"%2$s\"",
"Organizer:" : "Organisateur :",
@@ -83,7 +85,7 @@
"Link:" : "Lien :",
"Accept" : "Accepter",
"Decline" : "Refuser",
- "More options …" : "Plus d'options …",
+ "More options …" : "Plus d'options…",
"More options at %s" : "Plus d'options à %s",
"Contacts" : "Contacts",
"{actor} created address book {addressbook}" : "{actor} a créé le carnet d'adresses {addressbook}",
diff --git a/apps/dav/l10n/gl.js b/apps/dav/l10n/gl.js
index 503de28a313..3245bb7bb20 100644
--- a/apps/dav/l10n/gl.js
+++ b/apps/dav/l10n/gl.js
@@ -2,6 +2,7 @@ OC.L10N.register(
"dav",
{
"Calendar" : "Calendario",
+ "To-dos" : "Tarefas pendentes",
"Personal" : "Persoal",
"{actor} created calendar {calendar}" : "{actor} creou o calendario {calendar}",
"You created calendar {calendar}" : "Vostede creou o calendario {calendar}",
@@ -9,6 +10,8 @@ OC.L10N.register(
"You deleted calendar {calendar}" : "Vostede eliminou o calendario {calendar}",
"{actor} updated calendar {calendar}" : "{actor} actualizou o calendario {calendar}",
"You updated calendar {calendar}" : "Vostede actualizou o calendario {calendar}",
+ "{actor} restored calendar {calendar}" : "{actor} restaurou o calendario {calendar}",
+ "You restored calendar {calendar}" : "Restauraches o calendario {calendar}",
"You shared calendar {calendar} as public link" : "Vostede compartiu o calendario {calendar} como ligazón pública",
"You removed public link for calendar {calendar}" : "Vostede retirou a ligazón pública do calendario {calendar}",
"{actor} shared calendar {calendar} with you" : "{actor} compartiu o calendario {calendar} con vostede",
@@ -29,9 +32,27 @@ OC.L10N.register(
"You deleted event {event} from calendar {calendar}" : "Vostede eliminou o evento {event} do calendario {calendar}",
"{actor} updated event {event} in calendar {calendar}" : "{actor} actualizou o evento {event} no calendario {calendar}",
"You updated event {event} in calendar {calendar}" : "Vostede actualizou o evento {event} no calendario {calendar}",
+ "{actor} moved event {event} from calendar {sourceCalendar} to calendar {targetCalendar}" : "{actor} moveu o evento {event} do calendario {sourceCalendar} ao calendario {targetCalendar}",
+ "You moved event {event} from calendar {sourceCalendar} to calendar {targetCalendar}" : "Moveches o evento {evento} do calendario {sourceCalendar} ao calendario {targetCalendar}",
+ "{actor} restored event {event} of calendar {calendar}" : "{actor} restaurou o evento {evento} do calendario {calendar}",
+ "You restored event {event} of calendar {calendar}" : "Restauraches o evento {evento} do calendario {calendar}",
"Busy" : "Ocupado",
+ "{actor} created to-do {todo} in list {calendar}" : "{actor} creou as tarefas {todo} na lista {calendar}",
+ "You created to-do {todo} in list {calendar}" : "Creaches a tarefa {todo} na lista {calendar}",
+ "{actor} deleted to-do {todo} from list {calendar}" : "{actor} eliminou as tarefas pendentes {todo} da lista {calendar}",
+ "You deleted to-do {todo} from list {calendar}" : "Eliminaches as tarefas pendentes {todo} da lista {calendar}",
+ "{actor} updated to-do {todo} in list {calendar}" : "{actor} actualizou a tarefa {todo} na lista {calendar}",
+ "You updated to-do {todo} in list {calendar}" : "Actualizaches a tarefa {todo} na lista {calendar}",
+ "{actor} solved to-do {todo} in list {calendar}" : "{actor} resolveu as tarefas {todo} na lista {calendar}",
+ "You solved to-do {todo} in list {calendar}" : "Resolveches a tarefa {todo} na lista {calendar}",
+ "{actor} reopened to-do {todo} in list {calendar}" : "{actor} reabriu as tarefas {todo} na lista {calendar}",
+ "You reopened to-do {todo} in list {calendar}" : "Reabriches as tarefas {todo} na lista {calendar}",
+ "{actor} moved to-do {todo} from list {sourceCalendar} to list {targetCalendar}" : "{actor} moveu a tarefa {todo} da lista {sourceCalendar} á lista {targetCalendar}",
+ "You moved to-do {todo} from list {sourceCalendar} to list {targetCalendar}" : "Moveches a tarefa {todo} da lista {sourceCalendar} á lista {targetCalendar}",
+ "Calendar, contacts and tasks" : "Calendario, contactos e tarefas",
"A <strong>calendar</strong> was modified" : "Foi modificado un <strong>calendario</strong>",
"A calendar <strong>event</strong> was modified" : "Foi modificado un <strong>evento</strong> do calendario",
+ "A calendar <strong>to-do</strong> was modified" : "Modificouse unha <strong>tarefa</strong> do calendario",
"Contact birthdays" : "Aniversario do contacto",
"Death of %s" : "Falecemento de %s",
"Calendar:" : "Calendario:",
@@ -50,6 +71,14 @@ OC.L10N.register(
"Description: %s" : "Descrición: %s",
"Where: %s" : "Onde: %s",
"%1$s via %2$s" : "%1$s mediante %2$s",
+ "Cancelled: %1$s" : "Cancelado: %1$s",
+ "\"%1$s\" has been canceled" : "\"%1$s\" cancelouse",
+ "Re: %1$s" : "Re: %1$s",
+ "%1$s has responded to your invitation" : "%1$s respondeu á túa invitación",
+ "Invitation updated: %1$s" : "Invitación actualizada: %1$s",
+ "%1$s updated the event \"%2$s\"" : "%1$s actualizou o evento \"%2$s\"",
+ "Invitation: %1$s" : "Invitación: %1$s",
+ "%1$s would like to invite you to \"%2$s\"" : "%1$s quere invitarte a \"%2$s\"",
"Organizer:" : "Organizador:",
"Attendees:" : "Asistentes:",
"Title:" : "Título:",
@@ -61,6 +90,48 @@ OC.L10N.register(
"More options …" : "Máis opcións…",
"More options at %s" : "Máis opcións en %s",
"Contacts" : "Contactos",
+ "{actor} created address book {addressbook}" : "{actor} creou a axenda de enderezos {addressbook}",
+ "You created address book {addressbook}" : "Creaches a axenda de enderezos {addressbook}",
+ "{actor} deleted address book {addressbook}" : "{actor} eliminou a axenda de enderezos {addressbook}",
+ "You deleted address book {addressbook}" : "Eliminaches a axenda de enderezos {addressbook}",
+ "{actor} updated address book {addressbook}" : "{actor} actualizou a axenda de enderezos {addressbook}",
+ "You updated address book {addressbook}" : "Actualizaches a axenda de enderezos {addressbook}",
+ "{actor} shared address book {addressbook} with you" : "{actor} compartiu a axenda de enderezos {addressbook} contigo",
+ "You shared address book {addressbook} with {user}" : "Compartiches a axenda de enderezos {addressbook} con {user}",
+ "{actor} shared address book {addressbook} with {user}" : "{actor} compartiu a axenda de enderezos {addressbook} con {user}",
+ "{actor} unshared address book {addressbook} from you" : "{actor} deixou de compartir a túa axenda de enderezos {addressbook}",
+ "You unshared address book {addressbook} from {user}" : "Deixaches de compartir a axenda de enderezos {addressbook} de {user}",
+ "{actor} unshared address book {addressbook} from {user}" : "{actor} deixou de compartir a axenda de enderezos {addressbook} de {user}",
+ "{actor} unshared address book {addressbook} from themselves" : "{actor} deixaron de compartir a súa axenda de enderezos {addressbook}",
+ "You shared address book {addressbook} with group {group}" : "Compartiches a axenda de enderezos {addressbook} co grupo {group}",
+ "{actor} shared address book {addressbook} with group {group}" : "{actor} compartiu a axenda de enderezos {addressbook} co grupo {group}",
+ "You unshared address book {addressbook} from group {group}" : "Deixaches de compartir a axenda de enderezos {addressbook} do grupo {group}",
+ "{actor} unshared address book {addressbook} from group {group}" : "{actor} deixou de compartir axenda de enderezos {addressbook} do grupo {group}",
+ "{actor} created contact {card} in address book {addressbook}" : "{actor} creou o contacto {card} na axenda de enderezos {addressbook}",
+ "You created contact {card} in address book {addressbook}" : "Creaches o contacto {card} na axenda de enderezos {addressbook}",
+ "{actor} deleted contact {card} from address book {addressbook}" : "{actor} eliminou o contacto {card} da axenda de enderezos {addressbook}",
+ "You deleted contact {card} from address book {addressbook}" : "Eliminaches o contacto {card} da axenda de enderezos {addressbook}",
+ "{actor} updated contact {card} in address book {addressbook}" : "{actor} actualizou o contacto {card} na axenda de enderezos {addressbook}",
+ "You updated contact {card} in address book {addressbook}" : "Actualizaches o contacto {card} na axenda de enderezos {addressbook}",
+ "A <strong>contact</strong> or <strong>address book</strong> was modified" : "Modificouse un <strong>contacto</strong> ou <strong>axenda de enderezos</strong>",
+ "File is not updatable: %1$s" : "O ficheiro non se pode actualizar: %1$s",
+ "Could not write to final file, canceled by hook" : "Non foi posíbel escribir no ficheiro final, cancelouse polo hook",
+ "Could not write file contents" : "Non se puido escribir o contido do ficheiro",
+ "_%n byte_::_%n bytes_" : ["%n byte","%n bytes"],
+ "Error while copying file to target location (copied: %1$s, expected filesize: %2$s)" : "Produciuse un erro ao copiar o ficheiro na localización de destino (copiado: %1$s, tamaño esperado do ficheiro: %2$s)",
+ "Expected filesize of %1$s but read (from Nextcloud client) and wrote (to Nextcloud storage) %2$s. Could either be a network problem on the sending side or a problem writing to the storage on the server side." : "Tamaño esperado do ficheiro %1$s pero lido (do cliente de Nextcloud) e escrito (no almacenamento de Nextcloud) %2$s. Pode ser un problema de rede no lado do envío ou un problema ao escribir no almacenamento no lado do servidor.",
+ "Could not rename part file to final file, canceled by hook" : "Non foi posíbel cambiar o nome do ficheiro de parte ao ficheiro final, cancelado polo hook",
+ "Could not rename part file to final file" : "Non se puido cambiar o nome do ficheiro de parte ao ficheiro final",
+ "Failed to check file size: %1$s" : "Produciuse un erro ao comprobar o tamaño do ficheiro: %1$s",
+ "Could not open file" : "Non se puido abrir o ficheiro",
+ "Encryption not ready: %1$s" : "O cifrado non está listo: %1$s",
+ "Failed to open file: %1$s" : "Produciuse un erro ao abrir o ficheiro: %1$s",
+ "Failed to unlink: %1$s" : "Produciuse un erro ao desvincular: %1$s",
+ "Invalid chunk name" : "O nome do fragmento non é válido",
+ "Could not rename part file assembled from chunks" : "Non se puido cambiar o nome do ficheiro de pezas ensamblados a partir de fragmentos",
+ "Failed to write file contents: %1$s" : "Produciuse un erro ao escribir o contido do ficheiro: %1$s",
+ "File not found: %1$s" : "Arquivo non atopado: %1$s",
+ "System is in maintenance mode." : "O sistema está en modo de mantemento.",
"Upgrade needed" : "É necesario anovar actualizar",
"Your %s needs to be configured to use HTTPS in order to use CalDAV and CardDAV with iOS/macOS." : "É preciso configurar o seu %s para que empregue HTTPS para poder usar CalDAV e CardDAV con iOS / macOS.",
"Configures a CalDAV account" : "Configurar unha conta de CalDAV",
@@ -71,11 +142,18 @@ OC.L10N.register(
"Completed on %s" : "Rematado o %s",
"Due on %s by %s" : "Caduca o %s por %s",
"Due on %s" : "Caduca o %s",
+ "Migrated calendar (%1$s)" : "Calendario migrado (%1$s)",
+ "Calendars including events, details and attendees" : "Calendarios incluíndo eventos, detalles e asistentes",
"Contacts and groups" : "Contactos e grupos",
"WebDAV" : "WebDAV",
"WebDAV endpoint" : "Terminación WebDAV",
"Availability" : "Dispoñibilidade",
+ "If you configure your working hours, other users will see when you are out of office when they book a meeting." : "Se configuras o teu horario de traballo, outros usuarios verán cando estás fóra da oficina cando reserven unha reunión.",
+ "Time zone:" : "Franxa horaria:",
"to" : "para",
+ "Delete slot" : "Eliminar slot",
+ "No working hours set" : "Sen horario de traballo establecido",
+ "Add slot" : "Engadir slot",
"Monday" : "luns",
"Tuesday" : "martes",
"Wednesday" : "mércores",
@@ -83,7 +161,11 @@ OC.L10N.register(
"Friday" : "venres",
"Saturday" : "sábado",
"Sunday" : "domingo",
+ "Automatically set user status to \"Do not disturb\" outside of availability to mute all notifications." : "Establece automaticamente o estado do usuario en \"Non molestar\" fóra da dispoñibilidade para silenciar todas as notificacións.",
"Save" : "Gardar",
+ "Failed to load availability" : "Produciuse un erro ao cargar a dispoñibilidade",
+ "Saved availability" : "Dispoñibilidade gardada",
+ "Failed to save availability" : "Produciuse un erro ao gardar a dispoñibilidade",
"Calendar server" : "Servidor do calendario",
"Send invitations to attendees" : "Enviar convites aos asistentes",
"Automatically generate a birthday calendar" : "Xerar automaticamente o calendario de aniversarios",
@@ -91,7 +173,9 @@ OC.L10N.register(
"Hence they will not be available immediately after enabling but will show up after some time." : "Por isto, non estarán dispoñíbeis inmediatamente tras activalos, senón que aparecerán após certo tempo",
"Send notifications for events" : "Enviar notificacións para eventos",
"Notifications are sent via background jobs, so these must occur often enough." : "As notificacións enviaranse mediante procesos en segundo plano, polo que estes teñen que suceder con frecuencia.",
- "Enable notifications for events via push" : "Activar o envío de notificacións do servidor para eventos",
+ "Send reminder notifications to calendar sharees as well" : "Envía notificacións de recordatorio tamén aos que comparten calendario",
+ "Reminders are always sent to organizers and attendees." : "Os recordatorios sempre se envían aos organizadores e aos asistentes.",
+ "Enable notifications for events via push" : "Activar o envío de notificacións do automáticas para eventos",
"Also install the {calendarappstoreopen}Calendar app{linkclose}, or {calendardocopen}connect your desktop & mobile for syncing ↗{linkclose}." : "Instale tamén a {calendarappstoreopen}aplicación do Calendario{linkclose} ou {calendardocopen}conecte os seus escritorio e móbil para sincronizalos ↗{linkclose}.",
"Please make sure to properly set up {emailopen}the email server{linkclose}." : "Asegúrese de ter configurado correctamente {emailopen}o servidor de correo-e{linkclose}.",
"There was an error updating your attendance status." : "Produciuse un erro ao actualizar o seu estado de asistencia.",
diff --git a/apps/dav/l10n/gl.json b/apps/dav/l10n/gl.json
index bbabb9db7b0..3e8649ff7fb 100644
--- a/apps/dav/l10n/gl.json
+++ b/apps/dav/l10n/gl.json
@@ -1,5 +1,6 @@
{ "translations": {
"Calendar" : "Calendario",
+ "To-dos" : "Tarefas pendentes",
"Personal" : "Persoal",
"{actor} created calendar {calendar}" : "{actor} creou o calendario {calendar}",
"You created calendar {calendar}" : "Vostede creou o calendario {calendar}",
@@ -7,6 +8,8 @@
"You deleted calendar {calendar}" : "Vostede eliminou o calendario {calendar}",
"{actor} updated calendar {calendar}" : "{actor} actualizou o calendario {calendar}",
"You updated calendar {calendar}" : "Vostede actualizou o calendario {calendar}",
+ "{actor} restored calendar {calendar}" : "{actor} restaurou o calendario {calendar}",
+ "You restored calendar {calendar}" : "Restauraches o calendario {calendar}",
"You shared calendar {calendar} as public link" : "Vostede compartiu o calendario {calendar} como ligazón pública",
"You removed public link for calendar {calendar}" : "Vostede retirou a ligazón pública do calendario {calendar}",
"{actor} shared calendar {calendar} with you" : "{actor} compartiu o calendario {calendar} con vostede",
@@ -27,9 +30,27 @@
"You deleted event {event} from calendar {calendar}" : "Vostede eliminou o evento {event} do calendario {calendar}",
"{actor} updated event {event} in calendar {calendar}" : "{actor} actualizou o evento {event} no calendario {calendar}",
"You updated event {event} in calendar {calendar}" : "Vostede actualizou o evento {event} no calendario {calendar}",
+ "{actor} moved event {event} from calendar {sourceCalendar} to calendar {targetCalendar}" : "{actor} moveu o evento {event} do calendario {sourceCalendar} ao calendario {targetCalendar}",
+ "You moved event {event} from calendar {sourceCalendar} to calendar {targetCalendar}" : "Moveches o evento {evento} do calendario {sourceCalendar} ao calendario {targetCalendar}",
+ "{actor} restored event {event} of calendar {calendar}" : "{actor} restaurou o evento {evento} do calendario {calendar}",
+ "You restored event {event} of calendar {calendar}" : "Restauraches o evento {evento} do calendario {calendar}",
"Busy" : "Ocupado",
+ "{actor} created to-do {todo} in list {calendar}" : "{actor} creou as tarefas {todo} na lista {calendar}",
+ "You created to-do {todo} in list {calendar}" : "Creaches a tarefa {todo} na lista {calendar}",
+ "{actor} deleted to-do {todo} from list {calendar}" : "{actor} eliminou as tarefas pendentes {todo} da lista {calendar}",
+ "You deleted to-do {todo} from list {calendar}" : "Eliminaches as tarefas pendentes {todo} da lista {calendar}",
+ "{actor} updated to-do {todo} in list {calendar}" : "{actor} actualizou a tarefa {todo} na lista {calendar}",
+ "You updated to-do {todo} in list {calendar}" : "Actualizaches a tarefa {todo} na lista {calendar}",
+ "{actor} solved to-do {todo} in list {calendar}" : "{actor} resolveu as tarefas {todo} na lista {calendar}",
+ "You solved to-do {todo} in list {calendar}" : "Resolveches a tarefa {todo} na lista {calendar}",
+ "{actor} reopened to-do {todo} in list {calendar}" : "{actor} reabriu as tarefas {todo} na lista {calendar}",
+ "You reopened to-do {todo} in list {calendar}" : "Reabriches as tarefas {todo} na lista {calendar}",
+ "{actor} moved to-do {todo} from list {sourceCalendar} to list {targetCalendar}" : "{actor} moveu a tarefa {todo} da lista {sourceCalendar} á lista {targetCalendar}",
+ "You moved to-do {todo} from list {sourceCalendar} to list {targetCalendar}" : "Moveches a tarefa {todo} da lista {sourceCalendar} á lista {targetCalendar}",
+ "Calendar, contacts and tasks" : "Calendario, contactos e tarefas",
"A <strong>calendar</strong> was modified" : "Foi modificado un <strong>calendario</strong>",
"A calendar <strong>event</strong> was modified" : "Foi modificado un <strong>evento</strong> do calendario",
+ "A calendar <strong>to-do</strong> was modified" : "Modificouse unha <strong>tarefa</strong> do calendario",
"Contact birthdays" : "Aniversario do contacto",
"Death of %s" : "Falecemento de %s",
"Calendar:" : "Calendario:",
@@ -48,6 +69,14 @@
"Description: %s" : "Descrición: %s",
"Where: %s" : "Onde: %s",
"%1$s via %2$s" : "%1$s mediante %2$s",
+ "Cancelled: %1$s" : "Cancelado: %1$s",
+ "\"%1$s\" has been canceled" : "\"%1$s\" cancelouse",
+ "Re: %1$s" : "Re: %1$s",
+ "%1$s has responded to your invitation" : "%1$s respondeu á túa invitación",
+ "Invitation updated: %1$s" : "Invitación actualizada: %1$s",
+ "%1$s updated the event \"%2$s\"" : "%1$s actualizou o evento \"%2$s\"",
+ "Invitation: %1$s" : "Invitación: %1$s",
+ "%1$s would like to invite you to \"%2$s\"" : "%1$s quere invitarte a \"%2$s\"",
"Organizer:" : "Organizador:",
"Attendees:" : "Asistentes:",
"Title:" : "Título:",
@@ -59,6 +88,48 @@
"More options …" : "Máis opcións…",
"More options at %s" : "Máis opcións en %s",
"Contacts" : "Contactos",
+ "{actor} created address book {addressbook}" : "{actor} creou a axenda de enderezos {addressbook}",
+ "You created address book {addressbook}" : "Creaches a axenda de enderezos {addressbook}",
+ "{actor} deleted address book {addressbook}" : "{actor} eliminou a axenda de enderezos {addressbook}",
+ "You deleted address book {addressbook}" : "Eliminaches a axenda de enderezos {addressbook}",
+ "{actor} updated address book {addressbook}" : "{actor} actualizou a axenda de enderezos {addressbook}",
+ "You updated address book {addressbook}" : "Actualizaches a axenda de enderezos {addressbook}",
+ "{actor} shared address book {addressbook} with you" : "{actor} compartiu a axenda de enderezos {addressbook} contigo",
+ "You shared address book {addressbook} with {user}" : "Compartiches a axenda de enderezos {addressbook} con {user}",
+ "{actor} shared address book {addressbook} with {user}" : "{actor} compartiu a axenda de enderezos {addressbook} con {user}",
+ "{actor} unshared address book {addressbook} from you" : "{actor} deixou de compartir a túa axenda de enderezos {addressbook}",
+ "You unshared address book {addressbook} from {user}" : "Deixaches de compartir a axenda de enderezos {addressbook} de {user}",
+ "{actor} unshared address book {addressbook} from {user}" : "{actor} deixou de compartir a axenda de enderezos {addressbook} de {user}",
+ "{actor} unshared address book {addressbook} from themselves" : "{actor} deixaron de compartir a súa axenda de enderezos {addressbook}",
+ "You shared address book {addressbook} with group {group}" : "Compartiches a axenda de enderezos {addressbook} co grupo {group}",
+ "{actor} shared address book {addressbook} with group {group}" : "{actor} compartiu a axenda de enderezos {addressbook} co grupo {group}",
+ "You unshared address book {addressbook} from group {group}" : "Deixaches de compartir a axenda de enderezos {addressbook} do grupo {group}",
+ "{actor} unshared address book {addressbook} from group {group}" : "{actor} deixou de compartir axenda de enderezos {addressbook} do grupo {group}",
+ "{actor} created contact {card} in address book {addressbook}" : "{actor} creou o contacto {card} na axenda de enderezos {addressbook}",
+ "You created contact {card} in address book {addressbook}" : "Creaches o contacto {card} na axenda de enderezos {addressbook}",
+ "{actor} deleted contact {card} from address book {addressbook}" : "{actor} eliminou o contacto {card} da axenda de enderezos {addressbook}",
+ "You deleted contact {card} from address book {addressbook}" : "Eliminaches o contacto {card} da axenda de enderezos {addressbook}",
+ "{actor} updated contact {card} in address book {addressbook}" : "{actor} actualizou o contacto {card} na axenda de enderezos {addressbook}",
+ "You updated contact {card} in address book {addressbook}" : "Actualizaches o contacto {card} na axenda de enderezos {addressbook}",
+ "A <strong>contact</strong> or <strong>address book</strong> was modified" : "Modificouse un <strong>contacto</strong> ou <strong>axenda de enderezos</strong>",
+ "File is not updatable: %1$s" : "O ficheiro non se pode actualizar: %1$s",
+ "Could not write to final file, canceled by hook" : "Non foi posíbel escribir no ficheiro final, cancelouse polo hook",
+ "Could not write file contents" : "Non se puido escribir o contido do ficheiro",
+ "_%n byte_::_%n bytes_" : ["%n byte","%n bytes"],
+ "Error while copying file to target location (copied: %1$s, expected filesize: %2$s)" : "Produciuse un erro ao copiar o ficheiro na localización de destino (copiado: %1$s, tamaño esperado do ficheiro: %2$s)",
+ "Expected filesize of %1$s but read (from Nextcloud client) and wrote (to Nextcloud storage) %2$s. Could either be a network problem on the sending side or a problem writing to the storage on the server side." : "Tamaño esperado do ficheiro %1$s pero lido (do cliente de Nextcloud) e escrito (no almacenamento de Nextcloud) %2$s. Pode ser un problema de rede no lado do envío ou un problema ao escribir no almacenamento no lado do servidor.",
+ "Could not rename part file to final file, canceled by hook" : "Non foi posíbel cambiar o nome do ficheiro de parte ao ficheiro final, cancelado polo hook",
+ "Could not rename part file to final file" : "Non se puido cambiar o nome do ficheiro de parte ao ficheiro final",
+ "Failed to check file size: %1$s" : "Produciuse un erro ao comprobar o tamaño do ficheiro: %1$s",
+ "Could not open file" : "Non se puido abrir o ficheiro",
+ "Encryption not ready: %1$s" : "O cifrado non está listo: %1$s",
+ "Failed to open file: %1$s" : "Produciuse un erro ao abrir o ficheiro: %1$s",
+ "Failed to unlink: %1$s" : "Produciuse un erro ao desvincular: %1$s",
+ "Invalid chunk name" : "O nome do fragmento non é válido",
+ "Could not rename part file assembled from chunks" : "Non se puido cambiar o nome do ficheiro de pezas ensamblados a partir de fragmentos",
+ "Failed to write file contents: %1$s" : "Produciuse un erro ao escribir o contido do ficheiro: %1$s",
+ "File not found: %1$s" : "Arquivo non atopado: %1$s",
+ "System is in maintenance mode." : "O sistema está en modo de mantemento.",
"Upgrade needed" : "É necesario anovar actualizar",
"Your %s needs to be configured to use HTTPS in order to use CalDAV and CardDAV with iOS/macOS." : "É preciso configurar o seu %s para que empregue HTTPS para poder usar CalDAV e CardDAV con iOS / macOS.",
"Configures a CalDAV account" : "Configurar unha conta de CalDAV",
@@ -69,11 +140,18 @@
"Completed on %s" : "Rematado o %s",
"Due on %s by %s" : "Caduca o %s por %s",
"Due on %s" : "Caduca o %s",
+ "Migrated calendar (%1$s)" : "Calendario migrado (%1$s)",
+ "Calendars including events, details and attendees" : "Calendarios incluíndo eventos, detalles e asistentes",
"Contacts and groups" : "Contactos e grupos",
"WebDAV" : "WebDAV",
"WebDAV endpoint" : "Terminación WebDAV",
"Availability" : "Dispoñibilidade",
+ "If you configure your working hours, other users will see when you are out of office when they book a meeting." : "Se configuras o teu horario de traballo, outros usuarios verán cando estás fóra da oficina cando reserven unha reunión.",
+ "Time zone:" : "Franxa horaria:",
"to" : "para",
+ "Delete slot" : "Eliminar slot",
+ "No working hours set" : "Sen horario de traballo establecido",
+ "Add slot" : "Engadir slot",
"Monday" : "luns",
"Tuesday" : "martes",
"Wednesday" : "mércores",
@@ -81,7 +159,11 @@
"Friday" : "venres",
"Saturday" : "sábado",
"Sunday" : "domingo",
+ "Automatically set user status to \"Do not disturb\" outside of availability to mute all notifications." : "Establece automaticamente o estado do usuario en \"Non molestar\" fóra da dispoñibilidade para silenciar todas as notificacións.",
"Save" : "Gardar",
+ "Failed to load availability" : "Produciuse un erro ao cargar a dispoñibilidade",
+ "Saved availability" : "Dispoñibilidade gardada",
+ "Failed to save availability" : "Produciuse un erro ao gardar a dispoñibilidade",
"Calendar server" : "Servidor do calendario",
"Send invitations to attendees" : "Enviar convites aos asistentes",
"Automatically generate a birthday calendar" : "Xerar automaticamente o calendario de aniversarios",
@@ -89,7 +171,9 @@
"Hence they will not be available immediately after enabling but will show up after some time." : "Por isto, non estarán dispoñíbeis inmediatamente tras activalos, senón que aparecerán após certo tempo",
"Send notifications for events" : "Enviar notificacións para eventos",
"Notifications are sent via background jobs, so these must occur often enough." : "As notificacións enviaranse mediante procesos en segundo plano, polo que estes teñen que suceder con frecuencia.",
- "Enable notifications for events via push" : "Activar o envío de notificacións do servidor para eventos",
+ "Send reminder notifications to calendar sharees as well" : "Envía notificacións de recordatorio tamén aos que comparten calendario",
+ "Reminders are always sent to organizers and attendees." : "Os recordatorios sempre se envían aos organizadores e aos asistentes.",
+ "Enable notifications for events via push" : "Activar o envío de notificacións do automáticas para eventos",
"Also install the {calendarappstoreopen}Calendar app{linkclose}, or {calendardocopen}connect your desktop & mobile for syncing ↗{linkclose}." : "Instale tamén a {calendarappstoreopen}aplicación do Calendario{linkclose} ou {calendardocopen}conecte os seus escritorio e móbil para sincronizalos ↗{linkclose}.",
"Please make sure to properly set up {emailopen}the email server{linkclose}." : "Asegúrese de ter configurado correctamente {emailopen}o servidor de correo-e{linkclose}.",
"There was an error updating your attendance status." : "Produciuse un erro ao actualizar o seu estado de asistencia.",
diff --git a/apps/dav/l10n/hu.js b/apps/dav/l10n/hu.js
index d47163cf8f5..6fd13b3deef 100644
--- a/apps/dav/l10n/hu.js
+++ b/apps/dav/l10n/hu.js
@@ -74,6 +74,9 @@ OC.L10N.register(
"Cancelled: %1$s" : "Lemondva: %1$s",
"\"%1$s\" has been canceled" : "A következőt le lett mondva: „%1$s”",
"Re: %1$s" : "Vá: %1$s",
+ "%1$s has responded to your invitation" : "%1$s válaszolt a meghívására",
+ "Invitation updated: %1$s" : "Meghívó frissítve: %1$s",
+ "%1$s updated the event \"%2$s\"" : "%1$s frissítette a következő eseményt: %2$s",
"Invitation: %1$s" : "Meghívó: %1$s",
"%1$s would like to invite you to \"%2$s\"" : "%1$s meg szeretné hívni a következőre: „%2$s”",
"Organizer:" : "Szervező:",
diff --git a/apps/dav/l10n/hu.json b/apps/dav/l10n/hu.json
index 60ef5e10029..b2355dc43ce 100644
--- a/apps/dav/l10n/hu.json
+++ b/apps/dav/l10n/hu.json
@@ -72,6 +72,9 @@
"Cancelled: %1$s" : "Lemondva: %1$s",
"\"%1$s\" has been canceled" : "A következőt le lett mondva: „%1$s”",
"Re: %1$s" : "Vá: %1$s",
+ "%1$s has responded to your invitation" : "%1$s válaszolt a meghívására",
+ "Invitation updated: %1$s" : "Meghívó frissítve: %1$s",
+ "%1$s updated the event \"%2$s\"" : "%1$s frissítette a következő eseményt: %2$s",
"Invitation: %1$s" : "Meghívó: %1$s",
"%1$s would like to invite you to \"%2$s\"" : "%1$s meg szeretné hívni a következőre: „%2$s”",
"Organizer:" : "Szervező:",
diff --git a/apps/dav/l10n/mk.js b/apps/dav/l10n/mk.js
index 024a6fd7f38..b474d61133c 100644
--- a/apps/dav/l10n/mk.js
+++ b/apps/dav/l10n/mk.js
@@ -131,6 +131,7 @@ OC.L10N.register(
"Friday" : "Петок",
"Saturday" : "Сабота",
"Sunday" : "Недела",
+ "Automatically set user status to \"Do not disturb\" outside of availability to mute all notifications." : "Автоматско поставување на статус во \"Не вознемирувај\" недостапен за да ги занемите сите известувања.",
"Save" : "Зачувај",
"Calendar server" : "Календар сервер",
"Send invitations to attendees" : "Испрати покани на учесниците",
diff --git a/apps/dav/l10n/mk.json b/apps/dav/l10n/mk.json
index 5c4e42f7539..122a5121849 100644
--- a/apps/dav/l10n/mk.json
+++ b/apps/dav/l10n/mk.json
@@ -129,6 +129,7 @@
"Friday" : "Петок",
"Saturday" : "Сабота",
"Sunday" : "Недела",
+ "Automatically set user status to \"Do not disturb\" outside of availability to mute all notifications." : "Автоматско поставување на статус во \"Не вознемирувај\" недостапен за да ги занемите сите известувања.",
"Save" : "Зачувај",
"Calendar server" : "Календар сервер",
"Send invitations to attendees" : "Испрати покани на учесниците",
diff --git a/apps/dav/l10n/pl.js b/apps/dav/l10n/pl.js
index 32c4d158ce0..db40de74803 100644
--- a/apps/dav/l10n/pl.js
+++ b/apps/dav/l10n/pl.js
@@ -72,8 +72,13 @@ OC.L10N.register(
"Where: %s" : "Gdzie: %s",
"%1$s via %2$s" : "%1$s przez %2$s",
"Cancelled: %1$s" : "Anulowane: %1$s",
+ "\"%1$s\" has been canceled" : "\"%1$s\" zostało anulowane",
"Re: %1$s" : "Odp: %1$s",
+ "%1$s has responded to your invitation" : "%1$s odpowiedział/a na Twoje zaproszenie",
+ "Invitation updated: %1$s" : "Zaktualizowano zaproszenie: %1$s",
+ "%1$s updated the event \"%2$s\"" : "%1$s zaktualizował/a wydarzenie \"%2$s\"",
"Invitation: %1$s" : "Zaproszenie: %1$s",
+ "%1$s would like to invite you to \"%2$s\"" : "%1$s chce cię zaprosić na \"%2$s\"",
"Organizer:" : "Organizator:",
"Attendees:" : "Uczestnicy:",
"Title:" : "Tytuł:",
diff --git a/apps/dav/l10n/pl.json b/apps/dav/l10n/pl.json
index 2784dc03ed4..90c75076c0a 100644
--- a/apps/dav/l10n/pl.json
+++ b/apps/dav/l10n/pl.json
@@ -70,8 +70,13 @@
"Where: %s" : "Gdzie: %s",
"%1$s via %2$s" : "%1$s przez %2$s",
"Cancelled: %1$s" : "Anulowane: %1$s",
+ "\"%1$s\" has been canceled" : "\"%1$s\" zostało anulowane",
"Re: %1$s" : "Odp: %1$s",
+ "%1$s has responded to your invitation" : "%1$s odpowiedział/a na Twoje zaproszenie",
+ "Invitation updated: %1$s" : "Zaktualizowano zaproszenie: %1$s",
+ "%1$s updated the event \"%2$s\"" : "%1$s zaktualizował/a wydarzenie \"%2$s\"",
"Invitation: %1$s" : "Zaproszenie: %1$s",
+ "%1$s would like to invite you to \"%2$s\"" : "%1$s chce cię zaprosić na \"%2$s\"",
"Organizer:" : "Organizator:",
"Attendees:" : "Uczestnicy:",
"Title:" : "Tytuł:",
diff --git a/apps/dav/l10n/pt_BR.js b/apps/dav/l10n/pt_BR.js
index 802d9531aba..9cd73a722bc 100644
--- a/apps/dav/l10n/pt_BR.js
+++ b/apps/dav/l10n/pt_BR.js
@@ -75,6 +75,8 @@ OC.L10N.register(
"\"%1$s\" has been canceled" : "\"%1$s\" foi cancelado",
"Re: %1$s" : "Remetente: %1$s",
"%1$s has responded to your invitation" : "%1$s respondeu ao seu convite",
+ "Invitation updated: %1$s" : "Invitation updated: %1$s",
+ "%1$s updated the event \"%2$s\"" : "%1$s updated the event \"%2$s\"",
"Invitation: %1$s" : "Convite: %1$s",
"%1$s would like to invite you to \"%2$s\"" : "%1$s gostaria de convidá-lo para \"%2$s\"",
"Organizer:" : "Organizador:",
diff --git a/apps/dav/l10n/pt_BR.json b/apps/dav/l10n/pt_BR.json
index 4224071d29d..6a9765cb206 100644
--- a/apps/dav/l10n/pt_BR.json
+++ b/apps/dav/l10n/pt_BR.json
@@ -73,6 +73,8 @@
"\"%1$s\" has been canceled" : "\"%1$s\" foi cancelado",
"Re: %1$s" : "Remetente: %1$s",
"%1$s has responded to your invitation" : "%1$s respondeu ao seu convite",
+ "Invitation updated: %1$s" : "Invitation updated: %1$s",
+ "%1$s updated the event \"%2$s\"" : "%1$s updated the event \"%2$s\"",
"Invitation: %1$s" : "Convite: %1$s",
"%1$s would like to invite you to \"%2$s\"" : "%1$s gostaria de convidá-lo para \"%2$s\"",
"Organizer:" : "Organizador:",
diff --git a/apps/dav/l10n/ru.js b/apps/dav/l10n/ru.js
index b6eab41df26..95e2fa9e5e6 100644
--- a/apps/dav/l10n/ru.js
+++ b/apps/dav/l10n/ru.js
@@ -72,8 +72,13 @@ OC.L10N.register(
"Where: %s" : "Где: %s",
"%1$s via %2$s" : "%1$s через %2$s",
"Cancelled: %1$s" : "Событие отменено: %1$s",
+ "\"%1$s\" has been canceled" : "Событие «%1$s» отменено",
"Re: %1$s" : "Re: %1$s",
+ "%1$s has responded to your invitation" : "%1$s ответил(а) на ваше приглашение",
+ "Invitation updated: %1$s" : "Изменение приглашения: %1$s",
+ "%1$s updated the event \"%2$s\"" : "%1$s изменил(а) событие «%2$s»",
"Invitation: %1$s" : "Приглашение: %1$s",
+ "%1$s would like to invite you to \"%2$s\"" : "%1$s приглашает вас принять участие в событии «%2$s»",
"Organizer:" : "Организатор:",
"Attendees:" : "Участники:",
"Title:" : "Название:",
@@ -110,11 +115,20 @@ OC.L10N.register(
"You updated contact {card} in address book {addressbook}" : "Вы изменили запись {card} в адресной книге {addressbook}",
"A <strong>contact</strong> or <strong>address book</strong> was modified" : "Изменение <strong>контакта</strong> или <strong>адресной книги</strong>",
"File is not updatable: %1$s" : "Файл не подлежит обновлению: %1$s",
+ "Could not write to final file, canceled by hook" : "Не удалось записать результирующий файл, запись отменена вызовом обработчика",
"Could not write file contents" : "Не удалось записать содержимое файла",
+ "_%n byte_::_%n bytes_" : ["%n байт","%n байта","%n байт","%n байта"],
+ "Error while copying file to target location (copied: %1$s, expected filesize: %2$s)" : "Ошибка при копировании в целевое расположение, скопировано: %1$s, ожидаемый размер файла: %2$s",
+ "Expected filesize of %1$s but read (from Nextcloud client) and wrote (to Nextcloud storage) %2$s. Could either be a network problem on the sending side or a problem writing to the storage on the server side." : "Ожидаемый размер файла составляет %1$s, но из клиента приложения Nextcloud было прочитано и записано в хранилище %2$s. К этому могла привести ошибка при передаче данных на стороне отправителя либо проблема в подсистеме хранения данных на стороне сервера.",
+ "Could not rename part file to final file, canceled by hook" : "Не удалось переименовать временный файл в результирующий, операция отменена вызовом обработчика",
+ "Could not rename part file to final file" : "Не удалось переименовать временный файл в результирующий",
+ "Failed to check file size: %1$s" : "Не удалось проверить размер файла: %1$s",
"Could not open file" : "Не удалось открыть файл",
"Encryption not ready: %1$s" : "Подсистема шифрования не готова: %1$s",
"Failed to open file: %1$s" : "Не удалось открыть файл: %1$s",
+ "Failed to unlink: %1$s" : "Не удалось разорвать связь: %1$s",
"Invalid chunk name" : "Недопустимое имя сегмента",
+ "Could not rename part file assembled from chunks" : "Не удалось переименовать временный файл, сформированный из сегментов",
"Failed to write file contents: %1$s" : "Не удалось записать содержимое файла: %1$s",
"File not found: %1$s" : "Файл не найден: %1$s",
"System is in maintenance mode." : "Сервер находится в режиме обслуживания.",
@@ -128,6 +142,7 @@ OC.L10N.register(
"Completed on %s" : "Завершено %s",
"Due on %s by %s" : "До %s %s",
"Due on %s" : "До %s",
+ "Migrated calendar (%1$s)" : "Перенос календаря (%1$s)",
"Calendars including events, details and attendees" : "Календари, в том числе события, подробные сведения и участники",
"Contacts and groups" : "Контакты и группы",
"WebDAV" : "WebDAV",
@@ -146,8 +161,11 @@ OC.L10N.register(
"Friday" : "Пятница",
"Saturday" : "Суббота",
"Sunday" : "Воскресенье",
+ "Automatically set user status to \"Do not disturb\" outside of availability to mute all notifications." : "Автоматически изменять статус на «Не беспокоить» вне интервала доступности для отключения уведомлений.",
"Save" : "Сохранить",
"Failed to load availability" : "Не удалось получить сведения о доступности",
+ "Saved availability" : "Сведения о доступности сохранены",
+ "Failed to save availability" : "Не удалось сохранить сведения о доступности",
"Calendar server" : "Сервер календаря",
"Send invitations to attendees" : "Отправить приглашения",
"Automatically generate a birthday calendar" : "Создавать календарь дней рождения автоматически",
@@ -155,6 +173,8 @@ OC.L10N.register(
"Hence they will not be available immediately after enabling but will show up after some time." : "И поэтому они станут доступны не моментально, а через некоторое время.",
"Send notifications for events" : "Отправлять уведомления о событиях",
"Notifications are sent via background jobs, so these must occur often enough." : "Уведомления будут отправляться через фоновые задания, поэтому они должны выполняться достаточно часто.",
+ "Send reminder notifications to calendar sharees as well" : "Отправлять напоминания всем пользователям, имеющим доступ к календарю",
+ "Reminders are always sent to organizers and attendees." : "Организаторам и участникам уведомления отправляются во всех случаях.",
"Enable notifications for events via push" : "Включить уведомления о событиях с помощью push",
"Also install the {calendarappstoreopen}Calendar app{linkclose}, or {calendardocopen}connect your desktop & mobile for syncing ↗{linkclose}." : "Также установите {calendarappstoreopen}приложение Calendar{linkclose}, или {calendardocopen}подключите ваш ПК и мобильное устройство для синхронизации ↗{linkclose}.",
"Please make sure to properly set up {emailopen}the email server{linkclose}." : "Проверьте правильность настройки {emailopen}почтового сервера{linkclose}.",
diff --git a/apps/dav/l10n/ru.json b/apps/dav/l10n/ru.json
index 9c7625efd74..13ae27e6751 100644
--- a/apps/dav/l10n/ru.json
+++ b/apps/dav/l10n/ru.json
@@ -70,8 +70,13 @@
"Where: %s" : "Где: %s",
"%1$s via %2$s" : "%1$s через %2$s",
"Cancelled: %1$s" : "Событие отменено: %1$s",
+ "\"%1$s\" has been canceled" : "Событие «%1$s» отменено",
"Re: %1$s" : "Re: %1$s",
+ "%1$s has responded to your invitation" : "%1$s ответил(а) на ваше приглашение",
+ "Invitation updated: %1$s" : "Изменение приглашения: %1$s",
+ "%1$s updated the event \"%2$s\"" : "%1$s изменил(а) событие «%2$s»",
"Invitation: %1$s" : "Приглашение: %1$s",
+ "%1$s would like to invite you to \"%2$s\"" : "%1$s приглашает вас принять участие в событии «%2$s»",
"Organizer:" : "Организатор:",
"Attendees:" : "Участники:",
"Title:" : "Название:",
@@ -108,11 +113,20 @@
"You updated contact {card} in address book {addressbook}" : "Вы изменили запись {card} в адресной книге {addressbook}",
"A <strong>contact</strong> or <strong>address book</strong> was modified" : "Изменение <strong>контакта</strong> или <strong>адресной книги</strong>",
"File is not updatable: %1$s" : "Файл не подлежит обновлению: %1$s",
+ "Could not write to final file, canceled by hook" : "Не удалось записать результирующий файл, запись отменена вызовом обработчика",
"Could not write file contents" : "Не удалось записать содержимое файла",
+ "_%n byte_::_%n bytes_" : ["%n байт","%n байта","%n байт","%n байта"],
+ "Error while copying file to target location (copied: %1$s, expected filesize: %2$s)" : "Ошибка при копировании в целевое расположение, скопировано: %1$s, ожидаемый размер файла: %2$s",
+ "Expected filesize of %1$s but read (from Nextcloud client) and wrote (to Nextcloud storage) %2$s. Could either be a network problem on the sending side or a problem writing to the storage on the server side." : "Ожидаемый размер файла составляет %1$s, но из клиента приложения Nextcloud было прочитано и записано в хранилище %2$s. К этому могла привести ошибка при передаче данных на стороне отправителя либо проблема в подсистеме хранения данных на стороне сервера.",
+ "Could not rename part file to final file, canceled by hook" : "Не удалось переименовать временный файл в результирующий, операция отменена вызовом обработчика",
+ "Could not rename part file to final file" : "Не удалось переименовать временный файл в результирующий",
+ "Failed to check file size: %1$s" : "Не удалось проверить размер файла: %1$s",
"Could not open file" : "Не удалось открыть файл",
"Encryption not ready: %1$s" : "Подсистема шифрования не готова: %1$s",
"Failed to open file: %1$s" : "Не удалось открыть файл: %1$s",
+ "Failed to unlink: %1$s" : "Не удалось разорвать связь: %1$s",
"Invalid chunk name" : "Недопустимое имя сегмента",
+ "Could not rename part file assembled from chunks" : "Не удалось переименовать временный файл, сформированный из сегментов",
"Failed to write file contents: %1$s" : "Не удалось записать содержимое файла: %1$s",
"File not found: %1$s" : "Файл не найден: %1$s",
"System is in maintenance mode." : "Сервер находится в режиме обслуживания.",
@@ -126,6 +140,7 @@
"Completed on %s" : "Завершено %s",
"Due on %s by %s" : "До %s %s",
"Due on %s" : "До %s",
+ "Migrated calendar (%1$s)" : "Перенос календаря (%1$s)",
"Calendars including events, details and attendees" : "Календари, в том числе события, подробные сведения и участники",
"Contacts and groups" : "Контакты и группы",
"WebDAV" : "WebDAV",
@@ -144,8 +159,11 @@
"Friday" : "Пятница",
"Saturday" : "Суббота",
"Sunday" : "Воскресенье",
+ "Automatically set user status to \"Do not disturb\" outside of availability to mute all notifications." : "Автоматически изменять статус на «Не беспокоить» вне интервала доступности для отключения уведомлений.",
"Save" : "Сохранить",
"Failed to load availability" : "Не удалось получить сведения о доступности",
+ "Saved availability" : "Сведения о доступности сохранены",
+ "Failed to save availability" : "Не удалось сохранить сведения о доступности",
"Calendar server" : "Сервер календаря",
"Send invitations to attendees" : "Отправить приглашения",
"Automatically generate a birthday calendar" : "Создавать календарь дней рождения автоматически",
@@ -153,6 +171,8 @@
"Hence they will not be available immediately after enabling but will show up after some time." : "И поэтому они станут доступны не моментально, а через некоторое время.",
"Send notifications for events" : "Отправлять уведомления о событиях",
"Notifications are sent via background jobs, so these must occur often enough." : "Уведомления будут отправляться через фоновые задания, поэтому они должны выполняться достаточно часто.",
+ "Send reminder notifications to calendar sharees as well" : "Отправлять напоминания всем пользователям, имеющим доступ к календарю",
+ "Reminders are always sent to organizers and attendees." : "Организаторам и участникам уведомления отправляются во всех случаях.",
"Enable notifications for events via push" : "Включить уведомления о событиях с помощью push",
"Also install the {calendarappstoreopen}Calendar app{linkclose}, or {calendardocopen}connect your desktop & mobile for syncing ↗{linkclose}." : "Также установите {calendarappstoreopen}приложение Calendar{linkclose}, или {calendardocopen}подключите ваш ПК и мобильное устройство для синхронизации ↗{linkclose}.",
"Please make sure to properly set up {emailopen}the email server{linkclose}." : "Проверьте правильность настройки {emailopen}почтового сервера{linkclose}.",
diff --git a/apps/dav/l10n/sl.js b/apps/dav/l10n/sl.js
index 5c0f7300594..0214b0aaf7a 100644
--- a/apps/dav/l10n/sl.js
+++ b/apps/dav/l10n/sl.js
@@ -2,14 +2,17 @@ OC.L10N.register(
"dav",
{
"Calendar" : "Koledar",
+ "To-dos" : "Naloge To-Do",
"Personal" : "Osebno",
"{actor} created calendar {calendar}" : "{actor} ustvari koledar {calendar}",
- "You created calendar {calendar}" : "Ustvarim koledar {calendar}",
+ "You created calendar {calendar}" : "Ustvarite koledar {calendar}",
"{actor} deleted calendar {calendar}" : "{actor} izbriše koledar {calendar}",
- "You deleted calendar {calendar}" : "Izbrišem koledar {calendar}",
+ "You deleted calendar {calendar}" : "Izbrišete koledar {calendar}",
"{actor} updated calendar {calendar}" : "{actor} posodobi koledar {calendar}",
- "You updated calendar {calendar}" : "Posodobim koledar {calendar}",
- "You shared calendar {calendar} as public link" : "Omogočim souporabo koledarja {calendar} z javno povezavo",
+ "You updated calendar {calendar}" : "Posodobite koledar {calendar}",
+ "{actor} restored calendar {calendar}" : "{actor} obnovi koledar {calendar}",
+ "You restored calendar {calendar}" : "Ustvarite koledar {calendar}",
+ "You shared calendar {calendar} as public link" : "Omogočite souporabo koledarja {calendar} z javno povezavo",
"You removed public link for calendar {calendar}" : "Odstranite javno povezavo koledarja {calendar}",
"{actor} shared calendar {calendar} with you" : "{actor} mi omogoči souporabo koledarja {calendar}",
"You shared calendar {calendar} with {user}" : "Omogočite souporabo koledarja {calendar} z uporabnikom {user}",
@@ -29,6 +32,10 @@ OC.L10N.register(
"You deleted event {event} from calendar {calendar}" : "Izbrišete dogodek {event} v koledarju {calendar}",
"{actor} updated event {event} in calendar {calendar}" : "{actor} posodobi dogodek {event} v koledarju {calendar}",
"You updated event {event} in calendar {calendar}" : "Posodobite dogodek {event} v koledarju {calendar}",
+ "{actor} moved event {event} from calendar {sourceCalendar} to calendar {targetCalendar}" : "{actor} premakne dogodek {event} iz koledarja {sourceCalendar} v koledar {targetCalendar}",
+ "You moved event {event} from calendar {sourceCalendar} to calendar {targetCalendar}" : "Premaknete dogodek {event} iz koledarja {sourceCalendar} v koledar {targetCalendar}",
+ "{actor} restored event {event} of calendar {calendar}" : "{actor} obnovi dogodek {event} v koledarju {calendar}",
+ "You restored event {event} of calendar {calendar}" : "Obnovite dogodek {event} v koledarju {calendar}",
"Busy" : "Zasedeno",
"Calendar, contacts and tasks" : "Koledar, stiki in naloge",
"A <strong>calendar</strong> was modified" : "V <strong>koledar</strong> je vpisana sprememba",
@@ -67,6 +74,9 @@ OC.L10N.register(
"Contacts" : "Stiki",
"You deleted address book {addressbook}" : "Izbrišete imenik {addressbook}",
"Could not write file contents" : "Ni mogoče zapisati vsebine datoteke",
+ "Could not rename part file to final file" : "Ni mogoče preimenovati delne datoteke v končno ime.",
+ "Failed to check file size: %1$s" : "Preverjanje velikosti je spodletelo: %1$s",
+ "Could not open file" : "Datoteke ni mogoče odpreti",
"System is in maintenance mode." : "Sistem je v vzdrževalnem načinu.",
"Upgrade needed" : "Zahtevana je posodobitev",
"Your %s needs to be configured to use HTTPS in order to use CalDAV and CardDAV with iOS/macOS." : "Za uporabo CalDAV in CardDAV v okoljih iOS/macOS je treba %s nastaviti za uporabo HTTPS.",
@@ -107,7 +117,7 @@ OC.L10N.register(
"Also install the {calendarappstoreopen}Calendar app{linkclose}, or {calendardocopen}connect your desktop & mobile for syncing ↗{linkclose}." : "Namestite tudi {calendarappstoreopen}Koledar{linkclose}, ali pa se povežite z {calendardocopen}namiznim oziroma mobilnim usklajevalnikom ↗{linkclose}.",
"Please make sure to properly set up {emailopen}the email server{linkclose}." : "Prepričajte se, da je {emailopen}poštni strežnik{linkclose} pravilno nastavljen.",
"There was an error updating your attendance status." : "Prišlo je do napake med posodabljanjem vaše udeležbe.",
- "Please contact the organizer directly." : "Z organizatorjem stopite v stik neposredno.",
+ "Please contact the organizer directly." : "Z organizatorjem stopite neposredno v stik.",
"Are you accepting the invitation?" : "Ali želite sprejeti povabilo?",
"Tentative" : "Začasno",
"Your attendance was updated successfully." : "Vaša prisotnost je uspešno posodobljena.",
diff --git a/apps/dav/l10n/sl.json b/apps/dav/l10n/sl.json
index 1b8f86a8523..b42aadb7016 100644
--- a/apps/dav/l10n/sl.json
+++ b/apps/dav/l10n/sl.json
@@ -1,13 +1,16 @@
{ "translations": {
"Calendar" : "Koledar",
+ "To-dos" : "Naloge To-Do",
"Personal" : "Osebno",
"{actor} created calendar {calendar}" : "{actor} ustvari koledar {calendar}",
- "You created calendar {calendar}" : "Ustvarim koledar {calendar}",
+ "You created calendar {calendar}" : "Ustvarite koledar {calendar}",
"{actor} deleted calendar {calendar}" : "{actor} izbriše koledar {calendar}",
- "You deleted calendar {calendar}" : "Izbrišem koledar {calendar}",
+ "You deleted calendar {calendar}" : "Izbrišete koledar {calendar}",
"{actor} updated calendar {calendar}" : "{actor} posodobi koledar {calendar}",
- "You updated calendar {calendar}" : "Posodobim koledar {calendar}",
- "You shared calendar {calendar} as public link" : "Omogočim souporabo koledarja {calendar} z javno povezavo",
+ "You updated calendar {calendar}" : "Posodobite koledar {calendar}",
+ "{actor} restored calendar {calendar}" : "{actor} obnovi koledar {calendar}",
+ "You restored calendar {calendar}" : "Ustvarite koledar {calendar}",
+ "You shared calendar {calendar} as public link" : "Omogočite souporabo koledarja {calendar} z javno povezavo",
"You removed public link for calendar {calendar}" : "Odstranite javno povezavo koledarja {calendar}",
"{actor} shared calendar {calendar} with you" : "{actor} mi omogoči souporabo koledarja {calendar}",
"You shared calendar {calendar} with {user}" : "Omogočite souporabo koledarja {calendar} z uporabnikom {user}",
@@ -27,6 +30,10 @@
"You deleted event {event} from calendar {calendar}" : "Izbrišete dogodek {event} v koledarju {calendar}",
"{actor} updated event {event} in calendar {calendar}" : "{actor} posodobi dogodek {event} v koledarju {calendar}",
"You updated event {event} in calendar {calendar}" : "Posodobite dogodek {event} v koledarju {calendar}",
+ "{actor} moved event {event} from calendar {sourceCalendar} to calendar {targetCalendar}" : "{actor} premakne dogodek {event} iz koledarja {sourceCalendar} v koledar {targetCalendar}",
+ "You moved event {event} from calendar {sourceCalendar} to calendar {targetCalendar}" : "Premaknete dogodek {event} iz koledarja {sourceCalendar} v koledar {targetCalendar}",
+ "{actor} restored event {event} of calendar {calendar}" : "{actor} obnovi dogodek {event} v koledarju {calendar}",
+ "You restored event {event} of calendar {calendar}" : "Obnovite dogodek {event} v koledarju {calendar}",
"Busy" : "Zasedeno",
"Calendar, contacts and tasks" : "Koledar, stiki in naloge",
"A <strong>calendar</strong> was modified" : "V <strong>koledar</strong> je vpisana sprememba",
@@ -65,6 +72,9 @@
"Contacts" : "Stiki",
"You deleted address book {addressbook}" : "Izbrišete imenik {addressbook}",
"Could not write file contents" : "Ni mogoče zapisati vsebine datoteke",
+ "Could not rename part file to final file" : "Ni mogoče preimenovati delne datoteke v končno ime.",
+ "Failed to check file size: %1$s" : "Preverjanje velikosti je spodletelo: %1$s",
+ "Could not open file" : "Datoteke ni mogoče odpreti",
"System is in maintenance mode." : "Sistem je v vzdrževalnem načinu.",
"Upgrade needed" : "Zahtevana je posodobitev",
"Your %s needs to be configured to use HTTPS in order to use CalDAV and CardDAV with iOS/macOS." : "Za uporabo CalDAV in CardDAV v okoljih iOS/macOS je treba %s nastaviti za uporabo HTTPS.",
@@ -105,7 +115,7 @@
"Also install the {calendarappstoreopen}Calendar app{linkclose}, or {calendardocopen}connect your desktop & mobile for syncing ↗{linkclose}." : "Namestite tudi {calendarappstoreopen}Koledar{linkclose}, ali pa se povežite z {calendardocopen}namiznim oziroma mobilnim usklajevalnikom ↗{linkclose}.",
"Please make sure to properly set up {emailopen}the email server{linkclose}." : "Prepričajte se, da je {emailopen}poštni strežnik{linkclose} pravilno nastavljen.",
"There was an error updating your attendance status." : "Prišlo je do napake med posodabljanjem vaše udeležbe.",
- "Please contact the organizer directly." : "Z organizatorjem stopite v stik neposredno.",
+ "Please contact the organizer directly." : "Z organizatorjem stopite neposredno v stik.",
"Are you accepting the invitation?" : "Ali želite sprejeti povabilo?",
"Tentative" : "Začasno",
"Your attendance was updated successfully." : "Vaša prisotnost je uspešno posodobljena.",
diff --git a/apps/dav/l10n/sr.js b/apps/dav/l10n/sr.js
index 4cd28fe4b5b..fcf7c612d25 100644
--- a/apps/dav/l10n/sr.js
+++ b/apps/dav/l10n/sr.js
@@ -2,6 +2,7 @@ OC.L10N.register(
"dav",
{
"Calendar" : "Календар",
+ "To-dos" : "Обавезе",
"Personal" : "Лично",
"{actor} created calendar {calendar}" : "{actor} направи календар {calendar}",
"You created calendar {calendar}" : "Направили сте календар {calendar}",
@@ -9,6 +10,8 @@ OC.L10N.register(
"You deleted calendar {calendar}" : "Обрисали сте календар {calendar}",
"{actor} updated calendar {calendar}" : "{actor} ажурира календар {calendar}",
"You updated calendar {calendar}" : "Ажурирали сте календар {calendar}",
+ "{actor} restored calendar {calendar}" : "{actor} је обновио календар {calendar}",
+ "You restored calendar {calendar}" : "Обновили сте календар {calendar}",
"You shared calendar {calendar} as public link" : "Поделили сте календар {calendar} као јавну везу",
"You removed public link for calendar {calendar}" : "Уклонили сте јавну везу за календар {calendar}",
"{actor} shared calendar {calendar} with you" : "{actor} подели календар {calendar} са вама",
@@ -29,9 +32,27 @@ OC.L10N.register(
"You deleted event {event} from calendar {calendar}" : "Обрисали сте догађај {event} из календара {calendar}",
"{actor} updated event {event} in calendar {calendar}" : "{actor} је ажурирао догађај {event} у календару {calendar}",
"You updated event {event} in calendar {calendar}" : "Ажурирали сте догађај {event} у календару {calendar}",
+ "{actor} moved event {event} from calendar {sourceCalendar} to calendar {targetCalendar}" : "{actor} је преместио догађај {event} из календара {sourceCalendar} у календар {targetCalendar}",
+ "You moved event {event} from calendar {sourceCalendar} to calendar {targetCalendar}" : "Преместили сте догађај {event} из календара {sourceCalendar} у календар {targetCalendar}",
+ "{actor} restored event {event} of calendar {calendar}" : "{actor} је обновио догађај {event} календара {calendar}",
+ "You restored event {event} of calendar {calendar}" : "Обновили сте догађај {event} календара {calendar}",
"Busy" : "Заузет/а",
+ "{actor} created to-do {todo} in list {calendar}" : "{actor} је креирао обавезу {todo} у листи {calendar}",
+ "You created to-do {todo} in list {calendar}" : "Креирали сте обавезу {todo} у листи {calendar}",
+ "{actor} deleted to-do {todo} from list {calendar}" : "{actor} је обрисао обавезу {todo} из листе {calendar}",
+ "You deleted to-do {todo} from list {calendar}" : "обрисали сте обавезу {todo} из листе {calendar}",
+ "{actor} updated to-do {todo} in list {calendar}" : "{actor} је ажурирао обавезу {todo} у листи {calendar}",
+ "You updated to-do {todo} in list {calendar}" : "Ажурирали сте обавезу {todo} у листи {calendar}",
+ "{actor} solved to-do {todo} in list {calendar}" : "{actor} је извршио обавезу {todo} у листи {calendar}",
+ "You solved to-do {todo} in list {calendar}" : "Извршили сте обавезу {todo} у листи {calendar}",
+ "{actor} reopened to-do {todo} in list {calendar}" : "{actor} је поново отворио обавезу {todo} у листи {calendar}",
+ "You reopened to-do {todo} in list {calendar}" : "Поново сте отворили обавезу {todo} у листи {calendar}",
+ "{actor} moved to-do {todo} from list {sourceCalendar} to list {targetCalendar}" : "{actor} је преместио обавезу {todo} из листе {sourceCalendar} у листу {targetCalendar}",
+ "You moved to-do {todo} from list {sourceCalendar} to list {targetCalendar}" : "Преместили сте обавезу {todo} из листе {sourceCalendar} у листу {targetCalendar}",
+ "Calendar, contacts and tasks" : "Календар, контакти и задаци",
"A <strong>calendar</strong> was modified" : "<strong>Календар</strong> је измењен",
"A calendar <strong>event</strong> was modified" : "<strong>Догађај</strong> из календара је измењен",
+ "A calendar <strong>to-do</strong> was modified" : "Календар <strong>обавеза</strong> је измењен",
"Contact birthdays" : "Рођендани контаката",
"Death of %s" : " %s смрт",
"Calendar:" : "Календар:",
@@ -50,6 +71,14 @@ OC.L10N.register(
"Description: %s" : "Опис: %s",
"Where: %s" : "Место: %s",
"%1$s via %2$s" : "%1$s преко %2$s",
+ "Cancelled: %1$s" : "Отказано: %1$s",
+ "\"%1$s\" has been canceled" : "„%1$s” је отказано",
+ "Re: %1$s" : "Одг: %1$s",
+ "%1$s has responded to your invitation" : "%1$s је одговорио на вашу позивницу",
+ "Invitation updated: %1$s" : "Позивница је ажурирана: %1$s",
+ "%1$s updated the event \"%2$s\"" : "%1$s је ажурирао догађај „%2$s”",
+ "Invitation: %1$s" : "Позивница: %1$s",
+ "%1$s would like to invite you to \"%2$s\"" : "%1$s жели да вас позове на „%2$s",
"Organizer:" : "Организатор:",
"Attendees:" : "Присутни:",
"Title:" : "Наслов:",
@@ -61,6 +90,48 @@ OC.L10N.register(
"More options …" : "Још опција…",
"More options at %s" : "Још опција на %s",
"Contacts" : "Контакти",
+ "{actor} created address book {addressbook}" : "{actor} је креирао адресар {addressbook}",
+ "You created address book {addressbook}" : "Креирали сте адресар {addressbook}",
+ "{actor} deleted address book {addressbook}" : "{actor} је обрисао адресар {addressbook}",
+ "You deleted address book {addressbook}" : "Обрисали сте адресар {addressbook}",
+ "{actor} updated address book {addressbook}" : "{actor} је ажурирао адресар {addressbook}",
+ "You updated address book {addressbook}" : "Ажурирали сте адресар {addressbook}",
+ "{actor} shared address book {addressbook} with you" : "{actor} је са вама поделио адресар {addressbook}",
+ "You shared address book {addressbook} with {user}" : "Поделили сте адресар {addressbook} са {user}",
+ "{actor} shared address book {addressbook} with {user}" : "{actor} је поделио адресар {addressbook} са {user}",
+ "{actor} unshared address book {addressbook} from you" : "{actor} је уклонио дељење адресара {addressbook} са вама",
+ "You unshared address book {addressbook} from {user}" : "Уклонили сте дељење адресара {addressbook} са {user}",
+ "{actor} unshared address book {addressbook} from {user}" : "{actor} је уклонио дељење адресара {addressbook} са {user}",
+ "{actor} unshared address book {addressbook} from themselves" : "{actor} више не дели адресар {addressbook} са собом",
+ "You shared address book {addressbook} with group {group}" : "Поделили сте адресар {addressbook} са групом {group}",
+ "{actor} shared address book {addressbook} with group {group}" : "{actor} је поделио адресар {addressbook} са групом {group}",
+ "You unshared address book {addressbook} from group {group}" : "Уклонили сте дељење адресара {addressbook} са групом {group}",
+ "{actor} unshared address book {addressbook} from group {group}" : "{actor} је уклонио дељење адресара {addressbook} са групом {group}",
+ "{actor} created contact {card} in address book {addressbook}" : "{actor} је креирао контакт {card} у адресару {addressbook}",
+ "You created contact {card} in address book {addressbook}" : "Креирали сте контакт {card} у адресару {addressbook}",
+ "{actor} deleted contact {card} from address book {addressbook}" : "{actor} је обрисао контакт {card} из адресара {addressbook}",
+ "You deleted contact {card} from address book {addressbook}" : "Обрисали сте контакт {card} из адресара {addressbook}",
+ "{actor} updated contact {card} in address book {addressbook}" : "{actor} је ажурирао контакт {card} у адресару {addressbook}",
+ "You updated contact {card} in address book {addressbook}" : "Ажурирали сте контакт {card} у адресару {addressbook}",
+ "A <strong>contact</strong> or <strong>address book</strong> was modified" : "Измењен је <strong>контакт</strong> или <strong>адресар</strong>",
+ "File is not updatable: %1$s" : "Фајл не може да се ажурира: %1$s",
+ "Could not write to final file, canceled by hook" : "Не може да се упише у крајњи фајл, отказала је кука",
+ "Could not write file contents" : "Не може да се упише садржај фајла",
+ "_%n byte_::_%n bytes_" : ["%n бајт","%n бајта","%n бајтова"],
+ "Error while copying file to target location (copied: %1$s, expected filesize: %2$s)" : "Грешка приликом копирања фајла на циљну локацију (копирано: %1$s, очекивана величина фајла: %2$s)",
+ "Expected filesize of %1$s but read (from Nextcloud client) and wrote (to Nextcloud storage) %2$s. Could either be a network problem on the sending side or a problem writing to the storage on the server side." : "Очекивала се величина фајла %1$s, али је (од Nextcloud клијента) прочитано и уписано (у Nextcloud складиште) %2$s. Или може бити мрежни проблем на страни која шаље, или проблем код уписа у складиште на серверу.",
+ "Could not rename part file to final file, canceled by hook" : "Делимични фајл не може да се преименује у коначни фајл, отказала је кука",
+ "Could not rename part file to final file" : "Делимични фајл не може да се преименује у коначни фајл",
+ "Failed to check file size: %1$s" : "Није успела провера величине фајла: %1$s",
+ "Could not open file" : "Фајл не може да се отвори",
+ "Encryption not ready: %1$s" : "Шифрирање није спремно: %1$s",
+ "Failed to open file: %1$s" : "Фајл не може да се отвори: %1$s",
+ "Failed to unlink: %1$s" : "Није успело уклањање линка: %1$s",
+ "Invalid chunk name" : "Неисправни назив комада",
+ "Could not rename part file assembled from chunks" : "Име делимичног фајла састављеног од комада не може да се промени",
+ "Failed to write file contents: %1$s" : "Није успело уписивање садржаја фајла: %1$s",
+ "File not found: %1$s" : "Фајл не може да се пронађе: %1$s",
+ "System is in maintenance mode." : "Систем је у режиму одржавања.",
"Upgrade needed" : "Потребна надградња",
"Your %s needs to be configured to use HTTPS in order to use CalDAV and CardDAV with iOS/macOS." : "%s мора да буде подешен да користи HTTPS да бисте користи CalDAV и CardDAV са iOS/macOS-ом.",
"Configures a CalDAV account" : "Подешава CalDAV налог",
@@ -71,9 +142,18 @@ OC.L10N.register(
"Completed on %s" : "Завршено %s",
"Due on %s by %s" : "Рок је %s од стране %s",
"Due on %s" : "Рок је %s",
+ "Migrated calendar (%1$s)" : "Мигрирани календар (%1$s)",
+ "Calendars including events, details and attendees" : "Календари који укључују догађаје, детаље и учеснике",
+ "Contacts and groups" : "Контакти и групе",
"WebDAV" : "ВебДАВ",
"WebDAV endpoint" : "WebDAV крајња тачка",
+ "Availability" : "Доступност",
+ "If you configure your working hours, other users will see when you are out of office when they book a meeting." : "Ако подесите своје радне сате, када буду заказивали састанак, остали корисници ће видети када сте ван канцеларије.",
+ "Time zone:" : "Временска зона:",
"to" : "за",
+ "Delete slot" : "Обриши прорез",
+ "No working hours set" : "Нису подешени радни сати",
+ "Add slot" : "Додај прорез",
"Monday" : "Понедељак",
"Tuesday" : "Уторак",
"Wednesday" : "Среда",
@@ -81,7 +161,11 @@ OC.L10N.register(
"Friday" : "Петак",
"Saturday" : "Субота",
"Sunday" : "Недеља",
+ "Automatically set user status to \"Do not disturb\" outside of availability to mute all notifications." : "Аутоматски поставља статус кориниска на „Не узнемиравај” како би се ван доступности пригушила сва обавештења.",
"Save" : "Сачувај",
+ "Failed to load availability" : "Доступност није могла да се учита",
+ "Saved availability" : "Доступност је сачувана",
+ "Failed to save availability" : "Није успело снимање доступности ",
"Calendar server" : "Календар сервера",
"Send invitations to attendees" : "Пошаљи позивницу учесницима",
"Automatically generate a birthday calendar" : "Аутоматски изгенериши календар рођендана",
@@ -89,6 +173,8 @@ OC.L10N.register(
"Hence they will not be available immediately after enabling but will show up after some time." : "Зато можда неће бити видљиви баш одмах по укључивању, али ће се појавити после неког времена.",
"Send notifications for events" : "Шаљи обавештења о догађајима",
"Notifications are sent via background jobs, so these must occur often enough." : "Обавештења се шаљу кроз послове у позадини, па би требало да су постављени да се често извршавају.",
+ "Send reminder notifications to calendar sharees as well" : "Пошаљи подсетнике и корисницима којима је календар подељен",
+ "Reminders are always sent to organizers and attendees." : "Подсетници се увек шаљу организаторима и учесницима.",
"Enable notifications for events via push" : "Укључи обавештења за догађаје преко гурања догађаја",
"Also install the {calendarappstoreopen}Calendar app{linkclose}, or {calendardocopen}connect your desktop & mobile for syncing ↗{linkclose}." : "Такође инсталирајте {calendarappstoreopen}Календар апликацију{linkclose}, или {calendardocopen}повежите Ваш рачунар & мобилни за синхронизацију ↗{linkclose}.",
"Please make sure to properly set up {emailopen}the email server{linkclose}." : "Постарајте се да правилно подесите {emailopen}сервер е-поште{linkclose}.",
diff --git a/apps/dav/l10n/sr.json b/apps/dav/l10n/sr.json
index da5759d5335..a18db47c21a 100644
--- a/apps/dav/l10n/sr.json
+++ b/apps/dav/l10n/sr.json
@@ -1,5 +1,6 @@
{ "translations": {
"Calendar" : "Календар",
+ "To-dos" : "Обавезе",
"Personal" : "Лично",
"{actor} created calendar {calendar}" : "{actor} направи календар {calendar}",
"You created calendar {calendar}" : "Направили сте календар {calendar}",
@@ -7,6 +8,8 @@
"You deleted calendar {calendar}" : "Обрисали сте календар {calendar}",
"{actor} updated calendar {calendar}" : "{actor} ажурира календар {calendar}",
"You updated calendar {calendar}" : "Ажурирали сте календар {calendar}",
+ "{actor} restored calendar {calendar}" : "{actor} је обновио календар {calendar}",
+ "You restored calendar {calendar}" : "Обновили сте календар {calendar}",
"You shared calendar {calendar} as public link" : "Поделили сте календар {calendar} као јавну везу",
"You removed public link for calendar {calendar}" : "Уклонили сте јавну везу за календар {calendar}",
"{actor} shared calendar {calendar} with you" : "{actor} подели календар {calendar} са вама",
@@ -27,9 +30,27 @@
"You deleted event {event} from calendar {calendar}" : "Обрисали сте догађај {event} из календара {calendar}",
"{actor} updated event {event} in calendar {calendar}" : "{actor} је ажурирао догађај {event} у календару {calendar}",
"You updated event {event} in calendar {calendar}" : "Ажурирали сте догађај {event} у календару {calendar}",
+ "{actor} moved event {event} from calendar {sourceCalendar} to calendar {targetCalendar}" : "{actor} је преместио догађај {event} из календара {sourceCalendar} у календар {targetCalendar}",
+ "You moved event {event} from calendar {sourceCalendar} to calendar {targetCalendar}" : "Преместили сте догађај {event} из календара {sourceCalendar} у календар {targetCalendar}",
+ "{actor} restored event {event} of calendar {calendar}" : "{actor} је обновио догађај {event} календара {calendar}",
+ "You restored event {event} of calendar {calendar}" : "Обновили сте догађај {event} календара {calendar}",
"Busy" : "Заузет/а",
+ "{actor} created to-do {todo} in list {calendar}" : "{actor} је креирао обавезу {todo} у листи {calendar}",
+ "You created to-do {todo} in list {calendar}" : "Креирали сте обавезу {todo} у листи {calendar}",
+ "{actor} deleted to-do {todo} from list {calendar}" : "{actor} је обрисао обавезу {todo} из листе {calendar}",
+ "You deleted to-do {todo} from list {calendar}" : "обрисали сте обавезу {todo} из листе {calendar}",
+ "{actor} updated to-do {todo} in list {calendar}" : "{actor} је ажурирао обавезу {todo} у листи {calendar}",
+ "You updated to-do {todo} in list {calendar}" : "Ажурирали сте обавезу {todo} у листи {calendar}",
+ "{actor} solved to-do {todo} in list {calendar}" : "{actor} је извршио обавезу {todo} у листи {calendar}",
+ "You solved to-do {todo} in list {calendar}" : "Извршили сте обавезу {todo} у листи {calendar}",
+ "{actor} reopened to-do {todo} in list {calendar}" : "{actor} је поново отворио обавезу {todo} у листи {calendar}",
+ "You reopened to-do {todo} in list {calendar}" : "Поново сте отворили обавезу {todo} у листи {calendar}",
+ "{actor} moved to-do {todo} from list {sourceCalendar} to list {targetCalendar}" : "{actor} је преместио обавезу {todo} из листе {sourceCalendar} у листу {targetCalendar}",
+ "You moved to-do {todo} from list {sourceCalendar} to list {targetCalendar}" : "Преместили сте обавезу {todo} из листе {sourceCalendar} у листу {targetCalendar}",
+ "Calendar, contacts and tasks" : "Календар, контакти и задаци",
"A <strong>calendar</strong> was modified" : "<strong>Календар</strong> је измењен",
"A calendar <strong>event</strong> was modified" : "<strong>Догађај</strong> из календара је измењен",
+ "A calendar <strong>to-do</strong> was modified" : "Календар <strong>обавеза</strong> је измењен",
"Contact birthdays" : "Рођендани контаката",
"Death of %s" : " %s смрт",
"Calendar:" : "Календар:",
@@ -48,6 +69,14 @@
"Description: %s" : "Опис: %s",
"Where: %s" : "Место: %s",
"%1$s via %2$s" : "%1$s преко %2$s",
+ "Cancelled: %1$s" : "Отказано: %1$s",
+ "\"%1$s\" has been canceled" : "„%1$s” је отказано",
+ "Re: %1$s" : "Одг: %1$s",
+ "%1$s has responded to your invitation" : "%1$s је одговорио на вашу позивницу",
+ "Invitation updated: %1$s" : "Позивница је ажурирана: %1$s",
+ "%1$s updated the event \"%2$s\"" : "%1$s је ажурирао догађај „%2$s”",
+ "Invitation: %1$s" : "Позивница: %1$s",
+ "%1$s would like to invite you to \"%2$s\"" : "%1$s жели да вас позове на „%2$s",
"Organizer:" : "Организатор:",
"Attendees:" : "Присутни:",
"Title:" : "Наслов:",
@@ -59,6 +88,48 @@
"More options …" : "Још опција…",
"More options at %s" : "Још опција на %s",
"Contacts" : "Контакти",
+ "{actor} created address book {addressbook}" : "{actor} је креирао адресар {addressbook}",
+ "You created address book {addressbook}" : "Креирали сте адресар {addressbook}",
+ "{actor} deleted address book {addressbook}" : "{actor} је обрисао адресар {addressbook}",
+ "You deleted address book {addressbook}" : "Обрисали сте адресар {addressbook}",
+ "{actor} updated address book {addressbook}" : "{actor} је ажурирао адресар {addressbook}",
+ "You updated address book {addressbook}" : "Ажурирали сте адресар {addressbook}",
+ "{actor} shared address book {addressbook} with you" : "{actor} је са вама поделио адресар {addressbook}",
+ "You shared address book {addressbook} with {user}" : "Поделили сте адресар {addressbook} са {user}",
+ "{actor} shared address book {addressbook} with {user}" : "{actor} је поделио адресар {addressbook} са {user}",
+ "{actor} unshared address book {addressbook} from you" : "{actor} је уклонио дељење адресара {addressbook} са вама",
+ "You unshared address book {addressbook} from {user}" : "Уклонили сте дељење адресара {addressbook} са {user}",
+ "{actor} unshared address book {addressbook} from {user}" : "{actor} је уклонио дељење адресара {addressbook} са {user}",
+ "{actor} unshared address book {addressbook} from themselves" : "{actor} више не дели адресар {addressbook} са собом",
+ "You shared address book {addressbook} with group {group}" : "Поделили сте адресар {addressbook} са групом {group}",
+ "{actor} shared address book {addressbook} with group {group}" : "{actor} је поделио адресар {addressbook} са групом {group}",
+ "You unshared address book {addressbook} from group {group}" : "Уклонили сте дељење адресара {addressbook} са групом {group}",
+ "{actor} unshared address book {addressbook} from group {group}" : "{actor} је уклонио дељење адресара {addressbook} са групом {group}",
+ "{actor} created contact {card} in address book {addressbook}" : "{actor} је креирао контакт {card} у адресару {addressbook}",
+ "You created contact {card} in address book {addressbook}" : "Креирали сте контакт {card} у адресару {addressbook}",
+ "{actor} deleted contact {card} from address book {addressbook}" : "{actor} је обрисао контакт {card} из адресара {addressbook}",
+ "You deleted contact {card} from address book {addressbook}" : "Обрисали сте контакт {card} из адресара {addressbook}",
+ "{actor} updated contact {card} in address book {addressbook}" : "{actor} је ажурирао контакт {card} у адресару {addressbook}",
+ "You updated contact {card} in address book {addressbook}" : "Ажурирали сте контакт {card} у адресару {addressbook}",
+ "A <strong>contact</strong> or <strong>address book</strong> was modified" : "Измењен је <strong>контакт</strong> или <strong>адресар</strong>",
+ "File is not updatable: %1$s" : "Фајл не може да се ажурира: %1$s",
+ "Could not write to final file, canceled by hook" : "Не може да се упише у крајњи фајл, отказала је кука",
+ "Could not write file contents" : "Не може да се упише садржај фајла",
+ "_%n byte_::_%n bytes_" : ["%n бајт","%n бајта","%n бајтова"],
+ "Error while copying file to target location (copied: %1$s, expected filesize: %2$s)" : "Грешка приликом копирања фајла на циљну локацију (копирано: %1$s, очекивана величина фајла: %2$s)",
+ "Expected filesize of %1$s but read (from Nextcloud client) and wrote (to Nextcloud storage) %2$s. Could either be a network problem on the sending side or a problem writing to the storage on the server side." : "Очекивала се величина фајла %1$s, али је (од Nextcloud клијента) прочитано и уписано (у Nextcloud складиште) %2$s. Или може бити мрежни проблем на страни која шаље, или проблем код уписа у складиште на серверу.",
+ "Could not rename part file to final file, canceled by hook" : "Делимични фајл не може да се преименује у коначни фајл, отказала је кука",
+ "Could not rename part file to final file" : "Делимични фајл не може да се преименује у коначни фајл",
+ "Failed to check file size: %1$s" : "Није успела провера величине фајла: %1$s",
+ "Could not open file" : "Фајл не може да се отвори",
+ "Encryption not ready: %1$s" : "Шифрирање није спремно: %1$s",
+ "Failed to open file: %1$s" : "Фајл не може да се отвори: %1$s",
+ "Failed to unlink: %1$s" : "Није успело уклањање линка: %1$s",
+ "Invalid chunk name" : "Неисправни назив комада",
+ "Could not rename part file assembled from chunks" : "Име делимичног фајла састављеног од комада не може да се промени",
+ "Failed to write file contents: %1$s" : "Није успело уписивање садржаја фајла: %1$s",
+ "File not found: %1$s" : "Фајл не може да се пронађе: %1$s",
+ "System is in maintenance mode." : "Систем је у режиму одржавања.",
"Upgrade needed" : "Потребна надградња",
"Your %s needs to be configured to use HTTPS in order to use CalDAV and CardDAV with iOS/macOS." : "%s мора да буде подешен да користи HTTPS да бисте користи CalDAV и CardDAV са iOS/macOS-ом.",
"Configures a CalDAV account" : "Подешава CalDAV налог",
@@ -69,9 +140,18 @@
"Completed on %s" : "Завршено %s",
"Due on %s by %s" : "Рок је %s од стране %s",
"Due on %s" : "Рок је %s",
+ "Migrated calendar (%1$s)" : "Мигрирани календар (%1$s)",
+ "Calendars including events, details and attendees" : "Календари који укључују догађаје, детаље и учеснике",
+ "Contacts and groups" : "Контакти и групе",
"WebDAV" : "ВебДАВ",
"WebDAV endpoint" : "WebDAV крајња тачка",
+ "Availability" : "Доступност",
+ "If you configure your working hours, other users will see when you are out of office when they book a meeting." : "Ако подесите своје радне сате, када буду заказивали састанак, остали корисници ће видети када сте ван канцеларије.",
+ "Time zone:" : "Временска зона:",
"to" : "за",
+ "Delete slot" : "Обриши прорез",
+ "No working hours set" : "Нису подешени радни сати",
+ "Add slot" : "Додај прорез",
"Monday" : "Понедељак",
"Tuesday" : "Уторак",
"Wednesday" : "Среда",
@@ -79,7 +159,11 @@
"Friday" : "Петак",
"Saturday" : "Субота",
"Sunday" : "Недеља",
+ "Automatically set user status to \"Do not disturb\" outside of availability to mute all notifications." : "Аутоматски поставља статус кориниска на „Не узнемиравај” како би се ван доступности пригушила сва обавештења.",
"Save" : "Сачувај",
+ "Failed to load availability" : "Доступност није могла да се учита",
+ "Saved availability" : "Доступност је сачувана",
+ "Failed to save availability" : "Није успело снимање доступности ",
"Calendar server" : "Календар сервера",
"Send invitations to attendees" : "Пошаљи позивницу учесницима",
"Automatically generate a birthday calendar" : "Аутоматски изгенериши календар рођендана",
@@ -87,6 +171,8 @@
"Hence they will not be available immediately after enabling but will show up after some time." : "Зато можда неће бити видљиви баш одмах по укључивању, али ће се појавити после неког времена.",
"Send notifications for events" : "Шаљи обавештења о догађајима",
"Notifications are sent via background jobs, so these must occur often enough." : "Обавештења се шаљу кроз послове у позадини, па би требало да су постављени да се често извршавају.",
+ "Send reminder notifications to calendar sharees as well" : "Пошаљи подсетнике и корисницима којима је календар подељен",
+ "Reminders are always sent to organizers and attendees." : "Подсетници се увек шаљу организаторима и учесницима.",
"Enable notifications for events via push" : "Укључи обавештења за догађаје преко гурања догађаја",
"Also install the {calendarappstoreopen}Calendar app{linkclose}, or {calendardocopen}connect your desktop & mobile for syncing ↗{linkclose}." : "Такође инсталирајте {calendarappstoreopen}Календар апликацију{linkclose}, или {calendardocopen}повежите Ваш рачунар & мобилни за синхронизацију ↗{linkclose}.",
"Please make sure to properly set up {emailopen}the email server{linkclose}." : "Постарајте се да правилно подесите {emailopen}сервер е-поште{linkclose}.",
diff --git a/apps/dav/l10n/sv.js b/apps/dav/l10n/sv.js
index a837d8cb1a8..3c3f3ad10cc 100644
--- a/apps/dav/l10n/sv.js
+++ b/apps/dav/l10n/sv.js
@@ -72,8 +72,13 @@ OC.L10N.register(
"Where: %s" : "Var: %s",
"%1$s via %2$s" : "%1$s via %2$s",
"Cancelled: %1$s" : "Avbruten: %1$s",
+ "\"%1$s\" has been canceled" : "\"%1$s\" har avbrutits",
"Re: %1$s" : "Sv: %1$s",
+ "%1$s has responded to your invitation" : "%1$s har svarat på din inbjudan",
+ "Invitation updated: %1$s" : "Inbjudan uppdaterad: %1$s",
+ "%1$s updated the event \"%2$s\"" : "%1$s uppdaterade händelse \"%2$s\"",
"Invitation: %1$s" : "Inbjudan: %1$s",
+ "%1$s would like to invite you to \"%2$s\"" : "%1$s skulle vilja bjuda in dig till \"%2$s\"",
"Organizer:" : "Arrangör:",
"Attendees:" : "Deltagare:",
"Title:" : "Titel:",
@@ -110,8 +115,12 @@ OC.L10N.register(
"You updated contact {card} in address book {addressbook}" : "Du uppdaterade kontakten {card} i adressboken {addressbook}",
"A <strong>contact</strong> or <strong>address book</strong> was modified" : "En <strong>kontakt</strong> eller <strong>adressbok</strong> ändrades",
"File is not updatable: %1$s" : "Fil kan inte uppdateras: %1$s",
+ "Could not write to final file, canceled by hook" : "Kunde ej skriva till den slutgiltiga filen, avbröts av en kopplad åtgärd",
"Could not write file contents" : "Kunde inte skriva filens innehåll",
"_%n byte_::_%n bytes_" : ["%n byte","%n bytes"],
+ "Error while copying file to target location (copied: %1$s, expected filesize: %2$s)" : "Fel vid kopiering av fil till målplats (kopierade: %1$s, förväntad filstorlek: %2$s)",
+ "Expected filesize of %1$s but read (from Nextcloud client) and wrote (to Nextcloud storage) %2$s. Could either be a network problem on the sending side or a problem writing to the storage on the server side." : "Förväntad filstorlek på %1$s men läste (från Nextcloud-klienten) och skrev (till Nextcloud-lagringen) %2$s. Kan antingen vara ett nätverksproblem på sändnings-sidan eller problem med att skriva till lagringen på server-sidan.",
+ "Could not rename part file to final file, canceled by hook" : "Kunde inte byta namn på filfragment till slutgiltigt filnamn, avbröts av en kopplad åtgärd.",
"Could not rename part file to final file" : "Kunde inte ändra namn från temporära filen till slutliga filen",
"Failed to check file size: %1$s" : "Kunde inte kontrollera filstorleken: %1$s",
"Could not open file" : "Kunde inte öppna fil",
@@ -144,6 +153,7 @@ OC.L10N.register(
"to" : "till",
"Delete slot" : "Radera lucka",
"No working hours set" : "Inga arbetstimmar satta",
+ "Add slot" : "Lägg till lucka",
"Monday" : "Måndag",
"Tuesday" : "Tisdag",
"Wednesday" : "Onsdag",
@@ -151,7 +161,11 @@ OC.L10N.register(
"Friday" : "Fredag",
"Saturday" : "Lördag",
"Sunday" : "Söndag",
+ "Automatically set user status to \"Do not disturb\" outside of availability to mute all notifications." : "Sätt automatiskt användarstatus till \"Stör ej\" utanför tillgängliga tider för att tysta alla notifikationer.",
"Save" : "Spara",
+ "Failed to load availability" : "Misslyckades med att ladda tidsluckor",
+ "Saved availability" : "Sparade tidslucka",
+ "Failed to save availability" : "Misslyckades med att spara tidslucka",
"Calendar server" : "Kalenderserver",
"Send invitations to attendees" : "Skicka inbjudan till deltagare",
"Automatically generate a birthday calendar" : "Generera en födelsedagskalender automatiskt",
@@ -159,6 +173,8 @@ OC.L10N.register(
"Hence they will not be available immediately after enabling but will show up after some time." : "Därför kommer de inte vara tillgängliga direkt efter aktivering men kommer dyka upp efter en tid.",
"Send notifications for events" : "Skicka aviseringar för händelser",
"Notifications are sent via background jobs, so these must occur often enough." : "Aviseringar skickas genom bakgrundsjobb, så dessa måste ske tillräckligt ofta.",
+ "Send reminder notifications to calendar sharees as well" : "Skicka även påminnelser till kalenderdeltagare",
+ "Reminders are always sent to organizers and attendees." : "Påminnelser skickas alltid till arrangörer och deltagare.",
"Enable notifications for events via push" : "Aktivera aviseringar för händelser via push",
"Also install the {calendarappstoreopen}Calendar app{linkclose}, or {calendardocopen}connect your desktop & mobile for syncing ↗{linkclose}." : "Installera även {calendarappstoreopen}Kalender-app{linkclose}, eller {calendardocopen}anslut din dator & mobil för synkronisering ↗{linkclose}.",
"Please make sure to properly set up {emailopen}the email server{linkclose}." : "Verifiera och säkerställ inställningar för {emailopen}e-postserver{linkclose}.",
diff --git a/apps/dav/l10n/sv.json b/apps/dav/l10n/sv.json
index 91744d989fb..b42cda1141b 100644
--- a/apps/dav/l10n/sv.json
+++ b/apps/dav/l10n/sv.json
@@ -70,8 +70,13 @@
"Where: %s" : "Var: %s",
"%1$s via %2$s" : "%1$s via %2$s",
"Cancelled: %1$s" : "Avbruten: %1$s",
+ "\"%1$s\" has been canceled" : "\"%1$s\" har avbrutits",
"Re: %1$s" : "Sv: %1$s",
+ "%1$s has responded to your invitation" : "%1$s har svarat på din inbjudan",
+ "Invitation updated: %1$s" : "Inbjudan uppdaterad: %1$s",
+ "%1$s updated the event \"%2$s\"" : "%1$s uppdaterade händelse \"%2$s\"",
"Invitation: %1$s" : "Inbjudan: %1$s",
+ "%1$s would like to invite you to \"%2$s\"" : "%1$s skulle vilja bjuda in dig till \"%2$s\"",
"Organizer:" : "Arrangör:",
"Attendees:" : "Deltagare:",
"Title:" : "Titel:",
@@ -108,8 +113,12 @@
"You updated contact {card} in address book {addressbook}" : "Du uppdaterade kontakten {card} i adressboken {addressbook}",
"A <strong>contact</strong> or <strong>address book</strong> was modified" : "En <strong>kontakt</strong> eller <strong>adressbok</strong> ändrades",
"File is not updatable: %1$s" : "Fil kan inte uppdateras: %1$s",
+ "Could not write to final file, canceled by hook" : "Kunde ej skriva till den slutgiltiga filen, avbröts av en kopplad åtgärd",
"Could not write file contents" : "Kunde inte skriva filens innehåll",
"_%n byte_::_%n bytes_" : ["%n byte","%n bytes"],
+ "Error while copying file to target location (copied: %1$s, expected filesize: %2$s)" : "Fel vid kopiering av fil till målplats (kopierade: %1$s, förväntad filstorlek: %2$s)",
+ "Expected filesize of %1$s but read (from Nextcloud client) and wrote (to Nextcloud storage) %2$s. Could either be a network problem on the sending side or a problem writing to the storage on the server side." : "Förväntad filstorlek på %1$s men läste (från Nextcloud-klienten) och skrev (till Nextcloud-lagringen) %2$s. Kan antingen vara ett nätverksproblem på sändnings-sidan eller problem med att skriva till lagringen på server-sidan.",
+ "Could not rename part file to final file, canceled by hook" : "Kunde inte byta namn på filfragment till slutgiltigt filnamn, avbröts av en kopplad åtgärd.",
"Could not rename part file to final file" : "Kunde inte ändra namn från temporära filen till slutliga filen",
"Failed to check file size: %1$s" : "Kunde inte kontrollera filstorleken: %1$s",
"Could not open file" : "Kunde inte öppna fil",
@@ -142,6 +151,7 @@
"to" : "till",
"Delete slot" : "Radera lucka",
"No working hours set" : "Inga arbetstimmar satta",
+ "Add slot" : "Lägg till lucka",
"Monday" : "Måndag",
"Tuesday" : "Tisdag",
"Wednesday" : "Onsdag",
@@ -149,7 +159,11 @@
"Friday" : "Fredag",
"Saturday" : "Lördag",
"Sunday" : "Söndag",
+ "Automatically set user status to \"Do not disturb\" outside of availability to mute all notifications." : "Sätt automatiskt användarstatus till \"Stör ej\" utanför tillgängliga tider för att tysta alla notifikationer.",
"Save" : "Spara",
+ "Failed to load availability" : "Misslyckades med att ladda tidsluckor",
+ "Saved availability" : "Sparade tidslucka",
+ "Failed to save availability" : "Misslyckades med att spara tidslucka",
"Calendar server" : "Kalenderserver",
"Send invitations to attendees" : "Skicka inbjudan till deltagare",
"Automatically generate a birthday calendar" : "Generera en födelsedagskalender automatiskt",
@@ -157,6 +171,8 @@
"Hence they will not be available immediately after enabling but will show up after some time." : "Därför kommer de inte vara tillgängliga direkt efter aktivering men kommer dyka upp efter en tid.",
"Send notifications for events" : "Skicka aviseringar för händelser",
"Notifications are sent via background jobs, so these must occur often enough." : "Aviseringar skickas genom bakgrundsjobb, så dessa måste ske tillräckligt ofta.",
+ "Send reminder notifications to calendar sharees as well" : "Skicka även påminnelser till kalenderdeltagare",
+ "Reminders are always sent to organizers and attendees." : "Påminnelser skickas alltid till arrangörer och deltagare.",
"Enable notifications for events via push" : "Aktivera aviseringar för händelser via push",
"Also install the {calendarappstoreopen}Calendar app{linkclose}, or {calendardocopen}connect your desktop & mobile for syncing ↗{linkclose}." : "Installera även {calendarappstoreopen}Kalender-app{linkclose}, eller {calendardocopen}anslut din dator & mobil för synkronisering ↗{linkclose}.",
"Please make sure to properly set up {emailopen}the email server{linkclose}." : "Verifiera och säkerställ inställningar för {emailopen}e-postserver{linkclose}.",
diff --git a/apps/dav/l10n/tr.js b/apps/dav/l10n/tr.js
index 88f4d527f24..a0315fcf32b 100644
--- a/apps/dav/l10n/tr.js
+++ b/apps/dav/l10n/tr.js
@@ -75,6 +75,8 @@ OC.L10N.register(
"\"%1$s\" has been canceled" : "\"%1$s\" iptal edildi",
"Re: %1$s" : "Ynt: %1$s",
"%1$s has responded to your invitation" : "%1$s çağrınızı yanıtladı",
+ "Invitation updated: %1$s" : "Çağrı güncellendi: %1$s",
+ "%1$s updated the event \"%2$s\"" : "%1$s, \"%2$s\" etkinliğini güncelledi",
"Invitation: %1$s" : "Çağrı: %1$s",
"%1$s would like to invite you to \"%2$s\"" : "%1$s, size \"%2$s\" için çağrı gönderdi",
"Organizer:" : "Düzenleyen:",
@@ -111,7 +113,7 @@ OC.L10N.register(
"You deleted contact {card} from address book {addressbook}" : "{addressbook} adres defterinden {card} kişi kartını sildiniz",
"{actor} updated contact {card} in address book {addressbook}" : "{actor}, {addressbook} adres defterindeki {card} kişi kartını güncelledi",
"You updated contact {card} in address book {addressbook}" : "{addressbook} adres defterindeki {card} kişi kartını güncellediniz",
- "A <strong>contact</strong> or <strong>address book</strong> was modified" : "Bir <strong>kişi</strong> ya da <strong>adres defteri</strong> değiştirildi",
+ "A <strong>contact</strong> or <strong>address book</strong> was modified" : "Bir <strong>kişi</strong> ya da <strong>adres defteri</strong> değiştirildiğinde",
"File is not updatable: %1$s" : "Dosya güncellenebilir değil: %1$s",
"Could not write to final file, canceled by hook" : "Sonuç dosyasına yazılamadı, bağlantı tarafından iptal edildi",
"Could not write file contents" : "Dosya içerikleri yazılamadı",
@@ -145,7 +147,7 @@ OC.L10N.register(
"Contacts and groups" : "Kişiler ve gruplar",
"WebDAV" : "WebDAV",
"WebDAV endpoint" : "WebDAV bağlantı noktası",
- "Availability" : "Kullanılabilirlik",
+ "Availability" : "Uygunluk",
"If you configure your working hours, other users will see when you are out of office when they book a meeting." : "Çalışma saatlerinizi ayarlarsanız, diğer kullanıcılar bir toplantı ayarladıklarında ofis dışında olduğunuzu görürler.",
"Time zone:" : "Saat dilimi:",
"to" : "ile",
@@ -165,7 +167,7 @@ OC.L10N.register(
"Saved availability" : "Uygunluk kaydedildi",
"Failed to save availability" : "Uygunluk kaydedilemedi",
"Calendar server" : "Takvim sunucusu",
- "Send invitations to attendees" : "Katılımcılara çağrıları gönder",
+ "Send invitations to attendees" : "Katılımcılara çağrılar gönderilsin",
"Automatically generate a birthday calendar" : "Doğum günü takvimi otomatik oluşturulsun",
"Birthday calendars will be generated by a background job." : "Bu seçenek etkinleştirildiğinde, doğum günü takvimi arka plan görevi olarak oluşturulur.",
"Hence they will not be available immediately after enabling but will show up after some time." : "Etkinleştirildikten hemen sonra görüntülenmez, bir süre sonra görüntülenir.",
diff --git a/apps/dav/l10n/tr.json b/apps/dav/l10n/tr.json
index 204a5e4e861..32c46b864b1 100644
--- a/apps/dav/l10n/tr.json
+++ b/apps/dav/l10n/tr.json
@@ -73,6 +73,8 @@
"\"%1$s\" has been canceled" : "\"%1$s\" iptal edildi",
"Re: %1$s" : "Ynt: %1$s",
"%1$s has responded to your invitation" : "%1$s çağrınızı yanıtladı",
+ "Invitation updated: %1$s" : "Çağrı güncellendi: %1$s",
+ "%1$s updated the event \"%2$s\"" : "%1$s, \"%2$s\" etkinliğini güncelledi",
"Invitation: %1$s" : "Çağrı: %1$s",
"%1$s would like to invite you to \"%2$s\"" : "%1$s, size \"%2$s\" için çağrı gönderdi",
"Organizer:" : "Düzenleyen:",
@@ -109,7 +111,7 @@
"You deleted contact {card} from address book {addressbook}" : "{addressbook} adres defterinden {card} kişi kartını sildiniz",
"{actor} updated contact {card} in address book {addressbook}" : "{actor}, {addressbook} adres defterindeki {card} kişi kartını güncelledi",
"You updated contact {card} in address book {addressbook}" : "{addressbook} adres defterindeki {card} kişi kartını güncellediniz",
- "A <strong>contact</strong> or <strong>address book</strong> was modified" : "Bir <strong>kişi</strong> ya da <strong>adres defteri</strong> değiştirildi",
+ "A <strong>contact</strong> or <strong>address book</strong> was modified" : "Bir <strong>kişi</strong> ya da <strong>adres defteri</strong> değiştirildiğinde",
"File is not updatable: %1$s" : "Dosya güncellenebilir değil: %1$s",
"Could not write to final file, canceled by hook" : "Sonuç dosyasına yazılamadı, bağlantı tarafından iptal edildi",
"Could not write file contents" : "Dosya içerikleri yazılamadı",
@@ -143,7 +145,7 @@
"Contacts and groups" : "Kişiler ve gruplar",
"WebDAV" : "WebDAV",
"WebDAV endpoint" : "WebDAV bağlantı noktası",
- "Availability" : "Kullanılabilirlik",
+ "Availability" : "Uygunluk",
"If you configure your working hours, other users will see when you are out of office when they book a meeting." : "Çalışma saatlerinizi ayarlarsanız, diğer kullanıcılar bir toplantı ayarladıklarında ofis dışında olduğunuzu görürler.",
"Time zone:" : "Saat dilimi:",
"to" : "ile",
@@ -163,7 +165,7 @@
"Saved availability" : "Uygunluk kaydedildi",
"Failed to save availability" : "Uygunluk kaydedilemedi",
"Calendar server" : "Takvim sunucusu",
- "Send invitations to attendees" : "Katılımcılara çağrıları gönder",
+ "Send invitations to attendees" : "Katılımcılara çağrılar gönderilsin",
"Automatically generate a birthday calendar" : "Doğum günü takvimi otomatik oluşturulsun",
"Birthday calendars will be generated by a background job." : "Bu seçenek etkinleştirildiğinde, doğum günü takvimi arka plan görevi olarak oluşturulur.",
"Hence they will not be available immediately after enabling but will show up after some time." : "Etkinleştirildikten hemen sonra görüntülenmez, bir süre sonra görüntülenir.",
diff --git a/apps/dav/l10n/uk.js b/apps/dav/l10n/uk.js
index 94e7d68c1b8..b434ae4fc88 100644
--- a/apps/dav/l10n/uk.js
+++ b/apps/dav/l10n/uk.js
@@ -75,6 +75,8 @@ OC.L10N.register(
"\"%1$s\" has been canceled" : "\"%1$s\" скасовано",
"Re: %1$s" : "Re: %1$s",
"%1$s has responded to your invitation" : "%1$sвідповів(-ла) на ваше запрошення",
+ "Invitation updated: %1$s" : "Запрошення оновлено: %1$s",
+ "%1$s updated the event \"%2$s\"" : "%1$s оновив подію \"%2$s\"",
"Invitation: %1$s" : "Запрошення: %1$s",
"%1$s would like to invite you to \"%2$s\"" : "%1$s запрошує вас до \"%2$s\"",
"Organizer:" : "Організатор:",
@@ -152,14 +154,14 @@ OC.L10N.register(
"Delete slot" : "Вилучити діапазон",
"No working hours set" : "Робочий час не встановлено",
"Add slot" : "Додати діапазон",
- "Monday" : "понеділок",
+ "Monday" : "Понеділок",
"Tuesday" : "Вівторок",
"Wednesday" : "Середа",
"Thursday" : "Четвер",
"Friday" : "П'ятниця",
"Saturday" : "Субота",
"Sunday" : "Неділя",
- "Automatically set user status to \"Do not disturb\" outside of availability to mute all notifications." : "Автоматично встановлюйте статус користувача на \"Не турбувати\" поза доступністю, щоб вимкнути всі сповіщення.",
+ "Automatically set user status to \"Do not disturb\" outside of availability to mute all notifications." : "Автоматично встановлюйте статус користувача на \"Не турбувати\", коли ви не доступні, щоб вимкнути усі сповіщення.",
"Save" : "Зберегти",
"Failed to load availability" : "Не вдалося завантажити доступність",
"Saved availability" : "Збережена наявність",
diff --git a/apps/dav/l10n/uk.json b/apps/dav/l10n/uk.json
index 74fffa4bda4..6ba6c31dbfe 100644
--- a/apps/dav/l10n/uk.json
+++ b/apps/dav/l10n/uk.json
@@ -73,6 +73,8 @@
"\"%1$s\" has been canceled" : "\"%1$s\" скасовано",
"Re: %1$s" : "Re: %1$s",
"%1$s has responded to your invitation" : "%1$sвідповів(-ла) на ваше запрошення",
+ "Invitation updated: %1$s" : "Запрошення оновлено: %1$s",
+ "%1$s updated the event \"%2$s\"" : "%1$s оновив подію \"%2$s\"",
"Invitation: %1$s" : "Запрошення: %1$s",
"%1$s would like to invite you to \"%2$s\"" : "%1$s запрошує вас до \"%2$s\"",
"Organizer:" : "Організатор:",
@@ -150,14 +152,14 @@
"Delete slot" : "Вилучити діапазон",
"No working hours set" : "Робочий час не встановлено",
"Add slot" : "Додати діапазон",
- "Monday" : "понеділок",
+ "Monday" : "Понеділок",
"Tuesday" : "Вівторок",
"Wednesday" : "Середа",
"Thursday" : "Четвер",
"Friday" : "П'ятниця",
"Saturday" : "Субота",
"Sunday" : "Неділя",
- "Automatically set user status to \"Do not disturb\" outside of availability to mute all notifications." : "Автоматично встановлюйте статус користувача на \"Не турбувати\" поза доступністю, щоб вимкнути всі сповіщення.",
+ "Automatically set user status to \"Do not disturb\" outside of availability to mute all notifications." : "Автоматично встановлюйте статус користувача на \"Не турбувати\", коли ви не доступні, щоб вимкнути усі сповіщення.",
"Save" : "Зберегти",
"Failed to load availability" : "Не вдалося завантажити доступність",
"Saved availability" : "Збережена наявність",
diff --git a/apps/dav/l10n/zh_HK.js b/apps/dav/l10n/zh_HK.js
index d4e18756bd4..36c84b5a31e 100644
--- a/apps/dav/l10n/zh_HK.js
+++ b/apps/dav/l10n/zh_HK.js
@@ -75,6 +75,8 @@ OC.L10N.register(
"\"%1$s\" has been canceled" : "\"%1$s\" 已被取消",
"Re: %1$s" : "關於: %1$s",
"%1$s has responded to your invitation" : "%1$s 已回應您的邀請",
+ "Invitation updated: %1$s" : "邀請已更新︰%1$s",
+ "%1$s updated the event \"%2$s\"" : "%1$s 已更新了活動 \"%2$s\"",
"Invitation: %1$s" : "邀請:%1$s",
"%1$s would like to invite you to \"%2$s\"" : "%1$s 想邀請您加入“%2$s”",
"Organizer:" : "主辦單位:",
diff --git a/apps/dav/l10n/zh_HK.json b/apps/dav/l10n/zh_HK.json
index 7a1bdc824ef..4789f67fda1 100644
--- a/apps/dav/l10n/zh_HK.json
+++ b/apps/dav/l10n/zh_HK.json
@@ -73,6 +73,8 @@
"\"%1$s\" has been canceled" : "\"%1$s\" 已被取消",
"Re: %1$s" : "關於: %1$s",
"%1$s has responded to your invitation" : "%1$s 已回應您的邀請",
+ "Invitation updated: %1$s" : "邀請已更新︰%1$s",
+ "%1$s updated the event \"%2$s\"" : "%1$s 已更新了活動 \"%2$s\"",
"Invitation: %1$s" : "邀請:%1$s",
"%1$s would like to invite you to \"%2$s\"" : "%1$s 想邀請您加入“%2$s”",
"Organizer:" : "主辦單位:",
diff --git a/apps/dav/l10n/zh_TW.js b/apps/dav/l10n/zh_TW.js
index 38f38a0e42e..ae03ec07516 100644
--- a/apps/dav/l10n/zh_TW.js
+++ b/apps/dav/l10n/zh_TW.js
@@ -75,6 +75,8 @@ OC.L10N.register(
"\"%1$s\" has been canceled" : "「%1$s」已取消",
"Re: %1$s" : "回覆:%1$s",
"%1$s has responded to your invitation" : "%1$s 已回應您的邀請",
+ "Invitation updated: %1$s" : "邀請已更新:%1$s",
+ "%1$s updated the event \"%2$s\"" : "%1$s 已更新事件:「%2$s」",
"Invitation: %1$s" : "邀請:%1$s",
"%1$s would like to invite you to \"%2$s\"" : "%1$s 想邀請您加入「%2$s」",
"Organizer:" : "組織者:",
diff --git a/apps/dav/l10n/zh_TW.json b/apps/dav/l10n/zh_TW.json
index 86fd67cc337..9b612c9022d 100644
--- a/apps/dav/l10n/zh_TW.json
+++ b/apps/dav/l10n/zh_TW.json
@@ -73,6 +73,8 @@
"\"%1$s\" has been canceled" : "「%1$s」已取消",
"Re: %1$s" : "回覆:%1$s",
"%1$s has responded to your invitation" : "%1$s 已回應您的邀請",
+ "Invitation updated: %1$s" : "邀請已更新:%1$s",
+ "%1$s updated the event \"%2$s\"" : "%1$s 已更新事件:「%2$s」",
"Invitation: %1$s" : "邀請:%1$s",
"%1$s would like to invite you to \"%2$s\"" : "%1$s 想邀請您加入「%2$s」",
"Organizer:" : "組織者:",
diff --git a/apps/dav/lib/BackgroundJob/UserStatusAutomation.php b/apps/dav/lib/BackgroundJob/UserStatusAutomation.php
index 94feadcae93..43fccbf233e 100644
--- a/apps/dav/lib/BackgroundJob/UserStatusAutomation.php
+++ b/apps/dav/lib/BackgroundJob/UserStatusAutomation.php
@@ -92,7 +92,7 @@ class UserStatusAutomation extends TimedJob {
$isCurrentlyAvailable = false;
$nextPotentialToggles = [];
- $now = new \DateTime('now');
+ $now = $this->time->getDateTime();
$lastMidnight = (clone $now)->setTime(0, 0);
$vObject = Reader::read($property);
@@ -105,9 +105,16 @@ class UserStatusAutomation extends TimedJob {
foreach ($availables as $available) {
/** @var Available $available */
if ($available->name === 'AVAILABLE') {
- /** @var \DateTimeInterface $effectiveStart */
- /** @var \DateTimeInterface $effectiveEnd */
- [$effectiveStart, $effectiveEnd] = $available->getEffectiveStartEnd();
+ /** @var \DateTimeImmutable $originalStart */
+ /** @var \DateTimeImmutable $originalEnd */
+ [$originalStart, $originalEnd] = $available->getEffectiveStartEnd();
+
+ // Little shenanigans to fix the automation on the day the rules were adjusted
+ // Otherwise the $originalStart would match rules for Thursdays on a Friday, etc.
+ // So we simply wind back a week and then fastForward to the next occurrence
+ // since today's midnight, which then also accounts for the week days.
+ $effectiveStart = \DateTime::createFromImmutable($originalStart)->sub(new \DateInterval('P7D'));
+ $effectiveEnd = \DateTime::createFromImmutable($originalEnd)->sub(new \DateInterval('P7D'));
try {
$it = new RRuleIterator((string) $available->RRULE, $effectiveStart);
@@ -139,12 +146,21 @@ class UserStatusAutomation extends TimedJob {
}
}
+ if (empty($nextPotentialToggles)) {
+ $this->logger->info('Removing ' . self::class . ' background job for user "' . $userId . '" because the user has no valid availability rules set');
+ $this->jobList->remove(self::class, $argument);
+ $this->manager->revertUserStatus($userId, IUserStatus::MESSAGE_AVAILABILITY, IUserStatus::DND);
+ return;
+ }
+
$nextAutomaticToggle = min($nextPotentialToggles);
$this->setLastRunToNextToggleTime($userId, $nextAutomaticToggle - 1);
if ($isCurrentlyAvailable) {
+ $this->logger->debug('User is currently available, reverting DND status if applicable');
$this->manager->revertUserStatus($userId, IUserStatus::MESSAGE_AVAILABILITY, IUserStatus::DND);
} else {
+ $this->logger->debug('User is currently NOT available, reverting call status if applicable and then setting DND');
// The DND status automation is more important than the "Away - In call" so we also restore that one if it exists.
$this->manager->revertUserStatus($userId, IUserStatus::MESSAGE_CALL, IUserStatus::AWAY);
$this->manager->setUserStatus($userId, IUserStatus::MESSAGE_AVAILABILITY, IUserStatus::DND, true);
diff --git a/apps/dav/lib/CalDAV/CachedSubscription.php b/apps/dav/lib/CalDAV/CachedSubscription.php
index f42b5f97f5d..dc7f66e59b4 100644
--- a/apps/dav/lib/CalDAV/CachedSubscription.php
+++ b/apps/dav/lib/CalDAV/CachedSubscription.php
@@ -28,7 +28,6 @@ declare(strict_types=1);
namespace OCA\DAV\CalDAV;
use OCA\DAV\Exception\UnsupportedLimitOnInitialSyncException;
-use Sabre\CalDAV\Backend\BackendInterface;
use Sabre\DAV\Exception\MethodNotAllowed;
use Sabre\DAV\Exception\NotFound;
use Sabre\DAV\INode;
@@ -38,7 +37,7 @@ use Sabre\DAV\PropPatch;
* Class CachedSubscription
*
* @package OCA\DAV\CalDAV
- * @property BackendInterface|CalDavBackend $caldavBackend
+ * @property CalDavBackend $caldavBackend
*/
class CachedSubscription extends \Sabre\CalDAV\Calendar {
@@ -112,7 +111,7 @@ class CachedSubscription extends \Sabre\CalDAV\Calendar {
return parent::getOwner();
}
-
+
public function delete() {
$this->caldavBackend->deleteSubscription($this->calendarInfo['id']);
}
diff --git a/apps/dav/lib/CalDAV/EventComparisonService.php b/apps/dav/lib/CalDAV/EventComparisonService.php
index 0fd4d08e83e..d8d6ea07ed2 100644
--- a/apps/dav/lib/CalDAV/EventComparisonService.php
+++ b/apps/dav/lib/CalDAV/EventComparisonService.php
@@ -90,7 +90,7 @@ class EventComparisonService {
*
* @param VCalendar $new
* @param VCalendar|null $old
- * @return array<string, VEvent[]>
+ * @return array<string, VEvent[]|null>
*/
public function findModified(VCalendar $new, ?VCalendar $old): array {
$newEventComponents = $new->getComponents();
diff --git a/apps/dav/lib/CalDAV/Proxy/ProxyMapper.php b/apps/dav/lib/CalDAV/Proxy/ProxyMapper.php
index 19c72ffa0e9..e48e283484c 100644
--- a/apps/dav/lib/CalDAV/Proxy/ProxyMapper.php
+++ b/apps/dav/lib/CalDAV/Proxy/ProxyMapper.php
@@ -34,6 +34,8 @@ use OCP\IDBConnection;
* Class ProxyMapper
*
* @package OCA\DAV\CalDAV\Proxy
+ *
+ * @template-extends QBMapper<Proxy>
*/
class ProxyMapper extends QBMapper {
public const PERMISSION_READ = 1;
diff --git a/apps/dav/lib/CalDAV/Schedule/IMipPlugin.php b/apps/dav/lib/CalDAV/Schedule/IMipPlugin.php
index d4c2976fc1a..76e84a2b54b 100644
--- a/apps/dav/lib/CalDAV/Schedule/IMipPlugin.php
+++ b/apps/dav/lib/CalDAV/Schedule/IMipPlugin.php
@@ -173,7 +173,7 @@ class IMipPlugin extends SabreIMipPlugin {
$iTipMessage->scheduleStatus = '5.0; EMail delivery failed';
return;
}
- $recipientName = $iTipMessage->recipientName ?: null;
+ $recipientName = $iTipMessage->recipientName ? (string)$iTipMessage->recipientName : null;
$newEvents = $iTipMessage->message;
$oldEvents = $this->getVCalendar();
@@ -183,6 +183,7 @@ class IMipPlugin extends SabreIMipPlugin {
$vEvent = array_pop($modified['new']);
/** @var VEvent $oldVevent */
$oldVevent = !empty($modified['old']) && is_array($modified['old']) ? array_pop($modified['old']) : null;
+ $isModified = isset($oldVevent);
// No changed events after all - this shouldn't happen if there is significant change yet here we are
// The scheduling status is debatable
@@ -209,9 +210,11 @@ class IMipPlugin extends SabreIMipPlugin {
$senderName = $senderName->getValue() ?? null;
}
- if ($senderName === null || empty(trim($senderName))) {
+ // Try to get the sender name from the current user id if available.
+ if ($this->userId !== null && ($senderName === null || empty(trim($senderName)))) {
$senderName = $this->userManager->getDisplayName($this->userId);
}
+
$sender = substr($iTipMessage->sender, 7);
switch (strtolower($iTipMessage->method)) {
@@ -229,7 +232,6 @@ class IMipPlugin extends SabreIMipPlugin {
break;
}
-
$data['attendee_name'] = ($recipientName ?: $recipient);
$data['invitee_name'] = ($senderName ?: $sender);
@@ -237,14 +239,24 @@ class IMipPlugin extends SabreIMipPlugin {
$fromName = $this->imipService->getFrom($senderName, $this->defaults->getName());
$message = $this->mailer->createMessage()
- ->setFrom([$fromEMail => $fromName])
- ->setTo([$recipient => $recipientName])
- ->setReplyTo([$sender => $senderName]);
+ ->setFrom([$fromEMail => $fromName]);
+
+ if ($recipientName !== null) {
+ $message->setTo([$recipient => $recipientName]);
+ } else {
+ $message->setTo([$recipient]);
+ }
+
+ if ($senderName !== null) {
+ $message->setReplyTo([$sender => $senderName]);
+ } else {
+ $message->setReplyTo([$sender]);
+ }
$template = $this->mailer->createEMailTemplate('dav.calendarInvite.' . $method, $data);
$template->addHeader();
- $this->imipService->addSubjectAndHeading($template, $method, $data['invitee_name'], $data['meeting_title']);
+ $this->imipService->addSubjectAndHeading($template, $method, $data['invitee_name'], $data['meeting_title'], $isModified);
$this->imipService->addBulletList($template, $vEvent, $data);
// Only add response buttons to invitation requests: Fix Issue #11230
diff --git a/apps/dav/lib/CalDAV/Schedule/IMipService.php b/apps/dav/lib/CalDAV/Schedule/IMipService.php
index 3e8e72bd2e4..034a59a98d4 100644
--- a/apps/dav/lib/CalDAV/Schedule/IMipService.php
+++ b/apps/dav/lib/CalDAV/Schedule/IMipService.php
@@ -70,11 +70,15 @@ class IMipService {
}
/**
- * @param string $senderName
- * @param $default
+ * @param string|null $senderName
+ * @param string $default
* @return string
*/
- public function getFrom(string $senderName, $default): string {
+ public function getFrom(?string $senderName, string $default): string {
+ if ($senderName === null) {
+ return $default;
+ }
+
return $this->l10n->t('%1$s via %2$s', [$senderName, $default]);
}
@@ -94,7 +98,7 @@ class IMipService {
return $default;
}
$newstring = $vevent->$property->getValue();
- if(isset($oldVEvent->$property)) {
+ if(isset($oldVEvent->$property) && $oldVEvent->$property->getValue() !== $newstring ) {
$oldstring = $oldVEvent->$property->getValue();
return sprintf($strikethrough, $oldstring, $newstring);
}
@@ -124,7 +128,7 @@ class IMipService {
$data['meeting_location_html'] = $this->generateDiffString($vEvent, $oldVEvent, 'LOCATION', $data['meeting_location']);
$oldUrl = self::readPropertyWithDefault($oldVEvent, 'URL', $defaultVal);
- $data['meeting_url_html'] = !empty($oldUrl) ? sprintf('<a href="%1$s">%1$s</a>', $oldUrl) : $data['meeting_url'];
+ $data['meeting_url_html'] = !empty($oldUrl) && $oldUrl !== $data['meeting_url'] ? sprintf('<a href="%1$s">%1$s</a>', $oldUrl) : $data['meeting_url'];
$data['meeting_when_html'] =
($oldMeetingWhen !== $data['meeting_when'] && $oldMeetingWhen !== null)
@@ -359,9 +363,10 @@ class IMipService {
* @param string $sender
* @param string $summary
* @param string|null $partstat
+ * @param bool $isModified
*/
public function addSubjectAndHeading(IEMailTemplate $template,
- string $method, string $sender, string $summary): void {
+ string $method, string $sender, string $summary, bool $isModified): void {
if ($method === IMipPlugin::METHOD_CANCEL) {
// TRANSLATORS Subject for email, when an invitation is cancelled. Ex: "Cancelled: {{Event Name}}"
$template->setSubject($this->l10n->t('Cancelled: %1$s', [$summary]));
@@ -370,6 +375,10 @@ class IMipService {
// TRANSLATORS Subject for email, when an invitation is replied to. Ex: "Re: {{Event Name}}"
$template->setSubject($this->l10n->t('Re: %1$s', [$summary]));
$template->addHeading($this->l10n->t('%1$s has responded to your invitation', [$sender]));
+ } elseif ($method === IMipPlugin::METHOD_REQUEST && $isModified) {
+ // TRANSLATORS Subject for email, when an invitation is updated. Ex: "Invitation updated: {{Event Name}}"
+ $template->setSubject($this->l10n->t('Invitation updated: %1$s', [$summary]));
+ $template->addHeading($this->l10n->t('%1$s updated the event "%2$s"', [$sender, $summary]));
} else {
// TRANSLATORS Subject for email, when an invitation is sent. Ex: "Invitation: {{Event Name}}"
$template->setSubject($this->l10n->t('Invitation: %1$s', [$summary]));
@@ -468,7 +477,7 @@ class IMipService {
*/
public function addBulletList(IEMailTemplate $template, VEvent $vevent, $data) {
$template->addBodyListItem(
- $data['meeting_title'], $this->l10n->t('Title:'),
+ $data['meeting_title_html'] ?? $data['meeting_title'], $this->l10n->t('Title:'),
$this->getAbsoluteImagePath('caldav/title.png'), $data['meeting_title'], '', IMipPlugin::IMIP_INDENT);
if ($data['meeting_when'] !== '') {
$template->addBodyListItem($data['meeting_when_html'] ?? $data['meeting_when'], $this->l10n->t('Time:'),
diff --git a/apps/dav/lib/CalDAV/Schedule/Plugin.php b/apps/dav/lib/CalDAV/Schedule/Plugin.php
index ac8521acfee..0751638b697 100644
--- a/apps/dav/lib/CalDAV/Schedule/Plugin.php
+++ b/apps/dav/lib/CalDAV/Schedule/Plugin.php
@@ -48,7 +48,6 @@ use Sabre\VObject\Component;
use Sabre\VObject\Component\VCalendar;
use Sabre\VObject\Component\VEvent;
use Sabre\VObject\DateTimeParser;
-use Sabre\VObject\Document;
use Sabre\VObject\FreeBusyGenerator;
use Sabre\VObject\ITip;
use Sabre\VObject\Parameter;
@@ -329,12 +328,12 @@ EOF;
/** @var CalendarHome $calendarHome */
$calendarHome = $this->server->tree->getNodeForPath($calendarHomePath);
- if (!$calendarHome->childExists($uri)) {
+ $currentCalendarDeleted = false;
+ if (!$calendarHome->childExists($uri) || $currentCalendarDeleted = $this->isCalendarDeleted($calendarHome, $uri)) {
// If the default calendar doesn't exist
if ($isResourceOrRoom) {
- $calendarHome->getCalDAVBackend()->createCalendar($principalUrl, $uri, [
- '{DAV:}displayname' => $displayName,
- ]);
+ // Resources or rooms can't be in the trashbin, so we're fine
+ $this->createCalendar($calendarHome, $principalUrl, $uri, $displayName);
} else {
// And we're not handling scheduling on resource/room booking
$userCalendars = [];
@@ -359,9 +358,16 @@ EOF;
$uri = $userCalendars[0]->getName();
} else {
// Otherwise if we have really nothing, create a new calendar
- $calendarHome->getCalDAVBackend()->createCalendar($principalUrl, $uri, [
- '{DAV:}displayname' => $displayName,
- ]);
+ if ($currentCalendarDeleted) {
+ // If the calendar exists but is deleted, we need to purge it first
+ // This may cause some issues in a non synchronous database setup
+ $calendar = $this->getCalendar($calendarHome, $uri);
+ if ($calendar instanceof Calendar) {
+ $calendar->disableTrashbin();
+ $calendar->delete();
+ }
+ }
+ $this->createCalendar($calendarHome, $principalUrl, $uri, $displayName);
}
}
}
@@ -609,4 +615,19 @@ EOF;
return $email;
}
+
+ private function getCalendar(CalendarHome $calendarHome, string $uri): INode {
+ return $calendarHome->getChild($uri);
+ }
+
+ private function isCalendarDeleted(CalendarHome $calendarHome, string $uri): bool {
+ $calendar = $this->getCalendar($calendarHome, $uri);
+ return $calendar instanceof Calendar && $calendar->isDeleted();
+ }
+
+ private function createCalendar(CalendarHome $calendarHome, string $principalUri, string $uri, string $displayName): void {
+ $calendarHome->getCalDAVBackend()->createCalendar($principalUri, $uri, [
+ '{DAV:}displayname' => $displayName,
+ ]);
+ }
}
diff --git a/apps/dav/lib/CardDAV/AddressBook.php b/apps/dav/lib/CardDAV/AddressBook.php
index bca478feec1..f08a7b4b525 100644
--- a/apps/dav/lib/CardDAV/AddressBook.php
+++ b/apps/dav/lib/CardDAV/AddressBook.php
@@ -38,7 +38,7 @@ use Sabre\DAV\PropPatch;
* Class AddressBook
*
* @package OCA\DAV\CardDAV
- * @property BackendInterface|CardDavBackend $carddavBackend
+ * @property CardDavBackend $carddavBackend
*/
class AddressBook extends \Sabre\CardDAV\AddressBook implements IShareable {
diff --git a/apps/dav/lib/CardDAV/CardDavBackend.php b/apps/dav/lib/CardDAV/CardDavBackend.php
index ab21af1ce10..666f1e7a85c 100644
--- a/apps/dav/lib/CardDAV/CardDavBackend.php
+++ b/apps/dav/lib/CardDAV/CardDavBackend.php
@@ -989,11 +989,12 @@ class CardDavBackend implements BackendInterface, SyncSupport {
* @param string $pattern which should match within the $searchProperties
* @param array $searchProperties defines the properties within the query pattern should match
* @param array $options = array() to define the search behavior
+ * - 'types' boolean (since 15.0.0) If set to true, fields that come with a TYPE property will be an array
* - 'escape_like_param' - If set to false wildcards _ and % are not escaped, otherwise they are
* - 'limit' - Set a numeric limit for the search results
* - 'offset' - Set the offset for the limited search results
* - 'wildcard' - Whether the search should use wildcards
- * @psalm-param array{escape_like_param?: bool, limit?: int, offset?: int, wildcard?: bool} $options
+ * @psalm-param array{types?: bool, escape_like_param?: bool, limit?: int, offset?: int, wildcard?: bool} $options
* @return array an array of contacts which are arrays of key-value-pairs
*/
public function search($addressBookId, $pattern, $searchProperties, $options = []): array {
diff --git a/apps/dav/lib/Connector/Sabre/AppleQuirksPlugin.php b/apps/dav/lib/Connector/Sabre/AppleQuirksPlugin.php
new file mode 100644
index 00000000000..6c50f5682b7
--- /dev/null
+++ b/apps/dav/lib/Connector/Sabre/AppleQuirksPlugin.php
@@ -0,0 +1,133 @@
+<?php
+/**
+ * @copyright Copyright (c) 2023 Claus-Justus Heine
+ *
+ * @author Claus-Justus Heine <himself@claus-justus-heine.de>
+ *
+ * @license GNU AGPL version 3 or any later version
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as
+ * published by the Free Software Foundation, either version 3 of the
+ * License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+namespace OCA\DAV\Connector\Sabre;
+
+use Sabre\DAV\Server;
+use Sabre\DAV\ServerPlugin;
+use Sabre\HTTP\RequestInterface;
+use Sabre\HTTP\ResponseInterface;
+
+/**
+ * A plugin which tries to work-around peculiarities of the MacOS DAV client
+ * apps. The following problems are addressed:
+ *
+ * - OSX calendar client sends REPORT requests to a random principal
+ * collection but expects to find all principals (forgot to set
+ * {DAV:}principal-property-search flag?)
+ */
+class AppleQuirksPlugin extends ServerPlugin {
+
+ /*
+ private const OSX_CALENDAR_AGENT = 'CalendarAgent';
+ private const OSX_DATAACCESSD_AGENT = 'dataaccessd';
+ private const OSX_ACCOUNTSD_AGENT = 'accountsd';
+ private const OSX_CONTACTS_AGENT = 'AddressBookCore';
+ */
+
+ private const OSX_AGENT_PREFIX = 'macOS';
+
+ /** @var bool */
+ private $isMacOSDavAgent = false;
+
+ /**
+ * Sets up the plugin.
+ *
+ * This method is automatically called by the server class.
+ *
+ * @return void
+ */
+ public function initialize(Server $server)
+ {
+ $server->on('beforeMethod:REPORT', [$this, 'beforeReport'], 0);
+ $server->on('report', [$this, 'report'], 0);
+ }
+
+ /**
+ * Triggered before any method is handled.
+ *
+ * @return void
+ */
+ public function beforeReport(RequestInterface $request, ResponseInterface $response)
+ {
+ $userAgent = $request->getRawServerValue('HTTP_USER_AGENT') ?? 'unknown';
+ $this->isMacOSDavAgent = $this->isMacOSUserAgent($userAgent);
+ }
+
+ /**
+ * This method handles HTTP REPORT requests.
+ *
+ * @param string $reportName
+ * @param mixed $report
+ * @param mixed $path
+ *
+ * @return bool
+ */
+ public function report($reportName, $report, $path)
+ {
+ if ($reportName == '{DAV:}principal-property-search' && $this->isMacOSDavAgent) {
+ /** @var \Sabre\DAVACL\Xml\Request\PrincipalPropertySearchReport $report */
+ $report->applyToPrincipalCollectionSet = true;
+ }
+ return true;
+ }
+
+ /**
+ * Check whether the given $userAgent string pretends to originate from OSX.
+ *
+ * @param string $userAgent
+ *
+ * @return bool
+ */
+ protected function isMacOSUserAgent(string $userAgent):bool
+ {
+ return str_starts_with(self::OSX_AGENT_PREFIX, $userAgent);
+ }
+
+ /**
+ * Decode the given OSX DAV agent string.
+ *
+ * @param string $agent
+ *
+ * @return null|array
+ */
+ protected function decodeMacOSAgentString(string $userAgent):?array
+ {
+ // OSX agent string is like: macOS/13.2.1 (22D68) dataaccessd/1.0
+ if (preg_match('|^' . self::OSX_AGENT_PREFIX . '/([0-9]+)\\.([0-9]+)\\.([0-9]+)\s+\((\w+)\)\s+([^/]+)/([0-9]+)(?:\\.([0-9]+))?(?:\\.([0-9]+))?$|i', $userAgent, $matches)) {
+ return [
+ 'macOSVersion' => [
+ 'major' => $matches[1],
+ 'minor' => $matches[2],
+ 'patch' => $matches[3],
+ ],
+ 'macOSAgent' => $matches[5],
+ 'macOSAgentVersion' => [
+ 'major' => $matches[6],
+ 'minor' => $matches[7] ?? null,
+ 'patch' => $matches[8] ?? null,
+ ],
+ ];
+ }
+ return null;
+ }
+}
diff --git a/apps/dav/lib/Connector/Sabre/Directory.php b/apps/dav/lib/Connector/Sabre/Directory.php
index f4b1ee62190..c29070fe921 100644
--- a/apps/dav/lib/Connector/Sabre/Directory.php
+++ b/apps/dav/lib/Connector/Sabre/Directory.php
@@ -35,10 +35,10 @@ namespace OCA\DAV\Connector\Sabre;
use OC\Files\Mount\MoveableMount;
use OC\Files\View;
use OC\Metadata\FileMetadata;
-use OC\Metadata\MetadataGroup;
use OCA\DAV\Connector\Sabre\Exception\FileLocked;
use OCA\DAV\Connector\Sabre\Exception\Forbidden;
use OCA\DAV\Connector\Sabre\Exception\InvalidPath;
+use OCA\DAV\Upload\FutureFile;
use OCP\Files\FileInfo;
use OCP\Files\Folder;
use OCP\Files\ForbiddenException;
@@ -57,7 +57,6 @@ use Sabre\DAV\INode;
use OCP\Share\IManager as IShareManager;
class Directory extends \OCA\DAV\Connector\Sabre\Node implements \Sabre\DAV\ICollection, \Sabre\DAV\IQuota, \Sabre\DAV\IMoveTarget, \Sabre\DAV\ICopyTarget {
-
/**
* Cached directory content
* @var \OCP\Files\FileInfo[]
@@ -116,7 +115,6 @@ class Directory extends \OCA\DAV\Connector\Sabre\Node implements \Sabre\DAV\ICol
// for chunked upload also updating a existing file is a "createFile"
// because we create all the chunks before re-assemble them to the existing file.
if (isset($_SERVER['HTTP_OC_CHUNKED'])) {
-
// exit if we can't create a new file and we don't updatable existing file
$chunkInfo = \OC_FileChunking::decodeName($name);
if (!$this->fileView->isCreatable($this->path) &&
@@ -328,8 +326,14 @@ class Directory extends \OCA\DAV\Connector\Sabre\Node implements \Sabre\DAV\ICol
if ($this->quotaInfo) {
return $this->quotaInfo;
}
+ $relativePath = $this->fileView->getRelativePath($this->info->getPath());
+ if ($relativePath === null) {
+ $logger->warning("error while getting quota as the relative path cannot be found");
+ return [0, 0];
+ }
+
try {
- $storageInfo = \OC_Helper::getStorageInfo($this->info->getPath(), $this->info, false);
+ $storageInfo = \OC_Helper::getStorageInfo($relativePath, $this->info, false);
if ($storageInfo['quota'] === \OCP\Files\FileInfo::SPACE_UNLIMITED) {
$free = \OCP\Files\FileInfo::SPACE_UNLIMITED;
} else {
diff --git a/apps/dav/lib/Connector/Sabre/FilesPlugin.php b/apps/dav/lib/Connector/Sabre/FilesPlugin.php
index a6c9b8b4ebe..6b6f622a5a7 100644
--- a/apps/dav/lib/Connector/Sabre/FilesPlugin.php
+++ b/apps/dav/lib/Connector/Sabre/FilesPlugin.php
@@ -436,7 +436,7 @@ class FilesPlugin extends ServerPlugin {
\OC::$server->get(LoggerInterface::class)->debug('Inefficient fetching of metadata');
}
- return json_encode((object)$sizeMetadata->getMetadata(), JSON_THROW_ON_ERROR);
+ return $sizeMetadata->getValue();
});
}
}
diff --git a/apps/dav/lib/Connector/Sabre/Node.php b/apps/dav/lib/Connector/Sabre/Node.php
index ee159cef1d6..2c8d313eefd 100644
--- a/apps/dav/lib/Connector/Sabre/Node.php
+++ b/apps/dav/lib/Connector/Sabre/Node.php
@@ -261,6 +261,10 @@ abstract class Node implements \Sabre\DAV\INode {
return $this->info->getId();
}
+ public function getInternalPath(): string {
+ return $this->info->getInternalPath();
+ }
+
/**
* @param string $user
* @return int
diff --git a/apps/dav/lib/Connector/Sabre/SharesPlugin.php b/apps/dav/lib/Connector/Sabre/SharesPlugin.php
index 9fa6775c3b5..3d52a44b6a6 100644
--- a/apps/dav/lib/Connector/Sabre/SharesPlugin.php
+++ b/apps/dav/lib/Connector/Sabre/SharesPlugin.php
@@ -110,6 +110,7 @@ class SharesPlugin extends \Sabre\DAV\ServerPlugin {
IShare::TYPE_ROOM,
IShare::TYPE_CIRCLE,
IShare::TYPE_DECK,
+ IShare::TYPE_SCIENCEMESH,
];
foreach ($requestedShareTypes as $requestedShareType) {
$shares = $this->shareManager->getSharesBy(
diff --git a/apps/dav/lib/DAV/CustomPropertiesBackend.php b/apps/dav/lib/DAV/CustomPropertiesBackend.php
index 0110990a408..3bc3ba33173 100644
--- a/apps/dav/lib/DAV/CustomPropertiesBackend.php
+++ b/apps/dav/lib/DAV/CustomPropertiesBackend.php
@@ -98,7 +98,7 @@ class CustomPropertiesBackend implements BackendInterface {
/**
* Properties set by one user, readable by all others
*
- * @var array[]
+ * @var string[]
*/
private const PUBLISHED_READ_ONLY_PROPERTIES = [
'{urn:ietf:params:xml:ns:caldav}calendar-availability',
diff --git a/apps/dav/lib/Events/CalendarShareUpdatedEvent.php b/apps/dav/lib/Events/CalendarShareUpdatedEvent.php
index d5a568d149b..dedd9f8a566 100644
--- a/apps/dav/lib/Events/CalendarShareUpdatedEvent.php
+++ b/apps/dav/lib/Events/CalendarShareUpdatedEvent.php
@@ -38,7 +38,7 @@ use Sabre\CalDAV\Xml\Property\SupportedCalendarComponentSet;
class CalendarShareUpdatedEvent extends Event {
private int $calendarId;
- /** @var array{id: int, uri: string, '{http://calendarserver.org/ns/}getctag': string, '{http://sabredav.org/ns}sync-token': int, '{urn:ietf:params:xml:ns:caldav}supported-calendar-component-set': SupportedCalendarComponentSet, '{urn:ietf:params:xml:ns:caldav}schedule-calendar-transp': ScheduleCalendarTransp } */
+ /** @var array{id: int, uri: string, '{http://calendarserver.org/ns/}getctag': string, '{http://sabredav.org/ns}sync-token': int, '{urn:ietf:params:xml:ns:caldav}supported-calendar-component-set': SupportedCalendarComponentSet, '{urn:ietf:params:xml:ns:caldav}schedule-calendar-transp': ScheduleCalendarTransp, '{urn:ietf:params:xml:ns:caldav}calendar-timezone': ?string } */
private array $calendarData;
/** @var list<array{href: string, commonName: string, status: int, readOnly: bool, '{http://owncloud.org/ns}principal': string, '{http://owncloud.org/ns}group-share': bool}> */
@@ -54,7 +54,7 @@ class CalendarShareUpdatedEvent extends Event {
* CalendarShareUpdatedEvent constructor.
*
* @param int $calendarId
- * @param array{id: int, uri: string, '{http://calendarserver.org/ns/}getctag': string, '{http://sabredav.org/ns}sync-token': int, '{urn:ietf:params:xml:ns:caldav}supported-calendar-component-set': SupportedCalendarComponentSet, '{urn:ietf:params:xml:ns:caldav}schedule-calendar-transp': ScheduleCalendarTransp } $calendarData
+ * @param array{id: int, uri: string, '{http://calendarserver.org/ns/}getctag': string, '{http://sabredav.org/ns}sync-token': int, '{urn:ietf:params:xml:ns:caldav}supported-calendar-component-set': SupportedCalendarComponentSet, '{urn:ietf:params:xml:ns:caldav}schedule-calendar-transp': ScheduleCalendarTransp, '{urn:ietf:params:xml:ns:caldav}calendar-timezone': ?string } $calendarData
* @param list<array{href: string, commonName: string, status: int, readOnly: bool, '{http://owncloud.org/ns}principal': string, '{http://owncloud.org/ns}group-share': bool}> $oldShares
* @param list<array{href: string, commonName: string, readOnly: bool}> $added
* @param list<string> $removed
@@ -81,7 +81,7 @@ class CalendarShareUpdatedEvent extends Event {
}
/**
- * @return array{id: int, uri: string, '{http://calendarserver.org/ns/}getctag': string, '{http://sabredav.org/ns}sync-token': int, '{urn:ietf:params:xml:ns:caldav}supported-calendar-component-set': SupportedCalendarComponentSet, '{urn:ietf:params:xml:ns:caldav}schedule-calendar-transp': ScheduleCalendarTransp }
+ * @return array{id: int, uri: string, '{http://calendarserver.org/ns/}getctag': string, '{http://sabredav.org/ns}sync-token': int, '{urn:ietf:params:xml:ns:caldav}supported-calendar-component-set': SupportedCalendarComponentSet, '{urn:ietf:params:xml:ns:caldav}schedule-calendar-transp': ScheduleCalendarTransp, '{urn:ietf:params:xml:ns:caldav}calendar-timezone': ?string }
* @since 20.0.0
*/
public function getCalendarData(): array {
diff --git a/apps/dav/lib/Migration/RemoveObjectProperties.php b/apps/dav/lib/Migration/RemoveObjectProperties.php
index c72dfbebfea..b771b70e684 100644
--- a/apps/dav/lib/Migration/RemoveObjectProperties.php
+++ b/apps/dav/lib/Migration/RemoveObjectProperties.php
@@ -57,7 +57,7 @@ class RemoveObjectProperties implements IRepairStep {
$query = $this->connection->getQueryBuilder();
$updated = $query->delete('properties')
->where($query->expr()->in('propertyname', $query->createNamedParameter([self::RESOURCE_TYPE_PROPERTY, self::ME_CARD_PROPERTY, self::CALENDAR_TRANSP_PROPERTY], IQueryBuilder::PARAM_STR_ARRAY)))
- ->andWhere($query->expr()->eq('propertyvalue', $query->createNamedParameter('Object')))
+ ->andWhere($query->expr()->eq('propertyvalue', $query->createNamedParameter('Object'), IQueryBuilder::PARAM_STR))
->executeStatement();
$output->info("$updated invalid object properties removed.");
diff --git a/apps/dav/lib/Server.php b/apps/dav/lib/Server.php
index a5833e5175f..4be149ac440 100644
--- a/apps/dav/lib/Server.php
+++ b/apps/dav/lib/Server.php
@@ -71,9 +71,11 @@ use OCA\DAV\Profiler\ProfilerPlugin;
use OCA\DAV\Provisioning\Apple\AppleProvisioningPlugin;
use OCA\DAV\SystemTag\SystemTagPlugin;
use OCA\DAV\Upload\ChunkingPlugin;
+use OCA\DAV\Upload\ChunkingV2Plugin;
use OCP\AppFramework\Http\Response;
use OCP\Diagnostics\IEventLogger;
use OCP\EventDispatcher\IEventDispatcher;
+use OCP\ICacheFactory;
use OCP\IRequest;
use OCP\Profiler\IProfiler;
use OCP\SabrePluginEvent;
@@ -110,6 +112,8 @@ class Server {
// Add maintenance plugin
$this->server->addPlugin(new \OCA\DAV\Connector\Sabre\MaintenancePlugin(\OC::$server->getConfig(), \OC::$server->getL10N('dav')));
+ $this->server->addPlugin(new \OCA\DAV\Connector\Sabre\AppleQuirksPlugin());
+
// Backends
$authBackend = new Auth(
\OC::$server->getSession(),
@@ -218,6 +222,7 @@ class Server {
$this->server->addPlugin(new CopyEtagHeaderPlugin());
$this->server->addPlugin(new RequestIdHeaderPlugin(\OC::$server->get(IRequest::class)));
+ $this->server->addPlugin(new ChunkingV2Plugin(\OCP\Server::get(ICacheFactory::class)));
$this->server->addPlugin(new ChunkingPlugin());
// allow setup of additional plugins
diff --git a/apps/dav/lib/Upload/ChunkingV2Plugin.php b/apps/dav/lib/Upload/ChunkingV2Plugin.php
new file mode 100644
index 00000000000..cb7c802125c
--- /dev/null
+++ b/apps/dav/lib/Upload/ChunkingV2Plugin.php
@@ -0,0 +1,392 @@
+<?php
+
+declare(strict_types=1);
+/*
+ * @copyright Copyright (c) 2021 Julius Härtl <jus@bitgrid.net>
+ *
+ * @author Julius Härtl <jus@bitgrid.net>
+ *
+ * @license GNU AGPL version 3 or any later version
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as
+ * published by the Free Software Foundation, either version 3 of the
+ * License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+
+namespace OCA\DAV\Upload;
+
+use Exception;
+use InvalidArgumentException;
+use OC\Files\Filesystem;
+use OC\Files\ObjectStore\ObjectStoreStorage;
+use OC\Files\View;
+use OC_Hook;
+use OCA\DAV\Connector\Sabre\Directory;
+use OCA\DAV\Connector\Sabre\File;
+use OCP\Files\IMimeTypeDetector;
+use OCP\Files\IRootFolder;
+use OCP\Files\ObjectStore\IObjectStoreMultiPartUpload;
+use OCP\Files\Storage\IChunkedFileWrite;
+use OCP\Files\StorageInvalidException;
+use OCP\ICache;
+use OCP\ICacheFactory;
+use OCP\Lock\ILockingProvider;
+use Sabre\DAV\Exception\BadRequest;
+use Sabre\DAV\Exception\InsufficientStorage;
+use Sabre\DAV\Exception\NotFound;
+use Sabre\DAV\Exception\PreconditionFailed;
+use Sabre\DAV\ICollection;
+use Sabre\DAV\INode;
+use Sabre\DAV\Server;
+use Sabre\DAV\ServerPlugin;
+use Sabre\HTTP\RequestInterface;
+use Sabre\HTTP\ResponseInterface;
+use Sabre\Uri;
+
+class ChunkingV2Plugin extends ServerPlugin {
+ /** @var Server */
+ private $server;
+ /** @var UploadFolder */
+ private $uploadFolder;
+ /** @var ICache */
+ private $cache;
+
+ private ?string $uploadId = null;
+ private ?string $uploadPath = null;
+
+ private const TEMP_TARGET = '.target';
+
+ public const CACHE_KEY = 'chunking-v2';
+ public const UPLOAD_TARGET_PATH = 'upload-target-path';
+ public const UPLOAD_TARGET_ID = 'upload-target-id';
+ public const UPLOAD_ID = 'upload-id';
+
+ private const DESTINATION_HEADER = 'Destination';
+
+ public function __construct(ICacheFactory $cacheFactory) {
+ $this->cache = $cacheFactory->createDistributed(self::CACHE_KEY);
+ }
+
+ /**
+ * @inheritdoc
+ */
+ public function initialize(Server $server) {
+ $server->on('afterMethod:MKCOL', [$this, 'afterMkcol']);
+ $server->on('beforeMethod:PUT', [$this, 'beforePut']);
+ $server->on('beforeMethod:DELETE', [$this, 'beforeDelete']);
+ $server->on('beforeMove', [$this, 'beforeMove'], 90);
+
+ $this->server = $server;
+ }
+
+ /**
+ * @param string $path
+ * @param bool $createIfNotExists
+ * @return FutureFile|UploadFile|ICollection|INode
+ */
+ private function getUploadFile(string $path, bool $createIfNotExists = false) {
+ try {
+ $actualFile = $this->server->tree->getNodeForPath($path);
+ // Only directly upload to the target file if it is on the same storage
+ // There may be further potential to optimize here by also uploading
+ // to other storages directly. This would require to also carefully pick
+ // the storage/path used in getStorage()
+ if ($actualFile instanceof File && $this->uploadFolder->getStorage()->getId() === $actualFile->getNode()->getStorage()->getId()) {
+ return $actualFile;
+ }
+ } catch (NotFound $e) {
+ // If there is no target file we upload to the upload folder first
+ }
+
+ // Use file in the upload directory that will be copied or moved afterwards
+ if ($createIfNotExists) {
+ $this->uploadFolder->createFile(self::TEMP_TARGET);
+ }
+
+ /** @var UploadFile $uploadFile */
+ $uploadFile = $this->uploadFolder->getChild(self::TEMP_TARGET);
+ return $uploadFile->getFile();
+ }
+
+ public function afterMkcol(RequestInterface $request, ResponseInterface $response): bool {
+ try {
+ $this->prepareUpload($request->getPath());
+ $this->checkPrerequisites(false);
+ } catch (BadRequest|StorageInvalidException|NotFound $e) {
+ return true;
+ }
+
+ $this->uploadPath = $this->server->calculateUri($this->server->httpRequest->getHeader(self::DESTINATION_HEADER));
+ $targetFile = $this->getUploadFile($this->uploadPath, true);
+ [$storage, $storagePath] = $this->getUploadStorage($this->uploadPath);
+
+ $this->uploadId = $storage->startChunkedWrite($storagePath);
+
+ $this->cache->set($this->uploadFolder->getName(), [
+ self::UPLOAD_ID => $this->uploadId,
+ self::UPLOAD_TARGET_PATH => $this->uploadPath,
+ self::UPLOAD_TARGET_ID => $targetFile->getId(),
+ ], 86400);
+
+ $response->setStatus(201);
+ return true;
+ }
+
+ public function beforePut(RequestInterface $request, ResponseInterface $response): bool {
+ try {
+ $this->prepareUpload(dirname($request->getPath()));
+ $this->checkPrerequisites();
+ } catch (StorageInvalidException|BadRequest|NotFound $e) {
+ return true;
+ }
+
+ [$storage, $storagePath] = $this->getUploadStorage($this->uploadPath);
+
+ $chunkName = basename($request->getPath());
+ $partId = is_numeric($chunkName) ? (int)$chunkName : -1;
+ if (!($partId >= 1 && $partId <= 10000)) {
+ throw new BadRequest('Invalid chunk name, must be numeric between 1 and 10000');
+ }
+
+ $uploadFile = $this->getUploadFile($this->uploadPath);
+ $tempTargetFile = null;
+
+ $additionalSize = (int)$request->getHeader('Content-Length');
+ if ($this->uploadFolder->childExists(self::TEMP_TARGET) && $this->uploadPath) {
+ /** @var UploadFile $tempTargetFile */
+ $tempTargetFile = $this->uploadFolder->getChild(self::TEMP_TARGET);
+ [$destinationDir, $destinationName] = Uri\split($this->uploadPath);
+ /** @var Directory $destinationParent */
+ $destinationParent = $this->server->tree->getNodeForPath($destinationDir);
+ $free = $storage->free_space($destinationParent->getInternalPath());
+ $newSize = $tempTargetFile->getSize() + $additionalSize;
+ if ($free >= 0 && ($tempTargetFile->getSize() > $free || $newSize > $free)) {
+ throw new InsufficientStorage("Insufficient space in $this->uploadPath");
+ }
+ }
+
+ $stream = $request->getBodyAsStream();
+ $storage->putChunkedWritePart($storagePath, $this->uploadId, (string)$partId, $stream, $additionalSize);
+
+ $storage->getCache()->update($uploadFile->getId(), ['size' => $uploadFile->getSize() + $additionalSize]);
+ if ($tempTargetFile) {
+ $storage->getPropagator()->propagateChange($tempTargetFile->getInternalPath(), time(), $additionalSize);
+ }
+
+ $response->setStatus(201);
+ return false;
+ }
+
+ public function beforeMove($sourcePath, $destination): bool {
+ try {
+ $this->prepareUpload(dirname($sourcePath));
+ $this->checkPrerequisites();
+ } catch (StorageInvalidException|BadRequest|NotFound|PreconditionFailed $e) {
+ return true;
+ }
+ [$storage, $storagePath] = $this->getUploadStorage($this->uploadPath);
+
+ $targetFile = $this->getUploadFile($this->uploadPath);
+
+ [$destinationDir, $destinationName] = Uri\split($destination);
+ /** @var Directory $destinationParent */
+ $destinationParent = $this->server->tree->getNodeForPath($destinationDir);
+ $destinationExists = $destinationParent->childExists($destinationName);
+
+
+ // allow sync clients to send the modification and creation time along in a header
+ $updateFileInfo = [];
+ if ($this->server->httpRequest->getHeader('X-OC-MTime') !== null) {
+ $updateFileInfo['mtime'] = $this->sanitizeMtime($this->server->httpRequest->getHeader('X-OC-MTime'));
+ $this->server->httpResponse->setHeader('X-OC-MTime', 'accepted');
+ }
+ if ($this->server->httpRequest->getHeader('X-OC-CTime') !== null) {
+ $updateFileInfo['creation_time'] = $this->sanitizeMtime($this->server->httpRequest->getHeader('X-OC-CTime'));
+ $this->server->httpResponse->setHeader('X-OC-CTime', 'accepted');
+ }
+ $updateFileInfo['mimetype'] = \OCP\Server::get(IMimeTypeDetector::class)->detectPath($destinationName);
+
+ if ($storage->instanceOfStorage(ObjectStoreStorage::class) && $storage->getObjectStore() instanceof IObjectStoreMultiPartUpload) {
+ /** @var ObjectStoreStorage $storage */
+ /** @var IObjectStoreMultiPartUpload $objectStore */
+ $objectStore = $storage->getObjectStore();
+ $parts = $objectStore->getMultipartUploads($storage->getURN($targetFile->getId()), $this->uploadId);
+ $size = 0;
+ foreach ($parts as $part) {
+ $size += $part['Size'];
+ }
+ $free = $storage->free_space($destinationParent->getInternalPath());
+ if ($free >= 0 && ($size > $free)) {
+ throw new InsufficientStorage("Insufficient space in $this->uploadPath");
+ }
+ }
+
+ $destinationInView = $destinationParent->getFileInfo()->getPath() . '/' . $destinationName;
+ $this->completeChunkedWrite($destinationInView);
+
+ $rootView = new View();
+ $rootView->putFileInfo($destinationInView, $updateFileInfo);
+
+ $sourceNode = $this->server->tree->getNodeForPath($sourcePath);
+ if ($sourceNode instanceof FutureFile) {
+ $this->uploadFolder->delete();
+ }
+
+ $this->server->emit('afterMove', [$sourcePath, $destination]);
+ $this->server->emit('afterUnbind', [$sourcePath]);
+ $this->server->emit('afterBind', [$destination]);
+
+ $response = $this->server->httpResponse;
+ $response->setHeader('Content-Type', 'application/xml; charset=utf-8');
+ $response->setHeader('Content-Length', '0');
+ $response->setStatus($destinationExists ? 204 : 201);
+ return false;
+ }
+
+ public function beforeDelete(RequestInterface $request, ResponseInterface $response) {
+ try {
+ $this->prepareUpload($request->getPath());
+ if (!$this->uploadFolder instanceof UploadFolder) {
+ return true;
+ }
+
+ [$storage, $storagePath] = $this->getUploadStorage($this->uploadPath);
+ $storage->cancelChunkedWrite($storagePath, $this->uploadId);
+ return true;
+ } catch (NotFound $e) {
+ return true;
+ }
+ }
+
+ /**
+ * @throws BadRequest
+ * @throws PreconditionFailed
+ * @throws StorageInvalidException
+ */
+ private function checkPrerequisites(bool $checkUploadMetadata = true): void {
+ if (!$this->uploadFolder instanceof UploadFolder || empty($this->server->httpRequest->getHeader(self::DESTINATION_HEADER))) {
+ throw new BadRequest('Skipping chunked file writing as the destination header was not passed');
+ }
+ if (!$this->uploadFolder->getStorage()->instanceOfStorage(IChunkedFileWrite::class)) {
+ throw new StorageInvalidException('Storage does not support chunked file writing');
+ }
+
+ if ($checkUploadMetadata) {
+ if ($this->uploadId === null || $this->uploadPath === null) {
+ throw new PreconditionFailed('Missing metadata for chunked upload');
+ }
+ }
+ }
+
+ /**
+ * @return array [IStorage, string]
+ */
+ private function getUploadStorage(string $targetPath): array {
+ $storage = $this->uploadFolder->getStorage();
+ $targetFile = $this->getUploadFile($targetPath);
+ return [$storage, $targetFile->getInternalPath()];
+ }
+
+ protected function sanitizeMtime(string $mtimeFromRequest): int {
+ if (!is_numeric($mtimeFromRequest)) {
+ throw new InvalidArgumentException('X-OC-MTime header must be an integer (unix timestamp).');
+ }
+
+ return (int)$mtimeFromRequest;
+ }
+
+ /**
+ * @throws NotFound
+ */
+ public function prepareUpload($path): void {
+ $this->uploadFolder = $this->server->tree->getNodeForPath($path);
+ $uploadMetadata = $this->cache->get($this->uploadFolder->getName());
+ $this->uploadId = $uploadMetadata[self::UPLOAD_ID] ?? null;
+ $this->uploadPath = $uploadMetadata[self::UPLOAD_TARGET_PATH] ?? null;
+ }
+
+ private function completeChunkedWrite(string $targetAbsolutePath): void {
+ $uploadFile = $this->getUploadFile($this->uploadPath)->getNode();
+ [$storage, $storagePath] = $this->getUploadStorage($this->uploadPath);
+
+ $rootFolder = \OCP\Server::get(IRootFolder::class);
+ $exists = $rootFolder->nodeExists($targetAbsolutePath);
+
+ $uploadFile->lock(ILockingProvider::LOCK_SHARED);
+ $this->emitPreHooks($targetAbsolutePath, $exists);
+ try {
+ $uploadFile->changeLock(ILockingProvider::LOCK_EXCLUSIVE);
+ $storage->completeChunkedWrite($storagePath, $this->uploadId);
+ $uploadFile->changeLock(ILockingProvider::LOCK_SHARED);
+ } catch (Exception $e) {
+ $uploadFile->unlock(ILockingProvider::LOCK_EXCLUSIVE);
+ throw $e;
+ }
+
+ // If the file was not uploaded to the user storage directly we need to copy/move it
+ try {
+ $uploadFileAbsolutePath = Filesystem::getRoot() . $uploadFile->getPath();
+ if ($uploadFileAbsolutePath !== $targetAbsolutePath) {
+ $uploadFile = $rootFolder->get($uploadFile->getFileInfo()->getPath());
+ if ($exists) {
+ $uploadFile->copy($targetAbsolutePath);
+ } else {
+ $uploadFile->move($targetAbsolutePath);
+ }
+ }
+ $this->emitPostHooks($targetAbsolutePath, $exists);
+ } catch (Exception $e) {
+ $uploadFile->unlock(ILockingProvider::LOCK_SHARED);
+ throw $e;
+ }
+ }
+
+ private function emitPreHooks(string $target, bool $exists): void {
+ $hookPath = $this->getHookPath($target);
+ if (!$exists) {
+ OC_Hook::emit(Filesystem::CLASSNAME, Filesystem::signal_create, [
+ Filesystem::signal_param_path => $hookPath,
+ ]);
+ } else {
+ OC_Hook::emit(Filesystem::CLASSNAME, Filesystem::signal_update, [
+ Filesystem::signal_param_path => $hookPath,
+ ]);
+ }
+ OC_Hook::emit(Filesystem::CLASSNAME, Filesystem::signal_write, [
+ Filesystem::signal_param_path => $hookPath,
+ ]);
+ }
+
+ private function emitPostHooks(string $target, bool $exists): void {
+ $hookPath = $this->getHookPath($target);
+ if (!$exists) {
+ OC_Hook::emit(Filesystem::CLASSNAME, Filesystem::signal_post_create, [
+ Filesystem::signal_param_path => $hookPath,
+ ]);
+ } else {
+ OC_Hook::emit(Filesystem::CLASSNAME, Filesystem::signal_post_update, [
+ Filesystem::signal_param_path => $hookPath,
+ ]);
+ }
+ OC_Hook::emit(Filesystem::CLASSNAME, Filesystem::signal_post_write, [
+ Filesystem::signal_param_path => $hookPath,
+ ]);
+ }
+
+ private function getHookPath(string $path): ?string {
+ if (!Filesystem::getView()) {
+ return $path;
+ }
+ return Filesystem::getView()->getRelativePath($path);
+ }
+}
diff --git a/apps/dav/lib/Upload/FutureFile.php b/apps/dav/lib/Upload/FutureFile.php
index eba550a62da..0b158e364cf 100644
--- a/apps/dav/lib/Upload/FutureFile.php
+++ b/apps/dav/lib/Upload/FutureFile.php
@@ -36,7 +36,6 @@ use Sabre\DAV\IFile;
* @package OCA\DAV\Upload
*/
class FutureFile implements \Sabre\DAV\IFile {
-
/** @var Directory */
private $root;
/** @var string */
@@ -66,6 +65,10 @@ class FutureFile implements \Sabre\DAV\IFile {
return AssemblyStream::wrap($nodes);
}
+ public function getPath() {
+ return $this->root->getFileInfo()->getInternalPath() . '/.file';
+ }
+
/**
* @inheritdoc
*/
diff --git a/apps/dav/lib/Upload/PartFile.php b/apps/dav/lib/Upload/PartFile.php
new file mode 100644
index 00000000000..8bfe992a987
--- /dev/null
+++ b/apps/dav/lib/Upload/PartFile.php
@@ -0,0 +1,111 @@
+<?php
+/**
+ * @copyright Copyright (c) 2016, ownCloud, Inc.
+ *
+ * @author Christoph Wurst <christoph@winzerhof-wurst.at>
+ * @author Lukas Reschke <lukas@statuscode.ch>
+ * @author Thomas Müller <thomas.mueller@tmit.eu>
+ *
+ * @license AGPL-3.0
+ *
+ * This code is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License, version 3,
+ * as published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License, version 3,
+ * along with this program. If not, see <http://www.gnu.org/licenses/>
+ *
+ */
+namespace OCA\DAV\Upload;
+
+use OCA\DAV\Connector\Sabre\Directory;
+use Sabre\DAV\Exception\Forbidden;
+use Sabre\DAV\IFile;
+
+/**
+ * This class represents an Upload part which is not present on the storage itself
+ * but handled directly by external storage services like S3 with Multipart Upload
+ */
+class PartFile implements IFile {
+ /** @var Directory */
+ private $root;
+ /** @var array */
+ private $partInfo;
+
+ public function __construct(Directory $root, array $partInfo) {
+ $this->root = $root;
+ $this->partInfo = $partInfo;
+ }
+
+ /**
+ * @inheritdoc
+ */
+ public function put($data) {
+ throw new Forbidden('Permission denied to put into this file');
+ }
+
+ /**
+ * @inheritdoc
+ */
+ public function get() {
+ throw new Forbidden('Permission denied to get this file');
+ }
+
+ public function getPath() {
+ return $this->root->getFileInfo()->getInternalPath() . '/' . $this->partInfo['PartNumber'];
+ }
+
+ /**
+ * @inheritdoc
+ */
+ public function getContentType() {
+ return 'application/octet-stream';
+ }
+
+ /**
+ * @inheritdoc
+ */
+ public function getETag() {
+ return $this->partInfo['ETag'];
+ }
+
+ /**
+ * @inheritdoc
+ */
+ public function getSize() {
+ return $this->partInfo['Size'];
+ }
+
+ /**
+ * @inheritdoc
+ */
+ public function delete() {
+ $this->root->delete();
+ }
+
+ /**
+ * @inheritdoc
+ */
+ public function getName() {
+ return $this->partInfo['PartNumber'];
+ }
+
+ /**
+ * @inheritdoc
+ */
+ public function setName($name) {
+ throw new Forbidden('Permission denied to rename this file');
+ }
+
+ /**
+ * @inheritdoc
+ */
+ public function getLastModified() {
+ return $this->partInfo['LastModified'];
+ }
+}
diff --git a/apps/dav/lib/Upload/UploadFile.php b/apps/dav/lib/Upload/UploadFile.php
index 023d17955c1..efe1385c8ce 100644
--- a/apps/dav/lib/Upload/UploadFile.php
+++ b/apps/dav/lib/Upload/UploadFile.php
@@ -44,6 +44,10 @@ class UploadFile implements IFile {
return $this->file->get();
}
+ public function getId() {
+ return $this->file->getId();
+ }
+
public function getContentType() {
return $this->file->getContentType();
}
@@ -75,4 +79,16 @@ class UploadFile implements IFile {
public function getLastModified() {
return $this->file->getLastModified();
}
+
+ public function getInternalPath(): string {
+ return $this->file->getInternalPath();
+ }
+
+ public function getFile(): File {
+ return $this->file;
+ }
+
+ public function getNode() {
+ return $this->file->getNode();
+ }
}
diff --git a/apps/dav/lib/Upload/UploadFolder.php b/apps/dav/lib/Upload/UploadFolder.php
index bb7c494cee3..66c190d84d9 100644
--- a/apps/dav/lib/Upload/UploadFolder.php
+++ b/apps/dav/lib/Upload/UploadFolder.php
@@ -24,20 +24,25 @@
*/
namespace OCA\DAV\Upload;
+use OC\Files\ObjectStore\ObjectStoreStorage;
use OCA\DAV\Connector\Sabre\Directory;
+use OCP\Files\ObjectStore\IObjectStoreMultiPartUpload;
+use OCP\Files\Storage\IStorage;
use Sabre\DAV\Exception\Forbidden;
use Sabre\DAV\ICollection;
class UploadFolder implements ICollection {
-
/** @var Directory */
private $node;
/** @var CleanupService */
private $cleanupService;
+ /** @var IStorage */
+ private $storage;
- public function __construct(Directory $node, CleanupService $cleanupService) {
+ public function __construct(Directory $node, CleanupService $cleanupService, IStorage $storage) {
$this->node = $node;
$this->cleanupService = $cleanupService;
+ $this->storage = $storage;
}
public function createFile($name, $data = null) {
@@ -66,6 +71,23 @@ class UploadFolder implements ICollection {
$children[] = new UploadFile($child);
}
+ if ($this->storage->instanceOfStorage(ObjectStoreStorage::class)) {
+ /** @var ObjectStoreStorage $storage */
+ $objectStore = $this->storage->getObjectStore();
+ if ($objectStore instanceof IObjectStoreMultiPartUpload) {
+ $cache = \OC::$server->getMemCacheFactory()->createDistributed(ChunkingV2Plugin::CACHE_KEY);
+ $uploadSession = $cache->get($this->getName());
+ if ($uploadSession) {
+ $uploadId = $uploadSession[ChunkingV2Plugin::UPLOAD_ID];
+ $id = $uploadSession[ChunkingV2Plugin::UPLOAD_TARGET_ID];
+ $parts = $objectStore->getMultipartUploads($this->storage->getURN($id), $uploadId);
+ foreach ($parts as $part) {
+ $children[] = new PartFile($this->node, $part);
+ }
+ }
+ }
+ }
+
return $children;
}
@@ -94,4 +116,8 @@ class UploadFolder implements ICollection {
public function getLastModified() {
return $this->node->getLastModified();
}
+
+ public function getStorage() {
+ return $this->storage;
+ }
}
diff --git a/apps/dav/lib/Upload/UploadHome.php b/apps/dav/lib/Upload/UploadHome.php
index 35d47b6a82a..6664d8c85b6 100644
--- a/apps/dav/lib/Upload/UploadHome.php
+++ b/apps/dav/lib/Upload/UploadHome.php
@@ -32,7 +32,6 @@ use Sabre\DAV\Exception\Forbidden;
use Sabre\DAV\ICollection;
class UploadHome implements ICollection {
-
/** @var array */
private $principalInfo;
/** @var CleanupService */
@@ -55,12 +54,12 @@ class UploadHome implements ICollection {
}
public function getChild($name): UploadFolder {
- return new UploadFolder($this->impl()->getChild($name), $this->cleanupService);
+ return new UploadFolder($this->impl()->getChild($name), $this->cleanupService, $this->getStorage());
}
public function getChildren(): array {
return array_map(function ($node) {
- return new UploadFolder($node, $this->cleanupService);
+ return new UploadFolder($node, $this->cleanupService, $this->getStorage());
}, $this->impl()->getChildren());
}
@@ -89,14 +88,24 @@ class UploadHome implements ICollection {
* @return Directory
*/
private function impl() {
+ $view = $this->getView();
+ $rootInfo = $view->getFileInfo('');
+ return new Directory($view, $rootInfo);
+ }
+
+ private function getView() {
$rootView = new View();
$user = \OC::$server->getUserSession()->getUser();
Filesystem::initMountPoints($user->getUID());
if (!$rootView->file_exists('/' . $user->getUID() . '/uploads')) {
$rootView->mkdir('/' . $user->getUID() . '/uploads');
}
- $view = new View('/' . $user->getUID() . '/uploads');
- $rootInfo = $view->getFileInfo('');
- return new Directory($view, $rootInfo);
+ return new View('/' . $user->getUID() . '/uploads');
+ }
+
+ private function getStorage() {
+ $view = $this->getView();
+ $storage = $view->getFileInfo('')->getStorage();
+ return $storage;
}
}
diff --git a/apps/dav/src/dav/client.js b/apps/dav/src/dav/client.js
index ff858e0492c..b053e585ce8 100644
--- a/apps/dav/src/dav/client.js
+++ b/apps/dav/src/dav/client.js
@@ -1,4 +1,4 @@
-/*
+/**
* @copyright 2021 Christoph Wurst <christoph@winzerhof-wurst.at>
*
* @author 2021 Christoph Wurst <christoph@winzerhof-wurst.at>
@@ -21,7 +21,7 @@
import * as webdav from 'webdav'
import axios from '@nextcloud/axios'
-import memoize from 'lodash/fp/memoize'
+import memoize from 'lodash/fp/memoize.js'
import { generateRemoteUrl } from '@nextcloud/router'
import { getCurrentUser } from '@nextcloud/auth'
diff --git a/apps/dav/src/service/CalendarService.js b/apps/dav/src/service/CalendarService.js
index 2b416d6b670..46c92436d6b 100644
--- a/apps/dav/src/service/CalendarService.js
+++ b/apps/dav/src/service/CalendarService.js
@@ -18,9 +18,9 @@
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
-import { getClient } from '../dav/client'
-import logger from './logger'
-import { parseXML } from 'webdav/dist/node/tools/dav'
+import { getClient } from '../dav/client.js'
+import logger from './logger.js'
+import { parseXML } from 'webdav'
import {
slotsToVavailability,
diff --git a/apps/dav/src/settings-personal-availability.js b/apps/dav/src/settings-personal-availability.js
index b0d6b19aa8a..8b7bcba0c26 100644
--- a/apps/dav/src/settings-personal-availability.js
+++ b/apps/dav/src/settings-personal-availability.js
@@ -1,6 +1,6 @@
import Vue from 'vue'
import { translate } from '@nextcloud/l10n'
-import Availability from './views/Availability'
+import Availability from './views/Availability.vue'
Vue.prototype.$t = translate
diff --git a/apps/dav/src/settings.js b/apps/dav/src/settings.js
index 6744f22ad23..a99db386d69 100644
--- a/apps/dav/src/settings.js
+++ b/apps/dav/src/settings.js
@@ -1,7 +1,7 @@
import Vue from 'vue'
import { loadState } from '@nextcloud/initial-state'
import { translate } from '@nextcloud/l10n'
-import CalDavSettings from './views/CalDavSettings'
+import CalDavSettings from './views/CalDavSettings.vue'
Vue.prototype.$t = translate
diff --git a/apps/dav/src/views/Availability.vue b/apps/dav/src/views/Availability.vue
index e0128a59e0a..bdc0c733c98 100644
--- a/apps/dav/src/views/Availability.vue
+++ b/apps/dav/src/views/Availability.vue
@@ -47,15 +47,15 @@ import {
findScheduleInboxAvailability,
getEmptySlots,
saveScheduleInboxAvailability,
-} from '../service/CalendarService'
+} from '../service/CalendarService.js'
import {
enableUserStatusAutomation,
disableUserStatusAutomation,
-} from '../service/PreferenceService'
-import NcButton from '@nextcloud/vue/dist/Components/NcButton'
-import NcCheckboxRadioSwitch from '@nextcloud/vue/dist/Components/NcCheckboxRadioSwitch'
-import NcSettingsSection from '@nextcloud/vue/dist/Components/NcSettingsSection'
-import NcTimezonePicker from '@nextcloud/vue/dist/Components/NcTimezonePicker'
+} from '../service/PreferenceService.js'
+import NcButton from '@nextcloud/vue/dist/Components/NcButton.js'
+import NcCheckboxRadioSwitch from '@nextcloud/vue/dist/Components/NcCheckboxRadioSwitch.js'
+import NcSettingsSection from '@nextcloud/vue/dist/Components/NcSettingsSection.js'
+import NcTimezonePicker from '@nextcloud/vue/dist/Components/NcTimezonePicker.js'
export default {
name: 'Availability',
diff --git a/apps/dav/src/views/CalDavSettings.spec.js b/apps/dav/src/views/CalDavSettings.spec.js
index 5c81c6259a1..7bc6e2f7b40 100644
--- a/apps/dav/src/views/CalDavSettings.spec.js
+++ b/apps/dav/src/views/CalDavSettings.spec.js
@@ -1,5 +1,5 @@
import { render } from '@testing-library/vue'
-import CalDavSettings from './CalDavSettings'
+import CalDavSettings from './CalDavSettings.vue'
// eslint-disable-next-line no-unused-vars
import { generateUrl } from '@nextcloud/router'
diff --git a/apps/dav/src/views/CalDavSettings.vue b/apps/dav/src/views/CalDavSettings.vue
index 776f32bff36..6755cbd171d 100644
--- a/apps/dav/src/views/CalDavSettings.vue
+++ b/apps/dav/src/views/CalDavSettings.vue
@@ -75,8 +75,8 @@
import axios from '@nextcloud/axios'
import { generateUrl } from '@nextcloud/router'
import { loadState } from '@nextcloud/initial-state'
-import NcSettingsSection from '@nextcloud/vue/dist/Components/NcSettingsSection'
-import NcCheckboxRadioSwitch from '@nextcloud/vue/dist/Components/NcCheckboxRadioSwitch'
+import NcSettingsSection from '@nextcloud/vue/dist/Components/NcSettingsSection.js'
+import NcCheckboxRadioSwitch from '@nextcloud/vue/dist/Components/NcCheckboxRadioSwitch.js'
const userSyncCalendarsDocUrl = loadState('dav', 'userSyncCalendarsDocUrl', '#')
diff --git a/apps/dav/tests/unit/BackgroundJob/UserStatusAutomationTest.php b/apps/dav/tests/unit/BackgroundJob/UserStatusAutomationTest.php
new file mode 100644
index 00000000000..59438c7cd28
--- /dev/null
+++ b/apps/dav/tests/unit/BackgroundJob/UserStatusAutomationTest.php
@@ -0,0 +1,204 @@
+<?php
+
+declare(strict_types=1);
+
+/**
+ * @copyright Copyright (c) 2023 Joas Schilling <coding@schilljs.com>
+ *
+ * @author Joas Schilling <coding@schilljs.com>
+ *
+ * @license GNU AGPL version 3 or any later version
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as
+ * published by the Free Software Foundation, either version 3 of the
+ * License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+
+namespace OCA\DAV\Tests\unit\BackgroundJob;
+
+use OCA\DAV\BackgroundJob\UserStatusAutomation;
+use OCP\AppFramework\Utility\ITimeFactory;
+use OCP\BackgroundJob\IJobList;
+use OCP\IConfig;
+use OCP\UserStatus\IManager;
+use OCP\UserStatus\IUserStatus;
+use PHPUnit\Framework\MockObject\MockObject;
+use Psr\Log\LoggerInterface;
+use Test\TestCase;
+
+/**
+ * @group DB
+ */
+class UserStatusAutomationTest extends TestCase {
+
+ protected MockObject|ITimeFactory $time;
+ protected MockObject|IJobList $jobList;
+ protected MockObject|LoggerInterface $logger;
+ protected MockObject|IManager $statusManager;
+ protected MockObject|IConfig $config;
+
+ protected function setUp(): void {
+ parent::setUp();
+
+ $this->time = $this->createMock(ITimeFactory::class);
+ $this->jobList = $this->createMock(IJobList::class);
+ $this->logger = $this->createMock(LoggerInterface::class);
+ $this->statusManager = $this->createMock(IManager::class);
+ $this->config = $this->createMock(IConfig::class);
+
+ }
+
+ protected function getAutomationMock(array $methods): MockObject|UserStatusAutomation {
+ if (empty($methods)) {
+ return new UserStatusAutomation(
+ $this->time,
+ \OC::$server->getDatabaseConnection(),
+ $this->jobList,
+ $this->logger,
+ $this->statusManager,
+ $this->config,
+ );
+ }
+
+ return $this->getMockBuilder(UserStatusAutomation::class)
+ ->setConstructorArgs([
+ $this->time,
+ \OC::$server->getDatabaseConnection(),
+ $this->jobList,
+ $this->logger,
+ $this->statusManager,
+ $this->config,
+ ])
+ ->setMethods($methods)
+ ->getMock();
+ }
+
+ public function dataRun(): array {
+ return [
+ ['20230217', '2023-02-24 10:49:36.613834', true],
+ ['20230224', '2023-02-24 10:49:36.613834', true],
+ ['20230217', '2023-02-24 13:58:24.479357', false],
+ ['20230224', '2023-02-24 13:58:24.479357', false],
+ ];
+ }
+
+ /**
+ * @dataProvider dataRun
+ */
+ public function testRun(string $ruleDay, string $currentTime, bool $isAvailable): void {
+ $this->config->method('getUserValue')
+ ->with('user', 'dav', 'user_status_automation', 'no')
+ ->willReturn('yes');
+
+ $this->time->method('getDateTime')
+ ->willReturn(new \DateTime($currentTime, new \DateTimeZone('UTC')));
+
+ $automation = $this->getAutomationMock(['getAvailabilityFromPropertiesTable']);
+ $automation->method('getAvailabilityFromPropertiesTable')
+ ->with('user')
+ ->willReturn('BEGIN:VCALENDAR
+PRODID:Nextcloud DAV app
+BEGIN:VTIMEZONE
+TZID:Europe/Berlin
+BEGIN:STANDARD
+TZNAME:CET
+TZOFFSETFROM:+0200
+TZOFFSETTO:+0100
+DTSTART:19701025T030000
+RRULE:FREQ=YEARLY;BYMONTH=10;BYDAY=-1SU
+END:STANDARD
+BEGIN:DAYLIGHT
+TZNAME:CEST
+TZOFFSETFROM:+0100
+TZOFFSETTO:+0200
+DTSTART:19700329T020000
+RRULE:FREQ=YEARLY;BYMONTH=3;BYDAY=-1SU
+END:DAYLIGHT
+END:VTIMEZONE
+BEGIN:VAVAILABILITY
+BEGIN:AVAILABLE
+DTSTART;TZID=Europe/Berlin:' . $ruleDay . 'T090000
+DTEND;TZID=Europe/Berlin:' . $ruleDay . 'T170000
+UID:3e6feeec-8e00-4265-b822-b73174e8b39f
+RRULE:FREQ=WEEKLY;BYDAY=TH
+END:AVAILABLE
+BEGIN:AVAILABLE
+DTSTART;TZID=Europe/Berlin:' . $ruleDay . 'T090000
+DTEND;TZID=Europe/Berlin:' . $ruleDay . 'T120000
+UID:8a634e99-07cf-443b-b480-005a0e1db323
+RRULE:FREQ=WEEKLY;BYDAY=FR
+END:AVAILABLE
+END:VAVAILABILITY
+END:VCALENDAR');
+
+ if ($isAvailable) {
+ $this->statusManager->expects($this->once())
+ ->method('revertUserStatus')
+ ->with('user', IUserStatus::MESSAGE_AVAILABILITY, IUserStatus::DND);
+ } else {
+ $this->statusManager->expects($this->once())
+ ->method('revertUserStatus')
+ ->with('user', IUserStatus::MESSAGE_CALL, IUserStatus::AWAY);
+ $this->statusManager->expects($this->once())
+ ->method('setUserStatus')
+ ->with('user', IUserStatus::MESSAGE_AVAILABILITY, IUserStatus::DND, true);
+ }
+
+ self::invokePrivate($automation, 'run', [['userId' => 'user']]);
+ }
+
+ public function testRunNoMoreAvailabilityDefined(): void {
+ $this->config->method('getUserValue')
+ ->with('user', 'dav', 'user_status_automation', 'no')
+ ->willReturn('yes');
+
+ $this->time->method('getDateTime')
+ ->willReturn(new \DateTime('2023-02-24 13:58:24.479357', new \DateTimeZone('UTC')));
+
+ $automation = $this->getAutomationMock(['getAvailabilityFromPropertiesTable']);
+ $automation->method('getAvailabilityFromPropertiesTable')
+ ->with('user')
+ ->willReturn('BEGIN:VCALENDAR
+PRODID:Nextcloud DAV app
+BEGIN:VTIMEZONE
+TZID:Europe/Berlin
+BEGIN:STANDARD
+TZNAME:CET
+TZOFFSETFROM:+0200
+TZOFFSETTO:+0100
+DTSTART:19701025T030000
+RRULE:FREQ=YEARLY;BYMONTH=10;BYDAY=-1SU
+END:STANDARD
+BEGIN:DAYLIGHT
+TZNAME:CEST
+TZOFFSETFROM:+0100
+TZOFFSETTO:+0200
+DTSTART:19700329T020000
+RRULE:FREQ=YEARLY;BYMONTH=3;BYDAY=-1SU
+END:DAYLIGHT
+END:VTIMEZONE
+BEGIN:VAVAILABILITY
+END:VAVAILABILITY
+END:VCALENDAR');
+
+ $this->statusManager->expects($this->once())
+ ->method('revertUserStatus')
+ ->with('user', IUserStatus::MESSAGE_AVAILABILITY, IUserStatus::DND);
+
+ $this->jobList->expects($this->once())
+ ->method('remove')
+ ->with(UserStatusAutomation::class, ['userId' => 'user']);
+
+ self::invokePrivate($automation, 'run', [['userId' => 'user']]);
+ }
+}
diff --git a/apps/dav/tests/unit/CalDAV/Schedule/IMipPluginTest.php b/apps/dav/tests/unit/CalDAV/Schedule/IMipPluginTest.php
index fdd707247ac..bf28fb472a8 100644
--- a/apps/dav/tests/unit/CalDAV/Schedule/IMipPluginTest.php
+++ b/apps/dav/tests/unit/CalDAV/Schedule/IMipPluginTest.php
@@ -204,7 +204,7 @@ class IMipPluginTest extends TestCase {
->method('getFrom');
$this->service->expects(self::once())
->method('addSubjectAndHeading')
- ->with($this->emailTemplate, 'request', 'Mr. Wizard', 'Fellowship meeting without (!) Boromir');
+ ->with($this->emailTemplate, 'request', 'Mr. Wizard', 'Fellowship meeting without (!) Boromir', true);
$this->service->expects(self::once())
->method('addBulletList')
->with($this->emailTemplate, $newVevent, $data);
@@ -296,7 +296,7 @@ class IMipPluginTest extends TestCase {
->method('getFrom');
$this->service->expects(self::once())
->method('addSubjectAndHeading')
- ->with($this->emailTemplate, 'request', 'Mr. Wizard', 'Elevenses');
+ ->with($this->emailTemplate, 'request', 'Mr. Wizard', 'Elevenses', false);
$this->service->expects(self::once())
->method('addBulletList')
->with($this->emailTemplate, $newVevent, $data);
@@ -405,7 +405,7 @@ class IMipPluginTest extends TestCase {
->method('getFrom');
$this->service->expects(self::once())
->method('addSubjectAndHeading')
- ->with($this->emailTemplate, 'request', 'Mr. Wizard', 'Fellowship meeting without (!) Boromir');
+ ->with($this->emailTemplate, 'request', 'Mr. Wizard', 'Fellowship meeting without (!) Boromir', false);
$this->service->expects(self::once())
->method('addBulletList')
->with($this->emailTemplate, $newVevent, $data);
@@ -480,7 +480,7 @@ class IMipPluginTest extends TestCase {
->method('getFrom');
$this->service->expects(self::once())
->method('addSubjectAndHeading')
- ->with($this->emailTemplate, 'request', 'Mr. Wizard', 'Fellowship meeting');
+ ->with($this->emailTemplate, 'request', 'Mr. Wizard', 'Fellowship meeting', false);
$this->service->expects(self::once())
->method('addBulletList')
->with($this->emailTemplate, $newVevent, $data);
@@ -553,7 +553,7 @@ class IMipPluginTest extends TestCase {
->method('getFrom');
$this->service->expects(self::once())
->method('addSubjectAndHeading')
- ->with($this->emailTemplate, 'request', 'Mr. Wizard', 'Fellowship meeting');
+ ->with($this->emailTemplate, 'request', 'Mr. Wizard', 'Fellowship meeting', false);
$this->service->expects(self::once())
->method('addBulletList')
->with($this->emailTemplate, $newVevent, $data);
diff --git a/apps/dav/tests/unit/CalDAV/Schedule/PluginTest.php b/apps/dav/tests/unit/CalDAV/Schedule/PluginTest.php
index 4845188bc88..8f315eac0ee 100644
--- a/apps/dav/tests/unit/CalDAV/Schedule/PluginTest.php
+++ b/apps/dav/tests/unit/CalDAV/Schedule/PluginTest.php
@@ -197,6 +197,16 @@ class PluginTest extends TestCase {
false,
CalDavBackend::PERSONAL_CALENDAR_URI,
CalDavBackend::PERSONAL_CALENDAR_NAME,
+ true,
+ true
+ ],
+ [
+ 'principals/users/myuser',
+ 'calendars/myuser',
+ false,
+ CalDavBackend::PERSONAL_CALENDAR_URI,
+ CalDavBackend::PERSONAL_CALENDAR_NAME,
+ false,
false,
true
],
@@ -225,6 +235,7 @@ class PluginTest extends TestCase {
true,
false,
false,
+ false,
],
[
'principals/users/myuser',
@@ -263,16 +274,8 @@ class PluginTest extends TestCase {
/**
* @dataProvider propFindDefaultCalendarUrlProvider
- * @param string $principalUri
- * @param string|null $calendarHome
- * @param bool $isResource
- * @param string $calendarUri
- * @param string $displayName
- * @param bool $exists
- * @param bool $propertiesForPath
*/
- public function testPropFindDefaultCalendarUrl(string $principalUri, ?string $calendarHome, bool $isResource, string $calendarUri, string $displayName, bool $exists, bool $hasExistingCalendars = false, bool $propertiesForPath = true): void {
- /** @var PropFind $propFind */
+ public function testPropFindDefaultCalendarUrl(string $principalUri, ?string $calendarHome, bool $isResource, string $calendarUri, string $displayName, bool $exists, bool $deleted = false, bool $hasExistingCalendars = false, bool $propertiesForPath = true): void {
$propFind = new PropFind(
$principalUri,
[
@@ -328,6 +331,12 @@ class PluginTest extends TestCase {
->with($calendarUri)
->willReturn($exists);
+ if ($exists) {
+ $calendar = $this->createMock(Calendar::class);
+ $calendar->expects($this->once())->method('isDeleted')->willReturn($deleted);
+ $calendarHomeObject->expects($deleted && !$hasExistingCalendars ? $this->exactly(2) : $this->once())->method('getChild')->with($calendarUri)->willReturn($calendar);
+ }
+
$calendarBackend = $this->createMock(CalDavBackend::class);
$calendarUri = $hasExistingCalendars ? 'custom' : $calendarUri;
$displayName = $hasExistingCalendars ? 'Custom Calendar' : $displayName;
@@ -349,7 +358,7 @@ class PluginTest extends TestCase {
)
] : [];
- if (!$exists) {
+ if (!$exists || $deleted) {
if (!$hasExistingCalendars) {
$calendarBackend->expects($this->once())
->method('createCalendar')
diff --git a/apps/dav/tests/unit/Connector/Sabre/DirectoryTest.php b/apps/dav/tests/unit/Connector/Sabre/DirectoryTest.php
index edbe4278c3a..c6365cf3168 100644
--- a/apps/dav/tests/unit/Connector/Sabre/DirectoryTest.php
+++ b/apps/dav/tests/unit/Connector/Sabre/DirectoryTest.php
@@ -63,7 +63,7 @@ class TestViewDirectory extends \OC\Files\View {
return $this->canRename;
}
- public function getRelativePath($path) {
+ public function getRelativePath($path): ?string {
return $path;
}
}
@@ -73,7 +73,6 @@ class TestViewDirectory extends \OC\Files\View {
* @group DB
*/
class DirectoryTest extends \Test\TestCase {
-
use UserTrait;
/** @var \OC\Files\View | \PHPUnit\Framework\MockObject\MockObject */
@@ -304,6 +303,10 @@ class DirectoryTest extends \Test\TestCase {
->method('free_space')
->willReturn(800);
+ $this->info->expects($this->any())
+ ->method('getPath')
+ ->willReturn('/admin/files/foo');
+
$this->info->expects($this->once())
->method('getSize')
->willReturn(200);
@@ -312,6 +315,10 @@ class DirectoryTest extends \Test\TestCase {
->method('getMountPoint')
->willReturn($mountPoint);
+ $this->view->expects($this->any())
+ ->method('getRelativePath')
+ ->willReturn('/foo');
+
$mountPoint->method('getMountPoint')
->willReturn('/user/files/mymountpoint');
@@ -359,6 +366,10 @@ class DirectoryTest extends \Test\TestCase {
$mountPoint->method('getMountPoint')
->willReturn('/user/files/mymountpoint');
+ $this->view->expects($this->any())
+ ->method('getRelativePath')
+ ->willReturn('/foo');
+
$dir = new Directory($this->view, $this->info);
$this->assertEquals([200, 800], $dir->getQuotaInfo()); //200 used, 800 free
}
diff --git a/apps/dav/tests/unit/Connector/Sabre/ObjectTreeTest.php b/apps/dav/tests/unit/Connector/Sabre/ObjectTreeTest.php
index d219888ef15..8d6bfc1764b 100644
--- a/apps/dav/tests/unit/Connector/Sabre/ObjectTreeTest.php
+++ b/apps/dav/tests/unit/Connector/Sabre/ObjectTreeTest.php
@@ -62,8 +62,7 @@ class ObjectTreeTest extends \Test\TestCase {
$view = $this->createMock(View::class);
$view->expects($this->once())
->method('verifyPath')
- ->with($targetParent)
- ->willReturn(true);
+ ->with($targetParent);
$view->expects($this->once())
->method('file_exists')
->with($targetPath)
diff --git a/apps/dav/tests/unit/Connector/Sabre/SharesPluginTest.php b/apps/dav/tests/unit/Connector/Sabre/SharesPluginTest.php
index abbf13d5479..e6e90838966 100644
--- a/apps/dav/tests/unit/Connector/Sabre/SharesPluginTest.php
+++ b/apps/dav/tests/unit/Connector/Sabre/SharesPluginTest.php
@@ -278,6 +278,7 @@ class SharesPluginTest extends \Test\TestCase {
[[IShare::TYPE_REMOTE]],
[[IShare::TYPE_ROOM]],
[[IShare::TYPE_DECK]],
+ [[IShare::TYPE_SCIENCEMESH]],
[[IShare::TYPE_USER, IShare::TYPE_GROUP]],
[[IShare::TYPE_USER, IShare::TYPE_GROUP, IShare::TYPE_LINK]],
[[IShare::TYPE_USER, IShare::TYPE_LINK]],