Compare commits
277 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| edbb1e2821 | |||
| 047d464240 | |||
| 46b6185202 | |||
| d3ce30b46a | |||
| c2067e1889 | |||
| 1d33bbbda7 | |||
| 55268455a6 | |||
| a5c4640be7 | |||
| 63e1a49c0a | |||
| f9ae23b064 | |||
| b0294cf397 | |||
| 41c9b48346 | |||
| 28449695f7 | |||
| 57e54fd867 | |||
| 5aba60adab | |||
| a6d1fa1fa6 | |||
| 06f35ec4f8 | |||
| 16d3cee3a2 | |||
| 48d6d8df2b | |||
| 252449023f | |||
| 0f4d6b8027 | |||
| 88bb82ccfe | |||
| 560df4a73c | |||
| f2907f133c | |||
| b6d22f384b | |||
| 867ef52cfb | |||
| e9bba6a610 | |||
| 0b37d37884 | |||
| b0df06d533 | |||
| 00a5c028a4 | |||
| a32168b01e | |||
| fd5108b68b | |||
| c89939fbd6 | |||
| 503fad2b16 | |||
| e14d97b95a | |||
| b66292a0d4 | |||
| 16f4ff98e4 | |||
| 9c8f80102a | |||
| b0e99d0293 | |||
| 3a80443508 | |||
| 4eada2d804 | |||
| 672a29aa0c | |||
| 5f4a5d8c67 | |||
| c953ad9bcd | |||
| 7ceddb3ffb | |||
| 2d486c5629 | |||
| e454cc6765 | |||
| 3693cbeaf9 | |||
| e40d6b1c6b | |||
| bf71461bee | |||
| 045a41625a | |||
| 3ec7bd2ee1 | |||
| 4134df9eda | |||
| 91946d549f | |||
| 2d2139ab94 | |||
| 35f24bcd38 | |||
| d92c2de741 | |||
| 931d15e476 | |||
| 935c2416b5 | |||
| 9dc1d6372f | |||
| f06133bf13 | |||
| 891ec7bed5 | |||
| 53ac6d62b5 | |||
| b812040314 | |||
| d4416f10fe | |||
| eebcc7b5bb | |||
| 88b6c54d6b | |||
| ee65e8e79c | |||
| 15f1ec22f4 | |||
| a04d6d6de2 | |||
| ba2ddc1392 | |||
| 72d825a7dc | |||
| 096bff5fed | |||
| e0e37fcac4 | |||
| eec2e93d3a | |||
| f6dc6ec3e3 | |||
| 66c1e0ab61 | |||
| 93d6087eb6 | |||
| 2007491e11 | |||
| 0263a7f093 | |||
| d16a7b3653 | |||
| a69d56d1b7 | |||
| eb4194a973 | |||
| b1e71ae646 | |||
| 161b1098c0 | |||
| cf1c70ca62 | |||
| e9ec5026a6 | |||
| 1439a2caab | |||
| f9f35151c0 | |||
| 7ed3134160 | |||
| eb6f78e26d | |||
| e964351389 | |||
| c56938f6a9 | |||
| 8443c71d3e | |||
| b91dfee54a | |||
| 0cd375532d | |||
| 2391d58eb0 | |||
| 44e7082e5a | |||
| fee431f9c0 | |||
| 0f91aec1bd | |||
| bf83faf7cd | |||
| a793dac97c | |||
| d6842d720e | |||
| 742f8717e3 | |||
| acb9698012 | |||
| ac4c74156a | |||
| e00c98cf67 | |||
| 70d051f602 | |||
| bd4899fc42 | |||
| af1d11434b | |||
| 0578b0dd9c | |||
| 178517847c | |||
| 50c163f9a9 | |||
| 4cc2bb26ca | |||
| 202124cd28 | |||
| 6fb0072a00 | |||
| 8c5fb9c751 | |||
| eb07c52f95 | |||
| bb1b12b218 | |||
| f2574fdf34 | |||
| cd15bb0679 | |||
| 8a703c95ec | |||
| b80816dfb7 | |||
| 286d0d23a3 | |||
| c524562c88 | |||
| 7ab1c0f212 | |||
| 347c509308 | |||
| 0a5f75e719 | |||
| bb1d814e04 | |||
| 7ef66a6199 | |||
| 85c0dcf136 | |||
| f6c839d125 | |||
| 4d6959da27 | |||
| 3d4774edb6 | |||
| e38f96af72 | |||
| 0e66ed2233 | |||
| 95d7b5608b | |||
| e5c1d80a00 | |||
| 69f2c17675 | |||
| 2f29ad8ff8 | |||
| d35773b99a | |||
| 55fb2f395e | |||
| 3da621d7fb | |||
| edd37d349b | |||
| 9741f5f17d | |||
| 89a20598e5 | |||
| 3d18cd7cc5 | |||
| fdf200e343 | |||
| 5e6c8b6134 | |||
| ce99b0f7ed | |||
| 47b314067c | |||
| 4722f56778 | |||
| 0fc06b3b87 | |||
| f216bf5798 | |||
| d21351701a | |||
| 6c04307e13 | |||
| a4f396e648 | |||
| 5c47ce9b40 | |||
| e8d4d435ed | |||
| 615d343d96 | |||
| 83fbc64c99 | |||
| 4a9e04962c | |||
| feaebeb97e | |||
| bc5771b0ff | |||
| 63eb9679c2 | |||
| 98cb8b6155 | |||
| 6e5baa6928 | |||
| a096c89c66 | |||
| 6563214204 | |||
| 2ddf73f89f | |||
| fe9e43c165 | |||
| 1a5679b176 | |||
| 7b7d74fda2 | |||
| f075051f4a | |||
| 0e361550f1 | |||
| 38644873f2 | |||
| 643a815557 | |||
| c73b85aecb | |||
| 4a284f61e6 | |||
| e088473929 | |||
| 29b47c93ab | |||
| 935cd2910f | |||
| 422bca31bf | |||
| adce834b4f | |||
| 7da7f50203 | |||
| 8c01737a63 | |||
| e0c282d531 | |||
| d65aa0b7c3 | |||
| 543b46f3aa | |||
| 99a1150ec2 | |||
| a3cc9754f8 | |||
| 9cd337bebe | |||
| 83810aec99 | |||
| c373b8e614 | |||
| 39ff32a33b | |||
| 566e598a4e | |||
| 0469f57a3a | |||
| 97c09753c3 | |||
| 431a37e71e | |||
| 95b3bd98d9 | |||
| f3778bc9c7 | |||
| f12cecb684 | |||
| 53a67966c0 | |||
| 7efdfd161b | |||
| eb08dddcf5 | |||
| 823d0dcb7c | |||
| 36f4423887 | |||
| f70f7be416 | |||
| e586ca0071 | |||
| b4c7dcbf58 | |||
| 35e315bd91 | |||
| 21fff5160a | |||
| 5fd4b9fe52 | |||
| 6a2e9814af | |||
| 79600b0c9f | |||
| 38f1063be7 | |||
| 878cb525a3 | |||
| 0b36e05965 | |||
| 1081e32e55 | |||
| 9770eeae66 | |||
| caf1c669f1 | |||
| ec60d74b70 | |||
| 06fed8d96b | |||
| 481bf459b8 | |||
| db9f03c1bb | |||
| 2f57e9f520 | |||
| 559c04662e | |||
| 639f8ec974 | |||
| 4d6d4bd579 | |||
| 50e85a1622 | |||
| 85ab6a683a | |||
| 4bf784459d | |||
| 1c299fef9c | |||
| 6b36b15c2f | |||
| fa26f4f6ab | |||
| c353e0bc3a | |||
| 6a56f766cf | |||
| 1292b552a3 | |||
| ddfd854d76 | |||
| c1f2108c4b | |||
| 2909f66d47 | |||
| daf1ee91c1 | |||
| 34558b1f9a | |||
| 8b17197245 | |||
| 4f23d5e49d | |||
| cc45cf6138 | |||
| c10f35333e | |||
| b0c02b91b4 | |||
| 762ae4520a | |||
| 6eddda147b | |||
| 6c9418880f | |||
| 9c10593021 | |||
| 3b70d0fcf8 | |||
| c49665f5cd | |||
| 3e25c28d68 | |||
| 1dbaf178c3 | |||
| 3c7124d670 | |||
| c801d63e5a | |||
| da6bf8b116 | |||
| cfe19dbb5a | |||
| 70607a0d11 | |||
| 9a26169323 | |||
| e56e42e7e7 | |||
| c96ece0bcb | |||
| fb3f9fe2de | |||
| 3979c493f9 | |||
| acda4ff072 | |||
| fa75c1b659 | |||
| 65e769a861 | |||
| 6e48f9fedb | |||
| 7b6078875b | |||
| 5d0d0c17e5 | |||
| 80a10068d0 | |||
| b0bf531c19 | |||
| f296e5e652 | |||
| 2e9f6c37d5 | |||
| 7d95a88958 |
@@ -165,46 +165,3 @@ updates:
|
||||
# no major updates on stable branches
|
||||
- dependency-name: "*"
|
||||
update-types: ["version-update:semver-major"]
|
||||
|
||||
# Composer dependencies for linting and testing
|
||||
- package-ecosystem: composer
|
||||
target-branch: stable31
|
||||
directories:
|
||||
- "/"
|
||||
- "/build/integration"
|
||||
- "/vendor-bin/cs-fixer"
|
||||
- "/vendor-bin/openapi-extractor"
|
||||
- "/vendor-bin/phpunit"
|
||||
- "/vendor-bin/psalm"
|
||||
schedule:
|
||||
interval: weekly
|
||||
day: saturday
|
||||
time: "04:00"
|
||||
timezone: Europe/Paris
|
||||
labels:
|
||||
- "3. to review"
|
||||
- "feature: dependencies"
|
||||
ignore:
|
||||
# only patch updates on stable branches
|
||||
- dependency-name: "*"
|
||||
update-types: ["version-update:semver-major", "version-update:semver-minor"]
|
||||
|
||||
# frontend dependencies
|
||||
- package-ecosystem: npm
|
||||
target-branch: stable31
|
||||
directory: "/"
|
||||
schedule:
|
||||
interval: weekly
|
||||
day: saturday
|
||||
time: "04:00"
|
||||
timezone: Europe/Paris
|
||||
open-pull-requests-limit: 20
|
||||
labels:
|
||||
- "3. to review"
|
||||
- "feature: dependencies"
|
||||
# Disable automatic rebasing because without a build CI will likely fail anyway
|
||||
rebase-strategy: "disabled"
|
||||
ignore:
|
||||
# no major updates on stable branches
|
||||
- dependency-name: "*"
|
||||
update-types: ["version-update:semver-major"]
|
||||
|
||||
@@ -37,13 +37,13 @@ jobs:
|
||||
persist-credentials: false
|
||||
|
||||
- name: Initialize CodeQL
|
||||
uses: github/codeql-action/init@b20883b0cd1f46c72ae0ba6d1090936928f9fa30 # v4.32.0
|
||||
uses: github/codeql-action/init@45cbd0c69e560cd9e7cd7f8c32362050c9b7ded2 # v4.32.2
|
||||
with:
|
||||
languages: ${{ matrix.language }}
|
||||
build-mode: ${{ matrix.build-mode }}
|
||||
config-file: ./.github/codeql-config.yml
|
||||
|
||||
- name: Perform CodeQL Analysis
|
||||
uses: github/codeql-action/analyze@b20883b0cd1f46c72ae0ba6d1090936928f9fa30 # v4.32.0
|
||||
uses: github/codeql-action/analyze@45cbd0c69e560cd9e7cd7f8c32362050c9b7ded2 # v4.32.2
|
||||
with:
|
||||
category: "/language:${{matrix.language}}"
|
||||
|
||||
@@ -171,7 +171,7 @@ jobs:
|
||||
run: ./node_modules/cypress/bin/cypress install
|
||||
|
||||
- name: Run ${{ matrix.containers == 'component' && 'component' || 'E2E' }} cypress tests
|
||||
uses: cypress-io/github-action@0f330ebf0d60f87608ed72f1d6232e5644aa3171 # v7.1.1
|
||||
uses: cypress-io/github-action@84d178e4bbce871e23f2ffa3085898cde0e4f0ec # v7.1.2
|
||||
with:
|
||||
# We already installed the dependencies in the init job
|
||||
install: false
|
||||
|
||||
@@ -71,7 +71,7 @@ jobs:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
|
||||
- name: Set up Python
|
||||
uses: LizardByte/actions/actions/setup_python@09a6e10dc8175f2933c20bdf35fde0a193a9c00e # v2026.129.194351
|
||||
uses: LizardByte/actions/actions/setup_python@9bf3ef783775e17fe6b8dde3585d94ec570b93c2 # v2026.212.22356
|
||||
with:
|
||||
python-version: '2.7'
|
||||
|
||||
|
||||
@@ -0,0 +1,68 @@
|
||||
# This workflow is provided via the organization template repository
|
||||
#
|
||||
# https://github.com/nextcloud/.github
|
||||
# https://docs.github.com/en/actions/learn-github-actions/sharing-workflows-with-your-organization
|
||||
#
|
||||
# SPDX-FileCopyrightText: 2026 Nextcloud GmbH and Nextcloud contributors
|
||||
# SPDX-License-Identifier: MIT
|
||||
|
||||
name: Apply rector changes
|
||||
|
||||
on:
|
||||
workflow_dispatch:
|
||||
schedule:
|
||||
# At 14:30 on Sundays
|
||||
- cron: '30 14 * * 0'
|
||||
|
||||
permissions:
|
||||
contents: read
|
||||
|
||||
jobs:
|
||||
build:
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
name: rector-apply
|
||||
|
||||
steps:
|
||||
- name: Checkout
|
||||
id: checkout
|
||||
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
||||
with:
|
||||
persist-credentials: false
|
||||
ref: ${{ github.event.repository.default_branch }}
|
||||
|
||||
- name: Get php version
|
||||
id: versions
|
||||
uses: icewind1991/nextcloud-version-matrix@58becf3b4bb6dc6cef677b15e2fd8e7d48c0908f # v1.3.1
|
||||
|
||||
- name: Set up php${{ steps.versions.outputs.php-min }}
|
||||
uses: shivammathur/setup-php@44454db4f0199b8b9685a5d763dc37cbf79108e1 # v2.36.0
|
||||
with:
|
||||
php-version: ${{ steps.versions.outputs.php-min }}
|
||||
extensions: bz2, ctype, curl, dom, fileinfo, gd, iconv, intl, json, libxml, mbstring, openssl, pcntl, posix, session, simplexml, xmlreader, xmlwriter, zip, zlib, sqlite, pdo_sqlite
|
||||
coverage: none
|
||||
ini-file: development
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
|
||||
- name: Install dependencies
|
||||
run: |
|
||||
composer remove nextcloud/ocp --dev --no-scripts
|
||||
composer i
|
||||
|
||||
- name: Rector
|
||||
run: composer run rector
|
||||
|
||||
- name: Create Pull Request
|
||||
uses: peter-evans/create-pull-request@c0f553fe549906ede9cf27b5156039d195d2ece0 # v8.1.0
|
||||
with:
|
||||
token: ${{ secrets.COMMAND_BOT_PAT }}
|
||||
commit-message: 'refactor: Apply rector changes'
|
||||
committer: GitHub <noreply@github.com>
|
||||
author: nextcloud-command <nextcloud-command@users.noreply.github.com>
|
||||
signoff: true
|
||||
branch: automated/noid/rector-changes
|
||||
title: 'Apply rector changes'
|
||||
labels: |
|
||||
technical debt
|
||||
3. to review
|
||||
@@ -88,7 +88,7 @@ jobs:
|
||||
|
||||
- name: Upload Security Analysis results to GitHub
|
||||
if: always()
|
||||
uses: github/codeql-action/upload-sarif@b20883b0cd1f46c72ae0ba6d1090936928f9fa30 # v3
|
||||
uses: github/codeql-action/upload-sarif@45cbd0c69e560cd9e7cd7f8c32362050c9b7ded2 # v3
|
||||
with:
|
||||
sarif_file: results.sarif
|
||||
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
/**
|
||||
* SPDX-FileCopyrightText: 2022 Nextcloud GmbH and Nextcloud contributors
|
||||
* SPDX-License-Identifier: AGPL-3.0-or-later
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
OC.L10N.register(
|
||||
"cloud_federation_api",
|
||||
{
|
||||
"Cloud Federation API" : "Asl faylni o'chirishda kutilmagan xatolik yuz berdi.",
|
||||
"Cloud Federation API" : "Jamg'armaning bulutli APIsi",
|
||||
"Enable clouds to communicate with each other and exchange data" : "Bulutlar bir-biri bilan aloqa qilish va ma'lumot almashish imkonini beradi",
|
||||
"The Cloud Federation API enables various Nextcloud instances to communicate with each other and to exchange data." : "Cloud Federation API turli xil Nextcloud misollariga bir-biri bilan muloqot qilish va ma'lumotlarni almashish imkonini beradi."
|
||||
"The Cloud Federation API enables various Nextcloud instances to communicate with each other and to exchange data." : "Cloud Jamoada API turli xil Nextcloud misollariga bir-biri bilan muloqot qilish va ma'lumotlarni almashish imkonini beradi."
|
||||
},
|
||||
"nplurals=1; plural=0;");
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{ "translations": {
|
||||
"Cloud Federation API" : "Asl faylni o'chirishda kutilmagan xatolik yuz berdi.",
|
||||
"Cloud Federation API" : "Jamg'armaning bulutli APIsi",
|
||||
"Enable clouds to communicate with each other and exchange data" : "Bulutlar bir-biri bilan aloqa qilish va ma'lumot almashish imkonini beradi",
|
||||
"The Cloud Federation API enables various Nextcloud instances to communicate with each other and to exchange data." : "Cloud Federation API turli xil Nextcloud misollariga bir-biri bilan muloqot qilish va ma'lumotlarni almashish imkonini beradi."
|
||||
"The Cloud Federation API enables various Nextcloud instances to communicate with each other and to exchange data." : "Cloud Jamoada API turli xil Nextcloud misollariga bir-biri bilan muloqot qilish va ma'lumotlarni almashish imkonini beradi."
|
||||
},"pluralForm" :"nplurals=1; plural=0;"
|
||||
}
|
||||
@@ -1,5 +1,7 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
/**
|
||||
* SPDX-FileCopyrightText: 2017-2024 Nextcloud GmbH and Nextcloud contributors
|
||||
* SPDX-FileCopyrightText: 2016 ownCloud, Inc.
|
||||
|
||||
@@ -17,6 +17,7 @@ OC.L10N.register(
|
||||
"Delete comment" : "Zmazať komentár",
|
||||
"Cancel edit" : "Zrušiť upravovanie",
|
||||
"New comment" : "Nový komentár",
|
||||
"Write a comment …" : "Napísať komentár ...",
|
||||
"Post comment" : "Odoslať komentár",
|
||||
"@ for mentions, : for emoji, / for smart picker" : "@ pre spomienky, : pre emotikony, / pre inteligentný výber",
|
||||
"Could not reload comments" : "Nepodarilo sa obnoviť komentáre",
|
||||
|
||||
@@ -15,6 +15,7 @@
|
||||
"Delete comment" : "Zmazať komentár",
|
||||
"Cancel edit" : "Zrušiť upravovanie",
|
||||
"New comment" : "Nový komentár",
|
||||
"Write a comment …" : "Napísať komentár ...",
|
||||
"Post comment" : "Odoslať komentár",
|
||||
"@ for mentions, : for emoji, / for smart picker" : "@ pre spomienky, : pre emotikony, / pre inteligentný výber",
|
||||
"Could not reload comments" : "Nepodarilo sa obnoviť komentáre",
|
||||
|
||||
@@ -0,0 +1,37 @@
|
||||
OC.L10N.register(
|
||||
"comments",
|
||||
{
|
||||
"Comments" : "Izohlar",
|
||||
"You commented" : "Siz fikr bildirgansiz",
|
||||
"{author} commented" : "{author} izoh qoldirdi",
|
||||
"You commented on %1$s" : "Siz %1$s haqida fikr bildirdingiz",
|
||||
"You commented on {file}" : "Siz {file} ga izoh qoldirdingiz",
|
||||
"%1$s commented on %2$s" : "%1$s %2$s haqida fikr bildirdi",
|
||||
"{author} commented on {file}" : "{author} {file} ga izoh qoldirdi",
|
||||
"<strong>Comments</strong> for files" : "Fayllar uchun <strong>Izohlar</strong>",
|
||||
"Files" : "Fayllar",
|
||||
"You were mentioned on \"{file}\", in a comment by an account that has since been deleted" : "Siz \"{file}\"da, keyinchalik o'chirilgan hisob tomonidan izohda tilga olingansiz",
|
||||
"{user} mentioned you in a comment on \"{file}\"" : "{user} sizni \"{file}\" dagi izohda tilga oldi",
|
||||
"Files app plugin to add comments to files" : "Fayllarga izohlar qo'shish ilova plagini",
|
||||
"Edit comment" : "Izohni tahrirlash",
|
||||
"Delete comment" : "Izohni o'chirish",
|
||||
"Cancel edit" : "Tahrirni bekor qilish",
|
||||
"New comment" : "Yangi izoh",
|
||||
"Write a comment …" : "Fikr yozing…",
|
||||
"Post comment" : "Fikr qoldirish",
|
||||
"@ for mentions, : for emoji, / for smart picker" : "@ eslatmalar uchun, : emojilar uchun, / aqlli tanlovclar uchun",
|
||||
"Could not reload comments" : "Izohlarni qayta yuklab bo'lmadi",
|
||||
"Failed to mark comments as read" : "Izohlarni o'qilgan deb belgilashda xatolik yuz berdi",
|
||||
"Unable to load the comments list" : "Izohlar ro'yxatini yuklab bo'lmadi",
|
||||
"No comments yet, start the conversation!" : "Hali izohlar yo'q, suhbatni boshlang!",
|
||||
"No more messages" : "Boshqa xabarlar yo'q",
|
||||
"Retry" : "Qayta urinish",
|
||||
"_1 new comment_::_{unread} new comments_" : ["{unread} ta yangi izoh"],
|
||||
"Comment" : "Izoh",
|
||||
"An error occurred while trying to edit the comment" : "Izohni tahrirlashda xatolik yuz berdi",
|
||||
"Comment deleted" : "Izoh o'chirildi",
|
||||
"An error occurred while trying to delete the comment" : "Izohni o'chirishda xatolik yuz berdi",
|
||||
"An error occurred while trying to create the comment" : "Izoh yaratishda xatolik yuz berdi",
|
||||
"Write a comment …" : "Izoh yozing..."
|
||||
},
|
||||
"nplurals=1; plural=0;");
|
||||
@@ -0,0 +1,35 @@
|
||||
{ "translations": {
|
||||
"Comments" : "Izohlar",
|
||||
"You commented" : "Siz fikr bildirgansiz",
|
||||
"{author} commented" : "{author} izoh qoldirdi",
|
||||
"You commented on %1$s" : "Siz %1$s haqida fikr bildirdingiz",
|
||||
"You commented on {file}" : "Siz {file} ga izoh qoldirdingiz",
|
||||
"%1$s commented on %2$s" : "%1$s %2$s haqida fikr bildirdi",
|
||||
"{author} commented on {file}" : "{author} {file} ga izoh qoldirdi",
|
||||
"<strong>Comments</strong> for files" : "Fayllar uchun <strong>Izohlar</strong>",
|
||||
"Files" : "Fayllar",
|
||||
"You were mentioned on \"{file}\", in a comment by an account that has since been deleted" : "Siz \"{file}\"da, keyinchalik o'chirilgan hisob tomonidan izohda tilga olingansiz",
|
||||
"{user} mentioned you in a comment on \"{file}\"" : "{user} sizni \"{file}\" dagi izohda tilga oldi",
|
||||
"Files app plugin to add comments to files" : "Fayllarga izohlar qo'shish ilova plagini",
|
||||
"Edit comment" : "Izohni tahrirlash",
|
||||
"Delete comment" : "Izohni o'chirish",
|
||||
"Cancel edit" : "Tahrirni bekor qilish",
|
||||
"New comment" : "Yangi izoh",
|
||||
"Write a comment …" : "Fikr yozing…",
|
||||
"Post comment" : "Fikr qoldirish",
|
||||
"@ for mentions, : for emoji, / for smart picker" : "@ eslatmalar uchun, : emojilar uchun, / aqlli tanlovclar uchun",
|
||||
"Could not reload comments" : "Izohlarni qayta yuklab bo'lmadi",
|
||||
"Failed to mark comments as read" : "Izohlarni o'qilgan deb belgilashda xatolik yuz berdi",
|
||||
"Unable to load the comments list" : "Izohlar ro'yxatini yuklab bo'lmadi",
|
||||
"No comments yet, start the conversation!" : "Hali izohlar yo'q, suhbatni boshlang!",
|
||||
"No more messages" : "Boshqa xabarlar yo'q",
|
||||
"Retry" : "Qayta urinish",
|
||||
"_1 new comment_::_{unread} new comments_" : ["{unread} ta yangi izoh"],
|
||||
"Comment" : "Izoh",
|
||||
"An error occurred while trying to edit the comment" : "Izohni tahrirlashda xatolik yuz berdi",
|
||||
"Comment deleted" : "Izoh o'chirildi",
|
||||
"An error occurred while trying to delete the comment" : "Izohni o'chirishda xatolik yuz berdi",
|
||||
"An error occurred while trying to create the comment" : "Izoh yaratishda xatolik yuz berdi",
|
||||
"Write a comment …" : "Izoh yozing..."
|
||||
},"pluralForm" :"nplurals=1; plural=0;"
|
||||
}
|
||||
@@ -1,5 +1,7 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
/**
|
||||
* SPDX-FileCopyrightText: 2016 Nextcloud GmbH and Nextcloud contributors
|
||||
* SPDX-License-Identifier: AGPL-3.0-or-later
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
/**
|
||||
* SPDX-FileCopyrightText: 2016 Nextcloud GmbH and Nextcloud contributors
|
||||
* SPDX-License-Identifier: AGPL-3.0-or-later
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
/**
|
||||
* SPDX-FileCopyrightText: 2016 Nextcloud GmbH and Nextcloud contributors
|
||||
* SPDX-License-Identifier: AGPL-3.0-or-later
|
||||
|
||||
@@ -9,6 +9,7 @@ import CommentProcessingSvg from '@mdi/svg/svg/comment-processing.svg?raw'
|
||||
import { getSidebar } from '@nextcloud/files'
|
||||
import { n, t } from '@nextcloud/l10n'
|
||||
import logger from '../logger.js'
|
||||
import { isUsingActivityIntegration } from '../utils/activity.js'
|
||||
|
||||
export const action: IFileAction = {
|
||||
id: 'comments-unread',
|
||||
@@ -38,7 +39,13 @@ export const action: IFileAction = {
|
||||
|
||||
try {
|
||||
const sidebar = getSidebar()
|
||||
sidebar.open(nodes[0], 'comments')
|
||||
const sidebarTabId = isUsingActivityIntegration() ? 'activity' : 'comments'
|
||||
if (sidebar.isOpen && sidebar.node?.source === nodes[0].source) {
|
||||
logger.debug('Sidebar already open for this node, just activating comments tab')
|
||||
sidebar.setActiveTab(sidebarTabId)
|
||||
return null
|
||||
}
|
||||
sidebar.open(nodes[0], sidebarTabId)
|
||||
return null
|
||||
} catch (error) {
|
||||
logger.error('Error while opening sidebar', { error })
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
/**
|
||||
/*!
|
||||
* SPDX-FileCopyrightText: 2020 Nextcloud GmbH and Nextcloud contributors
|
||||
* SPDX-License-Identifier: AGPL-3.0-or-later
|
||||
*/
|
||||
@@ -6,18 +6,18 @@
|
||||
import MessageReplyText from '@mdi/svg/svg/message-reply-text.svg?raw'
|
||||
import { getCSPNonce } from '@nextcloud/auth'
|
||||
import { registerSidebarTab } from '@nextcloud/files'
|
||||
import { loadState } from '@nextcloud/initial-state'
|
||||
import { t } from '@nextcloud/l10n'
|
||||
import wrap from '@vue/web-component-wrapper'
|
||||
import { createPinia, PiniaVuePlugin } from 'pinia'
|
||||
import Vue from 'vue'
|
||||
import { registerCommentsPlugins } from './comments-activity-tab.ts'
|
||||
import { isUsingActivityIntegration } from './utils/activity.ts'
|
||||
|
||||
__webpack_nonce__ = getCSPNonce()
|
||||
|
||||
const tagName = 'comments_files-sidebar-tab'
|
||||
|
||||
if (loadState('comments', 'activityEnabled', false) && OCA?.Activity?.registerSidebarAction !== undefined) {
|
||||
if (isUsingActivityIntegration()) {
|
||||
// Do not mount own tab but mount into activity
|
||||
window.addEventListener('DOMContentLoaded', function() {
|
||||
registerCommentsPlugins()
|
||||
|
||||
@@ -0,0 +1,13 @@
|
||||
/*!
|
||||
* SPDX-FileCopyrightText: 2026 Nextcloud GmbH and Nextcloud contributors
|
||||
* SPDX-License-Identifier: AGPL-3.0-or-later
|
||||
*/
|
||||
|
||||
import { loadState } from '@nextcloud/initial-state'
|
||||
|
||||
/**
|
||||
* Check if the comments app is using the Activity app integration for the sidebar.
|
||||
*/
|
||||
export function isUsingActivityIntegration() {
|
||||
return loadState('comments', 'activityEnabled', false) && window.OCA?.Activity?.registerSidebarAction !== undefined
|
||||
}
|
||||
@@ -20,6 +20,7 @@ OC.L10N.register(
|
||||
"Edit widgets" : "Upraviť miniaplikácie",
|
||||
"Get more widgets from the App Store" : "Získať viac miniaplikácií v Obchode s aplikáciami",
|
||||
"Weather service" : "Služba počasie",
|
||||
"For your privacy, the weather data is requested by your {productName} server on your behalf so the weather service receives no personal information." : "Pre vaše súkromie sú údaje o počasí požadované vaším {productName} serverom vo vašom mene, takže služba počasia neobdrží žiadne osobné informácie.",
|
||||
"Weather data from Met.no" : "Dáta počasia z Met.no",
|
||||
"geocoding with Nominatim" : "geokódovanie pomocou Nominatim",
|
||||
"elevation data from OpenTopoData" : "dáta o nadmorskej výške z OpenTopoData",
|
||||
|
||||
@@ -18,6 +18,7 @@
|
||||
"Edit widgets" : "Upraviť miniaplikácie",
|
||||
"Get more widgets from the App Store" : "Získať viac miniaplikácií v Obchode s aplikáciami",
|
||||
"Weather service" : "Služba počasie",
|
||||
"For your privacy, the weather data is requested by your {productName} server on your behalf so the weather service receives no personal information." : "Pre vaše súkromie sú údaje o počasí požadované vaším {productName} serverom vo vašom mene, takže služba počasia neobdrží žiadne osobné informácie.",
|
||||
"Weather data from Met.no" : "Dáta počasia z Met.no",
|
||||
"geocoding with Nominatim" : "geokódovanie pomocou Nominatim",
|
||||
"elevation data from OpenTopoData" : "dáta o nadmorskej výške z OpenTopoData",
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
/**
|
||||
* SPDX-FileCopyrightText: 2017 Nextcloud GmbH and Nextcloud contributors
|
||||
* SPDX-License-Identifier: AGPL-3.0-or-later
|
||||
|
||||
+2
-2
@@ -159,9 +159,9 @@ OC.L10N.register(
|
||||
"Attendees:" : "Asistentes:",
|
||||
"Title:" : "Título:",
|
||||
"When:" : "Cando:",
|
||||
"Location:" : "Lugar:",
|
||||
"Location:" : "Onde:",
|
||||
"Link:" : "Ligazón:",
|
||||
"Occurring:" : "Acaecendo:",
|
||||
"Occurring:" : "Acaecerá:",
|
||||
"Accept" : "Aceptar",
|
||||
"Decline" : "Declinar",
|
||||
"More options …" : "Máis opcións…",
|
||||
|
||||
@@ -157,9 +157,9 @@
|
||||
"Attendees:" : "Asistentes:",
|
||||
"Title:" : "Título:",
|
||||
"When:" : "Cando:",
|
||||
"Location:" : "Lugar:",
|
||||
"Location:" : "Onde:",
|
||||
"Link:" : "Ligazón:",
|
||||
"Occurring:" : "Acaecendo:",
|
||||
"Occurring:" : "Acaecerá:",
|
||||
"Accept" : "Aceptar",
|
||||
"Decline" : "Declinar",
|
||||
"More options …" : "Máis opcións…",
|
||||
|
||||
@@ -75,7 +75,17 @@ OC.L10N.register(
|
||||
"In the past on %1$s for the entire day" : "W przeszłości w %1$s na cały dzień",
|
||||
"_In %n minute on %1$s for the entire day_::_In %n minutes on %1$s for the entire day_" : ["Za minutę o %1$s przez cały dzień","Za %n minut o %1$s przez cały dzień","Za %n minut o %1$s przez cały dzień","Za %n minut o %1$s przez cały dzień"],
|
||||
"_In %n hour on %1$s for the entire day_::_In %n hours on %1$s for the entire day_" : ["Za %n godzinę o %1$s przez cały dzień","Za %n godzin o %1$s przez cały dzień","Za %n godzin o %1$s przez cały dzień","Za %n godziny o %1$s przez cały dzień"],
|
||||
"_In %n day on %1$s for the entire day_::_In %n days on %1$s for the entire day_" : ["W ciągu %n dnia o %1$s przez cały dzień","W ciągu %n dni o %1$s przez cały dzień","W ciągu %n dni o %1$s przez cały dzień","W ciągu %n dni o %1$s przez cały dzień"],
|
||||
"_In %n week on %1$s for the entire day_::_In %n weeks on %1$s for the entire day_" : ["Za %n tydzień o %1$s przez cały dzień","W ciągu %n tygodni o %1$s przez cały dzień","W ciągu %n tygodni o %1$s przez cały dzień","W ciągu %n tygodni o %1$s przez cały dzień"],
|
||||
"_In %n month on %1$s for the entire day_::_In %n months on %1$s for the entire day_" : ["Za %n miesiąc, o %1$s przez cały dzień","W ciągu %n miesięcy, o %1$s przez cały dzień","W ciągu %n miesięcy, o %1$s przez cały dzień","W ciągu %n miesięcy, o %1$s przez cały dzień"],
|
||||
"_In %n year on %1$s for the entire day_::_In %n years on %1$s for the entire day_" : ["Za %n rok o %1$s przez cały dzień","Za %n lat o %1$s przez cały dzień","Za %n lat o %1$s przez cały dzień","Za %n lat o %1$s przez cały dzień"],
|
||||
"In the past on %1$s between %2$s - %3$s" : "W przeszłości dnia %1$s między %2$s - %3$s",
|
||||
"_In %n minute on %1$s between %2$s - %3$s_::_In %n minutes on %1$s between %2$s - %3$s_" : ["Za %n minutę o %1$s pomiędzy %2$s - %3$s","Za %n minut o %1$s pomiędzy %2$s - %3$s","Za %n minut o %1$s pomiędzy %2$s - %3$s","Za %n minut o %1$s pomiędzy %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_" : ["Za %n godzinę o %1$s między %2$s - %3$s","Za %n godzin o %1$s między %2$s - %3$s","Za %n godzin o %1$s między %2$s - %3$s","Za %n godzin o %1$s między %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_" : ["Za %n dzień o %1$s między %2$s - %3$s","Za %n dni o %1$s między %2$s - %3$s","Za %n dni o %1$s między %2$s - %3$s","Za %n dni o %1$s między %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_" : ["Za %n tydzień o %1$s między %2$s - %3$s","Za %n tygodni o %1$s między %2$s - %3$s","Za %n tygodni o %1$s między %2$s - %3$s","Za %n tygodnie o %1$s między %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_" : ["Za %n miesiąc o %1$s między %2$s - %3$s","Za %n miesięcy o %1$s między %2$s - %3$s","Za %n miesięcy o %1$s między %2$s - %3$s","Za %n miesiące o %1$s między %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_" : ["Za %n rok o %1$s między %2$s - %3$s","Za %n lat o %1$s między %2$s - %3$s","Za %n lat o %1$s między %2$s - %3$s","Za %n lata o %1$s między %2$s - %3$s"],
|
||||
"Could not generate when statement" : "Nie można wygenerować instrukcji when",
|
||||
"Every Day for the entire day" : "Codziennie przez cały dzień",
|
||||
"Every Day for the entire day until %1$s" : "Codziennie przez cały dzień do %1$s",
|
||||
@@ -113,6 +123,12 @@ OC.L10N.register(
|
||||
"On specific dates for the entire day until %1$s" : "W określonych datach przez cały dzień do %1$s",
|
||||
"On specific dates between %1$s - %2$s until %3$s" : "W określonych datach między %1$s - %2$s do %3$s",
|
||||
"In the past on %1$s" : "W przeszłości dnia %1$s",
|
||||
"_In %n minute on %1$s_::_In %n minutes on %1$s_" : ["Za %n minutę o %1$s","Za %n minuty o %1$s","Za %n minuty o %1$s","Za %n minuty o %1$s"],
|
||||
"_In %n hour on %1$s_::_In %n hours on %1$s_" : ["Za %n godzinę o %1$s","Za %n godzin o %1$s","Za %n godzin o %1$s","Za %n godzin o %1$s"],
|
||||
"_In %n day on %1$s_::_In %n days on %1$s_" : ["Za %n dzień o %1$s","Za %n dni o %1$s","Za %n dni o %1$s","Za %n dni o %1$s"],
|
||||
"_In %n week on %1$s_::_In %n weeks on %1$s_" : ["Za %n tydzień o %1$s","Za %n tygodnie o %1$s","Za %n tygodnie o %1$s","Za %n tygodnie o %1$s"],
|
||||
"_In %n month on %1$s_::_In %n months on %1$s_" : ["Za %n miesiąc o %1$s","W ciągu %n miesięcy o %1$s","W ciągu %n miesięcy o %1$s","W ciągu %n miesięcy o %1$s"],
|
||||
"_In %n year on %1$s_::_In %n years on %1$s_" : ["Za %n rok o %1$s","Za %n lat o %1$s","Za %n lat o %1$s","Za %n lat o %1$s"],
|
||||
"In the past on %1$s then on %2$s" : "W przeszłości dnia %1$s, a następnie %2$s",
|
||||
"In the past on %1$s then on %2$s and %3$s" : "W przeszłości dnia %1$s, następnie dnia %2$s i %3$s",
|
||||
"Could not generate next recurrence statement" : "Nie można wygenerować następnej instrukcji powtarzania",
|
||||
@@ -206,6 +222,8 @@ OC.L10N.register(
|
||||
"Could not rename part file to final file, canceled by hook" : "Nie można zmienić nazwy pliku podzielonego na plik końcowy, anulowane przez hook",
|
||||
"Could not rename part file to final file" : "Nie można zmienić nazwy pliku podzielonego na plik końcowy",
|
||||
"Failed to check file size: %1$s" : "Nie udało się sprawdzić rozmiaru pliku: %1$s",
|
||||
"Could not open file: %1$s (%2$d), file does seem to exist" : "Nie można otworzyć: %1$s (%2$d), zdaje się, że plik istnieje",
|
||||
"Could not open file: %1$s (%2$d), file doesn't seem to exist" : "Nie można otworzyć: %1$s (%2$d), zdaje się, że plik nie istnieje",
|
||||
"Encryption not ready: %1$s" : "Szyfrowanie nie jest gotowe: %1$s",
|
||||
"Failed to open file: %1$s" : "Nie udało się otworzyć pliku: %1$s",
|
||||
"Failed to unlink: %1$s" : "Nie udało się odłączyć: %1$s",
|
||||
|
||||
@@ -73,7 +73,17 @@
|
||||
"In the past on %1$s for the entire day" : "W przeszłości w %1$s na cały dzień",
|
||||
"_In %n minute on %1$s for the entire day_::_In %n minutes on %1$s for the entire day_" : ["Za minutę o %1$s przez cały dzień","Za %n minut o %1$s przez cały dzień","Za %n minut o %1$s przez cały dzień","Za %n minut o %1$s przez cały dzień"],
|
||||
"_In %n hour on %1$s for the entire day_::_In %n hours on %1$s for the entire day_" : ["Za %n godzinę o %1$s przez cały dzień","Za %n godzin o %1$s przez cały dzień","Za %n godzin o %1$s przez cały dzień","Za %n godziny o %1$s przez cały dzień"],
|
||||
"_In %n day on %1$s for the entire day_::_In %n days on %1$s for the entire day_" : ["W ciągu %n dnia o %1$s przez cały dzień","W ciągu %n dni o %1$s przez cały dzień","W ciągu %n dni o %1$s przez cały dzień","W ciągu %n dni o %1$s przez cały dzień"],
|
||||
"_In %n week on %1$s for the entire day_::_In %n weeks on %1$s for the entire day_" : ["Za %n tydzień o %1$s przez cały dzień","W ciągu %n tygodni o %1$s przez cały dzień","W ciągu %n tygodni o %1$s przez cały dzień","W ciągu %n tygodni o %1$s przez cały dzień"],
|
||||
"_In %n month on %1$s for the entire day_::_In %n months on %1$s for the entire day_" : ["Za %n miesiąc, o %1$s przez cały dzień","W ciągu %n miesięcy, o %1$s przez cały dzień","W ciągu %n miesięcy, o %1$s przez cały dzień","W ciągu %n miesięcy, o %1$s przez cały dzień"],
|
||||
"_In %n year on %1$s for the entire day_::_In %n years on %1$s for the entire day_" : ["Za %n rok o %1$s przez cały dzień","Za %n lat o %1$s przez cały dzień","Za %n lat o %1$s przez cały dzień","Za %n lat o %1$s przez cały dzień"],
|
||||
"In the past on %1$s between %2$s - %3$s" : "W przeszłości dnia %1$s między %2$s - %3$s",
|
||||
"_In %n minute on %1$s between %2$s - %3$s_::_In %n minutes on %1$s between %2$s - %3$s_" : ["Za %n minutę o %1$s pomiędzy %2$s - %3$s","Za %n minut o %1$s pomiędzy %2$s - %3$s","Za %n minut o %1$s pomiędzy %2$s - %3$s","Za %n minut o %1$s pomiędzy %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_" : ["Za %n godzinę o %1$s między %2$s - %3$s","Za %n godzin o %1$s między %2$s - %3$s","Za %n godzin o %1$s między %2$s - %3$s","Za %n godzin o %1$s między %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_" : ["Za %n dzień o %1$s między %2$s - %3$s","Za %n dni o %1$s między %2$s - %3$s","Za %n dni o %1$s między %2$s - %3$s","Za %n dni o %1$s między %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_" : ["Za %n tydzień o %1$s między %2$s - %3$s","Za %n tygodni o %1$s między %2$s - %3$s","Za %n tygodni o %1$s między %2$s - %3$s","Za %n tygodnie o %1$s między %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_" : ["Za %n miesiąc o %1$s między %2$s - %3$s","Za %n miesięcy o %1$s między %2$s - %3$s","Za %n miesięcy o %1$s między %2$s - %3$s","Za %n miesiące o %1$s między %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_" : ["Za %n rok o %1$s między %2$s - %3$s","Za %n lat o %1$s między %2$s - %3$s","Za %n lat o %1$s między %2$s - %3$s","Za %n lata o %1$s między %2$s - %3$s"],
|
||||
"Could not generate when statement" : "Nie można wygenerować instrukcji when",
|
||||
"Every Day for the entire day" : "Codziennie przez cały dzień",
|
||||
"Every Day for the entire day until %1$s" : "Codziennie przez cały dzień do %1$s",
|
||||
@@ -111,6 +121,12 @@
|
||||
"On specific dates for the entire day until %1$s" : "W określonych datach przez cały dzień do %1$s",
|
||||
"On specific dates between %1$s - %2$s until %3$s" : "W określonych datach między %1$s - %2$s do %3$s",
|
||||
"In the past on %1$s" : "W przeszłości dnia %1$s",
|
||||
"_In %n minute on %1$s_::_In %n minutes on %1$s_" : ["Za %n minutę o %1$s","Za %n minuty o %1$s","Za %n minuty o %1$s","Za %n minuty o %1$s"],
|
||||
"_In %n hour on %1$s_::_In %n hours on %1$s_" : ["Za %n godzinę o %1$s","Za %n godzin o %1$s","Za %n godzin o %1$s","Za %n godzin o %1$s"],
|
||||
"_In %n day on %1$s_::_In %n days on %1$s_" : ["Za %n dzień o %1$s","Za %n dni o %1$s","Za %n dni o %1$s","Za %n dni o %1$s"],
|
||||
"_In %n week on %1$s_::_In %n weeks on %1$s_" : ["Za %n tydzień o %1$s","Za %n tygodnie o %1$s","Za %n tygodnie o %1$s","Za %n tygodnie o %1$s"],
|
||||
"_In %n month on %1$s_::_In %n months on %1$s_" : ["Za %n miesiąc o %1$s","W ciągu %n miesięcy o %1$s","W ciągu %n miesięcy o %1$s","W ciągu %n miesięcy o %1$s"],
|
||||
"_In %n year on %1$s_::_In %n years on %1$s_" : ["Za %n rok o %1$s","Za %n lat o %1$s","Za %n lat o %1$s","Za %n lat o %1$s"],
|
||||
"In the past on %1$s then on %2$s" : "W przeszłości dnia %1$s, a następnie %2$s",
|
||||
"In the past on %1$s then on %2$s and %3$s" : "W przeszłości dnia %1$s, następnie dnia %2$s i %3$s",
|
||||
"Could not generate next recurrence statement" : "Nie można wygenerować następnej instrukcji powtarzania",
|
||||
@@ -204,6 +220,8 @@
|
||||
"Could not rename part file to final file, canceled by hook" : "Nie można zmienić nazwy pliku podzielonego na plik końcowy, anulowane przez hook",
|
||||
"Could not rename part file to final file" : "Nie można zmienić nazwy pliku podzielonego na plik końcowy",
|
||||
"Failed to check file size: %1$s" : "Nie udało się sprawdzić rozmiaru pliku: %1$s",
|
||||
"Could not open file: %1$s (%2$d), file does seem to exist" : "Nie można otworzyć: %1$s (%2$d), zdaje się, że plik istnieje",
|
||||
"Could not open file: %1$s (%2$d), file doesn't seem to exist" : "Nie można otworzyć: %1$s (%2$d), zdaje się, że plik nie istnieje",
|
||||
"Encryption not ready: %1$s" : "Szyfrowanie nie jest gotowe: %1$s",
|
||||
"Failed to open file: %1$s" : "Nie udało się otworzyć pliku: %1$s",
|
||||
"Failed to unlink: %1$s" : "Nie udało się odłączyć: %1$s",
|
||||
|
||||
@@ -73,7 +73,19 @@ OC.L10N.register(
|
||||
"Where: %s" : "Kde: %s",
|
||||
"%1$s via %2$s" : "%1$s cez %2$s",
|
||||
"In the past on %1$s for the entire day" : "V minulosti %1$s na celý deň",
|
||||
"_In %n minute on %1$s for the entire day_::_In %n minutes on %1$s for the entire day_" : ["Za %n minútu %1$s na celý deň","Za %n minúty %1$s na celý deň","Za %n minút %1$s na celý deň","Za %n minút %1$s na celý deň"],
|
||||
"_In %n hour on %1$s for the entire day_::_In %n hours on %1$s for the entire day_" : ["Za %n hodinu %1$s na celý deň","Za %n hodiny %1$s na celý deň","Za %n hodín %1$s na celý deň","Za %n hodín %1$s na celý deň"],
|
||||
"_In %n day on %1$s for the entire day_::_In %n days on %1$s for the entire day_" : ["Za %n deň %1$s na celý deň","Za %n dni %1$s na celý deň","Za %n dní %1$s na celý deň","Za %n dní %1$s na celý deň"],
|
||||
"_In %n week on %1$s for the entire day_::_In %n weeks on %1$s for the entire day_" : ["Za %n týždeň %1$s na celý deň","Za %n týždne %1$s na celý deň","Za %n týždňov %1$s na celý deň","Za %n týždňov %1$s na celý deň"],
|
||||
"_In %n month on %1$s for the entire day_::_In %n months on %1$s for the entire day_" : ["Za %n mesiac %1$s na celý deň","Za %n mesiace %1$s na celý deň","Za %n mesiacov %1$s na celý deň","Za %n mesiacov %1$s na celý deň"],
|
||||
"_In %n year on %1$s for the entire day_::_In %n years on %1$s for the entire day_" : ["Za %n rok %1$s na celý deň","Za %n roky %1$s na celý deň","Za %n rokov %1$s na celý deň","Za %n rokov %1$s na celý deň"],
|
||||
"In the past on %1$s between %2$s - %3$s" : "V minulosti %1$s medzi %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_" : ["Za %n minútu %1$s %2$s medzi %2$s - %3$s","Za %n minúty %1$s %2$s medzi %2$s - %3$s","Za %n minút %1$s %2$s medzi %2$s - %3$s","Za %n minút %1$s %2$s medzi %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_" : ["Za %n hodinu %1$s %2$s medzi %2$s - %3$s","Za %n hodiny %1$s %2$s medzi %2$s - %3$s","Za %n hodín %1$s %2$s medzi %2$s - %3$s","Za %n hodín %1$s %2$s medzi %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_" : ["Za %n deň %1$s %2$s medzi %2$s - %3$s","Za %n dni %1$s %2$s medzi %2$s - %3$s","Za %n dní %1$s %2$s medzi %2$s - %3$s","Za %n dní %1$s %2$s medzi %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_" : ["Za %n týždeň %1$s %2$s medzi %2$s - %3$s","Za %n týždne %1$s %2$s medzi %2$s - %3$s","Za %n týždňov %1$s %2$s medzi %2$s - %3$s","Za %n týždňov %1$s %2$s medzi %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_" : ["Za %n mesiac %1$s %2$s medzi %2$s - %3$s","Za %n mesiace %1$s %2$s medzi %2$s - %3$s","Za %n mesiacov %1$s %2$s medzi %2$s - %3$s","Za %n mesiacov %1$s %2$s medzi %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_" : ["Za %n rok %1$s %2$s medzi %2$s - %3$s","Za %n roky %1$s %2$s medzi %2$s - %3$s","Za %n rokov %1$s %2$s medzi %2$s - %3$s","Za %n rokov %1$s %2$s medzi %2$s - %3$s"],
|
||||
"Could not generate when statement" : "Nepodarilo sa vygenerovať vyhlásenie kedy",
|
||||
"Every Day for the entire day" : "Každý deň, na celý deň",
|
||||
"Every Day for the entire day until %1$s" : "Každý deň, na celý deň, do %1$s",
|
||||
@@ -111,8 +123,26 @@ OC.L10N.register(
|
||||
"On specific dates for the entire day until %1$s" : "V konkrétnych dátumoch na celý deň %1$s",
|
||||
"On specific dates between %1$s - %2$s until %3$s" : "V konkrétnych dátumoch medzi %1$s - %2$s do %3$s",
|
||||
"In the past on %1$s" : "V minulosti %1$s",
|
||||
"_In %n minute on %1$s_::_In %n minutes on %1$s_" : ["Za %n minútu na %1$s","Za %n minúty na %1$s","Za %n minút na %1$s","Za %n minút na %1$s"],
|
||||
"_In %n hour on %1$s_::_In %n hours on %1$s_" : ["Za %n hodinu na %1$s","Za %n hodiny na %1$s","Za %n hodín na %1$s","Za %n hodín na%1$s"],
|
||||
"_In %n day on %1$s_::_In %n days on %1$s_" : ["Za %n deň na %1$s","Za %n dni na %1$s","Za %n dní na %1$s","Za %n dní na %1$s"],
|
||||
"_In %n week on %1$s_::_In %n weeks on %1$s_" : ["Za %n týždeň %1$s","Za %n týždne na %1$s","Za %n týždňov na %1$s","Za %n týždňov na %1$s"],
|
||||
"_In %n month on %1$s_::_In %n months on %1$s_" : ["Za %n mesiac na%1$s","Za %n mesiace na%1$s","Za %n mesiacov na%1$s","Za %n mesiacov na %1$s"],
|
||||
"_In %n year on %1$s_::_In %n years on %1$s_" : ["Za %n rok na %1$s","Za %n roky na %1$s","Za %n rokov na %1$s","Za %n rokov na %1$s"],
|
||||
"In the past on %1$s then on %2$s" : "V minulosti %1$s a potom %2$s",
|
||||
"_In %n minute on %1$s then on %2$s_::_In %n minutes on %1$s then on %2$s_" : ["Za %n minútu %1$s a potom %2$s","Za %n minúty %1$s a potom %2$s","Za %n minút %1$s a potom %2$s","Za %n minút %1$s a potom %2$s"],
|
||||
"_In %n hour on %1$s then on %2$s_::_In %n hours on %1$s then on %2$s_" : ["Za %n hodinu %1$s a potom %2$s","Za %n hodiny %1$s a potom %2$s","Za %n hodín %1$s a potom %2$s","Za %n hodín %1$s a potom %2$s"],
|
||||
"_In %n day on %1$s then on %2$s_::_In %n days on %1$s then on %2$s_" : ["Za %n deň %1$s a potom %2$s","Za %n dni %1$s a potom %2$s","Za %n dní %1$s a potom %2$s","Za %n dní %1$s a potom %2$s"],
|
||||
"_In %n week on %1$s then on %2$s_::_In %n weeks on %1$s then on %2$s_" : ["Za %n týždeň %1$s a potom %2$s","Za %n týždne %1$s a potom %2$s","Za %n týždňov %1$s a potom %2$s","Za %n týždňov %1$s a potom %2$s"],
|
||||
"_In %n month on %1$s then on %2$s_::_In %n months on %1$s then on %2$s_" : ["Za %n mesiac %1$s a potom %2$s","Za %n mesiace %1$s a potom %2$s","Za %n mesiacov %1$s a potom %2$s","Za %n mesiacov %1$s a potom %2$s"],
|
||||
"_In %n year on %1$s then on %2$s_::_In %n years on %1$s then on %2$s_" : ["Za %n rok %1$s a potom %2$s","Za %n roky %1$s a potom %2$s","Za %n rokov %1$s a potom %2$s","Za %n rokov %1$s a potom %2$s"],
|
||||
"In the past on %1$s then on %2$s and %3$s" : "V minulosti %1$s potom %2$s a %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_" : ["Za %n minútu %1$s potom %2$s a %3$s","Za %n minúty %1$s potom %2$s a %3$s","Za %n minút %1$s potom %2$s a %3$s","Za %n minút %1$s potom %2$s a %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_" : ["Za %n hodinu %1$s potom %2$s a %3$s","Za %n hodiny %1$s potom %2$s a %3$s","Za %n hodín %1$s potom %2$s a %3$s","Za %nhodín %1$s potom %2$s a %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_" : ["Za %n deň %1$s potom %2$s a %3$s","Za %n dni %1$s potom %2$s a %3$s","Za %n dní %1$s potom %2$s a %3$s","Za %n dní %1$s potom %2$s a %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_" : ["Za %n týždeň %1$s potom %2$s a %3$s","Za %n týždne %1$s potom %2$s a %3$s","Za %n týždňov %1$s potom %2$s a %3$s","Za %n týždňov %1$s potom %2$s a %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_" : ["Za %n mesiac %1$s potom %2$s a %3$s","Za %n mesiace %1$s potom %2$s a %3$s","Za %n mesiacov %1$s potom %2$s a %3$s","Za %n mesiacov %1$s potom %2$s a %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_" : ["Za %n rok %1$s potom %2$s a %3$s","Za %n roky %1$s potom %2$s a %3$s","Za %n rokov %1$s potom %2$s a %3$s","Za %n rokov %1$s potom %2$s a %3$s"],
|
||||
"Could not generate next recurrence statement" : "Nepodarilo sa vygenerovať ďalšie opakovanie",
|
||||
"Cancelled: %1$s" : "Zrušené: %1$s",
|
||||
"\"%1$s\" has been canceled" : "\"%1$s\" bolo zrušené",
|
||||
@@ -190,6 +220,8 @@ OC.L10N.register(
|
||||
"{actor} updated contact {card} in address book {addressbook}" : "{actor} upravil kontakt {card} v adresári {addressbook}",
|
||||
"You updated contact {card} in address book {addressbook}" : "Upravili ste kontakt {card} v adresári {addressbook}",
|
||||
"A <strong>contact</strong> or <strong>address book</strong> was modified" : "<strong>kontakt</strong> alebo <strong>adresár</strong> bol upravený",
|
||||
"System address book disabled" : "Systémový adresár je vypnutý",
|
||||
"The system contacts address book has been automatically disabled during upgrade. This means that the address book will no longer be available to users in the contacts app or other clients. The system contacts address book was disabled because the amount of contacts in the address book exceeded the maximum recommended number of contacts. This limit is set to prevent performance issues. You can re-enable the system address book with the following command {command}" : "Systémový adresár kontaktov bol automaticky vypnutý počas aktualizácie. To znamená, že adresár kontaktov už nebude dostupný pre používateľov v aplikácii Kontakty ani v iných klientoch. Systémový adresár kontaktov bol vypnutý, pretože počet kontaktov v adresári prekročil maximálny odporúčaný počet kontaktov. Tento limit je nastavený na prevenciu problémov s výkonom. Systémový adresár kontaktov môžete znovu povoliť pomocou nasledujúcej príkazu {command}.",
|
||||
"Accounts" : "Účty",
|
||||
"System address book which holds all accounts" : "Systémový adresár, ktorý obsahuje všetky účty.",
|
||||
"File is not updatable: %1$s" : "Súbor nie je možné aktualizovať: %1$s",
|
||||
@@ -202,6 +234,8 @@ OC.L10N.register(
|
||||
"Could not rename part file to final file, canceled by hook" : "Nepodarilo sa premenovať dočasný súbor na finálny, zrušené háčikom (hook)",
|
||||
"Could not rename part file to final file" : "Nepodarilo sa premenovať dočasný súbor na finálny.",
|
||||
"Failed to check file size: %1$s" : "Kontrola veľkosti súboru zlyhala: %1$s",
|
||||
"Could not open file: %1$s (%2$d), file does seem to exist" : "Nie je možné otvoriť súbor: %1$s (%2$d), zdá sa že súbor existuje",
|
||||
"Could not open file: %1$s (%2$d), file doesn't seem to exist" : "Nie je možné otvoriť súbor: %1$s (%2$d), nezdá sa že súbor existuje",
|
||||
"Encryption not ready: %1$s" : "Šifrovanie nie je dostupné: %1$s",
|
||||
"Failed to open file: %1$s" : "Otvorenie súboru zlyhalo: %1$s",
|
||||
"Failed to unlink: %1$s" : "Odpojenie zlyhalo: %1$s",
|
||||
@@ -218,12 +252,18 @@ OC.L10N.register(
|
||||
"Completed on %s" : "Dokončené %s",
|
||||
"Due on %s by %s" : "Termín od %s do %s",
|
||||
"Due on %s" : "Termín do %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." : "Vitajte v Nextcloud Calendar!\n\nToto je ukážková udalosť – preskúmajte flexibilitu plánovania s Nextcloud Calendar tým, že vykonáte akékoľvek úpravy, ktoré chcete!\n\nS Nextcloud Calendar môžete:\n- Jednoducho vytvárať, upravovať a spravovať udalosti.\n- Vytvárať viacero kalendárov a zdieľať ich s tímom, priateľmi alebo rodinou.\n- Skontrolovať dostupnosť a zobraziť svoje zaneprázdnené časy pre ostatných.\n- Bezproblémovo integrovať s aplikáciami a zariadeniami cez CalDAV.\n- Prispôsobiť si skúsenosti: naplánovať opakujúce sa udalosti, nastaviť notifikácie a iné nastavenia.",
|
||||
"Example event - open me!" : "Príklad udalosti - otvorte ma!",
|
||||
"System Address Book" : "Systémový Adresár",
|
||||
"The system address book contains contact information for all users in your instance." : "Systémový adresár obsahuje kontaktné informácie o všetkých užívateľov vo vašej inštancii.",
|
||||
"Enable System Address Book" : "Povoliť systémový Adresár",
|
||||
"DAV system address book" : "Systémový DAV adresár",
|
||||
"No outstanding DAV system address book sync." : "Žiadna zostávajúca synchronizácia adresára systému DAV.",
|
||||
"The DAV system address book sync has not run yet as your instance has more than 1000 users or because an error occurred. Please run it manually by calling \"occ dav:sync-system-addressbook\"." : "DAV synchronizácia systémového adresára ešte nebola spustená, pretože vaša inštancia má viac ako 1000 užívateľov alebo sa vyskytla chyba. Prosím, spustite ju manuálne volaním \"occ dav:sync-system-addressbook\".",
|
||||
"DAV system address book size" : "Veľkosť adresára systému DAV",
|
||||
"The system address book is disabled" : "Systémový adresár je zakázaný",
|
||||
"The system address book is enabled, but contains more than the configured limit of %d contacts" : "Adresár systému je povolený, ale obsahuje viac kontaktov, než je nastavený limit %dkontaktov.",
|
||||
"The system address book is enabled and contains less than the configured limit of %d contacts" : "Adresár systému je povolený a obsahuje menej kontaktov, než je nastavený limit %dkontaktov.",
|
||||
"WebDAV endpoint" : "Koncový bod WebDAV",
|
||||
"Could not check that your web server is properly set up to allow file synchronization over WebDAV. Please check manually." : "Nepodarilo sa skontrolovať, či je váš webový server správne nastavený tak, aby umožňoval synchronizáciu súborov cez WebDAV. Skontrolujte prosím manuálne.",
|
||||
"Your web server is not yet properly set up to allow file synchronization, because the WebDAV interface seems to be broken." : "Váš webový server nie je zatiaľ správne nastavený, aby umožnil synchronizáciu súborov, pretože rozhranie WebDAV sa zdá byť nefunkčné.",
|
||||
@@ -266,6 +306,14 @@ OC.L10N.register(
|
||||
"Reset to default" : "Nastaviť predvolené",
|
||||
"Import contacts" : "Importovať kontakty",
|
||||
"Importing a new .vcf file will delete the existing default contact and replace it with the new one. Do you want to continue?" : "Importovaním nového súboru .vcf sa vymaže existujúci predvolený kontakt a nahradí sa novým. Chcete pokračovať?",
|
||||
"Failed to save example event creation setting" : "Nepodarilo sa uložiť nastavenie vytvorenia príkladu udalosti",
|
||||
"Failed to upload the example event" : "Nezvládlo sa nahrať príklad udalosti",
|
||||
"Custom example event was saved successfully" : "Vlastný príklad udalosti bol úspešne uložený",
|
||||
"Failed to delete the custom example event" : "Nepodarilo sa odstrániť vlastnú ukážkovú udalosť",
|
||||
"Custom example event was deleted successfully" : "Vlastný príklad udalosti bol úspešne odstránený",
|
||||
"Import calendar event" : "Importovať udalosť kalendára",
|
||||
"Uploading a new event will overwrite the existing one." : "Nahrávanie novej udalosti prepíše existujúcu.",
|
||||
"Upload event" : "Nahratie udalosti",
|
||||
"Also install the {calendarappstoreopen}Calendar app{linkclose}, or {calendardocopen}connect your desktop & mobile for syncing ↗{linkclose}." : "Tiež nainštalujte {calendarappstoreopen}apku Kalendár{linkclose} alebo {calendardocopen}pripojte svoj počítač a smartfón pre synchronizáciu ↗{linkclose}.",
|
||||
"Please make sure to properly set up {emailopen}the email server{linkclose}." : "Uistite sa, že ste správne nastavili {emailopen}e-mailový server{linkclose}.",
|
||||
"Calendar server" : "Kalendárový server",
|
||||
@@ -278,6 +326,8 @@ OC.L10N.register(
|
||||
"Send reminder notifications to calendar sharees as well" : "Posielať upozornenia na pripomienky aj zdieľaným osobám v kalendári",
|
||||
"Reminders are always sent to organizers and attendees." : "Upozornenia sa vždy posielajú organizátorom a účastníkom.",
|
||||
"Enable notifications for events via push" : "Zapnúť oznámenia o udalostiach prostredníctvom technológie push.",
|
||||
"Example content" : "Príklad obsahu",
|
||||
"Example content serves to showcase the features of Nextcloud. Default content is shipped with Nextcloud, and can be replaced by custom content." : "Príklad obsahu slúži na predvedenie funkcií Nextcloud. Predvolený obsah je dodávaný s Nextcloudom a môže byť nahradený vlastným obsahom.",
|
||||
"Availability" : "Dostupnosť",
|
||||
"If you configure your working hours, other people will see when you are out of office when they book a meeting." : "Ak nakonfigurujete svoj pracovný čas, ostatní užívatelia vás uvidia ako neprítomného, keď si rezervujete schôdzku",
|
||||
"Absence" : "Neprítomnosť",
|
||||
|
||||
@@ -71,7 +71,19 @@
|
||||
"Where: %s" : "Kde: %s",
|
||||
"%1$s via %2$s" : "%1$s cez %2$s",
|
||||
"In the past on %1$s for the entire day" : "V minulosti %1$s na celý deň",
|
||||
"_In %n minute on %1$s for the entire day_::_In %n minutes on %1$s for the entire day_" : ["Za %n minútu %1$s na celý deň","Za %n minúty %1$s na celý deň","Za %n minút %1$s na celý deň","Za %n minút %1$s na celý deň"],
|
||||
"_In %n hour on %1$s for the entire day_::_In %n hours on %1$s for the entire day_" : ["Za %n hodinu %1$s na celý deň","Za %n hodiny %1$s na celý deň","Za %n hodín %1$s na celý deň","Za %n hodín %1$s na celý deň"],
|
||||
"_In %n day on %1$s for the entire day_::_In %n days on %1$s for the entire day_" : ["Za %n deň %1$s na celý deň","Za %n dni %1$s na celý deň","Za %n dní %1$s na celý deň","Za %n dní %1$s na celý deň"],
|
||||
"_In %n week on %1$s for the entire day_::_In %n weeks on %1$s for the entire day_" : ["Za %n týždeň %1$s na celý deň","Za %n týždne %1$s na celý deň","Za %n týždňov %1$s na celý deň","Za %n týždňov %1$s na celý deň"],
|
||||
"_In %n month on %1$s for the entire day_::_In %n months on %1$s for the entire day_" : ["Za %n mesiac %1$s na celý deň","Za %n mesiace %1$s na celý deň","Za %n mesiacov %1$s na celý deň","Za %n mesiacov %1$s na celý deň"],
|
||||
"_In %n year on %1$s for the entire day_::_In %n years on %1$s for the entire day_" : ["Za %n rok %1$s na celý deň","Za %n roky %1$s na celý deň","Za %n rokov %1$s na celý deň","Za %n rokov %1$s na celý deň"],
|
||||
"In the past on %1$s between %2$s - %3$s" : "V minulosti %1$s medzi %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_" : ["Za %n minútu %1$s %2$s medzi %2$s - %3$s","Za %n minúty %1$s %2$s medzi %2$s - %3$s","Za %n minút %1$s %2$s medzi %2$s - %3$s","Za %n minút %1$s %2$s medzi %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_" : ["Za %n hodinu %1$s %2$s medzi %2$s - %3$s","Za %n hodiny %1$s %2$s medzi %2$s - %3$s","Za %n hodín %1$s %2$s medzi %2$s - %3$s","Za %n hodín %1$s %2$s medzi %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_" : ["Za %n deň %1$s %2$s medzi %2$s - %3$s","Za %n dni %1$s %2$s medzi %2$s - %3$s","Za %n dní %1$s %2$s medzi %2$s - %3$s","Za %n dní %1$s %2$s medzi %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_" : ["Za %n týždeň %1$s %2$s medzi %2$s - %3$s","Za %n týždne %1$s %2$s medzi %2$s - %3$s","Za %n týždňov %1$s %2$s medzi %2$s - %3$s","Za %n týždňov %1$s %2$s medzi %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_" : ["Za %n mesiac %1$s %2$s medzi %2$s - %3$s","Za %n mesiace %1$s %2$s medzi %2$s - %3$s","Za %n mesiacov %1$s %2$s medzi %2$s - %3$s","Za %n mesiacov %1$s %2$s medzi %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_" : ["Za %n rok %1$s %2$s medzi %2$s - %3$s","Za %n roky %1$s %2$s medzi %2$s - %3$s","Za %n rokov %1$s %2$s medzi %2$s - %3$s","Za %n rokov %1$s %2$s medzi %2$s - %3$s"],
|
||||
"Could not generate when statement" : "Nepodarilo sa vygenerovať vyhlásenie kedy",
|
||||
"Every Day for the entire day" : "Každý deň, na celý deň",
|
||||
"Every Day for the entire day until %1$s" : "Každý deň, na celý deň, do %1$s",
|
||||
@@ -109,8 +121,26 @@
|
||||
"On specific dates for the entire day until %1$s" : "V konkrétnych dátumoch na celý deň %1$s",
|
||||
"On specific dates between %1$s - %2$s until %3$s" : "V konkrétnych dátumoch medzi %1$s - %2$s do %3$s",
|
||||
"In the past on %1$s" : "V minulosti %1$s",
|
||||
"_In %n minute on %1$s_::_In %n minutes on %1$s_" : ["Za %n minútu na %1$s","Za %n minúty na %1$s","Za %n minút na %1$s","Za %n minút na %1$s"],
|
||||
"_In %n hour on %1$s_::_In %n hours on %1$s_" : ["Za %n hodinu na %1$s","Za %n hodiny na %1$s","Za %n hodín na %1$s","Za %n hodín na%1$s"],
|
||||
"_In %n day on %1$s_::_In %n days on %1$s_" : ["Za %n deň na %1$s","Za %n dni na %1$s","Za %n dní na %1$s","Za %n dní na %1$s"],
|
||||
"_In %n week on %1$s_::_In %n weeks on %1$s_" : ["Za %n týždeň %1$s","Za %n týždne na %1$s","Za %n týždňov na %1$s","Za %n týždňov na %1$s"],
|
||||
"_In %n month on %1$s_::_In %n months on %1$s_" : ["Za %n mesiac na%1$s","Za %n mesiace na%1$s","Za %n mesiacov na%1$s","Za %n mesiacov na %1$s"],
|
||||
"_In %n year on %1$s_::_In %n years on %1$s_" : ["Za %n rok na %1$s","Za %n roky na %1$s","Za %n rokov na %1$s","Za %n rokov na %1$s"],
|
||||
"In the past on %1$s then on %2$s" : "V minulosti %1$s a potom %2$s",
|
||||
"_In %n minute on %1$s then on %2$s_::_In %n minutes on %1$s then on %2$s_" : ["Za %n minútu %1$s a potom %2$s","Za %n minúty %1$s a potom %2$s","Za %n minút %1$s a potom %2$s","Za %n minút %1$s a potom %2$s"],
|
||||
"_In %n hour on %1$s then on %2$s_::_In %n hours on %1$s then on %2$s_" : ["Za %n hodinu %1$s a potom %2$s","Za %n hodiny %1$s a potom %2$s","Za %n hodín %1$s a potom %2$s","Za %n hodín %1$s a potom %2$s"],
|
||||
"_In %n day on %1$s then on %2$s_::_In %n days on %1$s then on %2$s_" : ["Za %n deň %1$s a potom %2$s","Za %n dni %1$s a potom %2$s","Za %n dní %1$s a potom %2$s","Za %n dní %1$s a potom %2$s"],
|
||||
"_In %n week on %1$s then on %2$s_::_In %n weeks on %1$s then on %2$s_" : ["Za %n týždeň %1$s a potom %2$s","Za %n týždne %1$s a potom %2$s","Za %n týždňov %1$s a potom %2$s","Za %n týždňov %1$s a potom %2$s"],
|
||||
"_In %n month on %1$s then on %2$s_::_In %n months on %1$s then on %2$s_" : ["Za %n mesiac %1$s a potom %2$s","Za %n mesiace %1$s a potom %2$s","Za %n mesiacov %1$s a potom %2$s","Za %n mesiacov %1$s a potom %2$s"],
|
||||
"_In %n year on %1$s then on %2$s_::_In %n years on %1$s then on %2$s_" : ["Za %n rok %1$s a potom %2$s","Za %n roky %1$s a potom %2$s","Za %n rokov %1$s a potom %2$s","Za %n rokov %1$s a potom %2$s"],
|
||||
"In the past on %1$s then on %2$s and %3$s" : "V minulosti %1$s potom %2$s a %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_" : ["Za %n minútu %1$s potom %2$s a %3$s","Za %n minúty %1$s potom %2$s a %3$s","Za %n minút %1$s potom %2$s a %3$s","Za %n minút %1$s potom %2$s a %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_" : ["Za %n hodinu %1$s potom %2$s a %3$s","Za %n hodiny %1$s potom %2$s a %3$s","Za %n hodín %1$s potom %2$s a %3$s","Za %nhodín %1$s potom %2$s a %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_" : ["Za %n deň %1$s potom %2$s a %3$s","Za %n dni %1$s potom %2$s a %3$s","Za %n dní %1$s potom %2$s a %3$s","Za %n dní %1$s potom %2$s a %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_" : ["Za %n týždeň %1$s potom %2$s a %3$s","Za %n týždne %1$s potom %2$s a %3$s","Za %n týždňov %1$s potom %2$s a %3$s","Za %n týždňov %1$s potom %2$s a %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_" : ["Za %n mesiac %1$s potom %2$s a %3$s","Za %n mesiace %1$s potom %2$s a %3$s","Za %n mesiacov %1$s potom %2$s a %3$s","Za %n mesiacov %1$s potom %2$s a %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_" : ["Za %n rok %1$s potom %2$s a %3$s","Za %n roky %1$s potom %2$s a %3$s","Za %n rokov %1$s potom %2$s a %3$s","Za %n rokov %1$s potom %2$s a %3$s"],
|
||||
"Could not generate next recurrence statement" : "Nepodarilo sa vygenerovať ďalšie opakovanie",
|
||||
"Cancelled: %1$s" : "Zrušené: %1$s",
|
||||
"\"%1$s\" has been canceled" : "\"%1$s\" bolo zrušené",
|
||||
@@ -188,6 +218,8 @@
|
||||
"{actor} updated contact {card} in address book {addressbook}" : "{actor} upravil kontakt {card} v adresári {addressbook}",
|
||||
"You updated contact {card} in address book {addressbook}" : "Upravili ste kontakt {card} v adresári {addressbook}",
|
||||
"A <strong>contact</strong> or <strong>address book</strong> was modified" : "<strong>kontakt</strong> alebo <strong>adresár</strong> bol upravený",
|
||||
"System address book disabled" : "Systémový adresár je vypnutý",
|
||||
"The system contacts address book has been automatically disabled during upgrade. This means that the address book will no longer be available to users in the contacts app or other clients. The system contacts address book was disabled because the amount of contacts in the address book exceeded the maximum recommended number of contacts. This limit is set to prevent performance issues. You can re-enable the system address book with the following command {command}" : "Systémový adresár kontaktov bol automaticky vypnutý počas aktualizácie. To znamená, že adresár kontaktov už nebude dostupný pre používateľov v aplikácii Kontakty ani v iných klientoch. Systémový adresár kontaktov bol vypnutý, pretože počet kontaktov v adresári prekročil maximálny odporúčaný počet kontaktov. Tento limit je nastavený na prevenciu problémov s výkonom. Systémový adresár kontaktov môžete znovu povoliť pomocou nasledujúcej príkazu {command}.",
|
||||
"Accounts" : "Účty",
|
||||
"System address book which holds all accounts" : "Systémový adresár, ktorý obsahuje všetky účty.",
|
||||
"File is not updatable: %1$s" : "Súbor nie je možné aktualizovať: %1$s",
|
||||
@@ -200,6 +232,8 @@
|
||||
"Could not rename part file to final file, canceled by hook" : "Nepodarilo sa premenovať dočasný súbor na finálny, zrušené háčikom (hook)",
|
||||
"Could not rename part file to final file" : "Nepodarilo sa premenovať dočasný súbor na finálny.",
|
||||
"Failed to check file size: %1$s" : "Kontrola veľkosti súboru zlyhala: %1$s",
|
||||
"Could not open file: %1$s (%2$d), file does seem to exist" : "Nie je možné otvoriť súbor: %1$s (%2$d), zdá sa že súbor existuje",
|
||||
"Could not open file: %1$s (%2$d), file doesn't seem to exist" : "Nie je možné otvoriť súbor: %1$s (%2$d), nezdá sa že súbor existuje",
|
||||
"Encryption not ready: %1$s" : "Šifrovanie nie je dostupné: %1$s",
|
||||
"Failed to open file: %1$s" : "Otvorenie súboru zlyhalo: %1$s",
|
||||
"Failed to unlink: %1$s" : "Odpojenie zlyhalo: %1$s",
|
||||
@@ -216,12 +250,18 @@
|
||||
"Completed on %s" : "Dokončené %s",
|
||||
"Due on %s by %s" : "Termín od %s do %s",
|
||||
"Due on %s" : "Termín do %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." : "Vitajte v Nextcloud Calendar!\n\nToto je ukážková udalosť – preskúmajte flexibilitu plánovania s Nextcloud Calendar tým, že vykonáte akékoľvek úpravy, ktoré chcete!\n\nS Nextcloud Calendar môžete:\n- Jednoducho vytvárať, upravovať a spravovať udalosti.\n- Vytvárať viacero kalendárov a zdieľať ich s tímom, priateľmi alebo rodinou.\n- Skontrolovať dostupnosť a zobraziť svoje zaneprázdnené časy pre ostatných.\n- Bezproblémovo integrovať s aplikáciami a zariadeniami cez CalDAV.\n- Prispôsobiť si skúsenosti: naplánovať opakujúce sa udalosti, nastaviť notifikácie a iné nastavenia.",
|
||||
"Example event - open me!" : "Príklad udalosti - otvorte ma!",
|
||||
"System Address Book" : "Systémový Adresár",
|
||||
"The system address book contains contact information for all users in your instance." : "Systémový adresár obsahuje kontaktné informácie o všetkých užívateľov vo vašej inštancii.",
|
||||
"Enable System Address Book" : "Povoliť systémový Adresár",
|
||||
"DAV system address book" : "Systémový DAV adresár",
|
||||
"No outstanding DAV system address book sync." : "Žiadna zostávajúca synchronizácia adresára systému DAV.",
|
||||
"The DAV system address book sync has not run yet as your instance has more than 1000 users or because an error occurred. Please run it manually by calling \"occ dav:sync-system-addressbook\"." : "DAV synchronizácia systémového adresára ešte nebola spustená, pretože vaša inštancia má viac ako 1000 užívateľov alebo sa vyskytla chyba. Prosím, spustite ju manuálne volaním \"occ dav:sync-system-addressbook\".",
|
||||
"DAV system address book size" : "Veľkosť adresára systému DAV",
|
||||
"The system address book is disabled" : "Systémový adresár je zakázaný",
|
||||
"The system address book is enabled, but contains more than the configured limit of %d contacts" : "Adresár systému je povolený, ale obsahuje viac kontaktov, než je nastavený limit %dkontaktov.",
|
||||
"The system address book is enabled and contains less than the configured limit of %d contacts" : "Adresár systému je povolený a obsahuje menej kontaktov, než je nastavený limit %dkontaktov.",
|
||||
"WebDAV endpoint" : "Koncový bod WebDAV",
|
||||
"Could not check that your web server is properly set up to allow file synchronization over WebDAV. Please check manually." : "Nepodarilo sa skontrolovať, či je váš webový server správne nastavený tak, aby umožňoval synchronizáciu súborov cez WebDAV. Skontrolujte prosím manuálne.",
|
||||
"Your web server is not yet properly set up to allow file synchronization, because the WebDAV interface seems to be broken." : "Váš webový server nie je zatiaľ správne nastavený, aby umožnil synchronizáciu súborov, pretože rozhranie WebDAV sa zdá byť nefunkčné.",
|
||||
@@ -264,6 +304,14 @@
|
||||
"Reset to default" : "Nastaviť predvolené",
|
||||
"Import contacts" : "Importovať kontakty",
|
||||
"Importing a new .vcf file will delete the existing default contact and replace it with the new one. Do you want to continue?" : "Importovaním nového súboru .vcf sa vymaže existujúci predvolený kontakt a nahradí sa novým. Chcete pokračovať?",
|
||||
"Failed to save example event creation setting" : "Nepodarilo sa uložiť nastavenie vytvorenia príkladu udalosti",
|
||||
"Failed to upload the example event" : "Nezvládlo sa nahrať príklad udalosti",
|
||||
"Custom example event was saved successfully" : "Vlastný príklad udalosti bol úspešne uložený",
|
||||
"Failed to delete the custom example event" : "Nepodarilo sa odstrániť vlastnú ukážkovú udalosť",
|
||||
"Custom example event was deleted successfully" : "Vlastný príklad udalosti bol úspešne odstránený",
|
||||
"Import calendar event" : "Importovať udalosť kalendára",
|
||||
"Uploading a new event will overwrite the existing one." : "Nahrávanie novej udalosti prepíše existujúcu.",
|
||||
"Upload event" : "Nahratie udalosti",
|
||||
"Also install the {calendarappstoreopen}Calendar app{linkclose}, or {calendardocopen}connect your desktop & mobile for syncing ↗{linkclose}." : "Tiež nainštalujte {calendarappstoreopen}apku Kalendár{linkclose} alebo {calendardocopen}pripojte svoj počítač a smartfón pre synchronizáciu ↗{linkclose}.",
|
||||
"Please make sure to properly set up {emailopen}the email server{linkclose}." : "Uistite sa, že ste správne nastavili {emailopen}e-mailový server{linkclose}.",
|
||||
"Calendar server" : "Kalendárový server",
|
||||
@@ -276,6 +324,8 @@
|
||||
"Send reminder notifications to calendar sharees as well" : "Posielať upozornenia na pripomienky aj zdieľaným osobám v kalendári",
|
||||
"Reminders are always sent to organizers and attendees." : "Upozornenia sa vždy posielajú organizátorom a účastníkom.",
|
||||
"Enable notifications for events via push" : "Zapnúť oznámenia o udalostiach prostredníctvom technológie push.",
|
||||
"Example content" : "Príklad obsahu",
|
||||
"Example content serves to showcase the features of Nextcloud. Default content is shipped with Nextcloud, and can be replaced by custom content." : "Príklad obsahu slúži na predvedenie funkcií Nextcloud. Predvolený obsah je dodávaný s Nextcloudom a môže byť nahradený vlastným obsahom.",
|
||||
"Availability" : "Dostupnosť",
|
||||
"If you configure your working hours, other people will see when you are out of office when they book a meeting." : "Ak nakonfigurujete svoj pracovný čas, ostatní užívatelia vás uvidia ako neprítomného, keď si rezervujete schôdzku",
|
||||
"Absence" : "Neprítomnosť",
|
||||
|
||||
@@ -89,6 +89,7 @@ use OCP\Contacts\IManager as IContactsManager;
|
||||
use OCP\DB\Events\AddMissingIndicesEvent;
|
||||
use OCP\Federation\Events\TrustedServerRemovedEvent;
|
||||
use OCP\Federation\ICloudFederationProviderManager;
|
||||
use OCP\Group\Events\GroupDeletedEvent;
|
||||
use OCP\IURLGenerator;
|
||||
use OCP\IUserSession;
|
||||
use OCP\Server;
|
||||
@@ -205,6 +206,7 @@ class Application extends App implements IBootstrap {
|
||||
$context->registerEventListener(UserCreatedEvent::class, UserEventsListener::class);
|
||||
$context->registerEventListener(UserChangedEvent::class, UserEventsListener::class);
|
||||
$context->registerEventListener(UserUpdatedEvent::class, UserEventsListener::class);
|
||||
$context->registerEventListener(GroupDeletedEvent::class, UserEventsListener::class);
|
||||
|
||||
$context->registerEventListener(SabrePluginAuthInitEvent::class, SabrePluginAuthInitListener::class);
|
||||
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
/**
|
||||
* SPDX-FileCopyrightText: 2016 Nextcloud GmbH and Nextcloud contributors
|
||||
* SPDX-License-Identifier: AGPL-3.0-or-later
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
/**
|
||||
* SPDX-FileCopyrightText: 2016 Nextcloud GmbH and Nextcloud contributors
|
||||
* SPDX-License-Identifier: AGPL-3.0-or-later
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
/**
|
||||
* SPDX-FileCopyrightText: 2016 Nextcloud GmbH and Nextcloud contributors
|
||||
* SPDX-License-Identifier: AGPL-3.0-or-later
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
/**
|
||||
* SPDX-FileCopyrightText: 2016 Nextcloud GmbH and Nextcloud contributors
|
||||
* SPDX-License-Identifier: AGPL-3.0-or-later
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
/**
|
||||
* SPDX-FileCopyrightText: 2016 Nextcloud GmbH and Nextcloud contributors
|
||||
* SPDX-License-Identifier: AGPL-3.0-or-later
|
||||
|
||||
@@ -67,7 +67,7 @@ class CalendarProvider implements ICalendarProvider {
|
||||
$user = str_replace('principals/users/', '', $calendarInfo['principaluri']);
|
||||
$path = 'calendars/' . $user . '/' . $calendarInfo['uri'];
|
||||
if (isset($additionalFederatedProps[$path])) {
|
||||
$calendarInfo = array_merge($calendarInfo, $additionalProperties[$path]);
|
||||
$calendarInfo = array_merge($calendarInfo, $additionalFederatedProps[$path]);
|
||||
}
|
||||
|
||||
$iCalendars[] = new FederatedCalendarImpl($calendarInfo, $this->calDavBackend);
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
/**
|
||||
* SPDX-FileCopyrightText: 2020 Nextcloud GmbH and Nextcloud contributors
|
||||
* SPDX-License-Identifier: AGPL-3.0-or-later
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
/**
|
||||
* SPDX-FileCopyrightText: 2020 Nextcloud GmbH and Nextcloud contributors
|
||||
* SPDX-License-Identifier: AGPL-3.0-or-later
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
/**
|
||||
* SPDX-FileCopyrightText: 2018 Nextcloud GmbH and Nextcloud contributors
|
||||
* SPDX-License-Identifier: AGPL-3.0-or-later
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
/**
|
||||
* SPDX-FileCopyrightText: 2017 Nextcloud GmbH and Nextcloud contributors
|
||||
* SPDX-License-Identifier: AGPL-3.0-or-later
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
/**
|
||||
* SPDX-FileCopyrightText: 2017 Nextcloud GmbH and Nextcloud contributors
|
||||
* SPDX-License-Identifier: AGPL-3.0-or-later
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
/**
|
||||
* SPDX-FileCopyrightText: 2018 Nextcloud GmbH and Nextcloud contributors
|
||||
* SPDX-License-Identifier: AGPL-3.0-or-later
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
/**
|
||||
* SPDX-FileCopyrightText: 2018 Nextcloud GmbH and Nextcloud contributors
|
||||
* SPDX-License-Identifier: AGPL-3.0-or-later
|
||||
|
||||
@@ -165,6 +165,7 @@ class Plugin extends \Sabre\CalDAV\Schedule\Plugin {
|
||||
|
||||
// Do not generate iTip and iMip messages if scheduling is disabled for this message
|
||||
if ($request->getHeader('x-nc-scheduling') === 'false') {
|
||||
$this->logger->debug('Skipping scheduling messages for calendar object change because x-nc-scheduling header is set to false');
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -212,6 +213,13 @@ class Plugin extends \Sabre\CalDAV\Schedule\Plugin {
|
||||
* @inheritDoc
|
||||
*/
|
||||
public function beforeUnbind($path): void {
|
||||
|
||||
// Do not generate iTip and iMip messages if scheduling is disabled for this message
|
||||
if ($this->server->httpRequest->getHeader('x-nc-scheduling') === 'false') {
|
||||
$this->logger->debug('Skipping scheduling messages for calendar object delete because x-nc-scheduling header is set to false');
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
parent::beforeUnbind($path);
|
||||
} catch (SameOrganizerForAllComponentsException $e) {
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
/**
|
||||
* SPDX-FileCopyrightText: 2017 Nextcloud GmbH and Nextcloud contributors
|
||||
* SPDX-License-Identifier: AGPL-3.0-or-later
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
/**
|
||||
* SPDX-FileCopyrightText: 2017 Nextcloud GmbH and Nextcloud contributors
|
||||
* SPDX-License-Identifier: AGPL-3.0-or-later
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
/**
|
||||
* SPDX-FileCopyrightText: 2017 Nextcloud GmbH and Nextcloud contributors
|
||||
* SPDX-License-Identifier: AGPL-3.0-or-later
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
/**
|
||||
* SPDX-FileCopyrightText: 2017 Nextcloud GmbH and Nextcloud contributors
|
||||
* SPDX-License-Identifier: AGPL-3.0-or-later
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
/**
|
||||
* SPDX-FileCopyrightText: 2017 Nextcloud GmbH and Nextcloud contributors
|
||||
* SPDX-License-Identifier: AGPL-3.0-or-later
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
/**
|
||||
* SPDX-FileCopyrightText: 2017 Nextcloud GmbH and Nextcloud contributors
|
||||
* SPDX-License-Identifier: AGPL-3.0-or-later
|
||||
|
||||
@@ -24,17 +24,14 @@ use function explode;
|
||||
|
||||
class RateLimitingPlugin extends ServerPlugin {
|
||||
|
||||
private Limiter $limiter;
|
||||
|
||||
public function __construct(
|
||||
Limiter $limiter,
|
||||
private Limiter $limiter,
|
||||
private IUserManager $userManager,
|
||||
private CalDavBackend $calDavBackend,
|
||||
private LoggerInterface $logger,
|
||||
private IAppConfig $config,
|
||||
private ?string $userId,
|
||||
) {
|
||||
$this->limiter = $limiter;
|
||||
}
|
||||
|
||||
public function initialize(DAV\Server $server): void {
|
||||
|
||||
+239
-126
@@ -9,6 +9,7 @@ declare(strict_types=1);
|
||||
|
||||
namespace OCA\DAV\CalDAV;
|
||||
|
||||
use Sabre\VObject\Component;
|
||||
use Sabre\VObject\Component\VCalendar;
|
||||
use Sabre\VObject\ITip\Broker;
|
||||
use Sabre\VObject\ITip\Message;
|
||||
@@ -27,9 +28,56 @@ class TipBroker extends Broker {
|
||||
'SUMMARY',
|
||||
'DESCRIPTION',
|
||||
'LOCATION',
|
||||
|
||||
];
|
||||
|
||||
/**
|
||||
* Processes incoming CANCEL messages.
|
||||
*
|
||||
* This is a message from an organizer, and means that either an
|
||||
* attendee got removed from an event, or an event got cancelled
|
||||
* altogether.
|
||||
*
|
||||
* @param VCalendar $existingObject
|
||||
*
|
||||
* @return VCalendar|null
|
||||
*/
|
||||
protected function processMessageCancel(Message $itipMessage, ?VCalendar $existingObject = null) {
|
||||
if ($existingObject === null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
$componentType = $itipMessage->component;
|
||||
$instances = [];
|
||||
|
||||
foreach ($itipMessage->message->$componentType as $component) {
|
||||
$instanceId = isset($component->{'RECURRENCE-ID'}) ? $component->{'RECURRENCE-ID'}->getValue() : 'base';
|
||||
$instances[$instanceId] = $component;
|
||||
}
|
||||
// any existing instances should be marked as cancelled
|
||||
foreach ($existingObject->$componentType as $component) {
|
||||
$instanceId = isset($component->{'RECURRENCE-ID'}) ? $component->{'RECURRENCE-ID'}->getValue() : 'base';
|
||||
if (isset($instances[$instanceId])) {
|
||||
if (isset($component->STATUS)) {
|
||||
$component->STATUS->setValue('CANCELLED');
|
||||
} else {
|
||||
$component->add('STATUS', 'CANCELLED');
|
||||
}
|
||||
if (isset($component->SEQUENCE)) {
|
||||
$component->SEQUENCE->setValue($itipMessage->sequence);
|
||||
} else {
|
||||
$component->add('SEQUENCE', $itipMessage->sequence);
|
||||
}
|
||||
unset($instances[$instanceId]);
|
||||
}
|
||||
}
|
||||
// any remaining instances are new and should be added
|
||||
foreach ($instances as $instance) {
|
||||
$existingObject->add($instance);
|
||||
}
|
||||
|
||||
return $existingObject;
|
||||
}
|
||||
|
||||
/**
|
||||
* This method is used in cases where an event got updated, and we
|
||||
* potentially need to send emails to attendees to let them know of updates
|
||||
@@ -38,154 +86,219 @@ class TipBroker extends Broker {
|
||||
* We will detect which attendees got added, which got removed and create
|
||||
* specific messages for these situations.
|
||||
*
|
||||
* @return array
|
||||
* @return array<int,Message>
|
||||
*/
|
||||
protected function parseEventForOrganizer(VCalendar $calendar, array $eventInfo, array $oldEventInfo) {
|
||||
// Merging attendee lists.
|
||||
$attendees = [];
|
||||
foreach ($oldEventInfo['attendees'] as $attendee) {
|
||||
$attendees[$attendee['href']] = [
|
||||
'href' => $attendee['href'],
|
||||
'oldInstances' => $attendee['instances'],
|
||||
'newInstances' => [],
|
||||
'name' => $attendee['name'],
|
||||
'forceSend' => null,
|
||||
];
|
||||
}
|
||||
foreach ($eventInfo['attendees'] as $attendee) {
|
||||
if (isset($attendees[$attendee['href']])) {
|
||||
$attendees[$attendee['href']]['name'] = $attendee['name'];
|
||||
$attendees[$attendee['href']]['newInstances'] = $attendee['instances'];
|
||||
$attendees[$attendee['href']]['forceSend'] = $attendee['forceSend'];
|
||||
} else {
|
||||
$attendees[$attendee['href']] = [
|
||||
'href' => $attendee['href'],
|
||||
'oldInstances' => [],
|
||||
'newInstances' => $attendee['instances'],
|
||||
'name' => $attendee['name'],
|
||||
'forceSend' => $attendee['forceSend'],
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
$messages = [];
|
||||
|
||||
// construct template calendar from original calendar without components
|
||||
$template = new VCalendar();
|
||||
foreach ($template->children() as $property) {
|
||||
$template->remove($property);
|
||||
}
|
||||
foreach ($calendar->children() as $property) {
|
||||
if (in_array($property->name, ['METHOD', 'VEVENT', 'VTODO', 'VJOURNAL', 'VFREEBUSY'], true) === false) {
|
||||
$template->add(clone $property);
|
||||
}
|
||||
}
|
||||
// extract event information
|
||||
$objectId = $eventInfo['uid'];
|
||||
if ($calendar->getBaseComponent() === null) {
|
||||
$objectType = $calendar->getComponents()[0]->name;
|
||||
} else {
|
||||
$objectType = $calendar->getBaseComponent()->name;
|
||||
}
|
||||
$objectSequence = $eventInfo['sequence'] ?? 1;
|
||||
$organizerHref = $eventInfo['organizer'] ?? $oldEventInfo['organizer'];
|
||||
if ($eventInfo['organizerName'] instanceof \Sabre\VObject\Parameter) {
|
||||
$organizerName = $eventInfo['organizerName']->getValue();
|
||||
} else {
|
||||
$organizerName = $eventInfo['organizerName'];
|
||||
}
|
||||
// detect if the singleton or recurring base instance was converted to non-scheduling
|
||||
if (count($eventInfo['instances']) === 0 && count($oldEventInfo['instances']) > 0) {
|
||||
foreach ($oldEventInfo['attendees'] as $attendee) {
|
||||
$messages[] = $this->generateMessage(
|
||||
$oldEventInfo['instances'], $organizerHref, $organizerName, $attendee, $objectId, $objectType, $objectSequence, 'CANCEL', $template
|
||||
);
|
||||
}
|
||||
return $messages;
|
||||
}
|
||||
// detect if the singleton or recurring base instance was cancelled
|
||||
if ($eventInfo['instances']['master']?->STATUS?->getValue() === 'CANCELLED' && $oldEventInfo['instances']['master']?->STATUS?->getValue() !== 'CANCELLED') {
|
||||
foreach ($eventInfo['attendees'] as $attendee) {
|
||||
$messages[] = $this->generateMessage(
|
||||
$eventInfo['instances'], $organizerHref, $organizerName, $attendee, $objectId, $objectType, $objectSequence, 'CANCEL', $template
|
||||
);
|
||||
}
|
||||
return $messages;
|
||||
}
|
||||
// detect if a new cancelled instance was created
|
||||
$cancelledNewInstances = [];
|
||||
if (isset($oldEventInfo['instances'])) {
|
||||
$instancesDelta = array_diff_key($eventInfo['instances'], $oldEventInfo['instances']);
|
||||
foreach ($instancesDelta as $id => $instance) {
|
||||
if ($instance->STATUS?->getValue() === 'CANCELLED') {
|
||||
$cancelledNewInstances[] = $id;
|
||||
foreach ($eventInfo['attendees'] as $attendee) {
|
||||
$messages[] = $this->generateMessage(
|
||||
[$id => $instance], $organizerHref, $organizerName, $attendee, $objectId, $objectType, $objectSequence, 'CANCEL', $template
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
// detect attendee mutations
|
||||
$attendees = array_unique(
|
||||
array_merge(
|
||||
array_keys($eventInfo['attendees']),
|
||||
array_keys($oldEventInfo['attendees'])
|
||||
)
|
||||
);
|
||||
foreach ($attendees as $attendee) {
|
||||
// An organizer can also be an attendee. We should not generate any
|
||||
// messages for those.
|
||||
if ($attendee['href'] === $eventInfo['organizer']) {
|
||||
// Skip organizer
|
||||
if ($attendee === $organizerHref) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$message = new Message();
|
||||
$message->uid = $eventInfo['uid'];
|
||||
$message->component = 'VEVENT';
|
||||
$message->sequence = $eventInfo['sequence'];
|
||||
$message->sender = $eventInfo['organizer'];
|
||||
$message->senderName = $eventInfo['organizerName'];
|
||||
$message->recipient = $attendee['href'];
|
||||
$message->recipientName = $attendee['name'];
|
||||
|
||||
// Creating the new iCalendar body.
|
||||
$icalMsg = new VCalendar();
|
||||
|
||||
foreach ($calendar->select('VTIMEZONE') as $timezone) {
|
||||
$icalMsg->add(clone $timezone);
|
||||
// Skip if SCHEDULE-AGENT=CLIENT (respect RFC 6638)
|
||||
if ($this->scheduleAgentServerRules
|
||||
&& isset($eventInfo['attendees'][$attendee]['scheduleAgent'])
|
||||
&& strtoupper($eventInfo['attendees'][$attendee]['scheduleAgent']) === 'CLIENT') {
|
||||
continue;
|
||||
}
|
||||
// If there are no instances the attendee is a part of, it means
|
||||
// the attendee was removed and we need to send them a CANCEL message.
|
||||
// Also If the meeting STATUS property was changed to CANCELLED
|
||||
// we need to send the attendee a CANCEL message.
|
||||
if (!$attendee['newInstances'] || $eventInfo['status'] === 'CANCELLED') {
|
||||
|
||||
$message->method = $icalMsg->METHOD = 'CANCEL';
|
||||
$message->significantChange = true;
|
||||
// clone base event
|
||||
if (isset($eventInfo['instances']['master'])) {
|
||||
$event = clone $eventInfo['instances']['master'];
|
||||
} else {
|
||||
$event = clone $oldEventInfo['instances']['master'];
|
||||
}
|
||||
// alter some properties
|
||||
unset($event->ATTENDEE);
|
||||
$event->add('ATTENDEE', $attendee['href'], ['CN' => $attendee['name'],]);
|
||||
$event->DTSTAMP = gmdate('Ymd\\THis\\Z');
|
||||
$event->SEQUENCE = $message->sequence;
|
||||
$icalMsg->add($event);
|
||||
// detect if attendee was removed and send cancel message
|
||||
if (!isset($eventInfo['attendees'][$attendee]) && isset($oldEventInfo['attendees'][$attendee])) {
|
||||
//get all instances of the attendee was removed from.
|
||||
$instances = array_intersect_key($oldEventInfo['instances'], array_flip(array_keys($oldEventInfo['attendees'][$attendee]['instances'])));
|
||||
$messages[] = $this->generateMessage(
|
||||
$instances, $organizerHref, $organizerName, $oldEventInfo['attendees'][$attendee], $objectId, $objectType, $objectSequence, 'CANCEL', $template
|
||||
);
|
||||
continue;
|
||||
}
|
||||
// otherwise any created or modified instances will be sent as REQUEST
|
||||
$instances = array_intersect_key($eventInfo['instances'], array_flip(array_keys($eventInfo['attendees'][$attendee]['instances'])));
|
||||
|
||||
} else {
|
||||
// The attendee gets the updated event body
|
||||
$message->method = $icalMsg->METHOD = 'REQUEST';
|
||||
// Remove already-cancelled new instances from REQUEST
|
||||
if (!empty($cancelledNewInstances)) {
|
||||
$instances = array_diff_key($instances, array_flip($cancelledNewInstances));
|
||||
}
|
||||
|
||||
// We need to find out that this change is significant. If it's
|
||||
// not, systems may opt to not send messages.
|
||||
//
|
||||
// We do this based on the 'significantChangeHash' which is
|
||||
// some value that changes if there's a certain set of
|
||||
// properties changed in the event, or simply if there's a
|
||||
// difference in instances that the attendee is invited to.
|
||||
// Skip if no instances left to send
|
||||
if (empty($instances)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$oldAttendeeInstances = array_keys($attendee['oldInstances']);
|
||||
$newAttendeeInstances = array_keys($attendee['newInstances']);
|
||||
// Add EXDATE for instances the attendee is NOT part of (only for recurring events with master)
|
||||
if (isset($instances['master']) && count($eventInfo['instances']) > 1) {
|
||||
$masterInstance = clone $instances['master'];
|
||||
$excludedDates = [];
|
||||
|
||||
$message->significantChange
|
||||
= $attendee['forceSend'] === 'REQUEST'
|
||||
|| count($oldAttendeeInstances) !== count($newAttendeeInstances)
|
||||
|| count(array_diff($oldAttendeeInstances, $newAttendeeInstances)) > 0
|
||||
|| $oldEventInfo['significantChangeHash'] !== $eventInfo['significantChangeHash'];
|
||||
|
||||
foreach ($attendee['newInstances'] as $instanceId => $instanceInfo) {
|
||||
$currentEvent = clone $eventInfo['instances'][$instanceId];
|
||||
if ($instanceId === 'master') {
|
||||
// We need to find a list of events that the attendee
|
||||
// is not a part of to add to the list of exceptions.
|
||||
$exceptions = [];
|
||||
foreach ($eventInfo['instances'] as $instanceId => $vevent) {
|
||||
if (!isset($attendee['newInstances'][$instanceId])) {
|
||||
$exceptions[] = $instanceId;
|
||||
}
|
||||
}
|
||||
|
||||
// If there were exceptions, we need to add it to an
|
||||
// existing EXDATE property, if it exists.
|
||||
if ($exceptions) {
|
||||
if (isset($currentEvent->EXDATE)) {
|
||||
$currentEvent->EXDATE->setParts(array_merge(
|
||||
$currentEvent->EXDATE->getParts(),
|
||||
$exceptions
|
||||
));
|
||||
} else {
|
||||
$currentEvent->EXDATE = $exceptions;
|
||||
}
|
||||
}
|
||||
|
||||
// Cleaning up any scheduling information that
|
||||
// shouldn't be sent along.
|
||||
unset($currentEvent->ORGANIZER['SCHEDULE-FORCE-SEND']);
|
||||
unset($currentEvent->ORGANIZER['SCHEDULE-STATUS']);
|
||||
|
||||
foreach ($currentEvent->ATTENDEE as $attendee) {
|
||||
unset($attendee['SCHEDULE-FORCE-SEND']);
|
||||
unset($attendee['SCHEDULE-STATUS']);
|
||||
|
||||
// We're adding PARTSTAT=NEEDS-ACTION to ensure that
|
||||
// iOS shows an "Inbox Item"
|
||||
if (!isset($attendee['PARTSTAT'])) {
|
||||
$attendee['PARTSTAT'] = 'NEEDS-ACTION';
|
||||
}
|
||||
}
|
||||
foreach ($eventInfo['instances'] as $instanceId => $instance) {
|
||||
if ($instanceId !== 'master' && !isset($eventInfo['attendees'][$attendee]['instances'][$instanceId])) {
|
||||
$excludedDates[] = $instance->{'RECURRENCE-ID'}->getValue();
|
||||
}
|
||||
}
|
||||
|
||||
$currentEvent->DTSTAMP = gmdate('Ymd\\THis\\Z');
|
||||
$icalMsg->add($currentEvent);
|
||||
if (!empty($excludedDates)) {
|
||||
if (isset($masterInstance->EXDATE)) {
|
||||
$currentExdates = $masterInstance->EXDATE->getParts();
|
||||
$masterInstance->EXDATE->setParts(array_merge($currentExdates, $excludedDates));
|
||||
} else {
|
||||
$masterInstance->EXDATE = $excludedDates;
|
||||
}
|
||||
$instances['master'] = $masterInstance;
|
||||
}
|
||||
}
|
||||
|
||||
$message->message = $icalMsg;
|
||||
$messages[] = $message;
|
||||
$messages[] = $this->generateMessage(
|
||||
$instances, $organizerHref, $organizerName, $eventInfo['attendees'][$attendee], $objectId, $objectType, $objectSequence, 'REQUEST', $template
|
||||
);
|
||||
}
|
||||
|
||||
return $messages;
|
||||
}
|
||||
|
||||
/**
|
||||
* Generates an iTip message for a specific attendee
|
||||
*
|
||||
* @param array<string, Component> $instances Array of event instances to include, keyed by instance ID:
|
||||
* - 'master' => Component: The master/base event
|
||||
* - '{RECURRENCE-ID}' => Component: Exception instances
|
||||
* @param string $organizerHref The organizer's calendar-user address (e.g., 'mailto:user@example.com')
|
||||
* @param string|null $organizerName The organizer's display name
|
||||
* @param array $attendee The attendee information containing:
|
||||
* - 'href' (string): The attendee's calendar-user address
|
||||
* - 'name' (string): The attendee's display name
|
||||
* - 'scheduleAgent' (string|null): SCHEDULE-AGENT parameter
|
||||
* - 'instances' (array): Instances this attendee is part of
|
||||
* @param string $objectId The UID of the event
|
||||
* @param string $objectType The component type ('VEVENT', 'VTODO', etc.)
|
||||
* @param int $objectSequence The sequence number of the event
|
||||
* @param string $method The iTip method ('REQUEST', 'CANCEL', 'REPLY', etc.)
|
||||
* @param VCalendar $template The template calendar object (without event components)
|
||||
* @return Message The generated iTip message ready to be sent
|
||||
*/
|
||||
protected function generateMessage(
|
||||
array $instances,
|
||||
string $organizerHref,
|
||||
?string $organizerName,
|
||||
array $attendee,
|
||||
string $objectId,
|
||||
string $objectType,
|
||||
int $objectSequence,
|
||||
string $method,
|
||||
VCalendar $template,
|
||||
): Message {
|
||||
|
||||
$recipientAddress = $attendee['href'] ?? '';
|
||||
$recipientName = $attendee['name'] ?? '';
|
||||
|
||||
$vObject = clone $template;
|
||||
if ($vObject->METHOD && $vObject->METHOD->getValue() !== $method) {
|
||||
$vObject->METHOD->setValue($method);
|
||||
} else {
|
||||
$vObject->add('METHOD', $method);
|
||||
}
|
||||
foreach ($instances as $instance) {
|
||||
$vObject->add($this->componentSanitizeScheduling(clone $instance));
|
||||
}
|
||||
|
||||
$message = new Message();
|
||||
$message->method = $method;
|
||||
$message->uid = $objectId;
|
||||
$message->component = $objectType;
|
||||
$message->sequence = $objectSequence;
|
||||
$message->sender = $organizerHref;
|
||||
$message->senderName = $organizerName;
|
||||
$message->recipient = $recipientAddress;
|
||||
$message->recipientName = $recipientName;
|
||||
$message->significantChange = true;
|
||||
$message->message = $vObject;
|
||||
|
||||
return $message;
|
||||
|
||||
}
|
||||
|
||||
protected function componentSanitizeScheduling(Component $component): Component {
|
||||
// Cleaning up any scheduling information that should not be sent or is missing
|
||||
unset($component->ORGANIZER['SCHEDULE-FORCE-SEND'], $component->ORGANIZER['SCHEDULE-STATUS']);
|
||||
foreach ($component->ATTENDEE as $attendee) {
|
||||
unset($attendee['SCHEDULE-FORCE-SEND'], $attendee['SCHEDULE-STATUS']);
|
||||
|
||||
if (!isset($attendee['PARTSTAT'])) {
|
||||
$attendee['PARTSTAT'] = 'NEEDS-ACTION';
|
||||
}
|
||||
}
|
||||
// Sequence is a required property, default is 0
|
||||
// https://datatracker.ietf.org/doc/html/rfc5545#section-3.8.7.4
|
||||
if ($component->SEQUENCE === null) {
|
||||
$component->add('SEQUENCE', 0);
|
||||
}
|
||||
|
||||
return $component;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
/**
|
||||
* SPDX-FileCopyrightText: 2016 ownCloud GmbH
|
||||
* SPDX-License-Identifier: AGPL-3.0-only
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
/**
|
||||
* SPDX-FileCopyrightText: 2021 Nextcloud GmbH and Nextcloud contributors
|
||||
* SPDX-License-Identifier: AGPL-3.0-or-later
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
/**
|
||||
* SPDX-FileCopyrightText: 2019 Nextcloud GmbH and Nextcloud contributors
|
||||
* SPDX-License-Identifier: AGPL-3.0-or-later
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
/**
|
||||
* SPDX-FileCopyrightText: 2016-2024 Nextcloud GmbH and Nextcloud contributors
|
||||
* SPDX-FileCopyrightText: 2016 ownCloud, Inc.
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
/**
|
||||
* SPDX-FileCopyrightText: 2021 Nextcloud GmbH and Nextcloud contributors
|
||||
* SPDX-License-Identifier: AGPL-3.0-only
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
/**
|
||||
* SPDX-FileCopyrightText: 2016-2024 Nextcloud GmbH and Nextcloud contributors
|
||||
* SPDX-FileCopyrightText: 2016 ownCloud, Inc.
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
/**
|
||||
* SPDX-FileCopyrightText: 2016-2024 Nextcloud GmbH and Nextcloud contributors
|
||||
* SPDX-FileCopyrightText: 2016 ownCloud, Inc.
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
/**
|
||||
* SPDX-FileCopyrightText: 2016-2024 Nextcloud GmbH and Nextcloud contributors
|
||||
* SPDX-FileCopyrightText: 2016 ownCloud, Inc.
|
||||
|
||||
@@ -236,7 +236,13 @@ class File extends Node implements IFile {
|
||||
// because we have no clue about the cause we can only throw back a 500/Internal Server Error
|
||||
throw new Exception($this->l10n->t('Could not write file contents'));
|
||||
}
|
||||
[$count, $result] = Files::streamCopy($data, $target, true);
|
||||
$count = stream_copy_to_stream($data, $target);
|
||||
if ($count === false) {
|
||||
$result = false;
|
||||
$count = 0;
|
||||
} else {
|
||||
$result = true;
|
||||
}
|
||||
fclose($target);
|
||||
}
|
||||
if ($result === false && $expected !== null) {
|
||||
|
||||
@@ -166,11 +166,6 @@ class FilesPlugin extends ServerPlugin {
|
||||
return;
|
||||
}
|
||||
|
||||
// Ensure source exists
|
||||
$sourceNodeFileInfo = $sourceNode->getFileInfo();
|
||||
if ($sourceNodeFileInfo === null) {
|
||||
throw new NotFound($source . ' does not exist');
|
||||
}
|
||||
// Ensure the target name is valid
|
||||
try {
|
||||
[$targetPath, $targetName] = \Sabre\Uri\split($target);
|
||||
|
||||
@@ -1,10 +1,11 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
/**
|
||||
* SPDX-FileCopyrightText: 2021 Nextcloud GmbH and Nextcloud contributors
|
||||
* SPDX-License-Identifier: AGPL-3.0-only
|
||||
*/
|
||||
|
||||
namespace OCA\DAV\Connector\Sabre;
|
||||
|
||||
class MtimeSanitizer {
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
/**
|
||||
* SPDX-FileCopyrightText: 2016-2024 Nextcloud GmbH and Nextcloud contributors
|
||||
* SPDX-FileCopyrightText: 2016 ownCloud, Inc.
|
||||
@@ -20,42 +22,48 @@ use OCP\Files\IRootFolder;
|
||||
use OCP\Files\NotFoundException;
|
||||
use OCP\Files\Storage\ISharedStorage;
|
||||
use OCP\Files\StorageNotAvailableException;
|
||||
use OCP\IUser;
|
||||
use OCP\Lock\ILockingProvider;
|
||||
use OCP\Lock\LockedException;
|
||||
use OCP\PreConditionNotMetException;
|
||||
use OCP\Server;
|
||||
use OCP\Share\Exceptions\ShareNotFound;
|
||||
use OCP\Share\IManager;
|
||||
use RuntimeException;
|
||||
use Sabre\DAV\Exception;
|
||||
use Sabre\DAV\Exception\Forbidden;
|
||||
use Sabre\DAV\INode;
|
||||
|
||||
abstract class Node implements \Sabre\DAV\INode {
|
||||
abstract class Node implements INode {
|
||||
/**
|
||||
* The path to the current node
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $path;
|
||||
protected string $path;
|
||||
|
||||
protected FileInfo $info;
|
||||
|
||||
/**
|
||||
* @var IManager
|
||||
*/
|
||||
protected $shareManager;
|
||||
protected IManager $shareManager;
|
||||
|
||||
protected \OCP\Files\Node $node;
|
||||
|
||||
/**
|
||||
* Sets up the node, expects a full path name
|
||||
* @throws PreConditionNotMetException
|
||||
*/
|
||||
public function __construct(
|
||||
protected View $fileView,
|
||||
FileInfo $info,
|
||||
?IManager $shareManager = null,
|
||||
) {
|
||||
$this->path = $this->fileView->getRelativePath($info->getPath());
|
||||
$this->info = $info;
|
||||
if ($shareManager) {
|
||||
$this->shareManager = $shareManager;
|
||||
} else {
|
||||
$this->shareManager = Server::get(\OCP\Share\IManager::class);
|
||||
$relativePath = $this->fileView->getRelativePath($info->getPath());
|
||||
if ($relativePath === null) {
|
||||
throw new RuntimeException('Failed to get relative path for ' . $info->getPath());
|
||||
}
|
||||
|
||||
$this->path = $relativePath;
|
||||
$this->info = $info;
|
||||
$this->shareManager = $shareManager instanceof IManager ? $shareManager : Server::get(IManager::class);
|
||||
|
||||
if ($info instanceof Folder || $info instanceof File) {
|
||||
$this->node = $info;
|
||||
} else {
|
||||
@@ -70,11 +78,16 @@ abstract class Node implements \Sabre\DAV\INode {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @throws Exception
|
||||
* @throws PreConditionNotMetException
|
||||
*/
|
||||
protected function refreshInfo(): void {
|
||||
$info = $this->fileView->getFileInfo($this->path);
|
||||
if ($info === false) {
|
||||
throw new \Sabre\DAV\Exception('Failed to get fileinfo for ' . $this->path);
|
||||
throw new Exception('Failed to get fileinfo for ' . $this->path);
|
||||
}
|
||||
|
||||
$this->info = $info;
|
||||
$root = Server::get(IRootFolder::class);
|
||||
$rootView = Server::get(View::class);
|
||||
@@ -87,19 +100,15 @@ abstract class Node implements \Sabre\DAV\INode {
|
||||
|
||||
/**
|
||||
* Returns the name of the node
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function getName() {
|
||||
public function getName(): string {
|
||||
return $this->info->getName();
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the full path
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function getPath() {
|
||||
public function getPath(): string {
|
||||
return $this->path;
|
||||
}
|
||||
|
||||
@@ -107,25 +116,30 @@ abstract class Node implements \Sabre\DAV\INode {
|
||||
* Renames the node
|
||||
*
|
||||
* @param string $name The new name
|
||||
* @throws \Sabre\DAV\Exception\BadRequest
|
||||
* @throws \Sabre\DAV\Exception\Forbidden
|
||||
* @throws Exception
|
||||
* @throws Forbidden
|
||||
* @throws InvalidPath
|
||||
* @throws PreConditionNotMetException
|
||||
* @throws LockedException
|
||||
*/
|
||||
public function setName($name) {
|
||||
public function setName($name): void {
|
||||
// rename is only allowed if the delete privilege is granted
|
||||
// (basically rename is a copy with delete of the original node)
|
||||
if (!($this->info->isDeletable() || ($this->info->getMountPoint() instanceof MoveableMount && $this->info->getInternalPath() === ''))) {
|
||||
throw new \Sabre\DAV\Exception\Forbidden();
|
||||
if (!$this->info->isDeletable() && !($this->info->getMountPoint() instanceof MoveableMount && $this->info->getInternalPath() === '')) {
|
||||
throw new Forbidden();
|
||||
}
|
||||
|
||||
/** @var string $parentPath */
|
||||
[$parentPath,] = \Sabre\Uri\split($this->path);
|
||||
/** @var string $newName */
|
||||
[, $newName] = \Sabre\Uri\split($name);
|
||||
$newPath = $parentPath . '/' . $newName;
|
||||
|
||||
// verify path of the target
|
||||
$this->verifyPath($newPath);
|
||||
|
||||
if (!$this->fileView->rename($this->path, $newPath)) {
|
||||
throw new \Sabre\DAV\Exception('Failed to rename ' . $this->path . ' to ' . $newPath);
|
||||
if ($this->fileView->rename($this->path, $newPath) === false) {
|
||||
throw new Exception('Failed to rename ' . $this->path . ' to ' . $newPath);
|
||||
}
|
||||
|
||||
$this->path = $newPath;
|
||||
@@ -138,12 +152,8 @@ abstract class Node implements \Sabre\DAV\INode {
|
||||
*
|
||||
* @return int timestamp as integer
|
||||
*/
|
||||
public function getLastModified() {
|
||||
$timestamp = $this->info->getMtime();
|
||||
if (!empty($timestamp)) {
|
||||
return (int)$timestamp;
|
||||
}
|
||||
return $timestamp;
|
||||
public function getLastModified(): int {
|
||||
return $this->info->getMtime();
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -151,7 +161,7 @@ abstract class Node implements \Sabre\DAV\INode {
|
||||
* in the second parameter or to now if the second param is empty.
|
||||
* Even if the modification time is set to a custom value the access time is set to now.
|
||||
*/
|
||||
public function touch($mtime) {
|
||||
public function touch(string $mtime): void {
|
||||
$mtime = $this->sanitizeMtime($mtime);
|
||||
$this->fileView->touch($this->path, $mtime);
|
||||
$this->refreshInfo();
|
||||
@@ -165,37 +175,29 @@ abstract class Node implements \Sabre\DAV\INode {
|
||||
* arbitrary string, but MUST be surrounded by double-quotes.
|
||||
*
|
||||
* Return null if the ETag can not effectively be determined
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function getETag() {
|
||||
public function getETag(): string {
|
||||
return '"' . $this->info->getEtag() . '"';
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the ETag
|
||||
*
|
||||
* @param string $etag
|
||||
*
|
||||
* @return int file id of updated file or -1 on failure
|
||||
*/
|
||||
public function setETag($etag) {
|
||||
public function setETag(string $etag): int {
|
||||
return $this->fileView->putFileInfo($this->path, ['etag' => $etag]);
|
||||
}
|
||||
|
||||
public function setCreationTime(int $time) {
|
||||
public function setCreationTime(int $time): int {
|
||||
return $this->fileView->putFileInfo($this->path, ['creation_time' => $time]);
|
||||
}
|
||||
|
||||
public function setUploadTime(int $time) {
|
||||
return $this->fileView->putFileInfo($this->path, ['upload_time' => $time]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the size of the node, in bytes
|
||||
*
|
||||
* @psalm-suppress UnusedPsalmSuppress psalm:strict actually thinks there is no mismatch, idk lol
|
||||
* @psalm-suppress ImplementedReturnTypeMismatch \Sabre\DAV\IFile::getSize signature does not support 32bit
|
||||
* @return int|float
|
||||
*/
|
||||
public function getSize(): int|float {
|
||||
return $this->info->getSize();
|
||||
@@ -203,28 +205,21 @@ abstract class Node implements \Sabre\DAV\INode {
|
||||
|
||||
/**
|
||||
* Returns the cache's file id
|
||||
*
|
||||
* @return int
|
||||
*/
|
||||
public function getId() {
|
||||
public function getId(): ?int {
|
||||
return $this->info->getId();
|
||||
}
|
||||
|
||||
/**
|
||||
* @return string|null
|
||||
*/
|
||||
public function getFileId() {
|
||||
if ($id = $this->info->getId()) {
|
||||
public function getFileId(): ?string {
|
||||
$id = $this->info->getId();
|
||||
if ($id !== null) {
|
||||
return DavUtil::getDavFileId($id);
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return integer
|
||||
*/
|
||||
public function getInternalFileId() {
|
||||
public function getInternalFileId(): ?int {
|
||||
return $this->info->getId();
|
||||
}
|
||||
|
||||
@@ -232,30 +227,24 @@ abstract class Node implements \Sabre\DAV\INode {
|
||||
return $this->info->getInternalPath();
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $user
|
||||
* @return int
|
||||
*/
|
||||
public function getSharePermissions($user) {
|
||||
public function getSharePermissions(?string $user): int {
|
||||
// check of we access a federated share
|
||||
if ($user !== null) {
|
||||
try {
|
||||
$share = $this->shareManager->getShareByToken($user);
|
||||
return $share->getPermissions();
|
||||
} catch (ShareNotFound $e) {
|
||||
return $this->shareManager->getShareByToken($user)->getPermissions();
|
||||
} catch (ShareNotFound) {
|
||||
// ignore
|
||||
}
|
||||
}
|
||||
|
||||
try {
|
||||
$storage = $this->info->getStorage();
|
||||
} catch (StorageNotAvailableException $e) {
|
||||
} catch (StorageNotAvailableException) {
|
||||
$storage = null;
|
||||
}
|
||||
|
||||
if ($storage && $storage->instanceOfStorage(ISharedStorage::class)) {
|
||||
/** @var ISharedStorage $storage */
|
||||
$permissions = (int)$storage->getShare()->getPermissions();
|
||||
$permissions = $storage->getShare()->getPermissions();
|
||||
} else {
|
||||
$permissions = $this->info->getPermissions();
|
||||
}
|
||||
@@ -266,6 +255,10 @@ abstract class Node implements \Sabre\DAV\INode {
|
||||
*/
|
||||
$mountpoint = $this->info->getMountPoint();
|
||||
if (!($mountpoint instanceof MoveableMount)) {
|
||||
/**
|
||||
* @psalm-suppress UnnecessaryVarAnnotation Rector doesn't trust the return type annotation
|
||||
* @var string $mountpointpath
|
||||
*/
|
||||
$mountpointpath = $mountpoint->getMountPoint();
|
||||
if (str_ends_with($mountpointpath, '/')) {
|
||||
$mountpointpath = substr($mountpointpath, 0, -1);
|
||||
@@ -286,25 +279,21 @@ abstract class Node implements \Sabre\DAV\INode {
|
||||
return $permissions;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array
|
||||
*/
|
||||
public function getShareAttributes(): array {
|
||||
try {
|
||||
$storage = $this->node->getStorage();
|
||||
} catch (NotFoundException $e) {
|
||||
} catch (NotFoundException) {
|
||||
return [];
|
||||
}
|
||||
|
||||
$attributes = [];
|
||||
if ($storage->instanceOfStorage(ISharedStorage::class)) {
|
||||
/** @var ISharedStorage $storage */
|
||||
$attributes = $storage->getShare()->getAttributes();
|
||||
if ($attributes === null) {
|
||||
return [];
|
||||
} else {
|
||||
return $attributes->toArray();
|
||||
}
|
||||
|
||||
return $attributes->toArray();
|
||||
}
|
||||
|
||||
return $attributes;
|
||||
@@ -318,63 +307,66 @@ abstract class Node implements \Sabre\DAV\INode {
|
||||
}
|
||||
|
||||
if ($storage->instanceOfStorage(ISharedStorage::class)) {
|
||||
/** @var ISharedStorage $storage */
|
||||
$share = $storage->getShare();
|
||||
if ($user === $share->getShareOwner()) {
|
||||
// Note is only for recipient not the owner
|
||||
return null;
|
||||
}
|
||||
|
||||
return $share->getNote();
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return string
|
||||
*/
|
||||
public function getDavPermissions() {
|
||||
public function getDavPermissions(): string {
|
||||
return DavUtil::getDavPermissions($this->info);
|
||||
}
|
||||
|
||||
public function getOwner() {
|
||||
public function getOwner(): ?IUser {
|
||||
return $this->info->getOwner();
|
||||
}
|
||||
|
||||
/**
|
||||
* @throws InvalidPath
|
||||
*/
|
||||
protected function verifyPath(?string $path = null): void {
|
||||
try {
|
||||
$path = $path ?? $this->info->getPath();
|
||||
$path ??= $this->info->getPath();
|
||||
$this->fileView->verifyPath(
|
||||
dirname($path),
|
||||
basename($path),
|
||||
);
|
||||
} catch (InvalidPathException $ex) {
|
||||
throw new InvalidPath($ex->getMessage());
|
||||
} catch (InvalidPathException $invalidPathException) {
|
||||
throw new InvalidPath($invalidPathException->getMessage(), false, $invalidPathException);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @param int $type \OCP\Lock\ILockingProvider::LOCK_SHARED or \OCP\Lock\ILockingProvider::LOCK_EXCLUSIVE
|
||||
* @param ILockingProvider::LOCK_* $type
|
||||
* @throws LockedException
|
||||
*/
|
||||
public function acquireLock($type) {
|
||||
public function acquireLock($type): void {
|
||||
$this->fileView->lockFile($this->path, $type);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param int $type \OCP\Lock\ILockingProvider::LOCK_SHARED or \OCP\Lock\ILockingProvider::LOCK_EXCLUSIVE
|
||||
* @param ILockingProvider::LOCK_* $type
|
||||
* @throws LockedException
|
||||
*/
|
||||
public function releaseLock($type) {
|
||||
public function releaseLock($type): void {
|
||||
$this->fileView->unlockFile($this->path, $type);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param int $type \OCP\Lock\ILockingProvider::LOCK_SHARED or \OCP\Lock\ILockingProvider::LOCK_EXCLUSIVE
|
||||
* @param ILockingProvider::LOCK_* $type
|
||||
* @throws LockedException
|
||||
*/
|
||||
public function changeLock($type) {
|
||||
public function changeLock($type): void {
|
||||
$this->fileView->changeLock($this->path, $type);
|
||||
}
|
||||
|
||||
public function getFileInfo() {
|
||||
public function getFileInfo(): FileInfo {
|
||||
return $this->info;
|
||||
}
|
||||
|
||||
|
||||
@@ -42,9 +42,6 @@ class Principal implements BackendInterface {
|
||||
/** @var bool */
|
||||
private $hasCircles;
|
||||
|
||||
/** @var KnownUserService */
|
||||
private $knownUserService;
|
||||
|
||||
public function __construct(
|
||||
private IUserManager $userManager,
|
||||
private IGroupManager $groupManager,
|
||||
@@ -53,14 +50,13 @@ class Principal implements BackendInterface {
|
||||
private IUserSession $userSession,
|
||||
private IAppManager $appManager,
|
||||
private ProxyMapper $proxyMapper,
|
||||
KnownUserService $knownUserService,
|
||||
private KnownUserService $knownUserService,
|
||||
private IConfig $config,
|
||||
private IFactory $languageFactory,
|
||||
string $principalPrefix = 'principals/users/',
|
||||
) {
|
||||
$this->principalPrefix = trim($principalPrefix, '/');
|
||||
$this->hasGroups = $this->hasCircles = ($principalPrefix === 'principals/users/');
|
||||
$this->knownUserService = $knownUserService;
|
||||
}
|
||||
|
||||
use PrincipalProxyTrait {
|
||||
|
||||
@@ -49,11 +49,7 @@ class TagsPlugin extends \Sabre\DAV\ServerPlugin {
|
||||
* @var \Sabre\DAV\Server
|
||||
*/
|
||||
private $server;
|
||||
|
||||
/**
|
||||
* @var ITags
|
||||
*/
|
||||
private $tagger;
|
||||
private ?ITags $tagger = null;
|
||||
|
||||
/**
|
||||
* Array of file id to tags array
|
||||
@@ -105,11 +101,17 @@ class TagsPlugin extends \Sabre\DAV\ServerPlugin {
|
||||
*
|
||||
* @return ITags tagger
|
||||
*/
|
||||
private function getTagger() {
|
||||
if (!$this->tagger) {
|
||||
$this->tagger = $this->tagManager->load('files');
|
||||
private function getTagger(): ITags {
|
||||
if ($this->tagger) {
|
||||
return $this->tagger;
|
||||
}
|
||||
return $this->tagger;
|
||||
|
||||
$tagger = $this->tagManager->load('files');
|
||||
if ($tagger === null) {
|
||||
throw new \RuntimeException('Tagger not found for files');
|
||||
}
|
||||
$this->tagger = $tagger;
|
||||
return $tagger;
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
/**
|
||||
* SPDX-FileCopyrightText: 2016-2024 Nextcloud GmbH and Nextcloud contributors
|
||||
* SPDX-FileCopyrightText: 2016 ownCloud, Inc.
|
||||
|
||||
@@ -86,6 +86,7 @@ class FileSearchBackend implements ISearchBackend {
|
||||
new SearchPropertyDefinition('{DAV:}displayname', true, true, true),
|
||||
new SearchPropertyDefinition('{DAV:}getcontenttype', true, true, true),
|
||||
new SearchPropertyDefinition('{DAV:}getlastmodified', true, true, true, SearchPropertyDefinition::DATATYPE_DATETIME),
|
||||
new SearchPropertyDefinition('{http://nextcloud.org/ns}upload_time', true, true, true, SearchPropertyDefinition::DATATYPE_DATETIME),
|
||||
new SearchPropertyDefinition(FilesPlugin::SIZE_PROPERTYNAME, true, true, true, SearchPropertyDefinition::DATATYPE_NONNEGATIVE_INTEGER),
|
||||
new SearchPropertyDefinition(TagsPlugin::FAVORITE_PROPERTYNAME, true, true, true, SearchPropertyDefinition::DATATYPE_BOOLEAN),
|
||||
new SearchPropertyDefinition(FilesPlugin::INTERNAL_FILEID_PROPERTYNAME, true, true, false, SearchPropertyDefinition::DATATYPE_NONNEGATIVE_INTEGER),
|
||||
@@ -298,6 +299,8 @@ class FileSearchBackend implements ISearchBackend {
|
||||
return $node->getName();
|
||||
case '{DAV:}getlastmodified':
|
||||
return $node->getLastModified();
|
||||
case '{http://nextcloud.org/ns}upload_time':
|
||||
return $node->getNode()->getUploadTime();
|
||||
case FilesPlugin::SIZE_PROPERTYNAME:
|
||||
return $node->getSize();
|
||||
case FilesPlugin::INTERNAL_FILEID_PROPERTYNAME:
|
||||
@@ -458,6 +461,8 @@ class FileSearchBackend implements ISearchBackend {
|
||||
return 'mimetype';
|
||||
case '{DAV:}getlastmodified':
|
||||
return 'mtime';
|
||||
case '{http://nextcloud.org/ns}upload_time':
|
||||
return 'upload_time';
|
||||
case FilesPlugin::SIZE_PROPERTYNAME:
|
||||
return 'size';
|
||||
case TagsPlugin::FAVORITE_PROPERTYNAME:
|
||||
|
||||
@@ -32,12 +32,12 @@ class FilesHome extends Directory {
|
||||
throw new Forbidden('Permission denied to delete home folder');
|
||||
}
|
||||
|
||||
public function getName() {
|
||||
public function getName(): string {
|
||||
[,$name] = \Sabre\Uri\split($this->principalInfo['uri']);
|
||||
return $name;
|
||||
}
|
||||
|
||||
public function setName($name) {
|
||||
public function setName($name): void {
|
||||
throw new Forbidden('Permission denied to rename this folder');
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
/**
|
||||
* SPDX-FileCopyrightText: 2016-2024 Nextcloud GmbH and Nextcloud contributors
|
||||
* SPDX-FileCopyrightText: 2016 ownCloud, Inc.
|
||||
|
||||
@@ -20,6 +20,8 @@ use OCP\BackgroundJob\IJobList;
|
||||
use OCP\Defaults;
|
||||
use OCP\EventDispatcher\Event;
|
||||
use OCP\EventDispatcher\IEventListener;
|
||||
use OCP\Group\Events\BeforeGroupDeletedEvent;
|
||||
use OCP\Group\Events\GroupDeletedEvent;
|
||||
use OCP\IUser;
|
||||
use OCP\IUserManager;
|
||||
use OCP\User\Events\BeforeUserDeletedEvent;
|
||||
@@ -32,7 +34,7 @@ use OCP\User\Events\UserIdAssignedEvent;
|
||||
use OCP\User\Events\UserIdUnassignedEvent;
|
||||
use Psr\Log\LoggerInterface;
|
||||
|
||||
/** @template-implements IEventListener<UserFirstTimeLoggedInEvent|UserIdAssignedEvent|BeforeUserIdUnassignedEvent|UserIdUnassignedEvent|BeforeUserDeletedEvent|UserDeletedEvent|UserCreatedEvent|UserChangedEvent|UserUpdatedEvent> */
|
||||
/** @template-implements IEventListener<UserFirstTimeLoggedInEvent|UserIdAssignedEvent|BeforeUserIdUnassignedEvent|UserIdUnassignedEvent|BeforeUserDeletedEvent|UserDeletedEvent|UserCreatedEvent|UserChangedEvent|UserUpdatedEvent|BeforeGroupDeletedEvent|GroupDeletedEvent> */
|
||||
class UserEventsListener implements IEventListener {
|
||||
|
||||
/** @var IUser[] */
|
||||
@@ -77,6 +79,8 @@ class UserEventsListener implements IEventListener {
|
||||
$this->firstLogin($event->getUser());
|
||||
} elseif ($event instanceof UserUpdatedEvent) {
|
||||
$this->updateUser($event->getUser());
|
||||
} elseif ($event instanceof GroupDeletedEvent) {
|
||||
$this->postDeleteGroup($event->getGroup()->getGID());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -135,6 +139,12 @@ class UserEventsListener implements IEventListener {
|
||||
unset($this->addressBooksToDelete[$uid]);
|
||||
}
|
||||
|
||||
public function postDeleteGroup(string $gid): void {
|
||||
$encodedGid = urlencode($gid);
|
||||
$this->calDav->deleteAllSharesByUser('principals/groups/' . $encodedGid);
|
||||
$this->cardDav->deleteAllSharesByUser('principals/groups/' . $encodedGid);
|
||||
}
|
||||
|
||||
public function changeUser(IUser $user, string $feature): void {
|
||||
// This case is already covered by the account manager firing up a signal
|
||||
// later on
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
/**
|
||||
* SPDX-FileCopyrightText: 2017 Nextcloud GmbH and Nextcloud contributors
|
||||
* SPDX-License-Identifier: AGPL-3.0-or-later
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
/**
|
||||
* SPDX-FileCopyrightText: 2017 Nextcloud GmbH and Nextcloud contributors
|
||||
* SPDX-License-Identifier: AGPL-3.0-or-later
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
/**
|
||||
* SPDX-FileCopyrightText: 2019 Nextcloud GmbH and Nextcloud contributors
|
||||
* SPDX-License-Identifier: AGPL-3.0-or-later
|
||||
|
||||
@@ -16,27 +16,16 @@ class RemoveObjectProperties implements IRepairStep {
|
||||
private const ME_CARD_PROPERTY = '{http://calendarserver.org/ns/}me-card';
|
||||
private const CALENDAR_TRANSP_PROPERTY = '{urn:ietf:params:xml:ns:caldav}schedule-calendar-transp';
|
||||
|
||||
/**
|
||||
* RemoveObjectProperties constructor.
|
||||
*
|
||||
* @param IDBConnection $connection
|
||||
*/
|
||||
public function __construct(
|
||||
private IDBConnection $connection,
|
||||
private readonly IDBConnection $connection,
|
||||
) {
|
||||
}
|
||||
|
||||
/**
|
||||
* @inheritdoc
|
||||
*/
|
||||
public function getName() {
|
||||
public function getName(): string {
|
||||
return 'Remove invalid object properties';
|
||||
}
|
||||
|
||||
/**
|
||||
* @inheritdoc
|
||||
*/
|
||||
public function run(IOutput $output) {
|
||||
public function run(IOutput $output): void {
|
||||
$query = $this->connection->getQueryBuilder();
|
||||
$updated = $query->delete('properties')
|
||||
->where($query->expr()->in('propertyname', $query->createNamedParameter([self::RESOURCE_TYPE_PROPERTY, self::ME_CARD_PROPERTY, self::CALENDAR_TRANSP_PROPERTY], IQueryBuilder::PARAM_STR_ARRAY)))
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
/**
|
||||
* SPDX-FileCopyrightText: 2016 Nextcloud GmbH and Nextcloud contributors
|
||||
* SPDX-License-Identifier: AGPL-3.0-or-later
|
||||
|
||||
@@ -17,21 +17,17 @@ use OCP\DB\Types;
|
||||
use OCP\IDBConnection;
|
||||
use OCP\Migration\IOutput;
|
||||
use OCP\Migration\SimpleMigrationStep;
|
||||
use Override;
|
||||
|
||||
class Version1025Date20240308063933 extends SimpleMigrationStep {
|
||||
|
||||
public function __construct(
|
||||
private IAppConfig $appConfig,
|
||||
private IDBConnection $db,
|
||||
private readonly IAppConfig $appConfig,
|
||||
private readonly IDBConnection $db,
|
||||
) {
|
||||
}
|
||||
|
||||
/**
|
||||
* @param IOutput $output
|
||||
* @param Closure(): ISchemaWrapper $schemaClosure
|
||||
* @param array $options
|
||||
* @return null|ISchemaWrapper
|
||||
*/
|
||||
#[Override]
|
||||
public function changeSchema(IOutput $output, Closure $schemaClosure, array $options): ?ISchemaWrapper {
|
||||
/** @var ISchemaWrapper $schema */
|
||||
$schema = $schemaClosure();
|
||||
@@ -50,6 +46,7 @@ class Version1025Date20240308063933 extends SimpleMigrationStep {
|
||||
return $schema;
|
||||
}
|
||||
|
||||
#[Override]
|
||||
public function postSchemaChange(IOutput $output, \Closure $schemaClosure, array $options): void {
|
||||
// The threshold is higher than the default of \OCA\DAV\BackgroundJob\PruneOutdatedSyncTokensJob
|
||||
// but small enough to fit into a cluster transaction size.
|
||||
|
||||
@@ -11,11 +11,11 @@ namespace OCA\DAV\Migration;
|
||||
|
||||
use Closure;
|
||||
use OCA\DAV\CardDAV\SyncService;
|
||||
use OCP\DB\ISchemaWrapper;
|
||||
use OCP\IConfig;
|
||||
use OCP\IUserManager;
|
||||
use OCP\Migration\IOutput;
|
||||
use OCP\Migration\SimpleMigrationStep;
|
||||
use Override;
|
||||
use Psr\Log\LoggerInterface;
|
||||
use Throwable;
|
||||
|
||||
@@ -27,11 +27,8 @@ class Version1027Date20230504122946 extends SimpleMigrationStep {
|
||||
private IConfig $config,
|
||||
) {
|
||||
}
|
||||
/**
|
||||
* @param IOutput $output
|
||||
* @param Closure(): ISchemaWrapper $schemaClosure
|
||||
* @param array $options
|
||||
*/
|
||||
|
||||
#[Override]
|
||||
public function postSchemaChange(IOutput $output, Closure $schemaClosure, array $options): void {
|
||||
if ($this->userManager->countSeenUsers() > 100 || $this->userManager->countUsersTotal(100) >= 100) {
|
||||
$this->config->setAppValue('dav', 'needs_system_address_book_sync', 'yes');
|
||||
|
||||
@@ -12,16 +12,14 @@ namespace OCA\DAV\Migration;
|
||||
use Closure;
|
||||
use OCP\DB\ISchemaWrapper;
|
||||
use OCP\DB\Types;
|
||||
use OCP\Migration\Attributes\CreateTable;
|
||||
use OCP\Migration\IOutput;
|
||||
use OCP\Migration\SimpleMigrationStep;
|
||||
use Override;
|
||||
|
||||
#[CreateTable(table: 'dav_absence')]
|
||||
class Version1029Date20231004091403 extends SimpleMigrationStep {
|
||||
/**
|
||||
* @param IOutput $output
|
||||
* @param Closure(): ISchemaWrapper $schemaClosure
|
||||
* @param array $options
|
||||
* @return null|ISchemaWrapper
|
||||
*/
|
||||
#[Override]
|
||||
public function changeSchema(IOutput $output, Closure $schemaClosure, array $options): ?ISchemaWrapper {
|
||||
/** @var ISchemaWrapper $schema */
|
||||
$schema = $schemaClosure();
|
||||
|
||||
@@ -11,17 +11,14 @@ namespace OCA\DAV\Migration;
|
||||
|
||||
use Closure;
|
||||
use OCP\DB\ISchemaWrapper;
|
||||
use OCP\Migration\Attributes\DropIndex;
|
||||
use OCP\Migration\IOutput;
|
||||
use OCP\Migration\SimpleMigrationStep;
|
||||
use Override;
|
||||
|
||||
#[DropIndex(table: 'cards')]
|
||||
class Version1030Date20240205103243 extends SimpleMigrationStep {
|
||||
|
||||
/**
|
||||
* @param IOutput $output
|
||||
* @param Closure(): ISchemaWrapper $schemaClosure
|
||||
* @param array $options
|
||||
* @return null|ISchemaWrapper
|
||||
*/
|
||||
#[Override]
|
||||
public function changeSchema(IOutput $output, Closure $schemaClosure, array $options): ?ISchemaWrapper {
|
||||
/** @var ISchemaWrapper $schema */
|
||||
$schema = $schemaClosure();
|
||||
|
||||
@@ -17,16 +17,12 @@ use OCP\Migration\Attributes\ColumnType;
|
||||
use OCP\Migration\Attributes\CreateTable;
|
||||
use OCP\Migration\IOutput;
|
||||
use OCP\Migration\SimpleMigrationStep;
|
||||
use Override;
|
||||
|
||||
#[AddColumn(table: 'dav_shares', name: 'token', type: ColumnType::STRING)]
|
||||
#[CreateTable(table: 'calendars_federated', columns: ['id', 'display_name', 'color', 'uri', 'principaluri', 'remote_Url', 'token', 'sync_token', 'last_sync', 'shared_by', 'shared_by_display_name', 'components', 'permissions'], description: 'Supporting Federated Calender')]
|
||||
class Version1034Date20250605132605 extends SimpleMigrationStep {
|
||||
/**
|
||||
* @param IOutput $output
|
||||
* @param Closure(): ISchemaWrapper $schemaClosure
|
||||
* @param array $options
|
||||
* @return null|ISchemaWrapper
|
||||
*/
|
||||
#[Override]
|
||||
public function changeSchema(IOutput $output, Closure $schemaClosure, array $options): ?ISchemaWrapper {
|
||||
/** @var ISchemaWrapper $schema */
|
||||
$schema = $schemaClosure();
|
||||
|
||||
@@ -10,7 +10,6 @@ declare(strict_types=1);
|
||||
namespace OCA\DAV\Migration;
|
||||
|
||||
use Closure;
|
||||
use OCP\DB\ISchemaWrapper;
|
||||
use OCP\DB\QueryBuilder\IQueryBuilder;
|
||||
use OCP\IDBConnection;
|
||||
use OCP\Migration\Attributes\DataCleansing;
|
||||
@@ -25,11 +24,6 @@ class Version1034Date20250813093701 extends SimpleMigrationStep {
|
||||
) {
|
||||
}
|
||||
|
||||
/**
|
||||
* @param IOutput $output
|
||||
* @param Closure(): ISchemaWrapper $schemaClosure
|
||||
* @param array $options
|
||||
*/
|
||||
#[Override]
|
||||
public function postSchemaChange(IOutput $output, Closure $schemaClosure, array $options): void {
|
||||
$qb = $this->db->getQueryBuilder();
|
||||
|
||||
@@ -21,9 +21,6 @@ use Override;
|
||||
#[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 */
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
/**
|
||||
* SPDX-FileCopyrightText: 2018 Nextcloud GmbH and Nextcloud contributors
|
||||
* SPDX-License-Identifier: AGPL-3.0-or-later
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
/**
|
||||
* SPDX-FileCopyrightText: 2018 Nextcloud GmbH and Nextcloud contributors
|
||||
* SPDX-License-Identifier: AGPL-3.0-or-later
|
||||
@@ -22,23 +24,17 @@ class AppleProvisioningPlugin extends ServerPlugin {
|
||||
*/
|
||||
protected $server;
|
||||
|
||||
/**
|
||||
* @var \OC_Defaults
|
||||
*/
|
||||
protected $themingDefaults;
|
||||
|
||||
/**
|
||||
* AppleProvisioningPlugin constructor.
|
||||
*/
|
||||
public function __construct(
|
||||
protected IUserSession $userSession,
|
||||
protected IURLGenerator $urlGenerator,
|
||||
\OC_Defaults $themingDefaults,
|
||||
protected \OC_Defaults $themingDefaults,
|
||||
protected IRequest $request,
|
||||
protected IL10N $l10n,
|
||||
protected \Closure $uuidClosure,
|
||||
) {
|
||||
$this->themingDefaults = $themingDefaults;
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
/**
|
||||
* SPDX-FileCopyrightText: 2017 Nextcloud GmbH and Nextcloud contributors
|
||||
* SPDX-License-Identifier: AGPL-3.0-or-later
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
/**
|
||||
* SPDX-FileCopyrightText: 2023 Nextcloud GmbH and Nextcloud contributors
|
||||
* SPDX-License-Identifier: AGPL-3.0-only
|
||||
|
||||
@@ -22,18 +22,15 @@ use Sabre\DAV\Exception\NotFound;
|
||||
use Sabre\DAV\SimpleCollection;
|
||||
|
||||
class SystemTagsInUseCollection extends SimpleCollection {
|
||||
protected SystemTagsInFilesDetector $systemTagsInFilesDetector;
|
||||
|
||||
/** @noinspection PhpMissingParentConstructorInspection */
|
||||
public function __construct(
|
||||
protected IUserSession $userSession,
|
||||
protected IRootFolder $rootFolder,
|
||||
protected ISystemTagManager $systemTagManager,
|
||||
protected ISystemTagObjectMapper $tagMapper,
|
||||
SystemTagsInFilesDetector $systemTagsInFilesDetector,
|
||||
protected SystemTagsInFilesDetector $systemTagsInFilesDetector,
|
||||
protected string $mediaType = '',
|
||||
) {
|
||||
$this->systemTagsInFilesDetector = $systemTagsInFilesDetector;
|
||||
$this->name = 'systemtags-assigned';
|
||||
if ($this->mediaType != '') {
|
||||
$this->name .= '/' . $this->mediaType;
|
||||
|
||||
@@ -15,11 +15,19 @@ class TipBrokerTest extends TestCase {
|
||||
|
||||
private TipBroker $broker;
|
||||
private VCalendar $vCalendar1a;
|
||||
private VCalendar $vCalendar2a;
|
||||
private array $templateEventInfo;
|
||||
|
||||
protected function setUp(): void {
|
||||
parent::setUp();
|
||||
|
||||
$this->broker = new TipBroker();
|
||||
|
||||
$this->templateEventInfo = [
|
||||
'organizer' => null,
|
||||
'attendees' => [],
|
||||
'significantChangeHash' => '',
|
||||
];
|
||||
// construct calendar with a 1 hour event and same start/end time zones
|
||||
$this->vCalendar1a = new VCalendar();
|
||||
/** @var VEvent $vEvent */
|
||||
@@ -28,7 +36,7 @@ class TipBrokerTest extends TestCase {
|
||||
$vEvent->add('DTSTAMP', '20240701T000000Z');
|
||||
$vEvent->add('CREATED', '20240701T000000Z');
|
||||
$vEvent->add('LAST-MODIFIED', '20240701T000000Z');
|
||||
$vEvent->add('SEQUENCE', '1');
|
||||
$vEvent->add('SEQUENCE', 1);
|
||||
$vEvent->add('STATUS', 'CONFIRMED');
|
||||
$vEvent->add('DTSTART', '20240701T080000', ['TZID' => 'America/Toronto']);
|
||||
$vEvent->add('DTEND', '20240701T090000', ['TZID' => 'America/Toronto']);
|
||||
@@ -41,140 +49,534 @@ class TipBrokerTest extends TestCase {
|
||||
'ROLE' => 'REQ-PARTICIPANT',
|
||||
'RSVP' => 'TRUE'
|
||||
]);
|
||||
}
|
||||
|
||||
public function testParseEventForOrganizerOnCreate(): void {
|
||||
|
||||
// construct calendar and generate event info for newly created event with one attendee
|
||||
$calendar = clone $this->vCalendar1a;
|
||||
$previousEventInfo = [
|
||||
'organizer' => null,
|
||||
'significantChangeHash' => '',
|
||||
'attendees' => [],
|
||||
];
|
||||
$currentEventInfo = $this->invokePrivate($this->broker, 'parseEventInfo', [$calendar]);
|
||||
// test iTip generation
|
||||
$messages = $this->invokePrivate($this->broker, 'parseEventForOrganizer', [$calendar, $currentEventInfo, $previousEventInfo]);
|
||||
$this->assertCount(1, $messages);
|
||||
$this->assertEquals('REQUEST', $messages[0]->method);
|
||||
$this->assertEquals($calendar->VEVENT->ORGANIZER->getValue(), $messages[0]->sender);
|
||||
$this->assertEquals($calendar->VEVENT->ATTENDEE[0]->getValue(), $messages[0]->recipient);
|
||||
|
||||
}
|
||||
|
||||
public function testParseEventForOrganizerOnModify(): void {
|
||||
|
||||
// construct calendar and generate event info for modified event with one attendee
|
||||
$calendar = clone $this->vCalendar1a;
|
||||
$previousEventInfo = $this->invokePrivate($this->broker, 'parseEventInfo', [$calendar]);
|
||||
$calendar->VEVENT->{'LAST-MODIFIED'}->setValue('20240701T020000Z');
|
||||
$calendar->VEVENT->SEQUENCE->setValue(2);
|
||||
$calendar->VEVENT->SUMMARY->setValue('Test Event Modified');
|
||||
$currentEventInfo = $this->invokePrivate($this->broker, 'parseEventInfo', [$calendar]);
|
||||
// test iTip generation
|
||||
$messages = $this->invokePrivate($this->broker, 'parseEventForOrganizer', [$calendar, $currentEventInfo, $previousEventInfo]);
|
||||
$this->assertCount(1, $messages);
|
||||
$this->assertEquals('REQUEST', $messages[0]->method);
|
||||
$this->assertEquals($calendar->VEVENT->ORGANIZER->getValue(), $messages[0]->sender);
|
||||
$this->assertEquals($calendar->VEVENT->ATTENDEE[0]->getValue(), $messages[0]->recipient);
|
||||
|
||||
}
|
||||
|
||||
public function testParseEventForOrganizerOnDelete(): void {
|
||||
|
||||
// construct calendar and generate event info for modified event with one attendee
|
||||
$calendar = clone $this->vCalendar1a;
|
||||
$previousEventInfo = $this->invokePrivate($this->broker, 'parseEventInfo', [$calendar]);
|
||||
$currentEventInfo = $previousEventInfo;
|
||||
$currentEventInfo['attendees'] = [];
|
||||
++$currentEventInfo['sequence'];
|
||||
// test iTip generation
|
||||
$messages = $this->invokePrivate($this->broker, 'parseEventForOrganizer', [$calendar, $currentEventInfo, $previousEventInfo]);
|
||||
$this->assertCount(1, $messages);
|
||||
$this->assertEquals('CANCEL', $messages[0]->method);
|
||||
$this->assertEquals($calendar->VEVENT->ORGANIZER->getValue(), $messages[0]->sender);
|
||||
$this->assertEquals($calendar->VEVENT->ATTENDEE[0]->getValue(), $messages[0]->recipient);
|
||||
|
||||
}
|
||||
|
||||
public function testParseEventForOrganizerOnStatusCancelled(): void {
|
||||
|
||||
// construct calendar and generate event info for modified event with one attendee
|
||||
$calendar = clone $this->vCalendar1a;
|
||||
$previousEventInfo = $this->invokePrivate($this->broker, 'parseEventInfo', [$calendar]);
|
||||
$calendar->VEVENT->{'LAST-MODIFIED'}->setValue('20240701T020000Z');
|
||||
$calendar->VEVENT->SEQUENCE->setValue(2);
|
||||
$calendar->VEVENT->STATUS->setValue('CANCELLED');
|
||||
$currentEventInfo = $this->invokePrivate($this->broker, 'parseEventInfo', [$calendar]);
|
||||
// test iTip generation
|
||||
$messages = $this->invokePrivate($this->broker, 'parseEventForOrganizer', [$calendar, $currentEventInfo, $previousEventInfo]);
|
||||
$this->assertCount(1, $messages);
|
||||
$this->assertEquals('CANCEL', $messages[0]->method);
|
||||
$this->assertEquals($calendar->VEVENT->ORGANIZER->getValue(), $messages[0]->sender);
|
||||
$this->assertEquals($calendar->VEVENT->ATTENDEE[0]->getValue(), $messages[0]->recipient);
|
||||
|
||||
}
|
||||
|
||||
public function testParseEventForOrganizerOnAddAttendee(): void {
|
||||
|
||||
// construct calendar and generate event info for modified event with two attendees
|
||||
$calendar = clone $this->vCalendar1a;
|
||||
$previousEventInfo = $this->invokePrivate($this->broker, 'parseEventInfo', [$calendar]);
|
||||
$calendar->VEVENT->{'LAST-MODIFIED'}->setValue('20240701T020000Z');
|
||||
$calendar->VEVENT->SEQUENCE->setValue(2);
|
||||
$calendar->VEVENT->add('ATTENDEE', 'mailto:attendee2@testing.com', [
|
||||
'CN' => 'Attendee Two',
|
||||
'CUTYPE' => 'INDIVIDUAL',
|
||||
'PARTSTAT' => 'NEEDS-ACTION',
|
||||
'ROLE' => 'REQ-PARTICIPANT',
|
||||
'RSVP' => 'TRUE'
|
||||
]);
|
||||
$currentEventInfo = $this->invokePrivate($this->broker, 'parseEventInfo', [$calendar]);
|
||||
// test iTip generation
|
||||
$messages = $this->invokePrivate($this->broker, 'parseEventForOrganizer', [$calendar, $currentEventInfo, $previousEventInfo]);
|
||||
$this->assertCount(2, $messages);
|
||||
$this->assertEquals('REQUEST', $messages[0]->method);
|
||||
$this->assertEquals($calendar->VEVENT->ORGANIZER->getValue(), $messages[0]->sender);
|
||||
$this->assertEquals($calendar->VEVENT->ATTENDEE[0]->getValue(), $messages[0]->recipient);
|
||||
$this->assertEquals('REQUEST', $messages[1]->method);
|
||||
$this->assertEquals($calendar->VEVENT->ORGANIZER->getValue(), $messages[1]->sender);
|
||||
$this->assertEquals($calendar->VEVENT->ATTENDEE[1]->getValue(), $messages[1]->recipient);
|
||||
|
||||
}
|
||||
|
||||
public function testParseEventForOrganizerOnRemoveAttendee(): void {
|
||||
|
||||
// construct calendar and generate event info for modified event with two attendees
|
||||
$calendar = clone $this->vCalendar1a;
|
||||
$calendar->VEVENT->add('ATTENDEE', 'mailto:attendee2@testing.com', [
|
||||
'CN' => 'Attendee Two',
|
||||
'CUTYPE' => 'INDIVIDUAL',
|
||||
'PARTSTAT' => 'NEEDS-ACTION',
|
||||
'ROLE' => 'REQ-PARTICIPANT',
|
||||
'RSVP' => 'TRUE'
|
||||
]);
|
||||
$previousEventInfo = $this->invokePrivate($this->broker, 'parseEventInfo', [$calendar]);
|
||||
$calendar->VEVENT->{'LAST-MODIFIED'}->setValue('20240701T020000Z');
|
||||
$calendar->VEVENT->SEQUENCE->setValue(2);
|
||||
$calendar->VEVENT->remove('ATTENDEE');
|
||||
$calendar->VEVENT->add('ATTENDEE', 'mailto:attendee1@testing.com', [
|
||||
// construct calendar with a 1 hour event and same start/end time zones
|
||||
// recurring every week on Monday for 12 weeks
|
||||
$this->vCalendar2a = new VCalendar();
|
||||
/** @var VEvent $vEvent */
|
||||
$vEvent = $this->vCalendar2a->add('VEVENT', []);
|
||||
$vEvent->add('UID', '96a0e6b1-d886-4a55-a60d-152b31401dcc');
|
||||
$vEvent->add('DTSTAMP', '20240701T000000Z');
|
||||
$vEvent->add('CREATED', '20240701T000000Z');
|
||||
$vEvent->add('LAST-MODIFIED', '20240701T000000Z');
|
||||
$vEvent->add('SEQUENCE', 1);
|
||||
$vEvent->add('STATUS', 'CONFIRMED');
|
||||
$vEvent->add('DTSTART', '20240701T080000', ['TZID' => 'America/Toronto']);
|
||||
$vEvent->add('DTEND', '20240701T090000', ['TZID' => 'America/Toronto']);
|
||||
$vEvent->add('RRULE', 'FREQ=WEEKLY;COUNT=12;BYDAY=MO');
|
||||
$vEvent->add('SUMMARY', 'Test Event');
|
||||
$vEvent->add('ORGANIZER', 'mailto:organizer@testing.com', ['CN' => 'Organizer']);
|
||||
$vEvent->add('ATTENDEE', 'mailto:attendee1@testing.com', [
|
||||
'CN' => 'Attendee One',
|
||||
'CUTYPE' => 'INDIVIDUAL',
|
||||
'PARTSTAT' => 'NEEDS-ACTION',
|
||||
'ROLE' => 'REQ-PARTICIPANT',
|
||||
'RSVP' => 'TRUE'
|
||||
]);
|
||||
$currentEventInfo = $this->invokePrivate($this->broker, 'parseEventInfo', [$calendar]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests user creating a new singleton or recurring event
|
||||
*/
|
||||
public function testParseEventForOrganizerCreated(): void {
|
||||
// construct calendar and generate event info for newly created event with one attendee
|
||||
$mutatedCalendar = clone $this->vCalendar1a;
|
||||
$originalEventInfo = $this->templateEventInfo;
|
||||
$mutatedEventInfo = $this->invokePrivate($this->broker, 'parseEventInfo', [$mutatedCalendar]);
|
||||
// test iTip generation
|
||||
$messages = $this->invokePrivate($this->broker, 'parseEventForOrganizer', [$calendar, $currentEventInfo, $previousEventInfo]);
|
||||
$messages = $this->invokePrivate($this->broker, 'parseEventForOrganizer', [$mutatedCalendar, $mutatedEventInfo, $originalEventInfo]);
|
||||
$this->assertCount(1, $messages);
|
||||
$this->assertEquals('REQUEST', $messages[0]->method);
|
||||
$this->assertEquals($mutatedCalendar->VEVENT->ORGANIZER->getValue(), $messages[0]->sender);
|
||||
$this->assertEquals($mutatedCalendar->VEVENT->ATTENDEE[0]->getValue(), $messages[0]->recipient);
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests user modifying an existing singleton or recurring (base) event
|
||||
*/
|
||||
public function testParseEventForOrganizerModified(): void {
|
||||
// construct calendar and generate event info for modified event with one attendee
|
||||
$originalCalendar = clone $this->vCalendar1a;
|
||||
$originalEventInfo = $this->invokePrivate($this->broker, 'parseEventInfo', [$originalCalendar]);
|
||||
$mutatedCalendar = clone $this->vCalendar1a;
|
||||
$mutatedCalendar->VEVENT->{'LAST-MODIFIED'}->setValue('20240701T020000Z');
|
||||
$mutatedCalendar->VEVENT->SEQUENCE->setValue(2);
|
||||
$mutatedCalendar->VEVENT->SUMMARY->setValue('Test Event Modified');
|
||||
$mutatedEventInfo = $this->invokePrivate($this->broker, 'parseEventInfo', [$mutatedCalendar]);
|
||||
// test iTip generation
|
||||
$messages = $this->invokePrivate($this->broker, 'parseEventForOrganizer', [$mutatedCalendar, $mutatedEventInfo, $originalEventInfo]);
|
||||
$this->assertCount(1, $messages);
|
||||
$this->assertEquals('REQUEST', $messages[0]->method);
|
||||
$this->assertEquals($mutatedCalendar->VEVENT->ORGANIZER->getValue(), $messages[0]->sender);
|
||||
$this->assertEquals($mutatedCalendar->VEVENT->ATTENDEE[0]->getValue(), $messages[0]->recipient);
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests user deleting an existing singleton or recurring (base) event
|
||||
*/
|
||||
public function testParseEventForOrganizerDeleted(): void {
|
||||
// construct calendar and generate event info for modified event with one attendee
|
||||
$originalCalendar = clone $this->vCalendar1a;
|
||||
$originalEventInfo = $this->invokePrivate($this->broker, 'parseEventInfo', [$originalCalendar]);
|
||||
$mutatedEventInfo = $originalEventInfo;
|
||||
$mutatedEventInfo['attendees'] = [];
|
||||
++$mutatedEventInfo['sequence'];
|
||||
// test iTip generation
|
||||
$messages = $this->invokePrivate($this->broker, 'parseEventForOrganizer', [$originalCalendar, $mutatedEventInfo, $originalEventInfo]);
|
||||
$this->assertCount(1, $messages);
|
||||
$this->assertEquals('CANCEL', $messages[0]->method);
|
||||
$this->assertEquals($originalCalendar->VEVENT->ORGANIZER->getValue(), $messages[0]->sender);
|
||||
$this->assertEquals($originalCalendar->VEVENT->ATTENDEE[0]->getValue(), $messages[0]->recipient);
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests user cancelling an existing singleton or recurring (base) event
|
||||
*/
|
||||
public function testParseEventForOrganizerStatusCancelled(): void {
|
||||
// construct calendar and generate event info for modified event with one attendee
|
||||
$originalCalendar = clone $this->vCalendar1a;
|
||||
$originalEventInfo = $this->invokePrivate($this->broker, 'parseEventInfo', [$originalCalendar]);
|
||||
$mutatedCalendar = clone $this->vCalendar1a;
|
||||
$mutatedCalendar->VEVENT->{'LAST-MODIFIED'}->setValue('20240701T020000Z');
|
||||
$mutatedCalendar->VEVENT->SEQUENCE->setValue(2);
|
||||
$mutatedCalendar->VEVENT->STATUS->setValue('CANCELLED');
|
||||
$mutatedEventInfo = $this->invokePrivate($this->broker, 'parseEventInfo', [$mutatedCalendar]);
|
||||
// test iTip generation
|
||||
$messages = $this->invokePrivate($this->broker, 'parseEventForOrganizer', [$mutatedCalendar, $mutatedEventInfo, $originalEventInfo]);
|
||||
$this->assertCount(1, $messages);
|
||||
$this->assertEquals('CANCEL', $messages[0]->method);
|
||||
$this->assertEquals($mutatedCalendar->VEVENT->ORGANIZER->getValue(), $messages[0]->sender);
|
||||
$this->assertEquals($mutatedCalendar->VEVENT->ATTENDEE[0]->getValue(), $messages[0]->recipient);
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests user adding an attendee to an existing singleton or recurring (base) event
|
||||
*/
|
||||
public function testParseEventForOrganizerAddAttendee(): void {
|
||||
// construct calendar and generate event info for modified event with two attendees
|
||||
$originalCalendar = clone $this->vCalendar1a;
|
||||
$originalEventInfo = $this->invokePrivate($this->broker, 'parseEventInfo', [$originalCalendar]);
|
||||
$mutatedCalendar = clone $this->vCalendar1a;
|
||||
$mutatedCalendar->VEVENT->{'LAST-MODIFIED'}->setValue('20240701T020000Z');
|
||||
$mutatedCalendar->VEVENT->SEQUENCE->setValue(2);
|
||||
$mutatedCalendar->VEVENT->add('ATTENDEE', 'mailto:attendee2@testing.com', [
|
||||
'CN' => 'Attendee Two',
|
||||
'CUTYPE' => 'INDIVIDUAL',
|
||||
'PARTSTAT' => 'NEEDS-ACTION',
|
||||
'ROLE' => 'REQ-PARTICIPANT',
|
||||
'RSVP' => 'TRUE'
|
||||
]);
|
||||
$mutatedEventInfo = $this->invokePrivate($this->broker, 'parseEventInfo', [$mutatedCalendar]);
|
||||
// test iTip generation
|
||||
$messages = $this->invokePrivate($this->broker, 'parseEventForOrganizer', [$mutatedCalendar, $mutatedEventInfo, $originalEventInfo]);
|
||||
// attendee modifications get generated in order of Added, Removed, Existing
|
||||
$this->assertCount(2, $messages);
|
||||
$this->assertEquals('REQUEST', $messages[0]->method);
|
||||
$this->assertEquals($calendar->VEVENT->ORGANIZER->getValue(), $messages[0]->sender);
|
||||
$this->assertEquals($calendar->VEVENT->ATTENDEE[0]->getValue(), $messages[0]->recipient);
|
||||
$this->assertEquals($mutatedCalendar->VEVENT->ORGANIZER->getValue(), $messages[0]->sender);
|
||||
$this->assertEquals($mutatedCalendar->VEVENT->ATTENDEE[0]->getValue(), $messages[0]->recipient);
|
||||
$this->assertEquals('REQUEST', $messages[1]->method);
|
||||
$this->assertEquals($mutatedCalendar->VEVENT->ORGANIZER->getValue(), $messages[1]->sender);
|
||||
$this->assertEquals($mutatedCalendar->VEVENT->ATTENDEE[1]->getValue(), $messages[1]->recipient);
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests user removing an attendee from an existing singleton or recurring (base) event
|
||||
*/
|
||||
public function testParseEventForOrganizerRemoveAttendee(): void {
|
||||
// construct calendar and generate event info for modified event with two attendees
|
||||
$originalCalendar = clone $this->vCalendar1a;
|
||||
$originalCalendar->VEVENT->add('ATTENDEE', 'mailto:attendee2@testing.com', [
|
||||
'CN' => 'Attendee Two',
|
||||
'CUTYPE' => 'INDIVIDUAL',
|
||||
'PARTSTAT' => 'NEEDS-ACTION',
|
||||
'ROLE' => 'REQ-PARTICIPANT',
|
||||
'RSVP' => 'TRUE'
|
||||
]);
|
||||
$originalEventInfo = $this->invokePrivate($this->broker, 'parseEventInfo', [$originalCalendar]);
|
||||
$mutatedCalendar = clone $this->vCalendar1a;
|
||||
$mutatedCalendar->VEVENT->{'LAST-MODIFIED'}->setValue('20240701T020000Z');
|
||||
$mutatedCalendar->VEVENT->SEQUENCE->setValue(2);
|
||||
$mutatedCalendar->VEVENT->remove('ATTENDEE');
|
||||
$mutatedCalendar->VEVENT->add('ATTENDEE', 'mailto:attendee1@testing.com', [
|
||||
'CN' => 'Attendee One',
|
||||
'CUTYPE' => 'INDIVIDUAL',
|
||||
'PARTSTAT' => 'NEEDS-ACTION',
|
||||
'ROLE' => 'REQ-PARTICIPANT',
|
||||
'RSVP' => 'TRUE'
|
||||
]);
|
||||
$mutatedEventInfo = $this->invokePrivate($this->broker, 'parseEventInfo', [$mutatedCalendar]);
|
||||
// test iTip generation
|
||||
$messages = $this->invokePrivate($this->broker, 'parseEventForOrganizer', [$mutatedCalendar, $mutatedEventInfo, $originalEventInfo]);
|
||||
// attendee modifications get generated in order of Added, Removed, Existing
|
||||
$this->assertCount(2, $messages);
|
||||
$this->assertEquals('REQUEST', $messages[0]->method);
|
||||
$this->assertEquals($mutatedCalendar->VEVENT->ORGANIZER->getValue(), $messages[0]->sender);
|
||||
$this->assertEquals($mutatedCalendar->VEVENT->ATTENDEE[0]->getValue(), $messages[0]->recipient);
|
||||
$this->assertEquals('CANCEL', $messages[1]->method);
|
||||
$this->assertEquals($calendar->VEVENT->ORGANIZER->getValue(), $messages[1]->sender);
|
||||
$this->assertEquals($mutatedCalendar->VEVENT->ORGANIZER->getValue(), $messages[1]->sender);
|
||||
$this->assertEquals('mailto:attendee2@testing.com', $messages[1]->recipient);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Tests user converts existing singleton or recurring (base) event from attended to attendeless
|
||||
*/
|
||||
public function testParseEventForOrganizerRemoveOrganizerAndAttendees(): void {
|
||||
// construct calendar and generate event info for modified event with two attendees
|
||||
$originalCalendar = clone $this->vCalendar1a;
|
||||
$originalEventInfo = $this->invokePrivate($this->broker, 'parseEventInfo', [$originalCalendar]);
|
||||
$mutatedCalendar = clone $this->vCalendar1a;
|
||||
$mutatedCalendar->VEVENT->{'LAST-MODIFIED'}->setValue('20240701T020000Z');
|
||||
$mutatedCalendar->VEVENT->SEQUENCE->setValue(2);
|
||||
$mutatedCalendar->VEVENT->remove('ORGANIZER');
|
||||
$mutatedCalendar->VEVENT->remove('ATTENDEE');
|
||||
$mutatedEventInfo = $this->invokePrivate($this->broker, 'parseEventInfo', [$mutatedCalendar]);
|
||||
// test iTip generation
|
||||
$messages = $this->invokePrivate($this->broker, 'parseEventForOrganizer', [$mutatedCalendar, $mutatedEventInfo, $originalEventInfo]);
|
||||
// attendee modifications get generated in order of Added, Removed, Existing
|
||||
$this->assertCount(1, $messages);
|
||||
$this->assertEquals('CANCEL', $messages[0]->method);
|
||||
$this->assertEquals(2, $messages[0]->sequence);
|
||||
$this->assertEquals($originalCalendar->VEVENT->ORGANIZER->getValue(), $messages[0]->sender);
|
||||
$this->assertEquals($originalCalendar->VEVENT->ATTENDEE[0]->getValue(), $messages[0]->recipient);
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests user modifying recurring (base) event by moving instance to a new date
|
||||
*/
|
||||
public function testParseEventForOrganizerCreatedInstance(): void {
|
||||
// construct calendar and generate event info for modified event with two attendees
|
||||
$originalCalendar = clone $this->vCalendar2a;
|
||||
$originalEventInfo = $this->invokePrivate($this->broker, 'parseEventInfo', [$originalCalendar]);
|
||||
$mutatedInstance = clone $originalCalendar->VEVENT;
|
||||
$mutatedInstance->add('RECURRENCE-ID', '20240715T080000', ['TZID' => 'America/Toronto']);
|
||||
$mutatedInstance->SEQUENCE->setValue(0);
|
||||
$mutatedInstance->DTSTART->setValue('20240717T080000');
|
||||
$mutatedInstance->DTEND->setValue('20240717T090000');
|
||||
$mutatedCalendar = clone $this->vCalendar2a;
|
||||
$mutatedCalendar->add($mutatedInstance);
|
||||
$mutatedEventInfo = $this->invokePrivate($this->broker, 'parseEventInfo', [$mutatedCalendar]);
|
||||
// test iTip generation
|
||||
$messages = $this->invokePrivate($this->broker, 'parseEventForOrganizer', [$mutatedCalendar, $mutatedEventInfo, $originalEventInfo]);
|
||||
// attendee modifications get generated in order of Added, Removed, Existing
|
||||
$this->assertCount(1, $messages);
|
||||
$this->assertEquals('REQUEST', $messages[0]->method);
|
||||
$this->assertEquals(1, $messages[0]->sequence);
|
||||
$this->assertEquals($mutatedCalendar->VEVENT->ORGANIZER->getValue(), $messages[0]->sender);
|
||||
$this->assertEquals($mutatedCalendar->VEVENT->ATTENDEE[0]->getValue(), $messages[0]->recipient);
|
||||
$this->assertCount(2, $messages[0]->message->VEVENT);
|
||||
$this->assertEquals('20240715T080000', $messages[0]->message->VEVENT[1]->{'RECURRENCE-ID'}->getValue());
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests user modifying recurring (base) event by cancelling a single instance
|
||||
*/
|
||||
public function testParseEventForOrganizerCreatedInstanceCancelled(): void {
|
||||
// construct calendar and generate event info for modified event with two attendees
|
||||
$originalCalendar = clone $this->vCalendar2a;
|
||||
$originalEventInfo = $this->invokePrivate($this->broker, 'parseEventInfo', [$originalCalendar]);
|
||||
$mutatedInstance = clone $originalCalendar->VEVENT;
|
||||
$mutatedInstance->add('RECURRENCE-ID', '20240715T080000', ['TZID' => 'America/Toronto']);
|
||||
$mutatedInstance->SEQUENCE->setValue(0);
|
||||
$mutatedInstance->STATUS->setValue('CANCELLED');
|
||||
$mutatedCalendar = clone $this->vCalendar2a;
|
||||
$mutatedCalendar->add($mutatedInstance);
|
||||
$mutatedEventInfo = $this->invokePrivate($this->broker, 'parseEventInfo', [$mutatedCalendar]);
|
||||
// test iTip generation
|
||||
$messages = $this->invokePrivate($this->broker, 'parseEventForOrganizer', [$mutatedCalendar, $mutatedEventInfo, $originalEventInfo]);
|
||||
// attendee modifications get generated in order of Added, Removed, Existing
|
||||
$this->assertCount(2, $messages);
|
||||
$this->assertEquals('CANCEL', $messages[0]->method);
|
||||
$this->assertEquals(1, $messages[0]->sequence);
|
||||
$this->assertEquals($mutatedCalendar->VEVENT[1]->ORGANIZER->getValue(), $messages[0]->sender);
|
||||
$this->assertEquals($mutatedCalendar->VEVENT[1]->ATTENDEE[0]->getValue(), $messages[0]->recipient);
|
||||
$this->assertCount(1, $messages[0]->message->VEVENT);
|
||||
$this->assertEquals('20240715T080000', $messages[0]->message->VEVENT->{'RECURRENCE-ID'}->getValue());
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests user modifying recurring (instance) event with non status or attendee changes
|
||||
*/
|
||||
public function testParseEventForOrganizerModifyInstance(): void {
|
||||
// construct calendar and generate event info for modified event with two attendees
|
||||
$originalCalendar = clone $this->vCalendar2a;
|
||||
$originalInstance = clone $originalCalendar->VEVENT;
|
||||
$originalInstance->add('RECURRENCE-ID', '20240715T080000', ['TZID' => 'America/Toronto']);
|
||||
$originalInstance->SEQUENCE->setValue(1);
|
||||
$originalInstance->DTSTART->setValue('20240717T080000');
|
||||
$originalInstance->DTEND->setValue('20240717T090000');
|
||||
$originalCalendar->add($originalInstance);
|
||||
$originalEventInfo = $this->invokePrivate($this->broker, 'parseEventInfo', [$originalCalendar]);
|
||||
|
||||
$mutatedInstance = clone $originalInstance;
|
||||
$mutatedInstance->SEQUENCE->setValue(2);
|
||||
$mutatedInstance->DTSTART->setValue('20240718T080000');
|
||||
$mutatedInstance->DTEND->setValue('20240718T090000');
|
||||
$mutatedCalendar = clone $this->vCalendar2a;
|
||||
$mutatedCalendar->add($mutatedInstance);
|
||||
$mutatedEventInfo = $this->invokePrivate($this->broker, 'parseEventInfo', [$mutatedCalendar]);
|
||||
// test iTip generation
|
||||
$messages = $this->invokePrivate($this->broker, 'parseEventForOrganizer', [$mutatedCalendar, $mutatedEventInfo, $originalEventInfo]);
|
||||
// attendee modifications get generated in order of Added, Removed, Existing
|
||||
$this->assertCount(1, $messages);
|
||||
$this->assertEquals('REQUEST', $messages[0]->method);
|
||||
$this->assertEquals(1, $messages[0]->sequence);
|
||||
$this->assertEquals($mutatedCalendar->VEVENT[1]->ORGANIZER->getValue(), $messages[0]->sender);
|
||||
$this->assertEquals($mutatedCalendar->VEVENT[1]->ATTENDEE[0]->getValue(), $messages[0]->recipient);
|
||||
$this->assertCount(2, $messages[0]->message->VEVENT);
|
||||
$this->assertEquals('20240715T080000', $messages[0]->message->VEVENT[1]->{'RECURRENCE-ID'}->getValue());
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests user modifying recurring (instance) event by setting status to cancelled
|
||||
*/
|
||||
public function testParseEventForOrganizerModifyInstanceStatus(): void {
|
||||
// construct calendar and generate event info for modified event with two attendees
|
||||
$originalCalendar = clone $this->vCalendar2a;
|
||||
$originalInstance = clone $originalCalendar->VEVENT;
|
||||
$originalInstance->add('RECURRENCE-ID', '20240715T080000', ['TZID' => 'America/Toronto']);
|
||||
$originalInstance->SEQUENCE->setValue(1);
|
||||
$originalInstance->DTSTART->setValue('20240717T080000');
|
||||
$originalInstance->DTEND->setValue('20240717T090000');
|
||||
$originalCalendar->add($originalInstance);
|
||||
$originalEventInfo = $this->invokePrivate($this->broker, 'parseEventInfo', [$originalCalendar]);
|
||||
$mutatedInstance = clone $originalInstance;
|
||||
$mutatedInstance->SEQUENCE->setValue(2);
|
||||
$mutatedInstance->STATUS->setValue('CANCELLED');
|
||||
$mutatedCalendar = clone $this->vCalendar2a;
|
||||
$mutatedCalendar->add($mutatedInstance);
|
||||
$mutatedEventInfo = $this->invokePrivate($this->broker, 'parseEventInfo', [$mutatedCalendar]);
|
||||
// test iTip generation
|
||||
$messages = $this->invokePrivate($this->broker, 'parseEventForOrganizer', [$mutatedCalendar, $mutatedEventInfo, $originalEventInfo]);
|
||||
// attendee modifications get generated in order of Added, Removed, Existing
|
||||
$this->assertCount(1, $messages);
|
||||
$this->assertEquals('REQUEST', $messages[0]->method);
|
||||
$this->assertEquals(1, $messages[0]->sequence);
|
||||
$this->assertEquals($mutatedCalendar->VEVENT[1]->ORGANIZER->getValue(), $messages[0]->sender);
|
||||
$this->assertEquals($mutatedCalendar->VEVENT[1]->ATTENDEE[0]->getValue(), $messages[0]->recipient);
|
||||
$this->assertCount(2, $messages[0]->message->VEVENT);
|
||||
$this->assertEquals('20240715T080000', $messages[0]->message->VEVENT[1]->{'RECURRENCE-ID'}->getValue());
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests user modifying recurring (instance) event by adding attendee
|
||||
*/
|
||||
public function testParseEventForOrganizerModifyInstanceAddAttendee(): void {
|
||||
// construct calendar and generate event info for modified event with two attendees
|
||||
$originalCalendar = clone $this->vCalendar2a;
|
||||
$originalInstance = clone $originalCalendar->VEVENT;
|
||||
$originalInstance->add('RECURRENCE-ID', '20240715T080000', ['TZID' => 'America/Toronto']);
|
||||
$originalInstance->SEQUENCE->setValue(1);
|
||||
$originalInstance->DTSTART->setValue('20240717T080000');
|
||||
$originalInstance->DTEND->setValue('20240717T090000');
|
||||
$originalCalendar->add($originalInstance);
|
||||
$originalEventInfo = $this->invokePrivate($this->broker, 'parseEventInfo', [$originalCalendar]);
|
||||
$mutatedInstance = clone $originalInstance;
|
||||
$mutatedInstance->SEQUENCE->setValue(2);
|
||||
$mutatedInstance->add('ATTENDEE', 'mailto:attendee2@testing.com', [
|
||||
'CN' => 'Attendee Two',
|
||||
'CUTYPE' => 'INDIVIDUAL',
|
||||
'PARTSTAT' => 'NEEDS-ACTION',
|
||||
'ROLE' => 'REQ-PARTICIPANT',
|
||||
'RSVP' => 'TRUE'
|
||||
]);
|
||||
$mutatedCalendar = clone $this->vCalendar2a;
|
||||
$mutatedCalendar->add($mutatedInstance);
|
||||
$mutatedEventInfo = $this->invokePrivate($this->broker, 'parseEventInfo', [$mutatedCalendar]);
|
||||
// test iTip generation
|
||||
$messages = $this->invokePrivate($this->broker, 'parseEventForOrganizer', [$mutatedCalendar, $mutatedEventInfo, $originalEventInfo]);
|
||||
// attendee modifications get generated in order of Added, Removed, Existing
|
||||
$this->assertCount(2, $messages);
|
||||
$this->assertEquals('REQUEST', $messages[0]->method);
|
||||
$this->assertEquals(1, $messages[0]->sequence);
|
||||
$this->assertEquals($mutatedCalendar->VEVENT[1]->ORGANIZER->getValue(), $messages[0]->sender);
|
||||
$this->assertEquals($mutatedCalendar->VEVENT[1]->ATTENDEE[0]->getValue(), $messages[0]->recipient);
|
||||
$this->assertCount(2, $messages[0]->message->VEVENT);
|
||||
$this->assertEquals('20240715T080000', $messages[0]->message->VEVENT[1]->{'RECURRENCE-ID'}->getValue());
|
||||
$this->assertEquals('REQUEST', $messages[1]->method);
|
||||
$this->assertEquals(1, $messages[1]->sequence);
|
||||
$this->assertEquals($mutatedCalendar->VEVENT[1]->ORGANIZER->getValue(), $messages[1]->sender);
|
||||
$this->assertEquals($mutatedCalendar->VEVENT[1]->ATTENDEE[1]->getValue(), $messages[1]->recipient);
|
||||
$this->assertCount(1, $messages[1]->message->VEVENT);
|
||||
$this->assertEquals('20240715T080000', $messages[1]->message->VEVENT->{'RECURRENCE-ID'}->getValue());
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests user modifying recurring (instance) event by removing attendee
|
||||
*/
|
||||
public function testParseEventForOrganizerModifyInstanceRemoveAttendee(): void {
|
||||
// construct calendar and generate event info for modified event with two attendees
|
||||
$originalCalendar = clone $this->vCalendar2a;
|
||||
$originalInstance = clone $originalCalendar->VEVENT;
|
||||
$originalInstance->add('RECURRENCE-ID', '20240715T080000', ['TZID' => 'America/Toronto']);
|
||||
$originalInstance->SEQUENCE->setValue(1);
|
||||
$originalInstance->DTSTART->setValue('20240717T080000');
|
||||
$originalInstance->DTEND->setValue('20240717T090000');
|
||||
$originalInstance->add('ATTENDEE', 'mailto:attendee2@testing.com', [
|
||||
'CN' => 'Attendee Two',
|
||||
'CUTYPE' => 'INDIVIDUAL',
|
||||
'PARTSTAT' => 'NEEDS-ACTION',
|
||||
'ROLE' => 'REQ-PARTICIPANT',
|
||||
'RSVP' => 'TRUE'
|
||||
]);
|
||||
$originalCalendar->add($originalInstance);
|
||||
$originalEventInfo = $this->invokePrivate($this->broker, 'parseEventInfo', [$originalCalendar]);
|
||||
$mutatedInstance = clone $originalInstance;
|
||||
$mutatedInstance->SEQUENCE->setValue(2);
|
||||
$mutatedInstance->remove('ATTENDEE');
|
||||
$mutatedInstance->add('ATTENDEE', 'mailto:attendee1@testing.com', [
|
||||
'CN' => 'Attendee One',
|
||||
'CUTYPE' => 'INDIVIDUAL',
|
||||
'PARTSTAT' => 'NEEDS-ACTION',
|
||||
'ROLE' => 'REQ-PARTICIPANT',
|
||||
'RSVP' => 'TRUE'
|
||||
]);
|
||||
$mutatedCalendar = clone $this->vCalendar2a;
|
||||
$mutatedCalendar->add($mutatedInstance);
|
||||
$mutatedEventInfo = $this->invokePrivate($this->broker, 'parseEventInfo', [$mutatedCalendar]);
|
||||
// test iTip generation
|
||||
$messages = $this->invokePrivate($this->broker, 'parseEventForOrganizer', [$mutatedCalendar, $mutatedEventInfo, $originalEventInfo]);
|
||||
// attendee modifications get generated in order of Added, Removed, Existing
|
||||
$this->assertCount(2, $messages);
|
||||
$this->assertEquals('REQUEST', $messages[0]->method);
|
||||
$this->assertEquals(1, $messages[0]->sequence);
|
||||
$this->assertEquals($originalCalendar->VEVENT[1]->ORGANIZER->getValue(), $messages[0]->sender);
|
||||
$this->assertEquals($originalCalendar->VEVENT[1]->ATTENDEE[0]->getValue(), $messages[0]->recipient);
|
||||
$this->assertCount(2, $messages[0]->message->VEVENT);
|
||||
$this->assertEquals('20240715T080000', $messages[0]->message->VEVENT[1]->{'RECURRENCE-ID'}->getValue());
|
||||
$this->assertEquals('CANCEL', $messages[1]->method);
|
||||
$this->assertEquals(1, $messages[1]->sequence);
|
||||
$this->assertEquals($originalCalendar->VEVENT[1]->ORGANIZER->getValue(), $messages[1]->sender);
|
||||
$this->assertEquals($originalCalendar->VEVENT[1]->ATTENDEE[1]->getValue(), $messages[1]->recipient);
|
||||
$this->assertCount(1, $messages[1]->message->VEVENT);
|
||||
$this->assertEquals('20240715T080000', $messages[1]->message->VEVENT->{'RECURRENCE-ID'}->getValue());
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests user deleting master instance of recurring event
|
||||
*/
|
||||
public function testParseEventForOrganizerDeleteMasterInstance(): void {
|
||||
// construct calendar with recurring event
|
||||
$originalCalendar = clone $this->vCalendar2a;
|
||||
$originalEventInfo = $this->invokePrivate($this->broker, 'parseEventInfo', [$originalCalendar]);
|
||||
// delete the master instance (convert to non-scheduling)
|
||||
$mutatedCalendar = clone $this->vCalendar2a;
|
||||
$mutatedCalendar->VEVENT->{'LAST-MODIFIED'}->setValue('20240701T020000Z');
|
||||
$mutatedCalendar->VEVENT->SEQUENCE->setValue(2);
|
||||
$mutatedCalendar->VEVENT->remove('ORGANIZER');
|
||||
$mutatedCalendar->VEVENT->remove('ATTENDEE');
|
||||
$mutatedEventInfo = $this->invokePrivate($this->broker, 'parseEventInfo', [$mutatedCalendar]);
|
||||
// test iTip generation
|
||||
$messages = $this->invokePrivate($this->broker, 'parseEventForOrganizer', [$mutatedCalendar, $mutatedEventInfo, $originalEventInfo]);
|
||||
$this->assertCount(1, $messages);
|
||||
$this->assertEquals('CANCEL', $messages[0]->method);
|
||||
$this->assertEquals(2, $messages[0]->sequence);
|
||||
$this->assertEquals($originalCalendar->VEVENT->ORGANIZER->getValue(), $messages[0]->sender);
|
||||
$this->assertEquals($originalCalendar->VEVENT->ATTENDEE[0]->getValue(), $messages[0]->recipient);
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests user adding EXDATE to master instance
|
||||
*/
|
||||
public function testParseEventForOrganizerAddExdate(): void {
|
||||
// construct calendar with recurring event
|
||||
$originalCalendar = clone $this->vCalendar2a;
|
||||
$originalEventInfo = $this->invokePrivate($this->broker, 'parseEventInfo', [$originalCalendar]);
|
||||
// add EXDATE to exclude specific occurrences
|
||||
$mutatedCalendar = clone $this->vCalendar2a;
|
||||
$mutatedCalendar->VEVENT->{'LAST-MODIFIED'}->setValue('20240701T020000Z');
|
||||
$mutatedCalendar->VEVENT->SEQUENCE->setValue(2);
|
||||
$mutatedCalendar->VEVENT->add('EXDATE', ['20240715T080000', '20240722T080000'], ['TZID' => 'America/Toronto']);
|
||||
$mutatedEventInfo = $this->invokePrivate($this->broker, 'parseEventInfo', [$mutatedCalendar]);
|
||||
// test iTip generation
|
||||
$messages = $this->invokePrivate($this->broker, 'parseEventForOrganizer', [$mutatedCalendar, $mutatedEventInfo, $originalEventInfo]);
|
||||
$this->assertCount(1, $messages);
|
||||
$this->assertEquals('REQUEST', $messages[0]->method);
|
||||
$this->assertEquals(2, $messages[0]->sequence);
|
||||
$this->assertEquals($mutatedCalendar->VEVENT->ORGANIZER->getValue(), $messages[0]->sender);
|
||||
$this->assertEquals($mutatedCalendar->VEVENT->ATTENDEE[0]->getValue(), $messages[0]->recipient);
|
||||
// verify EXDATE is present in the message
|
||||
$this->assertTrue(isset($messages[0]->message->VEVENT->EXDATE));
|
||||
$exdates = $messages[0]->message->VEVENT->EXDATE->getParts();
|
||||
$this->assertContains('20240715T080000', $exdates);
|
||||
$this->assertContains('20240722T080000', $exdates);
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests user removing EXDATE from master instance
|
||||
*/
|
||||
public function testParseEventForOrganizerRemoveExdate(): void {
|
||||
// construct calendar with recurring event that has EXDATE
|
||||
$originalCalendar = clone $this->vCalendar2a;
|
||||
$originalCalendar->VEVENT->add('EXDATE', ['20240715T080000', '20240722T080000'], ['TZID' => 'America/Toronto']);
|
||||
$originalEventInfo = $this->invokePrivate($this->broker, 'parseEventInfo', [$originalCalendar]);
|
||||
// remove EXDATE to restore excluded occurrences
|
||||
$mutatedCalendar = clone $this->vCalendar2a;
|
||||
$mutatedCalendar->VEVENT->{'LAST-MODIFIED'}->setValue('20240701T020000Z');
|
||||
$mutatedCalendar->VEVENT->SEQUENCE->setValue(2);
|
||||
$mutatedEventInfo = $this->invokePrivate($this->broker, 'parseEventInfo', [$mutatedCalendar]);
|
||||
// test iTip generation
|
||||
$messages = $this->invokePrivate($this->broker, 'parseEventForOrganizer', [$mutatedCalendar, $mutatedEventInfo, $originalEventInfo]);
|
||||
$this->assertCount(1, $messages);
|
||||
$this->assertEquals('REQUEST', $messages[0]->method);
|
||||
$this->assertEquals(2, $messages[0]->sequence);
|
||||
$this->assertEquals($mutatedCalendar->VEVENT->ORGANIZER->getValue(), $messages[0]->sender);
|
||||
$this->assertEquals($mutatedCalendar->VEVENT->ATTENDEE[0]->getValue(), $messages[0]->recipient);
|
||||
// verify EXDATE is not present in the message
|
||||
$this->assertFalse(isset($messages[0]->message->VEVENT->EXDATE));
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests user converting recurring event to non-scheduling
|
||||
*/
|
||||
public function testParseEventForOrganizerConvertRecurringToNonScheduling(): void {
|
||||
// construct calendar with recurring event
|
||||
$originalCalendar = clone $this->vCalendar2a;
|
||||
$originalEventInfo = $this->invokePrivate($this->broker, 'parseEventInfo', [$originalCalendar]);
|
||||
// remove ORGANIZER and ATTENDEE properties to convert to non-scheduling
|
||||
$mutatedCalendar = clone $this->vCalendar2a;
|
||||
$mutatedCalendar->VEVENT->{'LAST-MODIFIED'}->setValue('20240701T020000Z');
|
||||
$mutatedCalendar->VEVENT->SEQUENCE->setValue(2);
|
||||
$mutatedCalendar->VEVENT->remove('ORGANIZER');
|
||||
$mutatedCalendar->VEVENT->remove('ATTENDEE');
|
||||
$mutatedEventInfo = $this->invokePrivate($this->broker, 'parseEventInfo', [$mutatedCalendar]);
|
||||
// test iTip generation
|
||||
$messages = $this->invokePrivate($this->broker, 'parseEventForOrganizer', [$mutatedCalendar, $mutatedEventInfo, $originalEventInfo]);
|
||||
$this->assertCount(1, $messages);
|
||||
$this->assertEquals('CANCEL', $messages[0]->method);
|
||||
$this->assertEquals(2, $messages[0]->sequence);
|
||||
$this->assertEquals($originalCalendar->VEVENT->ORGANIZER->getValue(), $messages[0]->sender);
|
||||
$this->assertEquals($originalCalendar->VEVENT->ATTENDEE[0]->getValue(), $messages[0]->recipient);
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests SCHEDULE-FORCE-SEND parameter handling
|
||||
*/
|
||||
public function testParseEventForOrganizerScheduleForceSend(): void {
|
||||
// construct calendar with event
|
||||
$originalCalendar = clone $this->vCalendar1a;
|
||||
$originalEventInfo = $this->invokePrivate($this->broker, 'parseEventInfo', [$originalCalendar]);
|
||||
// add SCHEDULE-FORCE-SEND parameter to ATTENDEE
|
||||
$mutatedCalendar = clone $this->vCalendar1a;
|
||||
$mutatedCalendar->VEVENT->{'LAST-MODIFIED'}->setValue('20240701T020000Z');
|
||||
$mutatedCalendar->VEVENT->SEQUENCE->setValue(2);
|
||||
$mutatedCalendar->VEVENT->ATTENDEE->add('SCHEDULE-FORCE-SEND', 'REQUEST');
|
||||
$mutatedEventInfo = $this->invokePrivate($this->broker, 'parseEventInfo', [$mutatedCalendar]);
|
||||
// test iTip generation
|
||||
$messages = $this->invokePrivate($this->broker, 'parseEventForOrganizer', [$mutatedCalendar, $mutatedEventInfo, $originalEventInfo]);
|
||||
$this->assertCount(1, $messages);
|
||||
$this->assertEquals('REQUEST', $messages[0]->method);
|
||||
$this->assertEquals(2, $messages[0]->sequence);
|
||||
$this->assertEquals($mutatedCalendar->VEVENT->ORGANIZER->getValue(), $messages[0]->sender);
|
||||
$this->assertEquals($mutatedCalendar->VEVENT->ATTENDEE->getValue(), $messages[0]->recipient);
|
||||
// verify SCHEDULE-FORCE-SEND is removed from the message (sanitized)
|
||||
$this->assertFalse(isset($messages[0]->message->VEVENT->ATTENDEE['SCHEDULE-FORCE-SEND']));
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -15,16 +15,12 @@ use OCP\Security\Bruteforce\IThrottler;
|
||||
use OCP\Share\Exceptions\ShareNotFound;
|
||||
use OCP\Share\IManager;
|
||||
use OCP\Share\IShare;
|
||||
use PHPUnit\Framework\Attributes\Group;
|
||||
use PHPUnit\Framework\MockObject\MockObject;
|
||||
use Test\TestCase;
|
||||
|
||||
/**
|
||||
* Class LegacyPublicAuthTest
|
||||
*
|
||||
*
|
||||
* @package OCA\DAV\Tests\unit\Connector
|
||||
*/
|
||||
#[\PHPUnit\Framework\Attributes\Group(name: 'DB')]
|
||||
class LegacyPublicAuthTest extends \Test\TestCase {
|
||||
#[Group(name: 'DB')]
|
||||
class LegacyPublicAuthTest extends TestCase {
|
||||
private ISession&MockObject $session;
|
||||
private IRequest&MockObject $request;
|
||||
private IManager&MockObject $shareManager;
|
||||
@@ -55,7 +51,7 @@ class LegacyPublicAuthTest extends \Test\TestCase {
|
||||
\OC_User::setIncognitoMode(false);
|
||||
|
||||
// Set old user
|
||||
\OC_User::setUserId($this->oldUser);
|
||||
\OC_User::setUserId($this->oldUser ?: null);
|
||||
if ($this->oldUser !== false) {
|
||||
\OC_Util::setupFS($this->oldUser);
|
||||
}
|
||||
|
||||
@@ -61,14 +61,14 @@ class CommentsPropertiesPluginTest extends \Test\TestCase {
|
||||
|
||||
public static function baseUriProvider(): array {
|
||||
return [
|
||||
['owncloud/remote.php/webdav/', '4567', 'owncloud/remote.php/dav/comments/files/4567'],
|
||||
['owncloud/remote.php/files/', '4567', 'owncloud/remote.php/dav/comments/files/4567'],
|
||||
['owncloud/wicked.php/files/', '4567', null]
|
||||
['owncloud/remote.php/webdav/', 4567, 'owncloud/remote.php/dav/comments/files/4567'],
|
||||
['owncloud/remote.php/files/', 4567, 'owncloud/remote.php/dav/comments/files/4567'],
|
||||
['owncloud/wicked.php/files/', 4567, null]
|
||||
];
|
||||
}
|
||||
|
||||
#[\PHPUnit\Framework\Attributes\DataProvider(methodName: 'baseUriProvider')]
|
||||
public function testGetCommentsLink(string $baseUri, string $fid, ?string $expectedHref): void {
|
||||
public function testGetCommentsLink(string $baseUri, int $fid, ?string $expectedHref): void {
|
||||
$node = $this->createMock(File::class);
|
||||
$node->expects($this->any())
|
||||
->method('getId')
|
||||
@@ -94,7 +94,7 @@ class CommentsPropertiesPluginTest extends \Test\TestCase {
|
||||
$node = $this->createMock(File::class);
|
||||
$node->expects($this->any())
|
||||
->method('getId')
|
||||
->willReturn('4567');
|
||||
->willReturn(4567);
|
||||
|
||||
if ($user !== null) {
|
||||
$user = $this->createMock($user);
|
||||
|
||||
@@ -230,6 +230,10 @@ class DirectoryTest extends \Test\TestCase {
|
||||
$info->expects($this->any())
|
||||
->method('isReadable')
|
||||
->willReturn(false);
|
||||
$this->view
|
||||
->method('getRelativePath')
|
||||
->with(null)
|
||||
->willReturn('');
|
||||
|
||||
$dir = new Directory($this->view, $info);
|
||||
$dir->getChildren();
|
||||
@@ -242,6 +246,10 @@ class DirectoryTest extends \Test\TestCase {
|
||||
$this->info->expects($this->any())
|
||||
->method('isReadable')
|
||||
->willReturn(false);
|
||||
$this->view
|
||||
->method('getRelativePath')
|
||||
->with('/admin/files/folder')
|
||||
->willReturn('');
|
||||
|
||||
$dir = new Directory($this->view, $this->info);
|
||||
$dir->getChild('test');
|
||||
@@ -254,6 +262,10 @@ class DirectoryTest extends \Test\TestCase {
|
||||
$this->view->expects($this->once())
|
||||
->method('getFileInfo')
|
||||
->willThrowException(new StorageNotAvailableException());
|
||||
$this->view
|
||||
->method('getRelativePath')
|
||||
->with('/admin/files/folder')
|
||||
->willReturn('');
|
||||
|
||||
$dir = new Directory($this->view, $this->info);
|
||||
$dir->getChild('.');
|
||||
@@ -268,6 +280,10 @@ class DirectoryTest extends \Test\TestCase {
|
||||
->willThrowException(new InvalidPathException());
|
||||
$this->view->expects($this->never())
|
||||
->method('getFileInfo');
|
||||
$this->view
|
||||
->method('getRelativePath')
|
||||
->with('/admin/files/folder')
|
||||
->willReturn('');
|
||||
|
||||
$dir = new Directory($this->view, $this->info);
|
||||
$dir->getChild('.');
|
||||
@@ -376,6 +392,11 @@ class DirectoryTest extends \Test\TestCase {
|
||||
}
|
||||
|
||||
public function testGetNodeForPathFailsWithNoReadPermissionsForPath(): void {
|
||||
$this->view
|
||||
->method('getRelativePath')
|
||||
->with('/admin/files/')
|
||||
->willReturn('');
|
||||
|
||||
$directoryNode = $this->createMock(Folder::class);
|
||||
$pathNode = $this->createMock(Folder::class);
|
||||
$storage = $this->createMock(IStorage::class);
|
||||
@@ -396,7 +417,7 @@ class DirectoryTest extends \Test\TestCase {
|
||||
2 => false,
|
||||
};
|
||||
});
|
||||
$directoryNode->expects($this->once())
|
||||
$directoryNode
|
||||
->method('getPath')
|
||||
->willReturn('/admin/files/');
|
||||
$directoryNode->expects($this->once())
|
||||
|
||||
@@ -673,6 +673,10 @@ class FileTest extends TestCase {
|
||||
/** @var View&MockObject */
|
||||
$view = $this->getMockBuilder(View::class)
|
||||
->getMock();
|
||||
$view
|
||||
->method('getRelativePath')
|
||||
->with('/test.txt')
|
||||
->willReturn('');
|
||||
|
||||
$view->expects($this->once())
|
||||
->method('unlink')
|
||||
@@ -697,6 +701,10 @@ class FileTest extends TestCase {
|
||||
/** @var View&MockObject */
|
||||
$view = $this->getMockBuilder(View::class)
|
||||
->getMock();
|
||||
$view
|
||||
->method('getRelativePath')
|
||||
->with('/test.txt')
|
||||
->willReturn('');
|
||||
|
||||
$info = new \OC\Files\FileInfo('/test.txt', $this->getMockStorage(), null, [
|
||||
'permissions' => 0,
|
||||
@@ -717,6 +725,10 @@ class FileTest extends TestCase {
|
||||
/** @var View&MockObject */
|
||||
$view = $this->getMockBuilder(View::class)
|
||||
->getMock();
|
||||
$view
|
||||
->method('getRelativePath')
|
||||
->with('/test.txt')
|
||||
->willReturn('');
|
||||
|
||||
// but fails
|
||||
$view->expects($this->once())
|
||||
@@ -742,6 +754,10 @@ class FileTest extends TestCase {
|
||||
/** @var View&MockObject */
|
||||
$view = $this->getMockBuilder(View::class)
|
||||
->getMock();
|
||||
$view
|
||||
->method('getRelativePath')
|
||||
->with('/test.txt')
|
||||
->willReturn('');
|
||||
|
||||
// but fails
|
||||
$view->expects($this->once())
|
||||
|
||||
@@ -101,7 +101,7 @@ class FilesPluginTest extends TestCase {
|
||||
->willReturn('00000123instanceid');
|
||||
$node->expects($this->any())
|
||||
->method('getInternalFileId')
|
||||
->willReturn('123');
|
||||
->willReturn(123);
|
||||
$node->expects($this->any())
|
||||
->method('getEtag')
|
||||
->willReturn('"abc"');
|
||||
@@ -455,7 +455,7 @@ class FilesPluginTest extends TestCase {
|
||||
$node->expects($this->once())
|
||||
->method('setEtag')
|
||||
->with('newetag')
|
||||
->willReturn(true);
|
||||
->willReturn(123);
|
||||
|
||||
$node->expects($this->once())
|
||||
->method('setCreationTime')
|
||||
@@ -562,35 +562,11 @@ class FilesPluginTest extends TestCase {
|
||||
$this->plugin->checkMove('FolderA/test.txt', 'test.txt');
|
||||
}
|
||||
|
||||
public function testMoveSrcNotExist(): void {
|
||||
$this->expectException(\Sabre\DAV\Exception\NotFound::class);
|
||||
$this->expectExceptionMessage('FolderA/test.txt does not exist');
|
||||
|
||||
$node = $this->createMock(Node::class);
|
||||
$node->expects($this->atLeastOnce())
|
||||
->method('getFileInfo')
|
||||
->willReturn(null);
|
||||
|
||||
$this->tree->expects($this->atLeastOnce())
|
||||
->method('getNodeForPath')
|
||||
->willReturn($node);
|
||||
|
||||
$this->plugin->checkMove('FolderA/test.txt', 'test.txt');
|
||||
}
|
||||
|
||||
public function testMoveDestinationInvalid(): void {
|
||||
$this->expectException(InvalidPath::class);
|
||||
$this->expectExceptionMessage('Mocked exception');
|
||||
|
||||
$fileInfoFolderATestTXT = $this->createMock(FileInfo::class);
|
||||
$fileInfoFolderATestTXT->expects(self::any())
|
||||
->method('isDeletable')
|
||||
->willReturn(true);
|
||||
|
||||
$node = $this->createMock(Node::class);
|
||||
$node->expects($this->atLeastOnce())
|
||||
->method('getFileInfo')
|
||||
->willReturn($fileInfoFolderATestTXT);
|
||||
|
||||
$this->tree->expects($this->atLeastOnce())
|
||||
->method('getNodeForPath')
|
||||
@@ -604,31 +580,11 @@ class FilesPluginTest extends TestCase {
|
||||
$this->plugin->checkMove('FolderA/test.txt', 'invalid\\path.txt');
|
||||
}
|
||||
|
||||
public function testCopySrcNotExist(): void {
|
||||
$this->expectException(\Sabre\DAV\Exception\NotFound::class);
|
||||
$this->expectExceptionMessage('FolderA/test.txt does not exist');
|
||||
|
||||
$node = $this->createMock(Node::class);
|
||||
$node->expects($this->atLeastOnce())
|
||||
->method('getFileInfo')
|
||||
->willReturn(null);
|
||||
|
||||
$this->tree->expects($this->atLeastOnce())
|
||||
->method('getNodeForPath')
|
||||
->willReturn($node);
|
||||
|
||||
$this->plugin->checkCopy('FolderA/test.txt', 'test.txt');
|
||||
}
|
||||
|
||||
public function testCopyDestinationInvalid(): void {
|
||||
$this->expectException(InvalidPath::class);
|
||||
$this->expectExceptionMessage('Mocked exception');
|
||||
|
||||
$fileInfoFolderATestTXT = $this->createMock(FileInfo::class);
|
||||
$node = $this->createMock(Node::class);
|
||||
$node->expects($this->atLeastOnce())
|
||||
->method('getFileInfo')
|
||||
->willReturn($fileInfoFolderATestTXT);
|
||||
|
||||
$this->tree->expects($this->atLeastOnce())
|
||||
->method('getNodeForPath')
|
||||
|
||||
@@ -57,6 +57,10 @@ class FilesReportPluginTest extends \Test\TestCase {
|
||||
|
||||
$this->tree = $this->createMock(Tree::class);
|
||||
$this->view = $this->createMock(View::class);
|
||||
$this->view
|
||||
->method('getRelativePath')
|
||||
->with(null)
|
||||
->willReturn('');
|
||||
|
||||
$this->server = $this->getMockBuilder(Server::class)
|
||||
->setConstructorArgs([$this->tree])
|
||||
@@ -315,14 +319,14 @@ class FilesReportPluginTest extends \Test\TestCase {
|
||||
|
||||
$node1->expects($this->once())
|
||||
->method('getInternalFileId')
|
||||
->willReturn('111');
|
||||
->willReturn(111);
|
||||
$node1->expects($this->any())
|
||||
->method('getPath')
|
||||
->willReturn('/node1');
|
||||
$node1->method('getFileInfo')->willReturn($fileInfo);
|
||||
$node2->expects($this->once())
|
||||
->method('getInternalFileId')
|
||||
->willReturn('222');
|
||||
->willReturn(222);
|
||||
$node2->expects($this->once())
|
||||
->method('getSize')
|
||||
->willReturn(1024);
|
||||
|
||||
@@ -94,6 +94,10 @@ class NodeTest extends \Test\TestCase {
|
||||
$info->method('getStorage')
|
||||
->willReturn($storage);
|
||||
$view = $this->createMock(View::class);
|
||||
$view
|
||||
->method('getRelativePath')
|
||||
->with(null)
|
||||
->willReturn('');
|
||||
|
||||
$node = new File($view, $info);
|
||||
$this->assertEquals($expected, $node->getDavPermissions());
|
||||
@@ -169,6 +173,10 @@ class NodeTest extends \Test\TestCase {
|
||||
$info->method('getPermissions')->willReturn($permissions);
|
||||
|
||||
$view = $this->createMock(View::class);
|
||||
$view
|
||||
->method('getRelativePath')
|
||||
->with(null)
|
||||
->willReturn('');
|
||||
|
||||
$node = new File($view, $info);
|
||||
$this->invokePrivate($node, 'shareManager', [$shareManager]);
|
||||
@@ -204,6 +212,10 @@ class NodeTest extends \Test\TestCase {
|
||||
|
||||
/** @var View&MockObject $view */
|
||||
$view = $this->createMock(View::class);
|
||||
$view
|
||||
->method('getRelativePath')
|
||||
->with(null)
|
||||
->willReturn('');
|
||||
|
||||
$node = new File($view, $info);
|
||||
$this->invokePrivate($node, 'shareManager', [$shareManager]);
|
||||
@@ -225,6 +237,10 @@ class NodeTest extends \Test\TestCase {
|
||||
|
||||
/** @var View&MockObject */
|
||||
$view = $this->createMock(View::class);
|
||||
$view
|
||||
->method('getRelativePath')
|
||||
->with(null)
|
||||
->willReturn('');
|
||||
|
||||
$node = new File($view, $info);
|
||||
$this->invokePrivate($node, 'shareManager', [$shareManager]);
|
||||
@@ -243,6 +259,10 @@ class NodeTest extends \Test\TestCase {
|
||||
$view = $this->getMockBuilder(View::class)
|
||||
->disableOriginalConstructor()
|
||||
->getMock();
|
||||
$view
|
||||
->method('getRelativePath')
|
||||
->with(null)
|
||||
->willReturn('');
|
||||
$info = $this->getMockBuilder(FileInfo::class)
|
||||
->disableOriginalConstructor()
|
||||
->getMock();
|
||||
@@ -263,6 +283,11 @@ class NodeTest extends \Test\TestCase {
|
||||
$this->expectException(\InvalidArgumentException::class);
|
||||
|
||||
$view = $this->createMock(View::class);
|
||||
$view
|
||||
->method('getRelativePath')
|
||||
->with(null)
|
||||
->willReturn('');
|
||||
|
||||
$info = $this->createMock(FileInfo::class);
|
||||
|
||||
$node = new File($view, $info);
|
||||
|
||||
@@ -63,6 +63,10 @@ class ObjectTreeTest extends \Test\TestCase {
|
||||
->method('getFileInfo')
|
||||
->with($targetParent === '' ? '.' : $targetParent)
|
||||
->willReturn($info);
|
||||
$view
|
||||
->method('getRelativePath')
|
||||
->with(null)
|
||||
->willReturn('');
|
||||
|
||||
$rootDir = new Directory($view, $info);
|
||||
$objectTree = $this->getMockBuilder(ObjectTree::class)
|
||||
@@ -104,6 +108,10 @@ class ObjectTreeTest extends \Test\TestCase {
|
||||
->method('getFileInfo')
|
||||
->with($targetParent === '' ? '.' : $targetParent)
|
||||
->willReturn($info);
|
||||
$view
|
||||
->method('getRelativePath')
|
||||
->with(null)
|
||||
->willReturn('');
|
||||
|
||||
$rootDir = new Directory($view, $info);
|
||||
$objectTree = $this->getMockBuilder(ObjectTree::class)
|
||||
@@ -141,6 +149,10 @@ class ObjectTreeTest extends \Test\TestCase {
|
||||
$view->method('getFileInfo')
|
||||
->with($fileInfoQueryPath)
|
||||
->willReturn($fileInfo);
|
||||
$view
|
||||
->method('getRelativePath')
|
||||
->with(null)
|
||||
->willReturn('');
|
||||
|
||||
$tree = new ObjectTree();
|
||||
$tree->init($rootNode, $view, $mountManager);
|
||||
|
||||
@@ -78,7 +78,7 @@ class PrincipalTest extends TestCase {
|
||||
}
|
||||
|
||||
public function testGetPrincipalsByPrefixWithUsers(): void {
|
||||
$fooUser = $this->createMock(User::class);
|
||||
$fooUser = $this->createMock(IUser::class);
|
||||
$fooUser
|
||||
->expects($this->once())
|
||||
->method('getUID')
|
||||
@@ -91,7 +91,7 @@ class PrincipalTest extends TestCase {
|
||||
->expects($this->once())
|
||||
->method('getSystemEMailAddress')
|
||||
->willReturn('');
|
||||
$barUser = $this->createMock(User::class);
|
||||
$barUser = $this->createMock(IUser::class);
|
||||
$barUser
|
||||
->expects($this->once())
|
||||
->method('getUID')
|
||||
@@ -183,7 +183,7 @@ class PrincipalTest extends TestCase {
|
||||
}
|
||||
|
||||
public function testGetPrincipalsByPathWithoutMail(): void {
|
||||
$fooUser = $this->createMock(User::class);
|
||||
$fooUser = $this->createMock(IUser::class);
|
||||
$fooUser
|
||||
->expects($this->once())
|
||||
->method('getUID')
|
||||
@@ -211,7 +211,7 @@ class PrincipalTest extends TestCase {
|
||||
}
|
||||
|
||||
public function testGetPrincipalsByPathWithMail(): void {
|
||||
$fooUser = $this->createMock(User::class);
|
||||
$fooUser = $this->createMock(IUser::class);
|
||||
$fooUser
|
||||
->expects($this->once())
|
||||
->method('getSystemEMailAddress')
|
||||
|
||||
@@ -182,4 +182,15 @@ class UserEventsListenerTest extends TestCase {
|
||||
$this->userEventsListener->preDeleteUser($user);
|
||||
$this->userEventsListener->postDeleteUser('newUser');
|
||||
}
|
||||
|
||||
public function testDeleteGroup(): void {
|
||||
$this->calDavBackend->expects($this->once())
|
||||
->method('deleteAllSharesByUser')
|
||||
->with('principals/groups/testGroup');
|
||||
$this->cardDavBackend->expects($this->once())
|
||||
->method('deleteAllSharesByUser')
|
||||
->with('principals/groups/testGroup');
|
||||
|
||||
$this->userEventsListener->postDeleteGroup('testGroup');
|
||||
}
|
||||
}
|
||||
|
||||
@@ -22,19 +22,20 @@ OC.L10N.register(
|
||||
"Private key password successfully updated." : "Kişisel anahtar parolası güncellendi.",
|
||||
"Invalid private key for encryption app. Please update your private key password in your personal settings to recover access to your encrypted files." : "Şifreleme uygulaması kişisel anahtarı geçersiz. Şifrelenmiş dosyalarınıza erişebilmek için kişisel ayarlarınızdaki kişisel anahtar parolanızı güncelleyin.",
|
||||
"Encryption App is enabled, but your keys are not initialized. Please log-out and log-in again." : "Şifreleme uygulaması kullanıma alınmış ancak anahtarlarınız hazırlanmamış. Lütfen oturumunuzu kapatıp yeniden açın.",
|
||||
"Please enable server side encryption in the admin settings in order to use the encryption module." : "Şifreleme modülünü kullanabilmek için yönetici ayarlarından sunucu tarafında şifreleme seçeneğini açın.",
|
||||
"Please enable server side encryption in the admin settings in order to use the encryption module." : "Şifreleme modülünü kullanabilmek için yönetici ayarlarından sunucuda şifreleme seçeneğini açın.",
|
||||
"Encryption app is enabled and ready" : "Şifreleme uygulaması kullanıma alındı ve hazır",
|
||||
"Bad Signature" : "İmza bozuk",
|
||||
"Missing Signature" : "İmza eksik",
|
||||
"one-time password for server-side-encryption" : "sunucu tarafında şifreleme için tek kullanımlık parola",
|
||||
"one-time password for server-side-encryption" : "sunucuda şifreleme için tek kullanımlık parola",
|
||||
"Encryption password" : "Şifreleme parolası",
|
||||
"The administration enabled server-side-encryption. Your files were encrypted using the password <strong>%s</strong>." : "Yönetici, sunucu tarafında şifreleme özelliğini açmış. Dosyalarınız <strong>%s</strong> parolası ile şifrelendi.",
|
||||
"The administration enabled server-side-encryption. Your files were encrypted using the password \"%s\"." : "Yönetici, sunucu tarafında şifreleme özelliğini açmış. Dosyalarınız \"%s\" parolası ile şifrelendi.",
|
||||
"The administration enabled server-side-encryption. Your files were encrypted using the password <strong>%s</strong>." : "Yönetici, sunucuda şifreleme özelliğini açmış. Dosyalarınız <strong>%s</strong> parolası ile şifrelendi.",
|
||||
"The administration enabled server-side-encryption. Your files were encrypted using the password \"%s\"." : "Yönetici, sunucuda şifreleme özelliğini açmış. Dosyalarınız \"%s\" parolası ile şifrelendi.",
|
||||
"Please login to the web interface, go to the \"Security\" section of your personal settings and update your encryption password by entering this password into the \"Old login password\" field and your current login password." : "Lütfen yönetim bölümünden oturum açarak kişisel ayarlarınızdaki \"Güvenlik\" bölümüne gidin ve \"Eski oturum açma parolası\" alanına bu parolayı ve geçerli oturum açma parolanızı yazarak şifreleme parolanızı güncelleyin.",
|
||||
"Cannot decrypt this file, probably this is a shared file. Please ask the file owner to reshare the file with you." : "Bu dosyanın şifresi çözülemedi ve büyük olasılıkla paylaşılan bir dosya. Lütfen dosya sahibi ile görüşerek sizinle yeniden paylaşmasını isteyin.",
|
||||
"Cannot read this file, probably this is a shared file. Please ask the file owner to reshare the file with you." : "Bu dosya okunamadı ve büyük olasılıkla paylaşılan bir dosya. Lütfen dosya sahibi ile görüşerek sizinle yeniden paylaşmasını isteyin.",
|
||||
"Default Encryption Module" : "Varsayılan şifreleme modülü",
|
||||
"Default encryption module for Nextcloud Server-side Encryption (SSE)" : "Nextcloud sunucu tarafında şifreleme (SSE) için varsayılan şifreleme modülü",
|
||||
"Default encryption module for Nextcloud Server-side Encryption (SSE)" : "Nextcloud sunucuda şifreleme (SSE) için varsayılan şifreleme modülü",
|
||||
"This app provides the (default) cryptography implementation for Nextcloud's Server-side Encryption (SSE) feature.\n\n\t\t\t**Encryption Details**\n\t\t\t* **Cipher Mode:** AES-256-CTR (default)\n\t\t\t* **Authentication:** HMAC-SHA256\n\n\t\t\t**Important Warnings**\n\t\t\t* **DANGER:** Do not disable this application until all files have been decrypted (`occ encryption:decrypt-all`).\n\t\t\t* **WARNING**: Reverting to non-encrypted file storage after activation requires command-line access. The action is permanent via the Web UI.\"\n\n\t\t\t**Notes for Existing Files**\n\t\t\t* By default, enabling SSE does not encrypt existing files; only new files will be encrypted.\n\t\t\t* To encrypt all existing files, use the command `occ encryption:encrypt-all`.\n\n\t\t\t**Before You Begin**\n\t\t\t* **Read the Documentation:** Before you enable SSE, encrypt existing files, or disable SSE, it is critical to\n\t\t\t\tread the documentation to understand implications and the appropriate procedures to avoid data loss." : "Bu uygulama, Nextcloud sunucuda şifreleme (SSE) özelliği için (varsayılan) şifreleme uygulamasını sağlar.\n\n\t\t\t**Şifreleme ayrıntıları**\n\t\t\t* **Şifreleme kipi:** AES-256-CTR (varsayılan)\n\t\t\t* **Kimlik doğrulaması:** HMAC-SHA256\n\n\t\t\t**Önemli uyarılar**\n\t\t\t* **DİKKAT:** Tüm dosyaların şifresi çözülene kadar bu uygulamayı kapatmayın (`occ encryption:decrypt-all`).\n\t\t\t* **UYARI**: Açtıktan sonra şifrelenmemiş dosya depolama alanına geri dönmek için komut satırına erişim gerekir. İşlem, internet kullanıcı arayüzünden kalıcıdır.\"\n\n\t\t\t**Var olan dosyalar için notlar**\n\t\t\t* Varsayılan olarak, sunucuda şifrelemeyi açmak var olan dosyaları şifrelemez. Yalnızca yeni dosyalar şifrelenir.\n\t\t\t* Var olan tüm dosyaları şifrelemek için şu komutu kullanın: `occ encryption:encrypt-all`.\n\n\t\t\t**Başlamadan önce**\n\t\t\t* **Belgeleri okuyun:** Açmadan önce var olan dosyaları şifreleyin ya da sunucuda şifrelemeyi kapatın. \n\t\t\t\tVeri kaybını önlemek için, sonuçları ve uygun prosedürleri anlamak amacıyla belgeleri okumak çok önemlidir.",
|
||||
"Enabling this option encrypts all files stored on the main storage, otherwise only files on external storage will be encrypted" : "Bu seçenek açıldığında, ana depolama alanındaki tüm dosyalar şifrelenir. Kapatıldığında, yalnızca dış depolama alanındaki dosyalar şifrelenir",
|
||||
"Encrypt the home storage" : "Ana depolama şifrelensin",
|
||||
"Disable recovery key" : "Kurtarma anahtarını kullanımdan kaldır",
|
||||
@@ -63,7 +64,7 @@ OC.L10N.register(
|
||||
"Basic encryption module" : "Temel şifreleme modülü",
|
||||
"Missing parameters" : "Parametreler eksik",
|
||||
"Default encryption module for server-side encryption" : "Sunucu tarafında şifreleme için varsayılan şifreleme modülü",
|
||||
"In order to use this encryption module you need to enable server-side encryption in the admin settings. Once enabled this module will encrypt all your files transparently. The encryption is based on AES 256 keys.\nThe module will not touch existing files, only new files will be encrypted after server-side encryption was enabled. It is also not possible to disable the encryption again and switch back to an unencrypted system.\nPlease read the documentation to know all implications before you decide to enable server-side encryption." : "Bu şifreleme modülünün kullanılması için sunucu tarafında yönetim bölümünden şifreleme seçeneği açılmalıdır. Bu modül kullanıma alındıktan sonra tüm dosyalarınızı size farkettirmeden şifreler. Şifreleme AES 256 anahtarları ile yapılır. \nModül var olan dosyaları değiştirmez, yalnızca sunucu tarafında şifreleme açıldıktan sonra eklenen yeni dosyalar şifrelenir. Şifreleme açıldıktan sonra kapatılamaz ve şifreleme olmayan sisteme geri dönülemez.\nLütfen sunucu tarafı şifrelemeyi açmadan önce belgeleri okuyun ve uygulamadan doğacak tüm sonuçlarını öğrenin.",
|
||||
"In order to use this encryption module you need to enable server-side encryption in the admin settings. Once enabled this module will encrypt all your files transparently. The encryption is based on AES 256 keys.\nThe module will not touch existing files, only new files will be encrypted after server-side encryption was enabled. It is also not possible to disable the encryption again and switch back to an unencrypted system.\nPlease read the documentation to know all implications before you decide to enable server-side encryption." : "Bu şifreleme modülünün kullanılması için sunucuda yönetim bölümünden şifreleme seçeneği açılmalıdır. Bu modül kullanıma alındıktan sonra tüm dosyalarınızı size farkettirmeden şifreler. Şifreleme AES 256 anahtarları ile yapılır. \nModül var olan dosyaları değiştirmez, yalnızca sunucuda şifreleme açıldıktan sonra eklenen yeni dosyalar şifrelenir. Şifreleme açıldıktan sonra kapatılamaz ve şifreleme olmayan sisteme geri dönülemez.\nLütfen sunucuda şifrelemeyi açmadan önce belgeleri okuyun ve uygulamadan doğacak tüm sonuçlarını öğrenin.",
|
||||
"Change recovery key password:" : "Kurtarma anahtarı parolasını değiştir:",
|
||||
"Change Password" : "Parolayı değiştir",
|
||||
"Your private key password no longer matches your log-in password." : "Kişisel anahtar parolanız artık oturum açma parolanız ile eşleşmiyor.",
|
||||
@@ -72,6 +73,6 @@ OC.L10N.register(
|
||||
"Enable password recovery:" : "Parola kurtarma özelliğini aç:",
|
||||
"Enabled" : "Açık",
|
||||
"Disabled" : "Kapalı",
|
||||
"This app provides the (default) cryptography implementation for Nextcloud's Server-side Encryption (SSE) feature.\n\n\t\t\t**Encryption Details**\n\t\t\t* **Cipher Mode:** AES-256-CTR (default)\n\t\t\t* **Authentication:** HMAC-SHA256\n\n\t\t\t**Important Warnings**\n\t\t\t* **DANGER:** Do not disable this application until all files have been decrypted (`occ encryption:decrypt-all`).\n\t\t\t* **WARNING**: Reverting to non-encrypted file storage after activation requires command-line access. The action is permanent via the Web UI.\"\n\n\t\t\t**Notes for Existing Files**\n\t\t\t* By default, enabling SSE does not encrypt existing files; only new files will be encrypted.\n\t\t\t* To encrypt all existing files, use the command `occ encryption:encrypt-all`.\n\n\t\t\t**Before You Begin**\n\t\t\t* **Read the Documentation:** Before you enable SSE, encrypt existing files, or disable SSE, it is critical to \n\t\t\t\tread the documentation to understand implications and the appropriate procedures to avoid data loss." : "Bu uygulama, Nextcloud sunucu tarafı şifreleme (SSE) özelliği için (varsayılan) şifreleme uygulamasını sağlar.\n\n\t\t\t**Şifreleme ayrıntıları**\n\t\t\t* **Şifreleme kipi:** AES-256-CTR (varsayılan)\n\t\t\t* **Kimlik doğrulaması:** HMAC-SHA256\n\n\t\t\t**Önemli uyarılar**\n\t\t\t* **DİKKAT:** Tüm dosyaların şifresi çözülene kadar bu uygulamayı etkisizleştirmeyin (`occ encryption:decrypt-all`).\n\t\t\t* **UYARI**: Etkinleştirildikten sonra şifrelenmemiş dosya depolama alanına geri dönmek için komut satırına erişim gerekir. İşlem, internet kullanıcı arayüzünden kalıcıdır.\"\n\n\t\t\t**Var olan dosyalar için notlar**\n\t\t\t* Varsayılan olarak, sunucu tarafı şifrelemeyi etkinleştirmek var olan dosyaları şifrelemez. Yalnızca yeni dosyalar şifrelenir.\n\t\t\t* Var olan tüm dosyaları şifrelemek için şu komutu kullanın: `occ encryption:encrypt-all`.\n\n\t\t\t**Başlamadan önce**\n\t\t\t* **Belgeleri okuyun:** Sunucu tarafı şifrelemeyi etkinleştirmeden önce var olan dosyaları şifreleyin ya da sunucu tarafı şifrelemeyi etkisizleştirin. \n\t\t\t\tVeri kaybını önlemek için sonuçları ve uygun prosedürleri anlamak üzere belgeleri okumak çok önemlidir."
|
||||
"This app provides the (default) cryptography implementation for Nextcloud's Server-side Encryption (SSE) feature.\n\n\t\t\t**Encryption Details**\n\t\t\t* **Cipher Mode:** AES-256-CTR (default)\n\t\t\t* **Authentication:** HMAC-SHA256\n\n\t\t\t**Important Warnings**\n\t\t\t* **DANGER:** Do not disable this application until all files have been decrypted (`occ encryption:decrypt-all`).\n\t\t\t* **WARNING**: Reverting to non-encrypted file storage after activation requires command-line access. The action is permanent via the Web UI.\"\n\n\t\t\t**Notes for Existing Files**\n\t\t\t* By default, enabling SSE does not encrypt existing files; only new files will be encrypted.\n\t\t\t* To encrypt all existing files, use the command `occ encryption:encrypt-all`.\n\n\t\t\t**Before You Begin**\n\t\t\t* **Read the Documentation:** Before you enable SSE, encrypt existing files, or disable SSE, it is critical to \n\t\t\t\tread the documentation to understand implications and the appropriate procedures to avoid data loss." : "Bu uygulama, Nextcloud sunucuda şifreleme (SSE) özelliği için (varsayılan) şifreleme uygulamasını sağlar.\n\n\t\t\t**Şifreleme ayrıntıları**\n\t\t\t* **Şifreleme kipi:** AES-256-CTR (varsayılan)\n\t\t\t* **Kimlik doğrulaması:** HMAC-SHA256\n\n\t\t\t**Önemli uyarılar**\n\t\t\t* **DİKKAT:** Tüm dosyaların şifresi çözülene kadar bu uygulamayı kapatmayın (`occ encryption:decrypt-all`).\n\t\t\t* **UYARI**: Açıldıktan sonra şifrelenmemiş dosya depolama alanına geri dönmek için komut satırına erişim gerekir. İşlem, internet kullanıcı arayüzünden kalıcıdır.\"\n\n\t\t\t**Var olan dosyalar için notlar**\n\t\t\t* Varsayılan olarak, sunucuda şifrelemeyi açmak var olan dosyaları şifrelemez. Yalnızca yeni dosyalar şifrelenir.\n\t\t\t* Var olan tüm dosyaları şifrelemek için şu komutu kullanın: `occ encryption:encrypt-all`.\n\n\t\t\t**Başlamadan önce**\n\t\t\t* **Belgeleri okuyun:** Açmadan önce var olan dosyaları şifreleyin ya da sunucuda şifrelemeyi kapatın. \n\t\t\t\tVeri kaybını önlemek için sonuçları ve uygun prosedürleri anlamak amacıyla belgeleri okumak çok önemlidir."
|
||||
},
|
||||
"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