Compare commits
383 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 0ca558d47b | |||
| 38a91680b3 | |||
| 33618a14cf | |||
| e041305011 | |||
| f801fe74ec | |||
| 590620d7e5 | |||
| b7a15b7c62 | |||
| 2b748f0ae5 | |||
| e6e528defe | |||
| 6a2e0f819c | |||
| 047ff27e46 | |||
| 4720c39048 | |||
| 5c625b7a07 | |||
| b33fdaf085 | |||
| d40263b2aa | |||
| a449af6460 | |||
| ed6d0e59bb | |||
| c614a13d4b | |||
| 32327c6285 | |||
| 752d90c57c | |||
| a31d42d47a | |||
| c81422ed37 | |||
| 6e9ba894a2 | |||
| 4283f4790b | |||
| 3945981f0d | |||
| 5d05c8d61e | |||
| 25ee57fc60 | |||
| e90c114730 | |||
| 41feb5d29d | |||
| 06d99eba17 | |||
| 6f6c655b46 | |||
| 0c4bc3e495 | |||
| b307db6b19 | |||
| d79cf95ece | |||
| a366ec367a | |||
| 0c8f89c9d7 | |||
| bed17ca424 | |||
| 5cb262391f | |||
| 0934814a05 | |||
| 9919c2bc91 | |||
| 77f9897060 | |||
| 461d0edecd | |||
| 75e57760c7 | |||
| 105398699d | |||
| b820518a2a | |||
| 32508c1f78 | |||
| 754422aa00 | |||
| be8b2bfa8b | |||
| 8c90d4f822 | |||
| f791d91c00 | |||
| 6acf2a6a1b | |||
| fc37abd35a | |||
| 6c19fbcf4d | |||
| bbabf50984 | |||
| 22ca454130 | |||
| 299039e2f2 | |||
| 993c359830 | |||
| 1c8a31c36a | |||
| c38e604e5e | |||
| 6498c7d552 | |||
| 2835d0cde4 | |||
| 6fdfa436e1 | |||
| 0fddb6be2d | |||
| 8c0c0454bd | |||
| 58fd2d3df8 | |||
| 34330db471 | |||
| 9a1fd7a629 | |||
| 0d890d4217 | |||
| a8e186dba9 | |||
| 34267ef563 | |||
| f9132111ab | |||
| 84e52327f3 | |||
| 5a1a0b4759 | |||
| 530efa6783 | |||
| 0e5dd3d834 | |||
| ee04846dec | |||
| 2d6bd8b86a | |||
| cc806fbd8e | |||
| a56e274140 | |||
| 3a20529708 | |||
| 0d0264f057 | |||
| 36758db1bc | |||
| 869108de51 | |||
| 7f7c1ed3bd | |||
| a6a94fa45c | |||
| f937e10a08 | |||
| 9abcafb44c | |||
| e963d7e583 | |||
| 60cb5a3c90 | |||
| febc130423 | |||
| 531d4b08e7 | |||
| 9019c56e70 | |||
| 7deca60136 | |||
| 34448d3410 | |||
| 294057e32f | |||
| 8d164db8e9 | |||
| 274ff3692d | |||
| 6d24abc06f | |||
| ec617b6b3e | |||
| e0bd85b067 | |||
| aec12f5b51 | |||
| 226b7df65e | |||
| c27db5fdad | |||
| 56793fa5b8 | |||
| 6bc73b0dab | |||
| 121973d336 | |||
| e863c3c500 | |||
| 16727bf781 | |||
| 62fd47ee2c | |||
| 31605c7495 | |||
| 6a9be2e7f4 | |||
| e91840a61a | |||
| c1801b044a | |||
| 9d86b701ee | |||
| 9dc11443a2 | |||
| 24de30d70f | |||
| 9bbb6dedc3 | |||
| 632ffba69d | |||
| 7d5ffa50ae | |||
| c46510baba | |||
| 11eaa0479f | |||
| e7aaaff69d | |||
| 76e86fa920 | |||
| b585280534 | |||
| 75bed8535c | |||
| 154f3597b4 | |||
| 637392c084 | |||
| 0677888291 | |||
| 9b1227749f | |||
| 115c0e93a3 | |||
| e594e9d0e7 | |||
| 0eca299ca8 | |||
| 4b684897ed | |||
| 7c7010cdff | |||
| 06b6a5bc05 | |||
| 15cd533fcb | |||
| cc23025c2a | |||
| 8406984336 | |||
| 89fcefbfa0 | |||
| 01feb85809 | |||
| 99a2a31fa8 | |||
| 18964750f9 | |||
| a011cb7780 | |||
| bfb7e7dd8e | |||
| df2f3a8422 | |||
| 5c7f52f40e | |||
| 8482302e42 | |||
| 61dc0bbff0 | |||
| ce3f458ccc | |||
| b9d55ba30c | |||
| 1829269f9d | |||
| 3183ea79d2 | |||
| 91d3c63222 | |||
| 3bdb344224 | |||
| 93b258317d | |||
| aadf842039 | |||
| b573d8a58b | |||
| 6945a030f5 | |||
| 7b0f1c6dd0 | |||
| 5f6e6b305f | |||
| 3579c5a6a0 | |||
| 05eb4cbf46 | |||
| 197db6aa86 | |||
| 10fa17938e | |||
| a1c2a25d9d | |||
| bfd7138133 | |||
| 58437decb2 | |||
| 582bb11eae | |||
| c8ce13236e | |||
| 3e8502d239 | |||
| c6dd592d60 | |||
| fa7e569a16 | |||
| 922c3e01c7 | |||
| 958492d8af | |||
| b15e294a6e | |||
| 341fd348e6 | |||
| db530d1eae | |||
| 9350a6798c | |||
| 8034de84aa | |||
| 0745731806 | |||
| b7ef5d5855 | |||
| 0580014b73 | |||
| 19c9d88970 | |||
| 48f7dac9b9 | |||
| 0fe787558e | |||
| d957f6190c | |||
| 51ed61bb4a | |||
| de7ddb6e1c | |||
| 0ba2f5e537 | |||
| 400ade585d | |||
| a45f4b0cdc | |||
| 0edaff9426 | |||
| d9d8449340 | |||
| 2c62e695ba | |||
| 09dfa965b4 | |||
| ad23c85d0c | |||
| 6a570c0133 | |||
| 6d7a7f3146 | |||
| 7c266d9f23 | |||
| e1b9723428 | |||
| 9edcc864fe | |||
| 14f52a2303 | |||
| 3efb1d80e9 | |||
| 52e3762045 | |||
| 2eaf18dd49 | |||
| a95d781989 | |||
| 282341a8d6 | |||
| a7a64de6e6 | |||
| 1661855f5b | |||
| 0744d1cf6b | |||
| 2ae9626fec | |||
| e04072cc54 | |||
| 14daf4ca16 | |||
| 6eabaaf104 | |||
| 2c4b5c6b68 | |||
| 7aa6a74b31 | |||
| 0eadf1753d | |||
| 492bdb7010 | |||
| d15feb4ba6 | |||
| b92b3e7940 | |||
| 13df65850e | |||
| f892437210 | |||
| 69e6b6a483 | |||
| 0fd888da09 | |||
| 986af30032 | |||
| c4c247d535 | |||
| aeca72538a | |||
| f839d7825a | |||
| 5682ff4479 | |||
| 070ec61759 | |||
| 3a89c18888 | |||
| 186b12b718 | |||
| a86a2a070e | |||
| 8f82ad358a | |||
| 9af153f5b7 | |||
| 4a5bacc8c7 | |||
| d50828aa92 | |||
| 258670de61 | |||
| 7f76a6d4aa | |||
| edbcb6af6f | |||
| b07801a2b5 | |||
| 871262d6f6 | |||
| 2a4ee2df9f | |||
| aaf07ab73e | |||
| 5de1d46be4 | |||
| cf56d6325a | |||
| 08382282ea | |||
| f035ff3d3a | |||
| 61ebc6e251 | |||
| 6bde5db7da | |||
| d14a032220 | |||
| fcdb28e4a3 | |||
| 9b519b4679 | |||
| f47a586cdd | |||
| ac4e82d2a5 | |||
| 770ad6249e | |||
| 161e59929a | |||
| 247b66c5ee | |||
| dac8818102 | |||
| 08b39e2585 | |||
| 24c0f4b06d | |||
| 077cb3ebba | |||
| 3c809eaceb | |||
| 496404de56 | |||
| da1dd481e9 | |||
| b2bc785986 | |||
| f3c56f051a | |||
| e0e2d0fd2e | |||
| 65135bba31 | |||
| 75edec9d6c | |||
| 1a0535aa75 | |||
| 4185dfb599 | |||
| 162ebfaf3c | |||
| 6277ecf480 | |||
| e476a22a50 | |||
| 50a94a35ee | |||
| f78e2a33ea | |||
| f967134f58 | |||
| 71c2e94123 | |||
| b6313f68d3 | |||
| d343207b25 | |||
| 25044f82ae | |||
| 90948f5096 | |||
| 65d008b40c | |||
| b7b4a3a6d7 | |||
| 3355e6a2f7 | |||
| 1158319acb | |||
| 66d07f4ddd | |||
| f46f41eabc | |||
| 8e7cbcea40 | |||
| 2c6d3d6f76 | |||
| c0a4098f14 | |||
| 40c504ec2d | |||
| ca52687b73 | |||
| ae85eab73c | |||
| 980c243132 | |||
| babab821a7 | |||
| bbca4fe56e | |||
| 8136b14d81 | |||
| de381f3b5a | |||
| 8ec73e1976 | |||
| 842df498e0 | |||
| 594d22021a | |||
| 9d04624fe2 | |||
| 309e714f14 | |||
| 61b74102a5 | |||
| 818ce6ded2 | |||
| 93d1684617 | |||
| 040bdbaf28 | |||
| 5fcf28942f | |||
| 9e455f0650 | |||
| 511f54324a | |||
| d95ce6ac92 | |||
| 8bbd30693c | |||
| 4fce62376e | |||
| 3cdfe6dbfa | |||
| 5e5ed7d912 | |||
| 291ec1e474 | |||
| ae8b6290e1 | |||
| 75312017d4 | |||
| 2174e0b4fe | |||
| b801f583d8 | |||
| 06c10586b7 | |||
| 189e1fcf22 | |||
| 3c9b937e28 | |||
| 8a581c230b | |||
| 1ef465f804 | |||
| 0e686fc6a9 | |||
| aeed32cfba | |||
| 73017f564a | |||
| b18372e637 | |||
| cb80ec7ebb | |||
| 86f0cbf1e8 | |||
| 5bf67fd206 | |||
| 55756e626f | |||
| 10b82c8bf8 | |||
| e5367e11ba | |||
| 31af870ef0 | |||
| 5b1d928dcd | |||
| cc7655136c | |||
| 8e7bdabe15 | |||
| c23dbcce45 | |||
| f38c8c3d7c | |||
| bae24eb2e3 | |||
| 48f39f54c5 | |||
| aa498654c5 | |||
| d9a7e0855c | |||
| cc873ccaec | |||
| c6a68d5f17 | |||
| b520db7eb5 | |||
| 756efb76aa | |||
| cbe18a8677 | |||
| f977a7fec6 | |||
| 34c6cb7b2e | |||
| 0e9aa3dd78 | |||
| 54e23aeac9 | |||
| 1490ea95cb | |||
| 5a0651e7c7 | |||
| 09f774a61f | |||
| 7a5c79b0f2 | |||
| 3b41ab108f | |||
| 9aac7ecc60 | |||
| f5620538b9 | |||
| 4f933d3505 | |||
| d877262e07 | |||
| dabde76f9e | |||
| 33fba8c084 | |||
| ea8b133910 | |||
| 631318f86f | |||
| 4ac0fcf02e | |||
| 38f8423cd6 | |||
| c5ad20d925 | |||
| 9272a13e9d | |||
| 938e8d3a46 | |||
| fa2a7b9b2d | |||
| 7eefd725db | |||
| 7541afae07 | |||
| 40d3118751 | |||
| 69b85fab32 | |||
| 3279afc529 | |||
| 98ee89286d | |||
| 9522dde7a2 | |||
| 38fd84aa6a |
@@ -69,7 +69,7 @@ updates:
|
||||
target-branch: stable32
|
||||
directories:
|
||||
- "/"
|
||||
- "/build/integration"
|
||||
- "/vendor-bin/behat"
|
||||
- "/vendor-bin/cs-fixer"
|
||||
- "/vendor-bin/openapi-extractor"
|
||||
- "/vendor-bin/phpunit"
|
||||
|
||||
@@ -90,6 +90,7 @@ jobs:
|
||||
|
||||
- name: Set up Nextcloud
|
||||
run: |
|
||||
composer install
|
||||
mkdir data
|
||||
echo '<?php $CONFIG=["${{ matrix.key }}" => ["class" => "OC\Files\ObjectStore\S3", "arguments" => ["bucket" => "nextcloud", "autocreate" => true, "key" => "nextcloud", "secret" => "bWluaW8tc2VjcmV0LWtleS1uZXh0Y2xvdWQ=", "hostname" => "localhost", "port" => 9000, "use_ssl" => false, "use_path_style" => true, "uploadPartSize" => 52428800]]];' > config/config.php
|
||||
echo '<?php $CONFIG=["redis" => ["host" => "localhost", "port" => 6379], "memcache.local" => "\OC\Memcache\Redis", "memcache.distributed" => "\OC\Memcache\Redis"];' > config/redis.config.php
|
||||
|
||||
@@ -131,12 +131,9 @@ jobs:
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
|
||||
- name: Set up production dependencies
|
||||
run: composer i --no-dev
|
||||
|
||||
- name: Set up behat dependencies
|
||||
working-directory: build/integration
|
||||
run: composer i
|
||||
- name: Set up dependencies
|
||||
run: |
|
||||
composer install
|
||||
|
||||
- name: Set up Talk dependencies
|
||||
if: ${{ matrix.test-suite == 'videoverification_features' }}
|
||||
|
||||
@@ -47,7 +47,7 @@ jobs:
|
||||
|
||||
strategy:
|
||||
matrix:
|
||||
php-versions: [ '8.2', '8.3', '8.4' ]
|
||||
php-versions: [ '8.2', '8.3', '8.4', '8.5' ]
|
||||
|
||||
name: php-lint
|
||||
|
||||
|
||||
@@ -26,12 +26,10 @@ jobs:
|
||||
|
||||
if: ${{ github.repository_owner != 'nextcloud-gmbh' }}
|
||||
|
||||
container: shivammathur/node:latest-i386
|
||||
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
php-versions: ["8.2", "8.3", "8.4"]
|
||||
php-versions: ["8.4"]
|
||||
|
||||
steps:
|
||||
- name: Checkout server
|
||||
@@ -40,32 +38,22 @@ jobs:
|
||||
persist-credentials: false
|
||||
submodules: true
|
||||
|
||||
- name: Install tools
|
||||
run: |
|
||||
sudo apt-get update
|
||||
sudo apt-get install -y ffmpeg imagemagick libmagickcore-6.q16-3-extra
|
||||
|
||||
- name: Set up php ${{ matrix.php-versions }}
|
||||
uses: shivammathur/setup-php@bf6b4fbd49ca58e4608c9c89fba0b8d90bd2a39f #v2.35.5
|
||||
with:
|
||||
php-version: ${{ matrix.php-versions }}
|
||||
extensions: bz2, ctype, curl, dom, fileinfo, gd, iconv, imagick, intl, json, libxml, mbstring, openssl, pcntl, posix, redis, session, simplexml, xmlreader, xmlwriter, zip, zlib, sqlite, pdo_sqlite, apcu, ldap
|
||||
coverage: none
|
||||
ini-file: development
|
||||
ini-values: apc.enabled=on, apc.enable_cli=on, disable_functions= # https://github.com/shivammathur/setup-php/discussions/573
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
|
||||
- name: Set up dependencies
|
||||
run: composer i
|
||||
uses: docker://ghcr.io/nextcloud/continuous-integration-php8.4-32bit:latest
|
||||
with:
|
||||
args: /bin/sh -c "
|
||||
git config --global --add safe.directory /github/workspace &&
|
||||
composer install --no-interaction"
|
||||
|
||||
- name: Set up Nextcloud
|
||||
env:
|
||||
DB_PORT: 4444
|
||||
run: |
|
||||
mkdir data
|
||||
./occ maintenance:install --verbose --database=sqlite --database-name=nextcloud --database-host=127.0.0.1 --database-port=$DB_PORT --database-user=autotest --database-pass=rootpassword --admin-user admin --admin-pass admin
|
||||
php -f tests/enable_all.php
|
||||
uses: docker://ghcr.io/nextcloud/continuous-integration-php8.4-32bit:latest
|
||||
with:
|
||||
args: /bin/sh -c "
|
||||
mkdir data &&
|
||||
./occ maintenance:install --verbose --database=sqlite --database-name=nextcloud --database-user=autotest --database-pass=rootpassword --admin-user admin --admin-pass admin &&
|
||||
php -f tests/enable_all.php"
|
||||
|
||||
- name: PHPUnit
|
||||
run: composer run test -- --exclude-group PRIMARY-azure --exclude-group PRIMARY-s3 --exclude-group PRIMARY-swift --exclude-group Memcached --exclude-group Redis --exclude-group RoutingWeirdness
|
||||
uses: docker://ghcr.io/nextcloud/continuous-integration-php8.4-32bit:latest
|
||||
with:
|
||||
args: /bin/sh -c "composer run test -- --exclude-group PRIMARY-azure,PRIMARY-s3,PRIMARY-swift,Memcached,Redis,RoutingWeirdness"
|
||||
|
||||
@@ -60,13 +60,15 @@ jobs:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
php-versions: ['8.2']
|
||||
mariadb-versions: ['10.3', '10.6', '10.11', '11.4', '11.8']
|
||||
mariadb-versions: ['10.6', '10.11', '11.4', '11.8']
|
||||
include:
|
||||
- php-versions: '8.3'
|
||||
mariadb-versions: '10.11'
|
||||
coverage: ${{ github.event_name != 'pull_request' }}
|
||||
- php-versions: '8.4'
|
||||
mariadb-versions: '11.8'
|
||||
- php-versions: '8.5'
|
||||
mariadb-versions: '11.8'
|
||||
|
||||
name: MariaDB ${{ matrix.mariadb-versions }} (PHP ${{ matrix.php-versions }}) - database tests
|
||||
|
||||
|
||||
@@ -56,7 +56,7 @@ jobs:
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
php-versions: ['8.3', '8.4']
|
||||
php-versions: ['8.3', '8.4', '8.5']
|
||||
include:
|
||||
- php-versions: '8.2'
|
||||
coverage: ${{ github.event_name != 'pull_request' }}
|
||||
|
||||
@@ -67,6 +67,8 @@ jobs:
|
||||
coverage: ${{ github.event_name != 'pull_request' }}
|
||||
- mysql-versions: '8.4'
|
||||
php-versions: '8.4'
|
||||
- mysql-versions: '8.4'
|
||||
php-versions: '8.5'
|
||||
|
||||
name: MySQL ${{ matrix.mysql-versions }} (PHP ${{ matrix.php-versions }}) - database tests
|
||||
|
||||
|
||||
@@ -59,7 +59,7 @@ jobs:
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
php-versions: ['8.3', '8.4']
|
||||
php-versions: ['8.3', '8.4', '8.5']
|
||||
include:
|
||||
- php-versions: '8.2'
|
||||
coverage: ${{ github.event_name != 'pull_request' }}
|
||||
|
||||
@@ -69,6 +69,8 @@ jobs:
|
||||
php-versions: '8.3'
|
||||
- oracle-versions: '23'
|
||||
php-versions: '8.4'
|
||||
- oracle-versions: '23'
|
||||
php-versions: '8.5'
|
||||
|
||||
name: Oracle ${{ matrix.oracle-versions }} (PHP ${{ matrix.php-versions }}) - database tests
|
||||
|
||||
|
||||
@@ -68,6 +68,8 @@ jobs:
|
||||
coverage: ${{ github.event_name != 'pull_request' }}
|
||||
- php-versions: '8.4'
|
||||
postgres-versions: '18'
|
||||
- php-versions: '8.5'
|
||||
postgres-versions: '18'
|
||||
|
||||
name: PostgreSQL ${{ matrix.postgres-versions }} (PHP ${{ matrix.php-versions }}) - database tests
|
||||
|
||||
|
||||
@@ -59,7 +59,7 @@ jobs:
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
php-versions: ['8.3', '8.4']
|
||||
php-versions: ['8.3', '8.4', '8.5']
|
||||
include:
|
||||
- php-versions: '8.2'
|
||||
coverage: ${{ github.event_name != 'pull_request' }}
|
||||
|
||||
+1
-1
Submodule 3rdparty updated: 3e8f824169...086aa0783e
+1
-1
@@ -256,7 +256,7 @@ SPDX-FileCopyrightText = "2025 Nextcloud GmbH and Nextcloud contributors"
|
||||
SPDX-License-Identifier = "AGPL-3.0-or-later"
|
||||
|
||||
[[annotations]]
|
||||
path = ["composer.json", "composer.lock", ".github/CODEOWNERS", "__tests__/tsconfig.json", "tsconfig.json", "build/integration/composer.**", "vendor-bin/**/composer.json", "vendor-bin/**/composer.lock", "apps/**/composer/composer.json", "apps/**/composer/composer.lock", "apps/**/composer/composer/installed.json"]
|
||||
path = ["composer.json", "composer.lock", ".github/CODEOWNERS", "__tests__/tsconfig.json", "tsconfig.json", "apps/**/composer/composer.json", "apps/**/composer/composer.lock", "apps/**/composer/composer/installed.json"]
|
||||
precedence = "aggregate"
|
||||
SPDX-FileCopyrightText = "2011-2016 ownCloud, Inc., 2016-2024 Nextcloud GmbH and Nextcloud contributors"
|
||||
SPDX-License-Identifier = "AGPL-3.0-only OR AGPL-3.0-or-later"
|
||||
|
||||
@@ -19,6 +19,7 @@ return array(
|
||||
'OCA\\AdminAudit\\IAuditLogger' => $baseDir . '/../lib/IAuditLogger.php',
|
||||
'OCA\\AdminAudit\\Listener\\AppManagementEventListener' => $baseDir . '/../lib/Listener/AppManagementEventListener.php',
|
||||
'OCA\\AdminAudit\\Listener\\AuthEventListener' => $baseDir . '/../lib/Listener/AuthEventListener.php',
|
||||
'OCA\\AdminAudit\\Listener\\CacheEventListener' => $baseDir . '/../lib/Listener/CacheEventListener.php',
|
||||
'OCA\\AdminAudit\\Listener\\ConsoleEventListener' => $baseDir . '/../lib/Listener/ConsoleEventListener.php',
|
||||
'OCA\\AdminAudit\\Listener\\CriticalActionPerformedEventListener' => $baseDir . '/../lib/Listener/CriticalActionPerformedEventListener.php',
|
||||
'OCA\\AdminAudit\\Listener\\FileEventListener' => $baseDir . '/../lib/Listener/FileEventListener.php',
|
||||
|
||||
@@ -7,14 +7,14 @@ namespace Composer\Autoload;
|
||||
class ComposerStaticInitAdminAudit
|
||||
{
|
||||
public static $prefixLengthsPsr4 = array (
|
||||
'O' =>
|
||||
'O' =>
|
||||
array (
|
||||
'OCA\\AdminAudit\\' => 15,
|
||||
),
|
||||
);
|
||||
|
||||
public static $prefixDirsPsr4 = array (
|
||||
'OCA\\AdminAudit\\' =>
|
||||
'OCA\\AdminAudit\\' =>
|
||||
array (
|
||||
0 => __DIR__ . '/..' . '/../lib',
|
||||
),
|
||||
@@ -34,6 +34,7 @@ class ComposerStaticInitAdminAudit
|
||||
'OCA\\AdminAudit\\IAuditLogger' => __DIR__ . '/..' . '/../lib/IAuditLogger.php',
|
||||
'OCA\\AdminAudit\\Listener\\AppManagementEventListener' => __DIR__ . '/..' . '/../lib/Listener/AppManagementEventListener.php',
|
||||
'OCA\\AdminAudit\\Listener\\AuthEventListener' => __DIR__ . '/..' . '/../lib/Listener/AuthEventListener.php',
|
||||
'OCA\\AdminAudit\\Listener\\CacheEventListener' => __DIR__ . '/..' . '/../lib/Listener/CacheEventListener.php',
|
||||
'OCA\\AdminAudit\\Listener\\ConsoleEventListener' => __DIR__ . '/..' . '/../lib/Listener/ConsoleEventListener.php',
|
||||
'OCA\\AdminAudit\\Listener\\CriticalActionPerformedEventListener' => __DIR__ . '/..' . '/../lib/Listener/CriticalActionPerformedEventListener.php',
|
||||
'OCA\\AdminAudit\\Listener\\FileEventListener' => __DIR__ . '/..' . '/../lib/Listener/FileEventListener.php',
|
||||
|
||||
@@ -20,6 +20,7 @@ use OCA\AdminAudit\AuditLogger;
|
||||
use OCA\AdminAudit\IAuditLogger;
|
||||
use OCA\AdminAudit\Listener\AppManagementEventListener;
|
||||
use OCA\AdminAudit\Listener\AuthEventListener;
|
||||
use OCA\AdminAudit\Listener\CacheEventListener;
|
||||
use OCA\AdminAudit\Listener\ConsoleEventListener;
|
||||
use OCA\AdminAudit\Listener\CriticalActionPerformedEventListener;
|
||||
use OCA\AdminAudit\Listener\FileEventListener;
|
||||
@@ -40,6 +41,8 @@ use OCP\Authentication\TwoFactorAuth\TwoFactorProviderChallengeFailed;
|
||||
use OCP\Authentication\TwoFactorAuth\TwoFactorProviderChallengePassed;
|
||||
use OCP\Console\ConsoleEvent;
|
||||
use OCP\EventDispatcher\IEventDispatcher;
|
||||
use OCP\Files\Cache\CacheEntryInsertedEvent;
|
||||
use OCP\Files\Cache\CacheEntryRemovedEvent;
|
||||
use OCP\Files\Events\Node\BeforeNodeDeletedEvent;
|
||||
use OCP\Files\Events\Node\BeforeNodeReadEvent;
|
||||
use OCP\Files\Events\Node\NodeCopiedEvent;
|
||||
@@ -123,6 +126,10 @@ class Application extends App implements IBootstrap {
|
||||
|
||||
// Console events
|
||||
$context->registerEventListener(ConsoleEvent::class, ConsoleEventListener::class);
|
||||
|
||||
// Cache events
|
||||
$context->registerEventListener(CacheEntryInsertedEvent::class, CacheEventListener::class);
|
||||
$context->registerEventListener(CacheEntryRemovedEvent::class, CacheEventListener::class);
|
||||
}
|
||||
|
||||
public function boot(IBootContext $context): void {
|
||||
|
||||
@@ -0,0 +1,51 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
/**
|
||||
* SPDX-FileCopyrightText: 2025 Nextcloud GmbH and Nextcloud contributors
|
||||
* SPDX-License-Identifier: AGPL-3.0-or-later
|
||||
*/
|
||||
|
||||
namespace OCA\AdminAudit\Listener;
|
||||
|
||||
use OCA\AdminAudit\Actions\Action;
|
||||
use OCP\EventDispatcher\Event;
|
||||
use OCP\EventDispatcher\IEventListener;
|
||||
use OCP\Files\Cache\CacheEntryInsertedEvent;
|
||||
use OCP\Files\Cache\CacheEntryRemovedEvent;
|
||||
|
||||
/**
|
||||
* @template-implements IEventListener<CacheEntryInsertedEvent|CacheEntryRemovedEvent>
|
||||
*/
|
||||
class CacheEventListener extends Action implements IEventListener {
|
||||
public function handle(Event $event): void {
|
||||
if ($event instanceof CacheEntryInsertedEvent) {
|
||||
$this->entryInserted($event);
|
||||
} elseif ($event instanceof CacheEntryRemovedEvent) {
|
||||
$this->entryRemoved($event);
|
||||
}
|
||||
}
|
||||
|
||||
private function entryInserted(CacheEntryInsertedEvent $event): void {
|
||||
$this->log('Cache entry inserted for fileid "%1$d", path "%2$s" on storageid "%3$d"',
|
||||
[
|
||||
'fileid' => $event->getFileId(),
|
||||
'path' => $event->getPath(),
|
||||
'storageid' => $event->getStorageId(),
|
||||
],
|
||||
['fileid', 'path', 'storageid']
|
||||
);
|
||||
}
|
||||
|
||||
private function entryRemoved(CacheEntryRemovedEvent $event): void {
|
||||
$this->log('Cache entry removed for fileid "%1$d", path "%2$s" on storageid "%3$d"',
|
||||
[
|
||||
'fileid' => $event->getFileId(),
|
||||
'path' => $event->getPath(),
|
||||
'storageid' => $event->getStorageId(),
|
||||
],
|
||||
['fileid', 'path', 'storageid']
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -148,18 +148,6 @@ class SharingEventListener extends Action implements IEventListener {
|
||||
'id',
|
||||
]
|
||||
),
|
||||
IShare::TYPE_SCIENCEMESH => $this->log(
|
||||
'The %s "%s" with ID "%s" has been shared to the sciencemesh user "%s" with permissions "%s" (Share ID: %s)',
|
||||
$params,
|
||||
[
|
||||
'itemType',
|
||||
'path',
|
||||
'itemSource',
|
||||
'shareWith',
|
||||
'permissions',
|
||||
'id',
|
||||
]
|
||||
),
|
||||
default => null
|
||||
};
|
||||
}
|
||||
@@ -274,17 +262,6 @@ class SharingEventListener extends Action implements IEventListener {
|
||||
'id',
|
||||
]
|
||||
),
|
||||
IShare::TYPE_SCIENCEMESH => $this->log(
|
||||
'The %s "%s" with ID "%s" has been unshared from the sciencemesh user "%s" (Share ID: %s)',
|
||||
$params,
|
||||
[
|
||||
'itemType',
|
||||
'fileTarget',
|
||||
'itemSource',
|
||||
'shareWith',
|
||||
'id',
|
||||
]
|
||||
),
|
||||
default => null
|
||||
};
|
||||
}
|
||||
|
||||
@@ -7,14 +7,14 @@ namespace Composer\Autoload;
|
||||
class ComposerStaticInitCloudFederationAPI
|
||||
{
|
||||
public static $prefixLengthsPsr4 = array (
|
||||
'O' =>
|
||||
'O' =>
|
||||
array (
|
||||
'OCA\\CloudFederationAPI\\' => 23,
|
||||
),
|
||||
);
|
||||
|
||||
public static $prefixDirsPsr4 = array (
|
||||
'OCA\\CloudFederationAPI\\' =>
|
||||
'OCA\\CloudFederationAPI\\' =>
|
||||
array (
|
||||
0 => __DIR__ . '/..' . '/../lib',
|
||||
),
|
||||
|
||||
@@ -106,14 +106,16 @@ class RequestHandlerController extends Controller {
|
||||
#[NoCSRFRequired]
|
||||
#[BruteForceProtection(action: 'receiveFederatedShare')]
|
||||
public function addShare($shareWith, $name, $description, $providerId, $owner, $ownerDisplayName, $sharedBy, $sharedByDisplayName, $protocol, $shareType, $resourceType) {
|
||||
try {
|
||||
// if request is signed and well signed, no exception are thrown
|
||||
// if request is not signed and host is known for not supporting signed request, no exception are thrown
|
||||
$signedRequest = $this->getSignedRequest();
|
||||
$this->confirmSignedOrigin($signedRequest, 'owner', $owner);
|
||||
} catch (IncomingRequestException $e) {
|
||||
$this->logger->warning('incoming request exception', ['exception' => $e]);
|
||||
return new JSONResponse(['message' => $e->getMessage(), 'validationErrors' => []], Http::STATUS_BAD_REQUEST);
|
||||
if (!$this->appConfig->getValueBool('core', OCMSignatoryManager::APPCONFIG_SIGN_DISABLED, lazy: true)) {
|
||||
try {
|
||||
// if request is signed and well signed, no exception are thrown
|
||||
// if request is not signed and host is known for not supporting signed request, no exception are thrown
|
||||
$signedRequest = $this->getSignedRequest();
|
||||
$this->confirmSignedOrigin($signedRequest, 'owner', $owner);
|
||||
} catch (IncomingRequestException $e) {
|
||||
$this->logger->warning('incoming request exception', ['exception' => $e]);
|
||||
return new JSONResponse(['message' => $e->getMessage(), 'validationErrors' => []], Http::STATUS_BAD_REQUEST);
|
||||
}
|
||||
}
|
||||
|
||||
// check if all required parameters are set
|
||||
@@ -354,14 +356,16 @@ class RequestHandlerController extends Controller {
|
||||
);
|
||||
}
|
||||
|
||||
try {
|
||||
// if request is signed and well signed, no exception are thrown
|
||||
// if request is not signed and host is known for not supporting signed request, no exception are thrown
|
||||
$signedRequest = $this->getSignedRequest();
|
||||
$this->confirmNotificationIdentity($signedRequest, $resourceType, $notification);
|
||||
} catch (IncomingRequestException $e) {
|
||||
$this->logger->warning('incoming request exception', ['exception' => $e]);
|
||||
return new JSONResponse(['message' => $e->getMessage(), 'validationErrors' => []], Http::STATUS_BAD_REQUEST);
|
||||
if (!$this->appConfig->getValueBool('core', OCMSignatoryManager::APPCONFIG_SIGN_DISABLED, lazy: true)) {
|
||||
try {
|
||||
// if request is signed and well signed, no exception are thrown
|
||||
// if request is not signed and host is known for not supporting signed request, no exception are thrown
|
||||
$signedRequest = $this->getSignedRequest();
|
||||
$this->confirmNotificationIdentity($signedRequest, $resourceType, $notification);
|
||||
} catch (IncomingRequestException $e) {
|
||||
$this->logger->warning('incoming request exception', ['exception' => $e]);
|
||||
return new JSONResponse(['message' => $e->getMessage(), 'validationErrors' => []], Http::STATUS_BAD_REQUEST);
|
||||
}
|
||||
}
|
||||
|
||||
try {
|
||||
@@ -500,7 +504,6 @@ class RequestHandlerController extends Controller {
|
||||
*
|
||||
* @param IIncomingSignedRequest|null $signedRequest
|
||||
* @param string $resourceType
|
||||
* @param string $sharedSecret
|
||||
*
|
||||
* @throws IncomingRequestException
|
||||
* @throws BadRequestException
|
||||
@@ -524,7 +527,7 @@ class RequestHandlerController extends Controller {
|
||||
return;
|
||||
}
|
||||
} catch (\Exception $e) {
|
||||
throw new IncomingRequestException($e->getMessage());
|
||||
throw new IncomingRequestException($e->getMessage(), previous: $e);
|
||||
}
|
||||
|
||||
$this->confirmNotificationEntry($signedRequest, $identity);
|
||||
|
||||
@@ -7,14 +7,14 @@ namespace Composer\Autoload;
|
||||
class ComposerStaticInitComments
|
||||
{
|
||||
public static $prefixLengthsPsr4 = array (
|
||||
'O' =>
|
||||
'O' =>
|
||||
array (
|
||||
'OCA\\Comments\\' => 13,
|
||||
),
|
||||
);
|
||||
|
||||
public static $prefixDirsPsr4 = array (
|
||||
'OCA\\Comments\\' =>
|
||||
'OCA\\Comments\\' =>
|
||||
array (
|
||||
0 => __DIR__ . '/..' . '/../lib',
|
||||
),
|
||||
|
||||
@@ -17,6 +17,7 @@ OC.L10N.register(
|
||||
"Delete comment" : "Borrar comentario",
|
||||
"Cancel edit" : "Cacelar edición",
|
||||
"New comment" : "Comentario nuevo",
|
||||
"Write a comment …" : "Escribe un comentario ….",
|
||||
"Post comment" : "Publicar comentario",
|
||||
"@ for mentions, : for emoji, / for smart picker" : "@ para menciones, : para emoji, / para selector inteligente",
|
||||
"Could not reload comments" : "No se pudieron recargar los comentarios",
|
||||
|
||||
@@ -15,6 +15,7 @@
|
||||
"Delete comment" : "Borrar comentario",
|
||||
"Cancel edit" : "Cacelar edición",
|
||||
"New comment" : "Comentario nuevo",
|
||||
"Write a comment …" : "Escribe un comentario ….",
|
||||
"Post comment" : "Publicar comentario",
|
||||
"@ for mentions, : for emoji, / for smart picker" : "@ para menciones, : para emoji, / para selector inteligente",
|
||||
"Could not reload comments" : "No se pudieron recargar los comentarios",
|
||||
|
||||
@@ -15,6 +15,7 @@ OC.L10N.register(
|
||||
"Delete comment" : "Poista kommentti",
|
||||
"Cancel edit" : "Peruuta muokkaus",
|
||||
"New comment" : "Uusi kommentti",
|
||||
"Write a comment …" : "Kirjoita kommentti …",
|
||||
"Post comment" : "Lähetä viesti",
|
||||
"@ for mentions, : for emoji, / for smart picker" : "@ maininnoille, : emojille, / älykkäälle valitsimelle",
|
||||
"Could not reload comments" : "Kommenttien lataus epäonnistui",
|
||||
|
||||
@@ -13,6 +13,7 @@
|
||||
"Delete comment" : "Poista kommentti",
|
||||
"Cancel edit" : "Peruuta muokkaus",
|
||||
"New comment" : "Uusi kommentti",
|
||||
"Write a comment …" : "Kirjoita kommentti …",
|
||||
"Post comment" : "Lähetä viesti",
|
||||
"@ for mentions, : for emoji, / for smart picker" : "@ maininnoille, : emojille, / älykkäälle valitsimelle",
|
||||
"Could not reload comments" : "Kommenttien lataus epäonnistui",
|
||||
|
||||
@@ -17,7 +17,7 @@ OC.L10N.register(
|
||||
"Delete comment" : "Supprimer le commentaire",
|
||||
"Cancel edit" : "Annuler les modifications",
|
||||
"New comment" : "Nouveau commentaire",
|
||||
"Write a comment …" : "Écrire un commentaire …",
|
||||
"Write a comment …" : "Écrire un commentaire…",
|
||||
"Post comment" : "Publier le commentaire",
|
||||
"@ for mentions, : for emoji, / for smart picker" : "@ pour les mentions, : pour les émojis, / pour le sélecteur intelligent",
|
||||
"Could not reload comments" : "Impossible de recharger les commentaires",
|
||||
|
||||
@@ -15,7 +15,7 @@
|
||||
"Delete comment" : "Supprimer le commentaire",
|
||||
"Cancel edit" : "Annuler les modifications",
|
||||
"New comment" : "Nouveau commentaire",
|
||||
"Write a comment …" : "Écrire un commentaire …",
|
||||
"Write a comment …" : "Écrire un commentaire…",
|
||||
"Post comment" : "Publier le commentaire",
|
||||
"@ for mentions, : for emoji, / for smart picker" : "@ pour les mentions, : pour les émojis, / pour le sélecteur intelligent",
|
||||
"Could not reload comments" : "Impossible de recharger les commentaires",
|
||||
|
||||
@@ -18,8 +18,6 @@ use OCP\IUserManager;
|
||||
use OCP\L10N\IFactory;
|
||||
|
||||
class Provider implements IProvider {
|
||||
protected ?IL10N $l = null;
|
||||
|
||||
public function __construct(
|
||||
protected IFactory $languageFactory,
|
||||
protected IURLGenerator $url,
|
||||
@@ -42,9 +40,9 @@ class Provider implements IProvider {
|
||||
throw new UnknownActivityException();
|
||||
}
|
||||
|
||||
$this->l = $this->languageFactory->get('comments', $language);
|
||||
|
||||
if ($event->getSubject() === 'add_comment_subject') {
|
||||
$l = $this->languageFactory->get('comments', $language);
|
||||
|
||||
$this->parseMessage($event);
|
||||
if ($this->activityManager->getRequirePNG()) {
|
||||
$event->setIcon($this->url->getAbsoluteURL($this->url->imagePath('core', 'actions/comment.png')));
|
||||
@@ -54,13 +52,13 @@ class Provider implements IProvider {
|
||||
|
||||
if ($this->activityManager->isFormattingFilteredObject()) {
|
||||
try {
|
||||
return $this->parseShortVersion($event);
|
||||
return $this->parseShortVersion($event, $l);
|
||||
} catch (UnknownActivityException) {
|
||||
// Ignore and simply use the long version...
|
||||
}
|
||||
}
|
||||
|
||||
return $this->parseLongVersion($event);
|
||||
return $this->parseLongVersion($event, $l);
|
||||
}
|
||||
throw new UnknownActivityException();
|
||||
|
||||
@@ -69,15 +67,15 @@ class Provider implements IProvider {
|
||||
/**
|
||||
* @throws UnknownActivityException
|
||||
*/
|
||||
protected function parseShortVersion(IEvent $event): IEvent {
|
||||
protected function parseShortVersion(IEvent $event, IL10N $l): IEvent {
|
||||
$subjectParameters = $this->getSubjectParameters($event);
|
||||
|
||||
if ($event->getSubject() === 'add_comment_subject') {
|
||||
if ($subjectParameters['actor'] === $this->activityManager->getCurrentUserId()) {
|
||||
$event->setRichSubject($this->l->t('You commented'), []);
|
||||
$event->setRichSubject($l->t('You commented'), []);
|
||||
} else {
|
||||
$author = $this->generateUserParameter($subjectParameters['actor']);
|
||||
$event->setRichSubject($this->l->t('{author} commented'), [
|
||||
$event->setRichSubject($l->t('{author} commented'), [
|
||||
'author' => $author,
|
||||
]);
|
||||
}
|
||||
@@ -91,24 +89,24 @@ class Provider implements IProvider {
|
||||
/**
|
||||
* @throws UnknownActivityException
|
||||
*/
|
||||
protected function parseLongVersion(IEvent $event): IEvent {
|
||||
protected function parseLongVersion(IEvent $event, IL10N $l): IEvent {
|
||||
$subjectParameters = $this->getSubjectParameters($event);
|
||||
|
||||
if ($event->getSubject() === 'add_comment_subject') {
|
||||
if ($subjectParameters['actor'] === $this->activityManager->getCurrentUserId()) {
|
||||
$event->setParsedSubject($this->l->t('You commented on %1$s', [
|
||||
$event->setParsedSubject($l->t('You commented on %1$s', [
|
||||
$subjectParameters['filePath'],
|
||||
]))
|
||||
->setRichSubject($this->l->t('You commented on {file}'), [
|
||||
->setRichSubject($l->t('You commented on {file}'), [
|
||||
'file' => $this->generateFileParameter($subjectParameters['fileId'], $subjectParameters['filePath']),
|
||||
]);
|
||||
} else {
|
||||
$author = $this->generateUserParameter($subjectParameters['actor']);
|
||||
$event->setParsedSubject($this->l->t('%1$s commented on %2$s', [
|
||||
$event->setParsedSubject($l->t('%1$s commented on %2$s', [
|
||||
$author['name'],
|
||||
$subjectParameters['filePath'],
|
||||
]))
|
||||
->setRichSubject($this->l->t('{author} commented on {file}'), [
|
||||
->setRichSubject($l->t('{author} commented on {file}'), [
|
||||
'author' => $author,
|
||||
'file' => $this->generateFileParameter($subjectParameters['fileId'], $subjectParameters['filePath']),
|
||||
]);
|
||||
|
||||
@@ -11,7 +11,7 @@ use OCP\IL10N;
|
||||
|
||||
class Setting extends ActivitySettings {
|
||||
public function __construct(
|
||||
protected IL10N $l,
|
||||
protected readonly IL10N $l,
|
||||
) {
|
||||
}
|
||||
|
||||
@@ -23,11 +23,11 @@ class Setting extends ActivitySettings {
|
||||
return $this->l->t('<strong>Comments</strong> for files');
|
||||
}
|
||||
|
||||
public function getGroupIdentifier() {
|
||||
public function getGroupIdentifier(): string {
|
||||
return 'files';
|
||||
}
|
||||
|
||||
public function getGroupName() {
|
||||
public function getGroupName(): string {
|
||||
return $this->l->t('Files');
|
||||
}
|
||||
|
||||
|
||||
@@ -27,6 +27,10 @@ class CommentsEntityEventListener implements IEventListener {
|
||||
return;
|
||||
}
|
||||
|
||||
if ($this->userId === null) {
|
||||
return;
|
||||
}
|
||||
|
||||
$event->addEntityCollection('files', function ($name): bool {
|
||||
$nodes = $this->rootFolder->getUserFolder($this->userId)->getById((int)$name);
|
||||
return !empty($nodes);
|
||||
|
||||
@@ -35,8 +35,7 @@ class CommentsEventListener implements IEventListener {
|
||||
}
|
||||
|
||||
$eventType = $event->getEvent();
|
||||
if ($eventType === CommentsEvent::EVENT_ADD
|
||||
) {
|
||||
if ($eventType === CommentsEvent::EVENT_ADD) {
|
||||
$this->notificationHandler($event);
|
||||
$this->activityHandler($event);
|
||||
return;
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
* SPDX-License-Identifier: AGPL-3.0-or-later
|
||||
*/
|
||||
|
||||
import type { View } from '@nextcloud/files'
|
||||
import type { Folder, View } from '@nextcloud/files'
|
||||
|
||||
import { File, FileAction, Permission } from '@nextcloud/files'
|
||||
import { describe, expect, test, vi } from 'vitest'
|
||||
@@ -26,15 +26,41 @@ describe('Inline unread comments action display name tests', () => {
|
||||
attributes: {
|
||||
'comments-unread': 1,
|
||||
},
|
||||
root: '/files/admin',
|
||||
})
|
||||
|
||||
expect(action).toBeInstanceOf(FileAction)
|
||||
expect(action.id).toBe('comments-unread')
|
||||
expect(action.displayName([file], view)).toBe('')
|
||||
expect(action.title!([file], view)).toBe('1 new comment')
|
||||
expect(action.iconSvgInline([], view)).toMatch(/<svg.+<\/svg>/)
|
||||
expect(action.enabled!([file], view)).toBe(true)
|
||||
expect(action.inline!(file, view)).toBe(true)
|
||||
expect(action.displayName({
|
||||
nodes: [file],
|
||||
view,
|
||||
folder: {} as Folder,
|
||||
contents: [],
|
||||
})).toBe('')
|
||||
expect(action.title!({
|
||||
nodes: [file],
|
||||
view,
|
||||
folder: {} as Folder,
|
||||
contents: [],
|
||||
})).toBe('1 new comment')
|
||||
expect(action.iconSvgInline({
|
||||
nodes: [file],
|
||||
view,
|
||||
folder: {} as Folder,
|
||||
contents: [],
|
||||
})).toMatch(/<svg.+<\/svg>/)
|
||||
expect(action.enabled!({
|
||||
nodes: [file],
|
||||
view,
|
||||
folder: {} as Folder,
|
||||
contents: [],
|
||||
})).toBe(true)
|
||||
expect(action.inline!({
|
||||
nodes: [file],
|
||||
view,
|
||||
folder: {} as Folder,
|
||||
contents: [],
|
||||
})).toBe(true)
|
||||
expect(action.default).toBeUndefined()
|
||||
expect(action.order).toBe(-140)
|
||||
})
|
||||
@@ -49,10 +75,21 @@ describe('Inline unread comments action display name tests', () => {
|
||||
attributes: {
|
||||
'comments-unread': 2,
|
||||
},
|
||||
root: '/files/admin',
|
||||
})
|
||||
|
||||
expect(action.displayName([file], view)).toBe('')
|
||||
expect(action.title!([file], view)).toBe('2 new comments')
|
||||
expect(action.displayName({
|
||||
nodes: [file],
|
||||
view,
|
||||
folder: {} as Folder,
|
||||
contents: [],
|
||||
})).toBe('')
|
||||
expect(action.title!({
|
||||
nodes: [file],
|
||||
view,
|
||||
folder: {} as Folder,
|
||||
contents: [],
|
||||
})).toBe('2 new comments')
|
||||
})
|
||||
})
|
||||
|
||||
@@ -64,10 +101,16 @@ describe('Inline unread comments action enabled tests', () => {
|
||||
owner: 'admin',
|
||||
mime: 'text/plain',
|
||||
permissions: Permission.ALL,
|
||||
attributes: { },
|
||||
attributes: {},
|
||||
root: '/files/admin',
|
||||
})
|
||||
|
||||
expect(action.enabled!([file], view)).toBe(false)
|
||||
expect(action.enabled!({
|
||||
nodes: [file],
|
||||
view,
|
||||
folder: {} as Folder,
|
||||
contents: [],
|
||||
})).toBe(false)
|
||||
})
|
||||
|
||||
test('Action is disabled when file does not have unread comments', () => {
|
||||
@@ -80,9 +123,15 @@ describe('Inline unread comments action enabled tests', () => {
|
||||
attributes: {
|
||||
'comments-unread': 0,
|
||||
},
|
||||
root: '/files/admin',
|
||||
})
|
||||
|
||||
expect(action.enabled!([file], view)).toBe(false)
|
||||
expect(action.enabled!({
|
||||
nodes: [file],
|
||||
view,
|
||||
folder: {} as Folder,
|
||||
contents: [],
|
||||
})).toBe(false)
|
||||
})
|
||||
|
||||
test('Action is enabled when file has a single unread comment', () => {
|
||||
@@ -95,9 +144,15 @@ describe('Inline unread comments action enabled tests', () => {
|
||||
attributes: {
|
||||
'comments-unread': 1,
|
||||
},
|
||||
root: '/files/admin',
|
||||
})
|
||||
|
||||
expect(action.enabled!([file], view)).toBe(true)
|
||||
expect(action.enabled!({
|
||||
nodes: [file],
|
||||
view,
|
||||
folder: {} as Folder,
|
||||
contents: [],
|
||||
})).toBe(true)
|
||||
})
|
||||
|
||||
test('Action is enabled when file has a two unread comments', () => {
|
||||
@@ -110,9 +165,15 @@ describe('Inline unread comments action enabled tests', () => {
|
||||
attributes: {
|
||||
'comments-unread': 2,
|
||||
},
|
||||
root: '/files/admin',
|
||||
})
|
||||
|
||||
expect(action.enabled!([file], view)).toBe(true)
|
||||
expect(action.enabled!({
|
||||
nodes: [file],
|
||||
view,
|
||||
folder: {} as Folder,
|
||||
contents: [],
|
||||
})).toBe(true)
|
||||
})
|
||||
})
|
||||
|
||||
@@ -139,9 +200,15 @@ describe('Inline unread comments action execute tests', () => {
|
||||
attributes: {
|
||||
'comments-unread': 1,
|
||||
},
|
||||
root: '/files/admin',
|
||||
})
|
||||
|
||||
const result = await action.exec!(file, view, '/')
|
||||
const result = await action.exec!({
|
||||
nodes: [file],
|
||||
view,
|
||||
folder: {} as Folder,
|
||||
contents: [],
|
||||
})
|
||||
|
||||
expect(result).toBe(null)
|
||||
expect(setActiveTabMock).toBeCalledWith('comments')
|
||||
@@ -173,9 +240,15 @@ describe('Inline unread comments action execute tests', () => {
|
||||
attributes: {
|
||||
'comments-unread': 1,
|
||||
},
|
||||
root: '/files/admin',
|
||||
})
|
||||
|
||||
const result = await action.exec!(file, view, '/')
|
||||
const result = await action.exec!({
|
||||
nodes: [file],
|
||||
view,
|
||||
folder: {} as Folder,
|
||||
contents: [],
|
||||
})
|
||||
|
||||
expect(result).toBe(false)
|
||||
expect(setActiveTabMock).toBeCalledWith('comments')
|
||||
|
||||
@@ -2,9 +2,6 @@
|
||||
* SPDX-FileCopyrightText: 2023 Nextcloud GmbH and Nextcloud contributors
|
||||
* SPDX-License-Identifier: AGPL-3.0-or-later
|
||||
*/
|
||||
|
||||
import type { Node } from '@nextcloud/files'
|
||||
|
||||
import CommentProcessingSvg from '@mdi/svg/svg/comment-processing.svg?raw'
|
||||
import { FileAction } from '@nextcloud/files'
|
||||
import { n, t } from '@nextcloud/l10n'
|
||||
@@ -13,9 +10,9 @@ import logger from '../logger.js'
|
||||
export const action = new FileAction({
|
||||
id: 'comments-unread',
|
||||
|
||||
title(nodes: Node[]) {
|
||||
const unread = nodes[0].attributes['comments-unread'] as number
|
||||
if (unread >= 0) {
|
||||
title({ nodes }) {
|
||||
const unread = nodes[0]?.attributes['comments-unread'] as number | undefined
|
||||
if (typeof unread === 'number' && unread >= 0) {
|
||||
return n('comments', '1 new comment', '{unread} new comments', unread, { unread })
|
||||
}
|
||||
return t('comments', 'Comment')
|
||||
@@ -26,15 +23,19 @@ export const action = new FileAction({
|
||||
|
||||
iconSvgInline: () => CommentProcessingSvg,
|
||||
|
||||
enabled(nodes: Node[]) {
|
||||
const unread = nodes[0].attributes['comments-unread'] as number | undefined
|
||||
enabled({ nodes }) {
|
||||
const unread = nodes[0]?.attributes?.['comments-unread'] as number | undefined
|
||||
return typeof unread === 'number' && unread > 0
|
||||
},
|
||||
|
||||
async exec(node: Node) {
|
||||
async exec({ nodes }) {
|
||||
if (nodes.length !== 1 || !nodes[0]) {
|
||||
return false
|
||||
}
|
||||
|
||||
try {
|
||||
window.OCA.Files.Sidebar.setActiveTab('comments')
|
||||
await window.OCA.Files.Sidebar.open(node.path)
|
||||
await window.OCA.Files.Sidebar.open(nodes[0].path)
|
||||
return null
|
||||
} catch (error) {
|
||||
logger.error('Error while opening sidebar', { error })
|
||||
|
||||
@@ -12,7 +12,7 @@ use OCA\Comments\Activity\Listener;
|
||||
use OCP\Activity\IEvent;
|
||||
use OCP\Activity\IManager;
|
||||
use OCP\App\IAppManager;
|
||||
use OCP\Comments\CommentsEvent;
|
||||
use OCP\Comments\Events\CommentAddedEvent;
|
||||
use OCP\Comments\IComment;
|
||||
use OCP\Files\Config\ICachedMountFileInfo;
|
||||
use OCP\Files\Config\IMountProviderCollection;
|
||||
@@ -66,14 +66,7 @@ class ListenerTest extends TestCase {
|
||||
->method('getObjectType')
|
||||
->willReturn('files');
|
||||
|
||||
/** @var CommentsEvent|MockObject $event */
|
||||
$event = $this->createMock(CommentsEvent::class);
|
||||
$event->expects($this->any())
|
||||
->method('getComment')
|
||||
->willReturn($comment);
|
||||
$event->expects($this->any())
|
||||
->method('getEvent')
|
||||
->willReturn(CommentsEvent::EVENT_ADD);
|
||||
$event = new CommentAddedEvent($comment);
|
||||
|
||||
/** @var IUser|MockObject $ownerUser */
|
||||
$ownerUser = $this->createMock(IUser::class);
|
||||
|
||||
@@ -12,6 +12,10 @@ use OCA\Comments\Activity\Listener as ActivityListener;
|
||||
use OCA\Comments\Listener\CommentsEventListener;
|
||||
use OCA\Comments\Notification\Listener as NotificationListener;
|
||||
use OCP\Comments\CommentsEvent;
|
||||
use OCP\Comments\Events\BeforeCommentUpdatedEvent;
|
||||
use OCP\Comments\Events\CommentAddedEvent;
|
||||
use OCP\Comments\Events\CommentDeletedEvent;
|
||||
use OCP\Comments\Events\CommentUpdatedEvent;
|
||||
use OCP\Comments\IComment;
|
||||
use PHPUnit\Framework\MockObject\MockObject;
|
||||
use Test\TestCase;
|
||||
@@ -50,10 +54,10 @@ class EventHandlerTest extends TestCase {
|
||||
|
||||
public static function handledProvider(): array {
|
||||
return [
|
||||
[CommentsEvent::EVENT_DELETE],
|
||||
[CommentsEvent::EVENT_UPDATE],
|
||||
[CommentsEvent::EVENT_PRE_UPDATE],
|
||||
[CommentsEvent::EVENT_ADD]
|
||||
['delete'],
|
||||
['update'],
|
||||
['pre_update'],
|
||||
['add']
|
||||
];
|
||||
}
|
||||
|
||||
@@ -65,14 +69,12 @@ class EventHandlerTest extends TestCase {
|
||||
->method('getObjectType')
|
||||
->willReturn('files');
|
||||
|
||||
/** @var CommentsEvent|MockObject $event */
|
||||
$event = $this->createMock(CommentsEvent::class);
|
||||
$event->expects($this->atLeastOnce())
|
||||
->method('getComment')
|
||||
->willReturn($comment);
|
||||
$event->expects($this->atLeastOnce())
|
||||
->method('getEvent')
|
||||
->willReturn($eventType);
|
||||
$event = match ($eventType) {
|
||||
'add' => new CommentAddedEvent($comment),
|
||||
'pre_update' => new BeforeCommentUpdatedEvent($comment),
|
||||
'update' => new CommentUpdatedEvent($comment),
|
||||
'delete' => new CommentDeletedEvent($comment),
|
||||
};
|
||||
|
||||
$this->notificationListener->expects($this->once())
|
||||
->method('evaluate')
|
||||
|
||||
@@ -8,7 +8,10 @@
|
||||
namespace OCA\Comments\Tests\Unit\Notification;
|
||||
|
||||
use OCA\Comments\Notification\Listener;
|
||||
use OCP\Comments\CommentsEvent;
|
||||
use OCP\Comments\Events\BeforeCommentUpdatedEvent;
|
||||
use OCP\Comments\Events\CommentAddedEvent;
|
||||
use OCP\Comments\Events\CommentDeletedEvent;
|
||||
use OCP\Comments\Events\CommentUpdatedEvent;
|
||||
use OCP\Comments\IComment;
|
||||
use OCP\IURLGenerator;
|
||||
use OCP\IUserManager;
|
||||
@@ -37,10 +40,10 @@ class ListenerTest extends TestCase {
|
||||
|
||||
public static function eventProvider(): array {
|
||||
return [
|
||||
[CommentsEvent::EVENT_ADD, 'notify'],
|
||||
[CommentsEvent::EVENT_UPDATE, 'notify'],
|
||||
[CommentsEvent::EVENT_PRE_UPDATE, 'markProcessed'],
|
||||
[CommentsEvent::EVENT_DELETE, 'markProcessed']
|
||||
['add', 'notify'],
|
||||
['update', 'notify'],
|
||||
['pre_update', 'markProcessed'],
|
||||
['delete', 'markProcessed']
|
||||
];
|
||||
}
|
||||
|
||||
@@ -49,7 +52,7 @@ class ListenerTest extends TestCase {
|
||||
* @param string $notificationMethod
|
||||
*/
|
||||
#[\PHPUnit\Framework\Attributes\DataProvider('eventProvider')]
|
||||
public function testEvaluate($eventType, $notificationMethod): void {
|
||||
public function testEvaluate(string $eventType, $notificationMethod): void {
|
||||
/** @var IComment|MockObject $comment */
|
||||
$comment = $this->createMock(IComment::class);
|
||||
$comment->expects($this->any())
|
||||
@@ -72,14 +75,12 @@ class ListenerTest extends TestCase {
|
||||
->method('getId')
|
||||
->willReturn('1234');
|
||||
|
||||
/** @var CommentsEvent|MockObject $event */
|
||||
$event = $this->createMock(CommentsEvent::class);
|
||||
$event->expects($this->once())
|
||||
->method('getComment')
|
||||
->willReturn($comment);
|
||||
$event->expects(($this->any()))
|
||||
->method(('getEvent'))
|
||||
->willReturn($eventType);
|
||||
$event = match ($eventType) {
|
||||
'add' => new CommentAddedEvent($comment),
|
||||
'pre_update' => new BeforeCommentUpdatedEvent($comment),
|
||||
'update' => new CommentUpdatedEvent($comment),
|
||||
'delete' => new CommentDeletedEvent($comment),
|
||||
};
|
||||
|
||||
/** @var INotification|MockObject $notification */
|
||||
$notification = $this->createMock(INotification::class);
|
||||
@@ -124,14 +125,12 @@ class ListenerTest extends TestCase {
|
||||
->method('getMentions')
|
||||
->willReturn([]);
|
||||
|
||||
/** @var CommentsEvent|MockObject $event */
|
||||
$event = $this->createMock(CommentsEvent::class);
|
||||
$event->expects($this->once())
|
||||
->method('getComment')
|
||||
->willReturn($comment);
|
||||
$event->expects(($this->any()))
|
||||
->method(('getEvent'))
|
||||
->willReturn($eventType);
|
||||
$event = match ($eventType) {
|
||||
'add' => new CommentAddedEvent($comment),
|
||||
'pre_update' => new BeforeCommentUpdatedEvent($comment),
|
||||
'update' => new CommentUpdatedEvent($comment),
|
||||
'delete' => new CommentDeletedEvent($comment),
|
||||
};
|
||||
|
||||
$this->notificationManager->expects($this->never())
|
||||
->method('createNotification');
|
||||
@@ -162,14 +161,7 @@ class ListenerTest extends TestCase {
|
||||
->method('getId')
|
||||
->willReturn('1234');
|
||||
|
||||
/** @var CommentsEvent|MockObject $event */
|
||||
$event = $this->createMock(CommentsEvent::class);
|
||||
$event->expects($this->once())
|
||||
->method('getComment')
|
||||
->willReturn($comment);
|
||||
$event->expects(($this->any()))
|
||||
->method(('getEvent'))
|
||||
->willReturn(CommentsEvent::EVENT_ADD);
|
||||
$event = new CommentAddedEvent($comment);
|
||||
|
||||
/** @var INotification|MockObject $notification */
|
||||
$notification = $this->createMock(INotification::class);
|
||||
|
||||
@@ -7,14 +7,14 @@ namespace Composer\Autoload;
|
||||
class ComposerStaticInitContactsInteraction
|
||||
{
|
||||
public static $prefixLengthsPsr4 = array (
|
||||
'O' =>
|
||||
'O' =>
|
||||
array (
|
||||
'OCA\\ContactsInteraction\\' => 24,
|
||||
),
|
||||
);
|
||||
|
||||
public static $prefixDirsPsr4 = array (
|
||||
'OCA\\ContactsInteraction\\' =>
|
||||
'OCA\\ContactsInteraction\\' =>
|
||||
array (
|
||||
0 => __DIR__ . '/..' . '/../lib',
|
||||
),
|
||||
|
||||
@@ -7,14 +7,14 @@ namespace Composer\Autoload;
|
||||
class ComposerStaticInitDashboard
|
||||
{
|
||||
public static $prefixLengthsPsr4 = array (
|
||||
'O' =>
|
||||
'O' =>
|
||||
array (
|
||||
'OCA\\Dashboard\\' => 14,
|
||||
),
|
||||
);
|
||||
|
||||
public static $prefixDirsPsr4 = array (
|
||||
'OCA\\Dashboard\\' =>
|
||||
'OCA\\Dashboard\\' =>
|
||||
array (
|
||||
0 => __DIR__ . '/..' . '/../lib',
|
||||
),
|
||||
|
||||
@@ -10,7 +10,7 @@
|
||||
<name>WebDAV</name>
|
||||
<summary>WebDAV endpoint</summary>
|
||||
<description>WebDAV endpoint</description>
|
||||
<version>1.35.0</version>
|
||||
<version>1.36.0</version>
|
||||
<licence>agpl</licence>
|
||||
<author>owncloud.org</author>
|
||||
<namespace>DAV</namespace>
|
||||
|
||||
@@ -68,47 +68,55 @@ $requestUri = Server::get(IRequest::class)->getRequestUri();
|
||||
$linkCheckPlugin = new PublicLinkCheckPlugin();
|
||||
$filesDropPlugin = new FilesDropPlugin();
|
||||
|
||||
$server = $serverFactory->createServer(false, $baseuri, $requestUri, $authPlugin, function (\Sabre\DAV\Server $server) use ($authBackend, $linkCheckPlugin, $filesDropPlugin) {
|
||||
$isAjax = in_array('XMLHttpRequest', explode(',', $_SERVER['HTTP_X_REQUESTED_WITH'] ?? ''));
|
||||
/** @var FederatedShareProvider $shareProvider */
|
||||
$federatedShareProvider = Server::get(FederatedShareProvider::class);
|
||||
if ($federatedShareProvider->isOutgoingServer2serverShareEnabled() === false && !$isAjax) {
|
||||
// this is what is thrown when trying to access a non-existing share
|
||||
throw new \Sabre\DAV\Exception\NotAuthenticated();
|
||||
}
|
||||
$server = $serverFactory->createServer(
|
||||
true,
|
||||
$baseuri,
|
||||
$requestUri,
|
||||
$authPlugin,
|
||||
function (\Sabre\DAV\Server $server) use (
|
||||
$authBackend,
|
||||
$linkCheckPlugin,
|
||||
$filesDropPlugin
|
||||
) {
|
||||
$isAjax = in_array('XMLHttpRequest', explode(',', $_SERVER['HTTP_X_REQUESTED_WITH'] ?? ''));
|
||||
/** @var FederatedShareProvider $shareProvider */
|
||||
$federatedShareProvider = Server::get(FederatedShareProvider::class);
|
||||
if ($federatedShareProvider->isOutgoingServer2serverShareEnabled() === false && !$isAjax) {
|
||||
// this is what is thrown when trying to access a non-existing share
|
||||
throw new \Sabre\DAV\Exception\NotAuthenticated();
|
||||
}
|
||||
|
||||
$share = $authBackend->getShare();
|
||||
$owner = $share->getShareOwner();
|
||||
$isReadable = $share->getPermissions() & Constants::PERMISSION_READ;
|
||||
$fileId = $share->getNodeId();
|
||||
$share = $authBackend->getShare();
|
||||
$owner = $share->getShareOwner();
|
||||
$isReadable = $share->getPermissions() & Constants::PERMISSION_READ;
|
||||
$fileId = $share->getNodeId();
|
||||
|
||||
// FIXME: should not add storage wrappers outside of preSetup, need to find a better way
|
||||
$previousLog = Filesystem::logWarningWhenAddingStorageWrapper(false);
|
||||
Filesystem::addStorageWrapper('sharePermissions', function ($mountPoint, $storage) use ($share) {
|
||||
return new PermissionsMask(['storage' => $storage, 'mask' => $share->getPermissions() | Constants::PERMISSION_SHARE]);
|
||||
// FIXME: should not add storage wrappers outside of preSetup, need to find a better way
|
||||
$previousLog = Filesystem::logWarningWhenAddingStorageWrapper(false);
|
||||
Filesystem::addStorageWrapper('sharePermissions', function ($mountPoint, $storage) use ($share) {
|
||||
return new PermissionsMask(['storage' => $storage, 'mask' => $share->getPermissions() | Constants::PERMISSION_SHARE]);
|
||||
});
|
||||
Filesystem::addStorageWrapper('shareOwner', function ($mountPoint, $storage) use ($share) {
|
||||
return new PublicOwnerWrapper(['storage' => $storage, 'owner' => $share->getShareOwner()]);
|
||||
});
|
||||
Filesystem::logWarningWhenAddingStorageWrapper($previousLog);
|
||||
|
||||
$rootFolder = Server::get(IRootFolder::class);
|
||||
$userFolder = $rootFolder->getUserFolder($owner);
|
||||
$node = $userFolder->getFirstNodeById($fileId);
|
||||
if (!$node) {
|
||||
throw new \Sabre\DAV\Exception\NotFound();
|
||||
}
|
||||
$linkCheckPlugin->setFileInfo($node);
|
||||
|
||||
// If not readable (files_drop) enable the filesdrop plugin
|
||||
if (!$isReadable) {
|
||||
$filesDropPlugin->enable();
|
||||
}
|
||||
$filesDropPlugin->setShare($share);
|
||||
|
||||
return new View($node->getPath());
|
||||
});
|
||||
Filesystem::addStorageWrapper('shareOwner', function ($mountPoint, $storage) use ($share) {
|
||||
return new PublicOwnerWrapper(['storage' => $storage, 'owner' => $share->getShareOwner()]);
|
||||
});
|
||||
Filesystem::logWarningWhenAddingStorageWrapper($previousLog);
|
||||
|
||||
$rootFolder = Server::get(IRootFolder::class);
|
||||
$userFolder = $rootFolder->getUserFolder($owner);
|
||||
$node = $userFolder->getFirstNodeById($fileId);
|
||||
if (!$node) {
|
||||
throw new \Sabre\DAV\Exception\NotFound();
|
||||
}
|
||||
$linkCheckPlugin->setFileInfo($node);
|
||||
|
||||
// If not readable (files_drop) enable the filesdrop plugin
|
||||
if (!$isReadable) {
|
||||
$filesDropPlugin->enable();
|
||||
}
|
||||
$filesDropPlugin->setShare($share);
|
||||
|
||||
$view = new View($node->getPath());
|
||||
return $view;
|
||||
});
|
||||
|
||||
$server->addPlugin($linkCheckPlugin);
|
||||
$server->addPlugin($filesDropPlugin);
|
||||
|
||||
@@ -386,6 +386,7 @@ return array(
|
||||
'OCA\\DAV\\Migration\\Version1031Date20240610134258' => $baseDir . '/../lib/Migration/Version1031Date20240610134258.php',
|
||||
'OCA\\DAV\\Migration\\Version1034Date20250605132605' => $baseDir . '/../lib/Migration/Version1034Date20250605132605.php',
|
||||
'OCA\\DAV\\Migration\\Version1034Date20250813093701' => $baseDir . '/../lib/Migration/Version1034Date20250813093701.php',
|
||||
'OCA\\DAV\\Migration\\Version1036Date20251202000000' => $baseDir . '/../lib/Migration/Version1036Date20251202000000.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',
|
||||
|
||||
@@ -7,14 +7,14 @@ namespace Composer\Autoload;
|
||||
class ComposerStaticInitDAV
|
||||
{
|
||||
public static $prefixLengthsPsr4 = array (
|
||||
'O' =>
|
||||
'O' =>
|
||||
array (
|
||||
'OCA\\DAV\\' => 8,
|
||||
),
|
||||
);
|
||||
|
||||
public static $prefixDirsPsr4 = array (
|
||||
'OCA\\DAV\\' =>
|
||||
'OCA\\DAV\\' =>
|
||||
array (
|
||||
0 => __DIR__ . '/..' . '/../lib',
|
||||
),
|
||||
@@ -401,6 +401,7 @@ class ComposerStaticInitDAV
|
||||
'OCA\\DAV\\Migration\\Version1031Date20240610134258' => __DIR__ . '/..' . '/../lib/Migration/Version1031Date20240610134258.php',
|
||||
'OCA\\DAV\\Migration\\Version1034Date20250605132605' => __DIR__ . '/..' . '/../lib/Migration/Version1034Date20250605132605.php',
|
||||
'OCA\\DAV\\Migration\\Version1034Date20250813093701' => __DIR__ . '/..' . '/../lib/Migration/Version1034Date20250813093701.php',
|
||||
'OCA\\DAV\\Migration\\Version1036Date20251202000000' => __DIR__ . '/..' . '/../lib/Migration/Version1036Date20251202000000.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
@@ -252,7 +252,7 @@ OC.L10N.register(
|
||||
"Completed on %s" : "Erledigt am %s",
|
||||
"Due on %s by %s" : "Fällig am %s von %s",
|
||||
"Due on %s" : "Fällig am %s",
|
||||
"Welcome to Nextcloud Calendar!\n\nThis is a sample event - explore the flexibility of planning with Nextcloud Calendar by making any edits you want!\n\nWith Nextcloud Calendar, you can:\n- Create, edit, and manage events effortlessly.\n- Create multiple calendars and share them with teammates, friends, or family.\n- Check availability and display your busy times to others.\n- Seamlessly integrate with apps and devices via CalDAV.\n- Customize your experience: schedule recurring events, adjust notifications and other settings." : "Willkommen bei Nextcloud Calendar!\n\nDies ist ein Beispielereignis – entdecke die Flexibilität der Planung mit Nextcloud Calendar und nimm beliebige Änderungen vor!\n\nMit Nextcloud Calendar kannst du:\n– Ereignisse mühelos erstellen, bearbeiten und verwalten.\n– Mehrere Kalender erstellen und mit Teamkollegen, Freunden oder der Familie teilen.\n– Verfügbarkeit prüfen und Ihre Termine anderen anzeigen.\n– Nahtlose Integration mit Apps und Geräten über CalDAV.\n– Individuelle Gestaltung: Plane wiederkehrende Ereignisse, passe Benachrichtigungen und andere Einstellungen an.",
|
||||
"Welcome to Nextcloud Calendar!\n\nThis is a sample event - explore the flexibility of planning with Nextcloud Calendar by making any edits you want!\n\nWith Nextcloud Calendar, you can:\n- Create, edit, and manage events effortlessly.\n- Create multiple calendars and share them with teammates, friends, or family.\n- Check availability and display your busy times to others.\n- Seamlessly integrate with apps and devices via CalDAV.\n- Customize your experience: schedule recurring events, adjust notifications and other settings." : "Willkommen bei Nextcloud Calendar!\n\nDies ist ein Beispielereignis – entdecke die Flexibilität der Planung mit Nextcloud Calendar und nimm beliebige Änderungen vor!\n\nMit Nextcloud Calendar kannst du:\n– Ereignisse mühelos erstellen, bearbeiten und verwalten.\n– Mehrere Kalender erstellen und mit Teamkollegen, Freunden oder der Familie teilen.\n– Verfügbarkeit prüfen und deine Termine anderen anzeigen.\n– Nahtlose Integration mit Apps und Geräten über CalDAV.\n– Individuelle Gestaltung: Plane wiederkehrende Ereignisse, passe Benachrichtigungen und andere Einstellungen an.",
|
||||
"Example event - open me!" : "Beispielereignis – öffne mich!",
|
||||
"System Address Book" : "Systemadressbuch",
|
||||
"The system address book contains contact information for all users in your instance." : "Das Systemadressbuch enthält Kontaktinformationen für alle Benutzer in dieser Instanz.",
|
||||
|
||||
@@ -250,7 +250,7 @@
|
||||
"Completed on %s" : "Erledigt am %s",
|
||||
"Due on %s by %s" : "Fällig am %s von %s",
|
||||
"Due on %s" : "Fällig am %s",
|
||||
"Welcome to Nextcloud Calendar!\n\nThis is a sample event - explore the flexibility of planning with Nextcloud Calendar by making any edits you want!\n\nWith Nextcloud Calendar, you can:\n- Create, edit, and manage events effortlessly.\n- Create multiple calendars and share them with teammates, friends, or family.\n- Check availability and display your busy times to others.\n- Seamlessly integrate with apps and devices via CalDAV.\n- Customize your experience: schedule recurring events, adjust notifications and other settings." : "Willkommen bei Nextcloud Calendar!\n\nDies ist ein Beispielereignis – entdecke die Flexibilität der Planung mit Nextcloud Calendar und nimm beliebige Änderungen vor!\n\nMit Nextcloud Calendar kannst du:\n– Ereignisse mühelos erstellen, bearbeiten und verwalten.\n– Mehrere Kalender erstellen und mit Teamkollegen, Freunden oder der Familie teilen.\n– Verfügbarkeit prüfen und Ihre Termine anderen anzeigen.\n– Nahtlose Integration mit Apps und Geräten über CalDAV.\n– Individuelle Gestaltung: Plane wiederkehrende Ereignisse, passe Benachrichtigungen und andere Einstellungen an.",
|
||||
"Welcome to Nextcloud Calendar!\n\nThis is a sample event - explore the flexibility of planning with Nextcloud Calendar by making any edits you want!\n\nWith Nextcloud Calendar, you can:\n- Create, edit, and manage events effortlessly.\n- Create multiple calendars and share them with teammates, friends, or family.\n- Check availability and display your busy times to others.\n- Seamlessly integrate with apps and devices via CalDAV.\n- Customize your experience: schedule recurring events, adjust notifications and other settings." : "Willkommen bei Nextcloud Calendar!\n\nDies ist ein Beispielereignis – entdecke die Flexibilität der Planung mit Nextcloud Calendar und nimm beliebige Änderungen vor!\n\nMit Nextcloud Calendar kannst du:\n– Ereignisse mühelos erstellen, bearbeiten und verwalten.\n– Mehrere Kalender erstellen und mit Teamkollegen, Freunden oder der Familie teilen.\n– Verfügbarkeit prüfen und deine Termine anderen anzeigen.\n– Nahtlose Integration mit Apps und Geräten über CalDAV.\n– Individuelle Gestaltung: Plane wiederkehrende Ereignisse, passe Benachrichtigungen und andere Einstellungen an.",
|
||||
"Example event - open me!" : "Beispielereignis – öffne mich!",
|
||||
"System Address Book" : "Systemadressbuch",
|
||||
"The system address book contains contact information for all users in your instance." : "Das Systemadressbuch enthält Kontaktinformationen für alle Benutzer in dieser Instanz.",
|
||||
|
||||
@@ -73,7 +73,19 @@ OC.L10N.register(
|
||||
"Where: %s" : "Dove: %s",
|
||||
"%1$s via %2$s" : "%1$s tramite %2$s",
|
||||
"In the past on %1$s for the entire day" : "In passato ogni %1$s per l'intero giorno",
|
||||
"_In %n minute on %1$s for the entire day_::_In %n minutes on %1$s for the entire day_" : ["Fra %n minuto il %1$s per l'intero giorno","Fra %n minuti il %1$s per l'intero giorno","Fra %n minuti il %1$s per l'intero giorno"],
|
||||
"_In %n hour on %1$s for the entire day_::_In %n hours on %1$s for the entire day_" : ["Fra %n ora il %1$s per l'intero giorno","Fra %n ore il %1$s per l'intero giorno","Fra %n ore il %1$s per l'intero giorno"],
|
||||
"_In %n day on %1$s for the entire day_::_In %n days on %1$s for the entire day_" : ["Fra %n giorno il %1$s per l'intero giorno","Fra %n giorni il %1$s per l'intero giorno","Fra %n giorni il %1$s per l'intero giorno"],
|
||||
"_In %n week on %1$s for the entire day_::_In %n weeks on %1$s for the entire day_" : ["Fra %n settimana il %1$s per l'intero giorno","Fra %n settimane il %1$s per l'intero giorno","Fra %n settimane il %1$s per l'intero giorno"],
|
||||
"_In %n month on %1$s for the entire day_::_In %n months on %1$s for the entire day_" : ["Fra %n mese il %1$s per l'intero giorno","Fra %n mesi il %1$s per l'intero giorno","Fra %n mesi il %1$s per l'intero giorno"],
|
||||
"_In %n year on %1$s for the entire day_::_In %n years on %1$s for the entire day_" : ["Fra %n anno di %1$s per l'intero giorno","Fra %n anni di %1$s per l'intero giorno","Fra %n anni di %1$s per l'intero giorno"],
|
||||
"In the past on %1$s between %2$s - %3$s" : "In passato il %1$s nelle ore %2$s - %3$s",
|
||||
"_In %n minute on %1$s between %2$s - %3$s_::_In %n minutes on %1$s between %2$s - %3$s_" : ["Fra %n minuto il %1$s nelle ore %2$s - %3$s","Fra %n minuti il %1$s nelle ore %2$s - %3$s","Fra %n minuti il %1$s nelle ore %2$s - %3$s"],
|
||||
"_In %n hour on %1$s between %2$s - %3$s_::_In %n hours on %1$s between %2$s - %3$s_" : ["Fra %n ora il %1$s nelle ore %2$s - %3$s","Fra %n ore il %1$s nelle ore %2$s - %3$s","Fra %n ore il %1$s nelle ore %2$s - %3$s"],
|
||||
"_In %n day on %1$s between %2$s - %3$s_::_In %n days on %1$s between %2$s - %3$s_" : ["Fra %n giorno il %1$s nelle ore %2$s - %3$s","Fra %n giorni il %1$s nelle ore %2$s - %3$s","Fra %n giorni il %1$s nelle ore %2$s - %3$s"],
|
||||
"_In %n week on %1$s between %2$s - %3$s_::_In %n weeks on %1$s between %2$s - %3$s_" : ["Fra %n settimana il %1$s nelle ore %2$s - %3$s","Fra %n settimane il %1$s nelle ore %2$s - %3$s","Fra %n settimane il %1$s nelle ore %2$s - %3$s"],
|
||||
"_In %n month on %1$s between %2$s - %3$s_::_In %n months on %1$s between %2$s - %3$s_" : ["Fra %n mese il %1$s nelle ore %2$s - %3$s","Fra %n mesi il %1$s nelle ore %2$s - %3$s","Fra %n mesi il %1$s nelle ore %2$s - %3$s"],
|
||||
"_In %n year on %1$s between %2$s - %3$s_::_In %n years on %1$s between %2$s - %3$s_" : ["Fra %n anno il %1$s nelle ore %2$s - %3$s","Fra %n anni il %1$s nelle ore %2$s - %3$s","Fra %n anni il %1$s nelle ore %2$s - %3$s"],
|
||||
"Could not generate when statement" : "Impossibile generare l'istruzione \"quando\"",
|
||||
"Every Day for the entire day" : "Ogni giorno per l'intero giorno",
|
||||
"Every Day for the entire day until %1$s" : "Ogni Giorno per l'intero giorno fino a %1$s",
|
||||
@@ -111,8 +123,26 @@ OC.L10N.register(
|
||||
"On specific dates for the entire day until %1$s" : "In una specifica data per l'intero giorno fino al %1$s",
|
||||
"On specific dates between %1$s - %2$s until %3$s" : "In una specifica data nelle ore %1$s - %2$s fino al %3$s",
|
||||
"In the past on %1$s" : "In passato il %1$s",
|
||||
"_In %n minute on %1$s_::_In %n minutes on %1$s_" : ["Fra %n minuto il %1$s","Fra %n minuti il %1$s","Fra %n minuti il %1$s"],
|
||||
"_In %n hour on %1$s_::_In %n hours on %1$s_" : ["Fra %n ora il %1$s","Fra %n ore il %1$s","Fra %n ore il %1$s"],
|
||||
"_In %n day on %1$s_::_In %n days on %1$s_" : ["Fra %n giorno il %1$s","Fra %n giorni il %1$s","Fra %n giorni il %1$s"],
|
||||
"_In %n week on %1$s_::_In %n weeks on %1$s_" : ["Fra %n settimana il %1$s","Fra %n settimane il %1$s","Fra %n settimane il %1$s"],
|
||||
"_In %n month on %1$s_::_In %n months on %1$s_" : ["Fra %n mese il %1$s","Fra %n mesi il %1$s","Fra %n mesi il %1$s"],
|
||||
"_In %n year on %1$s_::_In %n years on %1$s_" : ["Fra %n anno il %1$s","Fra %n anni il %1$s","Fra %n anni il %1$s"],
|
||||
"In the past on %1$s then on %2$s" : "In passato il %1$s successivamente il %2$s",
|
||||
"_In %n minute on %1$s then on %2$s_::_In %n minutes on %1$s then on %2$s_" : ["Fra %n minuto %1$s successivamente il %2$s","Fra %n minuti %1$s successivamente il %2$s","Fra %n minuti %1$s successivamente il %2$s"],
|
||||
"_In %n hour on %1$s then on %2$s_::_In %n hours on %1$s then on %2$s_" : ["Fra %n ora %1$s successivamente il %2$s","Fra %n ore %1$s successivamente il %2$s","Fra %n ore %1$s successivamente il %2$s"],
|
||||
"_In %n day on %1$s then on %2$s_::_In %n days on %1$s then on %2$s_" : ["Fra %n giorno %1$s successivamente il %2$s","Fra %n giorni %1$s successivamente il %2$s","Fra %n giorni %1$s successivamente il %2$s"],
|
||||
"_In %n week on %1$s then on %2$s_::_In %n weeks on %1$s then on %2$s_" : ["Fra %n settimana %1$s successivamente il %2$s","Fra %n settimane %1$s successivamente il %2$s","Fra %n settimane %1$s successivamente il %2$s"],
|
||||
"_In %n month on %1$s then on %2$s_::_In %n months on %1$s then on %2$s_" : ["Fra %n mese %1$s successivamente il %2$s","Fra %n mesi %1$s successivamente il %2$s","Fra %n mesi %1$s successivamente il %2$s"],
|
||||
"_In %n year on %1$s then on %2$s_::_In %n years on %1$s then on %2$s_" : ["Fra %n anno %1$s successivamente il %2$s","Fra %n anni %1$s successivamente il %2$s","Fra %n anni %1$s successivamente il %2$s"],
|
||||
"In the past on %1$s then on %2$s and %3$s" : "In passato il %1$s successivamente il %2$s e %3$s",
|
||||
"_In %n minute on %1$s then on %2$s and %3$s_::_In %n minutes on %1$s then on %2$s and %3$s_" : ["Fra %n minuto il %1$s successivamente il %2$s e %3$s","Fra %n minuti il %1$s successivamente il %2$s e %3$s","Fra %n minuti il %1$s successivamente il %2$s e %3$s"],
|
||||
"_In %n hour on %1$s then on %2$s and %3$s_::_In %n hours on %1$s then on %2$s and %3$s_" : ["Fra %n ora il %1$s successivamente il %2$s e %3$s","Fra %n ore il %1$s successivamente il %2$s e %3$s","Fra %n ore il %1$s successivamente il %2$s e %3$s"],
|
||||
"_In %n day on %1$s then on %2$s and %3$s_::_In %n days on %1$s then on %2$s and %3$s_" : ["Fra %n giorno il %1$s successivamente il %2$s e %3$s","Fra %n giorni il %1$s successivamente il %2$s e %3$s","Fra %n giorni il %1$s successivamente il %2$s e %3$s"],
|
||||
"_In %n week on %1$s then on %2$s and %3$s_::_In %n weeks on %1$s then on %2$s and %3$s_" : ["Fra %n settimana il %1$s successivamente il %2$s e %3$s","Fra %n settimane il %1$s successivamente il %2$s e %3$s","Fra %n settimane il %1$s successivamente il %2$s e %3$s"],
|
||||
"_In %n month on %1$s then on %2$s and %3$s_::_In %n months on %1$s then on %2$s and %3$s_" : ["Fra %n mese il %1$s successivamente il %2$s e %3$s","Fra %n mesi il %1$s successivamente il %2$s e %3$s","Fra %n mesi il %1$s successivamente il %2$s e %3$s"],
|
||||
"_In %n year on %1$s then on %2$s and %3$s_::_In %n years on %1$s then on %2$s and %3$s_" : ["Fra %n anno il %1$s successivamente il %2$s e %3$s","Fra %n anni il %1$s successivamente il %2$s e %3$s","Fra %n anni il %1$s successivamente il %2$s e %3$s"],
|
||||
"Could not generate next recurrence statement" : "Impossibile generare l'istruzione della prossima ricorrenza ",
|
||||
"Cancelled: %1$s" : "Annullato: %1$s",
|
||||
"\"%1$s\" has been canceled" : "\"%1$s\" è stato annullato",
|
||||
|
||||
@@ -71,7 +71,19 @@
|
||||
"Where: %s" : "Dove: %s",
|
||||
"%1$s via %2$s" : "%1$s tramite %2$s",
|
||||
"In the past on %1$s for the entire day" : "In passato ogni %1$s per l'intero giorno",
|
||||
"_In %n minute on %1$s for the entire day_::_In %n minutes on %1$s for the entire day_" : ["Fra %n minuto il %1$s per l'intero giorno","Fra %n minuti il %1$s per l'intero giorno","Fra %n minuti il %1$s per l'intero giorno"],
|
||||
"_In %n hour on %1$s for the entire day_::_In %n hours on %1$s for the entire day_" : ["Fra %n ora il %1$s per l'intero giorno","Fra %n ore il %1$s per l'intero giorno","Fra %n ore il %1$s per l'intero giorno"],
|
||||
"_In %n day on %1$s for the entire day_::_In %n days on %1$s for the entire day_" : ["Fra %n giorno il %1$s per l'intero giorno","Fra %n giorni il %1$s per l'intero giorno","Fra %n giorni il %1$s per l'intero giorno"],
|
||||
"_In %n week on %1$s for the entire day_::_In %n weeks on %1$s for the entire day_" : ["Fra %n settimana il %1$s per l'intero giorno","Fra %n settimane il %1$s per l'intero giorno","Fra %n settimane il %1$s per l'intero giorno"],
|
||||
"_In %n month on %1$s for the entire day_::_In %n months on %1$s for the entire day_" : ["Fra %n mese il %1$s per l'intero giorno","Fra %n mesi il %1$s per l'intero giorno","Fra %n mesi il %1$s per l'intero giorno"],
|
||||
"_In %n year on %1$s for the entire day_::_In %n years on %1$s for the entire day_" : ["Fra %n anno di %1$s per l'intero giorno","Fra %n anni di %1$s per l'intero giorno","Fra %n anni di %1$s per l'intero giorno"],
|
||||
"In the past on %1$s between %2$s - %3$s" : "In passato il %1$s nelle ore %2$s - %3$s",
|
||||
"_In %n minute on %1$s between %2$s - %3$s_::_In %n minutes on %1$s between %2$s - %3$s_" : ["Fra %n minuto il %1$s nelle ore %2$s - %3$s","Fra %n minuti il %1$s nelle ore %2$s - %3$s","Fra %n minuti il %1$s nelle ore %2$s - %3$s"],
|
||||
"_In %n hour on %1$s between %2$s - %3$s_::_In %n hours on %1$s between %2$s - %3$s_" : ["Fra %n ora il %1$s nelle ore %2$s - %3$s","Fra %n ore il %1$s nelle ore %2$s - %3$s","Fra %n ore il %1$s nelle ore %2$s - %3$s"],
|
||||
"_In %n day on %1$s between %2$s - %3$s_::_In %n days on %1$s between %2$s - %3$s_" : ["Fra %n giorno il %1$s nelle ore %2$s - %3$s","Fra %n giorni il %1$s nelle ore %2$s - %3$s","Fra %n giorni il %1$s nelle ore %2$s - %3$s"],
|
||||
"_In %n week on %1$s between %2$s - %3$s_::_In %n weeks on %1$s between %2$s - %3$s_" : ["Fra %n settimana il %1$s nelle ore %2$s - %3$s","Fra %n settimane il %1$s nelle ore %2$s - %3$s","Fra %n settimane il %1$s nelle ore %2$s - %3$s"],
|
||||
"_In %n month on %1$s between %2$s - %3$s_::_In %n months on %1$s between %2$s - %3$s_" : ["Fra %n mese il %1$s nelle ore %2$s - %3$s","Fra %n mesi il %1$s nelle ore %2$s - %3$s","Fra %n mesi il %1$s nelle ore %2$s - %3$s"],
|
||||
"_In %n year on %1$s between %2$s - %3$s_::_In %n years on %1$s between %2$s - %3$s_" : ["Fra %n anno il %1$s nelle ore %2$s - %3$s","Fra %n anni il %1$s nelle ore %2$s - %3$s","Fra %n anni il %1$s nelle ore %2$s - %3$s"],
|
||||
"Could not generate when statement" : "Impossibile generare l'istruzione \"quando\"",
|
||||
"Every Day for the entire day" : "Ogni giorno per l'intero giorno",
|
||||
"Every Day for the entire day until %1$s" : "Ogni Giorno per l'intero giorno fino a %1$s",
|
||||
@@ -109,8 +121,26 @@
|
||||
"On specific dates for the entire day until %1$s" : "In una specifica data per l'intero giorno fino al %1$s",
|
||||
"On specific dates between %1$s - %2$s until %3$s" : "In una specifica data nelle ore %1$s - %2$s fino al %3$s",
|
||||
"In the past on %1$s" : "In passato il %1$s",
|
||||
"_In %n minute on %1$s_::_In %n minutes on %1$s_" : ["Fra %n minuto il %1$s","Fra %n minuti il %1$s","Fra %n minuti il %1$s"],
|
||||
"_In %n hour on %1$s_::_In %n hours on %1$s_" : ["Fra %n ora il %1$s","Fra %n ore il %1$s","Fra %n ore il %1$s"],
|
||||
"_In %n day on %1$s_::_In %n days on %1$s_" : ["Fra %n giorno il %1$s","Fra %n giorni il %1$s","Fra %n giorni il %1$s"],
|
||||
"_In %n week on %1$s_::_In %n weeks on %1$s_" : ["Fra %n settimana il %1$s","Fra %n settimane il %1$s","Fra %n settimane il %1$s"],
|
||||
"_In %n month on %1$s_::_In %n months on %1$s_" : ["Fra %n mese il %1$s","Fra %n mesi il %1$s","Fra %n mesi il %1$s"],
|
||||
"_In %n year on %1$s_::_In %n years on %1$s_" : ["Fra %n anno il %1$s","Fra %n anni il %1$s","Fra %n anni il %1$s"],
|
||||
"In the past on %1$s then on %2$s" : "In passato il %1$s successivamente il %2$s",
|
||||
"_In %n minute on %1$s then on %2$s_::_In %n minutes on %1$s then on %2$s_" : ["Fra %n minuto %1$s successivamente il %2$s","Fra %n minuti %1$s successivamente il %2$s","Fra %n minuti %1$s successivamente il %2$s"],
|
||||
"_In %n hour on %1$s then on %2$s_::_In %n hours on %1$s then on %2$s_" : ["Fra %n ora %1$s successivamente il %2$s","Fra %n ore %1$s successivamente il %2$s","Fra %n ore %1$s successivamente il %2$s"],
|
||||
"_In %n day on %1$s then on %2$s_::_In %n days on %1$s then on %2$s_" : ["Fra %n giorno %1$s successivamente il %2$s","Fra %n giorni %1$s successivamente il %2$s","Fra %n giorni %1$s successivamente il %2$s"],
|
||||
"_In %n week on %1$s then on %2$s_::_In %n weeks on %1$s then on %2$s_" : ["Fra %n settimana %1$s successivamente il %2$s","Fra %n settimane %1$s successivamente il %2$s","Fra %n settimane %1$s successivamente il %2$s"],
|
||||
"_In %n month on %1$s then on %2$s_::_In %n months on %1$s then on %2$s_" : ["Fra %n mese %1$s successivamente il %2$s","Fra %n mesi %1$s successivamente il %2$s","Fra %n mesi %1$s successivamente il %2$s"],
|
||||
"_In %n year on %1$s then on %2$s_::_In %n years on %1$s then on %2$s_" : ["Fra %n anno %1$s successivamente il %2$s","Fra %n anni %1$s successivamente il %2$s","Fra %n anni %1$s successivamente il %2$s"],
|
||||
"In the past on %1$s then on %2$s and %3$s" : "In passato il %1$s successivamente il %2$s e %3$s",
|
||||
"_In %n minute on %1$s then on %2$s and %3$s_::_In %n minutes on %1$s then on %2$s and %3$s_" : ["Fra %n minuto il %1$s successivamente il %2$s e %3$s","Fra %n minuti il %1$s successivamente il %2$s e %3$s","Fra %n minuti il %1$s successivamente il %2$s e %3$s"],
|
||||
"_In %n hour on %1$s then on %2$s and %3$s_::_In %n hours on %1$s then on %2$s and %3$s_" : ["Fra %n ora il %1$s successivamente il %2$s e %3$s","Fra %n ore il %1$s successivamente il %2$s e %3$s","Fra %n ore il %1$s successivamente il %2$s e %3$s"],
|
||||
"_In %n day on %1$s then on %2$s and %3$s_::_In %n days on %1$s then on %2$s and %3$s_" : ["Fra %n giorno il %1$s successivamente il %2$s e %3$s","Fra %n giorni il %1$s successivamente il %2$s e %3$s","Fra %n giorni il %1$s successivamente il %2$s e %3$s"],
|
||||
"_In %n week on %1$s then on %2$s and %3$s_::_In %n weeks on %1$s then on %2$s and %3$s_" : ["Fra %n settimana il %1$s successivamente il %2$s e %3$s","Fra %n settimane il %1$s successivamente il %2$s e %3$s","Fra %n settimane il %1$s successivamente il %2$s e %3$s"],
|
||||
"_In %n month on %1$s then on %2$s and %3$s_::_In %n months on %1$s then on %2$s and %3$s_" : ["Fra %n mese il %1$s successivamente il %2$s e %3$s","Fra %n mesi il %1$s successivamente il %2$s e %3$s","Fra %n mesi il %1$s successivamente il %2$s e %3$s"],
|
||||
"_In %n year on %1$s then on %2$s and %3$s_::_In %n years on %1$s then on %2$s and %3$s_" : ["Fra %n anno il %1$s successivamente il %2$s e %3$s","Fra %n anni il %1$s successivamente il %2$s e %3$s","Fra %n anni il %1$s successivamente il %2$s e %3$s"],
|
||||
"Could not generate next recurrence statement" : "Impossibile generare l'istruzione della prossima ricorrenza ",
|
||||
"Cancelled: %1$s" : "Annullato: %1$s",
|
||||
"\"%1$s\" has been canceled" : "\"%1$s\" è stato annullato",
|
||||
|
||||
@@ -73,7 +73,19 @@ OC.L10N.register(
|
||||
"Where: %s" : "Wapi: %s",
|
||||
"%1$s via %2$s" : "%1$skupitia %2$s",
|
||||
"In the past on %1$s for the entire day" : "Hapo awali kwenye %1$s kwa siku nzima",
|
||||
"_In %n minute on %1$s for the entire day_::_In %n minutes on %1$s for the entire day_" : ["In %n minute on %1$s for the entire day","Baada ya dakika %n kwenye %1$s kwa siku nzima"],
|
||||
"_In %n hour on %1$s for the entire day_::_In %n hours on %1$s for the entire day_" : ["In %n hour on %1$s for the entire day","Baada ya saa %n kwenye %1$s kwa siku nzima"],
|
||||
"_In %n day on %1$s for the entire day_::_In %n days on %1$s for the entire day_" : ["In %n day on %1$s for the entire day","Baada ya siku %n tarehe %1$s kwa siku nzima"],
|
||||
"_In %n week on %1$s for the entire day_::_In %n weeks on %1$s for the entire day_" : ["In %n week on %1$s for the entire day","Baada ya wiki %n mnamo %1$s kwa siku nzima"],
|
||||
"_In %n month on %1$s for the entire day_::_In %n months on %1$s for the entire day_" : ["In %n month on %1$s for the entire day","Baada ya miezi %n mnamo %1$s kwa siku nzima"],
|
||||
"_In %n year on %1$s for the entire day_::_In %n years on %1$s for the entire day_" : ["In %n year on %1$s for the entire day","Katika miaka %n kwenye %1$s kwa siku nzima"],
|
||||
"In the past on %1$s between %2$s - %3$s" : "Hapo awali kwenye %1$s kati ya %2$s - %3$s",
|
||||
"_In %n minute on %1$s between %2$s - %3$s_::_In %n minutes on %1$s between %2$s - %3$s_" : ["In %n minute on %1$s between %2$s - %3$s","Baada ya dakika %n kwenye %1$s kati ya %2$s - %3$s"],
|
||||
"_In %n hour on %1$s between %2$s - %3$s_::_In %n hours on %1$s between %2$s - %3$s_" : ["In %n hour on %1$s between %2$s - %3$s","Baada ya saa %n kwenye %1$s kati ya %2$s - %3$s"],
|
||||
"_In %n day on %1$s between %2$s - %3$s_::_In %n days on %1$s between %2$s - %3$s_" : ["In %n day on %1$s between %2$s - %3$s","Katika siku %n mnamo %1$s kati ya %2$s - %3$s"],
|
||||
"_In %n week on %1$s between %2$s - %3$s_::_In %n weeks on %1$s between %2$s - %3$s_" : ["In %n week on %1$s between %2$s - %3$s","Katika wiki %n mnamo %1$s kati ya %2$s - %3$s"],
|
||||
"_In %n month on %1$s between %2$s - %3$s_::_In %n months on %1$s between %2$s - %3$s_" : ["In %n month on %1$s between %2$s - %3$s","Katika miezi %n tarehe %1$s kati ya %2$s - %3$s"],
|
||||
"_In %n year on %1$s between %2$s - %3$s_::_In %n years on %1$s between %2$s - %3$s_" : ["In %n year on %1$s between %2$s - %3$s","Katika miaka %n kwenye %1$s kati ya %2$s - %3$s"],
|
||||
"Could not generate when statement" : "Haikuweza kuzalisha taarifa ya lini",
|
||||
"Every Day for the entire day" : "Kila Siku kwa siku nzima",
|
||||
"Every Day for the entire day until %1$s" : "Kila Siku kwa siku nzima hadi %1$s",
|
||||
@@ -111,8 +123,26 @@ OC.L10N.register(
|
||||
"On specific dates for the entire day until %1$s" : "Kwa tarehe mahususi kwa siku nzima hadi %1$s ",
|
||||
"On specific dates between %1$s - %2$s until %3$s" : "Katika tarehe mahususi kati ya %1$s - %2$s hadi %3$s",
|
||||
"In the past on %1$s" : "Hapo awali kwenye %1$s",
|
||||
"_In %n minute on %1$s_::_In %n minutes on %1$s_" : ["In %n minute on %1$s","Baada ya dakika %n kwenye %1$s"],
|
||||
"_In %n hour on %1$s_::_In %n hours on %1$s_" : ["In %n hour on %1$s","Baada ya saa %n kwenye %1$s"],
|
||||
"_In %n day on %1$s_::_In %n days on %1$s_" : ["In %n day on %1$s","Baada ya siku %n tarehe %1$s"],
|
||||
"_In %n week on %1$s_::_In %n weeks on %1$s_" : ["In %n week on %1$s","Baada ya wiki %n tarehe %1$s"],
|
||||
"_In %n month on %1$s_::_In %n months on %1$s_" : ["In %n month on %1$s","Baada ya miezi %n tarehe %1$s"],
|
||||
"_In %n year on %1$s_::_In %n years on %1$s_" : ["In %n year on %1$s","Katika miaka %n tarehe %1$s"],
|
||||
"In the past on %1$s then on %2$s" : "Hapo awali kwenye %1$s kisha kwenye %2$s",
|
||||
"_In %n minute on %1$s then on %2$s_::_In %n minutes on %1$s then on %2$s_" : ["In %n minute on %1$s then on %2$s","Baada ya dakika %n kwenye %1$s kisha %2$s"],
|
||||
"_In %n hour on %1$s then on %2$s_::_In %n hours on %1$s then on %2$s_" : ["In %n hour on %1$s then on %2$s","Baada ya saa %n kwenye %1$s kisha %2$s"],
|
||||
"_In %n day on %1$s then on %2$s_::_In %n days on %1$s then on %2$s_" : ["In %n day on %1$s then on %2$s","Baada ya siku %n mnamo %1$s kisha %2$s"],
|
||||
"_In %n week on %1$s then on %2$s_::_In %n weeks on %1$s then on %2$s_" : ["In %n week on %1$s then on %2$s","Baada ya wiki %n mnamo %1$s kisha %2$s"],
|
||||
"_In %n month on %1$s then on %2$s_::_In %n months on %1$s then on %2$s_" : ["In %n month on %1$s then on %2$s","Baada ya miezi %n mnamo %1$s kisha %2$s"],
|
||||
"_In %n year on %1$s then on %2$s_::_In %n years on %1$s then on %2$s_" : ["In %n year on %1$s then on %2$s","Katika miaka %n kwenye %1$s kisha %2$s"],
|
||||
"In the past on %1$s then on %2$s and %3$s" : "Hapo awali kwenye %1$s kisha kwenye %2$s na %3$s",
|
||||
"_In %n minute on %1$s then on %2$s and %3$s_::_In %n minutes on %1$s then on %2$s and %3$s_" : ["In %n minute on %1$s then on %2$s and %3$s","Baada ya dakika %n kwenye %1$s kisha %2$s na %3$s"],
|
||||
"_In %n hour on %1$s then on %2$s and %3$s_::_In %n hours on %1$s then on %2$s and %3$s_" : ["In %n hour on %1$s then on %2$s and %3$s","Baada ya saa %n kwenye %1$s kisha %2$s na %3$s"],
|
||||
"_In %n day on %1$s then on %2$s and %3$s_::_In %n days on %1$s then on %2$s and %3$s_" : ["In %n day on %1$s then on %2$s and %3$s","Baada ya siku %n mnamo %1$s kisha %2$s na %3$s"],
|
||||
"_In %n week on %1$s then on %2$s and %3$s_::_In %n weeks on %1$s then on %2$s and %3$s_" : ["In %n week on %1$s then on %2$s and %3$s","Baada ya wiki %n %1$s kisha %2$s na %3$s"],
|
||||
"_In %n month on %1$s then on %2$s and %3$s_::_In %n months on %1$s then on %2$s and %3$s_" : ["In %n month on %1$s then on %2$s and %3$s","Baada ya miezi %n kwenye %1$s kisha %2$s na %3$s"],
|
||||
"_In %n year on %1$s then on %2$s and %3$s_::_In %n years on %1$s then on %2$s and %3$s_" : ["In %n year on %1$s then on %2$s and %3$s","Katika miaka %n kwenye %1$s kisha %2$s na %3$s"],
|
||||
"Could not generate next recurrence statement" : "Haikuweza kutoa taarifa inayofuata ya kujirudia",
|
||||
"Cancelled: %1$s" : "Imeghairiwa: %1$s ",
|
||||
"\"%1$s\" has been canceled" : "\"%1$s\" imeghairiwa",
|
||||
|
||||
@@ -71,7 +71,19 @@
|
||||
"Where: %s" : "Wapi: %s",
|
||||
"%1$s via %2$s" : "%1$skupitia %2$s",
|
||||
"In the past on %1$s for the entire day" : "Hapo awali kwenye %1$s kwa siku nzima",
|
||||
"_In %n minute on %1$s for the entire day_::_In %n minutes on %1$s for the entire day_" : ["In %n minute on %1$s for the entire day","Baada ya dakika %n kwenye %1$s kwa siku nzima"],
|
||||
"_In %n hour on %1$s for the entire day_::_In %n hours on %1$s for the entire day_" : ["In %n hour on %1$s for the entire day","Baada ya saa %n kwenye %1$s kwa siku nzima"],
|
||||
"_In %n day on %1$s for the entire day_::_In %n days on %1$s for the entire day_" : ["In %n day on %1$s for the entire day","Baada ya siku %n tarehe %1$s kwa siku nzima"],
|
||||
"_In %n week on %1$s for the entire day_::_In %n weeks on %1$s for the entire day_" : ["In %n week on %1$s for the entire day","Baada ya wiki %n mnamo %1$s kwa siku nzima"],
|
||||
"_In %n month on %1$s for the entire day_::_In %n months on %1$s for the entire day_" : ["In %n month on %1$s for the entire day","Baada ya miezi %n mnamo %1$s kwa siku nzima"],
|
||||
"_In %n year on %1$s for the entire day_::_In %n years on %1$s for the entire day_" : ["In %n year on %1$s for the entire day","Katika miaka %n kwenye %1$s kwa siku nzima"],
|
||||
"In the past on %1$s between %2$s - %3$s" : "Hapo awali kwenye %1$s kati ya %2$s - %3$s",
|
||||
"_In %n minute on %1$s between %2$s - %3$s_::_In %n minutes on %1$s between %2$s - %3$s_" : ["In %n minute on %1$s between %2$s - %3$s","Baada ya dakika %n kwenye %1$s kati ya %2$s - %3$s"],
|
||||
"_In %n hour on %1$s between %2$s - %3$s_::_In %n hours on %1$s between %2$s - %3$s_" : ["In %n hour on %1$s between %2$s - %3$s","Baada ya saa %n kwenye %1$s kati ya %2$s - %3$s"],
|
||||
"_In %n day on %1$s between %2$s - %3$s_::_In %n days on %1$s between %2$s - %3$s_" : ["In %n day on %1$s between %2$s - %3$s","Katika siku %n mnamo %1$s kati ya %2$s - %3$s"],
|
||||
"_In %n week on %1$s between %2$s - %3$s_::_In %n weeks on %1$s between %2$s - %3$s_" : ["In %n week on %1$s between %2$s - %3$s","Katika wiki %n mnamo %1$s kati ya %2$s - %3$s"],
|
||||
"_In %n month on %1$s between %2$s - %3$s_::_In %n months on %1$s between %2$s - %3$s_" : ["In %n month on %1$s between %2$s - %3$s","Katika miezi %n tarehe %1$s kati ya %2$s - %3$s"],
|
||||
"_In %n year on %1$s between %2$s - %3$s_::_In %n years on %1$s between %2$s - %3$s_" : ["In %n year on %1$s between %2$s - %3$s","Katika miaka %n kwenye %1$s kati ya %2$s - %3$s"],
|
||||
"Could not generate when statement" : "Haikuweza kuzalisha taarifa ya lini",
|
||||
"Every Day for the entire day" : "Kila Siku kwa siku nzima",
|
||||
"Every Day for the entire day until %1$s" : "Kila Siku kwa siku nzima hadi %1$s",
|
||||
@@ -109,8 +121,26 @@
|
||||
"On specific dates for the entire day until %1$s" : "Kwa tarehe mahususi kwa siku nzima hadi %1$s ",
|
||||
"On specific dates between %1$s - %2$s until %3$s" : "Katika tarehe mahususi kati ya %1$s - %2$s hadi %3$s",
|
||||
"In the past on %1$s" : "Hapo awali kwenye %1$s",
|
||||
"_In %n minute on %1$s_::_In %n minutes on %1$s_" : ["In %n minute on %1$s","Baada ya dakika %n kwenye %1$s"],
|
||||
"_In %n hour on %1$s_::_In %n hours on %1$s_" : ["In %n hour on %1$s","Baada ya saa %n kwenye %1$s"],
|
||||
"_In %n day on %1$s_::_In %n days on %1$s_" : ["In %n day on %1$s","Baada ya siku %n tarehe %1$s"],
|
||||
"_In %n week on %1$s_::_In %n weeks on %1$s_" : ["In %n week on %1$s","Baada ya wiki %n tarehe %1$s"],
|
||||
"_In %n month on %1$s_::_In %n months on %1$s_" : ["In %n month on %1$s","Baada ya miezi %n tarehe %1$s"],
|
||||
"_In %n year on %1$s_::_In %n years on %1$s_" : ["In %n year on %1$s","Katika miaka %n tarehe %1$s"],
|
||||
"In the past on %1$s then on %2$s" : "Hapo awali kwenye %1$s kisha kwenye %2$s",
|
||||
"_In %n minute on %1$s then on %2$s_::_In %n minutes on %1$s then on %2$s_" : ["In %n minute on %1$s then on %2$s","Baada ya dakika %n kwenye %1$s kisha %2$s"],
|
||||
"_In %n hour on %1$s then on %2$s_::_In %n hours on %1$s then on %2$s_" : ["In %n hour on %1$s then on %2$s","Baada ya saa %n kwenye %1$s kisha %2$s"],
|
||||
"_In %n day on %1$s then on %2$s_::_In %n days on %1$s then on %2$s_" : ["In %n day on %1$s then on %2$s","Baada ya siku %n mnamo %1$s kisha %2$s"],
|
||||
"_In %n week on %1$s then on %2$s_::_In %n weeks on %1$s then on %2$s_" : ["In %n week on %1$s then on %2$s","Baada ya wiki %n mnamo %1$s kisha %2$s"],
|
||||
"_In %n month on %1$s then on %2$s_::_In %n months on %1$s then on %2$s_" : ["In %n month on %1$s then on %2$s","Baada ya miezi %n mnamo %1$s kisha %2$s"],
|
||||
"_In %n year on %1$s then on %2$s_::_In %n years on %1$s then on %2$s_" : ["In %n year on %1$s then on %2$s","Katika miaka %n kwenye %1$s kisha %2$s"],
|
||||
"In the past on %1$s then on %2$s and %3$s" : "Hapo awali kwenye %1$s kisha kwenye %2$s na %3$s",
|
||||
"_In %n minute on %1$s then on %2$s and %3$s_::_In %n minutes on %1$s then on %2$s and %3$s_" : ["In %n minute on %1$s then on %2$s and %3$s","Baada ya dakika %n kwenye %1$s kisha %2$s na %3$s"],
|
||||
"_In %n hour on %1$s then on %2$s and %3$s_::_In %n hours on %1$s then on %2$s and %3$s_" : ["In %n hour on %1$s then on %2$s and %3$s","Baada ya saa %n kwenye %1$s kisha %2$s na %3$s"],
|
||||
"_In %n day on %1$s then on %2$s and %3$s_::_In %n days on %1$s then on %2$s and %3$s_" : ["In %n day on %1$s then on %2$s and %3$s","Baada ya siku %n mnamo %1$s kisha %2$s na %3$s"],
|
||||
"_In %n week on %1$s then on %2$s and %3$s_::_In %n weeks on %1$s then on %2$s and %3$s_" : ["In %n week on %1$s then on %2$s and %3$s","Baada ya wiki %n %1$s kisha %2$s na %3$s"],
|
||||
"_In %n month on %1$s then on %2$s and %3$s_::_In %n months on %1$s then on %2$s and %3$s_" : ["In %n month on %1$s then on %2$s and %3$s","Baada ya miezi %n kwenye %1$s kisha %2$s na %3$s"],
|
||||
"_In %n year on %1$s then on %2$s and %3$s_::_In %n years on %1$s then on %2$s and %3$s_" : ["In %n year on %1$s then on %2$s and %3$s","Katika miaka %n kwenye %1$s kisha %2$s na %3$s"],
|
||||
"Could not generate next recurrence statement" : "Haikuweza kutoa taarifa inayofuata ya kujirudia",
|
||||
"Cancelled: %1$s" : "Imeghairiwa: %1$s ",
|
||||
"\"%1$s\" has been canceled" : "\"%1$s\" imeghairiwa",
|
||||
|
||||
@@ -73,7 +73,19 @@ OC.L10N.register(
|
||||
"Where: %s" : "Şurada: %s",
|
||||
"%1$s via %2$s" : "%1$s, %2$s aracılığıyla",
|
||||
"In the past on %1$s for the entire day" : "Tüm gün boyunca %1$s zamanında geçmişte",
|
||||
"_In %n minute on %1$s for the entire day_::_In %n minutes on %1$s for the entire day_" : ["Tüm gün boyunca %1$s zamanında %n dakika içinde","Tüm gün boyunca %1$s zamanında %n dakika içinde"],
|
||||
"_In %n hour on %1$s for the entire day_::_In %n hours on %1$s for the entire day_" : ["Tüm gün boyunca %1$s zamanında %n saat içinde","Tüm gün boyunca %1$s zamanında %n saat içinde"],
|
||||
"_In %n day on %1$s for the entire day_::_In %n days on %1$s for the entire day_" : ["Tüm gün boyunca %1$s zamanında %n gün içinde","Tüm gün boyunca %1$s zamanında %n gün içinde"],
|
||||
"_In %n week on %1$s for the entire day_::_In %n weeks on %1$s for the entire day_" : ["Tüm gün boyunca %1$s zamanında %n hafta içinde","Tüm gün boyunca %1$s zamanında %n hafta içinde"],
|
||||
"_In %n month on %1$s for the entire day_::_In %n months on %1$s for the entire day_" : ["Tüm gün boyunca %1$s zamanında %n ay içinde","Tüm gün boyunca %1$s zamanında %n ay içinde"],
|
||||
"_In %n year on %1$s for the entire day_::_In %n years on %1$s for the entire day_" : ["Tüm gün boyunca %1$s zamanında %n yıl içinde","Tüm gün boyunca %1$s zamanında %n yıl içinde"],
|
||||
"In the past on %1$s between %2$s - %3$s" : "Geçmişte %1$s zamanında %2$s ile %3$s arasında",
|
||||
"_In %n minute on %1$s between %2$s - %3$s_::_In %n minutes on %1$s between %2$s - %3$s_" : ["%n dakika içinde %1$s zamanında %2$s ile %3$s arasında","%n dakika içinde %1$s zamanında %2$s ile %3$s arasında"],
|
||||
"_In %n hour on %1$s between %2$s - %3$s_::_In %n hours on %1$s between %2$s - %3$s_" : ["%n saat içinde %1$s zamanında %2$s ile %3$s arasında","%n saat içinde %1$s zamanında %2$s ile %3$s arasında"],
|
||||
"_In %n day on %1$s between %2$s - %3$s_::_In %n days on %1$s between %2$s - %3$s_" : ["%n gün içinde %1$s zamanında %2$s ile %3$s arasında","%n gün içinde %1$s zamanında %2$s ile %3$s arasında"],
|
||||
"_In %n week on %1$s between %2$s - %3$s_::_In %n weeks on %1$s between %2$s - %3$s_" : ["%n hafta içinde %1$s zamanında %2$s ile %3$s arasında","%n hafta içinde %1$s zamanında %2$s ile %3$s arasında"],
|
||||
"_In %n month on %1$s between %2$s - %3$s_::_In %n months on %1$s between %2$s - %3$s_" : ["%n ay içinde %1$s zamanında %2$s ile %3$s arasında","%n ay içinde %1$s zamanında %2$s ile %3$s arasında"],
|
||||
"_In %n year on %1$s between %2$s - %3$s_::_In %n years on %1$s between %2$s - %3$s_" : ["%n yıl içinde %1$s zamanında %2$s ile %3$s arasında","%n yıl içinde %1$s zamanında %2$s ile %3$s arasında"],
|
||||
"Could not generate when statement" : "Zaman ifadesi oluşturulamadı",
|
||||
"Every Day for the entire day" : "Her gün tüm gün boyunca",
|
||||
"Every Day for the entire day until %1$s" : "%1$s zamanına kadar her gün tüm gün boyunca",
|
||||
@@ -111,8 +123,26 @@ OC.L10N.register(
|
||||
"On specific dates for the entire day until %1$s" : "Belirli tarihlerde tüm gün boyunca %1$s zamanına kadar",
|
||||
"On specific dates between %1$s - %2$s until %3$s" : "Belirli tarihlerde %1$s ile %2$s arasında %3$s zamanına kadar",
|
||||
"In the past on %1$s" : "%1$s zamanında geçmişte",
|
||||
"_In %n minute on %1$s_::_In %n minutes on %1$s_" : ["%1$s zamanında %n dakika içinde","%1$s zamanında %n dakika içinde"],
|
||||
"_In %n hour on %1$s_::_In %n hours on %1$s_" : ["%1$s zamanında %n saat içinde","%1$s zamanında %n saat içinde"],
|
||||
"_In %n day on %1$s_::_In %n days on %1$s_" : ["%1$s zamanında %n gün içinde","%1$s zamanında %n gün içinde"],
|
||||
"_In %n week on %1$s_::_In %n weeks on %1$s_" : ["%1$s zamanında %n hafta içinde","%1$s zamanında %n hafta içinde"],
|
||||
"_In %n month on %1$s_::_In %n months on %1$s_" : ["%1$s zamanında %n ay içinde","%1$s zamanında %n ay içinde"],
|
||||
"_In %n year on %1$s_::_In %n years on %1$s_" : ["%1$s zamanında %n yıl içinde","%1$s zamanında %n yıl içinde"],
|
||||
"In the past on %1$s then on %2$s" : "Geçmişte %1$s zamanında ardından %2$s zamanında",
|
||||
"_In %n minute on %1$s then on %2$s_::_In %n minutes on %1$s then on %2$s_" : ["%n dakika içinde %1$s zamanında ardından %2$s zamanında","%n dakika içinde %1$s zamanında ardından %2$s zamanında"],
|
||||
"_In %n hour on %1$s then on %2$s_::_In %n hours on %1$s then on %2$s_" : ["%n saat içinde %1$s zamanında ardından %2$s zamanında","%n saat içinde %1$s zamanında ardından %2$s zamanında"],
|
||||
"_In %n day on %1$s then on %2$s_::_In %n days on %1$s then on %2$s_" : ["%n gün içinde %1$s zamanında ardından %2$s zamanında","%n gün içinde %1$s zamanında ardından %2$s zamanında"],
|
||||
"_In %n week on %1$s then on %2$s_::_In %n weeks on %1$s then on %2$s_" : ["%n hafta içinde %1$s zamanında ardından %2$s zamanında","%n hafta içinde %1$s zamanında ardından %2$s zamanında"],
|
||||
"_In %n month on %1$s then on %2$s_::_In %n months on %1$s then on %2$s_" : ["%n ay içinde %1$s zamanında ardından %2$s zamanında","%n ay içinde %1$s zamanında ardından %2$s zamanında"],
|
||||
"_In %n year on %1$s then on %2$s_::_In %n years on %1$s then on %2$s_" : ["%n yıl içinde %1$s zamanında ardından %2$s zamanında","%n yıl içinde %1$s zamanında ardından %2$s zamanında"],
|
||||
"In the past on %1$s then on %2$s and %3$s" : "Geçmişte %1$s zamanında ardından %2$s ve %3$s zamanında",
|
||||
"_In %n minute on %1$s then on %2$s and %3$s_::_In %n minutes on %1$s then on %2$s and %3$s_" : ["%n dakika içinde %1$s zamanında ardından %2$s ve %3$s zamanında","%n dakika içinde %1$s zamanında ardından %2$s ve %3$s zamanında"],
|
||||
"_In %n hour on %1$s then on %2$s and %3$s_::_In %n hours on %1$s then on %2$s and %3$s_" : ["%n dakika içinde %1$s zamanında ardından %2$s ve %3$s zamanında","%n dakika içinde %1$s zamanında ardından %2$s ve %3$s zamanında"],
|
||||
"_In %n day on %1$s then on %2$s and %3$s_::_In %n days on %1$s then on %2$s and %3$s_" : ["%n dakika içinde %1$s zamanında ardından %2$s ve %3$s zamanında","%n dakika içinde %1$s zamanında ardından %2$s ve %3$s zamanında"],
|
||||
"_In %n week on %1$s then on %2$s and %3$s_::_In %n weeks on %1$s then on %2$s and %3$s_" : ["%n dakika içinde %1$s zamanında ardından %2$s ve %3$s zamanında","%n dakika içinde %1$s zamanında ardından %2$s ve %3$s zamanında"],
|
||||
"_In %n month on %1$s then on %2$s and %3$s_::_In %n months on %1$s then on %2$s and %3$s_" : ["%n dakika içinde %1$s zamanında ardından %2$s ve %3$s zamanında","%n dakika içinde %1$s zamanında ardından %2$s ve %3$s zamanında"],
|
||||
"_In %n year on %1$s then on %2$s and %3$s_::_In %n years on %1$s then on %2$s and %3$s_" : ["%n dakika içinde %1$s zamanında ardından %2$s ve %3$s zamanında","%n dakika içinde %1$s zamanında ardından %2$s ve %3$s zamanında"],
|
||||
"Could not generate next recurrence statement" : "Sonraki yinelenme ifadesi oluşturulamadı",
|
||||
"Cancelled: %1$s" : "İptal edildi: %1$s",
|
||||
"\"%1$s\" has been canceled" : "\"%1$s\" iptal edildi",
|
||||
|
||||
@@ -71,7 +71,19 @@
|
||||
"Where: %s" : "Şurada: %s",
|
||||
"%1$s via %2$s" : "%1$s, %2$s aracılığıyla",
|
||||
"In the past on %1$s for the entire day" : "Tüm gün boyunca %1$s zamanında geçmişte",
|
||||
"_In %n minute on %1$s for the entire day_::_In %n minutes on %1$s for the entire day_" : ["Tüm gün boyunca %1$s zamanında %n dakika içinde","Tüm gün boyunca %1$s zamanında %n dakika içinde"],
|
||||
"_In %n hour on %1$s for the entire day_::_In %n hours on %1$s for the entire day_" : ["Tüm gün boyunca %1$s zamanında %n saat içinde","Tüm gün boyunca %1$s zamanında %n saat içinde"],
|
||||
"_In %n day on %1$s for the entire day_::_In %n days on %1$s for the entire day_" : ["Tüm gün boyunca %1$s zamanında %n gün içinde","Tüm gün boyunca %1$s zamanında %n gün içinde"],
|
||||
"_In %n week on %1$s for the entire day_::_In %n weeks on %1$s for the entire day_" : ["Tüm gün boyunca %1$s zamanında %n hafta içinde","Tüm gün boyunca %1$s zamanında %n hafta içinde"],
|
||||
"_In %n month on %1$s for the entire day_::_In %n months on %1$s for the entire day_" : ["Tüm gün boyunca %1$s zamanında %n ay içinde","Tüm gün boyunca %1$s zamanında %n ay içinde"],
|
||||
"_In %n year on %1$s for the entire day_::_In %n years on %1$s for the entire day_" : ["Tüm gün boyunca %1$s zamanında %n yıl içinde","Tüm gün boyunca %1$s zamanında %n yıl içinde"],
|
||||
"In the past on %1$s between %2$s - %3$s" : "Geçmişte %1$s zamanında %2$s ile %3$s arasında",
|
||||
"_In %n minute on %1$s between %2$s - %3$s_::_In %n minutes on %1$s between %2$s - %3$s_" : ["%n dakika içinde %1$s zamanında %2$s ile %3$s arasında","%n dakika içinde %1$s zamanında %2$s ile %3$s arasında"],
|
||||
"_In %n hour on %1$s between %2$s - %3$s_::_In %n hours on %1$s between %2$s - %3$s_" : ["%n saat içinde %1$s zamanında %2$s ile %3$s arasında","%n saat içinde %1$s zamanında %2$s ile %3$s arasında"],
|
||||
"_In %n day on %1$s between %2$s - %3$s_::_In %n days on %1$s between %2$s - %3$s_" : ["%n gün içinde %1$s zamanında %2$s ile %3$s arasında","%n gün içinde %1$s zamanında %2$s ile %3$s arasında"],
|
||||
"_In %n week on %1$s between %2$s - %3$s_::_In %n weeks on %1$s between %2$s - %3$s_" : ["%n hafta içinde %1$s zamanında %2$s ile %3$s arasında","%n hafta içinde %1$s zamanında %2$s ile %3$s arasında"],
|
||||
"_In %n month on %1$s between %2$s - %3$s_::_In %n months on %1$s between %2$s - %3$s_" : ["%n ay içinde %1$s zamanında %2$s ile %3$s arasında","%n ay içinde %1$s zamanında %2$s ile %3$s arasında"],
|
||||
"_In %n year on %1$s between %2$s - %3$s_::_In %n years on %1$s between %2$s - %3$s_" : ["%n yıl içinde %1$s zamanında %2$s ile %3$s arasında","%n yıl içinde %1$s zamanında %2$s ile %3$s arasında"],
|
||||
"Could not generate when statement" : "Zaman ifadesi oluşturulamadı",
|
||||
"Every Day for the entire day" : "Her gün tüm gün boyunca",
|
||||
"Every Day for the entire day until %1$s" : "%1$s zamanına kadar her gün tüm gün boyunca",
|
||||
@@ -109,8 +121,26 @@
|
||||
"On specific dates for the entire day until %1$s" : "Belirli tarihlerde tüm gün boyunca %1$s zamanına kadar",
|
||||
"On specific dates between %1$s - %2$s until %3$s" : "Belirli tarihlerde %1$s ile %2$s arasında %3$s zamanına kadar",
|
||||
"In the past on %1$s" : "%1$s zamanında geçmişte",
|
||||
"_In %n minute on %1$s_::_In %n minutes on %1$s_" : ["%1$s zamanında %n dakika içinde","%1$s zamanında %n dakika içinde"],
|
||||
"_In %n hour on %1$s_::_In %n hours on %1$s_" : ["%1$s zamanında %n saat içinde","%1$s zamanında %n saat içinde"],
|
||||
"_In %n day on %1$s_::_In %n days on %1$s_" : ["%1$s zamanında %n gün içinde","%1$s zamanında %n gün içinde"],
|
||||
"_In %n week on %1$s_::_In %n weeks on %1$s_" : ["%1$s zamanında %n hafta içinde","%1$s zamanında %n hafta içinde"],
|
||||
"_In %n month on %1$s_::_In %n months on %1$s_" : ["%1$s zamanında %n ay içinde","%1$s zamanında %n ay içinde"],
|
||||
"_In %n year on %1$s_::_In %n years on %1$s_" : ["%1$s zamanında %n yıl içinde","%1$s zamanında %n yıl içinde"],
|
||||
"In the past on %1$s then on %2$s" : "Geçmişte %1$s zamanında ardından %2$s zamanında",
|
||||
"_In %n minute on %1$s then on %2$s_::_In %n minutes on %1$s then on %2$s_" : ["%n dakika içinde %1$s zamanında ardından %2$s zamanında","%n dakika içinde %1$s zamanında ardından %2$s zamanında"],
|
||||
"_In %n hour on %1$s then on %2$s_::_In %n hours on %1$s then on %2$s_" : ["%n saat içinde %1$s zamanında ardından %2$s zamanında","%n saat içinde %1$s zamanında ardından %2$s zamanında"],
|
||||
"_In %n day on %1$s then on %2$s_::_In %n days on %1$s then on %2$s_" : ["%n gün içinde %1$s zamanında ardından %2$s zamanında","%n gün içinde %1$s zamanında ardından %2$s zamanında"],
|
||||
"_In %n week on %1$s then on %2$s_::_In %n weeks on %1$s then on %2$s_" : ["%n hafta içinde %1$s zamanında ardından %2$s zamanında","%n hafta içinde %1$s zamanında ardından %2$s zamanında"],
|
||||
"_In %n month on %1$s then on %2$s_::_In %n months on %1$s then on %2$s_" : ["%n ay içinde %1$s zamanında ardından %2$s zamanında","%n ay içinde %1$s zamanında ardından %2$s zamanında"],
|
||||
"_In %n year on %1$s then on %2$s_::_In %n years on %1$s then on %2$s_" : ["%n yıl içinde %1$s zamanında ardından %2$s zamanında","%n yıl içinde %1$s zamanında ardından %2$s zamanında"],
|
||||
"In the past on %1$s then on %2$s and %3$s" : "Geçmişte %1$s zamanında ardından %2$s ve %3$s zamanında",
|
||||
"_In %n minute on %1$s then on %2$s and %3$s_::_In %n minutes on %1$s then on %2$s and %3$s_" : ["%n dakika içinde %1$s zamanında ardından %2$s ve %3$s zamanında","%n dakika içinde %1$s zamanında ardından %2$s ve %3$s zamanında"],
|
||||
"_In %n hour on %1$s then on %2$s and %3$s_::_In %n hours on %1$s then on %2$s and %3$s_" : ["%n dakika içinde %1$s zamanında ardından %2$s ve %3$s zamanında","%n dakika içinde %1$s zamanında ardından %2$s ve %3$s zamanında"],
|
||||
"_In %n day on %1$s then on %2$s and %3$s_::_In %n days on %1$s then on %2$s and %3$s_" : ["%n dakika içinde %1$s zamanında ardından %2$s ve %3$s zamanında","%n dakika içinde %1$s zamanında ardından %2$s ve %3$s zamanında"],
|
||||
"_In %n week on %1$s then on %2$s and %3$s_::_In %n weeks on %1$s then on %2$s and %3$s_" : ["%n dakika içinde %1$s zamanında ardından %2$s ve %3$s zamanında","%n dakika içinde %1$s zamanında ardından %2$s ve %3$s zamanında"],
|
||||
"_In %n month on %1$s then on %2$s and %3$s_::_In %n months on %1$s then on %2$s and %3$s_" : ["%n dakika içinde %1$s zamanında ardından %2$s ve %3$s zamanında","%n dakika içinde %1$s zamanında ardından %2$s ve %3$s zamanında"],
|
||||
"_In %n year on %1$s then on %2$s and %3$s_::_In %n years on %1$s then on %2$s and %3$s_" : ["%n dakika içinde %1$s zamanında ardından %2$s ve %3$s zamanında","%n dakika içinde %1$s zamanında ardından %2$s ve %3$s zamanında"],
|
||||
"Could not generate next recurrence statement" : "Sonraki yinelenme ifadesi oluşturulamadı",
|
||||
"Cancelled: %1$s" : "İptal edildi: %1$s",
|
||||
"\"%1$s\" has been canceled" : "\"%1$s\" iptal edildi",
|
||||
|
||||
@@ -73,7 +73,19 @@ OC.L10N.register(
|
||||
"Where: %s" : "قەيەردە: %s",
|
||||
"%1$s via %2$s" : "%1$s ئارقىلىق %2$s",
|
||||
"In the past on %1$s for the entire day" : "پۈتۈن كۈن ئۈچۈن ئۆتمۈشتىكى %1$s دا",
|
||||
"_In %n minute on %1$s for the entire day_::_In %n minutes on %1$s for the entire day_" : ["%1$s دا پۈتۈن كۈندە %n مىنۇتتا","%1$s دا پۈتۈن كۈندە %n مىنۇتتا"],
|
||||
"_In %n hour on %1$s for the entire day_::_In %n hours on %1$s for the entire day_" : ["%1$s دا پۈتۈن كۈندە %n سائەتتە","%1$s دا پۈتۈن كۈندە %n سائەتتە"],
|
||||
"_In %n day on %1$s for the entire day_::_In %n days on %1$s for the entire day_" : ["%1$s دا پۈتۈن كۈندە %n كۈندە","%1$s دا پۈتۈن كۈندە %n كۈندە"],
|
||||
"_In %n week on %1$s for the entire day_::_In %n weeks on %1$s for the entire day_" : ["%1$s دا پۈتۈن كۈندە %n ھەپتىدە","%1$s دا پۈتۈن كۈندە %n ھەپتىدە"],
|
||||
"_In %n month on %1$s for the entire day_::_In %n months on %1$s for the entire day_" : ["%1$s دا پۈتۈن كۈندە %n ئايدا","%1$s دا پۈتۈن كۈندە %n ئايدا"],
|
||||
"_In %n year on %1$s for the entire day_::_In %n years on %1$s for the entire day_" : ["%1$s دا پۈتۈن كۈندە %n يىلدا","%1$s دا پۈتۈن كۈندە %n يىلدا"],
|
||||
"In the past on %1$s between %2$s - %3$s" : "ئۆتمۈشتىكى %1$s دە %2$s - %3$s ئارىسىدا",
|
||||
"_In %n minute on %1$s between %2$s - %3$s_::_In %n minutes on %1$s between %2$s - %3$s_" : ["%1$s دە %2$s - %3$s ئارلىقىدا %n مىنۇتتا","%1$s دە %2$s - %3$s ئارلىقىدا %n مىنۇتتا"],
|
||||
"_In %n hour on %1$s between %2$s - %3$s_::_In %n hours on %1$s between %2$s - %3$s_" : ["%1$s دە %2$s - %3$s ئارلىقىدا %n سائەتتە","%1$s دە %2$s - %3$s ئارلىقىدا %n سائەتتە"],
|
||||
"_In %n day on %1$s between %2$s - %3$s_::_In %n days on %1$s between %2$s - %3$s_" : ["%1$s دە %2$s - %3$s ئارلىقىدا %n كۈندە","%1$s دە %2$s - %3$s ئارلىقىدا %n كۈندە"],
|
||||
"_In %n week on %1$s between %2$s - %3$s_::_In %n weeks on %1$s between %2$s - %3$s_" : ["%1$s دە %2$s - %3$s ئارلىقىدا %n ھەپتىدە","%1$s دە %2$s - %3$s ئارلىقىدا %n ھەپتىدە"],
|
||||
"_In %n month on %1$s between %2$s - %3$s_::_In %n months on %1$s between %2$s - %3$s_" : ["%1$s دە %2$s - %3$s ئارلىقىدا %n ئايدا","%1$s دە %2$s - %3$s ئارلىقىدا %n ئايدا"],
|
||||
"_In %n year on %1$s between %2$s - %3$s_::_In %n years on %1$s between %2$s - %3$s_" : ["%1$s دە %2$s - %3$s ئارلىقىدا %n يىلدا","%1$s دە %2$s - %3$s ئارلىقىدا %n يىلدا"],
|
||||
"Could not generate when statement" : "بايان قىلغاندا ھاسىل قىلالمىدى",
|
||||
"Every Day for the entire day" : "ھەر بىر كۈن پۈتۈن بىر كۈن",
|
||||
"Every Day for the entire day until %1$s" : "ھەر بىر كۈن پۈتۈن كۈن %1$s غىچە",
|
||||
@@ -111,8 +123,26 @@ OC.L10N.register(
|
||||
"On specific dates for the entire day until %1$s" : "%1$s غىچە بەلگىلىك چېسلادا پۈتۈن كۈن",
|
||||
"On specific dates between %1$s - %2$s until %3$s" : "%1$s - %2$s ئارلىقىدىكى %3$s غىچە بولغان بەلگىلىك چېسلادا",
|
||||
"In the past on %1$s" : "ئۆتمۈشتە %1$s دا",
|
||||
"_In %n minute on %1$s_::_In %n minutes on %1$s_" : ["%1$s دا %n مىنۇتتا","%1$s دا %n مىنۇتتا"],
|
||||
"_In %n hour on %1$s_::_In %n hours on %1$s_" : ["%1$s دا %n سائەتتە","%1$s دا %n سائەتتە"],
|
||||
"_In %n day on %1$s_::_In %n days on %1$s_" : ["%1$s دا %n كۈندە","%1$s دا %n كۈندە"],
|
||||
"_In %n week on %1$s_::_In %n weeks on %1$s_" : ["%1$s دا %n ھەپتىدە","%1$s دا %n ھەپتىدە"],
|
||||
"_In %n month on %1$s_::_In %n months on %1$s_" : ["%1$s دا %n ئايدا","%1$s دا %n ئايدا"],
|
||||
"_In %n year on %1$s_::_In %n years on %1$s_" : ["%1$s دا %n يىلدا","%1$s دا %n يىلدا"],
|
||||
"In the past on %1$s then on %2$s" : "ئۆتمۈشتە %1$s دە ئاندىن %2$s دا",
|
||||
"_In %n minute on %1$s then on %2$s_::_In %n minutes on %1$s then on %2$s_" : ["%1$s دا %n مىنۇتتا ئاندىن %2$s دا","%1$s دا %n مىنۇتتا ئاندىن %2$s دا"],
|
||||
"_In %n hour on %1$s then on %2$s_::_In %n hours on %1$s then on %2$s_" : ["%1$s دا %n سائەتتە ئاندىن %2$s دا","%1$s دا %n سائەتتە ئاندىن %2$s دا"],
|
||||
"_In %n day on %1$s then on %2$s_::_In %n days on %1$s then on %2$s_" : ["%1$s دا %n كۈندە ئاندىن %2$s دا","%1$s دا %n كۈندە ئاندىن %2$s دا"],
|
||||
"_In %n week on %1$s then on %2$s_::_In %n weeks on %1$s then on %2$s_" : ["%1$s دا %n ھەپتىدە ئاندىن %2$s دا","%1$s دا %n ھەپتىدە ئاندىن %2$s دا"],
|
||||
"_In %n month on %1$s then on %2$s_::_In %n months on %1$s then on %2$s_" : ["%1$s دا %n ئايدا ئاندىن %2$s دا","%1$s دا %n ئايدا ئاندىن %2$s دا"],
|
||||
"_In %n year on %1$s then on %2$s_::_In %n years on %1$s then on %2$s_" : ["%1$s دا %n يىلدا ئاندىن %2$s دا","%1$s دا %n يىلدا ئاندىن %2$s دا"],
|
||||
"In the past on %1$s then on %2$s and %3$s" : "ئۆتمۈشتە %1$s دە ئاندىن %2$s بىلەن %3$s دە",
|
||||
"_In %n minute on %1$s then on %2$s and %3$s_::_In %n minutes on %1$s then on %2$s and %3$s_" : ["%1$s دا %n مىنۇتتا ئاندىن كىيىن %2$s بىلەن %3$s دا","%1$s دا %n مىنۇتتا ئاندىن كىيىن %2$s بىلەن %3$s دا"],
|
||||
"_In %n hour on %1$s then on %2$s and %3$s_::_In %n hours on %1$s then on %2$s and %3$s_" : ["%1$s دا %n سائەتتە ئاندىن كىيىن %2$s بىلەن %3$s دا","%1$s دا %n سائەتتە ئاندىن كىيىن %2$s بىلەن %3$s دا"],
|
||||
"_In %n day on %1$s then on %2$s and %3$s_::_In %n days on %1$s then on %2$s and %3$s_" : ["%1$s دا %n كۈندە ئاندىن كىيىن %2$s بىلەن %3$s دا","%1$s دا %n كۈندە ئاندىن كىيىن %2$s بىلەن %3$s دا"],
|
||||
"_In %n week on %1$s then on %2$s and %3$s_::_In %n weeks on %1$s then on %2$s and %3$s_" : ["%1$s دا %n ھەپتىدە ئاندىن كىيىن %2$s بىلەن %3$s دا","%1$s دا %n ھەپتىدە ئاندىن كىيىن %2$s بىلەن %3$s دا"],
|
||||
"_In %n month on %1$s then on %2$s and %3$s_::_In %n months on %1$s then on %2$s and %3$s_" : ["%1$s دا %n ئايدا ئاندىن كىيىن %2$s بىلەن %3$s دا","%1$s دا %n ئايدا ئاندىن كىيىن %2$s بىلەن %3$s دا"],
|
||||
"_In %n year on %1$s then on %2$s and %3$s_::_In %n years on %1$s then on %2$s and %3$s_" : ["%1$s دا %n يىلدا ئاندىن كىيىن %2$s بىلەن %3$s دا","%1$s دا %n يىلدا ئاندىن كىيىن %2$s بىلەن %3$s دا"],
|
||||
"Could not generate next recurrence statement" : "كېيىنكى تەكرارلىنىش باياناتىنى ھاسىل قىلالمىدى",
|
||||
"Cancelled: %1$s" : "ئەمەلدىن قالدۇرۇلدى: %1$s",
|
||||
"\"%1$s\" has been canceled" : "\"%1$s\" ئەمەلدىن قالدۇرۇلدى",
|
||||
|
||||
@@ -71,7 +71,19 @@
|
||||
"Where: %s" : "قەيەردە: %s",
|
||||
"%1$s via %2$s" : "%1$s ئارقىلىق %2$s",
|
||||
"In the past on %1$s for the entire day" : "پۈتۈن كۈن ئۈچۈن ئۆتمۈشتىكى %1$s دا",
|
||||
"_In %n minute on %1$s for the entire day_::_In %n minutes on %1$s for the entire day_" : ["%1$s دا پۈتۈن كۈندە %n مىنۇتتا","%1$s دا پۈتۈن كۈندە %n مىنۇتتا"],
|
||||
"_In %n hour on %1$s for the entire day_::_In %n hours on %1$s for the entire day_" : ["%1$s دا پۈتۈن كۈندە %n سائەتتە","%1$s دا پۈتۈن كۈندە %n سائەتتە"],
|
||||
"_In %n day on %1$s for the entire day_::_In %n days on %1$s for the entire day_" : ["%1$s دا پۈتۈن كۈندە %n كۈندە","%1$s دا پۈتۈن كۈندە %n كۈندە"],
|
||||
"_In %n week on %1$s for the entire day_::_In %n weeks on %1$s for the entire day_" : ["%1$s دا پۈتۈن كۈندە %n ھەپتىدە","%1$s دا پۈتۈن كۈندە %n ھەپتىدە"],
|
||||
"_In %n month on %1$s for the entire day_::_In %n months on %1$s for the entire day_" : ["%1$s دا پۈتۈن كۈندە %n ئايدا","%1$s دا پۈتۈن كۈندە %n ئايدا"],
|
||||
"_In %n year on %1$s for the entire day_::_In %n years on %1$s for the entire day_" : ["%1$s دا پۈتۈن كۈندە %n يىلدا","%1$s دا پۈتۈن كۈندە %n يىلدا"],
|
||||
"In the past on %1$s between %2$s - %3$s" : "ئۆتمۈشتىكى %1$s دە %2$s - %3$s ئارىسىدا",
|
||||
"_In %n minute on %1$s between %2$s - %3$s_::_In %n minutes on %1$s between %2$s - %3$s_" : ["%1$s دە %2$s - %3$s ئارلىقىدا %n مىنۇتتا","%1$s دە %2$s - %3$s ئارلىقىدا %n مىنۇتتا"],
|
||||
"_In %n hour on %1$s between %2$s - %3$s_::_In %n hours on %1$s between %2$s - %3$s_" : ["%1$s دە %2$s - %3$s ئارلىقىدا %n سائەتتە","%1$s دە %2$s - %3$s ئارلىقىدا %n سائەتتە"],
|
||||
"_In %n day on %1$s between %2$s - %3$s_::_In %n days on %1$s between %2$s - %3$s_" : ["%1$s دە %2$s - %3$s ئارلىقىدا %n كۈندە","%1$s دە %2$s - %3$s ئارلىقىدا %n كۈندە"],
|
||||
"_In %n week on %1$s between %2$s - %3$s_::_In %n weeks on %1$s between %2$s - %3$s_" : ["%1$s دە %2$s - %3$s ئارلىقىدا %n ھەپتىدە","%1$s دە %2$s - %3$s ئارلىقىدا %n ھەپتىدە"],
|
||||
"_In %n month on %1$s between %2$s - %3$s_::_In %n months on %1$s between %2$s - %3$s_" : ["%1$s دە %2$s - %3$s ئارلىقىدا %n ئايدا","%1$s دە %2$s - %3$s ئارلىقىدا %n ئايدا"],
|
||||
"_In %n year on %1$s between %2$s - %3$s_::_In %n years on %1$s between %2$s - %3$s_" : ["%1$s دە %2$s - %3$s ئارلىقىدا %n يىلدا","%1$s دە %2$s - %3$s ئارلىقىدا %n يىلدا"],
|
||||
"Could not generate when statement" : "بايان قىلغاندا ھاسىل قىلالمىدى",
|
||||
"Every Day for the entire day" : "ھەر بىر كۈن پۈتۈن بىر كۈن",
|
||||
"Every Day for the entire day until %1$s" : "ھەر بىر كۈن پۈتۈن كۈن %1$s غىچە",
|
||||
@@ -109,8 +121,26 @@
|
||||
"On specific dates for the entire day until %1$s" : "%1$s غىچە بەلگىلىك چېسلادا پۈتۈن كۈن",
|
||||
"On specific dates between %1$s - %2$s until %3$s" : "%1$s - %2$s ئارلىقىدىكى %3$s غىچە بولغان بەلگىلىك چېسلادا",
|
||||
"In the past on %1$s" : "ئۆتمۈشتە %1$s دا",
|
||||
"_In %n minute on %1$s_::_In %n minutes on %1$s_" : ["%1$s دا %n مىنۇتتا","%1$s دا %n مىنۇتتا"],
|
||||
"_In %n hour on %1$s_::_In %n hours on %1$s_" : ["%1$s دا %n سائەتتە","%1$s دا %n سائەتتە"],
|
||||
"_In %n day on %1$s_::_In %n days on %1$s_" : ["%1$s دا %n كۈندە","%1$s دا %n كۈندە"],
|
||||
"_In %n week on %1$s_::_In %n weeks on %1$s_" : ["%1$s دا %n ھەپتىدە","%1$s دا %n ھەپتىدە"],
|
||||
"_In %n month on %1$s_::_In %n months on %1$s_" : ["%1$s دا %n ئايدا","%1$s دا %n ئايدا"],
|
||||
"_In %n year on %1$s_::_In %n years on %1$s_" : ["%1$s دا %n يىلدا","%1$s دا %n يىلدا"],
|
||||
"In the past on %1$s then on %2$s" : "ئۆتمۈشتە %1$s دە ئاندىن %2$s دا",
|
||||
"_In %n minute on %1$s then on %2$s_::_In %n minutes on %1$s then on %2$s_" : ["%1$s دا %n مىنۇتتا ئاندىن %2$s دا","%1$s دا %n مىنۇتتا ئاندىن %2$s دا"],
|
||||
"_In %n hour on %1$s then on %2$s_::_In %n hours on %1$s then on %2$s_" : ["%1$s دا %n سائەتتە ئاندىن %2$s دا","%1$s دا %n سائەتتە ئاندىن %2$s دا"],
|
||||
"_In %n day on %1$s then on %2$s_::_In %n days on %1$s then on %2$s_" : ["%1$s دا %n كۈندە ئاندىن %2$s دا","%1$s دا %n كۈندە ئاندىن %2$s دا"],
|
||||
"_In %n week on %1$s then on %2$s_::_In %n weeks on %1$s then on %2$s_" : ["%1$s دا %n ھەپتىدە ئاندىن %2$s دا","%1$s دا %n ھەپتىدە ئاندىن %2$s دا"],
|
||||
"_In %n month on %1$s then on %2$s_::_In %n months on %1$s then on %2$s_" : ["%1$s دا %n ئايدا ئاندىن %2$s دا","%1$s دا %n ئايدا ئاندىن %2$s دا"],
|
||||
"_In %n year on %1$s then on %2$s_::_In %n years on %1$s then on %2$s_" : ["%1$s دا %n يىلدا ئاندىن %2$s دا","%1$s دا %n يىلدا ئاندىن %2$s دا"],
|
||||
"In the past on %1$s then on %2$s and %3$s" : "ئۆتمۈشتە %1$s دە ئاندىن %2$s بىلەن %3$s دە",
|
||||
"_In %n minute on %1$s then on %2$s and %3$s_::_In %n minutes on %1$s then on %2$s and %3$s_" : ["%1$s دا %n مىنۇتتا ئاندىن كىيىن %2$s بىلەن %3$s دا","%1$s دا %n مىنۇتتا ئاندىن كىيىن %2$s بىلەن %3$s دا"],
|
||||
"_In %n hour on %1$s then on %2$s and %3$s_::_In %n hours on %1$s then on %2$s and %3$s_" : ["%1$s دا %n سائەتتە ئاندىن كىيىن %2$s بىلەن %3$s دا","%1$s دا %n سائەتتە ئاندىن كىيىن %2$s بىلەن %3$s دا"],
|
||||
"_In %n day on %1$s then on %2$s and %3$s_::_In %n days on %1$s then on %2$s and %3$s_" : ["%1$s دا %n كۈندە ئاندىن كىيىن %2$s بىلەن %3$s دا","%1$s دا %n كۈندە ئاندىن كىيىن %2$s بىلەن %3$s دا"],
|
||||
"_In %n week on %1$s then on %2$s and %3$s_::_In %n weeks on %1$s then on %2$s and %3$s_" : ["%1$s دا %n ھەپتىدە ئاندىن كىيىن %2$s بىلەن %3$s دا","%1$s دا %n ھەپتىدە ئاندىن كىيىن %2$s بىلەن %3$s دا"],
|
||||
"_In %n month on %1$s then on %2$s and %3$s_::_In %n months on %1$s then on %2$s and %3$s_" : ["%1$s دا %n ئايدا ئاندىن كىيىن %2$s بىلەن %3$s دا","%1$s دا %n ئايدا ئاندىن كىيىن %2$s بىلەن %3$s دا"],
|
||||
"_In %n year on %1$s then on %2$s and %3$s_::_In %n years on %1$s then on %2$s and %3$s_" : ["%1$s دا %n يىلدا ئاندىن كىيىن %2$s بىلەن %3$s دا","%1$s دا %n يىلدا ئاندىن كىيىن %2$s بىلەن %3$s دا"],
|
||||
"Could not generate next recurrence statement" : "كېيىنكى تەكرارلىنىش باياناتىنى ھاسىل قىلالمىدى",
|
||||
"Cancelled: %1$s" : "ئەمەلدىن قالدۇرۇلدى: %1$s",
|
||||
"\"%1$s\" has been canceled" : "\"%1$s\" ئەمەلدىن قالدۇرۇلدى",
|
||||
|
||||
@@ -73,7 +73,19 @@ OC.L10N.register(
|
||||
"Where: %s" : "地点:%s",
|
||||
"%1$s via %2$s" : "%1$s 通过 %2$s",
|
||||
"In the past on %1$s for the entire day" : "过去全天 %1$s ",
|
||||
"_In %n minute on %1$s for the entire day_::_In %n minutes on %1$s for the entire day_" : ["在 %n 分钟后全天 %1$s"],
|
||||
"_In %n hour on %1$s for the entire day_::_In %n hours on %1$s for the entire day_" : ["在 %n 小时后全天 %1$s"],
|
||||
"_In %n day on %1$s for the entire day_::_In %n days on %1$s for the entire day_" : ["在 %n 天后全天 %1$s"],
|
||||
"_In %n week on %1$s for the entire day_::_In %n weeks on %1$s for the entire day_" : ["在 %n 周后全天 %1$s"],
|
||||
"_In %n month on %1$s for the entire day_::_In %n months on %1$s for the entire day_" : ["在 %n 个月后全天 %1$s"],
|
||||
"_In %n year on %1$s for the entire day_::_In %n years on %1$s for the entire day_" : ["在 %n 年后全天 %1$s"],
|
||||
"In the past on %1$s between %2$s - %3$s" : "过去 %2$s - %3$s %1$s",
|
||||
"_In %n minute on %1$s between %2$s - %3$s_::_In %n minutes on %1$s between %2$s - %3$s_" : ["在 %2$s - %3$s %n 分钟后 %1$s"],
|
||||
"_In %n hour on %1$s between %2$s - %3$s_::_In %n hours on %1$s between %2$s - %3$s_" : ["在 %2$s - %3$s %n 小时后 %1$s"],
|
||||
"_In %n day on %1$s between %2$s - %3$s_::_In %n days on %1$s between %2$s - %3$s_" : ["在 %2$s - %3$s %n 天后 %1$s"],
|
||||
"_In %n week on %1$s between %2$s - %3$s_::_In %n weeks on %1$s between %2$s - %3$s_" : ["在 %2$s - %3$s %n 周后 %1$s"],
|
||||
"_In %n month on %1$s between %2$s - %3$s_::_In %n months on %1$s between %2$s - %3$s_" : ["在 %2$s - %3$s %n 个月后 %1$s"],
|
||||
"_In %n year on %1$s between %2$s - %3$s_::_In %n years on %1$s between %2$s - %3$s_" : ["在 %2$s - %3$s %n 年后 %1$s"],
|
||||
"Could not generate when statement" : "无法生成 when 语句",
|
||||
"Every Day for the entire day" : "每天全天",
|
||||
"Every Day for the entire day until %1$s" : "每天全天,直到 %1$s",
|
||||
@@ -111,8 +123,26 @@ OC.L10N.register(
|
||||
"On specific dates for the entire day until %1$s" : "在特定日期全天,直到 %1$s",
|
||||
"On specific dates between %1$s - %2$s until %3$s" : "在 %1$s - %2$s 特定日期,直到 %3$s",
|
||||
"In the past on %1$s" : "在过去 %1$s",
|
||||
"_In %n minute on %1$s_::_In %n minutes on %1$s_" : ["%n 分钟后 %1$s"],
|
||||
"_In %n hour on %1$s_::_In %n hours on %1$s_" : ["%n 小时后 %1$s"],
|
||||
"_In %n day on %1$s_::_In %n days on %1$s_" : ["%n 天后 %1$s"],
|
||||
"_In %n week on %1$s_::_In %n weeks on %1$s_" : ["%n 周后 %1$s"],
|
||||
"_In %n month on %1$s_::_In %n months on %1$s_" : ["%n 个月后 %1$s"],
|
||||
"_In %n year on %1$s_::_In %n years on %1$s_" : ["%n 年后 %1$s"],
|
||||
"In the past on %1$s then on %2$s" : "过去 %1$s,然后 %2$s",
|
||||
"_In %n minute on %1$s then on %2$s_::_In %n minutes on %1$s then on %2$s_" : ["%n 分钟后 %1$s,然后 %2$s"],
|
||||
"_In %n hour on %1$s then on %2$s_::_In %n hours on %1$s then on %2$s_" : ["%n 小时后 %1$s,然后 %2$s"],
|
||||
"_In %n day on %1$s then on %2$s_::_In %n days on %1$s then on %2$s_" : ["%n 天后 %1$s,然后 %2$s"],
|
||||
"_In %n week on %1$s then on %2$s_::_In %n weeks on %1$s then on %2$s_" : ["%n 周后 %1$s,然后 %2$s"],
|
||||
"_In %n month on %1$s then on %2$s_::_In %n months on %1$s then on %2$s_" : ["%n 个月后 %1$s,然后 %2$s"],
|
||||
"_In %n year on %1$s then on %2$s_::_In %n years on %1$s then on %2$s_" : ["%n 年后 %1$s,然后 %2$s"],
|
||||
"In the past on %1$s then on %2$s and %3$s" : "在过去 %1$s,然后 %2$s 和 %3$s",
|
||||
"_In %n minute on %1$s then on %2$s and %3$s_::_In %n minutes on %1$s then on %2$s and %3$s_" : ["%n 分钟后 %1$s,然后 %2$s 和 %3$s"],
|
||||
"_In %n hour on %1$s then on %2$s and %3$s_::_In %n hours on %1$s then on %2$s and %3$s_" : ["%n 小时后 %1$s,然后 %2$s 和 %3$s"],
|
||||
"_In %n day on %1$s then on %2$s and %3$s_::_In %n days on %1$s then on %2$s and %3$s_" : ["%n 天后 %1$s,然后 %2$s 和 %3$s"],
|
||||
"_In %n week on %1$s then on %2$s and %3$s_::_In %n weeks on %1$s then on %2$s and %3$s_" : ["%n 周后 %1$s,然后 %2$s 和 %3$s"],
|
||||
"_In %n month on %1$s then on %2$s and %3$s_::_In %n months on %1$s then on %2$s and %3$s_" : ["%n 个月后 %1$s,然后 %2$s 和 %3$s"],
|
||||
"_In %n year on %1$s then on %2$s and %3$s_::_In %n years on %1$s then on %2$s and %3$s_" : ["%n 年后 %1$s,然后 %2$s 和 %3$s"],
|
||||
"Could not generate next recurrence statement" : "无法生成下一个重复语句",
|
||||
"Cancelled: %1$s" : "已取消:%1$s",
|
||||
"\"%1$s\" has been canceled" : "“%1$s”已取消",
|
||||
|
||||
@@ -71,7 +71,19 @@
|
||||
"Where: %s" : "地点:%s",
|
||||
"%1$s via %2$s" : "%1$s 通过 %2$s",
|
||||
"In the past on %1$s for the entire day" : "过去全天 %1$s ",
|
||||
"_In %n minute on %1$s for the entire day_::_In %n minutes on %1$s for the entire day_" : ["在 %n 分钟后全天 %1$s"],
|
||||
"_In %n hour on %1$s for the entire day_::_In %n hours on %1$s for the entire day_" : ["在 %n 小时后全天 %1$s"],
|
||||
"_In %n day on %1$s for the entire day_::_In %n days on %1$s for the entire day_" : ["在 %n 天后全天 %1$s"],
|
||||
"_In %n week on %1$s for the entire day_::_In %n weeks on %1$s for the entire day_" : ["在 %n 周后全天 %1$s"],
|
||||
"_In %n month on %1$s for the entire day_::_In %n months on %1$s for the entire day_" : ["在 %n 个月后全天 %1$s"],
|
||||
"_In %n year on %1$s for the entire day_::_In %n years on %1$s for the entire day_" : ["在 %n 年后全天 %1$s"],
|
||||
"In the past on %1$s between %2$s - %3$s" : "过去 %2$s - %3$s %1$s",
|
||||
"_In %n minute on %1$s between %2$s - %3$s_::_In %n minutes on %1$s between %2$s - %3$s_" : ["在 %2$s - %3$s %n 分钟后 %1$s"],
|
||||
"_In %n hour on %1$s between %2$s - %3$s_::_In %n hours on %1$s between %2$s - %3$s_" : ["在 %2$s - %3$s %n 小时后 %1$s"],
|
||||
"_In %n day on %1$s between %2$s - %3$s_::_In %n days on %1$s between %2$s - %3$s_" : ["在 %2$s - %3$s %n 天后 %1$s"],
|
||||
"_In %n week on %1$s between %2$s - %3$s_::_In %n weeks on %1$s between %2$s - %3$s_" : ["在 %2$s - %3$s %n 周后 %1$s"],
|
||||
"_In %n month on %1$s between %2$s - %3$s_::_In %n months on %1$s between %2$s - %3$s_" : ["在 %2$s - %3$s %n 个月后 %1$s"],
|
||||
"_In %n year on %1$s between %2$s - %3$s_::_In %n years on %1$s between %2$s - %3$s_" : ["在 %2$s - %3$s %n 年后 %1$s"],
|
||||
"Could not generate when statement" : "无法生成 when 语句",
|
||||
"Every Day for the entire day" : "每天全天",
|
||||
"Every Day for the entire day until %1$s" : "每天全天,直到 %1$s",
|
||||
@@ -109,8 +121,26 @@
|
||||
"On specific dates for the entire day until %1$s" : "在特定日期全天,直到 %1$s",
|
||||
"On specific dates between %1$s - %2$s until %3$s" : "在 %1$s - %2$s 特定日期,直到 %3$s",
|
||||
"In the past on %1$s" : "在过去 %1$s",
|
||||
"_In %n minute on %1$s_::_In %n minutes on %1$s_" : ["%n 分钟后 %1$s"],
|
||||
"_In %n hour on %1$s_::_In %n hours on %1$s_" : ["%n 小时后 %1$s"],
|
||||
"_In %n day on %1$s_::_In %n days on %1$s_" : ["%n 天后 %1$s"],
|
||||
"_In %n week on %1$s_::_In %n weeks on %1$s_" : ["%n 周后 %1$s"],
|
||||
"_In %n month on %1$s_::_In %n months on %1$s_" : ["%n 个月后 %1$s"],
|
||||
"_In %n year on %1$s_::_In %n years on %1$s_" : ["%n 年后 %1$s"],
|
||||
"In the past on %1$s then on %2$s" : "过去 %1$s,然后 %2$s",
|
||||
"_In %n minute on %1$s then on %2$s_::_In %n minutes on %1$s then on %2$s_" : ["%n 分钟后 %1$s,然后 %2$s"],
|
||||
"_In %n hour on %1$s then on %2$s_::_In %n hours on %1$s then on %2$s_" : ["%n 小时后 %1$s,然后 %2$s"],
|
||||
"_In %n day on %1$s then on %2$s_::_In %n days on %1$s then on %2$s_" : ["%n 天后 %1$s,然后 %2$s"],
|
||||
"_In %n week on %1$s then on %2$s_::_In %n weeks on %1$s then on %2$s_" : ["%n 周后 %1$s,然后 %2$s"],
|
||||
"_In %n month on %1$s then on %2$s_::_In %n months on %1$s then on %2$s_" : ["%n 个月后 %1$s,然后 %2$s"],
|
||||
"_In %n year on %1$s then on %2$s_::_In %n years on %1$s then on %2$s_" : ["%n 年后 %1$s,然后 %2$s"],
|
||||
"In the past on %1$s then on %2$s and %3$s" : "在过去 %1$s,然后 %2$s 和 %3$s",
|
||||
"_In %n minute on %1$s then on %2$s and %3$s_::_In %n minutes on %1$s then on %2$s and %3$s_" : ["%n 分钟后 %1$s,然后 %2$s 和 %3$s"],
|
||||
"_In %n hour on %1$s then on %2$s and %3$s_::_In %n hours on %1$s then on %2$s and %3$s_" : ["%n 小时后 %1$s,然后 %2$s 和 %3$s"],
|
||||
"_In %n day on %1$s then on %2$s and %3$s_::_In %n days on %1$s then on %2$s and %3$s_" : ["%n 天后 %1$s,然后 %2$s 和 %3$s"],
|
||||
"_In %n week on %1$s then on %2$s and %3$s_::_In %n weeks on %1$s then on %2$s and %3$s_" : ["%n 周后 %1$s,然后 %2$s 和 %3$s"],
|
||||
"_In %n month on %1$s then on %2$s and %3$s_::_In %n months on %1$s then on %2$s and %3$s_" : ["%n 个月后 %1$s,然后 %2$s 和 %3$s"],
|
||||
"_In %n year on %1$s then on %2$s and %3$s_::_In %n years on %1$s then on %2$s and %3$s_" : ["%n 年后 %1$s,然后 %2$s 和 %3$s"],
|
||||
"Could not generate next recurrence statement" : "无法生成下一个重复语句",
|
||||
"Cancelled: %1$s" : "已取消:%1$s",
|
||||
"\"%1$s\" has been canceled" : "“%1$s”已取消",
|
||||
|
||||
@@ -187,9 +187,9 @@ class BirthdayService {
|
||||
$originalYear = (int)$dateParts['year'];
|
||||
}
|
||||
|
||||
$leapDay = ((int)$dateParts['month'] === 2
|
||||
&& (int)$dateParts['date'] === 29);
|
||||
if ($dateParts['year'] === null || $originalYear < 1970) {
|
||||
$leapDay = ((int)$dateParts['month'] === 2 && (int)$dateParts['date'] === 29);
|
||||
|
||||
if ($dateParts['year'] === null) {
|
||||
$birthday = ($leapDay ? '1972-' : '1970-')
|
||||
. $dateParts['month'] . '-' . $dateParts['date'];
|
||||
}
|
||||
|
||||
@@ -1066,9 +1066,9 @@ class CalDavBackend extends AbstractBackend implements SyncSupport, Subscription
|
||||
* @param int $calendarType
|
||||
* @return array
|
||||
*/
|
||||
public function getLimitedCalendarObjects(int $calendarId, int $calendarType = self::CALENDAR_TYPE_CALENDAR):array {
|
||||
public function getLimitedCalendarObjects(int $calendarId, int $calendarType = self::CALENDAR_TYPE_CALENDAR, array $fields = []):array {
|
||||
$query = $this->db->getQueryBuilder();
|
||||
$query->select(['id','uid', 'etag', 'uri', 'calendardata'])
|
||||
$query->select($fields ?: ['id', 'uid', 'etag', 'uri', 'calendardata'])
|
||||
->from('calendarobjects')
|
||||
->where($query->expr()->eq('calendarid', $query->createNamedParameter($calendarId)))
|
||||
->andWhere($query->expr()->eq('calendartype', $query->createNamedParameter($calendarType)))
|
||||
@@ -1077,12 +1077,7 @@ class CalDavBackend extends AbstractBackend implements SyncSupport, Subscription
|
||||
|
||||
$result = [];
|
||||
while (($row = $stmt->fetchAssociative()) !== false) {
|
||||
$result[$row['uid']] = [
|
||||
'id' => $row['id'],
|
||||
'etag' => $row['etag'],
|
||||
'uri' => $row['uri'],
|
||||
'calendardata' => $row['calendardata'],
|
||||
];
|
||||
$result[$row['uid']] = $row;
|
||||
}
|
||||
$stmt->closeCursor();
|
||||
|
||||
|
||||
@@ -23,9 +23,6 @@ use Sabre\VObject\UUIDUtil;
|
||||
*/
|
||||
class ImportService {
|
||||
|
||||
/** @var resource */
|
||||
private $source;
|
||||
|
||||
public function __construct(
|
||||
private CalDavBackend $backend,
|
||||
) {
|
||||
@@ -44,18 +41,15 @@ class ImportService {
|
||||
if (!is_resource($source)) {
|
||||
throw new InvalidArgumentException('Invalid import source must be a file resource');
|
||||
}
|
||||
|
||||
$this->source = $source;
|
||||
|
||||
switch ($options->getFormat()) {
|
||||
case 'ical':
|
||||
return $this->importProcess($calendar, $options, $this->importText(...));
|
||||
return $this->importProcess($source, $calendar, $options, $this->importText(...));
|
||||
break;
|
||||
case 'jcal':
|
||||
return $this->importProcess($calendar, $options, $this->importJson(...));
|
||||
return $this->importProcess($source, $calendar, $options, $this->importJson(...));
|
||||
break;
|
||||
case 'xcal':
|
||||
return $this->importProcess($calendar, $options, $this->importXml(...));
|
||||
return $this->importProcess($source, $calendar, $options, $this->importXml(...));
|
||||
break;
|
||||
default:
|
||||
throw new InvalidArgumentException('Invalid import format');
|
||||
@@ -65,10 +59,15 @@ class ImportService {
|
||||
/**
|
||||
* Generates object stream from a text formatted source (ical)
|
||||
*
|
||||
* @param resource $source
|
||||
*
|
||||
* @return Generator<\Sabre\VObject\Component\VCalendar>
|
||||
*/
|
||||
private function importText(): Generator {
|
||||
$importer = new TextImporter($this->source);
|
||||
public function importText($source): Generator {
|
||||
if (!is_resource($source)) {
|
||||
throw new InvalidArgumentException('Invalid import source must be a file resource');
|
||||
}
|
||||
$importer = new TextImporter($source);
|
||||
$structure = $importer->structure();
|
||||
$sObjectPrefix = $importer::OBJECT_PREFIX;
|
||||
$sObjectSuffix = $importer::OBJECT_SUFFIX;
|
||||
@@ -113,10 +112,15 @@ class ImportService {
|
||||
/**
|
||||
* Generates object stream from a xml formatted source (xcal)
|
||||
*
|
||||
* @param resource $source
|
||||
*
|
||||
* @return Generator<\Sabre\VObject\Component\VCalendar>
|
||||
*/
|
||||
private function importXml(): Generator {
|
||||
$importer = new XmlImporter($this->source);
|
||||
public function importXml($source): Generator {
|
||||
if (!is_resource($source)) {
|
||||
throw new InvalidArgumentException('Invalid import source must be a file resource');
|
||||
}
|
||||
$importer = new XmlImporter($source);
|
||||
$structure = $importer->structure();
|
||||
$sObjectPrefix = $importer::OBJECT_PREFIX;
|
||||
$sObjectSuffix = $importer::OBJECT_SUFFIX;
|
||||
@@ -155,11 +159,16 @@ class ImportService {
|
||||
/**
|
||||
* Generates object stream from a json formatted source (jcal)
|
||||
*
|
||||
* @param resource $source
|
||||
*
|
||||
* @return Generator<\Sabre\VObject\Component\VCalendar>
|
||||
*/
|
||||
private function importJson(): Generator {
|
||||
public function importJson($source): Generator {
|
||||
if (!is_resource($source)) {
|
||||
throw new InvalidArgumentException('Invalid import source must be a file resource');
|
||||
}
|
||||
/** @var VCALENDAR $importer */
|
||||
$importer = Reader::readJson($this->source);
|
||||
$importer = Reader::readJson($source);
|
||||
// calendar time zones
|
||||
$timezones = [];
|
||||
foreach ($importer->VTIMEZONE as $timezone) {
|
||||
@@ -212,17 +221,18 @@ class ImportService {
|
||||
*
|
||||
* @since 32.0.0
|
||||
*
|
||||
* @param resource $source
|
||||
* @param CalendarImportOptions $options
|
||||
* @param callable $generator<CalendarImportOptions>: Generator<\Sabre\VObject\Component\VCalendar>
|
||||
*
|
||||
* @return array<string,array<string,string|array<string>>>
|
||||
*/
|
||||
public function importProcess(CalendarImpl $calendar, CalendarImportOptions $options, callable $generator): array {
|
||||
public function importProcess($source, CalendarImpl $calendar, CalendarImportOptions $options, callable $generator): array {
|
||||
$calendarId = $calendar->getKey();
|
||||
$calendarUri = $calendar->getUri();
|
||||
$principalUri = $calendar->getPrincipalUri();
|
||||
$outcome = [];
|
||||
foreach ($generator() as $vObject) {
|
||||
foreach ($generator($source) as $vObject) {
|
||||
$components = $vObject->getBaseComponents();
|
||||
// determine if the object has no base component types
|
||||
if (count($components) === 0) {
|
||||
|
||||
@@ -144,19 +144,31 @@ class EmailProvider extends AbstractProvider {
|
||||
IL10N $l10n,
|
||||
string $calendarDisplayName,
|
||||
VEvent $vevent):void {
|
||||
$template->addBodyListItem($calendarDisplayName, $l10n->t('Calendar:'),
|
||||
$this->getAbsoluteImagePath('actions/info.png'));
|
||||
$template->addBodyListItem(
|
||||
htmlspecialchars($calendarDisplayName),
|
||||
$l10n->t('Calendar:'),
|
||||
$this->getAbsoluteImagePath('actions/info.png'),
|
||||
htmlspecialchars($calendarDisplayName),
|
||||
);
|
||||
|
||||
$template->addBodyListItem($this->generateDateString($l10n, $vevent), $l10n->t('Date:'),
|
||||
$this->getAbsoluteImagePath('places/calendar.png'));
|
||||
|
||||
if (isset($vevent->LOCATION)) {
|
||||
$template->addBodyListItem((string)$vevent->LOCATION, $l10n->t('Where:'),
|
||||
$this->getAbsoluteImagePath('actions/address.png'));
|
||||
$template->addBodyListItem(
|
||||
htmlspecialchars((string)$vevent->LOCATION),
|
||||
$l10n->t('Where:'),
|
||||
$this->getAbsoluteImagePath('actions/address.png'),
|
||||
htmlspecialchars((string)$vevent->LOCATION),
|
||||
);
|
||||
}
|
||||
if (isset($vevent->DESCRIPTION)) {
|
||||
$template->addBodyListItem((string)$vevent->DESCRIPTION, $l10n->t('Description:'),
|
||||
$this->getAbsoluteImagePath('actions/more.png'));
|
||||
$template->addBodyListItem(
|
||||
htmlspecialchars((string)$vevent->DESCRIPTION),
|
||||
$l10n->t('Description:'),
|
||||
$this->getAbsoluteImagePath('actions/more.png'),
|
||||
htmlspecialchars((string)$vevent->DESCRIPTION),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -11,7 +11,8 @@ namespace OCA\DAV\CalDAV\Schedule;
|
||||
use OC\URLGenerator;
|
||||
use OCA\DAV\CalDAV\EventReader;
|
||||
use OCP\AppFramework\Utility\ITimeFactory;
|
||||
use OCP\IConfig;
|
||||
use OCP\Config\IUserConfig;
|
||||
use OCP\IAppConfig;
|
||||
use OCP\IDBConnection;
|
||||
use OCP\IL10N;
|
||||
use OCP\IUserManager;
|
||||
@@ -25,6 +26,7 @@ use Sabre\VObject\ITip\Message;
|
||||
use Sabre\VObject\Parameter;
|
||||
use Sabre\VObject\Property;
|
||||
use Sabre\VObject\Recur\EventIterator;
|
||||
use function htmlspecialchars;
|
||||
|
||||
class IMipService {
|
||||
|
||||
@@ -40,12 +42,13 @@ class IMipService {
|
||||
|
||||
public function __construct(
|
||||
private URLGenerator $urlGenerator,
|
||||
private IConfig $config,
|
||||
private IDBConnection $db,
|
||||
private ISecureRandom $random,
|
||||
private L10NFactory $l10nFactory,
|
||||
private ITimeFactory $timeFactory,
|
||||
private readonly IUserManager $userManager,
|
||||
private readonly IUserConfig $userConfig,
|
||||
private readonly IAppConfig $appConfig,
|
||||
) {
|
||||
$language = $this->l10nFactory->findGenericLanguage();
|
||||
$locale = $this->l10nFactory->findLocale($language);
|
||||
@@ -80,10 +83,11 @@ class IMipService {
|
||||
if (!isset($vevent->$property)) {
|
||||
return $default;
|
||||
}
|
||||
$newstring = $vevent->$property->getValue();
|
||||
$value = $vevent->$property->getValue();
|
||||
$newstring = $value === null ? null : htmlspecialchars($value);
|
||||
if (isset($oldVEvent->$property) && $oldVEvent->$property->getValue() !== $newstring) {
|
||||
$oldstring = $oldVEvent->$property->getValue();
|
||||
return sprintf($strikethrough, $oldstring, $newstring);
|
||||
return sprintf($strikethrough, htmlspecialchars($oldstring), $newstring);
|
||||
}
|
||||
return $newstring;
|
||||
}
|
||||
@@ -96,8 +100,8 @@ class IMipService {
|
||||
return $default;
|
||||
}
|
||||
/** @var string|null $newString */
|
||||
$newString = $vevent->$property->getValue();
|
||||
$oldString = isset($oldVEvent->$property) ? $oldVEvent->$property->getValue() : null;
|
||||
$newString = htmlspecialchars($vevent->$property->getValue());
|
||||
$oldString = isset($oldVEvent->$property) ? htmlspecialchars($oldVEvent->$property->getValue()) : null;
|
||||
if ($oldString !== $newString) {
|
||||
return sprintf(
|
||||
"<span style='text-decoration: line-through'>%s</span><br />%s",
|
||||
@@ -797,10 +801,10 @@ class IMipService {
|
||||
$strikethrough = "<span style='text-decoration: line-through'>%s</span>";
|
||||
|
||||
$newMeetingWhen = $this->generateWhenString($eventReaderCurrent);
|
||||
$newSummary = isset($vEvent->SUMMARY) && (string)$vEvent->SUMMARY !== '' ? (string)$vEvent->SUMMARY : $this->l10n->t('Untitled event');
|
||||
$newDescription = isset($vEvent->DESCRIPTION) && (string)$vEvent->DESCRIPTION !== '' ? (string)$vEvent->DESCRIPTION : $defaultVal;
|
||||
$newSummary = htmlspecialchars(isset($vEvent->SUMMARY) && (string)$vEvent->SUMMARY !== '' ? (string)$vEvent->SUMMARY : $this->l10n->t('Untitled event'));
|
||||
$newDescription = htmlspecialchars(isset($vEvent->DESCRIPTION) && (string)$vEvent->DESCRIPTION !== '' ? (string)$vEvent->DESCRIPTION : $defaultVal);
|
||||
$newUrl = isset($vEvent->URL) && (string)$vEvent->URL !== '' ? sprintf('<a href="%1$s">%1$s</a>', $vEvent->URL) : $defaultVal;
|
||||
$newLocation = isset($vEvent->LOCATION) && (string)$vEvent->LOCATION !== '' ? (string)$vEvent->LOCATION : $defaultVal;
|
||||
$newLocation = htmlspecialchars(isset($vEvent->LOCATION) && (string)$vEvent->LOCATION !== '' ? (string)$vEvent->LOCATION : $defaultVal);
|
||||
$newLocationHtml = $this->linkify($newLocation) ?? $newLocation;
|
||||
|
||||
$data = [];
|
||||
@@ -885,8 +889,8 @@ class IMipService {
|
||||
$users = $this->userManager->getByEmail($userAddress);
|
||||
if ($users !== []) {
|
||||
$user = array_shift($users);
|
||||
$language = $this->config->getUserValue($user->getUID(), 'core', 'lang', null);
|
||||
$locale = $this->config->getUserValue($user->getUID(), 'core', 'locale', null);
|
||||
$language = $this->userConfig->getValueString($user->getUID(), 'core', 'lang', '') ?: null;
|
||||
$locale = $this->userConfig->getValueString($user->getUID(), 'core', 'locale', '') ?: null;
|
||||
}
|
||||
// fallback to attendee LANGUAGE parameter if language not set
|
||||
if ($language === null && isset($attendee['LANGUAGE']) && $attendee['LANGUAGE'] instanceof Parameter) {
|
||||
@@ -994,7 +998,7 @@ class IMipService {
|
||||
* The default is 'no', which matches old behavior, and is privacy preserving.
|
||||
*
|
||||
* To enable including attendees in invitation emails:
|
||||
* % php occ config:app:set dav invitation_list_attendees --value yes
|
||||
* % php occ config:app:set dav invitation_list_attendees --value yes --type bool
|
||||
*
|
||||
* @param IEMailTemplate $template
|
||||
* @param IL10N $this->l10n
|
||||
@@ -1002,12 +1006,12 @@ class IMipService {
|
||||
* @author brad2014 on github.com
|
||||
*/
|
||||
public function addAttendees(IEMailTemplate $template, VEvent $vevent) {
|
||||
if ($this->config->getAppValue('dav', 'invitation_list_attendees', 'no') === 'no') {
|
||||
if (!$this->appConfig->getValueBool('dav', 'invitation_list_attendees')) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (isset($vevent->ORGANIZER)) {
|
||||
/** @var Property | Property\ICalendar\CalAddress $organizer */
|
||||
/** @var Property&Property\ICalendar\CalAddress $organizer */
|
||||
$organizer = $vevent->ORGANIZER;
|
||||
$organizerEmail = substr($organizer->getNormalizedValue(), 7);
|
||||
/** @var string|null $organizerName */
|
||||
@@ -1037,8 +1041,14 @@ class IMipService {
|
||||
$attendeesHTML = [];
|
||||
$attendeesText = [];
|
||||
foreach ($attendees as $attendee) {
|
||||
/** @var Property&Property\ICalendar\CalAddress $attendee */
|
||||
$attendeeEmail = substr($attendee->getNormalizedValue(), 7);
|
||||
$attendeeName = isset($attendee['CN']) ? $attendee['CN']->getValue() : null;
|
||||
$attendeeName = null;
|
||||
if (isset($attendee['CN'])) {
|
||||
/** @var Parameter $cn */
|
||||
$cn = $attendee['CN'];
|
||||
$attendeeName = $cn->getValue();
|
||||
}
|
||||
$attendeeHTML = sprintf('<a href="%s">%s</a>',
|
||||
htmlspecialchars($attendee->getNormalizedValue()),
|
||||
htmlspecialchars($attendeeName ?: $attendeeEmail));
|
||||
@@ -1067,22 +1077,22 @@ class IMipService {
|
||||
*/
|
||||
public function addBulletList(IEMailTemplate $template, VEvent $vevent, $data) {
|
||||
$template->addBodyListItem(
|
||||
$data['meeting_title_html'] ?? $data['meeting_title'], $this->l10n->t('Title:'),
|
||||
$data['meeting_title_html'] ?? htmlspecialchars($data['meeting_title']), $this->l10n->t('Title:'),
|
||||
$this->getAbsoluteImagePath('caldav/title.png'), $data['meeting_title'], '', IMipPlugin::IMIP_INDENT);
|
||||
if ($data['meeting_when'] !== '') {
|
||||
$template->addBodyListItem($data['meeting_when_html'] ?? $data['meeting_when'], $this->l10n->t('When:'),
|
||||
$template->addBodyListItem($data['meeting_when_html'] ?? htmlspecialchars($data['meeting_when']), $this->l10n->t('When:'),
|
||||
$this->getAbsoluteImagePath('caldav/time.png'), $data['meeting_when'], '', IMipPlugin::IMIP_INDENT);
|
||||
}
|
||||
if ($data['meeting_location'] !== '') {
|
||||
$template->addBodyListItem($data['meeting_location_html'] ?? $data['meeting_location'], $this->l10n->t('Location:'),
|
||||
$template->addBodyListItem($data['meeting_location_html'] ?? htmlspecialchars($data['meeting_location']), $this->l10n->t('Location:'),
|
||||
$this->getAbsoluteImagePath('caldav/location.png'), $data['meeting_location'], '', IMipPlugin::IMIP_INDENT);
|
||||
}
|
||||
if ($data['meeting_url'] !== '') {
|
||||
$template->addBodyListItem($data['meeting_url_html'] ?? $data['meeting_url'], $this->l10n->t('Link:'),
|
||||
$template->addBodyListItem($data['meeting_url_html'] ?? htmlspecialchars($data['meeting_url']), $this->l10n->t('Link:'),
|
||||
$this->getAbsoluteImagePath('caldav/link.png'), $data['meeting_url'], '', IMipPlugin::IMIP_INDENT);
|
||||
}
|
||||
if (isset($data['meeting_occurring'])) {
|
||||
$template->addBodyListItem($data['meeting_occurring_html'] ?? $data['meeting_occurring'], $this->l10n->t('Occurring:'),
|
||||
$template->addBodyListItem($data['meeting_occurring_html'] ?? htmlspecialchars($data['meeting_occurring']), $this->l10n->t('Occurring:'),
|
||||
$this->getAbsoluteImagePath('caldav/time.png'), $data['meeting_occurring'], '', IMipPlugin::IMIP_INDENT);
|
||||
}
|
||||
|
||||
@@ -1090,7 +1100,7 @@ class IMipService {
|
||||
|
||||
/* Put description last, like an email body, since it can be arbitrarily long */
|
||||
if ($data['meeting_description']) {
|
||||
$template->addBodyListItem($data['meeting_description_html'] ?? $data['meeting_description'], $this->l10n->t('Description:'),
|
||||
$template->addBodyListItem($data['meeting_description_html'] ?? htmlspecialchars($data['meeting_description']), $this->l10n->t('Description:'),
|
||||
$this->getAbsoluteImagePath('caldav/description.png'), $data['meeting_description'], '', IMipPlugin::IMIP_INDENT);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -12,6 +12,7 @@ namespace OCA\DAV\CalDAV;
|
||||
use OCA\DAV\Db\PropertyMapper;
|
||||
use OCP\Calendar\ICalendar;
|
||||
use OCP\Calendar\IManager;
|
||||
use OCP\Config\IUserConfig;
|
||||
use OCP\IConfig;
|
||||
use Sabre\VObject\Component\VCalendar;
|
||||
use Sabre\VObject\Component\VTimeZone;
|
||||
@@ -22,13 +23,14 @@ class TimezoneService {
|
||||
|
||||
public function __construct(
|
||||
private IConfig $config,
|
||||
private IUserConfig $userConfig,
|
||||
private PropertyMapper $propertyMapper,
|
||||
private IManager $calendarManager,
|
||||
) {
|
||||
}
|
||||
|
||||
public function getUserTimezone(string $userId): ?string {
|
||||
$fromConfig = $this->config->getUserValue(
|
||||
$fromConfig = $this->userConfig->getValueString(
|
||||
$userId,
|
||||
'core',
|
||||
'timezone',
|
||||
@@ -51,7 +53,7 @@ class TimezoneService {
|
||||
}
|
||||
|
||||
$principal = 'principals/users/' . $userId;
|
||||
$uri = $this->config->getUserValue($userId, 'dav', 'defaultCalendar', CalDavBackend::PERSONAL_CALENDAR_URI);
|
||||
$uri = $this->userConfig->getValueString($userId, 'dav', 'defaultCalendar', CalDavBackend::PERSONAL_CALENDAR_URI);
|
||||
$calendars = $this->calendarManager->getCalendarsForPrincipal($principal);
|
||||
|
||||
/** @var ?VTimeZone $personalCalendarTimezone */
|
||||
|
||||
@@ -14,7 +14,6 @@ use OCP\Http\Client\IClientService;
|
||||
use OCP\Http\Client\LocalServerException;
|
||||
use OCP\IAppConfig;
|
||||
use Psr\Log\LoggerInterface;
|
||||
use Sabre\VObject\Reader;
|
||||
|
||||
class Connection {
|
||||
public function __construct(
|
||||
@@ -26,8 +25,10 @@ class Connection {
|
||||
|
||||
/**
|
||||
* gets webcal feed from remote server
|
||||
*
|
||||
* @return array{data: resource, format: string}|null
|
||||
*/
|
||||
public function queryWebcalFeed(array $subscription): ?string {
|
||||
public function queryWebcalFeed(array $subscription): ?array {
|
||||
$subscriptionId = $subscription['id'];
|
||||
$url = $this->cleanURL($subscription['source']);
|
||||
if ($url === null) {
|
||||
@@ -54,6 +55,7 @@ class Connection {
|
||||
'User-Agent' => $uaString,
|
||||
'Accept' => 'text/calendar, application/calendar+json, application/calendar+xml',
|
||||
],
|
||||
'stream' => true,
|
||||
];
|
||||
|
||||
$user = parse_url($subscription['source'], PHP_URL_USER);
|
||||
@@ -77,42 +79,22 @@ class Connection {
|
||||
return null;
|
||||
}
|
||||
|
||||
$body = $response->getBody();
|
||||
|
||||
$contentType = $response->getHeader('Content-Type');
|
||||
$contentType = explode(';', $contentType, 2)[0];
|
||||
switch ($contentType) {
|
||||
case 'application/calendar+json':
|
||||
try {
|
||||
$jCalendar = Reader::readJson($body, Reader::OPTION_FORGIVING);
|
||||
} catch (Exception $ex) {
|
||||
// In case of a parsing error return null
|
||||
$this->logger->warning("Subscription $subscriptionId could not be parsed", ['exception' => $ex]);
|
||||
return null;
|
||||
}
|
||||
return $jCalendar->serialize();
|
||||
|
||||
case 'application/calendar+xml':
|
||||
try {
|
||||
$xCalendar = Reader::readXML($body);
|
||||
} catch (Exception $ex) {
|
||||
// In case of a parsing error return null
|
||||
$this->logger->warning("Subscription $subscriptionId could not be parsed", ['exception' => $ex]);
|
||||
return null;
|
||||
}
|
||||
return $xCalendar->serialize();
|
||||
$format = match ($contentType) {
|
||||
'application/calendar+json' => 'jcal',
|
||||
'application/calendar+xml' => 'xcal',
|
||||
default => 'ical',
|
||||
};
|
||||
|
||||
case 'text/calendar':
|
||||
default:
|
||||
try {
|
||||
$vCalendar = Reader::read($body);
|
||||
} catch (Exception $ex) {
|
||||
// In case of a parsing error return null
|
||||
$this->logger->warning("Subscription $subscriptionId could not be parsed", ['exception' => $ex]);
|
||||
return null;
|
||||
}
|
||||
return $vCalendar->serialize();
|
||||
// With 'stream' => true, getBody() returns the underlying stream resource
|
||||
$stream = $response->getBody();
|
||||
if (!is_resource($stream)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return ['data' => $stream, 'format' => $format];
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -9,18 +9,14 @@ declare(strict_types=1);
|
||||
namespace OCA\DAV\CalDAV\WebcalCaching;
|
||||
|
||||
use OCA\DAV\CalDAV\CalDavBackend;
|
||||
use OCA\DAV\CalDAV\Import\ImportService;
|
||||
use OCP\AppFramework\Utility\ITimeFactory;
|
||||
use Psr\Log\LoggerInterface;
|
||||
use Sabre\DAV\Exception\BadRequest;
|
||||
use Sabre\DAV\Exception\Forbidden;
|
||||
use Sabre\DAV\PropPatch;
|
||||
use Sabre\VObject\Component;
|
||||
use Sabre\VObject\DateTimeParser;
|
||||
use Sabre\VObject\InvalidDataException;
|
||||
use Sabre\VObject\ParseException;
|
||||
use Sabre\VObject\Reader;
|
||||
use Sabre\VObject\Recur\NoInstancesException;
|
||||
use Sabre\VObject\Splitter\ICalendar;
|
||||
use Sabre\VObject\UUIDUtil;
|
||||
use function count;
|
||||
|
||||
@@ -36,20 +32,20 @@ class RefreshWebcalService {
|
||||
private LoggerInterface $logger,
|
||||
private Connection $connection,
|
||||
private ITimeFactory $time,
|
||||
private ImportService $importService,
|
||||
) {
|
||||
}
|
||||
|
||||
public function refreshSubscription(string $principalUri, string $uri) {
|
||||
$subscription = $this->getSubscription($principalUri, $uri);
|
||||
$mutations = [];
|
||||
if (!$subscription) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Check the refresh rate if there is any
|
||||
if (!empty($subscription['{http://apple.com/ns/ical/}refreshrate'])) {
|
||||
// add the refresh interval to the lastmodified timestamp
|
||||
$refreshInterval = new \DateInterval($subscription['{http://apple.com/ns/ical/}refreshrate']);
|
||||
if (!empty($subscription[self::REFRESH_RATE])) {
|
||||
// add the refresh interval to the last modified timestamp
|
||||
$refreshInterval = new \DateInterval($subscription[self::REFRESH_RATE]);
|
||||
$updateTime = $this->time->getDateTime();
|
||||
$updateTime->setTimestamp($subscription['lastmodified'])->add($refreshInterval);
|
||||
if ($updateTime->getTimestamp() > $this->time->getTime()) {
|
||||
@@ -57,109 +53,116 @@ class RefreshWebcalService {
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
$webcalData = $this->connection->queryWebcalFeed($subscription);
|
||||
if (!$webcalData) {
|
||||
$result = $this->connection->queryWebcalFeed($subscription);
|
||||
if (!$result) {
|
||||
return;
|
||||
}
|
||||
|
||||
$localData = $this->calDavBackend->getLimitedCalendarObjects((int)$subscription['id'], CalDavBackend::CALENDAR_TYPE_SUBSCRIPTION);
|
||||
$data = $result['data'];
|
||||
$format = $result['format'];
|
||||
|
||||
$stripTodos = ($subscription[self::STRIP_TODOS] ?? 1) === 1;
|
||||
$stripAlarms = ($subscription[self::STRIP_ALARMS] ?? 1) === 1;
|
||||
$stripAttachments = ($subscription[self::STRIP_ATTACHMENTS] ?? 1) === 1;
|
||||
|
||||
try {
|
||||
$splitter = new ICalendar($webcalData, Reader::OPTION_FORGIVING);
|
||||
$existingObjects = $this->calDavBackend->getLimitedCalendarObjects((int)$subscription['id'], CalDavBackend::CALENDAR_TYPE_SUBSCRIPTION, ['id', 'uid', 'etag', 'uri']);
|
||||
|
||||
while ($vObject = $splitter->getNext()) {
|
||||
/** @var Component $vObject */
|
||||
$compName = null;
|
||||
$uid = null;
|
||||
$generator = match ($format) {
|
||||
'xcal' => $this->importService->importXml(...),
|
||||
'jcal' => $this->importService->importJson(...),
|
||||
default => $this->importService->importText(...)
|
||||
};
|
||||
|
||||
foreach ($vObject->getComponents() as $component) {
|
||||
if ($component->name === 'VTIMEZONE') {
|
||||
continue;
|
||||
foreach ($generator($data) as $vObject) {
|
||||
/** @var Component\VCalendar $vObject */
|
||||
$vBase = $vObject->getBaseComponent();
|
||||
|
||||
if (!$vBase->UID) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// Some calendar providers (e.g. Google, MS) use very long UIDs
|
||||
if (strlen($vBase->UID->getValue()) > 512) {
|
||||
$this->logger->warning('Skipping calendar object with overly long UID from subscription "{subscriptionId}"', [
|
||||
'subscriptionId' => $subscription['id'],
|
||||
'uid' => $vBase->UID->getValue(),
|
||||
]);
|
||||
continue;
|
||||
}
|
||||
|
||||
if ($stripTodos && $vBase->name === 'VTODO') {
|
||||
continue;
|
||||
}
|
||||
|
||||
if ($stripAlarms || $stripAttachments) {
|
||||
foreach ($vObject->getComponents() as $component) {
|
||||
if ($component->name === 'VTIMEZONE') {
|
||||
continue;
|
||||
}
|
||||
if ($stripAlarms) {
|
||||
$component->remove('VALARM');
|
||||
}
|
||||
if ($stripAttachments) {
|
||||
$component->remove('ATTACH');
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
$compName = $component->name;
|
||||
$sObject = $vObject->serialize();
|
||||
$uid = $vBase->UID->getValue();
|
||||
$etag = md5($sObject);
|
||||
|
||||
if ($stripAlarms) {
|
||||
unset($component->{'VALARM'});
|
||||
// No existing object with this UID, create it
|
||||
if (!isset($existingObjects[$uid])) {
|
||||
try {
|
||||
$this->calDavBackend->createCalendarObject(
|
||||
$subscription['id'],
|
||||
UUIDUtil::getUUID() . '.ics',
|
||||
$sObject,
|
||||
CalDavBackend::CALENDAR_TYPE_SUBSCRIPTION
|
||||
);
|
||||
} catch (\Exception $ex) {
|
||||
$this->logger->warning('Unable to create calendar object from subscription {subscriptionId}', [
|
||||
'exception' => $ex,
|
||||
'subscriptionId' => $subscription['id'],
|
||||
'source' => $subscription['source'],
|
||||
]);
|
||||
}
|
||||
if ($stripAttachments) {
|
||||
unset($component->{'ATTACH'});
|
||||
}
|
||||
|
||||
$uid = $component->{ 'UID' }->getValue();
|
||||
}
|
||||
|
||||
if ($stripTodos && $compName === 'VTODO') {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (!isset($uid)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
try {
|
||||
$denormalized = $this->calDavBackend->getDenormalizedData($vObject->serialize());
|
||||
} catch (InvalidDataException|Forbidden $ex) {
|
||||
$this->logger->warning('Unable to denormalize calendar object from subscription {subscriptionId}', ['exception' => $ex, 'subscriptionId' => $subscription['id'], 'source' => $subscription['source']]);
|
||||
continue;
|
||||
}
|
||||
|
||||
// Find all identical sets and remove them from the update
|
||||
if (isset($localData[$uid]) && $denormalized['etag'] === $localData[$uid]['etag']) {
|
||||
unset($localData[$uid]);
|
||||
continue;
|
||||
}
|
||||
|
||||
$vObjectCopy = clone $vObject;
|
||||
$identical = isset($localData[$uid]) && $this->compareWithoutDtstamp($vObjectCopy, $localData[$uid]);
|
||||
if ($identical) {
|
||||
unset($localData[$uid]);
|
||||
continue;
|
||||
}
|
||||
|
||||
// Find all modified sets and update them
|
||||
if (isset($localData[$uid]) && $denormalized['etag'] !== $localData[$uid]['etag']) {
|
||||
$this->calDavBackend->updateCalendarObject($subscription['id'], $localData[$uid]['uri'], $vObject->serialize(), CalDavBackend::CALENDAR_TYPE_SUBSCRIPTION);
|
||||
unset($localData[$uid]);
|
||||
continue;
|
||||
}
|
||||
|
||||
// Only entirely new events get created here
|
||||
try {
|
||||
$objectUri = $this->getRandomCalendarObjectUri();
|
||||
$this->calDavBackend->createCalendarObject($subscription['id'], $objectUri, $vObject->serialize(), CalDavBackend::CALENDAR_TYPE_SUBSCRIPTION);
|
||||
} catch (NoInstancesException|BadRequest $ex) {
|
||||
$this->logger->warning('Unable to create calendar object from subscription {subscriptionId}', ['exception' => $ex, 'subscriptionId' => $subscription['id'], 'source' => $subscription['source']]);
|
||||
} elseif ($existingObjects[$uid]['etag'] !== $etag) {
|
||||
// Existing object with this UID but different etag, update it
|
||||
$this->calDavBackend->updateCalendarObject(
|
||||
$subscription['id'],
|
||||
$existingObjects[$uid]['uri'],
|
||||
$sObject,
|
||||
CalDavBackend::CALENDAR_TYPE_SUBSCRIPTION
|
||||
);
|
||||
unset($existingObjects[$uid]);
|
||||
} else {
|
||||
// Existing object with same etag, just remove from tracking
|
||||
unset($existingObjects[$uid]);
|
||||
}
|
||||
}
|
||||
|
||||
$ids = array_map(static function ($dataSet): int {
|
||||
return (int)$dataSet['id'];
|
||||
}, $localData);
|
||||
$uris = array_map(static function ($dataSet): string {
|
||||
return $dataSet['uri'];
|
||||
}, $localData);
|
||||
|
||||
if (!empty($ids) && !empty($uris)) {
|
||||
// Clean up on aisle 5
|
||||
// The only events left over in the $localData array should be those that don't exist upstream
|
||||
// All deleted VObjects from upstream are removed
|
||||
$this->calDavBackend->purgeCachedEventsForSubscription($subscription['id'], $ids, $uris);
|
||||
// Clean up objects that no longer exist in the remote feed
|
||||
// The only events left over should be those not found upstream
|
||||
if (!empty($existingObjects)) {
|
||||
$ids = array_map('intval', array_column($existingObjects, 'id'));
|
||||
$uris = array_column($existingObjects, 'uri');
|
||||
$this->calDavBackend->purgeCachedEventsForSubscription((int)$subscription['id'], $ids, $uris);
|
||||
}
|
||||
|
||||
$newRefreshRate = $this->checkWebcalDataForRefreshRate($subscription, $webcalData);
|
||||
if ($newRefreshRate) {
|
||||
$mutations[self::REFRESH_RATE] = $newRefreshRate;
|
||||
// Update refresh rate from the last processed object
|
||||
if (isset($vObject)) {
|
||||
$this->updateRefreshRate($subscription, $vObject);
|
||||
}
|
||||
|
||||
$this->updateSubscription($subscription, $mutations);
|
||||
} catch (ParseException $ex) {
|
||||
$this->logger->error('Subscription {subscriptionId} could not be refreshed due to a parsing error', ['exception' => $ex, 'subscriptionId' => $subscription['id']]);
|
||||
} finally {
|
||||
// Close the data stream to free resources
|
||||
if (is_resource($data)) {
|
||||
fclose($data);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -181,84 +184,34 @@ class RefreshWebcalService {
|
||||
return $subscriptions[0];
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* check if:
|
||||
* - current subscription stores a refreshrate
|
||||
* - the webcal feed suggests a refreshrate
|
||||
* - return suggested refreshrate if user didn't set a custom one
|
||||
*
|
||||
* Update refresh rate from calendar object if:
|
||||
* - current subscription does not store a refreshrate
|
||||
* - the webcal feed suggests a valid refreshrate
|
||||
*/
|
||||
private function checkWebcalDataForRefreshRate(array $subscription, string $webcalData): ?string {
|
||||
// if there is no refreshrate stored in the database, check the webcal feed
|
||||
// whether it suggests any refresh rate and store that in the database
|
||||
if (isset($subscription[self::REFRESH_RATE]) && $subscription[self::REFRESH_RATE] !== null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
/** @var Component\VCalendar $vCalendar */
|
||||
$vCalendar = Reader::read($webcalData);
|
||||
|
||||
$newRefreshRate = null;
|
||||
if (isset($vCalendar->{'X-PUBLISHED-TTL'})) {
|
||||
$newRefreshRate = $vCalendar->{'X-PUBLISHED-TTL'}->getValue();
|
||||
}
|
||||
if (isset($vCalendar->{'REFRESH-INTERVAL'})) {
|
||||
$newRefreshRate = $vCalendar->{'REFRESH-INTERVAL'}->getValue();
|
||||
}
|
||||
|
||||
if (!$newRefreshRate) {
|
||||
return null;
|
||||
}
|
||||
|
||||
// check if new refresh rate is even valid
|
||||
try {
|
||||
DateTimeParser::parseDuration($newRefreshRate);
|
||||
} catch (InvalidDataException $ex) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return $newRefreshRate;
|
||||
}
|
||||
|
||||
/**
|
||||
* update subscription stored in database
|
||||
* used to set:
|
||||
* - refreshrate
|
||||
* - source
|
||||
*
|
||||
* @param array $subscription
|
||||
* @param array $mutations
|
||||
*/
|
||||
private function updateSubscription(array $subscription, array $mutations) {
|
||||
if (empty($mutations)) {
|
||||
private function updateRefreshRate(array $subscription, Component\VCalendar $vCalendar): void {
|
||||
// if there is already a refreshrate stored in the database, don't override it
|
||||
if (!empty($subscription[self::REFRESH_RATE])) {
|
||||
return;
|
||||
}
|
||||
|
||||
$propPatch = new PropPatch($mutations);
|
||||
$refreshRate = $vCalendar->{'REFRESH-INTERVAL'}?->getValue()
|
||||
?? $vCalendar->{'X-PUBLISHED-TTL'}?->getValue();
|
||||
|
||||
if ($refreshRate === null) {
|
||||
return;
|
||||
}
|
||||
|
||||
// check if refresh rate is valid
|
||||
try {
|
||||
DateTimeParser::parseDuration($refreshRate);
|
||||
} catch (InvalidDataException) {
|
||||
return;
|
||||
}
|
||||
|
||||
$propPatch = new PropPatch([self::REFRESH_RATE => $refreshRate]);
|
||||
$this->calDavBackend->updateSubscription($subscription['id'], $propPatch);
|
||||
$propPatch->commit();
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a random uri for a calendar-object
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function getRandomCalendarObjectUri():string {
|
||||
return UUIDUtil::getUUID() . '.ics';
|
||||
}
|
||||
|
||||
private function compareWithoutDtstamp(Component $vObject, array $calendarObject): bool {
|
||||
foreach ($vObject->getComponents() as $component) {
|
||||
unset($component->{'DTSTAMP'});
|
||||
}
|
||||
|
||||
$localVobject = Reader::read($calendarObject['calendardata']);
|
||||
foreach ($localVobject->getComponents() as $component) {
|
||||
unset($component->{'DTSTAMP'});
|
||||
}
|
||||
|
||||
return strcasecmp($localVobject->serialize(), $vObject->serialize()) === 0;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -77,6 +77,10 @@ class EntityCollection extends RootCollection implements IProperties {
|
||||
public function getChild($name) {
|
||||
try {
|
||||
$comment = $this->commentsManager->get($name);
|
||||
if ($comment->getObjectType() !== $this->name
|
||||
|| $comment->getObjectId() !== $this->id) {
|
||||
throw new NotFound();
|
||||
}
|
||||
return new CommentNode(
|
||||
$this->commentsManager,
|
||||
$comment,
|
||||
@@ -130,8 +134,9 @@ class EntityCollection extends RootCollection implements IProperties {
|
||||
*/
|
||||
public function childExists($name) {
|
||||
try {
|
||||
$this->commentsManager->get($name);
|
||||
return true;
|
||||
$comment = $this->commentsManager->get($name);
|
||||
return $comment->getObjectType() === $this->name
|
||||
&& $comment->getObjectId() === $this->id;
|
||||
} catch (NotFoundException $e) {
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -49,7 +49,7 @@ class BlockLegacyClientPlugin extends ServerPlugin {
|
||||
return;
|
||||
}
|
||||
|
||||
$minimumSupportedDesktopVersion = $this->config->getSystemValueString('minimum.supported.desktop.version', '3.1.0');
|
||||
$minimumSupportedDesktopVersion = $this->config->getSystemValueString('minimum.supported.desktop.version', '3.0.82');
|
||||
$maximumSupportedDesktopVersion = $this->config->getSystemValueString('maximum.supported.desktop.version', '99.99.99');
|
||||
|
||||
// Check if the client is a desktop client
|
||||
|
||||
@@ -8,6 +8,7 @@
|
||||
namespace OCA\DAV\Connector\Sabre;
|
||||
|
||||
use OC\Files\Mount\MoveableMount;
|
||||
use OC\Files\Utils\PathHelper;
|
||||
use OC\Files\View;
|
||||
use OCA\DAV\AppInfo\Application;
|
||||
use OCA\DAV\Connector\Sabre\Exception\FileLocked;
|
||||
@@ -38,8 +39,14 @@ use Sabre\DAV\Exception\NotFound;
|
||||
use Sabre\DAV\Exception\ServiceUnavailable;
|
||||
use Sabre\DAV\IFile;
|
||||
use Sabre\DAV\INode;
|
||||
use Sabre\DAV\INodeByPath;
|
||||
|
||||
class Directory extends Node implements \Sabre\DAV\ICollection, \Sabre\DAV\IQuota, \Sabre\DAV\IMoveTarget, \Sabre\DAV\ICopyTarget {
|
||||
class Directory extends Node implements
|
||||
\Sabre\DAV\ICollection,
|
||||
\Sabre\DAV\IQuota,
|
||||
\Sabre\DAV\IMoveTarget,
|
||||
\Sabre\DAV\ICopyTarget,
|
||||
INodeByPath {
|
||||
/**
|
||||
* Cached directory content
|
||||
* @var FileInfo[]
|
||||
@@ -181,7 +188,7 @@ class Directory extends Node implements \Sabre\DAV\ICollection, \Sabre\DAV\IQuot
|
||||
// If we are, then only PUT and MKCOL are allowed (see plugin)
|
||||
// so we are safe to return the directory without a risk of
|
||||
// leaking files and folders structure.
|
||||
if ($storage instanceof PublicShareWrapper) {
|
||||
if ($storage->instanceOfStorage(PublicShareWrapper::class)) {
|
||||
$share = $storage->getShare();
|
||||
$allowDirectory = ($share->getPermissions() & Constants::PERMISSION_READ) !== Constants::PERMISSION_READ;
|
||||
}
|
||||
@@ -490,4 +497,79 @@ class Directory extends Node implements \Sabre\DAV\ICollection, \Sabre\DAV\IQuot
|
||||
public function getNode(): Folder {
|
||||
return $this->node;
|
||||
}
|
||||
|
||||
public function getNodeForPath($path): INode {
|
||||
$storage = $this->info->getStorage();
|
||||
$allowDirectory = false;
|
||||
|
||||
// Checking if we're in a file drop
|
||||
// If we are, then only PUT and MKCOL are allowed (see plugin)
|
||||
// so we are safe to return the directory without a risk of
|
||||
// leaking files and folders structure.
|
||||
if ($storage->instanceOfStorage(PublicShareWrapper::class)) {
|
||||
$share = $storage->getShare();
|
||||
$allowDirectory = ($share->getPermissions() & Constants::PERMISSION_READ) !== Constants::PERMISSION_READ;
|
||||
}
|
||||
|
||||
// For file drop we need to be allowed to read the directory with the nickname
|
||||
if (!$allowDirectory && !$this->info->isReadable()) {
|
||||
// avoid detecting files through this way
|
||||
throw new NotFound();
|
||||
}
|
||||
|
||||
$destinationPath = PathHelper::normalizePath($this->getPath() . '/' . $path);
|
||||
$destinationDir = dirname($destinationPath);
|
||||
|
||||
try {
|
||||
$info = $this->getNode()->get($path);
|
||||
} catch (NotFoundException $e) {
|
||||
throw new \Sabre\DAV\Exception\NotFound('File with name ' . $destinationPath
|
||||
. ' could not be located');
|
||||
} catch (StorageNotAvailableException $e) {
|
||||
throw new \Sabre\DAV\Exception\ServiceUnavailable($e->getMessage(), 0, $e);
|
||||
} catch (NotPermittedException $ex) {
|
||||
throw new InvalidPath($ex->getMessage(), false, $ex);
|
||||
}
|
||||
|
||||
// if not in a public share with no read permissions, throw Forbidden
|
||||
if (!$allowDirectory && !$info->isReadable()) {
|
||||
if (Server::get(IAppManager::class)->isEnabledForAnyone('files_accesscontrol')) {
|
||||
throw new Forbidden('No read permissions. This might be caused by files_accesscontrol, check your configured rules');
|
||||
}
|
||||
|
||||
throw new Forbidden('No read permissions');
|
||||
}
|
||||
|
||||
if ($info->getMimeType() === FileInfo::MIMETYPE_FOLDER) {
|
||||
$node = new \OCA\DAV\Connector\Sabre\Directory($this->fileView, $info, $this->tree, $this->shareManager);
|
||||
} else {
|
||||
// In case reading a directory was allowed but it turns out the node was a not a directory, reject it now.
|
||||
if (!$this->info->isReadable()) {
|
||||
throw new NotFound();
|
||||
}
|
||||
|
||||
$node = new File($this->fileView, $info, $this->shareManager);
|
||||
}
|
||||
$this->tree?->cacheNode($node);
|
||||
|
||||
// recurse upwards until the root and check for read permissions to keep
|
||||
// ACL checks working in files_accesscontrol
|
||||
if (!$allowDirectory && $destinationDir !== '') {
|
||||
$scanPath = $destinationPath;
|
||||
while (($scanPath = dirname($scanPath)) !== '/') {
|
||||
// fileView can get the parent info in a cheaper way compared
|
||||
// to the node API
|
||||
/** @psalm-suppress InternalMethod */
|
||||
$info = $this->fileView->getFileInfo($scanPath, false);
|
||||
$directory = new Directory($this->fileView, $info, $this->tree, $this->shareManager);
|
||||
$readable = $directory->getNode()->isReadable();
|
||||
if (!$readable) {
|
||||
throw new \Sabre\DAV\Exception\NotFound('File with name ' . $destinationPath
|
||||
. ' could not be located');
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return $node;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -18,6 +18,7 @@ use OCA\DAV\Connector\Sabre\Exception\FileLocked;
|
||||
use OCA\DAV\Connector\Sabre\Exception\Forbidden as DAVForbiddenException;
|
||||
use OCA\DAV\Connector\Sabre\Exception\UnsupportedMediaType;
|
||||
use OCP\App\IAppManager;
|
||||
use OCP\Constants;
|
||||
use OCP\Encryption\Exceptions\GenericEncryptionException;
|
||||
use OCP\Files;
|
||||
use OCP\Files\EntityTooLargeException;
|
||||
@@ -539,18 +540,24 @@ class File extends Node implements IFile {
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array|bool
|
||||
* @throws NotFoundException
|
||||
* @throws NotPermittedException
|
||||
*/
|
||||
public function getDirectDownload() {
|
||||
public function getDirectDownload(): array|false {
|
||||
if (Server::get(IAppManager::class)->isEnabledForUser('encryption')) {
|
||||
return [];
|
||||
return false;
|
||||
}
|
||||
[$storage, $internalPath] = $this->fileView->resolvePath($this->path);
|
||||
if (is_null($storage)) {
|
||||
return [];
|
||||
$node = $this->getNode();
|
||||
$storage = $node->getStorage();
|
||||
if (!$storage) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return $storage->getDirectDownload($internalPath);
|
||||
if (!($node->getPermissions() & Constants::PERMISSION_READ)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return $storage->getDirectDownloadById((string)$node->getId());
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -50,6 +50,7 @@ class FilesPlugin extends ServerPlugin {
|
||||
public const OCM_SHARE_PERMISSIONS_PROPERTYNAME = '{http://open-cloud-mesh.org/ns}share-permissions';
|
||||
public const SHARE_ATTRIBUTES_PROPERTYNAME = '{http://nextcloud.org/ns}share-attributes';
|
||||
public const DOWNLOADURL_PROPERTYNAME = '{http://owncloud.org/ns}downloadURL';
|
||||
public const DOWNLOADURL_EXPIRATION_PROPERTYNAME = '{http://nextcloud.org/ns}download-url-expiration';
|
||||
public const SIZE_PROPERTYNAME = '{http://owncloud.org/ns}size';
|
||||
public const GETETAG_PROPERTYNAME = '{DAV:}getetag';
|
||||
public const LASTMODIFIED_PROPERTYNAME = '{DAV:}lastmodified';
|
||||
@@ -120,6 +121,7 @@ class FilesPlugin extends ServerPlugin {
|
||||
$server->protectedProperties[] = self::SHARE_ATTRIBUTES_PROPERTYNAME;
|
||||
$server->protectedProperties[] = self::SIZE_PROPERTYNAME;
|
||||
$server->protectedProperties[] = self::DOWNLOADURL_PROPERTYNAME;
|
||||
$server->protectedProperties[] = self::DOWNLOADURL_EXPIRATION_PROPERTYNAME;
|
||||
$server->protectedProperties[] = self::OWNER_ID_PROPERTYNAME;
|
||||
$server->protectedProperties[] = self::OWNER_DISPLAY_NAME_PROPERTYNAME;
|
||||
$server->protectedProperties[] = self::CHECKSUMS_PROPERTYNAME;
|
||||
@@ -471,19 +473,30 @@ class FilesPlugin extends ServerPlugin {
|
||||
}
|
||||
|
||||
if ($node instanceof File) {
|
||||
$propFind->handle(self::DOWNLOADURL_PROPERTYNAME, function () use ($node) {
|
||||
$requestProperties = $propFind->getRequestedProperties();
|
||||
|
||||
if (in_array(self::DOWNLOADURL_PROPERTYNAME, $requestProperties, true)
|
||||
|| in_array(self::DOWNLOADURL_EXPIRATION_PROPERTYNAME, $requestProperties, true)) {
|
||||
try {
|
||||
$directDownloadUrl = $node->getDirectDownload();
|
||||
if (isset($directDownloadUrl['url'])) {
|
||||
} catch (StorageNotAvailableException|ForbiddenException) {
|
||||
$directDownloadUrl = null;
|
||||
}
|
||||
|
||||
$propFind->handle(self::DOWNLOADURL_PROPERTYNAME, function () use ($node, $directDownloadUrl) {
|
||||
if ($directDownloadUrl && isset($directDownloadUrl['url'])) {
|
||||
return $directDownloadUrl['url'];
|
||||
}
|
||||
} catch (StorageNotAvailableException $e) {
|
||||
return false;
|
||||
} catch (ForbiddenException $e) {
|
||||
});
|
||||
|
||||
$propFind->handle(self::DOWNLOADURL_EXPIRATION_PROPERTYNAME, function () use ($node, $directDownloadUrl) {
|
||||
if ($directDownloadUrl && isset($directDownloadUrl['expiration'])) {
|
||||
return $directDownloadUrl['expiration'];
|
||||
}
|
||||
return false;
|
||||
}
|
||||
return false;
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
$propFind->handle(self::CHECKSUMS_PROPERTYNAME, function () use ($node) {
|
||||
$checksum = $node->getChecksum();
|
||||
|
||||
@@ -37,6 +37,7 @@ use OCP\IRequest;
|
||||
use OCP\ITagManager;
|
||||
use OCP\IUserManager;
|
||||
use OCP\IUserSession;
|
||||
use OCP\L10N\IFactory;
|
||||
use OCP\SabrePluginEvent;
|
||||
use OCP\SystemTag\ISystemTagManager;
|
||||
use OCP\SystemTag\ISystemTagObjectMapper;
|
||||
@@ -70,15 +71,13 @@ class ServerFactory {
|
||||
Plugin $authPlugin,
|
||||
callable $viewCallBack,
|
||||
): Server {
|
||||
// /public.php/webdav/ shows the files in the share in the root itself
|
||||
// and not under /public.php/webdav/files/{token} so we should keep
|
||||
// compatibility for that.
|
||||
$needsSharesInRoot = $baseUri === '/public.php/webdav/';
|
||||
$useCollection = $isPublicShare && !$needsSharesInRoot;
|
||||
$debugEnabled = $this->config->getSystemValue('debug', false);
|
||||
// Fire up server
|
||||
if ($isPublicShare) {
|
||||
$rootCollection = new SimpleCollection('root');
|
||||
$tree = new CachingTree($rootCollection);
|
||||
} else {
|
||||
$rootCollection = null;
|
||||
$tree = new ObjectTree();
|
||||
}
|
||||
[$tree, $rootCollection] = $this->getTree($useCollection);
|
||||
$server = new Server($tree);
|
||||
// Set URL explicitly due to reverse-proxy situations
|
||||
$server->httpRequest->setUrl($requestUri);
|
||||
@@ -127,8 +126,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, $debugEnabled): void {
|
||||
$server->on('beforeMethod:*', function () use ($server,
|
||||
$tree, $viewCallBack, $isPublicShare, $rootCollection, $debugEnabled): void {
|
||||
// ensure the skeleton is copied
|
||||
$userFolder = \OC::$server->getUserFolder();
|
||||
|
||||
@@ -147,36 +146,8 @@ class ServerFactory {
|
||||
$root = new File($view, $rootInfo);
|
||||
}
|
||||
|
||||
if ($isPublicShare) {
|
||||
$userPrincipalBackend = new Principal(
|
||||
\OCP\Server::get(IUserManager::class),
|
||||
\OCP\Server::get(IGroupManager::class),
|
||||
\OCP\Server::get(IAccountManager::class),
|
||||
\OCP\Server::get(\OCP\Share\IManager::class),
|
||||
\OCP\Server::get(IUserSession::class),
|
||||
\OCP\Server::get(IAppManager::class),
|
||||
\OCP\Server::get(ProxyMapper::class),
|
||||
\OCP\Server::get(KnownUserService::class),
|
||||
\OCP\Server::get(IConfig::class),
|
||||
\OC::$server->getL10NFactory(),
|
||||
);
|
||||
|
||||
// Mount the share collection at /public.php/dav/shares/<share token>
|
||||
$rootCollection->addChild(new RootCollection(
|
||||
$root,
|
||||
$userPrincipalBackend,
|
||||
'principals/shares',
|
||||
));
|
||||
|
||||
// Mount the upload collection at /public.php/dav/uploads/<share token>
|
||||
$rootCollection->addChild(new \OCA\DAV\Upload\RootCollection(
|
||||
$userPrincipalBackend,
|
||||
'principals/shares',
|
||||
\OCP\Server::get(CleanupService::class),
|
||||
\OCP\Server::get(IRootFolder::class),
|
||||
\OCP\Server::get(IUserSession::class),
|
||||
\OCP\Server::get(\OCP\Share\IManager::class),
|
||||
));
|
||||
if ($rootCollection !== null) {
|
||||
$this->initRootCollection($rootCollection, $root);
|
||||
} else {
|
||||
/** @var ObjectTree $tree */
|
||||
$tree->init($root, $view, $this->mountManager);
|
||||
@@ -191,7 +162,7 @@ class ServerFactory {
|
||||
$this->userSession,
|
||||
\OCP\Server::get(IFilenameValidator::class),
|
||||
\OCP\Server::get(IAccountManager::class),
|
||||
false,
|
||||
$isPublicShare,
|
||||
!$debugEnabled
|
||||
)
|
||||
);
|
||||
@@ -252,4 +223,61 @@ class ServerFactory {
|
||||
}, 30); // priority 30: after auth (10) and acl(20), before lock(50) and handling the request
|
||||
return $server;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a Tree object and, if $useCollection is true, the collection used
|
||||
* as root.
|
||||
*
|
||||
* @param bool $useCollection Whether to use a collection or the legacy
|
||||
* ObjectTree, which doesn't use collections.
|
||||
* @return array{0: CachingTree|ObjectTree, 1: SimpleCollection|null}
|
||||
*/
|
||||
public function getTree(bool $useCollection): array {
|
||||
if ($useCollection) {
|
||||
$rootCollection = new SimpleCollection('root');
|
||||
$tree = new CachingTree($rootCollection);
|
||||
return [$tree, $rootCollection];
|
||||
}
|
||||
|
||||
return [new ObjectTree(), null];
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds the user's principal backend to $rootCollection.
|
||||
*/
|
||||
private function initRootCollection(SimpleCollection $rootCollection, Directory|File $root): void {
|
||||
$userPrincipalBackend = new Principal(
|
||||
\OCP\Server::get(IUserManager::class),
|
||||
\OCP\Server::get(IGroupManager::class),
|
||||
\OCP\Server::get(IAccountManager::class),
|
||||
\OCP\Server::get(\OCP\Share\IManager::class),
|
||||
\OCP\Server::get(IUserSession::class),
|
||||
\OCP\Server::get(IAppManager::class),
|
||||
\OCP\Server::get(ProxyMapper::class),
|
||||
\OCP\Server::get(KnownUserService::class),
|
||||
\OCP\Server::get(IConfig::class),
|
||||
\OCP\Server::get(IFactory::class),
|
||||
);
|
||||
|
||||
// Mount the share collection at /public.php/dav/files/<share token>
|
||||
$rootCollection->addChild(
|
||||
new RootCollection(
|
||||
$root,
|
||||
$userPrincipalBackend,
|
||||
'principals/shares',
|
||||
)
|
||||
);
|
||||
|
||||
// Mount the upload collection at /public.php/dav/uploads/<share token>
|
||||
$rootCollection->addChild(
|
||||
new \OCA\DAV\Upload\RootCollection(
|
||||
$userPrincipalBackend,
|
||||
'principals/shares',
|
||||
\OCP\Server::get(CleanupService::class),
|
||||
\OCP\Server::get(IRootFolder::class),
|
||||
\OCP\Server::get(IUserSession::class),
|
||||
\OCP\Server::get(\OCP\Share\IManager::class),
|
||||
)
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -99,7 +99,6 @@ class SharesPlugin extends \Sabre\DAV\ServerPlugin {
|
||||
IShare::TYPE_ROOM,
|
||||
IShare::TYPE_CIRCLE,
|
||||
IShare::TYPE_DECK,
|
||||
IShare::TYPE_SCIENCEMESH,
|
||||
];
|
||||
|
||||
foreach ($requestedShareTypes as $requestedShareType) {
|
||||
|
||||
@@ -0,0 +1,56 @@
|
||||
<?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\Migration\Attributes\ColumnType;
|
||||
use OCP\Migration\Attributes\ModifyColumn;
|
||||
use OCP\Migration\IOutput;
|
||||
use OCP\Migration\SimpleMigrationStep;
|
||||
use Override;
|
||||
|
||||
#[ModifyColumn(table: 'calendarobjects', name: 'uid', type: ColumnType::STRING, description: 'Increase uid length to 512 characters')]
|
||||
#[ModifyColumn(table: 'calendar_reminders', name: 'uid', type: ColumnType::STRING, description: 'Increase uid length to 512 characters')]
|
||||
#[ModifyColumn(table: 'calendar_invitations', name: 'uid', type: ColumnType::STRING, description: 'Increase uid length to 512 characters')]
|
||||
class Version1036Date20251202000000 extends SimpleMigrationStep {
|
||||
/**
|
||||
* @param Closure(): ISchemaWrapper $schemaClosure
|
||||
*/
|
||||
#[Override]
|
||||
public function changeSchema(IOutput $output, Closure $schemaClosure, array $options): ?ISchemaWrapper {
|
||||
/** @var ISchemaWrapper $schema */
|
||||
$schema = $schemaClosure();
|
||||
$modified = false;
|
||||
|
||||
$table = $schema->getTable('calendarobjects');
|
||||
$column = $table->getColumn('uid');
|
||||
if ($column->getLength() < 512) {
|
||||
$column->setLength(512);
|
||||
$modified = true;
|
||||
}
|
||||
|
||||
$table = $schema->getTable('calendar_reminders');
|
||||
$column = $table->getColumn('uid');
|
||||
if ($column->getLength() < 512) {
|
||||
$column->setLength(512);
|
||||
$modified = true;
|
||||
}
|
||||
|
||||
$table = $schema->getTable('calendar_invitations');
|
||||
$column = $table->getColumn('uid');
|
||||
if ($column->getLength() < 512) {
|
||||
$column->setLength(512);
|
||||
$modified = true;
|
||||
}
|
||||
|
||||
return $modified ? $schema : null;
|
||||
}
|
||||
}
|
||||
@@ -14,6 +14,7 @@ use OCP\Files\IRootFolder;
|
||||
use OCP\Files\NotFoundException;
|
||||
use OCP\IUserSession;
|
||||
use Sabre\DAV\Exception\Forbidden;
|
||||
use Sabre\DAV\Exception\NotFound;
|
||||
use Sabre\DAV\ICollection;
|
||||
|
||||
class UploadHome implements ICollection {
|
||||
@@ -72,7 +73,12 @@ class UploadHome implements ICollection {
|
||||
}
|
||||
|
||||
public function childExists($name): bool {
|
||||
return !is_null($this->getChild($name));
|
||||
try {
|
||||
$this->getChild($name);
|
||||
return true;
|
||||
} catch (NotFound $e) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
public function delete() {
|
||||
|
||||
@@ -220,8 +220,7 @@
|
||||
"schema": {
|
||||
"type": "object",
|
||||
"required": [
|
||||
"fileId",
|
||||
"expirationTime"
|
||||
"fileId"
|
||||
],
|
||||
"properties": {
|
||||
"fileId": {
|
||||
|
||||
@@ -45,7 +45,7 @@ class RefreshWebcalJobTest extends TestCase {
|
||||
#[\PHPUnit\Framework\Attributes\DataProvider('runDataProvider')]
|
||||
public function testRun(int $lastRun, int $time, bool $process): void {
|
||||
$backgroundJob = new RefreshWebcalJob($this->refreshWebcalService, $this->config, $this->logger, $this->timeFactory);
|
||||
$backgroundJob->setId(42);
|
||||
$backgroundJob->setId('42');
|
||||
|
||||
$backgroundJob->setArgument([
|
||||
'principaluri' => 'principals/users/testuser',
|
||||
|
||||
@@ -14,9 +14,9 @@ use OCA\DAV\CalDAV\EventComparisonService;
|
||||
use OCA\DAV\CalDAV\Schedule\IMipPlugin;
|
||||
use OCA\DAV\CalDAV\Schedule\IMipService;
|
||||
use OCP\AppFramework\Utility\ITimeFactory;
|
||||
use OCP\Config\IUserConfig;
|
||||
use OCP\Defaults;
|
||||
use OCP\IAppConfig;
|
||||
use OCP\IConfig;
|
||||
use OCP\IDBConnection;
|
||||
use OCP\IURLGenerator;
|
||||
use OCP\IUser;
|
||||
@@ -46,7 +46,7 @@ class IMipPluginCharsetTest extends TestCase {
|
||||
// Dependencies
|
||||
private Defaults&MockObject $defaults;
|
||||
private IAppConfig&MockObject $appConfig;
|
||||
private IConfig&MockObject $config;
|
||||
private IUserConfig&MockObject $userConfig;
|
||||
private IDBConnection&MockObject $db;
|
||||
private IFactory $l10nFactory;
|
||||
private IManager&MockObject $mailManager;
|
||||
@@ -77,7 +77,8 @@ class IMipPluginCharsetTest extends TestCase {
|
||||
|
||||
// IMipService
|
||||
$this->urlGenerator = $this->createMock(URLGenerator::class);
|
||||
$this->config = $this->createMock(IConfig::class);
|
||||
$this->userConfig = $this->createMock(IUserConfig::class);
|
||||
$this->appConfig = $this->createMock(IAppConfig::class);
|
||||
$this->db = $this->createMock(IDBConnection::class);
|
||||
$this->random = $this->createMock(ISecureRandom::class);
|
||||
$l10n = $this->createMock(L10N::class);
|
||||
@@ -92,19 +93,19 @@ class IMipPluginCharsetTest extends TestCase {
|
||||
$this->userManager->method('getByEmail')->willReturn([]);
|
||||
$this->imipService = new IMipService(
|
||||
$this->urlGenerator,
|
||||
$this->config,
|
||||
$this->db,
|
||||
$this->random,
|
||||
$this->l10nFactory,
|
||||
$this->timeFactory,
|
||||
$this->userManager
|
||||
$this->userManager,
|
||||
$this->userConfig,
|
||||
$this->appConfig,
|
||||
);
|
||||
|
||||
// EventComparisonService
|
||||
$this->eventComparisonService = new EventComparisonService();
|
||||
|
||||
// IMipPlugin
|
||||
$this->appConfig = $this->createMock(IAppConfig::class);
|
||||
$message = new \OC\Mail\Message(new Email(), false);
|
||||
$this->mailer = $this->createMock(IMailer::class);
|
||||
$this->mailer->method('createMessage')
|
||||
@@ -177,8 +178,13 @@ class IMipPluginCharsetTest extends TestCase {
|
||||
public function testCharsetMailProvider(): void {
|
||||
// Arrange
|
||||
$this->appConfig->method('getValueBool')
|
||||
->with('core', 'mail_providers_enabled', true)
|
||||
->willReturn(true);
|
||||
->willReturnCallback(function ($app, $key, $default) {
|
||||
if ($app === 'core') {
|
||||
$this->assertEquals($key, 'mail_providers_enabled');
|
||||
return true;
|
||||
}
|
||||
return $default;
|
||||
});
|
||||
$mailMessage = new MailProviderMessage();
|
||||
$mailService = $this->createMockForIntersectionOfInterfaces([IService::class, IMessageSend::class]);
|
||||
$mailService->method('initiateMessage')
|
||||
|
||||
@@ -13,7 +13,8 @@ use OC\URLGenerator;
|
||||
use OCA\DAV\CalDAV\EventReader;
|
||||
use OCA\DAV\CalDAV\Schedule\IMipService;
|
||||
use OCP\AppFramework\Utility\ITimeFactory;
|
||||
use OCP\IConfig;
|
||||
use OCP\Config\IUserConfig;
|
||||
use OCP\IAppConfig;
|
||||
use OCP\IDBConnection;
|
||||
use OCP\IL10N;
|
||||
use OCP\IUserManager;
|
||||
@@ -26,7 +27,8 @@ use Test\TestCase;
|
||||
|
||||
class IMipServiceTest extends TestCase {
|
||||
private URLGenerator&MockObject $urlGenerator;
|
||||
private IConfig&MockObject $config;
|
||||
private IUserConfig&MockObject $userConfig;
|
||||
private IAppConfig&MockObject $appConfig;
|
||||
private IDBConnection&MockObject $db;
|
||||
private ISecureRandom&MockObject $random;
|
||||
private IFactory&MockObject $l10nFactory;
|
||||
@@ -47,7 +49,8 @@ class IMipServiceTest extends TestCase {
|
||||
parent::setUp();
|
||||
|
||||
$this->urlGenerator = $this->createMock(URLGenerator::class);
|
||||
$this->config = $this->createMock(IConfig::class);
|
||||
$this->userConfig = $this->createMock(IUserConfig::class);
|
||||
$this->appConfig = $this->createMock(IAppConfig::class);
|
||||
$this->db = $this->createMock(IDBConnection::class);
|
||||
$this->random = $this->createMock(ISecureRandom::class);
|
||||
$this->l10nFactory = $this->createMock(IFactory::class);
|
||||
@@ -63,12 +66,13 @@ class IMipServiceTest extends TestCase {
|
||||
->willReturn($this->l10n);
|
||||
$this->service = new IMipService(
|
||||
$this->urlGenerator,
|
||||
$this->config,
|
||||
$this->db,
|
||||
$this->random,
|
||||
$this->l10nFactory,
|
||||
$this->timeFactory,
|
||||
$this->userManager
|
||||
$this->userManager,
|
||||
$this->userConfig,
|
||||
$this->appConfig,
|
||||
);
|
||||
|
||||
// construct calendar with a 1 hour event and same start/end time zones
|
||||
|
||||
@@ -9,12 +9,14 @@ declare(strict_types=1);
|
||||
namespace OCA\DAV\Tests\unit\CalDAV;
|
||||
|
||||
use DateTimeZone;
|
||||
use OCA\DAV\CalDAV\CalDavBackend;
|
||||
use OCA\DAV\CalDAV\CalendarImpl;
|
||||
use OCA\DAV\CalDAV\TimezoneService;
|
||||
use OCA\DAV\Db\Property;
|
||||
use OCA\DAV\Db\PropertyMapper;
|
||||
use OCP\Calendar\ICalendar;
|
||||
use OCP\Calendar\IManager;
|
||||
use OCP\Config\IUserConfig;
|
||||
use OCP\IConfig;
|
||||
use PHPUnit\Framework\MockObject\MockObject;
|
||||
use Sabre\VObject\Component\VTimeZone;
|
||||
@@ -22,6 +24,7 @@ use Test\TestCase;
|
||||
|
||||
class TimezoneServiceTest extends TestCase {
|
||||
private IConfig&MockObject $config;
|
||||
private IUserConfig&MockObject $userConfig;
|
||||
private PropertyMapper&MockObject $propertyMapper;
|
||||
private IManager&MockObject $calendarManager;
|
||||
private TimezoneService $service;
|
||||
@@ -30,19 +33,21 @@ class TimezoneServiceTest extends TestCase {
|
||||
parent::setUp();
|
||||
|
||||
$this->config = $this->createMock(IConfig::class);
|
||||
$this->userConfig = $this->createMock(IUserConfig::class);
|
||||
$this->propertyMapper = $this->createMock(PropertyMapper::class);
|
||||
$this->calendarManager = $this->createMock(IManager::class);
|
||||
|
||||
$this->service = new TimezoneService(
|
||||
$this->config,
|
||||
$this->userConfig,
|
||||
$this->propertyMapper,
|
||||
$this->calendarManager,
|
||||
);
|
||||
}
|
||||
|
||||
public function testGetUserTimezoneFromSettings(): void {
|
||||
$this->config->expects(self::once())
|
||||
->method('getUserValue')
|
||||
$this->userConfig->expects(self::once())
|
||||
->method('getValueString')
|
||||
->with('test123', 'core', 'timezone', '')
|
||||
->willReturn('Europe/Warsaw');
|
||||
|
||||
@@ -52,8 +57,8 @@ class TimezoneServiceTest extends TestCase {
|
||||
}
|
||||
|
||||
public function testGetUserTimezoneFromAvailability(): void {
|
||||
$this->config->expects(self::once())
|
||||
->method('getUserValue')
|
||||
$this->userConfig->expects(self::once())
|
||||
->method('getValueString')
|
||||
->with('test123', 'core', 'timezone', '')
|
||||
->willReturn('');
|
||||
$property = new Property();
|
||||
@@ -76,11 +81,11 @@ END:VCALENDAR');
|
||||
}
|
||||
|
||||
public function testGetUserTimezoneFromPersonalCalendar(): void {
|
||||
$this->config->expects(self::exactly(2))
|
||||
->method('getUserValue')
|
||||
$this->userConfig->expects(self::exactly(2))
|
||||
->method('getValueString')
|
||||
->willReturnMap([
|
||||
['test123', 'core', 'timezone', '', ''],
|
||||
['test123', 'dav', 'defaultCalendar', '', 'personal-1'],
|
||||
['test123', 'core', 'timezone', '', false, ''],
|
||||
['test123', 'dav', 'defaultCalendar', CalDavBackend::PERSONAL_CALENDAR_URI, false, 'personal-1'],
|
||||
]);
|
||||
$other = $this->createMock(ICalendar::class);
|
||||
$other->method('getUri')->willReturn('other');
|
||||
@@ -105,11 +110,11 @@ END:VCALENDAR');
|
||||
}
|
||||
|
||||
public function testGetUserTimezoneFromAny(): void {
|
||||
$this->config->expects(self::exactly(2))
|
||||
->method('getUserValue')
|
||||
$this->userConfig->expects(self::exactly(2))
|
||||
->method('getValueString')
|
||||
->willReturnMap([
|
||||
['test123', 'core', 'timezone', '', ''],
|
||||
['test123', 'dav', 'defaultCalendar', '', 'personal-1'],
|
||||
['test123', 'core', 'timezone', '', false, ''],
|
||||
['test123', 'dav', 'defaultCalendar', CalDavBackend::PERSONAL_CALENDAR_URI, false, 'personal-1'],
|
||||
]);
|
||||
$other = $this->createMock(ICalendar::class);
|
||||
$other->method('getUri')->willReturn('other');
|
||||
|
||||
@@ -89,12 +89,8 @@ class ConnectionTest extends TestCase {
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $result
|
||||
* @param string $contentType
|
||||
*/
|
||||
#[\PHPUnit\Framework\Attributes\DataProvider('urlDataProvider')]
|
||||
public function testConnection(string $url, string $result, string $contentType): void {
|
||||
public function testConnection(string $url, string $contentType, string $expectedFormat): void {
|
||||
$client = $this->createMock(IClient::class);
|
||||
$response = $this->createMock(IResponse::class);
|
||||
$subscription = [
|
||||
@@ -123,16 +119,76 @@ class ConnectionTest extends TestCase {
|
||||
->with('https://foo.bar/bla2')
|
||||
->willReturn($response);
|
||||
|
||||
$response->expects($this->once())
|
||||
->method('getBody')
|
||||
->with()
|
||||
->willReturn($result);
|
||||
$response->expects($this->once())
|
||||
->method('getHeader')
|
||||
->with('Content-Type')
|
||||
->willReturn($contentType);
|
||||
|
||||
$this->connection->queryWebcalFeed($subscription);
|
||||
// Create a stream resource to simulate streaming response
|
||||
$stream = fopen('php://temp', 'r+');
|
||||
fwrite($stream, 'test calendar data');
|
||||
rewind($stream);
|
||||
|
||||
$response->expects($this->once())
|
||||
->method('getBody')
|
||||
->willReturn($stream);
|
||||
|
||||
$output = $this->connection->queryWebcalFeed($subscription);
|
||||
|
||||
$this->assertIsArray($output);
|
||||
$this->assertArrayHasKey('data', $output);
|
||||
$this->assertArrayHasKey('format', $output);
|
||||
$this->assertIsResource($output['data']);
|
||||
$this->assertEquals($expectedFormat, $output['format']);
|
||||
|
||||
// Cleanup
|
||||
if (is_resource($output['data'])) {
|
||||
fclose($output['data']);
|
||||
}
|
||||
}
|
||||
|
||||
public function testConnectionReturnsNullWhenBodyIsNotResource(): void {
|
||||
$client = $this->createMock(IClient::class);
|
||||
$response = $this->createMock(IResponse::class);
|
||||
$subscription = [
|
||||
'id' => 42,
|
||||
'uri' => 'sub123',
|
||||
'refreshreate' => 'P1H',
|
||||
'striptodos' => 1,
|
||||
'stripalarms' => 1,
|
||||
'stripattachments' => 1,
|
||||
'source' => 'https://foo.bar/bla2',
|
||||
'lastmodified' => 0,
|
||||
];
|
||||
|
||||
$this->clientService->expects($this->once())
|
||||
->method('newClient')
|
||||
->with()
|
||||
->willReturn($client);
|
||||
|
||||
$this->config->expects($this->once())
|
||||
->method('getValueString')
|
||||
->with('dav', 'webcalAllowLocalAccess', 'no')
|
||||
->willReturn('no');
|
||||
|
||||
$client->expects($this->once())
|
||||
->method('get')
|
||||
->with('https://foo.bar/bla2')
|
||||
->willReturn($response);
|
||||
|
||||
$response->expects($this->once())
|
||||
->method('getHeader')
|
||||
->with('Content-Type')
|
||||
->willReturn('text/calendar');
|
||||
|
||||
// Return a string instead of a resource
|
||||
$response->expects($this->once())
|
||||
->method('getBody')
|
||||
->willReturn('not a resource');
|
||||
|
||||
$output = $this->connection->queryWebcalFeed($subscription);
|
||||
|
||||
$this->assertNull($output);
|
||||
}
|
||||
|
||||
public static function runLocalURLDataProvider(): array {
|
||||
@@ -156,21 +212,9 @@ class ConnectionTest extends TestCase {
|
||||
|
||||
public static function urlDataProvider(): array {
|
||||
return [
|
||||
[
|
||||
'https://foo.bar/bla2',
|
||||
"BEGIN:VCALENDAR\r\nVERSION:2.0\r\nPRODID:-//Sabre//Sabre VObject 4.1.1//EN\r\nCALSCALE:GREGORIAN\r\nBEGIN:VEVENT\r\nUID:12345\r\nDTSTAMP:20160218T133704Z\r\nDTSTART;VALUE=DATE:19000101\r\nDTEND;VALUE=DATE:19000102\r\nRRULE:FREQ=YEARLY\r\nSUMMARY:12345's Birthday (1900)\r\nTRANSP:TRANSPARENT\r\nEND:VEVENT\r\nEND:VCALENDAR\r\n",
|
||||
'text/calendar;charset=utf8',
|
||||
],
|
||||
[
|
||||
'https://foo.bar/bla2',
|
||||
'["vcalendar",[["prodid",{},"text","-//Example Corp.//Example Client//EN"],["version",{},"text","2.0"]],[["vtimezone",[["last-modified",{},"date-time","2004-01-10T03:28:45Z"],["tzid",{},"text","US/Eastern"]],[["daylight",[["dtstart",{},"date-time","2000-04-04T02:00:00"],["rrule",{},"recur",{"freq":"YEARLY","byday":"1SU","bymonth":4}],["tzname",{},"text","EDT"],["tzoffsetfrom",{},"utc-offset","-05:00"],["tzoffsetto",{},"utc-offset","-04:00"]],[]],["standard",[["dtstart",{},"date-time","2000-10-26T02:00:00"],["rrule",{},"recur",{"freq":"YEARLY","byday":"1SU","bymonth":10}],["tzname",{},"text","EST"],["tzoffsetfrom",{},"utc-offset","-04:00"],["tzoffsetto",{},"utc-offset","-05:00"]],[]]]],["vevent",[["dtstamp",{},"date-time","2006-02-06T00:11:21Z"],["dtstart",{"tzid":"US/Eastern"},"date-time","2006-01-02T14:00:00"],["duration",{},"duration","PT1H"],["recurrence-id",{"tzid":"US/Eastern"},"date-time","2006-01-04T12:00:00"],["summary",{},"text","Event #2"],["uid",{},"text","12345"]],[]]]]',
|
||||
'application/calendar+json',
|
||||
],
|
||||
[
|
||||
'https://foo.bar/bla2',
|
||||
'<?xml version="1.0" encoding="utf-8" ?><icalendar xmlns="urn:ietf:params:xml:ns:icalendar-2.0"><vcalendar><properties><prodid><text>-//Example Inc.//Example Client//EN</text></prodid><version><text>2.0</text></version></properties><components><vevent><properties><dtstamp><date-time>2006-02-06T00:11:21Z</date-time></dtstamp><dtstart><parameters><tzid><text>US/Eastern</text></tzid></parameters><date-time>2006-01-04T14:00:00</date-time></dtstart><duration><duration>PT1H</duration></duration><recurrence-id><parameters><tzid><text>US/Eastern</text></tzid></parameters><date-time>2006-01-04T12:00:00</date-time></recurrence-id><summary><text>Event #2 bis</text></summary><uid><text>12345</text></uid></properties></vevent></components></vcalendar></icalendar>',
|
||||
'application/calendar+xml',
|
||||
],
|
||||
['https://foo.bar/bla2', 'text/calendar;charset=utf8', 'ical'],
|
||||
['https://foo.bar/bla2', 'application/calendar+json', 'jcal'],
|
||||
['https://foo.bar/bla2', 'application/calendar+xml', 'xcal'],
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
@@ -8,6 +8,7 @@ declare(strict_types=1);
|
||||
namespace OCA\DAV\Tests\unit\CalDAV\WebcalCaching;
|
||||
|
||||
use OCA\DAV\CalDAV\CalDavBackend;
|
||||
use OCA\DAV\CalDAV\Import\ImportService;
|
||||
use OCA\DAV\CalDAV\WebcalCaching\Connection;
|
||||
use OCA\DAV\CalDAV\WebcalCaching\RefreshWebcalService;
|
||||
use OCP\AppFramework\Utility\ITimeFactory;
|
||||
@@ -23,7 +24,8 @@ class RefreshWebcalServiceTest extends TestCase {
|
||||
private CalDavBackend&MockObject $caldavBackend;
|
||||
private Connection&MockObject $connection;
|
||||
private LoggerInterface&MockObject $logger;
|
||||
private ITimeFactory&MockObject $time;
|
||||
private ImportService&MockObject $importService;
|
||||
private ITimeFactory&MockObject $timeFactory;
|
||||
|
||||
protected function setUp(): void {
|
||||
parent::setUp();
|
||||
@@ -31,19 +33,32 @@ class RefreshWebcalServiceTest extends TestCase {
|
||||
$this->caldavBackend = $this->createMock(CalDavBackend::class);
|
||||
$this->connection = $this->createMock(Connection::class);
|
||||
$this->logger = $this->createMock(LoggerInterface::class);
|
||||
$this->time = $this->createMock(ITimeFactory::class);
|
||||
$this->importService = $this->createMock(ImportService::class);
|
||||
$this->timeFactory = $this->createMock(ITimeFactory::class);
|
||||
// Default time factory behavior: current time is far in the future so refresh always happens
|
||||
$this->timeFactory->method('getTime')->willReturn(PHP_INT_MAX);
|
||||
$this->timeFactory->method('getDateTime')->willReturn(new \DateTime());
|
||||
}
|
||||
|
||||
/**
|
||||
* Helper to create a resource stream from string content
|
||||
*/
|
||||
private function createStreamFromString(string $content) {
|
||||
$stream = fopen('php://temp', 'r+');
|
||||
fwrite($stream, $content);
|
||||
rewind($stream);
|
||||
return $stream;
|
||||
}
|
||||
|
||||
#[\PHPUnit\Framework\Attributes\DataProvider('runDataProvider')]
|
||||
public function testRun(string $body, string $contentType, string $result): void {
|
||||
$refreshWebcalService = $this->getMockBuilder(RefreshWebcalService::class)
|
||||
->onlyMethods(['getRandomCalendarObjectUri'])
|
||||
->setConstructorArgs([$this->caldavBackend, $this->logger, $this->connection, $this->time])
|
||||
->getMock();
|
||||
|
||||
$refreshWebcalService
|
||||
->method('getRandomCalendarObjectUri')
|
||||
->willReturn('uri-1.ics');
|
||||
public function testRun(string $body, string $format, string $result): void {
|
||||
$refreshWebcalService = new RefreshWebcalService(
|
||||
$this->caldavBackend,
|
||||
$this->logger,
|
||||
$this->connection,
|
||||
$this->timeFactory,
|
||||
$this->importService
|
||||
);
|
||||
|
||||
$this->caldavBackend->expects(self::once())
|
||||
->method('getSubscriptionsForUser')
|
||||
@@ -71,26 +86,48 @@ class RefreshWebcalServiceTest extends TestCase {
|
||||
],
|
||||
]);
|
||||
|
||||
$stream = $this->createStreamFromString($body);
|
||||
|
||||
$this->connection->expects(self::once())
|
||||
->method('queryWebcalFeed')
|
||||
->willReturn($result);
|
||||
->willReturn(['data' => $stream, 'format' => $format]);
|
||||
|
||||
$this->caldavBackend->expects(self::once())
|
||||
->method('getLimitedCalendarObjects')
|
||||
->willReturn([]);
|
||||
|
||||
// Create a VCalendar object that will be yielded by the import service
|
||||
$vCalendar = VObject\Reader::read($result);
|
||||
|
||||
$generator = function () use ($vCalendar) {
|
||||
yield $vCalendar;
|
||||
};
|
||||
|
||||
$this->importService->expects(self::once())
|
||||
->method('importText')
|
||||
->willReturn($generator());
|
||||
|
||||
$this->caldavBackend->expects(self::once())
|
||||
->method('createCalendarObject')
|
||||
->with(42, 'uri-1.ics', $result, 1);
|
||||
->with(
|
||||
'42',
|
||||
self::matchesRegularExpression('/^[a-f0-9-]+\.ics$/'),
|
||||
$result,
|
||||
CalDavBackend::CALENDAR_TYPE_SUBSCRIPTION
|
||||
);
|
||||
|
||||
$refreshWebcalService->refreshSubscription('principals/users/testuser', 'sub123');
|
||||
}
|
||||
|
||||
#[\PHPUnit\Framework\Attributes\DataProvider('identicalDataProvider')]
|
||||
public function testRunIdentical(string $uid, array $calendarObject, string $body, string $contentType, string $result): void {
|
||||
$refreshWebcalService = $this->getMockBuilder(RefreshWebcalService::class)
|
||||
->onlyMethods(['getRandomCalendarObjectUri'])
|
||||
->setConstructorArgs([$this->caldavBackend, $this->logger, $this->connection, $this->time])
|
||||
->getMock();
|
||||
|
||||
$refreshWebcalService
|
||||
->method('getRandomCalendarObjectUri')
|
||||
->willReturn('uri-1.ics');
|
||||
public function testRunIdentical(string $uid, array $calendarObject, string $body, string $format, string $result): void {
|
||||
$refreshWebcalService = new RefreshWebcalService(
|
||||
$this->caldavBackend,
|
||||
$this->logger,
|
||||
$this->connection,
|
||||
$this->timeFactory,
|
||||
$this->importService
|
||||
);
|
||||
|
||||
$this->caldavBackend->expects(self::once())
|
||||
->method('getSubscriptionsForUser')
|
||||
@@ -118,78 +155,199 @@ class RefreshWebcalServiceTest extends TestCase {
|
||||
],
|
||||
]);
|
||||
|
||||
$stream = $this->createStreamFromString($body);
|
||||
|
||||
$this->connection->expects(self::once())
|
||||
->method('queryWebcalFeed')
|
||||
->willReturn(['data' => $stream, 'format' => $format]);
|
||||
|
||||
$this->caldavBackend->expects(self::once())
|
||||
->method('getLimitedCalendarObjects')
|
||||
->willReturn($calendarObject);
|
||||
|
||||
// Create a VCalendar object that will be yielded by the import service
|
||||
$vCalendar = VObject\Reader::read($result);
|
||||
|
||||
$generator = function () use ($vCalendar) {
|
||||
yield $vCalendar;
|
||||
};
|
||||
|
||||
$this->importService->expects(self::once())
|
||||
->method('importText')
|
||||
->willReturn($generator());
|
||||
|
||||
$this->caldavBackend->expects(self::never())
|
||||
->method('createCalendarObject');
|
||||
|
||||
$refreshWebcalService->refreshSubscription('principals/users/testuser', 'sub123');
|
||||
}
|
||||
|
||||
public function testSubscriptionNotFound(): void {
|
||||
$refreshWebcalService = new RefreshWebcalService(
|
||||
$this->caldavBackend,
|
||||
$this->logger,
|
||||
$this->connection,
|
||||
$this->timeFactory,
|
||||
$this->importService
|
||||
);
|
||||
|
||||
$this->caldavBackend->expects(self::once())
|
||||
->method('getSubscriptionsForUser')
|
||||
->with('principals/users/testuser')
|
||||
->willReturn([]);
|
||||
|
||||
$this->connection->expects(self::never())
|
||||
->method('queryWebcalFeed');
|
||||
|
||||
$refreshWebcalService->refreshSubscription('principals/users/testuser', 'sub123');
|
||||
}
|
||||
|
||||
public function testConnectionReturnsNull(): void {
|
||||
$refreshWebcalService = new RefreshWebcalService(
|
||||
$this->caldavBackend,
|
||||
$this->logger,
|
||||
$this->connection,
|
||||
$this->timeFactory,
|
||||
$this->importService
|
||||
);
|
||||
|
||||
$this->caldavBackend->expects(self::once())
|
||||
->method('getSubscriptionsForUser')
|
||||
->with('principals/users/testuser')
|
||||
->willReturn([
|
||||
[
|
||||
'id' => '42',
|
||||
'uri' => 'sub123',
|
||||
RefreshWebcalService::STRIP_TODOS => '1',
|
||||
RefreshWebcalService::STRIP_ALARMS => '1',
|
||||
RefreshWebcalService::STRIP_ATTACHMENTS => '1',
|
||||
'source' => 'webcal://foo.bar/bla2',
|
||||
'lastmodified' => 0,
|
||||
],
|
||||
]);
|
||||
|
||||
$this->connection->expects(self::once())
|
||||
->method('queryWebcalFeed')
|
||||
->willReturn($result);
|
||||
->willReturn(null);
|
||||
|
||||
$this->caldavBackend->expects(self::once())
|
||||
->method('getLimitedCalendarObjects')
|
||||
->willReturn($calendarObject);
|
||||
|
||||
$denormalised = [
|
||||
'etag' => 100,
|
||||
'size' => strlen($calendarObject[$uid]['calendardata']),
|
||||
'uid' => 'sub456'
|
||||
];
|
||||
|
||||
$this->caldavBackend->expects(self::once())
|
||||
->method('getDenormalizedData')
|
||||
->willReturn($denormalised);
|
||||
$this->importService->expects(self::never())
|
||||
->method('importText');
|
||||
|
||||
$this->caldavBackend->expects(self::never())
|
||||
->method('createCalendarObject');
|
||||
|
||||
$refreshWebcalService->refreshSubscription('principals/users/testuser', 'sub456');
|
||||
$refreshWebcalService->refreshSubscription('principals/users/testuser', 'sub123');
|
||||
}
|
||||
|
||||
public function testRunJustUpdated(): void {
|
||||
$refreshWebcalService = $this->getMockBuilder(RefreshWebcalService::class)
|
||||
->onlyMethods(['getRandomCalendarObjectUri'])
|
||||
->setConstructorArgs([$this->caldavBackend, $this->logger, $this->connection, $this->time])
|
||||
->getMock();
|
||||
|
||||
$refreshWebcalService
|
||||
->method('getRandomCalendarObjectUri')
|
||||
->willReturn('uri-1.ics');
|
||||
public function testDeletedObjectsArePurged(): void {
|
||||
$refreshWebcalService = new RefreshWebcalService(
|
||||
$this->caldavBackend,
|
||||
$this->logger,
|
||||
$this->connection,
|
||||
$this->timeFactory,
|
||||
$this->importService
|
||||
);
|
||||
|
||||
$this->caldavBackend->expects(self::once())
|
||||
->method('getSubscriptionsForUser')
|
||||
->with('principals/users/testuser')
|
||||
->willReturn([
|
||||
[
|
||||
'id' => '99',
|
||||
'uri' => 'sub456',
|
||||
RefreshWebcalService::REFRESH_RATE => 'P1D',
|
||||
RefreshWebcalService::STRIP_TODOS => '1',
|
||||
RefreshWebcalService::STRIP_ALARMS => '1',
|
||||
RefreshWebcalService::STRIP_ATTACHMENTS => '1',
|
||||
'source' => 'webcal://foo.bar/bla',
|
||||
'lastmodified' => time(),
|
||||
],
|
||||
[
|
||||
'id' => '42',
|
||||
'uri' => 'sub123',
|
||||
RefreshWebcalService::REFRESH_RATE => 'PT1H',
|
||||
RefreshWebcalService::STRIP_TODOS => '1',
|
||||
RefreshWebcalService::STRIP_ALARMS => '1',
|
||||
RefreshWebcalService::STRIP_ATTACHMENTS => '1',
|
||||
'source' => 'webcal://foo.bar/bla2',
|
||||
'lastmodified' => time(),
|
||||
'lastmodified' => 0,
|
||||
],
|
||||
]);
|
||||
|
||||
$timeMock = $this->createMock(\DateTime::class);
|
||||
$this->time->expects(self::once())
|
||||
->method('getDateTime')
|
||||
->willReturn($timeMock);
|
||||
$timeMock->expects(self::once())
|
||||
->method('getTimestamp')
|
||||
->willReturn(2101724667);
|
||||
$this->time->expects(self::once())
|
||||
->method('getTime')
|
||||
->willReturn(time());
|
||||
$this->connection->expects(self::never())
|
||||
->method('queryWebcalFeed');
|
||||
$body = "BEGIN:VCALENDAR\r\nVERSION:2.0\r\nPRODID:-//Test//Test//EN\r\nBEGIN:VEVENT\r\nUID:new-event\r\nDTSTAMP:20160218T133704Z\r\nDTSTART:20160218T133704Z\r\nSUMMARY:New Event\r\nEND:VEVENT\r\nEND:VCALENDAR\r\n";
|
||||
$stream = $this->createStreamFromString($body);
|
||||
|
||||
$this->connection->expects(self::once())
|
||||
->method('queryWebcalFeed')
|
||||
->willReturn(['data' => $stream, 'format' => 'ical']);
|
||||
|
||||
// Existing objects include one that won't be in the feed
|
||||
$this->caldavBackend->expects(self::once())
|
||||
->method('getLimitedCalendarObjects')
|
||||
->willReturn([
|
||||
'old-deleted-event' => [
|
||||
'id' => 99,
|
||||
'uid' => 'old-deleted-event',
|
||||
'etag' => 'old-etag',
|
||||
'uri' => 'old-event.ics',
|
||||
],
|
||||
]);
|
||||
|
||||
$vCalendar = VObject\Reader::read($body);
|
||||
$generator = function () use ($vCalendar) {
|
||||
yield $vCalendar;
|
||||
};
|
||||
|
||||
$this->importService->expects(self::once())
|
||||
->method('importText')
|
||||
->willReturn($generator());
|
||||
|
||||
$this->caldavBackend->expects(self::once())
|
||||
->method('createCalendarObject');
|
||||
|
||||
$this->caldavBackend->expects(self::once())
|
||||
->method('purgeCachedEventsForSubscription')
|
||||
->with(42, [99], ['old-event.ics']);
|
||||
|
||||
$refreshWebcalService->refreshSubscription('principals/users/testuser', 'sub123');
|
||||
}
|
||||
|
||||
public function testLongUidIsSkipped(): void {
|
||||
$refreshWebcalService = new RefreshWebcalService(
|
||||
$this->caldavBackend,
|
||||
$this->logger,
|
||||
$this->connection,
|
||||
$this->timeFactory,
|
||||
$this->importService
|
||||
);
|
||||
|
||||
$this->caldavBackend->expects(self::once())
|
||||
->method('getSubscriptionsForUser')
|
||||
->with('principals/users/testuser')
|
||||
->willReturn([
|
||||
[
|
||||
'id' => '42',
|
||||
'uri' => 'sub123',
|
||||
RefreshWebcalService::STRIP_TODOS => '1',
|
||||
RefreshWebcalService::STRIP_ALARMS => '1',
|
||||
RefreshWebcalService::STRIP_ATTACHMENTS => '1',
|
||||
'source' => 'webcal://foo.bar/bla2',
|
||||
'lastmodified' => 0,
|
||||
],
|
||||
]);
|
||||
|
||||
// Create a UID that is longer than 512 characters
|
||||
$longUid = str_repeat('a', 513);
|
||||
$body = "BEGIN:VCALENDAR\r\nVERSION:2.0\r\nPRODID:-//Test//Test//EN\r\nBEGIN:VEVENT\r\nUID:$longUid\r\nDTSTAMP:20160218T133704Z\r\nDTSTART:20160218T133704Z\r\nSUMMARY:Event with long UID\r\nEND:VEVENT\r\nEND:VCALENDAR\r\n";
|
||||
$stream = $this->createStreamFromString($body);
|
||||
|
||||
$this->connection->expects(self::once())
|
||||
->method('queryWebcalFeed')
|
||||
->willReturn(['data' => $stream, 'format' => 'ical']);
|
||||
|
||||
$this->caldavBackend->expects(self::once())
|
||||
->method('getLimitedCalendarObjects')
|
||||
->willReturn([]);
|
||||
|
||||
$vCalendar = VObject\Reader::read($body);
|
||||
$generator = function () use ($vCalendar) {
|
||||
yield $vCalendar;
|
||||
};
|
||||
|
||||
$this->importService->expects(self::once())
|
||||
->method('importText')
|
||||
->willReturn($generator());
|
||||
|
||||
// Event with long UID should be skipped, so createCalendarObject should never be called
|
||||
$this->caldavBackend->expects(self::never())
|
||||
->method('createCalendarObject');
|
||||
|
||||
@@ -197,16 +355,12 @@ class RefreshWebcalServiceTest extends TestCase {
|
||||
}
|
||||
|
||||
#[\PHPUnit\Framework\Attributes\DataProvider('runDataProvider')]
|
||||
public function testRunCreateCalendarNoException(string $body, string $contentType, string $result): void {
|
||||
public function testRunCreateCalendarNoException(string $body, string $format, string $result): void {
|
||||
$refreshWebcalService = $this->getMockBuilder(RefreshWebcalService::class)
|
||||
->onlyMethods(['getRandomCalendarObjectUri', 'getSubscription',])
|
||||
->setConstructorArgs([$this->caldavBackend, $this->logger, $this->connection, $this->time])
|
||||
->onlyMethods(['getSubscription'])
|
||||
->setConstructorArgs([$this->caldavBackend, $this->logger, $this->connection, $this->timeFactory, $this->importService])
|
||||
->getMock();
|
||||
|
||||
$refreshWebcalService
|
||||
->method('getRandomCalendarObjectUri')
|
||||
->willReturn('uri-1.ics');
|
||||
|
||||
$refreshWebcalService
|
||||
->method('getSubscription')
|
||||
->willReturn([
|
||||
@@ -220,13 +374,26 @@ class RefreshWebcalServiceTest extends TestCase {
|
||||
'lastmodified' => 0,
|
||||
]);
|
||||
|
||||
$stream = $this->createStreamFromString($body);
|
||||
|
||||
$this->connection->expects(self::once())
|
||||
->method('queryWebcalFeed')
|
||||
->willReturn($result);
|
||||
->willReturn(['data' => $stream, 'format' => $format]);
|
||||
|
||||
$this->caldavBackend->expects(self::once())
|
||||
->method('createCalendarObject')
|
||||
->with(42, 'uri-1.ics', $result, 1);
|
||||
->method('getLimitedCalendarObjects')
|
||||
->willReturn([]);
|
||||
|
||||
// Create a VCalendar object that will be yielded by the import service
|
||||
$vCalendar = VObject\Reader::read($result);
|
||||
|
||||
$generator = function () use ($vCalendar) {
|
||||
yield $vCalendar;
|
||||
};
|
||||
|
||||
$this->importService->expects(self::once())
|
||||
->method('importText')
|
||||
->willReturn($generator());
|
||||
|
||||
$noInstanceException = new NoInstancesException("can't add calendar object");
|
||||
$this->caldavBackend->expects(self::once())
|
||||
@@ -241,16 +408,12 @@ class RefreshWebcalServiceTest extends TestCase {
|
||||
}
|
||||
|
||||
#[\PHPUnit\Framework\Attributes\DataProvider('runDataProvider')]
|
||||
public function testRunCreateCalendarBadRequest(string $body, string $contentType, string $result): void {
|
||||
public function testRunCreateCalendarBadRequest(string $body, string $format, string $result): void {
|
||||
$refreshWebcalService = $this->getMockBuilder(RefreshWebcalService::class)
|
||||
->onlyMethods(['getRandomCalendarObjectUri', 'getSubscription'])
|
||||
->setConstructorArgs([$this->caldavBackend, $this->logger, $this->connection, $this->time])
|
||||
->onlyMethods(['getSubscription'])
|
||||
->setConstructorArgs([$this->caldavBackend, $this->logger, $this->connection, $this->timeFactory, $this->importService])
|
||||
->getMock();
|
||||
|
||||
$refreshWebcalService
|
||||
->method('getRandomCalendarObjectUri')
|
||||
->willReturn('uri-1.ics');
|
||||
|
||||
$refreshWebcalService
|
||||
->method('getSubscription')
|
||||
->willReturn([
|
||||
@@ -264,13 +427,26 @@ class RefreshWebcalServiceTest extends TestCase {
|
||||
'lastmodified' => 0,
|
||||
]);
|
||||
|
||||
$stream = $this->createStreamFromString($body);
|
||||
|
||||
$this->connection->expects(self::once())
|
||||
->method('queryWebcalFeed')
|
||||
->willReturn($result);
|
||||
->willReturn(['data' => $stream, 'format' => $format]);
|
||||
|
||||
$this->caldavBackend->expects(self::once())
|
||||
->method('createCalendarObject')
|
||||
->with(42, 'uri-1.ics', $result, 1);
|
||||
->method('getLimitedCalendarObjects')
|
||||
->willReturn([]);
|
||||
|
||||
// Create a VCalendar object that will be yielded by the import service
|
||||
$vCalendar = VObject\Reader::read($result);
|
||||
|
||||
$generator = function () use ($vCalendar) {
|
||||
yield $vCalendar;
|
||||
};
|
||||
|
||||
$this->importService->expects(self::once())
|
||||
->method('importText')
|
||||
->willReturn($generator());
|
||||
|
||||
$badRequestException = new BadRequest("can't add reach calendar url");
|
||||
$this->caldavBackend->expects(self::once())
|
||||
@@ -285,20 +461,22 @@ class RefreshWebcalServiceTest extends TestCase {
|
||||
}
|
||||
|
||||
public static function identicalDataProvider(): array {
|
||||
$icalBody = "BEGIN:VCALENDAR\r\nVERSION:2.0\r\nPRODID:-//Sabre//Sabre VObject " . VObject\Version::VERSION . "//EN\r\nCALSCALE:GREGORIAN\r\nBEGIN:VEVENT\r\nUID:12345\r\nDTSTAMP:20160218T133704Z\r\nDTSTART;VALUE=DATE:19000101\r\nDTEND;VALUE=DATE:19000102\r\nRRULE:FREQ=YEARLY\r\nSUMMARY:12345's Birthday (1900)\r\nTRANSP:TRANSPARENT\r\nEND:VEVENT\r\nEND:VCALENDAR\r\n";
|
||||
$etag = md5($icalBody);
|
||||
|
||||
return [
|
||||
[
|
||||
'12345',
|
||||
[
|
||||
'12345' => [
|
||||
'id' => 42,
|
||||
'etag' => 100,
|
||||
'uri' => 'sub456',
|
||||
'calendardata' => "BEGIN:VCALENDAR\r\nVERSION:2.0\r\nPRODID:-//Sabre//Sabre VObject 4.1.1//EN\r\nCALSCALE:GREGORIAN\r\nBEGIN:VEVENT\r\nUID:12345\r\nDTSTAMP:20160218T133704Z\r\nDTSTART;VALUE=DATE:19000101\r\nDTEND;VALUE=DATE:19000102\r\nRRULE:FREQ=YEARLY\r\nSUMMARY:12345's Birthday (1900)\r\nTRANSP:TRANSPARENT\r\nEND:VEVENT\r\nEND:VCALENDAR\r\n",
|
||||
'etag' => $etag,
|
||||
'uri' => 'sub456.ics',
|
||||
],
|
||||
],
|
||||
"BEGIN:VCALENDAR\r\nVERSION:2.0\r\nPRODID:-//Sabre//Sabre VObject 4.1.1//EN\r\nCALSCALE:GREGORIAN\r\nBEGIN:VEVENT\r\nUID:12345\r\nDTSTAMP:20160218T133704Z\r\nDTSTART;VALUE=DATE:19000101\r\nDTEND;VALUE=DATE:19000102\r\nRRULE:FREQ=YEARLY\r\nSUMMARY:12345's Birthday (1900)\r\nTRANSP:TRANSPARENT\r\nEND:VEVENT\r\nEND:VCALENDAR\r\n",
|
||||
'text/calendar;charset=utf8',
|
||||
"BEGIN:VCALENDAR\r\nVERSION:2.0\r\nPRODID:-//Sabre//Sabre VObject 4.1.1//EN\r\nCALSCALE:GREGORIAN\r\nBEGIN:VEVENT\r\nUID:12345\r\nDTSTAMP:20180218T133704Z\r\nDTSTART;VALUE=DATE:19000101\r\nDTEND;VALUE=DATE:19000102\r\nRRULE:FREQ=YEARLY\r\nSUMMARY:12345's Birthday (1900)\r\nTRANSP:TRANSPARENT\r\nEND:VEVENT\r\nEND:VCALENDAR\r\n",
|
||||
'ical',
|
||||
$icalBody,
|
||||
],
|
||||
];
|
||||
}
|
||||
@@ -307,19 +485,9 @@ class RefreshWebcalServiceTest extends TestCase {
|
||||
return [
|
||||
[
|
||||
"BEGIN:VCALENDAR\r\nVERSION:2.0\r\nPRODID:-//Sabre//Sabre VObject 4.1.1//EN\r\nCALSCALE:GREGORIAN\r\nBEGIN:VEVENT\r\nUID:12345\r\nDTSTAMP:20160218T133704Z\r\nDTSTART;VALUE=DATE:19000101\r\nDTEND;VALUE=DATE:19000102\r\nRRULE:FREQ=YEARLY\r\nSUMMARY:12345's Birthday (1900)\r\nTRANSP:TRANSPARENT\r\nEND:VEVENT\r\nEND:VCALENDAR\r\n",
|
||||
'text/calendar;charset=utf8',
|
||||
'ical',
|
||||
"BEGIN:VCALENDAR\r\nVERSION:2.0\r\nPRODID:-//Sabre//Sabre VObject " . VObject\Version::VERSION . "//EN\r\nCALSCALE:GREGORIAN\r\nBEGIN:VEVENT\r\nUID:12345\r\nDTSTAMP:20160218T133704Z\r\nDTSTART;VALUE=DATE:19000101\r\nDTEND;VALUE=DATE:19000102\r\nRRULE:FREQ=YEARLY\r\nSUMMARY:12345's Birthday (1900)\r\nTRANSP:TRANSPARENT\r\nEND:VEVENT\r\nEND:VCALENDAR\r\n",
|
||||
],
|
||||
[
|
||||
'["vcalendar",[["prodid",{},"text","-//Example Corp.//Example Client//EN"],["version",{},"text","2.0"]],[["vtimezone",[["last-modified",{},"date-time","2004-01-10T03:28:45Z"],["tzid",{},"text","US/Eastern"]],[["daylight",[["dtstart",{},"date-time","2000-04-04T02:00:00"],["rrule",{},"recur",{"freq":"YEARLY","byday":"1SU","bymonth":4}],["tzname",{},"text","EDT"],["tzoffsetfrom",{},"utc-offset","-05:00"],["tzoffsetto",{},"utc-offset","-04:00"]],[]],["standard",[["dtstart",{},"date-time","2000-10-26T02:00:00"],["rrule",{},"recur",{"freq":"YEARLY","byday":"1SU","bymonth":10}],["tzname",{},"text","EST"],["tzoffsetfrom",{},"utc-offset","-04:00"],["tzoffsetto",{},"utc-offset","-05:00"]],[]]]],["vevent",[["dtstamp",{},"date-time","2006-02-06T00:11:21Z"],["dtstart",{"tzid":"US/Eastern"},"date-time","2006-01-02T14:00:00"],["duration",{},"duration","PT1H"],["recurrence-id",{"tzid":"US/Eastern"},"date-time","2006-01-04T12:00:00"],["summary",{},"text","Event #2"],["uid",{},"text","12345"]],[]]]]',
|
||||
'application/calendar+json',
|
||||
"BEGIN:VCALENDAR\r\nVERSION:2.0\r\nPRODID:-//Sabre//Sabre VObject " . VObject\Version::VERSION . "//EN\r\nCALSCALE:GREGORIAN\r\nBEGIN:VTIMEZONE\r\nLAST-MODIFIED:20040110T032845Z\r\nTZID:US/Eastern\r\nBEGIN:DAYLIGHT\r\nDTSTART:20000404T020000\r\nRRULE:FREQ=YEARLY;BYDAY=1SU;BYMONTH=4\r\nTZNAME:EDT\r\nTZOFFSETFROM:-0500\r\nTZOFFSETTO:-0400\r\nEND:DAYLIGHT\r\nBEGIN:STANDARD\r\nDTSTART:20001026T020000\r\nRRULE:FREQ=YEARLY;BYDAY=1SU;BYMONTH=10\r\nTZNAME:EST\r\nTZOFFSETFROM:-0400\r\nTZOFFSETTO:-0500\r\nEND:STANDARD\r\nEND:VTIMEZONE\r\nBEGIN:VEVENT\r\nDTSTAMP:20060206T001121Z\r\nDTSTART;TZID=US/Eastern:20060102T140000\r\nDURATION:PT1H\r\nRECURRENCE-ID;TZID=US/Eastern:20060104T120000\r\nSUMMARY:Event #2\r\nUID:12345\r\nEND:VEVENT\r\nEND:VCALENDAR\r\n"
|
||||
],
|
||||
[
|
||||
'<?xml version="1.0" encoding="utf-8" ?><icalendar xmlns="urn:ietf:params:xml:ns:icalendar-2.0"><vcalendar><properties><prodid><text>-//Example Inc.//Example Client//EN</text></prodid><version><text>2.0</text></version></properties><components><vevent><properties><dtstamp><date-time>2006-02-06T00:11:21Z</date-time></dtstamp><dtstart><parameters><tzid><text>US/Eastern</text></tzid></parameters><date-time>2006-01-04T14:00:00</date-time></dtstart><duration><duration>PT1H</duration></duration><recurrence-id><parameters><tzid><text>US/Eastern</text></tzid></parameters><date-time>2006-01-04T12:00:00</date-time></recurrence-id><summary><text>Event #2 bis</text></summary><uid><text>12345</text></uid></properties></vevent></components></vcalendar></icalendar>',
|
||||
'application/calendar+xml',
|
||||
"BEGIN:VCALENDAR\r\nVERSION:2.0\r\nPRODID:-//Sabre//Sabre VObject " . VObject\Version::VERSION . "//EN\r\nCALSCALE:GREGORIAN\r\nBEGIN:VEVENT\r\nDTSTAMP:20060206T001121Z\r\nDTSTART;TZID=US/Eastern:20060104T140000\r\nDURATION:PT1H\r\nRECURRENCE-ID;TZID=US/Eastern:20060104T120000\r\nSUMMARY:Event #2 bis\r\nUID:12345\r\nEND:VEVENT\r\nEND:VCALENDAR\r\n"
|
||||
]
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
@@ -403,31 +403,31 @@ class BirthdayServiceTest extends TestCase {
|
||||
[null, null, null, null, null, null, null, "BEGIN:VCARD\r\nVERSION:3.0\r\nPRODID:-//Sabre//Sabre VObject 4.1.1//EN\r\nUID:12345\r\nFN:12345\r\nN:12345;;;;\r\nEND:VCARD\r\n", 'BDAY', '', true, null],
|
||||
[null, null, null, null, null, null, null, "BEGIN:VCARD\r\nVERSION:3.0\r\nPRODID:-//Sabre//Sabre VObject 4.1.1//EN\r\nUID:12345\r\nFN:12345\r\nN:12345;;;;\r\nBDAY:\r\nEND:VCARD\r\n", 'BDAY', '', true, null],
|
||||
[null, null, null, null, null, null, null, "BEGIN:VCARD\r\nVERSION:3.0\r\nPRODID:-//Sabre//Sabre VObject 4.1.1//EN\r\nUID:12345\r\nFN:12345\r\nN:12345;;;;\r\nBDAY:someday\r\nEND:VCARD\r\n", 'BDAY', '', true, null],
|
||||
['🎂 12345 (1900)', '19700101', 'FREQ=YEARLY', 'BDAY', '0', '1900', null, "BEGIN:VCARD\r\nVERSION:3.0\r\nPRODID:-//Sabre//Sabre VObject 4.1.1//EN\r\nUID:12345\r\nFN:12345\r\nN:12345;;;;\r\nBDAY:19000101\r\nEND:VCARD\r\n", 'BDAY', '', true, null],
|
||||
['🎂 12345 (1900)', '19701231', 'FREQ=YEARLY', 'BDAY', '0', '1900', null, "BEGIN:VCARD\r\nVERSION:3.0\r\nPRODID:-//Sabre//Sabre VObject 4.1.1//EN\r\nUID:12345\r\nFN:12345\r\nN:12345;;;;\r\nBDAY:19001231\r\nEND:VCARD\r\n", 'BDAY', '', true, null],
|
||||
['Death of 12345 (1900)', '19701231', 'FREQ=YEARLY', 'DEATHDATE', '0', '1900', null, "BEGIN:VCARD\r\nVERSION:3.0\r\nPRODID:-//Sabre//Sabre VObject 4.1.1//EN\r\nUID:12345\r\nFN:12345\r\nN:12345;;;;\r\nDEATHDATE:19001231\r\nEND:VCARD\r\n", 'DEATHDATE', '-death', true, null],
|
||||
['Death of 12345 (1900)', '19701231', 'FREQ=YEARLY', 'DEATHDATE', '0', '1900', null, "BEGIN:VCARD\r\nVERSION:3.0\r\nPRODID:-//Sabre//Sabre VObject 4.1.1//EN\r\nUID:12345\r\nFN:12345\r\nN:12345;;;;\r\nDEATHDATE:19001231\r\nEND:VCARD\r\n", 'DEATHDATE', '-death', false, null],
|
||||
['💍 12345 (1900)', '19701231', 'FREQ=YEARLY', 'ANNIVERSARY', '0', '1900', null, "BEGIN:VCARD\r\nVERSION:3.0\r\nPRODID:-//Sabre//Sabre VObject 4.1.1//EN\r\nUID:12345\r\nFN:12345\r\nN:12345;;;;\r\nANNIVERSARY:19001231\r\nEND:VCARD\r\n", 'ANNIVERSARY', '-anniversary', true, null],
|
||||
['12345 (⚭1900)', '19701231', 'FREQ=YEARLY', 'ANNIVERSARY', '0', '1900', null, "BEGIN:VCARD\r\nVERSION:3.0\r\nPRODID:-//Sabre//Sabre VObject 4.1.1//EN\r\nUID:12345\r\nFN:12345\r\nN:12345;;;;\r\nANNIVERSARY:19001231\r\nEND:VCARD\r\n", 'ANNIVERSARY', '-anniversary', false, null],
|
||||
['🎂 12345 (1900)', '19000101', 'FREQ=YEARLY', 'BDAY', '0', '1900', null, "BEGIN:VCARD\r\nVERSION:3.0\r\nPRODID:-//Sabre//Sabre VObject 4.1.1//EN\r\nUID:12345\r\nFN:12345\r\nN:12345;;;;\r\nBDAY:19000101\r\nEND:VCARD\r\n", 'BDAY', '', true, null],
|
||||
['🎂 12345 (1900)', '19001231', 'FREQ=YEARLY', 'BDAY', '0', '1900', null, "BEGIN:VCARD\r\nVERSION:3.0\r\nPRODID:-//Sabre//Sabre VObject 4.1.1//EN\r\nUID:12345\r\nFN:12345\r\nN:12345;;;;\r\nBDAY:19001231\r\nEND:VCARD\r\n", 'BDAY', '', true, null],
|
||||
['Death of 12345 (1900)', '19001231', 'FREQ=YEARLY', 'DEATHDATE', '0', '1900', null, "BEGIN:VCARD\r\nVERSION:3.0\r\nPRODID:-//Sabre//Sabre VObject 4.1.1//EN\r\nUID:12345\r\nFN:12345\r\nN:12345;;;;\r\nDEATHDATE:19001231\r\nEND:VCARD\r\n", 'DEATHDATE', '-death', true, null],
|
||||
['Death of 12345 (1900)', '19001231', 'FREQ=YEARLY', 'DEATHDATE', '0', '1900', null, "BEGIN:VCARD\r\nVERSION:3.0\r\nPRODID:-//Sabre//Sabre VObject 4.1.1//EN\r\nUID:12345\r\nFN:12345\r\nN:12345;;;;\r\nDEATHDATE:19001231\r\nEND:VCARD\r\n", 'DEATHDATE', '-death', false, null],
|
||||
['💍 12345 (1900)', '19001231', 'FREQ=YEARLY', 'ANNIVERSARY', '0', '1900', null, "BEGIN:VCARD\r\nVERSION:3.0\r\nPRODID:-//Sabre//Sabre VObject 4.1.1//EN\r\nUID:12345\r\nFN:12345\r\nN:12345;;;;\r\nANNIVERSARY:19001231\r\nEND:VCARD\r\n", 'ANNIVERSARY', '-anniversary', true, null],
|
||||
['12345 (⚭1900)', '19001231', 'FREQ=YEARLY', 'ANNIVERSARY', '0', '1900', null, "BEGIN:VCARD\r\nVERSION:3.0\r\nPRODID:-//Sabre//Sabre VObject 4.1.1//EN\r\nUID:12345\r\nFN:12345\r\nN:12345;;;;\r\nANNIVERSARY:19001231\r\nEND:VCARD\r\n", 'ANNIVERSARY', '-anniversary', false, null],
|
||||
['🎂 12345', '19701231', 'FREQ=YEARLY', 'BDAY', '1', null, null, "BEGIN:VCARD\r\nVERSION:3.0\r\nPRODID:-//Sabre//Sabre VObject 4.1.1//EN\r\nUID:12345\r\nFN:12345\r\nN:12345;;;;\r\nBDAY:--1231\r\nEND:VCARD\r\n", 'BDAY', '', true, null],
|
||||
['🎂 12345', '19701231', 'FREQ=YEARLY', 'BDAY', '1', null, null, "BEGIN:VCARD\r\nVERSION:3.0\r\nPRODID:-//Sabre//Sabre VObject 4.1.1//EN\r\nUID:12345\r\nFN:12345\r\nN:12345;;;;\r\nBDAY;X-APPLE-OMIT-YEAR=1604:16041231\r\nEND:VCARD\r\n", 'BDAY', '', true, null],
|
||||
[null, null, null, null, null, null, null, "BEGIN:VCARD\r\nVERSION:3.0\r\nPRODID:-//Sabre//Sabre VObject 4.1.1//EN\r\nUID:12345\r\nFN:12345\r\nN:12345;;;;\r\nBDAY:;VALUE=text:circa 1800\r\nEND:VCARD\r\n", 'BDAY', '', true, null],
|
||||
[null, null, null, null, null, null, null, "BEGIN:VCARD\r\nVERSION:3.0\r\nPRODID:-//Sabre//Sabre VObject 4.1.1//EN\r\nUID:12345\r\nN:12345;;;;\r\nBDAY:20031231\r\nEND:VCARD\r\n", 'BDAY', '', true, null],
|
||||
['🎂 12345 (900)', '19701231', 'FREQ=YEARLY', 'BDAY', '0', '900', null, "BEGIN:VCARD\r\nVERSION:3.0\r\nPRODID:-//Sabre//Sabre VObject 4.1.1//EN\r\nUID:12345\r\nFN:12345\r\nN:12345;;;;\r\nBDAY:09001231\r\nEND:VCARD\r\n", 'BDAY', '', true, null],
|
||||
['12345 (*1900)', '19700101', 'FREQ=YEARLY', 'BDAY', '0', '1900', null, "BEGIN:VCARD\r\nVERSION:3.0\r\nPRODID:-//Sabre//Sabre VObject 4.1.1//EN\r\nUID:12345\r\nFN:12345\r\nN:12345;;;;\r\nBDAY:19000101\r\nEND:VCARD\r\n", 'BDAY', '', false, null],
|
||||
['12345 (*1900)', '19701231', 'FREQ=YEARLY', 'BDAY', '0', '1900', null, "BEGIN:VCARD\r\nVERSION:3.0\r\nPRODID:-//Sabre//Sabre VObject 4.1.1//EN\r\nUID:12345\r\nFN:12345\r\nN:12345;;;;\r\nBDAY:19001231\r\nEND:VCARD\r\n", 'BDAY', '', false, null],
|
||||
['🎂 12345 (900)', '09001231', 'FREQ=YEARLY', 'BDAY', '0', '900', null, "BEGIN:VCARD\r\nVERSION:3.0\r\nPRODID:-//Sabre//Sabre VObject 4.1.1//EN\r\nUID:12345\r\nFN:12345\r\nN:12345;;;;\r\nBDAY:09001231\r\nEND:VCARD\r\n", 'BDAY', '', true, null],
|
||||
['12345 (*1900)', '19000101', 'FREQ=YEARLY', 'BDAY', '0', '1900', null, "BEGIN:VCARD\r\nVERSION:3.0\r\nPRODID:-//Sabre//Sabre VObject 4.1.1//EN\r\nUID:12345\r\nFN:12345\r\nN:12345;;;;\r\nBDAY:19000101\r\nEND:VCARD\r\n", 'BDAY', '', false, null],
|
||||
['12345 (*1900)', '19001231', 'FREQ=YEARLY', 'BDAY', '0', '1900', null, "BEGIN:VCARD\r\nVERSION:3.0\r\nPRODID:-//Sabre//Sabre VObject 4.1.1//EN\r\nUID:12345\r\nFN:12345\r\nN:12345;;;;\r\nBDAY:19001231\r\nEND:VCARD\r\n", 'BDAY', '', false, null],
|
||||
['12345 *', '19701231', 'FREQ=YEARLY', 'BDAY', '1', null, null, "BEGIN:VCARD\r\nVERSION:3.0\r\nPRODID:-//Sabre//Sabre VObject 4.1.1//EN\r\nUID:12345\r\nFN:12345\r\nN:12345;;;;\r\nBDAY:--1231\r\nEND:VCARD\r\n", 'BDAY', '', false, null],
|
||||
['12345 *', '19701231', 'FREQ=YEARLY', 'BDAY', '1', null, null, "BEGIN:VCARD\r\nVERSION:3.0\r\nPRODID:-//Sabre//Sabre VObject 4.1.1//EN\r\nUID:12345\r\nFN:12345\r\nN:12345;;;;\r\nBDAY;X-APPLE-OMIT-YEAR=1604:16041231\r\nEND:VCARD\r\n", 'BDAY', '', false, null],
|
||||
[null, null, null, null, null, null, null, "BEGIN:VCARD\r\nVERSION:3.0\r\nPRODID:-//Sabre//Sabre VObject 4.1.1//EN\r\nUID:12345\r\nFN:12345\r\nN:12345;;;;\r\nBDAY:;VALUE=text:circa 1800\r\nEND:VCARD\r\n", 'BDAY', '', false, null],
|
||||
[null, null, null, null, null, null, null, "BEGIN:VCARD\r\nVERSION:3.0\r\nPRODID:-//Sabre//Sabre VObject 4.1.1//EN\r\nUID:12345\r\nN:12345;;;;\r\nBDAY:20031231\r\nEND:VCARD\r\n", 'BDAY', '', false, null],
|
||||
['12345 (*900)', '19701231', 'FREQ=YEARLY', 'BDAY', '0', '900', null, "BEGIN:VCARD\r\nVERSION:3.0\r\nPRODID:-//Sabre//Sabre VObject 4.1.1//EN\r\nUID:12345\r\nFN:12345\r\nN:12345;;;;\r\nBDAY:09001231\r\nEND:VCARD\r\n", 'BDAY', '', false, null],
|
||||
['12345 (*1900)', '19701231', 'FREQ=YEARLY', 'BDAY', '0', '1900', 'PT9H', "BEGIN:VCARD\r\nVERSION:3.0\r\nPRODID:-//Sabre//Sabre VObject 4.1.1//EN\r\nUID:12345\r\nFN:12345\r\nN:12345;;;;\r\nBDAY:19001231\r\nEND:VCARD\r\n", 'BDAY', '', false, 'PT9H'],
|
||||
['12345 (*1900)', '19701231', 'FREQ=YEARLY', 'BDAY', '0', '1900', '-PT15H', "BEGIN:VCARD\r\nVERSION:3.0\r\nPRODID:-//Sabre//Sabre VObject 4.1.1//EN\r\nUID:12345\r\nFN:12345\r\nN:12345;;;;\r\nBDAY:19001231\r\nEND:VCARD\r\n", 'BDAY', '', false, '-PT15H'],
|
||||
['12345 (*1900)', '19701231', 'FREQ=YEARLY', 'BDAY', '0', '1900', '-P6DT15H', "BEGIN:VCARD\r\nVERSION:3.0\r\nPRODID:-//Sabre//Sabre VObject 4.1.1//EN\r\nUID:12345\r\nFN:12345\r\nN:12345;;;;\r\nBDAY:19001231\r\nEND:VCARD\r\n", 'BDAY', '', false, '-P6DT15H'],
|
||||
['12345 (*900)', '09001231', 'FREQ=YEARLY', 'BDAY', '0', '900', null, "BEGIN:VCARD\r\nVERSION:3.0\r\nPRODID:-//Sabre//Sabre VObject 4.1.1//EN\r\nUID:12345\r\nFN:12345\r\nN:12345;;;;\r\nBDAY:09001231\r\nEND:VCARD\r\n", 'BDAY', '', false, null],
|
||||
['12345 (*1900)', '19001231', 'FREQ=YEARLY', 'BDAY', '0', '1900', 'PT9H', "BEGIN:VCARD\r\nVERSION:3.0\r\nPRODID:-//Sabre//Sabre VObject 4.1.1//EN\r\nUID:12345\r\nFN:12345\r\nN:12345;;;;\r\nBDAY:19001231\r\nEND:VCARD\r\n", 'BDAY', '', false, 'PT9H'],
|
||||
['12345 (*1900)', '19001231', 'FREQ=YEARLY', 'BDAY', '0', '1900', '-PT15H', "BEGIN:VCARD\r\nVERSION:3.0\r\nPRODID:-//Sabre//Sabre VObject 4.1.1//EN\r\nUID:12345\r\nFN:12345\r\nN:12345;;;;\r\nBDAY:19001231\r\nEND:VCARD\r\n", 'BDAY', '', false, '-PT15H'],
|
||||
['12345 (*1900)', '19001231', 'FREQ=YEARLY', 'BDAY', '0', '1900', '-P6DT15H', "BEGIN:VCARD\r\nVERSION:3.0\r\nPRODID:-//Sabre//Sabre VObject 4.1.1//EN\r\nUID:12345\r\nFN:12345\r\nN:12345;;;;\r\nBDAY:19001231\r\nEND:VCARD\r\n", 'BDAY', '', false, '-P6DT15H'],
|
||||
[null, null, null, null, null, null, null, "BEGIN:VCARD\r\nVERSION:3.0\r\nPRODID:-//Sabre//Sabre VObject 4.1.1//EN\r\nUID:12345\r\nFN:12345\r\nN:12345;;;;\r\nBDAY:19000101\r\nX-NC-EXCLUDE-FROM-BIRTHDAY-CALENDAR;TYPE=boolean:true\r\nEND:VCARD\r\n", 'BDAY', '', true, null],
|
||||
[null, null, null, null, null, null, null, "BEGIN:VCARD\r\nVERSION:3.0\r\nPRODID:-//Sabre//Sabre VObject 4.1.1//EN\r\nUID:12345\r\nFN:12345\r\nN:12345;;;;\r\nX-NC-EXCLUDE-FROM-BIRTHDAY-CALENDAR;TYPE=boolean:true\r\nDEATHDATE:19001231\r\nEND:VCARD\r\n", 'DEATHDATE', '-death', true, null],
|
||||
[null, null, null, null, null, null, null, "BEGIN:VCARD\r\nVERSION:3.0\r\nPRODID:-//Sabre//Sabre VObject 4.1.1//EN\r\nUID:12345\r\nFN:12345\r\nN:12345;;;;\r\nANNIVERSARY:19001231\r\nX-NC-EXCLUDE-FROM-BIRTHDAY-CALENDAR;TYPE=boolean:true\r\nEND:VCARD\r\n", 'ANNIVERSARY', '-anniversary', true, null],
|
||||
['🎂 12345 (1902)', '19720229', 'FREQ=YEARLY;BYMONTH=2;BYMONTHDAY=-1', 'BDAY', '0', null, null, "BEGIN:VCARD\r\nVERSION:3.0\r\nPRODID:-//Sabre//Sabre VObject 4.1.1//EN\r\nUID:12345\r\nFN:12345\r\nN:12345;;;;\r\nBDAY:19020229\r\nEND:VCARD\r\n", 'BDAY', '', true, null],
|
||||
['🎂 12345 (1904)', '19040229', 'FREQ=YEARLY;BYMONTH=2;BYMONTHDAY=-1', 'BDAY', '0', '1904', null, "BEGIN:VCARD\r\nVERSION:3.0\r\nPRODID:-//Sabre//Sabre VObject 4.1.1//EN\r\nUID:12345\r\nFN:12345\r\nN:12345;;;;\r\nBDAY:19040229\r\nEND:VCARD\r\n", 'BDAY', '', true, null],
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
@@ -48,14 +48,16 @@ class EntityCollectionTest extends \Test\TestCase {
|
||||
}
|
||||
|
||||
public function testGetChild(): void {
|
||||
$comment = $this->createMock(IComment::class);
|
||||
$comment->method('getObjectType')
|
||||
->willReturn('files');
|
||||
$comment->method('getObjectId')
|
||||
->willReturn('19');
|
||||
|
||||
$this->commentsManager->expects($this->once())
|
||||
->method('get')
|
||||
->with('55')
|
||||
->willReturn(
|
||||
$this->getMockBuilder(IComment::class)
|
||||
->disableOriginalConstructor()
|
||||
->getMock()
|
||||
);
|
||||
->willReturn($comment);
|
||||
|
||||
$node = $this->collection->getChild('55');
|
||||
$this->assertInstanceOf(CommentNode::class, $node);
|
||||
@@ -107,6 +109,17 @@ class EntityCollectionTest extends \Test\TestCase {
|
||||
}
|
||||
|
||||
public function testChildExistsTrue(): void {
|
||||
$comment = $this->createMock(IComment::class);
|
||||
$comment->method('getObjectType')
|
||||
->willReturn('files');
|
||||
$comment->method('getObjectId')
|
||||
->willReturn('19');
|
||||
|
||||
$this->commentsManager->expects($this->once())
|
||||
->method('get')
|
||||
->with('44')
|
||||
->willReturn($comment);
|
||||
|
||||
$this->assertTrue($this->collection->childExists('44'));
|
||||
}
|
||||
|
||||
|
||||
@@ -10,6 +10,7 @@ namespace OCA\DAV\Tests\unit\Connector\Sabre;
|
||||
|
||||
use OC\Files\FileInfo;
|
||||
use OC\Files\Filesystem;
|
||||
use OC\Files\Node\Folder;
|
||||
use OC\Files\Node\Node;
|
||||
use OC\Files\Storage\Wrapper\Quota;
|
||||
use OC\Files\View;
|
||||
@@ -21,8 +22,10 @@ use OCP\Constants;
|
||||
use OCP\Files\ForbiddenException;
|
||||
use OCP\Files\InvalidPathException;
|
||||
use OCP\Files\Mount\IMountPoint;
|
||||
use OCP\Files\Storage\IStorage;
|
||||
use OCP\Files\StorageNotAvailableException;
|
||||
use PHPUnit\Framework\MockObject\MockObject;
|
||||
use Sabre\DAV\Exception\NotFound;
|
||||
use Test\Traits\UserTrait;
|
||||
|
||||
class TestViewDirectory extends View {
|
||||
@@ -61,12 +64,16 @@ class DirectoryTest extends \Test\TestCase {
|
||||
|
||||
private View&MockObject $view;
|
||||
private FileInfo&MockObject $info;
|
||||
private IStorage&MockObject $storage;
|
||||
|
||||
protected function setUp(): void {
|
||||
parent::setUp();
|
||||
|
||||
$this->view = $this->createMock(View::class);
|
||||
$this->info = $this->createMock(FileInfo::class);
|
||||
$this->storage = $this->createMock(IStorage::class);
|
||||
$this->info->method('getStorage')
|
||||
->willReturn($this->storage);
|
||||
$this->info->method('isReadable')
|
||||
->willReturn(true);
|
||||
$this->info->method('getType')
|
||||
@@ -266,6 +273,146 @@ class DirectoryTest extends \Test\TestCase {
|
||||
$dir->getChild('.');
|
||||
}
|
||||
|
||||
public function testGetNodeForPath(): void {
|
||||
$directoryNode = $this->createMock(Folder::class);
|
||||
$pathNode = $this->createMock(Folder::class);
|
||||
$pathParentNode = $this->createMock(Folder::class);
|
||||
$storage = $this->createMock(IStorage::class);
|
||||
|
||||
$directoryNode->expects($this->once())
|
||||
->method('getStorage')
|
||||
->willReturn($storage);
|
||||
$storage->expects($this->once())
|
||||
->method('instanceOfStorage')
|
||||
->willReturn(false);
|
||||
|
||||
$directoryNode->expects($this->once())
|
||||
->method('isReadable')
|
||||
->willReturn(true);
|
||||
$directoryNode->expects($this->once())
|
||||
->method('getPath')
|
||||
->willReturn('/admin/files/');
|
||||
$directoryNode->expects($this->once())
|
||||
->method('get')
|
||||
->willReturn($pathNode);
|
||||
|
||||
$pathNode->expects($this->once())
|
||||
->method('getPath')
|
||||
->willReturn('/admin/files/my/deep/folder/');
|
||||
$pathNode->expects($this->once())
|
||||
->method('isReadable')
|
||||
->willReturn(true);
|
||||
$pathNode->expects($this->once())
|
||||
->method('getMimetype')
|
||||
->willReturn(FileInfo::MIMETYPE_FOLDER);
|
||||
|
||||
$this->view->method('getRelativePath')
|
||||
->willReturnCallback(function ($path) {
|
||||
return str_replace('/admin/files/', '', $path);
|
||||
});
|
||||
|
||||
$this->view->expects($this->exactly(2))
|
||||
->method('getFileInfo')
|
||||
->willReturn($pathParentNode);
|
||||
|
||||
$pathParentNode->expects($this->exactly(2))
|
||||
->method('getPath')
|
||||
->willReturnOnConsecutiveCalls('/my/deep', '/my');
|
||||
$pathParentNode->expects($this->exactly(2))
|
||||
->method('isReadable')
|
||||
->willReturn(true);
|
||||
|
||||
$dir = new Directory($this->view, $directoryNode);
|
||||
$dir->getNodeForPath('/my/deep/folder/');
|
||||
}
|
||||
|
||||
public function testGetNodeForPathFailsWithNoReadPermissionsForParent(): void {
|
||||
$directoryNode = $this->createMock(Folder::class);
|
||||
$pathNode = $this->createMock(Folder::class);
|
||||
$pathParentNode = $this->createMock(Folder::class);
|
||||
$storage = $this->createMock(IStorage::class);
|
||||
|
||||
$directoryNode->expects($this->once())
|
||||
->method('getStorage')
|
||||
->willReturn($storage);
|
||||
$storage->expects($this->once())
|
||||
->method('instanceOfStorage')
|
||||
->willReturn(false);
|
||||
|
||||
$directoryNode->expects($this->once())
|
||||
->method('isReadable')
|
||||
->willReturn(true);
|
||||
$directoryNode->expects($this->once())
|
||||
->method('getPath')
|
||||
->willReturn('/admin/files/');
|
||||
$directoryNode->expects($this->once())
|
||||
->method('get')
|
||||
->willReturn($pathNode);
|
||||
|
||||
$pathNode->expects($this->once())
|
||||
->method('getPath')
|
||||
->willReturn('/admin/files/my/deep/folder/');
|
||||
$pathNode->expects($this->once())
|
||||
->method('isReadable')
|
||||
->willReturn(true);
|
||||
$pathNode->expects($this->once())
|
||||
->method('getMimetype')
|
||||
->willReturn(FileInfo::MIMETYPE_FOLDER);
|
||||
|
||||
$this->view->method('getRelativePath')
|
||||
->willReturnCallback(function ($path) {
|
||||
return str_replace('/admin/files/', '', $path);
|
||||
});
|
||||
|
||||
$this->view->expects($this->exactly(2))
|
||||
->method('getFileInfo')
|
||||
->willReturn($pathParentNode);
|
||||
|
||||
$pathParentNode->expects($this->exactly(2))
|
||||
->method('getPath')
|
||||
->willReturnOnConsecutiveCalls('/my/deep', '/my');
|
||||
$pathParentNode->expects($this->exactly(2))
|
||||
->method('isReadable')
|
||||
->willReturnOnConsecutiveCalls(true, false);
|
||||
|
||||
$this->expectException(NotFound::class);
|
||||
|
||||
$dir = new Directory($this->view, $directoryNode);
|
||||
$dir->getNodeForPath('/my/deep/folder/');
|
||||
}
|
||||
|
||||
public function testGetNodeForPathFailsWithNoReadPermissionsForPath(): void {
|
||||
$directoryNode = $this->createMock(Folder::class);
|
||||
$pathNode = $this->createMock(Folder::class);
|
||||
$storage = $this->createMock(IStorage::class);
|
||||
|
||||
$directoryNode->expects($this->once())
|
||||
->method('getStorage')
|
||||
->willReturn($storage);
|
||||
$storage->expects($this->once())
|
||||
->method('instanceOfStorage')
|
||||
->willReturn(false);
|
||||
|
||||
$directoryNode->expects($this->once())
|
||||
->method('isReadable')
|
||||
->willReturn(true);
|
||||
$directoryNode->expects($this->once())
|
||||
->method('getPath')
|
||||
->willReturn('/admin/files/');
|
||||
$directoryNode->expects($this->once())
|
||||
->method('get')
|
||||
->willReturn($pathNode);
|
||||
|
||||
$pathNode->expects($this->once())
|
||||
->method('isReadable')
|
||||
->willReturn(false);
|
||||
|
||||
$this->expectException(\Sabre\DAV\Exception\Forbidden::class);
|
||||
|
||||
$dir = new Directory($this->view, $directoryNode);
|
||||
$dir->getNodeForPath('/my/deep/folder/');
|
||||
}
|
||||
|
||||
public function testGetQuotaInfoUnlimited(): void {
|
||||
$this->createUser('user', 'password');
|
||||
self::loginAsUser('user');
|
||||
|
||||
@@ -253,7 +253,6 @@ class SharesPluginTest extends \Test\TestCase {
|
||||
[[IShare::TYPE_REMOTE]],
|
||||
[[IShare::TYPE_ROOM]],
|
||||
[[IShare::TYPE_DECK]],
|
||||
[[IShare::TYPE_SCIENCEMESH]],
|
||||
[[IShare::TYPE_USER, IShare::TYPE_GROUP]],
|
||||
[[IShare::TYPE_USER, IShare::TYPE_GROUP, IShare::TYPE_LINK]],
|
||||
[[IShare::TYPE_USER, IShare::TYPE_LINK]],
|
||||
|
||||
@@ -7,14 +7,14 @@ namespace Composer\Autoload;
|
||||
class ComposerStaticInitEncryption
|
||||
{
|
||||
public static $prefixLengthsPsr4 = array (
|
||||
'O' =>
|
||||
'O' =>
|
||||
array (
|
||||
'OCA\\Encryption\\' => 15,
|
||||
),
|
||||
);
|
||||
|
||||
public static $prefixDirsPsr4 = array (
|
||||
'OCA\\Encryption\\' =>
|
||||
'OCA\\Encryption\\' =>
|
||||
array (
|
||||
0 => __DIR__ . '/..' . '/../lib',
|
||||
),
|
||||
|
||||
@@ -7,14 +7,14 @@ namespace Composer\Autoload;
|
||||
class ComposerStaticInitFederatedFileSharing
|
||||
{
|
||||
public static $prefixLengthsPsr4 = array (
|
||||
'O' =>
|
||||
'O' =>
|
||||
array (
|
||||
'OCA\\FederatedFileSharing\\' => 25,
|
||||
),
|
||||
);
|
||||
|
||||
public static $prefixDirsPsr4 = array (
|
||||
'OCA\\FederatedFileSharing\\' =>
|
||||
'OCA\\FederatedFileSharing\\' =>
|
||||
array (
|
||||
0 => __DIR__ . '/..' . '/../lib',
|
||||
),
|
||||
|
||||
@@ -25,14 +25,14 @@ OC.L10N.register(
|
||||
"Provide federated file sharing across servers" : "توفير مشاركة الملفات عبر خوادم السحابة الاتحادية",
|
||||
"Confirm data upload to lookup server" : "تأكيد تحميل البيانات لخادوم البحث",
|
||||
"When enabled, all account properties (e.g. email address) with scope visibility set to \"published\", will be automatically synced and transmitted to an external system and made available in a public, global address book." : "عند تفعيل هذا الخيار، ستتم تلقائياً مزامنة جميع خصائص الحساب (على سبيل المثال عنوان البريد الإلكتروني) التي تم تعيين \"نطاق الرؤية\" visibility scope فيها على \"منشور published\"، وإرسالها إلى نظام خارجي وإتاحتها في دفتر عناوين عمومي شامل.",
|
||||
"Disable upload" : "تعطيل الرفع",
|
||||
"Enable data upload" : "تمكين رفع البيانات",
|
||||
"Disable upload" : "تعطيل الرفع",
|
||||
"Confirm querying lookup server" : "تأكيد استعلام خادوم البحث",
|
||||
"When enabled, the search input when creating shares will be sent to an external system that provides a public and global address book." : "عند تمكين هذا الخيار، سيتم إرسال إدخال البحث عند إنشاء المشاركات إلى نظام خارجي يوفر دفتر عناوين شامل وعمومي.",
|
||||
"This is used to retrieve the federated cloud ID to make federated sharing easier." : "يستعمل هذا لاسترجاع معرف السحابة الاتحادية وذلك لتسهيل المشاركات الاتحادية",
|
||||
"Moreover, email addresses of users might be sent to that system in order to verify them." : "علاوة على ذلك، قد يتم إرسال عناوين البريد الإلكتروني للمستخدِمين إلى ذلك النظام للتحقق منها.",
|
||||
"Disable querying" : "تعطيل الاستعلام",
|
||||
"Enable querying" : "تمكين الاستعلام",
|
||||
"Disable querying" : "تعطيل الاستعلام",
|
||||
"Unable to update federated files sharing config" : "تعذر تحديث تهيئة مشاركة الملفات عبر السحابة الاتحادية",
|
||||
"Adjust how people can share between servers. This includes shares between people on this server as well if they are using federated sharing." : "اضبط كيفية مشاركة الأشخاص بين الخوادم. هذا يشمل المشاركات بين الأشخاص على هذا الخادوم أيضاً إذا كانوا يستعملون المشاركة عبر السحابة الاتحادية.",
|
||||
"Allow people on this server to send shares to other servers (this option also allows WebDAV access to public shares)" : "السماح للأشخاص على هذا الخادوم بإرسال مشاركات إلى خوادم أخرى (هذا الخيار يسمح أيضاً بالوصول عبر WebDAV إلى المشاركات العمومية)",
|
||||
@@ -54,8 +54,6 @@ OC.L10N.register(
|
||||
"Your Federated Cloud ID" : "مُعرِّف شبكتك الاتحادية",
|
||||
"Share it so your friends can share files with you:" : "شاركه مع أصدقائك بحيث يمكنهم مُشاركة الملفات معك:",
|
||||
"Facebook" : "فيسبوك",
|
||||
"X (formerly Twitter)" : "منصة X (تويتر سابقاً)",
|
||||
"formerly Twitter" : "تويتر سابقاً",
|
||||
"Mastodon" : "برنامج ماستودون Mastodon",
|
||||
"Add to your website" : "أضِف إلى موقعك على الويب",
|
||||
"HTML Code:" : "كود HTML: ",
|
||||
@@ -67,6 +65,8 @@ OC.L10N.register(
|
||||
"Incoming share could not be processed" : "لا يمكن معالجة المشاركة الواردة",
|
||||
"Cloud ID copied to the clipboard" : "تمّ نسخ مُعرِّف السحابة إلى الحافظة",
|
||||
"Copy to clipboard" : "نسخ الرابط",
|
||||
"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" : "يُمكنك المشاركة مع أي شخص يستخدم خادم نكست كلاود أو خوادم وخدمات أخرى متوافقة مع بروتوكول Open Cloud Mesh (OCM)! فقط ضع مُعرّف السحابة الاتحادية في مربع حوار المُشاركة؛ و الذي سيكون شكله على هذا النسق: person@cloud.example.com"
|
||||
"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" : "يُمكنك المشاركة مع أي شخص يستخدم خادم نكست كلاود أو خوادم وخدمات أخرى متوافقة مع بروتوكول Open Cloud Mesh (OCM)! فقط ضع مُعرّف السحابة الاتحادية في مربع حوار المُشاركة؛ و الذي سيكون شكله على هذا النسق: person@cloud.example.com",
|
||||
"X (formerly Twitter)" : "منصة X (تويتر سابقاً)",
|
||||
"formerly Twitter" : "تويتر سابقاً"
|
||||
},
|
||||
"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;");
|
||||
|
||||
@@ -23,14 +23,14 @@
|
||||
"Provide federated file sharing across servers" : "توفير مشاركة الملفات عبر خوادم السحابة الاتحادية",
|
||||
"Confirm data upload to lookup server" : "تأكيد تحميل البيانات لخادوم البحث",
|
||||
"When enabled, all account properties (e.g. email address) with scope visibility set to \"published\", will be automatically synced and transmitted to an external system and made available in a public, global address book." : "عند تفعيل هذا الخيار، ستتم تلقائياً مزامنة جميع خصائص الحساب (على سبيل المثال عنوان البريد الإلكتروني) التي تم تعيين \"نطاق الرؤية\" visibility scope فيها على \"منشور published\"، وإرسالها إلى نظام خارجي وإتاحتها في دفتر عناوين عمومي شامل.",
|
||||
"Disable upload" : "تعطيل الرفع",
|
||||
"Enable data upload" : "تمكين رفع البيانات",
|
||||
"Disable upload" : "تعطيل الرفع",
|
||||
"Confirm querying lookup server" : "تأكيد استعلام خادوم البحث",
|
||||
"When enabled, the search input when creating shares will be sent to an external system that provides a public and global address book." : "عند تمكين هذا الخيار، سيتم إرسال إدخال البحث عند إنشاء المشاركات إلى نظام خارجي يوفر دفتر عناوين شامل وعمومي.",
|
||||
"This is used to retrieve the federated cloud ID to make federated sharing easier." : "يستعمل هذا لاسترجاع معرف السحابة الاتحادية وذلك لتسهيل المشاركات الاتحادية",
|
||||
"Moreover, email addresses of users might be sent to that system in order to verify them." : "علاوة على ذلك، قد يتم إرسال عناوين البريد الإلكتروني للمستخدِمين إلى ذلك النظام للتحقق منها.",
|
||||
"Disable querying" : "تعطيل الاستعلام",
|
||||
"Enable querying" : "تمكين الاستعلام",
|
||||
"Disable querying" : "تعطيل الاستعلام",
|
||||
"Unable to update federated files sharing config" : "تعذر تحديث تهيئة مشاركة الملفات عبر السحابة الاتحادية",
|
||||
"Adjust how people can share between servers. This includes shares between people on this server as well if they are using federated sharing." : "اضبط كيفية مشاركة الأشخاص بين الخوادم. هذا يشمل المشاركات بين الأشخاص على هذا الخادوم أيضاً إذا كانوا يستعملون المشاركة عبر السحابة الاتحادية.",
|
||||
"Allow people on this server to send shares to other servers (this option also allows WebDAV access to public shares)" : "السماح للأشخاص على هذا الخادوم بإرسال مشاركات إلى خوادم أخرى (هذا الخيار يسمح أيضاً بالوصول عبر WebDAV إلى المشاركات العمومية)",
|
||||
@@ -52,8 +52,6 @@
|
||||
"Your Federated Cloud ID" : "مُعرِّف شبكتك الاتحادية",
|
||||
"Share it so your friends can share files with you:" : "شاركه مع أصدقائك بحيث يمكنهم مُشاركة الملفات معك:",
|
||||
"Facebook" : "فيسبوك",
|
||||
"X (formerly Twitter)" : "منصة X (تويتر سابقاً)",
|
||||
"formerly Twitter" : "تويتر سابقاً",
|
||||
"Mastodon" : "برنامج ماستودون Mastodon",
|
||||
"Add to your website" : "أضِف إلى موقعك على الويب",
|
||||
"HTML Code:" : "كود HTML: ",
|
||||
@@ -65,6 +63,8 @@
|
||||
"Incoming share could not be processed" : "لا يمكن معالجة المشاركة الواردة",
|
||||
"Cloud ID copied to the clipboard" : "تمّ نسخ مُعرِّف السحابة إلى الحافظة",
|
||||
"Copy to clipboard" : "نسخ الرابط",
|
||||
"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" : "يُمكنك المشاركة مع أي شخص يستخدم خادم نكست كلاود أو خوادم وخدمات أخرى متوافقة مع بروتوكول Open Cloud Mesh (OCM)! فقط ضع مُعرّف السحابة الاتحادية في مربع حوار المُشاركة؛ و الذي سيكون شكله على هذا النسق: person@cloud.example.com"
|
||||
"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" : "يُمكنك المشاركة مع أي شخص يستخدم خادم نكست كلاود أو خوادم وخدمات أخرى متوافقة مع بروتوكول Open Cloud Mesh (OCM)! فقط ضع مُعرّف السحابة الاتحادية في مربع حوار المُشاركة؛ و الذي سيكون شكله على هذا النسق: person@cloud.example.com",
|
||||
"X (formerly Twitter)" : "منصة X (تويتر سابقاً)",
|
||||
"formerly Twitter" : "تويتر سابقاً"
|
||||
},"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;"
|
||||
}
|
||||
@@ -39,7 +39,6 @@ OC.L10N.register(
|
||||
"Federated Cloud" : "Nube federada",
|
||||
"Share it so your friends can share files with you:" : "Compártilu pa que los tos amigos puedan compartir ficheros contigo:",
|
||||
"Facebook" : "Facebook",
|
||||
"X (formerly Twitter)" : "X (anteriormente Twitter)",
|
||||
"Mastodon" : "Mastodon",
|
||||
"Add to your website" : "Amestar al to sitiu web",
|
||||
"HTML Code:" : "Códigu HTML:",
|
||||
@@ -50,6 +49,7 @@ OC.L10N.register(
|
||||
"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",
|
||||
"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"
|
||||
"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",
|
||||
"X (formerly Twitter)" : "X (anteriormente Twitter)"
|
||||
},
|
||||
"nplurals=2; plural=(n != 1);");
|
||||
|
||||
@@ -37,7 +37,6 @@
|
||||
"Federated Cloud" : "Nube federada",
|
||||
"Share it so your friends can share files with you:" : "Compártilu pa que los tos amigos puedan compartir ficheros contigo:",
|
||||
"Facebook" : "Facebook",
|
||||
"X (formerly Twitter)" : "X (anteriormente Twitter)",
|
||||
"Mastodon" : "Mastodon",
|
||||
"Add to your website" : "Amestar al to sitiu web",
|
||||
"HTML Code:" : "Códigu HTML:",
|
||||
@@ -48,6 +47,7 @@
|
||||
"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",
|
||||
"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"
|
||||
"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",
|
||||
"X (formerly Twitter)" : "X (anteriormente Twitter)"
|
||||
},"pluralForm" :"nplurals=2; plural=(n != 1);"
|
||||
}
|
||||
@@ -30,10 +30,9 @@ OC.L10N.register(
|
||||
"Copied!" : "Копирано!",
|
||||
"Federated Cloud" : "Федериран облак",
|
||||
"Share it so your friends can share files with you:" : "Споделете, за да могат приятелите ви да споделят файлове, с вас:",
|
||||
"Facebook" : "Фейсбук",
|
||||
"X (formerly Twitter)" : "X (преди Twitter)",
|
||||
"Mastodon" : "Mastodon",
|
||||
"Bluesky" : "Bluesky",
|
||||
"Facebook" : "Фейсбук",
|
||||
"Mastodon" : "Mastodon",
|
||||
"Add to your website" : "Добавете към вашия уеб сайт",
|
||||
"HTML Code:" : "HTML код:",
|
||||
"Cancel" : "Отказ",
|
||||
@@ -43,6 +42,7 @@ OC.L10N.register(
|
||||
"Remote share password" : "Парола за отдалечено споделяне",
|
||||
"Cloud ID copied to the clipboard" : "Cloud идентификатора е копиран в клипборда",
|
||||
"Copy to clipboard" : "Копиране в клипборда",
|
||||
"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"
|
||||
"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",
|
||||
"X (formerly Twitter)" : "X (преди Twitter)"
|
||||
},
|
||||
"nplurals=2; plural=(n != 1);");
|
||||
|
||||
@@ -28,10 +28,9 @@
|
||||
"Copied!" : "Копирано!",
|
||||
"Federated Cloud" : "Федериран облак",
|
||||
"Share it so your friends can share files with you:" : "Споделете, за да могат приятелите ви да споделят файлове, с вас:",
|
||||
"Facebook" : "Фейсбук",
|
||||
"X (formerly Twitter)" : "X (преди Twitter)",
|
||||
"Mastodon" : "Mastodon",
|
||||
"Bluesky" : "Bluesky",
|
||||
"Facebook" : "Фейсбук",
|
||||
"Mastodon" : "Mastodon",
|
||||
"Add to your website" : "Добавете към вашия уеб сайт",
|
||||
"HTML Code:" : "HTML код:",
|
||||
"Cancel" : "Отказ",
|
||||
@@ -41,6 +40,7 @@
|
||||
"Remote share password" : "Парола за отдалечено споделяне",
|
||||
"Cloud ID copied to the clipboard" : "Cloud идентификатора е копиран в клипборда",
|
||||
"Copy to clipboard" : "Копиране в клипборда",
|
||||
"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"
|
||||
"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",
|
||||
"X (formerly Twitter)" : "X (преди Twitter)"
|
||||
},"pluralForm" :"nplurals=2; plural=(n != 1);"
|
||||
}
|
||||
@@ -44,8 +44,6 @@ OC.L10N.register(
|
||||
"Your Federated Cloud ID" : "El vostre ID de núvol federat",
|
||||
"Share it so your friends can share files with you:" : "Compartiu-lo perquè els vostres amics puguin enviar-vos fitxers compartits:",
|
||||
"Facebook" : "Facebook",
|
||||
"X (formerly Twitter)" : "X (abans Twitter)",
|
||||
"formerly Twitter" : "abans Twitter",
|
||||
"Mastodon" : "Mastodon",
|
||||
"Add to your website" : "Afegiu-lo al vostre lloc web",
|
||||
"HTML Code:" : "Codi HTML:",
|
||||
@@ -57,6 +55,8 @@ OC.L10N.register(
|
||||
"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",
|
||||
"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" : "Podeu compartir contingut amb qualsevol persona que utilitzi un servidor del Nextcloud o altres servidors i serveis compatibles amb Open Cloud Mesh (OCM)! Només cal que n'indiqueu l'ID de núvol federat al quadre de diàleg d'ús compatit. És semblant a persona@nuvol.exemple.com"
|
||||
"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" : "Podeu compartir contingut amb qualsevol persona que utilitzi un servidor del Nextcloud o altres servidors i serveis compatibles amb Open Cloud Mesh (OCM)! Només cal que n'indiqueu l'ID de núvol federat al quadre de diàleg d'ús compatit. És semblant a persona@nuvol.exemple.com",
|
||||
"X (formerly Twitter)" : "X (abans Twitter)",
|
||||
"formerly Twitter" : "abans Twitter"
|
||||
},
|
||||
"nplurals=2; plural=(n != 1);");
|
||||
|
||||
@@ -42,8 +42,6 @@
|
||||
"Your Federated Cloud ID" : "El vostre ID de núvol federat",
|
||||
"Share it so your friends can share files with you:" : "Compartiu-lo perquè els vostres amics puguin enviar-vos fitxers compartits:",
|
||||
"Facebook" : "Facebook",
|
||||
"X (formerly Twitter)" : "X (abans Twitter)",
|
||||
"formerly Twitter" : "abans Twitter",
|
||||
"Mastodon" : "Mastodon",
|
||||
"Add to your website" : "Afegiu-lo al vostre lloc web",
|
||||
"HTML Code:" : "Codi HTML:",
|
||||
@@ -55,6 +53,8 @@
|
||||
"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",
|
||||
"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" : "Podeu compartir contingut amb qualsevol persona que utilitzi un servidor del Nextcloud o altres servidors i serveis compatibles amb Open Cloud Mesh (OCM)! Només cal que n'indiqueu l'ID de núvol federat al quadre de diàleg d'ús compatit. És semblant a persona@nuvol.exemple.com"
|
||||
"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" : "Podeu compartir contingut amb qualsevol persona que utilitzi un servidor del Nextcloud o altres servidors i serveis compatibles amb Open Cloud Mesh (OCM)! Només cal que n'indiqueu l'ID de núvol federat al quadre de diàleg d'ús compatit. És semblant a persona@nuvol.exemple.com",
|
||||
"X (formerly Twitter)" : "X (abans Twitter)",
|
||||
"formerly Twitter" : "abans Twitter"
|
||||
},"pluralForm" :"nplurals=2; plural=(n != 1);"
|
||||
}
|
||||
@@ -25,14 +25,14 @@ OC.L10N.register(
|
||||
"Provide federated file sharing across servers" : "Poskytnout federované sdílení souborů napříč servery",
|
||||
"Confirm data upload to lookup server" : "Potvrdit nahrávání dat na vyhledávací server",
|
||||
"When enabled, all account properties (e.g. email address) with scope visibility set to \"published\", will be automatically synced and transmitted to an external system and made available in a public, global address book." : "Pokud zapnuto, veškeré vlastnosti účtu (např. e-mailová adresa) s rozsahem viditelnosti nastavené na „zveřejněné“ budou automaticky synchronizovány a přenášeny do externího systému a veřejně k dispozici v globální adresáři kontaktů.",
|
||||
"Disable upload" : "Vypnout nahrávání",
|
||||
"Enable data upload" : "Zapnout nahrávání dat",
|
||||
"Disable upload" : "Vypnout nahrávání",
|
||||
"Confirm querying lookup server" : "Potvrdit dotazování vyhledávacího serveru",
|
||||
"When enabled, the search input when creating shares will be sent to an external system that provides a public and global address book." : "Pokud zapnuto, obsah hledání při vytváření sdílení bude odeslán na externí systém, který poskytuje veřejný a globální adresář kontaktů.",
|
||||
"This is used to retrieve the federated cloud ID to make federated sharing easier." : "Toto slouží k získávání identifikátorů v rámci federovaného cloudu pro usnadnění federovaného sdílení.",
|
||||
"Moreover, email addresses of users might be sent to that system in order to verify them." : "Dále mohou být pro ověření odeslány na server e-mailové adresy uživatelů.",
|
||||
"Disable querying" : "Vypnout dotazování",
|
||||
"Enable querying" : "Zapnout dotazování",
|
||||
"Disable querying" : "Vypnout dotazování",
|
||||
"Unable to update federated files sharing config" : "Nedaří se aktualizovat nastavení federovaného sdílení souborů",
|
||||
"Adjust how people can share between servers. This includes shares between people on this server as well if they are using federated sharing." : "Upravte, jak mohou lidé sdílet mezi servery. Týká se sdílení mezi lidmi na tomto serveru a stejně tak uživatelů federovaného sdílení.",
|
||||
"Allow people on this server to send shares to other servers (this option also allows WebDAV access to public shares)" : "Umožnit lidem na tomto serveru posílat sdílení na ostatní servery (tato volba také umožňuje WebDAV přístup k veřejným sdílením)",
|
||||
@@ -55,11 +55,9 @@ OC.L10N.register(
|
||||
"You can share with anyone who uses a {productName} 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" : "Můžete sdílet s kýmkoliv, kdo používá {productName} nebo jiný server či služby, kompatibilní se standardem Open Cloud Mesh (OCM)! Stačí do dialogu pro sdílení zadat jejich jejich identif. v rámci sdruženého cloudu. Má podobu person@cloud.example.com",
|
||||
"Your Federated Cloud ID" : "Váš identifikátor v rámci fedorovaného cloudu",
|
||||
"Share it so your friends can share files with you:" : "Podělte se o to, aby mohli vaši přátelé s vámi mohli sdílet soubory:",
|
||||
"Facebook" : "Facebook",
|
||||
"X (formerly Twitter)" : "X (dříve Twitter)",
|
||||
"formerly Twitter" : "dříve Twitter",
|
||||
"Mastodon" : "Mastodon",
|
||||
"Bluesky" : "Bluesky",
|
||||
"Facebook" : "Facebook",
|
||||
"Mastodon" : "Mastodon",
|
||||
"Add to your website" : "Přidat na svou webovou stránku",
|
||||
"Share with me via {productName}" : "Nasdíleno mě prostřednictvím {productName}",
|
||||
"HTML Code:" : "HTML kód:",
|
||||
@@ -71,6 +69,8 @@ OC.L10N.register(
|
||||
"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",
|
||||
"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" : "Můžete sdílet s kýmkoliv, kdo používá Nextcloud nebo jiný server či služby, kompatibilní se standardem Open Cloud Mesh (OCM)! Stačí do dialogu pro sdílení zadat jejich jejich identif. v rámci sdruženého cloudu. Má podobu person@cloud.example.com"
|
||||
"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" : "Můžete sdílet s kýmkoliv, kdo používá Nextcloud nebo jiný server či služby, kompatibilní se standardem Open Cloud Mesh (OCM)! Stačí do dialogu pro sdílení zadat jejich jejich identif. v rámci sdruženého cloudu. Má podobu person@cloud.example.com",
|
||||
"X (formerly Twitter)" : "X (dříve Twitter)",
|
||||
"formerly Twitter" : "dříve Twitter"
|
||||
},
|
||||
"nplurals=4; plural=(n == 1 && n % 1 == 0) ? 0 : (n >= 2 && n <= 4 && n % 1 == 0) ? 1: (n % 1 != 0 ) ? 2 : 3;");
|
||||
|
||||
@@ -23,14 +23,14 @@
|
||||
"Provide federated file sharing across servers" : "Poskytnout federované sdílení souborů napříč servery",
|
||||
"Confirm data upload to lookup server" : "Potvrdit nahrávání dat na vyhledávací server",
|
||||
"When enabled, all account properties (e.g. email address) with scope visibility set to \"published\", will be automatically synced and transmitted to an external system and made available in a public, global address book." : "Pokud zapnuto, veškeré vlastnosti účtu (např. e-mailová adresa) s rozsahem viditelnosti nastavené na „zveřejněné“ budou automaticky synchronizovány a přenášeny do externího systému a veřejně k dispozici v globální adresáři kontaktů.",
|
||||
"Disable upload" : "Vypnout nahrávání",
|
||||
"Enable data upload" : "Zapnout nahrávání dat",
|
||||
"Disable upload" : "Vypnout nahrávání",
|
||||
"Confirm querying lookup server" : "Potvrdit dotazování vyhledávacího serveru",
|
||||
"When enabled, the search input when creating shares will be sent to an external system that provides a public and global address book." : "Pokud zapnuto, obsah hledání při vytváření sdílení bude odeslán na externí systém, který poskytuje veřejný a globální adresář kontaktů.",
|
||||
"This is used to retrieve the federated cloud ID to make federated sharing easier." : "Toto slouží k získávání identifikátorů v rámci federovaného cloudu pro usnadnění federovaného sdílení.",
|
||||
"Moreover, email addresses of users might be sent to that system in order to verify them." : "Dále mohou být pro ověření odeslány na server e-mailové adresy uživatelů.",
|
||||
"Disable querying" : "Vypnout dotazování",
|
||||
"Enable querying" : "Zapnout dotazování",
|
||||
"Disable querying" : "Vypnout dotazování",
|
||||
"Unable to update federated files sharing config" : "Nedaří se aktualizovat nastavení federovaného sdílení souborů",
|
||||
"Adjust how people can share between servers. This includes shares between people on this server as well if they are using federated sharing." : "Upravte, jak mohou lidé sdílet mezi servery. Týká se sdílení mezi lidmi na tomto serveru a stejně tak uživatelů federovaného sdílení.",
|
||||
"Allow people on this server to send shares to other servers (this option also allows WebDAV access to public shares)" : "Umožnit lidem na tomto serveru posílat sdílení na ostatní servery (tato volba také umožňuje WebDAV přístup k veřejným sdílením)",
|
||||
@@ -53,11 +53,9 @@
|
||||
"You can share with anyone who uses a {productName} 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" : "Můžete sdílet s kýmkoliv, kdo používá {productName} nebo jiný server či služby, kompatibilní se standardem Open Cloud Mesh (OCM)! Stačí do dialogu pro sdílení zadat jejich jejich identif. v rámci sdruženého cloudu. Má podobu person@cloud.example.com",
|
||||
"Your Federated Cloud ID" : "Váš identifikátor v rámci fedorovaného cloudu",
|
||||
"Share it so your friends can share files with you:" : "Podělte se o to, aby mohli vaši přátelé s vámi mohli sdílet soubory:",
|
||||
"Facebook" : "Facebook",
|
||||
"X (formerly Twitter)" : "X (dříve Twitter)",
|
||||
"formerly Twitter" : "dříve Twitter",
|
||||
"Mastodon" : "Mastodon",
|
||||
"Bluesky" : "Bluesky",
|
||||
"Facebook" : "Facebook",
|
||||
"Mastodon" : "Mastodon",
|
||||
"Add to your website" : "Přidat na svou webovou stránku",
|
||||
"Share with me via {productName}" : "Nasdíleno mě prostřednictvím {productName}",
|
||||
"HTML Code:" : "HTML kód:",
|
||||
@@ -69,6 +67,8 @@
|
||||
"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",
|
||||
"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" : "Můžete sdílet s kýmkoliv, kdo používá Nextcloud nebo jiný server či služby, kompatibilní se standardem Open Cloud Mesh (OCM)! Stačí do dialogu pro sdílení zadat jejich jejich identif. v rámci sdruženého cloudu. Má podobu person@cloud.example.com"
|
||||
"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" : "Můžete sdílet s kýmkoliv, kdo používá Nextcloud nebo jiný server či služby, kompatibilní se standardem Open Cloud Mesh (OCM)! Stačí do dialogu pro sdílení zadat jejich jejich identif. v rámci sdruženého cloudu. Má podobu person@cloud.example.com",
|
||||
"X (formerly Twitter)" : "X (dříve Twitter)",
|
||||
"formerly Twitter" : "dříve Twitter"
|
||||
},"pluralForm" :"nplurals=4; plural=(n == 1 && n % 1 == 0) ? 0 : (n >= 2 && n <= 4 && n % 1 == 0) ? 1: (n % 1 != 0 ) ? 2 : 3;"
|
||||
}
|
||||
@@ -25,14 +25,14 @@ OC.L10N.register(
|
||||
"Provide federated file sharing across servers" : "Leverer fødereret fildeling på tværs af servere",
|
||||
"Confirm data upload to lookup server" : "Bekræft dataoverførsel til opslagsserver",
|
||||
"When enabled, all account properties (e.g. email address) with scope visibility set to \"published\", will be automatically synced and transmitted to an external system and made available in a public, global address book." : "Når det er aktiveret, så vil alle kontoegenskaber (f.eks. e-mailadresse) med omfangssynlighed indstillet til \"publiceret\", automatisk blive synkroniseret og transmitteret til et eksternt system og gjort tilgængelige i en offentlig, global adressebog.",
|
||||
"Disable upload" : "Deaktivér upload",
|
||||
"Enable data upload" : "Aktivér data upload",
|
||||
"Disable upload" : "Deaktivér upload",
|
||||
"Confirm querying lookup server" : "Bekræft forespørgsel til opslagsserver",
|
||||
"When enabled, the search input when creating shares will be sent to an external system that provides a public and global address book." : "Når det er aktiveret, vil søgeinputtet, når der oprettes delinger, blive sendt til et eksternt system, der leverer en offentlig og global adressebog.",
|
||||
"This is used to retrieve the federated cloud ID to make federated sharing easier." : "Dette bruges til at hente det fødererede cloud ID for at gøre fødereret deling nemmere.",
|
||||
"Moreover, email addresses of users might be sent to that system in order to verify them." : "Desuden kan e-mailadresser på brugere blive sendt til dette system for at verificere dem.",
|
||||
"Disable querying" : "Deaktivér forespørgsler",
|
||||
"Enable querying" : "Aktivér forespørgsler",
|
||||
"Disable querying" : "Deaktivér forespørgsler",
|
||||
"Unable to update federated files sharing config" : "Kan ikke opdatere fødereret fildelingskonfiguration",
|
||||
"Adjust how people can share between servers. This includes shares between people on this server as well if they are using federated sharing." : "Juster, hvordan brugere kan dele mellem servere. Dette inkluderer også delinger mellem brugere på denne server, hvis de bruger fødereret deling.",
|
||||
"Allow people on this server to send shares to other servers (this option also allows WebDAV access to public shares)" : "Tillad brugere på denne server at sende shares til andre servere (denne mulighed giver også WebDAV adgang til offentlige shares)",
|
||||
@@ -55,15 +55,13 @@ OC.L10N.register(
|
||||
"You can share with anyone who uses a {productName} 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" : "Du kan dele med enhver der anvender en {productName} server eller andre Open Cloud Mesh (OCM) kompatible serverer og services! Angiv blot deres fødererede Cloud ID i delings dialogen. Det ser ud som følger: person@cloud.example.com",
|
||||
"Your Federated Cloud ID" : "Dit sammenkoblings Cloud ID",
|
||||
"Share it so your friends can share files with you:" : "Del så dine venner kan dele filer med dig:",
|
||||
"Facebook" : "Facebook",
|
||||
"X (formerly Twitter)" : "X (tidligere Twitter)",
|
||||
"formerly Twitter" : "tidligere Twitter",
|
||||
"Mastodon" : "Mastodont",
|
||||
"Bluesky" : "Bluesky",
|
||||
"Facebook" : "Facebook",
|
||||
"Mastodon" : "Mastodont",
|
||||
"Add to your website" : "Tilføj til dit websted",
|
||||
"Share with me via {productName}" : "Del med mig via {productName}",
|
||||
"HTML Code:" : "HTML kode:",
|
||||
"Cancel" : "Annuller",
|
||||
"Cancel" : "Annullér",
|
||||
"Add remote share" : "Tilføj ekstern deling",
|
||||
"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}?",
|
||||
@@ -71,6 +69,8 @@ OC.L10N.register(
|
||||
"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",
|
||||
"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" : "Du kan dele med alle, der bruger en Nextcloud-server eller andre Open Cloud Mesh (OCM)-kompatible servere og tjenester! Indsæt blot deres Sammenkoblings Cloud ID i delingsdialogen. Det ligner person@cloud.example.com"
|
||||
"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" : "Du kan dele med alle, der bruger en Nextcloud-server eller andre Open Cloud Mesh (OCM)-kompatible servere og tjenester! Indsæt blot deres Sammenkoblings Cloud ID i delingsdialogen. Det ligner person@cloud.example.com",
|
||||
"X (formerly Twitter)" : "X (tidligere Twitter)",
|
||||
"formerly Twitter" : "tidligere Twitter"
|
||||
},
|
||||
"nplurals=2; plural=(n != 1);");
|
||||
|
||||
@@ -23,14 +23,14 @@
|
||||
"Provide federated file sharing across servers" : "Leverer fødereret fildeling på tværs af servere",
|
||||
"Confirm data upload to lookup server" : "Bekræft dataoverførsel til opslagsserver",
|
||||
"When enabled, all account properties (e.g. email address) with scope visibility set to \"published\", will be automatically synced and transmitted to an external system and made available in a public, global address book." : "Når det er aktiveret, så vil alle kontoegenskaber (f.eks. e-mailadresse) med omfangssynlighed indstillet til \"publiceret\", automatisk blive synkroniseret og transmitteret til et eksternt system og gjort tilgængelige i en offentlig, global adressebog.",
|
||||
"Disable upload" : "Deaktivér upload",
|
||||
"Enable data upload" : "Aktivér data upload",
|
||||
"Disable upload" : "Deaktivér upload",
|
||||
"Confirm querying lookup server" : "Bekræft forespørgsel til opslagsserver",
|
||||
"When enabled, the search input when creating shares will be sent to an external system that provides a public and global address book." : "Når det er aktiveret, vil søgeinputtet, når der oprettes delinger, blive sendt til et eksternt system, der leverer en offentlig og global adressebog.",
|
||||
"This is used to retrieve the federated cloud ID to make federated sharing easier." : "Dette bruges til at hente det fødererede cloud ID for at gøre fødereret deling nemmere.",
|
||||
"Moreover, email addresses of users might be sent to that system in order to verify them." : "Desuden kan e-mailadresser på brugere blive sendt til dette system for at verificere dem.",
|
||||
"Disable querying" : "Deaktivér forespørgsler",
|
||||
"Enable querying" : "Aktivér forespørgsler",
|
||||
"Disable querying" : "Deaktivér forespørgsler",
|
||||
"Unable to update federated files sharing config" : "Kan ikke opdatere fødereret fildelingskonfiguration",
|
||||
"Adjust how people can share between servers. This includes shares between people on this server as well if they are using federated sharing." : "Juster, hvordan brugere kan dele mellem servere. Dette inkluderer også delinger mellem brugere på denne server, hvis de bruger fødereret deling.",
|
||||
"Allow people on this server to send shares to other servers (this option also allows WebDAV access to public shares)" : "Tillad brugere på denne server at sende shares til andre servere (denne mulighed giver også WebDAV adgang til offentlige shares)",
|
||||
@@ -53,15 +53,13 @@
|
||||
"You can share with anyone who uses a {productName} 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" : "Du kan dele med enhver der anvender en {productName} server eller andre Open Cloud Mesh (OCM) kompatible serverer og services! Angiv blot deres fødererede Cloud ID i delings dialogen. Det ser ud som følger: person@cloud.example.com",
|
||||
"Your Federated Cloud ID" : "Dit sammenkoblings Cloud ID",
|
||||
"Share it so your friends can share files with you:" : "Del så dine venner kan dele filer med dig:",
|
||||
"Facebook" : "Facebook",
|
||||
"X (formerly Twitter)" : "X (tidligere Twitter)",
|
||||
"formerly Twitter" : "tidligere Twitter",
|
||||
"Mastodon" : "Mastodont",
|
||||
"Bluesky" : "Bluesky",
|
||||
"Facebook" : "Facebook",
|
||||
"Mastodon" : "Mastodont",
|
||||
"Add to your website" : "Tilføj til dit websted",
|
||||
"Share with me via {productName}" : "Del med mig via {productName}",
|
||||
"HTML Code:" : "HTML kode:",
|
||||
"Cancel" : "Annuller",
|
||||
"Cancel" : "Annullér",
|
||||
"Add remote share" : "Tilføj ekstern deling",
|
||||
"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}?",
|
||||
@@ -69,6 +67,8 @@
|
||||
"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",
|
||||
"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" : "Du kan dele med alle, der bruger en Nextcloud-server eller andre Open Cloud Mesh (OCM)-kompatible servere og tjenester! Indsæt blot deres Sammenkoblings Cloud ID i delingsdialogen. Det ligner person@cloud.example.com"
|
||||
"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" : "Du kan dele med alle, der bruger en Nextcloud-server eller andre Open Cloud Mesh (OCM)-kompatible servere og tjenester! Indsæt blot deres Sammenkoblings Cloud ID i delingsdialogen. Det ligner person@cloud.example.com",
|
||||
"X (formerly Twitter)" : "X (tidligere Twitter)",
|
||||
"formerly Twitter" : "tidligere Twitter"
|
||||
},"pluralForm" :"nplurals=2; plural=(n != 1);"
|
||||
}
|
||||
@@ -25,14 +25,14 @@ OC.L10N.register(
|
||||
"Provide federated file sharing across servers" : "Bietet Federated Datei-Freigaben über Servergrenzen hinweg",
|
||||
"Confirm data upload to lookup server" : "Hochladen der Daten zum Lookup-Server bestätigen",
|
||||
"When enabled, all account properties (e.g. email address) with scope visibility set to \"published\", will be automatically synced and transmitted to an external system and made available in a public, global address book." : "Wenn diese Option aktiviert ist, werden alle Kontoeigenschaften (z. B. E-Mail-Adresse) mit der auf \"veröffentlicht\" eingestellten Bereichssichtbarkeit automatisch synchronisiert und an ein externes System übertragen sowie in einem öffentlichen, globalen Adressbuch verfügbar gemacht.",
|
||||
"Disable upload" : "Hochladen deaktivieren",
|
||||
"Enable data upload" : "Hochladen von Daten aktivieren",
|
||||
"Disable upload" : "Hochladen deaktivieren",
|
||||
"Confirm querying lookup server" : "Abfrage des Lookup-Servers bestätigen",
|
||||
"When enabled, the search input when creating shares will be sent to an external system that provides a public and global address book." : "Wenn aktiviert, wird die Sucheingabe beim Erstellen von Freigaben an ein externes System gesendet, das ein öffentliches und globales Adressbuch bereitstellt.",
|
||||
"This is used to retrieve the federated cloud ID to make federated sharing easier." : "Dies wird verwendet, um die federierte Cloud-ID abzurufen und so die federierte Freigabe zu vereinfachen.",
|
||||
"Moreover, email addresses of users might be sent to that system in order to verify them." : "Darüber hinaus können E-Mail-Adressen von Benutzern an dieses System gesendet werden, um sie zu überprüfen.",
|
||||
"Disable querying" : "Abfragen deaktivieren",
|
||||
"Enable querying" : "Abfragen aktivieren",
|
||||
"Disable querying" : "Abfragen deaktivieren",
|
||||
"Unable to update federated files sharing config" : "Einstellungen zum federierten Teilen konnten nicht aktualisiert werden",
|
||||
"Adjust how people can share between servers. This includes shares between people on this server as well if they are using federated sharing." : "Legt fest, wie Personen zwischen Servern teilen können. Dies gilt auch für Freigaben zwischen Personen auf diesem Server, wenn sie Federated-Sharing verwenden.",
|
||||
"Allow people on this server to send shares to other servers (this option also allows WebDAV access to public shares)" : "Personen auf diesem Server das Senden von Freigaben an andere Server erlauben (Diese Option ermöglicht auch den WebDAV-Zugriff auf öffentliche Freigaben)",
|
||||
@@ -55,11 +55,9 @@ OC.L10N.register(
|
||||
"You can share with anyone who uses a {productName} 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" : "Du kannst mit jedem teilen, der einen {productName}-Server oder andere Open Cloud Mesh (OCM) kompatible Server und Dienste verwendet! Gebe einfach deren Federated-Cloud-ID in den Teilen-Dialog ein. Diese sieht wie folgt aus: person@cloud.example.com",
|
||||
"Your Federated Cloud ID" : "Deine Federated-Cloud-ID",
|
||||
"Share it so your friends can share files with you:" : "Teile es, damit deine Freunde Dateien mit dir teilen können:",
|
||||
"Facebook" : "Facebook",
|
||||
"X (formerly Twitter)" : "X (früher Twitter)",
|
||||
"formerly Twitter" : "früher Twitter",
|
||||
"Mastodon" : "Mastodon",
|
||||
"Bluesky" : "Bluesky",
|
||||
"Facebook" : "Facebook",
|
||||
"Mastodon" : "Mastodon",
|
||||
"Add to your website" : "Zu deiner Webseite hinzufügen",
|
||||
"Share with me via {productName}" : "Mit mir über {productName} teilen",
|
||||
"HTML Code:" : "HTML-Code:",
|
||||
@@ -71,6 +69,8 @@ OC.L10N.register(
|
||||
"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",
|
||||
"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" : "Du kannst mit jedem teilen, der einen Nextcloud-Server oder andere Open Cloud Mesh (OCM) kompatible Server und Dienste verwendet! Gib einfach deren Federated-Cloud-ID in den Teilen-Dialog ein. Diese sieht wie folgt aus: person@cloud.example.com"
|
||||
"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" : "Du kannst mit jedem teilen, der einen Nextcloud-Server oder andere Open Cloud Mesh (OCM) kompatible Server und Dienste verwendet! Gib einfach deren Federated-Cloud-ID in den Teilen-Dialog ein. Diese sieht wie folgt aus: person@cloud.example.com",
|
||||
"X (formerly Twitter)" : "X (früher Twitter)",
|
||||
"formerly Twitter" : "früher Twitter"
|
||||
},
|
||||
"nplurals=2; plural=(n != 1);");
|
||||
|
||||
@@ -23,14 +23,14 @@
|
||||
"Provide federated file sharing across servers" : "Bietet Federated Datei-Freigaben über Servergrenzen hinweg",
|
||||
"Confirm data upload to lookup server" : "Hochladen der Daten zum Lookup-Server bestätigen",
|
||||
"When enabled, all account properties (e.g. email address) with scope visibility set to \"published\", will be automatically synced and transmitted to an external system and made available in a public, global address book." : "Wenn diese Option aktiviert ist, werden alle Kontoeigenschaften (z. B. E-Mail-Adresse) mit der auf \"veröffentlicht\" eingestellten Bereichssichtbarkeit automatisch synchronisiert und an ein externes System übertragen sowie in einem öffentlichen, globalen Adressbuch verfügbar gemacht.",
|
||||
"Disable upload" : "Hochladen deaktivieren",
|
||||
"Enable data upload" : "Hochladen von Daten aktivieren",
|
||||
"Disable upload" : "Hochladen deaktivieren",
|
||||
"Confirm querying lookup server" : "Abfrage des Lookup-Servers bestätigen",
|
||||
"When enabled, the search input when creating shares will be sent to an external system that provides a public and global address book." : "Wenn aktiviert, wird die Sucheingabe beim Erstellen von Freigaben an ein externes System gesendet, das ein öffentliches und globales Adressbuch bereitstellt.",
|
||||
"This is used to retrieve the federated cloud ID to make federated sharing easier." : "Dies wird verwendet, um die federierte Cloud-ID abzurufen und so die federierte Freigabe zu vereinfachen.",
|
||||
"Moreover, email addresses of users might be sent to that system in order to verify them." : "Darüber hinaus können E-Mail-Adressen von Benutzern an dieses System gesendet werden, um sie zu überprüfen.",
|
||||
"Disable querying" : "Abfragen deaktivieren",
|
||||
"Enable querying" : "Abfragen aktivieren",
|
||||
"Disable querying" : "Abfragen deaktivieren",
|
||||
"Unable to update federated files sharing config" : "Einstellungen zum federierten Teilen konnten nicht aktualisiert werden",
|
||||
"Adjust how people can share between servers. This includes shares between people on this server as well if they are using federated sharing." : "Legt fest, wie Personen zwischen Servern teilen können. Dies gilt auch für Freigaben zwischen Personen auf diesem Server, wenn sie Federated-Sharing verwenden.",
|
||||
"Allow people on this server to send shares to other servers (this option also allows WebDAV access to public shares)" : "Personen auf diesem Server das Senden von Freigaben an andere Server erlauben (Diese Option ermöglicht auch den WebDAV-Zugriff auf öffentliche Freigaben)",
|
||||
@@ -53,11 +53,9 @@
|
||||
"You can share with anyone who uses a {productName} 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" : "Du kannst mit jedem teilen, der einen {productName}-Server oder andere Open Cloud Mesh (OCM) kompatible Server und Dienste verwendet! Gebe einfach deren Federated-Cloud-ID in den Teilen-Dialog ein. Diese sieht wie folgt aus: person@cloud.example.com",
|
||||
"Your Federated Cloud ID" : "Deine Federated-Cloud-ID",
|
||||
"Share it so your friends can share files with you:" : "Teile es, damit deine Freunde Dateien mit dir teilen können:",
|
||||
"Facebook" : "Facebook",
|
||||
"X (formerly Twitter)" : "X (früher Twitter)",
|
||||
"formerly Twitter" : "früher Twitter",
|
||||
"Mastodon" : "Mastodon",
|
||||
"Bluesky" : "Bluesky",
|
||||
"Facebook" : "Facebook",
|
||||
"Mastodon" : "Mastodon",
|
||||
"Add to your website" : "Zu deiner Webseite hinzufügen",
|
||||
"Share with me via {productName}" : "Mit mir über {productName} teilen",
|
||||
"HTML Code:" : "HTML-Code:",
|
||||
@@ -69,6 +67,8 @@
|
||||
"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",
|
||||
"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" : "Du kannst mit jedem teilen, der einen Nextcloud-Server oder andere Open Cloud Mesh (OCM) kompatible Server und Dienste verwendet! Gib einfach deren Federated-Cloud-ID in den Teilen-Dialog ein. Diese sieht wie folgt aus: person@cloud.example.com"
|
||||
"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" : "Du kannst mit jedem teilen, der einen Nextcloud-Server oder andere Open Cloud Mesh (OCM) kompatible Server und Dienste verwendet! Gib einfach deren Federated-Cloud-ID in den Teilen-Dialog ein. Diese sieht wie folgt aus: person@cloud.example.com",
|
||||
"X (formerly Twitter)" : "X (früher Twitter)",
|
||||
"formerly Twitter" : "früher Twitter"
|
||||
},"pluralForm" :"nplurals=2; plural=(n != 1);"
|
||||
}
|
||||
@@ -25,14 +25,14 @@ OC.L10N.register(
|
||||
"Provide federated file sharing across servers" : "Bietet Cloud-übergreifende Datei-Freigaben",
|
||||
"Confirm data upload to lookup server" : "Datenupload zum Lookup-Server bestätigen",
|
||||
"When enabled, all account properties (e.g. email address) with scope visibility set to \"published\", will be automatically synced and transmitted to an external system and made available in a public, global address book." : "Wenn diese Option aktiviert ist, werden alle Kontoeigenschaften (z. B. E-Mail-Adresse) mit der auf \"veröffentlicht\" eingestellten Bereichssichtbarkeit automatisch synchronisiert und an ein externes System übertragen sowie in einem öffentlichen, globalen Adressbuch verfügbar gemacht.",
|
||||
"Disable upload" : "Hochladen deaktivieren",
|
||||
"Enable data upload" : "Datenhochladen aktivieren",
|
||||
"Disable upload" : "Hochladen deaktivieren",
|
||||
"Confirm querying lookup server" : "Abfrage des Lookup-Servers bestätigen",
|
||||
"When enabled, the search input when creating shares will be sent to an external system that provides a public and global address book." : "Wenn aktiviert, wird die Sucheingabe beim Erstellen von Freigaben an ein externes System gesendet, das ein öffentliches und globales Adressbuch bereitstellt.",
|
||||
"This is used to retrieve the federated cloud ID to make federated sharing easier." : "Dies wird verwendet, um die Federated-Cloud-ID abzurufen und so die federierte Freigabe zu vereinfachen.",
|
||||
"Moreover, email addresses of users might be sent to that system in order to verify them." : "Darüber hinaus können E-Mail-Adressen von Benutzern an dieses System gesendet werden, um sie zu überprüfen.",
|
||||
"Disable querying" : "Abfragen deaktivieren",
|
||||
"Enable querying" : "Abfragen aktivieren",
|
||||
"Disable querying" : "Abfragen deaktivieren",
|
||||
"Unable to update federated files sharing config" : "Einstellungen zum Federated-Teilen konnten nicht aktualisiert werden",
|
||||
"Adjust how people can share between servers. This includes shares between people on this server as well if they are using federated sharing." : "Legt fest, wie Personen zwischen Servern teilen können. Dies gilt auch für Freigaben zwischen Personen auf diesem Server, wenn sie Federated-Sharing verwenden.",
|
||||
"Allow people on this server to send shares to other servers (this option also allows WebDAV access to public shares)" : "Personen auf diesem Server das Senden von Freigaben an andere Server erlauben (Diese Option ermöglicht auch den WebDAV-Zugriff auf öffentliche Freigaben)",
|
||||
@@ -55,11 +55,9 @@ OC.L10N.register(
|
||||
"You can share with anyone who uses a {productName} 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" : "Sie können mit jedem teilen, der einen {productName}-Server oder andere Open Cloud Mesh (OCM) kompatible Server und Dienste verwendet! Geben Sie einfach deren Federated-Cloud-ID in den Teilen-Dialog ein. Diese sieht wie folgt aus: person@cloud.example.com",
|
||||
"Your Federated Cloud ID" : "Ihre Federated-Cloud-ID",
|
||||
"Share it so your friends can share files with you:" : "Teilen Sie es, damit Ihre Freunde Dateien mit Ihnen teilen können:",
|
||||
"Facebook" : "Facebook",
|
||||
"X (formerly Twitter)" : "X (früher Twitter)",
|
||||
"formerly Twitter" : "früher Twitter",
|
||||
"Mastodon" : "Mastodon",
|
||||
"Bluesky" : "Bluesky",
|
||||
"Facebook" : "Facebook",
|
||||
"Mastodon" : "Mastodon",
|
||||
"Add to your website" : "Zu Ihrer Webseite hinzufügen",
|
||||
"Share with me via {productName}" : "Mit mir über {productName} teilen",
|
||||
"HTML Code:" : "HTML-Code:",
|
||||
@@ -71,6 +69,8 @@ OC.L10N.register(
|
||||
"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",
|
||||
"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" : "Sie können mit jedem teilen, der einen Nextcloud-Server oder andere Open Cloud Mesh (OCM) kompatible Server und Dienste verwendet! Geben Sie einfach deren Federated-Cloud-ID in den Teilen-Dialog ein. Diese sieht wie folgt aus: person@cloud.example.com"
|
||||
"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" : "Sie können mit jedem teilen, der einen Nextcloud-Server oder andere Open Cloud Mesh (OCM) kompatible Server und Dienste verwendet! Geben Sie einfach deren Federated-Cloud-ID in den Teilen-Dialog ein. Diese sieht wie folgt aus: person@cloud.example.com",
|
||||
"X (formerly Twitter)" : "X (früher Twitter)",
|
||||
"formerly Twitter" : "früher Twitter"
|
||||
},
|
||||
"nplurals=2; plural=(n != 1);");
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user