Compare commits
326 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 33684e6701 | |||
| 38327e4493 | |||
| d73537dd60 | |||
| 9c3acefe53 | |||
| fb38f2fd19 | |||
| 480517ae0c | |||
| 4927a283bd | |||
| 9a0892ca30 | |||
| 0b374e6c68 | |||
| 88ba65bcd4 | |||
| 996be0f441 | |||
| 9b2fff5931 | |||
| c4e6fbdae7 | |||
| c21b8169ff | |||
| a9d450285f | |||
| 78022f5447 | |||
| 9eb4c96d3b | |||
| a42bd7a507 | |||
| 805fe3e15b | |||
| ff4fdf1af8 | |||
| 75419ce8f2 | |||
| 4fe0799d26 | |||
| a38cb61d4d | |||
| 2f2049cbeb | |||
| 1d50cf3b51 | |||
| bdce9c3d79 | |||
| e17af0e174 | |||
| c8d6a14444 | |||
| 0221611757 | |||
| 796eec5962 | |||
| 61bb2dca40 | |||
| 8c93c00fd7 | |||
| 7d8735f48b | |||
| 4cf2203de8 | |||
| 6ee28229d5 | |||
| 03f22074d9 | |||
| 8c93efec89 | |||
| 1d3c3cfb80 | |||
| 22160b9d2b | |||
| 5518249158 | |||
| 5c707dd80a | |||
| be89b726b5 | |||
| e8aacb5f34 | |||
| f816a0b68e | |||
| edd5dee342 | |||
| 2f8920564b | |||
| bcc8ff7b13 | |||
| 0f4b27eccc | |||
| c15af7f228 | |||
| 7bc8c5be3f | |||
| 2c03a97386 | |||
| 568407b4d7 | |||
| 34097b6070 | |||
| 0bdabcf049 | |||
| 16ad73cd15 | |||
| b17745e7fd | |||
| 76dc41ea18 | |||
| ef5edb6801 | |||
| dc7db45297 | |||
| 289cb082a6 | |||
| e2ea6d4680 | |||
| cedd1ee24b | |||
| b247c97964 | |||
| 1b7418f848 | |||
| 5a73733ec1 | |||
| 3ba18f78b2 | |||
| 94c80aadd1 | |||
| 7e92b157e3 | |||
| 1cb6dc0e58 | |||
| b8338a05b3 | |||
| 6fb7f7f4e7 | |||
| 3b9da80293 | |||
| c5fa862b06 | |||
| 145e130c20 | |||
| 4f3958f7c6 | |||
| 081396c56c | |||
| 29c358e537 | |||
| bfee7226cb | |||
| f2de5c79cd | |||
| 809bbe3ca5 | |||
| 47b46d830a | |||
| 31d719e666 | |||
| 02f4a82088 | |||
| a7338b079f | |||
| 3df6d90a4c | |||
| 40117dced3 | |||
| e302983a87 | |||
| eb34ddbf97 | |||
| 9001ae2a4e | |||
| 289e87e00f | |||
| 34aaa0cf83 | |||
| 6a97e8e3d1 | |||
| 5b02d11a71 | |||
| 5b254ea39a | |||
| d785bcdc6e | |||
| f383db26eb | |||
| a1709f576e | |||
| e8986db7a4 | |||
| 0a79bc44df | |||
| 53c1268ead | |||
| bede81391b | |||
| c8a12a54fd | |||
| c1b3b3c4db | |||
| 6c85500bfb | |||
| 310cd23a6a | |||
| 995c97acb4 | |||
| e38dfef0dc | |||
| eba11750eb | |||
| 26ab3a6b74 | |||
| 5057d5fcc5 | |||
| 778c6c0d46 | |||
| d5c15d4d2f | |||
| 706c78ff52 | |||
| 5cbda3593f | |||
| 3999dcf796 | |||
| 025e081530 | |||
| c0595e9eaa | |||
| 646e8ee060 | |||
| a334e136d5 | |||
| 2908f81769 | |||
| 86b69c75e6 | |||
| 102c778228 | |||
| e512b5b21f | |||
| 2d87655e2c | |||
| b849f71e8f | |||
| 4b2a93cf0a | |||
| 4111bdbbcf | |||
| 4515974bbd | |||
| 247b254294 | |||
| 5262bac6b5 | |||
| 925aa821f5 | |||
| 8d9f73d4cf | |||
| 5087e3376a | |||
| 143cce065d | |||
| 1248ac93f2 | |||
| f08da99e79 | |||
| b7dc720848 | |||
| 896fb2fcb5 | |||
| 45f5daa45a | |||
| 673b8c9c9a | |||
| ed02d0df05 | |||
| 9e9f3b9d16 | |||
| abd971b65b | |||
| 46e624a089 | |||
| 7512b939b4 | |||
| 51468bc2a4 | |||
| 0d6e9f8410 | |||
| 7f31b6f298 | |||
| 2c53d34ecc | |||
| 7a367b6451 | |||
| e87830b369 | |||
| ec84001af7 | |||
| 176f575a92 | |||
| af38184b6f | |||
| 8b1ac839d7 | |||
| d0f819ba99 | |||
| daa69a6b12 | |||
| a9635044e3 | |||
| 5704bcda6b | |||
| 6a6f5f8086 | |||
| d31d6ec707 | |||
| 7ef1216392 | |||
| 784f76d2b9 | |||
| bc567d3c55 | |||
| d27f648b06 | |||
| cde2618356 | |||
| da5c912f3c | |||
| f8d6d9eb4a | |||
| c145870da5 | |||
| ff65b61d9c | |||
| a75060a07d | |||
| 6f9bb77b09 | |||
| f9030fce04 | |||
| 79184f3aed | |||
| 03332a1d13 | |||
| 853835b201 | |||
| eaaa9070ed | |||
| 4c3ad2eea8 | |||
| 2f4a8bb89c | |||
| 5835599fa1 | |||
| c9712b4953 | |||
| 78d69d5581 | |||
| 38c8ea75ae | |||
| 6bb941bfad | |||
| 3c1dc006c6 | |||
| 7c8c593f5c | |||
| caf664ea43 | |||
| a81d58970c | |||
| 5ab16f46f9 | |||
| 5283bb7c2f | |||
| aab11d35d3 | |||
| 39e2e70084 | |||
| d18b10ca11 | |||
| 983fd30285 | |||
| 18351be65c | |||
| e878ff9119 | |||
| bc1f9184b7 | |||
| 985eca3cf5 | |||
| 73cc94b80e | |||
| 9c1d306a3e | |||
| a23c087737 | |||
| b3467b433c | |||
| a7a78054aa | |||
| 3c14f9f933 | |||
| 5c9c2fe5e2 | |||
| 111350b17b | |||
| d4b05201d8 | |||
| 55f968aa4b | |||
| 494806ee64 | |||
| dbb5705152 | |||
| b7cdfddeda | |||
| 1b4722c330 | |||
| 51f596e0c2 | |||
| 4082a45d6d | |||
| 7996a3eb3a | |||
| 6d55224d46 | |||
| 8ac796cd86 | |||
| 130cb05ea4 | |||
| 61d5567913 | |||
| aaed9a9045 | |||
| 07b046f762 | |||
| 8c21f2b19c | |||
| 1091e59b90 | |||
| 894fda1a4d | |||
| 32f5f6e08e | |||
| 11aa997da3 | |||
| d8635180c7 | |||
| e2d028a3d6 | |||
| 2b3fec2900 | |||
| 1b05582b97 | |||
| bd6c16ea88 | |||
| eb0ddffaaa | |||
| cc3938da72 | |||
| 5d6dd51c8d | |||
| fad1f67156 | |||
| 5c4bd8b03f | |||
| 3712ba2c2a | |||
| 01c6095052 | |||
| 4d770f1c3c | |||
| e0baf69e45 | |||
| 301f567089 | |||
| 14132593f7 | |||
| 3bdff6b3f5 | |||
| 012a3a4584 | |||
| afe77e32f2 | |||
| 1a716578fd | |||
| ec024eb590 | |||
| 606c8b9a26 | |||
| ef0c1bd11c | |||
| 2975a99848 | |||
| b243fd31bd | |||
| 8473d95f84 | |||
| 10ef3a56d7 | |||
| ec6d6dfb44 | |||
| 591601d000 | |||
| fc5dad8343 | |||
| 85198fdc2a | |||
| c88a5b451c | |||
| 7b82f1330c | |||
| 8cbbb9ab15 | |||
| fa38efe0ad | |||
| c3857ee12e | |||
| 4f0d5634c7 | |||
| 295f0503d5 | |||
| a7a8738e63 | |||
| 8a8e1c83ec | |||
| e44305a8b7 | |||
| b35569abe2 | |||
| f9fa5abcf2 | |||
| fc8d188c9f | |||
| 3d36834284 | |||
| 4a35837741 | |||
| 3da919c783 | |||
| 8ffd30bbf9 | |||
| 94e2af0302 | |||
| d5417d63e0 | |||
| fa5548a1f3 | |||
| 2e816535cf | |||
| 109b454e48 | |||
| bdea4337d3 | |||
| ac0abb34b0 | |||
| 4d65b91b06 | |||
| 93b97f49e5 | |||
| b36e4e3824 | |||
| f7cdb228c9 | |||
| 4636e165e5 | |||
| 12a4ff89c3 | |||
| 0958fda1b4 | |||
| 205abce9ec | |||
| 5bfce62a48 | |||
| 5c995edd07 | |||
| a8a2edcaa8 | |||
| 88d7275c60 | |||
| ce41a2d727 | |||
| 9404059f6c | |||
| 971dfd7cbf | |||
| 776a689e09 | |||
| 357292f937 | |||
| fa60488ee7 | |||
| 1a781d5afa | |||
| b3c5707a0c | |||
| 7b6303dcc9 | |||
| a98ba27a0f | |||
| 4733369764 | |||
| 92e71d23f2 | |||
| f3850b141a | |||
| b9d97f2c47 | |||
| acf04ff752 | |||
| 5867a0f252 | |||
| 11e1d15de9 | |||
| fc0ecf963b | |||
| f304b80414 | |||
| b56b23cfb2 | |||
| e43e44d2ee | |||
| 3cacd7c035 | |||
| 434adefe71 | |||
| 4102af5324 | |||
| 9ba63683ae | |||
| f933a41a5a | |||
| 4b2fcac882 | |||
| 7d73be6bdd | |||
| d408327373 | |||
| cd310afe1a | |||
| e2b8ef769c | |||
| c7ba72552c | |||
| e2195a2f6d |
@@ -49,5 +49,12 @@ module.exports = {
|
||||
'@typescript-eslint/no-explicit-any': 'warn',
|
||||
},
|
||||
},
|
||||
{
|
||||
files: ['*.vue'],
|
||||
rules: {
|
||||
'no-irregular-whitespace': 'off',
|
||||
'vue/no-irregular-whitespace': 'error',
|
||||
},
|
||||
},
|
||||
],
|
||||
}
|
||||
|
||||
@@ -52,13 +52,13 @@ jobs:
|
||||
|
||||
steps:
|
||||
- name: Checkout server
|
||||
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
|
||||
uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
|
||||
with:
|
||||
persist-credentials: false
|
||||
submodules: true
|
||||
|
||||
- name: Set up php ${{ matrix.php-versions }}
|
||||
uses: shivammathur/setup-php@ccf2c627fe61b1b4d924adfcbd19d661a18133a0 #v2.35.2
|
||||
uses: shivammathur/setup-php@ec406be512d7077f68eed36e63f4d91bc006edc4 #v2.35.4
|
||||
with:
|
||||
php-version: ${{ matrix.php-versions }}
|
||||
extensions: bz2, ctype, curl, dom, fileinfo, gd, iconv, intl, json, libxml, mbstring, openssl, pcntl, posix, session, simplexml, xmlreader, xmlwriter, zip, zlib, sqlite, pdo_sqlite
|
||||
@@ -81,13 +81,13 @@ jobs:
|
||||
|
||||
steps:
|
||||
- name: Checkout server
|
||||
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
|
||||
uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
|
||||
with:
|
||||
persist-credentials: false
|
||||
submodules: true
|
||||
|
||||
- name: Set up php ${{ matrix.php-versions }}
|
||||
uses: shivammathur/setup-php@ccf2c627fe61b1b4d924adfcbd19d661a18133a0 #v2.35.2
|
||||
uses: shivammathur/setup-php@ec406be512d7077f68eed36e63f4d91bc006edc4 #v2.35.4
|
||||
with:
|
||||
php-version: ${{ matrix.php-versions }}
|
||||
extensions: bz2, ctype, curl, dom, fileinfo, gd, iconv, intl, json, libxml, mbstring, openssl, pcntl, posix, session, simplexml, xmlreader, xmlwriter, zip, zlib, sqlite, pdo_sqlite
|
||||
|
||||
@@ -31,7 +31,7 @@ jobs:
|
||||
- 'version.php'
|
||||
|
||||
- name: Checkout
|
||||
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
|
||||
uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
|
||||
with:
|
||||
persist-credentials: false
|
||||
|
||||
|
||||
@@ -27,7 +27,7 @@ jobs:
|
||||
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
|
||||
uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
|
||||
with:
|
||||
persist-credentials: false
|
||||
|
||||
|
||||
@@ -32,7 +32,7 @@ jobs:
|
||||
build-mode: none
|
||||
steps:
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@v4
|
||||
uses: actions/checkout@v5
|
||||
|
||||
- name: Initialize CodeQL
|
||||
uses: github/codeql-action/init@v3
|
||||
|
||||
@@ -103,7 +103,7 @@ jobs:
|
||||
key: git-repo
|
||||
|
||||
- name: Checkout ${{ needs.init.outputs.head_ref }}
|
||||
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
|
||||
uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
|
||||
with:
|
||||
# Needed to allow force push later
|
||||
persist-credentials: true
|
||||
|
||||
@@ -38,7 +38,7 @@ jobs:
|
||||
id: comment-branch
|
||||
|
||||
- name: Checkout ${{ steps.comment-branch.outputs.head_ref }}
|
||||
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
|
||||
uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
|
||||
with:
|
||||
persist-credentials: false
|
||||
fetch-depth: 0
|
||||
|
||||
@@ -48,7 +48,7 @@ jobs:
|
||||
exit 1
|
||||
|
||||
- name: Checkout server
|
||||
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
|
||||
uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
|
||||
with:
|
||||
persist-credentials: false
|
||||
# We need to checkout submodules for 3rdparty
|
||||
@@ -102,10 +102,10 @@ jobs:
|
||||
matrix:
|
||||
# Run multiple copies of the current job in parallel
|
||||
# Please increase the number or runners as your tests suite grows (0 based index for e2e tests)
|
||||
containers: ['component', 'setup', '0', '1', '2', '3', '4', '5', '6', '7']
|
||||
containers: ['component', 'setup', '0', '1', '2', '3', '4', '5', '6', '7', '8', '9']
|
||||
# Hack as strategy.job-total includes the component and GitHub does not allow math expressions
|
||||
# Always align this number with the total of e2e runners (max. index + 1)
|
||||
total-containers: [8]
|
||||
total-containers: [10]
|
||||
|
||||
services:
|
||||
mysql:
|
||||
|
||||
@@ -55,7 +55,7 @@ jobs:
|
||||
|
||||
steps:
|
||||
- name: Checkout server
|
||||
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
|
||||
uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
|
||||
with:
|
||||
persist-credentials: false
|
||||
submodules: true
|
||||
@@ -71,7 +71,7 @@ jobs:
|
||||
if [[ "${{ matrix.ftpd }}" == 'pure-ftpd' ]]; then docker run --name ftp -d --net host -e "PUBLICHOST=localhost" -e FTP_USER_NAME=test -e FTP_USER_PASS=test -e FTP_USER_HOME=/home/test -v /tmp/ftp:/home/test -v /tmp/ftp:/etc/pure-ftpd/passwd stilliard/pure-ftpd; fi
|
||||
|
||||
- name: Set up php ${{ matrix.php-versions }}
|
||||
uses: shivammathur/setup-php@ccf2c627fe61b1b4d924adfcbd19d661a18133a0 #v2.35.2
|
||||
uses: shivammathur/setup-php@ec406be512d7077f68eed36e63f4d91bc006edc4 #v2.35.4
|
||||
with:
|
||||
php-version: ${{ matrix.php-versions }}
|
||||
# https://docs.nextcloud.com/server/stable/admin_manual/installation/source_installation.html#prerequisites-for-manual-installation
|
||||
@@ -104,7 +104,7 @@ jobs:
|
||||
|
||||
- name: Upload code coverage
|
||||
if: ${{ !cancelled() && matrix.coverage }}
|
||||
uses: codecov/codecov-action@18283e04ce6e62d37312384ff67231eb8fd56d24 # v5.4.3
|
||||
uses: codecov/codecov-action@fdcc8476540edceab3de004e990f80d881c6cc00 # v5.5.0
|
||||
with:
|
||||
files: ./clover.xml
|
||||
flags: phpunit-files-external-ftp
|
||||
|
||||
@@ -64,13 +64,13 @@ jobs:
|
||||
|
||||
steps:
|
||||
- name: Checkout server
|
||||
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
|
||||
uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
|
||||
with:
|
||||
persist-credentials: false
|
||||
submodules: true
|
||||
|
||||
- name: Set up php ${{ matrix.php-versions }}
|
||||
uses: shivammathur/setup-php@ccf2c627fe61b1b4d924adfcbd19d661a18133a0 #v2.35.2
|
||||
uses: shivammathur/setup-php@ec406be512d7077f68eed36e63f4d91bc006edc4 #v2.35.4
|
||||
with:
|
||||
php-version: ${{ matrix.php-versions }}
|
||||
# https://docs.nextcloud.com/server/stable/admin_manual/installation/source_installation.html#prerequisites-for-manual-installation
|
||||
@@ -104,7 +104,7 @@ jobs:
|
||||
|
||||
- name: Upload code coverage
|
||||
if: ${{ !cancelled() && matrix.coverage }}
|
||||
uses: codecov/codecov-action@18283e04ce6e62d37312384ff67231eb8fd56d24 # v5.4.3
|
||||
uses: codecov/codecov-action@fdcc8476540edceab3de004e990f80d881c6cc00 # v5.5.0
|
||||
with:
|
||||
files: ./clover.xml
|
||||
flags: phpunit-files-external-s3
|
||||
@@ -152,13 +152,13 @@ jobs:
|
||||
|
||||
steps:
|
||||
- name: Checkout server
|
||||
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
|
||||
uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
|
||||
with:
|
||||
persist-credentials: false
|
||||
submodules: true
|
||||
|
||||
- name: Set up php ${{ matrix.php-versions }}
|
||||
uses: shivammathur/setup-php@ccf2c627fe61b1b4d924adfcbd19d661a18133a0 #v2.35.2
|
||||
uses: shivammathur/setup-php@ec406be512d7077f68eed36e63f4d91bc006edc4 #v2.35.4
|
||||
with:
|
||||
php-version: ${{ matrix.php-versions }}
|
||||
# https://docs.nextcloud.com/server/stable/admin_manual/installation/source_installation.html#prerequisites-for-manual-installation
|
||||
@@ -185,7 +185,7 @@ jobs:
|
||||
|
||||
- name: Upload code coverage
|
||||
if: ${{ !cancelled() && matrix.coverage }}
|
||||
uses: codecov/codecov-action@18283e04ce6e62d37312384ff67231eb8fd56d24 # v5.4.3
|
||||
uses: codecov/codecov-action@fdcc8476540edceab3de004e990f80d881c6cc00 # v5.5.0
|
||||
with:
|
||||
files: ./clover.xml
|
||||
flags: phpunit-files-external-s3
|
||||
|
||||
@@ -55,7 +55,7 @@ jobs:
|
||||
|
||||
steps:
|
||||
- name: Checkout server
|
||||
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
|
||||
uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
|
||||
with:
|
||||
persist-credentials: false
|
||||
submodules: true
|
||||
@@ -67,7 +67,7 @@ jobs:
|
||||
if [[ '${{ matrix.sftpd }}' == 'openssh' ]]; then docker run -p 2222:22 --name sftp -d -v /tmp/sftp:/home/test atmoz/sftp 'test:test:::data'; fi
|
||||
|
||||
- name: Set up php ${{ matrix.php-versions }}
|
||||
uses: shivammathur/setup-php@ccf2c627fe61b1b4d924adfcbd19d661a18133a0 #v2.35.2
|
||||
uses: shivammathur/setup-php@ec406be512d7077f68eed36e63f4d91bc006edc4 #v2.35.4
|
||||
with:
|
||||
php-version: ${{ matrix.php-versions }}
|
||||
# https://docs.nextcloud.com/server/stable/admin_manual/installation/source_installation.html#prerequisites-for-manual-installation
|
||||
@@ -93,7 +93,7 @@ jobs:
|
||||
|
||||
- name: Upload code coverage
|
||||
if: ${{ !cancelled() && matrix.coverage }}
|
||||
uses: codecov/codecov-action@18283e04ce6e62d37312384ff67231eb8fd56d24 # v5.4.3
|
||||
uses: codecov/codecov-action@fdcc8476540edceab3de004e990f80d881c6cc00 # v5.5.0
|
||||
with:
|
||||
files: ./clover.xml
|
||||
flags: phpunit-files-external-sftp
|
||||
|
||||
@@ -46,13 +46,13 @@ jobs:
|
||||
|
||||
steps:
|
||||
- name: Checkout server
|
||||
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
|
||||
uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
|
||||
with:
|
||||
persist-credentials: false
|
||||
submodules: true
|
||||
|
||||
- name: Checkout user_saml
|
||||
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
|
||||
uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
|
||||
with:
|
||||
persist-credentials: false
|
||||
repository: nextcloud/user_saml
|
||||
|
||||
@@ -60,13 +60,13 @@ jobs:
|
||||
|
||||
steps:
|
||||
- name: Checkout server
|
||||
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
|
||||
uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
|
||||
with:
|
||||
persist-credentials: false
|
||||
submodules: true
|
||||
|
||||
- name: Set up php ${{ matrix.php-versions }}
|
||||
uses: shivammathur/setup-php@ccf2c627fe61b1b4d924adfcbd19d661a18133a0 #v2.35.2
|
||||
uses: shivammathur/setup-php@ec406be512d7077f68eed36e63f4d91bc006edc4 #v2.35.4
|
||||
with:
|
||||
php-version: ${{ matrix.php-versions }}
|
||||
# https://docs.nextcloud.com/server/stable/admin_manual/installation/source_installation.html#prerequisites-for-manual-installation
|
||||
@@ -100,7 +100,7 @@ jobs:
|
||||
|
||||
- name: Upload code coverage
|
||||
if: ${{ !cancelled() && matrix.coverage }}
|
||||
uses: codecov/codecov-action@18283e04ce6e62d37312384ff67231eb8fd56d24 # v5.4.3
|
||||
uses: codecov/codecov-action@fdcc8476540edceab3de004e990f80d881c6cc00 # v5.5.0
|
||||
with:
|
||||
files: ./clover.xml
|
||||
flags: phpunit-files-external-smb
|
||||
|
||||
@@ -60,13 +60,13 @@ jobs:
|
||||
|
||||
steps:
|
||||
- name: Checkout server
|
||||
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
|
||||
uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
|
||||
with:
|
||||
persist-credentials: false
|
||||
submodules: true
|
||||
|
||||
- name: Set up php ${{ matrix.php-versions }}
|
||||
uses: shivammathur/setup-php@ccf2c627fe61b1b4d924adfcbd19d661a18133a0 #v2.35.2
|
||||
uses: shivammathur/setup-php@ec406be512d7077f68eed36e63f4d91bc006edc4 #v2.35.4
|
||||
with:
|
||||
php-version: ${{ matrix.php-versions }}
|
||||
# https://docs.nextcloud.com/server/stable/admin_manual/installation/source_installation.html#prerequisites-for-manual-installation
|
||||
@@ -97,7 +97,7 @@ jobs:
|
||||
|
||||
- name: Upload code coverage
|
||||
if: ${{ !cancelled() && matrix.coverage }}
|
||||
uses: codecov/codecov-action@18283e04ce6e62d37312384ff67231eb8fd56d24 # v5.4.3
|
||||
uses: codecov/codecov-action@fdcc8476540edceab3de004e990f80d881c6cc00 # v5.5.0
|
||||
with:
|
||||
files: ./clover.xml
|
||||
flags: phpunit-files-external-webdav
|
||||
|
||||
@@ -53,13 +53,13 @@ jobs:
|
||||
|
||||
steps:
|
||||
- name: Checkout server
|
||||
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
|
||||
uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
|
||||
with:
|
||||
persist-credentials: false
|
||||
submodules: true
|
||||
|
||||
- name: Set up php ${{ matrix.php-versions }}
|
||||
uses: shivammathur/setup-php@ccf2c627fe61b1b4d924adfcbd19d661a18133a0 #v2.35.2
|
||||
uses: shivammathur/setup-php@ec406be512d7077f68eed36e63f4d91bc006edc4 #v2.35.4
|
||||
with:
|
||||
php-version: ${{ matrix.php-versions }}
|
||||
# https://docs.nextcloud.com/server/stable/admin_manual/installation/source_installation.html#prerequisites-for-manual-installation
|
||||
@@ -85,7 +85,7 @@ jobs:
|
||||
|
||||
- name: Upload code coverage
|
||||
if: ${{ !cancelled() && matrix.coverage }}
|
||||
uses: codecov/codecov-action@18283e04ce6e62d37312384ff67231eb8fd56d24 # v5.4.3
|
||||
uses: codecov/codecov-action@fdcc8476540edceab3de004e990f80d881c6cc00 # v5.5.0
|
||||
with:
|
||||
files: ./clover.xml
|
||||
flags: phpunit-files-external-generic
|
||||
|
||||
@@ -24,14 +24,14 @@ jobs:
|
||||
require: write
|
||||
|
||||
- name: Checkout github_helper
|
||||
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
|
||||
uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
|
||||
with:
|
||||
persist-credentials: false
|
||||
repository: nextcloud/github_helper
|
||||
path: github_helper
|
||||
|
||||
- name: Checkout server
|
||||
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
|
||||
uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
|
||||
with:
|
||||
persist-credentials: false
|
||||
path: server
|
||||
@@ -47,8 +47,13 @@ jobs:
|
||||
TAGS=$(git log --decorate --oneline | egrep 'tag: ' | sed -r 's/^.+tag: ([^,\)]+)[,\)].+$/\1/g')
|
||||
CURRENT_TAG=$(echo "$TAGS" | head -n 1)
|
||||
|
||||
# If current tag is the first beta, we use the previous major RC1
|
||||
if echo "$CURRENT_TAG" | grep -q 'beta1'; then
|
||||
MAJOR=$(echo "$CURRENT_TAG" | sed -E 's/^v([0-9]+).*/\1/')
|
||||
PREV=$((MAJOR - 1))
|
||||
PREVIOUS_TAG="v${PREV}.0.0rc1"
|
||||
# Get the previous tag - filter pre-releases only if current tag is stable
|
||||
if echo "$CURRENT_TAG" | grep -q 'rc\|beta\|alpha'; then
|
||||
elif echo "$CURRENT_TAG" | grep -q 'rc\|beta\|alpha'; then
|
||||
# Current tag is pre-release, don't filter
|
||||
PREVIOUS_TAG=$(echo "$TAGS" | sed -n '2p')
|
||||
else
|
||||
@@ -68,7 +73,7 @@ jobs:
|
||||
fi
|
||||
|
||||
- name: Set up php 8.2
|
||||
uses: shivammathur/setup-php@ccf2c627fe61b1b4d924adfcbd19d661a18133a0 # v2.35.2
|
||||
uses: shivammathur/setup-php@ec406be512d7077f68eed36e63f4d91bc006edc4 # v2.35.4
|
||||
with:
|
||||
php-version: 8.2
|
||||
coverage: none
|
||||
|
||||
@@ -53,13 +53,13 @@ jobs:
|
||||
|
||||
steps:
|
||||
- name: Checkout server
|
||||
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
|
||||
uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
|
||||
with:
|
||||
persist-credentials: false
|
||||
submodules: true
|
||||
|
||||
- name: Set up php ${{ matrix.php-versions }}
|
||||
uses: shivammathur/setup-php@ccf2c627fe61b1b4d924adfcbd19d661a18133a0 #v2.35.2
|
||||
uses: shivammathur/setup-php@ec406be512d7077f68eed36e63f4d91bc006edc4 #v2.35.4
|
||||
with:
|
||||
php-version: ${{ matrix.php-versions }}
|
||||
# https://docs.nextcloud.com/server/stable/admin_manual/installation/source_installation.html#prerequisites-for-manual-installation
|
||||
@@ -70,7 +70,7 @@ jobs:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
|
||||
- name: Set up Python
|
||||
uses: LizardByte/actions/actions/setup_python@eddc8fc8b27048e25040e37e3585bd3ef9a968ed # v2025.715.25226
|
||||
uses: LizardByte/actions/actions/setup_python@bff0a193747a3ac7930a665fc1d4b23eba583b99 # v2025.814.40518
|
||||
with:
|
||||
python-version: '2.7'
|
||||
|
||||
|
||||
@@ -52,13 +52,13 @@ jobs:
|
||||
|
||||
steps:
|
||||
- name: Checkout server
|
||||
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
|
||||
uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
|
||||
with:
|
||||
persist-credentials: false
|
||||
submodules: true
|
||||
|
||||
- name: Set up php ${{ matrix.php-versions }}
|
||||
uses: shivammathur/setup-php@ccf2c627fe61b1b4d924adfcbd19d661a18133a0 #v2.35.2
|
||||
uses: shivammathur/setup-php@ec406be512d7077f68eed36e63f4d91bc006edc4 #v2.35.4
|
||||
with:
|
||||
php-version: ${{ matrix.php-versions }}
|
||||
# https://docs.nextcloud.com/server/stable/admin_manual/installation/source_installation.html#prerequisites-for-manual-installation
|
||||
|
||||
@@ -67,13 +67,13 @@ jobs:
|
||||
|
||||
steps:
|
||||
- name: Checkout server
|
||||
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
|
||||
uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
|
||||
with:
|
||||
persist-credentials: false
|
||||
submodules: true
|
||||
|
||||
- name: Set up php ${{ matrix.php-versions }}
|
||||
uses: shivammathur/setup-php@ccf2c627fe61b1b4d924adfcbd19d661a18133a0 #v2.35.2
|
||||
uses: shivammathur/setup-php@ec406be512d7077f68eed36e63f4d91bc006edc4 #v2.35.4
|
||||
with:
|
||||
php-version: ${{ matrix.php-versions }}
|
||||
# https://docs.nextcloud.com/server/stable/admin_manual/installation/source_installation.html#prerequisites-for-manual-installation
|
||||
|
||||
@@ -95,14 +95,14 @@ jobs:
|
||||
|
||||
steps:
|
||||
- name: Checkout server
|
||||
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
|
||||
uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
|
||||
with:
|
||||
persist-credentials: false
|
||||
submodules: true
|
||||
|
||||
- name: Checkout Talk app
|
||||
if: ${{ matrix.test-suite == 'videoverification_features' }}
|
||||
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
|
||||
uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
|
||||
with:
|
||||
persist-credentials: false
|
||||
repository: nextcloud/spreed
|
||||
@@ -111,7 +111,7 @@ jobs:
|
||||
|
||||
- name: Checkout Activity app
|
||||
if: ${{ matrix.test-suite == 'sharing_features' }}
|
||||
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
|
||||
uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
|
||||
with:
|
||||
persist-credentials: false
|
||||
repository: nextcloud/activity
|
||||
@@ -119,7 +119,7 @@ jobs:
|
||||
ref: ${{ matrix.activity-versions }}
|
||||
|
||||
- name: Set up php ${{ matrix.php-versions }}
|
||||
uses: shivammathur/setup-php@ccf2c627fe61b1b4d924adfcbd19d661a18133a0 #v2.35.2
|
||||
uses: shivammathur/setup-php@ec406be512d7077f68eed36e63f4d91bc006edc4 #v2.35.4
|
||||
with:
|
||||
php-version: ${{ matrix.php-versions }}
|
||||
# https://docs.nextcloud.com/server/stable/admin_manual/installation/source_installation.html#prerequisites-for-manual-installation
|
||||
|
||||
@@ -56,7 +56,7 @@ jobs:
|
||||
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
|
||||
uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
|
||||
with:
|
||||
persist-credentials: false
|
||||
|
||||
|
||||
@@ -48,12 +48,12 @@ jobs:
|
||||
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
|
||||
uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
|
||||
with:
|
||||
persist-credentials: false
|
||||
|
||||
- name: Set up php8.1
|
||||
uses: shivammathur/setup-php@ccf2c627fe61b1b4d924adfcbd19d661a18133a0 # v2.35.2
|
||||
uses: shivammathur/setup-php@ec406be512d7077f68eed36e63f4d91bc006edc4 # v2.35.4
|
||||
with:
|
||||
php-version: 8.1
|
||||
extensions: bz2, ctype, curl, dom, fileinfo, gd, iconv, intl, json, libxml, mbstring, openssl, pcntl, posix, session, simplexml, xmlreader, xmlwriter, zip, zlib, sqlite, pdo_sqlite
|
||||
|
||||
@@ -53,12 +53,12 @@ jobs:
|
||||
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
|
||||
uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
|
||||
with:
|
||||
persist-credentials: false
|
||||
|
||||
- name: Set up php ${{ matrix.php-versions }}
|
||||
uses: shivammathur/setup-php@ccf2c627fe61b1b4d924adfcbd19d661a18133a0 # v2.35.2
|
||||
uses: shivammathur/setup-php@ec406be512d7077f68eed36e63f4d91bc006edc4 # v2.35.4
|
||||
with:
|
||||
php-version: ${{ matrix.php-versions }}
|
||||
coverage: none
|
||||
|
||||
@@ -25,7 +25,7 @@ jobs:
|
||||
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
|
||||
uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
|
||||
with:
|
||||
persist-credentials: false
|
||||
|
||||
|
||||
@@ -62,7 +62,7 @@ jobs:
|
||||
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
|
||||
uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
|
||||
with:
|
||||
persist-credentials: false
|
||||
|
||||
@@ -85,7 +85,7 @@ jobs:
|
||||
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
|
||||
uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
|
||||
with:
|
||||
persist-credentials: false
|
||||
|
||||
@@ -106,7 +106,7 @@ jobs:
|
||||
run: npm run test:coverage --if-present
|
||||
|
||||
- name: Collect coverage
|
||||
uses: codecov/codecov-action@18283e04ce6e62d37312384ff67231eb8fd56d24 # v5.4.3
|
||||
uses: codecov/codecov-action@fdcc8476540edceab3de004e990f80d881c6cc00 # v5.5.0
|
||||
with:
|
||||
files: ./coverage/lcov.info
|
||||
|
||||
@@ -125,7 +125,7 @@ jobs:
|
||||
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
|
||||
uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
|
||||
with:
|
||||
persist-credentials: false
|
||||
|
||||
@@ -155,7 +155,7 @@ jobs:
|
||||
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
|
||||
uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
|
||||
with:
|
||||
persist-credentials: false
|
||||
|
||||
|
||||
@@ -56,7 +56,7 @@ jobs:
|
||||
name: NPM build
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
|
||||
uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
|
||||
with:
|
||||
persist-credentials: false
|
||||
|
||||
|
||||
@@ -31,7 +31,7 @@ jobs:
|
||||
steps:
|
||||
- name: Checkout
|
||||
id: checkout
|
||||
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
|
||||
uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
|
||||
with:
|
||||
persist-credentials: false
|
||||
ref: ${{ matrix.branches }}
|
||||
|
||||
@@ -73,13 +73,13 @@ jobs:
|
||||
|
||||
steps:
|
||||
- name: Checkout server
|
||||
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
|
||||
uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
|
||||
with:
|
||||
persist-credentials: false
|
||||
submodules: true
|
||||
|
||||
- name: Set up php ${{ matrix.php-versions }}
|
||||
uses: shivammathur/setup-php@ccf2c627fe61b1b4d924adfcbd19d661a18133a0 #v2.35.2
|
||||
uses: shivammathur/setup-php@ec406be512d7077f68eed36e63f4d91bc006edc4 #v2.35.4
|
||||
with:
|
||||
php-version: ${{ matrix.php-versions }}
|
||||
# https://docs.nextcloud.com/server/stable/admin_manual/installation/source_installation.html#prerequisites-for-manual-installation
|
||||
@@ -110,7 +110,7 @@ jobs:
|
||||
|
||||
- name: Upload code coverage
|
||||
if: ${{ !cancelled() && matrix.coverage }}
|
||||
uses: codecov/codecov-action@18283e04ce6e62d37312384ff67231eb8fd56d24 # v5.4.3
|
||||
uses: codecov/codecov-action@fdcc8476540edceab3de004e990f80d881c6cc00 # v5.5.0
|
||||
with:
|
||||
files: ./clover.xml
|
||||
flags: phpunit-azure
|
||||
|
||||
@@ -74,13 +74,13 @@ jobs:
|
||||
|
||||
steps:
|
||||
- name: Checkout server
|
||||
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
|
||||
uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
|
||||
with:
|
||||
persist-credentials: false
|
||||
submodules: true
|
||||
|
||||
- name: Set up php ${{ matrix.php-versions }}
|
||||
uses: shivammathur/setup-php@ccf2c627fe61b1b4d924adfcbd19d661a18133a0 #v2.35.2
|
||||
uses: shivammathur/setup-php@ec406be512d7077f68eed36e63f4d91bc006edc4 #v2.35.4
|
||||
with:
|
||||
php-version: ${{ matrix.php-versions }}
|
||||
# https://docs.nextcloud.com/server/stable/admin_manual/installation/source_installation.html#prerequisites-for-manual-installation
|
||||
@@ -116,7 +116,7 @@ jobs:
|
||||
|
||||
- name: Upload code coverage
|
||||
if: ${{ !cancelled() && matrix.coverage }}
|
||||
uses: codecov/codecov-action@18283e04ce6e62d37312384ff67231eb8fd56d24 # v5.4.3
|
||||
uses: codecov/codecov-action@fdcc8476540edceab3de004e990f80d881c6cc00 # v5.5.0
|
||||
with:
|
||||
files: ./clover.xml
|
||||
flags: phpunit-s3
|
||||
|
||||
@@ -71,13 +71,13 @@ jobs:
|
||||
|
||||
steps:
|
||||
- name: Checkout server
|
||||
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
|
||||
uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
|
||||
with:
|
||||
persist-credentials: false
|
||||
submodules: true
|
||||
|
||||
- name: Set up php ${{ matrix.php-versions }}
|
||||
uses: shivammathur/setup-php@ccf2c627fe61b1b4d924adfcbd19d661a18133a0 #v2.35.2
|
||||
uses: shivammathur/setup-php@ec406be512d7077f68eed36e63f4d91bc006edc4 #v2.35.4
|
||||
with:
|
||||
php-version: ${{ matrix.php-versions }}
|
||||
# https://docs.nextcloud.com/server/stable/admin_manual/installation/source_installation.html#prerequisites-for-manual-installation
|
||||
@@ -106,7 +106,7 @@ jobs:
|
||||
|
||||
- name: Upload code coverage
|
||||
if: ${{ !cancelled() && matrix.coverage }}
|
||||
uses: codecov/codecov-action@18283e04ce6e62d37312384ff67231eb8fd56d24 # v5.4.3
|
||||
uses: codecov/codecov-action@fdcc8476540edceab3de004e990f80d881c6cc00 # v5.5.0
|
||||
with:
|
||||
files: ./clover.xml
|
||||
flags: phpunit-swift
|
||||
|
||||
@@ -26,12 +26,12 @@ jobs:
|
||||
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
|
||||
uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
|
||||
with:
|
||||
persist-credentials: false
|
||||
|
||||
- name: Set up php
|
||||
uses: shivammathur/setup-php@ccf2c627fe61b1b4d924adfcbd19d661a18133a0 # v2.35.2
|
||||
uses: shivammathur/setup-php@ec406be512d7077f68eed36e63f4d91bc006edc4 # v2.35.4
|
||||
with:
|
||||
php-version: '8.1'
|
||||
extensions: ctype, curl, dom, fileinfo, gd, json, libxml, mbstring, openssl, pcntl, pdo, posix, session, simplexml, xml, xmlreader, xmlwriter, zip, zlib
|
||||
|
||||
@@ -35,14 +35,14 @@ jobs:
|
||||
exit 1
|
||||
|
||||
- name: Checkout server before PR
|
||||
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
|
||||
uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
|
||||
with:
|
||||
persist-credentials: false
|
||||
submodules: true
|
||||
ref: ${{ github.event.pull_request.base.ref }}
|
||||
|
||||
- name: Set up php ${{ matrix.php-versions }}
|
||||
uses: shivammathur/setup-php@ccf2c627fe61b1b4d924adfcbd19d661a18133a0 # v2.35.2
|
||||
uses: shivammathur/setup-php@ec406be512d7077f68eed36e63f4d91bc006edc4 # v2.35.4
|
||||
with:
|
||||
php-version: ${{ matrix.php-versions }}
|
||||
extensions: bz2, ctype, curl, dom, fileinfo, gd, iconv, intl, json, libxml, mbstring, openssl, pcntl, posix, redis, session, simplexml, xmlreader, xmlwriter, zip, zlib, sqlite, pdo_sqlite
|
||||
|
||||
@@ -34,7 +34,7 @@ jobs:
|
||||
|
||||
steps:
|
||||
- name: Checkout server
|
||||
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
|
||||
uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
|
||||
with:
|
||||
persist-credentials: false
|
||||
submodules: true
|
||||
@@ -45,7 +45,7 @@ jobs:
|
||||
sudo apt-get install -y ffmpeg imagemagick libmagickcore-6.q16-3-extra
|
||||
|
||||
- name: Set up php ${{ matrix.php-versions }}
|
||||
uses: shivammathur/setup-php@ccf2c627fe61b1b4d924adfcbd19d661a18133a0 #v2.35.2
|
||||
uses: shivammathur/setup-php@ec406be512d7077f68eed36e63f4d91bc006edc4 #v2.35.4
|
||||
with:
|
||||
php-version: ${{ matrix.php-versions }}
|
||||
extensions: bz2, ctype, curl, dom, fileinfo, gd, iconv, imagick, intl, json, libxml, mbstring, openssl, pcntl, posix, redis, session, simplexml, xmlreader, xmlwriter, zip, zlib, sqlite, pdo_sqlite, apcu, ldap
|
||||
|
||||
@@ -90,13 +90,13 @@ jobs:
|
||||
|
||||
steps:
|
||||
- name: Checkout server
|
||||
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
|
||||
uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
|
||||
with:
|
||||
persist-credentials: false
|
||||
submodules: true
|
||||
|
||||
- name: Set up php ${{ matrix.php-versions }}
|
||||
uses: shivammathur/setup-php@ccf2c627fe61b1b4d924adfcbd19d661a18133a0 # v2.35.2
|
||||
uses: shivammathur/setup-php@ec406be512d7077f68eed36e63f4d91bc006edc4 # v2.35.4
|
||||
with:
|
||||
php-version: ${{ matrix.php-versions }}
|
||||
# https://docs.nextcloud.com/server/stable/admin_manual/installation/source_installation.html#prerequisites-for-manual-installation
|
||||
@@ -129,7 +129,7 @@ jobs:
|
||||
|
||||
- name: Upload db code coverage
|
||||
if: ${{ !cancelled() && matrix.coverage }}
|
||||
uses: codecov/codecov-action@18283e04ce6e62d37312384ff67231eb8fd56d24 # v5.4.3
|
||||
uses: codecov/codecov-action@fdcc8476540edceab3de004e990f80d881c6cc00 # v5.5.0
|
||||
with:
|
||||
files: ./clover.db.xml
|
||||
flags: phpunit-mariadb
|
||||
|
||||
@@ -72,13 +72,13 @@ jobs:
|
||||
|
||||
steps:
|
||||
- name: Checkout server
|
||||
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
|
||||
uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
|
||||
with:
|
||||
persist-credentials: false
|
||||
submodules: true
|
||||
|
||||
- name: Set up php ${{ matrix.php-versions }}
|
||||
uses: shivammathur/setup-php@ccf2c627fe61b1b4d924adfcbd19d661a18133a0 #v2.35.2
|
||||
uses: shivammathur/setup-php@ec406be512d7077f68eed36e63f4d91bc006edc4 #v2.35.4
|
||||
with:
|
||||
php-version: ${{ matrix.php-versions }}
|
||||
# https://docs.nextcloud.com/server/stable/admin_manual/installation/source_installation.html#prerequisites-for-manual-installation
|
||||
@@ -103,7 +103,7 @@ jobs:
|
||||
|
||||
- name: Upload code coverage
|
||||
if: ${{ !cancelled() && matrix.coverage }}
|
||||
uses: codecov/codecov-action@18283e04ce6e62d37312384ff67231eb8fd56d24 # v5.4.3
|
||||
uses: codecov/codecov-action@fdcc8476540edceab3de004e990f80d881c6cc00 # v5.5.0
|
||||
with:
|
||||
files: ./clover.xml
|
||||
flags: phpunit-memcached
|
||||
|
||||
@@ -121,13 +121,13 @@ jobs:
|
||||
|
||||
steps:
|
||||
- name: Checkout server
|
||||
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
|
||||
uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
|
||||
with:
|
||||
persist-credentials: false
|
||||
submodules: true
|
||||
|
||||
- name: Set up php ${{ matrix.php-versions }}
|
||||
uses: shivammathur/setup-php@ccf2c627fe61b1b4d924adfcbd19d661a18133a0 #v2.35.2
|
||||
uses: shivammathur/setup-php@ec406be512d7077f68eed36e63f4d91bc006edc4 #v2.35.4
|
||||
with:
|
||||
php-version: ${{ matrix.php-versions }}
|
||||
# https://docs.nextcloud.com/server/stable/admin_manual/installation/source_installation.html#prerequisites-for-manual-installation
|
||||
@@ -161,7 +161,7 @@ jobs:
|
||||
|
||||
- name: Upload db code coverage
|
||||
if: ${{ !cancelled() && matrix.coverage }}
|
||||
uses: codecov/codecov-action@18283e04ce6e62d37312384ff67231eb8fd56d24 # v5.4.3
|
||||
uses: codecov/codecov-action@fdcc8476540edceab3de004e990f80d881c6cc00 # v5.5.0
|
||||
with:
|
||||
files: ./clover.db.xml
|
||||
flags: phpunit-mysql
|
||||
|
||||
@@ -90,13 +90,13 @@ jobs:
|
||||
|
||||
steps:
|
||||
- name: Checkout server
|
||||
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
|
||||
uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
|
||||
with:
|
||||
persist-credentials: false
|
||||
submodules: true
|
||||
|
||||
- name: Set up php ${{ matrix.php-versions }}
|
||||
uses: shivammathur/setup-php@ccf2c627fe61b1b4d924adfcbd19d661a18133a0 # v2.35.2
|
||||
uses: shivammathur/setup-php@ec406be512d7077f68eed36e63f4d91bc006edc4 # v2.35.4
|
||||
with:
|
||||
php-version: ${{ matrix.php-versions }}
|
||||
# https://docs.nextcloud.com/server/stable/admin_manual/installation/source_installation.html#prerequisites-for-manual-installation
|
||||
@@ -129,7 +129,7 @@ jobs:
|
||||
|
||||
- name: Upload db code coverage
|
||||
if: ${{ !cancelled() && matrix.coverage }}
|
||||
uses: codecov/codecov-action@18283e04ce6e62d37312384ff67231eb8fd56d24 # v5.4.3
|
||||
uses: codecov/codecov-action@fdcc8476540edceab3de004e990f80d881c6cc00 # v5.5.0
|
||||
with:
|
||||
files: ./clover.db.xml
|
||||
flags: phpunit-mysql
|
||||
|
||||
@@ -75,13 +75,13 @@ jobs:
|
||||
|
||||
steps:
|
||||
- name: Checkout server
|
||||
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
|
||||
uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
|
||||
with:
|
||||
persist-credentials: false
|
||||
submodules: true
|
||||
|
||||
- name: Set up php ${{ matrix.php-versions }}
|
||||
uses: shivammathur/setup-php@ccf2c627fe61b1b4d924adfcbd19d661a18133a0 #v2.35.2
|
||||
uses: shivammathur/setup-php@ec406be512d7077f68eed36e63f4d91bc006edc4 #v2.35.4
|
||||
with:
|
||||
php-version: ${{ matrix.php-versions }}
|
||||
# https://docs.nextcloud.com/server/stable/admin_manual/installation/source_installation.html#prerequisites-for-manual-installation
|
||||
@@ -109,7 +109,7 @@ jobs:
|
||||
|
||||
- name: Upload nodb code coverage
|
||||
if: ${{ !cancelled() && matrix.coverage }}
|
||||
uses: codecov/codecov-action@18283e04ce6e62d37312384ff67231eb8fd56d24 # v5.4.3
|
||||
uses: codecov/codecov-action@fdcc8476540edceab3de004e990f80d881c6cc00 # v5.5.0
|
||||
with:
|
||||
files: ./clover.nodb.xml
|
||||
flags: phpunit-nodb
|
||||
|
||||
@@ -72,13 +72,13 @@ jobs:
|
||||
|
||||
steps:
|
||||
- name: Checkout server
|
||||
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
|
||||
uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
|
||||
with:
|
||||
persist-credentials: false
|
||||
submodules: true
|
||||
|
||||
- name: Set up php ${{ matrix.php-versions }}
|
||||
uses: shivammathur/setup-php@ccf2c627fe61b1b4d924adfcbd19d661a18133a0 #v2.35.2
|
||||
uses: shivammathur/setup-php@ec406be512d7077f68eed36e63f4d91bc006edc4 #v2.35.4
|
||||
with:
|
||||
php-version: ${{ matrix.php-versions }}
|
||||
extensions: bz2, ctype, curl, dom, fileinfo, gd, iconv, intl, json, libxml, mbstring, openssl, pcntl, posix, redis, session, simplexml, xmlreader, xmlwriter, zip, zlib, sqlite, pdo_sqlite
|
||||
|
||||
@@ -101,13 +101,13 @@ jobs:
|
||||
|
||||
steps:
|
||||
- name: Checkout server
|
||||
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
|
||||
uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
|
||||
with:
|
||||
persist-credentials: false
|
||||
submodules: true
|
||||
|
||||
- name: Set up php ${{ matrix.php-versions }}
|
||||
uses: shivammathur/setup-php@ccf2c627fe61b1b4d924adfcbd19d661a18133a0 # v2.35.2
|
||||
uses: shivammathur/setup-php@ec406be512d7077f68eed36e63f4d91bc006edc4 # v2.35.4
|
||||
with:
|
||||
php-version: ${{ matrix.php-versions }}
|
||||
# https://docs.nextcloud.com/server/stable/admin_manual/installation/source_installation.html#prerequisites-for-manual-installation
|
||||
@@ -133,7 +133,7 @@ jobs:
|
||||
|
||||
- name: Upload db code coverage
|
||||
if: ${{ !cancelled() && matrix.coverage }}
|
||||
uses: codecov/codecov-action@18283e04ce6e62d37312384ff67231eb8fd56d24 # v5.4.3
|
||||
uses: codecov/codecov-action@fdcc8476540edceab3de004e990f80d881c6cc00 # v5.5.0
|
||||
with:
|
||||
files: ./clover.db.xml
|
||||
flags: phpunit-oci
|
||||
|
||||
@@ -90,13 +90,13 @@ jobs:
|
||||
|
||||
steps:
|
||||
- name: Checkout server
|
||||
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
|
||||
uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
|
||||
with:
|
||||
persist-credentials: false
|
||||
submodules: true
|
||||
|
||||
- name: Set up php ${{ matrix.php-versions }}
|
||||
uses: shivammathur/setup-php@ccf2c627fe61b1b4d924adfcbd19d661a18133a0 # v2.35.2
|
||||
uses: shivammathur/setup-php@ec406be512d7077f68eed36e63f4d91bc006edc4 # v2.35.4
|
||||
with:
|
||||
php-version: ${{ matrix.php-versions }}
|
||||
# https://docs.nextcloud.com/server/stable/admin_manual/installation/source_installation.html#prerequisites-for-manual-installation
|
||||
@@ -124,7 +124,7 @@ jobs:
|
||||
|
||||
- name: Upload db code coverage
|
||||
if: ${{ !cancelled() && matrix.coverage }}
|
||||
uses: codecov/codecov-action@18283e04ce6e62d37312384ff67231eb8fd56d24 # v5.4.3
|
||||
uses: codecov/codecov-action@fdcc8476540edceab3de004e990f80d881c6cc00 # v5.5.0
|
||||
with:
|
||||
files: ./clover.db.xml
|
||||
flags: phpunit-postgres
|
||||
|
||||
@@ -75,13 +75,13 @@ jobs:
|
||||
|
||||
steps:
|
||||
- name: Checkout server
|
||||
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
|
||||
uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
|
||||
with:
|
||||
persist-credentials: false
|
||||
submodules: true
|
||||
|
||||
- name: Set up php ${{ matrix.php-versions }}
|
||||
uses: shivammathur/setup-php@ccf2c627fe61b1b4d924adfcbd19d661a18133a0 # v2.35.2
|
||||
uses: shivammathur/setup-php@ec406be512d7077f68eed36e63f4d91bc006edc4 # v2.35.4
|
||||
with:
|
||||
php-version: ${{ matrix.php-versions }}
|
||||
# https://docs.nextcloud.com/server/stable/admin_manual/installation/source_installation.html#prerequisites-for-manual-installation
|
||||
@@ -110,7 +110,7 @@ jobs:
|
||||
|
||||
- name: Upload db code coverage
|
||||
if: ${{ !cancelled() && matrix.coverage }}
|
||||
uses: codecov/codecov-action@18283e04ce6e62d37312384ff67231eb8fd56d24 # v5.4.3
|
||||
uses: codecov/codecov-action@fdcc8476540edceab3de004e990f80d881c6cc00 # v5.5.0
|
||||
with:
|
||||
files: ./clover.db.xml
|
||||
flags: phpunit-sqlite
|
||||
|
||||
@@ -19,7 +19,7 @@ jobs:
|
||||
runs-on: ubuntu-latest-low
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
|
||||
uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
|
||||
with:
|
||||
persist-credentials: false
|
||||
|
||||
|
||||
@@ -28,13 +28,13 @@ jobs:
|
||||
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683
|
||||
uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8
|
||||
with:
|
||||
persist-credentials: false
|
||||
submodules: true
|
||||
|
||||
- name: Set up php
|
||||
uses: shivammathur/setup-php@ccf2c627fe61b1b4d924adfcbd19d661a18133a0 #v2.35.2
|
||||
uses: shivammathur/setup-php@ec406be512d7077f68eed36e63f4d91bc006edc4 #v2.35.4
|
||||
with:
|
||||
php-version: '8.1'
|
||||
extensions: apcu,ctype,curl,dom,fileinfo,ftp,gd,imagick,intl,json,ldap,mbstring,openssl,pdo_sqlite,posix,sqlite,xml,zip
|
||||
@@ -59,13 +59,13 @@ jobs:
|
||||
|
||||
steps:
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683
|
||||
uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8
|
||||
with:
|
||||
persist-credentials: false
|
||||
submodules: true
|
||||
|
||||
- name: Set up php
|
||||
uses: shivammathur/setup-php@ccf2c627fe61b1b4d924adfcbd19d661a18133a0 #v2.35.2
|
||||
uses: shivammathur/setup-php@ec406be512d7077f68eed36e63f4d91bc006edc4 #v2.35.4
|
||||
with:
|
||||
php-version: '8.1'
|
||||
extensions: ctype,curl,dom,fileinfo,ftp,gd,imagick,intl,json,ldap,mbstring,openssl,pdo_sqlite,posix,sqlite,xml,zip
|
||||
@@ -94,13 +94,13 @@ jobs:
|
||||
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683
|
||||
uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8
|
||||
with:
|
||||
persist-credentials: false
|
||||
submodules: true
|
||||
|
||||
- name: Set up php
|
||||
uses: shivammathur/setup-php@ccf2c627fe61b1b4d924adfcbd19d661a18133a0 #v2.35.2
|
||||
uses: shivammathur/setup-php@ec406be512d7077f68eed36e63f4d91bc006edc4 #v2.35.4
|
||||
with:
|
||||
php-version: '8.1'
|
||||
extensions: ctype,curl,dom,fileinfo,gd,imagick,intl,json,mbstring,openssl,pdo_sqlite,posix,sqlite,xml,zip
|
||||
@@ -125,13 +125,13 @@ jobs:
|
||||
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683
|
||||
uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8
|
||||
with:
|
||||
persist-credentials: false
|
||||
submodules: true
|
||||
|
||||
- name: Set up php
|
||||
uses: shivammathur/setup-php@ccf2c627fe61b1b4d924adfcbd19d661a18133a0 #v2.35.2
|
||||
uses: shivammathur/setup-php@ec406be512d7077f68eed36e63f4d91bc006edc4 #v2.35.4
|
||||
with:
|
||||
php-version: '8.1'
|
||||
extensions: ctype,curl,dom,fileinfo,gd,imagick,intl,json,mbstring,openssl,pdo_sqlite,posix,sqlite,xml,zip
|
||||
|
||||
@@ -22,7 +22,7 @@ jobs:
|
||||
name: update-ca-certificate-bundle-${{ matrix.branches }}
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
|
||||
- uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
|
||||
with:
|
||||
persist-credentials: false
|
||||
ref: ${{ matrix.branches }}
|
||||
|
||||
@@ -22,7 +22,7 @@ jobs:
|
||||
name: update-code-signing-crl-${{ matrix.branches }}
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
|
||||
- uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
|
||||
with:
|
||||
persist-credentials: false
|
||||
ref: ${{ matrix.branches }}
|
||||
|
||||
@@ -16,7 +16,7 @@ jobs:
|
||||
runs-on: ubuntu-latest-low
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
|
||||
- uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
|
||||
with:
|
||||
persist-credentials: false
|
||||
submodules: true
|
||||
|
||||
@@ -60,7 +60,6 @@
|
||||
</IfModule>
|
||||
|
||||
<IfModule mod_php.c>
|
||||
php_value mbstring.func_overload 0
|
||||
php_value default_charset 'UTF-8'
|
||||
php_value output_buffering 0
|
||||
<IfModule mod_env.c>
|
||||
|
||||
@@ -1,7 +1,9 @@
|
||||
; SPDX-FileCopyrightText: 2016-2024 Nextcloud GmbH and Nextcloud contributors
|
||||
; SPDX-FileCopyrightText: 2014-2016 ownCloud, Inc.
|
||||
; SPDX-License-Identifier: AGPL-3.0-only
|
||||
mbstring.func_overload=0
|
||||
;
|
||||
; NOTE: PHP caches this file for 300 seconds by default
|
||||
;
|
||||
always_populate_raw_post_data=-1
|
||||
default_charset='UTF-8'
|
||||
output_buffering=0
|
||||
|
||||
+3
-129
File diff suppressed because one or more lines are too long
@@ -14,4 +14,5 @@ export default {
|
||||
get: async () => ({ status: 200, data: {} }),
|
||||
delete: async () => ({ status: 200, data: {} }),
|
||||
post: async () => ({ status: 200, data: {} }),
|
||||
head: async () => ({ status: 200, data: {} }),
|
||||
}
|
||||
|
||||
@@ -2,7 +2,13 @@
|
||||
* SPDX-FileCopyrightText: 2024 Nextcloud GmbH and Nextcloud contributors
|
||||
* SPDX-License-Identifier: AGPL-3.0-or-later
|
||||
*/
|
||||
window.OC = { ...window.OC }
|
||||
window.OC = {
|
||||
...window.OC,
|
||||
config: {
|
||||
version: '32.0.0',
|
||||
...(window.OC?.config ?? {}),
|
||||
},
|
||||
}
|
||||
window.OCA = { ...window.OCA }
|
||||
window.OCP = { ...window.OCP }
|
||||
|
||||
|
||||
@@ -9,26 +9,14 @@ declare(strict_types=1);
|
||||
|
||||
namespace OCA\CloudFederationAPI;
|
||||
|
||||
use NCU\Security\Signature\Exceptions\IdentityNotFoundException;
|
||||
use NCU\Security\Signature\Exceptions\SignatoryException;
|
||||
use OC\OCM\OCMSignatoryManager;
|
||||
use OC\OCM\OCMDiscoveryService;
|
||||
use OCP\Capabilities\ICapability;
|
||||
use OCP\Capabilities\IInitialStateExcludedCapability;
|
||||
use OCP\IAppConfig;
|
||||
use OCP\IURLGenerator;
|
||||
use OCP\OCM\Exceptions\OCMArgumentException;
|
||||
use OCP\OCM\ICapabilityAwareOCMProvider;
|
||||
use Psr\Log\LoggerInterface;
|
||||
|
||||
class Capabilities implements ICapability, IInitialStateExcludedCapability {
|
||||
public const API_VERSION = '1.1.0';
|
||||
|
||||
public function __construct(
|
||||
private IURLGenerator $urlGenerator,
|
||||
private IAppConfig $appConfig,
|
||||
private ICapabilityAwareOCMProvider $provider,
|
||||
private readonly OCMSignatoryManager $ocmSignatoryManager,
|
||||
private readonly LoggerInterface $logger,
|
||||
private readonly OCMDiscoveryService $ocmDiscoveryService,
|
||||
) {
|
||||
}
|
||||
|
||||
@@ -39,40 +27,7 @@ class Capabilities implements ICapability, IInitialStateExcludedCapability {
|
||||
* @throws OCMArgumentException
|
||||
*/
|
||||
public function getCapabilities() {
|
||||
$url = $this->urlGenerator->linkToRouteAbsolute('cloud_federation_api.requesthandlercontroller.addShare');
|
||||
$pos = strrpos($url, '/');
|
||||
if ($pos === false) {
|
||||
throw new OCMArgumentException('generated route should contain a slash character');
|
||||
}
|
||||
|
||||
$this->provider->setEnabled(true);
|
||||
$this->provider->setApiVersion(self::API_VERSION);
|
||||
$this->provider->setCapabilities(['/invite-accepted', '/notifications', '/shares']);
|
||||
|
||||
$this->provider->setEndPoint(substr($url, 0, $pos));
|
||||
|
||||
$resource = $this->provider->createNewResourceType();
|
||||
$resource->setName('file')
|
||||
->setShareTypes(['user', 'group'])
|
||||
->setProtocols(['webdav' => '/public.php/webdav/']);
|
||||
|
||||
$this->provider->addResourceType($resource);
|
||||
|
||||
// Adding a public key to the ocm discovery
|
||||
try {
|
||||
if (!$this->appConfig->getValueBool('core', OCMSignatoryManager::APPCONFIG_SIGN_DISABLED, lazy: true)) {
|
||||
/**
|
||||
* @experimental 31.0.0
|
||||
* @psalm-suppress UndefinedInterfaceMethod
|
||||
*/
|
||||
$this->provider->setSignatory($this->ocmSignatoryManager->getLocalSignatory());
|
||||
} else {
|
||||
$this->logger->debug('ocm public key feature disabled');
|
||||
}
|
||||
} catch (SignatoryException|IdentityNotFoundException $e) {
|
||||
$this->logger->warning('cannot generate local signatory', ['exception' => $e]);
|
||||
}
|
||||
|
||||
return ['ocm' => $this->provider->jsonSerialize()];
|
||||
$provider = $this->ocmDiscoveryService->getLocalOCMProvider(false);
|
||||
return ['ocm' => $provider->jsonSerialize()];
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,36 @@
|
||||
OC.L10N.register(
|
||||
"comments",
|
||||
{
|
||||
"Comments" : "Каментарыі",
|
||||
"You commented" : "Вы пракаментавалі",
|
||||
"{author} commented" : "{author} пракаментаваў(-ла)",
|
||||
"You commented on %1$s" : "Вы пракаментавалі %1$s",
|
||||
"You commented on {file}" : "Вы пракаментавалі {file}",
|
||||
"%1$s commented on %2$s" : "%1$s пракаментаваў(-ла) %2$s",
|
||||
"{author} commented on {file}" : "{author} пракаментаваў(-ла) {file}",
|
||||
"<strong>Comments</strong> for files" : "<strong>Каментарыі</strong> да файлаў",
|
||||
"Files" : "Файлы",
|
||||
"You were mentioned on \"{file}\", in a comment by an account that has since been deleted" : "Вас згадалі ў каментарыі да \"{file}\" з уліковага запісу, які пазней быў выдалены.",
|
||||
"{user} mentioned you in a comment on \"{file}\"" : "{user} згадаў(-ла) вас у каментарыі да \"{file}\"",
|
||||
"Files app plugin to add comments to files" : "Убудова праграмы Файлы для дадавання каментарыяў да файлаў",
|
||||
"Edit comment" : "Рэдагаваць каментарый",
|
||||
"Delete comment" : "Выдаліць каментарый",
|
||||
"Cancel edit" : "Скасаваць рэдагаванне",
|
||||
"New comment" : "Новы каментарый",
|
||||
"Write a comment …" : "Напішыце каментарый …",
|
||||
"Post comment" : "Апублікаваць каментарый",
|
||||
"@ for mentions, : for emoji, / for smart picker" : "@ - згадкі, : - эмодзі, / - разумны выбар",
|
||||
"Could not reload comments" : "Не ўдалося перазагрузіць каментарыі",
|
||||
"Failed to mark comments as read" : "Не атрымалася пазначыць каментарыі як прачытаныя",
|
||||
"Unable to load the comments list" : "Не ўдалося загрузіць спіс каментарыяў",
|
||||
"No comments yet, start the conversation!" : "Пакуль няма каментарыяў, пачніце размову!",
|
||||
"No more messages" : "Больш паведамленняў няма",
|
||||
"Retry" : "Паўтарыць спробу",
|
||||
"_1 new comment_::_{unread} new comments_" : ["1 новы каментарый","{unread} новыя каментарыі","{unread} новых каментарыяў","{unread} новых каментарыяў"],
|
||||
"Comment" : "Каментарый",
|
||||
"An error occurred while trying to edit the comment" : "Падчас спробы рэдагавання каментарыя ўзнікла памылка",
|
||||
"Comment deleted" : "Каментарый выдалены",
|
||||
"An error occurred while trying to delete the comment" : "Падчас спробы выдалення каментарыя ўзнікла памылка",
|
||||
"An error occurred while trying to create the comment" : "Падчас спробы стварэння каментарыя ўзнікла памылка"
|
||||
},
|
||||
"nplurals=4; plural=(n%10==1 && n%100!=11 ? 0 : n%10>=2 && n%10<=4 && (n%100<12 || n%100>14) ? 1 : n%10==0 || (n%10>=5 && n%10<=9) || (n%100>=11 && n%100<=14)? 2 : 3);");
|
||||
@@ -0,0 +1,34 @@
|
||||
{ "translations": {
|
||||
"Comments" : "Каментарыі",
|
||||
"You commented" : "Вы пракаментавалі",
|
||||
"{author} commented" : "{author} пракаментаваў(-ла)",
|
||||
"You commented on %1$s" : "Вы пракаментавалі %1$s",
|
||||
"You commented on {file}" : "Вы пракаментавалі {file}",
|
||||
"%1$s commented on %2$s" : "%1$s пракаментаваў(-ла) %2$s",
|
||||
"{author} commented on {file}" : "{author} пракаментаваў(-ла) {file}",
|
||||
"<strong>Comments</strong> for files" : "<strong>Каментарыі</strong> да файлаў",
|
||||
"Files" : "Файлы",
|
||||
"You were mentioned on \"{file}\", in a comment by an account that has since been deleted" : "Вас згадалі ў каментарыі да \"{file}\" з уліковага запісу, які пазней быў выдалены.",
|
||||
"{user} mentioned you in a comment on \"{file}\"" : "{user} згадаў(-ла) вас у каментарыі да \"{file}\"",
|
||||
"Files app plugin to add comments to files" : "Убудова праграмы Файлы для дадавання каментарыяў да файлаў",
|
||||
"Edit comment" : "Рэдагаваць каментарый",
|
||||
"Delete comment" : "Выдаліць каментарый",
|
||||
"Cancel edit" : "Скасаваць рэдагаванне",
|
||||
"New comment" : "Новы каментарый",
|
||||
"Write a comment …" : "Напішыце каментарый …",
|
||||
"Post comment" : "Апублікаваць каментарый",
|
||||
"@ for mentions, : for emoji, / for smart picker" : "@ - згадкі, : - эмодзі, / - разумны выбар",
|
||||
"Could not reload comments" : "Не ўдалося перазагрузіць каментарыі",
|
||||
"Failed to mark comments as read" : "Не атрымалася пазначыць каментарыі як прачытаныя",
|
||||
"Unable to load the comments list" : "Не ўдалося загрузіць спіс каментарыяў",
|
||||
"No comments yet, start the conversation!" : "Пакуль няма каментарыяў, пачніце размову!",
|
||||
"No more messages" : "Больш паведамленняў няма",
|
||||
"Retry" : "Паўтарыць спробу",
|
||||
"_1 new comment_::_{unread} new comments_" : ["1 новы каментарый","{unread} новыя каментарыі","{unread} новых каментарыяў","{unread} новых каментарыяў"],
|
||||
"Comment" : "Каментарый",
|
||||
"An error occurred while trying to edit the comment" : "Падчас спробы рэдагавання каментарыя ўзнікла памылка",
|
||||
"Comment deleted" : "Каментарый выдалены",
|
||||
"An error occurred while trying to delete the comment" : "Падчас спробы выдалення каментарыя ўзнікла памылка",
|
||||
"An error occurred while trying to create the comment" : "Падчас спробы стварэння каментарыя ўзнікла памылка"
|
||||
},"pluralForm" :"nplurals=4; plural=(n%10==1 && n%100!=11 ? 0 : n%10>=2 && n%10<=4 && (n%100<12 || n%100>14) ? 1 : n%10==0 || (n%10>=5 && n%10<=9) || (n%100>=11 && n%100<=14)? 2 : 3);"
|
||||
}
|
||||
@@ -16,5 +16,6 @@ return array(
|
||||
'OCA\\ContactsInteraction\\Db\\RecentContact' => $baseDir . '/../lib/Db/RecentContact.php',
|
||||
'OCA\\ContactsInteraction\\Db\\RecentContactMapper' => $baseDir . '/../lib/Db/RecentContactMapper.php',
|
||||
'OCA\\ContactsInteraction\\Listeners\\ContactInteractionListener' => $baseDir . '/../lib/Listeners/ContactInteractionListener.php',
|
||||
'OCA\\ContactsInteraction\\Listeners\\UserDeletedListener' => $baseDir . '/../lib/Listeners/UserDeletedListener.php',
|
||||
'OCA\\ContactsInteraction\\Migration\\Version010000Date20200304152605' => $baseDir . '/../lib/Migration/Version010000Date20200304152605.php',
|
||||
);
|
||||
|
||||
@@ -31,6 +31,7 @@ class ComposerStaticInitContactsInteraction
|
||||
'OCA\\ContactsInteraction\\Db\\RecentContact' => __DIR__ . '/..' . '/../lib/Db/RecentContact.php',
|
||||
'OCA\\ContactsInteraction\\Db\\RecentContactMapper' => __DIR__ . '/..' . '/../lib/Db/RecentContactMapper.php',
|
||||
'OCA\\ContactsInteraction\\Listeners\\ContactInteractionListener' => __DIR__ . '/..' . '/../lib/Listeners/ContactInteractionListener.php',
|
||||
'OCA\\ContactsInteraction\\Listeners\\UserDeletedListener' => __DIR__ . '/..' . '/../lib/Listeners/UserDeletedListener.php',
|
||||
'OCA\\ContactsInteraction\\Migration\\Version010000Date20200304152605' => __DIR__ . '/..' . '/../lib/Migration/Version010000Date20200304152605.php',
|
||||
);
|
||||
|
||||
|
||||
@@ -9,11 +9,13 @@ declare(strict_types=1);
|
||||
namespace OCA\ContactsInteraction\AppInfo;
|
||||
|
||||
use OCA\ContactsInteraction\Listeners\ContactInteractionListener;
|
||||
use OCA\ContactsInteraction\Listeners\UserDeletedListener;
|
||||
use OCP\AppFramework\App;
|
||||
use OCP\AppFramework\Bootstrap\IBootContext;
|
||||
use OCP\AppFramework\Bootstrap\IBootstrap;
|
||||
use OCP\AppFramework\Bootstrap\IRegistrationContext;
|
||||
use OCP\Contacts\Events\ContactInteractedWithEvent;
|
||||
use OCP\User\Events\UserDeletedEvent;
|
||||
|
||||
class Application extends App implements IBootstrap {
|
||||
public const APP_ID = 'contactsinteraction';
|
||||
@@ -24,6 +26,7 @@ class Application extends App implements IBootstrap {
|
||||
|
||||
public function register(IRegistrationContext $context): void {
|
||||
$context->registerEventListener(ContactInteractedWithEvent::class, ContactInteractionListener::class);
|
||||
$context->registerEventListener(UserDeletedEvent::class, UserDeletedListener::class);
|
||||
}
|
||||
|
||||
public function boot(IBootContext $context): void {
|
||||
|
||||
@@ -112,4 +112,14 @@ class RecentContactMapper extends QBMapper {
|
||||
|
||||
$delete->executeStatement();
|
||||
}
|
||||
|
||||
public function deleteByUserId(string $uid): void {
|
||||
$qb = $this->db->getQueryBuilder();
|
||||
|
||||
$delete = $qb
|
||||
->delete($this->getTableName())
|
||||
->where($qb->expr()->eq('actor_uid', $qb->createNamedParameter($uid)));
|
||||
|
||||
$delete->executeStatement();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,35 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
/**
|
||||
* SPDX-FileCopyrightText: 2025 Nextcloud GmbH and Nextcloud contributors
|
||||
* SPDX-License-Identifier: AGPL-3.0-or-later
|
||||
*/
|
||||
|
||||
namespace OCA\ContactsInteraction\Listeners;
|
||||
|
||||
use OCA\ContactsInteraction\Db\RecentContactMapper;
|
||||
use OCP\EventDispatcher\Event;
|
||||
use OCP\EventDispatcher\IEventListener;
|
||||
use OCP\User\Events\UserDeletedEvent;
|
||||
|
||||
/**
|
||||
* @template-implements IEventListener<Event|UserDeletedEvent>
|
||||
*/
|
||||
class UserDeletedListener implements IEventListener {
|
||||
|
||||
public function __construct(
|
||||
private readonly RecentContactMapper $recentContactMapper,
|
||||
) {
|
||||
}
|
||||
|
||||
#[\Override]
|
||||
public function handle(Event $event): void {
|
||||
if (!($event instanceof UserDeletedEvent)) {
|
||||
return;
|
||||
}
|
||||
|
||||
$this->recentContactMapper->deleteByUserId($event->getUser()->getUID());
|
||||
}
|
||||
}
|
||||
@@ -267,6 +267,34 @@
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"401": {
|
||||
"description": "Current user is not logged in",
|
||||
"content": {
|
||||
"application/json": {
|
||||
"schema": {
|
||||
"type": "object",
|
||||
"required": [
|
||||
"ocs"
|
||||
],
|
||||
"properties": {
|
||||
"ocs": {
|
||||
"type": "object",
|
||||
"required": [
|
||||
"meta",
|
||||
"data"
|
||||
],
|
||||
"properties": {
|
||||
"meta": {
|
||||
"$ref": "#/components/schemas/OCSMeta"
|
||||
},
|
||||
"data": {}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -363,6 +391,34 @@
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"401": {
|
||||
"description": "Current user is not logged in",
|
||||
"content": {
|
||||
"application/json": {
|
||||
"schema": {
|
||||
"type": "object",
|
||||
"required": [
|
||||
"ocs"
|
||||
],
|
||||
"properties": {
|
||||
"ocs": {
|
||||
"type": "object",
|
||||
"required": [
|
||||
"meta",
|
||||
"data"
|
||||
],
|
||||
"properties": {
|
||||
"meta": {
|
||||
"$ref": "#/components/schemas/OCSMeta"
|
||||
},
|
||||
"data": {}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -427,6 +483,34 @@
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"401": {
|
||||
"description": "Current user is not logged in",
|
||||
"content": {
|
||||
"application/json": {
|
||||
"schema": {
|
||||
"type": "object",
|
||||
"required": [
|
||||
"ocs"
|
||||
],
|
||||
"properties": {
|
||||
"ocs": {
|
||||
"type": "object",
|
||||
"required": [
|
||||
"meta",
|
||||
"data"
|
||||
],
|
||||
"properties": {
|
||||
"meta": {
|
||||
"$ref": "#/components/schemas/OCSMeta"
|
||||
},
|
||||
"data": {}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -499,6 +583,34 @@
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"401": {
|
||||
"description": "Current user is not logged in",
|
||||
"content": {
|
||||
"application/json": {
|
||||
"schema": {
|
||||
"type": "object",
|
||||
"required": [
|
||||
"ocs"
|
||||
],
|
||||
"properties": {
|
||||
"ocs": {
|
||||
"type": "object",
|
||||
"required": [
|
||||
"meta",
|
||||
"data"
|
||||
],
|
||||
"properties": {
|
||||
"meta": {
|
||||
"$ref": "#/components/schemas/OCSMeta"
|
||||
},
|
||||
"data": {}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
@@ -591,6 +703,34 @@
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"401": {
|
||||
"description": "Current user is not logged in",
|
||||
"content": {
|
||||
"application/json": {
|
||||
"schema": {
|
||||
"type": "object",
|
||||
"required": [
|
||||
"ocs"
|
||||
],
|
||||
"properties": {
|
||||
"ocs": {
|
||||
"type": "object",
|
||||
"required": [
|
||||
"meta",
|
||||
"data"
|
||||
],
|
||||
"properties": {
|
||||
"meta": {
|
||||
"$ref": "#/components/schemas/OCSMeta"
|
||||
},
|
||||
"data": {}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -663,6 +803,34 @@
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"401": {
|
||||
"description": "Current user is not logged in",
|
||||
"content": {
|
||||
"application/json": {
|
||||
"schema": {
|
||||
"type": "object",
|
||||
"required": [
|
||||
"ocs"
|
||||
],
|
||||
"properties": {
|
||||
"ocs": {
|
||||
"type": "object",
|
||||
"required": [
|
||||
"meta",
|
||||
"data"
|
||||
],
|
||||
"properties": {
|
||||
"meta": {
|
||||
"$ref": "#/components/schemas/OCSMeta"
|
||||
},
|
||||
"data": {}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
@@ -755,6 +923,34 @@
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"401": {
|
||||
"description": "Current user is not logged in",
|
||||
"content": {
|
||||
"application/json": {
|
||||
"schema": {
|
||||
"type": "object",
|
||||
"required": [
|
||||
"ocs"
|
||||
],
|
||||
"properties": {
|
||||
"ocs": {
|
||||
"type": "object",
|
||||
"required": [
|
||||
"meta",
|
||||
"data"
|
||||
],
|
||||
"properties": {
|
||||
"meta": {
|
||||
"$ref": "#/components/schemas/OCSMeta"
|
||||
},
|
||||
"data": {}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -10,7 +10,7 @@
|
||||
<name>WebDAV</name>
|
||||
<summary>WebDAV endpoint</summary>
|
||||
<description>WebDAV endpoint</description>
|
||||
<version>1.34.1</version>
|
||||
<version>1.34.2</version>
|
||||
<licence>agpl</licence>
|
||||
<author>owncloud.org</author>
|
||||
<namespace>DAV</namespace>
|
||||
@@ -30,6 +30,7 @@
|
||||
<job>OCA\DAV\BackgroundJob\EventReminderJob</job>
|
||||
<job>OCA\DAV\BackgroundJob\CalendarRetentionJob</job>
|
||||
<job>OCA\DAV\BackgroundJob\PruneOutdatedSyncTokensJob</job>
|
||||
<job>OCA\DAV\BackgroundJob\FederatedCalendarPeriodicSyncJob</job>
|
||||
</background-jobs>
|
||||
|
||||
<repair-steps>
|
||||
@@ -66,6 +67,7 @@
|
||||
<command>OCA\DAV\Command\ExportCalendar</command>
|
||||
<command>OCA\DAV\Command\FixCalendarSyncCommand</command>
|
||||
<command>OCA\DAV\Command\GetAbsenceCommand</command>
|
||||
<command>OCA\DAV\Command\ImportCalendar</command>
|
||||
<command>OCA\DAV\Command\ListAddressbooks</command>
|
||||
<command>OCA\DAV\Command\ListCalendarShares</command>
|
||||
<command>OCA\DAV\Command\ListCalendars</command>
|
||||
|
||||
@@ -10,6 +10,8 @@ use OC\KnownUser\KnownUserService;
|
||||
use OCA\DAV\CalDAV\CalDavBackend;
|
||||
use OCA\DAV\CalDAV\CalendarRoot;
|
||||
use OCA\DAV\CalDAV\DefaultCalendarValidator;
|
||||
use OCA\DAV\CalDAV\Federation\FederatedCalendarFactory;
|
||||
use OCA\DAV\CalDAV\Federation\FederatedCalendarMapper;
|
||||
use OCA\DAV\CalDAV\Proxy\ProxyMapper;
|
||||
use OCA\DAV\CalDAV\Schedule\IMipPlugin;
|
||||
use OCA\DAV\CalDAV\Security\RateLimitingPlugin;
|
||||
@@ -29,6 +31,7 @@ use OCP\IRequest;
|
||||
use OCP\ISession;
|
||||
use OCP\IUserManager;
|
||||
use OCP\IUserSession;
|
||||
use OCP\L10N\IFactory as IL10NFactory;
|
||||
use OCP\Security\Bruteforce\IThrottler;
|
||||
use OCP\Security\ISecureRandom;
|
||||
use OCP\Server;
|
||||
@@ -61,6 +64,9 @@ $random = Server::get(ISecureRandom::class);
|
||||
$logger = Server::get(LoggerInterface::class);
|
||||
$dispatcher = Server::get(IEventDispatcher::class);
|
||||
$config = Server::get(IConfig::class);
|
||||
$l10nFactory = Server::get(IL10NFactory::class);
|
||||
$davL10n = $l10nFactory->get('dav');
|
||||
$federatedCalendarFactory = Server::get(FederatedCalendarFactory::class);
|
||||
|
||||
$calDavBackend = new CalDavBackend(
|
||||
$db,
|
||||
@@ -71,6 +77,8 @@ $calDavBackend = new CalDavBackend(
|
||||
$dispatcher,
|
||||
$config,
|
||||
Server::get(\OCA\DAV\CalDAV\Sharing\Backend::class),
|
||||
Server::get(FederatedCalendarMapper::class),
|
||||
Server::get(\OCP\ICacheFactory::class),
|
||||
true
|
||||
);
|
||||
|
||||
@@ -81,7 +89,7 @@ $sendInvitations = Server::get(IConfig::class)->getAppValue('dav', 'sendInvitati
|
||||
$principalCollection = new \Sabre\CalDAV\Principal\Collection($principalBackend);
|
||||
$principalCollection->disableListing = !$debugging; // Disable listing
|
||||
|
||||
$addressBookRoot = new CalendarRoot($principalBackend, $calDavBackend, 'principals', $logger);
|
||||
$addressBookRoot = new CalendarRoot($principalBackend, $calDavBackend, 'principals', $logger, $davL10n, $config, $federatedCalendarFactory);
|
||||
$addressBookRoot->disableListing = !$debugging; // Disable listing
|
||||
|
||||
$nodes = [
|
||||
@@ -96,7 +104,7 @@ $server->httpRequest->setUrl(Server::get(IRequest::class)->getRequestUri());
|
||||
$server->setBaseUri($baseuri);
|
||||
|
||||
// Add plugins
|
||||
$server->addPlugin(new MaintenancePlugin(Server::get(IConfig::class), \OC::$server->getL10N('dav')));
|
||||
$server->addPlugin(new MaintenancePlugin(Server::get(IConfig::class), $davL10n));
|
||||
$server->addPlugin(new \Sabre\DAV\Auth\Plugin($authBackend));
|
||||
$server->addPlugin(new \Sabre\CalDAV\Plugin());
|
||||
|
||||
|
||||
@@ -19,6 +19,8 @@ return array(
|
||||
'OCA\\DAV\\BackgroundJob\\CleanupOrphanedChildrenJob' => $baseDir . '/../lib/BackgroundJob/CleanupOrphanedChildrenJob.php',
|
||||
'OCA\\DAV\\BackgroundJob\\DeleteOutdatedSchedulingObjects' => $baseDir . '/../lib/BackgroundJob/DeleteOutdatedSchedulingObjects.php',
|
||||
'OCA\\DAV\\BackgroundJob\\EventReminderJob' => $baseDir . '/../lib/BackgroundJob/EventReminderJob.php',
|
||||
'OCA\\DAV\\BackgroundJob\\FederatedCalendarPeriodicSyncJob' => $baseDir . '/../lib/BackgroundJob/FederatedCalendarPeriodicSyncJob.php',
|
||||
'OCA\\DAV\\BackgroundJob\\FederatedCalendarSyncJob' => $baseDir . '/../lib/BackgroundJob/FederatedCalendarSyncJob.php',
|
||||
'OCA\\DAV\\BackgroundJob\\GenerateBirthdayCalendarBackgroundJob' => $baseDir . '/../lib/BackgroundJob/GenerateBirthdayCalendarBackgroundJob.php',
|
||||
'OCA\\DAV\\BackgroundJob\\OutOfOfficeEventDispatcherJob' => $baseDir . '/../lib/BackgroundJob/OutOfOfficeEventDispatcherJob.php',
|
||||
'OCA\\DAV\\BackgroundJob\\PruneOutdatedSyncTokensJob' => $baseDir . '/../lib/BackgroundJob/PruneOutdatedSyncTokensJob.php',
|
||||
@@ -66,9 +68,27 @@ return array(
|
||||
'OCA\\DAV\\CalDAV\\EventReaderRDate' => $baseDir . '/../lib/CalDAV/EventReaderRDate.php',
|
||||
'OCA\\DAV\\CalDAV\\EventReaderRRule' => $baseDir . '/../lib/CalDAV/EventReaderRRule.php',
|
||||
'OCA\\DAV\\CalDAV\\Export\\ExportService' => $baseDir . '/../lib/CalDAV/Export/ExportService.php',
|
||||
'OCA\\DAV\\CalDAV\\Federation\\CalendarFederationConfig' => $baseDir . '/../lib/CalDAV/Federation/CalendarFederationConfig.php',
|
||||
'OCA\\DAV\\CalDAV\\Federation\\CalendarFederationNotifier' => $baseDir . '/../lib/CalDAV/Federation/CalendarFederationNotifier.php',
|
||||
'OCA\\DAV\\CalDAV\\Federation\\CalendarFederationProvider' => $baseDir . '/../lib/CalDAV/Federation/CalendarFederationProvider.php',
|
||||
'OCA\\DAV\\CalDAV\\Federation\\FederatedCalendar' => $baseDir . '/../lib/CalDAV/Federation/FederatedCalendar.php',
|
||||
'OCA\\DAV\\CalDAV\\Federation\\FederatedCalendarAuth' => $baseDir . '/../lib/CalDAV/Federation/FederatedCalendarAuth.php',
|
||||
'OCA\\DAV\\CalDAV\\Federation\\FederatedCalendarEntity' => $baseDir . '/../lib/CalDAV/Federation/FederatedCalendarEntity.php',
|
||||
'OCA\\DAV\\CalDAV\\Federation\\FederatedCalendarFactory' => $baseDir . '/../lib/CalDAV/Federation/FederatedCalendarFactory.php',
|
||||
'OCA\\DAV\\CalDAV\\Federation\\FederatedCalendarImpl' => $baseDir . '/../lib/CalDAV/Federation/FederatedCalendarImpl.php',
|
||||
'OCA\\DAV\\CalDAV\\Federation\\FederatedCalendarMapper' => $baseDir . '/../lib/CalDAV/Federation/FederatedCalendarMapper.php',
|
||||
'OCA\\DAV\\CalDAV\\Federation\\FederatedCalendarSyncService' => $baseDir . '/../lib/CalDAV/Federation/FederatedCalendarSyncService.php',
|
||||
'OCA\\DAV\\CalDAV\\Federation\\FederationSharingService' => $baseDir . '/../lib/CalDAV/Federation/FederationSharingService.php',
|
||||
'OCA\\DAV\\CalDAV\\Federation\\Protocol\\CalendarFederationProtocolV1' => $baseDir . '/../lib/CalDAV/Federation/Protocol/CalendarFederationProtocolV1.php',
|
||||
'OCA\\DAV\\CalDAV\\Federation\\Protocol\\CalendarProtocolParseException' => $baseDir . '/../lib/CalDAV/Federation/Protocol/CalendarProtocolParseException.php',
|
||||
'OCA\\DAV\\CalDAV\\Federation\\Protocol\\ICalendarFederationProtocol' => $baseDir . '/../lib/CalDAV/Federation/Protocol/ICalendarFederationProtocol.php',
|
||||
'OCA\\DAV\\CalDAV\\Federation\\RemoteUserCalendarHome' => $baseDir . '/../lib/CalDAV/Federation/RemoteUserCalendarHome.php',
|
||||
'OCA\\DAV\\CalDAV\\FreeBusy\\FreeBusyGenerator' => $baseDir . '/../lib/CalDAV/FreeBusy/FreeBusyGenerator.php',
|
||||
'OCA\\DAV\\CalDAV\\ICSExportPlugin\\ICSExportPlugin' => $baseDir . '/../lib/CalDAV/ICSExportPlugin/ICSExportPlugin.php',
|
||||
'OCA\\DAV\\CalDAV\\IRestorable' => $baseDir . '/../lib/CalDAV/IRestorable.php',
|
||||
'OCA\\DAV\\CalDAV\\Import\\ImportService' => $baseDir . '/../lib/CalDAV/Import/ImportService.php',
|
||||
'OCA\\DAV\\CalDAV\\Import\\TextImporter' => $baseDir . '/../lib/CalDAV/Import/TextImporter.php',
|
||||
'OCA\\DAV\\CalDAV\\Import\\XmlImporter' => $baseDir . '/../lib/CalDAV/Import/XmlImporter.php',
|
||||
'OCA\\DAV\\CalDAV\\Integration\\ExternalCalendar' => $baseDir . '/../lib/CalDAV/Integration/ExternalCalendar.php',
|
||||
'OCA\\DAV\\CalDAV\\Integration\\ICalendarProvider' => $baseDir . '/../lib/CalDAV/Integration/ICalendarProvider.php',
|
||||
'OCA\\DAV\\CalDAV\\InvitationResponse\\InvitationResponseServer' => $baseDir . '/../lib/CalDAV/InvitationResponse/InvitationResponseServer.php',
|
||||
@@ -113,6 +133,8 @@ return array(
|
||||
'OCA\\DAV\\CalDAV\\Sharing\\Backend' => $baseDir . '/../lib/CalDAV/Sharing/Backend.php',
|
||||
'OCA\\DAV\\CalDAV\\Sharing\\Service' => $baseDir . '/../lib/CalDAV/Sharing/Service.php',
|
||||
'OCA\\DAV\\CalDAV\\Status\\StatusService' => $baseDir . '/../lib/CalDAV/Status/StatusService.php',
|
||||
'OCA\\DAV\\CalDAV\\SyncService' => $baseDir . '/../lib/CalDAV/SyncService.php',
|
||||
'OCA\\DAV\\CalDAV\\SyncServiceResult' => $baseDir . '/../lib/CalDAV/SyncServiceResult.php',
|
||||
'OCA\\DAV\\CalDAV\\TimeZoneFactory' => $baseDir . '/../lib/CalDAV/TimeZoneFactory.php',
|
||||
'OCA\\DAV\\CalDAV\\TimezoneService' => $baseDir . '/../lib/CalDAV/TimezoneService.php',
|
||||
'OCA\\DAV\\CalDAV\\TipBroker' => $baseDir . '/../lib/CalDAV/TipBroker.php',
|
||||
@@ -166,6 +188,7 @@ return array(
|
||||
'OCA\\DAV\\Command\\ExportCalendar' => $baseDir . '/../lib/Command/ExportCalendar.php',
|
||||
'OCA\\DAV\\Command\\FixCalendarSyncCommand' => $baseDir . '/../lib/Command/FixCalendarSyncCommand.php',
|
||||
'OCA\\DAV\\Command\\GetAbsenceCommand' => $baseDir . '/../lib/Command/GetAbsenceCommand.php',
|
||||
'OCA\\DAV\\Command\\ImportCalendar' => $baseDir . '/../lib/Command/ImportCalendar.php',
|
||||
'OCA\\DAV\\Command\\ListAddressbooks' => $baseDir . '/../lib/Command/ListAddressbooks.php',
|
||||
'OCA\\DAV\\Command\\ListCalendarShares' => $baseDir . '/../lib/Command/ListCalendarShares.php',
|
||||
'OCA\\DAV\\Command\\ListCalendars' => $baseDir . '/../lib/Command/ListCalendars.php',
|
||||
@@ -239,6 +262,7 @@ return array(
|
||||
'OCA\\DAV\\DAV\\CustomPropertiesBackend' => $baseDir . '/../lib/DAV/CustomPropertiesBackend.php',
|
||||
'OCA\\DAV\\DAV\\GroupPrincipalBackend' => $baseDir . '/../lib/DAV/GroupPrincipalBackend.php',
|
||||
'OCA\\DAV\\DAV\\PublicAuth' => $baseDir . '/../lib/DAV/PublicAuth.php',
|
||||
'OCA\\DAV\\DAV\\RemoteUserPrincipalBackend' => $baseDir . '/../lib/DAV/RemoteUserPrincipalBackend.php',
|
||||
'OCA\\DAV\\DAV\\Sharing\\Backend' => $baseDir . '/../lib/DAV/Sharing/Backend.php',
|
||||
'OCA\\DAV\\DAV\\Sharing\\IShareable' => $baseDir . '/../lib/DAV/Sharing/IShareable.php',
|
||||
'OCA\\DAV\\DAV\\Sharing\\Plugin' => $baseDir . '/../lib/DAV/Sharing/Plugin.php',
|
||||
@@ -300,6 +324,7 @@ return array(
|
||||
'OCA\\DAV\\Listener\\BirthdayListener' => $baseDir . '/../lib/Listener/BirthdayListener.php',
|
||||
'OCA\\DAV\\Listener\\CalendarContactInteractionListener' => $baseDir . '/../lib/Listener/CalendarContactInteractionListener.php',
|
||||
'OCA\\DAV\\Listener\\CalendarDeletionDefaultUpdaterListener' => $baseDir . '/../lib/Listener/CalendarDeletionDefaultUpdaterListener.php',
|
||||
'OCA\\DAV\\Listener\\CalendarFederationNotificationListener' => $baseDir . '/../lib/Listener/CalendarFederationNotificationListener.php',
|
||||
'OCA\\DAV\\Listener\\CalendarObjectReminderUpdaterListener' => $baseDir . '/../lib/Listener/CalendarObjectReminderUpdaterListener.php',
|
||||
'OCA\\DAV\\Listener\\CalendarPublicationListener' => $baseDir . '/../lib/Listener/CalendarPublicationListener.php',
|
||||
'OCA\\DAV\\Listener\\CalendarShareUpdateListener' => $baseDir . '/../lib/Listener/CalendarShareUpdateListener.php',
|
||||
@@ -307,6 +332,7 @@ return array(
|
||||
'OCA\\DAV\\Listener\\ClearPhotoCacheListener' => $baseDir . '/../lib/Listener/ClearPhotoCacheListener.php',
|
||||
'OCA\\DAV\\Listener\\DavAdminSettingsListener' => $baseDir . '/../lib/Listener/DavAdminSettingsListener.php',
|
||||
'OCA\\DAV\\Listener\\OutOfOfficeListener' => $baseDir . '/../lib/Listener/OutOfOfficeListener.php',
|
||||
'OCA\\DAV\\Listener\\SabrePluginAuthInitListener' => $baseDir . '/../lib/Listener/SabrePluginAuthInitListener.php',
|
||||
'OCA\\DAV\\Listener\\SubscriptionListener' => $baseDir . '/../lib/Listener/SubscriptionListener.php',
|
||||
'OCA\\DAV\\Listener\\TrustedServerRemovedListener' => $baseDir . '/../lib/Listener/TrustedServerRemovedListener.php',
|
||||
'OCA\\DAV\\Listener\\UserEventsListener' => $baseDir . '/../lib/Listener/UserEventsListener.php',
|
||||
@@ -355,6 +381,7 @@ return array(
|
||||
'OCA\\DAV\\Migration\\Version1029Date20231004091403' => $baseDir . '/../lib/Migration/Version1029Date20231004091403.php',
|
||||
'OCA\\DAV\\Migration\\Version1030Date20240205103243' => $baseDir . '/../lib/Migration/Version1030Date20240205103243.php',
|
||||
'OCA\\DAV\\Migration\\Version1031Date20240610134258' => $baseDir . '/../lib/Migration/Version1031Date20240610134258.php',
|
||||
'OCA\\DAV\\Migration\\Version1034Date20250605132605' => $baseDir . '/../lib/Migration/Version1034Date20250605132605.php',
|
||||
'OCA\\DAV\\Migration\\Version1034Date20250813093701' => $baseDir . '/../lib/Migration/Version1034Date20250813093701.php',
|
||||
'OCA\\DAV\\Model\\ExampleEvent' => $baseDir . '/../lib/Model/ExampleEvent.php',
|
||||
'OCA\\DAV\\Paginate\\LimitedCopyIterator' => $baseDir . '/../lib/Paginate/LimitedCopyIterator.php',
|
||||
@@ -371,6 +398,7 @@ return array(
|
||||
'OCA\\DAV\\Search\\TasksSearchProvider' => $baseDir . '/../lib/Search/TasksSearchProvider.php',
|
||||
'OCA\\DAV\\Server' => $baseDir . '/../lib/Server.php',
|
||||
'OCA\\DAV\\ServerFactory' => $baseDir . '/../lib/ServerFactory.php',
|
||||
'OCA\\DAV\\Service\\ASyncService' => $baseDir . '/../lib/Service/ASyncService.php',
|
||||
'OCA\\DAV\\Service\\AbsenceService' => $baseDir . '/../lib/Service/AbsenceService.php',
|
||||
'OCA\\DAV\\Service\\ExampleContactService' => $baseDir . '/../lib/Service/ExampleContactService.php',
|
||||
'OCA\\DAV\\Service\\ExampleEventService' => $baseDir . '/../lib/Service/ExampleEventService.php',
|
||||
|
||||
@@ -34,6 +34,8 @@ class ComposerStaticInitDAV
|
||||
'OCA\\DAV\\BackgroundJob\\CleanupOrphanedChildrenJob' => __DIR__ . '/..' . '/../lib/BackgroundJob/CleanupOrphanedChildrenJob.php',
|
||||
'OCA\\DAV\\BackgroundJob\\DeleteOutdatedSchedulingObjects' => __DIR__ . '/..' . '/../lib/BackgroundJob/DeleteOutdatedSchedulingObjects.php',
|
||||
'OCA\\DAV\\BackgroundJob\\EventReminderJob' => __DIR__ . '/..' . '/../lib/BackgroundJob/EventReminderJob.php',
|
||||
'OCA\\DAV\\BackgroundJob\\FederatedCalendarPeriodicSyncJob' => __DIR__ . '/..' . '/../lib/BackgroundJob/FederatedCalendarPeriodicSyncJob.php',
|
||||
'OCA\\DAV\\BackgroundJob\\FederatedCalendarSyncJob' => __DIR__ . '/..' . '/../lib/BackgroundJob/FederatedCalendarSyncJob.php',
|
||||
'OCA\\DAV\\BackgroundJob\\GenerateBirthdayCalendarBackgroundJob' => __DIR__ . '/..' . '/../lib/BackgroundJob/GenerateBirthdayCalendarBackgroundJob.php',
|
||||
'OCA\\DAV\\BackgroundJob\\OutOfOfficeEventDispatcherJob' => __DIR__ . '/..' . '/../lib/BackgroundJob/OutOfOfficeEventDispatcherJob.php',
|
||||
'OCA\\DAV\\BackgroundJob\\PruneOutdatedSyncTokensJob' => __DIR__ . '/..' . '/../lib/BackgroundJob/PruneOutdatedSyncTokensJob.php',
|
||||
@@ -81,9 +83,27 @@ class ComposerStaticInitDAV
|
||||
'OCA\\DAV\\CalDAV\\EventReaderRDate' => __DIR__ . '/..' . '/../lib/CalDAV/EventReaderRDate.php',
|
||||
'OCA\\DAV\\CalDAV\\EventReaderRRule' => __DIR__ . '/..' . '/../lib/CalDAV/EventReaderRRule.php',
|
||||
'OCA\\DAV\\CalDAV\\Export\\ExportService' => __DIR__ . '/..' . '/../lib/CalDAV/Export/ExportService.php',
|
||||
'OCA\\DAV\\CalDAV\\Federation\\CalendarFederationConfig' => __DIR__ . '/..' . '/../lib/CalDAV/Federation/CalendarFederationConfig.php',
|
||||
'OCA\\DAV\\CalDAV\\Federation\\CalendarFederationNotifier' => __DIR__ . '/..' . '/../lib/CalDAV/Federation/CalendarFederationNotifier.php',
|
||||
'OCA\\DAV\\CalDAV\\Federation\\CalendarFederationProvider' => __DIR__ . '/..' . '/../lib/CalDAV/Federation/CalendarFederationProvider.php',
|
||||
'OCA\\DAV\\CalDAV\\Federation\\FederatedCalendar' => __DIR__ . '/..' . '/../lib/CalDAV/Federation/FederatedCalendar.php',
|
||||
'OCA\\DAV\\CalDAV\\Federation\\FederatedCalendarAuth' => __DIR__ . '/..' . '/../lib/CalDAV/Federation/FederatedCalendarAuth.php',
|
||||
'OCA\\DAV\\CalDAV\\Federation\\FederatedCalendarEntity' => __DIR__ . '/..' . '/../lib/CalDAV/Federation/FederatedCalendarEntity.php',
|
||||
'OCA\\DAV\\CalDAV\\Federation\\FederatedCalendarFactory' => __DIR__ . '/..' . '/../lib/CalDAV/Federation/FederatedCalendarFactory.php',
|
||||
'OCA\\DAV\\CalDAV\\Federation\\FederatedCalendarImpl' => __DIR__ . '/..' . '/../lib/CalDAV/Federation/FederatedCalendarImpl.php',
|
||||
'OCA\\DAV\\CalDAV\\Federation\\FederatedCalendarMapper' => __DIR__ . '/..' . '/../lib/CalDAV/Federation/FederatedCalendarMapper.php',
|
||||
'OCA\\DAV\\CalDAV\\Federation\\FederatedCalendarSyncService' => __DIR__ . '/..' . '/../lib/CalDAV/Federation/FederatedCalendarSyncService.php',
|
||||
'OCA\\DAV\\CalDAV\\Federation\\FederationSharingService' => __DIR__ . '/..' . '/../lib/CalDAV/Federation/FederationSharingService.php',
|
||||
'OCA\\DAV\\CalDAV\\Federation\\Protocol\\CalendarFederationProtocolV1' => __DIR__ . '/..' . '/../lib/CalDAV/Federation/Protocol/CalendarFederationProtocolV1.php',
|
||||
'OCA\\DAV\\CalDAV\\Federation\\Protocol\\CalendarProtocolParseException' => __DIR__ . '/..' . '/../lib/CalDAV/Federation/Protocol/CalendarProtocolParseException.php',
|
||||
'OCA\\DAV\\CalDAV\\Federation\\Protocol\\ICalendarFederationProtocol' => __DIR__ . '/..' . '/../lib/CalDAV/Federation/Protocol/ICalendarFederationProtocol.php',
|
||||
'OCA\\DAV\\CalDAV\\Federation\\RemoteUserCalendarHome' => __DIR__ . '/..' . '/../lib/CalDAV/Federation/RemoteUserCalendarHome.php',
|
||||
'OCA\\DAV\\CalDAV\\FreeBusy\\FreeBusyGenerator' => __DIR__ . '/..' . '/../lib/CalDAV/FreeBusy/FreeBusyGenerator.php',
|
||||
'OCA\\DAV\\CalDAV\\ICSExportPlugin\\ICSExportPlugin' => __DIR__ . '/..' . '/../lib/CalDAV/ICSExportPlugin/ICSExportPlugin.php',
|
||||
'OCA\\DAV\\CalDAV\\IRestorable' => __DIR__ . '/..' . '/../lib/CalDAV/IRestorable.php',
|
||||
'OCA\\DAV\\CalDAV\\Import\\ImportService' => __DIR__ . '/..' . '/../lib/CalDAV/Import/ImportService.php',
|
||||
'OCA\\DAV\\CalDAV\\Import\\TextImporter' => __DIR__ . '/..' . '/../lib/CalDAV/Import/TextImporter.php',
|
||||
'OCA\\DAV\\CalDAV\\Import\\XmlImporter' => __DIR__ . '/..' . '/../lib/CalDAV/Import/XmlImporter.php',
|
||||
'OCA\\DAV\\CalDAV\\Integration\\ExternalCalendar' => __DIR__ . '/..' . '/../lib/CalDAV/Integration/ExternalCalendar.php',
|
||||
'OCA\\DAV\\CalDAV\\Integration\\ICalendarProvider' => __DIR__ . '/..' . '/../lib/CalDAV/Integration/ICalendarProvider.php',
|
||||
'OCA\\DAV\\CalDAV\\InvitationResponse\\InvitationResponseServer' => __DIR__ . '/..' . '/../lib/CalDAV/InvitationResponse/InvitationResponseServer.php',
|
||||
@@ -128,6 +148,8 @@ class ComposerStaticInitDAV
|
||||
'OCA\\DAV\\CalDAV\\Sharing\\Backend' => __DIR__ . '/..' . '/../lib/CalDAV/Sharing/Backend.php',
|
||||
'OCA\\DAV\\CalDAV\\Sharing\\Service' => __DIR__ . '/..' . '/../lib/CalDAV/Sharing/Service.php',
|
||||
'OCA\\DAV\\CalDAV\\Status\\StatusService' => __DIR__ . '/..' . '/../lib/CalDAV/Status/StatusService.php',
|
||||
'OCA\\DAV\\CalDAV\\SyncService' => __DIR__ . '/..' . '/../lib/CalDAV/SyncService.php',
|
||||
'OCA\\DAV\\CalDAV\\SyncServiceResult' => __DIR__ . '/..' . '/../lib/CalDAV/SyncServiceResult.php',
|
||||
'OCA\\DAV\\CalDAV\\TimeZoneFactory' => __DIR__ . '/..' . '/../lib/CalDAV/TimeZoneFactory.php',
|
||||
'OCA\\DAV\\CalDAV\\TimezoneService' => __DIR__ . '/..' . '/../lib/CalDAV/TimezoneService.php',
|
||||
'OCA\\DAV\\CalDAV\\TipBroker' => __DIR__ . '/..' . '/../lib/CalDAV/TipBroker.php',
|
||||
@@ -181,6 +203,7 @@ class ComposerStaticInitDAV
|
||||
'OCA\\DAV\\Command\\ExportCalendar' => __DIR__ . '/..' . '/../lib/Command/ExportCalendar.php',
|
||||
'OCA\\DAV\\Command\\FixCalendarSyncCommand' => __DIR__ . '/..' . '/../lib/Command/FixCalendarSyncCommand.php',
|
||||
'OCA\\DAV\\Command\\GetAbsenceCommand' => __DIR__ . '/..' . '/../lib/Command/GetAbsenceCommand.php',
|
||||
'OCA\\DAV\\Command\\ImportCalendar' => __DIR__ . '/..' . '/../lib/Command/ImportCalendar.php',
|
||||
'OCA\\DAV\\Command\\ListAddressbooks' => __DIR__ . '/..' . '/../lib/Command/ListAddressbooks.php',
|
||||
'OCA\\DAV\\Command\\ListCalendarShares' => __DIR__ . '/..' . '/../lib/Command/ListCalendarShares.php',
|
||||
'OCA\\DAV\\Command\\ListCalendars' => __DIR__ . '/..' . '/../lib/Command/ListCalendars.php',
|
||||
@@ -254,6 +277,7 @@ class ComposerStaticInitDAV
|
||||
'OCA\\DAV\\DAV\\CustomPropertiesBackend' => __DIR__ . '/..' . '/../lib/DAV/CustomPropertiesBackend.php',
|
||||
'OCA\\DAV\\DAV\\GroupPrincipalBackend' => __DIR__ . '/..' . '/../lib/DAV/GroupPrincipalBackend.php',
|
||||
'OCA\\DAV\\DAV\\PublicAuth' => __DIR__ . '/..' . '/../lib/DAV/PublicAuth.php',
|
||||
'OCA\\DAV\\DAV\\RemoteUserPrincipalBackend' => __DIR__ . '/..' . '/../lib/DAV/RemoteUserPrincipalBackend.php',
|
||||
'OCA\\DAV\\DAV\\Sharing\\Backend' => __DIR__ . '/..' . '/../lib/DAV/Sharing/Backend.php',
|
||||
'OCA\\DAV\\DAV\\Sharing\\IShareable' => __DIR__ . '/..' . '/../lib/DAV/Sharing/IShareable.php',
|
||||
'OCA\\DAV\\DAV\\Sharing\\Plugin' => __DIR__ . '/..' . '/../lib/DAV/Sharing/Plugin.php',
|
||||
@@ -315,6 +339,7 @@ class ComposerStaticInitDAV
|
||||
'OCA\\DAV\\Listener\\BirthdayListener' => __DIR__ . '/..' . '/../lib/Listener/BirthdayListener.php',
|
||||
'OCA\\DAV\\Listener\\CalendarContactInteractionListener' => __DIR__ . '/..' . '/../lib/Listener/CalendarContactInteractionListener.php',
|
||||
'OCA\\DAV\\Listener\\CalendarDeletionDefaultUpdaterListener' => __DIR__ . '/..' . '/../lib/Listener/CalendarDeletionDefaultUpdaterListener.php',
|
||||
'OCA\\DAV\\Listener\\CalendarFederationNotificationListener' => __DIR__ . '/..' . '/../lib/Listener/CalendarFederationNotificationListener.php',
|
||||
'OCA\\DAV\\Listener\\CalendarObjectReminderUpdaterListener' => __DIR__ . '/..' . '/../lib/Listener/CalendarObjectReminderUpdaterListener.php',
|
||||
'OCA\\DAV\\Listener\\CalendarPublicationListener' => __DIR__ . '/..' . '/../lib/Listener/CalendarPublicationListener.php',
|
||||
'OCA\\DAV\\Listener\\CalendarShareUpdateListener' => __DIR__ . '/..' . '/../lib/Listener/CalendarShareUpdateListener.php',
|
||||
@@ -322,6 +347,7 @@ class ComposerStaticInitDAV
|
||||
'OCA\\DAV\\Listener\\ClearPhotoCacheListener' => __DIR__ . '/..' . '/../lib/Listener/ClearPhotoCacheListener.php',
|
||||
'OCA\\DAV\\Listener\\DavAdminSettingsListener' => __DIR__ . '/..' . '/../lib/Listener/DavAdminSettingsListener.php',
|
||||
'OCA\\DAV\\Listener\\OutOfOfficeListener' => __DIR__ . '/..' . '/../lib/Listener/OutOfOfficeListener.php',
|
||||
'OCA\\DAV\\Listener\\SabrePluginAuthInitListener' => __DIR__ . '/..' . '/../lib/Listener/SabrePluginAuthInitListener.php',
|
||||
'OCA\\DAV\\Listener\\SubscriptionListener' => __DIR__ . '/..' . '/../lib/Listener/SubscriptionListener.php',
|
||||
'OCA\\DAV\\Listener\\TrustedServerRemovedListener' => __DIR__ . '/..' . '/../lib/Listener/TrustedServerRemovedListener.php',
|
||||
'OCA\\DAV\\Listener\\UserEventsListener' => __DIR__ . '/..' . '/../lib/Listener/UserEventsListener.php',
|
||||
@@ -370,6 +396,7 @@ class ComposerStaticInitDAV
|
||||
'OCA\\DAV\\Migration\\Version1029Date20231004091403' => __DIR__ . '/..' . '/../lib/Migration/Version1029Date20231004091403.php',
|
||||
'OCA\\DAV\\Migration\\Version1030Date20240205103243' => __DIR__ . '/..' . '/../lib/Migration/Version1030Date20240205103243.php',
|
||||
'OCA\\DAV\\Migration\\Version1031Date20240610134258' => __DIR__ . '/..' . '/../lib/Migration/Version1031Date20240610134258.php',
|
||||
'OCA\\DAV\\Migration\\Version1034Date20250605132605' => __DIR__ . '/..' . '/../lib/Migration/Version1034Date20250605132605.php',
|
||||
'OCA\\DAV\\Migration\\Version1034Date20250813093701' => __DIR__ . '/..' . '/../lib/Migration/Version1034Date20250813093701.php',
|
||||
'OCA\\DAV\\Model\\ExampleEvent' => __DIR__ . '/..' . '/../lib/Model/ExampleEvent.php',
|
||||
'OCA\\DAV\\Paginate\\LimitedCopyIterator' => __DIR__ . '/..' . '/../lib/Paginate/LimitedCopyIterator.php',
|
||||
@@ -386,6 +413,7 @@ class ComposerStaticInitDAV
|
||||
'OCA\\DAV\\Search\\TasksSearchProvider' => __DIR__ . '/..' . '/../lib/Search/TasksSearchProvider.php',
|
||||
'OCA\\DAV\\Server' => __DIR__ . '/..' . '/../lib/Server.php',
|
||||
'OCA\\DAV\\ServerFactory' => __DIR__ . '/..' . '/../lib/ServerFactory.php',
|
||||
'OCA\\DAV\\Service\\ASyncService' => __DIR__ . '/..' . '/../lib/Service/ASyncService.php',
|
||||
'OCA\\DAV\\Service\\AbsenceService' => __DIR__ . '/..' . '/../lib/Service/AbsenceService.php',
|
||||
'OCA\\DAV\\Service\\ExampleContactService' => __DIR__ . '/..' . '/../lib/Service/ExampleContactService.php',
|
||||
'OCA\\DAV\\Service\\ExampleEventService' => __DIR__ . '/..' . '/../lib/Service/ExampleEventService.php',
|
||||
|
||||
@@ -194,6 +194,7 @@ OC.L10N.register(
|
||||
"Second Last" : "Azken aurrekoa",
|
||||
"Third Last" : "Hirugarren azkena",
|
||||
"Fourth Last" : "Laugarren azkena",
|
||||
"Fifth Last" : "Bosgarren azkena",
|
||||
"Contacts" : "Kontaktuak",
|
||||
"{actor} created address book {addressbook}" : "{actor}-(e)k {addressbook} helbide-liburua sortu du ",
|
||||
"You created address book {addressbook}" : "{addressbook} helbide-liburua sortu duzu",
|
||||
|
||||
@@ -192,6 +192,7 @@
|
||||
"Second Last" : "Azken aurrekoa",
|
||||
"Third Last" : "Hirugarren azkena",
|
||||
"Fourth Last" : "Laugarren azkena",
|
||||
"Fifth Last" : "Bosgarren azkena",
|
||||
"Contacts" : "Kontaktuak",
|
||||
"{actor} created address book {addressbook}" : "{actor}-(e)k {addressbook} helbide-liburua sortu du ",
|
||||
"You created address book {addressbook}" : "{addressbook} helbide-liburua sortu duzu",
|
||||
|
||||
@@ -127,6 +127,20 @@ OC.L10N.register(
|
||||
"_In a week on %1$s_::_In %n weeks on %1$s_" : ["Over een week op %1$s","Over %n weken op %1$s"],
|
||||
"_In a month on %1$s_::_In %n months on %1$s_" : ["Over een maand op %1$s","Over %n maanden op %1$s"],
|
||||
"_In a year on %1$s_::_In %n years on %1$s_" : ["Over een jaar op %1$s","Over %n jaar op %1$s"],
|
||||
"In the past on %1$s then on %2$s" : "In het verleden op %1$s dan op %2$s",
|
||||
"_In a minute on %1$s then on %2$s_::_In %n minutes on %1$s then on %2$s_" : ["Over een minuut op %1$s dan op %2$s","Over %n minuten op %1$s dan op %2$s"],
|
||||
"_In a hour on %1$s then on %2$s_::_In %n hours on %1$s then on %2$s_" : ["Over een uur op %1$s dan op %2$s","Over %n uren op %1$s dan op %2$s"],
|
||||
"_In a day on %1$s then on %2$s_::_In %n days on %1$s then on %2$s_" : ["Over een dag op %1$s dan op %2$s","Over %n dagen op %1$s dan op %2$s"],
|
||||
"_In a week on %1$s then on %2$s_::_In %n weeks on %1$s then on %2$s_" : ["Over een week op %1$s dan op %2$s","Over %n weken op %1$s dan op %2$s"],
|
||||
"_In a month on %1$s then on %2$s_::_In %n months on %1$s then on %2$s_" : ["Over een maand op %1$s dan op %2$s","Over %n maanden op %1$s dan op %2$s"],
|
||||
"_In a year on %1$s then on %2$s_::_In %n years on %1$s then on %2$s_" : ["Over een jaar op %1$s dan op %2$s","Over %n jaren op %1$s dan op %2$s"],
|
||||
"In the past on %1$s then on %2$s and %3$s" : "In het verleden op %1$s dan op %2$s en %3$s",
|
||||
"_In a minute on %1$s then on %2$s and %3$s_::_In %n minutes on %1$s then on %2$s and %3$s_" : ["Over een minuut op %1$s dan op %2$s en %3$s","Over %n minuten op %1$s dan op %2$s en %3$s"],
|
||||
"_In a hour on %1$s then on %2$s and %3$s_::_In %n hours on %1$s then on %2$s and %3$s_" : ["Over een uur op %1$s dan op %2$s en %3$s","Over %n uren op %1$s dan op %2$s en %3$s"],
|
||||
"_In a day on %1$s then on %2$s and %3$s_::_In %n days on %1$s then on %2$s and %3$s_" : ["Over een dag op %1$s dan op %2$s en %3$s","Over %n dagen op %1$s dan op %2$s en %3$s"],
|
||||
"_In a week on %1$s then on %2$s and %3$s_::_In %n weeks on %1$s then on %2$s and %3$s_" : ["Over een week op %1$s dan op %2$s en %3$s","Over %n weken op %1$s dan op %2$s en %3$s"],
|
||||
"_In a month on %1$s then on %2$s and %3$s_::_In %n months on %1$s then on %2$s and %3$s_" : ["Over een maand op %1$s dan op %2$s en %3$s","Over %n maanden op %1$s then on %2$s and %3$s"],
|
||||
"_In a year on %1$s then on %2$s and %3$s_::_In %n years on %1$s then on %2$s and %3$s_" : ["Over een jaar op %1$s dan op %2$s en %3$s","Over %n jaren op %1$s dan op %2$s en %3$s"],
|
||||
"Cancelled: %1$s" : "Geannuleerd: %1$s",
|
||||
"\"%1$s\" has been canceled" : "\"%1$s\" is geannuleerd",
|
||||
"Re: %1$s" : "Re: %1$s",
|
||||
|
||||
@@ -125,6 +125,20 @@
|
||||
"_In a week on %1$s_::_In %n weeks on %1$s_" : ["Over een week op %1$s","Over %n weken op %1$s"],
|
||||
"_In a month on %1$s_::_In %n months on %1$s_" : ["Over een maand op %1$s","Over %n maanden op %1$s"],
|
||||
"_In a year on %1$s_::_In %n years on %1$s_" : ["Over een jaar op %1$s","Over %n jaar op %1$s"],
|
||||
"In the past on %1$s then on %2$s" : "In het verleden op %1$s dan op %2$s",
|
||||
"_In a minute on %1$s then on %2$s_::_In %n minutes on %1$s then on %2$s_" : ["Over een minuut op %1$s dan op %2$s","Over %n minuten op %1$s dan op %2$s"],
|
||||
"_In a hour on %1$s then on %2$s_::_In %n hours on %1$s then on %2$s_" : ["Over een uur op %1$s dan op %2$s","Over %n uren op %1$s dan op %2$s"],
|
||||
"_In a day on %1$s then on %2$s_::_In %n days on %1$s then on %2$s_" : ["Over een dag op %1$s dan op %2$s","Over %n dagen op %1$s dan op %2$s"],
|
||||
"_In a week on %1$s then on %2$s_::_In %n weeks on %1$s then on %2$s_" : ["Over een week op %1$s dan op %2$s","Over %n weken op %1$s dan op %2$s"],
|
||||
"_In a month on %1$s then on %2$s_::_In %n months on %1$s then on %2$s_" : ["Over een maand op %1$s dan op %2$s","Over %n maanden op %1$s dan op %2$s"],
|
||||
"_In a year on %1$s then on %2$s_::_In %n years on %1$s then on %2$s_" : ["Over een jaar op %1$s dan op %2$s","Over %n jaren op %1$s dan op %2$s"],
|
||||
"In the past on %1$s then on %2$s and %3$s" : "In het verleden op %1$s dan op %2$s en %3$s",
|
||||
"_In a minute on %1$s then on %2$s and %3$s_::_In %n minutes on %1$s then on %2$s and %3$s_" : ["Over een minuut op %1$s dan op %2$s en %3$s","Over %n minuten op %1$s dan op %2$s en %3$s"],
|
||||
"_In a hour on %1$s then on %2$s and %3$s_::_In %n hours on %1$s then on %2$s and %3$s_" : ["Over een uur op %1$s dan op %2$s en %3$s","Over %n uren op %1$s dan op %2$s en %3$s"],
|
||||
"_In a day on %1$s then on %2$s and %3$s_::_In %n days on %1$s then on %2$s and %3$s_" : ["Over een dag op %1$s dan op %2$s en %3$s","Over %n dagen op %1$s dan op %2$s en %3$s"],
|
||||
"_In a week on %1$s then on %2$s and %3$s_::_In %n weeks on %1$s then on %2$s and %3$s_" : ["Over een week op %1$s dan op %2$s en %3$s","Over %n weken op %1$s dan op %2$s en %3$s"],
|
||||
"_In a month on %1$s then on %2$s and %3$s_::_In %n months on %1$s then on %2$s and %3$s_" : ["Over een maand op %1$s dan op %2$s en %3$s","Over %n maanden op %1$s then on %2$s and %3$s"],
|
||||
"_In a year on %1$s then on %2$s and %3$s_::_In %n years on %1$s then on %2$s and %3$s_" : ["Over een jaar op %1$s dan op %2$s en %3$s","Over %n jaren op %1$s dan op %2$s en %3$s"],
|
||||
"Cancelled: %1$s" : "Geannuleerd: %1$s",
|
||||
"\"%1$s\" has been canceled" : "\"%1$s\" is geannuleerd",
|
||||
"Re: %1$s" : "Re: %1$s",
|
||||
|
||||
@@ -13,6 +13,7 @@ use OCA\DAV\CalDAV\AppCalendar\AppCalendarPlugin;
|
||||
use OCA\DAV\CalDAV\CachedSubscriptionProvider;
|
||||
use OCA\DAV\CalDAV\CalendarManager;
|
||||
use OCA\DAV\CalDAV\CalendarProvider;
|
||||
use OCA\DAV\CalDAV\Federation\CalendarFederationProvider;
|
||||
use OCA\DAV\CalDAV\Reminder\NotificationProvider\AudioProvider;
|
||||
use OCA\DAV\CalDAV\Reminder\NotificationProvider\EmailProvider;
|
||||
use OCA\DAV\CalDAV\Reminder\NotificationProvider\PushProvider;
|
||||
@@ -36,6 +37,7 @@ use OCA\DAV\Events\CalendarUpdatedEvent;
|
||||
use OCA\DAV\Events\CardCreatedEvent;
|
||||
use OCA\DAV\Events\CardDeletedEvent;
|
||||
use OCA\DAV\Events\CardUpdatedEvent;
|
||||
use OCA\DAV\Events\SabrePluginAuthInitEvent;
|
||||
use OCA\DAV\Events\SubscriptionCreatedEvent;
|
||||
use OCA\DAV\Events\SubscriptionDeletedEvent;
|
||||
use OCA\DAV\Listener\ActivityUpdaterListener;
|
||||
@@ -44,6 +46,7 @@ use OCA\DAV\Listener\AddressbookListener;
|
||||
use OCA\DAV\Listener\BirthdayListener;
|
||||
use OCA\DAV\Listener\CalendarContactInteractionListener;
|
||||
use OCA\DAV\Listener\CalendarDeletionDefaultUpdaterListener;
|
||||
use OCA\DAV\Listener\CalendarFederationNotificationListener;
|
||||
use OCA\DAV\Listener\CalendarObjectReminderUpdaterListener;
|
||||
use OCA\DAV\Listener\CalendarPublicationListener;
|
||||
use OCA\DAV\Listener\CalendarShareUpdateListener;
|
||||
@@ -51,6 +54,7 @@ use OCA\DAV\Listener\CardListener;
|
||||
use OCA\DAV\Listener\ClearPhotoCacheListener;
|
||||
use OCA\DAV\Listener\DavAdminSettingsListener;
|
||||
use OCA\DAV\Listener\OutOfOfficeListener;
|
||||
use OCA\DAV\Listener\SabrePluginAuthInitListener;
|
||||
use OCA\DAV\Listener\SubscriptionListener;
|
||||
use OCA\DAV\Listener\TrustedServerRemovedListener;
|
||||
use OCA\DAV\Listener\UserEventsListener;
|
||||
@@ -82,6 +86,7 @@ use OCP\Config\BeforePreferenceSetEvent;
|
||||
use OCP\Contacts\IManager as IContactsManager;
|
||||
use OCP\DB\Events\AddMissingIndicesEvent;
|
||||
use OCP\Federation\Events\TrustedServerRemovedEvent;
|
||||
use OCP\Federation\ICloudFederationProviderManager;
|
||||
use OCP\IUserSession;
|
||||
use OCP\Server;
|
||||
use OCP\Settings\Events\DeclarativeSettingsGetValueEvent;
|
||||
@@ -198,6 +203,12 @@ class Application extends App implements IBootstrap {
|
||||
$context->registerEventListener(UserChangedEvent::class, UserEventsListener::class);
|
||||
$context->registerEventListener(UserUpdatedEvent::class, UserEventsListener::class);
|
||||
|
||||
$context->registerEventListener(SabrePluginAuthInitEvent::class, SabrePluginAuthInitListener::class);
|
||||
|
||||
$context->registerEventListener(CalendarObjectCreatedEvent::class, CalendarFederationNotificationListener::class);
|
||||
$context->registerEventListener(CalendarObjectUpdatedEvent::class, CalendarFederationNotificationListener::class);
|
||||
$context->registerEventListener(CalendarObjectDeletedEvent::class, CalendarFederationNotificationListener::class);
|
||||
|
||||
$context->registerNotifierService(Notifier::class);
|
||||
|
||||
$context->registerCalendarProvider(CalendarProvider::class);
|
||||
@@ -213,7 +224,6 @@ class Application extends App implements IBootstrap {
|
||||
$context->registerDeclarativeSettings(SystemAddressBookSettings::class);
|
||||
$context->registerEventListener(DeclarativeSettingsGetValueEvent::class, DavAdminSettingsListener::class);
|
||||
$context->registerEventListener(DeclarativeSettingsSetValueEvent::class, DavAdminSettingsListener::class);
|
||||
|
||||
}
|
||||
|
||||
public function boot(IBootContext $context): void {
|
||||
@@ -223,6 +233,7 @@ class Application extends App implements IBootstrap {
|
||||
$context->injectFn($this->registerContactsManager(...));
|
||||
$context->injectFn($this->registerCalendarManager(...));
|
||||
$context->injectFn($this->registerCalendarReminders(...));
|
||||
$context->injectFn($this->registerCloudFederationProvider(...));
|
||||
}
|
||||
|
||||
public function registerContactsManager(IContactsManager $cm, IAppContainer $container): void {
|
||||
@@ -279,4 +290,14 @@ class Application extends App implements IBootstrap {
|
||||
$logger->error($ex->getMessage(), ['exception' => $ex]);
|
||||
}
|
||||
}
|
||||
|
||||
public function registerCloudFederationProvider(
|
||||
ICloudFederationProviderManager $manager,
|
||||
): void {
|
||||
$manager->addCloudFederationProvider(
|
||||
CalendarFederationProvider::PROVIDER_ID,
|
||||
'Calendar Federation',
|
||||
static fn () => Server::get(CalendarFederationProvider::class),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,62 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
/**
|
||||
* SPDX-FileCopyrightText: 2025 Nextcloud GmbH and Nextcloud contributors
|
||||
* SPDX-License-Identifier: AGPL-3.0-or-later
|
||||
*/
|
||||
|
||||
namespace OCA\DAV\BackgroundJob;
|
||||
|
||||
use OCA\DAV\CalDAV\Federation\CalendarFederationConfig;
|
||||
use OCA\DAV\CalDAV\Federation\FederatedCalendarMapper;
|
||||
use OCA\DAV\CalDAV\Federation\FederatedCalendarSyncService;
|
||||
use OCP\AppFramework\Utility\ITimeFactory;
|
||||
use OCP\BackgroundJob\TimedJob;
|
||||
use Psr\Http\Client\ClientExceptionInterface;
|
||||
use Psr\Log\LoggerInterface;
|
||||
|
||||
class FederatedCalendarPeriodicSyncJob extends TimedJob {
|
||||
private const DOWNLOAD_LIMIT = 500;
|
||||
|
||||
public function __construct(
|
||||
ITimeFactory $time,
|
||||
private readonly FederatedCalendarSyncService $syncService,
|
||||
private readonly FederatedCalendarMapper $federatedCalendarMapper,
|
||||
private readonly CalendarFederationConfig $calendarFederationConfig,
|
||||
private readonly LoggerInterface $logger,
|
||||
) {
|
||||
parent::__construct($time);
|
||||
|
||||
$this->setTimeSensitivity(self::TIME_SENSITIVE);
|
||||
$this->setAllowParallelRuns(false);
|
||||
$this->setInterval(3600);
|
||||
}
|
||||
|
||||
protected function run($argument): void {
|
||||
if (!$this->calendarFederationConfig->isFederationEnabled()) {
|
||||
return;
|
||||
}
|
||||
|
||||
$downloadedEvents = 0;
|
||||
$oneHourAgo = $this->time->getTime() - 3600;
|
||||
$calendars = $this->federatedCalendarMapper->findUnsyncedSinceBefore($oneHourAgo);
|
||||
foreach ($calendars as $calendar) {
|
||||
try {
|
||||
$downloadedEvents += $this->syncService->syncOne($calendar);
|
||||
} catch (ClientExceptionInterface $e) {
|
||||
$name = $calendar->getUri();
|
||||
$this->logger->error("Failed to sync federated calendar $name: " . $e->getMessage(), [
|
||||
'exception' => $e,
|
||||
'calendar' => $calendar->toCalendarInfo(),
|
||||
]);
|
||||
}
|
||||
|
||||
// Prevent stalling the background job queue for too long
|
||||
if ($downloadedEvents >= self::DOWNLOAD_LIMIT) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,67 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
/**
|
||||
* SPDX-FileCopyrightText: 2025 Nextcloud GmbH and Nextcloud contributors
|
||||
* SPDX-License-Identifier: AGPL-3.0-or-later
|
||||
*/
|
||||
|
||||
namespace OCA\DAV\BackgroundJob;
|
||||
|
||||
use OCA\DAV\CalDAV\Federation\CalendarFederationConfig;
|
||||
use OCA\DAV\CalDAV\Federation\FederatedCalendarMapper;
|
||||
use OCA\DAV\CalDAV\Federation\FederatedCalendarSyncService;
|
||||
use OCP\AppFramework\Db\DoesNotExistException;
|
||||
use OCP\AppFramework\Utility\ITimeFactory;
|
||||
use OCP\BackgroundJob\QueuedJob;
|
||||
use Psr\Http\Client\ClientExceptionInterface;
|
||||
use Psr\Log\LoggerInterface;
|
||||
|
||||
class FederatedCalendarSyncJob extends QueuedJob {
|
||||
public const ARGUMENT_ID = 'id';
|
||||
|
||||
public function __construct(
|
||||
ITimeFactory $time,
|
||||
private readonly FederatedCalendarSyncService $syncService,
|
||||
private readonly FederatedCalendarMapper $federatedCalendarMapper,
|
||||
private readonly CalendarFederationConfig $calendarFederationConfig,
|
||||
private readonly LoggerInterface $logger,
|
||||
) {
|
||||
parent::__construct($time);
|
||||
|
||||
$this->setAllowParallelRuns(false);
|
||||
}
|
||||
|
||||
protected function run($argument): void {
|
||||
if (!$this->calendarFederationConfig->isFederationEnabled()) {
|
||||
return;
|
||||
}
|
||||
|
||||
$id = $argument[self::ARGUMENT_ID] ?? null;
|
||||
if (!is_numeric($id)) {
|
||||
return;
|
||||
}
|
||||
|
||||
$id = (int)$id;
|
||||
try {
|
||||
$calendar = $this->federatedCalendarMapper->find($id);
|
||||
} catch (DoesNotExistException $e) {
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
$this->syncService->syncOne($calendar);
|
||||
} catch (ClientExceptionInterface $e) {
|
||||
$name = $calendar->getUri();
|
||||
$this->logger->error("Failed to sync federated calendar $name: " . $e->getMessage(), [
|
||||
'exception' => $e,
|
||||
'calendar' => $calendar->toCalendarInfo(),
|
||||
]);
|
||||
|
||||
// Let the periodic background job pick up the calendar at a later point
|
||||
$calendar->setLastSync(1);
|
||||
$this->federatedCalendarMapper->update($calendar);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -11,6 +11,7 @@ namespace OCA\DAV\CalDAV\AppCalendar;
|
||||
|
||||
use OCA\DAV\CalDAV\CachedSubscriptionImpl;
|
||||
use OCA\DAV\CalDAV\CalendarImpl;
|
||||
use OCA\DAV\CalDAV\Federation\FederatedCalendarImpl;
|
||||
use OCA\DAV\CalDAV\Integration\ExternalCalendar;
|
||||
use OCA\DAV\CalDAV\Integration\ICalendarProvider;
|
||||
use OCP\Calendar\IManager;
|
||||
@@ -51,7 +52,11 @@ class AppCalendarPlugin implements ICalendarProvider {
|
||||
return array_values(
|
||||
array_filter($this->manager->getCalendarsForPrincipal($principalUri, $calendarUris), function ($c) {
|
||||
// We must not provide a wrapper for DAV calendars
|
||||
return ! (($c instanceof CalendarImpl) || ($c instanceof CachedSubscriptionImpl));
|
||||
return !(
|
||||
($c instanceof CalendarImpl)
|
||||
|| ($c instanceof CachedSubscriptionImpl)
|
||||
|| ($c instanceof FederatedCalendarImpl)
|
||||
);
|
||||
})
|
||||
);
|
||||
}
|
||||
|
||||
@@ -12,6 +12,8 @@ use DateTimeImmutable;
|
||||
use DateTimeInterface;
|
||||
use Generator;
|
||||
use OCA\DAV\AppInfo\Application;
|
||||
use OCA\DAV\CalDAV\Federation\FederatedCalendarEntity;
|
||||
use OCA\DAV\CalDAV\Federation\FederatedCalendarMapper;
|
||||
use OCA\DAV\CalDAV\Sharing\Backend;
|
||||
use OCA\DAV\Connector\Sabre\Principal;
|
||||
use OCA\DAV\DAV\Sharing\IShareable;
|
||||
@@ -41,6 +43,8 @@ use OCP\Calendar\Exceptions\CalendarException;
|
||||
use OCP\DB\Exception;
|
||||
use OCP\DB\QueryBuilder\IQueryBuilder;
|
||||
use OCP\EventDispatcher\IEventDispatcher;
|
||||
use OCP\ICache;
|
||||
use OCP\ICacheFactory;
|
||||
use OCP\IConfig;
|
||||
use OCP\IDBConnection;
|
||||
use OCP\IUserManager;
|
||||
@@ -110,6 +114,7 @@ class CalDavBackend extends AbstractBackend implements SyncSupport, Subscription
|
||||
|
||||
public const CALENDAR_TYPE_CALENDAR = 0;
|
||||
public const CALENDAR_TYPE_SUBSCRIPTION = 1;
|
||||
public const CALENDAR_TYPE_FEDERATED = 2;
|
||||
|
||||
public const PERSONAL_CALENDAR_URI = 'personal';
|
||||
public const PERSONAL_CALENDAR_NAME = 'Personal';
|
||||
@@ -199,6 +204,8 @@ class CalDavBackend extends AbstractBackend implements SyncSupport, Subscription
|
||||
private string $dbObjectInvitationsTable = 'calendar_invitations';
|
||||
private array $cachedObjects = [];
|
||||
|
||||
private readonly ICache $publishStatusCache;
|
||||
|
||||
public function __construct(
|
||||
private IDBConnection $db,
|
||||
private Principal $principalBackend,
|
||||
@@ -208,8 +215,11 @@ class CalDavBackend extends AbstractBackend implements SyncSupport, Subscription
|
||||
private IEventDispatcher $dispatcher,
|
||||
private IConfig $config,
|
||||
private Sharing\Backend $calendarSharingBackend,
|
||||
private FederatedCalendarMapper $federatedCalendarMapper,
|
||||
ICacheFactory $cacheFactory,
|
||||
private bool $legacyEndpoint = false,
|
||||
) {
|
||||
$this->publishStatusCache = $cacheFactory->createInMemory();
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -535,7 +545,7 @@ class CalDavBackend extends AbstractBackend implements SyncSupport, Subscription
|
||||
'{' . Plugin::NS_CALDAV . '}supported-calendar-component-set' => new SupportedCalendarComponentSet($components),
|
||||
'{' . Plugin::NS_CALDAV . '}schedule-calendar-transp' => new ScheduleCalendarTransp($row['transparent']?'transparent':'opaque'),
|
||||
'{' . \OCA\DAV\DAV\Sharing\Plugin::NS_OWNCLOUD . '}owner-principal' => $this->convertPrincipal($row['principaluri'], $this->legacyEndpoint),
|
||||
'{' . \OCA\DAV\DAV\Sharing\Plugin::NS_OWNCLOUD . '}read-only' => (int)$row['access'] === Backend::ACCESS_READ,
|
||||
'{' . \OCA\DAV\DAV\Sharing\Plugin::NS_OWNCLOUD . '}read-only' => true,
|
||||
'{' . \OCA\DAV\DAV\Sharing\Plugin::NS_OWNCLOUD . '}public' => (int)$row['access'] === self::ACCESS_PUBLIC,
|
||||
];
|
||||
|
||||
@@ -600,7 +610,7 @@ class CalDavBackend extends AbstractBackend implements SyncSupport, Subscription
|
||||
'{' . Plugin::NS_CALDAV . '}supported-calendar-component-set' => new SupportedCalendarComponentSet($components),
|
||||
'{' . Plugin::NS_CALDAV . '}schedule-calendar-transp' => new ScheduleCalendarTransp($row['transparent']?'transparent':'opaque'),
|
||||
'{' . \OCA\DAV\DAV\Sharing\Plugin::NS_OWNCLOUD . '}owner-principal' => $this->convertPrincipal($row['principaluri'], !$this->legacyEndpoint),
|
||||
'{' . \OCA\DAV\DAV\Sharing\Plugin::NS_OWNCLOUD . '}read-only' => (int)$row['access'] === Backend::ACCESS_READ,
|
||||
'{' . \OCA\DAV\DAV\Sharing\Plugin::NS_OWNCLOUD . '}read-only' => true,
|
||||
'{' . \OCA\DAV\DAV\Sharing\Plugin::NS_OWNCLOUD . '}public' => (int)$row['access'] === self::ACCESS_PUBLIC,
|
||||
];
|
||||
|
||||
@@ -919,6 +929,8 @@ class CalDavBackend extends AbstractBackend implements SyncSupport, Subscription
|
||||
* @return void
|
||||
*/
|
||||
public function deleteCalendar($calendarId, bool $forceDeletePermanently = false) {
|
||||
$this->publishStatusCache->remove((string)$calendarId);
|
||||
|
||||
$this->atomic(function () use ($calendarId, $forceDeletePermanently): void {
|
||||
// The calendar is deleted right away if this is either enforced by the caller
|
||||
// or the special contacts birthday calendar or when the preference of an empty
|
||||
@@ -1408,10 +1420,12 @@ class CalDavBackend extends AbstractBackend implements SyncSupport, Subscription
|
||||
$shares = $this->getShares($calendarId);
|
||||
|
||||
$this->dispatcher->dispatchTyped(new CalendarObjectCreatedEvent($calendarId, $calendarRow, $shares, $objectRow));
|
||||
} else {
|
||||
} elseif ($calendarType === self::CALENDAR_TYPE_SUBSCRIPTION) {
|
||||
$subscriptionRow = $this->getSubscriptionById($calendarId);
|
||||
|
||||
$this->dispatcher->dispatchTyped(new CachedCalendarObjectCreatedEvent($calendarId, $subscriptionRow, [], $objectRow));
|
||||
} elseif ($calendarType === self::CALENDAR_TYPE_FEDERATED) {
|
||||
// TODO: implement custom event for federated calendars
|
||||
}
|
||||
|
||||
return '"' . $extraData['etag'] . '"';
|
||||
@@ -1468,10 +1482,12 @@ class CalDavBackend extends AbstractBackend implements SyncSupport, Subscription
|
||||
$shares = $this->getShares($calendarId);
|
||||
|
||||
$this->dispatcher->dispatchTyped(new CalendarObjectUpdatedEvent($calendarId, $calendarRow, $shares, $objectRow));
|
||||
} else {
|
||||
} elseif ($calendarType === self::CALENDAR_TYPE_SUBSCRIPTION) {
|
||||
$subscriptionRow = $this->getSubscriptionById($calendarId);
|
||||
|
||||
$this->dispatcher->dispatchTyped(new CachedCalendarObjectUpdatedEvent($calendarId, $subscriptionRow, [], $objectRow));
|
||||
} elseif ($calendarType === self::CALENDAR_TYPE_FEDERATED) {
|
||||
// TODO: implement custom event for federated calendars
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1978,6 +1994,8 @@ class CalDavBackend extends AbstractBackend implements SyncSupport, Subscription
|
||||
|
||||
if (isset($calendarInfo['source'])) {
|
||||
$calendarType = self::CALENDAR_TYPE_SUBSCRIPTION;
|
||||
} elseif (isset($calendarInfo['federated'])) {
|
||||
$calendarType = self::CALENDAR_TYPE_FEDERATED;
|
||||
} else {
|
||||
$calendarType = self::CALENDAR_TYPE_CALENDAR;
|
||||
}
|
||||
@@ -2424,7 +2442,7 @@ class CalDavBackend extends AbstractBackend implements SyncSupport, Subscription
|
||||
* @param string $uid
|
||||
* @return string|null
|
||||
*/
|
||||
public function getCalendarObjectByUID($principalUri, $uid) {
|
||||
public function getCalendarObjectByUID($principalUri, $uid, $calendarUri = null) {
|
||||
$query = $this->db->getQueryBuilder();
|
||||
$query->selectAlias('c.uri', 'calendaruri')->selectAlias('co.uri', 'objecturi')
|
||||
->from('calendarobjects', 'co')
|
||||
@@ -2432,6 +2450,11 @@ class CalDavBackend extends AbstractBackend implements SyncSupport, Subscription
|
||||
->where($query->expr()->eq('c.principaluri', $query->createNamedParameter($principalUri)))
|
||||
->andWhere($query->expr()->eq('co.uid', $query->createNamedParameter($uid)))
|
||||
->andWhere($query->expr()->isNull('co.deleted_at'));
|
||||
|
||||
if ($calendarUri !== null) {
|
||||
$query->andWhere($query->expr()->eq('c.uri', $query->createNamedParameter($calendarUri)));
|
||||
}
|
||||
|
||||
$stmt = $query->executeQuery();
|
||||
$row = $stmt->fetch();
|
||||
$stmt->closeCursor();
|
||||
@@ -3192,6 +3215,10 @@ class CalDavBackend extends AbstractBackend implements SyncSupport, Subscription
|
||||
return $this->calendarSharingBackend->getShares($resourceId);
|
||||
}
|
||||
|
||||
public function getSharesByShareePrincipal(string $principal): array {
|
||||
return $this->calendarSharingBackend->getSharesByShareePrincipal($principal);
|
||||
}
|
||||
|
||||
public function preloadShares(array $resourceIds): void {
|
||||
$this->calendarSharingBackend->preloadShares($resourceIds);
|
||||
}
|
||||
@@ -3202,7 +3229,7 @@ class CalDavBackend extends AbstractBackend implements SyncSupport, Subscription
|
||||
* @return string|null
|
||||
*/
|
||||
public function setPublishStatus($value, $calendar) {
|
||||
return $this->atomic(function () use ($value, $calendar) {
|
||||
$publishStatus = $this->atomic(function () use ($value, $calendar) {
|
||||
$calendarId = $calendar->getResourceId();
|
||||
$calendarData = $this->getCalendarById($calendarId);
|
||||
|
||||
@@ -3230,13 +3257,21 @@ class CalDavBackend extends AbstractBackend implements SyncSupport, Subscription
|
||||
$this->dispatcher->dispatchTyped(new CalendarUnpublishedEvent($calendarId, $calendarData));
|
||||
return null;
|
||||
}, $this->db);
|
||||
|
||||
$this->publishStatusCache->set((string)$calendar->getResourceId(), $publishStatus ?? false);
|
||||
return $publishStatus;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param Calendar $calendar
|
||||
* @return mixed
|
||||
* @return string|false
|
||||
*/
|
||||
public function getPublishStatus($calendar) {
|
||||
$cached = $this->publishStatusCache->get((string)$calendar->getResourceId());
|
||||
if ($cached !== null) {
|
||||
return $cached;
|
||||
}
|
||||
|
||||
$query = $this->db->getQueryBuilder();
|
||||
$result = $query->select('publicuri')
|
||||
->from('dav_shares')
|
||||
@@ -3244,9 +3279,46 @@ class CalDavBackend extends AbstractBackend implements SyncSupport, Subscription
|
||||
->andWhere($query->expr()->eq('access', $query->createNamedParameter(self::ACCESS_PUBLIC)))
|
||||
->executeQuery();
|
||||
|
||||
$row = $result->fetch();
|
||||
$publishStatus = $result->fetchOne();
|
||||
$result->closeCursor();
|
||||
|
||||
$this->publishStatusCache->set((string)$calendar->getResourceId(), $publishStatus);
|
||||
return $publishStatus;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param int[] $resourceIds
|
||||
*/
|
||||
public function preloadPublishStatuses(array $resourceIds): void {
|
||||
$query = $this->db->getQueryBuilder();
|
||||
$result = $query->select('resourceid', 'publicuri')
|
||||
->from('dav_shares')
|
||||
->where($query->expr()->in(
|
||||
'resourceid',
|
||||
$query->createNamedParameter($resourceIds, IQueryBuilder::PARAM_INT_ARRAY),
|
||||
IQueryBuilder::PARAM_INT_ARRAY,
|
||||
))
|
||||
->andWhere($query->expr()->eq(
|
||||
'access',
|
||||
$query->createNamedParameter(self::ACCESS_PUBLIC, IQueryBuilder::PARAM_INT),
|
||||
IQueryBuilder::PARAM_INT,
|
||||
))
|
||||
->executeQuery();
|
||||
|
||||
$hasPublishStatuses = [];
|
||||
while ($row = $result->fetch()) {
|
||||
$this->publishStatusCache->set((string)$row['resourceid'], $row['publicuri']);
|
||||
$hasPublishStatuses[(int)$row['resourceid']] = true;
|
||||
}
|
||||
|
||||
// Also remember resources with no publish status
|
||||
foreach ($resourceIds as $resourceId) {
|
||||
if (!isset($hasPublishStatuses[$resourceId])) {
|
||||
$this->publishStatusCache->set((string)$resourceId, false);
|
||||
}
|
||||
}
|
||||
|
||||
$result->closeCursor();
|
||||
return $row ? reset($row) : false;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -3565,7 +3637,9 @@ class CalDavBackend extends AbstractBackend implements SyncSupport, Subscription
|
||||
$uri = $calendarInfo['principaluri'];
|
||||
}
|
||||
|
||||
$principalInformation = $this->principalBackend->getPrincipalByPath($uri);
|
||||
$principalInformation = $this->principalBackend->getPrincipalPropertiesByPath($uri, [
|
||||
'{DAV:}displayname',
|
||||
]);
|
||||
if (isset($principalInformation['{DAV:}displayname'])) {
|
||||
$calendarInfo[$displaynameKey] = $principalInformation['{DAV:}displayname'];
|
||||
}
|
||||
@@ -3687,4 +3761,20 @@ class CalDavBackend extends AbstractBackend implements SyncSupport, Subscription
|
||||
}
|
||||
}, $this->db);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array<string, mixed>[]
|
||||
*/
|
||||
public function getFederatedCalendarsForUser(string $principalUri): array {
|
||||
$federatedCalendars = $this->federatedCalendarMapper->findByPrincipalUri($principalUri);
|
||||
return array_map(
|
||||
static fn (FederatedCalendarEntity $entity) => $entity->toCalendarInfo(),
|
||||
$federatedCalendars,
|
||||
);
|
||||
}
|
||||
|
||||
public function getFederatedCalendarByUri(string $principalUri, string $uri): ?array {
|
||||
$federatedCalendar = $this->federatedCalendarMapper->findByUri($principalUri, $uri);
|
||||
return $federatedCalendar?->toCalendarInfo();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -64,6 +64,10 @@ class Calendar extends \Sabre\CalDAV\Calendar implements IRestorable, IShareable
|
||||
return $this->calendarInfo['uri'];
|
||||
}
|
||||
|
||||
protected function getCalendarType(): int {
|
||||
return CalDavBackend::CALENDAR_TYPE_CALENDAR;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
* @throws Forbidden
|
||||
@@ -197,7 +201,7 @@ class Calendar extends \Sabre\CalDAV\Calendar implements IRestorable, IShareable
|
||||
$this->getOwner() . '/calendar-proxy-read',
|
||||
$this->getOwner() . '/calendar-proxy-write',
|
||||
parent::getOwner(),
|
||||
'principals/system/public'
|
||||
'principals/system/public',
|
||||
];
|
||||
/** @var list<array{privilege: string, principal: string, protected: bool}> $acl */
|
||||
$acl = array_filter($acl, function (array $rule) use ($allowedPrincipals): bool {
|
||||
@@ -247,7 +251,7 @@ class Calendar extends \Sabre\CalDAV\Calendar implements IRestorable, IShareable
|
||||
}
|
||||
|
||||
public function getChild($name) {
|
||||
$obj = $this->caldavBackend->getCalendarObject($this->calendarInfo['id'], $name);
|
||||
$obj = $this->caldavBackend->getCalendarObject($this->calendarInfo['id'], $name, $this->getCalendarType());
|
||||
|
||||
if (!$obj) {
|
||||
throw new NotFound('Calendar object not found');
|
||||
@@ -263,7 +267,7 @@ class Calendar extends \Sabre\CalDAV\Calendar implements IRestorable, IShareable
|
||||
}
|
||||
|
||||
public function getChildren() {
|
||||
$objs = $this->caldavBackend->getCalendarObjects($this->calendarInfo['id']);
|
||||
$objs = $this->caldavBackend->getCalendarObjects($this->calendarInfo['id'], $this->getCalendarType());
|
||||
$children = [];
|
||||
foreach ($objs as $obj) {
|
||||
if ($obj['classification'] === CalDavBackend::CLASSIFICATION_PRIVATE && $this->isShared()) {
|
||||
@@ -276,7 +280,7 @@ class Calendar extends \Sabre\CalDAV\Calendar implements IRestorable, IShareable
|
||||
}
|
||||
|
||||
public function getMultipleChildren(array $paths) {
|
||||
$objs = $this->caldavBackend->getMultipleCalendarObjects($this->calendarInfo['id'], $paths);
|
||||
$objs = $this->caldavBackend->getMultipleCalendarObjects($this->calendarInfo['id'], $paths, $this->getCalendarType());
|
||||
$children = [];
|
||||
foreach ($objs as $obj) {
|
||||
if ($obj['classification'] === CalDavBackend::CLASSIFICATION_PRIVATE && $this->isShared()) {
|
||||
@@ -289,7 +293,7 @@ class Calendar extends \Sabre\CalDAV\Calendar implements IRestorable, IShareable
|
||||
}
|
||||
|
||||
public function childExists($name) {
|
||||
$obj = $this->caldavBackend->getCalendarObject($this->calendarInfo['id'], $name);
|
||||
$obj = $this->caldavBackend->getCalendarObject($this->calendarInfo['id'], $name, $this->getCalendarType());
|
||||
if (!$obj) {
|
||||
return false;
|
||||
}
|
||||
@@ -301,7 +305,7 @@ class Calendar extends \Sabre\CalDAV\Calendar implements IRestorable, IShareable
|
||||
}
|
||||
|
||||
public function calendarQuery(array $filters) {
|
||||
$uris = $this->caldavBackend->calendarQuery($this->calendarInfo['id'], $filters);
|
||||
$uris = $this->caldavBackend->calendarQuery($this->calendarInfo['id'], $filters, $this->getCalendarType());
|
||||
if ($this->isShared()) {
|
||||
return array_filter($uris, function ($uri) {
|
||||
return $this->childExists($uri);
|
||||
|
||||
@@ -8,6 +8,7 @@
|
||||
namespace OCA\DAV\CalDAV;
|
||||
|
||||
use OCA\DAV\AppInfo\PluginManager;
|
||||
use OCA\DAV\CalDAV\Federation\FederatedCalendarFactory;
|
||||
use OCA\DAV\CalDAV\Integration\ExternalCalendar;
|
||||
use OCA\DAV\CalDAV\Integration\ICalendarProvider;
|
||||
use OCA\DAV\CalDAV\Trashbin\TrashbinHome;
|
||||
@@ -37,12 +38,14 @@ class CalendarHome extends \Sabre\CalDAV\CalendarHome {
|
||||
|
||||
/** @var PluginManager */
|
||||
private $pluginManager;
|
||||
|
||||
private ?array $cachedChildren = null;
|
||||
|
||||
public function __construct(
|
||||
BackendInterface $caldavBackend,
|
||||
array $principalInfo,
|
||||
private LoggerInterface $logger,
|
||||
private FederatedCalendarFactory $federatedCalendarFactory,
|
||||
private bool $returnCachedSubscriptions,
|
||||
) {
|
||||
parent::__construct($caldavBackend, $principalInfo);
|
||||
@@ -102,6 +105,15 @@ class CalendarHome extends \Sabre\CalDAV\CalendarHome {
|
||||
|
||||
if ($this->caldavBackend instanceof CalDavBackend) {
|
||||
$objects[] = new TrashbinHome($this->caldavBackend, $this->principalInfo);
|
||||
|
||||
$federatedCalendars = $this->caldavBackend->getFederatedCalendarsForUser(
|
||||
$this->principalInfo['uri'],
|
||||
);
|
||||
foreach ($federatedCalendars as $federatedCalendarInfo) {
|
||||
$objects[] = $this->federatedCalendarFactory->createFederatedCalendar(
|
||||
$federatedCalendarInfo,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
// If the backend supports subscriptions, we'll add those as well,
|
||||
@@ -147,13 +159,22 @@ class CalendarHome extends \Sabre\CalDAV\CalendarHome {
|
||||
return new TrashbinHome($this->caldavBackend, $this->principalInfo);
|
||||
}
|
||||
|
||||
// Calendar - this covers all "regular" calendars, but not shared
|
||||
// only check if the method is available
|
||||
// Only check if the methods are available
|
||||
if ($this->caldavBackend instanceof CalDavBackend) {
|
||||
// Calendar - this covers all "regular" calendars, but not shared
|
||||
$calendar = $this->caldavBackend->getCalendarByUri($this->principalInfo['uri'], $name);
|
||||
if (!empty($calendar)) {
|
||||
return new Calendar($this->caldavBackend, $calendar, $this->l10n, $this->config, $this->logger);
|
||||
}
|
||||
|
||||
// Federated calendar
|
||||
$federatedCalendar = $this->caldavBackend->getFederatedCalendarByUri(
|
||||
$this->principalInfo['uri'],
|
||||
$name,
|
||||
);
|
||||
if ($federatedCalendar !== null) {
|
||||
return $this->federatedCalendarFactory->createFederatedCalendar($federatedCalendar);
|
||||
}
|
||||
}
|
||||
|
||||
// Fallback to cover shared calendars
|
||||
|
||||
@@ -11,6 +11,7 @@ namespace OCA\DAV\CalDAV;
|
||||
use Generator;
|
||||
use OCA\DAV\CalDAV\Auth\CustomPrincipalPlugin;
|
||||
use OCA\DAV\CalDAV\InvitationResponse\InvitationResponseServer;
|
||||
use OCA\DAV\Connector\Sabre\Server;
|
||||
use OCP\Calendar\CalendarExportOptions;
|
||||
use OCP\Calendar\Exceptions\CalendarException;
|
||||
use OCP\Calendar\ICalendarExport;
|
||||
@@ -23,11 +24,12 @@ use OCP\Constants;
|
||||
use Sabre\CalDAV\Xml\Property\ScheduleCalendarTransp;
|
||||
use Sabre\DAV\Exception\Conflict;
|
||||
use Sabre\VObject\Component\VCalendar;
|
||||
use Sabre\VObject\Component\VEvent;
|
||||
use Sabre\VObject\Component\VTimeZone;
|
||||
use Sabre\VObject\ITip\Message;
|
||||
use Sabre\VObject\ParseException;
|
||||
use Sabre\VObject\Property;
|
||||
use Sabre\VObject\Reader;
|
||||
|
||||
use function Sabre\Uri\split as uriSplit;
|
||||
|
||||
class CalendarImpl implements ICreateFromString, IHandleImipMessage, ICalendarIsWritable, ICalendarIsShared, ICalendarExport, ICalendarIsEnabled {
|
||||
@@ -39,6 +41,9 @@ class CalendarImpl implements ICreateFromString, IHandleImipMessage, ICalendarIs
|
||||
) {
|
||||
}
|
||||
|
||||
private const DAV_PROPERTY_USER_ADDRESS = '{http://sabredav.org/ns}email-address';
|
||||
private const DAV_PROPERTY_USER_ADDRESSES = '{urn:ietf:params:xml:ns:caldav}calendar-user-address-set';
|
||||
|
||||
/**
|
||||
* @return string defining the technical unique key
|
||||
* @since 13.0.0
|
||||
@@ -54,6 +59,14 @@ class CalendarImpl implements ICreateFromString, IHandleImipMessage, ICalendarIs
|
||||
return $this->calendarInfo['uri'];
|
||||
}
|
||||
|
||||
/**
|
||||
* @return string the principal URI of the calendar owner
|
||||
* @since 32.0.0
|
||||
*/
|
||||
public function getPrincipalUri(): string {
|
||||
return $this->calendarInfo['principaluri'];
|
||||
}
|
||||
|
||||
/**
|
||||
* In comparison to getKey() this function returns a human readable (maybe translated) name
|
||||
* @since 13.0.0
|
||||
@@ -161,7 +174,7 @@ class CalendarImpl implements ICreateFromString, IHandleImipMessage, ICalendarIs
|
||||
private function createFromStringInServer(
|
||||
string $name,
|
||||
string $calendarData,
|
||||
\OCA\DAV\Connector\Sabre\Server $server,
|
||||
Server $server,
|
||||
): void {
|
||||
/** @var CustomPrincipalPlugin $plugin */
|
||||
$plugin = $server->getPlugin('auth');
|
||||
@@ -209,58 +222,93 @@ class CalendarImpl implements ICreateFromString, IHandleImipMessage, ICalendarIs
|
||||
* @throws CalendarException
|
||||
*/
|
||||
public function handleIMipMessage(string $name, string $calendarData): void {
|
||||
$server = $this->getInvitationResponseServer();
|
||||
|
||||
/** @var CustomPrincipalPlugin $plugin */
|
||||
$plugin = $server->getServer()->getPlugin('auth');
|
||||
// we're working around the previous implementation
|
||||
// that only allowed the public system principal to be used
|
||||
// so set the custom principal here
|
||||
$plugin->setCurrentPrincipal($this->calendar->getPrincipalURI());
|
||||
|
||||
try {
|
||||
/** @var VCalendar $vObject|null */
|
||||
$vObject = Reader::read($calendarData);
|
||||
} catch (ParseException $e) {
|
||||
throw new CalendarException('iMip message could not be processed because an error occurred while parsing the iMip message', 0, $e);
|
||||
}
|
||||
// validate the iMip message
|
||||
if (!isset($vObject->METHOD)) {
|
||||
throw new CalendarException('iMip message contains no valid method');
|
||||
}
|
||||
if (!isset($vObject->VEVENT)) {
|
||||
throw new CalendarException('iMip message contains no event');
|
||||
}
|
||||
if (!isset($vObject->VEVENT->UID)) {
|
||||
throw new CalendarException('iMip message event dose not contain a UID');
|
||||
}
|
||||
if (!isset($vObject->VEVENT->ORGANIZER)) {
|
||||
throw new CalendarException('iMip message event dose not contain an organizer');
|
||||
}
|
||||
if (!isset($vObject->VEVENT->ATTENDEE)) {
|
||||
throw new CalendarException('iMip message event dose not contain an attendee');
|
||||
}
|
||||
if (empty($this->calendarInfo['uri'])) {
|
||||
throw new CalendarException('Could not write to calendar as URI parameter is missing');
|
||||
}
|
||||
// Force calendar change URI
|
||||
/** @var Schedule\Plugin $schedulingPlugin */
|
||||
$schedulingPlugin = $server->getServer()->getPlugin('caldav-schedule');
|
||||
// Let sabre handle the rest
|
||||
$iTipMessage = new Message();
|
||||
/** @var VCalendar $vObject */
|
||||
$vObject = Reader::read($calendarData);
|
||||
/** @var VEvent $vEvent */
|
||||
$vEvent = $vObject->{'VEVENT'};
|
||||
|
||||
if ($vObject->{'METHOD'} === null) {
|
||||
throw new CalendarException('No Method provided for scheduling data. Could not process message');
|
||||
}
|
||||
|
||||
if (!isset($vEvent->{'ORGANIZER'}) || !isset($vEvent->{'ATTENDEE'})) {
|
||||
throw new CalendarException('Could not process scheduling data, neccessary data missing from ICAL');
|
||||
}
|
||||
$organizer = $vEvent->{'ORGANIZER'}->getValue();
|
||||
$attendee = $vEvent->{'ATTENDEE'}->getValue();
|
||||
|
||||
$iTipMessage->method = $vObject->{'METHOD'}->getValue();
|
||||
if ($iTipMessage->method === 'REQUEST') {
|
||||
$iTipMessage->sender = $organizer;
|
||||
$iTipMessage->recipient = $attendee;
|
||||
} elseif ($iTipMessage->method === 'REPLY') {
|
||||
if ($server->isExternalAttendee($vEvent->{'ATTENDEE'}->getValue())) {
|
||||
$iTipMessage->recipient = $organizer;
|
||||
} else {
|
||||
$iTipMessage->recipient = $attendee;
|
||||
// construct dav server
|
||||
$server = $this->getInvitationResponseServer();
|
||||
/** @var CustomPrincipalPlugin $authPlugin */
|
||||
$authPlugin = $server->getServer()->getPlugin('auth');
|
||||
// we're working around the previous implementation
|
||||
// that only allowed the public system principal to be used
|
||||
// so set the custom principal here
|
||||
$authPlugin->setCurrentPrincipal($this->calendar->getPrincipalURI());
|
||||
// retrieve all users addresses
|
||||
$userProperties = $server->getServer()->getProperties($this->calendar->getPrincipalURI(), [ self::DAV_PROPERTY_USER_ADDRESS, self::DAV_PROPERTY_USER_ADDRESSES ]);
|
||||
$userAddress = 'mailto:' . ($userProperties[self::DAV_PROPERTY_USER_ADDRESS] ?? null);
|
||||
$userAddresses = $userProperties[self::DAV_PROPERTY_USER_ADDRESSES]->getHrefs() ?? [];
|
||||
$userAddresses = array_map('strtolower', array_map('urldecode', $userAddresses));
|
||||
// validate the method, recipient and sender
|
||||
$imipMethod = strtoupper($vObject->METHOD->getValue());
|
||||
if (in_array($imipMethod, ['REPLY', 'REFRESH'], true)) {
|
||||
// extract sender (REPLY and REFRESH method should only have one attendee)
|
||||
$sender = strtolower($vObject->VEVENT->ATTENDEE->getValue());
|
||||
// extract and verify the recipient
|
||||
$recipient = strtolower($vObject->VEVENT->ORGANIZER->getValue());
|
||||
if (!in_array($recipient, $userAddresses, true)) {
|
||||
throw new CalendarException('iMip message dose not contain an organizer that matches the user');
|
||||
}
|
||||
$iTipMessage->sender = $attendee;
|
||||
} elseif ($iTipMessage->method === 'CANCEL') {
|
||||
$iTipMessage->recipient = $attendee;
|
||||
$iTipMessage->sender = $organizer;
|
||||
// if the recipient address is not the same as the user address this means an alias was used
|
||||
// the iTip broker uses the users primary email address during processing
|
||||
if ($userAddress !== $recipient) {
|
||||
$recipient = $userAddress;
|
||||
}
|
||||
} elseif (in_array($imipMethod, ['PUBLISH', 'REQUEST', 'ADD', 'CANCEL'], true)) {
|
||||
// extract sender
|
||||
$sender = strtolower($vObject->VEVENT->ORGANIZER->getValue());
|
||||
// extract and verify the recipient
|
||||
foreach ($vObject->VEVENT->ATTENDEE as $attendee) {
|
||||
$recipient = strtolower($attendee->getValue());
|
||||
if (in_array($recipient, $userAddresses, true)) {
|
||||
break;
|
||||
}
|
||||
$recipient = null;
|
||||
}
|
||||
if ($recipient === null) {
|
||||
throw new CalendarException('iMip message dose not contain an attendee that matches the user');
|
||||
}
|
||||
// if the recipient address is not the same as the user address this means an alias was used
|
||||
// the iTip broker uses the users primary email address during processing
|
||||
if ($userAddress !== $recipient) {
|
||||
$recipient = $userAddress;
|
||||
}
|
||||
} else {
|
||||
throw new CalendarException('iMip message contains a method that is not supported: ' . $imipMethod);
|
||||
}
|
||||
$iTipMessage->uid = isset($vEvent->{'UID'}) ? $vEvent->{'UID'}->getValue() : '';
|
||||
$iTipMessage->component = 'VEVENT';
|
||||
$iTipMessage->sequence = isset($vEvent->{'SEQUENCE'}) ? (int)$vEvent->{'SEQUENCE'}->getValue() : 0;
|
||||
$iTipMessage->message = $vObject;
|
||||
$server->server->emit('schedule', [$iTipMessage]);
|
||||
// generate the iTip message
|
||||
$iTip = new Message();
|
||||
$iTip->method = $imipMethod;
|
||||
$iTip->sender = $sender;
|
||||
$iTip->recipient = $recipient;
|
||||
$iTip->component = 'VEVENT';
|
||||
$iTip->uid = $vObject->VEVENT->UID->getValue();
|
||||
$iTip->sequence = isset($vObject->VEVENT->SEQUENCE) ? (int)$vObject->VEVENT->SEQUENCE->getValue() : 1;
|
||||
$iTip->message = $vObject;
|
||||
|
||||
$server->server->emit('schedule', [$iTip]);
|
||||
}
|
||||
|
||||
public function getInvitationResponseServer(): InvitationResponseServer {
|
||||
|
||||
@@ -53,7 +53,8 @@ class CalendarObject extends \Sabre\CalDAV\CalendarObject {
|
||||
}
|
||||
|
||||
// shows as busy if event is declared confidential
|
||||
if ($this->objectData['classification'] === CalDavBackend::CLASSIFICATION_CONFIDENTIAL) {
|
||||
if ($this->objectData['classification'] === CalDavBackend::CLASSIFICATION_CONFIDENTIAL
|
||||
&& ($this->isPublic() || !$this->canWrite())) {
|
||||
$this->createConfidentialObject($vObject);
|
||||
}
|
||||
|
||||
@@ -135,6 +136,10 @@ class CalendarObject extends \Sabre\CalDAV\CalendarObject {
|
||||
return true;
|
||||
}
|
||||
|
||||
private function isPublic(): bool {
|
||||
return $this->calendarInfo['{http://owncloud.org/ns}public'] ?? false;
|
||||
}
|
||||
|
||||
public function getCalendarId(): int {
|
||||
return (int)$this->objectData['calendarid'];
|
||||
}
|
||||
|
||||
@@ -8,6 +8,7 @@ declare(strict_types=1);
|
||||
*/
|
||||
namespace OCA\DAV\CalDAV;
|
||||
|
||||
use OCA\DAV\CalDAV\Federation\FederatedCalendarImpl;
|
||||
use OCA\DAV\Db\Property;
|
||||
use OCA\DAV\Db\PropertyMapper;
|
||||
use OCP\Calendar\ICalendarProvider;
|
||||
@@ -27,17 +28,24 @@ class CalendarProvider implements ICalendarProvider {
|
||||
}
|
||||
|
||||
public function getCalendars(string $principalUri, array $calendarUris = []): array {
|
||||
|
||||
/** @var array{uri: string, principaluri: string}[] $calendarInfos */
|
||||
$calendarInfos = $this->calDavBackend->getCalendarsForUser($principalUri) ?? [];
|
||||
/** @var array{uri: string, principaluri: string}[] $federatedCalendarInfos */
|
||||
$federatedCalendarInfos = $this->calDavBackend->getFederatedCalendarsForUser($principalUri);
|
||||
|
||||
if (!empty($calendarUris)) {
|
||||
$calendarInfos = array_filter($calendarInfos, function ($calendar) use ($calendarUris) {
|
||||
return in_array($calendar['uri'], $calendarUris);
|
||||
});
|
||||
|
||||
$federatedCalendarInfos = array_filter($federatedCalendarInfos, function ($federatedCalendar) use ($calendarUris) {
|
||||
return in_array($federatedCalendar['uri'], $calendarUris);
|
||||
});
|
||||
}
|
||||
|
||||
$additionalProperties = $this->getAdditionalPropertiesForCalendars($calendarInfos);
|
||||
$iCalendars = [];
|
||||
|
||||
foreach ($calendarInfos as $calendarInfo) {
|
||||
$user = str_replace('principals/users/', '', $calendarInfo['principaluri']);
|
||||
$path = 'calendars/' . $user . '/' . $calendarInfo['uri'];
|
||||
@@ -51,6 +59,20 @@ class CalendarProvider implements ICalendarProvider {
|
||||
$this->calDavBackend,
|
||||
);
|
||||
}
|
||||
|
||||
$additionalFederatedProps = $this->getAdditionalPropertiesForCalendars(
|
||||
$federatedCalendarInfos,
|
||||
);
|
||||
foreach ($federatedCalendarInfos as $calendarInfo) {
|
||||
$user = str_replace('principals/users/', '', $calendarInfo['principaluri']);
|
||||
$path = 'calendars/' . $user . '/' . $calendarInfo['uri'];
|
||||
if (isset($additionalFederatedProps[$path])) {
|
||||
$calendarInfo = array_merge($calendarInfo, $additionalProperties[$path]);
|
||||
}
|
||||
|
||||
$iCalendars[] = new FederatedCalendarImpl($calendarInfo, $this->calDavBackend);
|
||||
}
|
||||
|
||||
return $iCalendars;
|
||||
}
|
||||
|
||||
|
||||
@@ -7,8 +7,15 @@
|
||||
*/
|
||||
namespace OCA\DAV\CalDAV;
|
||||
|
||||
use OCA\DAV\CalDAV\Federation\FederatedCalendarFactory;
|
||||
use OCA\DAV\CalDAV\Federation\RemoteUserCalendarHome;
|
||||
use OCA\DAV\Connector\Sabre\Principal;
|
||||
use OCA\DAV\DAV\RemoteUserPrincipalBackend;
|
||||
use OCP\IConfig;
|
||||
use OCP\IL10N;
|
||||
use Psr\Log\LoggerInterface;
|
||||
use Sabre\CalDAV\Backend;
|
||||
use Sabre\DAV\Exception\NotFound;
|
||||
use Sabre\DAVACL\PrincipalBackend;
|
||||
|
||||
class CalendarRoot extends \Sabre\CalDAV\CalendarRoot {
|
||||
@@ -19,15 +26,30 @@ class CalendarRoot extends \Sabre\CalDAV\CalendarRoot {
|
||||
Backend\BackendInterface $caldavBackend,
|
||||
$principalPrefix,
|
||||
private LoggerInterface $logger,
|
||||
private IL10N $l10n,
|
||||
private IConfig $config,
|
||||
private FederatedCalendarFactory $federatedCalendarFactory,
|
||||
) {
|
||||
parent::__construct($principalBackend, $caldavBackend, $principalPrefix);
|
||||
}
|
||||
|
||||
public function getChildForPrincipal(array $principal) {
|
||||
[$prefix] = \Sabre\Uri\split($principal['uri']);
|
||||
if ($prefix === RemoteUserPrincipalBackend::PRINCIPAL_PREFIX) {
|
||||
return new RemoteUserCalendarHome(
|
||||
$this->caldavBackend,
|
||||
$principal,
|
||||
$this->l10n,
|
||||
$this->config,
|
||||
$this->logger,
|
||||
);
|
||||
}
|
||||
|
||||
return new CalendarHome(
|
||||
$this->caldavBackend,
|
||||
$principal,
|
||||
$this->logger,
|
||||
$this->federatedCalendarFactory,
|
||||
array_key_exists($principal['uri'], $this->returnCachedSubscriptions)
|
||||
);
|
||||
}
|
||||
@@ -40,10 +62,35 @@ class CalendarRoot extends \Sabre\CalDAV\CalendarRoot {
|
||||
return $parts[1];
|
||||
}
|
||||
|
||||
if ($this->principalPrefix === RemoteUserPrincipalBackend::PRINCIPAL_PREFIX) {
|
||||
return 'remote-calendars';
|
||||
}
|
||||
|
||||
return parent::getName();
|
||||
}
|
||||
|
||||
public function enableReturnCachedSubscriptions(string $principalUri): void {
|
||||
$this->returnCachedSubscriptions['principals/users/' . $principalUri] = true;
|
||||
}
|
||||
|
||||
public function childExists($name) {
|
||||
if (!($this->principalBackend instanceof Principal)) {
|
||||
return parent::childExists($name);
|
||||
}
|
||||
|
||||
// Fetch the most shallow version of the principal just to determine if it exists
|
||||
$principalInfo = $this->principalBackend->getPrincipalPropertiesByPath(
|
||||
$this->principalPrefix . '/' . $name,
|
||||
[],
|
||||
);
|
||||
if ($principalInfo === null) {
|
||||
return false;
|
||||
}
|
||||
|
||||
try {
|
||||
return $this->getChildForPrincipal($principalInfo) !== null;
|
||||
} catch (NotFound $e) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -13,6 +13,7 @@ use OCA\DAV\AppInfo\PluginManager;
|
||||
use OCA\DAV\CalDAV\Auth\CustomPrincipalPlugin;
|
||||
use OCA\DAV\CalDAV\Auth\PublicPrincipalPlugin;
|
||||
use OCA\DAV\CalDAV\Publishing\PublishPlugin;
|
||||
use OCA\DAV\CalDAV\Schedule\IMipPlugin;
|
||||
use OCA\DAV\Connector\Sabre\AnonymousOptionsPlugin;
|
||||
use OCA\DAV\Connector\Sabre\BlockLegacyClientPlugin;
|
||||
use OCA\DAV\Connector\Sabre\CachingTree;
|
||||
@@ -94,7 +95,7 @@ class EmbeddedCalDavServer {
|
||||
Server::get(IURLGenerator::class)
|
||||
));
|
||||
if ($appConfig->getValueString('dav', 'sendInvitations', 'yes') === 'yes') {
|
||||
$this->server->addPlugin(Server::get(\OCA\DAV\CalDAV\Schedule\IMipPlugin::class));
|
||||
$this->server->addPlugin(Server::get(IMipPlugin::class));
|
||||
}
|
||||
|
||||
// collection preload plugin
|
||||
|
||||
@@ -0,0 +1,23 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
/**
|
||||
* SPDX-FileCopyrightText: 2025 Nextcloud GmbH and Nextcloud contributors
|
||||
* SPDX-License-Identifier: AGPL-3.0-or-later
|
||||
*/
|
||||
|
||||
namespace OCA\DAV\CalDAV\Federation;
|
||||
|
||||
use OCP\AppFramework\Services\IAppConfig;
|
||||
|
||||
class CalendarFederationConfig {
|
||||
public function __construct(
|
||||
private readonly IAppConfig $appConfig,
|
||||
) {
|
||||
}
|
||||
|
||||
public function isFederationEnabled(): bool {
|
||||
return $this->appConfig->getAppValueBool('enableCalendarFederation', true);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,68 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
/**
|
||||
* SPDX-FileCopyrightText: 2025 Nextcloud GmbH and Nextcloud contributors
|
||||
* SPDX-License-Identifier: AGPL-3.0-or-later
|
||||
*/
|
||||
|
||||
namespace OCA\DAV\CalDAV\Federation;
|
||||
|
||||
use OCP\Federation\ICloudFederationFactory;
|
||||
use OCP\Federation\ICloudFederationProviderManager;
|
||||
use OCP\Federation\ICloudId;
|
||||
use OCP\Http\Client\IResponse;
|
||||
use OCP\IURLGenerator;
|
||||
use OCP\OCM\Exceptions\OCMProviderException;
|
||||
|
||||
class CalendarFederationNotifier {
|
||||
public const NOTIFICATION_SYNC_CALENDAR = 'SYNC_CALENDAR';
|
||||
|
||||
public const PROP_SYNC_CALENDAR_SHARE_WITH = 'shareWith';
|
||||
public const PROP_SYNC_CALENDAR_CALENDAR_URL = 'calendarUrl';
|
||||
|
||||
public function __construct(
|
||||
private readonly ICloudFederationFactory $federationFactory,
|
||||
private readonly ICloudFederationProviderManager $federationManager,
|
||||
private readonly IURLGenerator $url,
|
||||
) {
|
||||
}
|
||||
|
||||
/**
|
||||
* Notify a remote server to sync a calendar.
|
||||
*
|
||||
* @param ICloudId $shareWith The cloud id of the remote sharee.
|
||||
* @return IResponse
|
||||
*
|
||||
* @throws OCMProviderException If sending the notification fails.
|
||||
*/
|
||||
public function notifySyncCalendar(
|
||||
ICloudId $shareWith,
|
||||
string $calendarOwner,
|
||||
string $calendarName,
|
||||
string $sharedSecret,
|
||||
): IResponse {
|
||||
$sharedWithEncoded = base64_encode($shareWith->getId());
|
||||
$relativeCalendarUrl = "remote-calendars/$sharedWithEncoded/{$calendarName}_shared_by_$calendarOwner";
|
||||
$calendarUrl = $this->url->linkTo('', 'remote.php') . "/dav/$relativeCalendarUrl";
|
||||
$calendarUrl = $this->url->getAbsoluteURL($calendarUrl);
|
||||
|
||||
$notification = $this->federationFactory->getCloudFederationNotification();
|
||||
$notification->setMessage(
|
||||
self::NOTIFICATION_SYNC_CALENDAR,
|
||||
CalendarFederationProvider::CALENDAR_RESOURCE,
|
||||
CalendarFederationProvider::PROVIDER_ID,
|
||||
[
|
||||
'sharedSecret' => $sharedSecret,
|
||||
self::PROP_SYNC_CALENDAR_SHARE_WITH => $shareWith->getId(),
|
||||
self::PROP_SYNC_CALENDAR_CALENDAR_URL => $calendarUrl,
|
||||
],
|
||||
);
|
||||
|
||||
return $this->federationManager->sendCloudNotification(
|
||||
$shareWith->getRemote(),
|
||||
$notification,
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,203 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
/**
|
||||
* SPDX-FileCopyrightText: 2025 Nextcloud GmbH and Nextcloud contributors
|
||||
* SPDX-License-Identifier: AGPL-3.0-or-later
|
||||
*/
|
||||
|
||||
namespace OCA\DAV\CalDAV\Federation;
|
||||
|
||||
use OCA\DAV\BackgroundJob\FederatedCalendarSyncJob;
|
||||
use OCA\DAV\CalDAV\Federation\Protocol\CalendarFederationProtocolV1;
|
||||
use OCA\DAV\CalDAV\Federation\Protocol\ICalendarFederationProtocol;
|
||||
use OCA\DAV\DAV\Sharing\Backend as DavSharingBackend;
|
||||
use OCP\AppFramework\Http;
|
||||
use OCP\BackgroundJob\IJobList;
|
||||
use OCP\Constants;
|
||||
use OCP\Federation\Exceptions\BadRequestException;
|
||||
use OCP\Federation\Exceptions\ProviderCouldNotAddShareException;
|
||||
use OCP\Federation\ICloudFederationProvider;
|
||||
use OCP\Federation\ICloudFederationShare;
|
||||
use OCP\Federation\ICloudIdManager;
|
||||
use OCP\Share\Exceptions\ShareNotFound;
|
||||
use Psr\Log\LoggerInterface;
|
||||
|
||||
class CalendarFederationProvider implements ICloudFederationProvider {
|
||||
public const PROVIDER_ID = 'calendar';
|
||||
public const CALENDAR_RESOURCE = 'calendar';
|
||||
public const USER_SHARE_TYPE = 'user';
|
||||
|
||||
public function __construct(
|
||||
private readonly LoggerInterface $logger,
|
||||
private readonly FederatedCalendarMapper $federatedCalendarMapper,
|
||||
private readonly CalendarFederationConfig $calendarFederationConfig,
|
||||
private readonly IJobList $jobList,
|
||||
private readonly ICloudIdManager $cloudIdManager,
|
||||
) {
|
||||
}
|
||||
|
||||
public function getShareType(): string {
|
||||
return self::PROVIDER_ID;
|
||||
}
|
||||
|
||||
public function shareReceived(ICloudFederationShare $share): string {
|
||||
if (!$this->calendarFederationConfig->isFederationEnabled()) {
|
||||
$this->logger->debug('Received a federation invite but federation is disabled');
|
||||
throw new ProviderCouldNotAddShareException(
|
||||
'Server does not support calendar federation',
|
||||
'',
|
||||
Http::STATUS_SERVICE_UNAVAILABLE,
|
||||
);
|
||||
}
|
||||
|
||||
if (!in_array($share->getShareType(), $this->getSupportedShareTypes(), true)) {
|
||||
$this->logger->debug('Received a federation invite for invalid share type');
|
||||
throw new ProviderCouldNotAddShareException(
|
||||
'Support for sharing with non-users not implemented yet',
|
||||
'',
|
||||
Http::STATUS_NOT_IMPLEMENTED,
|
||||
);
|
||||
// TODO: Implement group shares
|
||||
}
|
||||
|
||||
$rawProtocol = $share->getProtocol();
|
||||
switch ($rawProtocol[ICalendarFederationProtocol::PROP_VERSION]) {
|
||||
case CalendarFederationProtocolV1::VERSION:
|
||||
try {
|
||||
$protocol = CalendarFederationProtocolV1::parse($rawProtocol);
|
||||
} catch (Protocol\CalendarProtocolParseException $e) {
|
||||
throw new ProviderCouldNotAddShareException(
|
||||
'Invalid protocol data (v1)',
|
||||
'',
|
||||
Http::STATUS_BAD_REQUEST,
|
||||
);
|
||||
}
|
||||
$calendarUrl = $protocol->getUrl();
|
||||
$displayName = $protocol->getDisplayName();
|
||||
$color = $protocol->getColor();
|
||||
$access = $protocol->getAccess();
|
||||
$components = $protocol->getComponents();
|
||||
break;
|
||||
default:
|
||||
throw new ProviderCouldNotAddShareException(
|
||||
'Unknown protocol version',
|
||||
'',
|
||||
Http::STATUS_BAD_REQUEST,
|
||||
);
|
||||
}
|
||||
|
||||
if (!$calendarUrl || !$displayName) {
|
||||
throw new ProviderCouldNotAddShareException(
|
||||
'Incomplete protocol data',
|
||||
'',
|
||||
Http::STATUS_BAD_REQUEST,
|
||||
);
|
||||
}
|
||||
|
||||
// TODO: implement read-write sharing
|
||||
$permissions = match ($access) {
|
||||
DavSharingBackend::ACCESS_READ => Constants::PERMISSION_READ,
|
||||
default => throw new ProviderCouldNotAddShareException(
|
||||
"Unsupported access value: $access",
|
||||
'',
|
||||
Http::STATUS_BAD_REQUEST,
|
||||
),
|
||||
};
|
||||
|
||||
// The calendar uri is the local name of the calendar. As such it must not contain slashes.
|
||||
// Just use the hashed url for simplicity here.
|
||||
// Example: calendars/foo-bar-user/<calendar-uri>
|
||||
$calendarUri = hash('md5', $calendarUrl);
|
||||
|
||||
$sharedWithPrincipal = 'principals/users/' . $share->getShareWith();
|
||||
|
||||
// Delete existing incoming federated share first
|
||||
$this->federatedCalendarMapper->deleteByUri($sharedWithPrincipal, $calendarUri);
|
||||
|
||||
$calendar = new FederatedCalendarEntity();
|
||||
$calendar->setPrincipaluri($sharedWithPrincipal);
|
||||
$calendar->setUri($calendarUri);
|
||||
$calendar->setRemoteUrl($calendarUrl);
|
||||
$calendar->setDisplayName($displayName);
|
||||
$calendar->setColor($color);
|
||||
$calendar->setToken($share->getShareSecret());
|
||||
$calendar->setSharedBy($share->getSharedBy());
|
||||
$calendar->setSharedByDisplayName($share->getSharedByDisplayName());
|
||||
$calendar->setPermissions($permissions);
|
||||
$calendar->setComponents($components);
|
||||
$calendar = $this->federatedCalendarMapper->insert($calendar);
|
||||
|
||||
$this->jobList->add(FederatedCalendarSyncJob::class, [
|
||||
FederatedCalendarSyncJob::ARGUMENT_ID => $calendar->getId(),
|
||||
]);
|
||||
|
||||
return (string)$calendar->getId();
|
||||
}
|
||||
|
||||
public function notificationReceived(
|
||||
$notificationType,
|
||||
$providerId,
|
||||
array $notification,
|
||||
): array {
|
||||
if ($providerId !== self::PROVIDER_ID) {
|
||||
throw new BadRequestException(['providerId']);
|
||||
}
|
||||
|
||||
switch ($notificationType) {
|
||||
case CalendarFederationNotifier::NOTIFICATION_SYNC_CALENDAR:
|
||||
return $this->handleSyncCalendarNotification($notification);
|
||||
default:
|
||||
return [];
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @return string[]
|
||||
*/
|
||||
public function getSupportedShareTypes(): array {
|
||||
return [self::USER_SHARE_TYPE];
|
||||
}
|
||||
|
||||
/**
|
||||
* @throws BadRequestException If notification props are missing.
|
||||
* @throws ShareNotFound If the notification is not related to a known share.
|
||||
*/
|
||||
private function handleSyncCalendarNotification(array $notification): array {
|
||||
$sharedSecret = $notification['sharedSecret'];
|
||||
$shareWithRaw = $notification[CalendarFederationNotifier::PROP_SYNC_CALENDAR_SHARE_WITH] ?? null;
|
||||
$calendarUrl = $notification[CalendarFederationNotifier::PROP_SYNC_CALENDAR_CALENDAR_URL] ?? null;
|
||||
|
||||
if ($shareWithRaw === null || $shareWithRaw === '') {
|
||||
throw new BadRequestException([CalendarFederationNotifier::PROP_SYNC_CALENDAR_SHARE_WITH]);
|
||||
}
|
||||
|
||||
if ($calendarUrl === null || $calendarUrl === '') {
|
||||
throw new BadRequestException([CalendarFederationNotifier::PROP_SYNC_CALENDAR_CALENDAR_URL]);
|
||||
}
|
||||
|
||||
try {
|
||||
$shareWith = $this->cloudIdManager->resolveCloudId($shareWithRaw);
|
||||
} catch (\InvalidArgumentException $e) {
|
||||
throw new ShareNotFound('Invalid sharee cloud id');
|
||||
}
|
||||
|
||||
$calendars = $this->federatedCalendarMapper->findByRemoteUrl(
|
||||
$calendarUrl,
|
||||
'principals/users/' . $shareWith->getUser(),
|
||||
$sharedSecret,
|
||||
);
|
||||
if (empty($calendars)) {
|
||||
throw new ShareNotFound('Calendar is not shared with the sharee');
|
||||
}
|
||||
|
||||
foreach ($calendars as $calendar) {
|
||||
$this->jobList->add(FederatedCalendarSyncJob::class, [
|
||||
FederatedCalendarSyncJob::ARGUMENT_ID => $calendar->getId(),
|
||||
]);
|
||||
}
|
||||
|
||||
return [];
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,38 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
/**
|
||||
* SPDX-FileCopyrightText: 2025 Nextcloud GmbH and Nextcloud contributors
|
||||
* SPDX-License-Identifier: AGPL-3.0-or-later
|
||||
*/
|
||||
|
||||
namespace OCA\DAV\CalDAV\Federation;
|
||||
|
||||
use OCA\DAV\CalDAV\CalDavBackend;
|
||||
use OCA\DAV\CalDAV\Calendar;
|
||||
use OCP\IConfig;
|
||||
use OCP\IL10N;
|
||||
use Psr\Log\LoggerInterface;
|
||||
use Sabre\CalDAV\Backend;
|
||||
|
||||
class FederatedCalendar extends Calendar {
|
||||
public function __construct(
|
||||
Backend\BackendInterface $caldavBackend,
|
||||
$calendarInfo,
|
||||
IL10N $l10n,
|
||||
IConfig $config,
|
||||
LoggerInterface $logger,
|
||||
private readonly FederatedCalendarMapper $federatedCalendarMapper,
|
||||
) {
|
||||
parent::__construct($caldavBackend, $calendarInfo, $l10n, $config, $logger);
|
||||
}
|
||||
|
||||
public function delete() {
|
||||
$this->federatedCalendarMapper->deleteById($this->getResourceId());
|
||||
}
|
||||
|
||||
protected function getCalendarType(): int {
|
||||
return CalDavBackend::CALENDAR_TYPE_FEDERATED;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,81 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
/**
|
||||
* SPDX-FileCopyrightText: 2025 Nextcloud GmbH and Nextcloud contributors
|
||||
* SPDX-License-Identifier: AGPL-3.0-or-later
|
||||
*/
|
||||
|
||||
namespace OCA\DAV\CalDAV\Federation;
|
||||
|
||||
use OCA\DAV\DAV\RemoteUserPrincipalBackend;
|
||||
use OCA\DAV\DAV\Sharing\SharingMapper;
|
||||
use OCP\Defaults;
|
||||
use Sabre\DAV\Auth\Backend\BackendInterface;
|
||||
use Sabre\HTTP\Auth\Basic as BasicAuth;
|
||||
use Sabre\HTTP\RequestInterface;
|
||||
use Sabre\HTTP\ResponseInterface;
|
||||
|
||||
class FederatedCalendarAuth implements BackendInterface {
|
||||
private readonly string $realm;
|
||||
|
||||
public function __construct(
|
||||
private readonly SharingMapper $sharingMapper,
|
||||
) {
|
||||
$defaults = new Defaults();
|
||||
$this->realm = $defaults->getName();
|
||||
}
|
||||
|
||||
/**
|
||||
* @return string|null A principal uri if the given combination of user and pass is valid and null otherwise.
|
||||
*/
|
||||
private function validateUserPass(
|
||||
string $requestPath,
|
||||
string $username,
|
||||
string $password,
|
||||
): ?string {
|
||||
$remoteUserPrincipalUri = RemoteUserPrincipalBackend::PRINCIPAL_PREFIX . "/$username";
|
||||
[, $remoteUserPrincipalId] = \Sabre\Uri\split($remoteUserPrincipalUri);
|
||||
|
||||
$rows = $this->sharingMapper->getSharedCalendarsForRemoteUser(
|
||||
$remoteUserPrincipalUri,
|
||||
$password,
|
||||
);
|
||||
|
||||
// Is the requested calendar actually shared with the remote user?
|
||||
foreach ($rows as $row) {
|
||||
$ownerPrincipalUri = $row['principaluri'];
|
||||
[, $ownerUserId] = \Sabre\Uri\split($ownerPrincipalUri);
|
||||
$shareUri = $row['uri'] . '_shared_by_' . $ownerUserId;
|
||||
if (str_starts_with($requestPath, "remote-calendars/$remoteUserPrincipalId/$shareUri")) {
|
||||
// Yes? -> return early
|
||||
return $remoteUserPrincipalUri;
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
public function check(RequestInterface $request, ResponseInterface $response): array {
|
||||
if (!str_starts_with($request->getPath(), 'remote-calendars/')) {
|
||||
return [false, 'This request is not for a federated calendar'];
|
||||
}
|
||||
|
||||
$auth = new BasicAuth($this->realm, $request, $response);
|
||||
$userpass = $auth->getCredentials();
|
||||
if ($userpass === null || count($userpass) !== 2) {
|
||||
return [false, "No 'Authorization: Basic' header found. Either the client didn't send one, or the server is misconfigured"];
|
||||
}
|
||||
$principal = $this->validateUserPass($request->getPath(), $userpass[0], $userpass[1]);
|
||||
if ($principal === null) {
|
||||
return [false, 'Username or password was incorrect'];
|
||||
}
|
||||
|
||||
return [true, $principal];
|
||||
}
|
||||
|
||||
public function challenge(RequestInterface $request, ResponseInterface $response): void {
|
||||
// No special challenge is needed here
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,101 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
/**
|
||||
* SPDX-FileCopyrightText: 2025 Nextcloud GmbH and Nextcloud contributors
|
||||
* SPDX-License-Identifier: AGPL-3.0-or-later
|
||||
*/
|
||||
|
||||
namespace OCA\DAV\CalDAV\Federation;
|
||||
|
||||
use OCA\DAV\DAV\RemoteUserPrincipalBackend;
|
||||
use OCP\AppFramework\Db\Entity;
|
||||
use OCP\DB\Types;
|
||||
use Sabre\CalDAV\Xml\Property\SupportedCalendarComponentSet;
|
||||
|
||||
/**
|
||||
* @method string getPrincipaluri()
|
||||
* @method void setPrincipaluri(string $principaluri)
|
||||
* @method string getUri()
|
||||
* @method void setUri(string $uri)
|
||||
* @method string getDisplayName()
|
||||
* @method void setDisplayName(string $displayName)
|
||||
* @method string|null getColor()
|
||||
* @method void setColor(string|null $color)
|
||||
* @method int getPermissions()
|
||||
* @method void setPermissions(int $permissions)
|
||||
* @method int getSyncToken()
|
||||
* @method void setSyncToken(int $syncToken)
|
||||
* @method string getRemoteUrl()
|
||||
* @method void setRemoteUrl(string $remoteUrl)
|
||||
* @method string getToken()
|
||||
* @method void setToken(string $token)
|
||||
* @method int|null getLastSync()
|
||||
* @method void setLastSync(int|null $lastSync)
|
||||
* @method string getSharedBy()
|
||||
* @method void setSharedBy(string $sharedBy)
|
||||
* @method string getSharedByDisplayName()
|
||||
* @method void setSharedByDisplayName(string $sharedByDisplayName)
|
||||
* @method string getComponents()
|
||||
* @method void setComponents(string $components)
|
||||
*/
|
||||
class FederatedCalendarEntity extends Entity {
|
||||
protected string $principaluri = '';
|
||||
protected string $uri = '';
|
||||
protected string $displayName = '';
|
||||
protected ?string $color = null;
|
||||
protected int $permissions = 0;
|
||||
protected int $syncToken = 0;
|
||||
protected string $remoteUrl = '';
|
||||
protected string $token = '';
|
||||
protected ?int $lastSync = null;
|
||||
protected string $sharedBy = '';
|
||||
protected string $sharedByDisplayName = '';
|
||||
protected string $components = '';
|
||||
|
||||
public function __construct() {
|
||||
$this->addType('principaluri', Types::STRING);
|
||||
$this->addType('uri', Types::STRING);
|
||||
$this->addType('color', Types::STRING);
|
||||
$this->addType('displayName', Types::STRING);
|
||||
$this->addType('permissions', Types::INTEGER);
|
||||
$this->addType('syncToken', Types::INTEGER);
|
||||
$this->addType('remoteUrl', Types::STRING);
|
||||
$this->addType('token', Types::STRING);
|
||||
$this->addType('lastSync', Types::INTEGER);
|
||||
$this->addType('sharedBy', Types::STRING);
|
||||
$this->addType('sharedByDisplayName', Types::STRING);
|
||||
$this->addType('components', Types::STRING);
|
||||
}
|
||||
|
||||
public function getSyncTokenForSabre(): string {
|
||||
return 'http://sabre.io/ns/sync/' . $this->getSyncToken();
|
||||
}
|
||||
|
||||
public function getSharedByPrincipal(): string {
|
||||
return RemoteUserPrincipalBackend::PRINCIPAL_PREFIX . '/' . base64_encode($this->getSharedBy());
|
||||
}
|
||||
|
||||
public function getSupportedCalendarComponentSet(): SupportedCalendarComponentSet {
|
||||
$components = explode(',', $this->getComponents());
|
||||
return new SupportedCalendarComponentSet($components);
|
||||
}
|
||||
|
||||
public function toCalendarInfo(): array {
|
||||
return [
|
||||
'id' => $this->getId(),
|
||||
'uri' => $this->getUri(),
|
||||
'principaluri' => $this->getPrincipaluri(),
|
||||
'federated' => 1,
|
||||
|
||||
'{DAV:}displayname' => $this->getDisplayName(),
|
||||
'{http://sabredav.org/ns}sync-token' => $this->getSyncToken(),
|
||||
'{' . \Sabre\CalDAV\Plugin::NS_CALENDARSERVER . '}getctag' => $this->getSyncTokenForSabre(),
|
||||
'{' . \Sabre\CalDAV\Plugin::NS_CALDAV . '}supported-calendar-component-set' => $this->getSupportedCalendarComponentSet(),
|
||||
'{' . \OCA\DAV\DAV\Sharing\Plugin::NS_OWNCLOUD . '}owner-principal' => $this->getSharedByPrincipal(),
|
||||
// TODO: implement read-write sharing
|
||||
'{' . \OCA\DAV\DAV\Sharing\Plugin::NS_OWNCLOUD . '}read-only' => 1
|
||||
];
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,42 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
/**
|
||||
* SPDX-FileCopyrightText: 2025 Nextcloud GmbH and Nextcloud contributors
|
||||
* SPDX-License-Identifier: AGPL-3.0-or-later
|
||||
*/
|
||||
|
||||
namespace OCA\DAV\CalDAV\Federation;
|
||||
|
||||
use OCA\DAV\AppInfo\Application;
|
||||
use OCA\DAV\CalDAV\CalDavBackend;
|
||||
use OCP\IConfig;
|
||||
use OCP\IL10N;
|
||||
use OCP\L10N\IFactory as IL10NFactory;
|
||||
use Psr\Log\LoggerInterface;
|
||||
|
||||
class FederatedCalendarFactory {
|
||||
private readonly IL10N $l10n;
|
||||
|
||||
public function __construct(
|
||||
private readonly CalDavBackend $caldavBackend,
|
||||
private readonly IConfig $config,
|
||||
private readonly LoggerInterface $logger,
|
||||
private readonly FederatedCalendarMapper $federatedCalendarMapper,
|
||||
IL10NFactory $l10nFactory,
|
||||
) {
|
||||
$this->l10n = $l10nFactory->get(Application::APP_ID);
|
||||
}
|
||||
|
||||
public function createFederatedCalendar(array $calendarInfo): FederatedCalendar {
|
||||
return new FederatedCalendar(
|
||||
$this->caldavBackend,
|
||||
$calendarInfo,
|
||||
$this->l10n,
|
||||
$this->config,
|
||||
$this->logger,
|
||||
$this->federatedCalendarMapper,
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,73 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
/**
|
||||
* SPDX-FileCopyrightText: 2025 Nextcloud GmbH and Nextcloud contributors
|
||||
* SPDX-License-Identifier: AGPL-3.0-or-later
|
||||
*/
|
||||
|
||||
namespace OCA\DAV\CalDAV\Federation;
|
||||
|
||||
use OCA\DAV\CalDAV\CalDavBackend;
|
||||
use OCP\Calendar\ICalendar;
|
||||
use OCP\Calendar\ICalendarIsEnabled;
|
||||
use OCP\Calendar\ICalendarIsShared;
|
||||
use OCP\Calendar\ICalendarIsWritable;
|
||||
use OCP\Constants;
|
||||
|
||||
class FederatedCalendarImpl implements ICalendar, ICalendarIsShared, ICalendarIsWritable, ICalendarIsEnabled {
|
||||
public function __construct(
|
||||
private readonly array $calendarInfo,
|
||||
private readonly CalDavBackend $calDavBackend,
|
||||
) {
|
||||
}
|
||||
|
||||
public function getKey(): string {
|
||||
return (string)$this->calendarInfo['id'];
|
||||
}
|
||||
|
||||
public function getUri(): string {
|
||||
return $this->calendarInfo['uri'];
|
||||
}
|
||||
|
||||
public function getDisplayName(): ?string {
|
||||
return $this->calendarInfo['{DAV:}displayname'];
|
||||
}
|
||||
|
||||
public function getDisplayColor(): ?string {
|
||||
return $this->calendarInfo['{http://apple.com/ns/ical/}calendar-color'];
|
||||
}
|
||||
|
||||
public function search(string $pattern, array $searchProperties = [], array $options = [], ?int $limit = null, ?int $offset = null): array {
|
||||
return $this->calDavBackend->search(
|
||||
$this->calendarInfo,
|
||||
$pattern,
|
||||
$searchProperties,
|
||||
$options,
|
||||
$limit,
|
||||
$offset,
|
||||
);
|
||||
}
|
||||
|
||||
public function getPermissions(): int {
|
||||
// TODO: implement read-write sharing
|
||||
return Constants::PERMISSION_READ;
|
||||
}
|
||||
|
||||
public function isDeleted(): bool {
|
||||
return false;
|
||||
}
|
||||
|
||||
public function isShared(): bool {
|
||||
return true;
|
||||
}
|
||||
|
||||
public function isWritable(): bool {
|
||||
return false;
|
||||
}
|
||||
|
||||
public function isEnabled(): bool {
|
||||
return $this->calendarInfo['{http://owncloud.org/ns}calendar-enabled'] ?? true;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,209 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
/**
|
||||
* SPDX-FileCopyrightText: 2025 Nextcloud GmbH and Nextcloud contributors
|
||||
* SPDX-License-Identifier: AGPL-3.0-or-later
|
||||
*/
|
||||
|
||||
namespace OCA\DAV\CalDAV\Federation;
|
||||
|
||||
use OCP\AppFramework\Db\DoesNotExistException;
|
||||
use OCP\AppFramework\Db\MultipleObjectsReturnedException;
|
||||
use OCP\AppFramework\Db\QBMapper;
|
||||
use OCP\AppFramework\Utility\ITimeFactory;
|
||||
use OCP\DB\QueryBuilder\IQueryBuilder;
|
||||
use OCP\IDBConnection;
|
||||
|
||||
/** @template-extends QBMapper<FederatedCalendarEntity> */
|
||||
class FederatedCalendarMapper extends QBMapper {
|
||||
public const TABLE_NAME = 'calendars_federated';
|
||||
|
||||
public function __construct(
|
||||
IDBConnection $db,
|
||||
private readonly ITimeFactory $time,
|
||||
) {
|
||||
parent::__construct($db, self::TABLE_NAME, FederatedCalendarEntity::class);
|
||||
}
|
||||
|
||||
/**
|
||||
* @throws DoesNotExistException If there is no federated calendar with the given id.
|
||||
*/
|
||||
public function find(int $id): FederatedCalendarEntity {
|
||||
$qb = $this->db->getQueryBuilder();
|
||||
$qb->select('*')
|
||||
->from(self::TABLE_NAME)
|
||||
->where($qb->expr()->eq(
|
||||
'id',
|
||||
$qb->createNamedParameter($id, IQueryBuilder::PARAM_INT),
|
||||
IQueryBuilder::PARAM_INT,
|
||||
));
|
||||
return $this->findEntity($qb);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return FederatedCalendarEntity[]
|
||||
*/
|
||||
public function findByPrincipalUri(string $principalUri): array {
|
||||
$qb = $this->db->getQueryBuilder();
|
||||
$qb->select('*')
|
||||
->from(self::TABLE_NAME)
|
||||
->where($qb->expr()->eq(
|
||||
'principaluri',
|
||||
$qb->createNamedParameter($principalUri, IQueryBuilder::PARAM_STR),
|
||||
IQueryBuilder::PARAM_STR,
|
||||
));
|
||||
return $this->findEntities($qb);
|
||||
}
|
||||
|
||||
public function findByUri(string $principalUri, string $uri): ?FederatedCalendarEntity {
|
||||
$qb = $this->db->getQueryBuilder();
|
||||
$qb->select('*')
|
||||
->from(self::TABLE_NAME)
|
||||
->where($qb->expr()->eq(
|
||||
'principaluri',
|
||||
$qb->createNamedParameter($principalUri, IQueryBuilder::PARAM_STR),
|
||||
IQueryBuilder::PARAM_STR,
|
||||
))
|
||||
->andWhere($qb->expr()->eq(
|
||||
'uri',
|
||||
$qb->createNamedParameter($uri, IQueryBuilder::PARAM_STR),
|
||||
IQueryBuilder::PARAM_STR,
|
||||
));
|
||||
|
||||
try {
|
||||
return $this->findEntity($qb);
|
||||
} catch (DoesNotExistException $e) {
|
||||
return null;
|
||||
} catch (MultipleObjectsReturnedException $e) {
|
||||
// Should never happen
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @return FederatedCalendarEntity[]
|
||||
*/
|
||||
public function findUnsyncedSinceBefore(int $beforeTimestamp): array {
|
||||
$qb = $this->db->getQueryBuilder();
|
||||
$qb->select('*')
|
||||
->from(self::TABLE_NAME)
|
||||
->where($qb->expr()->lt(
|
||||
'last_sync',
|
||||
$qb->createNamedParameter($beforeTimestamp, IQueryBuilder::PARAM_INT),
|
||||
IQueryBuilder::PARAM_INT,
|
||||
))
|
||||
// Omit unsynced calendars for now as they are synced by a separate job
|
||||
->andWhere($qb->expr()->isNotNull('last_sync'));
|
||||
return $this->findEntities($qb);
|
||||
}
|
||||
|
||||
public function deleteById(int $id): void {
|
||||
$qb = $this->db->getQueryBuilder();
|
||||
$qb->delete(self::TABLE_NAME)
|
||||
->where($qb->expr()->eq(
|
||||
'id',
|
||||
$qb->createNamedParameter($id, IQueryBuilder::PARAM_INT),
|
||||
IQueryBuilder::PARAM_INT,
|
||||
));
|
||||
$qb->executeStatement();
|
||||
}
|
||||
|
||||
public function updateSyncTime(int $id): void {
|
||||
$now = $this->time->getTime();
|
||||
|
||||
$qb = $this->db->getQueryBuilder();
|
||||
$qb->update(self::TABLE_NAME)
|
||||
->set('last_sync', $qb->createNamedParameter($now, IQueryBuilder::PARAM_INT))
|
||||
->where($qb->expr()->eq(
|
||||
'id',
|
||||
$qb->createNamedParameter($id, IQueryBuilder::PARAM_INT),
|
||||
IQueryBuilder::PARAM_INT,
|
||||
));
|
||||
$qb->executeStatement();
|
||||
}
|
||||
|
||||
public function updateSyncTokenAndTime(int $id, int $syncToken): void {
|
||||
$now = $this->time->getTime();
|
||||
|
||||
$qb = $this->db->getQueryBuilder();
|
||||
$qb->update(self::TABLE_NAME)
|
||||
->set('sync_token', $qb->createNamedParameter($syncToken, IQueryBuilder::PARAM_INT))
|
||||
->set('last_sync', $qb->createNamedParameter($now, IQueryBuilder::PARAM_INT))
|
||||
->where($qb->expr()->eq(
|
||||
'id',
|
||||
$qb->createNamedParameter($id, IQueryBuilder::PARAM_INT),
|
||||
IQueryBuilder::PARAM_INT,
|
||||
));
|
||||
$qb->executeStatement();
|
||||
}
|
||||
|
||||
/**
|
||||
* @return \Generator<mixed, FederatedCalendarEntity>
|
||||
*/
|
||||
public function findAll(): \Generator {
|
||||
$qb = $this->db->getQueryBuilder();
|
||||
$qb->select('*')
|
||||
->from(self::TABLE_NAME);
|
||||
|
||||
$result = $qb->executeQuery();
|
||||
while ($row = $result->fetch()) {
|
||||
yield $this->mapRowToEntity($row);
|
||||
}
|
||||
$result->closeCursor();
|
||||
}
|
||||
|
||||
public function countAll(): int {
|
||||
$qb = $this->db->getQueryBuilder();
|
||||
$qb->select($qb->func()->count('*'))
|
||||
->from(self::TABLE_NAME);
|
||||
$result = $qb->executeQuery();
|
||||
$count = (int)$result->fetchOne();
|
||||
$result->closeCursor();
|
||||
return $count;
|
||||
}
|
||||
|
||||
public function deleteByUri(string $principalUri, string $uri): void {
|
||||
$qb = $this->db->getQueryBuilder();
|
||||
$qb->delete(self::TABLE_NAME)
|
||||
->where($qb->expr()->eq(
|
||||
'principaluri',
|
||||
$qb->createNamedParameter($principalUri, IQueryBuilder::PARAM_STR),
|
||||
IQueryBuilder::PARAM_STR,
|
||||
))
|
||||
->andWhere($qb->expr()->eq(
|
||||
'uri',
|
||||
$qb->createNamedParameter($uri, IQueryBuilder::PARAM_STR),
|
||||
IQueryBuilder::PARAM_STR,
|
||||
));
|
||||
|
||||
$qb->executeStatement();
|
||||
}
|
||||
|
||||
/**
|
||||
* @return FederatedCalendarEntity[]
|
||||
*/
|
||||
public function findByRemoteUrl(string $remoteUrl, string $principalUri, string $token): array {
|
||||
$qb = $this->db->getQueryBuilder();
|
||||
$qb->select('*')
|
||||
->from(self::TABLE_NAME)
|
||||
->where($qb->expr()->eq(
|
||||
'remote_url',
|
||||
$qb->createNamedParameter($remoteUrl, IQueryBuilder::PARAM_STR),
|
||||
IQueryBuilder::PARAM_STR,
|
||||
))
|
||||
->andWhere($qb->expr()->eq(
|
||||
'principaluri',
|
||||
$qb->createNamedParameter($principalUri, IQueryBuilder::PARAM_STR),
|
||||
IQueryBuilder::PARAM_STR,
|
||||
))
|
||||
->andWhere($qb->expr()->eq(
|
||||
'token',
|
||||
$qb->createNamedParameter($token, IQueryBuilder::PARAM_STR),
|
||||
IQueryBuilder::PARAM_STR,
|
||||
));
|
||||
|
||||
return $this->findEntities($qb);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,76 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
/**
|
||||
* SPDX-FileCopyrightText: 2025 Nextcloud GmbH and Nextcloud contributors
|
||||
* SPDX-License-Identifier: AGPL-3.0-or-later
|
||||
*/
|
||||
|
||||
namespace OCA\DAV\CalDAV\Federation;
|
||||
|
||||
use OCA\DAV\CalDAV\SyncService as CalDavSyncService;
|
||||
use OCP\Federation\ICloudIdManager;
|
||||
use Psr\Http\Client\ClientExceptionInterface;
|
||||
use Psr\Log\LoggerInterface;
|
||||
|
||||
class FederatedCalendarSyncService {
|
||||
private const SYNC_TOKEN_PREFIX = 'http://sabre.io/ns/sync/';
|
||||
|
||||
public function __construct(
|
||||
private readonly FederatedCalendarMapper $federatedCalendarMapper,
|
||||
private readonly LoggerInterface $logger,
|
||||
private readonly CalDavSyncService $syncService,
|
||||
private readonly ICloudIdManager $cloudIdManager,
|
||||
) {
|
||||
}
|
||||
|
||||
/**
|
||||
* @return int Downloaded event count (created or updated).
|
||||
*
|
||||
* @throws ClientExceptionInterface If syncing the calendar fails.
|
||||
*/
|
||||
public function syncOne(FederatedCalendarEntity $calendar): int {
|
||||
[,, $sharedWith] = explode('/', $calendar->getPrincipaluri());
|
||||
$calDavUser = $this->cloudIdManager->getCloudId($sharedWith, null)->getId();
|
||||
$remoteUrl = $calendar->getRemoteUrl();
|
||||
$syncToken = $calendar->getSyncTokenForSabre();
|
||||
|
||||
// Need to encode the cloud id as it might contain a colon which is not allowed in basic
|
||||
// auth according to RFC 7617
|
||||
$calDavUser = base64_encode($calDavUser);
|
||||
|
||||
$syncResponse = $this->syncService->syncRemoteCalendar(
|
||||
$remoteUrl,
|
||||
$calDavUser,
|
||||
$calendar->getToken(),
|
||||
$syncToken,
|
||||
$calendar,
|
||||
);
|
||||
|
||||
$newSyncToken = $syncResponse->getSyncToken();
|
||||
|
||||
// Check sync token format and extract the actual sync token integer
|
||||
$matches = [];
|
||||
if (!preg_match('/^http:\/\/sabre\.io\/ns\/sync\/([0-9]+)$/', $newSyncToken, $matches)) {
|
||||
$this->logger->error("Failed to sync federated calendar at $remoteUrl: New sync token has unexpected format: $newSyncToken", [
|
||||
'calendar' => $calendar->toCalendarInfo(),
|
||||
'newSyncToken' => $newSyncToken,
|
||||
]);
|
||||
return 0;
|
||||
}
|
||||
|
||||
$newSyncToken = (int)$matches[1];
|
||||
if ($newSyncToken !== $calendar->getSyncToken()) {
|
||||
$this->federatedCalendarMapper->updateSyncTokenAndTime(
|
||||
$calendar->getId(),
|
||||
$newSyncToken,
|
||||
);
|
||||
} else {
|
||||
$this->logger->debug("Sync Token for $remoteUrl unchanged from previous sync");
|
||||
$this->federatedCalendarMapper->updateSyncTime($calendar->getId());
|
||||
}
|
||||
|
||||
return $syncResponse->getDownloadedEvents();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,180 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
/**
|
||||
* SPDX-FileCopyrightText: 2025 Nextcloud GmbH and Nextcloud contributors
|
||||
* SPDX-License-Identifier: AGPL-3.0-or-later
|
||||
*/
|
||||
|
||||
namespace OCA\DAV\CalDAV\Federation;
|
||||
|
||||
use OCA\DAV\CalDAV\Federation\Protocol\CalendarFederationProtocolV1;
|
||||
use OCA\DAV\DAV\RemoteUserPrincipalBackend;
|
||||
use OCA\DAV\DAV\Sharing\IShareable;
|
||||
use OCA\DAV\DAV\Sharing\SharingMapper;
|
||||
use OCP\AppFramework\Http;
|
||||
use OCP\Federation\ICloudFederationFactory;
|
||||
use OCP\Federation\ICloudFederationProviderManager;
|
||||
use OCP\IURLGenerator;
|
||||
use OCP\IUserManager;
|
||||
use OCP\OCM\Exceptions\OCMProviderException;
|
||||
use OCP\Security\ISecureRandom;
|
||||
use Psr\Log\LoggerInterface;
|
||||
use Sabre\CalDAV\Calendar;
|
||||
|
||||
// TODO: Convert this to an abstract service like the addressbook/calendar sharing services once we
|
||||
// support addressbook federation as well.
|
||||
class FederationSharingService {
|
||||
public function __construct(
|
||||
private readonly ICloudFederationProviderManager $federationManager,
|
||||
private readonly ICloudFederationFactory $federationFactory,
|
||||
private readonly IUserManager $userManager,
|
||||
private readonly IURLGenerator $url,
|
||||
private readonly LoggerInterface $logger,
|
||||
private readonly ISecureRandom $random,
|
||||
private readonly SharingMapper $sharingMapper,
|
||||
) {
|
||||
}
|
||||
|
||||
/**
|
||||
* Decode a (base64) encoded remote user principal and return the remote user's cloud id. Will
|
||||
* return null if the given principal is not belonging to a remote user (or has an invalid
|
||||
* format).
|
||||
*
|
||||
* The remote user/cloud id needs to be encoded as it might contain slashes.
|
||||
*/
|
||||
private function decodeRemoteUserPrincipal(string $principal): ?string {
|
||||
// Expected format: principals/remote-users/abcdef123
|
||||
[$prefix, $collection, $encodedId] = explode('/', $principal);
|
||||
if ($prefix !== 'principals' || $collection !== 'remote-users') {
|
||||
return null;
|
||||
}
|
||||
|
||||
$decodedId = base64_decode($encodedId);
|
||||
if (!is_string($decodedId)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return $decodedId;
|
||||
}
|
||||
|
||||
/**
|
||||
* Send a calendar share to a remote instance and create a federated share locally if it is
|
||||
* accepted.
|
||||
*
|
||||
* @param IShareable $shareable The calendar to be shared.
|
||||
* @param string $principal The principal to share with (should be a remote user principal).
|
||||
* @param int $access The access level. The remote serve might reject it.
|
||||
*/
|
||||
public function shareWith(IShareable $shareable, string $principal, int $access): void {
|
||||
$baseError = 'Failed to create federated calendar share: ';
|
||||
|
||||
// 1. Validate share data
|
||||
$shareWith = $this->decodeRemoteUserPrincipal($principal);
|
||||
if ($shareWith === null) {
|
||||
$this->logger->error($baseError . 'Principal of sharee is not belonging to a remote user', [
|
||||
'shareable' => $shareable->getName(),
|
||||
'encodedShareWith' => $principal,
|
||||
]);
|
||||
return;
|
||||
}
|
||||
|
||||
[,, $ownerUid] = explode('/', $shareable->getOwner());
|
||||
$owner = $this->userManager->get($ownerUid);
|
||||
if ($owner === null) {
|
||||
$this->logger->error($baseError . 'Shareable is not owned by a user on this server', [
|
||||
'shareable' => $shareable->getName(),
|
||||
'shareWith' => $shareWith,
|
||||
]);
|
||||
return;
|
||||
}
|
||||
|
||||
// Need a calendar instance to extract properties for the protocol
|
||||
$calendar = $shareable;
|
||||
if (!($calendar instanceof Calendar)) {
|
||||
$this->logger->error($baseError . 'Shareable is not a calendar', [
|
||||
'shareable' => $shareable->getName(),
|
||||
'owner' => $owner,
|
||||
'shareWith' => $shareWith,
|
||||
]);
|
||||
return;
|
||||
}
|
||||
|
||||
$getProp = static fn (string $prop) => $calendar->getProperties([$prop])[$prop] ?? null;
|
||||
|
||||
$displayName = $getProp('{DAV:}displayname') ?? '';
|
||||
|
||||
$token = $this->random->generate(32);
|
||||
$share = $this->federationFactory->getCloudFederationShare(
|
||||
$shareWith,
|
||||
$shareable->getName(),
|
||||
$displayName,
|
||||
CalendarFederationProvider::PROVIDER_ID,
|
||||
// Resharing is not possible so the owner is always the sharer
|
||||
$owner->getCloudId(),
|
||||
$owner->getDisplayName(),
|
||||
$owner->getCloudId(),
|
||||
$owner->getDisplayName(),
|
||||
$token,
|
||||
CalendarFederationProvider::USER_SHARE_TYPE,
|
||||
CalendarFederationProvider::CALENDAR_RESOURCE,
|
||||
);
|
||||
|
||||
// 2. Send share to federated instance
|
||||
$shareWithEncoded = base64_encode($shareWith);
|
||||
$relativeCalendarUrl = "remote-calendars/$shareWithEncoded/" . $calendar->getName() . '_shared_by_' . $ownerUid;
|
||||
$calendarUrl = $this->url->linkTo('', 'remote.php') . "/dav/$relativeCalendarUrl";
|
||||
$calendarUrl = $this->url->getAbsoluteURL($calendarUrl);
|
||||
$protocol = new CalendarFederationProtocolV1();
|
||||
$protocol->setUrl($calendarUrl);
|
||||
$protocol->setDisplayName($displayName);
|
||||
$protocol->setColor($getProp('{http://apple.com/ns/ical/}calendar-color'));
|
||||
$protocol->setAccess($access);
|
||||
$protocol->setComponents(implode(',', $getProp(
|
||||
'{urn:ietf:params:xml:ns:caldav}supported-calendar-component-set')?->getValue() ?? [],
|
||||
));
|
||||
$share->setProtocol([
|
||||
// Preserve original protocol contents
|
||||
...$share->getProtocol(),
|
||||
...$protocol->toProtocol(),
|
||||
]);
|
||||
|
||||
try {
|
||||
$response = $this->federationManager->sendCloudShare($share);
|
||||
} catch (OCMProviderException $e) {
|
||||
$this->logger->error($baseError . $e->getMessage(), [
|
||||
'exception' => $e,
|
||||
'owner' => $owner->getUID(),
|
||||
'calendar' => $shareable->getName(),
|
||||
'shareWith' => $shareWith,
|
||||
]);
|
||||
return;
|
||||
}
|
||||
|
||||
if ($response->getStatusCode() !== Http::STATUS_CREATED) {
|
||||
$this->logger->error($baseError . 'Server replied with code ' . $response->getStatusCode(), [
|
||||
'responseBody' => $response->getBody(),
|
||||
'owner' => $owner->getUID(),
|
||||
'calendar' => $shareable->getName(),
|
||||
'shareWith' => $shareWith,
|
||||
]);
|
||||
return;
|
||||
}
|
||||
|
||||
// 3. Create a local DAV share to track the token for authentication
|
||||
$shareWithPrincipalUri = RemoteUserPrincipalBackend::PRINCIPAL_PREFIX . '/' . $shareWithEncoded;
|
||||
$this->sharingMapper->deleteShare(
|
||||
$shareable->getResourceId(),
|
||||
'calendar',
|
||||
$shareWithPrincipalUri,
|
||||
);
|
||||
$this->sharingMapper->shareWithToken(
|
||||
$shareable->getResourceId(),
|
||||
'calendar',
|
||||
$access,
|
||||
$shareWithPrincipalUri,
|
||||
$token,
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,125 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
/**
|
||||
* SPDX-FileCopyrightText: 2025 Nextcloud GmbH and Nextcloud contributors
|
||||
* SPDX-License-Identifier: AGPL-3.0-or-later
|
||||
*/
|
||||
|
||||
namespace OCA\DAV\CalDAV\Federation\Protocol;
|
||||
|
||||
class CalendarFederationProtocolV1 implements ICalendarFederationProtocol {
|
||||
public const VERSION = 'v1';
|
||||
|
||||
public const PROP_URL = 'url';
|
||||
public const PROP_DISPLAY_NAME = 'displayName';
|
||||
public const PROP_COLOR = 'color';
|
||||
public const PROP_ACCESS = 'access';
|
||||
public const PROP_COMPONENTS = 'components';
|
||||
|
||||
private string $url = '';
|
||||
private string $displayName = '';
|
||||
private ?string $color = null;
|
||||
private int $access = 0;
|
||||
private string $components = '';
|
||||
|
||||
/**
|
||||
* @throws CalendarProtocolParseException If parsing the raw protocol array fails.
|
||||
*/
|
||||
public static function parse(array $rawProtocol): self {
|
||||
if ($rawProtocol[self::PROP_VERSION] !== self::VERSION) {
|
||||
throw new CalendarProtocolParseException('Unknown protocol version');
|
||||
}
|
||||
|
||||
$url = $rawProtocol[self::PROP_URL] ?? null;
|
||||
if (!is_string($url)) {
|
||||
throw new CalendarProtocolParseException('URL is missing or not a string');
|
||||
}
|
||||
|
||||
$displayName = $rawProtocol[self::PROP_DISPLAY_NAME] ?? null;
|
||||
if (!is_string($displayName)) {
|
||||
throw new CalendarProtocolParseException('Display name is missing or not a string');
|
||||
}
|
||||
|
||||
$color = $rawProtocol[self::PROP_COLOR] ?? null;
|
||||
if (!is_string($color) && $color !== null) {
|
||||
throw new CalendarProtocolParseException('Color is set but not a string');
|
||||
}
|
||||
|
||||
$access = $rawProtocol[self::PROP_ACCESS] ?? null;
|
||||
if (!is_int($access)) {
|
||||
throw new CalendarProtocolParseException('Access is missing or not an integer');
|
||||
}
|
||||
|
||||
$components = $rawProtocol[self::PROP_COMPONENTS] ?? null;
|
||||
if (!is_string($components)) {
|
||||
throw new CalendarProtocolParseException('Supported calendar components are missing or not a string');
|
||||
}
|
||||
|
||||
$protocol = new self();
|
||||
$protocol->setUrl($url);
|
||||
$protocol->setDisplayName($displayName);
|
||||
$protocol->setColor($color);
|
||||
$protocol->setAccess($access);
|
||||
$protocol->setComponents($components);
|
||||
return $protocol;
|
||||
}
|
||||
|
||||
#[\Override]
|
||||
public function toProtocol(): array {
|
||||
return [
|
||||
self::PROP_VERSION => $this->getVersion(),
|
||||
self::PROP_URL => $this->getUrl(),
|
||||
self::PROP_DISPLAY_NAME => $this->getDisplayName(),
|
||||
self::PROP_COLOR => $this->getColor(),
|
||||
self::PROP_ACCESS => $this->getAccess(),
|
||||
self::PROP_COMPONENTS => $this->getComponents(),
|
||||
];
|
||||
}
|
||||
|
||||
#[\Override]
|
||||
public function getVersion(): string {
|
||||
return self::VERSION;
|
||||
}
|
||||
|
||||
public function getUrl(): string {
|
||||
return $this->url;
|
||||
}
|
||||
|
||||
public function setUrl(string $url): void {
|
||||
$this->url = $url;
|
||||
}
|
||||
|
||||
public function getDisplayName(): string {
|
||||
return $this->displayName;
|
||||
}
|
||||
|
||||
public function setDisplayName(string $displayName): void {
|
||||
$this->displayName = $displayName;
|
||||
}
|
||||
|
||||
public function getColor(): ?string {
|
||||
return $this->color;
|
||||
}
|
||||
|
||||
public function setColor(?string $color): void {
|
||||
$this->color = $color;
|
||||
}
|
||||
|
||||
public function getAccess(): int {
|
||||
return $this->access;
|
||||
}
|
||||
|
||||
public function setAccess(int $access): void {
|
||||
$this->access = $access;
|
||||
}
|
||||
|
||||
public function getComponents(): string {
|
||||
return $this->components;
|
||||
}
|
||||
|
||||
public function setComponents(string $components): void {
|
||||
$this->components = $components;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,13 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
/**
|
||||
* SPDX-FileCopyrightText: 2025 Nextcloud GmbH and Nextcloud contributors
|
||||
* SPDX-License-Identifier: AGPL-3.0-or-later
|
||||
*/
|
||||
|
||||
namespace OCA\DAV\CalDAV\Federation\Protocol;
|
||||
|
||||
class CalendarProtocolParseException extends \Exception {
|
||||
}
|
||||
@@ -0,0 +1,25 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
/**
|
||||
* SPDX-FileCopyrightText: 2025 Nextcloud GmbH and Nextcloud contributors
|
||||
* SPDX-License-Identifier: AGPL-3.0-or-later
|
||||
*/
|
||||
|
||||
namespace OCA\DAV\CalDAV\Federation\Protocol;
|
||||
|
||||
interface ICalendarFederationProtocol {
|
||||
public const PROP_VERSION = 'version';
|
||||
|
||||
/**
|
||||
* Get the version of this protocol implementation.
|
||||
*/
|
||||
public function getVersion(): string;
|
||||
|
||||
/**
|
||||
* Convert the protocol to an associative array to be sent to a remote instance.
|
||||
* The resulting array still needs to be merged with the base protocol from the share!
|
||||
*/
|
||||
public function toProtocol(): array;
|
||||
}
|
||||
@@ -0,0 +1,67 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
/**
|
||||
* SPDX-FileCopyrightText: 2025 Nextcloud GmbH and Nextcloud contributors
|
||||
* SPDX-License-Identifier: AGPL-3.0-or-later
|
||||
*/
|
||||
|
||||
namespace OCA\DAV\CalDAV\Federation;
|
||||
|
||||
use OCA\DAV\CalDAV\Calendar;
|
||||
use OCP\IConfig;
|
||||
use OCP\IL10N;
|
||||
use Psr\Log\LoggerInterface;
|
||||
use Sabre\CalDAV\Backend;
|
||||
use Sabre\CalDAV\CalendarHome;
|
||||
use Sabre\DAV\Exception\NotFound;
|
||||
|
||||
class RemoteUserCalendarHome extends CalendarHome {
|
||||
public function __construct(
|
||||
Backend\BackendInterface $caldavBackend,
|
||||
$principalInfo,
|
||||
private readonly IL10N $l10n,
|
||||
private readonly IConfig $config,
|
||||
private readonly LoggerInterface $logger,
|
||||
) {
|
||||
parent::__construct($caldavBackend, $principalInfo);
|
||||
}
|
||||
|
||||
public function getChild($name) {
|
||||
// Remote users can only have incoming shared calendars so we can skip the rest of a regular
|
||||
// calendar home
|
||||
foreach ($this->caldavBackend->getCalendarsForUser($this->principalInfo['uri']) as $calendar) {
|
||||
if ($calendar['uri'] === $name) {
|
||||
return new Calendar(
|
||||
$this->caldavBackend,
|
||||
$calendar,
|
||||
$this->l10n,
|
||||
$this->config,
|
||||
$this->logger,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
throw new NotFound("Node with name $name could not be found");
|
||||
}
|
||||
|
||||
public function getChildren(): array {
|
||||
$objects = [];
|
||||
|
||||
// Remote users can only have incoming shared calendars so we can skip the rest of a regular
|
||||
// calendar home
|
||||
$calendars = $this->caldavBackend->getCalendarsForUser($this->principalInfo['uri']);
|
||||
foreach ($calendars as $calendar) {
|
||||
$objects[] = new Calendar(
|
||||
$this->caldavBackend,
|
||||
$calendar,
|
||||
$this->l10n,
|
||||
$this->config,
|
||||
$this->logger,
|
||||
);
|
||||
}
|
||||
|
||||
return $objects;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,334 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
/**
|
||||
* SPDX-FileCopyrightText: 2025 Nextcloud GmbH and Nextcloud contributors
|
||||
* SPDX-License-Identifier: AGPL-3.0-or-later
|
||||
*/
|
||||
namespace OCA\DAV\CalDAV\Import;
|
||||
|
||||
use Exception;
|
||||
use Generator;
|
||||
use InvalidArgumentException;
|
||||
use OCA\DAV\CalDAV\CalDavBackend;
|
||||
use OCA\DAV\CalDAV\CalendarImpl;
|
||||
use OCP\Calendar\CalendarImportOptions;
|
||||
use Sabre\VObject\Component\VCalendar;
|
||||
use Sabre\VObject\Node;
|
||||
use Sabre\VObject\Reader;
|
||||
use Sabre\VObject\UUIDUtil;
|
||||
|
||||
/**
|
||||
* Calendar Import Service
|
||||
*/
|
||||
class ImportService {
|
||||
|
||||
/** @var resource */
|
||||
private $source;
|
||||
|
||||
public function __construct(
|
||||
private CalDavBackend $backend,
|
||||
) {
|
||||
}
|
||||
|
||||
/**
|
||||
* Executes import with appropriate object generator based on format
|
||||
*
|
||||
* @param resource $source
|
||||
*
|
||||
* @return array<string,array<string,string|array<string>>>
|
||||
*
|
||||
* @throws \InvalidArgumentException
|
||||
*/
|
||||
public function import($source, CalendarImpl $calendar, CalendarImportOptions $options): array {
|
||||
if (!is_resource($source)) {
|
||||
throw new InvalidArgumentException('Invalid import source must be a file resource');
|
||||
}
|
||||
|
||||
$this->source = $source;
|
||||
|
||||
switch ($options->getFormat()) {
|
||||
case 'ical':
|
||||
return $this->importProcess($calendar, $options, $this->importText(...));
|
||||
break;
|
||||
case 'jcal':
|
||||
return $this->importProcess($calendar, $options, $this->importJson(...));
|
||||
break;
|
||||
case 'xcal':
|
||||
return $this->importProcess($calendar, $options, $this->importXml(...));
|
||||
break;
|
||||
default:
|
||||
throw new InvalidArgumentException('Invalid import format');
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Generates object stream from a text formatted source (ical)
|
||||
*
|
||||
* @return Generator<\Sabre\VObject\Component\VCalendar>
|
||||
*/
|
||||
private function importText(): Generator {
|
||||
$importer = new TextImporter($this->source);
|
||||
$structure = $importer->structure();
|
||||
$sObjectPrefix = $importer::OBJECT_PREFIX;
|
||||
$sObjectSuffix = $importer::OBJECT_SUFFIX;
|
||||
// calendar properties
|
||||
foreach ($structure['VCALENDAR'] as $entry) {
|
||||
if (!str_ends_with($entry, "\n") || !str_ends_with($entry, "\r\n")) {
|
||||
$sObjectPrefix .= PHP_EOL;
|
||||
}
|
||||
}
|
||||
// calendar time zones
|
||||
$timezones = [];
|
||||
foreach ($structure['VTIMEZONE'] as $tid => $collection) {
|
||||
$instance = $collection[0];
|
||||
$sObjectContents = $importer->extract((int)$instance[2], (int)$instance[3]);
|
||||
$vObject = Reader::read($sObjectPrefix . $sObjectContents . $sObjectSuffix);
|
||||
$timezones[$tid] = clone $vObject->VTIMEZONE;
|
||||
}
|
||||
// calendar components
|
||||
// for each component type, construct a full calendar object with all components
|
||||
// that match the same UID and appropriate time zones that are used in the components
|
||||
foreach (['VEVENT', 'VTODO', 'VJOURNAL'] as $type) {
|
||||
foreach ($structure[$type] as $cid => $instances) {
|
||||
/** @var array<int,VCalendar> $instances */
|
||||
// extract all instances of component and unserialize to object
|
||||
$sObjectContents = '';
|
||||
foreach ($instances as $instance) {
|
||||
$sObjectContents .= $importer->extract($instance[2], $instance[3]);
|
||||
}
|
||||
/** @var VCalendar $vObject */
|
||||
$vObject = Reader::read($sObjectPrefix . $sObjectContents . $sObjectSuffix);
|
||||
// add time zones to object
|
||||
foreach ($this->findTimeZones($vObject) as $zone) {
|
||||
if (isset($timezones[$zone])) {
|
||||
$vObject->add(clone $timezones[$zone]);
|
||||
}
|
||||
}
|
||||
yield $vObject;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Generates object stream from a xml formatted source (xcal)
|
||||
*
|
||||
* @return Generator<\Sabre\VObject\Component\VCalendar>
|
||||
*/
|
||||
private function importXml(): Generator {
|
||||
$importer = new XmlImporter($this->source);
|
||||
$structure = $importer->structure();
|
||||
$sObjectPrefix = $importer::OBJECT_PREFIX;
|
||||
$sObjectSuffix = $importer::OBJECT_SUFFIX;
|
||||
// calendar time zones
|
||||
$timezones = [];
|
||||
foreach ($structure['VTIMEZONE'] as $tid => $collection) {
|
||||
$instance = $collection[0];
|
||||
$sObjectContents = $importer->extract((int)$instance[2], (int)$instance[3]);
|
||||
$vObject = Reader::readXml($sObjectPrefix . $sObjectContents . $sObjectSuffix);
|
||||
$timezones[$tid] = clone $vObject->VTIMEZONE;
|
||||
}
|
||||
// calendar components
|
||||
// for each component type, construct a full calendar object with all components
|
||||
// that match the same UID and appropriate time zones that are used in the components
|
||||
foreach (['VEVENT', 'VTODO', 'VJOURNAL'] as $type) {
|
||||
foreach ($structure[$type] as $cid => $instances) {
|
||||
/** @var array<int,VCalendar> $instances */
|
||||
// extract all instances of component and unserialize to object
|
||||
$sObjectContents = '';
|
||||
foreach ($instances as $instance) {
|
||||
$sObjectContents .= $importer->extract($instance[2], $instance[3]);
|
||||
}
|
||||
/** @var VCalendar $vObject */
|
||||
$vObject = Reader::readXml($sObjectPrefix . $sObjectContents . $sObjectSuffix);
|
||||
// add time zones to object
|
||||
foreach ($this->findTimeZones($vObject) as $zone) {
|
||||
if (isset($timezones[$zone])) {
|
||||
$vObject->add(clone $timezones[$zone]);
|
||||
}
|
||||
}
|
||||
yield $vObject;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Generates object stream from a json formatted source (jcal)
|
||||
*
|
||||
* @return Generator<\Sabre\VObject\Component\VCalendar>
|
||||
*/
|
||||
private function importJson(): Generator {
|
||||
/** @var VCALENDAR $importer */
|
||||
$importer = Reader::readJson($this->source);
|
||||
// calendar time zones
|
||||
$timezones = [];
|
||||
foreach ($importer->VTIMEZONE as $timezone) {
|
||||
$tzid = $timezone->TZID?->getValue();
|
||||
if ($tzid !== null) {
|
||||
$timezones[$tzid] = clone $timezone;
|
||||
}
|
||||
}
|
||||
// calendar components
|
||||
foreach ($importer->getBaseComponents() as $base) {
|
||||
$vObject = new VCalendar;
|
||||
$vObject->VERSION = clone $importer->VERSION;
|
||||
$vObject->PRODID = clone $importer->PRODID;
|
||||
// extract all instances of component
|
||||
foreach ($importer->getByUID($base->UID->getValue()) as $instance) {
|
||||
$vObject->add(clone $instance);
|
||||
}
|
||||
// add time zones to object
|
||||
foreach ($this->findTimeZones($vObject) as $zone) {
|
||||
if (isset($timezones[$zone])) {
|
||||
$vObject->add(clone $timezones[$zone]);
|
||||
}
|
||||
}
|
||||
yield $vObject;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Searches through all component properties looking for defined timezones
|
||||
*
|
||||
* @return array<string>
|
||||
*/
|
||||
private function findTimeZones(VCalendar $vObject): array {
|
||||
$timezones = [];
|
||||
foreach ($vObject->getComponents() as $vComponent) {
|
||||
if ($vComponent->name !== 'VTIMEZONE') {
|
||||
foreach (['DTSTART', 'DTEND', 'DUE', 'RDATE', 'EXDATE'] as $property) {
|
||||
if (isset($vComponent->$property?->parameters['TZID'])) {
|
||||
$tid = $vComponent->$property->parameters['TZID']->getValue();
|
||||
$timezones[$tid] = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return array_keys($timezones);
|
||||
}
|
||||
|
||||
/**
|
||||
* Import objects
|
||||
*
|
||||
* @since 32.0.0
|
||||
*
|
||||
* @param CalendarImportOptions $options
|
||||
* @param callable $generator<CalendarImportOptions>: Generator<\Sabre\VObject\Component\VCalendar>
|
||||
*
|
||||
* @return array<string,array<string,string|array<string>>>
|
||||
*/
|
||||
public function importProcess(CalendarImpl $calendar, CalendarImportOptions $options, callable $generator): array {
|
||||
$calendarId = $calendar->getKey();
|
||||
$calendarUri = $calendar->getUri();
|
||||
$principalUri = $calendar->getPrincipalUri();
|
||||
$outcome = [];
|
||||
foreach ($generator() as $vObject) {
|
||||
$components = $vObject->getBaseComponents();
|
||||
// determine if the object has no base component types
|
||||
if (count($components) === 0) {
|
||||
$errorMessage = 'One or more objects discovered with no base component types';
|
||||
if ($options->getErrors() === $options::ERROR_FAIL) {
|
||||
throw new InvalidArgumentException('Error importing calendar data: ' . $errorMessage);
|
||||
}
|
||||
$outcome['nbct'] = ['outcome' => 'error', 'errors' => [$errorMessage]];
|
||||
continue;
|
||||
}
|
||||
// determine if the object has more than one base component type
|
||||
// object can have multiple base components with the same uid
|
||||
// but we need to make sure they are of the same type
|
||||
if (count($components) > 1) {
|
||||
$type = $components[0]->name;
|
||||
foreach ($components as $entry) {
|
||||
if ($type !== $entry->name) {
|
||||
$errorMessage = 'One or more objects discovered with multiple base component types';
|
||||
if ($options->getErrors() === $options::ERROR_FAIL) {
|
||||
throw new InvalidArgumentException('Error importing calendar data: ' . $errorMessage);
|
||||
}
|
||||
$outcome['mbct'] = ['outcome' => 'error', 'errors' => [$errorMessage]];
|
||||
continue 2;
|
||||
}
|
||||
}
|
||||
}
|
||||
// determine if the object has a uid
|
||||
if (!isset($components[0]->UID)) {
|
||||
$errorMessage = 'One or more objects discovered without a UID';
|
||||
if ($options->getErrors() === $options::ERROR_FAIL) {
|
||||
throw new InvalidArgumentException('Error importing calendar data: ' . $errorMessage);
|
||||
}
|
||||
$outcome['noid'] = ['outcome' => 'error', 'errors' => [$errorMessage]];
|
||||
continue;
|
||||
}
|
||||
$uid = $components[0]->UID->getValue();
|
||||
// validate object
|
||||
if ($options->getValidate() !== $options::VALIDATE_NONE) {
|
||||
$issues = $this->componentValidate($vObject, true, 3);
|
||||
if ($options->getValidate() === $options::VALIDATE_SKIP && $issues !== []) {
|
||||
$outcome[$uid] = ['outcome' => 'error', 'errors' => $issues];
|
||||
continue;
|
||||
} elseif ($options->getValidate() === $options::VALIDATE_FAIL && $issues !== []) {
|
||||
throw new InvalidArgumentException('Error importing calendar data: UID <' . $uid . '> - ' . $issues[0]);
|
||||
}
|
||||
}
|
||||
// create or update object in the data store
|
||||
$objectId = $this->backend->getCalendarObjectByUID($principalUri, $uid, $calendarUri);
|
||||
$objectData = $vObject->serialize();
|
||||
try {
|
||||
if ($objectId === null) {
|
||||
$objectId = UUIDUtil::getUUID();
|
||||
$this->backend->createCalendarObject(
|
||||
$calendarId,
|
||||
$objectId,
|
||||
$objectData
|
||||
);
|
||||
$outcome[$uid] = ['outcome' => 'created'];
|
||||
} else {
|
||||
[$cid, $oid] = explode('/', $objectId);
|
||||
if ($options->getSupersede()) {
|
||||
$this->backend->updateCalendarObject(
|
||||
$calendarId,
|
||||
$oid,
|
||||
$objectData
|
||||
);
|
||||
$outcome[$uid] = ['outcome' => 'updated'];
|
||||
} else {
|
||||
$outcome[$uid] = ['outcome' => 'exists'];
|
||||
}
|
||||
}
|
||||
} catch (Exception $e) {
|
||||
$errorMessage = $e->getMessage();
|
||||
if ($options->getErrors() === $options::ERROR_FAIL) {
|
||||
throw new Exception('Error importing calendar data: UID <' . $uid . '> - ' . $errorMessage, 0, $e);
|
||||
}
|
||||
$outcome[$uid] = ['outcome' => 'error', 'errors' => [$errorMessage]];
|
||||
}
|
||||
}
|
||||
|
||||
return $outcome;
|
||||
}
|
||||
|
||||
/**
|
||||
* Validate a component
|
||||
*
|
||||
* @param VCalendar $vObject
|
||||
* @param bool $repair attempt to repair the component
|
||||
* @param int $level minimum level of issues to return
|
||||
* @return list<mixed>
|
||||
*/
|
||||
private function componentValidate(VCalendar $vObject, bool $repair, int $level): array {
|
||||
// validate component(S)
|
||||
$issues = $vObject->validate(Node::PROFILE_CALDAV);
|
||||
// attempt to repair
|
||||
if ($repair && count($issues) > 0) {
|
||||
$issues = $vObject->validate(Node::REPAIR);
|
||||
}
|
||||
// filter out messages based on level
|
||||
$result = [];
|
||||
foreach ($issues as $key => $issue) {
|
||||
if (isset($issue['level']) && $issue['level'] >= $level) {
|
||||
$result[] = $issue['message'];
|
||||
}
|
||||
}
|
||||
|
||||
return $result;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,156 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
/**
|
||||
* SPDX-FileCopyrightText: 2025 Nextcloud GmbH and Nextcloud contributors
|
||||
* SPDX-License-Identifier: AGPL-3.0-or-later
|
||||
*/
|
||||
namespace OCA\DAV\CalDAV\Import;
|
||||
|
||||
use Exception;
|
||||
|
||||
class TextImporter {
|
||||
|
||||
public const OBJECT_PREFIX = 'BEGIN:VCALENDAR' . PHP_EOL;
|
||||
public const OBJECT_SUFFIX = PHP_EOL . 'END:VCALENDAR';
|
||||
private const COMPONENT_TYPES = ['VEVENT', 'VTODO', 'VJOURNAL', 'VTIMEZONE'];
|
||||
|
||||
private bool $analyzed = false;
|
||||
private array $structure = ['VCALENDAR' => [], 'VEVENT' => [], 'VTODO' => [], 'VJOURNAL' => [], 'VTIMEZONE' => []];
|
||||
|
||||
/**
|
||||
* @param resource $source
|
||||
*/
|
||||
public function __construct(
|
||||
private $source,
|
||||
) {
|
||||
// Ensure that source is a stream resource
|
||||
if (!is_resource($source) || get_resource_type($source) !== 'stream') {
|
||||
throw new Exception('Source must be a stream resource');
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Analyzes the source data and creates a structure of components
|
||||
*/
|
||||
private function analyze() {
|
||||
$componentStart = null;
|
||||
$componentEnd = null;
|
||||
$componentId = null;
|
||||
$componentType = null;
|
||||
$tagName = null;
|
||||
$tagValue = null;
|
||||
|
||||
// iterate through the source data line by line
|
||||
fseek($this->source, 0);
|
||||
while (!feof($this->source)) {
|
||||
$data = fgets($this->source);
|
||||
// skip empty lines
|
||||
if ($data === false || empty(trim($data))) {
|
||||
continue;
|
||||
}
|
||||
// lines with whitespace at the beginning are continuations of the previous line
|
||||
if (ctype_space($data[0]) === false) {
|
||||
// detect the line TAG
|
||||
// detect the first occurrence of ':' or ';'
|
||||
$colonPos = strpos($data, ':');
|
||||
$semicolonPos = strpos($data, ';');
|
||||
if ($colonPos !== false && $semicolonPos !== false) {
|
||||
$splitPosition = min($colonPos, $semicolonPos);
|
||||
} elseif ($colonPos !== false) {
|
||||
$splitPosition = $colonPos;
|
||||
} elseif ($semicolonPos !== false) {
|
||||
$splitPosition = $semicolonPos;
|
||||
} else {
|
||||
continue;
|
||||
}
|
||||
$tagName = strtoupper(trim(substr($data, 0, $splitPosition)));
|
||||
$tagValue = trim(substr($data, $splitPosition + 1));
|
||||
$tagContinuation = false;
|
||||
} else {
|
||||
$tagContinuation = true;
|
||||
$tagValue .= trim($data);
|
||||
}
|
||||
|
||||
if ($tagContinuation === false) {
|
||||
// check line for component start, remember the position and determine the type
|
||||
if ($tagName === 'BEGIN' && in_array($tagValue, self::COMPONENT_TYPES, true)) {
|
||||
$componentStart = ftell($this->source) - strlen($data);
|
||||
$componentType = $tagValue;
|
||||
}
|
||||
// check line for component end, remember the position
|
||||
if ($tagName === 'END' && $componentType === $tagValue) {
|
||||
$componentEnd = ftell($this->source);
|
||||
}
|
||||
// check line for component id
|
||||
if ($componentStart !== null && ($tagName === 'UID' || $tagName === 'TZID')) {
|
||||
$componentId = $tagValue;
|
||||
}
|
||||
} else {
|
||||
// check line for component id
|
||||
if ($componentStart !== null && ($tagName === 'UID' || $tagName === 'TZID')) {
|
||||
$componentId = $tagValue;
|
||||
}
|
||||
}
|
||||
// any line(s) not inside a component are VCALENDAR properties
|
||||
if ($componentStart === null) {
|
||||
if ($tagName !== 'BEGIN' && $tagName !== 'END' && $tagValue === 'VCALENDAR') {
|
||||
$components['VCALENDAR'][] = $data;
|
||||
}
|
||||
}
|
||||
// if component start and end are found, add the component to the structure
|
||||
if ($componentStart !== null && $componentEnd !== null) {
|
||||
if ($componentId !== null) {
|
||||
$this->structure[$componentType][$componentId][] = [
|
||||
$componentType,
|
||||
$componentId,
|
||||
$componentStart,
|
||||
$componentEnd
|
||||
];
|
||||
} else {
|
||||
$this->structure[$componentType][] = [
|
||||
$componentType,
|
||||
$componentId,
|
||||
$componentStart,
|
||||
$componentEnd
|
||||
];
|
||||
}
|
||||
$componentId = null;
|
||||
$componentType = null;
|
||||
$componentStart = null;
|
||||
$componentEnd = null;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the analyzed structure of the source data
|
||||
* the analyzed structure is a collection of components organized by type,
|
||||
* each entry is a collection of instances
|
||||
* [
|
||||
* 'VEVENT' => [
|
||||
* '7456f141-b478-4cb9-8efc-1427ba0d6839' => [
|
||||
* ['VEVENT', '7456f141-b478-4cb9-8efc-1427ba0d6839', 0, 100 ],
|
||||
* ['VEVENT', '7456f141-b478-4cb9-8efc-1427ba0d6839', 100, 200 ]
|
||||
* ]
|
||||
* ]
|
||||
* ]
|
||||
*/
|
||||
public function structure(): array {
|
||||
if (!$this->analyzed) {
|
||||
$this->analyze();
|
||||
}
|
||||
return $this->structure;
|
||||
}
|
||||
|
||||
/**
|
||||
* Extracts a string chuck from the source data
|
||||
*
|
||||
* @param int $start starting byte position
|
||||
* @param int $end ending byte position
|
||||
*/
|
||||
public function extract(int $start, int $end): string {
|
||||
fseek($this->source, $start);
|
||||
return fread($this->source, $end - $start);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,173 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
/**
|
||||
* SPDX-FileCopyrightText: 2025 Nextcloud GmbH and Nextcloud contributors
|
||||
* SPDX-License-Identifier: AGPL-3.0-or-later
|
||||
*/
|
||||
namespace OCA\DAV\CalDAV\Import;
|
||||
|
||||
use Exception;
|
||||
use XMLParser;
|
||||
|
||||
class XmlImporter {
|
||||
|
||||
public const OBJECT_PREFIX = '<?xml version="1.0" encoding="UTF-8"?><icalendar xmlns="urn:ietf:params:xml:ns:icalendar-2.0"><vcalendar><components>';
|
||||
public const OBJECT_SUFFIX = '</components></vcalendar></icalendar>';
|
||||
private const COMPONENT_TYPES = ['VEVENT', 'VTODO', 'VJOURNAL', 'VTIMEZONE'];
|
||||
|
||||
private bool $analyzed = false;
|
||||
private array $structure = ['VCALENDAR' => [], 'VEVENT' => [], 'VTODO' => [], 'VJOURNAL' => [], 'VTIMEZONE' => []];
|
||||
private int $praseLevel = 0;
|
||||
private array $prasePath = [];
|
||||
private ?int $componentStart = null;
|
||||
private ?int $componentEnd = null;
|
||||
private int $componentLevel = 0;
|
||||
private ?string $componentId = null;
|
||||
private ?string $componentType = null;
|
||||
private bool $componentIdProperty = false;
|
||||
|
||||
/**
|
||||
* @param resource $source
|
||||
*/
|
||||
public function __construct(
|
||||
private $source,
|
||||
) {
|
||||
// Ensure that source is a stream resource
|
||||
if (!is_resource($source) || get_resource_type($source) !== 'stream') {
|
||||
throw new Exception('Source must be a stream resource');
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Analyzes the source data and creates a structure of components
|
||||
*/
|
||||
private function analyze() {
|
||||
$this->praseLevel = 0;
|
||||
$this->prasePath = [];
|
||||
$this->componentStart = null;
|
||||
$this->componentEnd = null;
|
||||
$this->componentLevel = 0;
|
||||
$this->componentId = null;
|
||||
$this->componentType = null;
|
||||
$this->componentIdProperty = false;
|
||||
// Create the parser and assign tag handlers
|
||||
$parser = xml_parser_create();
|
||||
xml_set_object($parser, $this);
|
||||
xml_set_element_handler($parser, $this->tagStart(...), $this->tagEnd(...));
|
||||
xml_set_default_handler($parser, $this->tagContents(...));
|
||||
// iterate through the source data chuck by chunk to trigger the handlers
|
||||
@fseek($this->source, 0);
|
||||
while ($chunk = fread($this->source, 4096)) {
|
||||
if (!xml_parse($parser, $chunk, feof($this->source))) {
|
||||
throw new Exception(
|
||||
xml_error_string(xml_get_error_code($parser))
|
||||
. ' At line: '
|
||||
. xml_get_current_line_number($parser)
|
||||
);
|
||||
}
|
||||
}
|
||||
//Free up the parser
|
||||
xml_parser_free($parser);
|
||||
}
|
||||
|
||||
/**
|
||||
* Handles start of tag events from the parser for all tags
|
||||
*/
|
||||
private function tagStart(XMLParser $parser, string $tag, array $attributes): void {
|
||||
// add the tag to the path tracker and increment depth the level
|
||||
$this->praseLevel++;
|
||||
$this->prasePath[$this->praseLevel] = $tag;
|
||||
// determine if the tag is a component type and remember the byte position
|
||||
if (in_array($tag, self::COMPONENT_TYPES, true)) {
|
||||
$this->componentStart = xml_get_current_byte_index($parser) - (strlen($tag) + 1);
|
||||
$this->componentType = $tag;
|
||||
$this->componentLevel = $this->praseLevel;
|
||||
}
|
||||
// determine if the tag is a sub tag of the component and an id property
|
||||
if ($this->componentStart !== null
|
||||
&& ($this->componentLevel + 2) === $this->praseLevel
|
||||
&& ($tag === 'UID' || $tag === 'TZID')
|
||||
) {
|
||||
$this->componentIdProperty = true;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Handles end of tag events from the parser for all tags
|
||||
*/
|
||||
private function tagEnd(XMLParser $parser, string $tag): void {
|
||||
// if the end tag matched the component type or the component id property
|
||||
// then add the component to the structure
|
||||
if ($tag === 'UID' || $tag === 'TZID') {
|
||||
$this->componentIdProperty = false;
|
||||
} elseif ($this->componentType === $tag) {
|
||||
$this->componentEnd = xml_get_current_byte_index($parser);
|
||||
if ($this->componentId !== null) {
|
||||
$this->structure[$this->componentType][$this->componentId][] = [
|
||||
$this->componentType,
|
||||
$this->componentId,
|
||||
$this->componentStart,
|
||||
$this->componentEnd,
|
||||
implode('/', $this->prasePath)
|
||||
];
|
||||
} else {
|
||||
$this->structure[$this->componentType][] = [
|
||||
$this->componentType,
|
||||
$this->componentId,
|
||||
$this->componentStart,
|
||||
$this->componentEnd,
|
||||
implode('/', $this->prasePath)
|
||||
];
|
||||
}
|
||||
$this->componentStart = null;
|
||||
$this->componentEnd = null;
|
||||
$this->componentId = null;
|
||||
$this->componentType = null;
|
||||
$this->componentIdProperty = false;
|
||||
}
|
||||
// remove the tag from the path tacker and depth the level
|
||||
unset($this->prasePath[$this->praseLevel]);
|
||||
$this->praseLevel--;
|
||||
}
|
||||
|
||||
/**
|
||||
* Handles tag contents events from the parser for all tags
|
||||
*/
|
||||
private function tagContents(XMLParser $parser, string $data): void {
|
||||
if ($this->componentIdProperty) {
|
||||
$this->componentId = $data;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the analyzed structure of the source data
|
||||
* the analyzed structure is a collection of components organized by type,
|
||||
* each entry is a collection of instances
|
||||
* [
|
||||
* 'VEVENT' => [
|
||||
* '7456f141-b478-4cb9-8efc-1427ba0d6839' => [
|
||||
* ['VEVENT', '7456f141-b478-4cb9-8efc-1427ba0d6839', 0, 100 ],
|
||||
* ['VEVENT', '7456f141-b478-4cb9-8efc-1427ba0d6839', 100, 200 ]
|
||||
* ]
|
||||
* ]
|
||||
* ]
|
||||
*/
|
||||
public function structure(): array {
|
||||
if (!$this->analyzed) {
|
||||
$this->analyze();
|
||||
}
|
||||
return $this->structure;
|
||||
}
|
||||
|
||||
/**
|
||||
* Extracts a string chuck from the source data
|
||||
*
|
||||
* @param int $start starting byte position
|
||||
* @param int $end ending byte position
|
||||
*/
|
||||
public function extract(int $start, int $end): string {
|
||||
fseek($this->source, $start);
|
||||
return fread($this->source, $end - $start);
|
||||
}
|
||||
}
|
||||
@@ -6,7 +6,9 @@
|
||||
*/
|
||||
namespace OCA\DAV\CalDAV\Publishing;
|
||||
|
||||
use OCA\DAV\CalDAV\CalDavBackend;
|
||||
use OCA\DAV\CalDAV\Calendar;
|
||||
use OCA\DAV\CalDAV\CalendarHome;
|
||||
use OCA\DAV\CalDAV\Publishing\Xml\Publisher;
|
||||
use OCP\AppFramework\Http;
|
||||
use OCP\IConfig;
|
||||
@@ -91,6 +93,20 @@ class PublishPlugin extends ServerPlugin {
|
||||
}
|
||||
|
||||
public function propFind(PropFind $propFind, INode $node) {
|
||||
if ($node instanceof CalendarHome && $propFind->getDepth() === 1) {
|
||||
$backend = $node->getCalDAVBackend();
|
||||
if ($backend instanceof CalDavBackend) {
|
||||
$calendars = array_filter(
|
||||
$node->getChildren(),
|
||||
static fn ($child) => $child instanceof Calendar,
|
||||
);
|
||||
$resourceIds = array_map(
|
||||
static fn (Calendar $calendar) => $calendar->getResourceId(),
|
||||
$calendars,
|
||||
);
|
||||
$backend->preloadPublishStatuses($resourceIds);
|
||||
}
|
||||
}
|
||||
if ($node instanceof Calendar) {
|
||||
$propFind->handle('{' . self::NS_CALENDARSERVER . '}publish-url', function () use ($node) {
|
||||
if ($node->getPublishStatus()) {
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user