Compare commits
259 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 18713e5e9f | |||
| aaec698ece | |||
| 0bc2b34793 | |||
| 23573c4947 | |||
| 545194bc94 | |||
| 48f43b5583 | |||
| 22a51aace2 | |||
| b38c07c682 | |||
| 7bb0e0de7a | |||
| 75d9aaa3b5 | |||
| 89fa14fd77 | |||
| 4a0a00a5a2 | |||
| bdcd583045 | |||
| 9bbebd6034 | |||
| 9df79bae10 | |||
| 46f0c6ebb5 | |||
| 977541cedf | |||
| 64c52006dd | |||
| 2fc58bf45d | |||
| 5f81a77c72 | |||
| 92e282af0b | |||
| 61fe4c1aba | |||
| 806fe6619f | |||
| e0a21e5927 | |||
| d7f66eaee4 | |||
| 06aa7035db | |||
| 1a2d0d5c1d | |||
| b69109395b | |||
| 5c4e84f128 | |||
| 7fe5c8fdda | |||
| 50c9c7e332 | |||
| 1f24090770 | |||
| 4990d75007 | |||
| b3c53c7436 | |||
| 2d4bba7b0c | |||
| 385dd36ff8 | |||
| cf8203a5f6 | |||
| 6a3a244807 | |||
| 90cc0454a7 | |||
| 055b5ddb9e | |||
| 0305004709 | |||
| 248adf535c | |||
| 1b63b565d4 | |||
| ec3be25955 | |||
| 9dd661f3d8 | |||
| 71ef47e70b | |||
| 798f3734be | |||
| 7c0ffc0759 | |||
| ec176a933a | |||
| 06154dd116 | |||
| 34d5a103b4 | |||
| 5987584b90 | |||
| 46b5ff7467 | |||
| d658b9b558 | |||
| 7d45e9b2a3 | |||
| 32703d0500 | |||
| 2979a2bab0 | |||
| ec986d479b | |||
| 9e3269d3c4 | |||
| cb84ccc57d | |||
| aaca29bdad | |||
| 98d37090f4 | |||
| 17b4deb800 | |||
| c5d1f2f9ff | |||
| b4302fe57c | |||
| cf0b709ddf | |||
| a86662705e | |||
| b42d125950 | |||
| 2427b864ec | |||
| de39c5122c | |||
| 43f6f9b25c | |||
| 6309931feb | |||
| 49e35f111c | |||
| e54f990757 | |||
| d69e8923fe | |||
| 65f8de6d94 | |||
| dd882127ab | |||
| e7f4de6296 | |||
| 2e7ac880bb | |||
| 6001eba392 | |||
| 5cbfe61b18 | |||
| c60cc977b4 | |||
| 38f0ca875a | |||
| 9ad5214fce | |||
| 4d1051891e | |||
| 615c6916e1 | |||
| 7fb59a4493 | |||
| 106ac1b123 | |||
| 75ecce0b4c | |||
| 23c5856b39 | |||
| 06be851b1f | |||
| d9bd75bae9 | |||
| f0085789e6 | |||
| 05c90f5c46 | |||
| 9603805779 | |||
| 0e3d986bca | |||
| 5f1f95e2a6 | |||
| 52665f5b62 | |||
| 9330bc3200 | |||
| 73d7dde5e2 | |||
| 21c7ae62e0 | |||
| f3824f7d2c | |||
| f5c90b2f3d | |||
| a25af74028 | |||
| 8350fea770 | |||
| ae5d57371a | |||
| 109cd123fe | |||
| a5139f93eb | |||
| 7165e54b4e | |||
| 55f55984f0 | |||
| d0341bb323 | |||
| 9dfc0b1b23 | |||
| 32d263e363 | |||
| d08fe6d6b3 | |||
| 75770a00dd | |||
| 69c51bb163 | |||
| f5a5b005b6 | |||
| de4a35b202 | |||
| 9ce1066bc8 | |||
| c1003692f0 | |||
| b60a8ddc24 | |||
| 44b4741384 | |||
| 57f09b642e | |||
| 50ee26424d | |||
| 1f64ed9104 | |||
| 51d72dc4e3 | |||
| b8c6eb82ca | |||
| cb7669d72a | |||
| 375369cf68 | |||
| 1ae4764073 | |||
| eaae5e16d8 | |||
| bbc9ed138c | |||
| a4b9edc8eb | |||
| f32d865716 | |||
| 33c4fe504d | |||
| 8783679a49 | |||
| 7328754eb4 | |||
| 93296c2679 | |||
| 16f80a8d47 | |||
| e53ad663f1 | |||
| 7fbf997420 | |||
| 1b4e967604 | |||
| 4c887ec12b | |||
| d31be348af | |||
| d36602acd5 | |||
| aa227f1c55 | |||
| 3dac5b33ee | |||
| 5eeda5fcad | |||
| 2f18996347 | |||
| 17c40b9474 | |||
| 117d8dea0a | |||
| 5ecd3c4b49 | |||
| 9c4aaeff75 | |||
| 1121ae0838 | |||
| af53e9ca06 | |||
| 2b21913015 | |||
| e37bc0b580 | |||
| 57c62e1ca1 | |||
| e87bfbe278 | |||
| 1518ded8b1 | |||
| 2a96042db1 | |||
| da3a4c6a12 | |||
| 718cf4e15d | |||
| aa2ca86fb3 | |||
| e2c65b2493 | |||
| 222b19b805 | |||
| 2b9af820bd | |||
| 0dc93bc320 | |||
| 10921c05b7 | |||
| cc295f2452 | |||
| 19801f7ec4 | |||
| e6adbd921e | |||
| 8c52b6c0fe | |||
| 5660a73a3d | |||
| 43aa92f6c3 | |||
| 2e0baa5801 | |||
| a488bff27a | |||
| 9fef97c9ec | |||
| f16c449275 | |||
| 7139cac9d8 | |||
| f0c392e21c | |||
| 378ddda301 | |||
| 6b0bce8a2d | |||
| 58ae326f83 | |||
| 7f3fcdf495 | |||
| 09b0698255 | |||
| 67c14b0f11 | |||
| e7a28e787d | |||
| 3ba7888537 | |||
| 0c4bb5e8c5 | |||
| 67610f387c | |||
| 8b7aad82c7 | |||
| 6e9d48b558 | |||
| 13f25c9316 | |||
| b54c5392b6 | |||
| 36d9fcbb4d | |||
| b514d75323 | |||
| c67bdf02f0 | |||
| 7e76c91677 | |||
| 535bf3af9f | |||
| 6cae7d1f81 | |||
| 655ef1031b | |||
| 48dc04b571 | |||
| 3ff3ceae5d | |||
| 6adbe44976 | |||
| 96011a1579 | |||
| 61c9a6eb22 | |||
| 7c69862a5d | |||
| 761093daf8 | |||
| 1fdda4b202 | |||
| 84323ff6dd | |||
| ee6596782f | |||
| ad39dab6ca | |||
| 89577f6f94 | |||
| ab49a6d8a9 | |||
| 687df7c8ab | |||
| 1064c676cc | |||
| 88be308b06 | |||
| 9aac182109 | |||
| 51e5f7b159 | |||
| c695bf1a95 | |||
| b814f3bba6 | |||
| 97efc95efc | |||
| 83b8a390cd | |||
| 398b106f0c | |||
| 064e1133f5 | |||
| 2cf7e038b3 | |||
| 3a5769e8f9 | |||
| db8dd9f7f6 | |||
| ad85d65064 | |||
| 6f0120af16 | |||
| 520d8beaf5 | |||
| a4795a216c | |||
| b819d71337 | |||
| 2aa6894c38 | |||
| c9fa1db925 | |||
| ff3043346a | |||
| a3f1b079bb | |||
| 1750708509 | |||
| 12f31b69a0 | |||
| d24f0a3a54 | |||
| 78dcf3744d | |||
| ccda64fc66 | |||
| 662838b542 | |||
| 3535149418 | |||
| b694d2659b | |||
| 9195987d14 | |||
| f95fef9938 | |||
| d711d68701 | |||
| be7ef439cf | |||
| f22f7311de | |||
| 282bedcfb0 | |||
| 2e3fa51132 | |||
| c722f8c88b | |||
| 0bd5128d45 | |||
| 7f93711e68 | |||
| 627f8cabd6 | |||
| 222cbd0af6 | |||
| 54142b2b20 |
+13
-11
@@ -56,7 +56,7 @@ package-lock.json @nextcloud/server-dependabot
|
||||
/apps/webhook_listeners/appinfo/info.xml @come-nc @julien-nc
|
||||
/apps/workflowengine/appinfo/info.xml @blizzz @juliusknorr
|
||||
|
||||
# Frontend expertise
|
||||
# Files frontend expertise
|
||||
/apps/files/src* @skjnldsv @nextcloud/server-frontend
|
||||
/apps/files_external/src* @skjnldsv @nextcloud/server-frontend
|
||||
/apps/files_reminders/src* @skjnldsv @nextcloud/server-frontend
|
||||
@@ -91,17 +91,19 @@ ResponseDefinitions.php @provokateurin @nextcloud/server-backend
|
||||
/lib/public/OCM @nickvergessen @nextcloud/talk-backend @nextcloud/server-backend
|
||||
/lib/public/Talk @nickvergessen @nextcloud/talk-backend
|
||||
/lib/public/UserStatus @nickvergessen @nextcloud/talk-backend
|
||||
*/Notifications/* @nickvergessen @nextcloud/talk-backend
|
||||
|
||||
# Groupware
|
||||
/build/integration/dav_features/caldav.feature @st3iny @SebastianKrupinski @tcitworld
|
||||
/build/integration/dav_features/carddav.feature @hamza221 @SebastianKrupinski
|
||||
/lib/private/Calendar @st3iny @SebastianKrupinski @tcitworld
|
||||
/lib/private/Contacts @hamza221 @SebastianKrupinski
|
||||
/lib/public/Calendar @st3iny @SebastianKrupinski @tcitworld
|
||||
/lib/public/Contacts @hamza221 @SebastianKrupinski
|
||||
# Groupware team
|
||||
/build/integration/dav_features/caldav.feature @st3iny @SebastianKrupinski @tcitworld
|
||||
/build/integration/dav_features/carddav.feature @hamza221 @SebastianKrupinski
|
||||
/lib/private/Calendar @st3iny @SebastianKrupinski @tcitworld
|
||||
/lib/private/Contacts @hamza221 @SebastianKrupinski
|
||||
/lib/public/Calendar @st3iny @SebastianKrupinski @tcitworld
|
||||
/lib/public/Contacts @hamza221 @SebastianKrupinski
|
||||
|
||||
# Desktop client teamn
|
||||
/apps/dav/lib/Connector/Sabre/BlockLegacyClientPlugin.php @nextcloud/desktop
|
||||
|
||||
# Personal interest
|
||||
*/Activity/* @nickvergessen @nextcloud/server-backend
|
||||
*/Notifications/* @nickvergessen @nextcloud/talk-backend
|
||||
/apps/workflowengine/lib @nickvergessen
|
||||
|
||||
/apps/workflowengine/lib @nickvergessen @blizzz
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
# SPDX-FileCopyrightText: 2022 Nextcloud GmbH and Nextcloud contributors
|
||||
# SPDX-FileCopyrightText: 2025 Nextcloud GmbH and Nextcloud contributors
|
||||
# SPDX-License-Identifier: MIT
|
||||
|
||||
name: Update min supported desktop version
|
||||
|
||||
on:
|
||||
@@ -124,4 +125,4 @@ jobs:
|
||||
client: 💻 desktop
|
||||
automated
|
||||
3. to review
|
||||
reviewers: tobiasKaminsky, camilasan, claucambra
|
||||
reviewers: '@nextcloud/desktop'
|
||||
|
||||
-356
File diff suppressed because one or more lines are too long
+1
-1
Submodule 3rdparty updated: 99ab30df29...d93b7897ad
+532
File diff suppressed because one or more lines are too long
@@ -0,0 +1,21 @@
|
||||
OC.L10N.register(
|
||||
"dashboard",
|
||||
{
|
||||
"Dashboard" : "Панэль кіравання",
|
||||
"Dashboard app" : "Праграма Панэль кіравання",
|
||||
"Weather" : "Надвор'е",
|
||||
"Status" : "Стан",
|
||||
"Good morning" : "Добрай раніцы",
|
||||
"Good morning, {name}" : "Добрай раніцы, {name}",
|
||||
"Good afternoon" : "Добры дзень",
|
||||
"Good afternoon, {name}" : "Добры дзень, {name}",
|
||||
"Good evening" : "Добры вечар",
|
||||
"Good evening, {name}" : "Добры вечар, {name}",
|
||||
"Hello" : "Вітаем",
|
||||
"Hello, {name}" : "Вітаем, {name}",
|
||||
"Happy birthday 🥳🤩🎂🎉" : "З народзінамі 🥳🤩🎂🎉",
|
||||
"Happy birthday, {name} 🥳🤩🎂🎉" : "З народзінамі, {name} 🥳🤩🎂🎉",
|
||||
"Customize" : "Дапасаваць",
|
||||
"Edit widgets" : "Рэдагаваць віджэты"
|
||||
},
|
||||
"nplurals=4; plural=(n%10==1 && n%100!=11 ? 0 : n%10>=2 && n%10<=4 && (n%100<12 || n%100>14) ? 1 : n%10==0 || (n%10>=5 && n%10<=9) || (n%100>=11 && n%100<=14)? 2 : 3);");
|
||||
@@ -0,0 +1,19 @@
|
||||
{ "translations": {
|
||||
"Dashboard" : "Панэль кіравання",
|
||||
"Dashboard app" : "Праграма Панэль кіравання",
|
||||
"Weather" : "Надвор'е",
|
||||
"Status" : "Стан",
|
||||
"Good morning" : "Добрай раніцы",
|
||||
"Good morning, {name}" : "Добрай раніцы, {name}",
|
||||
"Good afternoon" : "Добры дзень",
|
||||
"Good afternoon, {name}" : "Добры дзень, {name}",
|
||||
"Good evening" : "Добры вечар",
|
||||
"Good evening, {name}" : "Добры вечар, {name}",
|
||||
"Hello" : "Вітаем",
|
||||
"Hello, {name}" : "Вітаем, {name}",
|
||||
"Happy birthday 🥳🤩🎂🎉" : "З народзінамі 🥳🤩🎂🎉",
|
||||
"Happy birthday, {name} 🥳🤩🎂🎉" : "З народзінамі, {name} 🥳🤩🎂🎉",
|
||||
"Customize" : "Дапасаваць",
|
||||
"Edit widgets" : "Рэдагаваць віджэты"
|
||||
},"pluralForm" :"nplurals=4; plural=(n%10==1 && n%100!=11 ? 0 : n%10>=2 && n%10<=4 && (n%100<12 || n%100>14) ? 1 : n%10==0 || (n%10>=5 && n%10<=9) || (n%100>=11 && n%100<=14)? 2 : 3);"
|
||||
}
|
||||
@@ -10,7 +10,7 @@
|
||||
<name>WebDAV</name>
|
||||
<summary>WebDAV endpoint</summary>
|
||||
<description>WebDAV endpoint</description>
|
||||
<version>1.34.0</version>
|
||||
<version>1.34.1</version>
|
||||
<licence>agpl</licence>
|
||||
<author>owncloud.org</author>
|
||||
<namespace>DAV</namespace>
|
||||
|
||||
@@ -63,6 +63,7 @@ $cardDavBackend = new CardDavBackend(
|
||||
Server::get(IUserManager::class),
|
||||
Server::get(IEventDispatcher::class),
|
||||
Server::get(\OCA\DAV\CardDAV\Sharing\Backend::class),
|
||||
Server::get(IConfig::class),
|
||||
);
|
||||
|
||||
$debugging = Server::get(IConfig::class)->getSystemValue('debug', false);
|
||||
|
||||
@@ -216,6 +216,8 @@ return array(
|
||||
'OCA\\DAV\\Connector\\Sabre\\Node' => $baseDir . '/../lib/Connector/Sabre/Node.php',
|
||||
'OCA\\DAV\\Connector\\Sabre\\ObjectTree' => $baseDir . '/../lib/Connector/Sabre/ObjectTree.php',
|
||||
'OCA\\DAV\\Connector\\Sabre\\Principal' => $baseDir . '/../lib/Connector/Sabre/Principal.php',
|
||||
'OCA\\DAV\\Connector\\Sabre\\PropFindMonitorPlugin' => $baseDir . '/../lib/Connector/Sabre/PropFindMonitorPlugin.php',
|
||||
'OCA\\DAV\\Connector\\Sabre\\PropFindPreloadNotifyPlugin' => $baseDir . '/../lib/Connector/Sabre/PropFindPreloadNotifyPlugin.php',
|
||||
'OCA\\DAV\\Connector\\Sabre\\PropfindCompressionPlugin' => $baseDir . '/../lib/Connector/Sabre/PropfindCompressionPlugin.php',
|
||||
'OCA\\DAV\\Connector\\Sabre\\PublicAuth' => $baseDir . '/../lib/Connector/Sabre/PublicAuth.php',
|
||||
'OCA\\DAV\\Connector\\Sabre\\QuotaPlugin' => $baseDir . '/../lib/Connector/Sabre/QuotaPlugin.php',
|
||||
@@ -353,6 +355,7 @@ return array(
|
||||
'OCA\\DAV\\Migration\\Version1029Date20231004091403' => $baseDir . '/../lib/Migration/Version1029Date20231004091403.php',
|
||||
'OCA\\DAV\\Migration\\Version1030Date20240205103243' => $baseDir . '/../lib/Migration/Version1030Date20240205103243.php',
|
||||
'OCA\\DAV\\Migration\\Version1031Date20240610134258' => $baseDir . '/../lib/Migration/Version1031Date20240610134258.php',
|
||||
'OCA\\DAV\\Migration\\Version1034Date20250813093701' => $baseDir . '/../lib/Migration/Version1034Date20250813093701.php',
|
||||
'OCA\\DAV\\Model\\ExampleEvent' => $baseDir . '/../lib/Model/ExampleEvent.php',
|
||||
'OCA\\DAV\\Paginate\\LimitedCopyIterator' => $baseDir . '/../lib/Paginate/LimitedCopyIterator.php',
|
||||
'OCA\\DAV\\Paginate\\PaginateCache' => $baseDir . '/../lib/Paginate/PaginateCache.php',
|
||||
|
||||
@@ -231,6 +231,8 @@ class ComposerStaticInitDAV
|
||||
'OCA\\DAV\\Connector\\Sabre\\Node' => __DIR__ . '/..' . '/../lib/Connector/Sabre/Node.php',
|
||||
'OCA\\DAV\\Connector\\Sabre\\ObjectTree' => __DIR__ . '/..' . '/../lib/Connector/Sabre/ObjectTree.php',
|
||||
'OCA\\DAV\\Connector\\Sabre\\Principal' => __DIR__ . '/..' . '/../lib/Connector/Sabre/Principal.php',
|
||||
'OCA\\DAV\\Connector\\Sabre\\PropFindMonitorPlugin' => __DIR__ . '/..' . '/../lib/Connector/Sabre/PropFindMonitorPlugin.php',
|
||||
'OCA\\DAV\\Connector\\Sabre\\PropFindPreloadNotifyPlugin' => __DIR__ . '/..' . '/../lib/Connector/Sabre/PropFindPreloadNotifyPlugin.php',
|
||||
'OCA\\DAV\\Connector\\Sabre\\PropfindCompressionPlugin' => __DIR__ . '/..' . '/../lib/Connector/Sabre/PropfindCompressionPlugin.php',
|
||||
'OCA\\DAV\\Connector\\Sabre\\PublicAuth' => __DIR__ . '/..' . '/../lib/Connector/Sabre/PublicAuth.php',
|
||||
'OCA\\DAV\\Connector\\Sabre\\QuotaPlugin' => __DIR__ . '/..' . '/../lib/Connector/Sabre/QuotaPlugin.php',
|
||||
@@ -368,6 +370,7 @@ class ComposerStaticInitDAV
|
||||
'OCA\\DAV\\Migration\\Version1029Date20231004091403' => __DIR__ . '/..' . '/../lib/Migration/Version1029Date20231004091403.php',
|
||||
'OCA\\DAV\\Migration\\Version1030Date20240205103243' => __DIR__ . '/..' . '/../lib/Migration/Version1030Date20240205103243.php',
|
||||
'OCA\\DAV\\Migration\\Version1031Date20240610134258' => __DIR__ . '/..' . '/../lib/Migration/Version1031Date20240610134258.php',
|
||||
'OCA\\DAV\\Migration\\Version1034Date20250813093701' => __DIR__ . '/..' . '/../lib/Migration/Version1034Date20250813093701.php',
|
||||
'OCA\\DAV\\Model\\ExampleEvent' => __DIR__ . '/..' . '/../lib/Model/ExampleEvent.php',
|
||||
'OCA\\DAV\\Paginate\\LimitedCopyIterator' => __DIR__ . '/..' . '/../lib/Paginate/LimitedCopyIterator.php',
|
||||
'OCA\\DAV\\Paginate\\PaginateCache' => __DIR__ . '/..' . '/../lib/Paginate/PaginateCache.php',
|
||||
|
||||
+1
-1
@@ -80,7 +80,7 @@ OC.L10N.register(
|
||||
"_In a month on %1$s for the entire day_::_In %n months on %1$s for the entire day_" : ["I en måned på %1$s for hele dagen","Om %n måneder den %1$s for hele dagen"],
|
||||
"_In a year on %1$s for the entire day_::_In %n years on %1$s for the entire day_" : ["I et år på %1$s for hele dagen","Om %n år den %1$s for hele dagen"],
|
||||
"In the past on %1$s between %2$s - %3$s" : "Tidligere den %1$s mellem %2$s - %3$s",
|
||||
"_In a minute on %1$s between %2$s - %3$s_::_In %n minutes on %1$s between %2$s - %3$s_" : ["I et minut på %1$s mellem% %2$s - %3$s","Om %n minutter den %1$s mellem %2$s - %3$s"],
|
||||
"_In a minute on %1$s between %2$s - %3$s_::_In %n minutes on %1$s between %2$s - %3$s_" : ["I et minut på %1$s mellem %2$s - %3$s","Om %n minutter den %1$s mellem %2$s - %3$s"],
|
||||
"_In a hour on %1$s between %2$s - %3$s_::_In %n hours on %1$s between %2$s - %3$s_" : ["I en time på %1$s mellem %2$s - %3$s","Om %n timer den %1$s mellem %2$s - %3$s"],
|
||||
"_In a day on %1$s between %2$s - %3$s_::_In %n days on %1$s between %2$s - %3$s_" : ["I en dag på %1$s mellem %2$s - %3$s","Om %n dage den %1$s mellem %2$s - %3$s"],
|
||||
"_In a week on %1$s between %2$s - %3$s_::_In %n weeks on %1$s between %2$s - %3$s_" : ["I en uge på %1$s mellem %2$s - %3$s","Om %n uger den %1$s mellem %2$s - %3$s"],
|
||||
|
||||
@@ -78,7 +78,7 @@
|
||||
"_In a month on %1$s for the entire day_::_In %n months on %1$s for the entire day_" : ["I en måned på %1$s for hele dagen","Om %n måneder den %1$s for hele dagen"],
|
||||
"_In a year on %1$s for the entire day_::_In %n years on %1$s for the entire day_" : ["I et år på %1$s for hele dagen","Om %n år den %1$s for hele dagen"],
|
||||
"In the past on %1$s between %2$s - %3$s" : "Tidligere den %1$s mellem %2$s - %3$s",
|
||||
"_In a minute on %1$s between %2$s - %3$s_::_In %n minutes on %1$s between %2$s - %3$s_" : ["I et minut på %1$s mellem% %2$s - %3$s","Om %n minutter den %1$s mellem %2$s - %3$s"],
|
||||
"_In a minute on %1$s between %2$s - %3$s_::_In %n minutes on %1$s between %2$s - %3$s_" : ["I et minut på %1$s mellem %2$s - %3$s","Om %n minutter den %1$s mellem %2$s - %3$s"],
|
||||
"_In a hour on %1$s between %2$s - %3$s_::_In %n hours on %1$s between %2$s - %3$s_" : ["I en time på %1$s mellem %2$s - %3$s","Om %n timer den %1$s mellem %2$s - %3$s"],
|
||||
"_In a day on %1$s between %2$s - %3$s_::_In %n days on %1$s between %2$s - %3$s_" : ["I en dag på %1$s mellem %2$s - %3$s","Om %n dage den %1$s mellem %2$s - %3$s"],
|
||||
"_In a week on %1$s between %2$s - %3$s_::_In %n weeks on %1$s between %2$s - %3$s_" : ["I en uge på %1$s mellem %2$s - %3$s","Om %n uger den %1$s mellem %2$s - %3$s"],
|
||||
|
||||
@@ -79,6 +79,9 @@ OC.L10N.register(
|
||||
"_In a week on %1$s for the entire day_::_In %n weeks on %1$s for the entire day_" : ["Za tydzień, dnia %1$s, przez cały dzień","Za %n tygodnie, dnia %1$s, przez cały dzień","Za %n tygodni, dnia %1$s, przez cały dzień","Za %n tygodni, dnia %1$s, przez cały dzień"],
|
||||
"_In a month on %1$s for the entire day_::_In %n months on %1$s for the entire day_" : ["W ciągu miesiąca, dnia %1$s przez cały dzień","W ciągu %n miesięcy, dnia %1$s przez cały dzień","W ciągu %n miesięcy, dnia %1$s przez cały dzień","W ciągu %n miesięcy, dnia %1$s przez cały dzień"],
|
||||
"_In a year on %1$s for the entire day_::_In %n years on %1$s for the entire day_" : ["Za rok dnia %1$s przez cały dzień","Za %n lata dnia %1$s przez cały dzień","Za %n lat dnia %1$s przez cały dzień","Za %n lat dnia %1$s przez cały dzień"],
|
||||
"In the past on %1$s between %2$s - %3$s" : "W przeszłości dnia %1$s między %2$s - %3$s",
|
||||
"_In a minute on %1$s between %2$s - %3$s_::_In %n minutes on %1$s between %2$s - %3$s_" : ["Za minutę dnia %1$s między %2$s - %3$s","Za %n minuty dnia %1$s między %2$s - %3$s","Za %n minut dnia %1$s między %2$s - %3$s","Za %n minut dnia %1$s między %2$s - %3$s"],
|
||||
"_In a hour on %1$s between %2$s - %3$s_::_In %n hours on %1$s between %2$s - %3$s_" : ["Za godzinę dnia %1$s między %2$s - %3$s","Za %n godziny dnia %1$s między %2$s - %3$s","Za %n godzin dnia %1$s między %2$s - %3$s","Za %n godzin dnia %1$s między %2$s - %3$s"],
|
||||
"_In a day on %1$s between %2$s - %3$s_::_In %n days on %1$s between %2$s - %3$s_" : ["Za dzień, dnia %1$s między %2$s - %3$s","Za %n dni dnia %1$s między %2$s - %3$s","Za %n dni dnia %1$s między %2$s - %3$s","Za %n dni dnia %1$s między %2$s - %3$s"],
|
||||
"_In a week on %1$s between %2$s - %3$s_::_In %n weeks on %1$s between %2$s - %3$s_" : ["Za tydzień dnia %1$s między %2$s - %3$s","Za %n tygodnie dnia %1$s między %2$s - %3$s","Za %n tygodni dnia %1$s między %2$s - %3$s","Za %n tygodni dnia %1$s między %2$s - %3$s"],
|
||||
"_In a month on %1$s between %2$s - %3$s_::_In %n months on %1$s between %2$s - %3$s_" : ["Za miesiąc dnia %1$s między %2$s - %3$s","Za %n miesięcy dnia %1$s między %2$s - %3$s","Za %n miesięcy dnia %1$s między %2$s - %3$s","Za %n miesięcy dnia %1$s między %2$s - %3$s"],
|
||||
@@ -87,18 +90,38 @@ OC.L10N.register(
|
||||
"Every Day for the entire day" : "Codziennie przez cały dzień",
|
||||
"Every Day for the entire day until %1$s" : "Codziennie przez cały dzień do %1$s",
|
||||
"Every Day between %1$s - %2$s" : "Codziennie między %1$s – %2$s",
|
||||
"Every Day between %1$s - %2$s until %3$s" : "Codziennie między %1$s - %2$s do %3$s",
|
||||
"Every %1$d Days for the entire day" : "Co %1$d dni przez cały dzień",
|
||||
"Every %1$d Days for the entire day until %2$s" : "Co %1$d dni przez cały dzień aż do %2$s",
|
||||
"Every %1$d Days between %2$s - %3$s" : "Co %1$d dni pomiędzy %2$s - %3$s",
|
||||
"Every %1$d Days between %2$s - %3$s until %4$s" : "Co %1$d dni, pomiędzy %2$s - %3$s aż do %4$s",
|
||||
"Could not generate event recurrence statement" : "Nie można wygenerować zestawienia powtórzeń zdarzenia",
|
||||
"Every Week on %1$s for the entire day" : "Każdego tygodnia w %1$s przez cały dzień",
|
||||
"Every Week on %1$s for the entire day until %2$s" : "Co tydzień w %1$s przez cały dzień do %2$s",
|
||||
"Every Week on %1$s between %2$s - %3$s" : "Co tydzień w %1$s między %2$s - %3$s",
|
||||
"Every Week on %1$s between %2$s - %3$s until %4$s" : "Co tydzień w %1$s między %2$s - %3$s do %4$s",
|
||||
"Every %1$d Weeks on %2$s for the entire day" : "Co %1$d tygodni w %2$s przez cały dzień",
|
||||
"Every %1$d Weeks on %2$s for the entire day until %3$s" : "Co %1$d tygodnie w %2$s przez cały dzień do %3$s",
|
||||
"Every %1$d Weeks on %2$s between %3$s - %4$s" : "Co %1$d tygodnie w %2$s między %3$s - %4$s",
|
||||
"Every %1$d Weeks on %2$s between %3$s - %4$s until %5$s" : "Co %1$d tygodnie w %2$s między %3$s - %4$s do %5$s",
|
||||
"Every Month on the %1$s for the entire day" : "Co miesiąc dnia %1$s przez cały dzień",
|
||||
"Every Month on the %1$s for the entire day until %2$s" : "Co miesiąc dnia %1$s przez cały dzień do %2$s",
|
||||
"Every Month on the %1$s between %2$s - %3$s" : "Co miesiąc dnia %1$s między %2$s - %3$s",
|
||||
"Every Month on the %1$s between %2$s - %3$s until %4$s" : "Co miesiąc dnia %1$s między %2$s - %3$s do %4$s",
|
||||
"Every %1$d Months on the %2$s for the entire day" : "Co %1$d miesiący dnia %2$s przez cały dzień",
|
||||
"Every %1$d Months on the %2$s for the entire day until %3$s" : "Co %1$d miesięcy dnia %2$s przez cały dzień do %3$s",
|
||||
"Every %1$d Months on the %2$s between %3$s - %4$s" : "Co %1$d miesięcy dnia %2$s między %3$s - %4$s",
|
||||
"Every %1$d Months on the %2$s between %3$s - %4$s until %5$s" : "Każdego %1$d miesiąca dnia %2$s między %3$s - %4$s do %5$s",
|
||||
"Every Year in %1$s on the %2$s for the entire day" : "Co rok w %1$s dnia %2$s przez cały dzień",
|
||||
"Every Year in %1$s on the %2$s for the entire day until %3$s" : "Co rok w %1$s dnia %2$s przez cały dzień do %3$s",
|
||||
"Every Year in %1$s on the %2$s between %3$s - %4$s" : "Co roku za %1$s dnia %2$s między %3$s - %4$s",
|
||||
"Every Year in %1$s on the %2$s between %3$s - %4$s until %5$s" : "Co roku za %1$s dnia %2$s między %3$s - %4$s do %5$s",
|
||||
"Every %1$d Years in %2$s on the %3$s for the entire day" : "Co %1$d lat dnia %2$s o %3$s przez cały dzień",
|
||||
"Every %1$d Years in %2$s on the %3$s for the entire day until %4$s" : "Co %1$d lat w %2$s dnia %3$s przez cały dzień do %4$s",
|
||||
"Every %1$d Years in %2$s on the %3$s between %4$s - %5$s" : "Co %1$d lat w %2$s dnia %3$s między %4$s - %5$s",
|
||||
"Every %1$d Years in %2$s on the %3$s between %4$s - %5$s until %6$s" : "Co %1$d lat w %2$s dnia %3$s między %4$s - %5$s do %6$s",
|
||||
"On specific dates for the entire day until %1$s" : "W określonych datach przez cały dzień do %1$s",
|
||||
"On specific dates between %1$s - %2$s until %3$s" : "W określonych datach między %1$s - %2$s do %3$s",
|
||||
"In the past on %1$s" : "W przeszłości dnia %1$s",
|
||||
"_In a minute on %1$s_::_In %n minutes on %1$s_" : ["Za minutę dnia %1$s","Za %n minut dnia %1$s","Za %n minut dnia %1$s","Za %n minut dnia %1$s"],
|
||||
"_In a hour on %1$s_::_In %n hours on %1$s_" : ["Za godzinę dnia %1$s","Za %n godziny dnia %1$s","Za %n godzin dnia %1$s","Za %n godzin dnia %1$s"],
|
||||
@@ -108,6 +131,18 @@ OC.L10N.register(
|
||||
"_In a year on %1$s_::_In %n years on %1$s_" : ["Za rok dnia %1$s","Za %n lat dnia %1$s","Za %n lat dnia %1$s","Za %n lat dnia %1$s"],
|
||||
"In the past on %1$s then on %2$s" : "W przeszłości dnia %1$s, a następnie %2$s",
|
||||
"_In a minute on %1$s then on %2$s_::_In %n minutes on %1$s then on %2$s_" : ["Za minutę dnia %1$s, a następnie %2$s","Za %n minuty dnia %1$s, a następnie %2$s","Za %n minut dnia %1$s, a następnie %2$s","Za %n minut dnia %1$s, a następnie %2$s"],
|
||||
"_In a hour on %1$s then on %2$s_::_In %n hours on %1$s then on %2$s_" : ["Za godzinę dnia %1$s, następnie dnia %2$s","Za %n godziny dnia %1$s, następnie dnia %2$s","Za %n godzin dnia %1$s, następnie dnia %2$s","Za %n godzin dnia %1$s, następnie dnia %2$s"],
|
||||
"_In a day on %1$s then on %2$s_::_In %n days on %1$s then on %2$s_" : ["Jutro dnia %1$s, następnie dnia %2$s","Za %n dni dnia %1$s, następnie dnia %2$s","Za %n dni dnia %1$s, następnie dnia %2$s","Za %n dni dnia %1$s, następnie dnia %2$s"],
|
||||
"_In a week on %1$s then on %2$s_::_In %n weeks on %1$s then on %2$s_" : ["Za tydzień dnia %1$s, następnie dnia %2$s","Za %n tygodnie dnia %1$s, następnie dnia %2$s","Za %n tygodni dnia %1$s, następnie dnia %2$s","Za %n tygodni dnia %1$s, następnie dnia %2$s"],
|
||||
"_In a month on %1$s then on %2$s_::_In %n months on %1$s then on %2$s_" : ["Za miesiąc dnia %1$s, następnie dnia %2$s","Za %n miesiące dnia %1$s, następnie dnia %2$s","Za %n miesięcy dnia %1$s, następnie dnia %2$s","Za %n miesięcy dnia %1$s, następnie dnia %2$s"],
|
||||
"_In a year on %1$s then on %2$s_::_In %n years on %1$s then on %2$s_" : ["Za rok dnia %1$s, następnie dnia %2$s","Za %n lata dnia %1$s, następnie dnia %2$s","Za %n lat dnia %1$s, następnie dnia %2$s","Za %n lat dnia %1$s, następnie dnia %2$s"],
|
||||
"In the past on %1$s then on %2$s and %3$s" : "W przeszłości dnia %1$s, następnie dnia %2$s i %3$s",
|
||||
"_In a minute on %1$s then on %2$s and %3$s_::_In %n minutes on %1$s then on %2$s and %3$s_" : ["Za minutę dnia %1$s, następnie dnia %2$s i %3$s","Za %n minuty dnia %1$s, następnie dnia %2$s i %3$s","Za %n minut dnia %1$s, następnie dnia %2$s i %3$s","Za %n minut dnia %1$s, następnie dnia %2$s i %3$s"],
|
||||
"_In a hour on %1$s then on %2$s and %3$s_::_In %n hours on %1$s then on %2$s and %3$s_" : ["Za godzinę dnia %1$s, następnie dnia %2$s i %3$s","Za %%ngodziny dnia %1$s, następnie dnia %2$s i %3$s","Za %n godzin dnia %1$s, następnie dnia %2$s i %3$s","Za %n godzin dnia %1$s, następnie dnia %2$s i %3$s"],
|
||||
"_In a day on %1$s then on %2$s and %3$s_::_In %n days on %1$s then on %2$s and %3$s_" : ["Jutro, dnia %1$s, następnie dnia %2$s i %3$s","Za %n dni dnia %1$s, następnie dnia %2$s i %3$s","Za %n dni dnia %1$s, następnie dnia %2$s i %3$s","Za %n dni dnia %1$s, następnie dnia %2$s i %3$s"],
|
||||
"_In a week on %1$s then on %2$s and %3$s_::_In %n weeks on %1$s then on %2$s and %3$s_" : ["Za tydzień dnia %1$s, następnie dnia %2$s i %3$s","Za %n tygodnie dnia %1$s, następnie dnia %2$s i %3$s","Za %n tygodni dnia %1$s, następnie dnia %2$s i %3$s","Za %n tygodni dnia %1$s, następnie dnia %2$s i %3$s"],
|
||||
"_In a month on %1$s then on %2$s and %3$s_::_In %n months on %1$s then on %2$s and %3$s_" : ["Za miesiąc dnia %1$s, następnie dnia %2$s i %3$s","Za %n miesiące dnia %1$s, następnie dnia %2$s i %3$s","Za %n miesięcy dnia %1$s, następnie dnia %2$s i %3$s","Za %n miesięcy dnia %1$s, następnie dnia %2$s i %3$s"],
|
||||
"_In a year on %1$s then on %2$s and %3$s_::_In %n years on %1$s then on %2$s and %3$s_" : ["Za rok dnia %1$s, następnie dnia %2$s i %3$s","Za %n lata dnia %1$s, następnie dnia %2$s i %3$s","Za %n lat dnia %1$s, następnie dnia %2$s i %3$s","Za %n lat dnia %1$s, następnie dnia %2$s i %3$s"],
|
||||
"Could not generate next recurrence statement" : "Nie można wygenerować następnej instrukcji powtarzania",
|
||||
"Cancelled: %1$s" : "Anulowane: %1$s",
|
||||
"\"%1$s\" has been canceled" : "\"%1$s\" zostało anulowane",
|
||||
|
||||
@@ -77,6 +77,9 @@
|
||||
"_In a week on %1$s for the entire day_::_In %n weeks on %1$s for the entire day_" : ["Za tydzień, dnia %1$s, przez cały dzień","Za %n tygodnie, dnia %1$s, przez cały dzień","Za %n tygodni, dnia %1$s, przez cały dzień","Za %n tygodni, dnia %1$s, przez cały dzień"],
|
||||
"_In a month on %1$s for the entire day_::_In %n months on %1$s for the entire day_" : ["W ciągu miesiąca, dnia %1$s przez cały dzień","W ciągu %n miesięcy, dnia %1$s przez cały dzień","W ciągu %n miesięcy, dnia %1$s przez cały dzień","W ciągu %n miesięcy, dnia %1$s przez cały dzień"],
|
||||
"_In a year on %1$s for the entire day_::_In %n years on %1$s for the entire day_" : ["Za rok dnia %1$s przez cały dzień","Za %n lata dnia %1$s przez cały dzień","Za %n lat dnia %1$s przez cały dzień","Za %n lat dnia %1$s przez cały dzień"],
|
||||
"In the past on %1$s between %2$s - %3$s" : "W przeszłości dnia %1$s między %2$s - %3$s",
|
||||
"_In a minute on %1$s between %2$s - %3$s_::_In %n minutes on %1$s between %2$s - %3$s_" : ["Za minutę dnia %1$s między %2$s - %3$s","Za %n minuty dnia %1$s między %2$s - %3$s","Za %n minut dnia %1$s między %2$s - %3$s","Za %n minut dnia %1$s między %2$s - %3$s"],
|
||||
"_In a hour on %1$s between %2$s - %3$s_::_In %n hours on %1$s between %2$s - %3$s_" : ["Za godzinę dnia %1$s między %2$s - %3$s","Za %n godziny dnia %1$s między %2$s - %3$s","Za %n godzin dnia %1$s między %2$s - %3$s","Za %n godzin dnia %1$s między %2$s - %3$s"],
|
||||
"_In a day on %1$s between %2$s - %3$s_::_In %n days on %1$s between %2$s - %3$s_" : ["Za dzień, dnia %1$s między %2$s - %3$s","Za %n dni dnia %1$s między %2$s - %3$s","Za %n dni dnia %1$s między %2$s - %3$s","Za %n dni dnia %1$s między %2$s - %3$s"],
|
||||
"_In a week on %1$s between %2$s - %3$s_::_In %n weeks on %1$s between %2$s - %3$s_" : ["Za tydzień dnia %1$s między %2$s - %3$s","Za %n tygodnie dnia %1$s między %2$s - %3$s","Za %n tygodni dnia %1$s między %2$s - %3$s","Za %n tygodni dnia %1$s między %2$s - %3$s"],
|
||||
"_In a month on %1$s between %2$s - %3$s_::_In %n months on %1$s between %2$s - %3$s_" : ["Za miesiąc dnia %1$s między %2$s - %3$s","Za %n miesięcy dnia %1$s między %2$s - %3$s","Za %n miesięcy dnia %1$s między %2$s - %3$s","Za %n miesięcy dnia %1$s między %2$s - %3$s"],
|
||||
@@ -85,18 +88,38 @@
|
||||
"Every Day for the entire day" : "Codziennie przez cały dzień",
|
||||
"Every Day for the entire day until %1$s" : "Codziennie przez cały dzień do %1$s",
|
||||
"Every Day between %1$s - %2$s" : "Codziennie między %1$s – %2$s",
|
||||
"Every Day between %1$s - %2$s until %3$s" : "Codziennie między %1$s - %2$s do %3$s",
|
||||
"Every %1$d Days for the entire day" : "Co %1$d dni przez cały dzień",
|
||||
"Every %1$d Days for the entire day until %2$s" : "Co %1$d dni przez cały dzień aż do %2$s",
|
||||
"Every %1$d Days between %2$s - %3$s" : "Co %1$d dni pomiędzy %2$s - %3$s",
|
||||
"Every %1$d Days between %2$s - %3$s until %4$s" : "Co %1$d dni, pomiędzy %2$s - %3$s aż do %4$s",
|
||||
"Could not generate event recurrence statement" : "Nie można wygenerować zestawienia powtórzeń zdarzenia",
|
||||
"Every Week on %1$s for the entire day" : "Każdego tygodnia w %1$s przez cały dzień",
|
||||
"Every Week on %1$s for the entire day until %2$s" : "Co tydzień w %1$s przez cały dzień do %2$s",
|
||||
"Every Week on %1$s between %2$s - %3$s" : "Co tydzień w %1$s między %2$s - %3$s",
|
||||
"Every Week on %1$s between %2$s - %3$s until %4$s" : "Co tydzień w %1$s między %2$s - %3$s do %4$s",
|
||||
"Every %1$d Weeks on %2$s for the entire day" : "Co %1$d tygodni w %2$s przez cały dzień",
|
||||
"Every %1$d Weeks on %2$s for the entire day until %3$s" : "Co %1$d tygodnie w %2$s przez cały dzień do %3$s",
|
||||
"Every %1$d Weeks on %2$s between %3$s - %4$s" : "Co %1$d tygodnie w %2$s między %3$s - %4$s",
|
||||
"Every %1$d Weeks on %2$s between %3$s - %4$s until %5$s" : "Co %1$d tygodnie w %2$s między %3$s - %4$s do %5$s",
|
||||
"Every Month on the %1$s for the entire day" : "Co miesiąc dnia %1$s przez cały dzień",
|
||||
"Every Month on the %1$s for the entire day until %2$s" : "Co miesiąc dnia %1$s przez cały dzień do %2$s",
|
||||
"Every Month on the %1$s between %2$s - %3$s" : "Co miesiąc dnia %1$s między %2$s - %3$s",
|
||||
"Every Month on the %1$s between %2$s - %3$s until %4$s" : "Co miesiąc dnia %1$s między %2$s - %3$s do %4$s",
|
||||
"Every %1$d Months on the %2$s for the entire day" : "Co %1$d miesiący dnia %2$s przez cały dzień",
|
||||
"Every %1$d Months on the %2$s for the entire day until %3$s" : "Co %1$d miesięcy dnia %2$s przez cały dzień do %3$s",
|
||||
"Every %1$d Months on the %2$s between %3$s - %4$s" : "Co %1$d miesięcy dnia %2$s między %3$s - %4$s",
|
||||
"Every %1$d Months on the %2$s between %3$s - %4$s until %5$s" : "Każdego %1$d miesiąca dnia %2$s między %3$s - %4$s do %5$s",
|
||||
"Every Year in %1$s on the %2$s for the entire day" : "Co rok w %1$s dnia %2$s przez cały dzień",
|
||||
"Every Year in %1$s on the %2$s for the entire day until %3$s" : "Co rok w %1$s dnia %2$s przez cały dzień do %3$s",
|
||||
"Every Year in %1$s on the %2$s between %3$s - %4$s" : "Co roku za %1$s dnia %2$s między %3$s - %4$s",
|
||||
"Every Year in %1$s on the %2$s between %3$s - %4$s until %5$s" : "Co roku za %1$s dnia %2$s między %3$s - %4$s do %5$s",
|
||||
"Every %1$d Years in %2$s on the %3$s for the entire day" : "Co %1$d lat dnia %2$s o %3$s przez cały dzień",
|
||||
"Every %1$d Years in %2$s on the %3$s for the entire day until %4$s" : "Co %1$d lat w %2$s dnia %3$s przez cały dzień do %4$s",
|
||||
"Every %1$d Years in %2$s on the %3$s between %4$s - %5$s" : "Co %1$d lat w %2$s dnia %3$s między %4$s - %5$s",
|
||||
"Every %1$d Years in %2$s on the %3$s between %4$s - %5$s until %6$s" : "Co %1$d lat w %2$s dnia %3$s między %4$s - %5$s do %6$s",
|
||||
"On specific dates for the entire day until %1$s" : "W określonych datach przez cały dzień do %1$s",
|
||||
"On specific dates between %1$s - %2$s until %3$s" : "W określonych datach między %1$s - %2$s do %3$s",
|
||||
"In the past on %1$s" : "W przeszłości dnia %1$s",
|
||||
"_In a minute on %1$s_::_In %n minutes on %1$s_" : ["Za minutę dnia %1$s","Za %n minut dnia %1$s","Za %n minut dnia %1$s","Za %n minut dnia %1$s"],
|
||||
"_In a hour on %1$s_::_In %n hours on %1$s_" : ["Za godzinę dnia %1$s","Za %n godziny dnia %1$s","Za %n godzin dnia %1$s","Za %n godzin dnia %1$s"],
|
||||
@@ -106,6 +129,18 @@
|
||||
"_In a year on %1$s_::_In %n years on %1$s_" : ["Za rok dnia %1$s","Za %n lat dnia %1$s","Za %n lat dnia %1$s","Za %n lat dnia %1$s"],
|
||||
"In the past on %1$s then on %2$s" : "W przeszłości dnia %1$s, a następnie %2$s",
|
||||
"_In a minute on %1$s then on %2$s_::_In %n minutes on %1$s then on %2$s_" : ["Za minutę dnia %1$s, a następnie %2$s","Za %n minuty dnia %1$s, a następnie %2$s","Za %n minut dnia %1$s, a następnie %2$s","Za %n minut dnia %1$s, a następnie %2$s"],
|
||||
"_In a hour on %1$s then on %2$s_::_In %n hours on %1$s then on %2$s_" : ["Za godzinę dnia %1$s, następnie dnia %2$s","Za %n godziny dnia %1$s, następnie dnia %2$s","Za %n godzin dnia %1$s, następnie dnia %2$s","Za %n godzin dnia %1$s, następnie dnia %2$s"],
|
||||
"_In a day on %1$s then on %2$s_::_In %n days on %1$s then on %2$s_" : ["Jutro dnia %1$s, następnie dnia %2$s","Za %n dni dnia %1$s, następnie dnia %2$s","Za %n dni dnia %1$s, następnie dnia %2$s","Za %n dni dnia %1$s, następnie dnia %2$s"],
|
||||
"_In a week on %1$s then on %2$s_::_In %n weeks on %1$s then on %2$s_" : ["Za tydzień dnia %1$s, następnie dnia %2$s","Za %n tygodnie dnia %1$s, następnie dnia %2$s","Za %n tygodni dnia %1$s, następnie dnia %2$s","Za %n tygodni dnia %1$s, następnie dnia %2$s"],
|
||||
"_In a month on %1$s then on %2$s_::_In %n months on %1$s then on %2$s_" : ["Za miesiąc dnia %1$s, następnie dnia %2$s","Za %n miesiące dnia %1$s, następnie dnia %2$s","Za %n miesięcy dnia %1$s, następnie dnia %2$s","Za %n miesięcy dnia %1$s, następnie dnia %2$s"],
|
||||
"_In a year on %1$s then on %2$s_::_In %n years on %1$s then on %2$s_" : ["Za rok dnia %1$s, następnie dnia %2$s","Za %n lata dnia %1$s, następnie dnia %2$s","Za %n lat dnia %1$s, następnie dnia %2$s","Za %n lat dnia %1$s, następnie dnia %2$s"],
|
||||
"In the past on %1$s then on %2$s and %3$s" : "W przeszłości dnia %1$s, następnie dnia %2$s i %3$s",
|
||||
"_In a minute on %1$s then on %2$s and %3$s_::_In %n minutes on %1$s then on %2$s and %3$s_" : ["Za minutę dnia %1$s, następnie dnia %2$s i %3$s","Za %n minuty dnia %1$s, następnie dnia %2$s i %3$s","Za %n minut dnia %1$s, następnie dnia %2$s i %3$s","Za %n minut dnia %1$s, następnie dnia %2$s i %3$s"],
|
||||
"_In a hour on %1$s then on %2$s and %3$s_::_In %n hours on %1$s then on %2$s and %3$s_" : ["Za godzinę dnia %1$s, następnie dnia %2$s i %3$s","Za %%ngodziny dnia %1$s, następnie dnia %2$s i %3$s","Za %n godzin dnia %1$s, następnie dnia %2$s i %3$s","Za %n godzin dnia %1$s, następnie dnia %2$s i %3$s"],
|
||||
"_In a day on %1$s then on %2$s and %3$s_::_In %n days on %1$s then on %2$s and %3$s_" : ["Jutro, dnia %1$s, następnie dnia %2$s i %3$s","Za %n dni dnia %1$s, następnie dnia %2$s i %3$s","Za %n dni dnia %1$s, następnie dnia %2$s i %3$s","Za %n dni dnia %1$s, następnie dnia %2$s i %3$s"],
|
||||
"_In a week on %1$s then on %2$s and %3$s_::_In %n weeks on %1$s then on %2$s and %3$s_" : ["Za tydzień dnia %1$s, następnie dnia %2$s i %3$s","Za %n tygodnie dnia %1$s, następnie dnia %2$s i %3$s","Za %n tygodni dnia %1$s, następnie dnia %2$s i %3$s","Za %n tygodni dnia %1$s, następnie dnia %2$s i %3$s"],
|
||||
"_In a month on %1$s then on %2$s and %3$s_::_In %n months on %1$s then on %2$s and %3$s_" : ["Za miesiąc dnia %1$s, następnie dnia %2$s i %3$s","Za %n miesiące dnia %1$s, następnie dnia %2$s i %3$s","Za %n miesięcy dnia %1$s, następnie dnia %2$s i %3$s","Za %n miesięcy dnia %1$s, następnie dnia %2$s i %3$s"],
|
||||
"_In a year on %1$s then on %2$s and %3$s_::_In %n years on %1$s then on %2$s and %3$s_" : ["Za rok dnia %1$s, następnie dnia %2$s i %3$s","Za %n lata dnia %1$s, następnie dnia %2$s i %3$s","Za %n lat dnia %1$s, następnie dnia %2$s i %3$s","Za %n lat dnia %1$s, następnie dnia %2$s i %3$s"],
|
||||
"Could not generate next recurrence statement" : "Nie można wygenerować następnej instrukcji powtarzania",
|
||||
"Cancelled: %1$s" : "Anulowane: %1$s",
|
||||
"\"%1$s\" has been canceled" : "\"%1$s\" zostało anulowane",
|
||||
|
||||
@@ -36,7 +36,7 @@ class Calendar extends \Sabre\CalDAV\Calendar implements IRestorable, IShareable
|
||||
|
||||
public function __construct(
|
||||
BackendInterface $caldavBackend,
|
||||
$calendarInfo,
|
||||
array $calendarInfo,
|
||||
IL10N $l10n,
|
||||
private IConfig $config,
|
||||
private LoggerInterface $logger,
|
||||
@@ -60,6 +60,10 @@ class Calendar extends \Sabre\CalDAV\Calendar implements IRestorable, IShareable
|
||||
$this->l10n = $l10n;
|
||||
}
|
||||
|
||||
public function getUri(): string {
|
||||
return $this->calendarInfo['uri'];
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
* @throws Forbidden
|
||||
|
||||
@@ -36,9 +36,14 @@ class CalendarProvider implements ICalendarProvider {
|
||||
});
|
||||
}
|
||||
|
||||
$additionalProperties = $this->getAdditionalPropertiesForCalendars($calendarInfos);
|
||||
$iCalendars = [];
|
||||
foreach ($calendarInfos as $calendarInfo) {
|
||||
$calendarInfo = array_merge($calendarInfo, $this->getAdditionalProperties($calendarInfo['principaluri'], $calendarInfo['uri']));
|
||||
$user = str_replace('principals/users/', '', $calendarInfo['principaluri']);
|
||||
$path = 'calendars/' . $user . '/' . $calendarInfo['uri'];
|
||||
|
||||
$calendarInfo = array_merge($calendarInfo, $additionalProperties[$path] ?? []);
|
||||
|
||||
$calendar = new Calendar($this->calDavBackend, $calendarInfo, $this->l10n, $this->config, $this->logger);
|
||||
$iCalendars[] = new CalendarImpl(
|
||||
$calendar,
|
||||
@@ -49,16 +54,34 @@ class CalendarProvider implements ICalendarProvider {
|
||||
return $iCalendars;
|
||||
}
|
||||
|
||||
public function getAdditionalProperties(string $principalUri, string $calendarUri): array {
|
||||
$user = str_replace('principals/users/', '', $principalUri);
|
||||
$path = 'calendars/' . $user . '/' . $calendarUri;
|
||||
/**
|
||||
* @param array{
|
||||
* principaluri: string,
|
||||
* uri: string,
|
||||
* }[] $uris
|
||||
* @return array<string, array<string, string|bool>>
|
||||
*/
|
||||
private function getAdditionalPropertiesForCalendars(array $uris): array {
|
||||
$calendars = [];
|
||||
foreach ($uris as $uri) {
|
||||
/** @var string $user */
|
||||
$user = str_replace('principals/users/', '', $uri['principaluri']);
|
||||
if (!array_key_exists($user, $calendars)) {
|
||||
$calendars[$user] = [];
|
||||
}
|
||||
$calendars[$user][] = 'calendars/' . $user . '/' . $uri['uri'];
|
||||
}
|
||||
|
||||
$properties = $this->propertyMapper->findPropertiesByPath($user, $path);
|
||||
$properties = $this->propertyMapper->findPropertiesByPathsAndUsers($calendars);
|
||||
|
||||
$list = [];
|
||||
foreach ($properties as $property) {
|
||||
if ($property instanceof Property) {
|
||||
$list[$property->getPropertyname()] = match ($property->getPropertyname()) {
|
||||
if (!isset($list[$property->getPropertypath()])) {
|
||||
$list[$property->getPropertypath()] = [];
|
||||
}
|
||||
|
||||
$list[$property->getPropertypath()][$property->getPropertyname()] = match ($property->getPropertyname()) {
|
||||
'{http://owncloud.org/ns}calendar-enabled' => (bool)$property->getPropertyvalue(),
|
||||
default => $property->getPropertyvalue()
|
||||
};
|
||||
|
||||
@@ -20,6 +20,7 @@ use OCA\DAV\Connector\Sabre\DavAclPlugin;
|
||||
use OCA\DAV\Connector\Sabre\ExceptionLoggerPlugin;
|
||||
use OCA\DAV\Connector\Sabre\LockPlugin;
|
||||
use OCA\DAV\Connector\Sabre\MaintenancePlugin;
|
||||
use OCA\DAV\Connector\Sabre\PropFindPreloadNotifyPlugin;
|
||||
use OCA\DAV\Events\SabrePluginAuthInitEvent;
|
||||
use OCA\DAV\RootCollection;
|
||||
use OCA\Theming\ThemingDefaults;
|
||||
@@ -96,6 +97,9 @@ class EmbeddedCalDavServer {
|
||||
$this->server->addPlugin(Server::get(\OCA\DAV\CalDAV\Schedule\IMipPlugin::class));
|
||||
}
|
||||
|
||||
// collection preload plugin
|
||||
$this->server->addPlugin(new PropFindPreloadNotifyPlugin());
|
||||
|
||||
// wait with registering these until auth is handled and the filesystem is setup
|
||||
$this->server->on('beforeMethod:*', function () use ($root): void {
|
||||
// register plugins from apps
|
||||
|
||||
@@ -1110,7 +1110,7 @@ class IMipService {
|
||||
$sequence = $iTipMessage->sequence;
|
||||
$recurrenceId = isset($vevent->{'RECURRENCE-ID'})
|
||||
? $vevent->{'RECURRENCE-ID'}->serialize() : null;
|
||||
$uid = $vevent->{'UID'};
|
||||
$uid = $vevent->{'UID'}?->getValue();
|
||||
|
||||
$query = $this->db->getQueryBuilder();
|
||||
$query->insert('calendar_invitations')
|
||||
|
||||
@@ -8,7 +8,6 @@
|
||||
namespace OCA\DAV\CardDAV;
|
||||
|
||||
use OCA\DAV\DAV\Sharing\IShareable;
|
||||
use OCA\DAV\Exception\UnsupportedLimitOnInitialSyncException;
|
||||
use OCP\DB\Exception;
|
||||
use OCP\IL10N;
|
||||
use OCP\Server;
|
||||
@@ -234,9 +233,6 @@ class AddressBook extends \Sabre\CardDAV\AddressBook implements IShareable, IMov
|
||||
}
|
||||
|
||||
public function getChanges($syncToken, $syncLevel, $limit = null) {
|
||||
if (!$syncToken && $limit) {
|
||||
throw new UnsupportedLimitOnInitialSyncException();
|
||||
}
|
||||
|
||||
return parent::getChanges($syncToken, $syncLevel, $limit);
|
||||
}
|
||||
|
||||
@@ -152,6 +152,10 @@ class AddressBookImpl implements IAddressBookEnabled {
|
||||
$permissions = $this->addressBook->getACL();
|
||||
$result = 0;
|
||||
foreach ($permissions as $permission) {
|
||||
if ($this->addressBookInfo['principaluri'] !== $permission['principal']) {
|
||||
continue;
|
||||
}
|
||||
|
||||
switch ($permission['privilege']) {
|
||||
case '{DAV:}read':
|
||||
$result |= Constants::PERMISSION_READ;
|
||||
|
||||
@@ -23,6 +23,7 @@ use OCP\AppFramework\Db\TTransactional;
|
||||
use OCP\DB\Exception;
|
||||
use OCP\DB\QueryBuilder\IQueryBuilder;
|
||||
use OCP\EventDispatcher\IEventDispatcher;
|
||||
use OCP\IConfig;
|
||||
use OCP\IDBConnection;
|
||||
use OCP\IUserManager;
|
||||
use PDO;
|
||||
@@ -59,6 +60,7 @@ class CardDavBackend implements BackendInterface, SyncSupport {
|
||||
private IUserManager $userManager,
|
||||
private IEventDispatcher $dispatcher,
|
||||
private Sharing\Backend $sharingBackend,
|
||||
private IConfig $config,
|
||||
) {
|
||||
}
|
||||
|
||||
@@ -851,6 +853,8 @@ class CardDavBackend implements BackendInterface, SyncSupport {
|
||||
* @return array
|
||||
*/
|
||||
public function getChangesForAddressBook($addressBookId, $syncToken, $syncLevel, $limit = null) {
|
||||
$maxLimit = $this->config->getSystemValueInt('carddav_sync_request_truncation', 2500);
|
||||
$limit = ($limit === null) ? $maxLimit : min($limit, $maxLimit);
|
||||
// Current synctoken
|
||||
return $this->atomic(function () use ($addressBookId, $syncToken, $syncLevel, $limit) {
|
||||
$qb = $this->db->getQueryBuilder();
|
||||
@@ -873,10 +877,35 @@ class CardDavBackend implements BackendInterface, SyncSupport {
|
||||
'modified' => [],
|
||||
'deleted' => [],
|
||||
];
|
||||
|
||||
if ($syncToken) {
|
||||
if (str_starts_with($syncToken, 'init_')) {
|
||||
$syncValues = explode('_', $syncToken);
|
||||
$lastID = $syncValues[1];
|
||||
$initialSyncToken = $syncValues[2];
|
||||
$qb = $this->db->getQueryBuilder();
|
||||
$qb->select('uri', 'operation')
|
||||
$qb->select('id', 'uri')
|
||||
->from('cards')
|
||||
->where(
|
||||
$qb->expr()->andX(
|
||||
$qb->expr()->eq('addressbookid', $qb->createNamedParameter($addressBookId)),
|
||||
$qb->expr()->gt('id', $qb->createNamedParameter($lastID)))
|
||||
)->orderBy('id')
|
||||
->setMaxResults($limit);
|
||||
$stmt = $qb->executeQuery();
|
||||
$values = $stmt->fetchAll(\PDO::FETCH_ASSOC);
|
||||
$stmt->closeCursor();
|
||||
if (count($values) === 0) {
|
||||
$result['syncToken'] = $initialSyncToken;
|
||||
$result['result_truncated'] = false;
|
||||
$result['added'] = [];
|
||||
} else {
|
||||
$lastID = $values[array_key_last($values)]['id'];
|
||||
$result['added'] = array_column($values, 'uri');
|
||||
$result['syncToken'] = count($result['added']) >= $limit ? "init_{$lastID}_$initialSyncToken" : $initialSyncToken ;
|
||||
$result['result_truncated'] = count($result['added']) >= $limit;
|
||||
}
|
||||
} elseif ($syncToken) {
|
||||
$qb = $this->db->getQueryBuilder();
|
||||
$qb->select('uri', 'operation', 'synctoken')
|
||||
->from('addressbookchanges')
|
||||
->where(
|
||||
$qb->expr()->andX(
|
||||
@@ -886,22 +915,31 @@ class CardDavBackend implements BackendInterface, SyncSupport {
|
||||
)
|
||||
)->orderBy('synctoken');
|
||||
|
||||
if (is_int($limit) && $limit > 0) {
|
||||
if ($limit > 0) {
|
||||
$qb->setMaxResults($limit);
|
||||
}
|
||||
|
||||
// Fetching all changes
|
||||
$stmt = $qb->executeQuery();
|
||||
$rowCount = $stmt->rowCount();
|
||||
|
||||
$changes = [];
|
||||
$highestSyncToken = 0;
|
||||
|
||||
// This loop ensures that any duplicates are overwritten, only the
|
||||
// last change on a node is relevant.
|
||||
while ($row = $stmt->fetch(\PDO::FETCH_ASSOC)) {
|
||||
$changes[$row['uri']] = $row['operation'];
|
||||
$highestSyncToken = $row['synctoken'];
|
||||
}
|
||||
|
||||
$stmt->closeCursor();
|
||||
|
||||
// No changes found, use current token
|
||||
if (empty($changes)) {
|
||||
$result['syncToken'] = $currentToken;
|
||||
}
|
||||
|
||||
foreach ($changes as $uri => $operation) {
|
||||
switch ($operation) {
|
||||
case 1:
|
||||
@@ -915,16 +953,43 @@ class CardDavBackend implements BackendInterface, SyncSupport {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* The synctoken in oc_addressbooks is always the highest synctoken in oc_addressbookchanges for a given addressbook plus one (see addChange).
|
||||
*
|
||||
* For truncated results, it is expected that we return the highest token from the response, so the client can continue from the latest change.
|
||||
*
|
||||
* For non-truncated results, it is expected to return the currentToken. If we return the highest token, as with truncated results, the client will always think it is one change behind.
|
||||
*
|
||||
* Therefore, we differentiate between truncated and non-truncated results when returning the synctoken.
|
||||
*/
|
||||
if ($rowCount === $limit && $highestSyncToken < $currentToken) {
|
||||
$result['syncToken'] = $highestSyncToken;
|
||||
$result['result_truncated'] = true;
|
||||
}
|
||||
} else {
|
||||
$qb = $this->db->getQueryBuilder();
|
||||
$qb->select('uri')
|
||||
$qb->select('id', 'uri')
|
||||
->from('cards')
|
||||
->where(
|
||||
$qb->expr()->eq('addressbookid', $qb->createNamedParameter($addressBookId))
|
||||
);
|
||||
// No synctoken supplied, this is the initial sync.
|
||||
$qb->setMaxResults($limit);
|
||||
$stmt = $qb->executeQuery();
|
||||
$result['added'] = $stmt->fetchAll(\PDO::FETCH_COLUMN);
|
||||
$values = $stmt->fetchAll(\PDO::FETCH_ASSOC);
|
||||
if (empty($values)) {
|
||||
$result['added'] = [];
|
||||
return $result;
|
||||
}
|
||||
$lastID = $values[array_key_last($values)]['id'];
|
||||
if (count($values) >= $limit) {
|
||||
$result['syncToken'] = 'init_' . $lastID . '_' . $currentToken;
|
||||
$result['result_truncated'] = true;
|
||||
}
|
||||
|
||||
$result['added'] = array_column($values, 'uri');
|
||||
|
||||
$stmt->closeCursor();
|
||||
}
|
||||
return $result;
|
||||
|
||||
@@ -22,6 +22,7 @@ use Psr\Log\LoggerInterface;
|
||||
use Sabre\DAV\Xml\Response\MultiStatus;
|
||||
use Sabre\DAV\Xml\Service;
|
||||
use Sabre\VObject\Reader;
|
||||
use Sabre\Xml\ParseException;
|
||||
use function is_null;
|
||||
|
||||
class SyncService {
|
||||
@@ -43,9 +44,10 @@ class SyncService {
|
||||
}
|
||||
|
||||
/**
|
||||
* @psalm-return list{0: ?string, 1: boolean}
|
||||
* @throws \Exception
|
||||
*/
|
||||
public function syncRemoteAddressBook(string $url, string $userName, string $addressBookUrl, string $sharedSecret, ?string $syncToken, string $targetBookHash, string $targetPrincipal, array $targetProperties): string {
|
||||
public function syncRemoteAddressBook(string $url, string $userName, string $addressBookUrl, string $sharedSecret, ?string $syncToken, string $targetBookHash, string $targetPrincipal, array $targetProperties): array {
|
||||
// 1. create addressbook
|
||||
$book = $this->ensureSystemAddressBookExists($targetPrincipal, $targetBookHash, $targetProperties);
|
||||
$addressBookId = $book['id'];
|
||||
@@ -83,7 +85,10 @@ class SyncService {
|
||||
}
|
||||
}
|
||||
|
||||
return $response['token'];
|
||||
return [
|
||||
$response['token'],
|
||||
$response['truncated'],
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -127,7 +132,7 @@ class SyncService {
|
||||
|
||||
private function prepareUri(string $host, string $path): string {
|
||||
/*
|
||||
* The trailing slash is important for merging the uris together.
|
||||
* The trailing slash is important for merging the uris.
|
||||
*
|
||||
* $host is stored in oc_trusted_servers.url and usually without a trailing slash.
|
||||
*
|
||||
@@ -158,7 +163,9 @@ class SyncService {
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array{response: array<string, array<array-key, mixed>>, token: ?string, truncated: bool}
|
||||
* @throws ClientExceptionInterface
|
||||
* @throws ParseException
|
||||
*/
|
||||
protected function requestSyncReport(string $url, string $userName, string $addressBookUrl, string $sharedSecret, ?string $syncToken): array {
|
||||
$client = $this->clientService->newClient();
|
||||
@@ -181,7 +188,7 @@ class SyncService {
|
||||
$body = $response->getBody();
|
||||
assert(is_string($body));
|
||||
|
||||
return $this->parseMultiStatus($body);
|
||||
return $this->parseMultiStatus($body, $addressBookUrl);
|
||||
}
|
||||
|
||||
protected function download(string $url, string $userName, string $sharedSecret, string $resourcePath): string {
|
||||
@@ -219,22 +226,50 @@ class SyncService {
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $body
|
||||
* @return array
|
||||
* @throws \Sabre\Xml\ParseException
|
||||
* @return array{response: array<string, array<array-key, mixed>>, token: ?string, truncated: bool}
|
||||
* @throws ParseException
|
||||
*/
|
||||
private function parseMultiStatus($body) {
|
||||
$xml = new Service();
|
||||
|
||||
private function parseMultiStatus(string $body, string $addressBookUrl): array {
|
||||
/** @var MultiStatus $multiStatus */
|
||||
$multiStatus = $xml->expect('{DAV:}multistatus', $body);
|
||||
$multiStatus = (new Service())->expect('{DAV:}multistatus', $body);
|
||||
|
||||
$result = [];
|
||||
$truncated = false;
|
||||
|
||||
foreach ($multiStatus->getResponses() as $response) {
|
||||
$result[$response->getHref()] = $response->getResponseProperties();
|
||||
$href = $response->getHref();
|
||||
if ($response->getHttpStatus() === '507' && $this->isResponseForRequestUri($href, $addressBookUrl)) {
|
||||
$truncated = true;
|
||||
} else {
|
||||
$result[$response->getHref()] = $response->getResponseProperties();
|
||||
}
|
||||
}
|
||||
|
||||
return ['response' => $result, 'token' => $multiStatus->getSyncToken()];
|
||||
return ['response' => $result, 'token' => $multiStatus->getSyncToken(), 'truncated' => $truncated];
|
||||
}
|
||||
|
||||
/**
|
||||
* Determines whether the provided response URI corresponds to the given request URI.
|
||||
*/
|
||||
private function isResponseForRequestUri(string $responseUri, string $requestUri): bool {
|
||||
/*
|
||||
* Example response uri:
|
||||
*
|
||||
* /remote.php/dav/addressbooks/system/system/system/
|
||||
* /cloud/remote.php/dav/addressbooks/system/system/system/ (when installed in a subdirectory)
|
||||
*
|
||||
* Example request uri:
|
||||
*
|
||||
* remote.php/dav/addressbooks/system/system/system
|
||||
*
|
||||
* References:
|
||||
* https://github.com/nextcloud/3rdparty/blob/e0a509739b13820f0a62ff9cad5d0fede00e76ee/sabre/dav/lib/DAV/Sync/Plugin.php#L172-L174
|
||||
* https://github.com/nextcloud/server/blob/b40acb34a39592070d8455eb91c5364c07928c50/apps/federation/lib/SyncFederationAddressBooks.php#L41
|
||||
*/
|
||||
return str_ends_with(
|
||||
rtrim($responseUri, '/'),
|
||||
rtrim($requestUri, '/')
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -8,7 +8,6 @@ declare(strict_types=1);
|
||||
*/
|
||||
namespace OCA\DAV\CardDAV;
|
||||
|
||||
use OCA\DAV\Exception\UnsupportedLimitOnInitialSyncException;
|
||||
use OCA\Federation\TrustedServers;
|
||||
use OCP\Accounts\IAccountManager;
|
||||
use OCP\IConfig;
|
||||
@@ -212,14 +211,7 @@ class SystemAddressbook extends AddressBook {
|
||||
}
|
||||
return new Card($this->carddavBackend, $this->addressBookInfo, $obj);
|
||||
}
|
||||
|
||||
/**
|
||||
* @throws UnsupportedLimitOnInitialSyncException
|
||||
*/
|
||||
public function getChanges($syncToken, $syncLevel, $limit = null) {
|
||||
if (!$syncToken && $limit) {
|
||||
throw new UnsupportedLimitOnInitialSyncException();
|
||||
}
|
||||
|
||||
if (!$this->carddavBackend instanceof SyncSupport) {
|
||||
return null;
|
||||
|
||||
@@ -49,7 +49,7 @@ class BlockLegacyClientPlugin extends ServerPlugin {
|
||||
return;
|
||||
}
|
||||
|
||||
$minimumSupportedDesktopVersion = $this->config->getSystemValueString('minimum.supported.desktop.version', '2.7.0');
|
||||
$minimumSupportedDesktopVersion = $this->config->getSystemValueString('minimum.supported.desktop.version', '3.1.0');
|
||||
$maximumSupportedDesktopVersion = $this->config->getSystemValueString('maximum.supported.desktop.version', '99.99.99');
|
||||
|
||||
// Check if the client is a desktop client
|
||||
|
||||
@@ -10,6 +10,7 @@ namespace OCA\DAV\Connector\Sabre;
|
||||
|
||||
use OCP\Comments\ICommentsManager;
|
||||
use OCP\IUserSession;
|
||||
use Sabre\DAV\ICollection;
|
||||
use Sabre\DAV\PropFind;
|
||||
use Sabre\DAV\Server;
|
||||
use Sabre\DAV\ServerPlugin;
|
||||
@@ -21,6 +22,7 @@ class CommentPropertiesPlugin extends ServerPlugin {
|
||||
|
||||
protected ?Server $server = null;
|
||||
private array $cachedUnreadCount = [];
|
||||
private array $cachedDirectories = [];
|
||||
|
||||
public function __construct(
|
||||
private ICommentsManager $commentsManager,
|
||||
@@ -41,6 +43,8 @@ class CommentPropertiesPlugin extends ServerPlugin {
|
||||
*/
|
||||
public function initialize(\Sabre\DAV\Server $server) {
|
||||
$this->server = $server;
|
||||
|
||||
$this->server->on('preloadCollection', $this->preloadCollection(...));
|
||||
$this->server->on('propFind', [$this, 'handleGetProperties']);
|
||||
}
|
||||
|
||||
@@ -69,6 +73,21 @@ class CommentPropertiesPlugin extends ServerPlugin {
|
||||
}
|
||||
}
|
||||
|
||||
private function preloadCollection(PropFind $propFind, ICollection $collection):
|
||||
void {
|
||||
if (!($collection instanceof Directory)) {
|
||||
return;
|
||||
}
|
||||
|
||||
$collectionPath = $collection->getPath();
|
||||
if (!isset($this->cachedDirectories[$collectionPath]) && $propFind->getStatus(
|
||||
self::PROPERTY_NAME_UNREAD
|
||||
) !== null) {
|
||||
$this->cacheDirectory($collection);
|
||||
$this->cachedDirectories[$collectionPath] = true;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds tags and favorites properties to the response,
|
||||
* if requested.
|
||||
@@ -85,14 +104,6 @@ class CommentPropertiesPlugin extends ServerPlugin {
|
||||
return;
|
||||
}
|
||||
|
||||
// need prefetch ?
|
||||
if ($node instanceof Directory
|
||||
&& $propFind->getDepth() !== 0
|
||||
&& !is_null($propFind->getStatus(self::PROPERTY_NAME_UNREAD))
|
||||
) {
|
||||
$this->cacheDirectory($node);
|
||||
}
|
||||
|
||||
$propFind->handle(self::PROPERTY_NAME_COUNT, function () use ($node): int {
|
||||
return $this->commentsManager->getNumberOfCommentsForObject('files', (string)$node->getId());
|
||||
});
|
||||
|
||||
@@ -204,6 +204,9 @@ class File extends Node implements IFile {
|
||||
}
|
||||
}
|
||||
|
||||
$lengthHeader = $this->request->getHeader('content-length');
|
||||
$expected = $lengthHeader !== '' ? (int)$lengthHeader : null;
|
||||
|
||||
if ($partStorage->instanceOfStorage(IWriteStreamStorage::class)) {
|
||||
$isEOF = false;
|
||||
$wrappedData = CallbackWrapper::wrap($data, null, null, null, null, function ($stream) use (&$isEOF): void {
|
||||
@@ -215,7 +218,7 @@ class File extends Node implements IFile {
|
||||
$count = -1;
|
||||
try {
|
||||
/** @var IWriteStreamStorage $partStorage */
|
||||
$count = $partStorage->writeStream($internalPartPath, $wrappedData);
|
||||
$count = $partStorage->writeStream($internalPartPath, $wrappedData, $expected);
|
||||
} catch (GenericFileException $e) {
|
||||
$logger = Server::get(LoggerInterface::class);
|
||||
$logger->error('Error while writing stream to storage: ' . $e->getMessage(), ['exception' => $e, 'app' => 'webdav']);
|
||||
@@ -235,10 +238,7 @@ class File extends Node implements IFile {
|
||||
[$count, $result] = Files::streamCopy($data, $target, true);
|
||||
fclose($target);
|
||||
}
|
||||
|
||||
$lengthHeader = $this->request->getHeader('content-length');
|
||||
$expected = $lengthHeader !== '' ? (int)$lengthHeader : -1;
|
||||
if ($result === false && $expected >= 0) {
|
||||
if ($result === false && $expected !== null) {
|
||||
throw new Exception(
|
||||
$this->l10n->t(
|
||||
'Error while copying file to target location (copied: %1$s, expected filesize: %2$s)',
|
||||
@@ -253,7 +253,7 @@ class File extends Node implements IFile {
|
||||
// if content length is sent by client:
|
||||
// double check if the file was fully received
|
||||
// compare expected and actual size
|
||||
if ($expected >= 0
|
||||
if ($expected !== null
|
||||
&& $expected !== $count
|
||||
&& $this->request->getMethod() === 'PUT'
|
||||
) {
|
||||
|
||||
@@ -0,0 +1,82 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types = 1);
|
||||
|
||||
/**
|
||||
* SPDX-FileCopyrightText: 2025 Nextcloud GmbH and Nextcloud contributors
|
||||
* SPDX-License-Identifier: AGPL-3.0-or-later
|
||||
*/
|
||||
namespace OCA\DAV\Connector\Sabre;
|
||||
|
||||
use Sabre\DAV\Server as SabreServer;
|
||||
use Sabre\DAV\ServerPlugin;
|
||||
use Sabre\HTTP\RequestInterface;
|
||||
use Sabre\HTTP\ResponseInterface;
|
||||
|
||||
/**
|
||||
* This plugin runs after requests and logs an error if a plugin is detected
|
||||
* to be doing too many SQL requests.
|
||||
*/
|
||||
class PropFindMonitorPlugin extends ServerPlugin {
|
||||
|
||||
/**
|
||||
* A Plugin can scan up to this amount of nodes without an error being
|
||||
* reported.
|
||||
*/
|
||||
public const THRESHOLD_NODES = 50;
|
||||
|
||||
/**
|
||||
* A plugin can use up to this amount of queries per node.
|
||||
*/
|
||||
public const THRESHOLD_QUERY_FACTOR = 1;
|
||||
|
||||
private SabreServer $server;
|
||||
|
||||
public function initialize(SabreServer $server): void {
|
||||
$this->server = $server;
|
||||
$this->server->on('afterResponse', [$this, 'afterResponse']);
|
||||
}
|
||||
|
||||
public function afterResponse(
|
||||
RequestInterface $request,
|
||||
ResponseInterface $response): void {
|
||||
if (!$this->server instanceof Server) {
|
||||
return;
|
||||
}
|
||||
|
||||
$pluginQueries = $this->server->getPluginQueries();
|
||||
if (empty($pluginQueries)) {
|
||||
return;
|
||||
}
|
||||
|
||||
$logger = $this->server->getLogger();
|
||||
foreach ($pluginQueries as $eventName => $eventQueries) {
|
||||
$maxDepth = max(0, ...array_keys($eventQueries));
|
||||
// entries at the top are usually not interesting
|
||||
unset($eventQueries[$maxDepth]);
|
||||
foreach ($eventQueries as $depth => $propFinds) {
|
||||
foreach ($propFinds as $pluginName => $propFind) {
|
||||
[
|
||||
'queries' => $queries,
|
||||
'nodes' => $nodes
|
||||
] = $propFind;
|
||||
if ($queries === 0 || $nodes > $queries || $nodes < self::THRESHOLD_NODES
|
||||
|| $queries < $nodes * self::THRESHOLD_QUERY_FACTOR) {
|
||||
continue;
|
||||
}
|
||||
$logger->error(
|
||||
'{name}:{event} scanned {scans} nodes with {count} queries in depth {depth}/{maxDepth}. This is bad for performance, please report to the plugin developer!',
|
||||
[
|
||||
'name' => $pluginName,
|
||||
'scans' => $nodes,
|
||||
'count' => $queries,
|
||||
'depth' => $depth,
|
||||
'maxDepth' => $maxDepth,
|
||||
'event' => $eventName,
|
||||
]
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,55 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types = 1);
|
||||
|
||||
/**
|
||||
* SPDX-FileCopyrightText: 2025 Nextcloud GmbH and Nextcloud contributors
|
||||
* SPDX-License-Identifier: AGPL-3.0-or-later
|
||||
*/
|
||||
namespace OCA\DAV\Connector\Sabre;
|
||||
|
||||
use Sabre\DAV\ICollection;
|
||||
use Sabre\DAV\INode;
|
||||
use Sabre\DAV\PropFind;
|
||||
use Sabre\DAV\Server;
|
||||
use Sabre\DAV\ServerPlugin;
|
||||
|
||||
/**
|
||||
* This plugin asks other plugins to preload data for a collection, so that
|
||||
* subsequent PROPFIND handlers for children do not query the DB on a per-node
|
||||
* basis.
|
||||
*/
|
||||
class PropFindPreloadNotifyPlugin extends ServerPlugin {
|
||||
|
||||
private Server $server;
|
||||
|
||||
public function initialize(Server $server): void {
|
||||
$this->server = $server;
|
||||
$this->server->on('propFind', [$this, 'collectionPreloadNotifier' ], 1);
|
||||
}
|
||||
|
||||
/**
|
||||
* Uses the server instance to emit a `preloadCollection` event to signal
|
||||
* to interested plugins that a collection can be preloaded.
|
||||
*
|
||||
* NOTE: this can be emitted several times, so ideally every plugin
|
||||
* should cache what they need and check if a cache exists before
|
||||
* re-fetching.
|
||||
*/
|
||||
public function collectionPreloadNotifier(PropFind $propFind, INode $node): bool {
|
||||
if (!$this->shouldPreload($propFind, $node)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return $this->server->emit('preloadCollection', [$propFind, $node]);
|
||||
}
|
||||
|
||||
private function shouldPreload(
|
||||
PropFind $propFind,
|
||||
INode $node,
|
||||
): bool {
|
||||
$depth = $propFind->getDepth();
|
||||
return $node instanceof ICollection
|
||||
&& ($depth === Server::DEPTH_INFINITY || $depth > 0);
|
||||
}
|
||||
}
|
||||
@@ -7,7 +7,11 @@
|
||||
*/
|
||||
namespace OCA\DAV\Connector\Sabre;
|
||||
|
||||
use OC\DB\Connection;
|
||||
use Override;
|
||||
use Sabre\DAV\Exception;
|
||||
use Sabre\DAV\INode;
|
||||
use Sabre\DAV\PropFind;
|
||||
use Sabre\DAV\Version;
|
||||
use TypeError;
|
||||
|
||||
@@ -21,6 +25,15 @@ use TypeError;
|
||||
class Server extends \Sabre\DAV\Server {
|
||||
/** @var CachingTree $tree */
|
||||
|
||||
/**
|
||||
* Tracks queries done by plugins.
|
||||
* @var array<string, array<int, array<string, array{nodes:int,
|
||||
* queries:int}>>> The keys represent: event name, depth and plugin name
|
||||
*/
|
||||
private array $pluginQueries = [];
|
||||
|
||||
public bool $debugEnabled = false;
|
||||
|
||||
/**
|
||||
* @see \Sabre\DAV\Server
|
||||
*/
|
||||
@@ -30,6 +43,106 @@ class Server extends \Sabre\DAV\Server {
|
||||
$this->enablePropfindDepthInfinity = true;
|
||||
}
|
||||
|
||||
#[Override]
|
||||
public function once(
|
||||
string $eventName,
|
||||
callable $callBack,
|
||||
int $priority = 100,
|
||||
): void {
|
||||
$this->debugEnabled ? $this->monitorPropfindQueries(
|
||||
parent::once(...),
|
||||
...\func_get_args(),
|
||||
) : parent::once(...\func_get_args());
|
||||
}
|
||||
|
||||
#[Override]
|
||||
public function on(
|
||||
string $eventName,
|
||||
callable $callBack,
|
||||
int $priority = 100,
|
||||
): void {
|
||||
$this->debugEnabled ? $this->monitorPropfindQueries(
|
||||
parent::on(...),
|
||||
...\func_get_args(),
|
||||
) : parent::on(...\func_get_args());
|
||||
}
|
||||
|
||||
/**
|
||||
* Wraps the handler $callBack into a query-monitoring function and calls
|
||||
* $parentFn to register it.
|
||||
*/
|
||||
private function monitorPropfindQueries(
|
||||
callable $parentFn,
|
||||
string $eventName,
|
||||
callable $callBack,
|
||||
int $priority = 100,
|
||||
): void {
|
||||
$pluginName = debug_backtrace(DEBUG_BACKTRACE_IGNORE_ARGS, 3)[2]['class'] ?? 'unknown';
|
||||
// The NotifyPlugin needs to be excluded as it emits the
|
||||
// `preloadCollection` event, which causes many plugins run queries.
|
||||
/** @psalm-suppress TypeDoesNotContainType */
|
||||
if ($pluginName === PropFindPreloadNotifyPlugin::class || ($eventName !== 'propFind'
|
||||
&& $eventName !== 'preloadCollection')) {
|
||||
$parentFn($eventName, $callBack, $priority);
|
||||
return;
|
||||
}
|
||||
|
||||
$callback = $this->getMonitoredCallback($callBack, $pluginName, $eventName);
|
||||
|
||||
$parentFn($eventName, $callback, $priority);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a callable that wraps $callBack with code that monitors and
|
||||
* records queries per plugin.
|
||||
*/
|
||||
private function getMonitoredCallback(
|
||||
callable $callBack,
|
||||
string $pluginName,
|
||||
string $eventName,
|
||||
): callable {
|
||||
return function (PropFind $propFind, INode $node) use (
|
||||
$callBack,
|
||||
$pluginName,
|
||||
$eventName,
|
||||
): bool {
|
||||
$connection = \OCP\Server::get(Connection::class);
|
||||
$queriesBefore = $connection->getStats()['executed'];
|
||||
$result = $callBack($propFind, $node);
|
||||
$queriesAfter = $connection->getStats()['executed'];
|
||||
$this->trackPluginQueries(
|
||||
$pluginName,
|
||||
$eventName,
|
||||
$queriesAfter - $queriesBefore,
|
||||
$propFind->getDepth()
|
||||
);
|
||||
|
||||
// many callbacks don't care about returning a bool
|
||||
return $result ?? true;
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Tracks the queries executed by a specific plugin.
|
||||
*/
|
||||
private function trackPluginQueries(
|
||||
string $pluginName,
|
||||
string $eventName,
|
||||
int $queriesExecuted,
|
||||
int $depth,
|
||||
): void {
|
||||
// report only nodes which cause queries to the DB
|
||||
if ($queriesExecuted === 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
$this->pluginQueries[$eventName][$depth][$pluginName]['nodes']
|
||||
= ($this->pluginQueries[$eventName][$depth][$pluginName]['nodes'] ?? 0) + 1;
|
||||
|
||||
$this->pluginQueries[$eventName][$depth][$pluginName]['queries']
|
||||
= ($this->pluginQueries[$eventName][$depth][$pluginName]['queries'] ?? 0) + $queriesExecuted;
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @return void
|
||||
@@ -115,4 +228,13 @@ class Server extends \Sabre\DAV\Server {
|
||||
$this->sapi->sendResponse($this->httpResponse);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns queries executed by registered plugins.
|
||||
* @return array<string, array<int, array<string, array{nodes:int,
|
||||
* queries:int}>>> The keys represent: event name, depth and plugin name
|
||||
*/
|
||||
public function getPluginQueries(): array {
|
||||
return $this->pluginQueries;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -14,6 +14,7 @@ use OCA\DAV\CalDAV\DefaultCalendarValidator;
|
||||
use OCA\DAV\CalDAV\Proxy\ProxyMapper;
|
||||
use OCA\DAV\DAV\CustomPropertiesBackend;
|
||||
use OCA\DAV\DAV\ViewOnlyPlugin;
|
||||
use OCA\DAV\Db\PropertyMapper;
|
||||
use OCA\DAV\Files\BrowserErrorPagePlugin;
|
||||
use OCA\DAV\Files\Sharing\RootCollection;
|
||||
use OCA\DAV\Upload\CleanupService;
|
||||
@@ -68,6 +69,7 @@ class ServerFactory {
|
||||
Plugin $authPlugin,
|
||||
callable $viewCallBack,
|
||||
): Server {
|
||||
$debugEnabled = $this->config->getSystemValue('debug', false);
|
||||
// Fire up server
|
||||
if ($isPublicShare) {
|
||||
$rootCollection = new SimpleCollection('root');
|
||||
@@ -89,6 +91,12 @@ class ServerFactory {
|
||||
));
|
||||
$server->addPlugin(new AnonymousOptionsPlugin());
|
||||
$server->addPlugin($authPlugin);
|
||||
if ($debugEnabled) {
|
||||
$server->debugEnabled = $debugEnabled;
|
||||
$server->addPlugin(new PropFindMonitorPlugin());
|
||||
}
|
||||
|
||||
$server->addPlugin(new PropFindPreloadNotifyPlugin());
|
||||
// FIXME: The following line is a workaround for legacy components relying on being able to send a GET to /
|
||||
$server->addPlugin(new DummyGetResponsePlugin());
|
||||
$server->addPlugin(new ExceptionLoggerPlugin('webdav', $this->logger));
|
||||
@@ -117,7 +125,8 @@ class ServerFactory {
|
||||
}
|
||||
|
||||
// wait with registering these until auth is handled and the filesystem is setup
|
||||
$server->on('beforeMethod:*', function () use ($server, $tree, $viewCallBack, $isPublicShare, $rootCollection): void {
|
||||
$server->on('beforeMethod:*', function () use ($server, $tree,
|
||||
$viewCallBack, $isPublicShare, $rootCollection, $debugEnabled): void {
|
||||
// ensure the skeleton is copied
|
||||
$userFolder = \OC::$server->getUserFolder();
|
||||
|
||||
@@ -181,7 +190,7 @@ class ServerFactory {
|
||||
\OCP\Server::get(IFilenameValidator::class),
|
||||
\OCP\Server::get(IAccountManager::class),
|
||||
false,
|
||||
!$this->config->getSystemValue('debug', false)
|
||||
!$debugEnabled
|
||||
)
|
||||
);
|
||||
$server->addPlugin(new QuotaPlugin($view));
|
||||
@@ -220,6 +229,7 @@ class ServerFactory {
|
||||
$tree,
|
||||
$this->databaseConnection,
|
||||
$this->userSession->getUser(),
|
||||
\OCP\Server::get(PropertyMapper::class),
|
||||
\OCP\Server::get(DefaultCalendarValidator::class),
|
||||
)
|
||||
)
|
||||
|
||||
@@ -15,6 +15,7 @@ use OCP\Files\NotFoundException;
|
||||
use OCP\IUserSession;
|
||||
use OCP\Share\IManager;
|
||||
use OCP\Share\IShare;
|
||||
use Sabre\DAV\ICollection;
|
||||
use Sabre\DAV\PropFind;
|
||||
use Sabre\DAV\Server;
|
||||
use Sabre\DAV\Tree;
|
||||
@@ -38,7 +39,14 @@ class SharesPlugin extends \Sabre\DAV\ServerPlugin {
|
||||
|
||||
/** @var IShare[][] */
|
||||
private array $cachedShares = [];
|
||||
/** @var string[] */
|
||||
|
||||
/**
|
||||
* Tracks which folders have been cached.
|
||||
* When a folder is cached, it will appear with its path as key and true
|
||||
* as value.
|
||||
*
|
||||
* @var bool[]
|
||||
*/
|
||||
private array $cachedFolders = [];
|
||||
|
||||
public function __construct(
|
||||
@@ -67,6 +75,7 @@ class SharesPlugin extends \Sabre\DAV\ServerPlugin {
|
||||
$server->protectedProperties[] = self::SHAREES_PROPERTYNAME;
|
||||
|
||||
$this->server = $server;
|
||||
$this->server->on('preloadCollection', $this->preloadCollection(...));
|
||||
$this->server->on('propFind', [$this, 'handleGetProperties']);
|
||||
}
|
||||
|
||||
@@ -89,28 +98,28 @@ class SharesPlugin extends \Sabre\DAV\ServerPlugin {
|
||||
];
|
||||
|
||||
foreach ($requestedShareTypes as $requestedShareType) {
|
||||
$result = array_merge($result, $this->shareManager->getSharesBy(
|
||||
$result[] = $this->shareManager->getSharesBy(
|
||||
$this->userId,
|
||||
$requestedShareType,
|
||||
$node,
|
||||
false,
|
||||
-1
|
||||
));
|
||||
);
|
||||
|
||||
// Also check for shares where the user is the recipient
|
||||
try {
|
||||
$result = array_merge($result, $this->shareManager->getSharedWith(
|
||||
$result[] = $this->shareManager->getSharedWith(
|
||||
$this->userId,
|
||||
$requestedShareType,
|
||||
$node,
|
||||
-1
|
||||
));
|
||||
);
|
||||
} catch (BackendError $e) {
|
||||
// ignore
|
||||
}
|
||||
}
|
||||
|
||||
return $result;
|
||||
return array_merge(...$result);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -141,7 +150,7 @@ class SharesPlugin extends \Sabre\DAV\ServerPlugin {
|
||||
|
||||
// if we already cached the folder containing this file
|
||||
// then we already know there are no shares here.
|
||||
if (array_search($parentPath, $this->cachedFolders) === false) {
|
||||
if (!isset($this->cachedFolders[$parentPath])) {
|
||||
try {
|
||||
$node = $sabreNode->getNode();
|
||||
} catch (NotFoundException $e) {
|
||||
@@ -156,6 +165,27 @@ class SharesPlugin extends \Sabre\DAV\ServerPlugin {
|
||||
return [];
|
||||
}
|
||||
|
||||
private function preloadCollection(PropFind $propFind, ICollection $collection): void {
|
||||
if (!$collection instanceof Directory
|
||||
|| isset($this->cachedFolders[$collection->getPath()])
|
||||
|| (
|
||||
$propFind->getStatus(self::SHARETYPES_PROPERTYNAME) === null
|
||||
&& $propFind->getStatus(self::SHAREES_PROPERTYNAME) === null
|
||||
)
|
||||
) {
|
||||
return;
|
||||
}
|
||||
|
||||
// If the node is a directory and we are requesting share types or sharees
|
||||
// then we get all the shares in the folder and cache them.
|
||||
// This is more performant than iterating each files afterwards.
|
||||
$folderNode = $collection->getNode();
|
||||
$this->cachedFolders[$collection->getPath()] = true;
|
||||
foreach ($this->getSharesFolder($folderNode) as $id => $shares) {
|
||||
$this->cachedShares[$id] = $shares;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds shares to propfind response
|
||||
*
|
||||
@@ -170,24 +200,6 @@ class SharesPlugin extends \Sabre\DAV\ServerPlugin {
|
||||
return;
|
||||
}
|
||||
|
||||
// If the node is a directory and we are requesting share types or sharees
|
||||
// then we get all the shares in the folder and cache them.
|
||||
// This is more performant than iterating each files afterwards.
|
||||
if ($sabreNode instanceof Directory
|
||||
&& $propFind->getDepth() !== 0
|
||||
&& (
|
||||
!is_null($propFind->getStatus(self::SHARETYPES_PROPERTYNAME))
|
||||
|| !is_null($propFind->getStatus(self::SHAREES_PROPERTYNAME))
|
||||
)
|
||||
) {
|
||||
$folderNode = $sabreNode->getNode();
|
||||
$this->cachedFolders[] = $sabreNode->getPath();
|
||||
$childShares = $this->getSharesFolder($folderNode);
|
||||
foreach ($childShares as $id => $shares) {
|
||||
$this->cachedShares[$id] = $shares;
|
||||
}
|
||||
}
|
||||
|
||||
$propFind->handle(self::SHARETYPES_PROPERTYNAME, function () use ($sabreNode): ShareTypeList {
|
||||
$shares = $this->getShares($sabreNode);
|
||||
|
||||
|
||||
@@ -31,6 +31,7 @@ use OCP\EventDispatcher\IEventDispatcher;
|
||||
use OCP\ITagManager;
|
||||
use OCP\ITags;
|
||||
use OCP\IUserSession;
|
||||
use Sabre\DAV\ICollection;
|
||||
use Sabre\DAV\PropFind;
|
||||
use Sabre\DAV\PropPatch;
|
||||
|
||||
@@ -61,6 +62,7 @@ class TagsPlugin extends \Sabre\DAV\ServerPlugin {
|
||||
* @var array
|
||||
*/
|
||||
private $cachedTags;
|
||||
private array $cachedDirectories;
|
||||
|
||||
/**
|
||||
* @param \Sabre\DAV\Tree $tree tree
|
||||
@@ -92,6 +94,7 @@ class TagsPlugin extends \Sabre\DAV\ServerPlugin {
|
||||
$server->xml->elementMap[self::TAGS_PROPERTYNAME] = TagList::class;
|
||||
|
||||
$this->server = $server;
|
||||
$this->server->on('preloadCollection', $this->preloadCollection(...));
|
||||
$this->server->on('propFind', [$this, 'handleGetProperties']);
|
||||
$this->server->on('propPatch', [$this, 'handleUpdateProperties']);
|
||||
$this->server->on('preloadProperties', [$this, 'handlePreloadProperties']);
|
||||
@@ -194,6 +197,29 @@ class TagsPlugin extends \Sabre\DAV\ServerPlugin {
|
||||
}
|
||||
}
|
||||
|
||||
private function preloadCollection(PropFind $propFind, ICollection $collection):
|
||||
void {
|
||||
if (!($collection instanceof Node)) {
|
||||
return;
|
||||
}
|
||||
|
||||
// need prefetch ?
|
||||
if ($collection instanceof Directory
|
||||
&& !isset($this->cachedDirectories[$collection->getPath()])
|
||||
&& (!is_null($propFind->getStatus(self::TAGS_PROPERTYNAME))
|
||||
|| !is_null($propFind->getStatus(self::FAVORITE_PROPERTYNAME))
|
||||
)) {
|
||||
// note: pre-fetching only supported for depth <= 1
|
||||
$folderContent = $collection->getChildren();
|
||||
$fileIds = [(int)$collection->getId()];
|
||||
foreach ($folderContent as $info) {
|
||||
$fileIds[] = (int)$info->getId();
|
||||
}
|
||||
$this->prefetchTagsForFileIds($fileIds);
|
||||
$this->cachedDirectories[$collection->getPath()] = true;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds tags and favorites properties to the response,
|
||||
* if requested.
|
||||
@@ -210,21 +236,6 @@ class TagsPlugin extends \Sabre\DAV\ServerPlugin {
|
||||
return;
|
||||
}
|
||||
|
||||
// need prefetch ?
|
||||
if ($node instanceof Directory
|
||||
&& $propFind->getDepth() !== 0
|
||||
&& (!is_null($propFind->getStatus(self::TAGS_PROPERTYNAME))
|
||||
|| !is_null($propFind->getStatus(self::FAVORITE_PROPERTYNAME))
|
||||
)) {
|
||||
// note: pre-fetching only supported for depth <= 1
|
||||
$folderContent = $node->getChildren();
|
||||
$fileIds = [(int)$node->getId()];
|
||||
foreach ($folderContent as $info) {
|
||||
$fileIds[] = (int)$info->getId();
|
||||
}
|
||||
$this->prefetchTagsForFileIds($fileIds);
|
||||
}
|
||||
|
||||
$isFav = null;
|
||||
|
||||
$propFind->handle(self::TAGS_PROPERTYNAME, function () use (&$isFav, $node) {
|
||||
|
||||
@@ -67,15 +67,16 @@ class ZipFolderPlugin extends ServerPlugin {
|
||||
// Remove the root path from the filename to make it relative to the requested folder
|
||||
$filename = str_replace($rootPath, '', $node->getPath());
|
||||
|
||||
$mtime = $node->getMTime();
|
||||
if ($node instanceof NcFile) {
|
||||
$resource = $node->fopen('rb');
|
||||
if ($resource === false) {
|
||||
$this->logger->info('Cannot read file for zip stream', ['filePath' => $node->getPath()]);
|
||||
throw new \Sabre\DAV\Exception\ServiceUnavailable('Requested file can currently not be accessed.');
|
||||
}
|
||||
$streamer->addFileFromStream($resource, $filename, $node->getSize(), $node->getMTime());
|
||||
$streamer->addFileFromStream($resource, $filename, $node->getSize(), $mtime);
|
||||
} elseif ($node instanceof NcFolder) {
|
||||
$streamer->addEmptyDir($filename);
|
||||
$streamer->addEmptyDir($filename, $mtime);
|
||||
$content = $node->getDirectoryListing();
|
||||
foreach ($content as $subNode) {
|
||||
$this->streamNode($streamer, $subNode, $rootPath);
|
||||
|
||||
@@ -9,13 +9,20 @@
|
||||
namespace OCA\DAV\DAV;
|
||||
|
||||
use Exception;
|
||||
use OCA\DAV\CalDAV\CalDavBackend;
|
||||
use OCA\DAV\CalDAV\Calendar;
|
||||
use OCA\DAV\CalDAV\CalendarHome;
|
||||
use OCA\DAV\CalDAV\CalendarObject;
|
||||
use OCA\DAV\CalDAV\DefaultCalendarValidator;
|
||||
use OCA\DAV\CalDAV\Integration\ExternalCalendar;
|
||||
use OCA\DAV\CalDAV\Outbox;
|
||||
use OCA\DAV\CalDAV\Trashbin\TrashbinHome;
|
||||
use OCA\DAV\Connector\Sabre\Directory;
|
||||
use OCA\DAV\Connector\Sabre\FilesPlugin;
|
||||
use OCA\DAV\Db\PropertyMapper;
|
||||
use OCP\DB\QueryBuilder\IQueryBuilder;
|
||||
use OCP\IDBConnection;
|
||||
use OCP\IUser;
|
||||
use Sabre\CalDAV\Schedule\Inbox;
|
||||
use Sabre\DAV\Exception as DavException;
|
||||
use Sabre\DAV\PropertyStorage\Backend\BackendInterface;
|
||||
use Sabre\DAV\PropFind;
|
||||
@@ -66,38 +73,16 @@ class CustomPropertiesBackend implements BackendInterface {
|
||||
'{DAV:}getetag',
|
||||
'{DAV:}quota-used-bytes',
|
||||
'{DAV:}quota-available-bytes',
|
||||
'{http://owncloud.org/ns}permissions',
|
||||
'{http://owncloud.org/ns}downloadURL',
|
||||
'{http://owncloud.org/ns}dDC',
|
||||
'{http://owncloud.org/ns}size',
|
||||
'{http://nextcloud.org/ns}is-encrypted',
|
||||
];
|
||||
|
||||
// Currently, returning null from any propfind handler would still trigger the backend,
|
||||
// so we add all known Nextcloud custom properties in here to avoid that
|
||||
|
||||
// text app
|
||||
'{http://nextcloud.org/ns}rich-workspace',
|
||||
'{http://nextcloud.org/ns}rich-workspace-file',
|
||||
// groupfolders
|
||||
'{http://nextcloud.org/ns}acl-enabled',
|
||||
'{http://nextcloud.org/ns}acl-can-manage',
|
||||
'{http://nextcloud.org/ns}acl-list',
|
||||
'{http://nextcloud.org/ns}inherited-acl-list',
|
||||
'{http://nextcloud.org/ns}group-folder-id',
|
||||
// files_lock
|
||||
'{http://nextcloud.org/ns}lock',
|
||||
'{http://nextcloud.org/ns}lock-owner-type',
|
||||
'{http://nextcloud.org/ns}lock-owner',
|
||||
'{http://nextcloud.org/ns}lock-owner-displayname',
|
||||
'{http://nextcloud.org/ns}lock-owner-editor',
|
||||
'{http://nextcloud.org/ns}lock-time',
|
||||
'{http://nextcloud.org/ns}lock-timeout',
|
||||
'{http://nextcloud.org/ns}lock-token',
|
||||
// photos
|
||||
'{http://nextcloud.org/ns}realpath',
|
||||
'{http://nextcloud.org/ns}nbItems',
|
||||
'{http://nextcloud.org/ns}face-detections',
|
||||
'{http://nextcloud.org/ns}face-preview-image',
|
||||
/**
|
||||
* Allowed properties for the oc/nc namespace, all other properties in the namespace are ignored
|
||||
*
|
||||
* @var string[]
|
||||
*/
|
||||
private const ALLOWED_NC_PROPERTIES = [
|
||||
'{http://owncloud.org/ns}calendar-enabled',
|
||||
'{http://owncloud.org/ns}enabled',
|
||||
];
|
||||
|
||||
/**
|
||||
@@ -119,11 +104,17 @@ class CustomPropertiesBackend implements BackendInterface {
|
||||
];
|
||||
|
||||
/**
|
||||
* Properties cache
|
||||
*
|
||||
* @var array
|
||||
* Map of well-known property names to default values
|
||||
*/
|
||||
private $userCache = [];
|
||||
private const PROPERTY_DEFAULT_VALUES = [
|
||||
'{http://owncloud.org/ns}calendar-enabled' => '1',
|
||||
];
|
||||
|
||||
/**
|
||||
* Properties cache
|
||||
*/
|
||||
private array $userCache = [];
|
||||
private array $publishedCache = [];
|
||||
private XmlService $xmlService;
|
||||
|
||||
/**
|
||||
@@ -136,6 +127,7 @@ class CustomPropertiesBackend implements BackendInterface {
|
||||
private Tree $tree,
|
||||
private IDBConnection $connection,
|
||||
private IUser $user,
|
||||
private PropertyMapper $propertyMapper,
|
||||
private DefaultCalendarValidator $defaultCalendarValidator,
|
||||
) {
|
||||
$this->xmlService = new XmlService();
|
||||
@@ -155,14 +147,9 @@ class CustomPropertiesBackend implements BackendInterface {
|
||||
public function propFind($path, PropFind $propFind) {
|
||||
$requestedProps = $propFind->get404Properties();
|
||||
|
||||
// these might appear
|
||||
$requestedProps = array_diff(
|
||||
$requestedProps,
|
||||
self::IGNORED_PROPERTIES,
|
||||
);
|
||||
$requestedProps = array_filter(
|
||||
$requestedProps,
|
||||
fn ($prop) => !str_starts_with($prop, FilesPlugin::FILE_METADATA_PREFIX),
|
||||
$this->isPropertyAllowed(...),
|
||||
);
|
||||
|
||||
// substr of calendars/ => path is inside the CalDAV component
|
||||
@@ -224,6 +211,18 @@ class CustomPropertiesBackend implements BackendInterface {
|
||||
$this->cacheDirectory($path, $node);
|
||||
}
|
||||
|
||||
if ($node instanceof CalendarHome && $propFind->getDepth() !== 0) {
|
||||
$backend = $node->getCalDAVBackend();
|
||||
if ($backend instanceof CalDavBackend) {
|
||||
$this->cacheCalendars($node, $requestedProps);
|
||||
}
|
||||
}
|
||||
|
||||
if ($node instanceof CalendarObject) {
|
||||
// No custom properties supported on individual events
|
||||
return;
|
||||
}
|
||||
|
||||
// First fetch the published properties (set by another user), then get the ones set by
|
||||
// the current user. If both are set then the latter as priority.
|
||||
foreach ($this->getPublishedProperties($path, $requestedProps) as $propName => $propValue) {
|
||||
@@ -244,6 +243,16 @@ class CustomPropertiesBackend implements BackendInterface {
|
||||
}
|
||||
}
|
||||
|
||||
private function isPropertyAllowed(string $property): bool {
|
||||
if (in_array($property, self::IGNORED_PROPERTIES)) {
|
||||
return false;
|
||||
}
|
||||
if (str_starts_with($property, '{http://owncloud.org/ns}') || str_starts_with($property, '{http://nextcloud.org/ns}')) {
|
||||
return in_array($property, self::ALLOWED_NC_PROPERTIES);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Updates properties for a path
|
||||
*
|
||||
@@ -328,6 +337,10 @@ class CustomPropertiesBackend implements BackendInterface {
|
||||
return [];
|
||||
}
|
||||
|
||||
if (isset($this->publishedCache[$path])) {
|
||||
return $this->publishedCache[$path];
|
||||
}
|
||||
|
||||
$qb = $this->connection->getQueryBuilder();
|
||||
$qb->select('*')
|
||||
->from(self::TABLE_NAME)
|
||||
@@ -338,6 +351,7 @@ class CustomPropertiesBackend implements BackendInterface {
|
||||
$props[$row['propertyname']] = $this->decodeValueFromDatabase($row['propertyvalue'], $row['valuetype']);
|
||||
}
|
||||
$result->closeCursor();
|
||||
$this->publishedCache[$path] = $props;
|
||||
return $props;
|
||||
}
|
||||
|
||||
@@ -376,6 +390,62 @@ class CustomPropertiesBackend implements BackendInterface {
|
||||
$this->userCache = array_merge($this->userCache, $propsByPath);
|
||||
}
|
||||
|
||||
private function cacheCalendars(CalendarHome $node, array $requestedProperties): void {
|
||||
$calendars = $node->getChildren();
|
||||
|
||||
$users = [];
|
||||
foreach ($calendars as $calendar) {
|
||||
if ($calendar instanceof Calendar) {
|
||||
$user = str_replace('principals/users/', '', $calendar->getPrincipalURI());
|
||||
if (!isset($users[$user])) {
|
||||
$users[$user] = ['calendars/' . $user];
|
||||
}
|
||||
$users[$user][] = 'calendars/' . $user . '/' . $calendar->getUri();
|
||||
} elseif ($calendar instanceof Inbox || $calendar instanceof Outbox || $calendar instanceof TrashbinHome || $calendar instanceof ExternalCalendar) {
|
||||
if ($calendar->getOwner()) {
|
||||
$user = str_replace('principals/users/', '', $calendar->getOwner());
|
||||
if (!isset($users[$user])) {
|
||||
$users[$user] = ['calendars/' . $user];
|
||||
}
|
||||
$users[$user][] = 'calendars/' . $user . '/' . $calendar->getName();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// user properties
|
||||
$properties = $this->propertyMapper->findPropertiesByPathsAndUsers($users);
|
||||
|
||||
$propsByPath = [];
|
||||
foreach ($users as $paths) {
|
||||
foreach ($paths as $path) {
|
||||
$propsByPath[$path] = [];
|
||||
}
|
||||
}
|
||||
|
||||
foreach ($properties as $property) {
|
||||
$propsByPath[$property->getPropertypath()][$property->getPropertyname()] = $this->decodeValueFromDatabase($property->getPropertyvalue(), $property->getValuetype());
|
||||
}
|
||||
$this->userCache = array_merge($this->userCache, $propsByPath);
|
||||
|
||||
// published properties
|
||||
$allowedProps = array_intersect(self::PUBLISHED_READ_ONLY_PROPERTIES, $requestedProperties);
|
||||
if (empty($allowedProps)) {
|
||||
return;
|
||||
}
|
||||
$paths = [];
|
||||
foreach ($users as $nestedPaths) {
|
||||
$paths = array_merge($paths, $nestedPaths);
|
||||
}
|
||||
$paths = array_unique($paths);
|
||||
|
||||
$propsByPath = array_fill_keys(array_values($paths), []);
|
||||
$properties = $this->propertyMapper->findPropertiesByPaths($paths, $allowedProps);
|
||||
foreach ($properties as $property) {
|
||||
$propsByPath[$property->getPropertypath()][$property->getPropertyname()] = $this->decodeValueFromDatabase($property->getPropertyvalue(), $property->getValuetype());
|
||||
}
|
||||
$this->publishedCache = array_merge($this->publishedCache, $propsByPath);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a list of properties for the given path and current user
|
||||
*
|
||||
@@ -422,6 +492,14 @@ class CustomPropertiesBackend implements BackendInterface {
|
||||
return $props;
|
||||
}
|
||||
|
||||
private function isPropertyDefaultValue(string $name, mixed $value): bool {
|
||||
if (!isset(self::PROPERTY_DEFAULT_VALUES[$name])) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return self::PROPERTY_DEFAULT_VALUES[$name] === $value;
|
||||
}
|
||||
|
||||
/**
|
||||
* @throws Exception
|
||||
*/
|
||||
@@ -438,8 +516,8 @@ class CustomPropertiesBackend implements BackendInterface {
|
||||
'propertyName' => $propertyName,
|
||||
];
|
||||
|
||||
// If it was null, we need to delete the property
|
||||
if (is_null($propertyValue)) {
|
||||
// If it was null or set to the default value, we need to delete the property
|
||||
if (is_null($propertyValue) || $this->isPropertyDefaultValue($propertyName, $propertyValue)) {
|
||||
if (array_key_exists($propertyName, $existing)) {
|
||||
$deleteQuery = $deleteQuery ?? $this->createDeleteQuery();
|
||||
$deleteQuery
|
||||
|
||||
@@ -16,6 +16,7 @@ use OCP\AppFramework\Http;
|
||||
use OCP\IConfig;
|
||||
use OCP\IRequest;
|
||||
use Sabre\DAV\Exception\NotFound;
|
||||
use Sabre\DAV\ICollection;
|
||||
use Sabre\DAV\INode;
|
||||
use Sabre\DAV\PropFind;
|
||||
use Sabre\DAV\Server;
|
||||
@@ -89,6 +90,7 @@ class Plugin extends ServerPlugin {
|
||||
$this->server->xml->elementMap['{' . Plugin::NS_OWNCLOUD . '}invite'] = Invite::class;
|
||||
|
||||
$this->server->on('method:POST', [$this, 'httpPost']);
|
||||
$this->server->on('preloadCollection', $this->preloadCollection(...));
|
||||
$this->server->on('propFind', [$this, 'propFind']);
|
||||
}
|
||||
|
||||
@@ -168,6 +170,24 @@ class Plugin extends ServerPlugin {
|
||||
}
|
||||
}
|
||||
|
||||
private function preloadCollection(PropFind $propFind, ICollection $collection): void {
|
||||
if (!$collection instanceof CalendarHome || $propFind->getDepth() !== 1) {
|
||||
return;
|
||||
}
|
||||
|
||||
$backend = $collection->getCalDAVBackend();
|
||||
if (!$backend instanceof CalDavBackend) {
|
||||
return;
|
||||
}
|
||||
|
||||
$calendars = $collection->getChildren();
|
||||
$calendars = array_filter($calendars, static fn (INode $node) => $node instanceof IShareable);
|
||||
/** @var int[] $resourceIds */
|
||||
$resourceIds = array_map(
|
||||
static fn (IShareable $node) => $node->getResourceId(), $calendars);
|
||||
$backend->preloadShares($resourceIds);
|
||||
}
|
||||
|
||||
/**
|
||||
* This event is triggered when properties are requested for a certain
|
||||
* node.
|
||||
@@ -179,20 +199,6 @@ class Plugin extends ServerPlugin {
|
||||
* @return void
|
||||
*/
|
||||
public function propFind(PropFind $propFind, INode $node) {
|
||||
if ($node instanceof CalendarHome && $propFind->getDepth() === 1) {
|
||||
$backend = $node->getCalDAVBackend();
|
||||
if ($backend instanceof CalDavBackend) {
|
||||
$calendars = $node->getChildren();
|
||||
$calendars = array_filter($calendars, function (INode $node) {
|
||||
return $node instanceof IShareable;
|
||||
});
|
||||
/** @var int[] $resourceIds */
|
||||
$resourceIds = array_map(function (IShareable $node) {
|
||||
return $node->getResourceId();
|
||||
}, $calendars);
|
||||
$backend->preloadShares($resourceIds);
|
||||
}
|
||||
}
|
||||
if ($node instanceof IShareable) {
|
||||
$propFind->handle('{' . Plugin::NS_OWNCLOUD . '}invite', function () use ($node) {
|
||||
return new Invite(
|
||||
|
||||
@@ -16,6 +16,7 @@ use OCP\AppFramework\Db\Entity;
|
||||
* @method string getPropertypath()
|
||||
* @method string getPropertyname()
|
||||
* @method string getPropertyvalue()
|
||||
* @method int getValuetype()
|
||||
*/
|
||||
class Property extends Entity {
|
||||
|
||||
|
||||
@@ -10,6 +10,7 @@ declare(strict_types=1);
|
||||
namespace OCA\DAV\Db;
|
||||
|
||||
use OCP\AppFramework\Db\QBMapper;
|
||||
use OCP\DB\QueryBuilder\IQueryBuilder;
|
||||
use OCP\IDBConnection;
|
||||
|
||||
/**
|
||||
@@ -39,17 +40,43 @@ class PropertyMapper extends QBMapper {
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array<string, string[]> $calendars
|
||||
* @return Property[]
|
||||
* @throws \OCP\DB\Exception
|
||||
*/
|
||||
public function findPropertiesByPath(string $userId, string $path): array {
|
||||
public function findPropertiesByPathsAndUsers(array $calendars): array {
|
||||
$selectQb = $this->db->getQueryBuilder();
|
||||
$selectQb->select('*')
|
||||
->from(self::TABLE_NAME)
|
||||
->where(
|
||||
$selectQb->expr()->eq('userid', $selectQb->createNamedParameter($userId)),
|
||||
$selectQb->expr()->eq('propertypath', $selectQb->createNamedParameter($path)),
|
||||
->from(self::TABLE_NAME);
|
||||
|
||||
foreach ($calendars as $user => $paths) {
|
||||
$selectQb->orWhere(
|
||||
$selectQb->expr()->andX(
|
||||
$selectQb->expr()->eq('userid', $selectQb->createNamedParameter($user)),
|
||||
$selectQb->expr()->in('propertypath', $selectQb->createNamedParameter($paths, IQueryBuilder::PARAM_STR_ARRAY)),
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
return $this->findEntities($selectQb);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string[] $calendars
|
||||
* @param string[] $allowedProperties
|
||||
* @return Property[]
|
||||
* @throws \OCP\DB\Exception
|
||||
*/
|
||||
public function findPropertiesByPaths(array $calendars, array $allowedProperties = []): array {
|
||||
$selectQb = $this->db->getQueryBuilder();
|
||||
$selectQb->select('*')
|
||||
->from(self::TABLE_NAME)
|
||||
->where($selectQb->expr()->in('propertypath', $selectQb->createNamedParameter($calendars, IQueryBuilder::PARAM_STR_ARRAY)));
|
||||
|
||||
if ($allowedProperties) {
|
||||
$selectQb->andWhere($selectQb->expr()->in('propertyname', $selectQb->createNamedParameter($allowedProperties, IQueryBuilder::PARAM_STR_ARRAY)));
|
||||
}
|
||||
|
||||
return $this->findEntities($selectQb);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,53 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
/**
|
||||
* SPDX-FileCopyrightText: 2025 Nextcloud GmbH and Nextcloud contributors
|
||||
* SPDX-License-Identifier: AGPL-3.0-or-later
|
||||
*/
|
||||
|
||||
namespace OCA\DAV\Migration;
|
||||
|
||||
use Closure;
|
||||
use OCP\DB\ISchemaWrapper;
|
||||
use OCP\DB\QueryBuilder\IQueryBuilder;
|
||||
use OCP\IDBConnection;
|
||||
use OCP\Migration\IOutput;
|
||||
use OCP\Migration\SimpleMigrationStep;
|
||||
use Override;
|
||||
|
||||
class Version1034Date20250813093701 extends SimpleMigrationStep {
|
||||
public function __construct(
|
||||
private IDBConnection $db,
|
||||
) {
|
||||
}
|
||||
|
||||
/**
|
||||
* @param IOutput $output
|
||||
* @param Closure(): ISchemaWrapper $schemaClosure
|
||||
* @param array $options
|
||||
*/
|
||||
#[Override]
|
||||
public function postSchemaChange(IOutput $output, Closure $schemaClosure, array $options): void {
|
||||
$qb = $this->db->getQueryBuilder();
|
||||
$qb->delete('properties')
|
||||
->where($qb->expr()->eq(
|
||||
'propertyname',
|
||||
$qb->createNamedParameter(
|
||||
'{http://owncloud.org/ns}calendar-enabled',
|
||||
IQueryBuilder::PARAM_STR,
|
||||
),
|
||||
IQueryBuilder::PARAM_STR,
|
||||
))
|
||||
->andWhere($qb->expr()->eq(
|
||||
'propertyvalue',
|
||||
$qb->createNamedParameter(
|
||||
'1',
|
||||
IQueryBuilder::PARAM_STR,
|
||||
),
|
||||
IQueryBuilder::PARAM_STR,
|
||||
))
|
||||
->executeStatement();
|
||||
}
|
||||
}
|
||||
@@ -132,6 +132,7 @@ class RootCollection extends SimpleCollection {
|
||||
);
|
||||
|
||||
$contactsSharingBackend = Server::get(\OCA\DAV\CardDAV\Sharing\Backend::class);
|
||||
$config = Server::get(IConfig::class);
|
||||
|
||||
$pluginManager = new PluginManager(\OC::$server, Server::get(IAppManager::class));
|
||||
$usersCardDavBackend = new CardDavBackend(
|
||||
@@ -140,6 +141,7 @@ class RootCollection extends SimpleCollection {
|
||||
$userManager,
|
||||
$dispatcher,
|
||||
$contactsSharingBackend,
|
||||
$config
|
||||
);
|
||||
$usersAddressBookRoot = new AddressBookRoot($userPrincipalBackend, $usersCardDavBackend, $pluginManager, $userSession->getUser(), $groupManager, 'principals/users');
|
||||
$usersAddressBookRoot->disableListing = $disableListing;
|
||||
@@ -150,6 +152,7 @@ class RootCollection extends SimpleCollection {
|
||||
$userManager,
|
||||
$dispatcher,
|
||||
$contactsSharingBackend,
|
||||
$config
|
||||
);
|
||||
$systemAddressBookRoot = new AddressBookRoot(new SystemPrincipalBackend(), $systemCardDavBackend, $pluginManager, $userSession->getUser(), $groupManager, 'principals/system');
|
||||
$systemAddressBookRoot->disableListing = $disableListing;
|
||||
|
||||
+11
-1
@@ -45,6 +45,8 @@ use OCA\DAV\Connector\Sabre\FilesReportPlugin;
|
||||
use OCA\DAV\Connector\Sabre\LockPlugin;
|
||||
use OCA\DAV\Connector\Sabre\MaintenancePlugin;
|
||||
use OCA\DAV\Connector\Sabre\PropfindCompressionPlugin;
|
||||
use OCA\DAV\Connector\Sabre\PropFindMonitorPlugin;
|
||||
use OCA\DAV\Connector\Sabre\PropFindPreloadNotifyPlugin;
|
||||
use OCA\DAV\Connector\Sabre\QuotaPlugin;
|
||||
use OCA\DAV\Connector\Sabre\RequestIdHeaderPlugin;
|
||||
use OCA\DAV\Connector\Sabre\SharesPlugin;
|
||||
@@ -53,6 +55,7 @@ use OCA\DAV\Connector\Sabre\ZipFolderPlugin;
|
||||
use OCA\DAV\DAV\CustomPropertiesBackend;
|
||||
use OCA\DAV\DAV\PublicAuth;
|
||||
use OCA\DAV\DAV\ViewOnlyPlugin;
|
||||
use OCA\DAV\Db\PropertyMapper;
|
||||
use OCA\DAV\Events\SabrePluginAddEvent;
|
||||
use OCA\DAV\Events\SabrePluginAuthInitEvent;
|
||||
use OCA\DAV\Files\BrowserErrorPagePlugin;
|
||||
@@ -108,6 +111,7 @@ class Server {
|
||||
private IRequest $request,
|
||||
private string $baseUri,
|
||||
) {
|
||||
$debugEnabled = \OCP\Server::get(IConfig::class)->getSystemValue('debug', false);
|
||||
$this->profiler = \OCP\Server::get(IProfiler::class);
|
||||
if ($this->profiler->isEnabled()) {
|
||||
/** @var IEventLogger $eventLogger */
|
||||
@@ -120,6 +124,7 @@ class Server {
|
||||
|
||||
$root = new RootCollection();
|
||||
$this->server = new \OCA\DAV\Connector\Sabre\Server(new CachingTree($root));
|
||||
$this->server->setLogger($logger);
|
||||
|
||||
// Add maintenance plugin
|
||||
$this->server->addPlugin(new MaintenancePlugin(\OCP\Server::get(IConfig::class), \OC::$server->getL10N('dav')));
|
||||
@@ -167,7 +172,9 @@ class Server {
|
||||
$authPlugin->addBackend($authBackend);
|
||||
|
||||
// debugging
|
||||
if (\OCP\Server::get(IConfig::class)->getSystemValue('debug', false)) {
|
||||
if ($debugEnabled) {
|
||||
$this->server->debugEnabled = true;
|
||||
$this->server->addPlugin(new PropFindMonitorPlugin());
|
||||
$this->server->addPlugin(new \Sabre\DAV\Browser\Plugin());
|
||||
} else {
|
||||
$this->server->addPlugin(new DummyGetResponsePlugin());
|
||||
@@ -232,6 +239,7 @@ class Server {
|
||||
\OCP\Server::get(IUserSession::class)
|
||||
));
|
||||
|
||||
// performance improvement plugins
|
||||
$this->server->addPlugin(new CopyEtagHeaderPlugin());
|
||||
$this->server->addPlugin(new RequestIdHeaderPlugin(\OCP\Server::get(IRequest::class)));
|
||||
$this->server->addPlugin(new UploadAutoMkcolPlugin());
|
||||
@@ -243,6 +251,7 @@ class Server {
|
||||
$eventDispatcher,
|
||||
));
|
||||
$this->server->addPlugin(\OCP\Server::get(PaginatePlugin::class));
|
||||
$this->server->addPlugin(new PropFindPreloadNotifyPlugin());
|
||||
|
||||
// allow setup of additional plugins
|
||||
$eventDispatcher->dispatch('OCA\DAV\Connector\Sabre::addPlugin', $event);
|
||||
@@ -301,6 +310,7 @@ class Server {
|
||||
$this->server->tree,
|
||||
\OCP\Server::get(IDBConnection::class),
|
||||
\OCP\Server::get(IUserSession::class)->getUser(),
|
||||
\OCP\Server::get(PropertyMapper::class),
|
||||
\OCP\Server::get(DefaultCalendarValidator::class),
|
||||
)
|
||||
)
|
||||
|
||||
@@ -27,6 +27,7 @@ use Sabre\DAV\Exception\BadRequest;
|
||||
use Sabre\DAV\Exception\Conflict;
|
||||
use Sabre\DAV\Exception\Forbidden;
|
||||
use Sabre\DAV\Exception\UnsupportedMediaType;
|
||||
use Sabre\DAV\ICollection;
|
||||
use Sabre\DAV\PropFind;
|
||||
use Sabre\DAV\PropPatch;
|
||||
use Sabre\HTTP\RequestInterface;
|
||||
@@ -94,6 +95,7 @@ class SystemTagPlugin extends \Sabre\DAV\ServerPlugin {
|
||||
|
||||
$server->protectedProperties[] = self::ID_PROPERTYNAME;
|
||||
|
||||
$server->on('preloadCollection', $this->preloadCollection(...));
|
||||
$server->on('propFind', [$this, 'handleGetProperties']);
|
||||
$server->on('propPatch', [$this, 'handleUpdateProperties']);
|
||||
$server->on('method:POST', [$this, 'httpPost']);
|
||||
@@ -199,6 +201,40 @@ class SystemTagPlugin extends \Sabre\DAV\ServerPlugin {
|
||||
}
|
||||
}
|
||||
|
||||
private function preloadCollection(
|
||||
PropFind $propFind,
|
||||
ICollection $collection,
|
||||
): void {
|
||||
if (!$collection instanceof Node) {
|
||||
return;
|
||||
}
|
||||
|
||||
if ($collection instanceof Directory
|
||||
&& !isset($this->cachedTagMappings[$collection->getId()])
|
||||
&& $propFind->getStatus(
|
||||
self::SYSTEM_TAGS_PROPERTYNAME
|
||||
) !== null) {
|
||||
$fileIds = [$collection->getId()];
|
||||
|
||||
// note: pre-fetching only supported for depth <= 1
|
||||
$folderContent = $collection->getChildren();
|
||||
foreach ($folderContent as $info) {
|
||||
if ($info instanceof Node) {
|
||||
$fileIds[] = $info->getId();
|
||||
}
|
||||
}
|
||||
|
||||
$tags = $this->tagMapper->getTagIdsForObjects($fileIds, 'files');
|
||||
|
||||
$this->cachedTagMappings += $tags;
|
||||
$emptyFileIds = array_diff($fileIds, array_keys($tags));
|
||||
|
||||
// also cache the ones that were not found
|
||||
foreach ($emptyFileIds as $fileId) {
|
||||
$this->cachedTagMappings[$fileId] = [];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves system tag properties
|
||||
@@ -297,29 +333,6 @@ class SystemTagPlugin extends \Sabre\DAV\ServerPlugin {
|
||||
}
|
||||
|
||||
private function propfindForFile(PropFind $propFind, Node $node): void {
|
||||
if ($node instanceof Directory
|
||||
&& $propFind->getDepth() !== 0
|
||||
&& !is_null($propFind->getStatus(self::SYSTEM_TAGS_PROPERTYNAME))) {
|
||||
$fileIds = [$node->getId()];
|
||||
|
||||
// note: pre-fetching only supported for depth <= 1
|
||||
$folderContent = $node->getChildren();
|
||||
foreach ($folderContent as $info) {
|
||||
if ($info instanceof Node) {
|
||||
$fileIds[] = $info->getId();
|
||||
}
|
||||
}
|
||||
|
||||
$tags = $this->tagMapper->getTagIdsForObjects($fileIds, 'files');
|
||||
|
||||
$this->cachedTagMappings = $this->cachedTagMappings + $tags;
|
||||
$emptyFileIds = array_diff($fileIds, array_keys($tags));
|
||||
|
||||
// also cache the ones that were not found
|
||||
foreach ($emptyFileIds as $fileId) {
|
||||
$this->cachedTagMappings[$fileId] = [];
|
||||
}
|
||||
}
|
||||
|
||||
$propFind->handle(self::SYSTEM_TAGS_PROPERTYNAME, function () use ($node) {
|
||||
$user = $this->userSession->getUser();
|
||||
|
||||
@@ -241,14 +241,15 @@ class AddressBookImplTest extends TestCase {
|
||||
public static function dataTestGetPermissions(): array {
|
||||
return [
|
||||
[[], 0],
|
||||
[[['privilege' => '{DAV:}read']], 1],
|
||||
[[['privilege' => '{DAV:}write']], 6],
|
||||
[[['privilege' => '{DAV:}all']], 31],
|
||||
[[['privilege' => '{DAV:}read'],['privilege' => '{DAV:}write']], 7],
|
||||
[[['privilege' => '{DAV:}read'],['privilege' => '{DAV:}all']], 31],
|
||||
[[['privilege' => '{DAV:}all'],['privilege' => '{DAV:}write']], 31],
|
||||
[[['privilege' => '{DAV:}read'],['privilege' => '{DAV:}write'],['privilege' => '{DAV:}all']], 31],
|
||||
[[['privilege' => '{DAV:}all'],['privilege' => '{DAV:}read'],['privilege' => '{DAV:}write']], 31],
|
||||
[[['privilege' => '{DAV:}read', 'principal' => 'principals/system/system']], 1],
|
||||
[[['privilege' => '{DAV:}read', 'principal' => 'principals/system/system'], ['privilege' => '{DAV:}write', 'principal' => 'principals/someone/else']], 1],
|
||||
[[['privilege' => '{DAV:}write', 'principal' => 'principals/system/system']], 6],
|
||||
[[['privilege' => '{DAV:}all', 'principal' => 'principals/system/system']], 31],
|
||||
[[['privilege' => '{DAV:}read', 'principal' => 'principals/system/system'],['privilege' => '{DAV:}write', 'principal' => 'principals/system/system']], 7],
|
||||
[[['privilege' => '{DAV:}read', 'principal' => 'principals/system/system'],['privilege' => '{DAV:}all', 'principal' => 'principals/system/system']], 31],
|
||||
[[['privilege' => '{DAV:}all', 'principal' => 'principals/system/system'],['privilege' => '{DAV:}write', 'principal' => 'principals/system/system']], 31],
|
||||
[[['privilege' => '{DAV:}read', 'principal' => 'principals/system/system'],['privilege' => '{DAV:}write', 'principal' => 'principals/system/system'],['privilege' => '{DAV:}all', 'principal' => 'principals/system/system']], 31],
|
||||
[[['privilege' => '{DAV:}all', 'principal' => 'principals/system/system'],['privilege' => '{DAV:}read', 'principal' => 'principals/system/system'],['privilege' => '{DAV:}write', 'principal' => 'principals/system/system']], 31],
|
||||
];
|
||||
}
|
||||
|
||||
|
||||
@@ -50,6 +50,7 @@ class CardDavBackendTest extends TestCase {
|
||||
private IUserManager&MockObject $userManager;
|
||||
private IGroupManager&MockObject $groupManager;
|
||||
private IEventDispatcher&MockObject $dispatcher;
|
||||
private IConfig&MockObject $config;
|
||||
private Backend $sharingBackend;
|
||||
private IDBConnection $db;
|
||||
private CardDavBackend $backend;
|
||||
@@ -96,6 +97,7 @@ class CardDavBackendTest extends TestCase {
|
||||
|
||||
$this->userManager = $this->createMock(IUserManager::class);
|
||||
$this->groupManager = $this->createMock(IGroupManager::class);
|
||||
$this->config = $this->createMock(IConfig::class);
|
||||
$this->principal = $this->getMockBuilder(Principal::class)
|
||||
->setConstructorArgs([
|
||||
$this->userManager,
|
||||
@@ -106,7 +108,7 @@ class CardDavBackendTest extends TestCase {
|
||||
$this->createMock(IAppManager::class),
|
||||
$this->createMock(ProxyMapper::class),
|
||||
$this->createMock(KnownUserService::class),
|
||||
$this->createMock(IConfig::class),
|
||||
$this->config,
|
||||
$this->createMock(IFactory::class)
|
||||
])
|
||||
->onlyMethods(['getPrincipalByPath', 'getGroupMembership', 'findByUri'])
|
||||
@@ -135,6 +137,7 @@ class CardDavBackendTest extends TestCase {
|
||||
$this->userManager,
|
||||
$this->dispatcher,
|
||||
$this->sharingBackend,
|
||||
$this->config,
|
||||
);
|
||||
// start every test with a empty cards_properties and cards table
|
||||
$query = $this->db->getQueryBuilder();
|
||||
@@ -231,7 +234,7 @@ class CardDavBackendTest extends TestCase {
|
||||
public function testCardOperations(): void {
|
||||
/** @var CardDavBackend&MockObject $backend */
|
||||
$backend = $this->getMockBuilder(CardDavBackend::class)
|
||||
->setConstructorArgs([$this->db, $this->principal, $this->userManager, $this->dispatcher, $this->sharingBackend])
|
||||
->setConstructorArgs([$this->db, $this->principal, $this->userManager, $this->dispatcher, $this->sharingBackend,$this->config])
|
||||
->onlyMethods(['updateProperties', 'purgeProperties'])
|
||||
->getMock();
|
||||
|
||||
@@ -291,7 +294,7 @@ class CardDavBackendTest extends TestCase {
|
||||
|
||||
public function testMultiCard(): void {
|
||||
$this->backend = $this->getMockBuilder(CardDavBackend::class)
|
||||
->setConstructorArgs([$this->db, $this->principal, $this->userManager, $this->dispatcher, $this->sharingBackend])
|
||||
->setConstructorArgs([$this->db, $this->principal, $this->userManager, $this->dispatcher, $this->sharingBackend,$this->config])
|
||||
->onlyMethods(['updateProperties'])
|
||||
->getMock();
|
||||
|
||||
@@ -345,7 +348,7 @@ class CardDavBackendTest extends TestCase {
|
||||
|
||||
public function testMultipleUIDOnDifferentAddressbooks(): void {
|
||||
$this->backend = $this->getMockBuilder(CardDavBackend::class)
|
||||
->setConstructorArgs([$this->db, $this->principal, $this->userManager, $this->dispatcher, $this->sharingBackend])
|
||||
->setConstructorArgs([$this->db, $this->principal, $this->userManager, $this->dispatcher, $this->sharingBackend,$this->config])
|
||||
->onlyMethods(['updateProperties'])
|
||||
->getMock();
|
||||
|
||||
@@ -368,7 +371,7 @@ class CardDavBackendTest extends TestCase {
|
||||
|
||||
public function testMultipleUIDDenied(): void {
|
||||
$this->backend = $this->getMockBuilder(CardDavBackend::class)
|
||||
->setConstructorArgs([$this->db, $this->principal, $this->userManager, $this->dispatcher, $this->sharingBackend])
|
||||
->setConstructorArgs([$this->db, $this->principal, $this->userManager, $this->dispatcher, $this->sharingBackend, $this->config])
|
||||
->onlyMethods(['updateProperties'])
|
||||
->getMock();
|
||||
|
||||
@@ -390,7 +393,7 @@ class CardDavBackendTest extends TestCase {
|
||||
|
||||
public function testNoValidUID(): void {
|
||||
$this->backend = $this->getMockBuilder(CardDavBackend::class)
|
||||
->setConstructorArgs([$this->db, $this->principal, $this->userManager, $this->dispatcher, $this->sharingBackend])
|
||||
->setConstructorArgs([$this->db, $this->principal, $this->userManager, $this->dispatcher, $this->sharingBackend, $this->config])
|
||||
->onlyMethods(['updateProperties'])
|
||||
->getMock();
|
||||
|
||||
@@ -408,7 +411,7 @@ class CardDavBackendTest extends TestCase {
|
||||
|
||||
public function testDeleteWithoutCard(): void {
|
||||
$this->backend = $this->getMockBuilder(CardDavBackend::class)
|
||||
->setConstructorArgs([$this->db, $this->principal, $this->userManager, $this->dispatcher, $this->sharingBackend])
|
||||
->setConstructorArgs([$this->db, $this->principal, $this->userManager, $this->dispatcher, $this->sharingBackend, $this->config])
|
||||
->onlyMethods([
|
||||
'getCardId',
|
||||
'addChange',
|
||||
@@ -453,7 +456,7 @@ class CardDavBackendTest extends TestCase {
|
||||
|
||||
public function testSyncSupport(): void {
|
||||
$this->backend = $this->getMockBuilder(CardDavBackend::class)
|
||||
->setConstructorArgs([$this->db, $this->principal, $this->userManager, $this->dispatcher, $this->sharingBackend])
|
||||
->setConstructorArgs([$this->db, $this->principal, $this->userManager, $this->dispatcher, $this->sharingBackend, $this->config])
|
||||
->onlyMethods(['updateProperties'])
|
||||
->getMock();
|
||||
|
||||
@@ -522,7 +525,7 @@ class CardDavBackendTest extends TestCase {
|
||||
$cardId = 2;
|
||||
|
||||
$backend = $this->getMockBuilder(CardDavBackend::class)
|
||||
->setConstructorArgs([$this->db, $this->principal, $this->userManager, $this->dispatcher, $this->sharingBackend])
|
||||
->setConstructorArgs([$this->db, $this->principal, $this->userManager, $this->dispatcher, $this->sharingBackend, $this->config])
|
||||
->onlyMethods(['getCardId'])->getMock();
|
||||
|
||||
$backend->expects($this->any())->method('getCardId')->willReturn($cardId);
|
||||
|
||||
@@ -108,7 +108,7 @@ class SyncServiceTest extends TestCase {
|
||||
'1',
|
||||
'principals/system/system',
|
||||
[]
|
||||
);
|
||||
)[0];
|
||||
|
||||
$this->assertEquals('http://sabre.io/ns/sync/1', $token);
|
||||
}
|
||||
@@ -179,7 +179,7 @@ END:VCARD';
|
||||
'1',
|
||||
'principals/system/system',
|
||||
[]
|
||||
);
|
||||
)[0];
|
||||
|
||||
$this->assertEquals('http://sabre.io/ns/sync/2', $token);
|
||||
}
|
||||
@@ -250,7 +250,7 @@ END:VCARD';
|
||||
'1',
|
||||
'principals/system/system',
|
||||
[]
|
||||
);
|
||||
)[0];
|
||||
|
||||
$this->assertEquals('http://sabre.io/ns/sync/3', $token);
|
||||
}
|
||||
@@ -291,7 +291,7 @@ END:VCARD';
|
||||
'1',
|
||||
'principals/system/system',
|
||||
[]
|
||||
);
|
||||
)[0];
|
||||
|
||||
$this->assertEquals('http://sabre.io/ns/sync/4', $token);
|
||||
}
|
||||
|
||||
@@ -12,6 +12,7 @@ use OCA\DAV\CalDAV\DefaultCalendarValidator;
|
||||
use OCA\DAV\Connector\Sabre\Directory;
|
||||
use OCA\DAV\Connector\Sabre\File;
|
||||
use OCA\DAV\DAV\CustomPropertiesBackend;
|
||||
use OCA\DAV\Db\PropertyMapper;
|
||||
use OCP\IDBConnection;
|
||||
use OCP\IUser;
|
||||
use OCP\Server;
|
||||
@@ -52,6 +53,7 @@ class CustomPropertiesBackendTest extends \Test\TestCase {
|
||||
$this->tree,
|
||||
Server::get(IDBConnection::class),
|
||||
$this->user,
|
||||
Server::get(PropertyMapper::class),
|
||||
$this->defaultCalendarValidator,
|
||||
);
|
||||
}
|
||||
|
||||
@@ -0,0 +1,133 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* SPDX-FileCopyrightText: 2025 Nextcloud GmbH and Nextcloud contributors
|
||||
* SPDX-License-Identifier: AGPL-3.0-or-later
|
||||
*/
|
||||
namespace unit\Connector\Sabre;
|
||||
|
||||
use OCA\DAV\Connector\Sabre\PropFindMonitorPlugin;
|
||||
use OCA\DAV\Connector\Sabre\Server;
|
||||
use PHPUnit\Framework\MockObject\MockObject;
|
||||
use Psr\Log\LoggerInterface;
|
||||
use Sabre\HTTP\Request;
|
||||
use Sabre\HTTP\Response;
|
||||
use Test\TestCase;
|
||||
|
||||
class PropFindMonitorPluginTest extends TestCase {
|
||||
|
||||
private PropFindMonitorPlugin $plugin;
|
||||
private Server&MockObject $server;
|
||||
private LoggerInterface&MockObject $logger;
|
||||
private Request&MockObject $request;
|
||||
private Response&MockObject $response;
|
||||
|
||||
public static function dataTest(): array {
|
||||
$minQueriesTrigger = PropFindMonitorPlugin::THRESHOLD_QUERY_FACTOR
|
||||
* PropFindMonitorPlugin::THRESHOLD_NODES;
|
||||
return [
|
||||
'No queries logged' => [[], 0],
|
||||
'Plugins with queries in less than threshold nodes should not be logged' => [
|
||||
[
|
||||
'propFind' => [
|
||||
[
|
||||
'PluginName' => [
|
||||
'queries' => 100,
|
||||
'nodes' => PropFindMonitorPlugin::THRESHOLD_NODES - 1]
|
||||
],
|
||||
[],
|
||||
]
|
||||
],
|
||||
0
|
||||
],
|
||||
'Plugins with query-to-node ratio less than threshold should not be logged' => [
|
||||
[
|
||||
'propFind' => [
|
||||
[
|
||||
'PluginName' => [
|
||||
'queries' => $minQueriesTrigger - 1,
|
||||
'nodes' => PropFindMonitorPlugin::THRESHOLD_NODES ],
|
||||
],
|
||||
[],
|
||||
]
|
||||
],
|
||||
0
|
||||
],
|
||||
'Plugins with more nodes scanned than queries executed should not be logged' => [
|
||||
[
|
||||
'propFind' => [
|
||||
[
|
||||
'PluginName' => [
|
||||
'queries' => $minQueriesTrigger,
|
||||
'nodes' => PropFindMonitorPlugin::THRESHOLD_NODES * 2],
|
||||
],
|
||||
[],]
|
||||
],
|
||||
0
|
||||
],
|
||||
'Plugins with queries only in highest depth level should not be logged' => [
|
||||
[
|
||||
'propFind' => [
|
||||
[
|
||||
'PluginName' => [
|
||||
'queries' => $minQueriesTrigger,
|
||||
'nodes' => PropFindMonitorPlugin::THRESHOLD_NODES - 1
|
||||
]
|
||||
],
|
||||
[
|
||||
'PluginName' => [
|
||||
'queries' => $minQueriesTrigger * 2,
|
||||
'nodes' => PropFindMonitorPlugin::THRESHOLD_NODES
|
||||
]
|
||||
],
|
||||
]
|
||||
],
|
||||
0
|
||||
],
|
||||
'Plugins with too many queries should be logged' => [
|
||||
[
|
||||
'propFind' => [
|
||||
[
|
||||
'FirstPlugin' => [
|
||||
'queries' => $minQueriesTrigger,
|
||||
'nodes' => PropFindMonitorPlugin::THRESHOLD_NODES,
|
||||
],
|
||||
'SecondPlugin' => [
|
||||
'queries' => $minQueriesTrigger,
|
||||
'nodes' => PropFindMonitorPlugin::THRESHOLD_NODES,
|
||||
]
|
||||
],
|
||||
[],
|
||||
]
|
||||
],
|
||||
2
|
||||
]
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* @dataProvider dataTest
|
||||
*/
|
||||
public function test(array $queries, $expectedLogCalls): void {
|
||||
$this->plugin->initialize($this->server);
|
||||
$this->server->expects($this->once())->method('getPluginQueries')
|
||||
->willReturn($queries);
|
||||
|
||||
$this->server->expects(empty($queries) ? $this->never() : $this->once())
|
||||
->method('getLogger')
|
||||
->willReturn($this->logger);
|
||||
|
||||
$this->logger->expects($this->exactly($expectedLogCalls))->method('error');
|
||||
$this->plugin->afterResponse($this->request, $this->response);
|
||||
}
|
||||
|
||||
protected function setUp(): void {
|
||||
parent::setUp();
|
||||
|
||||
$this->plugin = new PropFindMonitorPlugin();
|
||||
$this->server = $this->createMock(Server::class);
|
||||
$this->logger = $this->createMock(LoggerInterface::class);
|
||||
$this->request = $this->createMock(Request::class);
|
||||
$this->response = $this->createMock(Response::class);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,92 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
/**
|
||||
* SPDX-FileCopyrightText: 2025 Nextcloud GmbH and Nextcloud contributors
|
||||
* SPDX-License-Identifier: AGPL-3.0-or-later
|
||||
*/
|
||||
namespace OCA\DAV\Tests\unit\Connector\Sabre;
|
||||
|
||||
use OCA\DAV\Connector\Sabre\PropFindPreloadNotifyPlugin;
|
||||
use PHPUnit\Framework\Attributes\DataProvider;
|
||||
use PHPUnit\Framework\MockObject\MockObject;
|
||||
use Sabre\DAV\ICollection;
|
||||
use Sabre\DAV\IFile;
|
||||
use Sabre\DAV\PropFind;
|
||||
use Sabre\DAV\Server;
|
||||
use Test\TestCase;
|
||||
|
||||
class PropFindPreloadNotifyPluginTest extends TestCase {
|
||||
|
||||
private Server&MockObject $server;
|
||||
private PropFindPreloadNotifyPlugin $plugin;
|
||||
|
||||
protected function setUp(): void {
|
||||
parent::setUp();
|
||||
|
||||
$this->server = $this->createMock(Server::class);
|
||||
$this->plugin = new PropFindPreloadNotifyPlugin();
|
||||
}
|
||||
|
||||
public function testInitialize(): void {
|
||||
$this->server
|
||||
->expects(self::once())
|
||||
->method('on')
|
||||
->with('propFind',
|
||||
$this->anything(), 1);
|
||||
$this->plugin->initialize($this->server);
|
||||
}
|
||||
|
||||
public static function dataTestCollectionPreloadNotifier(): array {
|
||||
return [
|
||||
'When node is not a collection, should not emit' => [
|
||||
IFile::class,
|
||||
1,
|
||||
false,
|
||||
true
|
||||
],
|
||||
'When node is a collection but depth is zero, should not emit' => [
|
||||
ICollection::class,
|
||||
0,
|
||||
false,
|
||||
true
|
||||
],
|
||||
'When node is a collection, and depth > 0, should emit' => [
|
||||
ICollection::class,
|
||||
1,
|
||||
true,
|
||||
true
|
||||
],
|
||||
'When node is a collection, and depth is infinite, should emit'
|
||||
=> [
|
||||
ICollection::class,
|
||||
Server::DEPTH_INFINITY,
|
||||
true,
|
||||
true
|
||||
],
|
||||
'When called called handler returns false, it should be returned'
|
||||
=> [
|
||||
ICollection::class,
|
||||
1,
|
||||
true,
|
||||
false
|
||||
]
|
||||
];
|
||||
}
|
||||
|
||||
#[DataProvider(methodName: 'dataTestCollectionPreloadNotifier')]
|
||||
public function testCollectionPreloadNotifier(string $nodeType, int $depth, bool $shouldEmit, bool $emitReturns):
|
||||
void {
|
||||
$this->plugin->initialize($this->server);
|
||||
$propFind = $this->createMock(PropFind::class);
|
||||
$propFind->expects(self::any())->method('getDepth')->willReturn($depth);
|
||||
$node = $this->createMock($nodeType);
|
||||
|
||||
$expectation = $shouldEmit ? self::once() : self::never();
|
||||
$this->server->expects($expectation)->method('emit')->with('preloadCollection',
|
||||
[$propFind, $node])->willReturn($emitReturns);
|
||||
$return = $this->plugin->collectionPreloadNotifier($propFind, $node);
|
||||
$this->assertEquals($emitReturns, $return);
|
||||
}
|
||||
}
|
||||
@@ -223,6 +223,7 @@ class SharesPluginTest extends \Test\TestCase {
|
||||
0
|
||||
);
|
||||
|
||||
$this->server->emit('preloadCollection', [$propFindRoot, $sabreNode]);
|
||||
$this->plugin->handleGetProperties(
|
||||
$propFindRoot,
|
||||
$sabreNode
|
||||
|
||||
@@ -147,6 +147,8 @@ class TagsPluginTest extends \Test\TestCase {
|
||||
0
|
||||
);
|
||||
|
||||
$this->server->emit('preloadCollection', [$propFindRoot, $node]);
|
||||
|
||||
$this->plugin->handleGetProperties(
|
||||
$propFindRoot,
|
||||
$node
|
||||
|
||||
@@ -10,6 +10,7 @@ namespace OCA\DAV\Tests\unit\DAV;
|
||||
use OCA\DAV\CalDAV\Calendar;
|
||||
use OCA\DAV\CalDAV\DefaultCalendarValidator;
|
||||
use OCA\DAV\DAV\CustomPropertiesBackend;
|
||||
use OCA\DAV\Db\PropertyMapper;
|
||||
use OCP\DB\QueryBuilder\IQueryBuilder;
|
||||
use OCP\IDBConnection;
|
||||
use OCP\IUser;
|
||||
@@ -36,6 +37,7 @@ class CustomPropertiesBackendTest extends TestCase {
|
||||
private IUser&MockObject $user;
|
||||
private DefaultCalendarValidator&MockObject $defaultCalendarValidator;
|
||||
private CustomPropertiesBackend $backend;
|
||||
private PropertyMapper $propertyMapper;
|
||||
|
||||
protected function setUp(): void {
|
||||
parent::setUp();
|
||||
@@ -49,6 +51,7 @@ class CustomPropertiesBackendTest extends TestCase {
|
||||
->with()
|
||||
->willReturn('dummy_user_42');
|
||||
$this->dbConnection = \OCP\Server::get(IDBConnection::class);
|
||||
$this->propertyMapper = \OCP\Server::get(PropertyMapper::class);
|
||||
$this->defaultCalendarValidator = $this->createMock(DefaultCalendarValidator::class);
|
||||
|
||||
$this->backend = new CustomPropertiesBackend(
|
||||
@@ -56,6 +59,7 @@ class CustomPropertiesBackendTest extends TestCase {
|
||||
$this->tree,
|
||||
$this->dbConnection,
|
||||
$this->user,
|
||||
$this->propertyMapper,
|
||||
$this->defaultCalendarValidator,
|
||||
);
|
||||
}
|
||||
@@ -129,6 +133,7 @@ class CustomPropertiesBackendTest extends TestCase {
|
||||
$this->tree,
|
||||
$db,
|
||||
$this->user,
|
||||
$this->propertyMapper,
|
||||
$this->defaultCalendarValidator,
|
||||
);
|
||||
|
||||
|
||||
@@ -39,7 +39,7 @@ OC.L10N.register(
|
||||
"In order to use this encryption module you need to enable server-side encryption in the admin settings. Once enabled this module will encrypt all your files transparently. The encryption is based on AES 256 keys.\nThe module will not touch existing files, only new files will be encrypted after server-side encryption was enabled. It is also not possible to disable the encryption again and switch back to an unencrypted system.\nPlease read the documentation to know all implications before you decide to enable server-side encryption." : "De manera de usar este módulo de cifrado necesita activar el cifrado del lado del servidor en la configuraciones de administrador. Una vez que esté habilitado este módulo cifrará todos tus archivos de manera transparente. El cifrado está basado en llaves AES-256\nEl módulo no tocará los archivos existentes, solo los archivos nuevos serán cifrados una vez que el cifrado del lado del servidor se habilite. Además, no es posible deshabilitar el cifrado de nuevo y cambiar a un sistema sin cifrado.\nPor favor lea la documentación para que entienda todas las implicaciones antes de que decida habilitar el cifrado del lado del servidor.",
|
||||
"Encryption app is enabled but your keys are not initialized, please log-out and log-in again" : "La app de cifrado está habilitada pero sus claves no se han inicializado, por favor, cierre la sesión y vuelva a iniciarla de nuevo.",
|
||||
"Encrypt the home storage" : "Encriptar el almacenamiento personal",
|
||||
"Enabling this option encrypts all files stored on the main storage, otherwise only files on external storage will be encrypted" : "Al activar esta opción se encriptarán todos los archivos almacenados en la memoria principal, de lo contrario serán cifrados sólo los archivos de almacenamiento externo",
|
||||
"Enabling this option encrypts all files stored on the main storage, otherwise only files on external storage will be encrypted" : "Al activar esta opción se encriptarán todos los archivos almacenados en la memoria principal, de lo contrario, serán cifrados sólo los archivos de almacenamiento externo",
|
||||
"Enable recovery key" : "Activa la clave de recuperación",
|
||||
"Disable recovery key" : "Desactiva la clave de recuperación",
|
||||
"The recovery key is an additional encryption key used to encrypt files. It is used to recover files from an account if the password is forgotten." : "La llave de recuperación es una llave de cifrado adicional utilizada para cifrar archivos. Es utilizada para recuperar los archivos de una cuenta si la contraseña fuese olvidada.",
|
||||
|
||||
@@ -37,7 +37,7 @@
|
||||
"In order to use this encryption module you need to enable server-side encryption in the admin settings. Once enabled this module will encrypt all your files transparently. The encryption is based on AES 256 keys.\nThe module will not touch existing files, only new files will be encrypted after server-side encryption was enabled. It is also not possible to disable the encryption again and switch back to an unencrypted system.\nPlease read the documentation to know all implications before you decide to enable server-side encryption." : "De manera de usar este módulo de cifrado necesita activar el cifrado del lado del servidor en la configuraciones de administrador. Una vez que esté habilitado este módulo cifrará todos tus archivos de manera transparente. El cifrado está basado en llaves AES-256\nEl módulo no tocará los archivos existentes, solo los archivos nuevos serán cifrados una vez que el cifrado del lado del servidor se habilite. Además, no es posible deshabilitar el cifrado de nuevo y cambiar a un sistema sin cifrado.\nPor favor lea la documentación para que entienda todas las implicaciones antes de que decida habilitar el cifrado del lado del servidor.",
|
||||
"Encryption app is enabled but your keys are not initialized, please log-out and log-in again" : "La app de cifrado está habilitada pero sus claves no se han inicializado, por favor, cierre la sesión y vuelva a iniciarla de nuevo.",
|
||||
"Encrypt the home storage" : "Encriptar el almacenamiento personal",
|
||||
"Enabling this option encrypts all files stored on the main storage, otherwise only files on external storage will be encrypted" : "Al activar esta opción se encriptarán todos los archivos almacenados en la memoria principal, de lo contrario serán cifrados sólo los archivos de almacenamiento externo",
|
||||
"Enabling this option encrypts all files stored on the main storage, otherwise only files on external storage will be encrypted" : "Al activar esta opción se encriptarán todos los archivos almacenados en la memoria principal, de lo contrario, serán cifrados sólo los archivos de almacenamiento externo",
|
||||
"Enable recovery key" : "Activa la clave de recuperación",
|
||||
"Disable recovery key" : "Desactiva la clave de recuperación",
|
||||
"The recovery key is an additional encryption key used to encrypt files. It is used to recover files from an account if the password is forgotten." : "La llave de recuperación es una llave de cifrado adicional utilizada para cifrar archivos. Es utilizada para recuperar los archivos de una cuenta si la contraseña fuese olvidada.",
|
||||
|
||||
@@ -3,7 +3,7 @@ OC.L10N.register(
|
||||
{
|
||||
"Missing recovery key password" : "Muuda taastevõtme salasõna",
|
||||
"Please repeat the recovery key password" : "Palun korda uut taastevõtme salasõna",
|
||||
"Repeated recovery key password does not match the provided recovery key password" : "Sisestatud taastevõtme salasõna ei kattu",
|
||||
"Repeated recovery key password does not match the provided recovery key password" : "Teistkorda sisestatud taastevõtme salasõna ei kattu sisestatud taastevõtme salasõnaga",
|
||||
"Recovery key successfully enabled" : "Taastevõtme lubamine õnnestus",
|
||||
"Could not enable recovery key. Please check your recovery key password!" : "Ei suutnud taastevõtit kasutusele võtta. Palun kontrolli oma taastevõtme salasõna!",
|
||||
"Recovery key successfully disabled" : "Taastevõtme keelamine õnnestus",
|
||||
@@ -21,6 +21,8 @@ OC.L10N.register(
|
||||
"The current log-in password was not correct, please try again." : "Sisselogimise senine salasõna polnud õige, palun proovi uuesti.",
|
||||
"Private key password successfully updated." : "Privaatvõtme salasõna uuendamine õnnestus.",
|
||||
"Invalid private key for encryption app. Please update your private key password in your personal settings to recover access to your encrypted files." : "Krüptimisrakenduse vigane privaatvõti. Taastamaks ligipääsu krüptitud failidele palun uuenda oma isiklikest seadistustest privaatvõtme salasõna.",
|
||||
"Encryption App is enabled, but your keys are not initialized. Please log-out and log-in again." : "Krüptimisrakendus on kasutusel, kuid krüptovõtmed pole valmendatud. Palun logi välja ning uuesti sisse.",
|
||||
"Encryption app is enabled and ready" : "Krüptimisrakendus on kasutusel ja töövalmis",
|
||||
"Bad Signature" : "Vigane allkiri",
|
||||
"Missing Signature" : "Allkiri puudub",
|
||||
"one-time password for server-side-encryption" : "ühekordne salasõna serveripoolse krüptimise jaoks",
|
||||
@@ -29,6 +31,7 @@ OC.L10N.register(
|
||||
"The administration enabled server-side-encryption. Your files were encrypted using the password \"%s\"." : "Serveri peakasutaja lülitas sisse serveripoolse krüptimise. Sinu failid on krüptitud salasõnaga „%s“.",
|
||||
"Default encryption module" : "Vaikimisi krüptimismoodul",
|
||||
"Default encryption module for server-side encryption" : "Vaikimisi krüptimismoodul serveripoolse krüptimise jaoks",
|
||||
"Encryption app is enabled but your keys are not initialized, please log-out and log-in again" : "Krüptimisrakendus on kasutusel, kuid krüptovõtmed pole valmendatud. Palun logi välja ning uuesti sisse.",
|
||||
"Encrypt the home storage" : "Krüpti ka sisemine andmeruum",
|
||||
"Enabling this option encrypts all files stored on the main storage, otherwise only files on external storage will be encrypted" : "Selle valiku kasutamisel krüptitakse failid sisemises ja välises andmeruumis. Vastasel juhul vaid välises andmeruumis.",
|
||||
"Enable recovery key" : "Luba taastevõtme kasutamine",
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
{ "translations": {
|
||||
"Missing recovery key password" : "Muuda taastevõtme salasõna",
|
||||
"Please repeat the recovery key password" : "Palun korda uut taastevõtme salasõna",
|
||||
"Repeated recovery key password does not match the provided recovery key password" : "Sisestatud taastevõtme salasõna ei kattu",
|
||||
"Repeated recovery key password does not match the provided recovery key password" : "Teistkorda sisestatud taastevõtme salasõna ei kattu sisestatud taastevõtme salasõnaga",
|
||||
"Recovery key successfully enabled" : "Taastevõtme lubamine õnnestus",
|
||||
"Could not enable recovery key. Please check your recovery key password!" : "Ei suutnud taastevõtit kasutusele võtta. Palun kontrolli oma taastevõtme salasõna!",
|
||||
"Recovery key successfully disabled" : "Taastevõtme keelamine õnnestus",
|
||||
@@ -19,6 +19,8 @@
|
||||
"The current log-in password was not correct, please try again." : "Sisselogimise senine salasõna polnud õige, palun proovi uuesti.",
|
||||
"Private key password successfully updated." : "Privaatvõtme salasõna uuendamine õnnestus.",
|
||||
"Invalid private key for encryption app. Please update your private key password in your personal settings to recover access to your encrypted files." : "Krüptimisrakenduse vigane privaatvõti. Taastamaks ligipääsu krüptitud failidele palun uuenda oma isiklikest seadistustest privaatvõtme salasõna.",
|
||||
"Encryption App is enabled, but your keys are not initialized. Please log-out and log-in again." : "Krüptimisrakendus on kasutusel, kuid krüptovõtmed pole valmendatud. Palun logi välja ning uuesti sisse.",
|
||||
"Encryption app is enabled and ready" : "Krüptimisrakendus on kasutusel ja töövalmis",
|
||||
"Bad Signature" : "Vigane allkiri",
|
||||
"Missing Signature" : "Allkiri puudub",
|
||||
"one-time password for server-side-encryption" : "ühekordne salasõna serveripoolse krüptimise jaoks",
|
||||
@@ -27,6 +29,7 @@
|
||||
"The administration enabled server-side-encryption. Your files were encrypted using the password \"%s\"." : "Serveri peakasutaja lülitas sisse serveripoolse krüptimise. Sinu failid on krüptitud salasõnaga „%s“.",
|
||||
"Default encryption module" : "Vaikimisi krüptimismoodul",
|
||||
"Default encryption module for server-side encryption" : "Vaikimisi krüptimismoodul serveripoolse krüptimise jaoks",
|
||||
"Encryption app is enabled but your keys are not initialized, please log-out and log-in again" : "Krüptimisrakendus on kasutusel, kuid krüptovõtmed pole valmendatud. Palun logi välja ning uuesti sisse.",
|
||||
"Encrypt the home storage" : "Krüpti ka sisemine andmeruum",
|
||||
"Enabling this option encrypts all files stored on the main storage, otherwise only files on external storage will be encrypted" : "Selle valiku kasutamisel krüptitakse failid sisemises ja välises andmeruumis. Vastasel juhul vaid välises andmeruumis.",
|
||||
"Enable recovery key" : "Luba taastevõtme kasutamine",
|
||||
|
||||
@@ -2,16 +2,16 @@ OC.L10N.register(
|
||||
"encryption",
|
||||
{
|
||||
"Missing recovery key password" : "Pazudusi atkopšanas atslēgas parole",
|
||||
"Please repeat the recovery key password" : "Lūdzu atkārtot atgūšanas atslēgas paroli",
|
||||
"Please repeat the recovery key password" : "Lūgums atkārtot atkopes atslēgas paroli",
|
||||
"Repeated recovery key password does not match the provided recovery key password" : "Atkārtota atkopšanas atslēgas parole nesakrīt ar izsniegto atkopšanas atslēgu paroli",
|
||||
"Recovery key successfully enabled" : "Atkopšanas atslēga ir veiksmīgi iespējota",
|
||||
"Could not enable recovery key. Please check your recovery key password!" : "Atkopšanas atslēgu nevarēja iespējot. Lūdzu, pārbaudiet atkopšanas atslēgas paroli!",
|
||||
"Could not enable recovery key. Please check your recovery key password!" : "Atkopes atslēgu nevarēja iespējot. Lūgums pārbaudīt atkopes atslēgas paroli.",
|
||||
"Recovery key successfully disabled" : "Atkopšanas atslēga ir veiksmīgi deaktivizēta",
|
||||
"Could not disable recovery key. Please check your recovery key password!" : "Atkopšanas atslēgu nevarēja atspējot. Lūdzu, pārbaudiet atkopšanas atslēgas paroli!",
|
||||
"Could not disable recovery key. Please check your recovery key password!" : "Atkopes atslēgu nevarēja atspējot. Lūgums pārbaudīt atkopes atslēgas paroli.",
|
||||
"Missing parameters" : "Trūkstošos parametrs",
|
||||
"Please provide the old recovery password" : "Lūgums norādīt iepriekšējo atkopes paroli",
|
||||
"Please provide a new recovery password" : "Lūdzu, ievadiet jaunu paroli",
|
||||
"Please repeat the new recovery password" : "Lūdzu, atkārtojiet jauno atkopšanas paroli",
|
||||
"Please provide a new recovery password" : "Lūgums norādīt jaunu atkopes paroli",
|
||||
"Please repeat the new recovery password" : "Lūgums atkārtot jauno atkopes paroli",
|
||||
"Password successfully changed." : "Parole veiksmīgi nomainīta.",
|
||||
"Could not change the password. Maybe the old password was not correct." : "Nevarēja mainīt paroli. Varbūt vecā parole nav pareiza.",
|
||||
"Recovery Key disabled" : "Atkopšanas atslēga deaktivizēta",
|
||||
|
||||
@@ -1,15 +1,15 @@
|
||||
{ "translations": {
|
||||
"Missing recovery key password" : "Pazudusi atkopšanas atslēgas parole",
|
||||
"Please repeat the recovery key password" : "Lūdzu atkārtot atgūšanas atslēgas paroli",
|
||||
"Please repeat the recovery key password" : "Lūgums atkārtot atkopes atslēgas paroli",
|
||||
"Repeated recovery key password does not match the provided recovery key password" : "Atkārtota atkopšanas atslēgas parole nesakrīt ar izsniegto atkopšanas atslēgu paroli",
|
||||
"Recovery key successfully enabled" : "Atkopšanas atslēga ir veiksmīgi iespējota",
|
||||
"Could not enable recovery key. Please check your recovery key password!" : "Atkopšanas atslēgu nevarēja iespējot. Lūdzu, pārbaudiet atkopšanas atslēgas paroli!",
|
||||
"Could not enable recovery key. Please check your recovery key password!" : "Atkopes atslēgu nevarēja iespējot. Lūgums pārbaudīt atkopes atslēgas paroli.",
|
||||
"Recovery key successfully disabled" : "Atkopšanas atslēga ir veiksmīgi deaktivizēta",
|
||||
"Could not disable recovery key. Please check your recovery key password!" : "Atkopšanas atslēgu nevarēja atspējot. Lūdzu, pārbaudiet atkopšanas atslēgas paroli!",
|
||||
"Could not disable recovery key. Please check your recovery key password!" : "Atkopes atslēgu nevarēja atspējot. Lūgums pārbaudīt atkopes atslēgas paroli.",
|
||||
"Missing parameters" : "Trūkstošos parametrs",
|
||||
"Please provide the old recovery password" : "Lūgums norādīt iepriekšējo atkopes paroli",
|
||||
"Please provide a new recovery password" : "Lūdzu, ievadiet jaunu paroli",
|
||||
"Please repeat the new recovery password" : "Lūdzu, atkārtojiet jauno atkopšanas paroli",
|
||||
"Please provide a new recovery password" : "Lūgums norādīt jaunu atkopes paroli",
|
||||
"Please repeat the new recovery password" : "Lūgums atkārtot jauno atkopes paroli",
|
||||
"Password successfully changed." : "Parole veiksmīgi nomainīta.",
|
||||
"Could not change the password. Maybe the old password was not correct." : "Nevarēja mainīt paroli. Varbūt vecā parole nav pareiza.",
|
||||
"Recovery Key disabled" : "Atkopšanas atslēga deaktivizēta",
|
||||
|
||||
@@ -21,22 +21,28 @@ OC.L10N.register(
|
||||
"The old password was not correct, please try again." : "Het oude wachtwoord was onjuist, probeer het opnieuw.",
|
||||
"The current log-in password was not correct, please try again." : "Het huidige inlogwachtwoord was niet juist, probeer het opnieuw.",
|
||||
"Private key password successfully updated." : "Privésleutel succesvol bijgewerkt.",
|
||||
"Invalid private key for encryption app. Please update your private key password in your personal settings to recover access to your encrypted files." : "Ongeldige privésleutel voor de crypto app. Werk het privésleutel wachtwoord bij in je persoonlijke instellingen om opnieuw toegang te krijgen tot je versleutelde bestanden.",
|
||||
"Encryption App is enabled, but your keys are not initialized. Please log-out and log-in again." : "Crypto app is ingeschakeld, maar je sleutels werden niet geïnitialiseerd. Log uit en log daarna opnieuw in.",
|
||||
"Please enable server side encryption in the admin settings in order to use the encryption module." : "Activeer de server-encryptie in de beheerdersinstellingen om de encryptiemodule te kunnen gebruiken.",
|
||||
"Encryption app is enabled and ready" : "Encryptie app is ingeschakeld en gereed",
|
||||
"Invalid private key for encryption app. Please update your private key password in your personal settings to recover access to your encrypted files." : "Ongeldige persoonlijke sleutel voor de versleutelingsapp. Werk de persoonlijke wachtwoordsleutel bij in je persoonlijke instellingen om opnieuw toegang te krijgen tot je versleutelde bestanden.",
|
||||
"Encryption App is enabled, but your keys are not initialized. Please log-out and log-in again." : "Versleutelingsapp is ingeschakeld, maar je sleutels werden niet geïnitialiseerd. Log uit en log daarna opnieuw in.",
|
||||
"Please enable server side encryption in the admin settings in order to use the encryption module." : "Schakel versleuteling aan de serverzijde in de beheerdersinstellingen in om de versleutelingsmodule te gebruiken.",
|
||||
"Encryption app is enabled and ready" : "Versleutelingsapp is ingeschakeld en gereed",
|
||||
"Bad Signature" : "Verkeerde handtekening",
|
||||
"Missing Signature" : "Missende ondertekening",
|
||||
"one-time password for server-side-encryption" : "eenmalig wachtwoord voor server-side versleuteling",
|
||||
"one-time password for server-side-encryption" : "eenmalig wachtwoord voor versleuteling aan de serverzijde",
|
||||
"Encryption password" : "Versleutelingswachtwoord",
|
||||
"The administration enabled server-side-encryption. Your files were encrypted using the password <strong>%s</strong>." : "De beheerder heeft versleuteling aan de serverzijde mogelijk gemaakt. Je bestanden zijn gecodeerd met behulp van het wachtwoord <strong>%s</strong>.",
|
||||
"The administration enabled server-side-encryption. Your files were encrypted using the password \"%s\"." : "De beheerder heeft versleuteling aan de serverzijde mogelijk gemaakt. Je bestanden zijn gecodeerd met behulp van het wachtwoord \"%s\".",
|
||||
"Please login to the web interface, go to the \"Security\" section of your personal settings and update your encryption password by entering this password into the \"Old login password\" field and your current login password." : "Log in op de webinterface, ga naar het gedeelte 'Beveiliging' van je persoonlijke instellingen en update je coderingswachtwoord door dit wachtwoord in te voeren in het veld 'Oud inlogwachtwoord' en je huidige inlogwachtwoord.",
|
||||
"Cannot decrypt this file, probably this is a shared file. Please ask the file owner to reshare the file with you." : "Kan dit bestand niet ontcijferen, waarschijnlijk is het een gedeeld bestand, Vraag de eigenaar om het bestand opnieuw met je te delen.",
|
||||
"Cannot read this file, probably this is a shared file. Please ask the file owner to reshare the file with you." : "Kan dit bestand niet lezen, waarschijnlijk is het een gedeeld bestand. Vraag de eigenaar om het bestand opnieuw met je te delen.",
|
||||
"Default encryption module" : "Standaard cryptomodule",
|
||||
"Default encryption module for server-side encryption" : "Standaard cryptomodule voor server-side versleuteling",
|
||||
"Default encryption module" : "Standaard versleutelingsmodule",
|
||||
"Default encryption module for server-side encryption" : "Standaard module voor versleuteling aan de serverzijde",
|
||||
"In order to use this encryption module you need to enable server-side encryption in the admin settings. Once enabled this module will encrypt all your files transparently. The encryption is based on AES 256 keys.\nThe module will not touch existing files, only new files will be encrypted after server-side encryption was enabled. It is also not possible to disable the encryption again and switch back to an unencrypted system.\nPlease read the documentation to know all implications before you decide to enable server-side encryption." : "Om deze versleutelingsmodule te kunnen gebruiken, moet je versleuteling aan de serverzijde inschakelen in de beheerdersinstellingen. Eenmaal ingeschakeld zal deze module al je bestanden transparant versleutelen. De versleuteling is gebaseerd op AES 256-sleutels.\nDe module doet niets met bestaande bestanden, alleen nieuwe bestanden worden gecodeerd nadat versleuteling aan de serverzijde is ingeschakeld. Het is ook niet mogelijk om de codering weer uit te schakelen en terug te schakelen naar een niet-gecodeerd systeem.\nLees de documentatie om alle implicaties te kennen voordat je besluit versleuteling aan de serverzijde in te schakelen.",
|
||||
"Encryption app is enabled but your keys are not initialized, please log-out and log-in again" : "Crypto app is ingeschakeld, maar je sleutels werden niet geïnitialiseerd. Log uit en log daarna opnieuw in.",
|
||||
"Encrypt the home storage" : "Versleutel de eigen serveropslag",
|
||||
"Enabling this option encrypts all files stored on the main storage, otherwise only files on external storage will be encrypted" : "Het inschakelen van deze optie zorgt voor versleutelen van alle bestanden op de hoofdopslag, anders worden alleen bestanden op externe opslag versleuteld",
|
||||
"Enable recovery key" : "Activeer herstelsleutel",
|
||||
"Disable recovery key" : "Deactiveer herstelsleutel",
|
||||
"The recovery key is an additional encryption key used to encrypt files. It is used to recover files from an account if the password is forgotten." : "De herstelsleutel is een extra coderingssleutel die wordt gebruikt om bestanden te versleutelen. Het wordt gebruikt om bestanden van een account te herstellen als het wachtwoord wordt vergeten.",
|
||||
"Recovery key password" : "Wachtwoord herstelsleulel",
|
||||
"Repeat recovery key password" : "Herhaal wachtwoord herstelsleutel",
|
||||
"Change recovery key password:" : "Wijzig wachtwoord herstelsleutel:",
|
||||
@@ -44,9 +50,10 @@ OC.L10N.register(
|
||||
"New recovery key password" : "Nieuwe wachtwoord herstelsleutel",
|
||||
"Repeat new recovery key password" : "Herhaal nieuwe wachtwoord herstelsleutel",
|
||||
"Change Password" : "Wijzigen wachtwoord",
|
||||
"Basic encryption module" : "Basis versleutelingsmodule",
|
||||
"Basic encryption module" : "Basis-versleutelingsmodule",
|
||||
"Your private key password no longer matches your log-in password." : "Het wachtwoord van je privésleutel komt niet meer overeen met je inlogwachtwoord.",
|
||||
"Set your old private key password to your current log-in password:" : "Stel het wachtwoord van je oude privésleutel in op je huidige inlogwachtwoord.",
|
||||
"If you do not remember your old password you can ask your administrator to recover your files." : "Als je je oude wachtwoord niet meer weet, kun je de beheerder vragen je bestanden te herstellen.",
|
||||
"Old log-in password" : "Oude wachtwoord",
|
||||
"Current log-in password" : "Huidige wachtwoord",
|
||||
"Update Private Key Password" : "Bijwerken wachtwoord Privésleutel",
|
||||
|
||||
@@ -19,22 +19,28 @@
|
||||
"The old password was not correct, please try again." : "Het oude wachtwoord was onjuist, probeer het opnieuw.",
|
||||
"The current log-in password was not correct, please try again." : "Het huidige inlogwachtwoord was niet juist, probeer het opnieuw.",
|
||||
"Private key password successfully updated." : "Privésleutel succesvol bijgewerkt.",
|
||||
"Invalid private key for encryption app. Please update your private key password in your personal settings to recover access to your encrypted files." : "Ongeldige privésleutel voor de crypto app. Werk het privésleutel wachtwoord bij in je persoonlijke instellingen om opnieuw toegang te krijgen tot je versleutelde bestanden.",
|
||||
"Encryption App is enabled, but your keys are not initialized. Please log-out and log-in again." : "Crypto app is ingeschakeld, maar je sleutels werden niet geïnitialiseerd. Log uit en log daarna opnieuw in.",
|
||||
"Please enable server side encryption in the admin settings in order to use the encryption module." : "Activeer de server-encryptie in de beheerdersinstellingen om de encryptiemodule te kunnen gebruiken.",
|
||||
"Encryption app is enabled and ready" : "Encryptie app is ingeschakeld en gereed",
|
||||
"Invalid private key for encryption app. Please update your private key password in your personal settings to recover access to your encrypted files." : "Ongeldige persoonlijke sleutel voor de versleutelingsapp. Werk de persoonlijke wachtwoordsleutel bij in je persoonlijke instellingen om opnieuw toegang te krijgen tot je versleutelde bestanden.",
|
||||
"Encryption App is enabled, but your keys are not initialized. Please log-out and log-in again." : "Versleutelingsapp is ingeschakeld, maar je sleutels werden niet geïnitialiseerd. Log uit en log daarna opnieuw in.",
|
||||
"Please enable server side encryption in the admin settings in order to use the encryption module." : "Schakel versleuteling aan de serverzijde in de beheerdersinstellingen in om de versleutelingsmodule te gebruiken.",
|
||||
"Encryption app is enabled and ready" : "Versleutelingsapp is ingeschakeld en gereed",
|
||||
"Bad Signature" : "Verkeerde handtekening",
|
||||
"Missing Signature" : "Missende ondertekening",
|
||||
"one-time password for server-side-encryption" : "eenmalig wachtwoord voor server-side versleuteling",
|
||||
"one-time password for server-side-encryption" : "eenmalig wachtwoord voor versleuteling aan de serverzijde",
|
||||
"Encryption password" : "Versleutelingswachtwoord",
|
||||
"The administration enabled server-side-encryption. Your files were encrypted using the password <strong>%s</strong>." : "De beheerder heeft versleuteling aan de serverzijde mogelijk gemaakt. Je bestanden zijn gecodeerd met behulp van het wachtwoord <strong>%s</strong>.",
|
||||
"The administration enabled server-side-encryption. Your files were encrypted using the password \"%s\"." : "De beheerder heeft versleuteling aan de serverzijde mogelijk gemaakt. Je bestanden zijn gecodeerd met behulp van het wachtwoord \"%s\".",
|
||||
"Please login to the web interface, go to the \"Security\" section of your personal settings and update your encryption password by entering this password into the \"Old login password\" field and your current login password." : "Log in op de webinterface, ga naar het gedeelte 'Beveiliging' van je persoonlijke instellingen en update je coderingswachtwoord door dit wachtwoord in te voeren in het veld 'Oud inlogwachtwoord' en je huidige inlogwachtwoord.",
|
||||
"Cannot decrypt this file, probably this is a shared file. Please ask the file owner to reshare the file with you." : "Kan dit bestand niet ontcijferen, waarschijnlijk is het een gedeeld bestand, Vraag de eigenaar om het bestand opnieuw met je te delen.",
|
||||
"Cannot read this file, probably this is a shared file. Please ask the file owner to reshare the file with you." : "Kan dit bestand niet lezen, waarschijnlijk is het een gedeeld bestand. Vraag de eigenaar om het bestand opnieuw met je te delen.",
|
||||
"Default encryption module" : "Standaard cryptomodule",
|
||||
"Default encryption module for server-side encryption" : "Standaard cryptomodule voor server-side versleuteling",
|
||||
"Default encryption module" : "Standaard versleutelingsmodule",
|
||||
"Default encryption module for server-side encryption" : "Standaard module voor versleuteling aan de serverzijde",
|
||||
"In order to use this encryption module you need to enable server-side encryption in the admin settings. Once enabled this module will encrypt all your files transparently. The encryption is based on AES 256 keys.\nThe module will not touch existing files, only new files will be encrypted after server-side encryption was enabled. It is also not possible to disable the encryption again and switch back to an unencrypted system.\nPlease read the documentation to know all implications before you decide to enable server-side encryption." : "Om deze versleutelingsmodule te kunnen gebruiken, moet je versleuteling aan de serverzijde inschakelen in de beheerdersinstellingen. Eenmaal ingeschakeld zal deze module al je bestanden transparant versleutelen. De versleuteling is gebaseerd op AES 256-sleutels.\nDe module doet niets met bestaande bestanden, alleen nieuwe bestanden worden gecodeerd nadat versleuteling aan de serverzijde is ingeschakeld. Het is ook niet mogelijk om de codering weer uit te schakelen en terug te schakelen naar een niet-gecodeerd systeem.\nLees de documentatie om alle implicaties te kennen voordat je besluit versleuteling aan de serverzijde in te schakelen.",
|
||||
"Encryption app is enabled but your keys are not initialized, please log-out and log-in again" : "Crypto app is ingeschakeld, maar je sleutels werden niet geïnitialiseerd. Log uit en log daarna opnieuw in.",
|
||||
"Encrypt the home storage" : "Versleutel de eigen serveropslag",
|
||||
"Enabling this option encrypts all files stored on the main storage, otherwise only files on external storage will be encrypted" : "Het inschakelen van deze optie zorgt voor versleutelen van alle bestanden op de hoofdopslag, anders worden alleen bestanden op externe opslag versleuteld",
|
||||
"Enable recovery key" : "Activeer herstelsleutel",
|
||||
"Disable recovery key" : "Deactiveer herstelsleutel",
|
||||
"The recovery key is an additional encryption key used to encrypt files. It is used to recover files from an account if the password is forgotten." : "De herstelsleutel is een extra coderingssleutel die wordt gebruikt om bestanden te versleutelen. Het wordt gebruikt om bestanden van een account te herstellen als het wachtwoord wordt vergeten.",
|
||||
"Recovery key password" : "Wachtwoord herstelsleulel",
|
||||
"Repeat recovery key password" : "Herhaal wachtwoord herstelsleutel",
|
||||
"Change recovery key password:" : "Wijzig wachtwoord herstelsleutel:",
|
||||
@@ -42,9 +48,10 @@
|
||||
"New recovery key password" : "Nieuwe wachtwoord herstelsleutel",
|
||||
"Repeat new recovery key password" : "Herhaal nieuwe wachtwoord herstelsleutel",
|
||||
"Change Password" : "Wijzigen wachtwoord",
|
||||
"Basic encryption module" : "Basis versleutelingsmodule",
|
||||
"Basic encryption module" : "Basis-versleutelingsmodule",
|
||||
"Your private key password no longer matches your log-in password." : "Het wachtwoord van je privésleutel komt niet meer overeen met je inlogwachtwoord.",
|
||||
"Set your old private key password to your current log-in password:" : "Stel het wachtwoord van je oude privésleutel in op je huidige inlogwachtwoord.",
|
||||
"If you do not remember your old password you can ask your administrator to recover your files." : "Als je je oude wachtwoord niet meer weet, kun je de beheerder vragen je bestanden te herstellen.",
|
||||
"Old log-in password" : "Oude wachtwoord",
|
||||
"Current log-in password" : "Huidige wachtwoord",
|
||||
"Update Private Key Password" : "Bijwerken wachtwoord Privésleutel",
|
||||
|
||||
@@ -12,6 +12,7 @@ use OC\Files\View;
|
||||
use OCA\Encryption\KeyManager;
|
||||
use OCA\Encryption\Users\Setup;
|
||||
use OCA\Encryption\Util;
|
||||
use OCP\Files\FileInfo;
|
||||
use OCP\IConfig;
|
||||
use OCP\IL10N;
|
||||
use OCP\IUser;
|
||||
@@ -202,15 +203,19 @@ class EncryptAll {
|
||||
while ($root = array_pop($directories)) {
|
||||
$content = $this->rootView->getDirectoryContent($root);
|
||||
foreach ($content as $file) {
|
||||
$path = $root . '/' . $file['name'];
|
||||
if ($this->rootView->is_dir($path)) {
|
||||
$path = $root . '/' . $file->getName();
|
||||
if ($file->isShared()) {
|
||||
$progress->setMessage("Skip shared file/folder $path");
|
||||
$progress->advance();
|
||||
continue;
|
||||
} elseif ($file->getType() === FileInfo::TYPE_FOLDER) {
|
||||
$directories[] = $path;
|
||||
continue;
|
||||
} else {
|
||||
$progress->setMessage("encrypt files for user $userCount: $path");
|
||||
$progress->advance();
|
||||
try {
|
||||
if ($this->encryptFile($path) === false) {
|
||||
if ($this->encryptFile($file, $path) === false) {
|
||||
$progress->setMessage("encrypt files for user $userCount: $path (already encrypted)");
|
||||
$progress->advance();
|
||||
}
|
||||
@@ -231,17 +236,9 @@ class EncryptAll {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* encrypt file
|
||||
*
|
||||
* @param string $path
|
||||
* @return bool
|
||||
*/
|
||||
protected function encryptFile($path) {
|
||||
|
||||
protected function encryptFile(FileInfo $fileInfo, string $path): bool {
|
||||
// skip already encrypted files
|
||||
$fileInfo = $this->rootView->getFileInfo($path);
|
||||
if ($fileInfo !== false && $fileInfo->isEncrypted()) {
|
||||
if ($fileInfo->isEncrypted()) {
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
@@ -82,7 +82,7 @@ class EncryptAllTest extends TestCase {
|
||||
|
||||
/**
|
||||
* We need format method to return a string
|
||||
* @var OutputFormatterInterface|\PHPUnit\Framework\MockObject\MockObject
|
||||
* @var OutputFormatterInterface&MockObject
|
||||
*/
|
||||
$outputFormatter = $this->createMock(OutputFormatterInterface::class);
|
||||
$outputFormatter->method('isDecorated')->willReturn(false);
|
||||
@@ -114,6 +114,13 @@ class EncryptAllTest extends TestCase {
|
||||
);
|
||||
}
|
||||
|
||||
protected function createFileInfoMock($type, string $name): FileInfo&MockObject {
|
||||
$fileInfo = $this->createMock(FileInfo::class);
|
||||
$fileInfo->method('getType')->willReturn($type);
|
||||
$fileInfo->method('getName')->willReturn($name);
|
||||
return $fileInfo;
|
||||
}
|
||||
|
||||
public function testEncryptAll(): void {
|
||||
/** @var EncryptAll&MockObject $encryptAll */
|
||||
$encryptAll = $this->getMockBuilder(EncryptAll::class)
|
||||
@@ -299,8 +306,8 @@ class EncryptAllTest extends TestCase {
|
||||
'',
|
||||
null,
|
||||
[
|
||||
['name' => 'foo', 'type' => 'dir'],
|
||||
['name' => 'bar', 'type' => 'file'],
|
||||
$this->createFileInfoMock(FileInfo::TYPE_FOLDER, 'foo'),
|
||||
$this->createFileInfoMock(FileInfo::TYPE_FILE, 'bar'),
|
||||
],
|
||||
],
|
||||
[
|
||||
@@ -308,26 +315,17 @@ class EncryptAllTest extends TestCase {
|
||||
'',
|
||||
null,
|
||||
[
|
||||
['name' => 'subfile', 'type' => 'file']
|
||||
$this->createFileInfoMock(FileInfo::TYPE_FILE, 'subfile'),
|
||||
],
|
||||
],
|
||||
]);
|
||||
|
||||
$this->view->expects($this->any())->method('is_dir')
|
||||
->willReturnCallback(
|
||||
function ($path) {
|
||||
if ($path === '/user1/files/foo') {
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
);
|
||||
|
||||
$encryptAllCalls = [];
|
||||
$encryptAll->expects($this->exactly(2))
|
||||
->method('encryptFile')
|
||||
->willReturnCallback(function (string $path) use (&$encryptAllCalls): void {
|
||||
->willReturnCallback(function (FileInfo $file, string $path) use (&$encryptAllCalls): bool {
|
||||
$encryptAllCalls[] = $path;
|
||||
return true;
|
||||
});
|
||||
|
||||
$outputFormatter = $this->createMock(OutputFormatterInterface::class);
|
||||
@@ -362,8 +360,7 @@ class EncryptAllTest extends TestCase {
|
||||
$fileInfo = $this->createMock(FileInfo::class);
|
||||
$fileInfo->expects($this->any())->method('isEncrypted')
|
||||
->willReturn($isEncrypted);
|
||||
$this->view->expects($this->any())->method('getFileInfo')
|
||||
->willReturn($fileInfo);
|
||||
$this->view->expects($this->never())->method('getFileInfo');
|
||||
|
||||
|
||||
if ($isEncrypted) {
|
||||
@@ -375,7 +372,7 @@ class EncryptAllTest extends TestCase {
|
||||
}
|
||||
|
||||
$this->assertTrue(
|
||||
$this->invokePrivate($this->encryptAll, 'encryptFile', ['foo.txt'])
|
||||
$this->invokePrivate($this->encryptAll, 'encryptFile', [$fileInfo, 'foo.txt'])
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
@@ -47,8 +47,7 @@ OC.L10N.register(
|
||||
"Share with me through my #Nextcloud Federated Cloud ID, see {url}" : "شارك معي عبر #مُعرّف سحابة نكست كلاود الاتحادية، أنظُر {url} ",
|
||||
"Share with me through my #Nextcloud Federated Cloud ID" : "شارك معي عبر #مُعرّف سحابة نكست كلاود الاتحادية",
|
||||
"Share with me via Nextcloud" : "شاركه معي عبر النكست كلاود",
|
||||
"Cloud ID copied to the clipboard" : "تمّ نسخ مُعرِّف السحابة إلى الحافظة",
|
||||
"Copy to clipboard" : "نسخ الرابط",
|
||||
"Copy" : "إنسَخ",
|
||||
"Clipboard not available. Please copy the cloud ID manually." : "الحافظة غير متوفرة. رجاءً، قم بنسخ مُعرِّف السحابة يدوياً.",
|
||||
"Copied!" : "تمَّ النسخ!",
|
||||
"Federated Cloud" : "السحابة الاتحادية",
|
||||
@@ -66,6 +65,8 @@ OC.L10N.register(
|
||||
"Remote share" : "مُشاركة بعيدة remote",
|
||||
"Do you want to add the remote share {name} from {owner}@{remote}?" : "هل ترغب في إضافة مُشاركة بعيدة remote ـ {name} من {owner}@{remote}؟",
|
||||
"Remote share password" : "كلمة مرور المشاركة البعيدة remote",
|
||||
"Incoming share could not be processed" : "لا يمكن معالجة المشاركة الواردة"
|
||||
"Incoming share could not be processed" : "لا يمكن معالجة المشاركة الواردة",
|
||||
"Cloud ID copied to the clipboard" : "تمّ نسخ مُعرِّف السحابة إلى الحافظة",
|
||||
"Copy to clipboard" : "نسخ الرابط"
|
||||
},
|
||||
"nplurals=6; plural=n==0 ? 0 : n==1 ? 1 : n==2 ? 2 : n%100>=3 && n%100<=10 ? 3 : n%100>=11 && n%100<=99 ? 4 : 5;");
|
||||
|
||||
@@ -45,8 +45,7 @@
|
||||
"Share with me through my #Nextcloud Federated Cloud ID, see {url}" : "شارك معي عبر #مُعرّف سحابة نكست كلاود الاتحادية، أنظُر {url} ",
|
||||
"Share with me through my #Nextcloud Federated Cloud ID" : "شارك معي عبر #مُعرّف سحابة نكست كلاود الاتحادية",
|
||||
"Share with me via Nextcloud" : "شاركه معي عبر النكست كلاود",
|
||||
"Cloud ID copied to the clipboard" : "تمّ نسخ مُعرِّف السحابة إلى الحافظة",
|
||||
"Copy to clipboard" : "نسخ الرابط",
|
||||
"Copy" : "إنسَخ",
|
||||
"Clipboard not available. Please copy the cloud ID manually." : "الحافظة غير متوفرة. رجاءً، قم بنسخ مُعرِّف السحابة يدوياً.",
|
||||
"Copied!" : "تمَّ النسخ!",
|
||||
"Federated Cloud" : "السحابة الاتحادية",
|
||||
@@ -64,6 +63,8 @@
|
||||
"Remote share" : "مُشاركة بعيدة remote",
|
||||
"Do you want to add the remote share {name} from {owner}@{remote}?" : "هل ترغب في إضافة مُشاركة بعيدة remote ـ {name} من {owner}@{remote}؟",
|
||||
"Remote share password" : "كلمة مرور المشاركة البعيدة remote",
|
||||
"Incoming share could not be processed" : "لا يمكن معالجة المشاركة الواردة"
|
||||
"Incoming share could not be processed" : "لا يمكن معالجة المشاركة الواردة",
|
||||
"Cloud ID copied to the clipboard" : "تمّ نسخ مُعرِّف السحابة إلى الحافظة",
|
||||
"Copy to clipboard" : "نسخ الرابط"
|
||||
},"pluralForm" :"nplurals=6; plural=n==0 ? 0 : n==1 ? 1 : n==2 ? 2 : n%100>=3 && n%100<=10 ? 3 : n%100>=11 && n%100<=99 ? 4 : 5;"
|
||||
}
|
||||
@@ -34,8 +34,7 @@ OC.L10N.register(
|
||||
"Share with me through my #Nextcloud Federated Cloud ID, see {url}" : "Compartir conmigo pente la mio ID de nube federada de #Nextcloud, mira {url}",
|
||||
"Share with me through my #Nextcloud Federated Cloud ID" : "Compartir conmigo pente la mio ID de nube federada de #Nextcloud",
|
||||
"Share with me via Nextcloud" : "Compartir conmigo per Nextcloud",
|
||||
"Cloud ID copied to the clipboard" : "La ID de la nube copióse nel cartafueyu",
|
||||
"Copy to clipboard" : "Copiar nel cartafueyu",
|
||||
"Copy" : "Copiar",
|
||||
"Copied!" : "¡Copióse!",
|
||||
"Federated Cloud" : "Nube federada",
|
||||
"You can share with anyone who uses a Nextcloud server or other Open Cloud Mesh (OCM) compatible servers and services! Just put their Federated Cloud ID in the share dialog. It looks like person@cloud.example.com" : "Pues compartir conteníu con cualesquier persona qu'use un sirvidor de Nextcloud o otros sirvidores y servicios compatibles con Open Cloud Mesh (OCM). Namás indica la ID de na nube federada nel cuadru de diálogu d'usu compartíu. Aseméyase a persona@nube.exemplu.com",
|
||||
@@ -49,6 +48,8 @@ OC.L10N.register(
|
||||
"Add remote share" : "Amestar un elementu compartíu remotu",
|
||||
"Remote share" : "Compartición remota",
|
||||
"Do you want to add the remote share {name} from {owner}@{remote}?" : "¿Quies amestar la compartición remota «{name}» de {owner}@{remote}?",
|
||||
"Remote share password" : "Contraseña de la compartición remota"
|
||||
"Remote share password" : "Contraseña de la compartición remota",
|
||||
"Cloud ID copied to the clipboard" : "La ID de la nube copióse nel cartafueyu",
|
||||
"Copy to clipboard" : "Copiar nel cartafueyu"
|
||||
},
|
||||
"nplurals=2; plural=(n != 1);");
|
||||
|
||||
@@ -32,8 +32,7 @@
|
||||
"Share with me through my #Nextcloud Federated Cloud ID, see {url}" : "Compartir conmigo pente la mio ID de nube federada de #Nextcloud, mira {url}",
|
||||
"Share with me through my #Nextcloud Federated Cloud ID" : "Compartir conmigo pente la mio ID de nube federada de #Nextcloud",
|
||||
"Share with me via Nextcloud" : "Compartir conmigo per Nextcloud",
|
||||
"Cloud ID copied to the clipboard" : "La ID de la nube copióse nel cartafueyu",
|
||||
"Copy to clipboard" : "Copiar nel cartafueyu",
|
||||
"Copy" : "Copiar",
|
||||
"Copied!" : "¡Copióse!",
|
||||
"Federated Cloud" : "Nube federada",
|
||||
"You can share with anyone who uses a Nextcloud server or other Open Cloud Mesh (OCM) compatible servers and services! Just put their Federated Cloud ID in the share dialog. It looks like person@cloud.example.com" : "Pues compartir conteníu con cualesquier persona qu'use un sirvidor de Nextcloud o otros sirvidores y servicios compatibles con Open Cloud Mesh (OCM). Namás indica la ID de na nube federada nel cuadru de diálogu d'usu compartíu. Aseméyase a persona@nube.exemplu.com",
|
||||
@@ -47,6 +46,8 @@
|
||||
"Add remote share" : "Amestar un elementu compartíu remotu",
|
||||
"Remote share" : "Compartición remota",
|
||||
"Do you want to add the remote share {name} from {owner}@{remote}?" : "¿Quies amestar la compartición remota «{name}» de {owner}@{remote}?",
|
||||
"Remote share password" : "Contraseña de la compartición remota"
|
||||
"Remote share password" : "Contraseña de la compartición remota",
|
||||
"Cloud ID copied to the clipboard" : "La ID de la nube copióse nel cartafueyu",
|
||||
"Copy to clipboard" : "Copiar nel cartafueyu"
|
||||
},"pluralForm" :"nplurals=2; plural=(n != 1);"
|
||||
}
|
||||
@@ -26,8 +26,7 @@ OC.L10N.register(
|
||||
"Share with me through my #Nextcloud Federated Cloud ID, see {url}" : "Споделете с мен чрез моя #Nextcloud Federated Cloud ID, вижте {url}",
|
||||
"Share with me through my #Nextcloud Federated Cloud ID" : "Споделете с мен, чрез моя #Nextcloud Federated Cloud ID",
|
||||
"Share with me via Nextcloud" : "Споделете с мен, чрез Nextcloud",
|
||||
"Cloud ID copied to the clipboard" : "Cloud идентификатора е копиран в клипборда",
|
||||
"Copy to clipboard" : "Копиране в клипборда",
|
||||
"Copy" : "Копие",
|
||||
"Copied!" : "Копирано!",
|
||||
"Federated Cloud" : "Федериран облак",
|
||||
"You can share with anyone who uses a Nextcloud server or other Open Cloud Mesh (OCM) compatible servers and services! Just put their Federated Cloud ID in the share dialog. It looks like person@cloud.example.com" : "Можете да споделяте с всеки, който използва сървър Nextcloud или други сървъри и услуги, съвместими с Open Cloud Mesh (OCM)! Просто поставете техния идентификатор за Федериран облак в диалоговия прозорец за споделяне. Изглежда като person@cloud.example.com",
|
||||
@@ -40,6 +39,8 @@ OC.L10N.register(
|
||||
"Add remote share" : "Добави отдалечено споделяне",
|
||||
"Remote share" : "Отдалечено споделяне",
|
||||
"Do you want to add the remote share {name} from {owner}@{remote}?" : "Искате ли да добавите отдалечено споделяне {name} от {owner}@{remote}?",
|
||||
"Remote share password" : "Парола за отдалечено споделяне"
|
||||
"Remote share password" : "Парола за отдалечено споделяне",
|
||||
"Cloud ID copied to the clipboard" : "Cloud идентификатора е копиран в клипборда",
|
||||
"Copy to clipboard" : "Копиране в клипборда"
|
||||
},
|
||||
"nplurals=2; plural=(n != 1);");
|
||||
|
||||
@@ -24,8 +24,7 @@
|
||||
"Share with me through my #Nextcloud Federated Cloud ID, see {url}" : "Споделете с мен чрез моя #Nextcloud Federated Cloud ID, вижте {url}",
|
||||
"Share with me through my #Nextcloud Federated Cloud ID" : "Споделете с мен, чрез моя #Nextcloud Federated Cloud ID",
|
||||
"Share with me via Nextcloud" : "Споделете с мен, чрез Nextcloud",
|
||||
"Cloud ID copied to the clipboard" : "Cloud идентификатора е копиран в клипборда",
|
||||
"Copy to clipboard" : "Копиране в клипборда",
|
||||
"Copy" : "Копие",
|
||||
"Copied!" : "Копирано!",
|
||||
"Federated Cloud" : "Федериран облак",
|
||||
"You can share with anyone who uses a Nextcloud server or other Open Cloud Mesh (OCM) compatible servers and services! Just put their Federated Cloud ID in the share dialog. It looks like person@cloud.example.com" : "Можете да споделяте с всеки, който използва сървър Nextcloud или други сървъри и услуги, съвместими с Open Cloud Mesh (OCM)! Просто поставете техния идентификатор за Федериран облак в диалоговия прозорец за споделяне. Изглежда като person@cloud.example.com",
|
||||
@@ -38,6 +37,8 @@
|
||||
"Add remote share" : "Добави отдалечено споделяне",
|
||||
"Remote share" : "Отдалечено споделяне",
|
||||
"Do you want to add the remote share {name} from {owner}@{remote}?" : "Искате ли да добавите отдалечено споделяне {name} от {owner}@{remote}?",
|
||||
"Remote share password" : "Парола за отдалечено споделяне"
|
||||
"Remote share password" : "Парола за отдалечено споделяне",
|
||||
"Cloud ID copied to the clipboard" : "Cloud идентификатора е копиран в клипборда",
|
||||
"Copy to clipboard" : "Копиране в клипборда"
|
||||
},"pluralForm" :"nplurals=2; plural=(n != 1);"
|
||||
}
|
||||
@@ -37,8 +37,7 @@ OC.L10N.register(
|
||||
"Share with me through my #Nextcloud Federated Cloud ID, see {url}" : "Comparteix contingut amb mi amb el meu ID del núvol federat del #Nextcloud, consulta {url}",
|
||||
"Share with me through my #Nextcloud Federated Cloud ID" : "Comparteix contingut amb mi amb el meu ID del núvol federat del #Nextcloud",
|
||||
"Share with me via Nextcloud" : "Comparteix contingut amb mi a través del Nextcloud",
|
||||
"Cloud ID copied to the clipboard" : "S'ha copiat l'ID del núvol al porta-retalls",
|
||||
"Copy to clipboard" : "Copia-ho al porta-retalls",
|
||||
"Copy" : "Còpia",
|
||||
"Clipboard not available. Please copy the cloud ID manually." : "Porta-retalls no disponible. Copieu l'identificador del núvol manualment.",
|
||||
"Copied!" : "S'ha copiat!",
|
||||
"Federated Cloud" : "Núvol federat",
|
||||
@@ -56,6 +55,8 @@ OC.L10N.register(
|
||||
"Remote share" : "Element compartit remot",
|
||||
"Do you want to add the remote share {name} from {owner}@{remote}?" : "Voleu afegir l'element compartit remot {name} de {owner}@{remote}?",
|
||||
"Remote share password" : "Contrasenya de l'element compartit remot",
|
||||
"Incoming share could not be processed" : "No s'ha pogut processar la compartició entrant"
|
||||
"Incoming share could not be processed" : "No s'ha pogut processar la compartició entrant",
|
||||
"Cloud ID copied to the clipboard" : "S'ha copiat l'ID del núvol al porta-retalls",
|
||||
"Copy to clipboard" : "Copia-ho al porta-retalls"
|
||||
},
|
||||
"nplurals=2; plural=(n != 1);");
|
||||
|
||||
@@ -35,8 +35,7 @@
|
||||
"Share with me through my #Nextcloud Federated Cloud ID, see {url}" : "Comparteix contingut amb mi amb el meu ID del núvol federat del #Nextcloud, consulta {url}",
|
||||
"Share with me through my #Nextcloud Federated Cloud ID" : "Comparteix contingut amb mi amb el meu ID del núvol federat del #Nextcloud",
|
||||
"Share with me via Nextcloud" : "Comparteix contingut amb mi a través del Nextcloud",
|
||||
"Cloud ID copied to the clipboard" : "S'ha copiat l'ID del núvol al porta-retalls",
|
||||
"Copy to clipboard" : "Copia-ho al porta-retalls",
|
||||
"Copy" : "Còpia",
|
||||
"Clipboard not available. Please copy the cloud ID manually." : "Porta-retalls no disponible. Copieu l'identificador del núvol manualment.",
|
||||
"Copied!" : "S'ha copiat!",
|
||||
"Federated Cloud" : "Núvol federat",
|
||||
@@ -54,6 +53,8 @@
|
||||
"Remote share" : "Element compartit remot",
|
||||
"Do you want to add the remote share {name} from {owner}@{remote}?" : "Voleu afegir l'element compartit remot {name} de {owner}@{remote}?",
|
||||
"Remote share password" : "Contrasenya de l'element compartit remot",
|
||||
"Incoming share could not be processed" : "No s'ha pogut processar la compartició entrant"
|
||||
"Incoming share could not be processed" : "No s'ha pogut processar la compartició entrant",
|
||||
"Cloud ID copied to the clipboard" : "S'ha copiat l'ID del núvol al porta-retalls",
|
||||
"Copy to clipboard" : "Copia-ho al porta-retalls"
|
||||
},"pluralForm" :"nplurals=2; plural=(n != 1);"
|
||||
}
|
||||
@@ -47,8 +47,7 @@ OC.L10N.register(
|
||||
"Share with me through my #Nextcloud Federated Cloud ID, see {url}" : "Sdílejte se mnou prostřednictvím mého #Nextcloud identifikátoru v rámci federovaného cloudu – více na {url}",
|
||||
"Share with me through my #Nextcloud Federated Cloud ID" : "Sdílejte se mnou pomocí mého #Nextcloud identifikátoru v rámci federovaného cloudu",
|
||||
"Share with me via Nextcloud" : "Sdílet se mnou přes Nextcloud",
|
||||
"Cloud ID copied to the clipboard" : "Cloudový identifikátor zkopírován do schránky",
|
||||
"Copy to clipboard" : "Zkopírovat do schránky",
|
||||
"Copy" : "Zkopírovat",
|
||||
"Clipboard not available. Please copy the cloud ID manually." : "Schránka není k dispozici. Zkopírujte cloudový identifikátor ručně.",
|
||||
"Copied!" : "Zkopírováno",
|
||||
"Federated Cloud" : "Federovaný cloud",
|
||||
@@ -66,6 +65,8 @@ OC.L10N.register(
|
||||
"Remote share" : "Vzdálené sdílení",
|
||||
"Do you want to add the remote share {name} from {owner}@{remote}?" : "Chcete přidat vzdálené sdílení {name} od {owner}@{remote}?",
|
||||
"Remote share password" : "Heslo ke vzdálenému sdílení",
|
||||
"Incoming share could not be processed" : "Příchozí sdílení se nepodařilo zpracovat"
|
||||
"Incoming share could not be processed" : "Příchozí sdílení se nepodařilo zpracovat",
|
||||
"Cloud ID copied to the clipboard" : "Cloudový identifikátor zkopírován do schránky",
|
||||
"Copy to clipboard" : "Zkopírovat do schránky"
|
||||
},
|
||||
"nplurals=4; plural=(n == 1 && n % 1 == 0) ? 0 : (n >= 2 && n <= 4 && n % 1 == 0) ? 1: (n % 1 != 0 ) ? 2 : 3;");
|
||||
|
||||
@@ -45,8 +45,7 @@
|
||||
"Share with me through my #Nextcloud Federated Cloud ID, see {url}" : "Sdílejte se mnou prostřednictvím mého #Nextcloud identifikátoru v rámci federovaného cloudu – více na {url}",
|
||||
"Share with me through my #Nextcloud Federated Cloud ID" : "Sdílejte se mnou pomocí mého #Nextcloud identifikátoru v rámci federovaného cloudu",
|
||||
"Share with me via Nextcloud" : "Sdílet se mnou přes Nextcloud",
|
||||
"Cloud ID copied to the clipboard" : "Cloudový identifikátor zkopírován do schránky",
|
||||
"Copy to clipboard" : "Zkopírovat do schránky",
|
||||
"Copy" : "Zkopírovat",
|
||||
"Clipboard not available. Please copy the cloud ID manually." : "Schránka není k dispozici. Zkopírujte cloudový identifikátor ručně.",
|
||||
"Copied!" : "Zkopírováno",
|
||||
"Federated Cloud" : "Federovaný cloud",
|
||||
@@ -64,6 +63,8 @@
|
||||
"Remote share" : "Vzdálené sdílení",
|
||||
"Do you want to add the remote share {name} from {owner}@{remote}?" : "Chcete přidat vzdálené sdílení {name} od {owner}@{remote}?",
|
||||
"Remote share password" : "Heslo ke vzdálenému sdílení",
|
||||
"Incoming share could not be processed" : "Příchozí sdílení se nepodařilo zpracovat"
|
||||
"Incoming share could not be processed" : "Příchozí sdílení se nepodařilo zpracovat",
|
||||
"Cloud ID copied to the clipboard" : "Cloudový identifikátor zkopírován do schránky",
|
||||
"Copy to clipboard" : "Zkopírovat do schránky"
|
||||
},"pluralForm" :"nplurals=4; plural=(n == 1 && n % 1 == 0) ? 0 : (n >= 2 && n <= 4 && n % 1 == 0) ? 1: (n % 1 != 0 ) ? 2 : 3;"
|
||||
}
|
||||
@@ -47,8 +47,7 @@ OC.L10N.register(
|
||||
"Share with me through my #Nextcloud Federated Cloud ID, see {url}" : "Del med mig gennem min #Nextcloud Sammenkoblings Cloud ID, se {url}",
|
||||
"Share with me through my #Nextcloud Federated Cloud ID" : "Del med mig gennem min #Nextcloud sammenkoblings Cloud ID",
|
||||
"Share with me via Nextcloud" : "Del med mig gennem Nextcloud",
|
||||
"Cloud ID copied to the clipboard" : "Cloud ID er kopieret til udklipsholderen.",
|
||||
"Copy to clipboard" : "Kopier til udklipsholder",
|
||||
"Copy" : "Kopiér",
|
||||
"Clipboard not available. Please copy the cloud ID manually." : "Udklipsholder ikke tilgængelig. Kopier venligst Cloud ID'et manuelt.",
|
||||
"Copied!" : "Kopieret!",
|
||||
"Federated Cloud" : "Sammenkoblet Cloud",
|
||||
@@ -66,6 +65,8 @@ OC.L10N.register(
|
||||
"Remote share" : "Eksterne drev",
|
||||
"Do you want to add the remote share {name} from {owner}@{remote}?" : "Ønsker du at tilføje det eksterne drev {name} fra {owner}@{remote}?",
|
||||
"Remote share password" : "Fjerndrev adgangskode",
|
||||
"Incoming share could not be processed" : "Indgående deling kunne ikke behandles"
|
||||
"Incoming share could not be processed" : "Indgående deling kunne ikke behandles",
|
||||
"Cloud ID copied to the clipboard" : "Cloud ID er kopieret til udklipsholderen.",
|
||||
"Copy to clipboard" : "Kopier til udklipsholder"
|
||||
},
|
||||
"nplurals=2; plural=(n != 1);");
|
||||
|
||||
@@ -45,8 +45,7 @@
|
||||
"Share with me through my #Nextcloud Federated Cloud ID, see {url}" : "Del med mig gennem min #Nextcloud Sammenkoblings Cloud ID, se {url}",
|
||||
"Share with me through my #Nextcloud Federated Cloud ID" : "Del med mig gennem min #Nextcloud sammenkoblings Cloud ID",
|
||||
"Share with me via Nextcloud" : "Del med mig gennem Nextcloud",
|
||||
"Cloud ID copied to the clipboard" : "Cloud ID er kopieret til udklipsholderen.",
|
||||
"Copy to clipboard" : "Kopier til udklipsholder",
|
||||
"Copy" : "Kopiér",
|
||||
"Clipboard not available. Please copy the cloud ID manually." : "Udklipsholder ikke tilgængelig. Kopier venligst Cloud ID'et manuelt.",
|
||||
"Copied!" : "Kopieret!",
|
||||
"Federated Cloud" : "Sammenkoblet Cloud",
|
||||
@@ -64,6 +63,8 @@
|
||||
"Remote share" : "Eksterne drev",
|
||||
"Do you want to add the remote share {name} from {owner}@{remote}?" : "Ønsker du at tilføje det eksterne drev {name} fra {owner}@{remote}?",
|
||||
"Remote share password" : "Fjerndrev adgangskode",
|
||||
"Incoming share could not be processed" : "Indgående deling kunne ikke behandles"
|
||||
"Incoming share could not be processed" : "Indgående deling kunne ikke behandles",
|
||||
"Cloud ID copied to the clipboard" : "Cloud ID er kopieret til udklipsholderen.",
|
||||
"Copy to clipboard" : "Kopier til udklipsholder"
|
||||
},"pluralForm" :"nplurals=2; plural=(n != 1);"
|
||||
}
|
||||
@@ -47,8 +47,8 @@ OC.L10N.register(
|
||||
"Share with me through my #Nextcloud Federated Cloud ID, see {url}" : "Teile mit mir über meine #Nextcloud Federated-Cloud-ID, siehe {url}",
|
||||
"Share with me through my #Nextcloud Federated Cloud ID" : "Teile mit mir über meine #Nextcloud Federated-Cloud-ID",
|
||||
"Share with me via Nextcloud" : "Teile mit mir über Nextcloud",
|
||||
"Cloud ID copied to the clipboard" : "Cloud-ID in die Zwischenablage kopiert",
|
||||
"Copy to clipboard" : "In die Zwischenablage kopieren",
|
||||
"Cloud ID copied" : "Cloud-ID kopiert",
|
||||
"Copy" : "Kopieren",
|
||||
"Clipboard not available. Please copy the cloud ID manually." : "Zwischenablage nicht verfügbar. Bitte die Cloud-ID manuell kopieren.",
|
||||
"Copied!" : "Kopiert!",
|
||||
"Federated Cloud" : "Federated Cloud",
|
||||
@@ -59,6 +59,7 @@ OC.L10N.register(
|
||||
"X (formerly Twitter)" : "X (früher Twitter)",
|
||||
"formerly Twitter" : "früher Twitter",
|
||||
"Mastodon" : "Mastodon",
|
||||
"Bluesky" : "Bluesky",
|
||||
"Add to your website" : "Zu deiner Webseite hinzufügen",
|
||||
"HTML Code:" : "HTML-Code:",
|
||||
"Cancel" : "Abbrechen",
|
||||
@@ -66,6 +67,8 @@ OC.L10N.register(
|
||||
"Remote share" : "Externe Freigabe",
|
||||
"Do you want to add the remote share {name} from {owner}@{remote}?" : "Soll die externe Freigabe {name} von {owner}@{remote} hinzugefügt werden?",
|
||||
"Remote share password" : "Passwort für die externe Freigabe",
|
||||
"Incoming share could not be processed" : "Eingehende Freigabe konnte nicht verarbeitet werden"
|
||||
"Incoming share could not be processed" : "Eingehende Freigabe konnte nicht verarbeitet werden",
|
||||
"Cloud ID copied to the clipboard" : "Cloud-ID in die Zwischenablage kopiert",
|
||||
"Copy to clipboard" : "In die Zwischenablage kopieren"
|
||||
},
|
||||
"nplurals=2; plural=(n != 1);");
|
||||
|
||||
@@ -45,8 +45,8 @@
|
||||
"Share with me through my #Nextcloud Federated Cloud ID, see {url}" : "Teile mit mir über meine #Nextcloud Federated-Cloud-ID, siehe {url}",
|
||||
"Share with me through my #Nextcloud Federated Cloud ID" : "Teile mit mir über meine #Nextcloud Federated-Cloud-ID",
|
||||
"Share with me via Nextcloud" : "Teile mit mir über Nextcloud",
|
||||
"Cloud ID copied to the clipboard" : "Cloud-ID in die Zwischenablage kopiert",
|
||||
"Copy to clipboard" : "In die Zwischenablage kopieren",
|
||||
"Cloud ID copied" : "Cloud-ID kopiert",
|
||||
"Copy" : "Kopieren",
|
||||
"Clipboard not available. Please copy the cloud ID manually." : "Zwischenablage nicht verfügbar. Bitte die Cloud-ID manuell kopieren.",
|
||||
"Copied!" : "Kopiert!",
|
||||
"Federated Cloud" : "Federated Cloud",
|
||||
@@ -57,6 +57,7 @@
|
||||
"X (formerly Twitter)" : "X (früher Twitter)",
|
||||
"formerly Twitter" : "früher Twitter",
|
||||
"Mastodon" : "Mastodon",
|
||||
"Bluesky" : "Bluesky",
|
||||
"Add to your website" : "Zu deiner Webseite hinzufügen",
|
||||
"HTML Code:" : "HTML-Code:",
|
||||
"Cancel" : "Abbrechen",
|
||||
@@ -64,6 +65,8 @@
|
||||
"Remote share" : "Externe Freigabe",
|
||||
"Do you want to add the remote share {name} from {owner}@{remote}?" : "Soll die externe Freigabe {name} von {owner}@{remote} hinzugefügt werden?",
|
||||
"Remote share password" : "Passwort für die externe Freigabe",
|
||||
"Incoming share could not be processed" : "Eingehende Freigabe konnte nicht verarbeitet werden"
|
||||
"Incoming share could not be processed" : "Eingehende Freigabe konnte nicht verarbeitet werden",
|
||||
"Cloud ID copied to the clipboard" : "Cloud-ID in die Zwischenablage kopiert",
|
||||
"Copy to clipboard" : "In die Zwischenablage kopieren"
|
||||
},"pluralForm" :"nplurals=2; plural=(n != 1);"
|
||||
}
|
||||
@@ -47,8 +47,8 @@ OC.L10N.register(
|
||||
"Share with me through my #Nextcloud Federated Cloud ID, see {url}" : "Teilen Sie mit mir über meine #Nextcloud Federated-Cloud-ID, siehe {url}",
|
||||
"Share with me through my #Nextcloud Federated Cloud ID" : "Teilen Sie mit mir über meine #Nextcloud Federated-Cloud-ID",
|
||||
"Share with me via Nextcloud" : "Teilen Sie mit mir über Nextcloud",
|
||||
"Cloud ID copied to the clipboard" : "Cloud-ID wurde in die Zwischenablage kopiert",
|
||||
"Copy to clipboard" : "In die Zwischenablage kopieren",
|
||||
"Cloud ID copied" : "Cloud-ID kopiert",
|
||||
"Copy" : "Kopieren",
|
||||
"Clipboard not available. Please copy the cloud ID manually." : "Zwischenablage nicht verfügbar. Bitte die Cloud-ID manuell kopieren.",
|
||||
"Copied!" : "Kopiert!",
|
||||
"Federated Cloud" : "Federated Cloud",
|
||||
@@ -59,6 +59,7 @@ OC.L10N.register(
|
||||
"X (formerly Twitter)" : "X (früher Twitter)",
|
||||
"formerly Twitter" : "früher Twitter",
|
||||
"Mastodon" : "Mastodon",
|
||||
"Bluesky" : "Bluesky",
|
||||
"Add to your website" : "Zu Ihrer Webseite hinzufügen",
|
||||
"HTML Code:" : "HTML-Code:",
|
||||
"Cancel" : "Abbrechen",
|
||||
@@ -66,6 +67,8 @@ OC.L10N.register(
|
||||
"Remote share" : "Externe Freigabe",
|
||||
"Do you want to add the remote share {name} from {owner}@{remote}?" : "Möchten Sie die externe Freigabe {name} von {owner}@{remote} hinzufügen?",
|
||||
"Remote share password" : "Passwort für die externe Freigabe",
|
||||
"Incoming share could not be processed" : "Eingehende Freigabe konnte nicht verarbeitet werden"
|
||||
"Incoming share could not be processed" : "Eingehende Freigabe konnte nicht verarbeitet werden",
|
||||
"Cloud ID copied to the clipboard" : "Cloud-ID wurde in die Zwischenablage kopiert",
|
||||
"Copy to clipboard" : "In die Zwischenablage kopieren"
|
||||
},
|
||||
"nplurals=2; plural=(n != 1);");
|
||||
|
||||
@@ -45,8 +45,8 @@
|
||||
"Share with me through my #Nextcloud Federated Cloud ID, see {url}" : "Teilen Sie mit mir über meine #Nextcloud Federated-Cloud-ID, siehe {url}",
|
||||
"Share with me through my #Nextcloud Federated Cloud ID" : "Teilen Sie mit mir über meine #Nextcloud Federated-Cloud-ID",
|
||||
"Share with me via Nextcloud" : "Teilen Sie mit mir über Nextcloud",
|
||||
"Cloud ID copied to the clipboard" : "Cloud-ID wurde in die Zwischenablage kopiert",
|
||||
"Copy to clipboard" : "In die Zwischenablage kopieren",
|
||||
"Cloud ID copied" : "Cloud-ID kopiert",
|
||||
"Copy" : "Kopieren",
|
||||
"Clipboard not available. Please copy the cloud ID manually." : "Zwischenablage nicht verfügbar. Bitte die Cloud-ID manuell kopieren.",
|
||||
"Copied!" : "Kopiert!",
|
||||
"Federated Cloud" : "Federated Cloud",
|
||||
@@ -57,6 +57,7 @@
|
||||
"X (formerly Twitter)" : "X (früher Twitter)",
|
||||
"formerly Twitter" : "früher Twitter",
|
||||
"Mastodon" : "Mastodon",
|
||||
"Bluesky" : "Bluesky",
|
||||
"Add to your website" : "Zu Ihrer Webseite hinzufügen",
|
||||
"HTML Code:" : "HTML-Code:",
|
||||
"Cancel" : "Abbrechen",
|
||||
@@ -64,6 +65,8 @@
|
||||
"Remote share" : "Externe Freigabe",
|
||||
"Do you want to add the remote share {name} from {owner}@{remote}?" : "Möchten Sie die externe Freigabe {name} von {owner}@{remote} hinzufügen?",
|
||||
"Remote share password" : "Passwort für die externe Freigabe",
|
||||
"Incoming share could not be processed" : "Eingehende Freigabe konnte nicht verarbeitet werden"
|
||||
"Incoming share could not be processed" : "Eingehende Freigabe konnte nicht verarbeitet werden",
|
||||
"Cloud ID copied to the clipboard" : "Cloud-ID wurde in die Zwischenablage kopiert",
|
||||
"Copy to clipboard" : "In die Zwischenablage kopieren"
|
||||
},"pluralForm" :"nplurals=2; plural=(n != 1);"
|
||||
}
|
||||
@@ -22,7 +22,7 @@ OC.L10N.register(
|
||||
"Provide federated file sharing across servers" : "Παρέχει κοινής χρήσης αρχεία μεταξύ διακομιστών",
|
||||
"Share with me through my #Nextcloud Federated Cloud ID" : "Διαμοιρασμός με εμένα μέσω του #Nextcloud Federated Cloud ID μου",
|
||||
"Share with me via Nextcloud" : "Διαμοιραστείτε με εμένα μέσω του Nextcloud",
|
||||
"Copy to clipboard" : "Αντιγραφή στο πρόχειρο",
|
||||
"Copy" : "Αντιγραφή",
|
||||
"Copied!" : "Αντιγράφτηκε!",
|
||||
"Federated Cloud" : "Federated Cloud",
|
||||
"You can share with anyone who uses a Nextcloud server or other Open Cloud Mesh (OCM) compatible servers and services! Just put their Federated Cloud ID in the share dialog. It looks like person@cloud.example.com" : "Μπορείτε να διαμοιράζεστε με οποιονδήποτε χρησιμοποιεί Nextcloud ή άλλο συμβατό διακομιστή και υπηρεσιών Open Cloud Mesh (OCM)! Απλά προσθέστε το Federated Cloud ID στο πλαίσιο διαλόγου διαμοιρασμού. Θα μοιάζει με person@cloud.example.com",
|
||||
@@ -35,6 +35,7 @@ OC.L10N.register(
|
||||
"Add remote share" : "Προσθήκη απομακρυσμένου κοινόχρηστου φακέλου",
|
||||
"Remote share" : "Απομακρυσμένος κοινόχρηστος φάκελος",
|
||||
"Do you want to add the remote share {name} from {owner}@{remote}?" : "Θέλετε να προσθέσουμε τον απομακρυσμένο κοινόχρηστο φάκελο {name} από {owner}@{remote}?",
|
||||
"Remote share password" : "Συνθηματικό απομακρυσμένου κοινόχρηστου φακέλου"
|
||||
"Remote share password" : "Συνθηματικό απομακρυσμένου κοινόχρηστου φακέλου",
|
||||
"Copy to clipboard" : "Αντιγραφή στο πρόχειρο"
|
||||
},
|
||||
"nplurals=2; plural=(n != 1);");
|
||||
|
||||
@@ -20,7 +20,7 @@
|
||||
"Provide federated file sharing across servers" : "Παρέχει κοινής χρήσης αρχεία μεταξύ διακομιστών",
|
||||
"Share with me through my #Nextcloud Federated Cloud ID" : "Διαμοιρασμός με εμένα μέσω του #Nextcloud Federated Cloud ID μου",
|
||||
"Share with me via Nextcloud" : "Διαμοιραστείτε με εμένα μέσω του Nextcloud",
|
||||
"Copy to clipboard" : "Αντιγραφή στο πρόχειρο",
|
||||
"Copy" : "Αντιγραφή",
|
||||
"Copied!" : "Αντιγράφτηκε!",
|
||||
"Federated Cloud" : "Federated Cloud",
|
||||
"You can share with anyone who uses a Nextcloud server or other Open Cloud Mesh (OCM) compatible servers and services! Just put their Federated Cloud ID in the share dialog. It looks like person@cloud.example.com" : "Μπορείτε να διαμοιράζεστε με οποιονδήποτε χρησιμοποιεί Nextcloud ή άλλο συμβατό διακομιστή και υπηρεσιών Open Cloud Mesh (OCM)! Απλά προσθέστε το Federated Cloud ID στο πλαίσιο διαλόγου διαμοιρασμού. Θα μοιάζει με person@cloud.example.com",
|
||||
@@ -33,6 +33,7 @@
|
||||
"Add remote share" : "Προσθήκη απομακρυσμένου κοινόχρηστου φακέλου",
|
||||
"Remote share" : "Απομακρυσμένος κοινόχρηστος φάκελος",
|
||||
"Do you want to add the remote share {name} from {owner}@{remote}?" : "Θέλετε να προσθέσουμε τον απομακρυσμένο κοινόχρηστο φάκελο {name} από {owner}@{remote}?",
|
||||
"Remote share password" : "Συνθηματικό απομακρυσμένου κοινόχρηστου φακέλου"
|
||||
"Remote share password" : "Συνθηματικό απομακρυσμένου κοινόχρηστου φακέλου",
|
||||
"Copy to clipboard" : "Αντιγραφή στο πρόχειρο"
|
||||
},"pluralForm" :"nplurals=2; plural=(n != 1);"
|
||||
}
|
||||
@@ -47,8 +47,8 @@ OC.L10N.register(
|
||||
"Share with me through my #Nextcloud Federated Cloud ID, see {url}" : "Share with me through my #Nextcloud Federated Cloud ID, see {url}",
|
||||
"Share with me through my #Nextcloud Federated Cloud ID" : "Share with me through my #Nextcloud Federated Cloud ID",
|
||||
"Share with me via Nextcloud" : "Share with me via Nextcloud",
|
||||
"Cloud ID copied to the clipboard" : "Cloud ID copied to the clipboard",
|
||||
"Copy to clipboard" : "Copy to clipboard",
|
||||
"Cloud ID copied" : "Cloud ID copied",
|
||||
"Copy" : "Copy",
|
||||
"Clipboard not available. Please copy the cloud ID manually." : "Clipboard not available. Please copy the cloud ID manually.",
|
||||
"Copied!" : "Copied!",
|
||||
"Federated Cloud" : "Federated Cloud",
|
||||
@@ -59,6 +59,7 @@ OC.L10N.register(
|
||||
"X (formerly Twitter)" : "X (formerly Twitter)",
|
||||
"formerly Twitter" : "formerly Twitter",
|
||||
"Mastodon" : "Mastodon",
|
||||
"Bluesky" : "Bluesky",
|
||||
"Add to your website" : "Add to your website",
|
||||
"HTML Code:" : "HTML Code:",
|
||||
"Cancel" : "Cancel",
|
||||
@@ -66,6 +67,8 @@ OC.L10N.register(
|
||||
"Remote share" : "Remote share",
|
||||
"Do you want to add the remote share {name} from {owner}@{remote}?" : "Do you want to add the remote share {name} from {owner}@{remote}?",
|
||||
"Remote share password" : "Remote share password",
|
||||
"Incoming share could not be processed" : "Incoming share could not be processed"
|
||||
"Incoming share could not be processed" : "Incoming share could not be processed",
|
||||
"Cloud ID copied to the clipboard" : "Cloud ID copied to the clipboard",
|
||||
"Copy to clipboard" : "Copy to clipboard"
|
||||
},
|
||||
"nplurals=2; plural=(n != 1);");
|
||||
|
||||
@@ -45,8 +45,8 @@
|
||||
"Share with me through my #Nextcloud Federated Cloud ID, see {url}" : "Share with me through my #Nextcloud Federated Cloud ID, see {url}",
|
||||
"Share with me through my #Nextcloud Federated Cloud ID" : "Share with me through my #Nextcloud Federated Cloud ID",
|
||||
"Share with me via Nextcloud" : "Share with me via Nextcloud",
|
||||
"Cloud ID copied to the clipboard" : "Cloud ID copied to the clipboard",
|
||||
"Copy to clipboard" : "Copy to clipboard",
|
||||
"Cloud ID copied" : "Cloud ID copied",
|
||||
"Copy" : "Copy",
|
||||
"Clipboard not available. Please copy the cloud ID manually." : "Clipboard not available. Please copy the cloud ID manually.",
|
||||
"Copied!" : "Copied!",
|
||||
"Federated Cloud" : "Federated Cloud",
|
||||
@@ -57,6 +57,7 @@
|
||||
"X (formerly Twitter)" : "X (formerly Twitter)",
|
||||
"formerly Twitter" : "formerly Twitter",
|
||||
"Mastodon" : "Mastodon",
|
||||
"Bluesky" : "Bluesky",
|
||||
"Add to your website" : "Add to your website",
|
||||
"HTML Code:" : "HTML Code:",
|
||||
"Cancel" : "Cancel",
|
||||
@@ -64,6 +65,8 @@
|
||||
"Remote share" : "Remote share",
|
||||
"Do you want to add the remote share {name} from {owner}@{remote}?" : "Do you want to add the remote share {name} from {owner}@{remote}?",
|
||||
"Remote share password" : "Remote share password",
|
||||
"Incoming share could not be processed" : "Incoming share could not be processed"
|
||||
"Incoming share could not be processed" : "Incoming share could not be processed",
|
||||
"Cloud ID copied to the clipboard" : "Cloud ID copied to the clipboard",
|
||||
"Copy to clipboard" : "Copy to clipboard"
|
||||
},"pluralForm" :"nplurals=2; plural=(n != 1);"
|
||||
}
|
||||
@@ -1,38 +0,0 @@
|
||||
OC.L10N.register(
|
||||
"federatedfilesharing",
|
||||
{
|
||||
"Invalid Federated Cloud ID" : "Nevalida federnuba identigilo",
|
||||
"Server to server sharing is not enabled on this server" : "Interservila kunhavo ne estas ebligita en ĉi tiu servilo",
|
||||
"Couldn't establish a federated share." : "Ne povis fari federan kunhavon.",
|
||||
"Couldn't establish a federated share, maybe the password was wrong." : "Ne povis fari federan kunhavon, eble la pasvorto ne ĝustas.",
|
||||
"Federated Share request sent, you will receive an invitation. Check your notifications." : "Federkunhava peto sendita, vi ricevos inviton. Kontrolu viajn sciigojn.",
|
||||
"Couldn't establish a federated share, it looks like the server to federate with is too old (Nextcloud <= 9)." : "Ne povis fari federan kunhavon, ŝajnas, ke la servilo federota estas tro malnova (Nextcloud ⩽ 9).",
|
||||
"It is not allowed to send federated group shares from this server." : "Ne estas permesita sendi federajn grupajn kuhavojn el tiu ĉi servilo. ",
|
||||
"File is already shared with %s" : "Dosiero jam kuhaviĝas kun %s",
|
||||
"Sharing %1$s failed, could not find %2$s, maybe the server is currently unreachable or uses a self-signed certificate." : "Kunhavigo de %1$s malsukcesis, ne eblis trovi %2$s; eble la servilo estas provizore neatingebla aŭ uzas memsubskribitan atestilon.",
|
||||
"Could not find share" : "Ne eblis trovi kunhavon",
|
||||
"Federated sharing" : "Federa kunhavado",
|
||||
"You received {share} as a remote share from {user} (on behalf of {behalf})" : "Vi ricevis „{share}“ kiel foran kunhavon el {user} (nome de {behalf})",
|
||||
"You received {share} as a remote share from {user}" : "Vi ricevis „{share}“ kiel foran kunhavon el {user}",
|
||||
"Accept" : "Akcepti",
|
||||
"Decline" : "Malakcepti",
|
||||
"Federated Cloud Sharing" : "Federnuba kunhavigo",
|
||||
"Sharing" : "Kunhavigo",
|
||||
"Federated file sharing" : "Federa dosierkunhavado",
|
||||
"Provide federated file sharing across servers" : "Provizas federan dosierkunhavigon inter serviloj",
|
||||
"Share with me through my #Nextcloud Federated Cloud ID" : "Kunhavigi kun mi per mia #Nextcloud-federnuba identigilo",
|
||||
"Share with me via Nextcloud" : "Kunhavigi kun mi per Nextcloud",
|
||||
"Copy to clipboard" : "Kopii tondejen",
|
||||
"Copied!" : "Kopiita!",
|
||||
"Federated Cloud" : "Federa nubo",
|
||||
"You can share with anyone who uses a Nextcloud server or other Open Cloud Mesh (OCM) compatible servers and services! Just put their Federated Cloud ID in the share dialog. It looks like person@cloud.example.com" : "Vi povas kunhavigi kun iu, kiu uzas Nextcloud aŭ aliaj serviloj kaj servoj kungruaj kun „Open Cloud Mesh (OCM)“! Entajpu simple lian aŭ ŝian federnuban identigilon en la kunhaviga dialogo. Federnuba identigilo similas al persono@nubo.example.com",
|
||||
"Share it so your friends can share files with you:" : "Kunhavigu ĝin, por ke viaj amikoj povu kunhavigi dosierojn kun vi:",
|
||||
"Add to your website" : "Aldoni al via TTT-ejo",
|
||||
"HTML Code:" : "HTML-kodo:",
|
||||
"Cancel" : "Nuligi",
|
||||
"Add remote share" : "Aldoni foran kunhavon",
|
||||
"Remote share" : "Foran kunhavo",
|
||||
"Do you want to add the remote share {name} from {owner}@{remote}?" : "Ĉu vi volas aldoni la foran kunhavon {name} de {owner}@{remote}?",
|
||||
"Remote share password" : "Pasvorto de fora kunhavo"
|
||||
},
|
||||
"nplurals=2; plural=(n != 1);");
|
||||
@@ -1,36 +0,0 @@
|
||||
{ "translations": {
|
||||
"Invalid Federated Cloud ID" : "Nevalida federnuba identigilo",
|
||||
"Server to server sharing is not enabled on this server" : "Interservila kunhavo ne estas ebligita en ĉi tiu servilo",
|
||||
"Couldn't establish a federated share." : "Ne povis fari federan kunhavon.",
|
||||
"Couldn't establish a federated share, maybe the password was wrong." : "Ne povis fari federan kunhavon, eble la pasvorto ne ĝustas.",
|
||||
"Federated Share request sent, you will receive an invitation. Check your notifications." : "Federkunhava peto sendita, vi ricevos inviton. Kontrolu viajn sciigojn.",
|
||||
"Couldn't establish a federated share, it looks like the server to federate with is too old (Nextcloud <= 9)." : "Ne povis fari federan kunhavon, ŝajnas, ke la servilo federota estas tro malnova (Nextcloud ⩽ 9).",
|
||||
"It is not allowed to send federated group shares from this server." : "Ne estas permesita sendi federajn grupajn kuhavojn el tiu ĉi servilo. ",
|
||||
"File is already shared with %s" : "Dosiero jam kuhaviĝas kun %s",
|
||||
"Sharing %1$s failed, could not find %2$s, maybe the server is currently unreachable or uses a self-signed certificate." : "Kunhavigo de %1$s malsukcesis, ne eblis trovi %2$s; eble la servilo estas provizore neatingebla aŭ uzas memsubskribitan atestilon.",
|
||||
"Could not find share" : "Ne eblis trovi kunhavon",
|
||||
"Federated sharing" : "Federa kunhavado",
|
||||
"You received {share} as a remote share from {user} (on behalf of {behalf})" : "Vi ricevis „{share}“ kiel foran kunhavon el {user} (nome de {behalf})",
|
||||
"You received {share} as a remote share from {user}" : "Vi ricevis „{share}“ kiel foran kunhavon el {user}",
|
||||
"Accept" : "Akcepti",
|
||||
"Decline" : "Malakcepti",
|
||||
"Federated Cloud Sharing" : "Federnuba kunhavigo",
|
||||
"Sharing" : "Kunhavigo",
|
||||
"Federated file sharing" : "Federa dosierkunhavado",
|
||||
"Provide federated file sharing across servers" : "Provizas federan dosierkunhavigon inter serviloj",
|
||||
"Share with me through my #Nextcloud Federated Cloud ID" : "Kunhavigi kun mi per mia #Nextcloud-federnuba identigilo",
|
||||
"Share with me via Nextcloud" : "Kunhavigi kun mi per Nextcloud",
|
||||
"Copy to clipboard" : "Kopii tondejen",
|
||||
"Copied!" : "Kopiita!",
|
||||
"Federated Cloud" : "Federa nubo",
|
||||
"You can share with anyone who uses a Nextcloud server or other Open Cloud Mesh (OCM) compatible servers and services! Just put their Federated Cloud ID in the share dialog. It looks like person@cloud.example.com" : "Vi povas kunhavigi kun iu, kiu uzas Nextcloud aŭ aliaj serviloj kaj servoj kungruaj kun „Open Cloud Mesh (OCM)“! Entajpu simple lian aŭ ŝian federnuban identigilon en la kunhaviga dialogo. Federnuba identigilo similas al persono@nubo.example.com",
|
||||
"Share it so your friends can share files with you:" : "Kunhavigu ĝin, por ke viaj amikoj povu kunhavigi dosierojn kun vi:",
|
||||
"Add to your website" : "Aldoni al via TTT-ejo",
|
||||
"HTML Code:" : "HTML-kodo:",
|
||||
"Cancel" : "Nuligi",
|
||||
"Add remote share" : "Aldoni foran kunhavon",
|
||||
"Remote share" : "Foran kunhavo",
|
||||
"Do you want to add the remote share {name} from {owner}@{remote}?" : "Ĉu vi volas aldoni la foran kunhavon {name} de {owner}@{remote}?",
|
||||
"Remote share password" : "Pasvorto de fora kunhavo"
|
||||
},"pluralForm" :"nplurals=2; plural=(n != 1);"
|
||||
}
|
||||
@@ -47,8 +47,8 @@ OC.L10N.register(
|
||||
"Share with me through my #Nextcloud Federated Cloud ID, see {url}" : "Comparte conmigo a través de mi ID de Nube Federada #Nextcloud, ve {url}",
|
||||
"Share with me through my #Nextcloud Federated Cloud ID" : "Compartirlo conmigo a través de mi ID de Nube Federada #Nextcloud",
|
||||
"Share with me via Nextcloud" : "Compartirlo conmigo vía Nextcloud",
|
||||
"Cloud ID copied to the clipboard" : "ID de nube copiado al portapapeles",
|
||||
"Copy to clipboard" : "Copiar al portapapeles",
|
||||
"Cloud ID copied" : "ID de Nube copiado",
|
||||
"Copy" : "Copiar",
|
||||
"Clipboard not available. Please copy the cloud ID manually." : "Portapapeles no disponible. Por favor, copia el ID de nube manualmente.",
|
||||
"Copied!" : "¡Copiado!",
|
||||
"Federated Cloud" : "Nube Federada",
|
||||
@@ -59,6 +59,7 @@ OC.L10N.register(
|
||||
"X (formerly Twitter)" : "X (anteriormente Twitter)",
|
||||
"formerly Twitter" : "anteriormente Twitter",
|
||||
"Mastodon" : "Mastodon",
|
||||
"Bluesky" : "Bluesky",
|
||||
"Add to your website" : "Añadir a su sitio web",
|
||||
"HTML Code:" : "Código HTML:",
|
||||
"Cancel" : "Cancelar",
|
||||
@@ -66,6 +67,8 @@ OC.L10N.register(
|
||||
"Remote share" : "Recurso compartido remoto",
|
||||
"Do you want to add the remote share {name} from {owner}@{remote}?" : "¿Deseas añadir el recurso compartido remoto {name} de {owner}@{remote}?",
|
||||
"Remote share password" : "Contraseña del compartido remoto",
|
||||
"Incoming share could not be processed" : "Elemento compartido entrante no pudo ser procesado"
|
||||
"Incoming share could not be processed" : "Elemento compartido entrante no pudo ser procesado",
|
||||
"Cloud ID copied to the clipboard" : "ID de nube copiado al portapapeles",
|
||||
"Copy to clipboard" : "Copiar al portapapeles"
|
||||
},
|
||||
"nplurals=3; plural=n == 1 ? 0 : n != 0 && n % 1000000 == 0 ? 1 : 2;");
|
||||
|
||||
@@ -45,8 +45,8 @@
|
||||
"Share with me through my #Nextcloud Federated Cloud ID, see {url}" : "Comparte conmigo a través de mi ID de Nube Federada #Nextcloud, ve {url}",
|
||||
"Share with me through my #Nextcloud Federated Cloud ID" : "Compartirlo conmigo a través de mi ID de Nube Federada #Nextcloud",
|
||||
"Share with me via Nextcloud" : "Compartirlo conmigo vía Nextcloud",
|
||||
"Cloud ID copied to the clipboard" : "ID de nube copiado al portapapeles",
|
||||
"Copy to clipboard" : "Copiar al portapapeles",
|
||||
"Cloud ID copied" : "ID de Nube copiado",
|
||||
"Copy" : "Copiar",
|
||||
"Clipboard not available. Please copy the cloud ID manually." : "Portapapeles no disponible. Por favor, copia el ID de nube manualmente.",
|
||||
"Copied!" : "¡Copiado!",
|
||||
"Federated Cloud" : "Nube Federada",
|
||||
@@ -57,6 +57,7 @@
|
||||
"X (formerly Twitter)" : "X (anteriormente Twitter)",
|
||||
"formerly Twitter" : "anteriormente Twitter",
|
||||
"Mastodon" : "Mastodon",
|
||||
"Bluesky" : "Bluesky",
|
||||
"Add to your website" : "Añadir a su sitio web",
|
||||
"HTML Code:" : "Código HTML:",
|
||||
"Cancel" : "Cancelar",
|
||||
@@ -64,6 +65,8 @@
|
||||
"Remote share" : "Recurso compartido remoto",
|
||||
"Do you want to add the remote share {name} from {owner}@{remote}?" : "¿Deseas añadir el recurso compartido remoto {name} de {owner}@{remote}?",
|
||||
"Remote share password" : "Contraseña del compartido remoto",
|
||||
"Incoming share could not be processed" : "Elemento compartido entrante no pudo ser procesado"
|
||||
"Incoming share could not be processed" : "Elemento compartido entrante no pudo ser procesado",
|
||||
"Cloud ID copied to the clipboard" : "ID de nube copiado al portapapeles",
|
||||
"Copy to clipboard" : "Copiar al portapapeles"
|
||||
},"pluralForm" :"nplurals=3; plural=n == 1 ? 0 : n != 0 && n % 1000000 == 0 ? 1 : 2;"
|
||||
}
|
||||
@@ -25,8 +25,7 @@ OC.L10N.register(
|
||||
"Share with me through my #Nextcloud Federated Cloud ID, see {url}" : "Comparte conmigo a través de mi ID de Nube Federada de #Nextcloud, consulta {url}",
|
||||
"Share with me through my #Nextcloud Federated Cloud ID" : "Compartir conmigo a través de mi ID de Nube Federada #Nextcloud",
|
||||
"Share with me via Nextcloud" : "Compartir conmigo vía Nextcloud",
|
||||
"Cloud ID copied to the clipboard" : "ID de Nube copiado al portapapeles",
|
||||
"Copy to clipboard" : "Copiar al portapapeles",
|
||||
"Copy" : "Copiar",
|
||||
"Copied!" : "¡Copiado!",
|
||||
"Federated Cloud" : "Nube Federada",
|
||||
"You can share with anyone who uses a Nextcloud server or other Open Cloud Mesh (OCM) compatible servers and services! Just put their Federated Cloud ID in the share dialog. It looks like person@cloud.example.com" : "¡Puedes compartir con cualquier persona que utilice un servidor Nextcloud u otros servidores y servicios compatibles con Open Cloud Mesh (OCM)! Simplemente ingresa su ID de Nube Federada en el diálogo de compartición. Se ve como person@cloud.example.com",
|
||||
@@ -39,6 +38,8 @@ OC.L10N.register(
|
||||
"Add remote share" : "Agregar elemento compartido remoto",
|
||||
"Remote share" : "Elemento compartido remoto",
|
||||
"Do you want to add the remote share {name} from {owner}@{remote}?" : "¿Desea agregar el elemento compartido remoto {name} de {owner}@{remote}?",
|
||||
"Remote share password" : "Contraseña del elemento compartido remoto"
|
||||
"Remote share password" : "Contraseña del elemento compartido remoto",
|
||||
"Cloud ID copied to the clipboard" : "ID de Nube copiado al portapapeles",
|
||||
"Copy to clipboard" : "Copiar al portapapeles"
|
||||
},
|
||||
"nplurals=3; plural=n == 1 ? 0 : n != 0 && n % 1000000 == 0 ? 1 : 2;");
|
||||
|
||||
@@ -23,8 +23,7 @@
|
||||
"Share with me through my #Nextcloud Federated Cloud ID, see {url}" : "Comparte conmigo a través de mi ID de Nube Federada de #Nextcloud, consulta {url}",
|
||||
"Share with me through my #Nextcloud Federated Cloud ID" : "Compartir conmigo a través de mi ID de Nube Federada #Nextcloud",
|
||||
"Share with me via Nextcloud" : "Compartir conmigo vía Nextcloud",
|
||||
"Cloud ID copied to the clipboard" : "ID de Nube copiado al portapapeles",
|
||||
"Copy to clipboard" : "Copiar al portapapeles",
|
||||
"Copy" : "Copiar",
|
||||
"Copied!" : "¡Copiado!",
|
||||
"Federated Cloud" : "Nube Federada",
|
||||
"You can share with anyone who uses a Nextcloud server or other Open Cloud Mesh (OCM) compatible servers and services! Just put their Federated Cloud ID in the share dialog. It looks like person@cloud.example.com" : "¡Puedes compartir con cualquier persona que utilice un servidor Nextcloud u otros servidores y servicios compatibles con Open Cloud Mesh (OCM)! Simplemente ingresa su ID de Nube Federada en el diálogo de compartición. Se ve como person@cloud.example.com",
|
||||
@@ -37,6 +36,8 @@
|
||||
"Add remote share" : "Agregar elemento compartido remoto",
|
||||
"Remote share" : "Elemento compartido remoto",
|
||||
"Do you want to add the remote share {name} from {owner}@{remote}?" : "¿Desea agregar el elemento compartido remoto {name} de {owner}@{remote}?",
|
||||
"Remote share password" : "Contraseña del elemento compartido remoto"
|
||||
"Remote share password" : "Contraseña del elemento compartido remoto",
|
||||
"Cloud ID copied to the clipboard" : "ID de Nube copiado al portapapeles",
|
||||
"Copy to clipboard" : "Copiar al portapapeles"
|
||||
},"pluralForm" :"nplurals=3; plural=n == 1 ? 0 : n != 0 && n % 1000000 == 0 ? 1 : 2;"
|
||||
}
|
||||
@@ -34,8 +34,7 @@ OC.L10N.register(
|
||||
"Share with me through my #Nextcloud Federated Cloud ID, see {url}" : "Comparte conmigo a través de mi identificador de nube federada de #Nextcloud, vea {url}",
|
||||
"Share with me through my #Nextcloud Federated Cloud ID" : "Compartir conmigo a través de mi ID de Nube Federada #Nextcloud",
|
||||
"Share with me via Nextcloud" : "Compartir conmigo vía Nextcloud",
|
||||
"Cloud ID copied to the clipboard" : "Identificador de nube copiado al portapapeles",
|
||||
"Copy to clipboard" : "Copiar al portapapeles",
|
||||
"Copy" : "Copiar",
|
||||
"Copied!" : "¡Copiado!",
|
||||
"Federated Cloud" : "Nube Federada",
|
||||
"You can share with anyone who uses a Nextcloud server or other Open Cloud Mesh (OCM) compatible servers and services! Just put their Federated Cloud ID in the share dialog. It looks like person@cloud.example.com" : "¡Puede compartir con cualquier persona que use un servidor Nextcloud u otros servidores y servicios compatibles con Open Cloud Mesh (OCM)!. Sólo ponga el identificador de nube federada en el diálogo de compartir. Tiene la forma: persona@nube.ejemplo.com",
|
||||
@@ -48,6 +47,8 @@ OC.L10N.register(
|
||||
"Add remote share" : "Agregar elemento compartido remoto",
|
||||
"Remote share" : "Elemento compartido remoto",
|
||||
"Do you want to add the remote share {name} from {owner}@{remote}?" : "¿Desea agregar el elemento compartido remoto {name} de {owner}@{remote}?",
|
||||
"Remote share password" : "Contraseña del elemento compartido remoto"
|
||||
"Remote share password" : "Contraseña del elemento compartido remoto",
|
||||
"Cloud ID copied to the clipboard" : "Identificador de nube copiado al portapapeles",
|
||||
"Copy to clipboard" : "Copiar al portapapeles"
|
||||
},
|
||||
"nplurals=3; plural=n == 1 ? 0 : n != 0 && n % 1000000 == 0 ? 1 : 2;");
|
||||
|
||||
@@ -32,8 +32,7 @@
|
||||
"Share with me through my #Nextcloud Federated Cloud ID, see {url}" : "Comparte conmigo a través de mi identificador de nube federada de #Nextcloud, vea {url}",
|
||||
"Share with me through my #Nextcloud Federated Cloud ID" : "Compartir conmigo a través de mi ID de Nube Federada #Nextcloud",
|
||||
"Share with me via Nextcloud" : "Compartir conmigo vía Nextcloud",
|
||||
"Cloud ID copied to the clipboard" : "Identificador de nube copiado al portapapeles",
|
||||
"Copy to clipboard" : "Copiar al portapapeles",
|
||||
"Copy" : "Copiar",
|
||||
"Copied!" : "¡Copiado!",
|
||||
"Federated Cloud" : "Nube Federada",
|
||||
"You can share with anyone who uses a Nextcloud server or other Open Cloud Mesh (OCM) compatible servers and services! Just put their Federated Cloud ID in the share dialog. It looks like person@cloud.example.com" : "¡Puede compartir con cualquier persona que use un servidor Nextcloud u otros servidores y servicios compatibles con Open Cloud Mesh (OCM)!. Sólo ponga el identificador de nube federada en el diálogo de compartir. Tiene la forma: persona@nube.ejemplo.com",
|
||||
@@ -46,6 +45,8 @@
|
||||
"Add remote share" : "Agregar elemento compartido remoto",
|
||||
"Remote share" : "Elemento compartido remoto",
|
||||
"Do you want to add the remote share {name} from {owner}@{remote}?" : "¿Desea agregar el elemento compartido remoto {name} de {owner}@{remote}?",
|
||||
"Remote share password" : "Contraseña del elemento compartido remoto"
|
||||
"Remote share password" : "Contraseña del elemento compartido remoto",
|
||||
"Cloud ID copied to the clipboard" : "Identificador de nube copiado al portapapeles",
|
||||
"Copy to clipboard" : "Copiar al portapapeles"
|
||||
},"pluralForm" :"nplurals=3; plural=n == 1 ? 0 : n != 0 && n % 1000000 == 0 ? 1 : 2;"
|
||||
}
|
||||
@@ -47,8 +47,8 @@ OC.L10N.register(
|
||||
"Share with me through my #Nextcloud Federated Cloud ID, see {url}" : "Jaga minuga minu #Nextcloudi kasutajatunnuse abil liitpilves, vaata {url}",
|
||||
"Share with me through my #Nextcloud Federated Cloud ID" : "Jaga minuga minu #Nextcloudi kasutajatunnuse abil liitpilves",
|
||||
"Share with me via Nextcloud" : "Jaga minuga Nextcloudi vahendusel",
|
||||
"Cloud ID copied to the clipboard" : "Kasutajatunnus liitpilves on kopeeritud lõikelauale",
|
||||
"Copy to clipboard" : "Kopeeri lõikelauale",
|
||||
"Cloud ID copied" : "Kasutajatunnus liitpilves on kopeeritud",
|
||||
"Copy" : "Kopeeri",
|
||||
"Clipboard not available. Please copy the cloud ID manually." : "Lõikelaud pole saadaval. Palun kopeeri kasutajatunnus liitpilves käsitsi.",
|
||||
"Copied!" : "Kopeeritud!",
|
||||
"Federated Cloud" : "Liitpilv",
|
||||
@@ -59,6 +59,7 @@ OC.L10N.register(
|
||||
"X (formerly Twitter)" : "X (varasemalt Twitter)",
|
||||
"formerly Twitter" : "varasemalt Twitter",
|
||||
"Mastodon" : "Mastodon",
|
||||
"Bluesky" : "Bluesky",
|
||||
"Add to your website" : "Lisa oma veebisaidile",
|
||||
"HTML Code:" : "HTML kood:",
|
||||
"Cancel" : "Tühista",
|
||||
@@ -66,6 +67,8 @@ OC.L10N.register(
|
||||
"Remote share" : "Kaugjagamine",
|
||||
"Do you want to add the remote share {name} from {owner}@{remote}?" : "Soovid lisada kaugjagamise {name} asukohast {owner}@{remote}?",
|
||||
"Remote share password" : "Kaugjagamise salasõna",
|
||||
"Incoming share could not be processed" : "Sissetulevat kausta ei saanud töödelda"
|
||||
"Incoming share could not be processed" : "Sissetulevat kausta ei saanud töödelda",
|
||||
"Cloud ID copied to the clipboard" : "Kasutajatunnus liitpilves on kopeeritud lõikelauale",
|
||||
"Copy to clipboard" : "Kopeeri lõikelauale"
|
||||
},
|
||||
"nplurals=2; plural=(n != 1);");
|
||||
|
||||
@@ -45,8 +45,8 @@
|
||||
"Share with me through my #Nextcloud Federated Cloud ID, see {url}" : "Jaga minuga minu #Nextcloudi kasutajatunnuse abil liitpilves, vaata {url}",
|
||||
"Share with me through my #Nextcloud Federated Cloud ID" : "Jaga minuga minu #Nextcloudi kasutajatunnuse abil liitpilves",
|
||||
"Share with me via Nextcloud" : "Jaga minuga Nextcloudi vahendusel",
|
||||
"Cloud ID copied to the clipboard" : "Kasutajatunnus liitpilves on kopeeritud lõikelauale",
|
||||
"Copy to clipboard" : "Kopeeri lõikelauale",
|
||||
"Cloud ID copied" : "Kasutajatunnus liitpilves on kopeeritud",
|
||||
"Copy" : "Kopeeri",
|
||||
"Clipboard not available. Please copy the cloud ID manually." : "Lõikelaud pole saadaval. Palun kopeeri kasutajatunnus liitpilves käsitsi.",
|
||||
"Copied!" : "Kopeeritud!",
|
||||
"Federated Cloud" : "Liitpilv",
|
||||
@@ -57,6 +57,7 @@
|
||||
"X (formerly Twitter)" : "X (varasemalt Twitter)",
|
||||
"formerly Twitter" : "varasemalt Twitter",
|
||||
"Mastodon" : "Mastodon",
|
||||
"Bluesky" : "Bluesky",
|
||||
"Add to your website" : "Lisa oma veebisaidile",
|
||||
"HTML Code:" : "HTML kood:",
|
||||
"Cancel" : "Tühista",
|
||||
@@ -64,6 +65,8 @@
|
||||
"Remote share" : "Kaugjagamine",
|
||||
"Do you want to add the remote share {name} from {owner}@{remote}?" : "Soovid lisada kaugjagamise {name} asukohast {owner}@{remote}?",
|
||||
"Remote share password" : "Kaugjagamise salasõna",
|
||||
"Incoming share could not be processed" : "Sissetulevat kausta ei saanud töödelda"
|
||||
"Incoming share could not be processed" : "Sissetulevat kausta ei saanud töödelda",
|
||||
"Cloud ID copied to the clipboard" : "Kasutajatunnus liitpilves on kopeeritud lõikelauale",
|
||||
"Copy to clipboard" : "Kopeeri lõikelauale"
|
||||
},"pluralForm" :"nplurals=2; plural=(n != 1);"
|
||||
}
|
||||
@@ -36,8 +36,7 @@ OC.L10N.register(
|
||||
"Share with me through my #Nextcloud Federated Cloud ID, see {url}" : "Partekatu nirekin, nire federatutako #Nextcloud hodei IDa erabiliz, ikus {url}",
|
||||
"Share with me through my #Nextcloud Federated Cloud ID" : "Partekatu nirekin, nire federatutako #Nextcloud hodei IDa erabiliz",
|
||||
"Share with me via Nextcloud" : "Partekatu nirekin Nextcloud bidez",
|
||||
"Cloud ID copied to the clipboard" : "Hodei IDa arbelean kopiatu da",
|
||||
"Copy to clipboard" : "Kopiatu arbelera",
|
||||
"Copy" : "Kopiatu",
|
||||
"Clipboard not available. Please copy the cloud ID manually." : "Arbela ez dago eskuragarri, mesedez kopiatu hodei IDa eskuz.",
|
||||
"Copied!" : "Kopiatuta!",
|
||||
"Federated Cloud" : "Hodei Federatua",
|
||||
@@ -55,6 +54,8 @@ OC.L10N.register(
|
||||
"Remote share" : "Urruneko partekatzea",
|
||||
"Do you want to add the remote share {name} from {owner}@{remote}?" : "{owner}@{remote}(r)en {name} urruneko partekatzea gehitu nahi duzu?",
|
||||
"Remote share password" : "Urruneko partekatzearen pasahitza",
|
||||
"Incoming share could not be processed" : "Sarrerako partekatzea ezin izan da prozesatu"
|
||||
"Incoming share could not be processed" : "Sarrerako partekatzea ezin izan da prozesatu",
|
||||
"Cloud ID copied to the clipboard" : "Hodei IDa arbelean kopiatu da",
|
||||
"Copy to clipboard" : "Kopiatu arbelera"
|
||||
},
|
||||
"nplurals=2; plural=(n != 1);");
|
||||
|
||||
@@ -34,8 +34,7 @@
|
||||
"Share with me through my #Nextcloud Federated Cloud ID, see {url}" : "Partekatu nirekin, nire federatutako #Nextcloud hodei IDa erabiliz, ikus {url}",
|
||||
"Share with me through my #Nextcloud Federated Cloud ID" : "Partekatu nirekin, nire federatutako #Nextcloud hodei IDa erabiliz",
|
||||
"Share with me via Nextcloud" : "Partekatu nirekin Nextcloud bidez",
|
||||
"Cloud ID copied to the clipboard" : "Hodei IDa arbelean kopiatu da",
|
||||
"Copy to clipboard" : "Kopiatu arbelera",
|
||||
"Copy" : "Kopiatu",
|
||||
"Clipboard not available. Please copy the cloud ID manually." : "Arbela ez dago eskuragarri, mesedez kopiatu hodei IDa eskuz.",
|
||||
"Copied!" : "Kopiatuta!",
|
||||
"Federated Cloud" : "Hodei Federatua",
|
||||
@@ -53,6 +52,8 @@
|
||||
"Remote share" : "Urruneko partekatzea",
|
||||
"Do you want to add the remote share {name} from {owner}@{remote}?" : "{owner}@{remote}(r)en {name} urruneko partekatzea gehitu nahi duzu?",
|
||||
"Remote share password" : "Urruneko partekatzearen pasahitza",
|
||||
"Incoming share could not be processed" : "Sarrerako partekatzea ezin izan da prozesatu"
|
||||
"Incoming share could not be processed" : "Sarrerako partekatzea ezin izan da prozesatu",
|
||||
"Cloud ID copied to the clipboard" : "Hodei IDa arbelean kopiatu da",
|
||||
"Copy to clipboard" : "Kopiatu arbelera"
|
||||
},"pluralForm" :"nplurals=2; plural=(n != 1);"
|
||||
}
|
||||
@@ -26,8 +26,7 @@ OC.L10N.register(
|
||||
"Share with me through my #Nextcloud Federated Cloud ID, see {url}" : "از طریق شناسه ابری فدرال #Nextcloud با من به اشتراک بگذارید، به {url} مراجعه کنید",
|
||||
"Share with me through my #Nextcloud Federated Cloud ID" : "از طریق شناسه ابری فدرال #Nextcloud با من به اشتراک بگذارید",
|
||||
"Share with me via Nextcloud" : "همرسانی با من روی نسکتکلود",
|
||||
"Cloud ID copied to the clipboard" : "Cloud ID در کلیپ بورد کپی شد",
|
||||
"Copy to clipboard" : "رونوشت به تختهگیره",
|
||||
"Copy" : "کپی",
|
||||
"Copied!" : "رونوشت شد!",
|
||||
"Federated Cloud" : "ابر خودگردان",
|
||||
"You can share with anyone who uses a Nextcloud server or other Open Cloud Mesh (OCM) compatible servers and services! Just put their Federated Cloud ID in the share dialog. It looks like person@cloud.example.com" : "میتوانید با هر کسی که از سرور Nextcloud یا سایر سرورها و سرویسهای سازگار با Open Cloud Mesh (OCM) استفاده میکند، اشتراکگذاری کنید! فقط شناسه ابری فدرال آنها را در گفتگوی اشتراک گذاری قرار دهید. به نظر می رسد person@cloud.example.com",
|
||||
@@ -40,6 +39,8 @@ OC.L10N.register(
|
||||
"Add remote share" : "افزودن همرسانی دوردست",
|
||||
"Remote share" : "همرسانی دوردست",
|
||||
"Do you want to add the remote share {name} from {owner}@{remote}?" : "میخواهید همرسانی دوردست {name} را از {owner}@{remote} بیفزایید؟",
|
||||
"Remote share password" : "گذرواژهٔ همرسانی دوردست"
|
||||
"Remote share password" : "گذرواژهٔ همرسانی دوردست",
|
||||
"Cloud ID copied to the clipboard" : "Cloud ID در کلیپ بورد کپی شد",
|
||||
"Copy to clipboard" : "رونوشت به تختهگیره"
|
||||
},
|
||||
"nplurals=2; plural=(n > 1);");
|
||||
|
||||
@@ -24,8 +24,7 @@
|
||||
"Share with me through my #Nextcloud Federated Cloud ID, see {url}" : "از طریق شناسه ابری فدرال #Nextcloud با من به اشتراک بگذارید، به {url} مراجعه کنید",
|
||||
"Share with me through my #Nextcloud Federated Cloud ID" : "از طریق شناسه ابری فدرال #Nextcloud با من به اشتراک بگذارید",
|
||||
"Share with me via Nextcloud" : "همرسانی با من روی نسکتکلود",
|
||||
"Cloud ID copied to the clipboard" : "Cloud ID در کلیپ بورد کپی شد",
|
||||
"Copy to clipboard" : "رونوشت به تختهگیره",
|
||||
"Copy" : "کپی",
|
||||
"Copied!" : "رونوشت شد!",
|
||||
"Federated Cloud" : "ابر خودگردان",
|
||||
"You can share with anyone who uses a Nextcloud server or other Open Cloud Mesh (OCM) compatible servers and services! Just put their Federated Cloud ID in the share dialog. It looks like person@cloud.example.com" : "میتوانید با هر کسی که از سرور Nextcloud یا سایر سرورها و سرویسهای سازگار با Open Cloud Mesh (OCM) استفاده میکند، اشتراکگذاری کنید! فقط شناسه ابری فدرال آنها را در گفتگوی اشتراک گذاری قرار دهید. به نظر می رسد person@cloud.example.com",
|
||||
@@ -38,6 +37,8 @@
|
||||
"Add remote share" : "افزودن همرسانی دوردست",
|
||||
"Remote share" : "همرسانی دوردست",
|
||||
"Do you want to add the remote share {name} from {owner}@{remote}?" : "میخواهید همرسانی دوردست {name} را از {owner}@{remote} بیفزایید؟",
|
||||
"Remote share password" : "گذرواژهٔ همرسانی دوردست"
|
||||
"Remote share password" : "گذرواژهٔ همرسانی دوردست",
|
||||
"Cloud ID copied to the clipboard" : "Cloud ID در کلیپ بورد کپی شد",
|
||||
"Copy to clipboard" : "رونوشت به تختهگیره"
|
||||
},"pluralForm" :"nplurals=2; plural=(n > 1);"
|
||||
}
|
||||
@@ -22,7 +22,7 @@ OC.L10N.register(
|
||||
"Provide federated file sharing across servers" : "Mahdollistaa federoidun tiedostojaon palvelinten välillä",
|
||||
"Share with me through my #Nextcloud Federated Cloud ID" : "Jaa kanssani käyttäen #Nextcloud ja federoitua pilvitunnistetta",
|
||||
"Share with me via Nextcloud" : "Jaa kanssani Nextcloudin kautta",
|
||||
"Copy to clipboard" : "Kopioi leikepöydälle",
|
||||
"Copy" : "Kopioi",
|
||||
"Copied!" : "Kopioitu!",
|
||||
"Federated Cloud" : "Federoitu pilvi",
|
||||
"You can share with anyone who uses a Nextcloud server or other Open Cloud Mesh (OCM) compatible servers and services! Just put their Federated Cloud ID in the share dialog. It looks like person@cloud.example.com" : "Voit jakaa kenelle tahansa, joka käyttää Nextcloud-palvelinta tai muuta Open Cloud Mesh (OCM) -yhteensopivaa palvelinta tai palvelua! Kirjoita heidän federoidun pilven tunniste jaon kohteeksi. Se on muodossa henkilö@cloud.example.com",
|
||||
@@ -36,6 +36,7 @@ OC.L10N.register(
|
||||
"Add remote share" : "Lisää etäjako",
|
||||
"Remote share" : "Etäjako",
|
||||
"Do you want to add the remote share {name} from {owner}@{remote}?" : "Haluatko lisätä etäjaon {name} kohteesta {owner}@{remote}?",
|
||||
"Remote share password" : "Etäjaon salasana"
|
||||
"Remote share password" : "Etäjaon salasana",
|
||||
"Copy to clipboard" : "Kopioi leikepöydälle"
|
||||
},
|
||||
"nplurals=2; plural=(n != 1);");
|
||||
|
||||
@@ -20,7 +20,7 @@
|
||||
"Provide federated file sharing across servers" : "Mahdollistaa federoidun tiedostojaon palvelinten välillä",
|
||||
"Share with me through my #Nextcloud Federated Cloud ID" : "Jaa kanssani käyttäen #Nextcloud ja federoitua pilvitunnistetta",
|
||||
"Share with me via Nextcloud" : "Jaa kanssani Nextcloudin kautta",
|
||||
"Copy to clipboard" : "Kopioi leikepöydälle",
|
||||
"Copy" : "Kopioi",
|
||||
"Copied!" : "Kopioitu!",
|
||||
"Federated Cloud" : "Federoitu pilvi",
|
||||
"You can share with anyone who uses a Nextcloud server or other Open Cloud Mesh (OCM) compatible servers and services! Just put their Federated Cloud ID in the share dialog. It looks like person@cloud.example.com" : "Voit jakaa kenelle tahansa, joka käyttää Nextcloud-palvelinta tai muuta Open Cloud Mesh (OCM) -yhteensopivaa palvelinta tai palvelua! Kirjoita heidän federoidun pilven tunniste jaon kohteeksi. Se on muodossa henkilö@cloud.example.com",
|
||||
@@ -34,6 +34,7 @@
|
||||
"Add remote share" : "Lisää etäjako",
|
||||
"Remote share" : "Etäjako",
|
||||
"Do you want to add the remote share {name} from {owner}@{remote}?" : "Haluatko lisätä etäjaon {name} kohteesta {owner}@{remote}?",
|
||||
"Remote share password" : "Etäjaon salasana"
|
||||
"Remote share password" : "Etäjaon salasana",
|
||||
"Copy to clipboard" : "Kopioi leikepöydälle"
|
||||
},"pluralForm" :"nplurals=2; plural=(n != 1);"
|
||||
}
|
||||
@@ -47,8 +47,7 @@ OC.L10N.register(
|
||||
"Share with me through my #Nextcloud Federated Cloud ID, see {url}" : "Partagez avec moi grâce à mon ID de Cloud Fédéré #Nextcloud, voir {url}.",
|
||||
"Share with me through my #Nextcloud Federated Cloud ID" : "Partagez avec moi grâce à mon ID de Cloud Fédéré #Nextcloud",
|
||||
"Share with me via Nextcloud" : "Partagez avec moi via Nextcloud",
|
||||
"Cloud ID copied to the clipboard" : "ID de Cloud Fédéré copié dans le presse-papiers",
|
||||
"Copy to clipboard" : "Copier dans le presse-papiers",
|
||||
"Copy" : "Copier",
|
||||
"Clipboard not available. Please copy the cloud ID manually." : "Presse-papiers non disponible. Veuillez copier l'ID cloud manuellement.",
|
||||
"Copied!" : "Copié !",
|
||||
"Federated Cloud" : "Cloud Fédéré",
|
||||
@@ -66,6 +65,8 @@ OC.L10N.register(
|
||||
"Remote share" : "Partage distant",
|
||||
"Do you want to add the remote share {name} from {owner}@{remote}?" : "Voulez-vous ajouter le partage distant {name} depuis {owner}@{remote} ?",
|
||||
"Remote share password" : "Mot de passe du partage distant",
|
||||
"Incoming share could not be processed" : "Le partage entrant n'a pas pu être traité"
|
||||
"Incoming share could not be processed" : "Le partage entrant n'a pas pu être traité",
|
||||
"Cloud ID copied to the clipboard" : "ID de Cloud Fédéré copié dans le presse-papiers",
|
||||
"Copy to clipboard" : "Copier dans le presse-papiers"
|
||||
},
|
||||
"nplurals=3; plural=(n == 0 || n == 1) ? 0 : n != 0 && n % 1000000 == 0 ? 1 : 2;");
|
||||
|
||||
@@ -45,8 +45,7 @@
|
||||
"Share with me through my #Nextcloud Federated Cloud ID, see {url}" : "Partagez avec moi grâce à mon ID de Cloud Fédéré #Nextcloud, voir {url}.",
|
||||
"Share with me through my #Nextcloud Federated Cloud ID" : "Partagez avec moi grâce à mon ID de Cloud Fédéré #Nextcloud",
|
||||
"Share with me via Nextcloud" : "Partagez avec moi via Nextcloud",
|
||||
"Cloud ID copied to the clipboard" : "ID de Cloud Fédéré copié dans le presse-papiers",
|
||||
"Copy to clipboard" : "Copier dans le presse-papiers",
|
||||
"Copy" : "Copier",
|
||||
"Clipboard not available. Please copy the cloud ID manually." : "Presse-papiers non disponible. Veuillez copier l'ID cloud manuellement.",
|
||||
"Copied!" : "Copié !",
|
||||
"Federated Cloud" : "Cloud Fédéré",
|
||||
@@ -64,6 +63,8 @@
|
||||
"Remote share" : "Partage distant",
|
||||
"Do you want to add the remote share {name} from {owner}@{remote}?" : "Voulez-vous ajouter le partage distant {name} depuis {owner}@{remote} ?",
|
||||
"Remote share password" : "Mot de passe du partage distant",
|
||||
"Incoming share could not be processed" : "Le partage entrant n'a pas pu être traité"
|
||||
"Incoming share could not be processed" : "Le partage entrant n'a pas pu être traité",
|
||||
"Cloud ID copied to the clipboard" : "ID de Cloud Fédéré copié dans le presse-papiers",
|
||||
"Copy to clipboard" : "Copier dans le presse-papiers"
|
||||
},"pluralForm" :"nplurals=3; plural=(n == 0 || n == 1) ? 0 : n != 0 && n % 1000000 == 0 ? 1 : 2;"
|
||||
}
|
||||
@@ -47,8 +47,7 @@ OC.L10N.register(
|
||||
"Share with me through my #Nextcloud Federated Cloud ID, see {url}" : "Roinn liom trí m'aitheantas scamall #Nextcloud Federated Cloud, féach {url}",
|
||||
"Share with me through my #Nextcloud Federated Cloud ID" : "Roinn liom trí m'aitheantas scamall #Nextcloud Federated Cloud",
|
||||
"Share with me via Nextcloud" : "Roinn liom trí Nextcloud",
|
||||
"Cloud ID copied to the clipboard" : "Cóipeáladh Cloud ID chuig an ngearrthaisce",
|
||||
"Copy to clipboard" : "Cóipeáil chuig an ngearrthaisce",
|
||||
"Copy" : "Cóipeáil",
|
||||
"Clipboard not available. Please copy the cloud ID manually." : "Níl an gearrthaisce ar fáil. Cóipeáil an t-aitheantas néil de láimh.",
|
||||
"Copied!" : "Cóipeáladh!",
|
||||
"Federated Cloud" : "Scamall Cónaidhme",
|
||||
@@ -59,6 +58,7 @@ OC.L10N.register(
|
||||
"X (formerly Twitter)" : "X (Twitter roimhe seo)",
|
||||
"formerly Twitter" : "Twitter roimhe seo",
|
||||
"Mastodon" : "Mastodon",
|
||||
"Bluesky" : "Bluesky",
|
||||
"Add to your website" : "Cuir le do láithreán gréasáin",
|
||||
"HTML Code:" : "Cód HTML:",
|
||||
"Cancel" : "Cealaigh",
|
||||
@@ -66,6 +66,8 @@ OC.L10N.register(
|
||||
"Remote share" : "Comhroinnt iargúlta",
|
||||
"Do you want to add the remote share {name} from {owner}@{remote}?" : "Ar mhaith leat an sciar cianda {name} ó {owner}@{remote} a chur leis?",
|
||||
"Remote share password" : "Pasfhocal comhroinnte cianda",
|
||||
"Incoming share could not be processed" : "Níorbh fhéidir an sciar isteach a phróiseáil"
|
||||
"Incoming share could not be processed" : "Níorbh fhéidir an sciar isteach a phróiseáil",
|
||||
"Cloud ID copied to the clipboard" : "Cóipeáladh Cloud ID chuig an ngearrthaisce",
|
||||
"Copy to clipboard" : "Cóipeáil chuig an ngearrthaisce"
|
||||
},
|
||||
"nplurals=5; plural=(n==1 ? 0 : n==2 ? 1 : n<7 ? 2 : n<11 ? 3 : 4);");
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user