Compare commits

...

326 Commits

Author SHA1 Message Date
Côme Chilliet 33684e6701 Removing copyUpdate to see which tests are failing in CI
Signed-off-by: Côme Chilliet <come.chilliet@nextcloud.com>
2025-09-02 15:41:56 +02:00
Andy Scherzinger 38327e4493 Merge pull request #54777 from nextcloud/perf/carddav/ungreedify-search
perf(carddav): ungreedify search result aggregation
2025-09-02 14:59:02 +02:00
Carl Schwan d73537dd60 Merge pull request #54664 from nextcloud/carl/port-away-execute-lib
Port away from IQueryBuilder::execute in lib/ and tests/ and fix invalid usage of UniqueConstraintViolationException
2025-09-02 14:38:11 +02:00
Ferdinand Thiessen 9c3acefe53 Merge pull request #54722 from nextcloud/feat/filename-sanitize-ui
feat(files): provide UI to sanitize filenames after enabling WCF
2025-09-02 14:30:03 +02:00
Ferdinand Thiessen fb38f2fd19 Merge pull request #54789 from nextcloud/fix/dark-theme-assistant
fix(theming): adjust assistant icon color on dark theme
2025-09-02 14:24:59 +02:00
Daniel 480517ae0c Merge pull request #54794 from nextcloud/fix/add-missing-listener
fix: add missing listener
2025-09-02 14:24:43 +02:00
max-nextcloud 4927a283bd Merge pull request #54798 from nextcloud/enh/different-color-for-guest-with-same-name
enh(avatars): use different colors for guests for the same name
2025-09-02 14:04:21 +02:00
Christoph Wurst 9a0892ca30 Merge pull request #53831 from nextcloud/feat/meeting-proposals
feat: Meeting Proposals
2025-09-02 13:16:33 +02:00
nextcloud-command 0b374e6c68 chore(assets): Recompile assets
Signed-off-by: nextcloud-command <nextcloud-command@users.noreply.github.com>
2025-09-02 10:42:50 +00:00
Andy Scherzinger 88ba65bcd4 Merge pull request #54800 from nextcloud/artonge/fix/remove_intro_from_presets
fix: Remove intro from presets
2025-09-02 12:36:04 +02:00
Carl Schwan 996be0f441 fix(comments): Don's catch invalid DriverException
These are nowadays also OCP\DB\Exception and if the id is not a numeric
deleting by id is also non working.

Signed-off-by: Carl Schwan <carl.schwan@nextclound.com>
2025-09-02 11:55:58 +02:00
Carl Schwan 9b2fff5931 refactor(querybuilder): Port away from qb::execute() in tests/
Replace by either executeStatement or executeQuery

Signed-off-by: Carl Schwan <carl.schwan@nextclound.com>
2025-09-02 11:55:58 +02:00
Carl Schwan c4e6fbdae7 fix(query-builder): Don't catch UniqueConstraintViolationException
UniqueConstraintViolationException is no longer throw directly but
instead is now wrapped inside a \OCP\DB\Exception. So check the
exception reason.

Signed-off-by: Carl Schwan <carl.schwan@nextclound.com>
2025-09-02 11:55:58 +02:00
Carl Schwan c21b8169ff refactor(querybuilder): Port away from qb::execute() in lib/
Replace by either executeStatement or executeQuery

Signed-off-by: Carl Schwan <carl.schwan@nextclound.com>
2025-09-02 11:55:56 +02:00
Louis Chemineau a9d450285f fix: Remove intro from presets
Signed-off-by: Louis Chemineau <louis@chmn.me>
2025-09-02 11:37:37 +02:00
Max 78022f5447 feat(avatars): use different colors for guests for the same name
Make it harder to impersonate users who have not set their avatar.

Signed-off-by: Max <max@nextcloud.com>
2025-09-02 11:31:14 +02:00
Ferdinand Thiessen 9eb4c96d3b fix(theming): adjust assistant icon color on dark theme
* resolves https://github.com/nextcloud/server/issues/54782

Signed-off-by: Ferdinand Thiessen <opensource@fthiessen.de>
2025-09-02 11:03:34 +02:00
Ferdinand Thiessen a42bd7a507 refactor: add enum for filename sanitization also in the frontend
Signed-off-by: Ferdinand Thiessen <opensource@fthiessen.de>
2025-09-02 11:01:53 +02:00
Ferdinand Thiessen 805fe3e15b feat(files): provide UI to sanitize filenames after enabling WCF
Signed-off-by: Ferdinand Thiessen <opensource@fthiessen.de>
2025-09-02 10:53:01 +02:00
SebastianKrupinski ff4fdf1af8 feat: meeting proposals
Signed-off-by: SebastianKrupinski <krupinskis05@gmail.com>
Signed-off-by: Richard Steinmetz <richard@steinmetz.cloud>
2025-09-02 10:34:19 +02:00
SebastianKrupinski 75419ce8f2 fix: add missing listener
Signed-off-by: SebastianKrupinski <krupinskis05@gmail.com>
Signed-off-by: Richard Steinmetz <richard@steinmetz.cloud>
2025-09-02 10:31:55 +02:00
Joas Schilling 4fe0799d26 Merge pull request #54795 from nextcloud/fix/psalm-repair-mime-types
fix(RepairMimeTypes): Fix psalm issue
2025-09-02 10:27:37 +02:00
Marcel Klehr a38cb61d4d fix(RepairMimeTypes): Fix psalm issue
Signed-off-by: Marcel Klehr <mklehr@gmx.net>
2025-09-02 10:21:39 +02:00
Nextcloud bot 2f2049cbeb fix(l10n): Update translations from Transifex
Signed-off-by: Nextcloud bot <bot@nextcloud.com>
2025-09-02 00:12:15 +00:00
Kate 1d50cf3b51 Merge pull request #54742 from nextcloud/carl/ParamNameMismatch 2025-09-01 21:00:06 +02:00
Kate bdce9c3d79 Merge pull request #54785 from nextcloud/haze-2.1.1 2025-09-01 19:21:20 +02:00
Robin Appelman e17af0e174 chore(flake): update haze to 2.1.1
Signed-off-by: Robin Appelman <robin@icewind.nl>
2025-09-01 18:41:48 +02:00
Kate c8d6a14444 Merge pull request #54783 from nextcloud/chore/deps/openapi-extractor-v1.8.3 2025-09-01 17:06:13 +02:00
provokateurin 0221611757 chore(deps): Update nextcloud/openapi-extractor to v1.8.3
Signed-off-by: provokateurin <kate@provokateurin.de>
2025-09-01 16:04:54 +02:00
Kate 796eec5962 Merge pull request #54738 from nextcloud/fix-resolve-conflict-with-dragndrop-upload 2025-09-01 15:13:38 +02:00
Marcel Klehr 61bb2dca40 Merge pull request #54737 from nextcloud/feat/text-ext
feat: Add text/plain MIME type mapping for 'text' extension
2025-09-01 13:36:25 +02:00
Marcel Klehr 8c93c00fd7 feat: Add text/plain MIME type mapping for 'text' extension
Signed-off-by: Marcel Klehr <mklehr@gmx.net>
2025-09-01 13:13:56 +02:00
nextcloud-command 7d8735f48b chore(assets): Recompile assets
Signed-off-by: nextcloud-command <nextcloud-command@users.noreply.github.com>
2025-09-01 11:00:56 +00:00
Richard Steinmetz 4cf2203de8 perf(carddav): ungreedify search result aggregation
Signed-off-by: Richard Steinmetz <richard@steinmetz.cloud>
2025-09-01 12:06:48 +02:00
Carl Schwan 6ee28229d5 Merge pull request #54775 from nextcloud/carl/delete-lostpassword-token
fix(lostpassord): Delete lost password token on password change
2025-09-01 12:05:24 +02:00
Andy Scherzinger 03f22074d9 Merge pull request #54713 from nextcloud/fix/fix-status.php-cookies
fix(status.php): Fix samesite cookies
2025-09-01 11:48:27 +02:00
Andy Scherzinger 8c93efec89 Merge pull request #54774 from nextcloud/style/noid/outlineDelete
Replace filled icon with outlined variant
2025-09-01 11:47:57 +02:00
Carl Schwan 1d3c3cfb80 fix(lostpassword): Delete lost password token on password change
Signed-off-by: Carl Schwan <carl.schwan@nextcloud.com>
2025-09-01 11:36:15 +02:00
Côme Chilliet 22160b9d2b chore: Add comment to make it clear the bail out for status.php should
not be moved above ini_set calls.

