Compare commits

..

87 Commits

Author SHA1 Message Date
Nextcloud bot
b84025d59b fix(l10n): Update translations from Transifex
Signed-off-by: Nextcloud bot <bot@nextcloud.com>
2025-10-09 00:49:32 +00:00
Julius Knorr
b866db3559 Merge pull request #7287 from holger-dev/holger-dev-patch-readme-iosApp
Update README.md
2025-10-08 22:50:44 +02:00
Julius Knorr
9e3fbae6c3 Merge pull request #7138 from nextcloud/automated/noid/main-fix-npm-audit 2025-10-08 18:33:30 +02:00
Julius Knorr
a20f2444b9 Merge pull request #7182 from nextcloud/feat/package-node-npm-engines-update
build: update node and npm engines versions
2025-10-08 12:29:57 +02:00
Julius Knorr
682778b6b6 Merge pull request #7277 from nextcloud/dependabot/npm_and_yarn/tar-fs-2.1.4
Chore(deps-dev): Bump tar-fs from 2.1.3 to 2.1.4
2025-10-07 09:14:57 +02:00
Nextcloud bot
39693e842b fix(l10n): Update translations from Transifex
Signed-off-by: Nextcloud bot <bot@nextcloud.com>
2025-10-07 00:31:51 +00:00
grnd-alt
4c09c65e9e Merge pull request #7293 from nextcloud/chore/bump-min-php-version
Chore/bump min php version
2025-10-06 18:17:49 +02:00
grnd-alt
637b8666b1 chore: bump min php to 8.2
Signed-off-by: grnd-alt <github@belakkaf.net>
2025-10-06 16:57:36 +02:00
grnd-alt
9e098f17c7 fix: comments with mentions cant be submitted
Signed-off-by: grnd-alt <github@belakkaf.net>
2025-10-06 16:33:11 +02:00
nextcloud-command
7c74e28735 fix(deps): Fix npm audit
Signed-off-by: GitHub <noreply@github.com>
2025-10-05 03:26:30 +00:00
Holger Heidkamp
56b2a63723 Update README.md
Replace the outdated (non-existing) iOS app reference with the newly released and fully working app Next Deck.
The new app looks great and works reliably.