Signed-off-by: Côme Chilliet <come.chilliet@nextcloud.com>
2025-09-01 10:52:31 +02:00
nextcloud-command 5518249158 chore(assets): Recompile assets
Signed-off-by: nextcloud-command <nextcloud-command@users.noreply.github.com>
2025-09-01 07:55:54 +00:00
Andy Scherzinger 5c707dd80a style(icon): Replace filled icon with outlined variant
Signed-off-by: Andy Scherzinger <info@andy-scherzinger.de>
2025-09-01 09:46:43 +02:00
Andy Scherzinger be89b726b5 Merge pull request #54772 from nextcloud/dependabot/github_actions/github-actions-887c337615
build(deps): bump the github-actions group with 4 updates
2025-09-01 09:14:16 +02:00
Nextcloud bot e8aacb5f34 fix(l10n): Update translations from Transifex
Signed-off-by: Nextcloud bot <bot@nextcloud.com>
2025-09-01 00:12:15 +00:00
dependabot[bot] f816a0b68e build(deps): bump the github-actions group with 4 updates
Bumps the github-actions group with 4 updates: [actions/checkout](https://github.com/actions/checkout), [shivammathur/setup-php](https://github.com/shivammathur/setup-php), [codecov/codecov-action](https://github.com/codecov/codecov-action) and [LizardByte/actions](https://github.com/lizardbyte/actions).


Updates `actions/checkout` from 4 to 5
- [Release notes](https://github.com/actions/checkout/releases)
- [Commits](https://github.com/actions/checkout/compare/v4...v5)

Updates `shivammathur/setup-php` from 2.35.2 to 2.35.4
- [Release notes](https://github.com/shivammathur/setup-php/releases)
- [Commits](https://github.com/shivammathur/setup-php/compare/ccf2c627fe61b1b4d924adfcbd19d661a18133a0...ec406be512d7077f68eed36e63f4d91bc006edc4)

Updates `codecov/codecov-action` from 5.4.3 to 5.5.0
- [Release notes](https://github.com/codecov/codecov-action/releases)
- [Changelog](https://github.com/codecov/codecov-action/blob/main/CHANGELOG.md)
- [Commits](https://github.com/codecov/codecov-action/compare/18283e04ce6e62d37312384ff67231eb8fd56d24...fdcc8476540edceab3de004e990f80d881c6cc00)

Updates `LizardByte/actions` from 2025.715.25226 to 2025.814.40518
- [Release notes](https://github.com/lizardbyte/actions/releases)
- [Commits](https://github.com/lizardbyte/actions/compare/eddc8fc8b27048e25040e37e3585bd3ef9a968ed...bff0a193747a3ac7930a665fc1d4b23eba583b99)

---
updated-dependencies:
- dependency-name: actions/checkout
  dependency-version: '5'
  dependency-type: direct:production
  update-type: version-update:semver-major
  dependency-group: github-actions
- dependency-name: shivammathur/setup-php
  dependency-version: 2.35.4
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: github-actions
- dependency-name: codecov/codecov-action
  dependency-version: 5.5.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: github-actions
- dependency-name: LizardByte/actions
  dependency-version: 2025.814.40518
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: github-actions
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-08-31 22:12:42 +00:00
Luka Trovic edd5dee342 fix: resolving conflict when uploading multiple files via drag&drop
Signed-off-by: Luka Trovic <luka@nextcloud.com>
2025-08-31 21:20:30 +02:00
Nextcloud bot 2f8920564b fix(l10n): Update translations from Transifex
Signed-off-by: Nextcloud bot <bot@nextcloud.com>
2025-08-31 00:12:14 +00:00
dependabot[bot] bcc8ff7b13 Merge pull request #54745 from nextcloud/dependabot/npm_and_yarn/cypress-axe-1.7.0 2025-08-30 08:36:00 +00:00
dependabot[bot] 0f4b27eccc build(deps-dev): bump cypress-axe from 1.6.0 to 1.7.0
Bumps [cypress-axe](https://github.com/component-driven/cypress-axe) from 1.6.0 to 1.7.0.
- [Release notes](https://github.com/component-driven/cypress-axe/releases)
- [Commits](https://github.com/component-driven/cypress-axe/compare/v1.6.0...v1.7.0)

---
updated-dependencies:
- dependency-name: cypress-axe
  dependency-version: 1.7.0
  dependency-type: direct:development
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-08-30 01:04:16 +00:00
Nextcloud bot c15af7f228 fix(l10n): Update translations from Transifex
Signed-off-by: Nextcloud bot <bot@nextcloud.com>
2025-08-30 00:12:10 +00:00
Andy Scherzinger 7bc8c5be3f Merge pull request #54741 from nextcloud/style/noid/helpOutlineIcon
Use outline icon for Help/Copy
2025-08-29 17:18:38 +02:00
Andy Scherzinger 2c03a97386 style(icon): Replace filled with outline variant
Signed-off-by: Andy Scherzinger <info@andy-scherzinger.de>
2025-08-29 16:22:23 +02:00
Andy Scherzinger 568407b4d7 style(icon): Replace help icon with Material Symbol outlined variant
Signed-off-by: Andy Scherzinger <info@andy-scherzinger.de>
2025-08-29 16:19:20 +02:00
Richard Steinmetz 34097b6070 Merge pull request #54729 from nextcloud/feat/ocp/address-book-create-from-string
feat(ocp): create contacts from string
2025-08-29 15:25:44 +02:00
Kate 0bdabcf049 Merge pull request #54630 from nextcloud/fix/54626/occThemingTypeError 2025-08-29 15:15:57 +02:00
Richard Steinmetz 16ad73cd15 feat(ocp): create contacts from string
Signed-off-by: Richard Steinmetz <richard@steinmetz.cloud>
2025-08-29 15:04:57 +02:00
Kate b17745e7fd Merge pull request #54733 from nextcloud/fix/version-autjo-string 2025-08-29 15:04:04 +02:00
Carl Schwan 76dc41ea18 fix(psalm): Fix ParamNameMismatch with IRepairStep::run
Signed-off-by: Carl Schwan <carl.schwan@nextcloud.com>
2025-08-29 14:31:46 +02:00
Carl Schwan ef5edb6801 fix(psalm): Fix ParamNameMismatch with Job::run
Signed-off-by: Carl Schwan <carl.schwan@nextcloud.com>
2025-08-29 14:31:39 +02:00
nextcloud-command dc7db45297 chore(assets): Recompile assets
Signed-off-by: nextcloud-command <nextcloud-command@users.noreply.github.com>
2025-08-29 12:05:01 +00:00
John Molakvoæ (skjnldsv) 289cb082a6 fix(files_versions): author and label parsing fallback
Signed-off-by: John Molakvoæ (skjnldsv) <skjnldsv@protonmail.com>
2025-08-29 12:03:01 +00:00
Andy Scherzinger e2ea6d4680 Merge pull request #54728 from nextcloud/fix/version-autjo-string
fix(files_versions): ensure users IDs are strings
2025-08-29 12:20:39 +02:00
nextcloud-command cedd1ee24b chore(assets): Recompile assets
Signed-off-by: nextcloud-command <nextcloud-command@users.noreply.github.com>
2025-08-29 09:30:42 +00:00
Salvatore Martire b247c97964 fix(theming): use IAppConfig instead of IConfig to set theming config
Signed-off-by: Salvatore Martire <4652631+salmart-dev@users.noreply.github.com>
2025-08-29 11:06:09 +02:00
John Molakvoæ (skjnldsv) 1b7418f848 fix(files_versions): ensure users IDs are strings
Signed-off-by: John Molakvoæ (skjnldsv) <skjnldsv@protonmail.com>
2025-08-29 11:04:11 +02:00
Carl Schwan 5a73733ec1 Merge pull request #54684 from nextcloud/createFilesVersionsFolder
fix(files_versions): Create version folder if it doesn't exist
2025-08-29 08:11:00 +02:00
Nextcloud bot 3ba18f78b2 fix(l10n): Update translations from Transifex
Signed-off-by: Nextcloud bot <bot@nextcloud.com>
2025-08-29 00:12:39 +00:00
Sebastian Krupinski 94c80aadd1 Merge pull request #52622 from nextcloud/fix/refactor-imip
fix: aliases and capitalization of emails
2025-08-28 18:30:27 -04:00
SebastianKrupinski 7e92b157e3 fix: aliases and capitalization of emails
Signed-off-by: SebastianKrupinski <krupinskis05@gmail.com>
2025-08-28 17:40:04 -04:00
Andy Scherzinger 1cb6dc0e58 Merge pull request #54313 from nextcloud/fix/download-action
fix(files): verify files are still accessible before downloading
2025-08-28 22:53:52 +02:00
nextcloud-command b8338a05b3 chore(assets): Recompile assets
Signed-off-by: nextcloud-command <nextcloud-command@users.noreply.github.com>
2025-08-28 20:34:25 +00:00
John Molakvoæ (skjnldsv) 6fb7f7f4e7 fix(files): verify files are still accessible before downloading
Signed-off-by: John Molakvoæ (skjnldsv) <skjnldsv@protonmail.com>
2025-08-28 22:27:26 +02:00
Andy Scherzinger 3b9da80293 Merge pull request #54707 from nextcloud/dependabot/npm_and_yarn/nextcloud/calendar-availability-vue-2.2.10
build(deps): bump @nextcloud/calendar-availability-vue from 2.2.8 to 2.2.10
2025-08-28 21:58:57 +02:00
Git'Fellow c5fa862b06 fix(files_versions): Use helper function
Signed-off-by: Git'Fellow <12234510+solracsf@users.noreply.github.com>
2025-08-28 21:24:52 +02:00
nextcloud-command 145e130c20 chore(assets): Recompile assets
Signed-off-by: nextcloud-command <nextcloud-command@users.noreply.github.com>
2025-08-28 18:24:03 +00:00
dependabot[bot] 4f3958f7c6 build(deps): bump @nextcloud/calendar-availability-vue
Bumps [@nextcloud/calendar-availability-vue](https://github.com/nextcloud/calendar-availability-vue) from 2.2.8 to 2.2.10.
- [Release notes](https://github.com/nextcloud/calendar-availability-vue/releases)
- [Changelog](https://github.com/nextcloud/calendar-availability-vue/blob/v2.2.10/CHANGELOG.md)
- [Commits](https://github.com/nextcloud/calendar-availability-vue/compare/v2.2.8...v2.2.10)

---
updated-dependencies:
- dependency-name: "@nextcloud/calendar-availability-vue"
  dependency-version: 2.2.10
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-08-28 20:16:56 +02:00
dependabot[bot] 081396c56c Merge pull request #54709 from nextcloud/dependabot/npm_and_yarn/marked-16.2.1 2025-08-28 18:11:24 +00:00
nextcloud-command 29c358e537 chore(assets): Recompile assets
Signed-off-by: nextcloud-command <nextcloud-command@users.noreply.github.com>
2025-08-28 17:48:11 +00:00
dependabot[bot] bfee7226cb build(deps): bump marked from 16.2.0 to 16.2.1
Bumps [marked](https://github.com/markedjs/marked) from 16.2.0 to 16.2.1.
- [Release notes](https://github.com/markedjs/marked/releases)
- [Changelog](https://github.com/markedjs/marked/blob/master/.releaserc.json)
- [Commits](https://github.com/markedjs/marked/compare/v16.2.0...v16.2.1)

---
updated-dependencies:
- dependency-name: marked
  dependency-version: 16.2.1
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-08-28 19:34:37 +02:00
Andy Scherzinger f2de5c79cd Merge pull request #54721 from nextcloud/bucket-mapper-fixes
fix: make bucket mapper work with new multi-object-store config
2025-08-28 19:33:47 +02:00
Andy Scherzinger 809bbe3ca5 Merge pull request #54708 from nextcloud/dependabot/npm_and_yarn/p-limit-7.1.1
build(deps): bump p-limit from 7.1.0 to 7.1.1
2025-08-28 19:25:58 +02:00
John Molakvoæ 47b46d830a Merge pull request #54688 from nextcloud/release/32.0.0_beta_4 2025-08-28 18:16:50 +02:00
Joas Schilling 31d719e666 Merge pull request #54717 from nextcloud/revert-53920-revert-53918-revert-53141-perf/files/setup-fs-basic-auth-request
Revert "perf(base): Stop setting up the FS for every basic auth request"
2025-08-28 17:59:42 +02:00
Robin Appelman 02f4a82088 fix: make bucket mapper work with new multi-object-store config
Signed-off-by: Robin Appelman <robin@icewind.nl>
2025-08-28 17:51:25 +02:00
Sebastian Krupinski a7338b079f Merge pull request #51727 from nextcloud/fix/noid-retrieve-all-authors-at-the-same-time
fix(files_versions): retrieve all display names with one request
2025-08-28 11:13:39 -04:00
Joas Schilling 3df6d90a4c Revert "perf(base): Stop setting up the FS for every basic auth request" 2025-08-28 17:11:31 +02:00
Richard Steinmetz 40117dced3 Merge pull request #54426 from nextcloud/perf/prevent-fetching-account
perf: prevent fetching a principal's user account if the data is not needed
2025-08-28 16:59:38 +02:00
Git'Fellow e302983a87 Merge pull request #54695 from nextcloud/mtimeSanitizerPrintBadValues
chore(MtimeSanitizer): Print bad values to help debugging
2025-08-28 16:53:36 +02:00
Richard Steinmetz eb34ddbf97 Merge pull request #54438 from nextcloud/perf/caldav/cache-empty-shares-when-preloading
perf(caldav): also cache empty share arrays
2025-08-28 16:42:46 +02:00
Richard Steinmetz 9001ae2a4e Merge pull request #54435 from nextcloud/perf/caldav/preload-calendar-publish-status
perf(caldav): preload publish statuses for a whole calendar home at once
2025-08-28 16:40:32 +02:00
nextcloud-command 289e87e00f chore(assets): Recompile assets
Signed-off-by: nextcloud-command <nextcloud-command@users.noreply.github.com>
2025-08-28 14:08:59 +00:00
Côme Chilliet 34aaa0cf83 fix(status.php): Fix samesite cookies
This skips less calls for status.php so that ini vars are correctly set
 and the code to set samesite cookies has the correct information when
 Nextcloud is installed in a subpath.

Signed-off-by: Côme Chilliet <come.chilliet@nextcloud.com>
2025-08-28 16:02:13 +02:00
dependabot[bot] 6a97e8e3d1 build(deps): bump p-limit from 7.1.0 to 7.1.1
Bumps [p-limit](https://github.com/sindresorhus/p-limit) from 7.1.0 to 7.1.1.
- [Release notes](https://github.com/sindresorhus/p-limit/releases)
- [Commits](https://github.com/sindresorhus/p-limit/compare/v7.1.0...v7.1.1)

---
updated-dependencies:
- dependency-name: p-limit
  dependency-version: 7.1.1
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-08-28 13:05:44 +00:00
SebastianKrupinski 5b02d11a71 fix: retrieve all display names with one request
Signed-off-by: SebastianKrupinski <krupinskis05@gmail.com>
2025-08-28 09:03:35 -04:00
Richard Steinmetz 5b254ea39a perf: prevent fetching a principal's user account if the data is not needed
Signed-off-by: Richard Steinmetz <richard@steinmetz.cloud>
2025-08-28 14:13:29 +02:00
John Molakvoæ d785bcdc6e Merge pull request #53920 from nextcloud/revert-53918-revert-53141-perf/files/setup-fs-basic-auth-request 2025-08-28 14:12:57 +02:00
Kate f383db26eb Merge pull request #54703 from nextcloud/fix/accounts/v2-scope-migration 2025-08-28 14:07:53 +02:00
Kate a1709f576e Merge pull request #54627 from nextcloud/fix/ocs/accept-header 2025-08-28 14:03:23 +02:00
Richard Steinmetz e8986db7a4 perf(caldav): preload publish statuses for a whole calendar home at once
Signed-off-by: Richard Steinmetz <richard@steinmetz.cloud>
2025-08-28 14:00:18 +02:00
Richard Steinmetz 0a79bc44df perf(caldav): also cache empty share arrays
Signed-off-by: Richard Steinmetz <richard@steinmetz.cloud>
2025-08-28 13:50:54 +02:00
provokateurin 53c1268ead chore(provisioning_api): Remove legacy scopes
Signed-off-by: provokateurin <kate@provokateurin.de>
2025-08-28 13:30:10 +02:00
provokateurin bede81391b fix(Accounts): Add back v2 scope migration
Signed-off-by: provokateurin <kate@provokateurin.de>
2025-08-28 13:30:04 +02:00
Marcel Klehr c8a12a54fd Merge pull request #54690 from nextcloud/fix/taskprocessing-no-anonymous-use
fix(TaskProcessingApiController): Don't allow anonymous access anymore
2025-08-28 13:15:28 +02:00
dependabot[bot] c1b3b3c4db Merge pull request #54693 from nextcloud/dependabot/composer/build/integration/guzzlehttp/guzzle-7.10.0 2025-08-28 10:44:33 +00:00
Marcel Klehr 6c85500bfb fix: update openapi specs
Signed-off-by: Marcel Klehr <mklehr@gmx.net>
2025-08-28 11:46:37 +02:00
Marcel Klehr 310cd23a6a fix(TaskProcessingApiController): Don't allow anonymous access anymore
Signed-off-by: Marcel Klehr <mklehr@gmx.net>
2025-08-28 11:46:37 +02:00
Git'Fellow 995c97acb4 chore(MtimeSanitizer): Print bad values to help debugging
Signed-off-by: Git'Fellow <12234510+solracsf@users.noreply.github.com>
2025-08-28 11:44:06 +02:00
provokateurin e38dfef0dc chore: Remove unused \OC\Cache\CappedMemoryCache
Signed-off-by: provokateurin <kate@provokateurin.de>
Signed-off-by: nextcloud-command <nextcloud-command@users.noreply.github.com>
2025-08-28 09:32:51 +00:00
provokateurin eba11750eb chore: Remove unused \OC\Cache\File
Signed-off-by: provokateurin <kate@provokateurin.de>
2025-08-28 09:28:11 +00:00
provokateurin 26ab3a6b74 refactor(Server): Deprecate \OCP\ICache service and replace it with a local cache
Signed-off-by: provokateurin <kate@provokateurin.de>
2025-08-28 09:28:11 +00:00
provokateurin 5057d5fcc5 fix(core): Stop abusing the cache for avatar upload
Signed-off-by: provokateurin <kate@provokateurin.de>
2025-08-28 09:28:11 +00:00
provokateurin 778c6c0d46 perf(base): Stop setting up the FS for every basic auth request
Signed-off-by: provokateurin <kate@provokateurin.de>
2025-08-28 09:28:11 +00:00
provokateurin d5c15d4d2f test(integration): Check ocs share permission for correct folder
Signed-off-by: provokateurin <kate@provokateurin.de>
2025-08-28 09:28:10 +00:00
Kate 706c78ff52 Merge pull request #54691 from nextcloud/leftybournes/feat/teams_groups_readonly 2025-08-28 11:24:36 +02:00
Ferdinand Thiessen 5cbda3593f Merge pull request #54692 from nextcloud/chore/nc-vue-8-29-2
chore(deps): update `@nextcloud/vue` to v8.29.2
2025-08-28 11:16:26 +02:00
Kate 3999dcf796 Merge pull request #54689 from nextcloud/bugfix/noid/fix-missing-return-type 2025-08-28 11:13:01 +02:00
dependabot[bot] 025e081530 build(deps-dev): bump guzzlehttp/guzzle in /build/integration
Bumps [guzzlehttp/guzzle](https://github.com/guzzle/guzzle) from 7.9.3 to 7.10.0.
- [Release notes](https://github.com/guzzle/guzzle/releases)
- [Changelog](https://github.com/guzzle/guzzle/blob/7.10/CHANGELOG.md)
- [Commits](https://github.com/guzzle/guzzle/compare/7.9.3...7.10.0)

---
updated-dependencies:
- dependency-name: guzzlehttp/guzzle
  dependency-version: 7.10.0
  dependency-type: direct:development
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-08-28 08:51:04 +00:00
Kate c0595e9eaa Merge pull request #54683 from nextcloud/chore/preview/remove-avconv-support 2025-08-28 10:11:36 +02:00
Ferdinand Thiessen 646e8ee060 chore: compile assets
Signed-off-by: Ferdinand Thiessen <opensource@fthiessen.de>
2025-08-28 10:02:41 +02:00
Ferdinand Thiessen a334e136d5 chore(deps): update @nextcloud/vue to v8.29.2
Signed-off-by: Ferdinand Thiessen <opensource@fthiessen.de>
2025-08-28 10:00:53 +02:00
Kent Delante 2908f81769 feat: allow group's members to view fellow members
Signed-off-by: Kent Delante <kent.delante@proton.me>
2025-08-28 15:56:15 +08:00
Andy Scherzinger 86b69c75e6 Merge pull request #54490 from nextcloud/delete-share-orphan-filters
feat: allow filtering sharing:delete-orphan-shares by share owner or target
2025-08-28 09:34:38 +02:00
Joas Schilling 102c778228 fix(comments): Fix missing return type on new method
Signed-off-by: Joas Schilling <coding@schilljs.com>
2025-08-28 08:58:52 +02:00
Worker Builder e512b5b21f build(hub): 32.0.0 beta 4
Signed-off-by: Worker Builder <worker-builder@nextcloud.com>
2025-08-28 06:57:25 +00:00
Nextcloud bot 2d87655e2c fix(l10n): Update translations from Transifex
Signed-off-by: Nextcloud bot <bot@nextcloud.com>
2025-08-28 00:12:20 +00:00
Robin Appelman b849f71e8f feat: allow filtering sharing:delete-orphan-shares by share owner or target
Signed-off-by: Robin Appelman <robin@icewind.nl>
2025-08-27 21:46:42 +02:00
provokateurin 4b2a93cf0a chore(Preview): Remove avconv support
Signed-off-by: provokateurin <kate@provokateurin.de>
2025-08-27 20:51:08 +02:00
Thomas Citharel 4111bdbbcf Merge pull request #53458 from nextcloud/fix-putcsv-default
fix(profiler): set default values for fputcsv arguments to avoid depreciation warning
2025-08-27 20:29:16 +02:00
Thomas Citharel 4515974bbd fix(profiler): set default values for fputcsv arguments to avoid depreciation warning
Co-authored-by: Joas Schilling <213943+nickvergessen@users.noreply.github.com>
Signed-off-by: Thomas Citharel <tcit@tcit.fr>
2025-08-27 19:46:30 +02:00
Andy Scherzinger 247b254294 Merge pull request #54127 from nextcloud/feat/federated-calendar-sharing
feat: federated calendar shares
2025-08-27 19:23:20 +02:00
Andy Scherzinger 5262bac6b5 Merge pull request #54681 from nextcloud/style/noid/iconMaterialSymbolVariant
More material symbol variants
2025-08-27 18:54:58 +02:00
nextcloud-command 925aa821f5 chore(assets): Recompile assets
Signed-off-by: nextcloud-command <nextcloud-command@users.noreply.github.com>
2025-08-27 15:22:00 +00:00
Andy Scherzinger 8d9f73d4cf fix(svg): Add height and width
Signed-off-by: Andy Scherzinger <info@andy-scherzinger.de>
2025-08-27 15:19:47 +00:00
Andy Scherzinger 5087e3376a style(icon): Use material symbol variant of upload icon
Signed-off-by: Andy Scherzinger <info@andy-scherzinger.de>
2025-08-27 15:19:47 +00:00
Andy Scherzinger 143cce065d style(icon): Use material symbols for action icon
Signed-off-by: Andy Scherzinger <info@andy-scherzinger.de>
2025-08-27 15:19:47 +00:00
Andy Scherzinger 1248ac93f2 style(icon): Use information-outline instead of information-variant
Signed-off-by: Andy Scherzinger <info@andy-scherzinger.de>
2025-08-27 15:19:47 +00:00
Andy Scherzinger f08da99e79 style(icon): Use material symbol variant of download icon
Signed-off-by: Andy Scherzinger <info@andy-scherzinger.de>
2025-08-27 15:19:46 +00:00
Richard Steinmetz b7dc720848 feat: calendar federation
Signed-off-by: Richard Steinmetz <richard@steinmetz.cloud>
2025-08-27 17:14:15 +02:00
Louis 896fb2fcb5 Merge pull request #54570 from nextcloud/artonge/feat/add_preset_selection_in_settings 2025-08-27 16:54:53 +02:00
Kate 45f5daa45a Merge pull request #54666 from nextcloud/carl/apply-rector 2025-08-27 16:29:58 +02:00
nextcloud-command 673b8c9c9a chore(assets): Recompile assets
Signed-off-by: nextcloud-command <nextcloud-command@users.noreply.github.com>
2025-08-27 14:23:01 +00:00
Louis Chemineau ed02d0df05 feat(Settings): Add section to select preset
Signed-off-by: Louis Chemineau <louis@chmn.me>
2025-08-27 14:20:58 +00:00
Sebastian Krupinski 9e9f3b9d16 Merge pull request #51925 from nextcloud/feat/issue-563-calendar-import
feat: Calendar Import
2025-08-27 09:04:47 -04:00
Salvatore Martire abd971b65b Merge pull request #54667 from nextcloud/fix/51733/othersWithAccessMissing
Restore the "Others with access" section in the sharing tab
2025-08-27 14:57:04 +02:00
SebastianKrupinski 46e624a089 feat: Calendar Import
Signed-off-by: SebastianKrupinski <krupinskis05@gmail.com>
2025-08-27 08:30:33 -04:00
Kate 7512b939b4 Merge pull request #54679 from nextcloud/feat/assistant-colors 2025-08-27 14:25:57 +02:00
nextcloud-command 51468bc2a4 chore(assets): Recompile assets
Signed-off-by: nextcloud-command <nextcloud-command@users.noreply.github.com>
2025-08-27 12:16:29 +00:00
Salvatore Martire 0d6e9f8410 fix(files_sharing): remove condition on "Others with access"
The condition was checking shares.length to display the Others with
access component, but shares is filled only on expanding that section,
rendering the section never visible.

Signed-off-by: Salvatore Martire <4652631+salmart-dev@users.noreply.github.com>
2025-08-27 12:14:34 +00:00
Andy Scherzinger 7f31b6f298 Merge pull request #54677 from nextcloud/style/noid/deleteIconMaterialSymbolVariant
style(icon): Use material symbol variant of delete icon
2025-08-27 13:36:01 +02:00
Daniel 2c53d34ecc Merge pull request #53760 from nextcloud/bug/noid/federation-background-job-same-url-different-token
fix(federation): remove background jobs when removing trusted servers
2025-08-27 13:25:58 +02:00
Ferdinand Thiessen 7a367b6451 feat(theming): provide Nextcloud Assistant theming colors
Signed-off-by: Ferdinand Thiessen <opensource@fthiessen.de>
2025-08-27 13:19:05 +02:00
nextcloud-command e87830b369 chore(assets): Recompile assets
Signed-off-by: nextcloud-command <nextcloud-command@users.noreply.github.com>
2025-08-27 10:38:36 +00:00
Andy Scherzinger ec84001af7 style(icon): Use material symbol variant of delete icon
Signed-off-by: Andy Scherzinger <info@andy-scherzinger.de>
2025-08-27 12:29:34 +02:00
Andy Scherzinger 176f575a92 Merge pull request #54654 from nextcloud/fix/guest-l10n
fix(core): more understandable guest avatar menu notice
2025-08-27 12:29:14 +02:00
Daniel Kesselberg af38184b6f fix: Make DummyJobList.getJobsIterator return an interable instance
iterator_to_array on PHP 8.1 does not accept an array and fails hard with a type error

Signed-off-by: Daniel Kesselberg <mail@danielkesselberg.de>
2025-08-27 12:28:16 +02:00
Daniel Kesselberg 8b1ac839d7 fix(federation): remove background jobs when removing trusted servers
Add event listener to remove matching background jobs for trusted server url.

Signed-off-by: Daniel Kesselberg <mail@danielkesselberg.de>
2025-08-27 12:28:15 +02:00
Carl Schwan d0f819ba99 Merge pull request #54409 from nextcloud/comments-db-multiple
perf(comments): Add a way to get comments for multiple objects at the same time
2025-08-27 11:57:24 +02:00
Carl Schwan daa69a6b12 fix(cypress): Adapt test to check the correct string
Signed-off-by: Carl Schwan <carl.schwan@nextclound.com>
2025-08-27 09:48:58 +02:00
Andy Scherzinger a9635044e3 Merge pull request #54533 from nextcloud/cal-edit-private-event
fix(caldav): show confidential event if writable
2025-08-27 09:41:57 +02:00
Joas Schilling 5704bcda6b Merge pull request #54672 from nextcloud/followup/54165/avoid-future-confusion
docs: Adjust comment to avoid future confusion
2025-08-27 08:54:00 +02:00
Joas Schilling 6a6f5f8086 docs: Adjust comment to avoid future confusion
Signed-off-by: Joas Schilling <coding@schilljs.com>
2025-08-27 08:16:04 +02:00
nextcloud-command d31d6ec707 chore(assets): Recompile assets
Signed-off-by: nextcloud-command <nextcloud-command@users.noreply.github.com>
2025-08-27 07:52:40 +02:00
skjnldsv 7ef1216392 fix(core): more understandable guest avatar menu notice
Signed-off-by: skjnldsv <skjnldsv@protonmail.com>
2025-08-27 07:52:40 +02:00
Nextcloud bot 784f76d2b9 fix(l10n): Update translations from Transifex
Signed-off-by: Nextcloud bot <bot@nextcloud.com>
2025-08-27 00:12:30 +00:00
Andy Scherzinger bc567d3c55 Merge pull request #54648 from nextcloud/fix/sharing-entry
fix(files_sharing): correctly apply icons and copy share link
2025-08-26 22:28:37 +02:00
nextcloud-command d27f648b06 chore(assets): Recompile assets
Signed-off-by: nextcloud-command <nextcloud-command@users.noreply.github.com>
2025-08-26 20:09:36 +00:00
Ferdinand Thiessen cde2618356 fix(files_sharing): correctly apply icons and copy share link
1. Use correct color
2. Do not mix legacy icon classes with MDI icons this will show two
   icons...
3. Use proper aria label and put status into the title

Signed-off-by: Ferdinand Thiessen <opensource@fthiessen.de>
2025-08-26 20:07:37 +00:00
Andy Scherzinger da5c912f3c Merge pull request #54655 from nextcloud/fix/fix-owncloud-dav-properties-migration
feat(repair): Migrate oc_properties.propertyvalue to the correct type if needed
2025-08-26 21:58:04 +02:00
Kate f8d6d9eb4a Merge pull request #54671 from nextcloud/chore/noid/hub25Autumn 2025-08-26 21:34:39 +02:00
Andy Scherzinger c145870da5 chore(version): Hub 10 -> Hub 25 Autumn
Signed-off-by: Andy Scherzinger <info@andy-scherzinger.de>
2025-08-26 20:25:38 +02:00
Robin Appelman ff65b61d9c Merge pull request #54580 from nextcloud/sharded-cache-move-jail
fix moving cache items from cache jail with sharding
2025-08-26 20:20:35 +02:00
Ferdinand Thiessen a75060a07d Merge pull request #54656 from nextcloud/fix/color-variables
feat: add proper element colors and adjust setup checks
2025-08-26 19:40:43 +02:00
nextcloud-command 6f9bb77b09 chore(assets): Recompile assets
Signed-off-by: nextcloud-command <nextcloud-command@users.noreply.github.com>
2025-08-26 16:53:17 +00:00
Ferdinand Thiessen f9030fce04 chore: fix eslint rules for non breaking spaces in Vue files
Signed-off-by: Ferdinand Thiessen <opensource@fthiessen.de>
2025-08-26 18:35:55 +02:00
Ferdinand Thiessen 79184f3aed feat(settings): migrate setup checks to Vue to prevent visual issues
Signed-off-by: Ferdinand Thiessen <opensource@fthiessen.de>
2025-08-26 18:35:55 +02:00
Ferdinand Thiessen 03332a1d13 fix(theming): provide proper element color variables for status colors
Signed-off-by: Ferdinand Thiessen <opensource@fthiessen.de>
2025-08-26 18:35:55 +02:00
Salvatore Martire 853835b201 Merge pull request #54538 from nextcloud/fix/54537/sharePasswordCheckboxCheckedByDefault
Do not always check password checkbox when sharing by mail and without password
2025-08-26 18:13:30 +02:00
Salvatore Martire eaaa9070ed fix(files_sharing): do not check password checkbox by default
The checkbox appears checked when no password is set (share.password ===
undefined)

Signed-off-by: Salvatore Martire <4652631+salmart-dev@users.noreply.github.com>
2025-08-26 17:50:30 +02:00
Kate 4c3ad2eea8 Merge pull request #54643 from nextcloud/chore/deps/openapi-extractor-v1.8.2 2025-08-26 17:31:58 +02:00
Marcel Klehr 2f4a8bb89c Merge pull request #54620 from nextcloud/fix/set-typed-value
fix(AppConfig#setTypedValue): Catch AppConfigUnknownKeyException
2025-08-26 16:59:26 +02:00
Carl Schwan 5835599fa1 chore(rector): Apply current rector config
Signed-off-by: Carl Schwan <carl.schwan@nextclound.com>
2025-08-26 16:27:26 +02:00
Joas Schilling c9712b4953 Merge pull request #54605 from nextcloud/bugfix/noid/fix-2fa-setup-during-ephemeral-session
fix(2fa): Fix 2FA session setup when ephemeral session is used
2025-08-26 15:45:15 +02:00
Côme Chilliet 78d69d5581 feat(repair): Migrate oc_properties.propertyvalue to the correct type if needed
This is sometimes needed when migration from owncloud

Signed-off-by: Côme Chilliet <come.chilliet@nextcloud.com>
2025-08-26 15:11:26 +02:00
Richard Steinmetz 38c8ea75ae Merge pull request #54650 from nextcloud/fix/carddav/get-key-string
fix(carddav): IAddressBook::getKey() should return a string
2025-08-26 14:20:53 +02:00
Kate 6bb941bfad Merge pull request #54618 from nextcloud/fix/oauth2/limit-getToken-grant_type-values 2025-08-26 14:07:56 +02:00
Kate 3c1dc006c6 Merge pull request #54558 from nextcloud/feat/noid/set-preset-controller 2025-08-26 14:06:59 +02:00
Benjamin Gaussorgues 7c8c593f5c Merge pull request #54197 from nextcloud/perf/reduce_mount_db_load 2025-08-26 13:53:34 +02:00
Richard Steinmetz caf664ea43 fix(carddav): IAddressBook::getKey() should return a string
Signed-off-by: Richard Steinmetz <richard@steinmetz.cloud>
2025-08-26 13:31:04 +02:00
Maxence Lange a81d58970c feat(lexicon): set/get current preset from controller
Signed-off-by: Maxence Lange <maxence@artificial-owl.com>

feat(lexicon): set/get current preset from controller
Signed-off-by: Maxence Lange <maxence@artificial-owl.com>
2025-08-26 09:00:04 -01:00
John Molakvoæ 5ab16f46f9 Merge pull request #54606 from nextcloud/release/32.0.0_beta_3 2025-08-26 11:18:12 +02:00
provokateurin 5283bb7c2f chore(deps): Update nextcloud/openapi-extractor to v1.8.2
Signed-off-by: provokateurin <kate@provokateurin.de>
2025-08-26 10:21:10 +02:00
provokateurin aab11d35d3 fix(OCS): Add IRequest::getFormat to determine the response Content-Type the same way everywhere
Signed-off-by: provokateurin <kate@provokateurin.de>
2025-08-26 09:50:03 +02:00
Benjamin Gaussorgues 39e2e70084 feat(mounts): optimize load of mounts by filtering on database side
Signed-off-by: Benjamin Gaussorgues <benjamin.gaussorgues@nextcloud.com>
2025-08-26 08:55:24 +02:00
Côme Chilliet d18b10ca11 Merge pull request #54632 from nextcloud/fix/fix-trashbin-plugin-dot-not-allowed
fix(trashbin): Fix errors in the log on MOVE operations
2025-08-26 08:33:58 +02:00
Nextcloud bot 983fd30285 fix(l10n): Update translations from Transifex
Signed-off-by: Nextcloud bot <bot@nextcloud.com>
2025-08-26 00:13:50 +00:00
Maxence Lange 18351be65c Merge pull request #54520 from nextcloud/fix/noid/preset-for-userconfig
feat(preset): compare userconfig lexicon entries
2025-08-25 18:19:44 -01:00
Andy Scherzinger e878ff9119 Merge pull request #54617 from nextcloud/followup/54608/double-space
test: Fix double space
2025-08-25 21:08:40 +02:00
Ferdinand Thiessen bc1f9184b7 Merge pull request #54624 from nextcloud/fix/missing-variable
fix(theming): add missing `color-text-success` variable
2025-08-25 19:45:43 +02:00
Ferdinand Thiessen 985eca3cf5 Merge pull request #54631 from nextcloud/fix/account-menu-mask
fix(core): make sure no alpha mask is applied on the account menu
2025-08-25 18:47:13 +02:00
Worker Builder 73cc94b80e build(hub): 32.0.0 beta 3
Signed-off-by: Worker Builder <worker-builder@nextcloud.com>
2025-08-25 17:55:23 +02:00
nextcloud-command 9c1d306a3e chore(assets): Recompile assets
Signed-off-by: nextcloud-command <nextcloud-command@users.noreply.github.com>
2025-08-25 15:37:09 +00:00
Kate a23c087737 Merge pull request #54625 from nextcloud/fix/files_sharing/reject-pending-remote-share 2025-08-25 17:34:54 +02:00
Ferdinand Thiessen b3467b433c fix(core): make sure no alpha mask is applied on the account menu
Signed-off-by: Ferdinand Thiessen <opensource@fthiessen.de>
2025-08-25 17:15:09 +02:00
Ferdinand Thiessen a7a78054aa Merge pull request #54621 from nextcloud/fix/setup-checks-color
fix(settings): adjust colors used by legacy setup checks ui
2025-08-25 17:14:40 +02:00
Côme Chilliet 3c14f9f933 Merge pull request #53578 from nextcloud/jtr-chore-mbstring-func-overload
chore: remove all `mbstring.func_overload` references
2025-08-25 17:00:28 +02:00
Côme Chilliet 5c9c2fe5e2 fix(trashbin): Fix errors in the log on MOVE operations
dirname will return '.' for files at the root, which will cause an
 Exception that gets logged.
Instead use \Sabre\Uri\split like other sabre plugins, to get an empty
 string for root directory.

Signed-off-by: Côme Chilliet <come.chilliet@nextcloud.com>
2025-08-25 16:36:55 +02:00
Kate 111350b17b Merge pull request #54607 from nextcloud/fix/fix-type-error-in-encryption 2025-08-25 15:34:35 +02:00
provokateurin d4b05201d8 fix(files_sharing): Correctly reject pending remote shares
Signed-off-by: provokateurin <kate@provokateurin.de>
2025-08-25 14:55:27 +02:00
Ferdinand Thiessen 55f968aa4b fix(theming): add missing color-text-success variable
This was planned to be added and already documented.
The use case is some rare occurences where we use success like text.

Signed-off-by: Ferdinand Thiessen <opensource@fthiessen.de>
2025-08-25 14:48:06 +02:00
Ferdinand Thiessen 494806ee64 fix(settings): adjust colors used by legacy setup checks ui
Even if the rule is `background-color` in reality it is the color of the
icon thus needs the text color.

Signed-off-by: Ferdinand Thiessen <opensource@fthiessen.de>
2025-08-25 14:27:15 +02:00
Marcel Klehr dbb5705152 fix(AppConfig#setTypedValue): Catch AppConfigUnknownKeyException
Signed-off-by: Marcel Klehr <mklehr@gmx.net>
2025-08-25 14:06:29 +02:00
Maxence Lange b7cdfddeda feat(preset): compare userconfig lexicon entries
Signed-off-by: Maxence Lange <maxence@artificial-owl.com>
2025-08-25 11:05:56 -01:00
provokateurin 1b4722c330 fix(oauth2): Limit allowed grant_type values in getToken
Signed-off-by: provokateurin <kate@provokateurin.de>
2025-08-25 13:40:35 +02:00
Joas Schilling 51f596e0c2 test: Fix double space
Signed-off-by: Joas Schilling <coding@schilljs.com>
2025-08-25 13:29:17 +02:00
Joas Schilling 4082a45d6d test: Adjust the hashes and signatures
Signed-off-by: Joas Schilling <coding@schilljs.com>
2025-08-25 13:19:36 +02:00
Josh 7996a3eb3a chore: remove mbstring.func_overload from htaccessWithValidModifiedContent
Signed-off-by: Josh <josh.t.richards@gmail.com>
2025-08-25 13:19:36 +02:00
Josh 6d55224d46 chore: remove mbstring.func_overload from setUploadLimit .user.ini
Signed-off-by: Josh <josh.t.richards@gmail.com>
2025-08-25 13:19:36 +02:00
Josh 8ac796cd86 chore: remove mbstring.func_overload from setUploadLimit .htaccess
Signed-off-by: Josh <josh.t.richards@gmail.com>
2025-08-25 13:19:36 +02:00
Josh 130cb05ea4 chore: remove mbstring.func_overload check from checkserver()
Signed-off-by: Josh <josh.t.richards@gmail.com>
2025-08-25 13:19:36 +02:00
Josh 61d5567913 chore: remove mbstring.func_overload from .htaccess
Deprecated in PHP 7.2
Removed in PHP 8.0
https://www.php.net/manual/en/mbstring.configuration.php#ini.mbstring.func-overload

Signed-off-by: Josh <josh.t.richards@gmail.com>
2025-08-25 13:19:36 +02:00
Josh aaed9a9045 chore: remove mbstring.func_overload from .user.ini
Deprecated in PHP 7.2
Removed in PHP 8.0
https://www.php.net/manual/en/mbstring.configuration.php#ini.mbstring.func-overload

Signed-off-by: Josh <josh.t.richards@gmail.com>
2025-08-25 13:19:36 +02:00
Côme Chilliet 07b046f762 fix(tests): Fix fopen in mock returning null which is not a valid return
Signed-off-by: Côme Chilliet <come.chilliet@nextcloud.com>
2025-08-25 13:18:50 +02:00
Côme Chilliet 8c21f2b19c fix(encryption): Fix TypeError when trying to decrypt unencrypted file
Signed-off-by: Côme Chilliet <come.chilliet@nextcloud.com>
2025-08-25 13:18:50 +02:00
Richard Steinmetz 1091e59b90 Merge pull request #54608 from nextcloud/bugfix/noid/fix-failing-certificate-tests
tests: Update testing certificates that expired after 10 years
2025-08-25 13:17:27 +02:00
Joas Schilling 894fda1a4d ci: Update testing certificates that expired after 10 years
Signed-off-by: Joas Schilling <coding@schilljs.com>
2025-08-25 12:15:57 +02:00
Kate 32f5f6e08e Merge pull request #54598 from nextcloud/chore/flake/haze 2025-08-25 10:43:45 +02:00
Joas Schilling 11aa997da3 fix(2fa): Fix 2FA session setup when ephemeral session is used
Signed-off-by: Joas Schilling <coding@schilljs.com>
2025-08-25 10:39:17 +02:00
Kate d8635180c7 Merge pull request #54099 from nextcloud/translatable-string 2025-08-25 10:01:22 +02:00
provokateurin e2d028a3d6 chore(flake): Add haze
Signed-off-by: provokateurin <kate@provokateurin.de>
2025-08-25 08:18:35 +02:00
provokateurin 2b3fec2900 chore(flake): Update flake.lock
Signed-off-by: provokateurin <kate@provokateurin.de>
2025-08-25 08:18:04 +02:00
Nextcloud bot 1b05582b97 fix(l10n): Update translations from Transifex
Signed-off-by: Nextcloud bot <bot@nextcloud.com>
2025-08-25 00:12:17 +00:00
Andy Scherzinger bd6c16ea88 Merge pull request #53503 from nextcloud/fix-json-decoding-groups-excluded-from-share
fix(sharing): fix json decoding the list of groups excluded from sharing
2025-08-24 21:38:31 +02:00
Enjeck C eb0ddffaaa fix: Make some strings in CodeIntegrity.php translatable
Signed-off-by: Enjeck C <patrathewhiz@gmail.com>
2025-08-24 20:23:51 +02:00
Nextcloud bot cc3938da72 fix(l10n): Update translations from Transifex
Signed-off-by: Nextcloud bot <bot@nextcloud.com>
2025-08-24 00:12:07 +00:00
Ferdinand Thiessen 5d6dd51c8d Merge pull request #54588 from nextcloud/dependabot/npm_and_yarn/p-limit-7.1.0
chore(deps): bump p-limit from 6.2.0 to 7.1.0
2025-08-23 19:04:07 +02:00
nextcloud-command fad1f67156 chore(assets): Recompile assets
Signed-off-by: nextcloud-command <nextcloud-command@users.noreply.github.com>
2025-08-23 13:24:05 +00:00
dependabot[bot] 5c4bd8b03f chore(deps): bump p-limit from 6.2.0 to 7.1.0
Bumps [p-limit](https://github.com/sindresorhus/p-limit) from 6.2.0 to 7.1.0.
- [Release notes](https://github.com/sindresorhus/p-limit/releases)
- [Commits](https://github.com/sindresorhus/p-limit/compare/v6.2.0...v7.1.0)

---
updated-dependencies:
- dependency-name: p-limit
  dependency-version: 7.1.0
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-08-23 01:27:03 +00:00
Nextcloud bot 3712ba2c2a fix(l10n): Update translations from Transifex
Signed-off-by: Nextcloud bot <bot@nextcloud.com>
2025-08-23 00:12:18 +00:00
Richard Steinmetz 01c6095052 Merge pull request #54575 from nextcloud/fix/outlook-user-agent
fix: use appropriate user agent string when syncing Outlook calendar subscriptions
2025-08-22 23:03:30 +02:00
Matt Evans 4d770f1c3c fix: use appropriate user agent string when syncing Outlook calendar subscriptions
Signed-off-by: Matt Evans <matt@mattevans.org>
Signed-off-by: Richard Steinmetz <richard@steinmetz.cloud>
2025-08-22 22:19:31 +02:00
John Molakvoæ e0baf69e45 Merge pull request #54581 from nextcloud/release/32.0.0_beta_2 2025-08-22 20:39:34 +02:00
John Molakvoæ 301f567089 Merge pull request #54329 from nextcloud/fix/files-batch-actions 2025-08-22 20:34:30 +02:00
nextcloud-command 14132593f7 chore(assets): Recompile assets
Signed-off-by: nextcloud-command <nextcloud-command@users.noreply.github.com>
2025-08-22 18:18:11 +00:00
skjnldsv 3bdff6b3f5 fix(files): filter non batch actions in header
Signed-off-by: skjnldsv <skjnldsv@protonmail.com>
2025-08-22 18:16:09 +00:00
Worker Builder 012a3a4584 build(hub): 32.0.0 beta 2
Signed-off-by: Worker Builder <worker-builder@nextcloud.com>
2025-08-22 17:47:43 +00:00
Robin Appelman afe77e32f2 test: test moving cache items from cache jail with sharding
Signed-off-by: Robin Appelman <robin@icewind.nl>
2025-08-22 19:21:33 +02:00
Robin Appelman 1a716578fd fix: fix moving cache items from cache jail with sharding
Signed-off-by: Robin Appelman <robin@icewind.nl>
2025-08-22 19:21:31 +02:00
Maksim Sukharev ec024eb590 Merge pull request #54579 from nextcloud/fix/noid/sidebar-outline-icons 2025-08-22 17:57:05 +02:00
nextcloud-command 606c8b9a26 chore(assets): Recompile assets
Signed-off-by: nextcloud-command <nextcloud-command@users.noreply.github.com>
2025-08-22 15:03:47 +00:00
Maksim Sukharev ef0c1bd11c fix(Sidebar): change icons to outline
Signed-off-by: Maksim Sukharev <antreesy.web@gmail.com>
2025-08-22 15:29:04 +02:00
Maxence Lange 2975a99848 Merge pull request #54335 from nextcloud/feat/noid/lexicon-appconfig-controller
feat(lexicon): get value type from lexicon
2025-08-22 12:27:48 -01:00
John Molakvoæ b243fd31bd Merge pull request #54561 from nextcloud/fix/files-breakpoint-improvements 2025-08-22 13:33:06 +02:00
John Molakvoæ 8473d95f84 Merge branch 'master' into fix/files-breakpoint-improvements
Signed-off-by: John Molakvoæ <skjnldsv@users.noreply.github.com>
2025-08-22 10:04:13 +02:00
Nextcloud bot 10ef3a56d7 fix(l10n): Update translations from Transifex
Signed-off-by: Nextcloud bot <bot@nextcloud.com>
2025-08-22 00:12:17 +00:00
Ferdinand Thiessen ec6d6dfb44 Merge pull request #54554 from nextcloud/feat/new-background
feat(theming): add new default background for Nextcloud 32
2025-08-21 23:27:12 +02:00
Ferdinand Thiessen 591601d000 fix(theming): previews need the same file extension as their originals
Signed-off-by: Ferdinand Thiessen <opensource@fthiessen.de>
2025-08-21 19:22:45 +02:00
Ferdinand Thiessen fc5dad8343 feat(theming): add new default background for Nextcloud 32
With Nextcloud 32 we ship a new default background: "Fluid" by Jo Myoung Hee.
Copyright by Nextcloud GmbH under the CC-BY-SA-4.0

- added the background compressed as WebP (90% quality)
- set as new default
- added license reference and move theming related reuse config

Signed-off-by: Ferdinand Thiessen <opensource@fthiessen.de>
2025-08-21 19:22:45 +02:00
dependabot[bot] 85198fdc2a Merge pull request #54568 from nextcloud/dependabot/npm_and_yarn/cipher-base-1.0.6 2025-08-21 16:45:15 +00:00
Côme Chilliet c88a5b451c Merge pull request #54519 from nextcloud/fix/fix-duplicated-category-migration
fix: Fix unique constraint violation in oc_vcategory migration
2025-08-21 18:01:33 +02:00
dependabot[bot] 7b82f1330c Merge pull request #54569 from nextcloud/dependabot/npm_and_yarn/sha.js-2.4.12 2025-08-21 15:47:17 +00:00
dependabot[bot] 8cbbb9ab15 chore(deps): bump sha.js from 2.4.11 to 2.4.12
Bumps [sha.js](https://github.com/crypto-browserify/sha.js) from 2.4.11 to 2.4.12.
- [Changelog](https://github.com/browserify/sha.js/blob/master/CHANGELOG.md)
- [Commits](https://github.com/crypto-browserify/sha.js/compare/v2.4.11...v2.4.12)

---
updated-dependencies:
- dependency-name: sha.js
  dependency-version: 2.4.12
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-08-21 15:23:09 +00:00
dependabot[bot] fa38efe0ad chore(deps): bump cipher-base from 1.0.4 to 1.0.6
Bumps [cipher-base](https://github.com/crypto-browserify/cipher-base) from 1.0.4 to 1.0.6.
- [Changelog](https://github.com/browserify/cipher-base/blob/master/CHANGELOG.md)
- [Commits](https://github.com/crypto-browserify/cipher-base/compare/v1.0.4...v1.0.6)

---
updated-dependencies:
- dependency-name: cipher-base
  dependency-version: 1.0.6
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-08-21 15:23:00 +00:00
Jonas Stene c3857ee12e chore: compile assets
Signed-off-by: skjnldsv <skjnldsv@protonmail.com>
2025-08-21 17:03:22 +02:00
Maxence Lange 4f0d5634c7 fix(lexicon): convert instead of cast
Signed-off-by: Maxence Lange <maxence@artificial-owl.com>
2025-08-21 13:17:19 -01:00
Maxence Lange 295f0503d5 feat(lexicon): get value type from lexicon
Signed-off-by: Maxence Lange <maxence@artificial-owl.com>
2025-08-21 13:01:10 -01:00
Kate a7a8738e63 Merge pull request #54559 from nextcloud/feat/noid/store-description 2025-08-21 15:01:02 +02:00
Julien Veyssier 8a8e1c83ec Merge pull request #54545 from nextcloud/enh/noid/add-token-invalidated-event
Dispatch new event when invalidating an authentication token
2025-08-21 14:31:31 +02:00
Jonas Stene e44305a8b7 fix(files): changed prop to kebab-case
Co-authored-by: Ferdinand Thiessen <opensource@fthiessen.de>
Signed-off-by: Jonas Stene <jonas@stene.li>
2025-08-21 14:26:33 +02:00
Jonas Stene b35569abe2 fix(files): added noLabel prop to UploadPicker with breakpoint
Signed-off-by: Jonas Stene <jonas@stene.li>
2025-08-21 14:26:33 +02:00
Jonas Stene f9fa5abcf2 fix(files): removed breakpoint on gridView button
Signed-off-by: Jonas Stene <jonas@stene.li>
2025-08-21 14:26:33 +02:00
Maxence Lange fc8d188c9f feat(lexicon): remove limitation to store definition only on cli
Signed-off-by: Maxence Lange <maxence@artificial-owl.com>
2025-08-21 10:21:24 -01:00
Julien Veyssier 3d36834284 feat(auth): include the token entity in TokenInvalidatedEvent
Signed-off-by: Julien Veyssier <julien-nc@posteo.net>
2025-08-21 12:42:44 +02:00
Julien Veyssier 4a35837741 feat(auth): adjust PublicKeyTokenProviderTest
Signed-off-by: Julien Veyssier <julien-nc@posteo.net>
2025-08-21 12:42:44 +02:00
Julien Veyssier 3da919c783 feat(auth): dispatch new TokenInvalidatedEvent when PublicKeyTokenProvider::invalidateToken is called
Signed-off-by: Julien Veyssier <julien-nc@posteo.net>
2025-08-21 12:42:44 +02:00
Julien Veyssier 8ffd30bbf9 feat(auth): dispatch new TokenInvalidatedEvent when PublicKeyTokenProvider::invalidateTokenById is called
Signed-off-by: Julien Veyssier <julien-nc@posteo.net>
2025-08-21 12:42:43 +02:00
Carl Schwan 94e2af0302 perf(comments): Also cache the comments count
Since we now have an easy way to fetch the comments count.

Signed-off-by: Carl Schwan <carl.schwan@nextclound.com>
2025-08-21 11:30:56 +02:00
John Molakvoæ d5417d63e0 Merge pull request #54410 from nextcloud/fix/sharing-status-action-sidebar-promise-return 2025-08-21 11:24:15 +02:00
Carl Schwan fa5548a1f3 perf(comments): Add a way to get comments for multiple objects at the same time
Signed-off-by: Carl Schwan <carl.schwan@nextclound.com>
2025-08-21 11:18:57 +02:00
John Molakvoæ 2e816535cf Merge pull request #54552 from makoehr/extend-user-ini-with-lines-from-configuration 2025-08-21 11:09:11 +02:00
Mathias Koehrer 109b454e48 feat: allow updater to extend .user.ini on update
When running nextcloud with a web hoster it might be necessary
to extend .user.ini after each update (e.g. adding memory_limit).
To automate this step, an additional config entry may be provided
in config.php that specifies the lines to be added to .user.ini.

If the config option 'user_ini_additional_lines' exists, the provided
value (string or array of strings) will be added to .user.ini.

Signed-off-by: Mathias Koehrer <koehrer08@koehrer-mail.de>
Signed-off-by: skjnldsv <skjnldsv@protonmail.com>
2025-08-21 10:43:37 +02:00
Nextcloud bot bdea4337d3 fix(l10n): Update translations from Transifex
Signed-off-by: Nextcloud bot <bot@nextcloud.com>
2025-08-21 00:12:23 +00:00
Richard Steinmetz ac0abb34b0 Merge pull request #54535 from nextcloud/fix/caldav/public-calendar-read-only
fix(caldav): public calendars are always read-only
2025-08-20 23:30:35 +02:00
Git'Fellow 4d65b91b06 Merge pull request #54541 from nextcloud/fixAppManagerNullObjs
fix(AppManager): Argument must be of type array|object
2025-08-20 20:12:48 +02:00
John Molakvoæ 93b97f49e5 Merge pull request #54540 from nextcloud/chore/cypress-runners 2025-08-20 17:59:07 +02:00
Git'Fellow b36e4e3824 fix(AppManager): Argument must be of type array|object
Signed-off-by: Git'Fellow <12234510+solracsf@users.noreply.github.com>
2025-08-20 17:16:33 +02:00
Kate f7cdb228c9 Merge pull request #54530 from nextcloud/fix/lowercase-profile-page-route-name 2025-08-20 17:07:02 +02:00
John Molakvoæ 4636e165e5 Merge pull request #54331 from nextcloud/fix/systemtags-heighh-align 2025-08-20 16:58:52 +02:00
skjnldsv 12a4ff89c3 chore: increase cypress runners to 10
Signed-off-by: skjnldsv <skjnldsv@protonmail.com>
2025-08-20 16:56:14 +02:00
John Molakvoæ 0958fda1b4 Merge pull request #54338 from nextcloud/chore/cleanup 2025-08-20 16:54:41 +02:00
nextcloud-command 205abce9ec chore(assets): Recompile assets
Signed-off-by: nextcloud-command <nextcloud-command@users.noreply.github.com>
2025-08-20 14:31:29 +00:00
nextcloud-command 5bfce62a48 chore(assets): Recompile assets
Signed-off-by: nextcloud-command <nextcloud-command@users.noreply.github.com>
2025-08-20 14:30:37 +00:00
nextcloud-command 5c995edd07 chore(assets): Recompile assets
Signed-off-by: nextcloud-command <nextcloud-command@users.noreply.github.com>
2025-08-20 14:30:36 +00:00
skjnldsv a8a2edcaa8 fix(systemtags): picker tag list height alignment
Signed-off-by: skjnldsv <skjnldsv@protonmail.com>
2025-08-20 14:29:18 +00:00
skjnldsv 88d7275c60 chore(files_sharing): remove unused legacy scripts
Signed-off-by: skjnldsv <skjnldsv@protonmail.com>
2025-08-20 14:28:36 +00:00
skjnldsv ce41a2d727 chore: add window.d.ts to release ignore list
Signed-off-by: skjnldsv <skjnldsv@protonmail.com>
2025-08-20 16:21:34 +02:00
skjnldsv 9404059f6c fix(files_sharing): sharing status action permission and sidebar await
Signed-off-by: skjnldsv <skjnldsv@protonmail.com>
2025-08-20 16:21:09 +02:00
John Molakvoæ 971dfd7cbf fix(files): properly type window Files APIs
Signed-off-by: John Molakvoæ <skjnldsv@users.noreply.github.com>
2025-08-20 16:21:09 +02:00
Maxence Lange 776a689e09 Merge pull request #54385 from nextcloud/fix/51946/split-discovery-capacities
feat(ocm): split ocm discovery and capacities
2025-08-20 12:31:25 -01:00
Kate 357292f937 Merge pull request #54532 from nextcloud/fix/theming/manifest-not-found-response 2025-08-20 15:23:31 +02:00
Maxence Lange fa60488ee7 feat(ocm): split ocm discovery and capacities
Signed-off-by: Maxence Lange <maxence@artificial-owl.com>
2025-08-20 11:57:01 -01:00
Richard Steinmetz 1a781d5afa fix(caldav): public calendars are always read-only
Signed-off-by: Richard Steinmetz <richard@steinmetz.cloud>
2025-08-20 13:37:47 +02:00
Arusekk b3c5707a0c fix(caldav): show confidential event if writable
If a party can edit the calendar/event, just display it instead of
hiding the details and risking overwrites.
This might be considered a change impacting privacy,
but it actually improves semantics.

Relevant test updates included, improving assertion correctness.

I think all the relevant use cases are solved by this.

Closes https://github.com/nextcloud/server/issues/5551
Closes https://github.com/nextcloud/calendar/issues/4044
Closes https://github.com/nextcloud/server/issues/11214

Signed-off-by: Arusekk <floss@arusekk.pl>
Signed-off-by: Richard Steinmetz <richard@steinmetz.cloud>
2025-08-20 13:34:38 +02:00
provokateurin 7b6303dcc9 fix(theming): Document correct response schema if app is not enabled
Signed-off-by: provokateurin <kate@provokateurin.de>
2025-08-20 12:45:47 +02:00
zak39 a98ba27a0f fix(route): Change "core.ProfilePage.index" to lowercase
Signed-off-by: zak39 <fotia.baptiste@hotmail.com>
2025-08-20 11:52:36 +02:00
Nextcloud bot 4733369764 fix(l10n): Update translations from Transifex
Signed-off-by: Nextcloud bot <bot@nextcloud.com>
2025-08-20 00:12:26 +00:00
John Molakvoæ 92e71d23f2 Merge pull request #53752 from nextcloud/trasbin-event-fixes
fix: fix trashbin restore events
2025-08-19 21:17:06 +02:00
Robin Appelman f3850b141a fix: fix trashbin restore events
Signed-off-by: Robin Appelman <robin@icewind.nl>
2025-08-19 20:46:56 +02:00
John Molakvoæ b9d97f2c47 Merge pull request #54523 from nextcloud/fix/changelog-gen 2025-08-19 20:46:01 +02:00
Andy Scherzinger acf04ff752 Merge pull request #54450 from nextcloud/dependabot/npm_and_yarn/marked-16.1.2
chore(deps): bump marked from 16.1.1 to 16.1.2
2025-08-19 20:17:56 +02:00
nextcloud-command 5867a0f252 chore(assets): Recompile assets
Signed-off-by: nextcloud-command <nextcloud-command@users.noreply.github.com>
2025-08-19 17:58:56 +00:00
dependabot[bot] 11e1d15de9 chore(deps): bump marked from 16.1.1 to 16.1.2
Dependabot couldn't find the original pull request head commit, 72842cc765c044e08939a1342f03d4252001c7ed.
2025-08-19 17:52:14 +00:00
John Molakvoæ fc0ecf963b Merge pull request #54452 from nextcloud/dependabot/npm_and_yarn/msw-2.10.5 2025-08-19 19:48:58 +02:00
skjnldsv f304b80414 fix: changelog generation workflow
Signed-off-by: skjnldsv <skjnldsv@protonmail.com>
2025-08-19 19:06:08 +02:00
dependabot[bot] b56b23cfb2 chore(deps-dev): bump msw from 2.10.4 to 2.10.5
Bumps [msw](https://github.com/mswjs/msw) from 2.10.4 to 2.10.5.
- [Release notes](https://github.com/mswjs/msw/releases)
- [Changelog](https://github.com/mswjs/msw/blob/main/CHANGELOG.md)
- [Commits](https://github.com/mswjs/msw/compare/v2.10.4...v2.10.5)

---
updated-dependencies:
- dependency-name: msw
  dependency-version: 2.10.5
  dependency-type: direct:development
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-08-19 19:05:18 +02:00
John Molakvoæ e43e44d2ee Merge pull request #54502 from nextcloud/release/32.0.0_beta_1
32.0.0 beta 1
2025-08-19 18:23:36 +02:00
John Molakvoæ 3cacd7c035 Merge pull request #54497 from nextcloud/chore/update-nextcloud-vue-8-29 2025-08-19 18:23:27 +02:00
skjnldsv 434adefe71 chore: update tests snapshots
Signed-off-by: skjnldsv <skjnldsv@protonmail.com>
2025-08-19 18:05:43 +02:00
nextcloud-command 4102af5324 chore(assets): Recompile assets
Signed-off-by: nextcloud-command <nextcloud-command@users.noreply.github.com>
2025-08-19 16:04:14 +00:00
nextcloud-command 9ba63683ae chore(assets): Recompile assets
Signed-off-by: nextcloud-command <nextcloud-command@users.noreply.github.com>
2025-08-19 16:01:31 +00:00
Ferdinand Thiessen f933a41a5a chore: adjust webpack plugin to also extract inline asset licenses
Signed-off-by: Ferdinand Thiessen <opensource@fthiessen.de>
2025-08-19 16:01:30 +00:00
Ferdinand Thiessen 4b2fcac882 fix: also add version to OC.config during setup
Signed-off-by: Ferdinand Thiessen <opensource@fthiessen.de>
2025-08-19 16:01:30 +00:00
Ferdinand Thiessen 7d73be6bdd fix(files): ensure no invalid color is passed to NcColorPicker
Signed-off-by: Ferdinand Thiessen <opensource@fthiessen.de>
2025-08-19 16:01:30 +00:00
skjnldsv d408327373 chore: update @nextcloud/vue to v8.29.1
Signed-off-by: skjnldsv <skjnldsv@protonmail.com>
2025-08-19 16:01:30 +00:00
Ferdinand Thiessen cd310afe1a chore: update @nextcloud/vue to v8.29.0
Signed-off-by: Ferdinand Thiessen <opensource@fthiessen.de>
2025-08-19 16:01:30 +00:00
Côme Chilliet e2b8ef769c fix: Fix unique constraint violation in oc_vcategory migration
Signed-off-by: Côme Chilliet <come.chilliet@nextcloud.com>
2025-08-19 17:02:54 +02:00
Worker Builder c7ba72552c build(hub): 32.0.0 beta 1
Signed-off-by: Worker Builder <worker-builder@nextcloud.com>
2025-08-19 07:31:56 +00:00
Thomas Citharel e2195a2f6d fix(sharing): fix json decoding the list of groups excluded from sharing
json_decode() returns stdclass by default instead of an associative object, which can't be used for
array_diff or array_intersect later

Signed-off-by: Thomas Citharel <tcit@tcit.fr>
2025-06-16 08:30:31 +02:00
1408 changed files with 39417 additions and 12781 deletions
+7
View File
@@ -49,5 +49,12 @@ module.exports = {
'@typescript-eslint/no-explicit-any': 'warn',
},
},
{
files: ['*.vue'],
rules: {
'no-irregular-whitespace': 'off',
'vue/no-irregular-whitespace': 'error',
},
},
],
}
+4 -4
View File
@@ -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
+1 -1
View File
@@ -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
+1 -1
View File
@@ -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
+1 -1
View File
@@ -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
+3 -3
View File
@@ -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:
+3 -3
View File
@@ -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
+6 -6
View File
@@ -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
+3 -3
View File
@@ -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
+3 -3
View File
@@ -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
+3 -3
View File
@@ -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
+3 -3
View File
@@ -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
+3 -3
View File
@@ -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'
+2 -2
View File
@@ -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
+2 -2
View File
@@ -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
+4 -4
View File
@@ -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
+1 -1
View File
@@ -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
+2 -2
View File
@@ -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
+2 -2
View File
@@ -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
+1 -1
View File
@@ -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
+5 -5
View File
@@ -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
+1 -1
View File
@@ -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
+1 -1
View File
@@ -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 }}
+3 -3
View File
@@ -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
+3 -3
View File
@@ -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
+3 -3
View File
@@ -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
+2 -2
View File
@@ -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
+2 -2
View File
@@ -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
+2 -2
View File
@@ -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
+3 -3
View File
@@ -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
+3 -3
View File
@@ -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
+3 -3
View File
@@ -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
+3 -3
View File
@@ -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
+3 -3
View File
@@ -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
+3 -3
View File
@@ -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
+3 -3
View File
@@ -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
+3 -3
View File
@@ -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
+1 -1
View File
@@ -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
+8 -8
View File
@@ -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
+1 -1
View File
@@ -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
-1
View File
@@ -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>
+3 -1
View File
@@ -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
View File
File diff suppressed because one or more lines are too long
+1
View File
@@ -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: {} }),
}
+7 -1
View File
@@ -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 }
+4 -49
View File
@@ -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()];
}
}
+36
View File
@@ -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);");
+34
View File
@@ -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());
}
}
+196
View File
@@ -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": {}
}
}
}
}
}
}
}
}
}
+3 -1
View File
@@ -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 -2
View File
@@ -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',
+1
View File
@@ -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",
+1
View File
@@ -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",
+14
View File
@@ -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",
+14
View File
@@ -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",
+22 -1
View File
@@ -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)
);
})
);
}
+100 -10
View File
@@ -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();
}
}
+10 -6
View File
@@ -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);
+23 -2
View File
@@ -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
+96 -48
View File
@@ -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 {
+6 -1
View File
@@ -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'];
}
+23 -1
View File
@@ -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;
}
+47
View File
@@ -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;
}
}
}
+2 -1
View File
@@ -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;
}
}
+156
View File
@@ -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);
}
}
+173
View File
@@ -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