Signed-off-by: Holger Heidkamp <holger@heidkamp.dev>
2025-10-04 07:06:49 +02:00
Nextcloud bot
90f51f931f fix(l10n): Update translations from Transifex
Signed-off-by: Nextcloud bot <bot@nextcloud.com>
2025-10-04 00:31:15 +00:00
Nextcloud bot
0115fee075 fix(l10n): Update translations from Transifex
Signed-off-by: Nextcloud bot <bot@nextcloud.com>
2025-10-03 00:33:29 +00:00
Nextcloud bot
42cf69a05c fix(l10n): Update translations from Transifex
Signed-off-by: Nextcloud bot <bot@nextcloud.com>
2025-10-02 00:32:21 +00:00
Nextcloud bot
20b5716e33 fix(l10n): Update translations from Transifex
Signed-off-by: Nextcloud bot <bot@nextcloud.com>
2025-10-01 00:32:01 +00:00
Nextcloud bot
60d02ff837 fix(l10n): Update translations from Transifex
Signed-off-by: Nextcloud bot <bot@nextcloud.com>
2025-09-30 00:32:00 +00:00
Luka Trovic
604018008c Merge pull request #7258 from nextcloud/carl/psalm
refactor: Fix psalm issues
2025-09-29 14:39:56 +02:00
dependabot[bot]
e2d83ce90c Chore(deps-dev): Bump tar-fs from 2.1.3 to 2.1.4
Bumps [tar-fs](https://github.com/mafintosh/tar-fs) from 2.1.3 to 2.1.4.
- [Commits](https://github.com/mafintosh/tar-fs/compare/v2.1.3...v2.1.4)

---
updated-dependencies:
- dependency-name: tar-fs
  dependency-version: 2.1.4
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-09-28 23:43:38 +02:00
Carl Schwan
5cf486150a refactor: Fix psalm issues
- Add typing for most of the services, controllers and mappers
- Add api doc for mappers
- Use vendor-bin for psalm
- Use attributes for controllers
- Fix upload of attachments

Signed-off-by: Carl Schwan <carl.schwan@nextcloud.com>
2025-09-28 11:49:06 +02:00
github-actions[bot]
64741e455d Merge pull request #7278 from nextcloud/automated/noid/main-update-nextcloud-ocp
[main] Update nextcloud/ocp dependency
2025-09-28 03:04:59 +00:00
nextcloud-command
9a7eb9b8de chore(dev-deps): Bump nextcloud/ocp package
Signed-off-by: GitHub <noreply@github.com>
2025-09-28 02:40:40 +00:00
Nextcloud bot
8ad3c9ac5c fix(l10n): Update translations from Transifex
Signed-off-by: Nextcloud bot <bot@nextcloud.com>
2025-09-28 00:31:35 +00:00
Nextcloud bot
445d842481 fix(l10n): Update translations from Transifex
Signed-off-by: Nextcloud bot <bot@nextcloud.com>
2025-09-27 00:32:12 +00:00
Luka Trovic
6a7c02d8d8 Merge pull request #7154 from vdiezel/fix-reorder-card-rest-endpoint
fix: parse arguments to CardService.reorder correctly to int
2025-09-26 15:26:44 +02:00
Luka Trovic
f347f96474 Merge pull request #7261 from nextcloud/fix/correct-pointer-for-title
fix: use text cursor for card title on dashboard
2025-09-26 07:42:06 +02:00
Nextcloud bot
5828f90163 fix(l10n): Update translations from Transifex
Signed-off-by: Nextcloud bot <bot@nextcloud.com>
2025-09-26 00:32:16 +00:00
grnd-alt
6ccb4b7c3f fix: use text cursor for card title on dashboard
Signed-off-by: grnd-alt <github@belakkaf.net>
2025-09-25 13:38:21 +02:00
Luka Trovic
094efdaa06 Merge pull request #7255 from nextcloud/fix-missing-push-notifications
fix: missing push notifications
2025-09-25 11:16:21 +02:00
Luka Trovic
161003faf9 chore: update base query count
Signed-off-by: Luka Trovic <luka@nextcloud.com>
2025-09-25 11:08:54 +02:00
Luka Trovic
fa31256b8a fix: missing push notifications
Signed-off-by: Luka Trovic <luka@nextcloud.com>
2025-09-25 11:08:54 +02:00
Nextcloud bot
c590c25f68 fix(l10n): Update translations from Transifex
Signed-off-by: Nextcloud bot <bot@nextcloud.com>
2025-09-25 00:31:40 +00:00
Luka Trovic
05832f69e9 Merge pull request #7266 from nextcloud/redirect-if-RewriteBase-enabled
fix: redirect to cleaner URL if RewriteBase is enabled
2025-09-24 20:26:18 +02:00
Luka Trovic
c9bacabd4c fix: redirect to cleaner URL if RewriteBase is enabled
Signed-off-by: Luka Trovic <luka@nextcloud.com>
2025-09-24 19:15:49 +02:00
Nextcloud bot
4abf895d22 fix(l10n): Update translations from Transifex
Signed-off-by: Nextcloud bot <bot@nextcloud.com>
2025-09-24 00:31:53 +00:00
Nextcloud bot
d216e56dac fix(l10n): Update translations from Transifex
Signed-off-by: Nextcloud bot <bot@nextcloud.com>
2025-09-23 00:31:50 +00:00
Luka Trovic
15514162f3 Merge pull request #7256 from nextcloud/bump-nextcloud-vue-8.31.0
Chore(deps): Bump @nextcloud/vue from 8.27.0 to 8.31.0
2025-09-22 21:34:25 +02:00
Luka Trovic
b3bb29157c Chore(deps): Bump @nextcloud/vue from 8.27.0 to 8.31.0
Signed-off-by: Luka Trovic <luka@nextcloud.com>
2025-09-22 20:45:52 +02:00
Nextcloud bot
12b656dd8c fix(l10n): Update translations from Transifex
Signed-off-by: Nextcloud bot <bot@nextcloud.com>
2025-09-22 00:31:30 +00:00
github-actions[bot]
661eea3018 Merge pull request #7250 from nextcloud/automated/noid/main-update-nextcloud-ocp
[main] Update nextcloud/ocp dependency
2025-09-21 03:31:25 +00:00
nextcloud-command
ecd3cb42de chore(dev-deps): Bump nextcloud/ocp package
Signed-off-by: GitHub <noreply@github.com>
2025-09-21 02:40:50 +00:00
github-actions[bot]
f49c8f6ee4 Merge pull request #7231 from nextcloud/automated/noid/main-update-nextcloud-ocp
[main] Update nextcloud/ocp dependency
2025-09-19 17:09:19 +00:00
nextcloud-command
96b56a2447 chore(dev-deps): Bump nextcloud/ocp package
Signed-off-by: GitHub <noreply@github.com>
2025-09-19 19:03:21 +02:00
Nextcloud bot
26badb58dd fix(l10n): Update translations from Transifex
Signed-off-by: Nextcloud bot <bot@nextcloud.com>
2025-09-19 00:32:01 +00:00
Nextcloud bot
04d9433bdd fix(l10n): Update translations from Transifex
Signed-off-by: Nextcloud bot <bot@nextcloud.com>
2025-09-18 00:31:43 +00:00
Nextcloud bot
a69eb654fd fix(l10n): Update translations from Transifex
Signed-off-by: Nextcloud bot <bot@nextcloud.com>
2025-09-17 00:31:43 +00:00
Elizabeth Danzberger
ab5ccb7bc1 Merge pull request #7238 from nextcloud/bugfix/noid/activity-icons
fix(darkmode): Fix activity icon colors
2025-09-16 14:57:18 -04:00
Luka Trovic
f054cc2fbd Merge pull request #7225 from ABartelt/fix/mysql-error-1093-user-deletion
fix: resolve MySQL Error 1093 when deleting users from boards
2025-09-16 20:43:16 +02:00
Luka Trovic
1afc5cdbcc Merge pull request #7237 from nextcloud/bugfix/7207/fix-colors
fix: Fix colors from due dates and done
2025-09-16 20:16:18 +02:00
Joas Schilling
1a6e5929b2 fix(darkmode): Fix activity icon colors
Signed-off-by: Joas Schilling <coding@schilljs.com>
2025-09-16 15:17:54 +02:00
Joas Schilling
74a3ab5008 fix: Fix colors from due dates and done
Signed-off-by: Joas Schilling <coding@schilljs.com>
2025-09-16 14:42:54 +02:00
Luka Trovic
0ebd05e649 Merge pull request #7210 from nextcloud/style/noid/deleteMaterialSymbolOutline
Migrate delete icon to Material Symbol outline variant
2025-09-16 13:43:08 +02:00
Luka Trovic
3611c8f8ac Merge pull request #7159 from Somebodyisnobody/patch-1
Docs: Updating docs for API endpoints for uploading attachments
2025-09-16 10:31:31 +02:00
Luka Trovic
da3b857ab0 Merge pull request #7165 from nextcloud/get-cards-at-once
perf(cards): fetch all cards at once
2025-09-16 10:31:00 +02:00
Nextcloud bot
199b698cb7 fix(l10n): Update translations from Transifex
Signed-off-by: Nextcloud bot <bot@nextcloud.com>
2025-09-16 00:31:49 +00:00
Nextcloud bot
fb1132652a fix(l10n): Update translations from Transifex
Signed-off-by: Nextcloud bot <bot@nextcloud.com>
2025-09-15 00:31:00 +00:00
Nextcloud bot
f78c3d42df fix(l10n): Update translations from Transifex
Signed-off-by: Nextcloud bot <bot@nextcloud.com>
2025-09-14 00:30:54 +00:00
Nextcloud bot
4a879ab1fb fix(l10n): Update translations from Transifex
Signed-off-by: Nextcloud bot <bot@nextcloud.com>
2025-09-13 00:31:23 +00:00
Nextcloud bot
b4e9dd39cc fix(l10n): Update translations from Transifex
Signed-off-by: Nextcloud bot <bot@nextcloud.com>
2025-09-12 00:31:48 +00:00
Nextcloud bot
9035a23af8 fix(l10n): Update translations from Transifex
Signed-off-by: Nextcloud bot <bot@nextcloud.com>
2025-09-11 00:31:52 +00:00
Arne Bartelt
bffa4d0925 fix: resolve MySQL Error 1093 when deleting users from boards
Fixes #7125 and #7069 by implementing a two-step deletion process
that avoids MySQL's restriction on deleting from a table while
selecting from it in a subquery.

The fix separates the SELECT and DELETE operations:
1. First query: Get card IDs for assignments to delete
2. Second query: Delete assignments using the collected IDs

This approach works on all supported database systems (MySQL 5.7+,
MySQL 8.0+, MariaDB 10.x+) and follows MySQL's official best practices
for handling Error 1093: 'You can't specify target table for update in FROM clause'.

The issue occurred because the original deleteByParticipantOnBoard method
used a subquery that referenced the same table being deleted from,
which MySQL prohibits but MariaDB allows (explaining why it worked
in development but failed in production).

Signed-off-by: Arne Bartelt <arne.bartelt@gmail.com>
Signed-off-by: Arne Bartelt <Arne.Bartelt@gmail.com>
2025-09-10 16:39:51 +02:00
Luka Trovic
516294ee23 Merge pull request #6640 from ludij/feature/3331-dynamic-column-width
Feature/3331 dynamic column width
2025-09-10 09:50:07 +02:00
Luutzen Dijkstra
5a39996357 feature(3331): aligned stack children spacing and fixed linting issues
Signed-off-by: Luutzen Dijkstra <luutzen.dijkstra@gmail.com>
2025-09-10 09:44:36 +02:00
Luutzen Dijkstra
569a0961fd feature(3331): card image refactor
Signed-off-by: Luutzen Dijkstra <luutzen.dijkstra@gmail.com>
2025-09-10 09:44:36 +02:00
Luutzen Dijkstra
376f46b7ba feature(3331): scroll column instead of page for board and overview
Signed-off-by: Luutzen Dijkstra <luutzen.dijkstra@gmail.com>
2025-09-10 09:44:36 +02:00
Luutzen Dijkstra
447dc60f37 feature(3331): removed duplicate flex-grow property from GlobalSearchResults
Signed-off-by: Luutzen Dijkstra <luutzen.dijkstra@gmail.com>
2025-09-10 09:44:36 +02:00
Luutzen Dijkstra
480af39d56 feature(3331): specify phrasing for search results depending on view
Signed-off-by: Luutzen Dijkstra <luutzen.dijkstra@gmail.com>
2025-09-10 09:44:36 +02:00
Luutzen Dijkstra
7d18f3a48b feature(3331): improve accessibility attributes for overview column titles
Signed-off-by: Luutzen Dijkstra <luutzen.dijkstra@gmail.com>
2025-09-10 09:44:36 +02:00
Luutzen Dijkstra
51f5b8f023 fix(3331): bind title attribute on h3 in stack
Signed-off-by: Luutzen Dijkstra <luutzen.dijkstra@gmail.com>
2025-09-10 09:44:36 +02:00
Luutzen Dijkstra
1e68e1ff03 feature(3331): use existingIndex to define existingCard
Signed-off-by: Luutzen Dijkstra <luutzen.dijkstra@gmail.com>
2025-09-10 09:44:36 +02:00
Luutzen Dijkstra
a13619690e fix(3331): fixed border around add list form in boards
Signed-off-by: Luutzen Dijkstra <luutzen.dijkstra@gmail.com>
2025-09-10 09:44:36 +02:00
Luutzen Dijkstra
3115363c28 feature(3331): made board, overview, stack and search result cards width behave more dynamic
Signed-off-by: Luutzen Dijkstra <luutzen.dijkstra@gmail.com>
2025-09-10 09:44:36 +02:00
rakekniven
6ace1867e1 Merge pull request #7222 from nextcloud/fix/4466/refproviders-translator-comments 2025-09-09 14:02:53 +02:00
Julien Veyssier
af4a610a25 feat(reference): add translators comments in reference providers
Signed-off-by: Julien Veyssier <julien-nc@posteo.net>
2025-09-09 12:57:14 +02:00
Nextcloud bot
d40661a985 fix(l10n): Update translations from Transifex
Signed-off-by: Nextcloud bot <bot@nextcloud.com>
2025-09-09 00:33:39 +00:00
Nextcloud bot
0a14a0dfd8 fix(l10n): Update translations from Transifex
Signed-off-by: Nextcloud bot <bot@nextcloud.com>
2025-09-08 00:32:13 +00:00
github-actions[bot]
72d3cfa771 Merge pull request #7215 from nextcloud/automated/noid/main-update-nextcloud-ocp
[main] Update nextcloud/ocp dependency
2025-09-07 03:02:50 +00:00
nextcloud-command
411c0d9d46 chore(dev-deps): Bump nextcloud/ocp package
Signed-off-by: GitHub <noreply@github.com>
2025-09-07 02:39:28 +00:00
Nextcloud bot
ec33c26fe4 fix(l10n): Update translations from Transifex
Signed-off-by: Nextcloud bot <bot@nextcloud.com>
2025-09-07 00:33:30 +00:00
Nextcloud bot
8fcb4a47ae fix(l10n): Update translations from Transifex
Signed-off-by: Nextcloud bot <bot@nextcloud.com>
2025-09-06 00:31:26 +00:00
Andy Scherzinger
44d244f9aa style(icon): Migrate delete icon to Material Symbol outline variant
Signed-off-by: Andy Scherzinger <info@andy-scherzinger.de>
2025-09-05 18:26:28 +02:00
Nextcloud bot
3ed1fbcc40 fix(l10n): Update translations from Transifex
Signed-off-by: Nextcloud bot <bot@nextcloud.com>
2025-09-05 00:32:02 +00:00
Luka Trovic
7394cb2d48 Merge pull request #7204 from nextcloud/update-main-version
feat(deps): Add Nextcloud 33 support on main
2025-09-04 21:24:36 +02:00
Joas Schilling
4f39328d59 feat(deps): Add Nextcloud 33 support on main
Signed-off-by: Joas Schilling <coding@schilljs.com>
2025-09-04 12:22:14 +02:00
Nextcloud bot
b3d7e1b128 build: update node and npm engines versions
Signed-off-by: nextcloud-command <nextcloud-command@users.noreply.github.com>
2025-08-25 12:49:07 +00:00
Carl Schwan
a3fa72341d perf(cards): fetch all cards at once
Instead of one by one

Signed-off-by: Carl Schwan <carl.schwan@nextclound.com>
2025-08-13 16:01:52 +02:00
Viktor Diezel
eee35ac6ad fix: parse arguments to CardService.reorder correctly to int
Signed-off-by: Viktor Diezel <viktor.diezel@posteo.de>
2025-08-07 11:54:55 +02:00
Somebodyisnobody
152181ff67 docs: Update API docs
Clarifying documentation for the attachment upload endpoints.

Signed-off-by: Somebodyisnobody <35230554+Somebodyisnobody@users.noreply.github.com>
2025-08-07 11:43:42 +02:00
112 changed files with 4835 additions and 1728 deletions

View File

@@ -13,6 +13,23 @@ updates:
- juliushaertl
- luka-nextcloud
- package-ecosystem: npm
target-branch: stable32
versioning-strategy: lockfile-only
directory: "/"
schedule:
interval: weekly
day: saturday
time: "03:15"
timezone: Europe/Paris
ignore:
- dependency-name: "*"
update-types: ["version-update:semver-major"]
open-pull-requests-limit: 30
labels:
- 3. to review
- dependencies
- package-ecosystem: npm
target-branch: stable31
versioning-strategy: lockfile-only

View File

@@ -22,7 +22,7 @@ jobs:
node-version: [20.x]
# containers: [1, 2, 3]
php-versions: [ '8.2' ]
server-versions: [ 'stable32' ]
server-versions: [ 'master' ]
env:
extensions: mbstring, iconv, fileinfo, intl, sqlite, pdo_sqlite, zip, gd, apcu

View File

@@ -28,7 +28,7 @@ jobs:
matrix:
php-versions: ['8.1']
databases: ['sqlite', 'mysql', 'pgsql']
server-versions: ['stable32']
server-versions: ['master']
name: php${{ matrix.php-versions }}-${{ matrix.databases }}-${{ matrix.server-versions }}

View File

@@ -24,7 +24,7 @@ jobs:
strategy:
fail-fast: false
matrix:
branches: ['main', 'master', 'stable31', 'stable30']
branches: ['main', 'master', 'stable32', 'stable31', 'stable30']
name: npm-audit-fix-${{ matrix.branches }}

View File

@@ -23,7 +23,7 @@ jobs:
strategy:
fail-fast: false
matrix:
branches: ['main', 'master', 'stable31', 'stable30']
branches: ['main', 'master', 'stable32', 'stable31', 'stable30']
name: update-nextcloud-ocp-${{ matrix.branches }}

View File

@@ -6,6 +6,7 @@
- Adrian Missy <adrian.missy@onewavestudios.com>
- Alexandru Puiu <alexpuiu20@yahoo.com>
- Arne Bartelt <arne.bartelt@gmail.com>
- Chandi Langecker <git@chandi.it>
- Christoph Wurst <christoph@winzerhof-wurst.at>
- Gary Kim <gary@garykim.dev>

View File

@@ -5,24 +5,6 @@
# Changelog
All notable changes to this project will be documented in this file.
## 1.16.0
### Added
- feat: update default content @luka-nextcloud [#6740](https://github.com/nextcloud/deck/pull/6740)
- feat: add board import and export @luka-nextcloud [#6872](https://github.com/nextcloud/deck/pull/6872)
- feat: use outline icons @luka-nextcloud [#7114](https://github.com/nextcloud/deck/pull/7114)
- Add OCC commands for global calendar feature opt-in and opt-out in Deck @Fledermaus-20 [#7080](https://github.com/nextcloud/deck/pull/7080)
- [stable32] Migrate delete icon to Material Symbol outline variant @backportbot [#7211](https://github.com/nextcloud/deck/pull/7211)
- Chore(deps): Bump @nextcloud/vue from 8.27.0 to 8.31.0 @luka-nextcloud [#7271](https://github.com/nextcloud/deck/pull/7271)
### Fixed
- CSV export fixes @gidan80 [#6800](https://github.com/nextcloud/deck/pull/6800)
- [stable32] fix: Fix colors from due dates and done @backportbot [#7239](https://github.com/nextcloud/deck/pull/7239)
- [stable32] fix(darkmode): Fix activity icon colors @backportbot [#7241](https://github.com/nextcloud/deck/pull/7241)
- [stable32] fix: redirect to cleaner URL if RewriteBase is enabled @backportbot [#7268](https://github.com/nextcloud/deck/pull/7268)
- [stable32] fix: missing push notifications @backportbot [#7270](https://github.com/nextcloud/deck/pull/7270)
- [stable32] fix: use text cursor for card title on dashboard @backportbot [#7273](https://github.com/nextcloud/deck/pull/7273)
- [stable32] fix: parse arguments to CardService.reorder correctly to int @backportbot [#7276](https://github.com/nextcloud/deck/pull/7276)
## 1.16.0-beta.1
### Added

View File

@@ -24,7 +24,7 @@ Deck is a kanban style organization tool aimed at personal planning and project
### Mobile apps
- [Nextcloud Deck app for Android](https://github.com/stefan-niedermann/nextcloud-deck) - It is available in [F-Droid](https://f-droid.org/de/packages/it.niedermann.nextcloud.deck/) and the [Google Play Store](https://play.google.com/store/apps/details?id=it.niedermann.nextcloud.deck.play)
- [Nextcloud Deck app for iOS](https://github.com/StCyr/deck-react-native) - It is available in [Apple App store](https://apps.apple.com/ml/app/nextcloud-deck/id1570892788)
- Nextcloud Deck app for iOS - It is available in [Apple App store](https://apps.apple.com/de/app/next-deck/id6752478755)
### 3rd-Party Integrations

View File

@@ -20,7 +20,7 @@
- 🚀 Get your project organized
</description>
<version>1.16.0</version>
<version>2.0.0-dev.0</version>
<licence>agpl</licence>
<author>Julius Härtl</author>
<namespace>Deck</namespace>
@@ -42,7 +42,7 @@
<database min-version="9.4">pgsql</database>
<database>sqlite</database>
<database min-version="8.0">mysql</database>
<nextcloud min-version="32" max-version="32"/>
<nextcloud min-version="33" max-version="33"/>
</dependencies>
<background-jobs>
<job>OCA\Deck\Cron\DeleteCron</job>

View File

@@ -9,31 +9,35 @@
}
],
"require": {
"justinrainbow/json-schema": "^6.0"
"justinrainbow/json-schema": "^6.0",
"bamarni/composer-bin-plugin": "^1.8"
},
"require-dev": {
"roave/security-advisories": "dev-master",
"phpunit/phpunit": "^9",
"nextcloud/coding-standard": "^1.1",
"nextcloud/ocp": "dev-stable32",
"psalm/phar": "^5.13"
"nextcloud/ocp": "dev-master"
},
"config": {
"optimize-autoloader": true,
"allow-plugins": {
"composer/package-versions-deprecated": true
"composer/package-versions-deprecated": true,
"bamarni/composer-bin-plugin": true
},
"platform": {
"php": "8.1"
}
},
"scripts": {
"post-install-cmd": [
"@composer bin all install --ansi"
],
"lint": "find . -name \\*.php -not -path './vendor/*' -print0 | xargs -0 -n1 php -l",
"cs:check": "php-cs-fixer fix --dry-run --diff",
"cs:fix": "php-cs-fixer fix",
"psalm": "psalm.phar",
"psalm:update-baseline": "psalm.phar --update-baseline",
"psalm:fix": "psalm.phar --alter --issues=InvalidReturnType,InvalidNullableReturnType,MismatchingDocblockParamType,MismatchingDocblockReturnType,MissingParamType,InvalidFalsableReturnType",
"psalm": "psalm",
"psalm:update-baseline": "psalm --threads=$(nproc) --no-cache --update-baseline",
"psalm:fix": "psalm --alter --issues=InvalidReturnType,InvalidNullableReturnType,MismatchingDocblockParamType,MismatchingDocblockReturnType,MissingParamType,InvalidFalsableReturnType",
"test": [
"@test:unit",
"@test:integration"

109
composer.lock generated
View File

@@ -4,8 +4,65 @@
"Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies",
"This file is @generated automatically"
],
"content-hash": "a802f567c811977afdf6203368ae43e9",
"content-hash": "263f9ff9e6a13d50ab09bc9f4e06b749",
"packages": [
{
"name": "bamarni/composer-bin-plugin",
"version": "1.8.2",
"source": {
"type": "git",
"url": "https://github.com/bamarni/composer-bin-plugin.git",
"reference": "92fd7b1e6e9cdae19b0d57369d8ad31a37b6a880"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/bamarni/composer-bin-plugin/zipball/92fd7b1e6e9cdae19b0d57369d8ad31a37b6a880",
"reference": "92fd7b1e6e9cdae19b0d57369d8ad31a37b6a880",
"shasum": ""
},
"require": {
"composer-plugin-api": "^2.0",
"php": "^7.2.5 || ^8.0"
},
"require-dev": {
"composer/composer": "^2.0",
"ext-json": "*",
"phpstan/extension-installer": "^1.1",
"phpstan/phpstan": "^1.8",
"phpstan/phpstan-phpunit": "^1.1",
"phpunit/phpunit": "^8.5 || ^9.5",
"symfony/console": "^2.8.52 || ^3.4.35 || ^4.4 || ^5.0 || ^6.0",
"symfony/finder": "^2.8.52 || ^3.4.35 || ^4.4 || ^5.0 || ^6.0",
"symfony/process": "^2.8.52 || ^3.4.35 || ^4.4 || ^5.0 || ^6.0"
},
"type": "composer-plugin",
"extra": {
"class": "Bamarni\\Composer\\Bin\\BamarniBinPlugin"
},
"autoload": {
"psr-4": {
"Bamarni\\Composer\\Bin\\": "src"
}
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"MIT"
],
"description": "No conflicts for your bin dependencies",
"keywords": [
"composer",
"conflict",
"dependency",
"executable",
"isolation",
"tool"
],
"support": {
"issues": "https://github.com/bamarni/composer-bin-plugin/issues",
"source": "https://github.com/bamarni/composer-bin-plugin/tree/1.8.2"
},
"time": "2022-10-31T08:38:03+00:00"
},
{
"name": "justinrainbow/json-schema",
"version": "6.4.2",
@@ -376,16 +433,16 @@
},
{
"name": "nextcloud/ocp",
"version": "dev-stable32",
"version": "dev-master",
"source": {
"type": "git",
"url": "https://github.com/nextcloud-deps/ocp.git",
"reference": "5f0db0fc0e5f09a4c8ac06ebc5dedefd21d53603"
"reference": "9a2e6c0bf6f2d87e1db8d18063a5bedf85040bb2"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/nextcloud-deps/ocp/zipball/5f0db0fc0e5f09a4c8ac06ebc5dedefd21d53603",
"reference": "5f0db0fc0e5f09a4c8ac06ebc5dedefd21d53603",
"url": "https://api.github.com/repos/nextcloud-deps/ocp/zipball/9a2e6c0bf6f2d87e1db8d18063a5bedf85040bb2",
"reference": "9a2e6c0bf6f2d87e1db8d18063a5bedf85040bb2",
"shasum": ""
},
"require": {
@@ -395,10 +452,11 @@
"psr/event-dispatcher": "^1.0",
"psr/log": "^3.0.2"
},
"default-branch": true,
"type": "library",
"extra": {
"branch-alias": {
"dev-stable32": "32.0.0-dev"
"dev-master": "33.0.0-dev"
}
},
"notification-url": "https://packagist.org/downloads/",
@@ -418,9 +476,9 @@
"description": "Composer package containing Nextcloud's public OCP API and the unstable NCU API",
"support": {
"issues": "https://github.com/nextcloud-deps/ocp/issues",
"source": "https://github.com/nextcloud-deps/ocp/tree/stable32"
"source": "https://github.com/nextcloud-deps/ocp/tree/master"
},
"time": "2025-09-10T00:46:52+00:00"
"time": "2025-09-27T00:45:05+00:00"
},
{
"name": "nikic/php-parser",
@@ -1072,41 +1130,6 @@
],
"time": "2024-12-05T13:48:26+00:00"
},
{
"name": "psalm/phar",
"version": "5.26.1",
"source": {
"type": "git",
"url": "https://github.com/psalm/phar.git",
"reference": "8a38e7ad04499a0ccd2c506fd1da6fc01fff4547"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/psalm/phar/zipball/8a38e7ad04499a0ccd2c506fd1da6fc01fff4547",
"reference": "8a38e7ad04499a0ccd2c506fd1da6fc01fff4547",
"shasum": ""
},
"require": {
"php": "^7.1 || ^8.0"
},
"conflict": {
"vimeo/psalm": "*"
},
"bin": [
"psalm.phar"
],
"type": "library",
"notification-url": "https://packagist.org/downloads/",
"license": [
"MIT"
],
"description": "Composer-based Psalm Phar",
"support": {
"issues": "https://github.com/psalm/phar/issues",
"source": "https://github.com/psalm/phar/tree/5.26.1"
},
"time": "2024-09-09T16:22:43+00:00"
},
{
"name": "psr/clock",
"version": "1.0.0",

View File

@@ -14,6 +14,7 @@
input[type=submit].icon-confirm {
border-color: var(--color-border-maxcontrast) !important;
border-style: solid;
border-left: none;
}

View File

@@ -6,7 +6,7 @@ The REST API provides access for authenticated users to their data inside the De
# Prerequisites
- All requests require a `OCS-APIRequest` HTTP header to be set to `true` and a `Content-Type` of `application/json`.
- All requests require a `OCS-APIRequest` HTTP header to be set to `true` and a `Content-Type` of `application/json`. This does not apply to the endpoint for uploading attachments, which consumes `multipart/form-data`.
- The API is located at https://nextcloud.local/index.php/apps/deck/api/v1.0
- All request parameters are required, unless otherwise specified
@@ -733,6 +733,7 @@ The board list endpoint supports setting an `If-Modified-Since` header to limit
| Parameter | Type | Description |
| --------- | ------- | --------------------------------------- |
| labelId | Integer | The label id to assign to the card |
#### Response
##### 200 Success
@@ -997,10 +998,12 @@ The request can fail with a bad request response for the following reasons:
#### Request data
The request is performed as `multipart/form-data`.
| Parameter | Type | Description |
| --------- | ------- | --------------------------------------------- |
| type | String | The type of the attachement |
| file | Binary | File data to add as an attachment |
| --------- | ------- | ----------------------------------------------------------------------------------------------- |
| type | String | The type of the attachement. Use `file` or `deck_file`. |
| file | Binary | File data to add as an attachment together with the `filename` parameter according to RFC 7578. |
- Prior to Deck version v1.3.0 (API v1.0), attachments were stored within deck. For this type of attachments `deck_file` was used as the default type of attachments
- Starting with Deck version 1.3.0 (API v1.1) files are stored within the users regular Nextcloud files and the type `file` has been introduced for that
@@ -1022,12 +1025,13 @@ The request can fail with a bad request response for the following reasons:
#### Request data
| Parameter | Type | Description |
| --------- | ------- | --------------------------------------------- |
| type | String | The type of the attachement |
| file | Binary | File data to add as an attachment |
The request is performed as `multipart/form-data`.
| Parameter | Type | Description |
| --------- | ------- | ----------------------------------------------------------------------------------------------- |
| type | String | The type of the attachement. For now only `deck_file` is supported as an attachment type. |
| file | Binary | File data to add as an attachment together with the `filename` parameter according to RFC 7578. |
For now only `deck_file` is supported as an attachment type.
#### Response

View File

@@ -9,9 +9,11 @@ OC.L10N.register(
"Missing a temporary folder" : "Адсутнічае часовая папка",
"Could not write file to disk" : "Не ўдалося запісаць файл на дыск",
"A PHP extension stopped the file upload" : "Пашырэнне PHP спыніла запампоўванне файла",
"No file uploaded or file size exceeds maximum of %s" : "Файл не запампаваны, або памер файла перавышае максімальны %s",
"copy" : "копія",
"Done" : "Гатова",
"File" : "Файл",
"Invalid date, date format must be YYYY-MM-DD" : "Памылковая дата, дата павінна быць у фармаце ГГГГ-ММ-ДД",
"Cancel" : "Скасаваць",
"Drop your files to upload" : "Перацягніце файлы для запампоўвання",
"File already exists" : "Файл ужо існуе",
@@ -50,6 +52,7 @@ OC.L10N.register(
"Reply" : "Адказаць",
"Update" : "Абнавіць",
"Description" : "Апісанне",
"Formatting help" : "Даведка па фармаціраванні",
"Later today {timeLocale}" : "Пазней сёння {timeLocale}",
"Set due date for later today" : "Задаць дату выканання на пазней сёння",
"Tomorrow {timeLocale}" : "Заўтра {timeLocale}",
@@ -66,6 +69,7 @@ OC.L10N.register(
"Keyboard shortcut" : "Спалучэнне клавіш",
"Action" : "Дзеянне",
"Shift" : "Shift",
"Ctrl" : "Ctrl",
"Search" : "Пошук",
"Enter" : "Enter",
"Shared with you" : "Абагулена з вамі",
@@ -80,6 +84,9 @@ OC.L10N.register(
"Failed to upload {name}" : "Не ўдалося запампаваць {name}",
"Share" : "Абагуліць",
"Personal" : "Асабістыя",
"Example Task 3" : "Прыклад задання 3",
"Example Task 2" : "Прыклад задання 2",
"Example Task 1" : "Прыклад задання 1",
"Today" : "Сёння",
"Tomorrow" : "Заўтра"
},

View File

@@ -7,9 +7,11 @@
"Missing a temporary folder" : "Адсутнічае часовая папка",
"Could not write file to disk" : "Не ўдалося запісаць файл на дыск",
"A PHP extension stopped the file upload" : "Пашырэнне PHP спыніла запампоўванне файла",
"No file uploaded or file size exceeds maximum of %s" : "Файл не запампаваны, або памер файла перавышае максімальны %s",
"copy" : "копія",
"Done" : "Гатова",
"File" : "Файл",
"Invalid date, date format must be YYYY-MM-DD" : "Памылковая дата, дата павінна быць у фармаце ГГГГ-ММ-ДД",
"Cancel" : "Скасаваць",
"Drop your files to upload" : "Перацягніце файлы для запампоўвання",
"File already exists" : "Файл ужо існуе",
@@ -48,6 +50,7 @@
"Reply" : "Адказаць",
"Update" : "Абнавіць",
"Description" : "Апісанне",
"Formatting help" : "Даведка па фармаціраванні",
"Later today {timeLocale}" : "Пазней сёння {timeLocale}",
"Set due date for later today" : "Задаць дату выканання на пазней сёння",
"Tomorrow {timeLocale}" : "Заўтра {timeLocale}",
@@ -64,6 +67,7 @@
"Keyboard shortcut" : "Спалучэнне клавіш",
"Action" : "Дзеянне",
"Shift" : "Shift",
"Ctrl" : "Ctrl",
"Search" : "Пошук",
"Enter" : "Enter",
"Shared with you" : "Абагулена з вамі",
@@ -78,6 +82,9 @@
"Failed to upload {name}" : "Не ўдалося запампаваць {name}",
"Share" : "Абагуліць",
"Personal" : "Асабістыя",
"Example Task 3" : "Прыклад задання 3",
"Example Task 2" : "Прыклад задання 2",
"Example Task 1" : "Прыклад задання 1",
"Today" : "Сёння",
"Tomorrow" : "Заўтра"
},"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);"

View File

@@ -188,6 +188,10 @@ OC.L10N.register(
"Add Attachment" : "Tilføj vedhæftning",
"Choose attachment" : "Vælg en vedhæftning",
"Select Date" : "Vælg dato",
"Later today {timeLocale}" : "Senere i dag {timeLocale}",
"Tomorrow {timeLocale}" : "I morgen {timeLocale}",
"This weekend {timeLocale}" : "Denne weekend {timeLocale}",
"Next week {timeLocale}" : "Næste uge {timeLocale}",
"Set a due date" : "Angiv en forfaldsdato",
"Remove due date" : "Fjern forfaldsdato",
"Mark as done" : "Marker som færdig",

View File

@@ -186,6 +186,10 @@
"Add Attachment" : "Tilføj vedhæftning",
"Choose attachment" : "Vælg en vedhæftning",
"Select Date" : "Vælg dato",
"Later today {timeLocale}" : "Senere i dag {timeLocale}",
"Tomorrow {timeLocale}" : "I morgen {timeLocale}",
"This weekend {timeLocale}" : "Denne weekend {timeLocale}",
"Next week {timeLocale}" : "Næste uge {timeLocale}",
"Set a due date" : "Angiv en forfaldsdato",
"Remove due date" : "Fjern forfaldsdato",
"Mark as done" : "Marker som færdig",

View File

@@ -373,6 +373,7 @@ OC.L10N.register(
"Note: Only the JSON format is supported for importing back into the Deck app." : "Megjegyzés: Csak a JSON formátum támogatott a Kártyák alkalmazásba való importáláskor.",
"Export" : "Exportálás",
"Loading filtered view" : "Szűrt nézet betöltése",
"Search for {searchQuery} in other boards" : "Keresés a(z) {searchQuery} kifejezésre a többi táblában",
"Search for {searchQuery} in all boards" : "Keresés a(z) {searchQuery} kifejezésre az összes táblában",
"No results found" : "Nincs találat",
"Deck board {name}\n* Last modified on {lastMod}" : "{name} kártyatábla\n* Legutóbb módosítva: {lastMod}",

View File

@@ -371,6 +371,7 @@
"Note: Only the JSON format is supported for importing back into the Deck app." : "Megjegyzés: Csak a JSON formátum támogatott a Kártyák alkalmazásba való importáláskor.",
"Export" : "Exportálás",
"Loading filtered view" : "Szűrt nézet betöltése",
"Search for {searchQuery} in other boards" : "Keresés a(z) {searchQuery} kifejezésre a többi táblában",
"Search for {searchQuery} in all boards" : "Keresés a(z) {searchQuery} kifejezésre az összes táblában",
"No results found" : "Nincs találat",
"Deck board {name}\n* Last modified on {lastMod}" : "{name} kártyatábla\n* Legutóbb módosítva: {lastMod}",

View File

@@ -81,10 +81,14 @@ OC.L10N.register(
"Could not write file to disk" : "Неможе да се запишува на дискот",
"A PHP extension stopped the file upload" : "PHP додаток го стопираше прикачувањето на датотеката",
"No file uploaded or file size exceeds maximum of %s" : "Нема прикачена дадотека или големината го надмминува максимумот од %s",
"Invalid file type. Only JSON files are allowed." : "Невалиден тип на датотека. Дозволени се само JSON датотеки.",
"Invalid JSON data" : "Невалидни JSON податоци",
"Failed to import board" : "Неуспешен увоз на табла",
"Cards due today" : "Картици со рок до денес",
"Cards due tomorrow" : "Картици со рок до утре",
"Upcoming cards" : "Престојни картици",
"Load more" : "Вчитај повеќе",
"Welcome to Nextcloud Deck!" : "Добредојдовте во Nextcloud Deck!",
"The card \"%s\" on \"%s\" has been assigned to you by %s." : "Картицата \"%s\" на \"%s\" ти е доделена од %s.",
"{user} has assigned the card {deck-card} on {deck-board} to you." : "{user} ти ја додели картицата {deck-card} на {deck-board}.",
"The card \"%s\" on \"%s\" has reached its due date." : "Картицата \"%s\" на \"%s\" го достигна датумот на истекување.",
@@ -108,9 +112,15 @@ OC.L10N.register(
"Later" : "Покасно",
"copy" : "копирај",
"Read more inside" : "Прочитај повеќе",
"Custom lists - click to rename!" : "Прилагодени листи кликнете за преименување!",
"To Do" : "За правење",
"In Progress" : "Во тек",
"Done" : "Готово",
"1. Open to learn more about boards and cards" : "1. Отворете за да дознаете повеќе за таблите и картичките",
"2. Drag cards left and right, up and down" : "2. Влечете ги картичките лево и десно, горе и долу",
"3. Apply rich formatting and link content" : "3. Применете богато форматирање и поврзете содржина",
"4. Share, comment and collaborate!" : "4. Споделувајте, коментирајте и соработувајте!",
"Create your first card!" : "Креирајте ја вашата прва картичка!",
"This comment has more than %s characters.\nAdded as an attachment to the card with name %s.\nAccessible on URL: %s." : "Коментарот има повеќе од %s карактери.\nДодаден е како пролог на картицата со име %s.\nДостапен е на линк: %s.",
"Attachments" : "Прилози",
"File" : "Датотека",
@@ -152,6 +162,7 @@ OC.L10N.register(
"Filter by assigned user" : "Филтрирај по назначени корисници",
"Unassigned" : "Неназначени",
"Filter by status" : "Филтрирај по статус",
"Open and completed" : "Отворени и завршени",
"Open" : "Отвори",
"Completed" : "Завршено",
"Filter by due date" : "Филтрирај по краен рок",
@@ -277,7 +288,7 @@ OC.L10N.register(
"Create a new tag:" : "Направи нова ознака:",
"(group)" : "(group)",
"{count} comments, {unread} unread" : "{count} коментари, {unread} непрочитани",
"Todo items" : "Работи за правење",
"Todo items" : "Задачи",
"Edit card title" : "Измени наслов на картица",
"Open link" : "Отвори линк",
"Card deleted" : "Картицата е избришана",
@@ -294,7 +305,9 @@ OC.L10N.register(
"Action" : "Акција",
"Shift" : "Shift",
"Scroll" : "Scroll",
"Scroll sideways" : "Лизгај странично",
"Navigate between cards" : "Навигација помеѓу картиците",
"Esc" : "Esc",
"Close card details" : "Затвори детали на картица",
"Ctrl" : "Ctrl",
"Search" : "Барај",
@@ -342,6 +355,9 @@ OC.L10N.register(
"Assigned cards" : "Доделени картици",
"No notifications" : "Нема известувања",
"Delete board" : "Избриши табла",
"Importing board..." : "Увезување табла...",
"Board imported successfully" : "Таблата е успешно увезена",
"Import board" : "Увези табла",
"Clone {boardTitle}" : "Клонирај {boardTitle}",
"Clone cards" : "Клинирај картици",
"Clone assignments" : "Клонирај задачи",
@@ -352,6 +368,9 @@ OC.L10N.register(
"Restore archived cards" : "Врати архивирани картици",
"Clone" : "Клонирај",
"Export {boardTitle}" : "Извези {boardTitle}",
"Export as JSON" : "Извези како JSON",
"Export as CSV" : "Извези како CSV",
"Note: Only the JSON format is supported for importing back into the Deck app." : "Забелешка: Поддржан е само JSON формат за увоз назад во апликацијата Deck.",
"Export" : "Извези",
"Loading filtered view" : "Вчитување на филтриран поглед",
"Search for {searchQuery} in other boards" : "Барај {searchQuery} во други табли",

View File

@@ -79,10 +79,14 @@
"Could not write file to disk" : "Неможе да се запишува на дискот",
"A PHP extension stopped the file upload" : "PHP додаток го стопираше прикачувањето на датотеката",
"No file uploaded or file size exceeds maximum of %s" : "Нема прикачена дадотека или големината го надмминува максимумот од %s",
"Invalid file type. Only JSON files are allowed." : "Невалиден тип на датотека. Дозволени се само JSON датотеки.",
"Invalid JSON data" : "Невалидни JSON податоци",
"Failed to import board" : "Неуспешен увоз на табла",
"Cards due today" : "Картици со рок до денес",
"Cards due tomorrow" : "Картици со рок до утре",
"Upcoming cards" : "Престојни картици",
"Load more" : "Вчитај повеќе",
"Welcome to Nextcloud Deck!" : "Добредојдовте во Nextcloud Deck!",
"The card \"%s\" on \"%s\" has been assigned to you by %s." : "Картицата \"%s\" на \"%s\" ти е доделена од %s.",
"{user} has assigned the card {deck-card} on {deck-board} to you." : "{user} ти ја додели картицата {deck-card} на {deck-board}.",
"The card \"%s\" on \"%s\" has reached its due date." : "Картицата \"%s\" на \"%s\" го достигна датумот на истекување.",
@@ -106,9 +110,15 @@
"Later" : "Покасно",
"copy" : "копирај",
"Read more inside" : "Прочитај повеќе",
"Custom lists - click to rename!" : "Прилагодени листи кликнете за преименување!",
"To Do" : "За правење",
"In Progress" : "Во тек",
"Done" : "Готово",
"1. Open to learn more about boards and cards" : "1. Отворете за да дознаете повеќе за таблите и картичките",
"2. Drag cards left and right, up and down" : "2. Влечете ги картичките лево и десно, горе и долу",
"3. Apply rich formatting and link content" : "3. Применете богато форматирање и поврзете содржина",
"4. Share, comment and collaborate!" : "4. Споделувајте, коментирајте и соработувајте!",
"Create your first card!" : "Креирајте ја вашата прва картичка!",
"This comment has more than %s characters.\nAdded as an attachment to the card with name %s.\nAccessible on URL: %s." : "Коментарот има повеќе од %s карактери.\nДодаден е како пролог на картицата со име %s.\nДостапен е на линк: %s.",
"Attachments" : "Прилози",
"File" : "Датотека",
@@ -150,6 +160,7 @@
"Filter by assigned user" : "Филтрирај по назначени корисници",
"Unassigned" : "Неназначени",
"Filter by status" : "Филтрирај по статус",
"Open and completed" : "Отворени и завршени",
"Open" : "Отвори",
"Completed" : "Завршено",
"Filter by due date" : "Филтрирај по краен рок",
@@ -275,7 +286,7 @@
"Create a new tag:" : "Направи нова ознака:",
"(group)" : "(group)",
"{count} comments, {unread} unread" : "{count} коментари, {unread} непрочитани",
"Todo items" : "Работи за правење",
"Todo items" : "Задачи",
"Edit card title" : "Измени наслов на картица",
"Open link" : "Отвори линк",
"Card deleted" : "Картицата е избришана",
@@ -292,7 +303,9 @@
"Action" : "Акција",
"Shift" : "Shift",
"Scroll" : "Scroll",
"Scroll sideways" : "Лизгај странично",
"Navigate between cards" : "Навигација помеѓу картиците",
"Esc" : "Esc",
"Close card details" : "Затвори детали на картица",
"Ctrl" : "Ctrl",
"Search" : "Барај",
@@ -340,6 +353,9 @@
"Assigned cards" : "Доделени картици",
"No notifications" : "Нема известувања",
"Delete board" : "Избриши табла",
"Importing board..." : "Увезување табла...",
"Board imported successfully" : "Таблата е успешно увезена",
"Import board" : "Увези табла",
"Clone {boardTitle}" : "Клонирај {boardTitle}",
"Clone cards" : "Клинирај картици",
"Clone assignments" : "Клонирај задачи",
@@ -350,6 +366,9 @@
"Restore archived cards" : "Врати архивирани картици",
"Clone" : "Клонирај",
"Export {boardTitle}" : "Извези {boardTitle}",
"Export as JSON" : "Извези како JSON",
"Export as CSV" : "Извези како CSV",
"Note: Only the JSON format is supported for importing back into the Deck app." : "Забелешка: Поддржан е само JSON формат за увоз назад во апликацијата Deck.",
"Export" : "Извези",
"Loading filtered view" : "Вчитување на филтриран поглед",
"Search for {searchQuery} in other boards" : "Барај {searchQuery} во други табли",

View File

@@ -266,6 +266,7 @@ OC.L10N.register(
"{count} comments, {unread} unread" : "{count} reacties, {unread} ongelezen",
"Todo items" : "Te doen onderwerpen",
"Edit card title" : "Wijzig de titel van de kaart",
"Open link" : "Open link",
"Card deleted" : "Kaart verwijderd",
"Edit title" : "Titel bewerken",
"Assign to me" : "Aan mij toewijzen",
@@ -279,6 +280,7 @@ OC.L10N.register(
"Shift" : "Shift",
"Ctrl" : "Ctrl",
"Search" : "Zoeken",
"Enter" : "Enter",
"All boards" : "Alle borden",
"Archived boards" : "Gearchiveerde borden",
"Shared with you" : "Deelde met jou",

View File

@@ -264,6 +264,7 @@
"{count} comments, {unread} unread" : "{count} reacties, {unread} ongelezen",
"Todo items" : "Te doen onderwerpen",
"Edit card title" : "Wijzig de titel van de kaart",
"Open link" : "Open link",
"Card deleted" : "Kaart verwijderd",
"Edit title" : "Titel bewerken",
"Assign to me" : "Aan mij toewijzen",
@@ -277,6 +278,7 @@
"Shift" : "Shift",
"Ctrl" : "Ctrl",
"Search" : "Zoeken",
"Enter" : "Enter",
"All boards" : "Alle borden",
"Archived boards" : "Gearchiveerde borden",
"Shared with you" : "Deelde met jou",

View File

@@ -516,7 +516,7 @@ class ActivityManager {
];
}
private function findDetailsForCard($cardId, $subject = null) {
private function findDetailsForCard(int $cardId, ?string $subject = null): array {
$card = $this->cardMapper->find($cardId);
$stack = $this->stackMapper->find($card->getStackId());
$board = $this->boardMapper->find($stack->getBoardId());

View File

@@ -7,6 +7,9 @@
namespace OCA\Deck\Activity;
/**
* @psalm-api SettingComment
*/
class SettingComment extends SettingBase {
/**

View File

@@ -6,9 +6,13 @@
*/
namespace OCA\Deck\Controller;
use OCA\Deck\Db\Attachment;
use OCA\Deck\Service\AttachmentService;
use OCP\AppFramework\ApiController;
use OCP\AppFramework\Http;
use OCP\AppFramework\Http\Attribute\CORS;
use OCP\AppFramework\Http\Attribute\NoAdminRequired;
use OCP\AppFramework\Http\Attribute\NoCSRFRequired;
use OCP\AppFramework\Http\DataResponse;
use OCP\IRequest;
@@ -21,72 +25,52 @@ class AttachmentApiController extends ApiController {
parent::__construct($appName, $request);
}
/**
* @NoAdminRequired
* @CORS
* @NoCSRFRequired
*
*/
public function getAll($apiVersion) {
#[NoAdminRequired]
#[CORS]
#[NoCSRFRequired]
public function getAll(string $apiVersion): DataResponse {
$attachment = $this->attachmentService->findAll($this->request->getParam('cardId'), true);
if ($apiVersion === '1.0') {
$attachment = array_filter($attachment, function ($attachment) {
return $attachment->getType() === 'deck_file';
});
$attachment = array_filter($attachment, fn (Attachment $attachment): bool => $attachment->getType() === 'deck_file');
}
return new DataResponse($attachment, HTTP::STATUS_OK);
}
/**
* @NoAdminRequired
* @CORS
* @NoCSRFRequired
*
*/
public function display($cardId, $attachmentId, $type = 'deck_file') {
#[NoAdminRequired]
#[CORS]
#[NoCSRFRequired]
public function display(int $cardId, int $attachmentId, string $type = 'deck_file') {
return $this->attachmentService->display($cardId, $attachmentId, $type);
}
/**
* @NoAdminRequired
* @CORS
* @NoCSRFRequired
*
*/
public function create($cardId, $type, $data) {
#[NoAdminRequired]
#[CORS]
#[NoCSRFRequired]
public function create(int $cardId, string $type, string $data): DataResponse {
$attachment = $this->attachmentService->create($cardId, $type, $data);
return new DataResponse($attachment, HTTP::STATUS_OK);
}
/**
* @NoAdminRequired
* @CORS
* @NoCSRFRequired
*
*/
public function update($cardId, $attachmentId, $data, $type = 'deck_file') {
#[NoAdminRequired]
#[CORS]
#[NoCSRFRequired]
public function update(int $cardId, int $attachmentId, string $data, string $type = 'deck_file'): DataResponse {
$attachment = $this->attachmentService->update($cardId, $attachmentId, $data, $type);
return new DataResponse($attachment, HTTP::STATUS_OK);
}
/**
* @NoAdminRequired
* @CORS
* @NoCSRFRequired
*
*/
public function delete($cardId, $attachmentId, $type = 'deck_file') {
#[NoAdminRequired]
#[CORS]
#[NoCSRFRequired]
public function delete(int $cardId, int $attachmentId, string $type = 'deck_file'): DataResponse {
$attachment = $this->attachmentService->delete($cardId, $attachmentId, $type);
return new DataResponse($attachment, HTTP::STATUS_OK);
}
/**
* @NoAdminRequired
* @CORS
* @NoCSRFRequired
*
*/
public function restore($cardId, $attachmentId, $type = 'deck_file') {
#[NoAdminRequired]
#[CORS]
#[NoCSRFRequired]
public function restore(int $cardId, int $attachmentId, string $type = 'deck_file'): DataResponse {
$attachment = $this->attachmentService->restore($cardId, $attachmentId, $type);
return new DataResponse($attachment, HTTP::STATUS_OK);
}

View File

@@ -7,8 +7,13 @@
namespace OCA\Deck\Controller;
use OCA\Deck\BadRequestException;
use OCA\Deck\Db\Attachment;
use OCA\Deck\Service\AttachmentService;
use OCP\AppFramework\Controller;
use OCP\AppFramework\Http\Attribute\NoAdminRequired;
use OCP\AppFramework\Http\Attribute\NoCSRFRequired;
use OCP\AppFramework\Http\Response;
use OCP\IRequest;
class AttachmentController extends Controller {
@@ -20,74 +25,66 @@ class AttachmentController extends Controller {
parent::__construct($appName, $request);
}
/**
* @NoAdminRequired
*/
public function getAll($cardId) {
#[NoAdminRequired]
public function getAll(int $cardId): array {
return $this->attachmentService->findAll($cardId, true);
}
/**
* @param $cardId
* @param $attachmentId
* @NoCSRFRequired
* @NoAdminRequired
* @return \OCP\AppFramework\Http\Response
* @throws \OCA\Deck\NotFoundException
*/
public function display($cardId, $attachmentId) {
if (!str_contains($attachmentId, ':')) {
$type = 'deck_file';
} else {
[$type, $attachmentId] = explode(':', $attachmentId);
}
#[NoAdminRequired]
#[NoCSRFRequired]
public function display(int $cardId, string $attachmentId): Response {
['type' => $type, 'attachmentId' => $attachmentId] = $this->extractTypeAndAttachmentId($attachmentId);
return $this->attachmentService->display($cardId, $attachmentId, $type);
}
/**
* @NoAdminRequired
*/
public function create($cardId) {
#[NoAdminRequired]
public function create(int $cardId): Attachment {
return $this->attachmentService->create(
$cardId,
$this->request->getParam('type'),
$this->request->getParam('data')
$this->request->getParam('data') ?? '',
);
}
/**
* @NoAdminRequired
*/
public function update($cardId, $attachmentId) {
if (!str_contains($attachmentId, ':')) {
$type = 'deck_file';
} else {
[$type, $attachmentId] = explode(':', $attachmentId);
}
return $this->attachmentService->update($cardId, $attachmentId, $this->request->getParam('data'), $type);
#[NoAdminRequired]
public function update(int $cardId, string $attachmentId): Attachment {
['type' => $type, 'attachmentId' => $attachmentId] = $this->extractTypeAndAttachmentId($attachmentId);
return $this->attachmentService->update($cardId, $attachmentId, $this->request->getParam('data') ?? '', $type);
}
/**
* @NoAdminRequired
*/
public function delete($cardId, $attachmentId) {
if (!str_contains($attachmentId, ':')) {
$type = 'deck_file';
} else {
[$type, $attachmentId] = explode(':', $attachmentId);
}
#[NoAdminRequired]
public function delete(int $cardId, string $attachmentId): Attachment {
['type' => $type, 'attachmentId' => $attachmentId] = $this->extractTypeAndAttachmentId($attachmentId);
return $this->attachmentService->delete($cardId, $attachmentId, $type);
}
#[NoAdminRequired]
public function restore(int $cardId, string $attachmentId): Attachment {
['type' => $type, 'attachmentId' => $attachmentId] = $this->extractTypeAndAttachmentId($attachmentId);
return $this->attachmentService->restore($cardId, $attachmentId, $type);
}
/**
* @NoAdminRequired
* @return array{type: string, attachmentId: int}
* @throws BadRequestException
*/
public function restore($cardId, $attachmentId) {
private function extractTypeAndAttachmentId(string $attachmentId): array {
if (!str_contains($attachmentId, ':')) {
$type = 'deck_file';
} else {
[$type, $attachmentId] = explode(':', $attachmentId);
[$type, $attachmentId] = [...explode(':', $attachmentId), '', ''];
}
return $this->attachmentService->restore($cardId, $attachmentId, $type);
if ($type === '' || !is_numeric($attachmentId)) {
throw new BadRequestException('Invalid attachment id');
}
return [
'type' => $type,
'attachmentId' => (int)$attachmentId,
];
}
}

View File

@@ -12,10 +12,13 @@ use OCA\Deck\Service\BoardService;
use OCA\Deck\StatusException;
use OCP\AppFramework\ApiController;
use OCP\AppFramework\Http;
use OCP\AppFramework\Http\Attribute\CORS;
use OCP\AppFramework\Http\Attribute\NoAdminRequired;
use OCP\AppFramework\Http\Attribute\NoCSRFRequired;
use OCP\AppFramework\Http\DataResponse;
use OCP\IRequest;
use Sabre\HTTP\Util;
use function Sabre\HTTP\parseDate;
/**
* Class BoardApiController
@@ -36,21 +39,18 @@ class BoardApiController extends ApiController {
}
/**
* @NoAdminRequired
* @CORS
* @NoCSRFRequired
*
* Return all of the boards that the current user has access to.
*
* @param bool $details
* Return all the boards that the current user has access to.
* @throws StatusException
*/
public function index(bool $details = false) {
#[NoAdminRequired]
#[NoCSRFRequired]
#[CORS]
public function index(bool $details = false): DataResponse {
$modified = $this->request->getHeader('If-Modified-Since');
if ($modified === null || $modified === '') {
if ($modified === '') {
$boards = $this->boardService->findAll(0, $details === true);
} else {
$date = Util::parseHTTPDate($modified);
$date = parseDate($modified);
if (!$date) {
throw new StatusException('Invalid If-Modified-Since header provided.');
}
@@ -64,14 +64,12 @@ class BoardApiController extends ApiController {
}
/**
* @NoAdminRequired
* @CORS
* @NoCSRFRequired
*
*
* Return the board specified by $this->request->getParam('boardId').
*/
public function get() {
#[NoAdminRequired]
#[NoCSRFRequired]
#[CORS]
public function get(): DataResponse {
$board = $this->boardService->find($this->request->getParam('boardId'));
$response = new DataResponse($board, HTTP::STATUS_OK);
$response->setETag($board->getEtag());
@@ -79,68 +77,53 @@ class BoardApiController extends ApiController {
}
/**
* @NoAdminRequired
* @CORS
* @NoCSRFRequired
*
* @params $title
* @params $color
*
* Create a board with the specified title and color.
*/
public function create($title, $color) {
#[NoAdminRequired]
#[NoCSRFRequired]
#[CORS]
public function create(string $title, string $color): DataResponse {
$board = $this->boardService->create($title, $this->userId, $color);
return new DataResponse($board, HTTP::STATUS_OK);
}
/**
* @NoAdminRequired
* @CORS
* @NoCSRFRequired
*
* @params $title
* @params $color
* @params $archived
*
* Update a board with the specified boardId, title and color, and archived state.
*/
public function update($title, $color, $archived = false) {
#[NoAdminRequired]
#[NoCSRFRequired]
#[CORS]
public function update(string $title, string $color, bool $archived = false): DataResponse {
$board = $this->boardService->update($this->request->getParam('boardId'), $title, $color, $archived);
return new DataResponse($board, HTTP::STATUS_OK);
}
/**
* @NoAdminRequired
* @CORS
* @NoCSRFRequired
*
*
* Delete the board specified by $boardId. Return the board that was deleted.
*/
public function delete() {
#[NoAdminRequired]
#[NoCSRFRequired]
#[CORS]
public function delete(): DataResponse {
$board = $this->boardService->delete($this->request->getParam('boardId'));
return new DataResponse($board, HTTP::STATUS_OK);
}
/**
* @NoAdminRequired
* @CORS
* @NoCSRFRequired
*
*
* Undo the deletion of the board specified by $boardId.
*/
public function undoDelete() {
#[NoAdminRequired]
#[NoCSRFRequired]
#[CORS]
public function undoDelete(): DataResponse {
$board = $this->boardService->deleteUndo($this->request->getParam('boardId'));
return new DataResponse($board, HTTP::STATUS_OK);
}
/**
* @NoAdminRequired
* @CORS
* @NoCSRFRequired
*/
public function addAcl($boardId, $type, $participant, $permissionEdit, $permissionShare, $permissionManage) {
#[NoAdminRequired]
#[NoCSRFRequired]
#[CORS]
public function addAcl(int $boardId, $type, $participant, $permissionEdit, $permissionShare, $permissionManage) {
$acl = $this->boardService->addAcl($boardId, $type, $participant, $permissionEdit, $permissionShare, $permissionManage);
return new DataResponse($acl, HTTP::STATUS_OK);
}

View File

@@ -14,6 +14,7 @@ use OCA\Deck\Service\Importer\BoardImportService;
use OCA\Deck\Service\PermissionService;
use OCP\AppFramework\ApiController;
use OCP\AppFramework\Http;
use OCP\AppFramework\Http\Attribute\NoAdminRequired;
use OCP\AppFramework\Http\DataResponse;
use OCP\IL10N;
use OCP\IRequest;
@@ -31,68 +32,38 @@ class BoardController extends ApiController {
parent::__construct($appName, $request);
}
/**
* @NoAdminRequired
*/
#[NoAdminRequired]
public function index() {
return $this->boardService->findAll();
}
/**
* @NoAdminRequired
* @param $boardId
* @return \OCP\AppFramework\Db\Entity
*/
public function read(int $boardId) {
#[NoAdminRequired]
public function read(int $boardId): Board {
return $this->boardService->find($boardId);
}
/**
* @NoAdminRequired
* @param $title
* @param $color
* @return \OCP\AppFramework\Db\Entity
*/
public function create($title, $color) {
#[NoAdminRequired]
public function create(string $title, string $color): Board {
return $this->boardService->create($title, $this->userId, $color);
}
/**
* @NoAdminRequired
* @param $id
* @param $title
* @param $color
* @param $archived
* @return \OCP\AppFramework\Db\Entity
*/
public function update($id, $title, $color, $archived) {
#[NoAdminRequired]
public function update(int $id, string $title, string $color, bool $archived): Board {
return $this->boardService->update($id, $title, $color, $archived);
}
/**
* @NoAdminRequired
* @param $boardId
* @return \OCP\AppFramework\Db\Entity
*/
public function delete($boardId) {
#[NoAdminRequired]
public function delete(int $boardId): Board {
return $this->boardService->delete($boardId);
}
/**
* @NoAdminRequired
* @param $boardId
* @return \OCP\AppFramework\Db\Entity
*/
public function deleteUndo($boardId) {
#[NoAdminRequired]
public function deleteUndo(int $boardId): Board {
return $this->boardService->deleteUndo($boardId);
}
/**
* @NoAdminRequired
* @param $boardId
* @return array|bool
* @internal param $userId
*/
public function getUserPermissions($boardId) {
#[NoAdminRequired]
public function getUserPermissions(int $boardId): array {
$permissions = $this->permissionService->getPermissions($boardId);
return [
'PERMISSION_READ' => $permissions[Acl::PERMISSION_READ],
@@ -103,16 +74,10 @@ class BoardController extends ApiController {
}
/**
* @NoAdminRequired
* @param $boardId
* @param $type
* @param $participant
* @param $permissionEdit
* @param $permissionShare
* @param $permissionManage
* @return \OCP\AppFramework\Db\Entity
*/
public function addAcl($boardId, $type, $participant, $permissionEdit, $permissionShare, $permissionManage) {
#[NoAdminRequired]
public function addAcl(int $boardId, int $type, $participant, bool $permissionEdit, bool $permissionShare, bool $permissionManage): Acl {
return $this->boardService->addAcl($boardId, $type, $participant, $permissionEdit, $permissionShare, $permissionManage);
}

View File

@@ -9,6 +9,9 @@ namespace OCA\Deck\Controller;
use OCA\Deck\Service\Importer\BoardImportService;
use OCP\AppFramework\Http;
use OCP\AppFramework\Http\Attribute\CORS;
use OCP\AppFramework\Http\Attribute\NoAdminRequired;
use OCP\AppFramework\Http\Attribute\NoCSRFRequired;
use OCP\AppFramework\Http\DataResponse;
use OCP\AppFramework\OCSController;
use OCP\IRequest;
@@ -23,11 +26,9 @@ class BoardImportApiController extends OCSController {
parent::__construct($appName, $request);
}
/**
* @NoAdminRequired
* @CORS
* @NoCSRFRequired
*/
#[NoAdminRequired]
#[CORS]
#[NoCSRFRequired]
public function import(string $system, array $config, array $data): DataResponse {
$this->boardImportService->setSystem($system);
$config = json_decode(json_encode($config));
@@ -38,21 +39,17 @@ class BoardImportApiController extends OCSController {
return new DataResponse($this->boardImportService->getBoard(), Http::STATUS_OK);
}
/**
* @NoAdminRequired
* @CORS
* @NoCSRFRequired
*/
#[NoAdminRequired]
#[CORS]
#[NoCSRFRequired]
public function getAllowedSystems(): DataResponse {
$allowedSystems = $this->boardImportService->getAllowedImportSystems();
return new DataResponse($allowedSystems, Http::STATUS_OK);
}
/**
* @NoAdminRequired
* @CORS
* @NoCSRFRequired
*/
#[NoAdminRequired]
#[CORS]
#[NoCSRFRequired]
public function getConfigSchema(string $name): DataResponse {
$this->boardImportService->setSystem($name);
$this->boardImportService->validateSystem();

View File

@@ -12,6 +12,9 @@ use OCA\Deck\Service\AssignmentService;
use OCA\Deck\Service\CardService;
use OCP\AppFramework\ApiController;
use OCP\AppFramework\Http;
use OCP\AppFramework\Http\Attribute\CORS;
use OCP\AppFramework\Http\Attribute\NoAdminRequired;
use OCP\AppFramework\Http\Attribute\NoCSRFRequired;
use OCP\AppFramework\Http\DataResponse;
use OCP\IRequest;
@@ -27,7 +30,7 @@ class CardApiController extends ApiController {
* @param IRequest $request
* @param CardService $cardService
* @param AssignmentService $assignmentService
* @param $userId
* @param string $userId
*/
public function __construct(
string $appName,
@@ -80,112 +83,102 @@ class CardApiController extends ApiController {
}
/**
* @NoAdminRequired
* @CORS
* @NoCSRFRequired
*
*
* Update a card
*/
public function update($title, $type, $owner, $description = '', $order = 0, $duedate = null, $archived = null) {
#[NoAdminRequired]
#[CORS]
#[NoCSRFRequired]
public function update(string $title, $type, string $owner, string $description = '', int $order = 0, $duedate = null, $archived = null): DataResponse {
$done = array_key_exists('done', $this->request->getParams()) ? new OptionalNullableValue($this->request->getParam('done', null)) : null;
$card = $this->cardService->update($this->request->getParam('cardId'), $title, $this->request->getParam('stackId'), $type, $owner, $description, $order, $duedate, 0, $archived, $done);
return new DataResponse($card, HTTP::STATUS_OK);
}
/**
* @NoAdminRequired
* @CORS
* @NoCSRFRequired
*
* Delete a specific card.
*/
public function delete() {
#[NoAdminRequired]
#[CORS]
#[NoCSRFRequired]
public function delete(): DataResponse {
$card = $this->cardService->delete($this->request->getParam('cardId'));
return new DataResponse($card, HTTP::STATUS_OK);
}
/**
* @NoAdminRequired
* @CORS
* @NoCSRFRequired
*
* Assign a label to a card.
*/
public function assignLabel($labelId) {
#[NoAdminRequired]
#[CORS]
#[NoCSRFRequired]
public function assignLabel(int $labelId): DataResponse {
$card = $this->cardService->assignLabel($this->request->getParam('cardId'), $labelId);
return new DataResponse($card, HTTP::STATUS_OK);
}
/**
* @NoAdminRequired
* @CORS
* @NoCSRFRequired
*
* Assign a label to a card.
*/
public function removeLabel($labelId) {
#[NoAdminRequired]
#[CORS]
#[NoCSRFRequired]
public function removeLabel(int $labelId): DataResponse {
$card = $this->cardService->removeLabel($this->request->getParam('cardId'), $labelId);
return new DataResponse($card, HTTP::STATUS_OK);
}
/**
* @NoAdminRequired
* @CORS
* @NoCSRFRequired
*
* Assign a user to a card
*/
public function assignUser($cardId, $userId, $type = 0) {
#[NoAdminRequired]
#[CORS]
#[NoCSRFRequired]
public function assignUser(int $cardId, string $userId, int $type = 0): DataResponse {
$card = $this->assignmentService->assignUser($cardId, $userId, $type);
return new DataResponse($card, HTTP::STATUS_OK);
}
/**
* @NoAdminRequired
* @CORS
* @NoCSRFRequired
*
* Unassign a user from a card
*/
public function unassignUser($cardId, $userId, $type = 0) {
#[NoAdminRequired]
#[CORS]
#[NoCSRFRequired]
public function unassignUser(int $cardId, string $userId, int $type = 0): DataResponse {
$card = $this->assignmentService->unassignUser($cardId, $userId, $type);
return new DataResponse($card, HTTP::STATUS_OK);
}
/**
* @NoAdminRequired
* @CORS
* @NoCSRFRequired
*
* Archive card
*/
public function archive($cardId) {
#[NoAdminRequired]
#[CORS]
#[NoCSRFRequired]
public function archive(int $cardId): DataResponse {
$card = $this->cardService->archive($cardId);
return new DataResponse($card, HTTP::STATUS_OK);
}
/**
* @NoAdminRequired
* @CORS
* @NoCSRFRequired
*
* Unarchive card
*/
public function unarchive($cardId) {
#[NoAdminRequired]
#[CORS]
#[NoCSRFRequired]
public function unarchive(int $cardId): DataResponse {
$card = $this->cardService->unarchive($cardId);
return new DataResponse($card, HTTP::STATUS_OK);
}
/**
* @NoAdminRequired
* @CORS
* @NoCSRFRequired
*
* Reorder cards
*/
public function reorder($stackId, $order) {
$card = $this->cardService->reorder((int)$this->request->getParam('cardId'), (int)$stackId, (int)$order);
#[NoAdminRequired]
#[CORS]
#[NoCSRFRequired]
public function reorder(int $stackId, int $order): DataResponse {
$card = $this->cardService->reorder((int)$this->request->getParam('cardId'), $stackId, $order);
return new DataResponse($card, HTTP::STATUS_OK);
}
}

View File

@@ -7,9 +7,12 @@
namespace OCA\Deck\Controller;
use OCA\Deck\Db\Assignment;
use OCA\Deck\Db\Card;
use OCA\Deck\Service\AssignmentService;
use OCA\Deck\Service\CardService;
use OCP\AppFramework\Controller;
use OCP\AppFramework\Http\Attribute\NoAdminRequired;
use OCP\IRequest;
class CardController extends Controller {
@@ -23,45 +26,26 @@ class CardController extends Controller {
parent::__construct($appName, $request);
}
/**
* @NoAdminRequired
* @param $cardId
* @return \OCP\AppFramework\Db\Entity
*/
public function read($cardId) {
#[NoAdminRequired]
public function read(int $cardId): Card {
return $this->cardService->find($cardId);
}
/**
* @NoAdminRequired
* @param $cardId
* @param $stackId
* @param $order
* @return array
* @return Card[]
*/
public function reorder($cardId, $stackId, $order) {
return $this->cardService->reorder((int)$cardId, (int)$stackId, (int)$order);
#[NoAdminRequired]
public function reorder(int $cardId, int $stackId, int $order): array {
return $this->cardService->reorder($cardId, $stackId, $order);
}
/**
* @NoAdminRequired
* @param $cardId
* @param $title
* @return \OCP\AppFramework\Db\Entity
*/
public function rename($cardId, $title) {
#[NoAdminRequired]
public function rename(int $cardId, string $title): Card {
return $this->cardService->rename($cardId, $title);
}
/**
* @NoAdminRequired
* @param $title
* @param $stackId
* @param $type
* @param int $order
* @return \OCP\AppFramework\Db\Entity
*/
public function create($title, $stackId, $type = 'plain', $order = 999, string $description = '', $duedate = null, $labels = [], $users = []) {
#[NoAdminRequired]
public function create(string $title, int $stackId, string $type = 'plain', int $order = 999, string $description = '', $duedate = null, array $labels = [], array $users = []): Card {
$card = $this->cardService->create($title, $stackId, $type, $order, $this->userId, $description, $duedate);
foreach ($labels as $label) {
@@ -76,113 +60,68 @@ class CardController extends Controller {
}
/**
* @NoAdminRequired
* @param $id
* @param $title
* @param $stackId
* @param $type
* @param $order
* @param $description
* @param $duedate
* @param $deletedAt
* @return \OCP\AppFramework\Db\Entity
*/
public function update($id, $title, $stackId, $type, $order, $description, $duedate, $deletedAt) {
#[NoAdminRequired]
public function update(int $id, string $title, int $stackId, string $type, int $order, string $description, $duedate, $deletedAt): Card {
return $this->cardService->update($id, $title, $stackId, $type, $this->userId, $description, $order, $duedate, $deletedAt);
}
/**
* @NoAdminRequired
* @param $cardId
* @param $targetStackId
* @return \OCP\AppFramework\Db\Entity
*/
public function clone(int $cardId, ?int $targetStackId = null) {
#[NoAdminRequired]
public function clone(int $cardId, ?int $targetStackId = null): Card {
return $this->cardService->cloneCard($cardId, $targetStackId);
}
/**
* @NoAdminRequired
* @param $cardId
* @return \OCP\AppFramework\Db\Entity
*/
public function delete($cardId) {
#[NoAdminRequired]
public function delete(int $cardId): Card {
return $this->cardService->delete($cardId);
}
/**
* @NoAdminRequired
* @param $boardId
* @return \OCP\AppFramework\Db\Entity
* @return Card[]
*/
public function deleted($boardId) {
#[NoAdminRequired]
public function deleted(int $boardId): array {
return $this->cardService->fetchDeleted($boardId);
}
/**
* @NoAdminRequired
* @param $cardId
* @return \OCP\AppFramework\Db\Entity
*/
#[NoAdminRequired]
public function archive($cardId) {
return $this->cardService->archive($cardId);
}
/**
* @NoAdminRequired
* @param $cardId
* @return \OCP\AppFramework\Db\Entity
*/
public function unarchive($cardId) {
#[NoAdminRequired]
public function unarchive(int $cardId): Card {
return $this->cardService->unarchive($cardId);
}
/**
* @NoAdminRequired
* @param $cardId
* @return \OCP\AppFramework\Db\Entity
*/
public function done(int $cardId) {
#[NoAdminRequired]
public function done(int $cardId): Card {
return $this->cardService->done($cardId);
}
/**
* @NoAdminRequired
* @param $cardId
* @return \OCP\AppFramework\Db\Entity
*/
public function undone(int $cardId) {
#[NoAdminRequired]
public function undone(int $cardId): Card {
return $this->cardService->undone($cardId);
}
/**
* @NoAdminRequired
* @param $cardId
* @param $labelId
*/
public function assignLabel($cardId, $labelId) {
#[NoAdminRequired]
public function assignLabel(int $cardId, int $labelId): void {
$this->cardService->assignLabel($cardId, $labelId);
}
/**
* @NoAdminRequired
* @param $cardId
* @param $labelId
*/
public function removeLabel($cardId, $labelId) {
#[NoAdminRequired]
public function removeLabel(int $cardId, int $labelId): void {
$this->cardService->removeLabel($cardId, $labelId);
}
/**
* @NoAdminRequired
*/
public function assignUser($cardId, $userId, $type = 0) {
#[NoAdminRequired]
public function assignUser(int $cardId, string $userId, int $type = 0): Assignment {
return $this->assignmentService->assignUser($cardId, $userId, $type);
}
/**
* @NoAdminRequired
*/
public function unassignUser($cardId, $userId, $type = 0) {
#[NoAdminRequired]
public function unassignUser(int $cardId, string $userId, int $type = 0): Assignment {
return $this->assignmentService->unassignUser($cardId, $userId, $type);
}
}

View File

@@ -9,11 +9,15 @@ namespace OCA\Deck\Controller;
use OCA\Deck\Service\CommentService;
use OCA\Deck\StatusException;
use OCP\AppFramework\Http\Attribute\NoAdminRequired;
use OCP\AppFramework\Http\DataResponse;
use OCP\AppFramework\OCSController;
use OCP\IRequest;
/**
* @psalm-api
*/
class CommentsApiController extends OCSController {
public function __construct(
string $appName,
@@ -27,33 +31,33 @@ class CommentsApiController extends OCSController {
}
/**
* @NoAdminRequired
* @throws StatusException
*/
public function list(string $cardId, int $limit = 20, int $offset = 0): DataResponse {
#[NoAdminRequired]
public function list(int $cardId, int $limit = 20, int $offset = 0): DataResponse {
return $this->commentService->list($cardId, $limit, $offset);
}
/**
* @NoAdminRequired
* @throws StatusException
*/
#[NoAdminRequired]
public function create(int $cardId, string $message, int $parentId = 0): DataResponse {
return $this->commentService->create($cardId, $message, $parentId);
}
/**
* @NoAdminRequired
* @throws StatusException
*/
#[NoAdminRequired]
public function update(int $cardId, int $commentId, string $message): DataResponse {
return $this->commentService->update($cardId, $commentId, $message);
}
/**
* @NoAdminRequired
* @throws StatusException
*/
#[NoAdminRequired]
public function delete(int $cardId, int $commentId): DataResponse {
return $this->commentService->delete($cardId, $commentId);
}

View File

@@ -8,6 +8,8 @@
namespace OCA\Deck\Controller;
use OCA\Deck\Service\ConfigService;
use OCP\AppFramework\Http\Attribute\NoAdminRequired;
use OCP\AppFramework\Http\Attribute\NoCSRFRequired;
use OCP\AppFramework\Http\DataResponse;
use OCP\AppFramework\Http\NotFoundResponse;
use OCP\AppFramework\OCSController;
@@ -22,19 +24,15 @@ class ConfigController extends OCSController {
parent::__construct($AppName, $request);
}
/**
* @NoCSRFRequired
* @NoAdminRequired
*/
#[NoAdminRequired]
#[NoCSRFRequired]
public function get(): DataResponse {
return new DataResponse($this->configService->getAll());
}
/**
* @NoCSRFRequired
* @NoAdminRequired
*/
public function setValue(string $key, $value) {
#[NoAdminRequired]
#[NoCSRFRequired]
public function setValue(string $key, mixed $value): DataResponse|NotFoundResponse {
$result = $this->configService->set($key, $value);
if ($result === null) {
return new NotFoundResponse();

View File

@@ -10,6 +10,9 @@ namespace OCA\Deck\Controller;
use OCA\Deck\Service\LabelService;
use OCP\AppFramework\ApiController;
use OCP\AppFramework\Http;
use OCP\AppFramework\Http\Attribute\CORS;
use OCP\AppFramework\Http\Attribute\NoAdminRequired;
use OCP\AppFramework\Http\Attribute\NoCSRFRequired;
use OCP\AppFramework\Http\DataResponse;
use OCP\IRequest;
@@ -26,59 +29,50 @@ class LabelApiController extends ApiController {
$appName,
IRequest $request,
private LabelService $labelService,
private $userId,
) {
parent::__construct($appName, $request);
}
/**
* @NoAdminRequired
* @CORS
* @NoCSRFRequired
*
* Get a specific label.
*/
public function get() {
#[NoAdminRequired]
#[NoCSRFRequired]
#[CORS]
public function get(): DataResponse {
$label = $this->labelService->find($this->request->getParam('labelId'));
return new DataResponse($label, HTTP::STATUS_OK);
}
/**
* @NoAdminRequired
* @CORS
* @NoCSRFRequired
*
* @params $title
* @params $color
* Create a new label
*/
public function create($title, $color) {
#[NoAdminRequired]
#[NoCSRFRequired]
#[CORS]
public function create(string $title, string $color): DataResponse {
$label = $this->labelService->create($title, $color, $this->request->getParam('boardId'));
return new DataResponse($label, HTTP::STATUS_OK);
}
/**
* @NoAdminRequired
* @CORS
* @NoCSRFRequired
*
* @params $title
* @params $color
* Update a specific label
*/
public function update($title, $color) {
#[NoAdminRequired]
#[NoCSRFRequired]
#[CORS]
public function update(string $title, string $color): DataResponse {
$label = $this->labelService->update($this->request->getParam('labelId'), $title, $color);
return new DataResponse($label, HTTP::STATUS_OK);
}
/**
* @NoAdminRequired
* @CORS
* @NoCSRFRequired
*
* Delete a specific label
*/
public function delete() {
#[NoAdminRequired]
#[NoCSRFRequired]
#[CORS]
public function delete(): DataResponse {
$label = $this->labelService->delete($this->request->getParam('labelId'));
return new DataResponse($label, HTTP::STATUS_OK);
}

View File

@@ -7,8 +7,10 @@
namespace OCA\Deck\Controller;
use OCA\Deck\Db\Label;
use OCA\Deck\Service\LabelService;
use OCP\AppFramework\Controller;
use OCP\AppFramework\Http\Attribute\NoAdminRequired;
use OCP\IRequest;
class LabelController extends Controller {
@@ -20,34 +22,18 @@ class LabelController extends Controller {
parent::__construct($appName, $request);
}
/**
* @NoAdminRequired
* @param $title
* @param $color
* @param $boardId
* @return \OCP\AppFramework\Db\Entity
*/
public function create($title, $color, $boardId) {
#[NoAdminRequired]
public function create(string $title, string $color, int $boardId): Label {
return $this->labelService->create($title, $color, $boardId);
}
/**
* @NoAdminRequired
* @param $id
* @param $title
* @param $color
* @return \OCP\AppFramework\Db\Entity
*/
public function update($id, $title, $color) {
#[NoAdminRequired]
public function update(int $id, string $title, string $color): Label {
return $this->labelService->update($id, $title, $color);
}
/**
* @NoAdminRequired
* @param $labelId
* @return \OCP\AppFramework\Db\Entity
*/
public function delete($labelId) {
#[NoAdminRequired]
public function delete(int $labelId): Label {
return $this->labelService->delete($labelId);
}
}

View File

@@ -10,6 +10,7 @@ declare(strict_types=1);
namespace OCA\Deck\Controller;
use OCA\Deck\Service\OverviewService;
use OCP\AppFramework\Http\Attribute\NoAdminRequired;
use OCP\AppFramework\Http\DataResponse;
use OCP\AppFramework\OCSController;
use OCP\IRequest;
@@ -24,9 +25,7 @@ class OverviewApiController extends OCSController {
parent::__construct($appName, $request);
}
/**
* @NoAdminRequired
*/
#[NoAdminRequired]
public function upcomingCards(): DataResponse {
return new DataResponse($this->dashboardService->findUpcomingCards($this->userId));
}

View File

@@ -13,6 +13,7 @@ namespace OCA\Deck\Controller;
use OCA\Deck\Db\Card;
use OCA\Deck\Model\CardDetails;
use OCA\Deck\Service\SearchService;
use OCP\AppFramework\Http\Attribute\NoAdminRequired;
use OCP\AppFramework\Http\DataResponse;
use OCP\AppFramework\OCSController;
use OCP\IRequest;
@@ -26,9 +27,7 @@ class SearchController extends OCSController {
parent::__construct($appName, $request);
}
/**
* @NoAdminRequired
*/
#[NoAdminRequired]
public function search(string $term, ?int $limit = null, ?int $cursor = null): DataResponse {
$cards = $this->searchService->searchCards($term, $limit, $cursor);
return new DataResponse(array_map(function (Card $card) {

View File

@@ -7,14 +7,16 @@
namespace OCA\Deck\Controller;
use OCA\Deck\Service\BoardService;
use OCA\Deck\Service\StackService;
use OCA\Deck\StatusException;
use OCP\AppFramework\ApiController;
use OCP\AppFramework\Http;
use OCP\AppFramework\Http\Attribute\CORS;
use OCP\AppFramework\Http\Attribute\NoAdminRequired;
use OCP\AppFramework\Http\Attribute\NoCSRFRequired;
use OCP\AppFramework\Http\DataResponse;
use OCP\IRequest;
use Sabre\HTTP\Util;
use function Sabre\HTTP\parseDate;
/**
* Class StackApiController
@@ -29,23 +31,21 @@ class StackApiController extends ApiController {
$appName,
IRequest $request,
private StackService $stackService,
private BoardService $boardService,
) {
parent::__construct($appName, $request);
}
/**
* @NoAdminRequired
* @CORS
* @NoCSRFRequired
*
* Return all of the stacks in the specified board.
* Return all the stacks in the specified board.
*/
public function index() {
#[NoAdminRequired]
#[CORS]
#[NoCSRFRequired]
public function index(): DataResponse {
$since = 0;
$modified = $this->request->getHeader('If-Modified-Since');
if ($modified !== null && $modified !== '') {
$date = Util::parseHTTPDate($modified);
if ($modified !== '') {
$date = parseDate($modified);
if (!$date) {
throw new StatusException('Invalid If-Modified-Since header provided.');
}
@@ -56,13 +56,12 @@ class StackApiController extends ApiController {
}
/**
* @NoAdminRequired
* @CORS
* @NoCSRFRequired
*
* Return all of the stacks in the specified board.
* Return all the stacks in the specified board.
*/
public function get() {
#[NoAdminRequired]
#[CORS]
#[NoCSRFRequired]
public function get(): DataResponse {
$stack = $this->stackService->find($this->request->getParam('stackId'));
$response = new DataResponse($stack, HTTP::STATUS_OK);
$response->setETag($stack->getETag());
@@ -70,55 +69,45 @@ class StackApiController extends ApiController {
}
/**
* @NoAdminRequired
* @CORS
* @NoCSRFRequired
*
* @params $title
* @params $order
*
* Create a stack with the specified title and order.
*/
public function create($title, $order) {
#[NoAdminRequired]
#[CORS]
#[NoCSRFRequired]
public function create(string $title, int $order): DataResponse {
$stack = $this->stackService->create($title, $this->request->getParam('boardId'), $order);
return new DataResponse($stack, HTTP::STATUS_OK);
}
/**
* @NoAdminRequired
* @CORS
* @NoCSRFRequired
*
* @params $title
* @params $order
*
* Update a stack by the specified stackId and boardId with the values that were put.
*/
public function update($title, $order) {
#[NoAdminRequired]
#[CORS]
#[NoCSRFRequired]
public function update(string $title, int $order) {
$stack = $this->stackService->update($this->request->getParam('stackId'), $title, $this->request->getParam('boardId'), $order, 0);
return new DataResponse($stack, HTTP::STATUS_OK);
}
/**
* @NoAdminRequired
* @CORS
* @NoCSRFRequired
*
* Delete the stack specified by $this->request->getParam('stackId').
*/
public function delete() {
#[NoAdminRequired]
#[CORS]
#[NoCSRFRequired]
public function delete(): DataResponse {
$stack = $this->stackService->delete($this->request->getParam('stackId'));
return new DataResponse($stack, HTTP::STATUS_OK);
}
/**
* @NoAdminRequired
* @CORS
* @NoCSRFRequired
*
* get the stacks that have been archived.
* Get the stacks that have been archived.
*/
public function getArchived() {
#[NoAdminRequired]
#[CORS]
#[NoCSRFRequired]
public function getArchived(): DataResponse {
$stacks = $this->stackService->findAllArchived($this->request->getParam('boardId'));
return new DataResponse($stacks, HTTP::STATUS_OK);
}

View File

@@ -7,10 +7,12 @@
namespace OCA\Deck\Controller;
use OCA\Deck\Db\Stack;
use OCA\Deck\Service\StackService;
use OCP\AppFramework\Controller;
use OCP\AppFramework\Http\Attribute\NoAdminRequired;
use OCP\IRequest;
class StackController extends Controller {
@@ -18,78 +20,54 @@ class StackController extends Controller {
string $appName,
IRequest $request,
private StackService $stackService,
private $userId,
) {
parent::__construct($appName, $request);
}
/**
* @NoAdminRequired
* @param $boardId
* @return array
* @return Stack[]
*/
public function index($boardId) {
#[NoAdminRequired]
public function index(int $boardId): array {
return $this->stackService->findAll($boardId);
}
/**
* @NoAdminRequired
* @param $boardId
* @return array
* @return Stack[]
*/
public function archived($boardId) {
#[NoAdminRequired]
public function archived(int $boardId): array {
return $this->stackService->findAllArchived($boardId);
}
/**
* @NoAdminRequired
* @param $title
* @param $boardId
* @param int $order
* @return \OCP\AppFramework\Db\Entity
*/
public function create($title, $boardId, $order = 999) {
#[NoAdminRequired]
public function create(string $title, int $boardId, int $order = 999): Stack {
return $this->stackService->create($title, $boardId, $order);
}
/**
* @NoAdminRequired
* @param $id
* @param $title
* @param $boardId
* @param $order
* @param $deletedAt
* @return \OCP\AppFramework\Db\Entity
*/
public function update($id, $title, $boardId, $order, $deletedAt) {
#[NoAdminRequired]
public function update(int $id, string $title, int $boardId, int $order, ?int $deletedAt = null): Stack {
return $this->stackService->update($id, $title, $boardId, $order, $deletedAt);
}
/**
* @NoAdminRequired
* @param $stackId
* @param $order
* @return array
* @return array<int, Stack>
*/
public function reorder($stackId, $order) {
return $this->stackService->reorder((int)$stackId, (int)$order);
#[NoAdminRequired]
public function reorder(int $stackId, int $order): array {
return $this->stackService->reorder($stackId, $order);
}
/**
* @NoAdminRequired
* @param $stackId
* @return \OCP\AppFramework\Db\Entity
*/
public function delete($stackId) {
#[NoAdminRequired]
public function delete(int $stackId): Stack {
return $this->stackService->delete($stackId);
}
/**
* @NoAdminRequired
* @param $boardId
* @return \OCP\AppFramework\Db\Entity
* @return Stack[]
*/
public function deleted($boardId) {
#[NoAdminRequired]
public function deleted(int $boardId): array {
return $this->stackService->fetchDeleted($boardId);
}
}

View File

@@ -7,6 +7,20 @@
namespace OCA\Deck\Db;
/**
* @method int getBoardId()
* @method bool isPermissionEdit()
* @method void setPermissionEdit(bool $permissionEdit)
* @method bool isPermissionShare()
* @method void setPermissionShare(bool $permissionShare)
* @method bool isPermissionManage()
* @method void setPermissionManage(bool $permissionManage)
* @method int getType()
* @method void setType(int $type)
* @method bool isOwner()
* @method void setOwner(int $owner)
*
*/
class Acl extends RelationalEntity {
public const PERMISSION_READ = 0;
public const PERMISSION_EDIT = 1;
@@ -37,17 +51,13 @@ class Acl extends RelationalEntity {
$this->addResolvable('participant');
}
public function getPermission($permission) {
switch ($permission) {
case self::PERMISSION_READ:
return true;
case self::PERMISSION_EDIT:
return $this->getPermissionEdit();
case self::PERMISSION_SHARE:
return $this->getPermissionShare();
case self::PERMISSION_MANAGE:
return $this->getPermissionManage();
}
return false;
public function getPermission(int $permission): bool {
return match ($permission) {
self::PERMISSION_READ => true,
self::PERMISSION_EDIT => $this->getPermissionEdit(),
self::PERMISSION_SHARE => $this->getPermissionShare(),
self::PERMISSION_MANAGE => $this->getPermissionManage(),
default => false,
};
}
}

View File

@@ -19,13 +19,10 @@ class AclMapper extends DeckMapper implements IPermissionMapper {
}
/**
* @param numeric $boardId
* @param int|null $limit
* @param int|null $offset
* @return Acl[]
* @throws \OCP\DB\Exception
*/
public function findAll($boardId, $limit = null, $offset = null) {
public function findAll(int $boardId, ?int $limit = null, ?int $offset = null) {
$qb = $this->db->getQueryBuilder();
$qb->select('id', 'board_id', 'type', 'participant', 'permission_edit', 'permission_share', 'permission_manage')
->from('deck_board_acl')
@@ -51,12 +48,9 @@ class AclMapper extends DeckMapper implements IPermissionMapper {
}
/**
* @param numeric $userId
* @param numeric $id
* @return bool
* @throws \OCP\DB\Exception
*/
public function isOwner($userId, $id): bool {
public function isOwner(string $userId, int $id): bool {
$aclId = $id;
$qb = $this->db->getQueryBuilder();
$qb->select('acl.id')
@@ -68,11 +62,7 @@ class AclMapper extends DeckMapper implements IPermissionMapper {
return count($qb->executeQuery()->fetchAll()) > 0;
}
/**
* @param numeric $id
* @return int|null
*/
public function findBoardId($id): ?int {
public function findBoardId(int $id): ?int {
try {
$entity = $this->find($id);
return $entity->getBoardId();
@@ -87,7 +77,7 @@ class AclMapper extends DeckMapper implements IPermissionMapper {
* @return Acl[]
* @throws \OCP\DB\Exception
*/
public function findByParticipant($type, $participant): array {
public function findByParticipant(int $type, string $participant): array {
$qb = $this->db->getQueryBuilder();
$qb->select('*')

View File

@@ -77,26 +77,41 @@ class AssignmentMapper extends DeckMapper implements IPermissionMapper {
}
public function deleteByParticipantOnBoard(string $participant, int $boardId, $type = Assignment::TYPE_USER) {
$qb = $this->db->getQueryBuilder();
// Step 1: Get all card IDs for the board that have assignments for this participant
// This avoids MySQL Error 1093 by separating the SELECT from the DELETE operation
$cardIdQuery = $this->db->getQueryBuilder();
$cardIdQuery->select('a.card_id')
->from('deck_assigned_users', 'a')
->innerJoin('a', 'deck_cards', 'c', 'c.id = a.card_id')
->innerJoin('c', 'deck_stacks', 's', 's.id = c.stack_id')
->where($cardIdQuery->expr()->eq('a.participant', $qb->createNamedParameter($participant, IQueryBuilder::PARAM_STR)))
->andWhere($cardIdQuery->expr()->eq('s.board_id', $qb->createNamedParameter($boardId, IQueryBuilder::PARAM_INT)))
->andWhere($cardIdQuery->expr()->eq('a.type', $qb->createNamedParameter($type, IQueryBuilder::PARAM_INT)));
$qb->delete('deck_assigned_users')
->where($qb->expr()->in('card_id', $qb->createFunction($cardIdQuery->getSQL()), IQueryBuilder::PARAM_INT_ARRAY));
$qb->executeStatement();
->where($cardIdQuery->expr()->eq('a.participant', $cardIdQuery->createNamedParameter($participant, IQueryBuilder::PARAM_STR)))
->andWhere($cardIdQuery->expr()->eq('s.board_id', $cardIdQuery->createNamedParameter($boardId, IQueryBuilder::PARAM_INT)))
->andWhere($cardIdQuery->expr()->eq('a.type', $cardIdQuery->createNamedParameter($type, IQueryBuilder::PARAM_INT)));
$result = $cardIdQuery->executeQuery();
$cardIds = [];
while ($row = $result->fetch()) {
$cardIds[] = $row['card_id'];
}
$result->closeCursor();
// Step 2: If we have card IDs, delete the assignments
if (!empty($cardIds)) {
$deleteQuery = $this->db->getQueryBuilder();
$deleteQuery->delete('deck_assigned_users')
->where($deleteQuery->expr()->eq('participant', $deleteQuery->createNamedParameter($participant, IQueryBuilder::PARAM_STR)))
->andWhere($deleteQuery->expr()->eq('type', $deleteQuery->createNamedParameter($type, IQueryBuilder::PARAM_INT)))
->andWhere($deleteQuery->expr()->in('card_id', $deleteQuery->createNamedParameter($cardIds, IQueryBuilder::PARAM_INT_ARRAY)));
$deleteQuery->executeStatement();
}
}
public function isOwner($userId, $id): bool {
public function isOwner(string $userId, int $id): bool {
return $this->cardMapper->isOwner($userId, $id);
}
public function findBoardId($id): ?int {
public function findBoardId(int $id): ?int {
return $this->cardMapper->findBoardId($id);
}
@@ -108,6 +123,9 @@ class AssignmentMapper extends DeckMapper implements IPermissionMapper {
* @throws NotFoundException
*/
public function insert(Entity $entity): Entity {
if (!($entity instanceof Assignment)) {
throw new \LogicException('Trying to insert a ' . get_class($entity) . ' in the assignment mapper');
}
$origin = $this->getOrigin($entity);
if ($origin === null) {
throw new NotFoundException('No origin found for assignment');
@@ -126,7 +144,7 @@ class AssignmentMapper extends DeckMapper implements IPermissionMapper {
});
}
public function isUserAssigned($cardId, $userId): bool {
public function isUserAssigned(int $cardId, string $userId): bool {
$assignments = $this->findAll($cardId);
foreach ($assignments as $assignment) {
$origin = $this->getOrigin($assignment);

View File

@@ -36,13 +36,11 @@ class AttachmentMapper extends DeckMapper implements IPermissionMapper {
}
/**
* @param int $id
* @return Attachment
* @throws DoesNotExistException
* @throws MultipleObjectsReturnedException
* @throws \OCP\DB\Exception
*/
public function find($id) {
public function find(int $id): Attachment {
$qb = $this->db->getQueryBuilder();
$qb->select('*')
->from($this->getTableName())
@@ -52,14 +50,11 @@ class AttachmentMapper extends DeckMapper implements IPermissionMapper {
}
/**
* @param int $cardId
* @param string $data
* @return Attachment
* @throws DoesNotExistException
* @throws MultipleObjectsReturnedException
* @throws \OCP\DB\Exception
*/
public function findByData($cardId, $data) {
public function findByData(int $cardId, string $data): Attachment {
$qb = $this->db->getQueryBuilder();
$qb->select('*')
->from($this->getTableName())
@@ -70,11 +65,10 @@ class AttachmentMapper extends DeckMapper implements IPermissionMapper {
}
/**
* @param $cardId
* @return Entity[]
* @throws \OCP\DB\Exception
*/
public function findAll($cardId) {
public function findAll(int $cardId): array {
$qb = $this->db->getQueryBuilder();
$qb->select('*')
->from($this->getTableName())
@@ -86,11 +80,9 @@ class AttachmentMapper extends DeckMapper implements IPermissionMapper {
}
/**
* @param null $cardId
* @param bool $withOffset
* @return array
* @return Attachment[]
*/
public function findToDelete($cardId = null, $withOffset = true) {
public function findToDelete(?int $cardId = null, bool $withOffset = true): array {
// add buffer of 5 min
$timeLimit = time() - (60 * 5);
$qb = $this->db->getQueryBuilder();
@@ -112,12 +104,8 @@ class AttachmentMapper extends DeckMapper implements IPermissionMapper {
/**
* Check if $userId is owner of Entity with $id
*
* @param $userId string userId
* @param $id int|string unique entity identifier
* @return boolean
*/
public function isOwner($userId, $id): bool {
public function isOwner(string $userId, int $id): bool {
try {
$attachment = $this->find($id);
return $this->cardMapper->isOwner($userId, $attachment->getCardId());
@@ -130,10 +118,10 @@ class AttachmentMapper extends DeckMapper implements IPermissionMapper {
/**
* Query boardId for Entity of given $id
*
* @param $id int|string unique entity identifier
* @param $id int unique entity identifier
* @return int|null id of Board
*/
public function findBoardId($id): ?int {
public function findBoardId(int $id): ?int {
try {
$attachment = $this->find($id);
} catch (\Exception $e) {

View File

@@ -10,10 +10,20 @@ namespace OCA\Deck\Db;
/**
* @method int getId()
* @method string getTitle()
* @method void setTitle(string $title)
* @method int getShared()
* @method void setShared(int $shared)
* @method bool isArchived()
* @method bool getArchived()
* @method void setArchived(bool $archived)
* @method int getDeletedAt()
* @method void setDeletedAt(int $deletedAt)
* @method int getLastModified()
* @method void setLastModified(int $lastModified)
* @method string getOwner()
* @method void setOwner(string $owner)
* @method string getColor()
* @method void setColor(string $color)
*/
class Board extends RelationalEntity {
protected $title;

View File

@@ -469,16 +469,16 @@ class BoardMapper extends QBMapper implements IPermissionMapper {
return parent::delete($entity);
}
public function isOwner($userId, $id): bool {
public function isOwner(string $userId, int $id): bool {
$board = $this->find($id);
return ($board->getOwner() === $userId);
}
public function findBoardId($id): ?int {
public function findBoardId(int $id): ?int {
return $id;
}
public function mapAcl(Acl &$acl) {
public function mapAcl(Acl &$acl): void {
$acl->resolveRelation('participant', function ($participant) use (&$acl) {
if ($acl->getType() === Acl::PERMISSION_TYPE_USER) {
if ($this->userManager->userExists($acl->getParticipant())) {

View File

@@ -15,13 +15,18 @@ use Sabre\VObject\Component\VCalendar;
/**
* @method string getTitle()
* @method void setTitle(string $title)
* @method string getDescription()
* @method string getDescriptionPrev()
* @method int getStackId()
* @method void setStackId(int $stackId)
* @method int getOrder()
* @method void setOrder(int $order)
* @method int getLastModified()
* @method int getCreatedAt()
* @method bool getArchived()
* @method string getType()
* @method void setType(string $type)
* @method int getDeletedAt()
* @method void setDeletedAt(int $deletedAt)
* @method bool getNotified()
@@ -68,8 +73,8 @@ class Card extends RelationalEntity {
protected $createdAt;
protected $labels;
protected $assignedUsers;
protected $attachments;
protected $attachmentCount;
protected array $attachments = [];
protected int $attachmentCount = 0;
protected $owner;
protected $order;
protected $archived = false;

View File

@@ -86,16 +86,15 @@ class CardMapper extends QBMapper implements IPermissionMapper {
$updatedFields = $entity->getUpdatedFields();
if (isset($updatedFields['duedate']) && $updatedFields['duedate']) {
try {
/** @var Card $existing */
$existing = $this->find($entity->getId());
if ($existing && $entity->getDuedate() !== $existing->getDuedate()) {
if ($entity->getDueDate() !== $existing->getDueDate()) {
$entity->setNotified(false);
}
// remove pending notifications
$notification = $this->notificationManager->createNotification();
$notification
->setApp('deck')
->setObject('card', $entity->getId());
->setObject('card', (string)$entity->getId());
$this->notificationManager->markProcessed($notification);
} catch (Exception $e) {
}
@@ -131,7 +130,11 @@ class CardMapper extends QBMapper implements IPermissionMapper {
return $card;
}
public function findAll($stackId, $limit = null, $offset = null, $since = -1) {
/**
* @return Card[]
* @throws \OCP\DB\Exception
*/
public function findAll($stackId, ?int $limit = null, int $offset = 0, int $since = -1) {
$qb = $this->db->getQueryBuilder();
$qb->select('*')
->from('deck_cards')
@@ -146,6 +149,32 @@ class CardMapper extends QBMapper implements IPermissionMapper {
return $this->findEntities($qb);
}
/**
* @param int[] $stackIds
* @return array<int, null|Card[]>
* @throws \OCP\DB\Exception
*/
public function findAllForStacks(array $stackIds, ?int $limit = null, int $offset = 0, int $since = -1): array {
$qb = $this->db->getQueryBuilder();
$qb->select('*')
->from('deck_cards')
->where($qb->expr()->in('stack_id', $qb->createNamedParameter($stackIds, IQueryBuilder::PARAM_INT_ARRAY)))
->andWhere($qb->expr()->eq('archived', $qb->createNamedParameter(false, IQueryBuilder::PARAM_BOOL)))
->andWhere($qb->expr()->eq('deleted_at', $qb->createNamedParameter(0, IQueryBuilder::PARAM_INT)))
->andWhere($qb->expr()->gt('last_modified', $qb->createNamedParameter($since, IQueryBuilder::PARAM_INT)))
->setMaxResults($limit)
->setFirstResult($offset)
->orderBy('order')
->addOrderBy('id');
$rawCards = $this->findEntities($qb);
$cards = array_fill_keys($stackIds, null);
foreach ($rawCards as $card) {
$cards[$card->getStackId()][] = $card;
}
return $cards;
}
public function queryCardsByBoard(int $boardId): IQueryBuilder {
$qb = $this->db->getQueryBuilder();
$qb->select('c.*')
@@ -164,7 +193,10 @@ class CardMapper extends QBMapper implements IPermissionMapper {
return $qb;
}
public function findToDelete($timeLimit, $limit = null) {
/**
* @return Card[]
*/
public function findToDelete(int $timeLimit, ?int $limit = null): array {
$qb = $this->db->getQueryBuilder();
$qb->select('id', 'title', 'owner', 'archived', 'deleted_at', 'last_modified')
->from('deck_cards')
@@ -175,7 +207,10 @@ class CardMapper extends QBMapper implements IPermissionMapper {
return $this->findEntities($qb);
}
public function findDeleted($boardId, $limit = null, $offset = null) {
/**
* @return Card[]
*/
public function findDeleted(int $boardId, ?int $limit = null, int $offset = 0): array {
$qb = $this->queryCardsByBoard($boardId);
$qb->andWhere($qb->expr()->neq('c.deleted_at', $qb->createNamedParameter(0, IQueryBuilder::PARAM_INT)))
->setMaxResults($limit)
@@ -185,7 +220,10 @@ class CardMapper extends QBMapper implements IPermissionMapper {
return $this->findEntities($qb);
}
public function findCalendarEntries($boardId, $limit = null, $offset = null) {
/**
* @return Card[]
*/
public function findCalendarEntries(int $boardId, ?int $limit = null, $offset = 0): array {
$qb = $this->db->getQueryBuilder();
$qb->select('c.*')
->from('deck_cards', 'c')
@@ -240,7 +278,11 @@ class CardMapper extends QBMapper implements IPermissionMapper {
return $this->findEntities($qb);
}
public function findAllWithDue(array $boardIds) {
/**
* @param int[] $boardIds
* @return Card[]
*/
public function findAllWithDue(array $boardIds): array {
$qb = $this->db->getQueryBuilder();
$qb->select('c.*')
->from('deck_cards', 'c')
@@ -257,7 +299,11 @@ class CardMapper extends QBMapper implements IPermissionMapper {
return $this->findEntities($qb);
}
public function findToMeOrNotAssignedCards(array $boardIds, string $username) {
/**
* @param int[] $boardIds
* @return Card[]
*/
public function findToMeOrNotAssignedCards(array $boardIds, string $username): array {
$qb = $this->db->getQueryBuilder();
$qb->select('c.*')
->from('deck_cards', 'c')
@@ -279,7 +325,10 @@ class CardMapper extends QBMapper implements IPermissionMapper {
return $this->findEntities($qb);
}
public function findOverdue() {
/**
* @return Card[]
*/
public function findOverdue(): array {
$qb = $this->db->getQueryBuilder();
$qb->select('id', 'title', 'duedate', 'notified')
->from('deck_cards')
@@ -291,6 +340,9 @@ class CardMapper extends QBMapper implements IPermissionMapper {
return $this->findEntities($qb);
}
/**
* @return Card[]
*/
public function findUnexposedDescriptionChances() {
$qb = $this->db->getQueryBuilder();
$qb->select('id', 'title', 'duedate', 'notified', 'description_prev', 'last_editor', 'description')
@@ -299,6 +351,9 @@ class CardMapper extends QBMapper implements IPermissionMapper {
return $this->findEntities($qb);
}
/**
* @return Card[]
*/
public function search(array $boardIds, SearchQuery $query, ?int $limit = null, ?int $offset = null): array {
$qb = $this->queryCardsByBoards($boardIds);
$this->extendQueryByFilter($qb, $query);
@@ -333,7 +388,7 @@ class CardMapper extends QBMapper implements IPermissionMapper {
$qb->andWhere($qb->expr()->lt('c.last_modified', $qb->createNamedParameter($offset, IQueryBuilder::PARAM_INT)));
}
$result = $qb->execute();
$result = $qb->executeQuery();
$entities = [];
while ($row = $result->fetch()) {
$entities[] = Card::fromRow($row);
@@ -376,7 +431,7 @@ class CardMapper extends QBMapper implements IPermissionMapper {
$qb->andWhere($qb->expr()->lt('comments.id', $qb->createNamedParameter($offset, IQueryBuilder::PARAM_INT)));
}
$result = $qb->execute();
$result = $qb->executeQuery();
$entities = $result->fetchAll();
$result->closeCursor();
return $entities;
@@ -472,7 +527,7 @@ class CardMapper extends QBMapper implements IPermissionMapper {
});
$groups = $this->groupManager->search($assignment->getValue());
foreach ($searchUsers as $user) {
$groups = array_merge($groups, $this->groupManager->getUserIdGroups($user->getUID()));
$groups = array_merge($groups, $this->groupManager->getUserGroups($user));
}
$assignmentSearches = [];
@@ -525,7 +580,7 @@ class CardMapper extends QBMapper implements IPermissionMapper {
if ($offset !== null) {
$qb->setFirstResult($offset);
}
$result = $qb->execute();
$result = $qb->executeQuery();
$all = $result->fetchAll();
$result->closeCursor();
return $all;
@@ -537,32 +592,32 @@ class CardMapper extends QBMapper implements IPermissionMapper {
return parent::delete($entity);
}
public function deleteByStack($stackId) {
public function deleteByStack($stackId): void {
$cards = $this->findAllByStack($stackId);
foreach ($cards as $card) {
$this->delete($card);
}
}
public function assignLabel($card, $label) {
public function assignLabel(int $card, int $label): void {
$qb = $this->db->getQueryBuilder();
$qb->insert('deck_assigned_labels')
->values([
'label_id' => $qb->createNamedParameter($label, IQueryBuilder::PARAM_INT),
'card_id' => $qb->createNamedParameter($card, IQueryBuilder::PARAM_INT),
]);
$qb->execute();
$qb->executeStatement();
}
public function removeLabel($card, $label) {
public function removeLabel(int $card, int $label): void {
$qb = $this->db->getQueryBuilder();
$qb->delete('deck_assigned_labels')
->where($qb->expr()->eq('card_id', $qb->createNamedParameter($card, IQueryBuilder::PARAM_INT)))
->andWhere($qb->expr()->eq('label_id', $qb->createNamedParameter($label, IQueryBuilder::PARAM_INT)));
$qb->execute();
$qb->executeStatement();
}
public function isOwner($userId, $id): bool {
public function isOwner(string $userId, int $id): bool {
$qb = $this->db->getQueryBuilder();
$qb->select('c.id')
->from($this->getTableName(), 'c')
@@ -574,7 +629,7 @@ class CardMapper extends QBMapper implements IPermissionMapper {
return count($qb->executeQuery()->fetchAll()) > 0;
}
public function findBoardId($id): ?int {
public function findBoardId(int $id): ?int {
$result = $this->cache->get('findBoardId:' . $id);
if ($result === null) {
try {
@@ -604,13 +659,11 @@ class CardMapper extends QBMapper implements IPermissionMapper {
}
public function transferOwnership(string $ownerId, string $newOwnerId, ?int $boardId = null): void {
$params = [
'owner' => $ownerId,
'newOwner' => $newOwnerId
];
$sql = "UPDATE `*PREFIX*{$this->tableName}` SET `owner` = :newOwner WHERE `owner` = :owner";
$stmt = $this->db->executeQuery($sql, $params);
$stmt->closeCursor();
$qb = $this->db->getQueryBuilder();
$qb->update($this->getTableName())
->set('owner', $qb->createNamedParameter($newOwnerId, IQueryBuilder::PARAM_STR))
->where('owner', $qb->createNamedParameter($ownerId, IQueryBuilder::PARAM_STR))
->executeStatement();
}
public function remapCardOwner(int $boardId, string $userId, string $newUserId): void {

View File

@@ -19,12 +19,11 @@ use OCP\DB\QueryBuilder\IQueryBuilder;
abstract class DeckMapper extends QBMapper {
/**
* @param $id
* @return T
* @throws \OCP\AppFramework\Db\MultipleObjectsReturnedException
* @throws \OCP\AppFramework\Db\DoesNotExistException
*/
public function find($id) {
public function find(int $id): Entity {
$qb = $this->db->getQueryBuilder();
$qb->select('*')
->from($this->getTableName())
@@ -37,7 +36,7 @@ abstract class DeckMapper extends QBMapper {
* Helper function to split passed array into chunks of 1000 elements and
* call a given callback for fetching query results
*
* Can be useful to limit to 1000 results per query for oracle compatiblity
* Can be useful to limit to 1000 results per query for oracle compatibility
* but still iterate over all results
*/
public function chunkQuery(array $ids, callable $callback): Generator {

View File

@@ -8,22 +8,25 @@
namespace OCA\Deck\Db;
/**
*
*/
interface IPermissionMapper {
/**
* Check if $userId is owner of Entity with $id
*
* @param $userId string userId
* @param $id int|string unique entity identifier
* @param $id int unique entity identifier
* @return boolean
*/
public function isOwner($userId, $id): bool;
public function isOwner(string $userId, int $id): bool;
/**
* Query boardId for Entity of given $id
*
* @param $id int|string unique entity identifier
* @return int|null id of Board
* @param $id int unique entity identifier
* @return ?int id of Board
*/
public function findBoardId($id): ?int;
public function findBoardId(int $id): ?int;
}

View File

@@ -8,7 +8,16 @@
namespace OCA\Deck\Db;
/**
* @method getTitle(): string
* @method string getTitle()
* @method void setTitle(string $title)
* @method string getColor()
* @method void setColor(string $color)
* @method int getBoardId()
* @method void setBoardId(int $boardId)
* @method int getCardId()
* @method void setCardId(int $cardId)
* @method int getLastModified()
* @method void setLastModified(int $lastModified)
*/
class Label extends RelationalEntity {
protected $title;
@@ -24,7 +33,7 @@ class Label extends RelationalEntity {
$this->addType('lastModified', 'integer');
}
public function getETag() {
public function getETag(): string {
return md5((string)$this->getLastModified());
}
}

View File

@@ -20,13 +20,10 @@ class LabelMapper extends DeckMapper implements IPermissionMapper {
}
/**
* @param numeric $boardId
* @param int|null $limit
* @param int|null $offset
* @return Label[]
* @throws \OCP\DB\Exception
*/
public function findAll($boardId, $limit = null, $offset = null): array {
public function findAll(int $boardId, ?int $limit = null, int $offset = 0): array {
$qb = $this->db->getQueryBuilder();
$qb->select('*')
->from($this->getTableName())
@@ -44,13 +41,10 @@ class LabelMapper extends DeckMapper implements IPermissionMapper {
}
/**
* @param numeric $cardId
* @param int|null $limit
* @param int|null $offset
* @return Label[]
* @throws \OCP\DB\Exception
*/
public function findAssignedLabelsForCard($cardId, $limit = null, $offset = null): array {
public function findAssignedLabelsForCard(int $cardId, ?int $limit = null, int $offset = 0): array {
$qb = $this->db->getQueryBuilder();
$qb->select('l.*', 'card_id')
->from($this->getTableName(), 'l')
@@ -63,7 +57,7 @@ class LabelMapper extends DeckMapper implements IPermissionMapper {
return $this->findEntities($qb);
}
public function findAssignedLabelsForCards($cardIds, $limit = null, $offset = null): array {
public function findAssignedLabelsForCards(array $cardIds, ?int $limit = null, int $offset = 0): array {
$qb = $this->db->getQueryBuilder();
$qb->select('l.*', 'card_id')
->from($this->getTableName(), 'l')
@@ -77,13 +71,10 @@ class LabelMapper extends DeckMapper implements IPermissionMapper {
}
/**
* @param numeric $boardId
* @param int|null $limit
* @param int|null $offset
* @return Label[]
* @throws \OCP\DB\Exception
*/
public function findAssignedLabelsForBoard($boardId, $limit = null, $offset = null): array {
public function findAssignedLabelsForBoard(int $boardId, ?int $limit = null, int $offset = 0): array {
$qb = $this->db->getQueryBuilder();
$qb->select('l.id as id', 'l.title as title', 'l.color as color')
->selectAlias('c.id', 'card_id')
@@ -113,11 +104,10 @@ class LabelMapper extends DeckMapper implements IPermissionMapper {
}
/**
* @param numeric $boardId
* @return array
* @return array<int, list<Label>>
* @throws \OCP\DB\Exception
*/
public function getAssignedLabelsForBoard($boardId) {
public function getAssignedLabelsForBoard(int $boardId): array {
$labels = $this->findAssignedLabelsForBoard($boardId);
$result = [];
foreach ($labels as $label) {
@@ -130,11 +120,9 @@ class LabelMapper extends DeckMapper implements IPermissionMapper {
}
/**
* @param numeric $labelId
* @return void
* @throws \OCP\DB\Exception
*/
public function deleteLabelAssignments($labelId) {
public function deleteLabelAssignments(int $labelId): void {
$qb = $this->db->getQueryBuilder();
$qb->delete('deck_assigned_labels')
->where($qb->expr()->eq('label_id', $qb->createNamedParameter($labelId, IQueryBuilder::PARAM_INT)));
@@ -142,11 +130,9 @@ class LabelMapper extends DeckMapper implements IPermissionMapper {
}
/**
* @param numeric $cardId
* @return void
* @throws \OCP\DB\Exception
*/
public function deleteLabelAssignmentsForCard($cardId) {
public function deleteLabelAssignmentsForCard(int $cardId): void {
$qb = $this->db->getQueryBuilder();
$qb->delete('deck_assigned_labels')
->where($qb->expr()->eq('card_id', $qb->createNamedParameter($cardId, IQueryBuilder::PARAM_INT)));
@@ -154,33 +140,25 @@ class LabelMapper extends DeckMapper implements IPermissionMapper {
}
/**
* @param string $userId
* @param numeric $labelId
* @return bool
* @throws \OCP\DB\Exception
*/
public function isOwner($userId, $labelId): bool {
public function isOwner(string $userId, int $id): bool {
$qb = $this->db->getQueryBuilder();
$qb->select('l.id')
->from($this->getTableName(), 'l')
->innerJoin('l', 'deck_boards', 'b', 'l.board_id = b.id')
->where($qb->expr()->eq('l.id', $qb->createNamedParameter($labelId, IQueryBuilder::PARAM_INT)))
->where($qb->expr()->eq('l.id', $qb->createNamedParameter($id, IQueryBuilder::PARAM_INT)))
->andWhere($qb->expr()->eq('b.owner', $qb->createNamedParameter($userId, IQueryBuilder::PARAM_STR)));
return count($qb->executeQuery()->fetchAll()) > 0;
}
/**
* @param numeric $id
* @return int|null
*/
public function findBoardId($id): ?int {
public function findBoardId(int $id): ?int {
try {
$entity = $this->find($id);
return $entity->getBoardId();
} catch (DoesNotExistException $e) {
} catch (MultipleObjectsReturnedException $e) {
}
} catch (DoesNotExistException|MultipleObjectsReturnedException) {
return null;
}
}
}

View File

@@ -11,10 +11,17 @@ use Sabre\VObject\Component\VCalendar;
/**
* @method int getId()
* @method string getTitle()
* @method void setTitle(string $title)
* @method int getBoardId()
* @method void setBoardId(int $boardId)
* @method int getDeletedAt()
* @method void setDeletedAt(int $deletedAt)
* @method int getLastModified()
* @method int getOrder()
* @method void setLastModified(int $lastModified)
* @method \int getOrder()
* @method void setOrder(int $order)
* @method Card[] getCards()
*/
class Stack extends RelationalEntity {
protected $title;

View File

@@ -35,13 +35,11 @@ class StackMapper extends DeckMapper implements IPermissionMapper {
/**
* @param numeric $id
* @return Stack
* @throws DoesNotExistException
* @throws MultipleObjectsReturnedException
* @throws \OCP\DB\Exception
*/
public function find($id): Stack {
public function find(int $id): Stack {
if (isset($this->stackCache[(string)$id])) {
return $this->stackCache[(string)$id];
}
@@ -56,11 +54,9 @@ class StackMapper extends DeckMapper implements IPermissionMapper {
}
/**
* @param $cardId
* @return Stack|null
* @throws \OCP\DB\Exception
*/
public function findStackFromCardId($cardId): ?Stack {
public function findStackFromCardId(int $cardId): ?Stack {
$qb = $this->db->getQueryBuilder();
$qb->select('s.*')
->from($this->getTableName(), 's')
@@ -76,13 +72,10 @@ class StackMapper extends DeckMapper implements IPermissionMapper {
}
/**
* @param numeric $boardId
* @param int|null $limit
* @param int|null $offset
* @return Stack[]
* @throws \OCP\DB\Exception
*/
public function findAll($boardId, $limit = null, $offset = null): array {
public function findAll(int $boardId, ?int $limit = null, int $offset = 0): array {
$qb = $this->db->getQueryBuilder();
$qb->select('*')
->from($this->getTableName())
@@ -95,13 +88,9 @@ class StackMapper extends DeckMapper implements IPermissionMapper {
}
/**
* @param numeric $boardId
* @param int|null $limit
* @param int|null $offset
* @return Stack[]
* @throws \OCP\DB\Exception
*/
public function findDeleted($boardId, $limit = null, $offset = null) {
public function findDeleted(int $boardId, ?int $limit = null, int $offset = 0): array {
$qb = $this->db->getQueryBuilder();
$qb->select('*')
->from($this->getTableName())
@@ -127,12 +116,9 @@ class StackMapper extends DeckMapper implements IPermissionMapper {
}
/**
* @param numeric $userId
* @param numeric $stackId
* @return bool
* @throws \OCP\DB\Exception
*/
public function isOwner($userId, $id): bool {
public function isOwner(string $userId, int $id): bool {
$qb = $this->db->getQueryBuilder();
$qb->select('s.id')
->from($this->getTableName(), 's')
@@ -144,11 +130,9 @@ class StackMapper extends DeckMapper implements IPermissionMapper {
}
/**
* @param numeric $id
* @return int|null
* @throws \OCP\DB\Exception
*/
public function findBoardId($id): ?int {
public function findBoardId(int $id): ?int {
$result = $this->cache->get('findBoardId:' . $id);
if ($result !== null) {
return $result !== false ? $result : null;
@@ -165,6 +149,10 @@ class StackMapper extends DeckMapper implements IPermissionMapper {
return $result !== false ? $result : null;
}
/**
* @return array<Stack>
* @throws \OCP\DB\Exception
*/
public function findToDelete(): array {
// add buffer of 5 min
$timeLimit = time() - (60 * 5);

View File

@@ -35,6 +35,10 @@ class BeforeTemplateRenderedListener implements IEventListener {
Util::addStyle('deck', 'deck');
$pathInfo = $this->request->getPathInfo();
if (!$pathInfo) {
return;
}
if (str_starts_with($pathInfo, '/apps/calendar')) {
Util::addScript('deck', 'deck-calendar');
}

View File

@@ -18,14 +18,10 @@ use OCP\EventDispatcher\IEventListener;
/** @template-implements IEventListener<Event|AclDeletedEvent|AclCreatedEvent> */
class ResourceListener implements IEventListener {
/** @var IManager */
private $resourceManager;
/** @var ResourceProviderCard */
private $resourceProviderCard;
public function __construct(IManager $resourceManager, ResourceProviderCard $resourceProviderCard) {
$this->resourceManager = $resourceManager;
$this->resourceProviderCard = $resourceProviderCard;
public function __construct(
private readonly IManager $resourceManager,
private readonly ResourceProviderCard $resourceProviderCard,
) {
}
public function handle(Event $event): void {
@@ -38,10 +34,10 @@ class ResourceListener implements IEventListener {
$this->resourceManager->invalidateAccessCacheForProvider($this->resourceProviderCard);
try {
$resource = $this->resourceManager->getResourceForUser(ResourceProvider::RESOURCE_TYPE, $boardId, null);
$resource = $this->resourceManager->getResourceForUser(ResourceProvider::RESOURCE_TYPE, (string)$boardId, null);
$this->resourceManager->invalidateAccessCacheForResource($resource);
} catch (ResourceException $e) {
// If there is no resource we don't need to invalidate anything, but this should not happen anyways
// If there is no resource we don't need to invalidate anything, but this should not happen anyway
}
}
}

View File

@@ -11,6 +11,7 @@ use OCA\Deck\Db\Board;
class BoardSummary extends Board {
private Board $board;
/** @psalm-suppress ConstructorSignatureMismatch */
public function __construct(Board $board) {
parent::__construct();
$this->board = $board;
@@ -27,7 +28,7 @@ class BoardSummary extends Board {
return $this->board->getter($name);
}
public function __call($name, $arguments) {
return $this->board->__call($name, $arguments);
public function __call($methodName, $args) {
return $this->board->__call($methodName, $args);
}
}

View File

@@ -15,6 +15,7 @@ class CardDetails extends Card {
private ?Board $board;
private ?Reference $referenceData = null;
/** @psalm-suppress ConstructorSignatureMismatch */
public function __construct(Card $card, ?Board $board = null) {
parent::__construct();
$this->card = $card;

View File

@@ -95,7 +95,7 @@ class NotificationHelper {
$shouldNotify = $notificationSetting === ConfigService::SETTING_BOARD_NOTIFICATION_DUE_ALL;
if ($user->getUID() === $board->getOwner() && count($board->getAcl()) === 0) {
if ($user->getUID() === $board->getOwner() && count($board->getAcl() ?? []) === 0) {
// Notify if all or assigned is configured for unshared boards
$shouldNotify = true;
} elseif ($notificationSetting === ConfigService::SETTING_BOARD_NOTIFICATION_DUE_ASSIGNED && $this->assignmentMapper->isUserAssigned($card->getId(), $user->getUID())) {

View File

@@ -64,6 +64,7 @@ class BoardReferenceProvider implements IReferenceProvider {
$reference = new Reference($referenceText);
$reference->setTitle($this->l10n->t('Deck board') . ': ' . $board['title']);
$ownerDisplayName = $board['owner']['displayname'] ?? $board['owner']['uid'] ?? '???';
// TRANSLATORS Owned by {boardOwnerName}
$reference->setDescription($this->l10n->t('Owned by %1$s', [$ownerDisplayName]));
$imageUrl = $this->urlGenerator->getAbsoluteURL(
$this->urlGenerator->imagePath(Application::APP_ID, 'deck-dark.svg')

View File

@@ -90,6 +90,7 @@ class CommentReferenceProvider implements IReferenceProvider {
$reference->setTitle($comment['message']);
$boardOwnerDisplayName = $board['owner']['displayname'] ?? $board['owner']['uid'] ?? '???';
$reference->setDescription(
// TRANSLATORS From {userName}, in {boardTitle}/{stackTitle}, owned by {boardOwnerName}
$this->l10n->t('From %1$s, in %2$s/%3$s, owned by %4$s', [
$comment['actorDisplayName'],
$board['title'],

View File

@@ -21,7 +21,6 @@ use OCA\Deck\NotFoundException;
use OCA\Deck\Notification\NotificationHelper;
use OCA\Deck\Validators\AssignmentServiceValidator;
use OCP\AppFramework\Db\DoesNotExistException;
use OCP\AppFramework\Db\Entity;
use OCP\AppFramework\Db\MultipleObjectsReturnedException;
use OCP\EventDispatcher\IEventDispatcher;
@@ -92,15 +91,12 @@ class AssignmentService {
}
/**
* @param $cardId
* @param $userId
* @return bool|null|Entity
* @throws BadRequestException
* @throws NoPermissionException
* @throws MultipleObjectsReturnedException
* @throws DoesNotExistException
*/
public function assignUser($cardId, $userId, int $type = Assignment::TYPE_USER) {
public function assignUser(int $cardId, string $userId, int $type = Assignment::TYPE_USER): Assignment {
$this->assignmentServiceValidator->check(compact('cardId', 'userId'));
if ($type !== Assignment::TYPE_USER && $type !== Assignment::TYPE_GROUP) {
@@ -144,16 +140,13 @@ class AssignmentService {
}
/**
* @param $cardId
* @param $userId
* @return Entity
* @throws BadRequestException
* @throws NotFoundException
* @throws NoPermissionException
* @throws DoesNotExistException
* @throws MultipleObjectsReturnedException
*/
public function unassignUser($cardId, $userId, $type = 0) {
public function unassignUser(int $cardId, string $userId, int $type = 0): Assignment {
$this->assignmentServiceValidator->check(compact('cardId', 'userId'));
$this->permissionService->checkPermission($this->cardMapper, $cardId, Acl::PERMISSION_EDIT);

View File

@@ -25,6 +25,7 @@ use OCP\AppFramework\Db\IMapperException;
use OCP\AppFramework\Http\Response;
use OCP\IL10N;
use OCP\IUserManager;
use Psr\Container\ContainerExceptionInterface;
class AttachmentService {
private $attachmentMapper;
@@ -80,20 +81,16 @@ class AttachmentService {
}
/**
* @param string $type
* @param string $class
* @throws \OCP\AppFramework\QueryException
* @throws ContainerExceptionInterface
*/
public function registerAttachmentService($type, $class) {
$this->services[$type] = $this->application->getContainer()->query($class);
public function registerAttachmentService(string $type, string $class): void {
$this->services[$type] = $this->application->getContainer()->get($class);
}
/**
* @param string $type
* @return IAttachmentService
* @throws InvalidAttachmentType
*/
public function getService($type) {
public function getService(string $type): IAttachmentService {
if (isset($this->services[$type])) {
return $this->services[$type];
}
@@ -101,16 +98,11 @@ class AttachmentService {
}
/**
* @param $cardId
* @return array
* @return Attachment[]
* @throws \OCA\Deck\NoPermissionException
* @throws BadRequestException
*/
public function findAll($cardId, $withDeleted = false) {
if (is_numeric($cardId) === false) {
throw new BadRequestException('card id must be a number');
}
public function findAll(int $cardId, bool $withDeleted = false): array {
$this->permissionService->checkPermission($this->cardMapper, $cardId, Acl::PERMISSION_READ);
$attachments = $this->attachmentMapper->findAll($cardId);
@@ -122,7 +114,7 @@ class AttachmentService {
/** @var IAttachmentService $service */
$service = $this->getService($attachmentType);
if ($service instanceof ICustomAttachmentService) {
$attachments = array_merge($attachments, $service->listAttachments((int)$cardId));
$attachments = array_merge($attachments, $service->listAttachments($cardId));
}
}
@@ -140,49 +132,40 @@ class AttachmentService {
}
/**
* @param $cardId
* @return int|mixed
* @throws BadRequestException
* @throws InvalidAttachmentType
* @throws \OCP\DB\Exception
*/
public function count($cardId) {
if (is_numeric($cardId) === false) {
throw new BadRequestException('card id must be a number');
}
$count = $this->attachmentCacheHelper->getAttachmentCount((int)$cardId);
public function count(int $cardId): int {
$count = $this->attachmentCacheHelper->getAttachmentCount($cardId);
if ($count === null) {
$count = count($this->attachmentMapper->findAll($cardId));
foreach (array_keys($this->services) as $attachmentType) {
$service = $this->getService($attachmentType);
if ($service instanceof ICustomAttachmentService) {
$count += $service->getAttachmentCount((int)$cardId);
$count += $service->getAttachmentCount($cardId);
}
}
$this->attachmentCacheHelper->setAttachmentCount((int)$cardId, $count);
$this->attachmentCacheHelper->setAttachmentCount($cardId, $count);
}
return $count;
}
/**
* @param $cardId
* @param $type
* @param $data
* @return Attachment|\OCP\AppFramework\Db\Entity
* @throws NoPermissionException
* @throws StatusException
* @throws BadRequestException
*/
public function create($cardId, $type, $data) {
public function create(int $cardId, string $type, string $data) {
$this->attachmentServiceValidator->check(compact('cardId', 'type'));
$this->permissionService->checkPermission($this->cardMapper, $cardId, Acl::PERMISSION_EDIT);
$this->attachmentCacheHelper->clearAttachmentCount((int)$cardId);
$this->attachmentCacheHelper->clearAttachmentCount($cardId);
$attachment = new Attachment();
$attachment->setCardId($cardId);
$attachment->setType($type);
@@ -218,12 +201,10 @@ class AttachmentService {
/**
* Display the attachment
*
* @param $attachmentId
* @return Response
* @throws NoPermissionException
* @throws NotFoundException
*/
public function display($cardId, $attachmentId, $type = 'deck_file') {
public function display(int $cardId, int $attachmentId, string $type = 'deck_file'): Response {
try {
$service = $this->getService($type);
} catch (InvalidAttachmentType $e) {
@@ -257,13 +238,10 @@ class AttachmentService {
/**
* Update an attachment with custom data
*
* @param $attachmentId
* @param $data
* @return mixed
* @throws BadRequestException
* @throws NoPermissionException
*/
public function update($cardId, $attachmentId, $data, $type = 'deck_file') {
public function update(int $cardId, int $attachmentId, string $data, string $type = 'deck_file'): Attachment {
$this->attachmentServiceValidator->check(compact('cardId', 'type', 'data'));
try {
@@ -384,8 +362,6 @@ class AttachmentService {
}
/**
* @param Attachment $attachment
* @return Attachment
* @throws \ReflectionException
*/
private function addCreator(Attachment $attachment): Attachment {

View File

@@ -75,8 +75,6 @@ class BoardService {
/**
* Set a different user than the current one, e.g. when no user is available in occ
*
* @param string $userId
*/
public function setUserId(string $userId): void {
$this->userId = $userId;
@@ -117,22 +115,18 @@ class BoardService {
}
$this->permissionService->checkPermission($this->boardMapper, $boardId, Acl::PERMISSION_READ);
/** @var Board $board */
$board = $this->boardMapper->find($boardId, true, true, $allowDeleted);
[$board] = $this->enrichBoards([$board], $fullDetails);
return $board;
}
/**
* @param $mapper
* @param $id
* @return bool
* @throws DoesNotExistException
* @throws NoPermissionException
* @throws \OCP\AppFramework\Db\MultipleObjectsReturnedException
* @throws BadRequestException
*/
public function isArchived($mapper, $id) {
public function isArchived($mapper, int $id): bool {
$this->boardServiceValidator->check(compact('id'));
try {
@@ -151,15 +145,12 @@ class BoardService {
}
/**
* @param $mapper
* @param $id
* @return bool
* @throws DoesNotExistException
* @throws NoPermissionException
* @throws \OCP\AppFramework\Db\MultipleObjectsReturnedException
* @throws BadRequestException
*/
public function isDeleted($mapper, $id) {
public function isDeleted($mapper, int $id): bool {
$this->boardServiceValidator->check(compact('mapper', 'id'));
try {
@@ -179,13 +170,9 @@ class BoardService {
/**
* @param $title
* @param $userId
* @param $color
* @return \OCP\AppFramework\Db\Entity
* @throws BadRequestException
*/
public function create($title, $userId, $color) {
public function create(string $title, string $userId, string $color): Board {
$this->boardServiceValidator->check(compact('title', 'userId', 'color'));
if (!$this->permissionService->canCreate()) {
@@ -196,7 +183,8 @@ class BoardService {
$board->setTitle($title);
$board->setOwner($userId);
$board->setColor($color);
$new_board = $this->boardMapper->insert($board);
/** @var Board $board */
$board = $this->boardMapper->insert($board);
// create new labels
$default_labels = [
@@ -210,33 +198,31 @@ class BoardService {
$label = new Label();
$label->setColor($labelColor);
$label->setTitle($labelTitle);
$label->setBoardId($new_board->getId());
$label->setBoardId($board->getId());
$labels[] = $this->labelMapper->insert($label);
}
$new_board->setLabels($labels);
$this->boardMapper->mapOwner($new_board);
$permissions = $this->permissionService->matchPermissions($new_board);
$new_board->setPermissions([
$board->setLabels($labels);
$this->boardMapper->mapOwner($board);
$permissions = $this->permissionService->matchPermissions($board);
$board->setPermissions([
'PERMISSION_READ' => $permissions[Acl::PERMISSION_READ] ?? false,
'PERMISSION_EDIT' => $permissions[Acl::PERMISSION_EDIT] ?? false,
'PERMISSION_MANAGE' => $permissions[Acl::PERMISSION_MANAGE] ?? false,
'PERMISSION_SHARE' => $permissions[Acl::PERMISSION_SHARE] ?? false
]);
$this->activityManager->triggerEvent(ActivityManager::DECK_OBJECT_BOARD, $new_board, ActivityManager::SUBJECT_BOARD_CREATE, [], $userId);
$this->changeHelper->boardChanged($new_board->getId());
$this->activityManager->triggerEvent(ActivityManager::DECK_OBJECT_BOARD, $board, ActivityManager::SUBJECT_BOARD_CREATE, [], $userId);
$this->changeHelper->boardChanged($board->getId());
return $new_board;
return $board;
}
/**
* @param $id
* @return Board
* @throws DoesNotExistException
* @throws NoPermissionException
* @throws \OCP\AppFramework\Db\MultipleObjectsReturnedException
* @throws BadRequestException
*/
public function delete($id) {
public function delete(int $id): Board {
$this->boardServiceValidator->check(compact('id'));
$this->permissionService->checkPermission($this->boardMapper, $id, Acl::PERMISSION_MANAGE);
@@ -253,13 +239,11 @@ class BoardService {
}
/**
* @param $id
* @return \OCP\AppFramework\Db\Entity
* @throws DoesNotExistException
* @throws NoPermissionException
* @throws \OCP\AppFramework\Db\MultipleObjectsReturnedException
*/
public function deleteUndo($id) {
public function deleteUndo(int $id): Board {
$this->boardServiceValidator->check(compact('id'));
$this->permissionService->checkPermission($this->boardMapper, $id, Acl::PERMISSION_MANAGE);
@@ -273,14 +257,12 @@ class BoardService {
}
/**
* @param $id
* @return \OCP\AppFramework\Db\Entity
* @throws DoesNotExistException
* @throws NoPermissionException
* @throws \OCP\AppFramework\Db\MultipleObjectsReturnedException
* @throws BadRequestException
*/
public function deleteForce($id) {
public function deleteForce(int $id): Board {
$this->boardServiceValidator->check(compact('id'));
$this->permissionService->checkPermission($this->boardMapper, $id, Acl::PERMISSION_MANAGE);
@@ -291,17 +273,12 @@ class BoardService {
}
/**
* @param $id
* @param $title
* @param $color
* @param $archived
* @return \OCP\AppFramework\Db\Entity
* @throws DoesNotExistException
* @throws NoPermissionException
* @throws \OCP\AppFramework\Db\MultipleObjectsReturnedException
* @throws BadRequestException
*/
public function update($id, $title, $color, $archived) {
public function update(int $id, string $title, string $color, bool $archived): Board {
$this->boardServiceValidator->check(compact('id', 'title', 'color', 'archived'));
$this->permissionService->checkPermission($this->boardMapper, $id, Acl::PERMISSION_MANAGE);
@@ -320,7 +297,7 @@ class BoardService {
return $board;
}
private function applyPermissions($boardId, $edit, $share, $manage, $oldAcl = null) {
private function applyPermissions(int $boardId, bool $edit, bool $share, bool $manage, ?Acl $oldAcl = null): array {
try {
$this->permissionService->checkPermission($this->boardMapper, $boardId, Acl::PERMISSION_MANAGE);
} catch (NoPermissionException $e) {
@@ -332,7 +309,7 @@ class BoardService {
return [$edit, $share, $manage];
}
public function enrichWithBoardSettings(Board $board) {
public function enrichWithBoardSettings(Board $board): void {
$globalCalendarConfig = (bool)$this->config->getUserValue($this->userId, Application::APP_ID, 'calendar', true);
$settings = [
'notify-due' => $this->config->getUserValue($this->userId, Application::APP_ID, 'board:' . $board->getId() . ':notify-due', ConfigService::SETTING_BOARD_NOTIFICATION_DUE_ASSIGNED),
@@ -341,7 +318,7 @@ class BoardService {
$board->setSettings($settings);
}
public function enrichWithActiveSessions(Board $board) {
public function enrichWithActiveSessions(Board $board): void {
$sessions = $this->sessionMapper->findAllActive($board->getId());
$board->setActiveSessions(array_values(
@@ -354,17 +331,11 @@ class BoardService {
}
/**
* @param $boardId
* @param $type
* @param $participant
* @param $edit
* @param $share
* @param $manage
* @return \OCP\AppFramework\Db\Entity
* @param Acl::PERMISSION_TYPE_* $type
* @throws BadRequestException
* @throws NoPermissionException
*/
public function addAcl($boardId, $type, $participant, $edit, $share, $manage) {
public function addAcl(int $boardId, int $type, $participant, bool $edit, bool $share, bool $manage): Acl {
$this->boardServiceValidator->check(compact('boardId', 'type', 'participant', 'edit', 'share', 'manage'));
$this->permissionService->checkPermission($this->boardMapper, $boardId, Acl::PERMISSION_SHARE);
@@ -400,17 +371,12 @@ class BoardService {
}
/**
* @param $id
* @param $edit
* @param $share
* @param $manage
* @return \OCP\AppFramework\Db\Entity
* @throws DoesNotExistException
* @throws NoPermissionException
* @throws \OCP\AppFramework\Db\MultipleObjectsReturnedException
* @throws BadRequestException
*/
public function updateAcl($id, $edit, $share, $manage) {
public function updateAcl(int $id, bool $edit, bool $share, bool $manage): Acl {
$this->boardServiceValidator->check(compact('id', 'edit', 'share', 'manage'));
$this->permissionService->checkPermission($this->aclMapper, $id, Acl::PERMISSION_SHARE);
@@ -422,12 +388,12 @@ class BoardService {
$acl->setPermissionShare($share);
$acl->setPermissionManage($manage);
$this->boardMapper->mapAcl($acl);
$board = $this->aclMapper->update($acl);
$acl = $this->aclMapper->update($acl);
$this->changeHelper->boardChanged($acl->getBoardId());
$this->eventDispatcher->dispatchTyped(new AclUpdatedEvent($acl));
return $board;
return $acl;
}
/**
@@ -579,27 +545,23 @@ class BoardService {
}
/**
* @param $id
* @return Board
* @throws DoesNotExistException
* @throws NoPermissionException
* @throws \OCP\AppFramework\Db\MultipleObjectsReturnedException
* @throws BadRequestException
*/
public function export($id) : Board {
if (is_numeric($id) === false) {
throw new BadRequestException('board id must be a number');
}
public function export(int $id): Board {
$this->permissionService->checkPermission($this->boardMapper, $id, Acl::PERMISSION_READ);
$board = $this->boardMapper->find((int)$id);
$board = $this->boardMapper->find($id);
$this->enrichWithCards($board);
$this->enrichWithLabels($board);
return $board;
}
/** @param Board[] $boards */
/**
* @param Board[] $boards
* @return Board[]
*/
private function enrichBoards(array $boards, bool $fullDetails = true): array {
$result = [];
foreach ($boards as $board) {
@@ -715,8 +677,8 @@ class BoardService {
}
}
private function enrichWithStacks($board, $since = -1) {
$stacks = $this->stackMapper->findAll($board->getId(), null, null, $since);
private function enrichWithStacks(Board $board): void {
$stacks = $this->stackMapper->findAll($board->getId());
if (\count($stacks) === 0) {
return;
@@ -725,8 +687,8 @@ class BoardService {
$board->setStacks($stacks);
}
private function enrichWithLabels($board, $since = -1) {
$labels = $this->labelMapper->findAll($board->getId(), null, null, $since);
private function enrichWithLabels(Board $board): void {
$labels = $this->labelMapper->findAll($board->getId());
if (\count($labels) === 0) {
return;
@@ -735,7 +697,7 @@ class BoardService {
$board->setLabels($labels);
}
private function enrichWithUsers($board, $since = -1) {
private function enrichWithUsers(Board $board): void {
$boardUsers = $this->permissionService->findUsers($board->getId());
if ($boardUsers === null || \count($boardUsers) === 0) {
return;
@@ -746,7 +708,7 @@ class BoardService {
/**
* Clean a given board data from the Cache
*/
private function clearBoardFromCache(Board $board) {
private function clearBoardFromCache(Board $board): void {
$boardId = $board->getId();
$boardOwnerId = $board->getOwner();
@@ -755,7 +717,7 @@ class BoardService {
unset($this->boardsCachePartial[$boardId]);
}
private function enrichWithCards($board) {
private function enrichWithCards(Board $board): void {
$stacks = $this->stackMapper->findAll($board->getId());
foreach ($stacks as $stack) {
$cards = $this->cardMapper->findAllByStack($stack->getId());

View File

@@ -64,10 +64,14 @@ class CardService {
) {
}
public function enrichCards($cards) {
/**
* @param Card[] $cards
* @return CardDetails[]
*/
public function enrichCards(array $cards): array {
$user = $this->userManager->get($this->userId);
$cardIds = array_map(function (Card $card) use ($user) {
$cardIds = array_map(function (Card $card) use ($user): int {
// Everything done in here might be heavy as it is executed for every card
$cardId = $card->getId();
$this->cardMapper->mapOwner($card);
@@ -120,7 +124,8 @@ class CardService {
);
}
public function fetchDeleted($boardId) {
/** @return Card[] */
public function fetchDeleted($boardId): array {
$this->cardServiceValidator->check(compact('boardId'));
$this->permissionService->checkPermission($this->boardMapper, $boardId, Acl::PERMISSION_READ);
$cards = $this->cardMapper->findDeleted($boardId);
@@ -129,13 +134,12 @@ class CardService {
}
/**
* @return \OCA\Deck\Db\RelationalEntity
* @throws \OCA\Deck\NoPermissionException
* @throws \OCP\AppFramework\Db\DoesNotExistException
* @throws \OCP\AppFramework\Db\MultipleObjectsReturnedException
* @throws BadRequestException
*/
public function find(int $cardId) {
public function find(int $cardId): Card {
$this->permissionService->checkPermission($this->cardMapper, $cardId, Acl::PERMISSION_READ);
$card = $this->cardMapper->find($cardId);
[$card] = $this->enrichCards([$card]);
@@ -152,7 +156,10 @@ class CardService {
return $card;
}
public function findCalendarEntries($boardId) {
/**
* @return Card[]
*/
public function findCalendarEntries(int $boardId): array {
try {
$this->permissionService->checkPermission($this->boardMapper, $boardId, Acl::PERMISSION_READ);
} catch (NoPermissionException $e) {
@@ -163,20 +170,13 @@ class CardService {
}
/**
* @param $title
* @param $stackId
* @param $type
* @param integer $order
* @param $description
* @param $owner
* @return \OCP\AppFramework\Db\Entity
* @throws StatusException
* @throws \OCA\Deck\NoPermissionException
* @throws \OCP\AppFramework\Db\DoesNotExistException
* @throws \OCP\AppFramework\Db\MultipleObjectsReturnedException
* @throws BadrequestException
*/
public function create($title, $stackId, $type, $order, $owner, $description = '', $duedate = null) {
public function create(string $title, int $stackId, string $type, int $order, string $owner, string $description = '', $duedate = null): Card {
$this->cardServiceValidator->check(compact('title', 'stackId', 'type', 'order', 'owner'));
$this->permissionService->checkPermission($this->stackMapper, $stackId, Acl::PERMISSION_EDIT);
@@ -203,19 +203,13 @@ class CardService {
}
/**
* @param $id
* @return \OCP\AppFramework\Db\Entity
* @throws StatusException
* @throws \OCA\Deck\NoPermissionException
* @throws \OCP\AppFramework\Db\DoesNotExistException
* @throws \OCP\AppFramework\Db\MultipleObjectsReturnedException
* @throws BadRequestException
*/
public function delete($id) {
if (is_numeric($id) === false) {
throw new BadRequestException('card id must be a number');
}
public function delete(int $id): Card {
$this->permissionService->checkPermission($this->cardMapper, $id, Acl::PERMISSION_EDIT);
if ($this->boardService->isArchived($this->cardMapper, $id)) {
throw new StatusException('Operation not allowed. This board is archived.');
@@ -233,25 +227,13 @@ class CardService {
}
/**
* @param $id
* @param $title
* @param $stackId
* @param $type
* @param $owner
* @param $description
* @param $order
* @param $duedate
* @param $deletedAt
* @param $archived
* @param $done
* @return \OCP\AppFramework\Db\Entity
* @throws StatusException
* @throws \OCA\Deck\NoPermissionException
* @throws \OCP\AppFramework\Db\DoesNotExistException
* @throws \OCP\AppFramework\Db\MultipleObjectsReturnedException
* @throws BadRequestException
*/
public function update($id, $title, $stackId, $type, $owner, $description = '', $order = 0, $duedate = null, $deletedAt = null, $archived = null, ?OptionalNullableValue $done = null) {
public function update(int $id, string $title, int $stackId, string $type, string $owner, string $description = '', int $order = 0, ?string $duedate = null, ?int $deletedAt = null, ?bool $archived = null, ?OptionalNullableValue $done = null): Card {
$this->cardServiceValidator->check(compact('id', 'title', 'stackId', 'type', 'owner', 'order'));
$this->permissionService->checkPermission($this->cardMapper, $id, Acl::PERMISSION_EDIT, allowDeletedCard: true);
@@ -398,16 +380,13 @@ class CardService {
}
/**
* @param $id
* @param $title
* @return \OCP\AppFramework\Db\Entity
* @throws StatusException
* @throws \OCA\Deck\NoPermissionException
* @throws \OCP\AppFramework\Db\DoesNotExistException
* @throws \OCP\AppFramework\Db\MultipleObjectsReturnedException
* @throws BadRequestException
*/
public function rename($id, $title) {
public function rename(int $id, string $title): Card {
$this->cardServiceValidator->check(compact('id', 'title'));
$this->permissionService->checkPermission($this->cardMapper, $id, Acl::PERMISSION_EDIT);
@@ -429,17 +408,14 @@ class CardService {
}
/**
* @param $id
* @param $stackId
* @param $order
* @return array
* @return list<Card>
* @throws StatusException
* @throws \OCA\Deck\NoPermissionException
* @throws \OCP\AppFramework\Db\DoesNotExistException
* @throws \OCP\AppFramework\Db\MultipleObjectsReturnedException
* @throws BadRequestException
*/
public function reorder(int $id, int $stackId, int $order) {
public function reorder(int $id, int $stackId, int $order): array {
$this->cardServiceValidator->check(compact('id', 'stackId', 'order'));
$this->permissionService->checkPermission($this->cardMapper, $id, Acl::PERMISSION_EDIT);
@@ -488,15 +464,13 @@ class CardService {
}
/**
* @param $id
* @return \OCP\AppFramework\Db\Entity
* @throws StatusException
* @throws \OCA\Deck\NoPermissionException
* @throws \OCP\AppFramework\Db\DoesNotExistException
* @throws \OCP\AppFramework\Db\MultipleObjectsReturnedException
* @throws BadRequestException
*/
public function archive($id) {
public function archive(int $id): Card {
$this->cardServiceValidator->check(compact('id'));
$this->permissionService->checkPermission($this->cardMapper, $id, Acl::PERMISSION_EDIT);
@@ -517,15 +491,13 @@ class CardService {
}
/**
* @param $id
* @return \OCP\AppFramework\Db\Entity
* @throws StatusException
* @throws \OCA\Deck\NoPermissionException
* @throws \OCP\AppFramework\Db\DoesNotExistException
* @throws \OCP\AppFramework\Db\MultipleObjectsReturnedException
* @throws BadRequestException
*/
public function unarchive($id) {
public function unarchive(int $id): Card {
$this->cardServiceValidator->check(compact('id'));
@@ -546,8 +518,6 @@ class CardService {
}
/**
* @param $id
* @return \OCA\Deck\Db\Card
* @throws StatusException
* @throws \OCA\Deck\NoPermissionException
* @throws \OCP\AppFramework\Db\DoesNotExistException
@@ -599,15 +569,13 @@ class CardService {
}
/**
* @param $cardId
* @param $labelId
* @throws StatusException
* @throws \OCA\Deck\NoPermissionException
* @throws \OCP\AppFramework\Db\DoesNotExistException
* @throws \OCP\AppFramework\Db\MultipleObjectsReturnedException
* @throws BadRequestException
*/
public function assignLabel($cardId, $labelId) {
public function assignLabel(int $cardId, int $labelId): Card {
$this->cardServiceValidator->check(compact('cardId', 'labelId'));
$this->permissionService->checkPermission($this->cardMapper, $cardId, Acl::PERMISSION_EDIT);
@@ -629,18 +597,16 @@ class CardService {
$this->activityManager->triggerEvent(ActivityManager::DECK_OBJECT_CARD, $card, ActivityManager::SUBJECT_LABEL_ASSIGN, ['label' => $label]);
$this->eventDispatcher->dispatchTyped(new CardUpdatedEvent($card));
return $card;
}
/**
* @param $cardId
* @param $labelId
* @throws StatusException
* @throws \OCA\Deck\NoPermissionException
* @throws \OCP\AppFramework\Db\DoesNotExistException
* @throws \OCP\AppFramework\Db\MultipleObjectsReturnedException
* @throws BadRequestException
*/
public function removeLabel($cardId, $labelId) {
public function removeLabel(int $cardId, int $labelId): Card {
$this->cardServiceValidator->check(compact('cardId', 'labelId'));
@@ -660,6 +626,7 @@ class CardService {
$this->activityManager->triggerEvent(ActivityManager::DECK_OBJECT_CARD, $card, ActivityManager::SUBJECT_LABEL_UNASSING, ['label' => $label]);
$this->eventDispatcher->dispatchTyped(new CardUpdatedEvent($card));
return $card;
}
public function getCardUrl(int $cardId): string {

View File

@@ -66,7 +66,7 @@ class CirclesService {
$circlesManager->startSession($federatedUser);
$circle = $circlesManager->getCircle($circleId);
$member = $circle->getInitiator();
$isUserInCircle = $member !== null && $member->getLevel() >= Member::LEVEL_MEMBER;
$isUserInCircle = $member->getLevel() >= Member::LEVEL_MEMBER;
if (!isset($this->userCircleCache[$circleId])) {
$this->userCircleCache[$circleId] = [];

View File

@@ -22,8 +22,6 @@ use OCP\IUserManager;
use OutOfBoundsException;
use Psr\Log\LoggerInterface;
use function is_numeric;
class CommentService {
public function __construct(
@@ -36,12 +34,9 @@ class CommentService {
) {
}
public function list(string $cardId, int $limit = 20, int $offset = 0): DataResponse {
if (!is_numeric($cardId)) {
throw new BadRequestException('A valid card id must be provided');
}
public function list(int $cardId, int $limit = 20, int $offset = 0): DataResponse {
$this->permissionService->checkPermission($this->cardMapper, $cardId, Acl::PERMISSION_READ);
$comments = $this->commentsManager->getForObject(Application::COMMENT_ENTITY_TYPE, $cardId, $limit, $offset);
$comments = $this->commentsManager->getForObject(Application::COMMENT_ENTITY_TYPE, (string)$cardId, $limit, $offset);
$result = [];
foreach ($comments as $comment) {
$formattedComment = $this->formatComment($comment);
@@ -96,13 +91,13 @@ class CommentService {
* @throws BadRequestException
* @throws NotFoundException|NoPermissionException
*/
public function create(int $cardId, string $message, string $replyTo = '0'): DataResponse {
public function create(int $cardId, string $message, int $replyTo = 0): DataResponse {
$this->permissionService->checkPermission($this->cardMapper, $cardId, Acl::PERMISSION_READ);
// Check if parent is a comment on the same card
if ($replyTo !== '0') {
if ($replyTo !== 0) {
try {
$comment = $this->commentsManager->get($replyTo);
$comment = $this->commentsManager->get((string)$replyTo);
if ($comment->getObjectType() !== Application::COMMENT_ENTITY_TYPE || (int)$comment->getObjectId() !== $cardId) {
throw new CommentNotFoundException();
}
@@ -115,7 +110,7 @@ class CommentService {
$comment = $this->commentsManager->create('users', $this->userId, Application::COMMENT_ENTITY_TYPE, (string)$cardId);
$comment->setMessage($message);
$comment->setVerb('comment');
$comment->setParentId($replyTo);
$comment->setParentId((string)$replyTo);
$this->commentsManager->save($comment);
return new DataResponse($this->formatComment($comment, true));
} catch (\InvalidArgumentException $e) {
@@ -128,14 +123,8 @@ class CommentService {
}
}
public function update(string $cardId, string $commentId, string $message): DataResponse {
if (!is_numeric($cardId)) {
throw new BadRequestException('A valid card id must be provided');
}
if (!is_numeric($commentId)) {
throw new BadRequestException('A valid comment id must be provided');
}
$comment = $this->get((int)$cardId, (int)$commentId);
public function update(int $cardId, int $commentId, string $message): DataResponse {
$comment = $this->get($cardId, $commentId);
if ($comment->getActorType() !== 'users' || $comment->getActorId() !== $this->userId) {
throw new NoPermissionException('Only authors are allowed to edit their comment.');
}
@@ -145,18 +134,12 @@ class CommentService {
return new DataResponse($this->formatComment($comment));
}
public function delete(string $cardId, string $commentId): DataResponse {
if (!is_numeric($cardId)) {
throw new BadRequestException('A valid card id must be provided');
}
if (!is_numeric($commentId)) {
throw new BadRequestException('A valid comment id must be provided');
}
public function delete(int $cardId, int $commentId): DataResponse {
$this->permissionService->checkPermission($this->cardMapper, $cardId, Acl::PERMISSION_READ);
try {
$comment = $this->commentsManager->get($commentId);
if ($comment->getObjectType() !== Application::COMMENT_ENTITY_TYPE || $comment->getObjectId() !== $cardId) {
$comment = $this->commentsManager->get((string)$commentId);
if ($comment->getObjectType() !== Application::COMMENT_ENTITY_TYPE || (int)$comment->getObjectId() !== $cardId) {
throw new CommentNotFoundException();
}
} catch (CommentNotFoundException $e) {
@@ -165,11 +148,11 @@ class CommentService {
if ($comment->getActorType() !== 'users' || $comment->getActorId() !== $this->userId) {
throw new NoPermissionException('Only authors are allowed to edit their comment.');
}
$this->commentsManager->delete($commentId);
$this->commentsManager->delete((string)$commentId);
return new DataResponse([]);
}
private function formatComment(IComment $comment, $addReplyTo = false): array {
private function formatComment(IComment $comment, bool $addReplyTo = false): array {
$actorDisplayName = $this->userManager->getDisplayName($comment->getActorId()) ?? $comment->getActorId();
$formattedComment = [

View File

@@ -48,7 +48,8 @@ class ConfigService {
}
public function getAll(): array {
if ($this->getUserId() === null) {
$userId = $this->getUserId();
if ($userId === null) {
return [];
}
@@ -57,7 +58,7 @@ class ConfigService {
'cardDetailsInModal' => $this->isCardDetailsInModal(),
'cardIdBadge' => $this->isCardIdBadgeEnabled()
];
if ($this->groupManager->isAdmin($this->getUserId())) {
if ($this->groupManager->isAdmin($userId)) {
$data['groupLimit'] = $this->get('groupLimit');
}
return $data;
@@ -95,44 +96,48 @@ class ConfigService {
}
public function isCalendarEnabled(?int $boardId = null): bool {
if ($this->getUserId() === null) {
$userId = $this->getUserId();
if ($userId === null) {
return false;
}
$appConfigState = $this->config->getAppValue(Application::APP_ID, 'calendar', 'yes') === 'yes';
$defaultState = (bool)$this->config->getUserValue($this->getUserId(), Application::APP_ID, 'calendar', $appConfigState);
$defaultState = (bool)$this->config->getUserValue($userId, Application::APP_ID, 'calendar', $appConfigState);
if ($boardId === null) {
return $defaultState;
}
return (bool)$this->config->getUserValue($this->getUserId(), Application::APP_ID, 'board:' . $boardId . ':calendar', $defaultState);
return (bool)$this->config->getUserValue($userId, Application::APP_ID, 'board:' . $boardId . ':calendar', $defaultState);
}
public function isCardDetailsInModal(?int $boardId = null): bool {
if ($this->getUserId() === null) {
$userId = $this->getUserId();
if ($userId === null) {
return false;
}
$defaultState = (bool)$this->config->getUserValue($this->getUserId(), Application::APP_ID, 'cardDetailsInModal', true);
$defaultState = (bool)$this->config->getUserValue($userId, Application::APP_ID, 'cardDetailsInModal', true);
if ($boardId === null) {
return $defaultState;
}
return (bool)$this->config->getUserValue($this->getUserId(), Application::APP_ID, 'board:' . $boardId . ':cardDetailsInModal', $defaultState);
return (bool)$this->config->getUserValue($userId, Application::APP_ID, 'board:' . $boardId . ':cardDetailsInModal', $defaultState);
}
public function isCardIdBadgeEnabled(): bool {
if ($this->getUserId() === null) {
$userId = $this->getUserId();
if ($userId === null) {
return false;
}
$appConfigState = $this->config->getAppValue(Application::APP_ID, 'cardIdBadge', 'yes') === 'no';
$defaultState = (bool)$this->config->getUserValue($this->getUserId(), Application::APP_ID, 'cardIdBadge', $appConfigState);
$defaultState = (bool)$this->config->getUserValue($userId, Application::APP_ID, 'cardIdBadge', $appConfigState);
return (bool)$this->config->getUserValue($this->getUserId(), Application::APP_ID, 'cardIdBadge', $defaultState);
return (bool)$this->config->getUserValue($userId, Application::APP_ID, 'cardIdBadge', $defaultState);
}
public function set($key, $value) {
if ($this->getUserId() === null) {
$userId = $this->getUserId();
if ($userId === null) {
throw new NoPermissionException('Must be logged in to set user config');
}
@@ -140,21 +145,21 @@ class ConfigService {
[$scope] = explode(':', $key, 2);
switch ($scope) {
case 'groupLimit':
if (!$this->groupManager->isAdmin($this->getUserId())) {
if (!$this->groupManager->isAdmin($userId)) {
throw new NoPermissionException('You must be admin to set the group limit');
}
$result = $this->setGroupLimit($value);
break;
case 'calendar':
$this->config->setUserValue($this->getUserId(), Application::APP_ID, 'calendar', (string)$value);
$this->config->setUserValue($userId, Application::APP_ID, 'calendar', (string)$value);
$result = $value;
break;
case 'cardDetailsInModal':
$this->config->setUserValue($this->getUserId(), Application::APP_ID, 'cardDetailsInModal', (string)$value);
$this->config->setUserValue($userId, Application::APP_ID, 'cardDetailsInModal', (string)$value);
$result = $value;
break;
case 'cardIdBadge':
$this->config->setUserValue($this->getUserId(), Application::APP_ID, 'cardIdBadge', (string)$value);
$this->config->setUserValue($userId, Application::APP_ID, 'cardIdBadge', (string)$value);
$result = $value;
break;
case 'board':
@@ -162,7 +167,7 @@ class ConfigService {
if ($boardConfigKey === 'notify-due' && !in_array($value, [self::SETTING_BOARD_NOTIFICATION_DUE_ALL, self::SETTING_BOARD_NOTIFICATION_DUE_ASSIGNED, self::SETTING_BOARD_NOTIFICATION_DUE_OFF], true)) {
throw new BadRequestException('Board notification option must be one of: off, assigned, all');
}
$this->config->setUserValue($this->getUserId(), Application::APP_ID, $key, (string)$value);
$this->config->setUserValue($userId, Application::APP_ID, $key, (string)$value);
$result = $value;
}
return $result;

View File

@@ -124,18 +124,15 @@ class FullTextSearchService {
/**
* @param int $cardId
*
* @return Board
* @throws DoesNotExistException
* @throws MultipleObjectsReturnedException
*/
public function getBoardFromCardId(int $cardId): Board {
$boardId = (int)$this->cardMapper->findBoardId($cardId);
/** @var Board $board */
$board = $this->boardMapper->find($boardId);
return $board;
$boardId = $this->cardMapper->findBoardId($cardId);
if ($boardId === null) {
throw new DoesNotExistException("Board '$cardId' does not exist");
}
return $this->boardMapper->find($boardId);
}
@@ -145,7 +142,7 @@ class FullTextSearchService {
* @return Card[]
*/
private function getCardsFromStack(int $stackId): array {
return $this->cardMapper->findAll($stackId, null, null);
return $this->cardMapper->findAll($stackId);
}
@@ -155,7 +152,7 @@ class FullTextSearchService {
* @return Stack[]
*/
private function getStacksFromBoard(int $boardId): array {
return $this->stackMapper->findAll($boardId, null, null);
return $this->stackMapper->findAll($boardId);
}
@@ -165,6 +162,6 @@ class FullTextSearchService {
* @return Board[]
*/
private function getBoardsFromUser(string $userId): array {
return $this->boardMapper->findAllByUser($userId, null, null, null);
return $this->boardMapper->findAllByUser($userId);
}
}

View File

@@ -19,29 +19,25 @@ use OCP\Comments\IComment;
abstract class ABoardImportService {
/** @var string */
public static $name = '';
/** @var BoardImportService */
private $boardImportService;
/** @var bool */
protected $needValidateData = true;
private BoardImportService $boardImportService;
protected bool $needValidateData = true;
/** @var Stack[] */
protected $stacks = [];
protected array $stacks = [];
/** @var Label[] */
protected $labels = [];
protected array $labels = [];
/** @var Card[] */
protected $cards = [];
protected array $cards = [];
/** @var Acl[] */
protected $acls = [];
protected array $acls = [];
/** @var IComment[][] */
protected $comments = [];
protected array $comments = [];
/** @var Assignment[] */
protected $assignments = [];
/** @var string[][] */
protected $labelCardAssignments = [];
protected array $assignments = [];
/** @var int[][] */
protected array $labelCardAssignments = [];
/**
* Configure import service
*
* @return void
*/
abstract public function bootstrap(): void;
@@ -68,10 +64,13 @@ abstract class ABoardImportService {
abstract public function getCardAssignments(): array;
/**
* @return array<int, array<int, int>>
*/
abstract public function getCardLabelAssignment(): array;
/**
* @return IComment[][]|array
* @return array<int, array<string, IComment>>
*/
abstract public function getComments(): array;
@@ -98,16 +97,16 @@ abstract class ABoardImportService {
$this->acls[$code] = $acl;
}
public function updateComment(string $cardId, string $commentId, IComment $comment): void {
public function updateComment(int $cardId, string $commentId, IComment $comment): void {
$this->comments[$cardId][$commentId] = $comment;
}
public function updateCardAssignment(string $cardId, string $assignmentId, Entity $assignment): void {
public function updateCardAssignment(int $cardId, int $assignmentId, Entity $assignment): void {
$this->assignments[$cardId][$assignmentId] = $assignment;
}
public function updateCardLabelsAssignment(string $cardId, string $assignmentId, string $assignment): void {
$this->labelCardAssignments[$cardId][$assignmentId] = $assignment;
public function updateCardLabelsAssignment(int $cardId, int $assignmentId, int $labelId): void {
$this->labelCardAssignments[$cardId][$assignmentId] = $labelId;
}
public function setImportService(BoardImportService $service): void {

View File

@@ -209,15 +209,16 @@ class BoardImportService {
public function importBoard(): void {
$board = $this->getImportSystem()->getBoard();
if ($board === null) {
throw new \LogicException('Import board not found');
}
if (!$this->userManager->userExists($board->getOwner())) {
throw new \Exception('Target owner ' . $board->getOwner() . ' not found. Please provide a mapping through the import config.');
}
if ($board) {
$this->boardMapper->insert($board);
$this->board = $board;
}
}
public function getBoard(bool $reset = false): Board {
if ($reset) {
@@ -292,12 +293,7 @@ class BoardImportService {
}
}
/**
* @param mixed $cardId
* @param mixed $labelId
* @return self
*/
public function assignCardToLabel($cardId, $labelId): self {
public function assignCardToLabel(int $cardId, int $labelId): self {
$this->cardMapper->assignLabel(
$cardId,
$labelId
@@ -307,14 +303,14 @@ class BoardImportService {
public function assignCardsToLabels(): void {
$data = $this->getImportSystem()->getCardLabelAssignment();
foreach ($data as $cardId => $assignemnt) {
foreach ($assignemnt as $assignmentId => $labelId) {
foreach ($data as $cardId => $assignment) {
foreach ($assignment as $assignmentId => $labelId) {
try {
$this->assignCardToLabel(
$cardId,
(int)$cardId,
$labelId
);
$this->getImportSystem()->updateCardLabelsAssignment($cardId, $assignmentId, $labelId);
$this->getImportSystem()->updateCardLabelsAssignment((int)$cardId, (int)$assignmentId, $labelId);
} catch (\Exception $e) {
$this->addError('Failed to assign label ' . $labelId . ' to ' . $cardId, $e);
}
@@ -326,20 +322,20 @@ class BoardImportService {
$allComments = $this->getImportSystem()->getComments();
foreach ($allComments as $cardId => $comments) {
foreach ($comments as $commentId => $comment) {
$this->insertComment($cardId, $comment);
$this->getImportSystem()->updateComment($cardId, $commentId, $comment);
$this->insertComment((int)$cardId, $comment);
$this->getImportSystem()->updateComment((int)$cardId, $commentId, $comment);
}
}
}
private function insertComment(string $cardId, IComment $comment): void {
$comment->setObject('deckCard', $cardId);
private function insertComment(int $cardId, IComment $comment): void {
$comment->setObject('deckCard', (string)$cardId);
$comment->setVerb('comment');
// Check if parent is a comment on the same card
if ($comment->getParentId() !== '0') {
try {
$parent = $this->commentsManager->get($comment->getParentId());
if ($parent->getObjectType() !== Application::COMMENT_ENTITY_TYPE || $parent->getObjectId() !== $cardId) {
if ($parent->getObjectType() !== Application::COMMENT_ENTITY_TYPE || (int)$parent->getObjectId() !== $cardId) {
throw new CommentNotFoundException();
}
} catch (CommentNotFoundException $e) {
@@ -362,7 +358,7 @@ class BoardImportService {
foreach ($assignments as $assignment) {
try {
$assignment = $this->assignmentMapper->insert($assignment);
$this->getImportSystem()->updateCardAssignment($cardId, (string)$assignment->getId(), $assignment);
$this->getImportSystem()->updateCardAssignment((int)$cardId, $assignment->getId(), $assignment);
$this->addOutput('Assignment ' . $assignment->getParticipant() . ' added');
} catch (NotFoundException $e) {
$this->addError('No origin or mapping found for card "' . $cardId . '" and ' . $assignment->getTypeString() . ' assignment "' . $assignment->getParticipant(), $e);

View File

@@ -16,6 +16,7 @@ use OCA\Deck\Db\Card;
use OCA\Deck\Db\Label;
use OCA\Deck\Db\Stack;
use OCA\Deck\Service\Importer\ABoardImportService;
use OCP\Comments\IComment;
use OCP\IUser;
use OCP\IUserManager;
@@ -118,6 +119,7 @@ class DeckJsonService extends ABoardImportService {
$comments[$this->cards[$sourceCard->id]->getId()][$commentOriginal->id] = $comment;
}
}
/** @var array<int, array<string, IComment>> */
return $comments;
}

View File

@@ -17,7 +17,7 @@ use Psr\Log\LoggerInterface;
class TrelloApiService extends TrelloJsonService {
/** @var string */
public static $name = 'Trello API';
protected $needValidateData = false;
protected bool $needValidateData = false;
/** @var IClient */
private $httpClient;
/** @var LoggerInterface */
@@ -176,7 +176,7 @@ class TrelloApiService extends TrelloJsonService {
if (empty($queryString['limit'])) {
return [];
}
if (count($data) < $queryString['limit']) {
if ((count($data) < $queryString['limit']) || (count($data) === 0)) {
return [];
}
$queryString['before'] = end($data)->id;

View File

@@ -159,6 +159,7 @@ class TrelloJsonService extends ABoardImportService {
$comments[$cardId][$commentId] = $comment;
}
}
/** @var array<int, array<string, IComment>> */
return $comments;
}

View File

@@ -43,33 +43,24 @@ class LabelService {
}
/**
* @param $labelId
* @return \OCP\AppFramework\Db\Entity
* @throws \OCA\Deck\NoPermissionException
* @throws \OCP\AppFramework\Db\DoesNotExistException
* @throws \OCP\AppFramework\Db\MultipleObjectsReturnedException
* @throws BadRequestException
*/
public function find($labelId) {
if (is_numeric($labelId) === false) {
throw new BadRequestException('label id must be a number');
}
public function find(int $labelId): Label {
$this->permissionService->checkPermission($this->labelMapper, $labelId, Acl::PERMISSION_READ);
return $this->labelMapper->find($labelId);
}
/**
* @param $title
* @param $color
* @param $boardId
* @return \OCP\AppFramework\Db\Entity
* @throws StatusException
* @throws \OCA\Deck\NoPermissionException
* @throws \OCP\AppFramework\Db\DoesNotExistException
* @throws \OCP\AppFramework\Db\MultipleObjectsReturnedException
* @throws BadRequestException
*/
public function create($title, $color, $boardId) {
public function create(string $title, string $color, int $boardId): Label {
$this->labelServiceValidator->check(compact('title', 'color', 'boardId'));
$this->permissionService->checkPermission(null, $boardId, Acl::PERMISSION_MANAGE);
@@ -106,15 +97,13 @@ class LabelService {
}
/**
* @param $id
* @return \OCP\AppFramework\Db\Entity
* @throws StatusException
* @throws \OCA\Deck\NoPermissionException
* @throws \OCP\AppFramework\Db\DoesNotExistException
* @throws \OCP\AppFramework\Db\MultipleObjectsReturnedException
* @throws BadRequestException
*/
public function delete($id) {
public function delete(int $id): Label {
$this->labelServiceValidator->check(compact('id'));
$this->permissionService->checkPermission($this->labelMapper, $id, Acl::PERMISSION_MANAGE);
@@ -127,17 +116,13 @@ class LabelService {
}
/**
* @param $id
* @param $title
* @param $color
* @return \OCP\AppFramework\Db\Entity
* @throws StatusException
* @throws \OCA\Deck\NoPermissionException
* @throws \OCP\AppFramework\Db\DoesNotExistException
* @throws \OCP\AppFramework\Db\MultipleObjectsReturnedException
* @throws BadRequestException
*/
public function update($id, $title, $color) {
public function update(int $id, string $title, string $color): Label {
$this->labelServiceValidator->check(compact('title', 'color', 'id'));
$this->permissionService->checkPermission($this->labelMapper, $id, Acl::PERMISSION_MANAGE);

View File

@@ -9,53 +9,28 @@ declare(strict_types=1);
namespace OCA\Deck\Service;
use OCA\Deck\Db\AssignmentMapper;
use OCA\Deck\Db\Board;
use OCA\Deck\Db\BoardMapper;
use OCA\Deck\Db\CardMapper;
use OCA\Deck\Db\LabelMapper;
use OCA\Deck\Model\CardDetails;
use OCP\Comments\ICommentsManager;
use OCP\IUserManager;
class OverviewService {
private CardService $cardService;
private BoardMapper $boardMapper;
private LabelMapper $labelMapper;
private CardMapper $cardMapper;
private AssignmentMapper $assignedUsersMapper;
private IUserManager $userManager;
private ICommentsManager $commentsManager;
private AttachmentService $attachmentService;
public function __construct(
CardService $cardService,
BoardMapper $boardMapper,
LabelMapper $labelMapper,
CardMapper $cardMapper,
AssignmentMapper $assignedUsersMapper,
IUserManager $userManager,
ICommentsManager $commentsManager,
AttachmentService $attachmentService,
private readonly CardService $cardService,
private readonly BoardMapper $boardMapper,
private readonly CardMapper $cardMapper,
) {
$this->cardService = $cardService;
$this->boardMapper = $boardMapper;
$this->labelMapper = $labelMapper;
$this->cardMapper = $cardMapper;
$this->assignedUsersMapper = $assignedUsersMapper;
$this->userManager = $userManager;
$this->commentsManager = $commentsManager;
$this->attachmentService = $attachmentService;
}
public function findUpcomingCards(string $userId): array {
$userBoards = $this->boardMapper->findAllForUser($userId);
$boardOwnerIds = array_filter(array_map(function (Board $board) {
return count($board->getAcl()) === 0 ? $board->getId() : null;
return count($board->getAcl() ?? []) === 0 ? $board->getId() : null;
}, $userBoards));
$boardSharedIds = array_filter(array_map(function (Board $board) {
return count($board->getAcl()) > 0 ? $board->getId() : null;
return count($board->getAcl() ?? []) > 0 ? $board->getId() : null;
}, $userBoards));
$foundCards = array_merge(

View File

@@ -29,6 +29,7 @@ class PermissionService {
private array $users = [];
private CappedMemoryCache $boardCache;
/** @var CappedMemoryCache<array<Acl::PERMISSION_*, bool>> */
private CappedMemoryCache $permissionCache;
public function __construct(
@@ -49,15 +50,16 @@ class PermissionService {
/**
* Get current user permissions for a board by id
*
* @return bool|array
* @return array<Acl::PERMISSION_*, bool>
*/
public function getPermissions(int $boardId, ?string $userId = null) {
public function getPermissions(int $boardId, ?string $userId = null): array {
if ($userId === null) {
$userId = $this->userId;
}
$cacheKey = $boardId . '-' . $userId;
if ($cached = $this->permissionCache->get($cacheKey)) {
/** @var array<Acl::PERMISSION_*, bool> $cached */
return $cached;
}

View File

@@ -75,35 +75,31 @@ class StackService {
$this->stackServiceValidator = $stackServiceValidator;
}
private function enrichStackWithCards($stack, $since = -1) {
$cards = $this->cardMapper->findAll($stack->getId(), null, null, $since);
/** @param Stack[] $stacks */
private function enrichStacksWithCards(array $stacks, $since = -1): void {
$cardsByStackId = $this->cardMapper->findAllForStacks(array_map(fn (Stack $stack) => $stack->getId(), $stacks), null, 0, $since);
if (\count($cards) === 0) {
return;
foreach ($cardsByStackId as $stackId => $cards) {
if (!$cards) {
continue;
}
$stack->setCards($this->cardService->enrichCards($cards));
}
private function enrichStacksWithCards($stacks, $since = -1) {
foreach ($stacks as $stack) {
$this->enrichStackWithCards($stack, $since);
if ($stack->getId() === $stackId) {
$stack->setCards($this->cardService->enrichCards($cards));
break;
}
}
}
}
/**
* @param $stackId
*
* @return \OCP\AppFramework\Db\Entity
* @throws \OCP\AppFramework\Db\DoesNotExistException
* @throws \OCP\AppFramework\Db\MultipleObjectsReturnedException
* @throws BadRequestException
*/
public function find($stackId) {
if (is_numeric($stackId) === false) {
throw new BadRequestException('stack id must be a number');
}
public function find(int $stackId): Stack {
$this->permissionService->checkPermission($this->stackMapper, $stackId, Acl::PERMISSION_READ);
$stack = $this->stackMapper->find($stackId);
@@ -124,17 +120,11 @@ class StackService {
}
/**
* @param $boardId
*
* @return array
* @return Stack[]
* @throws \OCA\Deck\NoPermissionException
* @throws BadRequestException
*/
public function findAll($boardId, $since = -1) {
if (is_numeric($boardId) === false) {
throw new BadRequestException('boardId must be a number');
}
public function findAll(int $boardId, int $since = -1): array {
$this->permissionService->checkPermission(null, $boardId, Acl::PERMISSION_READ);
$stacks = $this->stackMapper->findAll($boardId);
$this->enrichStacksWithCards($stacks, $since);
@@ -142,7 +132,11 @@ class StackService {
return $stacks;
}
public function findCalendarEntries($boardId) {
/**
* @return Stack[]
* @throws \OCP\DB\Exception
*/
public function findCalendarEntries(int $boardId): array {
try {
$this->permissionService->checkPermission(null, $boardId, Acl::PERMISSION_READ);
} catch (NoPermissionException $e) {
@@ -152,7 +146,11 @@ class StackService {
return $this->stackMapper->findAll($boardId);
}
public function fetchDeleted($boardId) {
/**
* @return Stack[]
* @throws \OCP\DB\Exception
*/
public function fetchDeleted(int $boardId): array {
$this->permissionService->checkPermission($this->boardMapper, $boardId, Acl::PERMISSION_READ);
$stacks = $this->stackMapper->findDeleted($boardId);
$this->enrichStacksWithCards($stacks);
@@ -161,17 +159,11 @@ class StackService {
}
/**
* @param $boardId
*
* @return array
* @return Stack[]
* @throws \OCA\Deck\NoPermissionException
* @throws BadRequestException
*/
public function findAllArchived($boardId) {
if (is_numeric($boardId) === false) {
throw new BadRequestException('board id must be a number');
}
public function findAllArchived(int $boardId): array {
$this->permissionService->checkPermission(null, $boardId, Acl::PERMISSION_READ);
$stacks = $this->stackMapper->findAll($boardId);
$labels = $this->labelMapper->getAssignedLabelsForBoard($boardId);
@@ -186,22 +178,18 @@ class StackService {
$stacks[$stackIndex]->setCards($cards);
}
/** @var Stack[] $stacks */
return $stacks;
}
/**
* @param $title
* @param $boardId
* @param integer $order
*
* @return \OCP\AppFramework\Db\Entity
* @throws StatusException
* @throws \OCA\Deck\NoPermissionException
* @throws \OCP\AppFramework\Db\DoesNotExistException
* @throws \OCP\AppFramework\Db\MultipleObjectsReturnedException
* @throws BadRequestException
*/
public function create($title, $boardId, $order) {
public function create(string $title, int $boardId, int $order): Stack {
$this->stackServiceValidator->check(compact('title', 'boardId', 'order'));
$this->permissionService->checkPermission(null, $boardId, Acl::PERMISSION_MANAGE);
@@ -223,19 +211,13 @@ class StackService {
}
/**
* @param $id
*
* @return \OCP\AppFramework\Db\Entity
* @return Stack The deleted stack.
* @throws \OCA\Deck\NoPermissionException
* @throws \OCP\AppFramework\Db\DoesNotExistException
* @throws \OCP\AppFramework\Db\MultipleObjectsReturnedException
* @throws BadRequestException
*/
public function delete($id) {
if (is_numeric($id) === false) {
throw new BadRequestException('stack id must be a number');
}
public function delete(int $id): Stack {
$this->permissionService->checkPermission($this->stackMapper, $id, Acl::PERMISSION_MANAGE);
$stack = $this->stackMapper->find($id);
@@ -247,26 +229,19 @@ class StackService {
);
$this->changeHelper->boardChanged($stack->getBoardId());
$this->eventDispatcher->dispatchTyped(new BoardUpdatedEvent($stack->getBoardId()));
$this->enrichStackWithCards($stack);
$this->enrichStacksWithCards([$stack]);
return $stack;
}
/**
* @param $id
* @param $title
* @param $boardId
* @param $order
* @param $deletedAt
*
* @return \OCP\AppFramework\Db\Entity
* @throws StatusException
* @throws \OCA\Deck\NoPermissionException
* @throws \OCP\AppFramework\Db\DoesNotExistException
* @throws \OCP\AppFramework\Db\MultipleObjectsReturnedException
* @throws BadRequestException
*/
public function update($id, $title, $boardId, $order, $deletedAt) {
public function update(int $id, string $title, int $boardId, int $order, ?int $deletedAt): Stack {
$this->stackServiceValidator->check(compact('id', 'title', 'boardId', 'order'));
$this->permissionService->checkPermission($this->stackMapper, $id, Acl::PERMISSION_MANAGE);
@@ -294,16 +269,13 @@ class StackService {
}
/**
* @param $id
* @param $order
*
* @return array
* @return array<int, Stack> The stacks in the correct order.
* @throws \OCA\Deck\NoPermissionException
* @throws \OCP\AppFramework\Db\DoesNotExistException
* @throws \OCP\AppFramework\Db\MultipleObjectsReturnedException
* @throws BadRequestException
*/
public function reorder($id, $order) {
public function reorder(int $id, int $order): array {
$this->stackServiceValidator->check(compact('id', 'order'));
$this->permissionService->checkPermission($this->stackMapper, $id, Acl::PERMISSION_MANAGE);
@@ -324,7 +296,7 @@ class StackService {
if ($stack->id !== $id) {
$stack->setOrder($i++);
}
$this->stackMapper->update($stack);
$stack = $this->stackMapper->update($stack);
$result[$stack->getOrder()] = $stack;
}
$this->changeHelper->boardChanged($stackToSort->getBoardId());

View File

@@ -13,7 +13,6 @@ namespace OCA\Deck\Sharing;
use OC\Files\Cache\Cache;
use OCA\Deck\Cache\AttachmentCacheHelper;
use OCA\Deck\Db\Acl;
use OCA\Deck\Db\Board;
use OCA\Deck\Db\BoardMapper;
use OCA\Deck\Db\CardMapper;
use OCA\Deck\Db\User;
@@ -99,9 +98,13 @@ class DeckShareProvider implements \OCP\Share\IShareProvider {
* @inheritDoc
*/
public function create(IShare $share) {
$cardId = $share->getSharedWith();
$cardId = (int)$share->getSharedWith();
$boardId = $this->cardMapper->findBoardId($cardId);
$valid = $boardId !== null;
if (!$valid) {
throw new GenericShareException('Card not found', $this->l->t('Card not found'), 404);
}
try {
$this->permissionService->checkPermission(null, $boardId, Acl::PERMISSION_EDIT);
} catch (NoPermissionException $e) {
@@ -147,7 +150,7 @@ class DeckShareProvider implements \OCP\Share\IShareProvider {
);
$data = $this->getRawShare($shareId);
$this->attachmentCacheHelper->clearAttachmentCount((int)$cardId);
$this->attachmentCacheHelper->clearAttachmentCount($cardId);
return $this->createShareObject($data);
}
@@ -213,7 +216,7 @@ class DeckShareProvider implements \OCP\Share\IShareProvider {
->from('share')
->where($qb->expr()->eq('id', $qb->createNamedParameter($id)));
$cursor = $qb->execute();
$cursor = $qb->executeQuery();
$data = $cursor->fetch();
$cursor->closeCursor();
@@ -297,7 +300,7 @@ class DeckShareProvider implements \OCP\Share\IShareProvider {
->set('item_source', $qb->createNamedParameter($share->getNode()->getId()))
->set('file_source', $qb->createNamedParameter($share->getNode()->getId()))
->set('expiration', $qb->createNamedParameter($share->getExpirationDate(), IQueryBuilder::PARAM_DATE))
->execute();
->executeStatement();
/*
* Update all user defined group shares
@@ -310,7 +313,7 @@ class DeckShareProvider implements \OCP\Share\IShareProvider {
->set('item_source', $qb->createNamedParameter($share->getNode()->getId()))
->set('file_source', $qb->createNamedParameter($share->getNode()->getId()))
->set('expiration', $qb->createNamedParameter($share->getExpirationDate(), IQueryBuilder::PARAM_DATE))
->execute();
->executeStatement();
/*
* Now update the permissions for all children that have not set it to 0
@@ -320,7 +323,7 @@ class DeckShareProvider implements \OCP\Share\IShareProvider {
->where($qb->expr()->eq('parent', $qb->createNamedParameter($share->getId())))
->andWhere($qb->expr()->neq('permissions', $qb->createNamedParameter(0)))
->set('permissions', $qb->createNamedParameter($share->getPermissions()))
->execute();
->executeStatement();
return $share;
}
@@ -335,7 +338,7 @@ class DeckShareProvider implements \OCP\Share\IShareProvider {
$qb->orWhere($qb->expr()->eq('parent', $qb->createNamedParameter($share->getId())));
$qb->execute();
$qb->executeStatement();
$this->attachmentCacheHelper->clearAttachmentCount((int)$share->getSharedWith());
}
@@ -355,7 +358,7 @@ class DeckShareProvider implements \OCP\Share\IShareProvider {
$qb->expr()->eq('item_type', $qb->createNamedParameter('file')),
$qb->expr()->eq('item_type', $qb->createNamedParameter('folder'))
))
->execute();
->executeQuery();
$data = $stmt->fetch();
$stmt->closeCursor();
@@ -376,14 +379,14 @@ class DeckShareProvider implements \OCP\Share\IShareProvider {
'file_target' => $qb->createNamedParameter($share->getTarget()),
'permissions' => $qb->createNamedParameter(0),
'stime' => $qb->createNamedParameter($share->getShareTime()->getTimestamp()),
])->execute();
])->executeStatement();
} elseif ($data['permissions'] !== 0) {
// Already a userroom share. Update it.
$qb = $this->dbConnection->getQueryBuilder();
$qb->update('share')
->set('permissions', $qb->createNamedParameter(0))
->where($qb->expr()->eq('id', $qb->createNamedParameter($data['id'])))
->execute();
->executeStatement();
}
}
@@ -397,7 +400,7 @@ class DeckShareProvider implements \OCP\Share\IShareProvider {
->where(
$qb->expr()->eq('id', $qb->createNamedParameter($share->getId()))
);
$cursor = $qb->execute();
$cursor = $qb->executeQuery();
$data = $cursor->fetch();
$cursor->closeCursor();
@@ -414,7 +417,7 @@ class DeckShareProvider implements \OCP\Share\IShareProvider {
$qb->expr()->eq('share_with', $qb->createNamedParameter($recipient))
);
$qb->execute();
$qb->executeStatement();
return $this->getShareById((int)$share->getId(), $recipient);
}
@@ -435,7 +438,7 @@ class DeckShareProvider implements \OCP\Share\IShareProvider {
$qb->expr()->eq('item_type', $qb->createNamedParameter('folder'))
))
->setMaxResults(1)
->execute();
->executeQuery();
$data = $stmt->fetch();
$stmt->closeCursor();
@@ -509,7 +512,7 @@ class DeckShareProvider implements \OCP\Share\IShareProvider {
$qb->orderBy('s.id');
$cursor = $qb->execute();
$cursor = $qb->executeQuery();
$shares = [];
while ($data = $cursor->fetch()) {
$shares[$data['fileid']][] = $this->createShareObject($data);
@@ -554,7 +557,7 @@ class DeckShareProvider implements \OCP\Share\IShareProvider {
$qb->setFirstResult($offset);
$qb->orderBy('id');
$cursor = $qb->execute();
$cursor = $qb->executeQuery();
$shares = [];
while ($data = $cursor->fetch()) {
$shares[] = $this->createShareObject($data);
@@ -582,7 +585,7 @@ class DeckShareProvider implements \OCP\Share\IShareProvider {
->where($qb->expr()->eq('s.id', $qb->createNamedParameter($id)))
->andWhere($qb->expr()->eq('s.share_type', $qb->createNamedParameter(IShare::TYPE_DECK)));
$cursor = $qb->execute();
$cursor = $qb->executeQuery();
$data = $cursor->fetch();
$cursor->closeCursor();
@@ -647,7 +650,7 @@ class DeckShareProvider implements \OCP\Share\IShareProvider {
$qb->expr()->eq('item_type', $qb->createNamedParameter('folder'))
));
$stmt = $query->execute();
$stmt = $query->executeQuery();
while ($data = $stmt->fetch()) {
$this->applyBoardPermission($shareMap[$data['parent']], (int)$data['permissions'], $userId);
@@ -677,7 +680,7 @@ class DeckShareProvider implements \OCP\Share\IShareProvider {
->from('share')
->andWhere($qb->expr()->eq('file_source', $qb->createNamedParameter($path->getId())))
->andWhere($qb->expr()->eq('share_type', $qb->createNamedParameter(IShare::TYPE_DECK)))
->execute();
->executeQuery();
$shares = [];
while ($data = $cursor->fetch()) {
@@ -749,7 +752,7 @@ class DeckShareProvider implements \OCP\Share\IShareProvider {
$qb->andWhere($qb->expr()->eq('dc.deleted_at', $qb->createNamedParameter(0, IQueryBuilder::PARAM_INT)));
$cursor = $qb->execute();
$cursor = $qb->executeQuery();
while ($data = $cursor->fetch()) {
if (!$this->isAccessibleResult($data)) {
continue;
@@ -842,6 +845,7 @@ class DeckShareProvider implements \OCP\Share\IShareProvider {
/**
* @inheritDoc
* @psalm-suppress InvalidReturnType Not returning anything
*/
public function getShareByToken($token) {
throw new ShareNotFound();
@@ -851,7 +855,7 @@ class DeckShareProvider implements \OCP\Share\IShareProvider {
->from('share')
->where($qb->expr()->eq('share_type', $qb->createNamedParameter(IShare::TYPE_ROOM)))
->andWhere($qb->expr()->eq('token', $qb->createNamedParameter($token)))
->execute();
->executeQuery();
$data = $cursor->fetch();
@@ -1015,7 +1019,7 @@ class DeckShareProvider implements \OCP\Share\IShareProvider {
->andWhere($qb->expr()->eq('share_type', $qb->createNamedParameter(IShare::TYPE_DECK)))
->orderBy('id');
$cursor = $qb->execute();
$cursor = $qb->executeQuery();
while ($data = $cursor->fetch()) {
$children[] = $this->createShareObject($data);
}
@@ -1038,7 +1042,7 @@ class DeckShareProvider implements \OCP\Share\IShareProvider {
)
);
$cursor = $qb->execute();
$cursor = $qb->executeQuery();
while ($data = $cursor->fetch()) {
$share = $this->createShareObject($data);
@@ -1055,7 +1059,7 @@ class DeckShareProvider implements \OCP\Share\IShareProvider {
->where($qb->expr()->eq('s.share_type', $qb->createNamedParameter(IShare::TYPE_DECK)))
->andWhere($qb->expr()->notIn('s.share_with', $qb->createNamedParameter($allCardIds, IQueryBuilder::PARAM_STR_ARRAY)));
$cursor = $qb->execute();
$cursor = $qb->executeQuery();
$shares = [];
while ($data = $cursor->fetch()) {
$shares[] = $this->createShareObject($data);

282
package-lock.json generated
View File

@@ -1,12 +1,12 @@
{
"name": "deck",
"version": "1.16.0",
"version": "2.0.0-dev.0",
"lockfileVersion": 3,
"requires": true,
"packages": {
"": {
"name": "deck",
"version": "1.16.0",
"version": "2.0.0-dev.0",
"license": "agpl",
"dependencies": {
"@babel/polyfill": "^7.12.1",
@@ -3636,9 +3636,9 @@
"license": "MIT"
},
"node_modules/@nextcloud/dialogs": {
"version": "6.3.1",
"resolved": "https://registry.npmjs.org/@nextcloud/dialogs/-/dialogs-6.3.1.tgz",
"integrity": "sha512-lklTssGdphRZKoR07pYU88btqguEKcQjEpKYom342i1eiMPiejgmoPZEignWJvJhpaN9CT5FoGndCrqqS3BswA==",
"version": "6.3.2",
"resolved": "https://registry.npmjs.org/@nextcloud/dialogs/-/dialogs-6.3.2.tgz",
"integrity": "sha512-ioZ483wmKdNX1HdSJ1EG7ewTSyQAqlmbBALkhT4guZdR9JG8VIdnijX15qwKgAWITG2y36PWoi9Rimb3dDf+7A==",
"license": "AGPL-3.0-or-later",
"dependencies": {
"@mdi/js": "^7.4.47",
@@ -3661,11 +3661,11 @@
"webdav": "^5.8.0"
},
"engines": {
"node": "^20.0.0",
"npm": "^10.0.0"
"node": "^20.0.0 || ^22.0.0 || ^24.0.0",
"npm": "^10.5.1"
},
"peerDependencies": {
"@nextcloud/vue": "^8.23.1",
"@nextcloud/vue": "^8.24.0",
"vue": "^2.7.16"
}
},
@@ -5402,10 +5402,11 @@
}
},
"node_modules/@typescript-eslint/typescript-estree/node_modules/brace-expansion": {
"version": "2.0.1",
"resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz",
"integrity": "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==",
"version": "2.0.2",
"resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.2.tgz",
"integrity": "sha512-Jt0vHyM+jmUBqojB7E1NIYadt0vI0Qxjxd2TErW94wDz+E2LAm5vKMXXwg6ZZBTHPuUlDgQHKXvjGBdfcF1ZDQ==",
"dev": true,
"license": "MIT",
"peer": true,
"dependencies": {
"balanced-match": "^1.0.0"
@@ -6440,13 +6441,13 @@
"dev": true
},
"node_modules/axios": {
"version": "1.8.2",
"resolved": "https://registry.npmjs.org/axios/-/axios-1.8.2.tgz",
"integrity": "sha512-ls4GYBm5aig9vWx8AWDSGLpnpDQRtWAfrjU+EuytuODrFBkqesN2RkOQCBzrA1RQNHw1SmRMSDDDSwzNAYQ6Rg==",
"version": "1.12.2",
"resolved": "https://registry.npmjs.org/axios/-/axios-1.12.2.tgz",
"integrity": "sha512-vMJzPewAlRyOgxV2dU0Cuz2O8zzzx9VYtbJOaBgXFeLc4IV/Eg50n4LowmehOOR61S8ZMpc2K5Sa7g6A4jfkUw==",
"license": "MIT",
"dependencies": {
"follow-redirects": "^1.15.6",
"form-data": "^4.0.0",
"form-data": "^4.0.4",
"proxy-from-env": "^1.1.0"
}
},
@@ -6853,16 +6854,6 @@
"npm": "1.2.8000 || >= 1.4.16"
}
},
"node_modules/body-parser/node_modules/bytes": {
"version": "3.1.2",
"resolved": "https://registry.npmjs.org/bytes/-/bytes-3.1.2.tgz",
"integrity": "sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg==",
"dev": true,
"peer": true,
"engines": {
"node": ">= 0.8"
}
},
"node_modules/body-parser/node_modules/debug": {
"version": "2.6.9",
"resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz",
@@ -6899,7 +6890,9 @@
"peer": true
},
"node_modules/brace-expansion": {
"version": "1.1.11",
"version": "1.1.12",
"resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.12.tgz",
"integrity": "sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg==",
"dev": true,
"license": "MIT",
"dependencies": {
@@ -7243,10 +7236,11 @@
"integrity": "sha512-ovBpjmsgd/teRmgcPh23d4gJvxDoXtAzEL9xTfMU8Yc2kqCDb7L9jAG0XHl1nzuGl+h3ebCIF1i62UFyA9V/2Q=="
},
"node_modules/bytes": {
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/bytes/-/bytes-3.0.0.tgz",
"integrity": "sha512-pMhOfFDPiv9t5jjIXkHosWmkSyQbvsgEVNkz0ERHbuLh2T/7j4Mqqpz523Fe8MVY89KC6Sh/QfS2sM+SjgFDcw==",
"version": "3.1.2",
"resolved": "https://registry.npmjs.org/bytes/-/bytes-3.1.2.tgz",
"integrity": "sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg==",
"dev": true,
"license": "MIT",
"peer": true,
"engines": {
"node": ">= 0.8"
@@ -7284,7 +7278,6 @@
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/call-bind-apply-helpers/-/call-bind-apply-helpers-1.0.2.tgz",
"integrity": "sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ==",
"dev": true,
"license": "MIT",
"dependencies": {
"es-errors": "^1.3.0",
@@ -7451,16 +7444,43 @@
"dev": true
},
"node_modules/cipher-base": {
"version": "1.0.4",
"resolved": "https://registry.npmjs.org/cipher-base/-/cipher-base-1.0.4.tgz",
"integrity": "sha512-Kkht5ye6ZGmwv40uUDZztayT2ThLQGfnj/T71N/XzeZeo3nf8foyW7zGTsPYkEya3m5f3cAypH+qe7YOrM1U2Q==",
"version": "1.0.7",
"resolved": "https://registry.npmjs.org/cipher-base/-/cipher-base-1.0.7.tgz",
"integrity": "sha512-Mz9QMT5fJe7bKI7MH31UilT5cEK5EHHRCccw/YRFsRY47AuNgaV6HY3rscp0/I4Q+tTW/5zoqpSeRRI54TkDWA==",
"dev": true,
"license": "MIT",
"peer": true,
"dependencies": {
"inherits": "^2.0.1",
"safe-buffer": "^5.0.1"
"inherits": "^2.0.4",
"safe-buffer": "^5.2.1",
"to-buffer": "^1.2.2"
},
"engines": {
"node": ">= 0.10"
}
},
"node_modules/cipher-base/node_modules/safe-buffer": {
"version": "5.2.1",
"resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz",
"integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==",
"dev": true,
"funding": [
{
"type": "github",
"url": "https://github.com/sponsors/feross"
},
{
"type": "patreon",
"url": "https://www.patreon.com/feross"
},
{
"type": "consulting",
"url": "https://feross.org/support"
}
],
"license": "MIT",
"peer": true
},
"node_modules/cjs-module-lexer": {
"version": "1.2.3",
"resolved": "https://registry.npmjs.org/cjs-module-lexer/-/cjs-module-lexer-1.2.3.tgz",
@@ -7735,18 +7755,19 @@
}
},
"node_modules/compression": {
"version": "1.7.4",
"resolved": "https://registry.npmjs.org/compression/-/compression-1.7.4.tgz",
"integrity": "sha512-jaSIDzP9pZVS4ZfQ+TzvtiWhdpFhE2RDHz8QJkpX9SIpLq88VueF5jJw6t+6CUQcAoA6t+x89MLrWAqpfDE8iQ==",
"version": "1.8.1",
"resolved": "https://registry.npmjs.org/compression/-/compression-1.8.1.tgz",
"integrity": "sha512-9mAqGPHLakhCLeNyxPkK4xVo746zQ/czLH1Ky+vkitMnWfWZps8r0qXuwhwizagCRttsL4lfG4pIOvaWLpAP0w==",
"dev": true,
"license": "MIT",
"peer": true,
"dependencies": {
"accepts": "~1.3.5",
"bytes": "3.0.0",
"compressible": "~2.0.16",
"bytes": "3.1.2",
"compressible": "~2.0.18",
"debug": "2.6.9",
"on-headers": "~1.0.2",
"safe-buffer": "5.1.2",
"negotiator": "~0.6.4",
"on-headers": "~1.1.0",
"safe-buffer": "5.2.1",
"vary": "~1.1.2"
},
"engines": {
@@ -7770,6 +7791,39 @@
"dev": true,
"peer": true
},
"node_modules/compression/node_modules/negotiator": {
"version": "0.6.4",
"resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.6.4.tgz",
"integrity": "sha512-myRT3DiWPHqho5PrJaIRyaMv2kgYf0mUVgBNOYMuCH5Ki1yEiQaf/ZJuQ62nvpc44wL5WDbTX7yGJi1Neevw8w==",
"dev": true,
"license": "MIT",
"peer": true,
"engines": {
"node": ">= 0.6"
}
},
"node_modules/compression/node_modules/safe-buffer": {
"version": "5.2.1",
"resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz",
"integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==",
"dev": true,
"funding": [
{
"type": "github",
"url": "https://github.com/sponsors/feross"
},
{
"type": "patreon",
"url": "https://www.patreon.com/feross"
},
{
"type": "consulting",
"url": "https://feross.org/support"
}
],
"license": "MIT",
"peer": true
},
"node_modules/concat-map": {
"version": "0.0.1",
"dev": true,
@@ -9015,7 +9069,6 @@
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/dunder-proto/-/dunder-proto-1.0.1.tgz",
"integrity": "sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A==",
"dev": true,
"license": "MIT",
"dependencies": {
"call-bind-apply-helpers": "^1.0.1",
@@ -9084,10 +9137,11 @@
}
},
"node_modules/editorconfig/node_modules/brace-expansion": {
"version": "2.0.1",
"resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz",
"integrity": "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==",
"version": "2.0.2",
"resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.2.tgz",
"integrity": "sha512-Jt0vHyM+jmUBqojB7E1NIYadt0vI0Qxjxd2TErW94wDz+E2LAm5vKMXXwg6ZZBTHPuUlDgQHKXvjGBdfcF1ZDQ==",
"dev": true,
"license": "MIT",
"dependencies": {
"balanced-match": "^1.0.0"
}
@@ -9363,7 +9417,6 @@
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/es-define-property/-/es-define-property-1.0.1.tgz",
"integrity": "sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g==",
"dev": true,
"license": "MIT",
"engines": {
"node": ">= 0.4"
@@ -9373,7 +9426,6 @@
"version": "1.3.0",
"resolved": "https://registry.npmjs.org/es-errors/-/es-errors-1.3.0.tgz",
"integrity": "sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==",
"dev": true,
"engines": {
"node": ">= 0.4"
}
@@ -9388,7 +9440,6 @@
"version": "1.1.1",
"resolved": "https://registry.npmjs.org/es-object-atoms/-/es-object-atoms-1.1.1.tgz",
"integrity": "sha512-FGgH2h8zKNim9ljj7dankFPcICIK9Cp5bm+c2gQSYePhpaG5+esrLODihIorn+Pe6FGJzWhXQotPv73jTaldXA==",
"dev": true,
"license": "MIT",
"dependencies": {
"es-errors": "^1.3.0"
@@ -9398,15 +9449,15 @@
}
},
"node_modules/es-set-tostringtag": {
"version": "2.0.1",
"resolved": "https://registry.npmjs.org/es-set-tostringtag/-/es-set-tostringtag-2.0.1.tgz",
"integrity": "sha512-g3OMbtlwY3QewlqAiMLI47KywjWZoEytKr8pf6iTC8uJq5bIAH52Z9pnQ8pVL6whrCto53JZDuUIsifGeLorTg==",
"dev": true,
"peer": true,
"version": "2.1.0",
"resolved": "https://registry.npmjs.org/es-set-tostringtag/-/es-set-tostringtag-2.1.0.tgz",
"integrity": "sha512-j6vWzfrGVfyXxge+O0x5sh6cvxAog0a/4Rdd2K36zCMV5eJ+/+tOAngRO8cODMNWbVRdVlmGZQL2YS3yR8bIUA==",
"license": "MIT",
"dependencies": {
"get-intrinsic": "^1.1.3",
"has": "^1.0.3",
"has-tostringtag": "^1.0.0"
"es-errors": "^1.3.0",
"get-intrinsic": "^1.2.6",
"has-tostringtag": "^1.0.2",
"hasown": "^2.0.2"
},
"engines": {
"node": ">= 0.4"
@@ -10926,12 +10977,15 @@
}
},
"node_modules/form-data": {
"version": "4.0.0",
"resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.0.tgz",
"integrity": "sha512-ETEklSGi5t0QMZuiXoA/Q6vcnxcLQP5vdugSpuAyi6SVGi2clPPp+xgEhuMaHC+zGgn31Kd235W35f7Hykkaww==",
"version": "4.0.4",
"resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.4.tgz",
"integrity": "sha512-KrGhL9Q4zjj0kiUt5OO4Mr/A/jlI2jDYs5eHBpYHPcBEVSiipAvn2Ko2HnPe20rmcuuvMHNdZFp+4IlGTMF0Ow==",
"license": "MIT",
"dependencies": {
"asynckit": "^0.4.0",
"combined-stream": "^1.0.8",
"es-set-tostringtag": "^2.1.0",
"hasown": "^2.0.2",
"mime-types": "^2.1.12"
},
"engines": {
@@ -11035,7 +11089,6 @@
"version": "1.1.2",
"resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz",
"integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==",
"dev": true,
"funding": {
"url": "https://github.com/sponsors/ljharb"
}
@@ -11089,7 +11142,6 @@
"version": "1.3.0",
"resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.3.0.tgz",
"integrity": "sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ==",
"dev": true,
"license": "MIT",
"dependencies": {
"call-bind-apply-helpers": "^1.0.2",
@@ -11123,7 +11175,6 @@
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/get-proto/-/get-proto-1.0.1.tgz",
"integrity": "sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g==",
"dev": true,
"license": "MIT",
"dependencies": {
"dunder-proto": "^1.0.1",
@@ -11346,7 +11397,6 @@
"version": "1.2.0",
"resolved": "https://registry.npmjs.org/gopd/-/gopd-1.2.0.tgz",
"integrity": "sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg==",
"dev": true,
"license": "MIT",
"engines": {
"node": ">= 0.4"
@@ -11432,7 +11482,6 @@
"version": "1.1.0",
"resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.1.0.tgz",
"integrity": "sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ==",
"dev": true,
"license": "MIT",
"engines": {
"node": ">= 0.4"
@@ -11445,9 +11494,7 @@
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/has-tostringtag/-/has-tostringtag-1.0.2.tgz",
"integrity": "sha512-NqADB8VjPFLM2V0VvHUewwwsw0ZWBaIdgo+ieHtK3hasLz4qeCRjYcqfB6AQrBggRKppKF8L52/VqdVsO47Dlw==",
"dev": true,
"license": "MIT",
"peer": true,
"dependencies": {
"has-symbols": "^1.0.3"
},
@@ -11492,7 +11539,6 @@
"version": "2.0.2",
"resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.2.tgz",
"integrity": "sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==",
"dev": true,
"dependencies": {
"function-bind": "^1.1.2"
},
@@ -14387,10 +14433,11 @@
}
},
"node_modules/js-beautify/node_modules/brace-expansion": {
"version": "2.0.1",
"resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz",
"integrity": "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==",
"version": "2.0.2",
"resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.2.tgz",
"integrity": "sha512-Jt0vHyM+jmUBqojB7E1NIYadt0vI0Qxjxd2TErW94wDz+E2LAm5vKMXXwg6ZZBTHPuUlDgQHKXvjGBdfcF1ZDQ==",
"dev": true,
"license": "MIT",
"dependencies": {
"balanced-match": "^1.0.0"
}
@@ -15061,7 +15108,6 @@
"version": "1.1.0",
"resolved": "https://registry.npmjs.org/math-intrinsics/-/math-intrinsics-1.1.0.tgz",
"integrity": "sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g==",
"dev": true,
"license": "MIT",
"engines": {
"node": ">= 0.4"
@@ -16335,10 +16381,11 @@
}
},
"node_modules/on-headers": {
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/on-headers/-/on-headers-1.0.2.tgz",
"integrity": "sha512-pZAE+FJLoyITytdqK0U5s+FIpjN0JP3OzFi/u8Rx+EV5/W+JTWGXG8xFzevE7AjBfDqHv/8vL8qQsIhHnqRkrA==",
"version": "1.1.0",
"resolved": "https://registry.npmjs.org/on-headers/-/on-headers-1.1.0.tgz",
"integrity": "sha512-737ZY3yNnXy37FHkQxPzt4UZ2UWPWiCZWLvFZ4fu5cueciegX0zGPnrlY6bwRg4FdQOe9YU8MkmJwGhoMybl8A==",
"dev": true,
"license": "MIT",
"peer": true,
"engines": {
"node": ">= 0.8"
@@ -17423,16 +17470,6 @@
"node": ">= 0.8"
}
},
"node_modules/raw-body/node_modules/bytes": {
"version": "3.1.2",
"resolved": "https://registry.npmjs.org/bytes/-/bytes-3.1.2.tgz",
"integrity": "sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg==",
"dev": true,
"peer": true,
"engines": {
"node": ">= 0.8"
}
},
"node_modules/react-is": {
"version": "18.2.0",
"resolved": "https://registry.npmjs.org/react-is/-/react-is-18.2.0.tgz",
@@ -18534,19 +18571,49 @@
"peer": true
},
"node_modules/sha.js": {
"version": "2.4.11",
"resolved": "https://registry.npmjs.org/sha.js/-/sha.js-2.4.11.tgz",
"integrity": "sha512-QMEp5B7cftE7APOjk5Y6xgrbWu+WkLVQwk8JNjZ8nKRciZaByEW6MubieAiToS7+dwvrjGhH8jRXz3MVd0AYqQ==",
"version": "2.4.12",
"resolved": "https://registry.npmjs.org/sha.js/-/sha.js-2.4.12.tgz",
"integrity": "sha512-8LzC5+bvI45BjpfXU8V5fdU2mfeKiQe1D1gIMn7XUlF3OTUrpdJpPPH4EMAnF0DsHHdSZqCdSss5qCmJKuiO3w==",
"dev": true,
"license": "(MIT AND BSD-3-Clause)",
"peer": true,
"dependencies": {
"inherits": "^2.0.1",
"safe-buffer": "^5.0.1"
"inherits": "^2.0.4",
"safe-buffer": "^5.2.1",
"to-buffer": "^1.2.0"
},
"bin": {
"sha.js": "bin.js"
},
"engines": {
"node": ">= 0.10"
},
"funding": {
"url": "https://github.com/sponsors/ljharb"
}
},
"node_modules/sha.js/node_modules/safe-buffer": {
"version": "5.2.1",
"resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz",
"integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==",
"dev": true,
"funding": [
{
"type": "github",
"url": "https://github.com/sponsors/feross"
},
{
"type": "patreon",
"url": "https://www.patreon.com/feross"
},
{
"type": "consulting",
"url": "https://feross.org/support"
}
],
"license": "MIT",
"peer": true
},
"node_modules/shallow-clone": {
"version": "3.0.1",
"resolved": "https://registry.npmjs.org/shallow-clone/-/shallow-clone-3.0.1.tgz",
@@ -19660,11 +19727,10 @@
}
},
"node_modules/tar-fs": {
"version": "2.1.3",
"resolved": "https://registry.npmjs.org/tar-fs/-/tar-fs-2.1.3.tgz",
"integrity": "sha512-090nwYJDmlhwFwEW3QQl+vaNnxsO2yVsd45eTKRBzSzu+hlb1w2K9inVq5b0ngXuLVqQ4ApvsUHHnu/zQNkWAg==",
"version": "2.1.4",
"resolved": "https://registry.npmjs.org/tar-fs/-/tar-fs-2.1.4.tgz",
"integrity": "sha512-mDAjwmZdh7LTT6pNleZ05Yt65HC3E+NiQzl672vQG38jIrehtJk/J3mNwIg+vShQPcLF/LV7CMnDW6vjj6sfYQ==",
"dev": true,
"license": "MIT",
"dependencies": {
"chownr": "^1.1.1",
"mkdirp-classic": "^0.5.2",
@@ -19886,10 +19952,11 @@
"dev": true
},
"node_modules/tmp": {
"version": "0.2.3",
"resolved": "https://registry.npmjs.org/tmp/-/tmp-0.2.3.tgz",
"integrity": "sha512-nZD7m9iCPC5g0pYmcaxogYKggSfLsdxl8of3Q/oIbqCqLLIO9IAF0GWjX1z9NZRHPiXv8Wex4yDCaZsgEw0Y8w==",
"version": "0.2.5",
"resolved": "https://registry.npmjs.org/tmp/-/tmp-0.2.5.tgz",
"integrity": "sha512-voyz6MApa1rQGUxT3E+BK7/ROe8itEx7vD8/HEvt4xwXucvQ5G5oeEiHkmHZJuBO21RpOf+YYm9MOivj709jow==",
"dev": true,
"license": "MIT",
"engines": {
"node": ">=14.14"
}
@@ -19901,9 +19968,9 @@
"dev": true
},
"node_modules/to-buffer": {
"version": "1.2.1",
"resolved": "https://registry.npmjs.org/to-buffer/-/to-buffer-1.2.1.tgz",
"integrity": "sha512-tB82LpAIWjhLYbqjx3X4zEeHN6M8CiuOEy2JY8SEQVdYRe3CCHOFaqrBW1doLDrfpWhplcW7BL+bO3/6S3pcDQ==",
"version": "1.2.2",
"resolved": "https://registry.npmjs.org/to-buffer/-/to-buffer-1.2.2.tgz",
"integrity": "sha512-db0E3UJjcFhpDhAF4tLo03oli3pwl3dbnzXOUIlRKrp+ldk/VUxzpWYZENsw2SZiuBjHAk7DfB0VU7NKdpb6sw==",
"dev": true,
"license": "MIT",
"peer": true,
@@ -20843,9 +20910,9 @@
}
},
"node_modules/vue-at": {
"version": "2.5.1",
"resolved": "https://registry.npmjs.org/vue-at/-/vue-at-2.5.1.tgz",
"integrity": "sha512-c17Rn8vVTXp/rvWV76jNB2PVx534eqIAP5/Tv1twCt0ebtRPIgNaJJwSaoRVISQ6vM6m3+owmtpAG1qr5g9fyQ==",
"version": "2.5.2",
"resolved": "https://registry.npmjs.org/vue-at/-/vue-at-2.5.2.tgz",
"integrity": "sha512-06FFK8V/VIS3appZf/l65H9ijmoVDrL1GHm/O/zRpHADGUulmOLa7OP3O3u6ab49n4HNJySQqYedPGqJz8EpQw==",
"engines": {
"node": ">= 14.x"
},
@@ -21174,9 +21241,10 @@
}
},
"node_modules/webdav/node_modules/brace-expansion": {
"version": "2.0.1",
"resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz",
"integrity": "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==",
"version": "2.0.2",
"resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.2.tgz",
"integrity": "sha512-Jt0vHyM+jmUBqojB7E1NIYadt0vI0Qxjxd2TErW94wDz+E2LAm5vKMXXwg6ZZBTHPuUlDgQHKXvjGBdfcF1ZDQ==",
"license": "MIT",
"dependencies": {
"balanced-match": "^1.0.0"
}

View File

@@ -1,7 +1,7 @@
{
"name": "deck",
"description": "",
"version": "1.16.0",
"version": "2.0.0-dev.0",
"authors": [
{
"name": "Julius Härtl",
@@ -70,8 +70,8 @@
"extends @nextcloud/browserslist-config"
],
"engines": {
"node": "^20.0.0",
"npm": "^10.0.0"
"node": "^22.0.0",
"npm": "^10.5.0"
},
"devDependencies": {
"@nextcloud/babel-config": "^1.2.0",

View File

@@ -6,7 +6,9 @@
xmlns="https://getpsalm.org/schema/config"
xsi:schemaLocation="https://getpsalm.org/schema/config"
errorBaseline="tests/psalm-baseline.xml"
phpVersion="8.1"
findUnusedBaselineEntry="true"
findUnusedCode="false"
phpVersion="8.2"
>
<stubs>
<file name="tests/stub.phpstub" preloadClasses="true"/>
@@ -15,6 +17,7 @@
<directory name="lib" />
<ignoreFiles>
<directory name="vendor" />
<directory name="vendor-bin" />
</ignoreFiles>
</projectFiles>
<extraFiles>
@@ -26,13 +29,6 @@
<referencedMethod name="/Db\\.*::.*/" />
</errorLevel>
</UndefinedMagicMethod>
<UndefinedInterfaceMethod>
<errorLevel type="suppress">
<!-- FIXME Deprecated event handling -->
<referencedMethod name="OCP\IUserManager::listen" />
<referencedMethod name="OCP\IGroupManager::listen" />
</errorLevel>
</UndefinedInterfaceMethod>
<UndefinedClass>
<errorLevel type="suppress">
<referencedClass name="OC\*" />

View File

@@ -276,18 +276,15 @@ export default {
position: relative;
width: 100%;
height: 100%;
max-height: calc(100vh - 50px);
display: flex;
flex-direction: column;
}
.board {
padding-left: $board-spacing;
position: relative;
max-height: calc(100% - var(--default-clickable-area));
overflow: hidden;
overflow-x: auto;
flex-grow: 1;
scrollbar-gutter: stable;
}
/**
@@ -297,11 +294,15 @@ export default {
.smooth-dnd-container.horizontal {
display: flex;
align-items: stretch;
gap: $board-gap;
padding: 0 $board-gap;
height: 100%;
&:deep(.stack-draggable-wrapper.smooth-dnd-draggable-wrapper) {
display: flex;
height: auto;
flex: 0 1 $card-max-width;
min-width: $card-min-width;
.stack {
display: flex;
@@ -309,16 +310,13 @@ export default {
position: relative;
.smooth-dnd-container.vertical {
flex-grow: 1;
$margin-x: calc($stack-gap * -1);
display: flex;
flex-direction: column;
// Margin left instead of padidng to avoid jumps on dropping a card
margin-left: $stack-spacing;
padding-right: $stack-spacing;
overflow-x: hidden;
gap: $stack-gap;
padding: $stack-gap;
margin: 0 $margin-x;
overflow-y: auto;
padding-top: 15px;
margin-top: -10px;
scrollbar-gutter: stable;
}

View File

@@ -14,10 +14,10 @@
{{ stack.title }}
</h3>
<h3 v-else-if="!editing"
title="stack.title"
dir="auto"
tabindex="0"
:aria-label="stack.title"
:title="stack.title"
class="stack__title"
@click="startEditing(stack)"
@keydown.enter="startEditing(stack)">
@@ -108,7 +108,7 @@
</Container>
<transition name="slide-bottom" appear>
<div v-show="showAddCard" class="stack__card-add">
<div v-if="showAddCard" class="stack__card-add">
<form :class="{ 'icon-loading-small': stateCardCreating }"
@submit.prevent.stop="clickAddCard()">
<label for="new-stack-input-main" class="hidden-visually">{{ t('deck', 'Add a new card') }}</label>
@@ -365,37 +365,31 @@ export default {
@import './../../css/variables';
.stack {
width: $stack-width + $stack-spacing * 3;
width: 100%;
}
.stack__header {
display: flex;
position: sticky;
top: 0;
height: var(--default-clickable-area);
z-index: 100;
padding-left: $card-spacing;
padding-right: $card-spacing;
margin: 6px;
margin-top: 0;
cursor: grab;
background-color: var(--color-main-background);
// Smooth fade out of the cards at the top
&:before {
content: ' ';
content: '';
display: block;
position: absolute;
width: calc(100% - 16px);
height: 20px;
top: 30px;
left: 0px;
width: 100%;
height: $stack-gap;
bottom: 0;
z-index: 99;
transition: top var(--animation-slow);
background-image: linear-gradient(180deg, var(--color-main-background) 3px, rgba(255, 255, 255, 0) 100%);
body.theme--dark & {
background-image: linear-gradient(180deg, var(--color-main-background) 3px, rgba(0, 0, 0, 0) 100%);
}
background-image: linear-gradient(180deg, var(--color-main-background) 0%, transparent 100%);
transform: translateY(100%);
}
& > * {
@@ -404,8 +398,10 @@ export default {
}
h3, form {
flex-grow: 1;
flex: 1 1 auto;
min-width: 0;
display: flex;
align-items: center;
cursor: inherit;
margin: 0;
@@ -418,9 +414,8 @@ export default {
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
max-width: calc($stack-width - 60px);
border-radius: 3px;
padding: 4px 4px;
padding: $card-padding;
font-size: var(--default-font-size);
&:focus-visible {
@@ -430,7 +425,6 @@ export default {
}
form {
margin: -4px;
input {
font-weight: bold;
padding: 0 6px;
@@ -453,14 +447,25 @@ export default {
flex-shrink: 0;
z-index: 100;
display: flex;
margin-bottom: 5px;
padding-top: var(--default-grid-baseline);
padding-bottom: $stack-gap;
background-color: var(--color-main-background);
position: relative;
// Smooth fade out of the cards at the top
&:before {
content: '';
display: block;
position: absolute;
width: 100%;
height: $stack-gap;
z-index: 99;
transition: bottom var(--animation-slow);
background-image: linear-gradient(0deg, var(--color-main-background) 0%, transparent 100%);
transform: translateY(-100%);
}
form {
display: flex;
margin-left: $stack-spacing;
margin-right: $stack-spacing;
width: 100%;
border: 2px solid var(--color-border-maxcontrast);
border-radius: var(--border-radius-large);
@@ -481,7 +486,6 @@ export default {
input {
border: none;
margin: 0;
padding: 4px;
}
}

View File

@@ -101,6 +101,8 @@ export default {
</script>
<style lang="scss" scoped>
@import './../../css/variables';
.badges {
display: flex;
width: 100%;
@@ -111,6 +113,7 @@ export default {
.icon-badge {
color: var(--color-text-maxcontrast);
display: flex;
align-items: center;
margin-right: 2px;
span,
@@ -125,6 +128,7 @@ export default {
flex-direction: row;
flex-wrap: wrap;
gap: 3px;
height: var(--default-clickable-area);
}
.badge-left, .badge-right {

View File

@@ -4,13 +4,10 @@
-->
<template>
<div v-if="referencePreview" class="card-cover">
<div class="image-wrapper rounded-left rounded-right" :style="{ backgroundImage: `url(${referencePreview})`}" />
</div>
<div v-else-if="cardId && ( attachments.length > 0 )" class="card-cover">
<div v-for="(attachment, index) in attachments"
<div v-if="cardId && ( attachments.length > 0 )" class="card-cover">
<div v-for="attachment in attachments"
:key="attachment.id"
:class="['image-wrapper', { 'rounded-left': index === 0 }, { 'rounded-right': index === attachments.length - 1 }]"
class="image-wrapper"
:style="{ backgroundImage: `url(${attachmentPreview(attachment)})` }" />
</div>
</template>
@@ -77,9 +74,7 @@ export default {
.card-cover {
height: 90px;
display: flex;
margin-top: -4px;
margin-left: -4px;
margin-right: -4px;
margin: $card-image-margin $card-image-margin 0;
.image-wrapper {
flex: 1;
@@ -87,12 +82,6 @@ export default {
background-size: cover;
background-repeat: no-repeat;
background-position: center center;
&.rounded-left {
border-top-left-radius: calc(var(--border-radius-large) - 1px);
}
&.rounded-right {
border-top-right-radius: calc(var(--border-radius-large) - 1px);
}
}
}
</style>

View File

@@ -6,7 +6,7 @@
<template>
<AttachmentDragAndDrop v-if="card" :card-id="card.id" class="drop-upload--card">
<div :ref="`card${card.id}`"
:class="{'compact': compactMode, 'current-card': currentCard, 'has-labels': card.labels && card.labels.length > 0, 'card__editable': canEdit, 'card__archived': card.archived, 'card__highlight': highlight}"
:class="{'compact': compactMode, 'current-card': currentCard, 'no-labels': !hasLabels, 'card__editable': canEdit, 'card__archived': card.archived, 'card__highlight': highlight}"
tag="div"
:tabindex="0"
class="card"
@@ -331,12 +331,14 @@ export default {
border-radius: var(--border-radius-large);
font-size: 100%;
background-color: var(--color-main-background);
margin-bottom: $card-spacing;
padding: var(--default-grid-baseline) $card-padding;
padding: $card-padding;
border: 2px solid var(--color-border-dark);
width: 100%;
display: flex;
flex-direction: column;
gap: $card-gap;
overflow: hidden;
cursor: pointer;
&:deep(*:not(.dragDisabled)) {
cursor: pointer;
@@ -359,12 +361,10 @@ export default {
h4 {
font-weight: normal;
margin: 0;
padding: var(--default-grid-baseline);
flex-grow: 1;
font-size: 100%;
overflow: hidden;
word-wrap: break-word;
padding-left: 4px;
align-self: center;
:deep(a) {
@@ -374,9 +374,6 @@ export default {
&.editable {
span {
cursor: text;
padding-right: 8px;
padding-top: 3px;
padding-bottom: 3px;
&:focus, &:focus-visible {
outline: none;
@@ -385,6 +382,7 @@ export default {
&:focus-within {
outline: 2px solid var(--color-border-dark);
outline-offset: 4px;
border-radius: 3px;
}
}
@@ -427,8 +425,6 @@ export default {
.card-labels {
display: flex;
align-items: end;
padding-left: var(--default-grid-baseline);
padding-top: var(--default-grid-baseline);
.labels {
flex-wrap: wrap;
@@ -444,7 +440,7 @@ export default {
.card-related {
display: flex;
padding: 12px;
padding: 4px;
padding-bottom: 0px;
color: var(--color-text-maxcontrast);
@@ -469,8 +465,8 @@ export default {
height: 32px;
width: 32px;
}
&.has-labels {
padding-bottom: $card-padding;
&.no-labels {
padding-bottom: var(--default-grid-baseline);
}
.labels {
height: 6px;

View File

@@ -14,45 +14,25 @@
</div>
<div v-else-if="isValidFilter" class="overview">
<div class="dashboard-column">
<h3>{{ t('deck', 'Overdue') }}</h3>
<div v-for="card in sortCards('overdue')" :key="card.id">
<CardItem :id="card.id" />
<div v-for="columnProps in columnPropsList" :key="columnProps.title" class="dashboard-column">
<div class="dashboard-column__header">
<h3 class="dashboard-column__header-title"
:title="columnProps.title"
:aria-label="columnProps.title">
{{ t('deck', columnProps.title) }}
</h3>
</div>
</div>
<div class="dashboard-column">
<h3>{{ t('deck', 'Today') }}</h3>
<div v-for="card in sortCards('today')" :key="card.id">
<CardItem :id="card.id" />
</div>
</div>
<div class="dashboard-column">
<h3>{{ t('deck', 'Tomorrow') }}</h3>
<div v-for="card in sortCards('tomorrow')" :key="card.id">
<CardItem :id="card.id" />
</div>
</div>
<div class="dashboard-column">
<h3>{{ t('deck', 'Next 7 days') }}</h3>
<div v-for="card in sortCards('nextSevenDays')" :key="card.id">
<CardItem :id="card.id" />
</div>
</div>
<div class="dashboard-column">
<h3>{{ t('deck', 'Later') }}</h3>
<div v-for="card in sortCards('later')" :key="card.id">
<CardItem :id="card.id" />
</div>
</div>
<div class="dashboard-column">
<h3>{{ t('deck', 'No due') }}</h3>
<div v-for="card in assignedCardsDashboard.nodue" :key="card.id">
<CardItem :id="card.id" />
<div class="dashboard-column__list">
<template v-if="columnProps.sort === false">
<CardItem v-for="card in filterCards(columnProps.filter)"
:id="card.id"
:key="card.id" />
</template>
<template v-else>
<CardItem v-for="card in sortCards(filterCards(columnProps.filter))"
:id="card.id"
:key="card.id" />
</template>
</div>
</div>
</div>
@@ -73,6 +53,34 @@ const SUPPORTED_FILTERS = [
FILTER_UPCOMING,
]
const COLUMN_PROPS_LIST = [
{
title: 'Overdue',
filter: 'overdue',
},
{
title: 'Today',
filter: 'today',
},
{
title: 'Tomorrow',
filter: 'tomorrow',
},
{
title: 'Next 7 days',
filter: 'nextSevenDays',
},
{
title: 'Later',
filter: 'later',
},
{
title: 'No due',
filter: 'nodue',
sort: false,
},
]
export default {
name: 'Overview',
components: {
@@ -89,6 +97,7 @@ export default {
data() {
return {
loading: true,
columnPropsList: COLUMN_PROPS_LIST,
}
},
computed: {
@@ -125,16 +134,16 @@ export default {
}
this.loading = false
},
sortCards(when) {
const cards = this.assignedCardsDashboard[when]
filterCards(when) {
return this.assignedCardsDashboard[when]
},
sortCards(cards) {
if (!cards) {
return null
} else {
return cards.toSorted((current, next) => {
const currentDueDate = new Date(current.duedate)
const nextDueDate = new Date(next.duedate)
return currentDueDate - nextDueDate
})
}
@@ -151,38 +160,75 @@ export default {
position: relative;
width: 100%;
height: 100%;
max-height: calc(100vh - 50px);
display: flex;
flex-direction: column;
}
.overview {
position: relative;
height: calc(100% - var(--default-clickable-area));
overflow-x: scroll;
overflow-x: auto;
flex-grow: 1;
scrollbar-gutter: stable;
display: flex;
align-items: stretch;
padding-left: $board-spacing;
padding-right: $board-spacing;
gap: $board-gap;
padding: 0 $board-gap;
.dashboard-column {
display: flex;
flex-direction: column;
min-width: $stack-width;
width: $stack-width;
margin-left: $stack-spacing;
margin-right: $stack-spacing;
flex: 0 1 $card-max-width;
min-width: $card-min-width;
h3 {
font-size: var(--default-font-size);
margin: -6px;
margin-bottom: 12px;
padding: 6px 13px;
.dashboard-column__header {
display: flex;
position: sticky;
top: 0;
height: var(--default-clickable-area);
z-index: 100;
margin-top: 0;
background-color: var(--color-main-background);
border: 1px solid var(--color-main-background);
// Smooth fade out of the cards at the top
&:before {
content: '';
display: block;
position: absolute;
width: 100%;
height: 20px;
top: 30px;
left: 0px;
z-index: 99;
transition: top var(--animation-slow);
background-image: linear-gradient(180deg, var(--color-main-background) 3px, rgba(255, 255, 255, 0) 100%);
body.theme--dark & {
background-image: linear-gradient(180deg, var(--color-main-background) 3px, rgba(0, 0, 0, 0) 100%);
}
}
}
.dashboard-column__header-title {
display: flex;
align-items: center;
height: var(--default-clickable-area);
margin: 0;
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
padding: $card-padding;
font-size: var(--default-font-size);
}
.dashboard-column__list {
$margin-x: calc($stack-gap * -1);
display: flex;
flex-direction: column;
gap: $stack-gap;
padding: $stack-gap;
margin: 0 $margin-x;
overflow-y: auto;
scrollbar-gutter: stable;
}
}
}

View File

@@ -4,16 +4,19 @@
-->
<template>
<div v-if="searchQuery!==''" class="global-search">
<section v-if="searchQuery!==''" class="global-search">
<header class="search-header">
<h2>
<NcRichText :text="t('deck', 'Search for {searchQuery} in all boards')" :arguments="queryStringArgs" />
<div v-if="loading" class="icon-loading-small" />
<NcRichText :text="$route.params.id ? t('deck', 'Search for {searchQuery} in other boards') : t('deck', 'Search for {searchQuery} in all boards')"
:arguments="queryStringArgs" />
<span v-if="loading" class="icon-loading-small" />
</h2>
<NcActions>
<NcActionButton icon="icon-close" @click="$store.commit('setSearchQuery', '')" />
</NcActions>
</header>
<div class="search-wrapper">
<div v-if="loading || filteredResults.length > 0" class="search-results">
<template v-if="loading || filteredResults.length > 0">
<CardItem v-for="card in filteredResults"
:id="card.id"
:key="card.id"
@@ -26,12 +29,12 @@
{{ t('deck', 'No results found') }}
</div>
</InfiniteLoading>
</div>
<div v-else>
</template>
<template v-else>
<p>{{ t('deck', 'No results found') }}</p>
</template>
</div>
</div>
</div>
</section>
</template>
<script>
@@ -159,7 +162,7 @@ export default {
.global-search {
width: 100%;
padding: $board-spacing + $stack-spacing;
padding: $board-gap;
padding-bottom: 0;
overflow: hidden;
min-height: 35vh;
@@ -169,17 +172,24 @@ export default {
border-top: 1px solid var(--color-border);
z-index: 1010;
position: relative;
display: flex;
flex-direction: column;
.action-item.icon-close {
position: absolute;
top: 10px;
right: 10px;
}
.search-wrapper {
overflow: scroll;
height: 100%;
position: relative;
padding: 10px;
.search-header {
display: flex;
align-items: flex-start;
justify-content: space-between;
}
h2 {
margin: 0;
padding: var(--default-grid-baseline) var(--default-grid-baseline) $board-gap;
}
h2 > div {
@@ -189,23 +199,24 @@ export default {
margin-right: 20px;
}
}
h2:deep(span) {
background-color: var(--color-background-dark);
padding: 3px;
border-radius: var(--border-radius);
}
.search-results {
.search-wrapper {
overflow: auto;
height: 100%;
position: relative;
display: flex;
flex-wrap: wrap;
gap: $stack-gap;
& > div {
flex-grow: 0;
& > .drop-upload--card {
flex: 0 1 $card-max-width;
min-width: $card-min-width;
}
}
&:deep(.card) {
width: $stack-width;
margin-right: $stack-spacing;
}
}
</style>

View File

@@ -62,14 +62,13 @@ export default {
$clickable-area: var(--default-clickable-area);
.card--placeholder {
width: $stack-width;
margin-right: $stack-spacing;
min-width: $card-min-width;
max-width: $card-min-width;
padding: $card-padding;
transition: box-shadow 0.1s ease-in-out;
box-shadow: 0 0 2px 0 var(--color-box-shadow);
border-radius: var(--border-radius-large);
font-size: 100%;
margin-bottom: $card-spacing;
height: 100px;
}

View File

@@ -2,8 +2,10 @@
* SPDX-FileCopyrightText: 2020 Nextcloud GmbH and Nextcloud contributors
* SPDX-License-Identifier: AGPL-3.0-or-later
*/
$card-spacing: 8px;
$card-padding: 4px;
$stack-spacing: 12px;
$stack-width: 280px;
$board-spacing: 16px;
$card-min-width: 216px;
$card-max-width: 300px;
$card-padding: calc(var(--default-grid-baseline) * 2) calc(var(--default-grid-baseline) * 2) var(--default-grid-baseline);
$card-gap: calc(var(--default-grid-baseline) * 3);
$card-image-margin: calc(var(--default-grid-baseline) * -2);
$stack-gap: calc(var(--default-grid-baseline) * 3);
$board-gap: calc(var(--default-grid-baseline) * 4);

View File

@@ -189,7 +189,7 @@ export default {
card.assignedUsers = card.assignedUsers || []
const existingIndex = state.cards.findIndex(_card => _card.id === card.id)
if (existingIndex !== -1) {
const existingCard = state.cards.find(_card => _card.id === card.id)
const existingCard = state.cards[existingIndex]
Vue.set(state.cards, existingIndex, Object.assign({}, existingCard, card))
} else {
state.cards.push(card)

View File

@@ -90,8 +90,8 @@
}
],
"assignedUsers": [],
"attachments": null,
"attachmentCount": null,
"attachments": [],
"attachmentCount": 0,
"owner": {
"primaryKey": "admin",
"uid": "admin",
@@ -123,8 +123,8 @@
"createdAt": 1689667572,
"labels": [],
"assignedUsers": [],
"attachments": null,
"attachmentCount": null,
"attachments": [],
"attachmentCount": 0,
"owner": {
"primaryKey": "admin",
"uid": "admin",
@@ -168,8 +168,8 @@
"type": 0
}
],
"attachments": null,
"attachmentCount": null,
"attachments": [],
"attachmentCount": 0,
"owner": {
"primaryKey": "admin",
"uid": "admin",
@@ -240,8 +240,8 @@
}
],
"assignedUsers": [],
"attachments": null,
"attachmentCount": null,
"attachments": [],
"attachmentCount": 0,
"owner": {
"primaryKey": "admin",
"uid": "admin",
@@ -283,8 +283,8 @@
}
],
"assignedUsers": [],
"attachments": null,
"attachmentCount": null,
"attachments": [],
"attachmentCount": 0,
"owner": {
"primaryKey": "admin",
"uid": "admin",
@@ -328,8 +328,8 @@
"type": 0
}
],
"attachments": null,
"attachmentCount": null,
"attachments": [],
"attachmentCount": 0,
"owner": {
"primaryKey": "admin",
"uid": "admin",
@@ -493,8 +493,8 @@
"createdAt": 1689667483,
"labels": [],
"assignedUsers": [],
"attachments": null,
"attachmentCount": null,
"attachments": [],
"attachmentCount": 0,
"owner": {
"primaryKey": "admin",
"uid": "admin",
@@ -526,8 +526,8 @@
"createdAt": 1689667518,
"labels": [],
"assignedUsers": [],
"attachments": null,
"attachmentCount": null,
"attachments": [],
"attachmentCount": 0,
"owner": {
"primaryKey": "admin",
"uid": "admin",
@@ -559,8 +559,8 @@
"createdAt": 1689667527,
"labels": [],
"assignedUsers": [],
"attachments": null,
"attachmentCount": null,
"attachments": [],
"attachmentCount": 0,
"owner": {
"primaryKey": "admin",
"uid": "admin",
@@ -592,8 +592,8 @@
"createdAt": 1689667537,
"labels": [],
"assignedUsers": [],
"attachments": null,
"attachmentCount": null,
"attachments": [],
"attachmentCount": 0,
"owner": {
"primaryKey": "admin",
"uid": "admin",
@@ -636,8 +636,8 @@
"createdAt": 1689667488,
"labels": [],
"assignedUsers": [],
"attachments": null,
"attachmentCount": null,
"attachments": [],
"attachmentCount": 0,
"owner": {
"primaryKey": "admin",
"uid": "admin",
@@ -680,8 +680,8 @@
"createdAt": 1689667493,
"labels": [],
"assignedUsers": [],
"attachments": null,
"attachmentCount": null,
"attachments": [],
"attachmentCount": 0,
"owner": {
"primaryKey": "admin",
"uid": "admin",
@@ -713,8 +713,8 @@
"createdAt": 1689667502,
"labels": [],
"assignedUsers": [],
"attachments": null,
"attachmentCount": null,
"attachments": [],
"attachmentCount": 0,
"owner": {
"primaryKey": "admin",
"uid": "admin",

View File

@@ -1 +1 @@
82773
82774

View File

@@ -1,152 +1,78 @@
<?xml version="1.0" encoding="UTF-8"?>
<files psalm-version="5.6.0@e784128902dfe01d489c4123d69918a9f3c1eac5">
<files psalm-version="6.5.0@38fc8444edf0cebc9205296ee6e30e906ade783b">
<file src="lib/Activity/Filter.php">
<MethodSignatureMismatch>
<code>$types</code>
<code><![CDATA[$types]]></code>
</MethodSignatureMismatch>
</file>
<file src="lib/Command/UserExport.php">
<ImplementedReturnTypeMismatch>
<code>void</code>
</ImplementedReturnTypeMismatch>
<UndefinedThisPropertyAssignment>
<code>$this-&gt;boardMapper</code>
<code>$this-&gt;stackMapper</code>
</UndefinedThisPropertyAssignment>
<UndefinedThisPropertyFetch>
<code>$this-&gt;boardMapper</code>
<code>$this-&gt;stackMapper</code>
</UndefinedThisPropertyFetch>
</file>
<file src="lib/Controller/BoardApiController.php">
<TypeDoesNotContainNull>
<code>$modified === null</code>
<code>$modified === null</code>
</TypeDoesNotContainNull>
<UndefinedClass>
<code>Util</code>
</UndefinedClass>
<UndefinedThisPropertyAssignment>
<code>$this-&gt;userId</code>
</UndefinedThisPropertyAssignment>
<UndefinedThisPropertyFetch>
<code>$this-&gt;userId</code>
</UndefinedThisPropertyFetch>
</file>
<file src="lib/Controller/CommentsApiController.php">
<InvalidScalarArgument>
<code>$cardId</code>
<code>$cardId</code>
<code>$cardId</code>
<code>$commentId</code>
<code>$commentId</code>
<code>$parentId</code>
</InvalidScalarArgument>
<UndefinedFunction>
<code><![CDATA[parseDate($modified)]]></code>
</UndefinedFunction>
</file>
<file src="lib/Controller/PageController.php">
<UndefinedClass>
<code>LoadSidebar</code>
<code><![CDATA[LoadSidebar]]></code>
</UndefinedClass>
</file>
<file src="lib/Controller/StackApiController.php">
<RedundantCondition>
<code>$modified !== null</code>
</RedundantCondition>
<UndefinedClass>
<code>Util</code>
</UndefinedClass>
<UndefinedFunction>
<code><![CDATA[parseDate($modified)]]></code>
</UndefinedFunction>
</file>
<file src="lib/DAV/Calendar.php">
<UndefinedClass>
<code>ExternalCalendar</code>
<code><![CDATA[ExternalCalendar]]></code>
</UndefinedClass>
</file>
<file src="lib/DAV/CalendarObject.php">
<UndefinedClass>
<code>ICalendarObject</code>
<code><![CDATA[ICalendarObject]]></code>
</UndefinedClass>
</file>
<file src="lib/DAV/CalendarPlugin.php">
<UndefinedClass>
<code>ICalendarProvider</code>
<code><![CDATA[ICalendarProvider]]></code>
</UndefinedClass>
</file>
<file src="lib/DAV/DeckCalendarBackend.php">
<UndefinedClass>
<code>NotFound</code>
<code><![CDATA[NotFound]]></code>
</UndefinedClass>
</file>
<file src="lib/Db/Card.php">
<UndefinedClass>
<code>VCalendar</code>
<code>VCalendar</code>
<code><![CDATA[VCalendar]]></code>
<code><![CDATA[VCalendar]]></code>
</UndefinedClass>
</file>
<file src="lib/Db/CardMapper.php">
<InvalidScalarArgument>
<code>$entity-&gt;getId()</code>
</InvalidScalarArgument>
<UndefinedInterfaceMethod>
<code>getUserIdGroups</code>
</UndefinedInterfaceMethod>
</file>
<file src="lib/Db/LabelMapper.php">
<ParamNameMismatch>
<code>$labelId</code>
</ParamNameMismatch>
</file>
<file src="lib/Db/Stack.php">
<UndefinedClass>
<code>VCalendar</code>
<code>VCalendar</code>
<code><![CDATA[VCalendar]]></code>
<code><![CDATA[VCalendar]]></code>
</UndefinedClass>
</file>
<file src="lib/Model/BoardSummary.php">
<ConstructorSignatureMismatch>
<code>public function __construct(Board $board) {</code>
<code>public function __construct(Board $board) {</code>
</ConstructorSignatureMismatch>
</file>
<file src="lib/Model/CardDetails.php">
<ConstructorSignatureMismatch>
<code>public function __construct(Card $card, ?Board $board = null) {</code>
<code>public function __construct(Card $card, ?Board $board = null) {</code>
</ConstructorSignatureMismatch>
</file>
<file src="lib/Service/AttachmentService.php">
<InvalidCatch>
<code>try {
$attachment = $this-&gt;attachmentMapper-&gt;find($attachmentId);
} catch (IMapperException $e) {
throw new NoPermissionException('Permission denied');
}</code>
</InvalidCatch>
</file>
<file src="lib/Service/BoardService.php">
<TooManyArguments>
<code>findAll</code>
<code>findAll</code>
</TooManyArguments>
</file>
<file src="lib/Service/CirclesService.php">
<RedundantCondition>
<code>$member !== null</code>
</RedundantCondition>
</file>
<file src="lib/Service/FileService.php">
<RedundantCondition>
<code>is_resource($content)</code>
<code>is_resource($content)</code>
<code><![CDATA[is_resource($content)]]></code>
<code><![CDATA[is_resource($content)]]></code>
</RedundantCondition>
</file>
<file src="lib/Sharing/DeckShareProvider.php">
<InvalidReturnType>
<code>getShareByToken</code>
</InvalidReturnType>
<file src="lib/Service/Importer/BoardImportCommandService.php">
<UndefinedInterfaceMethod>
<code><![CDATA[ask]]></code>
<code><![CDATA[ask]]></code>
<code><![CDATA[ask]]></code>
</UndefinedInterfaceMethod>
</file>
<file src="lib/Sharing/Listener.php">
<InvalidArgument>
<code>[self::class, 'listenPreShare']</code>
</InvalidArgument>
<file src="lib/Service/Importer/Systems/TrelloJsonService.php">
<InvalidPropertyFetch>
<code><![CDATA[$createCardDate->date]]></code>
</InvalidPropertyFetch>
</file>
<file src="lib/Teams/DeckTeamResourceProvider.php">
<FalsableReturnStatement>
<code><![CDATA[file_get_contents(__DIR__ . '/../../img/deck-current.svg')]]></code>
</FalsableReturnStatement>
</file>
</files>

View File

@@ -160,6 +160,7 @@ class ActivityManagerTest extends TestCase {
'id' => 123,
'title' => 'My card',
'description' => str_repeat('A', 1000),
'stackId' => 42,
]);
$this->cardMapper->expects(self::any())
->method('find')
@@ -170,6 +171,7 @@ class ActivityManagerTest extends TestCase {
]);
$this->stackMapper->expects(self::any())
->method('find')
->with(42)
->willReturn($stack);
$expectedCard = $card->jsonSerialize();
@@ -207,6 +209,7 @@ class ActivityManagerTest extends TestCase {
$card->setDescription(str_repeat('A', 5000));
$card->setTitle('My card');
$card->setId(123);
$card->setStackId(42);
$this->cardMapper->expects(self::any())
->method('find')
->willReturn($card);
@@ -254,6 +257,7 @@ class ActivityManagerTest extends TestCase {
$card->setDescription(str_repeat('A', 5000));
$card->setTitle('My card');
$card->setId(123);
$card->setStackId(42);
$this->cardMapper->expects(self::any())
->method('find')
->willReturn($card);

View File

@@ -119,7 +119,7 @@ class AttachmentMapperTest extends TestCase {
->method('isOwner')
->with('admin', 1)
->willReturn(true);
$this->assertTrue($this->attachmentMapper->isOwner('admin', (string)$this->attachments[0]->getId()));
$this->assertTrue($this->attachmentMapper->isOwner('admin', $this->attachments[0]->getId()));
}
public function testIsOwnerInvalid() {
@@ -127,7 +127,7 @@ class AttachmentMapperTest extends TestCase {
->method('isOwner')
->with('admin', 1)
->will($this->throwException(new DoesNotExistException('does not exist')));
$this->assertFalse($this->attachmentMapper->isOwner('admin', (string)$this->attachments[0]->getId()));
$this->assertFalse($this->attachmentMapper->isOwner('admin', $this->attachments[0]->getId()));
}
public function testFindBoardId() {

View File

@@ -81,8 +81,8 @@ class CardTest extends TestCase {
'duedate' => null,
'overdue' => 0,
'archived' => false,
'attachments' => null,
'attachmentCount' => null,
'attachments' => [],
'attachmentCount' => 0,
'assignedUsers' => null,
'deletedAt' => 0,
'commentsUnread' => 0,
@@ -110,8 +110,8 @@ class CardTest extends TestCase {
'duedate' => null,
'overdue' => 0,
'archived' => false,
'attachments' => null,
'attachmentCount' => null,
'attachments' => [],
'attachmentCount' => 0,
'assignedUsers' => null,
'deletedAt' => 0,
'commentsUnread' => 0,
@@ -141,8 +141,8 @@ class CardTest extends TestCase {
'duedate' => null,
'overdue' => 0,
'archived' => false,
'attachments' => null,
'attachmentCount' => null,
'attachments' => [],
'attachmentCount' => 0,
'assignedUsers' => ['user1'],
'deletedAt' => 0,
'commentsUnread' => 0,

View File

@@ -1,5 +1,7 @@
<?php
declare(strict_types=1);
/**
* @copyright Copyright (c) 2018 Julius Härtl <jus@bitgrid.net>
*
@@ -116,7 +118,7 @@ class AttachmentServiceTest extends TestCase {
$this->activityManager = $this->createMock(ActivityManager::class);
$this->appContainer->expects($this->exactly(2))
->method('query')
->method('get')
->withConsecutive(
[FileService::class],
[FilesAppService::class]
@@ -156,7 +158,7 @@ class AttachmentServiceTest extends TestCase {
$fileAppServiceMock = $this->createMock(FilesAppService::class);
$appContainer->expects($this->exactly(3))
->method('query')
->method('get')
->withConsecutive(
[FileService::class],
[FilesAppService::class],
@@ -185,7 +187,7 @@ class AttachmentServiceTest extends TestCase {
$fileAppServiceMock = $this->createMock(FilesAppService::class);
$appContainer->expects($this->exactly(3))
->method('query')
->method('get')
->withConsecutive(
[FileService::class],
[FilesAppService::class],

View File

@@ -1,5 +1,7 @@
<?php
declare(strict_types=1);
/**
* @copyright Copyright (c) 2016 Julius Härtl <jus@bitgrid.net>
*
@@ -240,6 +242,7 @@ class BoardServiceTest extends TestCase {
public function testDelete() {
$board = new Board();
$board->setId(42);
$board->setOwner('admin');
$board->setDeletedAt(0);
$this->boardMapper->expects($this->once())
@@ -252,7 +255,7 @@ class BoardServiceTest extends TestCase {
]);
$this->sessionMapper->expects($this->once())
->method('findAllActive')
->with(null)
->with(42)
->willReturn([]);
$boardDeleted = clone $board;
$boardDeleted->setDeletedAt(1);
@@ -267,7 +270,7 @@ class BoardServiceTest extends TestCase {
$user->method('getUID')->willReturn('admin');
$acl = new Acl();
$acl->setBoardId(123);
$acl->setType('user');
$acl->setType(Acl::PERMISSION_TYPE_USER);
$acl->setParticipant('admin');
$acl->setPermissionEdit(true);
$acl->setPermissionShare(true);
@@ -287,7 +290,7 @@ class BoardServiceTest extends TestCase {
'admin' => 'admin',
]);
$this->assertEquals($acl, $this->service->addAcl(
123, 'user', 'admin', true, true, true
123, Acl::PERMISSION_TYPE_USER, 'admin', true, true, true
));
}
@@ -323,7 +326,7 @@ class BoardServiceTest extends TestCase {
public function testAddAclExtendPermission($currentUserAcl, $providedAcl, $resultingAcl) {
$existingAcl = new Acl();
$existingAcl->setBoardId(123);
$existingAcl->setType('user');
$existingAcl->setType(Acl::PERMISSION_TYPE_USER);
$existingAcl->setParticipant('admin');
$existingAcl->setPermissionEdit($currentUserAcl[0]);
$existingAcl->setPermissionShare($currentUserAcl[1]);
@@ -391,14 +394,14 @@ class BoardServiceTest extends TestCase {
->method('dispatchTyped')
->with(new AclCreatedEvent($acl));
$this->assertEquals($expected, $this->service->addAcl(
123, 'user', 'admin', $providedAcl[0], $providedAcl[1], $providedAcl[2]
123, Acl::PERMISSION_TYPE_USER, 'admin', $providedAcl[0], $providedAcl[1], $providedAcl[2]
));
}
public function testUpdateAcl() {
$acl = new Acl();
$acl->setBoardId(123);
$acl->setType('user');
$acl->setType(Acl::PERMISSION_TYPE_USER);
$acl->setParticipant('admin');
$acl->setPermissionEdit(true);
$acl->setPermissionShare(true);

Some files were not shown because too many files have changed in this diff Show More