Compare commits

..

355 Commits

Author SHA1 Message Date
Julius Härtl
21b50292de chore(release): Bump version to 1.6.6
Signed-off-by: Julius Härtl <jus@bitgrid.net>
2024-01-16 15:53:31 +01:00
Julius Härtl
197f81abe2 Merge pull request #5450 from nextcloud/backport/5449/stable23
[stable23] Fix deleted card/board issues
2024-01-12 09:30:23 +01:00
Julius Härtl
227d9df978 test: Adapt unit tests
Signed-off-by: Julius Härtl <jus@bitgrid.net>
2024-01-12 08:43:16 +01:00
Julius Härtl
1be21db78e ci: Remove unused test
Signed-off-by: Julius Härtl <jus@bitgrid.net>
2024-01-12 08:43:16 +01:00
Julius Härtl
82deb677d2 chore: update actions
Signed-off-by: Julius Härtl <jus@bitgrid.net>
2024-01-12 08:43:15 +01:00
Julius Härtl
16e5b36d2f fix: PHP 7.4 compatibility
Signed-off-by: Julius Härtl <jus@bitgrid.net>
2024-01-12 08:43:15 +01:00
Julius Härtl
635880a27b tests: Fix missing behat context methods
Signed-off-by: Julius Härtl <jus@bitgrid.net>
2024-01-09 23:09:15 +01:00
Julius Härtl
6a3f4ecee9 chore: Fix ci setup for activity
Signed-off-by: Julius Härtl <jus@bitgrid.net>
2024-01-09 23:09:15 +01:00
Julius Härtl
2fc91455ee fix: Limit card activities for deleted cards
Signed-off-by: Julius Härtl <jus@bitgrid.net>
2024-01-09 23:09:13 +01:00
Julius Härtl
23a0ec226b fix: Further limit updating cards
Signed-off-by: Julius Härtl <jus@bitgrid.net>
2024-01-09 23:08:13 +01:00
Julius Härtl
b812075e06 fix: limit to non-deleted cards
Signed-off-by: Julius Härtl <jus@bitgrid.net>
2024-01-09 23:08:13 +01:00
Julius Härtl
b135c1fa06 fix: Consider a deleted board inaccessible to share recipients
Only the owner can delete/undo a board deletion so there is no reason
other users should have any permission on a board marked as deleted

Signed-off-by: Julius Härtl <jus@bitgrid.net>
2024-01-09 23:08:13 +01:00
Julius Härtl
9720a4464c tests: Add integration tests for deleted boards/cards
Signed-off-by: Julius Härtl <jus@bitgrid.net>
2024-01-09 23:08:13 +01:00
Julius Härtl
0b6df4bc54 Merge pull request #5447 from nextcloud/backport/5446/stable23
[stable23] Fix small issues around delete/undo
2024-01-09 23:03:33 +01:00
Julius Härtl
eac673c129 ci: Update actions
Signed-off-by: Julius Härtl <jus@bitgrid.net>
2024-01-09 22:55:07 +01:00
Julius Härtl
f9a76508a6 ci: Bump setup-php
Signed-off-by: Julius Härtl <jus@bitgrid.net>
2024-01-09 19:40:41 +01:00
Julius Härtl
e53938038e fix: Psalm and CI
Signed-off-by: Julius Härtl <jus@bitgrid.net>
2024-01-09 19:40:24 +01:00
Julius Härtl
d88844bd7a ci: Update phpunit github action
Signed-off-by: Julius Härtl <jus@bitgrid.net>
2024-01-09 19:40:24 +01:00
Julius Härtl
f094bce286 fix: Only query boards not marked for deletion unless we want to undo
Signed-off-by: Julius Härtl <jus@bitgrid.net>
2024-01-09 19:40:23 +01:00
Julius Härtl
b017f441e3 Merge pull request #5073 from nextcloud/backport/4810/stable23 2023-08-30 19:44:51 +02:00
Max
bf21052d51 Fix(occ): set user id for permission sevice from board service
Fixes #4010.

Signed-off-by: Max <max@nextcloud.com>
2023-08-30 08:51:08 +00:00
Jonas
ab5d45acc6 Merge pull request #4570 from nextcloud/backport/4566/stable23
[stable23] Gracefully handle not found card for a share
2023-03-29 11:28:12 +02:00
Jonas
a3aebb14d7 Gracefully handle not found card for a share
Fixes: #3464
Fixes: #4565

Signed-off-by: Jonas <jonas@freesources.org>
2023-03-29 09:08:46 +00:00
Julius Härtl
3d58448159 Merge pull request #4241 from nextcloud/dependabot/npm_and_yarn/stable23/vue-2.7.14 2022-12-30 14:10:37 +01:00
Julius Härtl
735b26ae88 Merge pull request #4341 from nextcloud/automated/noid/stable23-update-nextcloud-ocp 2022-12-30 13:53:48 +01:00
nextcloud-command
aa74bea139 Update psalm baseline
Signed-off-by: GitHub <noreply@github.com>
2022-12-25 02:41:46 +00:00
dependabot[bot]
bcaffdf1d8 Merge pull request #4334 from nextcloud/dependabot/npm_and_yarn/stable23/babel/runtime-7.20.7 2022-12-24 03:34:27 +00:00
dependabot[bot]
d0be727931 Bump @babel/runtime from 7.20.6 to 7.20.7
Bumps [@babel/runtime](https://github.com/babel/babel/tree/HEAD/packages/babel-runtime) from 7.20.6 to 7.20.7.
- [Release notes](https://github.com/babel/babel/releases)
- [Changelog](https://github.com/babel/babel/blob/main/CHANGELOG.md)
- [Commits](https://github.com/babel/babel/commits/v7.20.7/packages/babel-runtime)

---
updated-dependencies:
- dependency-name: "@babel/runtime"
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2022-12-24 02:01:45 +00:00
Nextcloud bot
675f0e68e1 [tx-robot] updated from transifex
Signed-off-by: Nextcloud bot <bot@nextcloud.com>
2022-12-18 02:27:13 +00:00
Nextcloud bot
c43a1ba358 [tx-robot] updated from transifex
Signed-off-by: Nextcloud bot <bot@nextcloud.com>
2022-12-16 02:27:08 +00:00
Nextcloud bot
d1d4a50252 [tx-robot] updated from transifex
Signed-off-by: Nextcloud bot <bot@nextcloud.com>
2022-12-14 02:29:33 +00:00
Nextcloud bot
af1139a260 [tx-robot] updated from transifex
Signed-off-by: Nextcloud bot <bot@nextcloud.com>
2022-12-13 02:40:08 +00:00
Nextcloud bot
6f336d8e76 [tx-robot] updated from transifex
Signed-off-by: Nextcloud bot <bot@nextcloud.com>
2022-12-04 02:22:15 +00:00
dependabot[bot]
504a32257f Merge pull request #4289 from nextcloud/dependabot/npm_and_yarn/stable23/babel/runtime-7.20.6 2022-12-03 05:49:02 +00:00
dependabot[bot]
8b9699e694 Bump @babel/runtime from 7.20.1 to 7.20.6
Bumps [@babel/runtime](https://github.com/babel/babel/tree/HEAD/packages/babel-runtime) from 7.20.1 to 7.20.6.
- [Release notes](https://github.com/babel/babel/releases)
- [Changelog](https://github.com/babel/babel/blob/main/CHANGELOG.md)
- [Commits](https://github.com/babel/babel/commits/v7.20.6/packages/babel-runtime)

---
updated-dependencies:
- dependency-name: "@babel/runtime"
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2022-12-03 03:39:21 +00:00
Nextcloud bot
55c126a2b9 [tx-robot] updated from transifex
Signed-off-by: Nextcloud bot <bot@nextcloud.com>
2022-12-01 02:23:07 +00:00
Nextcloud bot
757301eb67 [tx-robot] updated from transifex
Signed-off-by: Nextcloud bot <bot@nextcloud.com>
2022-11-30 02:23:30 +00:00
Nextcloud bot
85f4de1feb [tx-robot] updated from transifex
Signed-off-by: Nextcloud bot <bot@nextcloud.com>
2022-11-23 02:26:04 +00:00
Nextcloud bot
ffe66f6bbd [tx-robot] updated from transifex
Signed-off-by: Nextcloud bot <bot@nextcloud.com>
2022-11-22 02:26:50 +00:00
dependabot[bot]
b56c93b631 Bump vue from 2.6.14 to 2.7.14
Bumps [vue](https://github.com/vuejs/core) from 2.6.14 to 2.7.14.
- [Release notes](https://github.com/vuejs/core/releases)
- [Changelog](https://github.com/vuejs/core/blob/main/CHANGELOG.md)
- [Commits](https://github.com/vuejs/core/commits)

---
updated-dependencies:
- dependency-name: vue
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2022-11-21 06:43:13 +00:00
Julius Härtl
11c4a67974 Merge pull request #4251 from nextcloud/dependabot/npm_and_yarn/stable23/vue-router-3.6.5 2022-11-21 07:42:40 +01:00
Julius Härtl
8112ce6833 Merge pull request #4255 from nextcloud/dependabot/npm_and_yarn/stable23/nextcloud/axios-1.11.0 2022-11-21 07:42:23 +01:00
Nextcloud bot
72f52e64c1 [tx-robot] updated from transifex
Signed-off-by: Nextcloud bot <bot@nextcloud.com>
2022-11-21 02:22:59 +00:00
Nextcloud bot
d3b5dea49b [tx-robot] updated from transifex
Signed-off-by: Nextcloud bot <bot@nextcloud.com>
2022-11-20 02:24:09 +00:00
dependabot[bot]
3e723f7b73 Bump @nextcloud/axios from 1.7.0 to 1.11.0
Bumps [@nextcloud/axios](https://github.com/nextcloud/nextcloud-axios) from 1.7.0 to 1.11.0.
- [Release notes](https://github.com/nextcloud/nextcloud-axios/releases)
- [Changelog](https://github.com/nextcloud/nextcloud-axios/blob/master/CHANGELOG.md)
- [Commits](https://github.com/nextcloud/nextcloud-axios/compare/v1.7.0...v1.11.0)

---
updated-dependencies:
- dependency-name: "@nextcloud/axios"
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2022-11-19 08:30:07 +00:00
Julius Härtl
5b2d62ce1b Merge pull request #4262 from nextcloud/dependabot/npm_and_yarn/stable23/nextcloud/browserslist-config-2.3.0
Bump @nextcloud/browserslist-config from 2.2.0 to 2.3.0
2022-11-19 09:29:30 +01:00
Julius Härtl
d4efb1e2d8 Merge pull request #4239 from nextcloud/dependabot/npm_and_yarn/stable23/jest-27.5.1
Bump jest from 27.3.1 to 27.5.1
2022-11-19 09:28:47 +01:00
Julius Härtl
9eb9b2d6f8 Merge pull request #4249 from nextcloud/dependabot/npm_and_yarn/stable23/nextcloud/l10n-1.6.0
Bump @nextcloud/l10n from 1.4.1 to 1.6.0
2022-11-19 09:28:28 +01:00
Julius Härtl
c6398ad7d8 Merge pull request #4246 from nextcloud/dependabot/npm_and_yarn/stable23/nextcloud/moment-1.2.1
Bump @nextcloud/moment from 1.1.1 to 1.2.1
2022-11-19 09:26:56 +01:00
dependabot[bot]
0646379e65 Bump jest from 27.3.1 to 27.5.1
Bumps [jest](https://github.com/facebook/jest/tree/HEAD/packages/jest) from 27.3.1 to 27.5.1.
- [Release notes](https://github.com/facebook/jest/releases)
- [Changelog](https://github.com/facebook/jest/blob/main/CHANGELOG.md)
- [Commits](https://github.com/facebook/jest/commits/v27.5.1/packages/jest)

---
updated-dependencies:
- dependency-name: jest
  dependency-type: direct:development
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2022-11-19 08:26:43 +00:00
Julius Härtl
4418398394 Merge pull request #4244 from nextcloud/dependabot/npm_and_yarn/stable23/dompurify-2.4.1
Bump dompurify from 2.3.3 to 2.4.1
2022-11-19 09:26:33 +01:00
Julius Härtl
c49e4dc0b4 Merge pull request #4223 from nextcloud/dependabot/npm_and_yarn/stable23/relative-ci/agent-3.1.3
Bump @relative-ci/agent from 3.0.0 to 3.1.3
2022-11-19 09:26:05 +01:00
Nextcloud bot
0957f0d13e [tx-robot] updated from transifex
Signed-off-by: Nextcloud bot <bot@nextcloud.com>
2022-11-19 02:25:11 +00:00
dependabot[bot]
d219d386e4 Bump @relative-ci/agent from 3.0.0 to 3.1.3
Bumps [@relative-ci/agent](https://github.com/relative-ci/agent) from 3.0.0 to 3.1.3.
- [Release notes](https://github.com/relative-ci/agent/releases)
- [Commits](https://github.com/relative-ci/agent/compare/v3.0.0...v3.1.3)

---
updated-dependencies:
- dependency-name: "@relative-ci/agent"
  dependency-type: direct:development
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2022-11-18 21:15:58 +00:00
Julius Härtl
097a4fbfe2 Merge pull request #4230 from nextcloud/dependabot/npm_and_yarn/stable23/nextcloud/vue-4.4.0
Bump @nextcloud/vue from 4.2.0 to 4.4.0
2022-11-18 22:15:03 +01:00
dependabot[bot]
f87d20bc4c Merge pull request #4259 from nextcloud/dependabot/npm_and_yarn/stable23/nextcloud/eslint-config-6.1.2 2022-11-18 20:42:38 +00:00
Julius Härtl
83a1c47be5 Merge pull request #4226 from nextcloud/dependabot/npm_and_yarn/stable23/nextcloud/dialogs-3.2.0
Bump @nextcloud/dialogs from 3.1.2 to 3.2.0
2022-11-18 21:12:17 +01:00
Julius Härtl
f15421ae60 Merge pull request #4237 from nextcloud/dependabot/npm_and_yarn/stable23/vue/test-utils-1.3.3
Bump @vue/test-utils from 1.2.2 to 1.3.3
2022-11-18 21:11:36 +01:00
Julius Härtl
3e8c2a70d6 Merge pull request #4228 from nextcloud/dependabot/npm_and_yarn/stable23/babel/runtime-7.20.1
Bump @babel/runtime from 7.16.0 to 7.20.1
2022-11-18 21:11:17 +01:00
Julius Härtl
79f26d64a3 Merge pull request #4235 from nextcloud/dependabot/npm_and_yarn/stable23/vue-at-2.5.1
Bump vue-at from 2.5.0-beta.2 to 2.5.1
2022-11-18 21:10:52 +01:00
Julius Härtl
3dd71981d5 Merge pull request #4234 from nextcloud/dependabot/npm_and_yarn/stable23/nextcloud/webpack-vue-config-4.3.2
Bump @nextcloud/webpack-vue-config from 4.1.2 to 4.3.2
2022-11-18 21:10:37 +01:00
dependabot[bot]
2b82cd063a Merge pull request #4231 from nextcloud/dependabot/npm_and_yarn/stable23/moment-2.29.4 2022-11-18 19:50:04 +00:00
dependabot[bot]
72ad9bb646 Bump @nextcloud/browserslist-config from 2.2.0 to 2.3.0
Bumps [@nextcloud/browserslist-config](https://github.com/nextcloud/browserslist-config) from 2.2.0 to 2.3.0.
- [Release notes](https://github.com/nextcloud/browserslist-config/releases)
- [Commits](https://github.com/nextcloud/browserslist-config/compare/v2.2.0...v2.3.0)

---
updated-dependencies:
- dependency-name: "@nextcloud/browserslist-config"
  dependency-type: direct:development
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2022-11-18 17:31:23 +00:00
dependabot[bot]
d41001a2d3 Bump @nextcloud/eslint-config from 6.1.0 to 6.1.2
Bumps [@nextcloud/eslint-config](https://github.com/nextcloud/eslint-config) from 6.1.0 to 6.1.2.
- [Release notes](https://github.com/nextcloud/eslint-config/releases)
- [Changelog](https://github.com/nextcloud/eslint-config/blob/master/CHANGELOG.md)
- [Commits](https://github.com/nextcloud/eslint-config/compare/v6.1.0...v6.1.2)

---
updated-dependencies:
- dependency-name: "@nextcloud/eslint-config"
  dependency-type: direct:development
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2022-11-18 17:31:11 +00:00
dependabot[bot]
c48e3e1a9f Bump vue-router from 3.5.3 to 3.6.5
Bumps [vue-router](https://github.com/vuejs/router) from 3.5.3 to 3.6.5.
- [Release notes](https://github.com/vuejs/router/releases)
- [Commits](https://github.com/vuejs/router/commits)

---
updated-dependencies:
- dependency-name: vue-router
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2022-11-18 17:30:35 +00:00
dependabot[bot]
d34c3f389a Bump @nextcloud/l10n from 1.4.1 to 1.6.0
Bumps [@nextcloud/l10n](https://github.com/nextcloud/nextcloud-l10n) from 1.4.1 to 1.6.0.
- [Release notes](https://github.com/nextcloud/nextcloud-l10n/releases)
- [Changelog](https://github.com/nextcloud/nextcloud-l10n/blob/master/CHANGELOG.md)
- [Commits](https://github.com/nextcloud/nextcloud-l10n/compare/v1.4.1...v1.6.0)

---
updated-dependencies:
- dependency-name: "@nextcloud/l10n"
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2022-11-18 17:30:26 +00:00
dependabot[bot]
40744773fc Bump @nextcloud/moment from 1.1.1 to 1.2.1
Bumps [@nextcloud/moment](https://github.com/nextcloud/nextcloud-moment) from 1.1.1 to 1.2.1.
- [Release notes](https://github.com/nextcloud/nextcloud-moment/releases)
- [Changelog](https://github.com/nextcloud/nextcloud-moment/blob/master/CHANGELOG.md)
- [Commits](https://github.com/nextcloud/nextcloud-moment/compare/v1.1.1...v1.2.1)

---
updated-dependencies:
- dependency-name: "@nextcloud/moment"
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2022-11-18 17:30:15 +00:00
dependabot[bot]
14fa58f3f1 Bump dompurify from 2.3.3 to 2.4.1
Bumps [dompurify](https://github.com/cure53/DOMPurify) from 2.3.3 to 2.4.1.
- [Release notes](https://github.com/cure53/DOMPurify/releases)
- [Commits](https://github.com/cure53/DOMPurify/compare/2.3.3...2.4.1)

---
updated-dependencies:
- dependency-name: dompurify
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2022-11-18 17:30:05 +00:00
dependabot[bot]
1bfae5107f Bump @vue/test-utils from 1.2.2 to 1.3.3
Bumps [@vue/test-utils](https://github.com/vuejs/test-utils) from 1.2.2 to 1.3.3.
- [Release notes](https://github.com/vuejs/test-utils/releases)
- [Commits](https://github.com/vuejs/test-utils/commits)

---
updated-dependencies:
- dependency-name: "@vue/test-utils"
  dependency-type: direct:development
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2022-11-18 17:29:29 +00:00
dependabot[bot]
099f6d0bea Bump vue-at from 2.5.0-beta.2 to 2.5.1
Bumps [vue-at](https://github.com/fritx/vue-at) from 2.5.0-beta.2 to 2.5.1.
- [Release notes](https://github.com/fritx/vue-at/releases)
- [Commits](https://github.com/fritx/vue-at/commits)

---
updated-dependencies:
- dependency-name: vue-at
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2022-11-18 17:29:19 +00:00
dependabot[bot]
0c48e4acb2 Bump @nextcloud/webpack-vue-config from 4.1.2 to 4.3.2
Bumps [@nextcloud/webpack-vue-config](https://github.com/nextcloud/webpack-vue-config) from 4.1.2 to 4.3.2.
- [Release notes](https://github.com/nextcloud/webpack-vue-config/releases)
- [Changelog](https://github.com/nextcloud/webpack-vue-config/blob/master/CHANGELOG.md)
- [Commits](https://github.com/nextcloud/webpack-vue-config/compare/v4.1.2...v4.3.2)

---
updated-dependencies:
- dependency-name: "@nextcloud/webpack-vue-config"
  dependency-type: direct:development
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2022-11-18 17:29:09 +00:00
dependabot[bot]
39aa878d43 Bump moment from 2.29.1 to 2.29.4
Bumps [moment](https://github.com/moment/moment) from 2.29.1 to 2.29.4.
- [Release notes](https://github.com/moment/moment/releases)
- [Changelog](https://github.com/moment/moment/blob/develop/CHANGELOG.md)
- [Commits](https://github.com/moment/moment/compare/2.29.1...2.29.4)

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

Signed-off-by: dependabot[bot] <support@github.com>
2022-11-18 17:28:47 +00:00
dependabot[bot]
171e161706 Bump @nextcloud/vue from 4.2.0 to 4.4.0
Bumps [@nextcloud/vue](https://github.com/nextcloud/nextcloud-vue) from 4.2.0 to 4.4.0.
- [Release notes](https://github.com/nextcloud/nextcloud-vue/releases)
- [Changelog](https://github.com/nextcloud/nextcloud-vue/blob/master/CHANGELOG.md)
- [Commits](https://github.com/nextcloud/nextcloud-vue/compare/v4.2.0...v4.4.0)

---
updated-dependencies:
- dependency-name: "@nextcloud/vue"
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2022-11-18 17:28:38 +00:00
dependabot[bot]
7bea294e47 Bump @babel/runtime from 7.16.0 to 7.20.1
Bumps [@babel/runtime](https://github.com/babel/babel/tree/HEAD/packages/babel-runtime) from 7.16.0 to 7.20.1.
- [Release notes](https://github.com/babel/babel/releases)
- [Changelog](https://github.com/babel/babel/blob/main/CHANGELOG.md)
- [Commits](https://github.com/babel/babel/commits/v7.20.1/packages/babel-runtime)

---
updated-dependencies:
- dependency-name: "@babel/runtime"
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2022-11-18 17:28:26 +00:00
dependabot[bot]
7e4d501884 Bump @nextcloud/dialogs from 3.1.2 to 3.2.0
Bumps [@nextcloud/dialogs](https://github.com/nextcloud/nextcloud-dialogs) from 3.1.2 to 3.2.0.
- [Release notes](https://github.com/nextcloud/nextcloud-dialogs/releases)
- [Changelog](https://github.com/nextcloud/nextcloud-dialogs/blob/master/CHANGELOG.md)
- [Commits](https://github.com/nextcloud/nextcloud-dialogs/compare/v3.1.2...v3.2.0)

---
updated-dependencies:
- dependency-name: "@nextcloud/dialogs"
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2022-11-18 17:28:10 +00:00
Julius Härtl
e9e55f8091 Bump version to 1.6.5
Signed-off-by: Julius Härtl <jus@bitgrid.net>
2022-11-18 16:27:25 +01:00
Nextcloud bot
aff4cb6ac0 [tx-robot] updated from transifex
Signed-off-by: Nextcloud bot <bot@nextcloud.com>
2022-11-17 02:21:32 +00:00
Nextcloud bot
69e8fe23b4 [tx-robot] updated from transifex
Signed-off-by: Nextcloud bot <bot@nextcloud.com>
2022-11-12 02:22:19 +00:00
Julius Härtl
ec9c4220b5 Merge pull request #4203 from nextcloud/backport/4180/stable23 2022-11-11 14:59:19 +01:00
Nextcloud bot
babf767286 [tx-robot] updated from transifex
Signed-off-by: Nextcloud bot <bot@nextcloud.com>
2022-11-11 02:21:24 +00:00
Johannes Szeibert
5e3eca1e29 minor style fixes
Signed-off-by: Johannes Szeibert <johannes@szeibert.de>
2022-11-10 08:31:51 +00:00
Nextcloud bot
983365830c [tx-robot] updated from transifex
Signed-off-by: Nextcloud bot <bot@nextcloud.com>
2022-11-10 02:23:02 +00:00
Marcel Klehr
139e901af6 Merge pull request #4183 from nextcloud/backport/4101/stable23 2022-11-09 14:13:24 +01:00
Nextcloud bot
f98ab9ed8e [tx-robot] updated from transifex
Signed-off-by: Nextcloud bot <bot@nextcloud.com>
2022-11-09 02:21:49 +00:00
Nextcloud bot
d34562b7bd [tx-robot] updated from transifex
Signed-off-by: Nextcloud bot <bot@nextcloud.com>
2022-11-05 02:22:36 +00:00
Julius Härtl
512b0d7664 Merge pull request #4177 from nextcloud/backport/4173/stable23 2022-11-04 08:25:57 +01:00
Nextcloud bot
d2c81dde9c [tx-robot] updated from transifex
Signed-off-by: Nextcloud bot <bot@nextcloud.com>
2022-11-04 02:21:22 +00:00
Julius Härtl
2dff9d5a09 Fix test compatibility with PHP 7.3
Signed-off-by: Julius Härtl <jus@bitgrid.net>
2022-11-03 21:46:05 +01:00
Julius Härtl
76e08f2a21 Fix validation of attachment data
Signed-off-by: Julius Härtl <jus@bitgrid.net>
2022-11-03 20:43:09 +01:00
Julius Härtl
d83fdfcb0a Unify getting the share for attachments
Signed-off-by: Julius Härtl <jus@bitgrid.net>
2022-11-03 20:42:46 +01:00
Julius Härtl
9d498ca84c Add integration test for attachment handling on cards
Signed-off-by: Julius Härtl <jus@bitgrid.net>
2022-11-03 20:42:46 +01:00
mokkin
2d05e84de9 disables autocomplete on card creation
solves https://github.com/nextcloud/deck/issues/4083

Signed-off-by: mokkin <markus@haybach.com>
2022-11-03 18:26:25 +01:00
Nextcloud bot
3bf57dd6d7 [tx-robot] updated from transifex
Signed-off-by: Nextcloud bot <bot@nextcloud.com>
2022-11-03 02:21:19 +00:00
Nextcloud bot
27110581fd [tx-robot] updated from transifex
Signed-off-by: Nextcloud bot <bot@nextcloud.com>
2022-11-02 02:21:37 +00:00
Nextcloud bot
c735e4b017 [tx-robot] updated from transifex
Signed-off-by: Nextcloud bot <bot@nextcloud.com>
2022-11-01 02:21:44 +00:00
Julius Härtl
c20da909a5 Merge pull request #4175 from nextcloud/backport/4059/stable23 2022-10-31 21:17:45 +01:00
Julius Härtl
2d64c52079 Adapt to older PHP versions
Signed-off-by: Julius Härtl <jus@bitgrid.net>
2022-10-31 14:22:41 +01:00
Julius Härtl
53dc650e51 Add some tests for parameter validation
Signed-off-by: Julius Härtl <jus@bitgrid.net>
2022-10-31 14:22:41 +01:00
Luka Trovic
ff77c45a50 feat: add validators to check values in services
Signed-off-by: Luka Trovic <luka@nextcloud.com>
2022-10-31 14:22:38 +01:00
Nextcloud bot
357f30464d [tx-robot] updated from transifex
Signed-off-by: Nextcloud bot <bot@nextcloud.com>
2022-10-31 02:21:08 +00:00
Nextcloud bot
561d081a95 [tx-robot] updated from transifex
Signed-off-by: Nextcloud bot <bot@nextcloud.com>
2022-10-30 02:24:14 +00:00
Nextcloud bot
34541cab03 [tx-robot] updated from transifex
Signed-off-by: Nextcloud bot <bot@nextcloud.com>
2022-10-29 02:23:27 +00:00
Marcel Klehr
0dfbcd4631 Merge pull request #4159 from nextcloud/release/1.6.4 2022-10-27 11:47:12 +02:00
Nextcloud bot
2742334cf9 [tx-robot] updated from transifex
Signed-off-by: Nextcloud bot <bot@nextcloud.com>
2022-10-27 02:21:19 +00:00
Marcel Klehr
1a1cb058d3 v1.6.4
Signed-off-by: Marcel Klehr <mklehr@gmx.net>
2022-10-26 16:19:23 +02:00
Nextcloud bot
03dd572d0c [tx-robot] updated from transifex
Signed-off-by: Nextcloud bot <bot@nextcloud.com>
2022-10-25 02:26:20 +00:00
Nextcloud bot
59efdf8964 [tx-robot] updated from transifex
Signed-off-by: Nextcloud bot <bot@nextcloud.com>
2022-10-24 02:21:58 +00:00
Julius Härtl
631e688a03 Merge pull request #4133 from nextcloud/backport/4065/stable23 2022-10-18 19:22:04 +02:00
Julius Härtl
1db5d7cf47 Cache user membership for circles
Signed-off-by: Julius Härtl <jus@bitgrid.net>
2022-10-18 11:35:12 +02:00
Julius Härtl
6f6d2df9de Pin postgres to 14
Signed-off-by: Julius Härtl <jus@bitgrid.net>
2022-10-18 11:30:30 +02:00
Julius Härtl
a5eecd9d19 Merge pull request #4119 from nextcloud/backport/3439/stable23 2022-10-18 11:26:24 +02:00
Nextcloud bot
7e949052dc [tx-robot] updated from transifex
Signed-off-by: Nextcloud bot <bot@nextcloud.com>
2022-10-18 02:21:43 +00:00
Julius Härtl
4cc32a1e8b Activity: Set event link also for notifications that get emitted from activity
Signed-off-by: Julius Härtl <jus@bitgrid.net>
2022-10-14 10:51:08 +00:00
Nextcloud bot
bf9f9da3b0 [tx-robot] updated from transifex
Signed-off-by: Nextcloud bot <bot@nextcloud.com>
2022-10-13 02:22:25 +00:00
Nextcloud bot
f70595ea2a [tx-robot] updated from transifex
Signed-off-by: Nextcloud bot <bot@nextcloud.com>
2022-10-08 02:21:54 +00:00
Marcel Klehr
e6f9c136e9 Merge pull request #4020 from nextcloud/backport/4014/stable23 2022-10-07 12:07:49 +02:00
Nextcloud bot
632cd3079f [tx-robot] Update transifex configuration
Signed-off-by: Nextcloud bot <bot@nextcloud.com>
2022-10-07 02:21:52 +00:00
Nextcloud bot
578fd46765 [tx-robot] updated from transifex
Signed-off-by: Nextcloud bot <bot@nextcloud.com>
2022-10-06 02:26:18 +00:00
Nextcloud bot
3dec4b7f2c [tx-robot] updated from transifex
Signed-off-by: Nextcloud bot <bot@nextcloud.com>
2022-10-05 02:21:25 +00:00
Côme Chilliet
3f7a2b31b3 Merge pull request #4094 from nextcloud/migrate-stable23-christophwurst-package
Migrate to nextcloud/OCP package in stable23
2022-10-02 10:39:42 +02:00
Joas Schilling
c072d713cb Migrate to nextcloud/OCP package in stable23
Signed-off-by: Joas Schilling <coding@schilljs.com>
2022-10-02 09:18:37 +02:00
Nextcloud bot
9fe5ec7fab [tx-robot] updated from transifex
Signed-off-by: Nextcloud bot <bot@nextcloud.com>
2022-10-01 02:24:40 +00:00
Nextcloud bot
3ec0579256 [tx-robot] updated from transifex
Signed-off-by: Nextcloud bot <bot@nextcloud.com>
2022-09-30 03:23:43 +00:00
Nextcloud bot
ebc4ad45ff [tx-robot] updated from transifex
Signed-off-by: Nextcloud bot <bot@nextcloud.com>
2022-09-28 02:46:19 +00:00
Nextcloud bot
a4d89a5b3d [tx-robot] updated from transifex
Signed-off-by: Nextcloud bot <bot@nextcloud.com>
2022-09-26 02:47:20 +00:00
Nextcloud bot
bf504624f4 [tx-robot] updated from transifex
Signed-off-by: Nextcloud bot <bot@nextcloud.com>
2022-09-24 02:47:25 +00:00
Nextcloud bot
4e7ce3e061 [tx-robot] updated from transifex
Signed-off-by: Nextcloud bot <bot@nextcloud.com>
2022-09-23 02:42:12 +00:00
Nextcloud bot
96da8d8b06 [tx-robot] updated from transifex
Signed-off-by: Nextcloud bot <bot@nextcloud.com>
2022-09-22 02:45:51 +00:00
Nextcloud bot
8d51826169 [tx-robot] updated from transifex
Signed-off-by: Nextcloud bot <bot@nextcloud.com>
2022-09-20 02:45:59 +00:00
Nextcloud bot
188e60c7d0 [tx-robot] updated from transifex
Signed-off-by: Nextcloud bot <bot@nextcloud.com>
2022-09-17 02:45:16 +00:00
Nextcloud bot
fc47f8b719 [tx-robot] updated from transifex
Signed-off-by: Nextcloud bot <bot@nextcloud.com>
2022-09-16 02:39:40 +00:00
Nextcloud bot
44b744017d [tx-robot] updated from transifex
Signed-off-by: Nextcloud bot <bot@nextcloud.com>
2022-09-15 02:45:11 +00:00
Nextcloud bot
693acfa8df [tx-robot] updated from transifex
Signed-off-by: Nextcloud bot <bot@nextcloud.com>
2022-09-14 02:39:56 +00:00
Nextcloud bot
655f82e1cf [tx-robot] updated from transifex
Signed-off-by: Nextcloud bot <bot@nextcloud.com>
2022-09-13 02:40:43 +00:00
Nextcloud bot
4e54a77075 [tx-robot] updated from transifex
Signed-off-by: Nextcloud bot <bot@nextcloud.com>
2022-09-12 02:39:44 +00:00
Nextcloud bot
ce123d10e5 [tx-robot] updated from transifex
Signed-off-by: Nextcloud bot <bot@nextcloud.com>
2022-09-09 02:40:43 +00:00
Nextcloud bot
82c9e7d174 [tx-robot] updated from transifex
Signed-off-by: Nextcloud bot <bot@nextcloud.com>
2022-09-08 02:40:34 +00:00
Nextcloud bot
f8cf9a1856 [tx-robot] updated from transifex
Signed-off-by: Nextcloud bot <bot@nextcloud.com>
2022-09-07 02:40:03 +00:00
Nextcloud bot
6219004c51 [tx-robot] updated from transifex
Signed-off-by: Nextcloud bot <bot@nextcloud.com>
2022-09-06 02:39:40 +00:00
Nextcloud bot
28a65a2e98 [tx-robot] updated from transifex
Signed-off-by: Nextcloud bot <bot@nextcloud.com>
2022-09-05 02:39:01 +00:00
Nextcloud bot
edcb5c4eca [tx-robot] updated from transifex
Signed-off-by: Nextcloud bot <bot@nextcloud.com>
2022-09-04 02:38:04 +00:00
Nextcloud bot
05109c351f [tx-robot] updated from transifex
Signed-off-by: Nextcloud bot <bot@nextcloud.com>
2022-09-03 02:44:38 +00:00
Robin Appelman
c3a5898271 disable Create card button while no stack is chosen
make the `isBoardAndStackChoosen` method do what it says

Signed-off-by: Robin Appelman <robin@icewind.nl>
2022-09-02 11:12:48 +00:00
Nextcloud bot
5befacc95e [tx-robot] updated from transifex
Signed-off-by: Nextcloud bot <bot@nextcloud.com>
2022-09-01 02:50:09 +00:00
Nextcloud bot
3e0a0e0a0e [tx-robot] updated from transifex
Signed-off-by: Nextcloud bot <bot@nextcloud.com>
2022-08-28 02:36:34 +00:00
Nextcloud bot
5f4bd548fe [tx-robot] updated from transifex
Signed-off-by: Nextcloud bot <bot@nextcloud.com>
2022-08-27 02:36:46 +00:00
Julius Härtl
ae7e4e04ad Merge pull request #3998 from nextcloud/backport/3980/stable23 2022-08-26 10:50:31 +02:00
Julius Härtl
89173f4ef7 Make CappedMemoryCache usage compatible with older releases
Signed-off-by: Julius Härtl <jus@bitgrid.net>
2022-08-26 10:04:07 +02:00
Raul Ferreira Fuentes
ff09639924 Merge pull request #3996 from nextcloud/backport/3982/stable23
[stable23] Improve CalDAV integration performance
2022-08-25 12:13:49 +00:00
Julius Härtl
811e19db0e Use capped memory cache for board permissions
Signed-off-by: Julius Härtl <jus@bitgrid.net>
2022-08-25 10:47:43 +00:00
Julius Härtl
1cf0930f2a Avoid fetching archived cards for calendars
Signed-off-by: Julius Härtl <jus@bitgrid.net>
2022-08-25 10:47:37 +00:00
Julius Härtl
8154741d6d Avoid querying each card when getting the calendars only
Signed-off-by: Julius Härtl <jus@bitgrid.net>
2022-08-25 10:47:36 +00:00
Nextcloud bot
e4a9482979 [tx-robot] updated from transifex
Signed-off-by: Nextcloud bot <bot@nextcloud.com>
2022-08-24 02:39:43 +00:00
Nextcloud bot
b23d53e541 [tx-robot] updated from transifex
Signed-off-by: Nextcloud bot <bot@nextcloud.com>
2022-08-22 02:37:17 +00:00
Nextcloud bot
65e4e65bd6 [tx-robot] updated from transifex
Signed-off-by: Nextcloud bot <bot@nextcloud.com>
2022-08-21 02:36:37 +00:00
Nextcloud bot
2ebcda8ca9 [tx-robot] updated from transifex
Signed-off-by: Nextcloud bot <bot@nextcloud.com>
2022-08-20 02:37:00 +00:00
Nextcloud bot
e686c6b32b [tx-robot] updated from transifex
Signed-off-by: Nextcloud bot <bot@nextcloud.com>
2022-08-13 02:37:52 +00:00
Nextcloud bot
f3526cc996 [tx-robot] updated from transifex
Signed-off-by: Nextcloud bot <bot@nextcloud.com>
2022-08-12 02:36:58 +00:00
Nextcloud bot
673addc6ba [tx-robot] updated from transifex
Signed-off-by: Nextcloud bot <bot@nextcloud.com>
2022-08-09 02:36:39 +00:00
Julius Härtl
7a5c745bdf Merge pull request #3960 from nextcloud/backport/3952/stable23 2022-08-08 09:32:35 +02:00
Julius Härtl
c46ca8a03a Fetch attachment folder for the correct user during cron job
Signed-off-by: Julius Härtl <jus@bitgrid.net>
2022-08-08 07:00:56 +00:00
Nextcloud bot
b686cae095 [tx-robot] updated from transifex
Signed-off-by: Nextcloud bot <bot@nextcloud.com>
2022-08-07 02:41:01 +00:00
Nextcloud bot
4c7f64e427 [tx-robot] updated from transifex
Signed-off-by: Nextcloud bot <bot@nextcloud.com>
2022-08-06 02:37:37 +00:00
Nextcloud bot
699b089655 [tx-robot] updated from transifex
Signed-off-by: Nextcloud bot <bot@nextcloud.com>
2022-08-05 02:13:44 +00:00
Nextcloud bot
3cc1db09d1 [tx-robot] updated from transifex
Signed-off-by: Nextcloud bot <bot@nextcloud.com>
2022-08-03 02:13:45 +00:00
Julius Härtl
912dd4778e Merge pull request #3926 from nextcloud/backport/3898/stable23
[stable23] Switch to 'markdown-it-task-checkbox' for rendering of task lists
2022-08-01 22:05:56 +02:00
Nextcloud bot
cd3ff3e8bc [tx-robot] updated from transifex
Signed-off-by: Nextcloud bot <bot@nextcloud.com>
2022-07-29 02:13:36 +00:00
Nextcloud bot
28c39ae6b4 [tx-robot] updated from transifex
Signed-off-by: Nextcloud bot <bot@nextcloud.com>
2022-07-28 02:13:42 +00:00
Nextcloud bot
efa7838993 [tx-robot] updated from transifex
Signed-off-by: Nextcloud bot <bot@nextcloud.com>
2022-07-27 02:13:40 +00:00
Nextcloud bot
04c131d58f [tx-robot] updated from transifex
Signed-off-by: Nextcloud bot <bot@nextcloud.com>
2022-07-25 02:39:12 +00:00
Nextcloud bot
da8e2886ae [tx-robot] updated from transifex
Signed-off-by: Nextcloud bot <bot@nextcloud.com>
2022-07-24 02:40:50 +00:00
Nextcloud bot
57c7ac6ebf [tx-robot] updated from transifex
Signed-off-by: Nextcloud bot <bot@nextcloud.com>
2022-07-23 02:38:37 +00:00
Nextcloud bot
cfc5bb266d [tx-robot] updated from transifex
Signed-off-by: Nextcloud bot <bot@nextcloud.com>
2022-07-21 02:38:55 +00:00
q-wertz
04619d16dd Switch to 'markdown-it-task-checkbox' for rendering of task lists
Signed-off-by: q-wertz <clemens.sonnleitner@web.de>
2022-07-19 09:49:38 +02:00
Nextcloud bot
9f709a851f [tx-robot] updated from transifex
Signed-off-by: Nextcloud bot <bot@nextcloud.com>
2022-07-19 02:38:02 +00:00
Nextcloud bot
0b1accc395 [tx-robot] updated from transifex
Signed-off-by: Nextcloud bot <bot@nextcloud.com>
2022-07-18 02:37:18 +00:00
Nextcloud bot
db1167a05f [tx-robot] updated from transifex
Signed-off-by: Nextcloud bot <bot@nextcloud.com>
2022-07-14 02:37:46 +00:00
Julius Härtl
1f4d1a7ea2 Merge pull request #3918 from nextcloud/backport/3916/stable23
[stable23] Prevent opening card and applyLabelFilter on card drag end
2022-07-12 16:36:58 +02:00
Julien Veyssier
0f83e606a5 refs #2594 prevent opening card and applyLabelFilter on card drag end
Signed-off-by: Julien Veyssier <eneiluj@posteo.net>
2022-07-12 11:57:24 +00:00
Nextcloud bot
6edb957849 [tx-robot] updated from transifex
Signed-off-by: Nextcloud bot <bot@nextcloud.com>
2022-07-08 02:37:18 +00:00
Nextcloud bot
1151f907f5 [tx-robot] updated from transifex
Signed-off-by: Nextcloud bot <bot@nextcloud.com>
2022-07-06 02:35:57 +00:00
Nextcloud bot
c330cff60b [tx-robot] updated from transifex
Signed-off-by: Nextcloud bot <bot@nextcloud.com>
2022-07-04 02:35:51 +00:00
Nextcloud bot
809383702a [tx-robot] updated from transifex
Signed-off-by: Nextcloud bot <bot@nextcloud.com>
2022-07-02 02:37:25 +00:00
Raul Ferreira Fuentes
a341206ff0 Merge pull request #3886 from nextcloud/backport/3884/stable23
[stable23] Fix z-index for deck sidebar
2022-06-28 15:26:13 +02:00
Raul
b33fe37499 Fix z-index for deck sidebar
The sidebar would previously render above the user menu (logout, settings, etc)

Signed-off-by: Raul <r.ferreira.fuentes@gmail.com>
2022-06-28 12:40:37 +00:00
Nextcloud bot
05f764bc17 [tx-robot] updated from transifex
Signed-off-by: Nextcloud bot <bot@nextcloud.com>
2022-06-22 02:40:28 +00:00
Nextcloud bot
f5307362ee [tx-robot] updated from transifex
Signed-off-by: Nextcloud bot <bot@nextcloud.com>
2022-06-14 02:41:54 +00:00
Nextcloud bot
ce40630b2f [tx-robot] updated from transifex
Signed-off-by: Nextcloud bot <bot@nextcloud.com>
2022-06-12 02:41:06 +00:00
Nextcloud bot
afbc1c263c [tx-robot] updated from transifex
Signed-off-by: Nextcloud bot <bot@nextcloud.com>
2022-06-09 02:45:34 +00:00
Nextcloud bot
24a2ee5bd4 [tx-robot] updated from transifex
Signed-off-by: Nextcloud bot <bot@nextcloud.com>
2022-06-07 02:44:08 +00:00
Nextcloud bot
8542dfc7e1 [tx-robot] updated from transifex
Signed-off-by: Nextcloud bot <bot@nextcloud.com>
2022-06-03 02:47:54 +00:00
Nextcloud bot
165604cbac [tx-robot] updated from transifex
Signed-off-by: Nextcloud bot <bot@nextcloud.com>
2022-06-02 02:47:11 +00:00
Julius Härtl
841f66e854 Merge pull request #3848 from nextcloud/release/1.6.3
release/1.6.3
2022-06-01 11:30:13 +02:00
Julius Härtl
c4a3eec533 Pin engines
Signed-off-by: Julius Härtl <jus@bitgrid.net>
2022-06-01 11:26:40 +02:00
Julius Härtl
9fb6db4c31 Merge pull request #3845 from nextcloud/backport/3528/stable23
[stable23] Add app config to toggle the default calendar setting as an admin
2022-05-30 18:20:52 +02:00
Julius Härtl
47b71f93c8 Add app config to toggle the default calendar setting as an admin
Signed-off-by: Julius Härtl <jus@bitgrid.net>
2022-05-30 12:42:32 +00:00
Luka Trovic
42c01d7c3b changes for 1.6.3
Signed-off-by: Luka Trovic <luka@nextcloud.com>
2022-05-29 22:39:56 +02:00
Nextcloud bot
c3b610bc38 [tx-robot] updated from transifex
Signed-off-by: Nextcloud bot <bot@nextcloud.com>
2022-05-29 02:46:55 +00:00
Nextcloud bot
2d7edbf2f2 [tx-robot] updated from transifex
Signed-off-by: Nextcloud bot <bot@nextcloud.com>
2022-05-28 02:43:26 +00:00
Nextcloud bot
26f1288440 [tx-robot] updated from transifex
Signed-off-by: Nextcloud bot <bot@nextcloud.com>
2022-05-26 02:44:19 +00:00
Nextcloud bot
55f52d5d68 [tx-robot] updated from transifex
Signed-off-by: Nextcloud bot <bot@nextcloud.com>
2022-05-24 02:47:14 +00:00
Nextcloud bot
e8ed6fb89e [tx-robot] updated from transifex
Signed-off-by: Nextcloud bot <bot@nextcloud.com>
2022-05-23 02:43:47 +00:00
Nextcloud bot
36e8a88588 [tx-robot] updated from transifex
Signed-off-by: Nextcloud bot <bot@nextcloud.com>
2022-05-22 02:43:42 +00:00
Nextcloud bot
aa7dfbd749 [tx-robot] updated from transifex
Signed-off-by: Nextcloud bot <bot@nextcloud.com>
2022-05-21 02:47:14 +00:00
Nextcloud bot
cec655c1fa [tx-robot] updated from transifex
Signed-off-by: Nextcloud bot <bot@nextcloud.com>
2022-05-20 02:46:29 +00:00
Julius Härtl
228457e387 Merge pull request #3816 from nextcloud/backport/3811/stable23
[stable23] Align Duedate-delete icon properly - fixes nextcloud/deck#3791
2022-05-13 09:34:59 +02:00
Nextcloud bot
66f985a9fb [tx-robot] updated from transifex
Signed-off-by: Nextcloud bot <bot@nextcloud.com>
2022-05-13 02:41:49 +00:00
ben
54706cfe8d Align Duedate-delete icon properly - fixes nextcloud/deck#3791
Signed-off-by: ben <ben@ro.tt>
2022-05-12 14:54:20 +00:00
Julius Härtl
48bf117339 Merge pull request #3805 from nextcloud/backport/3682/stable23
[stable23] Increase file count after sharing
2022-05-12 09:57:43 +02:00
Julius Härtl
d585e14240 Fix php-cs-fixer failure
Signed-off-by: Julius Härtl <jus@bitgrid.net>
2022-05-12 09:09:32 +02:00
Julius Härtl
6a790154c2 Move all caching to helper
Signed-off-by: Julius Härtl <jus@bitgrid.net>
2022-05-11 14:44:04 +00:00
Luka Trovic
33913be41f fix: move shares count cache logic to the DeckShareProvider
Signed-off-by: Luka Trovic <luka@nextcloud.com>

fix: conflicts

Signed-off-by: Luka Trovic <luka@nextcloud.com>

fix: conflicts and test issues

Signed-off-by: Luka Trovic <luka@nextcloud.com>
2022-05-11 14:44:04 +00:00
Luka Trovic
393e08519d fix: update attachments count when sharing
Signed-off-by: Luka Trovic <luka@nextcloud.com>
2022-05-11 14:44:03 +00:00
Luka Trovic
dfcf096caa fix: increase file count after sharing
Signed-off-by: Luka Trovic <luka@nextcloud.com>
2022-05-11 14:44:03 +00:00
Julius Härtl
4859456588 Merge pull request #3795 from nextcloud/backport/3681/stable23-bis
[stable23] Show cards after moving into another list
2022-05-10 12:33:22 +02:00
Luka Trovic
366df500e6 fix: feedback
Signed-off-by: Luka Trovic <luka@nextcloud.com>
2022-05-09 16:35:23 +01:00
Luka Trovic
7c6cdfd498 fix: show card after moving into another list
Signed-off-by: Luka Trovic <luka@nextcloud.com>
2022-05-09 16:34:15 +01:00
Nextcloud bot
29fb2e63ad [tx-robot] updated from transifex
Signed-off-by: Nextcloud bot <bot@nextcloud.com>
2022-05-06 02:41:17 +00:00
Nextcloud bot
e021d400f3 [tx-robot] updated from transifex
Signed-off-by: Nextcloud bot <bot@nextcloud.com>
2022-05-05 02:41:07 +00:00
Julius Härtl
139a8a0fb7 Merge pull request #3780 from nextcloud/backport/3777/stable23
[stable23] Fetch full board data after cloning
2022-05-04 17:17:10 +02:00
Julius Härtl
43e91b9082 Fetch full board data after cloning
Signed-off-by: Julius Härtl <jus@bitgrid.net>
2022-05-04 10:29:29 +00:00
Nextcloud bot
316f5fb25a [tx-robot] updated from transifex
Signed-off-by: Nextcloud bot <bot@nextcloud.com>
2022-05-04 02:42:59 +00:00
Nextcloud bot
008b41a2ad [tx-robot] updated from transifex
Signed-off-by: Nextcloud bot <bot@nextcloud.com>
2022-05-03 02:41:25 +00:00
Nextcloud bot
e5e836101c [tx-robot] updated from transifex
Signed-off-by: Nextcloud bot <bot@nextcloud.com>
2022-04-30 02:39:56 +00:00
Julius Härtl
dd261fec6c Merge pull request #3770 from nextcloud/backport/3769/stable23
[stable23] Handle qb mapper exception messages properly
2022-04-29 18:45:33 +02:00
Julius Härtl
5214377341 Handle qb mapper exception messages properly
Signed-off-by: Julius Härtl <jus@bitgrid.net>
2022-04-29 15:28:34 +00:00
Julius Härtl
ad4174f6bd Bump version to 1.6.2
Signed-off-by: Julius Härtl <jus@bitgrid.net>
2022-04-29 16:32:07 +02:00
Julius Härtl
976e68e40c Merge pull request #3766 from nextcloud/backport/3761/stable23
[stable23] Fix text selection in dark mode and modal view
2022-04-29 16:29:09 +02:00
Julius Härtl
9c33cdf3d3 Fix text selection in dark mode and modal view
Signed-off-by: Julius Härtl <jus@bitgrid.net>
2022-04-29 15:57:53 +02:00
Nextcloud bot
d5eea830a5 [tx-robot] updated from transifex
Signed-off-by: Nextcloud bot <bot@nextcloud.com>
2022-04-29 02:41:08 +00:00
Julius Härtl
c8e94c2de4 Merge pull request #3755 from nextcloud/backport/3745/stable23
[stable23] Add missing indices
2022-04-28 10:11:28 +02:00
Nextcloud bot
f838d91304 [tx-robot] updated from transifex
Signed-off-by: Nextcloud bot <bot@nextcloud.com>
2022-04-28 02:41:23 +00:00
Julius Härtl
f4c615252b Add missing indices
Signed-off-by: Julius Härtl <jus@bitgrid.net>
2022-04-26 16:26:29 +00:00
Nextcloud bot
f084193daf [tx-robot] updated from transifex
Signed-off-by: Nextcloud bot <bot@nextcloud.com>
2022-04-24 02:41:16 +00:00
Nextcloud bot
20b9b1e1e9 [tx-robot] updated from transifex
Signed-off-by: Nextcloud bot <bot@nextcloud.com>
2022-04-23 02:39:29 +00:00
Nextcloud bot
eae7fd88d8 [tx-robot] updated from transifex
Signed-off-by: Nextcloud bot <bot@nextcloud.com>
2022-04-22 02:40:27 +00:00
Julius Härtl
983247b9e8 Merge pull request #3741 from nextcloud/backport/stable23/3683
[stable23] Fix paramter replacements when creating deck cards from talk messages
2022-04-21 09:36:47 +02:00
Joas Schilling
afbd6ce1b0 Fix node linting
Signed-off-by: Joas Schilling <coding@schilljs.com>
2022-04-20 18:39:44 +02:00
Joas Schilling
31311f1123 Fix paramter replacements when creating deck cards from talk messages
Signed-off-by: Joas Schilling <coding@schilljs.com>
2022-04-20 18:39:33 +02:00
Julius Härtl
7a3fb009d6 Merge pull request #3734 from nextcloud/backport/3692/stable23
[stable23] Fix hidden attachment icon on archived cards
2022-04-20 18:14:17 +02:00
Luka Trovic
a7974d32ee fix: hidden attachment icon on archived cards
Signed-off-by: Luka Trovic <luka@nextcloud.com>
2022-04-20 14:22:25 +00:00
Julius Härtl
558ac704f5 Merge pull request #3497 from nextcloud/bugfix/noid/share-query-opt
[stable23] Use explicit cast to make use of index
2022-04-19 21:43:16 +02:00
Nextcloud bot
0c98a04699 [tx-robot] updated from transifex
Signed-off-by: Nextcloud bot <bot@nextcloud.com>
2022-04-17 02:38:10 +00:00
Nextcloud bot
4f9b71dcad [tx-robot] updated from transifex
Signed-off-by: Nextcloud bot <bot@nextcloud.com>
2022-04-14 02:42:23 +00:00
Nextcloud bot
b2367314fc [tx-robot] updated from transifex
Signed-off-by: Nextcloud bot <bot@nextcloud.com>
2022-04-13 02:40:14 +00:00
Julius Härtl
c04e5c1681 Use explicit cast to make use of index
Signed-off-by: Julius Härtl <jus@bitgrid.net>
2022-04-12 10:13:22 +02:00
Julius Härtl
541eeb0b06 Merge pull request #3664 from nextcloud/backport/stable23/2496
[stable23] Transfer ownership
2022-04-11 18:29:14 +02:00
Julius Härtl
1bd62d7fa2 Merge pull request #3713 from nextcloud/backport/3670/stable23
[stable23] Properly check for the stack AND setting board permissions
2022-04-11 18:28:14 +02:00
Julius Härtl
03a9915dd5 Merge pull request #3717 from nextcloud/backport/3625/stable23
[stable23] Fix: Check all circle shares for permissions
2022-04-11 18:27:51 +02:00
Bink
b6cce55325 Fix: Check all circle shares for permissions instead of returning after the first 2022-04-11 14:45:58 +00:00
Julius Härtl
47345e10f1 Properly check for the stack AND setting board permissions
Signed-off-by: Julius Härtl <jus@bitgrid.net>
2022-04-11 14:35:10 +00:00
Julius Härtl
39784dc940 Remove unused argument
Signed-off-by: Julius Härtl <jus@bitgrid.net>
2022-04-11 16:25:54 +02:00
Julius Härtl
1cdea9dba0 Merge pull request #3706 from nextcloud/backport/3501/stable23
[stable23] Add a missing translation - not found in transifex
2022-04-11 09:53:30 +02:00
Nextcloud bot
49591d6699 [tx-robot] updated from transifex
Signed-off-by: Nextcloud bot <bot@nextcloud.com>
2022-04-09 02:39:15 +00:00
Luka Trovic
101e15c1b7 add a missing translation - not found in transifex
Signed-off-by: Luka Trovic <luka@nextcloud.com>
2022-04-08 19:35:33 +00:00
Nextcloud bot
4d9743f8d3 [tx-robot] updated from transifex
Signed-off-by: Nextcloud bot <bot@nextcloud.com>
2022-04-06 02:39:52 +00:00
Nextcloud bot
51433ac4a9 [tx-robot] updated from transifex
Signed-off-by: Nextcloud bot <bot@nextcloud.com>
2022-04-05 02:39:55 +00:00
Nextcloud bot
2cde9ac16c [tx-robot] updated from transifex
Signed-off-by: Nextcloud bot <bot@nextcloud.com>
2022-03-31 02:41:41 +00:00
Nextcloud bot
fb2a38e684 [tx-robot] updated from transifex
Signed-off-by: Nextcloud bot <bot@nextcloud.com>
2022-03-30 02:39:31 +00:00
Nextcloud bot
ff4f33264b [tx-robot] updated from transifex
Signed-off-by: Nextcloud bot <bot@nextcloud.com>
2022-03-29 02:39:53 +00:00
Nextcloud bot
879e936728 [tx-robot] updated from transifex
Signed-off-by: Nextcloud bot <bot@nextcloud.com>
2022-03-28 02:39:00 +00:00
Nextcloud bot
c47bfd21eb [tx-robot] updated from transifex
Signed-off-by: Nextcloud bot <bot@nextcloud.com>
2022-03-25 03:07:27 +00:00
Nextcloud bot
c59b6f8882 [tx-robot] updated from transifex
Signed-off-by: Nextcloud bot <bot@nextcloud.com>
2022-03-24 02:38:34 +00:00
Julius Härtl
4dabc22f27 lint: fix eslint
Signed-off-by: Julius Härtl <jus@bitgrid.net>
2022-03-22 16:03:22 +01:00
Julius Härtl
490cfb2396 Fix tests and move to 7.3 as a min php version
Signed-off-by: Julius Härtl <jus@bitgrid.net>
2022-03-22 16:03:22 +01:00
Julius Härtl
f840bbba11 PHP 7.2 compatbility
Signed-off-by: Julius Härtl <jus@bitgrid.net>
2022-03-22 16:03:22 +01:00
Julius Härtl
cffcd4fd66 Adjust documentaion wording
Signed-off-by: Julius Härtl <jus@bitgrid.net>
2022-03-22 14:22:40 +01:00
Julius Härtl
a3336cb0a0 Handle board exceptions more gracefully
Signed-off-by: Julius Härtl <jus@bitgrid.net>
2022-03-22 14:22:40 +01:00
Julius Härtl
379f1144b3 Cover case where the owner is preserved
Signed-off-by: Julius Härtl <jus@bitgrid.net>
2022-03-22 14:22:40 +01:00
Luka Trovic
eb69512b5f fix: feedback
Signed-off-by: Luka Trovic <luka@nextcloud.com>
2022-03-22 14:22:40 +01:00
Luka Trovic
f46c31f120 feat: add api endpoint and UI to transfer a board to a different user
Signed-off-by: Luka Trovic <luka@nextcloud.com>
2022-03-22 14:22:40 +01:00
Julius Härtl
b75fb76c08 fix: test cases using generator
Signed-off-by: Julius Härtl <jus@bitgrid.net>
2022-03-22 14:22:39 +01:00
Julius Härtl
3d925fb145 Reuse single board transfer for all user boards
Signed-off-by: Julius Härtl <jus@bitgrid.net>
2022-03-22 14:22:39 +01:00
Julius Härtl
7786c86f47 fix: Properly handle limited scope for remapping users
Signed-off-by: Julius Härtl <jus@bitgrid.net>
2022-03-22 14:22:39 +01:00
Julius Härtl
7f3a318fc5 cleanup test cases
Signed-off-by: Julius Härtl <jus@bitgrid.net>
2022-03-22 14:22:39 +01:00
Julius Härtl
525a14c428 Allow transfer of single boards
Signed-off-by: Julius Härtl <jus@bitgrid.net>
2022-03-22 14:22:39 +01:00
Julius Härtl
48ec97386c fix: Psalm
Signed-off-by: Julius Härtl <jus@bitgrid.net>
2022-03-22 14:22:33 +01:00
Julius Härtl
26c76fbb46 fix: unit tests
Signed-off-by: Julius Härtl <jus@bitgrid.net>
2022-03-22 14:15:15 +01:00
Luka Trovic
c95c96fb40 feat: add integration test for transferring board ownership with data
Signed-off-by: Luka Trovic <luka@nextcloud.com>
2022-03-22 14:15:15 +01:00
Luka Trovic
981fc8e16f fix: integration tests
Signed-off-by: Luka Trovic <luka@nextcloud.com>
2022-03-22 14:15:15 +01:00
Luka Trovic
b7f3c2d140 fix: unit test & psalm static code analysis issues
Signed-off-by: Luka Trovic <luka@nextcloud.com>
2022-03-22 14:15:15 +01:00
Max
b01d472745 fix: queries with the new base mapper in BoardMapper
Signed-off-by: Max <max@nextcloud.com>
2022-03-22 14:15:15 +01:00
Max
05dbf67531 fix: Assignment is the new AssignedUsers
Signed-off-by: Max <max@nextcloud.com>
2022-03-22 14:15:14 +01:00
Julius Härtl
a8c22482f6 Make queries work with the new base mapper
Signed-off-by: Julius Härtl <jus@bitgrid.net>

fix: conflicts
2022-03-22 14:15:14 +01:00
Julius Härtl
c0886cfc7a Just cleanup old ACL rules, there are none for the board owner so nothing to cleanup or persist there
Signed-off-by: Julius Härtl <jus@bitgrid.net>
2022-03-22 14:15:14 +01:00
Julius Härtl
7c68414435 Use proper description of what gets transferred
Signed-off-by: Julius Härtl <jus@bitgrid.net>
2022-03-22 14:15:14 +01:00
Julius Härtl
da745f9306 Fix card mapper query for transfer
Signed-off-by: Julius Härtl <jus@bitgrid.net>
2022-03-22 14:15:14 +01:00
Sergey Shliakhov
16413735d2 Fix coding styles
Signed-off-by: Julius Härtl <jus@bitgrid.net>
Signed-off-by: Max <max@nextcloud.com>
2022-03-22 14:15:14 +01:00
Sergey Shliakhov
e70e7128c0 Transfer deck ownership even if target user already participant of a board
https://github.com/nextcloud/deck/pull/1955#issuecomment-640392715
Signed-off-by: Sergey Shliakhov <husband.sergey@gmail.com>
2022-03-22 14:15:14 +01:00
Sergey Shliakhov
36a9d2e95c Check type before transfer card participants ownership
Signed-off-by: Sergey Shliakhov <husband.sergey@gmail.com>

temp
2022-03-22 14:15:14 +01:00
Sergey Shliakhov
ce85b4378f Fix wrong class name
Signed-off-by: Sergey Shliakhov <husband.sergey@gmail.com>
2022-03-22 14:15:14 +01:00
Sergey Shliakhov
d52697823d Fix code style
Signed-off-by: Sergey Shliakhov <husband.sergey@gmail.com>
2022-03-22 14:15:13 +01:00
Sergey Shliakhov
53d462b3da Add tests
Signed-off-by: Sergey Shliakhov <husband.sergey@gmail.com>
2022-03-22 14:15:13 +01:00
Sergey Shliakhov
fc9fa5dc25 Update docs
Signed-off-by: Sergey Shliakhov <husband.sergey@gmail.com>

fix: conflicts
2022-03-22 14:15:11 +01:00
Sergey Shliakhov
eb8a321637 Add deck:transfer-ownership command
Signed-off-by: Sergey Shliakhov <husband.sergey@gmail.com>
2022-03-22 14:14:46 +01:00
Julius Härtl
ce77725ef0 Merge pull request #3663 from nextcloud/backport/3560/stable23
[stable23] Sort boards non case sensitive
2022-03-22 09:53:25 +01:00
ben
a88e028793 fixes nextcloud/deck#3410
Signed-off-by: ben <git@rott.io>
2022-03-22 08:43:03 +00:00
Nextcloud bot
0655ab8d59 [tx-robot] updated from transifex
Signed-off-by: Nextcloud bot <bot@nextcloud.com>
2022-03-18 02:38:31 +00:00
Julius Härtl
2c5b8c5fba Merge pull request #3641 from nextcloud/backport/3635/stable23
[stable23] 🐛 Fix missing files sidebar
2022-03-17 08:19:44 +01:00
Vinicius Reis
1057dc7039 fix style-lint
Signed-off-by: Vinicius Reis <vinicius.reis@nextcloud.com>
2022-03-17 00:02:32 -03:00
Nextcloud bot
8274d090c3 [tx-robot] updated from transifex
Signed-off-by: Nextcloud bot <bot@nextcloud.com>
2022-03-17 02:41:19 +00:00
Vinicius Reis
c213727180 🐛 Fix missing files sidebar
Signed-off-by: Vinicius Reis <vinicius.reis@nextcloud.com>
2022-03-14 14:10:53 +00:00
Julius Härtl
a5565d192d Merge pull request #3630 from nextcloud/release/1.6.1
Release 1.6.1
2022-03-10 09:54:27 +01:00
Julius Härtl
d25c8f6945 Bump version to 1.6.1
Signed-off-by: Julius Härtl <jus@bitgrid.net>
2022-03-10 09:35:57 +01:00
Julius Härtl
e31ff9f247 Merge pull request #3626 from nextcloud/backport/3611/stable23
[stable23] Generate fixed link for activity emails
2022-03-09 20:19:59 +01:00
Luka Trovic
2d8bb1e654 fix: generate fixed link for activity emails
Signed-off-by: Luka Trovic <luka@nextcloud.com>

fix: generate fixed link for activity emails

Signed-off-by: Luka Trovic <luka@nextcloud.com>

Fix tests

Signed-off-by: Julius Härtl <jus@bitgrid.net>
2022-03-09 17:24:18 +00:00
Julius Härtl
18babccfe1 Create appstore-build-publish.yml 2022-03-04 13:58:09 +01:00
Julius Härtl
4646499917 Merge pull request #3618 from nextcloud/backport/3552/stable23
[stable23] return the selector for collections
2022-03-04 13:55:39 +01:00
dartcafe
da9c18b4cb return the selector for collections
Signed-off-by: dartcafe <github@dartcafe.de>
2022-03-04 08:53:40 +00:00
Julius Härtl
94134fc7d6 Merge pull request #3614 from nextcloud/backport/3612/stable23
[stable23] Make insert attachment buttom easy to click
2022-03-02 21:04:30 +01:00
Luka Trovic
113cc5c578 fix: make insert attachment buttom easy to click
Signed-off-by: Luka Trovic <luka@nextcloud.com>
2022-03-02 16:24:21 +00:00
Nextcloud bot
0d12c2a4ec [tx-robot] updated from transifex
Signed-off-by: Nextcloud bot <bot@nextcloud.com>
2022-02-19 02:38:00 +00:00
Nextcloud bot
3d1ed51ea9 [tx-robot] updated from transifex
Signed-off-by: Nextcloud bot <bot@nextcloud.com>
2022-02-17 02:38:51 +00:00
Nextcloud bot
31152d9763 [tx-robot] updated from transifex
Signed-off-by: Nextcloud bot <bot@nextcloud.com>
2022-02-08 02:38:05 +00:00
Nextcloud bot
044afef32d [tx-robot] updated from transifex
Signed-off-by: Nextcloud bot <bot@nextcloud.com>
2022-02-06 02:36:59 +00:00
Nextcloud bot
1e35c7b836 [tx-robot] updated from transifex
Signed-off-by: Nextcloud bot <bot@nextcloud.com>
2022-02-05 02:37:27 +00:00
Nextcloud bot
0a43156a91 [tx-robot] updated from transifex
Signed-off-by: Nextcloud bot <bot@nextcloud.com>
2022-02-01 02:37:25 +00:00
Nextcloud bot
fd9ad30e3e [tx-robot] updated from transifex
Signed-off-by: Nextcloud bot <bot@nextcloud.com>
2022-01-31 02:37:25 +00:00
Nextcloud bot
c5fc9f6833 [tx-robot] updated from transifex
Signed-off-by: Nextcloud bot <bot@nextcloud.com>
2022-01-24 02:37:10 +00:00
Nextcloud bot
298d72c651 [tx-robot] updated from transifex
Signed-off-by: Nextcloud bot <bot@nextcloud.com>
2022-01-19 02:38:12 +00:00
Julien Veyssier
64e231630f Merge pull request #3543 from nextcloud/backport/3541/stable23
[stable23] Fix confusion between stackId and boardId in StackService
2022-01-18 16:37:37 +01:00
Julien Veyssier
314fdc43ad fix confusion between stackId and boardId in StackService::update()
Signed-off-by: Julien Veyssier <eneiluj@posteo.net>
2022-01-18 15:23:22 +00:00
Nextcloud bot
6962187497 [tx-robot] updated from transifex
Signed-off-by: Nextcloud bot <bot@nextcloud.com>
2022-01-18 02:56:03 +00:00
Julius Härtl
0f9fdbd82a Merge pull request #3537 from nextcloud/backport/3529/stable23
[stable23] Fix talk integration
2022-01-14 23:05:23 +01:00
Joas Schilling
c6f8b22837 Fix talk integration
title and description where not populated and also opposed to documentation the link is not absolute

Signed-off-by: Joas Schilling <coding@schilljs.com>
2022-01-14 21:11:14 +00:00
Nextcloud bot
a3cdd5e19d [tx-robot] updated from transifex
Signed-off-by: Nextcloud bot <bot@nextcloud.com>
2022-01-13 02:37:09 +00:00
Julius Härtl
239bb5e4cb Merge pull request #3523 from nextcloud/backport/3502/stable23 2022-01-12 13:45:53 +01:00
Nextcloud bot
2a121536de [tx-robot] updated from transifex
Signed-off-by: Nextcloud bot <bot@nextcloud.com>
2022-01-12 02:37:24 +00:00
Julius Härtl
1f645a3059 Merge pull request #3526 from nextcloud/backport/3500/stable23
[stable23] Fix CalDAV blocking and modernize circles API usage
2022-01-11 19:06:10 +01:00
Julius Härtl
92efa7f5ec Move any circles API usage to internal service
Signed-off-by: Julius Härtl <jus@bitgrid.net>
2022-01-11 08:27:16 +00:00
Julius Härtl
70664c1853 Avoid blocking calendar access if something goes wrong while fetching deck entries
Signed-off-by: Julius Härtl <jus@bitgrid.net>
2022-01-11 08:27:15 +00:00
Luka Trovic
8ac2bbbbbc exclude deleted boards in the selection for target
Signed-off-by: Luka Trovic <luka@nextcloud.com>
2022-01-11 08:00:46 +00:00
Nextcloud bot
7560619939 [tx-robot] updated from transifex
Signed-off-by: Nextcloud bot <bot@nextcloud.com>
2022-01-11 02:37:39 +00:00
Julius Härtl
7ce68f74c4 Merge pull request #3520 from nextcloud/backport/3512/stable23 2022-01-10 21:08:19 +01:00
Simon Spannagel
fa493ddd9a CardApiController: Fix order of optional parameters
Signed-off-by: Simon Spannagel <simonspa@kth.se>
2022-01-10 11:02:06 +00:00
Julius Härtl
d4c9147419 Bump version to 1.6.0
Signed-off-by: Julius Härtl <jus@bitgrid.net>
2021-11-30 16:35:01 +01:00
Julius Härtl
21b81ff56c Merge pull request #3462 from nextcloud/backport/3459/stable23 2021-11-30 16:31:59 +01:00
Julius Härtl
4645c5077d Merge pull request #3463 from nextcloud/backport/3458/stable23 2021-11-30 16:31:52 +01:00
Julius Härtl
9a7bcafa6c Properly handle setters now that there is a default value of null with unset acl/labels
Signed-off-by: Julius Härtl <jus@bitgrid.net>
2021-11-30 12:24:00 +00:00
Julius Härtl
53ebe480cf Fix cursor generation if no results are found
Signed-off-by: Julius Härtl <jus@bitgrid.net>
2021-11-30 12:22:22 +00:00
Julius Härtl
e4db5e4d28 Bump version to 1.6.0-beta3
Signed-off-by: Julius Härtl <jus@bitgrid.net>
2021-11-24 16:07:12 +01:00
Julius Härtl
2b8dee5081 Merge pull request #3449 from nextcloud/backport/3444/stable23 2021-11-24 15:57:59 +01:00
Julius Härtl
a8d41797ef Keep API results the same as before
Signed-off-by: Julius Härtl <jus@bitgrid.net>
2021-11-24 14:06:49 +00:00
Julius Härtl
bc9fe51036 Avoid fetching board details multiple times
Signed-off-by: Julius Härtl <jus@bitgrid.net>
2021-11-24 14:06:49 +00:00
Julius Härtl
d16799948f Cache card to board id relation
Signed-off-by: Julius Härtl <jus@bitgrid.net>
2021-11-24 14:06:49 +00:00
Julien Veyssier
764dd947d4 Merge pull request #3446 from nextcloud/backport/3365/stable23
[stable23] Switch to QBMapper in BoardMapper
2021-11-24 10:53:51 +01:00
Julius Härtl
ea35f64d2a Update psalm baseline
Signed-off-by: Julius Härtl <jus@bitgrid.net>
2021-11-24 08:41:08 +00:00
Julien Veyssier
a2205db748 switch to QBMapper in BoardMapper
Signed-off-by: Julien Veyssier <eneiluj@posteo.net>
2021-11-24 08:41:08 +00:00
Jonas
a80d87a3e9 Merge pull request #3440 from nextcloud/backport/3428/stable23
[stable23] Allow to download an attachment without navigating to the files app
2021-11-22 19:10:45 +01:00
Julius Härtl
12ed617a56 Allow to download an attachment without navigating to the files app
Signed-off-by: Julius Härtl <jus@bitgrid.net>
2021-11-22 17:40:34 +00:00
Julius Härtl
ffccbf73fd Bump version to 1.6.0-beta2
Signed-off-by: Julius Härtl <jus@bitgrid.net>
2021-11-19 17:15:00 +01:00
Julius Härtl
8514e91edc Merge pull request #3433 from nextcloud/backport/3432/stable23
[stable23] Fix event name for updating the description
2021-11-19 12:13:51 +01:00
Julius Härtl
8926363092 Fix event name for updating the description
Signed-off-by: Julius Härtl <jus@bitgrid.net>
2021-11-19 09:14:44 +00:00
Joas Schilling
2be4fff781 Merge pull request #3418 from nextcloud/update-stable23-target-versions
Update stable23 target versions
2021-11-11 22:53:17 +01:00
Joas Schilling
8f52667d2c Update stable23 target versions
Signed-off-by: Joas Schilling <coding@schilljs.com>
2021-11-11 09:27:35 +01:00
232 changed files with 35459 additions and 41711 deletions

View File

@@ -3,10 +3,7 @@ root = true
[*]
charset = utf-8
end_of_line = lf
indent_size = tab
indent_style = tab
insert_final_newline = true
trim_trailing_whitespace = true
[*.{js,vue}]
indent_style = tab

View File

@@ -1,5 +1,4 @@
module.exports = {
root: true,
extends: [
'@nextcloud',
],
@@ -8,7 +7,6 @@ module.exports = {
'jsdoc/require-param-type': ['off'],
'jsdoc/check-param-names': ['off'],
'jsdoc/no-undefined-types': ['off'],
'jsdoc/require-property-description': ['off'],
'import/no-named-as-default-member': ['off']
'jsdoc/require-property-description' : ['off']
},
}

View File

@@ -11,58 +11,19 @@ updates:
open-pull-requests-limit: 10
reviewers:
- juliushaertl
- package-ecosystem: npm
target-branch: stable25
versioning-strategy: lockfile-only
directory: "/"
schedule:
interval: weekly
day: saturday
time: "03:00"
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: stable24
versioning-strategy: lockfile-only
directory: "/"
schedule:
interval: weekly
day: saturday
time: "03:00"
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: stable23
versioning-strategy: lockfile-only
directory: "/"
schedule:
interval: weekly
day: saturday
time: "03:00"
timezone: Europe/Paris
ignore:
- dependency-name: "*"
update-types: ["version-update:semver-major"]
open-pull-requests-limit: 30
labels:
- 3. to review
- dependencies
- jakobroehrl
#- package-ecosystem: npm
# directory: "/"
# target-branch: "stable1.1"
# schedule:
# interval: weekly
# day: saturday
# time: "03:00"
# timezone: Europe/Paris
# open-pull-requests-limit: 10
# reviewers:
# - juliushaertl
# - jakobroehrl
- package-ecosystem: composer
directory: "/"
schedule:
@@ -73,16 +34,11 @@ updates:
open-pull-requests-limit: 10
reviewers:
- juliushaertl
- package-ecosystem: composer
directory: "/tests/integration"
schedule:
interval: weekly
day: saturday
time: "03:00"
timezone: Europe/Paris
open-pull-requests-limit: 10
reviewers:
- juliushaertl
ignore:
- dependency-name: christophwurst/nextcloud
versions:
- "< 16"
- ">= 15.a"
- package-ecosystem: github-actions
directory: "/"
schedule:

View File

@@ -1,43 +0,0 @@
name: Package build
on:
push:
branches:
- main
- master
- stable*
jobs:
build:
runs-on: ubuntu-18.04
strategy:
matrix:
node-version: [14.x]
steps:
- uses: actions/checkout@v3
- name: Use Node.js ${{ matrix.node-version }}
uses: actions/setup-node@v3
with:
node-version: ${{ matrix.node-version }}
- name: Set up npm7
run: npm i -g npm@7
- name: Setup PHP
uses: shivammathur/setup-php@2.22.0
with:
php-version: '7.4'
tools: composer
- name: install dependencies
run: |
wget https://github.com/ChristophWurst/krankerl/releases/download/v0.12.2/krankerl_0.12.2_amd64.deb
sudo dpkg -i krankerl_0.12.2_amd64.deb
- name: package
run: |
uname -a
RUST_BACKTRACE=1 krankerl --version
RUST_BACKTRACE=1 krankerl package
- uses: actions/upload-artifact@v3
with:
name: Deck app tarball
path: build/artifacts/deck.tar.gz

View File

@@ -44,7 +44,7 @@ jobs:
expression: "//info//dependencies//nextcloud/@min-version"
- name: Read package.json node and npm engines version
uses: skjnldsv/read-package-engines-version-actions@v2.0
uses: skjnldsv/read-package-engines-version-actions@v1.2
id: versions
# Continue if no package.json
continue-on-error: true
@@ -66,14 +66,14 @@ jobs:
run: npm i -g npm@"${{ steps.versions.outputs.npmVersion }}"
- name: Set up php ${{ env.PHP_VERSION }}
uses: shivammathur/setup-php@2.22.0
uses: shivammathur/setup-php@v2
with:
php-version: ${{ env.PHP_VERSION }}
coverage: none
- name: Check composer.json
id: check_composer
uses: andstor/file-existence-action@v2
uses: andstor/file-existence-action@v1
with:
files: "${{ env.APP_NAME }}/composer.json"

View File

@@ -9,21 +9,16 @@ on:
issue_comment:
types: created
permissions:
contents: read
jobs:
rebase:
runs-on: ubuntu-latest
permissions:
contents: none
# On pull requests and if the comment starts with `/rebase`
if: github.event.issue.pull_request != '' && startsWith(github.event.comment.body, '/rebase')
steps:
- name: Add reaction on start
uses: peter-evans/create-or-update-comment@v2
uses: peter-evans/create-or-update-comment@v1
with:
token: ${{ secrets.COMMAND_BOT_PAT }}
repository: ${{ github.event.repository.full_name }}
@@ -31,18 +26,18 @@ jobs:
reaction-type: "+1"
- name: Checkout the latest code
uses: actions/checkout@v3
uses: actions/checkout@v2.4.0
with:
fetch-depth: 0
token: ${{ secrets.COMMAND_BOT_PAT }}
- name: Automatic Rebase
uses: cirrus-actions/rebase@1.8
uses: cirrus-actions/rebase@1.5
env:
GITHUB_TOKEN: ${{ secrets.COMMAND_BOT_PAT }}
- name: Add reaction on failure
uses: peter-evans/create-or-update-comment@v2
uses: peter-evans/create-or-update-comment@v1
if: failure()
with:
token: ${{ secrets.COMMAND_BOT_PAT }}

View File

@@ -1,112 +0,0 @@
name: Cypress
on:
pull_request:
push:
branches:
- master
- stable*
env:
APP_NAME: deck
CYPRESS_baseUrl: http://localhost:8081/index.php
jobs:
cypress:
runs-on: ubuntu-latest
strategy:
fail-fast: false
matrix:
node-version: [14.x]
# containers: [1, 2, 3]
php-versions: [ '7.4' ]
databases: [ 'sqlite' ]
server-versions: [ 'master' ]
steps:
- name: Use Node.js ${{ matrix.node-version }}
uses: actions/setup-node@v3
with:
node-version: ${{ matrix.node-version }}
- name: Set up npm7
run: npm i -g npm@7
- name: Checkout server
uses: actions/checkout@v3
with:
repository: nextcloud/server
ref: ${{ matrix.server-versions }}
- name: Checkout submodules
shell: bash
run: |
auth_header="$(git config --local --get http.https://github.com/.extraheader)"
git submodule sync --recursive
git -c "http.extraheader=$auth_header" -c protocol.version=2 submodule update --init --force --recursive --depth=1
- name: Checkout ${{ env.APP_NAME }}
uses: actions/checkout@v3
with:
path: apps/${{ env.APP_NAME }}
- name: Set up php ${{ matrix.php-versions }}
uses: shivammathur/setup-php@2.22.0
with:
php-version: ${{ matrix.php-versions }}
extensions: mbstring, iconv, fileinfo, intl, sqlite, pdo_sqlite, zip, gd, apcu
ini-values:
apc.enable_cli=on
coverage: none
- name: Set up Nextcloud
env:
DB_PORT: 4444
PHP_CLI_SERVER_WORKERS: 10
run: |
mkdir data
php occ maintenance:install --verbose --database=${{ matrix.databases }} --database-name=nextcloud --database-host=127.0.0.1 --database-port=$DB_PORT --database-user=root --database-pass=rootpassword --admin-user admin --admin-pass admin
php occ config:system:set memcache.local --value="\\OC\\Memcache\\APCu"
php occ config:system:set debug --value=true --type=boolean
php -f index.php
php -S 0.0.0.0:8081 &
export OC_PASS=1234561
php occ user:add --password-from-env user1
php occ user:add --password-from-env user2
php occ app:enable deck
php occ app:list
cd apps/deck
composer install --no-dev
npm ci
npm run build
cd ../../
curl -v http://localhost:8081/index.php/login
- name: Cypress run
uses: cypress-io/github-action@v5
with:
record: true
parallel: false
wait-on: '${{ env.CYPRESS_baseUrl }}'
working-directory: 'apps/${{ env.APP_NAME }}'
config: defaultCommandTimeout=10000,video=false
env:
CYPRESS_RECORD_KEY: ${{ secrets.CYPRESS_RECORD_KEY }}
npm_package_name: ${{ env.APP_NAME }}
- name: Upload test failure screenshots
uses: actions/upload-artifact@v3
if: failure()
with:
name: Upload screenshots
path: apps/${{ env.APP_NAME }}/cypress/screenshots/
retention-days: 5
- name: Upload nextcloud logs
uses: actions/upload-artifact@v3
if: failure()
with:
name: Upload nextcloud log
path: data/nextcloud.log
retention-days: 5

View File

@@ -8,29 +8,22 @@ name: Dependabot
on:
pull_request_target:
branches:
- main
- master
- stable*
permissions:
contents: read
jobs:
auto-approve-merge:
if: github.actor == 'dependabot[bot]'
runs-on: ubuntu-latest
permissions:
# for hmarr/auto-approve-action to approve PRs
pull-requests: write
steps:
# Github actions bot approve
- uses: hmarr/auto-approve-action@v3
- uses: hmarr/auto-approve-action@v2
with:
github-token: ${{ secrets.GITHUB_TOKEN }}
# Nextcloud bot approve and merge request
- uses: ahmadnassri/action-dependabot-auto-merge@v2
with:
target: minor
target: patch
github-token: ${{ secrets.DEPENDABOT_AUTOMERGE_TOKEN }}

View File

@@ -3,18 +3,31 @@
# https://github.com/nextcloud/.github
# https://docs.github.com/en/actions/learn-github-actions/sharing-workflows-with-your-organization
name: Pull request checks
name: Block fixup and squash commits
on: pull_request
on:
pull_request:
types: [opened, ready_for_review, reopened, synchronize]
permissions:
contents: read
concurrency:
group: fixup-${{ github.head_ref || github.run_id }}
cancel-in-progress: true
jobs:
commit-message-check:
if: github.event.pull_request.draft == false
permissions:
pull-requests: write
name: Block fixup and squash commits
runs-on: ubuntu-latest
steps:
- name: Run check
uses: xt0rted/block-autosquash-commits-action@v2
uses: skjnldsv/block-fixup-merge-action@42d26e1b536ce61e5cf467d65fb76caf4aa85acf # v1
with:
repo-token: ${{ secrets.GITHUB_TOKEN }}

View File

@@ -2,14 +2,6 @@ name: Integration tests
on:
pull_request:
paths:
- '.github/workflows/integration.yml'
- 'appinfo/**'
- 'lib/**'
- 'templates/**'
- 'tests/**'
- 'composer.json'
- 'composer.lock'
push:
branches:
- master
@@ -27,7 +19,7 @@ jobs:
matrix:
php-versions: ['7.4']
databases: ['sqlite', 'mysql', 'pgsql']
server-versions: ['master']
server-versions: ['stable23']
name: php${{ matrix.php-versions }}-${{ matrix.databases }}-${{ matrix.server-versions }}
@@ -51,7 +43,7 @@ jobs:
steps:
- name: Checkout server
uses: actions/checkout@v3
uses: actions/checkout@v2.4.0
with:
repository: nextcloud/server
ref: ${{ matrix.server-versions }}
@@ -62,15 +54,21 @@ jobs:
auth_header="$(git config --local --get http.https://github.com/.extraheader)"
git submodule sync --recursive
git -c "http.extraheader=$auth_header" -c protocol.version=2 submodule update --init --force --recursive --depth=1
cd build/integration && composer require --dev phpunit/phpunit:~9
- name: Checkout app
uses: actions/checkout@v3
uses: actions/checkout@v2.4.0
with:
path: apps/${{ env.APP_NAME }}
- name: Checkout activity
uses: actions/checkout@c85c95e3d7251135ab7dc9ce3241c5835cc595a9 # v3.5.3
with:
repository: nextcloud/activity
ref: ${{ matrix.server-versions }}
path: apps/activity
- name: Set up php ${{ matrix.php-versions }}
uses: shivammathur/setup-php@2.22.0
uses: shivammathur/setup-php@2.15.0
with:
php-version: ${{ matrix.php-versions }}
tools: phpunit
@@ -79,7 +77,7 @@ jobs:
- name: Set up PHPUnit
working-directory: apps/${{ env.APP_NAME }}
run: composer i --no-dev
run: composer i
- name: Set up Nextcloud
run: |

View File

@@ -13,13 +13,13 @@ jobs:
strategy:
matrix:
php-versions: ['7.4', '8.0', '8.1']
php-versions: ['7.3', '7.4']
name: php${{ matrix.php-versions }} lint
steps:
- uses: actions/checkout@v3
- uses: actions/checkout@v2.4.0
- name: Set up php${{ matrix.php-versions }}
uses: shivammathur/setup-php@2.22.0
uses: shivammathur/setup-php@2.15.0
with:
php-version: ${{ matrix.php-versions }}
coverage: none
@@ -31,9 +31,9 @@ jobs:
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v3
uses: actions/checkout@v2.4.0
- name: Set up php
uses: shivammathur/setup-php@2.22.0
uses: shivammathur/setup-php@2.15.0
with:
php-version: 7.4
coverage: none
@@ -50,9 +50,9 @@ jobs:
node-version: [14.x]
steps:
- uses: actions/checkout@v3
- uses: actions/checkout@v2.4.0
- name: Use node ${{ matrix.node-version }}
uses: actions/setup-node@v3
uses: actions/setup-node@v2.4.1
with:
node-version: ${{ matrix.node-version }}
- name: Set up npm7
@@ -71,10 +71,10 @@ jobs:
name: stylelint node${{ matrix.node-version }}
steps:
- uses: actions/checkout@v3
- uses: actions/checkout@v2.4.0
- name: Set up node ${{ matrix.node-version }}
uses: actions/setup-node@v3
uses: actions/setup-node@v2.4.1
with:
node-version: ${{ matrix.node-version }}

View File

@@ -17,15 +17,15 @@ jobs:
node-version: [14.x]
steps:
- uses: actions/checkout@v3
- uses: actions/checkout@v2.4.0
- name: Use Node.js ${{ matrix.node-version }}
uses: actions/setup-node@v3
uses: actions/setup-node@v2.4.1
with:
node-version: ${{ matrix.node-version }}
- name: Set up npm7
run: npm i -g npm@7
- name: Setup PHP
uses: shivammathur/setup-php@2.22.0
uses: shivammathur/setup-php@2.15.0
with:
php-version: '7.4'
tools: composer

View File

@@ -12,9 +12,9 @@ jobs:
node-version: [14.x]
steps:
- uses: actions/checkout@v3
- uses: actions/checkout@v2.4.0
- name: Use Node.js ${{ matrix.node-version }}
uses: actions/setup-node@v3
uses: actions/setup-node@v2.4.1
with:
node-version: ${{ matrix.node-version }}
- name: Set up npm7

View File

@@ -2,14 +2,6 @@ name: PHPUnit
on:
pull_request:
paths:
- '.github/workflows/phpunit.yml'
- 'appinfo/**'
- 'lib/**'
- 'templates/**'
- 'tests/**'
- 'composer.json'
- 'composer.lock'
push:
branches:
- master
@@ -21,14 +13,14 @@ env:
jobs:
integration:
runs-on: ubuntu-18.04
runs-on: ubuntu-latest
strategy:
fail-fast: false
matrix:
php-versions: ['7.4', '8.0', '8.1']
php-versions: ['7.3', '7.4']
databases: ['sqlite', 'mysql', 'pgsql']
server-versions: ['master']
server-versions: ['stable23']
name: php${{ matrix.php-versions }}-${{ matrix.databases }}-${{ matrix.server-versions }}
@@ -70,7 +62,7 @@ jobs:
path: apps/${{ env.APP_NAME }}
- name: Set up php ${{ matrix.php-versions }}
uses: shivammathur/setup-php@2.22.0
uses: shivammathur/setup-php@2.18.0
with:
php-version: ${{ matrix.php-versions }}
tools: phpunit

View File

@@ -23,7 +23,7 @@ jobs:
uses: actions/checkout@v3
- name: Set up php
uses: shivammathur/setup-php@2.22.0
uses: shivammathur/setup-php@v2
with:
php-version: 7.4
coverage: none

View File

@@ -28,7 +28,7 @@ jobs:
submodules: true
- name: Set up php7.4
uses: shivammathur/setup-php@2.22.0
uses: shivammathur/setup-php@v2
with:
php-version: 7.4
extensions: ctype,curl,dom,fileinfo,gd,intl,json,mbstring,openssl,pdo_sqlite,posix,sqlite,xml,zip
@@ -49,7 +49,7 @@ jobs:
continue-on-error: true
- name: Create Pull Request
uses: peter-evans/create-pull-request@v4
uses: peter-evans/create-pull-request@v3
with:
token: ${{ secrets.COMMAND_BOT_PAT }}
commit-message: Update psalm baseline

1
.gitignore vendored
View File

@@ -9,4 +9,3 @@ tests/.phpunit.result.cache
vendor/
.php_cs.cache
\.idea/
settings.json

View File

@@ -1,6 +1,6 @@
[main]
host = https://www.transifex.com
lang_map = hu_HU: hu, nb_NO: nb, sk_SK: sk, th_TH: th, ja_JP: ja, bg_BG: bg, cs_CZ: cs, fi_FI: fi
lang_map = sk_SK: sk, th_TH: th, ja_JP: ja, bg_BG: bg, cs_CZ: cs, fi_FI: fi, hu_HU: hu, nb_NO: nb
[o:nextcloud:p:nextcloud:r:deck]
file_filter = translationfiles/<lang>/deck.po

View File

@@ -1,102 +1,92 @@
# Changelog
All notable changes to this project will be documented in this file.
## 1.8.0-beta.1
### Enhancements
- Nextcloud 25 compatibility
- Performance improvements
- Use capped memory cache for board permissions @juliushaertl [#3980](https://github.com/nextcloud/deck/pull/3980)
- Improve CalDAV integration performance @juliushaertl [#3982](https://github.com/nextcloud/deck/pull/3982)
- Simpify query for getting shared files @juliushaertl [#3983](https://github.com/nextcloud/deck/pull/3983)
- Accessibility improvements
- Add a11y label for sidebar button @marcelklehr [#3986](https://github.com/nextcloud/deck/pull/3986)
- Improve filter popover accessibility @juliushaertl [#3820](https://github.com/nextcloud/deck/pull/3820)
- Set ids to skip to content/navigation @juliushaertl [#3924](https://github.com/nextcloud/deck/pull/3924)
- Invert icons properly in dark mode @juliushaertl [#3939](https://github.com/nextcloud/deck/pull/3939)
- Bump dependencies
## 1.6.6
### Fixed
- set last modified when the card was found. Fixes #3763 @ylebre [#3796](https://github.com/nextcloud/deck/pull/3796)
- Increase file count after sharing @luka-nextcloud [#3682](https://github.com/nextcloud/deck/pull/3682)
- Align Duedate-delete icon properly - fixes nextcloud/deck#3791 @Ben-Ro [#3811](https://github.com/nextcloud/deck/pull/3811)
- Fix for issue #3637 @flummer [#3833](https://github.com/nextcloud/deck/pull/3833)
- Switch to 'markdown-it-task-checkbox' for rendering of task lists @q-wertz [#3898](https://github.com/nextcloud/deck/pull/3898)
- Make rename functions accessibly by keyboard navigation @juliushaertl [#3813](https://github.com/nextcloud/deck/pull/3813)
- Prevent opening card and applyLabelFilter on card drag end @eneiluj [#3916](https://github.com/nextcloud/deck/pull/3916)
- Inserted required property in the rename list field, to prevent the l… @mstolf [#3862](https://github.com/nextcloud/deck/pull/3862)
- Fix share provider for master changes @nickvergessen [#3942](https://github.com/nextcloud/deck/pull/3942)
- Fetch attachment folder for the correct user during cron job @juliushaertl [#3952](https://github.com/nextcloud/deck/pull/3952)
- Fix z-index for deck sidebar @Raudius [#3884](https://github.com/nextcloud/deck/pull/3884)
- Gracefully handle not found card for a share [#4570](https://github.com/nextcloud/deck/pull/4570)
- Fix(occ): set user id for permission sevice from board service [#5073](https://github.com/nextcloud/deck/pull/5073)
- Fix deleted card/board issues @juliushaertl [#5450](https://github.com/nextcloud/deck/pull/5450)
- Fix small issues around delete/undo @juliushaertl [#5447](https://github.com/nextcloud/deck/pull/5447)
## 1.6.5
### Fixed
- minor style fixes [#4203](https://github.com/nextcloud/deck/pull/4203)
- Add integration test for attachment handling on cards [#4177](https://github.com/nextcloud/deck/pull/4177)
- feat: add validators to check values in services @juliushaertl [#4175](https://github.com/nextcloud/deck/pull/4175)
- disables autocomplete on card creation @juliushaertl [#4183](https://github.com/nextcloud/deck/pull/4183)
## 1.6.4
### Fixed
- Cache user membership for circles [#4133](https://github.com/nextcloud/deck/pull/4133)
- Set event link also for notifications that get emitted from activities [#4119](https://github.com/nextcloud/deck/pull/4119)
- disable Create card button while no stack is chosen [#4020](https://github.com/nextcloud/deck/pull/4020)
- to nextcloud/OCP package in stable23 [#4094](https://github.com/nextcloud/deck/pull/4094)
- Use capped memory cache for board permissions [#3998](https://github.com/nextcloud/deck/pull/3998)
- Improve CalDAV integration performance [#3996](https://github.com/nextcloud/deck/pull/3996)
- Fetch attachment folder for the correct user during cron job [#3960](https://github.com/nextcloud/deck/pull/3960)
- Switch to 'markdown-it-task-checkbox' for rendering of task lists [#3926](https://github.com/nextcloud/deck/pull/3926)
- Prevent opening card and applyLabelFilter on card drag end [#3918](https://github.com/nextcloud/deck/pull/3918)
- Fix z-index for deck sidebar [#3886](https://github.com/nextcloud/deck/pull/3886)
## 1.6.3
### Fixed
- Align Duedate-delete icon properly - fixes nextcloud/deck#3791 [#3816](https://github.com/nextcloud/deck/pull/3816)
- Increase file count after sharing [#3805](https://github.com/nextcloud/deck/pull/3805)
- Show cards after moving into another list [#3795](https://github.com/nextcloud/deck/pull/3795)
- Fetch full board data after cloning [#3780](https://github.com/nextcloud/deck/pull/3780)
- Handle qb mapper exception messages properly [#3770](https://github.com/nextcloud/deck/pull/3770)
## 1.6.2
### Added
- Transfer ownership @juliushaertl [#3664](https://github.com/nextcloud/deck/pull/3664)
### Fixed
- 🐛 Fix missing files sidebar [#3641](https://github.com/nextcloud/deck/pull/3641)
- Add a missing translation - not found in transifex [#3706](https://github.com/nextcloud/deck/pull/3706)
- Fix: Check all circle shares for permissions [#3717](https://github.com/nextcloud/deck/pull/3717)
- Sort boards non case sensitive [#3663](https://github.com/nextcloud/deck/pull/3663)
- Use explicit cast to make use of index @juliushaertl [#3497](https://github.com/nextcloud/deck/pull/3497)
- Fix paramter replacements when creating deck cards from talk messages @juliushaertl [#3741](https://github.com/nextcloud/deck/pull/3741)
- Fix hidden attachment icon on archived cards [#3734](https://github.com/nextcloud/deck/pull/3734)
- Fix text selection in dark mode and modal view [#3766](https://github.com/nextcloud/deck/pull/3766)
### Other
- Switch from OC::$server->get to OCP\Server::get @CarlSchwan [#3801](https://github.com/nextcloud/deck/pull/3801)
- Add performance section in README @eneiluj [#3830](https://github.com/nextcloud/deck/pull/3830)
- Fix static analysis by stubbing more circle methods @juliushaertl [#3900](https://github.com/nextcloud/deck/pull/3900)
- fix(docs): fix links to JSON schemas for Trello @wiktor2200 [#3872](https://github.com/nextcloud/deck/pull/3872)
- Move to OCP\Collaboration\Resources\LoadAdditionalScriptsEvent @juliushaertl [#3818](https://github.com/nextcloud/deck/pull/3818)
- Rename settings to deck settings @PVince81 [#3928](https://github.com/nextcloud/deck/pull/3928)
- SCSS cleanup @juliushaertl [#3803](https://github.com/nextcloud/deck/pull/3803)
- Hide deprecated projects in sidebar and card details by default @Pytal [#3984](https://github.com/nextcloud/deck/pull/3984)
- Properly check for the stack AND setting board permissions [#3713](https://github.com/nextcloud/deck/pull/3713)
- Add missing indices [#3755](https://github.com/nextcloud/deck/pull/3755)
## 1.7.0
### Added
- Transfer ownership @matchish @luka-nextcloud @juliushaertl [#2496](https://github.com/nextcloud/deck/pull/2496)
- Import from trello via CLI @vitormattos [#3182](https://github.com/nextcloud/deck/pull/3182)
- Add app config to toggle the default calendar setting as an admin @juliushaertl [#3528](https://github.com/nextcloud/deck/pull/3528)
- Show board name in browser title @luka-nextcloud [#3499](https://github.com/nextcloud/deck/pull/3499)
- Move DeleteCron to be time insensitive @juliushaertl [#3599](https://github.com/nextcloud/deck/pull/3599)
- 🚸 Shows error on board fetchData @vinicius73 [#3653](https://github.com/nextcloud/deck/pull/3653)
- Add support for PHP 8.1 @juliushaertl [#3601](https://github.com/nextcloud/deck/pull/3601)
- Nextcloud 24 compatibility
## 1.6.1
### Fixed
- CardApiController: Fix order of optional parameters @simonspa [#3512](https://github.com/nextcloud/deck/pull/3512)
- Exclude deleted boards in the selection for target @luka-nextcloud [#3502](https://github.com/nextcloud/deck/pull/3502)
- Fix CalDAV blocking and modernize circles API usage @juliushaertl [#3500](https://github.com/nextcloud/deck/pull/3500)
- Timestamps on created and modified at values @luka-nextcloud [#3532](https://github.com/nextcloud/deck/pull/3532)
- return the selector for collections @dartcafe [#3552](https://github.com/nextcloud/deck/pull/3552)
- Generate fixed link for activity emails @luka-nextcloud [#3611](https://github.com/nextcloud/deck/pull/3611)
- 🐛 Fix missing files sidebar @vinicius73 [#3635](https://github.com/nextcloud/deck/pull/3635)
- Handle description shortening more gracefully @juliushaertl [#3650](https://github.com/nextcloud/deck/pull/3650)
- Sort boards non case sensitive @Ben-Ro [#3560](https://github.com/nextcloud/deck/pull/3560)
- Remove unused argument from transfer ownership @juliushaertl [#3712](https://github.com/nextcloud/deck/pull/3712)
- Fix: Check all circle shares for permissions @bink [#3625](https://github.com/nextcloud/deck/pull/3625)
- Extend API changelog @juliushaertl [#3522](https://github.com/nextcloud/deck/pull/3522)
- Fix talk integration @nickvergessen [#3529](https://github.com/nextcloud/deck/pull/3529)
- Fix confusion between stackId and boardId in StackService @eneiluj [#3541](https://github.com/nextcloud/deck/pull/3541)
- Add horizontal scrollbar into the large table inside description @luka-nextcloud [#3531](https://github.com/nextcloud/deck/pull/3531)
- Make links in markdown note bolder @luka-nextcloud [#3530](https://github.com/nextcloud/deck/pull/3530)
- Update master php testing versions @nickvergessen [#3561](https://github.com/nextcloud/deck/pull/3561)
- Update master php enviroment @nickvergessen [#3582](https://github.com/nextcloud/deck/pull/3582)
- Make insert attachment buttom easy to click @luka-nextcloud [#3612](https://github.com/nextcloud/deck/pull/3612)
- Remove extra bullet @elitejake [#3613](https://github.com/nextcloud/deck/pull/3613)
- l10n: Delete space @Valdnet [#3666](https://github.com/nextcloud/deck/pull/3666)
- Update master php testing versions @nickvergessen [#3688](https://github.com/nextcloud/deck/pull/3688)
- Fix wording to represent the code behavior @q-wertz [#3685](https://github.com/nextcloud/deck/pull/3685)
- Fix cron jobs @nickvergessen [#3689](https://github.com/nextcloud/deck/pull/3689)
- Update master php testing versions @nickvergessen [#3695](https://github.com/nextcloud/deck/pull/3695)
- Optimise queries when preparing card related notifications @Raudius [#3690](https://github.com/nextcloud/deck/pull/3690)
- Properly check for the stack AND setting board permissions @juliushaertl [#3670](https://github.com/nextcloud/deck/pull/3670)
- Replace deprecated String.prototype.substr() @CommanderRoot [#3669](https://github.com/nextcloud/deck/pull/3669)
- Dependency updates
- Show cards after moving into another list [#3736](https://github.com/nextcloud/deck/pull/3736)
- Fix paramter replacements when creating deck cards from talk messages @nickvergessen [#3683](https://github.com/nextcloud/deck/pull/3683)
- Fix hidden attachment icon on archived cards [#3733](https://github.com/nextcloud/deck/pull/3733)
- Adapt the card modal to upstream changes [#3764](https://github.com/nextcloud/deck/pull/3764)
- Fix text selection in dark mode and modal view [#3765](https://github.com/nextcloud/deck/pull/3765)
- Add missing indices [#3754](https://github.com/nextcloud/deck/pull/3754)
- Exclude deleted boards in the selection for target [#3523](https://api.github.com/repos/nextcloud/deck/pulls/3523)
- CardApiController: Fix order of optional parameters [#3520](https://api.github.com/repos/nextcloud/deck/pulls/3520)
- Fix cursor generation if no results are found [#3462](https://api.github.com/repos/nextcloud/deck/pulls/3462)
- Fix CalDAV blocking and modernize circles API usage [#3526](https://api.github.com/repos/nextcloud/deck/pulls/3526)
- Fix overview card listing [#3463](https://api.github.com/repos/nextcloud/deck/pulls/3463)
- Generate fixed link for activity emails [#3626](https://api.github.com/repos/nextcloud/deck/pulls/3626)
- return the selector for collections [#3618](https://api.github.com/repos/nextcloud/deck/pulls/3618)
- Fix confusion between stackId and boardId in StackService [#3543](https://api.github.com/repos/nextcloud/deck/pulls/3543)
- Fix talk integration [#3537](https://api.github.com/repos/nextcloud/deck/pulls/3537)
- Make insert attachment buttom easy to click [#3614](https://api.github.com/repos/nextcloud/deck/pulls/3614)
## 1.6.0-beta1
## 1.6.0
### Added
- #3449 Cache most frequent queries
- #3177 Use async import for vue component on collections entrypoint @juliushaertl
- #2791 Open description links in new tab @fm-sys
- #3344 Improve combined search @eneiluj
@@ -105,6 +95,11 @@ All notable changes to this project will be documented in this file.
### Fixed
- #3446 Switch to QBMapper in BoardMapper
- #3433 Fix event name for updating the description
- #3463 Fix overview card listing
- #3440 Allow to download an attachment without navigating to the files app
- #3462 Fix cursor generation if no results are found
- #3161 Reduce duplicate queries when fetching user boards an permissions @juliushaertl
- #3151 Always log generic exceptions @juliushaertl
- #3217 Move circle checks to a unified service and improve member checks @juliushaertl

View File

@@ -30,16 +30,6 @@ build: clean-dist install-deps build-js
release: clean-dist install-deps-nodev build-js
lint: lint-js lint-php
lint-js:
npm run lint
npm run stylelint
lint-php:
composer run lint 1>/dev/null
composer run cs:check
build-js: install-deps-js
npm run build

View File

@@ -20,14 +20,13 @@ 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)
- [deck NG for Android and iOS](https://github.com/meltzow/deck-ng) - It is available in [Google Play Store](https://play.google.com/store/apps/details?id=net.meltzow.deckng) and [Apple App Store](https://apps.apple.com/us/app/deck-ng/id6443334702)
### 3rd-Party Integrations
- [trello-to-deck](https://github.com/maxammann/trello-to-deck) - Migrates cards from Trello
- [mail2deck](https://github.com/newroco/mail2deck) - Provides an "email in" solution
- [mail2deck](https://github.com/newroco/mail2deck) - Provides an "email in" solution
- [A-deck](https://github.com/leoossa/A-deck) - Chrome Extension that allows to create new card in selected stack based on current tab
-
## Installation/Update
This app is supposed to work on the two latest Nextcloud versions.
@@ -53,32 +52,14 @@ Please make sure you have installed the following dependencies: `make, which, ta
Instead of setting everything up manually, you can just [download the nightly build](https://github.com/nextcloud/deck/releases/tag/nightly) instead. These builds are updated every 24 hours, and are pre-configured with all the needed dependencies.
## Performance limitations
Deck is not yet ready for intensive usage.
A lot of database queries are generated when the number of boards, cards and attachments is high.
For example, a user having access to 13 boards, with each board having on average 100 cards,
and each card having on average 5 attachments,
would generate 6500 database queries when doing the file related queries
which would increase the page loading time significantly.
Improvements on Nextcloud server and Deck itself will improve the situation.
## Developing
### Nextcloud environment
You need to setup a [development environment](https://docs.nextcloud.com/server/latest/developer_manual//getting_started/devenv.html) of the current nextcloud version. You can also alternatively install & run the [nextcloud docker container](https://github.com/juliushaertl/nextcloud-docker-dev).
After the finished installation, you can clone the deck project directly in the `/[nextcloud-docker-dev-dir]/workspace/server/apps/` folder.
### PHP
Nothing to prepare, just dig into the code.
### JavaScript
This requires at least Node 16 and npm 7 to be installed.
Deck requires running a `make build-js` to install npm dependencies and build the JavaScript code using webpack. While developing you can also use `make watch` to rebuild everytime the code changes.
#### Hot reloading

View File

@@ -7,16 +7,16 @@
- 📥 Add your tasks to cards and put them in order
- 📄 Write down additional notes in Markdown
- 📄 Write down additional notes in markdown
- 🔖 Assign labels for even better organization
- 👥 Share with your team, friends or family
- 📎 Attach files and embed them in your Markdown description
- 📎 Attach files and embed them in your markdown description
- 💬 Discuss with your team using comments
- ⚡ Keep track of changes in the activity stream
- 🚀 Get your project organized
</description>
<version>1.9.0-beta.1</version>
<version>1.6.6</version>
<licence>agpl</licence>
<author>Julius Härtl</author>
<namespace>Deck</namespace>
@@ -31,24 +31,19 @@
<screenshot>https://download.bitgrid.net/nextcloud/deck/screenshots/1.0/Deck-1.png</screenshot>
<screenshot>https://download.bitgrid.net/nextcloud/deck/screenshots/1.0/Deck-2.png</screenshot>
<dependencies>
<php min-version="7.3"/>
<database min-version="9.4">pgsql</database>
<database>sqlite</database>
<database min-version="8.0">mysql</database>
<nextcloud min-version="26" max-version="26"/>
<database min-version="5.5">mysql</database>
<nextcloud min-version="23" max-version="23"/>
</dependencies>
<background-jobs>
<job>OCA\Deck\Cron\DeleteCron</job>
<job>OCA\Deck\Cron\ScheduledNotifications</job>
<job>OCA\Deck\Cron\CardDescriptionActivity</job>
</background-jobs>
<repair-steps>
<live-migration>
<step>OCA\Deck\Migration\DeletedCircleCleanup</step>
</live-migration>
</repair-steps>
<commands>
<command>OCA\Deck\Command\UserExport</command>
<command>OCA\Deck\Command\BoardImport</command>
<command>OCA\Deck\Command\TransferOwnership</command>
</commands>
<activity>

View File

@@ -92,10 +92,6 @@ return [
['name' => 'board_api#deleteAcl', 'url' => '/api/v{apiVersion}/boards/{boardId}/acl/{aclId}', 'verb' => 'DELETE'],
['name' => 'board_api#updateAcl', 'url' => '/api/v{apiVersion}/boards/{boardId}/acl/{aclId}', 'verb' => 'PUT'],
['name' => 'board_import_api#getAllowedSystems', 'url' => '/api/v{apiVersion}/boards/import/getSystems','verb' => 'GET'],
['name' => 'board_import_api#getConfigSchema', 'url' => '/api/v{apiVersion}/boards/import/config/schema/{name}','verb' => 'GET'],
['name' => 'board_import_api#import', 'url' => '/api/v{apiVersion}/boards/import','verb' => 'POST'],
['name' => 'stack_api#index', 'url' => '/api/v{apiVersion}/boards/{boardId}/stacks', 'verb' => 'GET'],
['name' => 'stack_api#getArchived', 'url' => '/api/v{apiVersion}/boards/{boardId}/stacks/archived', 'verb' => 'GET'],

View File

@@ -9,41 +9,37 @@
}
],
"require": {
"cogpowered/finediff": "0.3.*",
"justinrainbow/json-schema": "^5.2"
"cogpowered/finediff": "0.3.*"
},
"require-dev": {
"roave/security-advisories": "dev-master",
"phpunit/phpunit": "^9",
"nextcloud/coding-standard": "^1.0.0",
"phpunit/phpunit": "^8",
"nextcloud/coding-standard": "^0.5.0",
"symfony/event-dispatcher": "^4.0",
"vimeo/psalm": "^4.3",
"php-parallel-lint/php-parallel-lint": "^1.2",
"nextcloud/ocp": "dev-master"
"nextcloud/ocp": "dev-stable23"
},
"config": {
"optimize-autoloader": true,
"classmap-authoritative": true,
"allow-plugins": {
"composer/package-versions-deprecated": true
},
"platform": {
"php": "7.4"
}
"php": "7.3"
},
"optimize-autoloader": true,
"classmap-authoritative": true
},
"scripts": {
"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",
"psalm:update-baseline": "psalm --update-baseline",
"psalm:fix": "psalm --alter --issues=InvalidReturnType,InvalidNullableReturnType,MismatchingDocblockParamType,MismatchingDocblockReturnType,MissingParamType,InvalidFalsableReturnType",
"test": [
"@test:unit",
"@test:integration"
],
"test:unit": "phpunit -c tests/phpunit.xml",
"test:integration": "phpunit -c tests/phpunit.integration.xml && cd tests/integration && ./run.sh"
"test:integration": "phpunit -c tests/phpunit.integration.xml",
"test:api": "cd tests/integration && ./run.sh"
},
"autoload-dev": {
"psr-4": {

1743
composer.lock generated

File diff suppressed because it is too large Load Diff

View File

@@ -1,9 +0,0 @@
.icon-deck {
background-image: url(../img/deck-dark.svg);
filter: var(--background-invert-if-dark);
}
.icon-deck-white, .icon-deck.icon-white {
background-image: url(../img/deck.svg);
filter: var(--background-invert-if-dark);
}

1
css/deck.scss Normal file
View File

@@ -0,0 +1 @@
@include icon-black-white('deck', 'deck', 1);

View File

@@ -1,8 +1,11 @@
<?php
/**
* @copyright Copyright (c) 2022 Raul Ferreira Fuentes <raul@nextcloud.com>
/*
* @copyright Copyright (c) 2016 Julius Härtl <jus@bitgrid.net>
*
* @author Raul Ferreira Fuentes <raul@nextcloud.com>
* @author Julius Härtl <jus@bitgrid.net>
* @author Artem Anufrij <artem.anufrij@live.de>
* @author Marin Treselj <marin@pixelipo.com>
* @author Oskar Kurz <oskar.kurz@gmail.com>
* @author Ryan Fletcher <ryan.fletcher@codepassion.ca>
*
* @license GNU AGPL version 3 or any later version
*
@@ -20,26 +23,6 @@
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
*/
namespace OCA\Deck\Model;
use OCA\Deck\Db\Board;
class BoardSummary extends Board {
private Board $board;
public function __construct(Board $board) {
parent::__construct();
$this->board = $board;
}
public function jsonSerialize(): array {
return [
'id' => $this->getId(),
'title' => $this->getTitle()
];
}
public function __call($name, $arguments) {
return $this->board->__call($name, $arguments);
}
}
@import 'icons';
@import 'print';

41
css/icons.scss Normal file
View File

@@ -0,0 +1,41 @@
/**
* Custom icons
*/
@include icon-black-white('deck', 'deck', 1);
@include icon-black-white('archive', 'deck', 1);
@include icon-black-white('circles', 'deck', 1);
@include icon-black-white('clone', 'deck', 1);
@include icon-black-white('filter', 'deck', 1);
@include icon-black-white('filter_set', 'deck', 1);
@include icon-black-white('attach', 'deck', 1);
@include icon-black-white('reply', 'deck', 1);
@include icon-black-white('notifications-dark', 'deck', 1);
@include icon-black-white('description', 'deck', 1);
.icon-toggle-compact-collapsed {
@include icon-color('toggle-view-expand', 'deck', $color-black);
}
.icon-toggle-compact-expanded {
@include icon-color('toggle-view-collapse', 'deck', $color-black);
}
.icon-activity {
@include icon-color('activity-dark', 'activity', $color-black);
}
.icon-comment--unread {
@include icon-color('comment', 'actions', $color-primary, 1, true);
}
.avatardiv.circles {
background: var(--color-primary);
}
.icon-circles {
opacity: 1;
background-size: 20px;
background-position: center center;
}
.icon-colorpicker {
background-image: url('../img/color_picker.svg');
}

View File

@@ -1,17 +0,0 @@
const { defineConfig } = require('cypress')
module.exports = defineConfig({
projectId: '1s7wkc',
viewportWidth: 1280,
viewportHeight: 720,
e2e: {
// We've imported your old cypress plugins here.
// You may want to clean this up later by importing these.
setupNodeEvents(on, config) {
return require('./cypress/plugins/index.js')(on, config)
},
baseUrl: 'http://nextcloud.local/index.php',
experimentalSessionAndOrigin: true,
specPattern: 'cypress/e2e/**/*.{js,jsx,ts,tsx}',
},
})

View File

@@ -1,41 +0,0 @@
import { randHash } from '../utils'
const randUser = randHash()
describe('Board', function() {
const password = 'pass123'
before(function() {
cy.nextcloudCreateUser(randUser, password)
})
beforeEach(function() {
cy.login(randUser, password)
})
it('Can create a board', function() {
const board = 'TestBoard'
cy.intercept({
method: 'POST',
url: '/index.php/apps/deck/boards',
}).as('createBoardRequest')
// Click "Add board"
cy.openLeftSidebar()
cy.get('#app-navigation-vue .app-navigation__list .app-navigation-entry')
.eq(3).find('a').first().click({ force: true })
// Type the board title
cy.get('.board-create form input[type=text]')
.type(board, { force: true })
// Submit
cy.get('.board-create form input[type=submit]')
.first().click({ force: true })
cy.wait('@createBoardRequest').its('response.statusCode').should('equal', 200)
cy.get('.app-navigation__list .app-navigation-entry__children .app-navigation-entry')
.contains(board).should('be.visible')
})
})

View File

@@ -1,67 +0,0 @@
import { randHash } from '../utils'
const randUser = randHash()
const testBoardData = {
title: 'MyBoardTest',
color: '00ff00',
stacks: [
{
title: 'TestList',
cards: [
{
title: 'Hello world',
},
],
},
],
}
describe('Card', function() {
before(function() {
cy.nextcloudCreateUser(randUser, randUser)
cy.createExampleBoard({
user: randUser,
password: randUser,
board: testBoardData,
})
})
beforeEach(function() {
cy.login(randUser, randUser)
})
it('Can show card details modal', function() {
cy.openLeftSidebar()
cy.getNavigationEntry(testBoardData.title)
.first().click({ force: true })
cy.get('.board .stack').eq(0).within(() => {
cy.get('.card:contains("Hello world")').should('be.visible').click()
})
cy.get('.modal__card').should('be.visible')
cy.get('.app-sidebar-header__maintitle').contains('Hello world')
})
it('Can add a card', function() {
const newCardTitle = 'Write some cypress tests'
cy.openLeftSidebar()
cy.getNavigationEntry(testBoardData.title)
.first().click({ force: true })
cy.get('.board .stack').eq(0).within(() => {
cy.get('.card:contains("Hello world")').should('be.visible')
cy.get('.button-vue[aria-label*="Add card"]')
.first().click()
cy.get('.stack__card-add form input#new-stack-input-main')
.type(newCardTitle)
cy.get('.stack__card-add form input[type=submit]')
.first().click()
cy.get(`.card:contains("${newCardTitle}")`).should('be.visible')
})
})
})

View File

@@ -1,31 +0,0 @@
import { randHash } from '../utils'
const randUser = randHash()
describe('Deck dashboard', function() {
const password = 'pass123'
before(function() {
cy.nextcloudCreateUser(randUser, password)
})
beforeEach(function() {
cy.login(randUser, password)
})
it('Can show the right title on the dashboard', function() {
cy.get('.board-title h2')
.should('have.length', 1).first()
.should('have.text', 'Upcoming cards')
})
it('Can see the default "Personal Board" created for user by default', function() {
const defaultBoard = 'Personal'
cy.openLeftSidebar()
cy.get('.app-navigation-entry-wrapper[icon=icon-deck]')
.find('ul.app-navigation-entry__children .app-navigation-entry:contains(' + defaultBoard + ')')
.first()
.contains(defaultBoard)
.should('be.visible')
})
})

View File

@@ -1,30 +0,0 @@
import { randHash } from '../utils'
const randUser = randHash()
describe('Stack', function() {
const board = 'TestBoard'
const password = 'pass123'
const stack = 'List 1'
before(function() {
cy.nextcloudCreateUser(randUser, password)
cy.deckCreateBoard({ user: randUser, password }, board)
})
beforeEach(function() {
cy.logout()
cy.login(randUser, password)
})
it('Can create a stack', function() {
cy.openLeftSidebar()
cy.getNavigationEntry(board)
.click({ force: true })
cy.get('#stack-add button').first().click()
cy.get('#stack-add form input#new-stack-input-main').type(stack)
cy.get('#stack-add form input[type=submit]').first().click()
cy.get('.board .stack').eq(0).contains(stack).should('be.visible')
})
})

View File

@@ -1,5 +0,0 @@
{
"name": "Using fixtures to represent data",
"email": "hello@cypress.io",
"body": "Fixtures are a great way to mock data for responses to routes"
}

View File

@@ -1,22 +0,0 @@
/// <reference types="cypress" />
// ***********************************************************
// This example plugins/index.js can be used to load plugins
//
// You can change the location of this file or turn off loading
// the plugins file with the 'pluginsFile' configuration option.
//
// You can read more here:
// https://on.cypress.io/plugins-guide
// ***********************************************************
// This function is called when a project is opened or re-opened (e.g. due to
// the project's config changing)
/**
* @type {Cypress.PluginConfig}
*/
// eslint-disable-next-line no-unused-vars
module.exports = (on, config) => {
// `on` is used to hook into various events Cypress emits
// `config` is the resolved Cypress config
}

View File

@@ -1,159 +0,0 @@
/**
* @copyright Copyright (c) 2019 John Molakvoæ <skjnldsv@protonmail.com>
*
* @author John Molakvoæ <skjnldsv@protonmail.com>
*
* @license GNU AGPL version 3 or any later version
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
*/
const url = Cypress.config('baseUrl').replace(/\/index.php\/?$/g, '')
Cypress.env('baseUrl', url)
Cypress.Commands.add('login', (user, password, route = '/apps/deck/') => {
const session = `${user}-${Date.now()}`
cy.session(session, function() {
cy.visit(route)
cy.get('input[name=user]').type(user)
cy.get('input[name=password]').type(password)
cy.get('form[name=login] [type=submit]').click()
cy.url().should('include', route)
})
cy.visit(route)
})
Cypress.Commands.add('logout', (route = '/') => {
cy.session('_guest', function() {})
})
Cypress.Commands.add('nextcloudCreateUser', (user, password) => {
cy.clearCookies()
cy.request({
method: 'POST',
url: `${Cypress.env('baseUrl')}/ocs/v1.php/cloud/users?format=json`,
form: true,
body: {
userid: user,
password,
},
auth: { user: 'admin', pass: 'admin' },
headers: {
'OCS-ApiRequest': 'true',
'Content-Type': 'application/x-www-form-urlencoded',
},
}).then((response) => {
cy.log(`Created user ${user}`, response.status)
})
})
Cypress.Commands.add('nextcloudUpdateUser', (user, password, key, value) => {
cy.request({
method: 'PUT',
url: `${Cypress.env('baseUrl')}/ocs/v2.php/cloud/users/${user}`,
form: true,
body: { key, value },
auth: { user, pass: password },
headers: {
'OCS-ApiRequest': 'true',
'Content-Type': 'application/x-www-form-urlencoded',
},
}).then((response) => {
cy.log(`Updated user ${user} ${key} to ${value}`, response.status)
})
})
Cypress.Commands.add('openLeftSidebar', () => {
cy.get('.app-navigation button.app-navigation-toggle').click()
})
Cypress.Commands.add('deckCreateBoard', ({ user, password }, title) => {
cy.login(user, password)
cy.get('.app-navigation button.app-navigation-toggle').click()
cy.get('#app-navigation-vue .app-navigation__list .app-navigation-entry')
.eq(3)
.find('a')
.first()
.click({ force: true })
cy.get('.board-create form input[type=text]').type(title, { force: true })
cy.get('.board-create form input[type=submit]')
.first()
.click({ force: true })
})
Cypress.Commands.add('deckCreateList', ({ user, password }, title) => {
cy.login(user, password)
cy.get('.app-navigation button.app-navigation-toggle').click()
cy.get('#app-navigation-vue .app-navigation__list .app-navigation-entry')
.eq(3)
.find('a.app-navigation-entry-link')
.first()
.click({ force: true })
cy.get('#stack-add button').first().click()
cy.get('#stack-add form input#new-stack-input-main').type(title)
cy.get('#stack-add form input[type=submit]').first().click()
})
Cypress.Commands.add('createExampleBoard', ({ user, password, board }) => {
cy.request({
method: 'POST',
url: `${Cypress.env('baseUrl')}/index.php/apps/deck/api/v1.0/boards`,
auth: {
user,
password,
},
body: { title: board.title, color: board.color ?? 'ff0000' },
}).then((boardResponse) => {
expect(boardResponse.status).to.eq(200)
const boardData = boardResponse.body
for (const stackIndex in board.stacks) {
const stack = board.stacks[stackIndex]
cy.request({
method: 'POST',
url: `${Cypress.env('baseUrl')}/index.php/apps/deck/api/v1.0/boards/${boardData.id}/stacks`,
auth: {
user,
password,
},
body: { title: stack.title, order: 0 },
}).then((stackResponse) => {
const stackData = stackResponse.body
for (const cardIndex in stack.cards) {
const card = stack.cards[cardIndex]
cy.request({
method: 'POST',
url: `${Cypress.env('baseUrl')}/index.php/apps/deck/api/v1.0/boards/${boardData.id}/stacks/${stackData.id}/cards`,
auth: {
user,
password,
},
body: { title: card.title },
})
}
})
}
})
})
Cypress.Commands.add('getNavigationEntry', (boardTitle) => {
return cy.get('.app-navigation-entry-wrapper[icon=icon-deck]')
.find('ul.app-navigation-entry__children .app-navigation-entry:contains(' + boardTitle + ')')
.find('a.app-navigation-entry-link')
})

View File

@@ -1,20 +0,0 @@
// ***********************************************************
// This example support/index.js is processed and
// loaded automatically before your test files.
//
// This is a great place to put global configuration and
// behavior that modifies Cypress.
//
// You can change the location of this file or turn off
// automatically serving support files with the
// 'supportFile' configuration option.
//
// You can read more here:
// https://on.cypress.io/configuration
// ***********************************************************
// Import commands.js using ES2015 syntax:
import './commands'
// Alternatively you can use CommonJS syntax:
// require('./commands')

View File

@@ -1 +0,0 @@
export const randHash = () => Math.random().toString(36).replace(/[^a-z]+/g, '').slice(0, 10)

View File

@@ -1,7 +1,7 @@
The REST API provides access for authenticated users to their data inside the Deck app. To get a better understanding of Decks data models and their relations, please have a look at the [data structure](structure.md) documentation.
# Prerequisites
# Prequisited
- All requests require a `OCS-APIRequest` HTTP header to be set to `true` and a `Content-Type` of `application/json`.
- The API is located at https://nextcloud.local/index.php/apps/deck/api/v1.0
@@ -9,7 +9,7 @@ The REST API provides access for authenticated users to their data inside the De
## Naming
- Board is the project like grouping of tasks that can be shared to different users and groups
- Board is the the project like grouping of tasks that can be shared to different users and groups
- Stack is the grouping of cards which is rendered in vertical columns in the UI
@@ -96,27 +96,10 @@ If available the ETag will also be part of JSON response objects as shown below
# Changelog
## API version 1.0
- Deck >=1.0.0: The maximum length of the card title has been extended from 100 to 255 characters
- Deck >=1.0.0: The API will now return a 400 Bad request response if the length limitation of a board, stack or card title is exceeded
## API version 1.1
This API version has become available with **Deck 1.3.0**.
## 1.0.0 (unreleased)
- The maximum length of the card title has been extended from 100 to 255 characters
- The API will now return a 400 Bad request response if the length limitation of a board, stack or card title is exceeded
- The attachments API endpoints will return other attachment types than deck_file
- 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
## API version 1.2 (unreleased)
- Endpoints for the new import functionality have been added:
- [GET /boards/import/getSystems - Import a board](#get-boardsimportgetsystems-import-a-board)
- [GET /boards/import/config/system/{schema} - Import a board](#get-boardsimportconfigsystemschema-import-a-board)
- [POST /boards/import - Import a board](#post-boardsimport-import-a-board)
# Endpoints
@@ -944,8 +927,7 @@ The request can fail with a bad request response for the following reasons:
| type | String | The type of the attachement |
| file | Binary | File data to add as an attachment |
- 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
For now only `deck_file` is supported as an attachment type.
#### Response
@@ -1006,49 +988,6 @@ For now only `deck_file` is supported as an attachment type.
##### 200 Success
### GET /boards/import/getSystems - Import a board
#### Request parameters
| Parameter | Type | Description |
| ------------ | ------- | --------------------------------------------- |
| system | Integer | The system name. Example: trello |
#### Response
Make a request to see the json schema of system
```json
{
}
```
### GET /boards/import/config/system/{schema} - Import a board
#### Request parameters
#### Response
```json
[
"trello"
]
```
### POST /boards/import - Import a board
#### Request parameters
| Parameter | Type | Description |
| ------------ | ------- | --------------------------------------------- |
| system | string | The allowed name of system to import from |
| config | Object | The config object (JSON) |
| data | Object | The data object to import (JSON) |
#### Response
##### 200 Success
# OCS API
The following endpoints are available through the Nextcloud OCS endpoint, which is available at `/ocs/v2.php/apps/deck/api/v1.0/`.
@@ -1065,8 +1004,6 @@ Deck stores user and app configuration values globally and per board. The GET en
| Config key | Description |
| --- | --- |
| calendar | Determines if the calendar/tasks integration through the CalDAV backend is enabled for the user (boolean) |
| cardDetailsInModal | Determines if the bigger view is used (boolean) |
| cardIdBadge | Determines if the ID badges are displayed on cards (boolean) |
| groupLimit | Determines if creating new boards is limited to certain groups of the instance. The resulting output is an array of group objects with the id and the displayname (Admin only)|
```
@@ -1079,8 +1016,6 @@ Deck stores user and app configuration values globally and per board. The GET en
},
"data": {
"calendar": true,
"cardDetailsInModal": true,
"cardIdBadge": true,
"groupLimit": [
{
"id": "admin",
@@ -1110,8 +1045,6 @@ Deck stores user and app configuration values globally and per board. The GET en
| --- | ----- |
| notify-due | `off`, `assigned` or `all` |
| calendar | Boolean |
| cardDetailsInModal | Boolean |
| cardIdBadge | Boolean |
#### Example request

View File

@@ -14,9 +14,7 @@ Overall, Deck is easy to use. You can create boards, add users, share the Deck,
3. [Handle cards options](#3-handle-cards-options)
4. [Archive old tasks](#4-archive-old-tasks)
5. [Manage your board](#5-manage-your-board)
6. [Import boards](#6-import-boards)
7. [Search](#7-search)
8. [New owner for the deck entities](#8-new-owner-for-the-deck-entities)
6. [New owner for the deck entities](#8-new-owner-for-the-deck-entities)
### 1. Create my first board
In this example, we're going to create a board and share it with an other nextcloud user.
@@ -72,80 +70,14 @@ The **sharing tab** allows you to add users or even groups to your boards.
**Deleted objects** allows you to return previously deleted stacks or cards.
The **Timeline** allows you to see everything that happened in your boards. Everything!
### 6. Import boards
Importing can be done using the API or the `occ` `deck:import` command.
Comments with more than 1000 characters are placed as attached files to the card.
It is possible to import from the following sources:
#### Trello JSON
Steps:
* Create the data file
* Access Trello
* go to the board you want to export
* Follow the steps in [Trello documentation](https://help.trello.com/article/747-exporting-data-from-trello-1) and export as JSON
* Create the configuration file
* Execute the import informing the import file path, data file and source as `Trello JSON`
Create the configuration file respecting the [JSON Schema](https://github.com/nextcloud/deck/blob/master/lib/Service/Importer/fixtures/config-trelloJson-schema.json) for import `Trello JSON`
Example configuration file:
```json
{
"owner": "admin",
"color": "0800fd",
"uidRelation": {
"johndoe": "johndoe"
}
}
```
**Limitations**:
Importing from a JSON file imports up to 1000 actions. To find out how many actions the board to be imported has, identify how many actions the JSON has.
#### Trello API
Import using API is recommended for boards with more than 1000 actions.
Trello makes it possible to attach links to a card. Deck does not have this feature. Attachments and attachment links are added in a markdown table at the end of the description for every imported card that has attachments in Trello.
* Get the API Key and API Token [here](https://developer.atlassian.com/cloud/trello/guides/rest-api/api-introduction/#authentication-and-authorization)
* Get the ID of the board you want to import by making a request to:
https://api.trello.com/1/members/me/boards?key={yourKey}&token={yourToken}&fields=id,name
This ID you will use in the configuration file in the `board` property
* Create the configuration file
Create the configuration file respecting the [JSON Schema](https://github.com/nextcloud/deck/blob/master/lib/Service/Importer/fixtures/config-trelloApi-schema.json) for import `Trello JSON`
Example configuration file:
```json
{
"owner": "admin",
"color": "0800fd",
"api": {
"key": "0cc175b9c0f1b6a831c399e269772661",
"token": "92eb5ffee6ae2fec3ad71c777531578f4a8a08f09d37b73795649038408b5f33"
},
"board": "8277e0910d750195b4487976",
"uidRelation": {
"johndoe": "johndoe"
}
}
```
### 7. Search
## Search
Deck provides a global search either through the unified search in the Nextcloud header or with the inline search next to the board controls.
This search allows advanced filtering of cards across all board of the logged in user.
For example the search `project tag:ToDo assigned:alice assigned:bob` will return all cards where the card title or description contains project **and** the tag ToDo is set **and** the user alice is assigned **and** the user bob is assigned.
#### Supported search filters
### Supported search filters
| Filter | Operators | Query |
| ----------- | ----------------- | ------------------------------------------------------------ |

View File

@@ -1,32 +0,0 @@
## Implement import
* Create a new importer class extending `ABoardImportService`
* Create a listener for event `BoardImportGetAllowedEvent` to enable your importer.
> You can read more about listeners on [Nextcloud](https://docs.nextcloud.com/server/latest/developer_manual/basics/events.html?highlight=event#writing-a-listener) doc.
Example:
```php
class YourCustomImporterListener {
public function handle(Event $event): void {
if (!($event instanceof BoardImportGetAllowedEvent)) {
return;
}
$event->getService()->addAllowedImportSystem([
'name' => YourCustomImporterService::$name,
'class' => YourCustomImporterService::class,
'internalName' => 'YourCustomImporter'
]);
}
}
```
* Register your listener on your `Application` class like this:
```php
$dispatcher = $this->getContainer()->query(IEventDispatcher::class);
$dispatcher->registerEventListener(
BoardImportGetAllowedEvent::class,
YourCustomImporterListener::class
);
```
* Use the `lib/Service/Importer/Systems/TrelloJsonService.php` class as inspiration

View File

@@ -1,7 +0,0 @@
## Import class diagram
Importing boards to the Deck implements the class diagram below.
> **NOTE**: When making any changes to the structure of the classes or implementing import from other sources, edit the `BoardImport.yuml` file
![Screenshot](resources/BoardImport.svg)

View File

@@ -1,214 +0,0 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN"
"http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
<!-- Generated by graphviz version 2.40.1 (20161225.0304)
-->
<!-- Title: G Pages: 1 -->
<svg width="417pt" height="830pt"
viewBox="0.00 0.00 417.01 830.00" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
<g id="graph0" class="graph" transform="scale(1 1) rotate(0) translate(4 826)">
<title>G</title>
<polygon fill="#ffffff" stroke="transparent" points="-4,4 -4,-826 413.012,-826 413.012,4 -4,4"/>
<!-- A0 -->
<g id="node1" class="node">
<title>A0</title>
<polygon fill="#fff8dc" stroke="#000000" points="165.909,-822 70.091,-822 70.091,-766 171.909,-766 171.909,-816 165.909,-822"/>
<polyline fill="none" stroke="#000000" points="165.909,-822 165.909,-816 "/>
<polyline fill="none" stroke="#000000" points="171.909,-816 165.909,-816 "/>
<text text-anchor="middle" x="121" y="-809" font-family="Helvetica,sans-Serif" font-size="10.00" fill="#000000">Classes used on</text>
<text text-anchor="middle" x="121" y="-797" font-family="Helvetica,sans-Serif" font-size="10.00" fill="#000000">board import.</text>
<text text-anchor="middle" x="121" y="-785" font-family="Helvetica,sans-Serif" font-size="10.00" fill="#000000">Methods just to</text>
<text text-anchor="middle" x="121" y="-773" font-family="Helvetica,sans-Serif" font-size="10.00" fill="#000000">illustrate.</text>
</g>
<!-- A1 -->
<g id="node2" class="node">
<title>A1</title>
<polygon fill="none" stroke="#000000" points="108.7773,-680 23.2227,-680 23.2227,-644 108.7773,-644 108.7773,-680"/>
<text text-anchor="middle" x="66" y="-659" font-family="Helvetica,sans-Serif" font-size="10.00" fill="#000000">ApiController</text>
</g>
<!-- A2 -->
<g id="node3" class="node">
<title>A2</title>
<polygon fill="none" stroke="#000000" points="0,-514 0,-546 132,-546 132,-514 0,-514"/>
<text text-anchor="start" x="9.607" y="-527" font-family="Helvetica,sans-Serif" font-size="10.00" fill="#000000">BoardImportApiController</text>
<polygon fill="none" stroke="#000000" points="0,-458 0,-514 132,-514 132,-458 0,-458"/>
<text text-anchor="start" x="45.8645" y="-495" font-family="Helvetica,sans-Serif" font-size="10.00" fill="#000000">+import()</text>
<text text-anchor="start" x="16.1335" y="-483" font-family="Helvetica,sans-Serif" font-size="10.00" fill="#000000">+getAllowedSystems()</text>
<text text-anchor="start" x="20.0185" y="-471" font-family="Helvetica,sans-Serif" font-size="10.00" fill="#000000">+getConfigSchema()</text>
</g>
<!-- A1&#45;&gt;A2 -->
<g id="edge1" class="edge">
<title>A1&#45;&gt;A2</title>
<path fill="none" stroke="#000000" d="M66,-633.6693C66,-609.4424 66,-574.1663 66,-546.2238"/>
<polygon fill="#000000" stroke="#000000" points="66,-643.957 61.5001,-633.9569 66,-638.957 66.0001,-633.957 66.0001,-633.957 66.0001,-633.957 66,-638.957 70.5001,-633.957 66,-643.957 66,-643.957"/>
</g>
<!-- A3 -->
<g id="node4" class="node">
<title>A3</title>
<polygon fill="none" stroke="#000000" points="92,-364 92,-396 200,-396 200,-364 92,-364"/>
<text text-anchor="start" x="101.828" y="-377" font-family="Helvetica,sans-Serif" font-size="10.00" fill="#000000">BoardImportService</text>
<polygon fill="none" stroke="#000000" points="92,-284 92,-364 200,-364 200,-284 92,-284"/>
<text text-anchor="start" x="125.8645" y="-345" font-family="Helvetica,sans-Serif" font-size="10.00" fill="#000000">+import()</text>
<text text-anchor="start" x="118.9105" y="-333" font-family="Helvetica,sans-Serif" font-size="10.00" fill="#000000">+bootstrap()</text>
<text text-anchor="start" x="105.857" y="-321" font-family="Helvetica,sans-Serif" font-size="10.00" fill="#000000">+validateSystem()</text>
<text text-anchor="start" x="108.218" y="-309" font-family="Helvetica,sans-Serif" font-size="10.00" fill="#000000">#validateConfig()</text>
<text text-anchor="start" x="112.107" y="-297" font-family="Helvetica,sans-Serif" font-size="10.00" fill="#000000">#validateData()</text>
</g>
<!-- A2&#45;&gt;A3 -->
<g id="edge2" class="edge">
<title>A2&#45;&gt;A3</title>
<path fill="none" stroke="#000000" stroke-dasharray="5,2" d="M87.8604,-457.7328C95.8577,-441.5382 105.0823,-422.8583 113.7939,-405.2174"/>
<polygon fill="#000000" stroke="#000000" points="118.2935,-396.1057 117.9004,-407.0646 116.0795,-400.5889 113.8656,-405.072 113.8656,-405.072 113.8656,-405.072 116.0795,-400.5889 109.8308,-403.0795 118.2935,-396.1057 118.2935,-396.1057"/>
<text text-anchor="middle" x="88.3076" y="-434.7378" font-family="Helvetica,sans-Serif" font-size="10.00" fill="#000000">uses</text>
</g>
<!-- A7 -->
<g id="node8" class="node">
<title>A7</title>
<polygon fill="none" stroke="#000000" points="37,-196 37,-228 129,-228 129,-196 37,-196"/>
<text text-anchor="start" x="46.612" y="-209" font-family="Helvetica,sans-Serif" font-size="10.00" fill="#000000">TrelloApiService</text>
<polygon fill="none" stroke="#000000" points="37,-164 37,-196 129,-196 129,-164 37,-164"/>
<text text-anchor="start" x="53.9655" y="-177" font-family="Helvetica,sans-Serif" font-size="10.00" fill="#000000">+name:string</text>
</g>
<!-- A3&#45;&gt;A7 -->
<g id="edge6" class="edge">
<title>A3&#45;&gt;A7</title>
<path fill="none" stroke="#000000" stroke-dasharray="5,2" d="M114.8609,-283.9135C107.8316,-268.5143 100.7854,-252.0928 95.0404,-237.6613"/>
<polygon fill="#000000" stroke="#000000" points="91.2872,-228.0253 99.1098,-235.7102 93.1019,-232.6844 94.9167,-237.3434 94.9167,-237.3434 94.9167,-237.3434 93.1019,-232.6844 90.7235,-238.9767 91.2872,-228.0253 91.2872,-228.0253"/>
<text text-anchor="middle" x="99.6759" y="-267.8975" font-family="Helvetica,sans-Serif" font-size="10.00" fill="#000000">uses</text>
</g>
<!-- A9 -->
<g id="node10" class="node">
<title>A9</title>
<polygon fill="none" stroke="#000000" points="148,-202 148,-234 273,-234 273,-202 148,-202"/>
<text text-anchor="start" x="170.7765" y="-215" font-family="Helvetica,sans-Serif" font-size="10.00" fill="#000000">TrelloJsonService</text>
<polygon fill="none" stroke="#000000" points="148,-158 148,-202 273,-202 273,-158 148,-158"/>
<text text-anchor="start" x="181.4655" y="-183" font-family="Helvetica,sans-Serif" font-size="10.00" fill="#000000">+name:string</text>
<text text-anchor="start" x="157.981" y="-171" font-family="Helvetica,sans-Serif" font-size="10.00" fill="#000000">#needValidateData:true</text>
</g>
<!-- A3&#45;&gt;A9 -->
<g id="edge9" class="edge">
<title>A3&#45;&gt;A9</title>
<path fill="none" stroke="#000000" stroke-dasharray="5,2" d="M164.3261,-283.9135C170.0039,-270.5688 176.3462,-256.4563 182.4816,-243.5365"/>
<polygon fill="#000000" stroke="#000000" points="186.9002,-234.3677 186.6126,-245.3298 184.7295,-238.872 182.5588,-243.3762 182.5588,-243.3762 182.5588,-243.3762 184.7295,-238.872 178.505,-241.4226 186.9002,-234.3677 186.9002,-234.3677"/>
<text text-anchor="middle" x="163.6874" y="-260.9237" font-family="Helvetica,sans-Serif" font-size="10.00" fill="#000000">uses</text>
</g>
<!-- A10 -->
<g id="node11" class="node">
<title>A10</title>
<polygon fill="#fff8dc" stroke="#000000" points="317.7872,-362 218.2128,-362 218.2128,-318 323.7872,-318 323.7872,-356 317.7872,-362"/>
<polyline fill="none" stroke="#000000" points="317.7872,-362 317.7872,-356 "/>
<polyline fill="none" stroke="#000000" points="323.7872,-356 317.7872,-356 "/>
<text text-anchor="middle" x="271" y="-349" font-family="Helvetica,sans-Serif" font-size="10.00" fill="#000000">validateSystem is</text>
<text text-anchor="middle" x="271" y="-337" font-family="Helvetica,sans-Serif" font-size="10.00" fill="#000000">public because is</text>
<text text-anchor="middle" x="271" y="-325" font-family="Helvetica,sans-Serif" font-size="10.00" fill="#000000">used on Api.</text>
</g>
<!-- A3&#45;&gt;A10 -->
<g id="edge11" class="edge">
<title>A3&#45;&gt;A10</title>
<path fill="none" stroke="#000000" stroke-dasharray="5,2" d="M200.1992,-340C206.1915,-340 212.1837,-340 218.176,-340"/>
</g>
<!-- A4 -->
<g id="node5" class="node">
<title>A4</title>
<polygon fill="none" stroke="#000000" points="264.1131,-812 189.8869,-812 189.8869,-776 264.1131,-776 264.1131,-812"/>
<text text-anchor="middle" x="227" y="-791" font-family="Helvetica,sans-Serif" font-size="10.00" fill="#000000">Command</text>
</g>
<!-- A5 -->
<g id="node6" class="node">
<title>A5</title>
<polygon fill="none" stroke="#000000" points="148,-684 148,-716 307,-716 307,-684 148,-684"/>
<text text-anchor="start" x="199.9955" y="-697" font-family="Helvetica,sans-Serif" font-size="10.00" fill="#000000">BoardImport</text>
<polygon fill="none" stroke="#000000" points="148,-652 148,-684 307,-684 307,-652 148,-652"/>
<text text-anchor="start" x="157.907" y="-665" font-family="Helvetica,sans-Serif" font-size="10.00" fill="#000000">+boardImportCommandService</text>
<polygon fill="none" stroke="#000000" points="148,-608 148,-652 307,-652 307,-608 148,-608"/>
<text text-anchor="start" x="200.8305" y="-633" font-family="Helvetica,sans-Serif" font-size="10.00" fill="#000000">#configure()</text>
<text text-anchor="start" x="177.76" y="-621" font-family="Helvetica,sans-Serif" font-size="10.00" fill="#000000">#execute(input,output)</text>
</g>
<!-- A4&#45;&gt;A5 -->
<g id="edge3" class="edge">
<title>A4&#45;&gt;A5</title>
<path fill="none" stroke="#000000" d="M227,-765.6356C227,-751.1554 227,-733.0451 227,-716.0324"/>
<polygon fill="#000000" stroke="#000000" points="227,-775.9227 222.5001,-765.9227 227,-770.9227 227.0001,-765.9227 227.0001,-765.9227 227.0001,-765.9227 227,-770.9227 231.5001,-765.9228 227,-775.9227 227,-775.9227"/>
</g>
<!-- A6 -->
<g id="node7" class="node">
<title>A6</title>
<polygon fill="none" stroke="#000000" points="150,-526 150,-558 304,-558 304,-526 150,-526"/>
<text text-anchor="start" x="159.7715" y="-539" font-family="Helvetica,sans-Serif" font-size="10.00" fill="#000000">BoardImportCommandService</text>
<polygon fill="none" stroke="#000000" points="150,-446 150,-526 304,-526 304,-446 150,-446"/>
<text text-anchor="start" x="199.9105" y="-507" font-family="Helvetica,sans-Serif" font-size="10.00" fill="#000000">+bootstrap()</text>
<text text-anchor="start" x="206.8645" y="-495" font-family="Helvetica,sans-Serif" font-size="10.00" fill="#000000">+import()</text>
<text text-anchor="start" x="186.857" y="-483" font-family="Helvetica,sans-Serif" font-size="10.00" fill="#000000">+validateSystem()</text>
<text text-anchor="start" x="189.218" y="-471" font-family="Helvetica,sans-Serif" font-size="10.00" fill="#000000">#validateConfig()</text>
<text text-anchor="start" x="193.107" y="-459" font-family="Helvetica,sans-Serif" font-size="10.00" fill="#000000">#validateData()</text>
</g>
<!-- A5&#45;&gt;A6 -->
<g id="edge4" class="edge">
<title>A5&#45;&gt;A6</title>
<path fill="none" stroke="#000000" stroke-dasharray="5,2" d="M227,-607.8313C227,-595.0442 227,-581.2707 227,-568.0248"/>
<polygon fill="#000000" stroke="#000000" points="227,-558.0234 231.5001,-568.0234 227,-563.0234 227.0001,-568.0234 227.0001,-568.0234 227.0001,-568.0234 227,-563.0234 222.5001,-568.0235 227,-558.0234 227,-558.0234"/>
<text text-anchor="middle" x="218.5476" y="-586.7051" font-family="Helvetica,sans-Serif" font-size="10.00" fill="#000000">uses</text>
</g>
<!-- A6&#45;&gt;A3 -->
<g id="edge5" class="edge">
<title>A6&#45;&gt;A3</title>
<path fill="none" stroke="#000000" d="M198.8975,-445.7949C192.3634,-432.7268 185.3528,-418.7057 178.6417,-405.2834"/>
<polygon fill="#000000" stroke="#000000" points="174.0529,-396.1057 182.55,-403.0375 176.289,-400.5779 178.5251,-405.05 178.5251,-405.05 178.5251,-405.05 176.289,-400.5779 174.5001,-407.0625 174.0529,-396.1057 174.0529,-396.1057"/>
</g>
<!-- A7&#45;&gt;A3 -->
<g id="edge7" class="edge">
<title>A7&#45;&gt;A3</title>
<path fill="none" stroke="#000000" stroke-dasharray="5,2" d="M102.735,-228.0253C109.5347,-241.763 117.1224,-258.3431 124.0627,-274.4849"/>
<polygon fill="#000000" stroke="#000000" points="128.0634,-283.9135 120.0148,-276.4657 126.1104,-279.3107 124.1573,-274.7079 124.1573,-274.7079 124.1573,-274.7079 126.1104,-279.3107 128.2998,-272.9502 128.0634,-283.9135 128.0634,-283.9135"/>
<text text-anchor="middle" x="118.307" y="-237.5757" font-family="Helvetica,sans-Serif" font-size="10.00" fill="#000000">uses</text>
</g>
<!-- A8 -->
<g id="node9" class="node">
<title>A8</title>
<polygon fill="none" stroke="#000000" points="80,-64 80,-108 213,-108 213,-64 80,-64"/>
<text text-anchor="start" x="117.04" y="-89" font-family="Helvetica,sans-Serif" font-size="10.00" fill="#000000">&lt;&lt;abstract&gt;&gt;</text>
<text text-anchor="start" x="98.9935" y="-77" font-family="Helvetica,sans-Serif" font-size="10.00" fill="#000000">ABoardImportService</text>
<polygon fill="none" stroke="#000000" points="80,-32 80,-64 213,-64 213,-32 80,-32"/>
<text text-anchor="start" x="92.036" y="-45" font-family="Helvetica,sans-Serif" font-size="10.00" fill="#000000">#needValidateData:false</text>
<polygon fill="none" stroke="#000000" points="80,0 80,-32 213,-32 213,0 80,0"/>
<text text-anchor="start" x="89.677" y="-13" font-family="Helvetica,sans-Serif" font-size="10.00" fill="#000000">+needValidateData():bool</text>
</g>
<!-- A7&#45;&gt;A8 -->
<g id="edge8" class="edge">
<title>A7&#45;&gt;A8</title>
<path fill="none" stroke="#000000" stroke-dasharray="5,2" d="M97.2957,-163.778C103.3956,-150.029 110.7371,-133.4813 117.8485,-117.4527"/>
<polygon fill="none" stroke="#000000" points="121.1416,-118.6605 121.9978,-108.1003 114.743,-115.8216 121.1416,-118.6605"/>
<text text-anchor="middle" x="96.9205" y="-140.7815" font-family="Helvetica,sans-Serif" font-size="10.00" fill="#000000">implements</text>
</g>
<!-- A9&#45;&gt;A3 -->
<g id="edge10" class="edge">
<title>A9&#45;&gt;A3</title>
<path fill="none" stroke="#000000" stroke-dasharray="5,2" d="M198.9952,-234.3677C194.0646,-246.7117 188.0483,-260.7568 181.8434,-274.4849"/>
<polygon fill="#000000" stroke="#000000" points="177.5286,-283.9135 177.598,-272.9478 179.6093,-279.367 181.6899,-274.8204 181.6899,-274.8204 181.6899,-274.8204 179.6093,-279.367 185.7818,-276.693 177.5286,-283.9135 177.5286,-283.9135"/>
<text text-anchor="middle" x="200.0654" y="-251.3391" font-family="Helvetica,sans-Serif" font-size="10.00" fill="#000000">uses</text>
</g>
<!-- A9&#45;&gt;A8 -->
<g id="edge13" class="edge">
<title>A9&#45;&gt;A8</title>
<path fill="none" stroke="#000000" stroke-dasharray="5,2" d="M192.8492,-157.9466C187.2535,-145.5313 180.8796,-131.389 174.6742,-117.6209"/>
<polygon fill="none" stroke="#000000" points="177.7167,-115.8534 170.4168,-108.1747 171.3349,-118.7297 177.7167,-115.8534"/>
<text text-anchor="middle" x="177.6953" y="-141.8944" font-family="Helvetica,sans-Serif" font-size="10.00" fill="#000000">implements</text>
</g>
<!-- A11 -->
<g id="node12" class="node">
<title>A11</title>
<polygon fill="#fff8dc" stroke="#000000" points="403.024,-224 290.976,-224 290.976,-168 409.024,-168 409.024,-218 403.024,-224"/>
<polyline fill="none" stroke="#000000" points="403.024,-224 403.024,-218 "/>
<polyline fill="none" stroke="#000000" points="409.024,-218 403.024,-218 "/>
<text text-anchor="middle" x="350" y="-211" font-family="Helvetica,sans-Serif" font-size="10.00" fill="#000000">To create an import</text>
<text text-anchor="middle" x="350" y="-199" font-family="Helvetica,sans-Serif" font-size="10.00" fill="#000000">to another system,</text>
<text text-anchor="middle" x="350" y="-187" font-family="Helvetica,sans-Serif" font-size="10.00" fill="#000000">create another class</text>
<text text-anchor="middle" x="350" y="-175" font-family="Helvetica,sans-Serif" font-size="10.00" fill="#000000">similar to this.</text>
</g>
<!-- A9&#45;&gt;A11 -->
<g id="edge12" class="edge">
<title>A9&#45;&gt;A11</title>
<path fill="none" stroke="#000000" stroke-dasharray="5,2" d="M272.6172,-196C278.6627,-196 284.7083,-196 290.7538,-196"/>
</g>
</g>
</svg>

Before

Width:  |  Height:  |  Size: 16 KiB

View File

@@ -1,24 +0,0 @@
// Created using [yUML](https://github.com/jaime-olivares/vscode-yuml)
// {type:class}
// {direction:topDown}
// {generate:true}
[note: Classes used on board import. Methods just to illustrate. {bg:cornsilk}]
[ApiController]<-[BoardImportApiController|+import();+getAllowedSystems();+getConfigSchema()]
[BoardImportApiController]uses-.->[BoardImportService|+import();+bootstrap();+validateSystem();#validateConfig();#validateData();]
[Command]<-[BoardImport|+boardImportCommandService|#configure();#execute(input,output)]
[BoardImport]uses-.->[BoardImportCommandService|+bootstrap();+import();+validateSystem();#validateConfig();#validateData()]
[BoardImportCommandService]->[BoardImportService]
[BoardImportService]uses-.->[TrelloApiService|+name:string]
[TrelloApiService]uses-.->[BoardImportService]
[TrelloApiService]implements-.-^[<<abstract>> ABoardImportService|#needValidateData:false|+needValidateData():bool]
[BoardImportService]uses-.->[TrelloJsonService|+name:string;#needValidateData:true]
[TrelloJsonService]uses-.->[BoardImportService]
[BoardImportService]-[note: validateSystem is public because is used on Api. {bg:cornsilk}]
[TrelloJsonService]-[note: To create an import to another system, create another class similar to this. {bg:cornsilk}]
[TrelloJsonService]implements-.-^[<<abstract>> ABoardImportService]

View File

@@ -1,4 +0,0 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<svg xmlns="http://www.w3.org/2000/svg" height="32" width="32" version="1.0" viewbox="0 0 32 32">
<path d="m16 1-10 18h11l-1 12 10-18h-11z"/>
</svg>

Before

Width:  |  Height:  |  Size: 205 B

View File

@@ -1,4 +0,0 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<svg xmlns="http://www.w3.org/2000/svg" height="32" width="32" version="1.0" viewBox="0 0 32 32">
<path d="m16 1-10 18h11l-1 12 10-18h-11z" fill="#FFF"/>
</svg>

Before

Width:  |  Height:  |  Size: 217 B

1
img/archive-white.svg Normal file
View File

@@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 16 16"><g transform="translate(0 -1036.362)" fill="#fff"><path d="M1.93 1041.296c-.185 0-.336.138-.336.31v9.842c0 .172.15.313.336.313h12.517c.185 0 .333-.14.333-.313v-9.842c0-.172-.148-.31-.333-.31H1.93zm4.124 1.507h4.223c.39 0 .705.314.705.704v.43c0 .39-.315.705-.705.705H6.054a.703.703 0 0 1-.705-.705v-.43c0-.39.314-.704.705-.704z"/><rect width="15.742" height="2.296" x=".136" y="1037.543" ry="0"/></g></svg>

After

Width:  |  Height:  |  Size: 488 B

View File

@@ -1 +0,0 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 58 58" width="512" height="512"><g fill="#000"><path d="M54.319 37.839C54.762 35.918 55 33.96 55 32c0-9.095-4.631-17.377-12.389-22.153a1 1 0 1 0-1.049 1.703C48.724 15.96 53 23.604 53 32c0 1.726-.2 3.451-.573 5.147A6.992 6.992 0 0 0 51 37c-3.86 0-7 3.141-7 7s3.14 7 7 7 7-3.141 7-7a7.006 7.006 0 0 0-3.681-6.161zM38.171 54.182A23.867 23.867 0 0 1 29 56a24.047 24.047 0 0 1-17.017-7.092A6.974 6.974 0 0 0 14 44c0-3.859-3.14-7-7-7s-7 3.141-7 7 3.14 7 7 7a6.952 6.952 0 0 0 3.381-.875C15.26 55.136 21.994 58 29 58c3.435 0 6.778-.663 9.936-1.971.51-.211.753-.796.542-1.307a1.001 1.001 0 0 0-1.307-.54zM4 31.213a1 1 0 0 0 1.068-.927c.712-10.089 7.586-18.52 17.22-21.314C23.142 11.874 25.825 14 29 14c3.86 0 7-3.141 7-7s-3.14-7-7-7c-3.851 0-6.985 3.127-6.999 6.975C11.42 9.922 3.851 19.12 3.073 30.146A.999.999 0 0 0 4 31.213z"/></g></svg>

Before

Width:  |  Height:  |  Size: 885 B

View File

@@ -1 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 58 58" width="512" height="512"><g fill="#fff"><path d="M54.319 37.839C54.762 35.918 55 33.96 55 32c0-9.095-4.631-17.377-12.389-22.153a1 1 0 1 0-1.049 1.703C48.724 15.96 53 23.604 53 32c0 1.726-.2 3.451-.573 5.147A6.992 6.992 0 0 0 51 37c-3.86 0-7 3.141-7 7s3.14 7 7 7 7-3.141 7-7a7.006 7.006 0 0 0-3.681-6.161zM38.171 54.182A23.867 23.867 0 0 1 29 56a24.047 24.047 0 0 1-17.017-7.092A6.974 6.974 0 0 0 14 44c0-3.859-3.14-7-7-7s-7 3.141-7 7 3.14 7 7 7a6.952 6.952 0 0 0 3.381-.875C15.26 55.136 21.994 58 29 58c3.435 0 6.778-.663 9.936-1.971.51-.211.753-.796.542-1.307a1.001 1.001 0 0 0-1.307-.54zM4 31.213a1 1 0 0 0 1.068-.927c.712-10.089 7.586-18.52 17.22-21.314C23.142 11.874 25.825 14 29 14c3.86 0 7-3.141 7-7s-3.14-7-7-7c-3.851 0-6.985 3.127-6.999 6.975C11.42 9.922 3.851 19.12 3.073 30.146A.999.999 0 0 0 4 31.213z"/></g></svg>
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 58 58" width="512" height="512"><g fill="#000"><path d="M54.319 37.839C54.762 35.918 55 33.96 55 32c0-9.095-4.631-17.377-12.389-22.153a1 1 0 1 0-1.049 1.703C48.724 15.96 53 23.604 53 32c0 1.726-.2 3.451-.573 5.147A6.992 6.992 0 0 0 51 37c-3.86 0-7 3.141-7 7s3.14 7 7 7 7-3.141 7-7a7.006 7.006 0 0 0-3.681-6.161zM38.171 54.182A23.867 23.867 0 0 1 29 56a24.047 24.047 0 0 1-17.017-7.092A6.974 6.974 0 0 0 14 44c0-3.859-3.14-7-7-7s-7 3.141-7 7 3.14 7 7 7a6.952 6.952 0 0 0 3.381-.875C15.26 55.136 21.994 58 29 58c3.435 0 6.778-.663 9.936-1.971.51-.211.753-.796.542-1.307a1.001 1.001 0 0 0-1.307-.54zM4 31.213a1 1 0 0 0 1.068-.927c.712-10.089 7.586-18.52 17.22-21.314C23.142 11.874 25.825 14 29 14c3.86 0 7-3.141 7-7s-3.14-7-7-7c-3.851 0-6.985 3.127-6.999 6.975C11.42 9.922 3.851 19.12 3.073 30.146A.999.999 0 0 0 4 31.213z"/></g></svg>

Before

Width:  |  Height:  |  Size: 885 B

After

Width:  |  Height:  |  Size: 885 B

1
img/clone.svg Normal file
View File

@@ -0,0 +1 @@
<svg width="16" height="16" version="1.1" xmlns="http://www.w3.org/2000/svg"><path d="M11.8 13.8H2.2V4.2h9.6m1.2 0c0-.67-.53-1.2-1.2-1.2H2.2C1.53 3 1 3.53 1 4.2v9.6c0 .67.53 1.2 1.2 1.2h9.6c.67 0 1.2-.53 1.2-1.2"/><path d="m4.2 1c-0.67 0-1.2 0.54-1.2 1.2h10.8v10.8c0.67 0 1.2-0.53 1.2-1.2v-9.6c0-0.67-0.53-1.2-1.2-1.2z"/></svg>

After

Width:  |  Height:  |  Size: 327 B

1
img/reply.svg Normal file
View File

@@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" height="16" width="16"><path d="M15 15s-.4-7.8-7-10V1L1 8l7 7v-4c5.1 0 7 4 7 4z"/></svg>

After

Width:  |  Height:  |  Size: 128 B

View File

@@ -31,6 +31,7 @@ use OCA\Deck\Db\Acl;
use OCA\Deck\Db\AclMapper;
use OCA\Deck\Db\Assignment;
use OCA\Deck\Db\Attachment;
use OCA\Deck\Db\AttachmentMapper;
use OCA\Deck\Db\Board;
use OCA\Deck\Db\BoardMapper;
use OCA\Deck\Db\Card;
@@ -38,6 +39,7 @@ use OCA\Deck\Db\CardMapper;
use OCA\Deck\Db\Label;
use OCA\Deck\Db\Stack;
use OCA\Deck\Db\StackMapper;
use OCA\Deck\NoPermissionException;
use OCA\Deck\Service\PermissionService;
use OCP\Activity\IEvent;
use OCP\Activity\IManager;
@@ -45,24 +47,19 @@ use OCP\AppFramework\Db\DoesNotExistException;
use OCP\AppFramework\Db\MultipleObjectsReturnedException;
use OCP\Comments\IComment;
use OCP\IUser;
use OCP\Server;
use OCP\L10N\IFactory;
use Psr\Log\LoggerInterface;
class ActivityManager {
public const DECK_NOAUTHOR_COMMENT_SYSTEM_ENFORCED = 'DECK_NOAUTHOR_COMMENT_SYSTEM_ENFORCED';
public const SUBJECT_PARAMS_MAX_LENGTH = 4000;
public const SHORTENED_DESCRIPTION_MAX_LENGTH = 2000;
private IManager $manager;
private ?string $userId;
private PermissionService $permissionService;
private BoardMapper $boardMapper;
private CardMapper $cardMapper;
private AclMapper $aclMapper;
private StackMapper $stackMapper;
private IFactory $l10nFactory;
private $manager;
private $userId;
private $permissionService;
private $boardMapper;
private $cardMapper;
private $attachmentMapper;
private $aclMapper;
private $stackMapper;
private $l10nFactory;
public const DECK_OBJECT_BOARD = 'deck_board';
public const DECK_OBJECT_CARD = 'deck_card';
@@ -114,15 +111,17 @@ class ActivityManager {
BoardMapper $boardMapper,
CardMapper $cardMapper,
StackMapper $stackMapper,
AttachmentMapper $attachmentMapper,
AclMapper $aclMapper,
IFactory $l10nFactory,
?string $userId
$userId
) {
$this->manager = $manager;
$this->permissionService = $permissionsService;
$this->boardMapper = $boardMapper;
$this->cardMapper = $cardMapper;
$this->stackMapper = $stackMapper;
$this->attachmentMapper = $attachmentMapper;
$this->aclMapper = $aclMapper;
$this->l10nFactory = $l10nFactory;
$this->userId = $userId;
@@ -251,6 +250,19 @@ class ActivityManager {
try {
$event = $this->createEvent($objectType, $entity, $subject, $additionalParams, $author);
if ($event !== null) {
$json = json_encode($event->getSubjectParameters());
if (mb_strlen($json) > 4000) {
$params = json_decode(json_encode($event->getSubjectParameters()), true);
$newContent = $params['after'];
unset($params['before'], $params['after'], $params['card']['description']);
$params['after'] = mb_substr($newContent, 0, 2000);
if (mb_strlen($newContent) > 2000) {
$params['after'] .= '...';
}
$event->setSubject($event->getSubject(), $params);
}
$this->sendToUsers($event);
}
} catch (\Exception $e) {
@@ -312,10 +324,10 @@ class ActivityManager {
try {
$object = $this->findObjectForEntity($objectType, $entity);
} catch (DoesNotExistException $e) {
Server::get(LoggerInterface::class)->error('Could not create activity entry for ' . $subject . '. Entity not found.', (array)$entity);
\OC::$server->getLogger()->error('Could not create activity entry for ' . $subject . '. Entity not found.', (array)$entity);
return null;
} catch (MultipleObjectsReturnedException $e) {
Server::get(LoggerInterface::class)->error('Could not create activity entry for ' . $subject . '. Entity not found.', (array)$entity);
\OC::$server->getLogger()->error('Could not create activity entry for ' . $subject . '. Entity not found.', (array)$entity);
return null;
}
@@ -367,15 +379,7 @@ class ActivityManager {
case self::SUBJECT_CARD_USER_ASSIGN:
case self::SUBJECT_CARD_USER_UNASSIGN:
$subjectParams = $this->findDetailsForCard($entity->getId(), $subject);
if (isset($additionalParams['after']) && $additionalParams['after'] instanceof \DateTimeInterface) {
$additionalParams['after'] = $additionalParams['after']->format('c');
}
if (isset($additionalParams['before']) && $additionalParams['before'] instanceof \DateTimeInterface) {
$additionalParams['before'] = $additionalParams['before']->format('c');
}
break;
break;
case self::SUBJECT_ATTACHMENT_CREATE:
case self::SUBJECT_ATTACHMENT_UPDATE:
case self::SUBJECT_ATTACHMENT_DELETE:
@@ -407,31 +411,12 @@ class ActivityManager {
$subjectParams['author'] = $author === null ? $this->userId : $author;
$subjectParams = array_merge($subjectParams, $additionalParams);
$json = json_encode($subjectParams);
if (mb_strlen($json) > self::SUBJECT_PARAMS_MAX_LENGTH) {
$params = json_decode(json_encode($subjectParams), true);
if ($subject === self::SUBJECT_CARD_UPDATE_DESCRIPTION && isset($params['after'])) {
$newContent = $params['after'];
unset($params['before'], $params['after'], $params['card']['description']);
$params['after'] = mb_substr($newContent, 0, self::SHORTENED_DESCRIPTION_MAX_LENGTH);
if (mb_strlen($newContent) > self::SHORTENED_DESCRIPTION_MAX_LENGTH) {
$params['after'] .= '...';
}
$subjectParams = $params;
} else {
throw new \Exception('Subject parameters too long');
}
}
$event = $this->manager->generateEvent();
$event->setApp('deck')
->setType($eventType)
->setAuthor($subjectParams['author'])
->setObject($objectType, (int)$object->getId(), $object->getTitle())
->setSubject($subject, $subjectParams)
->setSubject($subject, array_merge($subjectParams, $additionalParams))
->setTimestamp(time());
if ($message !== null) {
@@ -559,4 +544,24 @@ class ActivityManager {
'board' => $board
];
}
public function canSeeCardActivity(int $cardId): bool {
try {
$this->permissionService->checkPermission($this->cardMapper, $cardId, Acl::PERMISSION_READ);
$card = $this->cardMapper->find($cardId);
return $card->getDeletedAt() === 0;
} catch (NoPermissionException $e) {
return false;
}
}
public function canSeeBoardActivity(int $boardId): bool {
try {
$this->permissionService->checkPermission($this->boardMapper, $boardId, Acl::PERMISSION_READ);
$board = $this->boardMapper->find($boardId);
return $board->getDeletedAt() === 0;
} catch (NoPermissionException $e) {
return false;
}
}
}

View File

@@ -69,7 +69,15 @@ class ChangeSet implements \JsonSerializable {
return $this->after;
}
public function jsonSerialize(): array {
/**
* Specify data which should be serialized to JSON
*
* @link http://php.net/manual/en/jsonserializable.jsonserialize.php
* @return mixed data which can be serialized by <b>json_encode</b>,
* which is a value of any type other than a resource.
* @since 5.4.0
*/
public function jsonSerialize() {
return [
'before' => $this->getBefore(),
'after' => $this->getAfter(),

View File

@@ -111,6 +111,9 @@ class DeckProvider implements IProvider {
$event->setAuthor($author);
}
if ($event->getObjectType() === ActivityManager::DECK_OBJECT_BOARD) {
if (!$this->activityManager->canSeeBoardActivity($event->getObjectId())) {
throw new \InvalidArgumentException();
}
if (isset($subjectParams['board']) && $event->getObjectName() === '') {
$event->setObject($event->getObjectType(), $event->getObjectId(), $subjectParams['board']['title']);
}
@@ -125,6 +128,9 @@ class DeckProvider implements IProvider {
}
if (isset($subjectParams['card']) && $event->getObjectType() === ActivityManager::DECK_OBJECT_CARD) {
if (!$this->activityManager->canSeeCardActivity($event->getObjectId())) {
throw new \InvalidArgumentException();
}
if ($event->getObjectName() === '') {
$event->setObject($event->getObjectType(), $event->getObjectId(), $subjectParams['card']['title']);
}
@@ -312,19 +318,12 @@ class DeckProvider implements IProvider {
$userLanguage = $this->config->getUserValue($event->getAuthor(), 'core', 'lang', $this->l10nFactory->findLanguage());
$userLocale = $this->config->getUserValue($event->getAuthor(), 'core', 'locale', $this->l10nFactory->findLocale());
$l10n = $this->l10nFactory->get('deck', $userLanguage, $userLocale);
if (is_array($subjectParams['after'])) {
// Unluckily there was a time when we stored jsonSerialized date objects in the database
// Broken in 1.8.0 and fixed again in 1.8.1
$date = new \DateTime($subjectParams['after']['date']);
$date->setTimezone(new \DateTimeZone(\date_default_timezone_get()));
} else {
$date = new \DateTime($subjectParams['after']);
$date->setTimezone(new \DateTimeZone(\date_default_timezone_get()));
}
$date = new \DateTime($subjectParams['after']);
$date->setTimezone(new \DateTimeZone(\date_default_timezone_get()));
$params['after'] = [
'type' => 'highlight',
'id' => 'dt:' . $subjectParams['after'],
'name' => $l10n->l('datetime', $date),
'name' => $l10n->l('datetime', $date)
];
}
return $params;

View File

@@ -26,13 +26,15 @@ namespace OCA\Deck\AppInfo;
use Closure;
use Exception;
use OC\EventDispatcher\SymfonyAdapter;
use OCA\Circles\Events\CircleDestroyedEvent;
use OCA\Deck\Activity\CommentEventHandler;
use OCA\Deck\Capabilities;
use OCA\Deck\Collaboration\Resources\ResourceProvider;
use OCA\Deck\Collaboration\Resources\ResourceProviderCard;
use OCA\Deck\Dashboard\DeckWidget;
use OCA\Deck\Db\Acl;
use OCA\Deck\Db\AclMapper;
use OCA\Deck\Db\AssignmentMapper;
use OCA\Deck\Db\BoardMapper;
use OCA\Deck\Db\CardMapper;
use OCA\Deck\Event\AclCreatedEvent;
use OCA\Deck\Event\AclDeletedEvent;
@@ -41,13 +43,10 @@ use OCA\Deck\Event\CardCreatedEvent;
use OCA\Deck\Event\CardDeletedEvent;
use OCA\Deck\Event\CardUpdatedEvent;
use OCA\Deck\Listeners\BeforeTemplateRenderedListener;
use OCA\Deck\Listeners\ParticipantCleanupListener;
use OCA\Deck\Listeners\FullTextSearchEventListener;
use OCA\Deck\Listeners\ResourceListener;
use OCA\Deck\Middleware\DefaultBoardMiddleware;
use OCA\Deck\Middleware\ExceptionMiddleware;
use OCA\Deck\Notification\Notifier;
use OCA\Deck\Reference\CardReferenceProvider;
use OCA\Deck\Search\CardCommentProvider;
use OCA\Deck\Search\DeckProvider;
use OCA\Deck\Service\PermissionService;
@@ -58,19 +57,20 @@ use OCP\AppFramework\Bootstrap\IBootContext;
use OCP\AppFramework\Bootstrap\IBootstrap;
use OCP\AppFramework\Bootstrap\IRegistrationContext;
use OCP\AppFramework\Http\Events\BeforeTemplateRenderedEvent;
use OCP\Collaboration\Reference\RenderReferenceEvent;
use OCP\Collaboration\Resources\IProviderManager;
use OCP\Comments\CommentsEntityEvent;
use OCP\Comments\ICommentsManager;
use OCP\EventDispatcher\Event;
use OCP\EventDispatcher\IEventDispatcher;
use OCP\Group\Events\GroupDeletedEvent;
use OCP\IConfig;
use OCP\IDBConnection;
use OCP\IRequest;
use OCP\Server;
use OCP\IGroup;
use OCP\IGroupManager;
use OCP\IServerContainer;
use OCP\IUser;
use OCP\IUserManager;
use OCP\Notification\IManager as NotificationManager;
use OCP\Share\IManager;
use OCP\User\Events\UserDeletedEvent;
use OCP\Util;
use Psr\Container\ContainerInterface;
@@ -79,19 +79,17 @@ class Application extends App implements IBootstrap {
public const COMMENT_ENTITY_TYPE = 'deckCard';
/** @var IServerContainer */
private $server;
public function __construct(array $urlParams = []) {
parent::__construct(self::APP_ID, $urlParams);
// TODO move this back to ::register after fixing the autoload issue
// (and use a listener class)
$container = $this->getContainer();
$eventDispatcher = $container->get(IEventDispatcher::class);
$eventDispatcher->addListener(RenderReferenceEvent::class, function () {
Util::addScript(self::APP_ID, self::APP_ID . '-card-reference');
});
$this->server = \OC::$server;
}
public function boot(IBootContext $context): void {
$context->injectFn(Closure::fromCallable([$this, 'registerUserGroupHooks']));
$context->injectFn(Closure::fromCallable([$this, 'registerCommentsEntity']));
$context->injectFn(Closure::fromCallable([$this, 'registerCommentsEventHandler']));
$context->injectFn(Closure::fromCallable([$this, 'registerNotifications']));
@@ -126,12 +124,8 @@ class Application extends App implements IBootstrap {
$context->registerSearchProvider(CardCommentProvider::class);
$context->registerDashboardWidget(DeckWidget::class);
// reference widget
$context->registerReferenceProvider(CardReferenceProvider::class);
// $context->registerEventListener(RenderReferenceEvent::class, CardReferenceListener::class);
$context->registerEventListener(BeforeTemplateRenderedEvent::class, BeforeTemplateRenderedListener::class);
// Event listening for full text search indexing
$context->registerEventListener(CardCreatedEvent::class, FullTextSearchEventListener::class);
$context->registerEventListener(CardUpdatedEvent::class, FullTextSearchEventListener::class);
@@ -139,26 +133,54 @@ class Application extends App implements IBootstrap {
$context->registerEventListener(AclCreatedEvent::class, FullTextSearchEventListener::class);
$context->registerEventListener(AclUpdatedEvent::class, FullTextSearchEventListener::class);
$context->registerEventListener(AclDeletedEvent::class, FullTextSearchEventListener::class);
// Handling cache invalidation for collections
$context->registerEventListener(AclCreatedEvent::class, ResourceListener::class);
$context->registerEventListener(AclDeletedEvent::class, ResourceListener::class);
$context->registerEventListener(UserDeletedEvent::class, ParticipantCleanupListener::class);
$context->registerEventListener(GroupDeletedEvent::class, ParticipantCleanupListener::class);
$context->registerEventListener(CircleDestroyedEvent::class, ParticipantCleanupListener::class);
}
public function registerNotifications(NotificationManager $notificationManager): void {
$notificationManager->registerNotifierService(Notifier::class);
}
private function registerUserGroupHooks(IUserManager $userManager, IGroupManager $groupManager): void {
$container = $this->getContainer();
// Delete user/group acl entries when they get deleted
$userManager->listen('\OC\User', 'postDelete', static function (IUser $user) use ($container) {
// delete existing acl entries for deleted user
/** @var AclMapper $aclMapper */
$aclMapper = $container->query(AclMapper::class);
$acls = $aclMapper->findByParticipant(Acl::PERMISSION_TYPE_USER, $user->getUID());
foreach ($acls as $acl) {
$aclMapper->delete($acl);
}
// delete existing user assignments
$assignmentMapper = $container->query(AssignmentMapper::class);
$assignments = $assignmentMapper->findByParticipant($user->getUID());
foreach ($assignments as $assignment) {
$assignmentMapper->delete($assignment);
}
/** @var BoardMapper $boardMapper */
$boardMapper = $container->query(BoardMapper::class);
$boards = $boardMapper->findAllByOwner($user->getUID());
foreach ($boards as $board) {
$boardMapper->delete($board);
}
});
$groupManager->listen('\OC\Group', 'postDelete', static function (IGroup $group) use ($container) {
/** @var AclMapper $aclMapper */
$aclMapper = $container->query(AclMapper::class);
$aclMapper->findByParticipant(Acl::PERMISSION_TYPE_GROUP, $group->getGID());
$acls = $aclMapper->findByParticipant(Acl::PERMISSION_TYPE_GROUP, $group->getGID());
foreach ($acls as $acl) {
$aclMapper->delete($acl);
}
});
}
public function registerCommentsEntity(IEventDispatcher $eventDispatcher): void {
$eventDispatcher->addListener(CommentsEntityEvent::EVENT_ENTITY, function (CommentsEntityEvent $event) {
$event->addEntityCollection(self::COMMENT_ENTITY_TYPE, function ($name) {
/** @var CardMapper */
$cardMapper = $this->getContainer()->get(CardMapper::class);
/** @var PermissionService $permissionService */
$permissionService = $this->getContainer()->get(PermissionService::class);
try {
@@ -181,7 +203,7 @@ class Application extends App implements IBootstrap {
$resourceManager->registerResourceProvider(ResourceProviderCard::class);
$symfonyAdapter->addListener('\OCP\Collaboration\Resources::loadAdditionalScripts', static function () {
if (strpos(Server::get(IRequest::class)->getPathInfo(), '/call/') === 0) {
if (strpos(\OC::$server->getRequest()->getPathInfo(), '/call/') === 0) {
// Talk integration has its own entrypoint which already includes collections handling
return;
}

View File

@@ -32,23 +32,20 @@ use OCP\AppFramework\QueryException;
use OCP\Collaboration\Resources\IManager;
use OCP\Collaboration\Resources\IProvider;
use OCP\Collaboration\Resources\IResource;
use OCP\IURLGenerator;
use OCP\IUser;
use OCP\Server;
class ResourceProvider implements IProvider {
public const RESOURCE_TYPE = 'deck';
private BoardMapper $boardMapper;
private PermissionService $permissionService;
private IURLGenerator $urlGenerator;
private $boardMapper;
private $permissionService;
protected array $nodes = [];
/** @var array */
protected $nodes = [];
public function __construct(BoardMapper $boardMapper, PermissionService $permissionService, IURLGenerator $urlGenerator) {
public function __construct(BoardMapper $boardMapper, PermissionService $permissionService) {
$this->boardMapper = $boardMapper;
$this->permissionService = $permissionService;
$this->urlGenerator = $urlGenerator;
}
/**
@@ -73,14 +70,14 @@ class ResourceProvider implements IProvider {
*/
public function getResourceRichObject(IResource $resource): array {
$board = $this->getBoard($resource);
$link = $this->urlGenerator->linkToRoute('deck.page.index') . '#/board/' . $resource->getId();
$link = \OC::$server->getURLGenerator()->linkToRoute('deck.page.index') . '#/board/' . $resource->getId();
return [
'type' => self::RESOURCE_TYPE,
'id' => $resource->getId(),
'name' => $board->getTitle(),
'link' => $link,
'iconUrl' => $this->urlGenerator->imagePath('deck', 'deck-dark.svg')
'iconUrl' => \OC::$server->getURLGenerator()->imagePath('deck', 'deck-dark.svg')
];
}
@@ -111,7 +108,7 @@ class ResourceProvider implements IProvider {
private function getBoard(IResource $resource) {
try {
return $this->boardMapper->find($resource->getId(), false, true);
return $this->boardMapper->find((int)$resource->getId(), false, true);
} catch (DoesNotExistException $e) {
} catch (MultipleObjectsReturnedException $e) {
return null;
@@ -121,7 +118,7 @@ class ResourceProvider implements IProvider {
public function invalidateAccessCache($boardId = null) {
try {
/** @var IManager $resourceManager */
$resourceManager = Server::get(IManager::class);
$resourceManager = \OC::$server->query(IManager::class);
} catch (QueryException $e) {
}
if ($boardId !== null) {

View File

@@ -37,16 +37,24 @@ use OCP\Collaboration\Resources\IResource;
use OCP\Collaboration\Resources\ResourceException;
use OCP\IURLGenerator;
use OCP\IUser;
use OCP\Server;
class ResourceProviderCard implements IProvider {
public const RESOURCE_TYPE = 'deck-card';
private CardMapper $cardMapper;
private BoardMapper $boardMapper;
private PermissionService $permissionService;
private IURLGenerator $urlGenerator;
protected array $nodes = [];
/** @var CardMapper */
private $cardMapper;
/** @var BoardMapper */
private $boardMapper;
/** @var PermissionService */
private $permissionService;
/** @var IURLGenerator */
private $urlGenerator;
/** @var array */
protected $nodes = [];
public function __construct(CardMapper $cardMapper, BoardMapper $boardMapper, PermissionService $permissionService, IURLGenerator $urlGenerator) {
$this->cardMapper = $cardMapper;
@@ -139,7 +147,7 @@ class ResourceProviderCard implements IProvider {
public function invalidateAccessCache($cardId = null) {
try {
/** @var IManager $resourceManager */
$resourceManager = Server::get(IManager::class);
$resourceManager = \OC::$server->query(IManager::class);
} catch (QueryException $e) {
}
if ($cardId !== null) {

View File

@@ -1,91 +0,0 @@
<?php
/**
* @copyright Copyright (c) 2021 Vitor Mattos <vitor@php.rio>
*
* @author Vitor Mattos <vitor@php.rio>
*
* @license GNU AGPL version 3 or any later version
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
*/
namespace OCA\Deck\Command;
use OCA\Deck\Service\Importer\BoardImportCommandService;
use Symfony\Component\Console\Command\Command;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Input\InputOption;
use Symfony\Component\Console\Output\OutputInterface;
class BoardImport extends Command {
private BoardImportCommandService $boardImportCommandService;
public function __construct(
BoardImportCommandService $boardImportCommandService
) {
$this->boardImportCommandService = $boardImportCommandService;
parent::__construct();
}
/**
* @return void
*/
protected function configure() {
$allowedSystems = $this->boardImportCommandService->getAllowedImportSystems();
$names = array_column($allowedSystems, 'name');
$this
->setName('deck:import')
->setDescription('Import data')
->addOption(
'system',
null,
InputOption::VALUE_REQUIRED,
'Source system for import. Available options: ' . implode(', ', $names) . '.',
null
)
->addOption(
'config',
null,
InputOption::VALUE_REQUIRED,
'Configuration json file.',
'config.json'
)
->addOption(
'data',
null,
InputOption::VALUE_OPTIONAL,
'Data file to import.',
'data.json'
)
;
}
/**
* @param InputInterface $input
* @param OutputInterface $output
*
* @return int
*/
protected function execute(InputInterface $input, OutputInterface $output): int {
$this
->boardImportCommandService
->setInput($input)
->setOutput($output)
->setCommand($this)
->import();
$output->writeln('Done!');
return 0;
}
}

View File

@@ -27,7 +27,6 @@ use OCA\Deck\Db\AssignmentMapper;
use OCA\Deck\Db\BoardMapper;
use OCA\Deck\Db\CardMapper;
use OCA\Deck\Db\StackMapper;
use OCA\Deck\Model\CardDetails;
use OCA\Deck\Service\BoardService;
use OCP\AppFramework\Db\DoesNotExistException;
use OCP\AppFramework\Db\MultipleObjectsReturnedException;
@@ -102,9 +101,7 @@ class UserExport extends Command {
$fullCard = $this->cardMapper->find($card->getId());
$assignedUsers = $this->assignedUsersMapper->findAll($card->getId());
$fullCard->setAssignedUsers($assignedUsers);
$cardDetails = new CardDetails($fullCard, $fullBoard);
$data[$board->getId()]['stacks'][$stack->getId()]['cards'][] = $cardDetails->jsonSerialize();
$data[$board->getId()]['stacks'][$stack->getId()]['cards'][] = (array)$fullCard->jsonSerialize();
}
}
}

View File

@@ -1,85 +0,0 @@
<?php
/**
* @copyright Copyright (c) 2021 Vitor Mattos <vitor@php.rio>
*
* @author Vitor Mattos <vitor@php.rio>
*
* @license GNU AGPL version 3 or any later version
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
*/
namespace OCA\Deck\Controller;
use OCA\Deck\Service\Importer\BoardImportService;
use OCP\AppFramework\Http;
use OCP\AppFramework\Http\DataResponse;
use OCP\AppFramework\OCSController;
use OCP\IRequest;
class BoardImportApiController extends OCSController {
/** @var BoardImportService */
private $boardImportService;
/** @var string */
private $userId;
public function __construct(
string $appName,
IRequest $request,
BoardImportService $boardImportService,
string $userId
) {
parent::__construct($appName, $request);
$this->boardImportService = $boardImportService;
$this->userId = $userId;
}
/**
* @NoAdminRequired
* @CORS
* @NoCSRFRequired
*/
public function import(string $system, array $config, array $data): DataResponse {
$this->boardImportService->setSystem($system);
$config = json_decode(json_encode($config));
$config->owner = $this->userId;
$this->boardImportService->setConfigInstance($config);
$this->boardImportService->setData(json_decode(json_encode($data)));
$this->boardImportService->import();
return new DataResponse($this->boardImportService->getBoard(), Http::STATUS_OK);
}
/**
* @NoAdminRequired
* @CORS
* @NoCSRFRequired
*/
public function getAllowedSystems(): DataResponse {
$allowedSystems = $this->boardImportService->getAllowedImportSystems();
return new DataResponse($allowedSystems, Http::STATUS_OK);
}
/**
* @NoAdminRequired
* @CORS
* @NoCSRFRequired
*/
public function getConfigSchema(string $name): DataResponse {
$this->boardImportService->setSystem($name);
$this->boardImportService->validateSystem();
$jsonSchemaPath = json_decode(file_get_contents($this->boardImportService->getJsonSchemaPath()));
return new DataResponse($jsonSchemaPath, Http::STATUS_OK);
}
}

View File

@@ -29,9 +29,7 @@ use OCA\Deck\Service\PermissionService;
use OCA\Files\Event\LoadSidebar;
use OCA\Viewer\Event\LoadViewer;
use OCP\AppFramework\Http\ContentSecurityPolicy;
use OCP\Collaboration\Resources\LoadAdditionalScriptsEvent as CollaborationResourcesEvent;
use OCP\EventDispatcher\IEventDispatcher;
use OCP\IConfig;
use OCP\IInitialStateService;
use OCP\IRequest;
use OCP\AppFramework\Http\TemplateResponse;
@@ -43,17 +41,16 @@ use OCA\Deck\Db\Acl;
use OCA\Deck\Service\CardService;
class PageController extends Controller {
private PermissionService $permissionService;
private IInitialStateService $initialState;
private ConfigService $configService;
private IEventDispatcher $eventDispatcher;
private CardMapper $cardMapper;
private IURLGenerator $urlGenerator;
private CardService $cardService;
private IConfig $config;
private $permissionService;
private $initialState;
private $configService;
private $eventDispatcher;
private $cardMapper;
private $urlGenerator;
private $cardService;
public function __construct(
string $AppName,
$AppName,
IRequest $request,
PermissionService $permissionService,
IInitialStateService $initialStateService,
@@ -61,8 +58,7 @@ class PageController extends Controller {
IEventDispatcher $eventDispatcher,
CardMapper $cardMapper,
IURLGenerator $urlGenerator,
CardService $cardService,
IConfig $config
CardService $cardService
) {
parent::__construct($AppName, $request);
@@ -73,7 +69,6 @@ class PageController extends Controller {
$this->cardMapper = $cardMapper;
$this->urlGenerator = $urlGenerator;
$this->cardService = $cardService;
$this->config = $config;
}
/**
@@ -89,17 +84,13 @@ class PageController extends Controller {
$this->initialState->provideInitialState(Application::APP_ID, 'config', $this->configService->getAll());
$this->eventDispatcher->dispatchTyped(new LoadSidebar());
$this->eventDispatcher->dispatchTyped(new CollaborationResourcesEvent());
if (class_exists(LoadViewer::class)) {
$this->eventDispatcher->dispatchTyped(new LoadViewer());
}
$response = new TemplateResponse('deck', 'main', [
'id-app-content' => '#app-content-vue',
'id-app-navigation' => '#app-navigation-vue',
]);
$response = new TemplateResponse('deck', 'main');
if ($this->config->getSystemValueBool('debug', false)) {
if (\OC::$server->getConfig()->getSystemValueBool('debug', false)) {
$csp = new ContentSecurityPolicy();
$csp->addAllowedConnectDomain('*');
$csp->addAllowedScriptDomain('*');

View File

@@ -27,7 +27,6 @@ declare(strict_types=1);
namespace OCA\Deck\Controller;
use OCA\Deck\Db\Card;
use OCA\Deck\Model\CardDetails;
use OCA\Deck\Service\SearchService;
use OCP\AppFramework\Http\DataResponse;
use OCP\AppFramework\OCSController;
@@ -51,12 +50,9 @@ class SearchController extends OCSController {
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) {
$board = $card->getRelatedBoard();
$json = (new CardDetails($card, $board))->jsonSerialize();
$json['relatedBoard'] = $board;
$json = $card->jsonSerialize();
$json['relatedStack'] = $card->getRelatedStack();
$json['relatedBoard'] = $card->getRelatedBoard();
return $json;
}, $cards));
}

View File

@@ -24,8 +24,7 @@
namespace OCA\Deck\Cron;
use OCP\AppFramework\Utility\ITimeFactory;
use OCP\BackgroundJob\Job;
use OC\BackgroundJob\Job;
use OCA\Deck\Activity\ActivityManager;
use OCA\Deck\Db\CardMapper;
@@ -36,8 +35,7 @@ class CardDescriptionActivity extends Job {
/** @var CardMapper */
private $cardMapper;
public function __construct(ITimeFactory $time, ActivityManager $activityManager, CardMapper $cardMapper) {
parent::__construct($time);
public function __construct(ActivityManager $activityManager, CardMapper $cardMapper) {
$this->activityManager = $activityManager;
$this->cardMapper = $cardMapper;
}

View File

@@ -24,35 +24,25 @@
namespace OCA\Deck\Cron;
use OCP\AppFramework\Utility\ITimeFactory;
use OCP\BackgroundJob\TimedJob;
use OC\BackgroundJob\Job;
use OCA\Deck\Db\AttachmentMapper;
use OCA\Deck\Db\BoardMapper;
use OCA\Deck\Db\CardMapper;
use OCA\Deck\InvalidAttachmentType;
use OCA\Deck\Service\AttachmentService;
use OCP\BackgroundJob\IJob;
class DeleteCron extends TimedJob {
class DeleteCron extends Job {
/** @var BoardMapper */
private $boardMapper;
/** @var CardMapper */
private $cardMapper;
/** @var AttachmentService */
private $attachmentService;
/** @var AttachmentMapper */
private $attachmentMapper;
public function __construct(ITimeFactory $time, BoardMapper $boardMapper, CardMapper $cardMapper, AttachmentService $attachmentService, AttachmentMapper $attachmentMapper) {
parent::__construct($time);
public function __construct(BoardMapper $boardMapper, AttachmentService $attachmentService, AttachmentMapper $attachmentMapper) {
$this->boardMapper = $boardMapper;
$this->cardMapper = $cardMapper;
$this->attachmentService = $attachmentService;
$this->attachmentMapper = $attachmentMapper;
$this->setInterval(60 * 60 * 24);
$this->setTimeSensitivity(IJob::TIME_INSENSITIVE);
}
/**
@@ -65,12 +55,6 @@ class DeleteCron extends TimedJob {
$this->boardMapper->delete($board);
}
$timeLimit = time() - (60 * 5); // 5 min buffer
$cards = $this->cardMapper->findToDelete($timeLimit, 500);
foreach ($cards as $card) {
$this->cardMapper->delete($card);
}
$attachments = $this->attachmentMapper->findToDelete();
foreach ($attachments as $attachment) {
try {

View File

@@ -23,8 +23,7 @@
namespace OCA\Deck\Cron;
use OCP\AppFramework\Utility\ITimeFactory;
use OCP\BackgroundJob\Job;
use OC\BackgroundJob\Job;
use OCA\Deck\Db\Card;
use OCA\Deck\Db\CardMapper;
use OCA\Deck\Notification\NotificationHelper;
@@ -41,12 +40,10 @@ class ScheduledNotifications extends Job {
protected $logger;
public function __construct(
ITimeFactory $time,
CardMapper $cardMapper,
NotificationHelper $notificationHelper,
ILogger $logger
) {
parent::__construct($time);
$this->cardMapper = $cardMapper;
$this->notificationHelper = $notificationHelper;
$this->logger = $logger;

View File

@@ -26,34 +26,18 @@ declare(strict_types=1);
namespace OCA\Deck\Dashboard;
use DateTime;
use OCA\Deck\AppInfo\Application;
use OCA\Deck\Db\Label;
use OCA\Deck\Service\OverviewService;
use OCP\Dashboard\IAPIWidget;
use OCP\Dashboard\IButtonWidget;
use OCP\Dashboard\IIconWidget;
use OCP\Dashboard\Model\WidgetButton;
use OCP\Dashboard\Model\WidgetItem;
use OCP\IDateTimeFormatter;
use OCP\Dashboard\IWidget;
use OCP\IL10N;
use OCP\IURLGenerator;
use OCP\Util;
class DeckWidget implements IAPIWidget, IButtonWidget, IIconWidget {
private IL10N $l10n;
private OverviewService $dashboardService;
private IURLGenerator $urlGenerator;
private IDateTimeFormatter $dateTimeFormatter;
class DeckWidget implements IWidget {
public function __construct(IL10N $l10n,
OverviewService $dashboardService,
IDateTimeFormatter $dateTimeFormatter,
IURLGenerator $urlGenerator) {
/**
* @var IL10N
*/
private $l10n;
public function __construct(IL10N $l10n) {
$this->l10n = $l10n;
$this->dashboardService = $dashboardService;
$this->urlGenerator = $urlGenerator;
$this->dateTimeFormatter = $dateTimeFormatter;
}
/**
@@ -84,88 +68,17 @@ class DeckWidget implements IAPIWidget, IButtonWidget, IIconWidget {
return 'icon-deck';
}
/**
* @inheritDoc
*/
public function getIconUrl(): string {
return $this->urlGenerator->getAbsoluteURL(
$this->urlGenerator->imagePath(Application::APP_ID, 'deck-dark.svg')
);
}
/**
* @inheritDoc
*/
public function getUrl(): ?string {
return $this->urlGenerator->getAbsoluteURL(
$this->urlGenerator->linkToRoute(Application::APP_ID . '.page.index')
);
return null;
}
/**
* @inheritDoc
*/
public function load(): void {
Util::addScript('deck', 'deck-dashboard');
}
/**
* @inheritDoc
*/
public function getItems(string $userId, ?string $since = null, int $limit = 7): array {
$upcomingCards = $this->dashboardService->findUpcomingCards($userId);
$nowTimestamp = (new Datetime())->getTimestamp();
$sinceTimestamp = $since !== null ? (new Datetime($since))->getTimestamp() : null;
$upcomingCards = array_filter($upcomingCards, static function (array $card) use ($nowTimestamp, $sinceTimestamp) {
if ($card['duedate']) {
$ts = (new Datetime($card['duedate']))->getTimestamp();
return $ts > $nowTimestamp && ($sinceTimestamp === null || $ts > $sinceTimestamp);
}
return false;
});
usort($upcomingCards, static function ($a, $b) {
$a = new Datetime($a['duedate']);
$ta = $a->getTimestamp();
$b = new Datetime($b['duedate']);
$tb = $b->getTimestamp();
return ($ta > $tb) ? 1 : -1;
});
$upcomingCards = array_slice($upcomingCards, 0, $limit);
$urlGenerator = $this->urlGenerator;
$dateTimeFormatter = $this->dateTimeFormatter;
return array_map(static function (array $card) use ($urlGenerator, $dateTimeFormatter) {
$formattedDueDate = $dateTimeFormatter->formatDateTime(new DateTime($card['duedate']));
return new WidgetItem(
$card['title'] . ' (' . $formattedDueDate . ')',
implode(
', ',
array_map(static function (Label $label) {
return $label->jsonSerialize()['title'];
}, $card['labels'])
),
$urlGenerator->getAbsoluteURL(
$urlGenerator->linkToRoute(Application::APP_ID . '.page.redirectToCard', ['cardId' => $card['id']])
),
$urlGenerator->getAbsoluteURL(
$urlGenerator->imagePath(Application::APP_ID, 'deck-dark.svg')
),
$card['duedate']
);
}, $upcomingCards);
}
/**
* @inheritDoc
*/
public function getWidgetButtons(string $userId): array {
return [
new WidgetButton(
WidgetButton::TYPE_MORE,
$this->urlGenerator->getAbsoluteURL(
$this->urlGenerator->linkToRoute(Application::APP_ID . '.page.index')
),
$this->l10n->t('Load more')
),
];
\OCP\Util::addScript('deck', 'deck-dashboard');
}
}

View File

@@ -33,46 +33,18 @@ class AclMapper extends DeckMapper implements IPermissionMapper {
parent::__construct($db, 'deck_board_acl', Acl::class);
}
/**
* @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) {
$qb = $this->db->getQueryBuilder();
$qb->select('id', 'board_id', 'type', 'participant', 'permission_edit', 'permission_share', 'permission_manage')
->from('deck_board_acl')
->where($qb->expr()->eq('board_id', $qb->createNamedParameter($boardId, IQueryBuilder::PARAM_INT)))
->setMaxResults($limit)
->setFirstResult($offset);
return $this->findEntities($qb);
$sql = 'SELECT id, board_id, type, participant, permission_edit, permission_share, permission_manage FROM `*PREFIX*deck_board_acl` WHERE `board_id` = ? ';
return $this->findEntities($sql, [$boardId], $limit, $offset);
}
/**
* @param numeric $userId
* @param numeric $id
* @return bool
* @throws \OCP\DB\Exception
*/
public function isOwner($userId, $id): bool {
$aclId = $id;
$qb = $this->db->getQueryBuilder();
$qb->select('acl.id')
->from($this->getTableName(), 'acl')
->innerJoin('acl', 'deck_boards', 'b', 'acl.board_id = b.id')
->where($qb->expr()->eq('owner', $qb->createNamedParameter($userId, IQueryBuilder::PARAM_STR)))
->andWhere($qb->expr()->eq('acl.id', $qb->createNamedParameter($aclId, IQueryBuilder::PARAM_INT)));
return count($qb->executeQuery()->fetchAll()) > 0;
public function isOwner($userId, $aclId): bool {
$sql = 'SELECT owner FROM `*PREFIX*deck_boards` WHERE `id` IN (SELECT board_id FROM `*PREFIX*deck_board_acl` WHERE id = ?)';
$stmt = $this->execute($sql, [$aclId]);
$row = $stmt->fetch();
return ($row['owner'] === $userId);
}
/**
* @param numeric $id
* @return int|null
*/
public function findBoardId($id): ?int {
try {
$entity = $this->find($id);
@@ -82,21 +54,9 @@ class AclMapper extends DeckMapper implements IPermissionMapper {
return null;
}
/**
* @param int $type
* @param string $participant
* @return Acl[]
* @throws \OCP\DB\Exception
*/
public function findByParticipant($type, $participant): array {
$qb = $this->db->getQueryBuilder();
$qb->select('*')
->from($this->getTableName())
->where($qb->expr()->eq('type', $qb->createNamedParameter($type, IQueryBuilder::PARAM_INT)))
->andWhere($qb->expr()->eq('participant', $qb->createNamedParameter($participant, IQueryBuilder::PARAM_STR)));
return $this->findEntities($qb);
$sql = 'SELECT * from *PREFIX*deck_board_acl WHERE type = ? AND participant = ?';
return $this->findEntities($sql, [$type, $participant]);
}
/**
@@ -110,12 +70,4 @@ class AclMapper extends DeckMapper implements IPermissionMapper {
->andWhere($qb->expr()->eq('board_id', $qb->createNamedParameter($boardId, IQueryBuilder::PARAM_INT)));
$qb->executeStatement();
}
public function findByType(int $type): array {
$qb = $this->db->getQueryBuilder();
$qb->select('*')
->from('deck_board_acl')
->where($qb->expr()->eq('type', $qb->createNamedParameter($type, IQueryBuilder::PARAM_INT)));
return $this->findEntities($qb);
}
}

View File

@@ -55,6 +55,9 @@ class AssignmentMapper extends QBMapper implements IPermissionMapper {
$this->circleService = $circleService;
}
/**
* @return Assignment[]
*/
public function findAll(int $cardId): array {
$qb = $this->db->getQueryBuilder();
$qb->select('*')
@@ -77,8 +80,8 @@ class AssignmentMapper extends QBMapper implements IPermissionMapper {
}
public function isOwner($userId, $id): bool {
return $this->cardMapper->isOwner($userId, $id);
public function isOwner($userId, $cardId): bool {
return $this->cardMapper->isOwner($userId, $cardId);
}
public function findBoardId($id): ?int {

View File

@@ -30,6 +30,7 @@ use OCP\AppFramework\Db\MultipleObjectsReturnedException;
use OCP\DB\QueryBuilder\IQueryBuilder;
use OCP\IDBConnection;
use OCP\IUserManager;
use PDO;
class AttachmentMapper extends DeckMapper implements IPermissionMapper {
private $cardMapper;
@@ -51,53 +52,70 @@ class AttachmentMapper extends DeckMapper implements IPermissionMapper {
}
/**
* @param int $id
* @return Attachment
* @throws DoesNotExistException
* @throws MultipleObjectsReturnedException
* @throws \OCP\DB\Exception
* @param $id
* @return Entity|Attachment
* @throws \OCP\AppFramework\Db\DoesNotExistException
* @throws \OCP\AppFramework\Db\MultipleObjectsReturnedException
*/
public function find($id) {
$qb = $this->db->getQueryBuilder();
$qb->select('*')
->from($this->getTableName())
->from('deck_attachment')
->where($qb->expr()->eq('id', $qb->createNamedParameter($id, IQueryBuilder::PARAM_INT)));
return $this->findEntity($qb);
$cursor = $qb->execute();
$row = $cursor->fetch(PDO::FETCH_ASSOC);
if ($row === false) {
$cursor->closeCursor();
throw new DoesNotExistException('Did expect one result but found none when executing query: ' . $qb->getSQL());
}
$row2 = $cursor->fetch();
$cursor->closeCursor();
if ($row2 !== false) {
throw new MultipleObjectsReturnedException('Did not expect more than one result when executing query: ' . $qb->getSQL());
}
return $this->mapRowToEntity($row);
}
/**
* @param int $cardId
* @param string $data
* @return Attachment
* @throws DoesNotExistException
* @throws MultipleObjectsReturnedException
* @throws \OCP\DB\Exception
*/
public function findByData($cardId, $data) {
$qb = $this->db->getQueryBuilder();
$qb->select('*')
->from($this->getTableName())
->from('deck_attachment')
->where($qb->expr()->eq('card_id', $qb->createNamedParameter($cardId, IQueryBuilder::PARAM_INT)))
->andWhere($qb->expr()->eq('data', $qb->createNamedParameter($data, IQueryBuilder::PARAM_STR)));
return $this->findEntity($qb);
$cursor = $qb->execute();
$row = $cursor->fetch(PDO::FETCH_ASSOC);
if ($row === false) {
$cursor->closeCursor();
throw new DoesNotExistException('Did expect one result but found none when executing query: ' . $qb->getSQL());
}
$cursor->closeCursor();
return $this->mapRowToEntity($row);
}
/**
* Find all attachments for a card
*
* @param $cardId
* @return Entity[]
* @throws \OCP\DB\Exception
* @return array
*/
public function findAll($cardId) {
$qb = $this->db->getQueryBuilder();
$qb->select('*')
->from($this->getTableName())
->from('deck_attachment')
->where($qb->expr()->eq('card_id', $qb->createNamedParameter($cardId, IQueryBuilder::PARAM_INT)))
->andWhere($qb->expr()->eq('deleted_at', $qb->createNamedParameter(0, IQueryBuilder::PARAM_INT)));
return $this->findEntities($qb);
$entities = [];
$cursor = $qb->execute();
while ($row = $cursor->fetch()) {
$entities[] = $this->mapRowToEntity($row);
}
$cursor->closeCursor();
return $entities;
}
/**
@@ -110,7 +128,7 @@ class AttachmentMapper extends DeckMapper implements IPermissionMapper {
$timeLimit = time() - (60 * 5);
$qb = $this->db->getQueryBuilder();
$qb->select('*')
->from($this->getTableName())
->from('deck_attachment')
->where($qb->expr()->gt('deleted_at', $qb->createNamedParameter(0, IQueryBuilder::PARAM_INT)));
if ($withOffset) {
$qb
@@ -121,7 +139,13 @@ class AttachmentMapper extends DeckMapper implements IPermissionMapper {
->andWhere($qb->expr()->eq('card_id', $qb->createNamedParameter($cardId, IQueryBuilder::PARAM_INT)));
}
return $this->findEntities($qb);
$entities = [];
$cursor = $qb->execute();
while ($row = $cursor->fetch()) {
$entities[] = $this->mapRowToEntity($row);
}
$cursor->closeCursor();
return $entities;
}

View File

@@ -23,14 +23,6 @@
namespace OCA\Deck\Db;
/**
* @method int getId()
* @method string getTitle()
* @method int getShared()
* @method bool getArchived()
* @method int getDeletedAt()
* @method int getLastModified()
*/
class Board extends RelationalEntity {
protected $title;
protected $owner;
@@ -66,7 +58,7 @@ class Board extends RelationalEntity {
$this->shared = -1;
}
public function jsonSerialize(): array {
public function jsonSerialize() {
$json = parent::jsonSerialize();
if ($this->shared === -1) {
unset($json['shared']);

View File

@@ -42,9 +42,9 @@ class BoardMapper extends QBMapper implements IPermissionMapper {
private $circlesService;
private $logger;
/** @var CappedMemoryCache<Board[]> */
/** @var CappedMemoryCache */
private $userBoardCache;
/** @var CappedMemoryCache<Board> */
/** @var CappedMemoryCache */
private $boardCache;
public function __construct(
@@ -79,12 +79,14 @@ class BoardMapper extends QBMapper implements IPermissionMapper {
* @throws \OCP\AppFramework\Db\MultipleObjectsReturnedException
* @throws DoesNotExistException
*/
public function find($id, $withLabels = false, $withAcl = false): Board {
public function find(int $id, bool $withLabels = false, bool $withAcl = false, bool $allowDeleted = false): Board {
if (!isset($this->boardCache[$id])) {
$qb = $this->db->getQueryBuilder();
$deletedWhere = $allowDeleted ? $qb->expr()->gte('deleted_at', $qb->createNamedParameter(0, IQueryBuilder::PARAM_INT)) : $qb->expr()->eq('deleted_at', $qb->createNamedParameter(0, IQueryBuilder::PARAM_INT));
$qb->select('*')
->from('deck_boards')
->where($qb->expr()->eq('id', $qb->createNamedParameter($id, IQueryBuilder::PARAM_INT)))
->andWhere($deletedWhere)
->orderBy('id');
$this->boardCache[$id] = $this->findEntity($qb);
}
@@ -107,47 +109,6 @@ class BoardMapper extends QBMapper implements IPermissionMapper {
return $this->boardCache[$id];
}
public function findBoardIds(string $userId): array {
$qb = $this->db->getQueryBuilder();
$qb->selectDistinct('b.id')
->from($this->getTableName(), 'b')
->leftJoin('b', 'deck_board_acl', 'acl', $qb->expr()->eq('b.id', 'acl.board_id'));
// Owned by the user
$qb->where($qb->expr()->andX(
$qb->expr()->eq('owner', $qb->createNamedParameter($userId, IQueryBuilder::PARAM_STR)),
));
// Shared to the user
$qb->orWhere($qb->expr()->andX(
$qb->expr()->eq('acl.participant', $qb->createNamedParameter($userId, IQueryBuilder::PARAM_STR)),
$qb->expr()->eq('acl.type', $qb->createNamedParameter(Acl::PERMISSION_TYPE_USER, IQueryBuilder::PARAM_INT)),
));
// Shared to user groups of the user
$groupIds = $this->groupManager->getUserGroupIds($this->userManager->get($userId));
if (count($groupIds) !== 0) {
$qb->orWhere($qb->expr()->andX(
$qb->expr()->in('acl.participant', $qb->createNamedParameter($groupIds, IQueryBuilder::PARAM_STR_ARRAY)),
$qb->expr()->eq('acl.type', $qb->createNamedParameter(Acl::PERMISSION_TYPE_GROUP, IQueryBuilder::PARAM_INT)),
));
}
// Shared to circles of the user
$circles = $this->circlesService->getUserCircles($userId);
if (count($circles) !== 0) {
$qb->orWhere($qb->expr()->andX(
$qb->expr()->in('acl.participant', $qb->createNamedParameter($circles, IQueryBuilder::PARAM_STR_ARRAY)),
$qb->expr()->eq('acl.type', $qb->createNamedParameter(Acl::PERMISSION_TYPE_CIRCLE, IQueryBuilder::PARAM_INT)),
));
}
$result = $qb->executeQuery();
return array_map(function (string $id) {
return (int)$id;
}, $result->fetchAll(\PDO::FETCH_COLUMN));
}
public function findAllForUser(string $userId, ?int $since = null, bool $includeArchived = true, ?int $before = null,
?string $term = null): array {
$useCache = ($since === -1 && $includeArchived === true && $before === null && $term === null);
@@ -172,9 +133,14 @@ class BoardMapper extends QBMapper implements IPermissionMapper {
/**
* Find all boards for a given user
*
* @param $userId
* @param null $limit
* @param null $offset
* @return array
*/
public function findAllByUser(string $userId, ?int $limit = null, ?int $offset = null, ?int $since = null,
bool $includeArchived = true, ?int $before = null, ?string $term = null): array {
bool $includeArchived = true, ?int $before = null, ?string $term = null) {
// FIXME this used to be a UNION to get boards owned by $userId and the user shares in one single query
// Is it possible with the query builder?
$qb = $this->db->getQueryBuilder();
@@ -283,9 +249,15 @@ class BoardMapper extends QBMapper implements IPermissionMapper {
/**
* Find all boards for a given user
*
* @param $userId
* @param $groups
* @param null $limit
* @param null $offset
* @return array
*/
public function findAllByGroups(string $userId, array $groups, ?int $limit = null, ?int $offset = null, ?int $since = null,
bool $includeArchived = true, ?int $before = null, ?string $term = null): array {
bool $includeArchived = true, ?int $before = null, ?string $term = null) {
if (count($groups) <= 0) {
return [];
}
@@ -444,8 +416,8 @@ class BoardMapper extends QBMapper implements IPermissionMapper {
return parent::delete($entity);
}
public function isOwner($userId, $id): bool {
$board = $this->find($id);
public function isOwner($userId, $boardId): bool {
$board = $this->find($boardId);
return ($board->getOwner() === $userId);
}

View File

@@ -27,44 +27,6 @@ use DateTime;
use DateTimeZone;
use Sabre\VObject\Component\VCalendar;
/**
* @method string getTitle()
* @method string getDescription()
* @method string getDescriptionPrev()
* @method int getStackId()
* @method int getOrder()
* @method int getLastModified()
* @method int getCreatedAt()
* @method bool getArchived()
* @method bool getNotified()
*
* @method void setLabels(Label[] $labels)
* @method null|Label[] getLabels()
*
* @method void setAssignedUsers(Assignment[] $users)
* @method null|User[] getAssignedUsers()
*
* @method void setAttachments(Attachment[] $attachments)
* @method null|Attachment[] getAttachments()
*
* @method void setAttachmentCount(int $count)
* @method null|int getAttachmentCount()
*
* @method void setCommentsUnread(int $count)
* @method null|int getCommentsUnread()
*
* @method void setCommentsCount(int $count)
* @method null|int getCommentsCount()
*
* @method void setOwner(string $user)
* @method null|string getOwner()
*
* @method void setRelatedStack(Stack $stack)
* @method null|Stack getRelatedStack()
*
* @method void setRelatedBoard(Board $board)
* @method null|Board getRelatedBoard()
*/
class Card extends RelationalEntity {
public const TITLE_MAX_LENGTH = 255;
@@ -88,7 +50,7 @@ class Card extends RelationalEntity {
protected $deletedAt = 0;
protected $commentsUnread = 0;
protected $commentsCount = 0;
protected $relatedStack = null;
protected $relatedBoard = null;
@@ -108,7 +70,6 @@ class Card extends RelationalEntity {
$this->addType('archived', 'boolean');
$this->addType('notified', 'boolean');
$this->addType('deletedAt', 'integer');
$this->addType('duedate', 'datetime');
$this->addRelation('labels');
$this->addRelation('assignedUsers');
$this->addRelation('attachments');
@@ -117,7 +78,7 @@ class Card extends RelationalEntity {
$this->addRelation('commentsUnread');
$this->addRelation('commentsCount');
$this->addResolvable('owner');
$this->addRelation('relatedStack');
$this->addRelation('relatedBoard');
}
@@ -126,6 +87,51 @@ class Card extends RelationalEntity {
$this->databaseType = $type;
}
public function getDuedate($isoFormat = false) {
if ($this->duedate === null) {
return null;
}
$dt = new DateTime($this->duedate);
if (!$isoFormat && $this->databaseType === 'mysql') {
return $dt->format('Y-m-d H:i:s');
}
return $dt->format('c');
}
public function jsonSerialize() {
$json = parent::jsonSerialize();
$json['overdue'] = self::DUEDATE_FUTURE;
$due = strtotime($this->duedate);
$today = new DateTime();
$today->setTime(0, 0);
$match_date = new DateTime($this->duedate);
$match_date->setTime(0, 0);
$diff = $today->diff($match_date);
$diffDays = (integer) $diff->format('%R%a'); // Extract days count in interval
if ($due !== false) {
if ($diffDays === 1) {
$json['overdue'] = self::DUEDATE_NEXT;
}
if ($diffDays === 0) {
$json['overdue'] = self::DUEDATE_NOW;
}
if ($diffDays < 0) {
$json['overdue'] = self::DUEDATE_OVERDUE;
}
}
$json['duedate'] = $this->getDuedate(true);
unset($json['notified']);
unset($json['descriptionPrev']);
unset($json['relatedStack']);
unset($json['relatedBoard']);
return $json;
}
public function getCalendarObject(): VCalendar {
$calendar = new VCalendar();
$event = $calendar->createComponent('VTODO');
@@ -134,7 +140,7 @@ class Card extends RelationalEntity {
$creationDate = new DateTime();
$creationDate->setTimestamp($this->createdAt);
$event->DTSTAMP = $creationDate;
$event->DUE = new DateTime($this->getDuedate()->format('c'), new DateTimeZone('UTC'));
$event->DUE = new DateTime($this->getDuedate(true), new DateTimeZone('UTC'));
}
$event->add('RELATED-TO', 'deck-stack-' . $this->getStackId());

View File

@@ -177,17 +177,6 @@ class CardMapper extends QBMapper implements IPermissionMapper {
return $qb;
}
public function findToDelete($timeLimit, $limit = null) {
$qb = $this->db->getQueryBuilder();
$qb->select('id', 'title', 'owner', 'archived', 'deleted_at', 'last_modified')
->from('deck_cards')
->where($qb->expr()->gt('deleted_at', $qb->createNamedParameter(0, IQueryBuilder::PARAM_INT)))
->andWhere($qb->expr()->lt('deleted_at', $qb->createNamedParameter($timeLimit, IQueryBuilder::PARAM_INT)))
->orderBy('deleted_at')
->setMaxResults($limit);
return $this->findEntities($qb);
}
public function findDeleted($boardId, $limit = null, $offset = null) {
$qb = $this->queryCardsByBoard($boardId);
$qb->andWhere($qb->expr()->neq('c.deleted_at', $qb->createNamedParameter(0, IQueryBuilder::PARAM_INT)))
@@ -238,21 +227,6 @@ class CardMapper extends QBMapper implements IPermissionMapper {
return $this->findEntities($qb);
}
public function findAllByBoardId(int $boardId, ?int $limit = null, ?int $offset = null): array {
$qb = $this->db->getQueryBuilder();
$qb->select('c.*')
->from('deck_cards', 'c')
->innerJoin('c', 'deck_stacks', 's', 's.id = c.stack_id')
->innerJoin('s', 'deck_boards', 'b', 'b.id = s.board_id')
->where($qb->expr()->eq('board_id', $qb->createNamedParameter($boardId, IQueryBuilder::PARAM_INT)))
->andWhere($qb->expr()->eq('archived', $qb->createNamedParameter(false, IQueryBuilder::PARAM_BOOL)))
->setMaxResults($limit)
->setFirstResult($offset)
->orderBy('c.lastmodified')
->addOrderBy('c.id');
return $this->findEntities($qb);
}
public function findAllWithDue($boardId) {
$qb = $this->db->getQueryBuilder();
$qb->select('c.*')
@@ -292,7 +266,7 @@ class CardMapper extends QBMapper implements IPermissionMapper {
public function findOverdue() {
$qb = $this->db->getQueryBuilder();
$qb->select('id', 'title', 'duedate', 'notified')
$qb->select('id','title','duedate','notified')
->from('deck_cards')
->where($qb->expr()->lt('duedate', $qb->createFunction('NOW()')))
->andWhere($qb->expr()->eq('notified', $qb->createNamedParameter(false, IQueryBuilder::PARAM_BOOL)))
@@ -303,7 +277,7 @@ class CardMapper extends QBMapper implements IPermissionMapper {
public function findUnexposedDescriptionChances() {
$qb = $this->db->getQueryBuilder();
$qb->select('id', 'title', 'duedate', 'notified', 'description_prev', 'last_editor', 'description')
$qb->select('id','title','duedate','notified','description_prev','last_editor','description')
->from('deck_cards')
->where($qb->expr()->isNotNull('last_editor'))
->andWhere($qb->expr()->isNotNull('description_prev'));
@@ -575,10 +549,10 @@ class CardMapper extends QBMapper implements IPermissionMapper {
$qb->execute();
}
public function isOwner($userId, $id): bool {
public function isOwner($userId, $cardId): bool {
$sql = 'SELECT owner FROM `*PREFIX*deck_boards` WHERE `id` IN (SELECT board_id FROM `*PREFIX*deck_stacks` WHERE id IN (SELECT stack_id FROM `*PREFIX*deck_cards` WHERE id = ?))';
$stmt = $this->db->prepare($sql);
$stmt->bindParam(1, $id, \PDO::PARAM_INT, 0);
$stmt->bindParam(1, $cardId, \PDO::PARAM_INT);
$stmt->execute();
$row = $stmt->fetch();
return ($row['owner'] === $userId);

View File

@@ -24,7 +24,6 @@
namespace OCA\Deck\Db;
use OCP\ICacheFactory;
use OCP\ICache;
use OCP\IDBConnection;
use OCP\IRequest;
@@ -32,16 +31,13 @@ class ChangeHelper {
public const TYPE_BOARD = 'boardChanged';
public const TYPE_CARD = 'cardChanged';
private IDBConnection $db;
private ICache $cache;
private IRequest $request;
private ?string $userId;
private $db;
public function __construct(
IDBConnection $db,
ICacheFactory $cacheFactory,
IRequest $request,
?string $userId
$userId
) {
$this->db = $db;
$this->cache = $cacheFactory->createDistributed('deck_changes');

View File

@@ -23,15 +23,17 @@
namespace OCA\Deck\Db;
use OCP\AppFramework\Db\QBMapper;
use OCP\DB\QueryBuilder\IQueryBuilder;
use OCP\AppFramework\Db\Mapper;
/**
* Class DeckMapper
*
* @package OCA\Deck\Db
* @deprecated use QBMapper
*
* TODO: Move to QBMapper once Nextcloud 14 is a minimum requirement
*/
class DeckMapper extends QBMapper {
class DeckMapper extends Mapper {
/**
* @param $id
@@ -40,11 +42,11 @@ class DeckMapper extends QBMapper {
* @throws \OCP\AppFramework\Db\DoesNotExistException
*/
public function find($id) {
$qb = $this->db->getQueryBuilder();
$qb->select('*')
->from($this->getTableName())
->where($qb->expr()->eq('id', $qb->createNamedParameter($id, IQueryBuilder::PARAM_INT)));
$sql = 'SELECT * FROM `' . $this->tableName . '` ' . 'WHERE `id` = ?';
return $this->findEntity($sql, [$id]);
}
return $this->findEntity($qb);
protected function execute($sql, array $params = [], $limit = null, $offset = null) {
return parent::execute($sql, $params, $limit, $offset);
}
}

View File

@@ -26,7 +26,6 @@ namespace OCA\Deck\Db;
use OCP\AppFramework\Db\DoesNotExistException;
use OCP\AppFramework\Db\Entity;
use OCP\AppFramework\Db\MultipleObjectsReturnedException;
use OCP\DB\QueryBuilder\IQueryBuilder;
use OCP\IDBConnection;
class LabelMapper extends DeckMapper implements IPermissionMapper {
@@ -34,105 +33,41 @@ class LabelMapper extends DeckMapper implements IPermissionMapper {
parent::__construct($db, 'deck_labels', Label::class);
}
/**
* @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 {
$qb = $this->db->getQueryBuilder();
$qb->select('*')
->from($this->getTableName())
->where($qb->expr()->eq('board_id', $qb->createNamedParameter($boardId, IQueryBuilder::PARAM_INT)))
->setMaxResults($limit)
->setFirstResult($offset);
return $this->findEntities($qb);
public function findAll($boardId, $limit = null, $offset = null) {
$sql = 'SELECT * FROM `*PREFIX*deck_labels` WHERE `board_id` = ? ORDER BY `id`';
return $this->findEntities($sql, [$boardId], $limit, $offset);
}
/**
* @param Entity $entity
* @return Entity
* @throws \OCP\DB\Exception
*/
public function delete(Entity $entity): Entity {
public function delete(\OCP\AppFramework\Db\Entity $entity) {
// delete assigned labels
$this->deleteLabelAssignments($entity->getId());
// delete label
return parent::delete($entity);
}
/**
* @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 {
$qb = $this->db->getQueryBuilder();
$qb->select('l.*', 'card_id')
->from($this->getTableName(), 'l')
->innerJoin('l', 'deck_assigned_labels', 'al', 'l.id = al.label_id')
->where($qb->expr()->eq('card_id', $qb->createNamedParameter($cardId, IQueryBuilder::PARAM_INT)))
->orderBy('l.id')
->setMaxResults($limit)
->setFirstResult($offset);
return $this->findEntities($qb);
public function findAssignedLabelsForCard($cardId, $limit = null, $offset = null) {
$sql = 'SELECT l.*,card_id FROM `*PREFIX*deck_assigned_labels` as al INNER JOIN *PREFIX*deck_labels as l ON l.id = al.label_id WHERE `card_id` = ? ORDER BY l.id';
return $this->findEntities($sql, [$cardId], $limit, $offset);
}
public function findAssignedLabelsForBoard($boardId, $limit = null, $offset = null) {
$sql = 'SELECT c.id as card_id, l.id as id, l.title as title, l.color as color FROM `*PREFIX*deck_cards` as c ' .
' INNER JOIN `*PREFIX*deck_assigned_labels` as al ON al.card_id = c.id INNER JOIN `*PREFIX*deck_labels` as l ON al.label_id = l.id WHERE board_id=? ORDER BY l.id';
return $this->findEntities($sql, [$boardId], $limit, $offset);
}
/**
* @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 {
$qb = $this->db->getQueryBuilder();
$qb->select('l.id as id', 'l.title as title', 'l.color as color')
->selectAlias('c.id', 'card_id')
->from($this->getTableName(), 'l')
->innerJoin('l', 'deck_assigned_labels', 'al', 'al.label_id = l.id')
->innerJoin('l', 'deck_cards', 'c', 'al.card_id = c.id')
->where($qb->expr()->eq('board_id', $qb->createNamedParameter($boardId, IQueryBuilder::PARAM_INT)))
->orderBy('l.id')
->setMaxResults($limit)
->setFirstResult($offset);
return $this->findEntities($qb);
}
/**
* @param Entity $entity
* @return Entity
* @throws \OCP\DB\Exception
*/
public function insert(Entity $entity): Entity {
public function insert(Entity $entity) {
$entity->setLastModified(time());
return parent::insert($entity);
}
/**
* @param Entity $entity
* @param bool $updateModified
* @return Entity
* @throws \OCP\DB\Exception
*/
public function update(Entity $entity, $updateModified = true): Entity {
public function update(Entity $entity, $updateModified = true) {
if ($updateModified) {
$entity->setLastModified(time());
}
return parent::update($entity);
}
/**
* @param numeric $boardId
* @return array
* @throws \OCP\DB\Exception
*/
public function getAssignedLabelsForBoard($boardId) {
$labels = $this->findAssignedLabelsForBoard($boardId);
$result = [];
@@ -145,51 +80,27 @@ class LabelMapper extends DeckMapper implements IPermissionMapper {
return $result;
}
/**
* @param numeric $labelId
* @return void
* @throws \OCP\DB\Exception
*/
public function deleteLabelAssignments($labelId) {
$qb = $this->db->getQueryBuilder();
$qb->delete('deck_assigned_labels')
->where($qb->expr()->eq('label_id', $qb->createNamedParameter($labelId, IQueryBuilder::PARAM_INT)));
$qb->executeStatement();
$sql = 'DELETE FROM `*PREFIX*deck_assigned_labels` WHERE label_id = ?';
$stmt = $this->db->prepare($sql);
$stmt->bindParam(1, $labelId, \PDO::PARAM_INT);
$stmt->execute();
}
/**
* @param numeric $cardId
* @return void
* @throws \OCP\DB\Exception
*/
public function deleteLabelAssignmentsForCard($cardId) {
$qb = $this->db->getQueryBuilder();
$qb->delete('deck_assigned_labels')
->where($qb->expr()->eq('card_id', $qb->createNamedParameter($cardId, IQueryBuilder::PARAM_INT)));
$qb->executeStatement();
$sql = 'DELETE FROM `*PREFIX*deck_assigned_labels` WHERE card_id = ?';
$stmt = $this->db->prepare($sql);
$stmt->bindParam(1, $cardId, \PDO::PARAM_INT);
$stmt->execute();
}
/**
* @param string $userId
* @param numeric $labelId
* @return bool
* @throws \OCP\DB\Exception
*/
public function isOwner($userId, $labelId): 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)))
->andWhere($qb->expr()->eq('b.owner', $qb->createNamedParameter($userId, IQueryBuilder::PARAM_STR)));
return count($qb->executeQuery()->fetchAll()) > 0;
$sql = 'SELECT owner FROM `*PREFIX*deck_boards` WHERE `id` IN (SELECT board_id FROM `*PREFIX*deck_labels` WHERE id = ?)';
$stmt = $this->execute($sql, [$labelId]);
$row = $stmt->fetch();
return ($row['owner'] === $userId);
}
/**
* @param numeric $id
* @return int|null
*/
public function findBoardId($id): ?int {
try {
$entity = $this->find($id);

View File

@@ -63,7 +63,7 @@ class RelationalEntity extends Entity implements \JsonSerializable {
* @return array serialized data
* @throws \ReflectionException
*/
public function jsonSerialize(): array {
public function jsonSerialize() {
$properties = get_object_vars($this);
$reflection = new \ReflectionClass($this);
$json = [];
@@ -72,9 +72,6 @@ class RelationalEntity extends Entity implements \JsonSerializable {
$propertyReflection = $reflection->getProperty($property);
if (!$propertyReflection->isPrivate() && !in_array($property, $this->_resolvedProperties, true)) {
$json[$property] = $this->getter($property);
if ($json[$property] instanceof \DateTimeInterface) {
$json[$property] = $json[$property]->format('c');
}
}
}
}

View File

@@ -23,9 +23,7 @@
namespace OCA\Deck\Db;
use JsonSerializable;
class RelationalObject implements JsonSerializable {
class RelationalObject implements \JsonSerializable {
protected $primaryKey;
protected $object;
@@ -40,7 +38,7 @@ class RelationalObject implements JsonSerializable {
$this->object = $object;
}
public function jsonSerialize(): array {
public function jsonSerialize() {
return array_merge(
['primaryKey' => $this->primaryKey],
$this->getObjectSerialization()
@@ -53,8 +51,8 @@ class RelationalObject implements JsonSerializable {
* @throws \Exception
*/
public function getObjectSerialization() {
if ($this->object instanceof JsonSerializable) {
return $this->object->jsonSerialize();
if ($this->object instanceof \JsonSerializable) {
$this->object->jsonSerialize();
} else {
throw new \Exception('jsonSerialize is not implemented on ' . get_class($this));
}

View File

@@ -25,13 +25,6 @@ namespace OCA\Deck\Db;
use Sabre\VObject\Component\VCalendar;
/**
* @method int getId()
* @method int getBoardId()
* @method int getDeletedAt()
* @method int getLastModified()
* @method int getOrder()
*/
class Stack extends RelationalEntity {
protected $title;
protected $boardId;
@@ -52,7 +45,7 @@ class Stack extends RelationalEntity {
$this->cards = $cards;
}
public function jsonSerialize(): array {
public function jsonSerialize() {
$json = parent::jsonSerialize();
if (empty($this->cards)) {
unset($json['cards']);

View File

@@ -26,7 +26,6 @@ namespace OCA\Deck\Db;
use OCP\AppFramework\Db\DoesNotExistException;
use OCP\AppFramework\Db\Entity;
use OCP\AppFramework\Db\MultipleObjectsReturnedException;
use OCP\DB\QueryBuilder\IQueryBuilder;
use OCP\IDBConnection;
class StackMapper extends DeckMapper implements IPermissionMapper {
@@ -39,112 +38,43 @@ class StackMapper extends DeckMapper implements IPermissionMapper {
/**
* @param numeric $id
* @return Stack
* @throws DoesNotExistException
* @param $id
* @throws MultipleObjectsReturnedException
* @throws \OCP\DB\Exception
* @throws DoesNotExistException
*/
public function find($id): Stack {
$qb = $this->db->getQueryBuilder();
$qb->select('*')
->from($this->getTableName())
->where($qb->expr()->eq('id', $qb->createNamedParameter($id, IQueryBuilder::PARAM_INT)));
return $this->findEntity($qb);
$sql = 'SELECT * FROM `*PREFIX*deck_stacks` ' .
'WHERE `id` = ?';
return $this->findEntity($sql, [$id]);
}
/**
* @param $cardId
* @return Stack|null
* @throws \OCP\DB\Exception
*/
public function findStackFromCardId($cardId): ?Stack {
$qb = $this->db->getQueryBuilder();
$qb->select('s.*')
->from($this->getTableName(), 's')
->innerJoin('s', 'deck_cards', 'c', 's.id = c.stack_id')
->where($qb->expr()->eq('c.id', $qb->createNamedParameter($cardId, IQueryBuilder::PARAM_INT)));
try {
return $this->findEntity($qb);
} catch (MultipleObjectsReturnedException|DoesNotExistException $e) {
}
return null;
public function findAll($boardId, $limit = null, $offset = null) {
$sql = 'SELECT * FROM `*PREFIX*deck_stacks` WHERE `board_id` = ? AND deleted_at = 0 ORDER BY `order`, `id`';
return $this->findEntities($sql, [$boardId], $limit, $offset);
}
/**
* @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 {
$qb = $this->db->getQueryBuilder();
$qb->select('*')
->from($this->getTableName())
->where($qb->expr()->eq('board_id', $qb->createNamedParameter($boardId, IQueryBuilder::PARAM_INT)))
->andWhere($qb->expr()->eq('deleted_at', $qb->createNamedParameter(0, IQueryBuilder::PARAM_INT)))
->setFirstResult($offset)
->setMaxResults($limit);
return $this->findEntities($qb);
}
/**
* @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) {
$qb = $this->db->getQueryBuilder();
$qb->select('*')
->from($this->getTableName())
->where($qb->expr()->eq('board_id', $qb->createNamedParameter($boardId, IQueryBuilder::PARAM_INT)))
->andWhere($qb->expr()->neq('deleted_at', $qb->createNamedParameter(0, IQueryBuilder::PARAM_INT)))
->setFirstResult($offset)
->setMaxResults($limit);
return $this->findEntities($qb);
$sql = 'SELECT * FROM `*PREFIX*deck_stacks` s
WHERE `s`.`board_id` = ? AND NOT s.deleted_at = 0';
return $this->findEntities($sql, [$boardId], $limit, $offset);
}
/**
* @param Entity $entity
* @return Entity
* @throws \OCP\DB\Exception
*/
public function delete(Entity $entity): Entity {
public function delete(Entity $entity) {
// delete cards on stack
$this->cardMapper->deleteByStack($entity->getId());
return parent::delete($entity);
}
/**
* @param numeric $userId
* @param numeric $stackId
* @return bool
* @throws \OCP\DB\Exception
*/
public function isOwner($userId, $id): bool {
$qb = $this->db->getQueryBuilder();
$qb->select('s.id')
->from($this->getTableName(), 's')
->innerJoin('s', 'deck_boards', 'b', 'b.id = s.board_id')
->where($qb->expr()->eq('s.id', $qb->createNamedParameter($id, IQueryBuilder::PARAM_INT)))
->andWhere($qb->expr()->eq('owner', $qb->createNamedParameter($userId, IQueryBuilder::PARAM_STR)));
return count($qb->executeQuery()->fetchAll()) > 0;
public function isOwner($userId, $stackId): bool {
$sql = 'SELECT owner FROM `*PREFIX*deck_boards` WHERE `id` IN (SELECT board_id FROM `*PREFIX*deck_stacks` WHERE id = ?)';
$stmt = $this->execute($sql, [$stackId]);
$row = $stmt->fetch();
return ($row['owner'] === $userId);
}
/**
* @param numeric $id
* @return int|null
* @throws \OCP\DB\Exception
*/
public function findBoardId($id): ?int {
try {
$entity = $this->find($id);

View File

@@ -1,44 +0,0 @@
<?php
/*
* @copyright Copyright (c) 2021 Vitor Mattos <vitor@php.rio>
*
* @author Vitor Mattos <vitor@php.rio>
*
* @license GNU AGPL version 3 or any later version
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
*/
declare(strict_types=1);
namespace OCA\Deck\Event;
use OCA\Deck\Service\Importer\BoardImportService;
use OCP\EventDispatcher\Event;
abstract class ABoardImportGetAllowedEvent extends Event {
private $service;
public function __construct(BoardImportService $service) {
parent::__construct();
$this->service = $service;
}
public function getService(): BoardImportService {
return $this->service;
}
}

View File

@@ -1,29 +0,0 @@
<?php
/*
* @copyright Copyright (c) 2021 Vitor Mattos <vitor@php.rio>
*
* @author Vitor Mattos <vitor@php.rio>
*
* @license GNU AGPL version 3 or any later version
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
*/
declare(strict_types=1);
namespace OCA\Deck\Event;
class BoardImportGetAllowedEvent extends ABoardImportGetAllowedEvent {
}

View File

@@ -1,57 +0,0 @@
<?php
namespace OCA\Deck\Listeners;
use OCA\Circles\Events\CircleDestroyedEvent;
use OCA\Deck\Db\Acl;
use OCA\Deck\Db\AclMapper;
use OCA\Deck\Db\AssignmentMapper;
use OCA\Deck\Db\BoardMapper;
use OCP\EventDispatcher\Event;
use OCP\EventDispatcher\IEventListener;
use OCP\Group\Events\GroupDeletedEvent;
use OCP\User\Events\UserDeletedEvent;
class ParticipantCleanupListener implements IEventListener {
private AclMapper $aclMapper;
private AssignmentMapper $assignmentMapper;
private BoardMapper $boardMapper;
public function __construct(AclMapper $aclMapper, AssignmentMapper $assignmentMapper, BoardMapper $boardMapper) {
$this->aclMapper = $aclMapper;
$this->assignmentMapper = $assignmentMapper;
$this->boardMapper = $boardMapper;
}
public function handle(Event $event): void {
if ($event instanceof UserDeletedEvent) {
$boards = $this->boardMapper->findAllByOwner($event->getUser()->getUID());
foreach ($boards as $board) {
$this->boardMapper->delete($board);
}
$this->cleanupByParticipant(Acl::PERMISSION_TYPE_USER, $event->getUser()->getUID());
}
if ($event instanceof GroupDeletedEvent) {
$this->cleanupByParticipant(Acl::PERMISSION_TYPE_GROUP, $event->getGroup()->getGID());
}
if ($event instanceof CircleDestroyedEvent) {
$circleId = $event->getCircle()->getSingleId();
$this->cleanupByParticipant(Acl::PERMISSION_TYPE_CIRCLE, $circleId);
}
}
private function cleanupByParticipant(int $type, string $participant): void {
$acls = $this->aclMapper->findByParticipant($type, $participant);
foreach ($acls as $acl) {
$this->aclMapper->delete($acl);
}
$assignments = $this->assignmentMapper->findByParticipant($participant, $type);
foreach ($assignments as $assignment) {
$this->assignmentMapper->delete($assignment);
}
}
}

View File

@@ -1,42 +0,0 @@
<?php
namespace OCA\Deck\Listeners;
use OCA\Deck\Collaboration\Resources\ResourceProvider;
use OCA\Deck\Collaboration\Resources\ResourceProviderCard;
use OCA\Deck\Event\AclCreatedEvent;
use OCA\Deck\Event\AclDeletedEvent;
use OCP\Collaboration\Resources\IManager;
use OCP\Collaboration\Resources\ResourceException;
use OCP\EventDispatcher\Event;
use OCP\EventDispatcher\IEventListener;
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 handle(Event $event): void {
if (!$event instanceof AclDeletedEvent && !$event instanceof AclCreatedEvent) {
return;
}
$boardId = $event->getAcl()->getBoardId();
$this->resourceManager->invalidateAccessCacheForProvider($this->resourceProviderCard);
try {
$resource = $this->resourceManager->getResourceForUser(ResourceProvider::RESOURCE_TYPE, $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
}
}
}

View File

@@ -33,6 +33,7 @@ use OCP\AppFramework\OCS\OCSException;
use OCP\AppFramework\OCSController;
use OCP\ILogger;
use OCP\IRequest;
use OCP\Util;
use OCP\IConfig;
class ExceptionMiddleware extends Middleware {
@@ -84,9 +85,9 @@ class ExceptionMiddleware extends Middleware {
'message' => 'Permission denied'
], 403);
}
if ($exception instanceof StatusException) {
if ($this->config->getSystemValue('loglevel', ILogger::WARN) === ILogger::DEBUG) {
if ($this->config->getSystemValue('loglevel', Util::WARN) === Util::DEBUG) {
$this->logger->logException($exception);
}

View File

@@ -1,36 +0,0 @@
<?php
namespace OCA\Deck\Migration;
use OCA\Deck\Db\Acl;
use OCA\Deck\Db\AclMapper;
use OCA\Deck\Service\CirclesService;
use OCP\Migration\IOutput;
use OCP\Migration\IRepairStep;
class DeletedCircleCleanup implements IRepairStep {
private AclMapper $aclMapper;
private CirclesService $circleService;
public function __construct(AclMapper $aclMapper, CirclesService $circlesService) {
$this->aclMapper = $aclMapper;
$this->circleService = $circlesService;
}
public function getName() {
return 'Cleanup Deck ACL entries for circles which have been already deleted';
}
public function run(IOutput $output) {
if (!$this->circleService->isCirclesEnabled()) {
return;
}
foreach ($this->aclMapper->findByType(Acl::PERMISSION_TYPE_CIRCLE) as $acl) {
if ($this->circleService->getCircle($acl->getParticipant()) === null) {
$this->aclMapper->delete($acl);
$output->info('Removed circle with id ' . $acl->getParticipant());
}
}
}
}

View File

@@ -1,92 +0,0 @@
<?php
/**
* @copyright Copyright (c) 2022 Raul Ferreira Fuentes <raul@nextcloud.com>
*
* @author Raul Ferreira Fuentes <raul@nextcloud.com>
*
* @license GNU AGPL version 3 or any later version
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
*/
namespace OCA\Deck\Model;
use DateTime;
use OCA\Deck\Db\Board;
use OCA\Deck\Db\Card;
class CardDetails extends Card {
private Card $card;
private ?Board $board;
public function __construct(Card $card, ?Board $board = null) {
parent::__construct();
$this->card = $card;
$this->board = $board;
}
public function setBoard(?Board $board): void {
$this->board = $board;
}
public function jsonSerialize(array $extras = []): array {
$array = $this->card->jsonSerialize();
unset($array['notified'], $array['descriptionPrev'], $array['relatedStack'], $array['relatedBoard']);
$array['overdue'] = $this->getDueStatus();
$this->appendBoardDetails($array);
return $array;
}
private function getDueStatus(): int {
$today = new DateTime();
$today->setTime(0, 0);
$match_date = $this->card->getDuedate();
if (!$match_date) {
return Card::DUEDATE_FUTURE;
}
$match_date->setTime(0, 0);
$diff = $today->diff($match_date);
$diffDays = (int) $diff->format('%R%a'); // Extract days count in interval
if ($diffDays === 1) {
return Card::DUEDATE_NEXT;
}
if ($diffDays === 0) {
return Card::DUEDATE_NOW;
}
if ($diffDays < 0) {
return Card::DUEDATE_OVERDUE;
}
return Card::DUEDATE_FUTURE;
}
private function appendBoardDetails(&$array): void {
if (!$this->board) {
return;
}
$array['boardId'] = $this->board->id;
$array['board'] = (new BoardSummary($this->board))->jsonSerialize();
}
public function __call($name, $arguments) {
return $this->card->__call($name, $arguments);
}
}

View File

@@ -129,7 +129,7 @@ class NotificationHelper {
->setSubject('card-overdue', [
$card->getTitle(), $board->getTitle()
])
->setDateTime($card->getDuedate());
->setDateTime(new DateTime($card->getDuedate()));
$this->notificationManager->notify($notification);
}
}
@@ -242,7 +242,7 @@ class NotificationHelper {
}
return $this->boards[$boardId];
}
private function generateBoardShared(Board $board, string $userId): INotification {
$notification = $this->notificationManager->createNotification();
$notification

View File

@@ -101,12 +101,15 @@ class Notifier implements INotifier {
switch ($notification->getSubject()) {
case 'card-assigned':
$cardId = $notification->getObjectId();
$stack = $this->stackMapper->findStackFromCardId($cardId);
$boardId = $stack ? $stack->getBoardId() : null;
$boardId = $this->cardMapper->findBoardId($cardId);
if (!$boardId) {
throw new AlreadyProcessedException();
}
$card = $this->cardMapper->find($cardId);
$stackId = $card->getStackId();
$stack = $this->stackMapper->find($stackId);
$initiator = $this->userManager->get($params[2]);
if ($initiator !== null) {
$dn = $initiator->getDisplayName();
@@ -114,7 +117,7 @@ class Notifier implements INotifier {
$dn = $params[2];
}
$notification->setParsedSubject(
$l->t('The card "%s" on "%s" has been assigned to you by %s.', [$params[0], $params[1], $dn])
(string) $l->t('The card "%s" on "%s" has been assigned to you by %s.', [$params[0], $params[1], $dn])
);
$notification->setRichSubject(
$l->t('{user} has assigned the card {deck-card} on {deck-board} to you.'),
@@ -144,14 +147,17 @@ class Notifier implements INotifier {
break;
case 'card-overdue':
$cardId = $notification->getObjectId();
$stack = $this->stackMapper->findStackFromCardId($cardId);
$boardId = $stack ? $stack->getBoardId() : null;
$boardId = $this->cardMapper->findBoardId($cardId);
if (!$boardId) {
throw new AlreadyProcessedException();
}
$card = $this->cardMapper->find($cardId);
$stackId = $card->getStackId();
$stack = $this->stackMapper->find($stackId);
$notification->setParsedSubject(
$l->t('The card "%s" on "%s" has reached its due date.', $params)
(string) $l->t('The card "%s" on "%s" has reached its due date.', $params)
);
$notification->setRichSubject(
$l->t('The card {deck-card} on {deck-board} has reached its due date.'),
@@ -176,12 +182,15 @@ class Notifier implements INotifier {
break;
case 'card-comment-mentioned':
$cardId = $notification->getObjectId();
$stack = $this->stackMapper->findStackFromCardId($cardId);
$boardId = $stack ? $stack->getBoardId() : null;
$boardId = $this->cardMapper->findBoardId($cardId);
if (!$boardId) {
throw new AlreadyProcessedException();
}
$card = $this->cardMapper->find($cardId);
$stackId = $card->getStackId();
$stack = $this->stackMapper->find($stackId);
$initiator = $this->userManager->get($params[2]);
if ($initiator !== null) {
$dn = $initiator->getDisplayName();
@@ -189,7 +198,7 @@ class Notifier implements INotifier {
$dn = $params[2];
}
$notification->setParsedSubject(
$l->t('%s has mentioned you in a comment on "%s".', [$dn, $params[0]])
(string) $l->t('%s has mentioned you in a comment on "%s".', [$dn, $params[0]])
);
$notification->setRichSubject(
$l->t('{user} has mentioned you in a comment on {deck-card}.'),
@@ -226,7 +235,7 @@ class Notifier implements INotifier {
$dn = $params[1];
}
$notification->setParsedSubject(
$l->t('The board "%s" has been shared with you by %s.', [$params[0], $dn])
(string) $l->t('The board "%s" has been shared with you by %s.', [$params[0], $dn])
);
$notification->setRichSubject(
$l->t('{user} has shared {deck-board} with you.'),

View File

@@ -54,11 +54,22 @@ use OCP\IURLGenerator;
class DeckProvider implements IFullTextSearchProvider {
public const DECK_PROVIDER_ID = 'deck';
private IL10N $l10n;
private IUrlGenerator $urlGenerator;
private FullTextSearchService $fullTextSearchService;
private ?IRunner $runner = null;
private ?IIndexOptions $indexOptions = null;
/** @var IL10N */
private $l10n;
/** @var IUrlGenerator */
private $urlGenerator;
/** @var FullTextSearchService */
private $fullTextSearchService;
/** @var IRunner */
private $runner;
/** @var IIndexOptions */
private $indexOptions = [];
/**

View File

@@ -1,169 +0,0 @@
<?php
/**
* @copyright Copyright (c) 2022 Julien Veyssier <eneiluj@posteo.net>
*
* @author Julien Veyssier <eneiluj@posteo.net>
*
* @license GNU AGPL version 3 or any later version
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
namespace OCA\Deck\Reference;
use OCA\Deck\AppInfo\Application;
use OCA\Deck\Db\Assignment;
use OCA\Deck\Db\Attachment;
use OCA\Deck\Db\Label;
use OCA\Deck\Model\CardDetails;
use OCA\Deck\Service\BoardService;
use OCA\Deck\Service\CardService;
use OCA\Deck\Service\StackService;
use OCP\Collaboration\Reference\IReference;
use OCP\Collaboration\Reference\IReferenceProvider;
use OCP\Collaboration\Reference\Reference;
use OCP\IURLGenerator;
class CardReferenceProvider implements IReferenceProvider {
private CardService $cardService;
private IURLGenerator $urlGenerator;
private BoardService $boardService;
private StackService $stackService;
private ?string $userId;
public function __construct(CardService $cardService,
BoardService $boardService,
StackService $stackService,
IURLGenerator $urlGenerator,
?string $userId) {
$this->cardService = $cardService;
$this->urlGenerator = $urlGenerator;
$this->boardService = $boardService;
$this->stackService = $stackService;
$this->userId = $userId;
}
/**
* @inheritDoc
*/
public function matchReference(string $referenceText): bool {
$start = $this->urlGenerator->getAbsoluteURL('/apps/' . Application::APP_ID);
$startIndex = $this->urlGenerator->getAbsoluteURL('/index.php/apps/' . Application::APP_ID);
// link example: https://nextcloud.local/index.php/apps/deck/#/board/2/card/11
$noIndexMatch = preg_match('/^' . preg_quote($start, '/') . '\/#\/board\/[0-9]+\/card\/[0-9]+$/', $referenceText) === 1;
$indexMatch = preg_match('/^' . preg_quote($startIndex, '/') . '\/#\/board\/[0-9]+\/card\/[0-9]+$/', $referenceText) === 1;
return $noIndexMatch || $indexMatch;
}
/**
* @inheritDoc
*/
public function resolveReference(string $referenceText): ?IReference {
if ($this->matchReference($referenceText)) {
$ids = $this->getBoardCardId($referenceText);
if ($ids !== null) {
[$boardId, $cardId] = $ids;
$card = $this->cardService->find((int) $cardId)->jsonSerialize();
$board = $this->boardService->find((int) $boardId)->jsonSerialize();
$stack = $this->stackService->find((int) $card['stackId'])->jsonSerialize();
$card = $this->sanitizeSerializedCard($card);
$board = $this->sanitizeSerializedBoard($board);
$stack = $this->sanitizeSerializedStack($stack);
/** @var IReference $reference */
$reference = new Reference($referenceText);
$reference->setRichObject(Application::APP_ID . '-card', [
'id' => $boardId . '/' . $cardId,
'card' => $card,
'board' => $board,
'stack' => $stack,
]);
return $reference;
}
}
return null;
}
private function sanitizeSerializedStack(array $stack): array {
$stack['cards'] = array_map(function (CardDetails $cardDetails) {
$result = $cardDetails->jsonSerialize();
unset($result['assignedUsers']);
return $result;
}, $stack['cards']);
return $stack;
}
private function sanitizeSerializedBoard(array $board): array {
unset($board['labels']);
$board['owner'] = $board['owner']->jsonSerialize();
unset($board['acl']);
unset($board['users']);
return $board;
}
private function sanitizeSerializedCard(array $card): array {
$card['labels'] = array_map(function (Label $label) {
return $label->jsonSerialize();
}, $card['labels']);
$card['assignedUsers'] = array_map(function (Assignment $assignment) {
$result = $assignment->jsonSerialize();
$result['participant'] = $result['participant']->jsonSerialize();
return $result;
}, $card['assignedUsers']);
$card['owner'] = $card['owner']->jsonSerialize();
unset($card['relatedStack']);
unset($card['relatedBoard']);
$card['attachments'] = array_map(function (Attachment $attachment) {
return $attachment->jsonSerialize();
}, $card['attachments']);
return $card;
}
private function getBoardCardId(string $url): ?array {
$start = $this->urlGenerator->getAbsoluteURL('/apps/' . Application::APP_ID);
$startIndex = $this->urlGenerator->getAbsoluteURL('/index.php/apps/' . Application::APP_ID);
preg_match('/^' . preg_quote($start, '/') . '\/#\/board\/([0-9]+)\/card\/([0-9]+)$/', $url, $matches);
if ($matches && count($matches) > 2) {
return [$matches[1], $matches[2]];
}
preg_match('/^' . preg_quote($startIndex, '/') . '\/#\/board\/([0-9]+)\/card\/([0-9]+)$/', $url, $matches2);
if ($matches2 && count($matches2) > 2) {
return [$matches2[1], $matches2[2]];
}
return null;
}
public function getCachePrefix(string $referenceId): string {
$ids = $this->getBoardCardId($referenceId);
if ($ids !== null) {
[$boardId, $cardId] = $ids;
return $boardId . '/' . $cardId;
}
return $referenceId;
}
public function getCacheKey(string $referenceId): ?string {
return $this->userId ?? '';
}
}

View File

@@ -33,6 +33,6 @@ use OCP\Search\SearchResultEntry;
class CardSearchResultEntry extends SearchResultEntry {
public function __construct(Board $board, Stack $stack, Card $card, $urlGenerator) {
parent::__construct('', $card->getTitle(), $board->getTitle() . ' » ' . $stack->getTitle(), $urlGenerator->linkToRouteAbsolute('deck.page.index') . '#/board/' . $board->getId() . '/card/' . $card->getId(), 'icon-deck');
parent::__construct('', $card->getTitle(), $board->getTitle() . ' » ' . $stack->getTitle() , $urlGenerator->linkToRouteAbsolute('deck.page.index') . '#/board/' . $board->getId() . '/card/' . $card->getId(), 'icon-deck');
}
}

View File

@@ -40,7 +40,6 @@ use OCA\Deck\Validators\AttachmentServiceValidator;
use OCP\AppFramework\Db\IMapperException;
use OCP\AppFramework\Http\Response;
use OCP\IL10N;
use OCP\IUserManager;
class AttachmentService {
private $attachmentMapper;
@@ -60,25 +59,10 @@ class AttachmentService {
private $activityManager;
/** @var ChangeHelper */
private $changeHelper;
/** @var IUserManager */
private IUserManager $userManager;
/** @var AttachmentServiceValidator */
private AttachmentServiceValidator $attachmentServiceValidator;
private $attachmentServiceValidator;
public function __construct(
AttachmentMapper $attachmentMapper,
CardMapper $cardMapper,
IUserManager $userManager,
ChangeHelper $changeHelper,
PermissionService $permissionService,
Application $application,
ICacheFactory $cacheFactory,
AttachmentCacheHelper $attachmentCacheHelper,
$userId,
IL10N $l10n,
ActivityManager $activityManager,
AttachmentServiceValidator $attachmentServiceValidator
) {
public function __construct(AttachmentMapper $attachmentMapper, CardMapper $cardMapper, ChangeHelper $changeHelper, PermissionService $permissionService, Application $application, AttachmentCacheHelper $attachmentCacheHelper, $userId, IL10N $l10n, ActivityManager $activityManager, AttachmentServiceValidator $attachmentServiceValidator) {
$this->attachmentMapper = $attachmentMapper;
$this->cardMapper = $cardMapper;
$this->permissionService = $permissionService;
@@ -88,7 +72,6 @@ class AttachmentService {
$this->l10n = $l10n;
$this->activityManager = $activityManager;
$this->changeHelper = $changeHelper;
$this->userManager = $userManager;
$this->attachmentServiceValidator = $attachmentServiceValidator;
// Register shipped attachment services
@@ -148,7 +131,6 @@ class AttachmentService {
try {
$service = $this->getService($attachment->getType());
$service->extendData($attachment);
$this->addCreator($attachment);
} catch (InvalidAttachmentType $e) {
// Ingore invalid attachment types when extending the data
}
@@ -222,7 +204,6 @@ class AttachmentService {
}
$service->extendData($attachment);
$this->addCreator($attachment);
} catch (InvalidAttachmentType $e) {
// just store the data
}
@@ -325,7 +306,6 @@ class AttachmentService {
$this->attachmentMapper->update($attachment);
// extend data so the frontend can use it properly after creating
$service->extendData($attachment);
$this->addCreator($attachment);
$this->changeHelper->cardChanged($attachment->getCardId());
$this->activityManager->triggerEvent(ActivityManager::DECK_OBJECT_CARD, $attachment, ActivityManager::SUBJECT_ATTACHMENT_UPDATE);
@@ -400,28 +380,4 @@ class AttachmentService {
}
throw new NoPermissionException('Restore is not allowed.');
}
/**
* @param Attachment $attachment
* @return Attachment
* @throws \ReflectionException
*/
private function addCreator(Attachment $attachment): Attachment {
$createdBy = $attachment->jsonSerialize()['createdBy'] ?? '';
$creator = [
'displayName' => $createdBy,
'id' => $createdBy,
'email' => null,
];
if ($this->userManager->userExists($createdBy)) {
$user = $this->userManager->get($createdBy);
$creator['displayName'] = $user->getDisplayName();
$creator['email'] = $user->getEMailAddress();
}
$extendedData = $attachment->jsonSerialize()['extendedData'] ?? [];
$extendedData['attachmentCreator'] = $creator;
$attachment->setExtendedData($extendedData);
return $attachment;
}
}

View File

@@ -24,6 +24,7 @@
namespace OCA\Deck\Service;
use Doctrine\DBAL\Exception\UniqueConstraintViolationException;
use OCA\Deck\Activity\ActivityManager;
use OCA\Deck\Activity\ChangeSet;
use OCA\Deck\AppInfo\Application;
@@ -42,13 +43,10 @@ use OCA\Deck\Event\AclUpdatedEvent;
use OCA\Deck\NoPermissionException;
use OCA\Deck\Notification\NotificationHelper;
use OCP\AppFramework\Db\DoesNotExistException;
use OCP\AppFramework\Db\MultipleObjectsReturnedException;
use OCP\EventDispatcher\IEventDispatcher;
use OCP\IConfig;
use OCP\IDBConnection;
use OCP\IGroupManager;
use OCP\IL10N;
use OCP\DB\Exception as DbException;
use OCA\Deck\Db\Board;
use OCA\Deck\Db\BoardMapper;
use OCA\Deck\Db\LabelMapper;
@@ -56,31 +54,30 @@ use OCP\IUserManager;
use OCA\Deck\BadRequestException;
use OCA\Deck\Validators\BoardServiceValidator;
use OCP\IURLGenerator;
use OCP\Server;
use Psr\Container\ContainerExceptionInterface;
use Psr\Container\NotFoundExceptionInterface;
class BoardService {
private BoardMapper $boardMapper;
private StackMapper $stackMapper;
private LabelMapper $labelMapper;
private AclMapper $aclMapper;
private IConfig $config;
private IL10N $l10n;
private PermissionService $permissionService;
private NotificationHelper $notificationHelper;
private AssignmentMapper $assignedUsersMapper;
private IUserManager $userManager;
private IGroupManager $groupManager;
private ?string $userId;
private ActivityManager $activityManager;
private IEventDispatcher $eventDispatcher;
private ChangeHelper $changeHelper;
private CardMapper $cardMapper;
private ?array $boardsCache = null;
private IURLGenerator $urlGenerator;
private IDBConnection $connection;
private BoardServiceValidator $boardServiceValidator;
private $boardMapper;
private $stackMapper;
private $labelMapper;
private $aclMapper;
/** @var IConfig */
private $config;
private $l10n;
private $permissionService;
private $notificationHelper;
private $assignedUsersMapper;
private $userManager;
private $groupManager;
private $userId;
private $activityManager;
private $eventDispatcher;
private $changeHelper;
private $cardMapper;
private $boardsCache = null;
private $urlGenerator;
private $boardServiceValidator;
public function __construct(
BoardMapper $boardMapper,
@@ -99,9 +96,8 @@ class BoardService {
IEventDispatcher $eventDispatcher,
ChangeHelper $changeHelper,
IURLGenerator $urlGenerator,
IDBConnection $connection,
BoardServiceValidator $boardServiceValidator,
?string $userId
$userId
) {
$this->boardMapper = $boardMapper;
$this->stackMapper = $stackMapper;
@@ -120,7 +116,6 @@ class BoardService {
$this->userId = $userId;
$this->urlGenerator = $urlGenerator;
$this->cardMapper = $cardMapper;
$this->connection = $connection;
$this->boardServiceValidator = $boardServiceValidator;
}
@@ -131,6 +126,7 @@ class BoardService {
*/
public function setUserId(string $userId): void {
$this->userId = $userId;
$this->permissionService->setUserId($userId);
}
/**
@@ -185,7 +181,7 @@ class BoardService {
* @throws \OCP\AppFramework\Db\MultipleObjectsReturnedException
* @throws BadRequestException
*/
public function find($boardId) {
public function find($boardId, bool $allowDeleted = false) {
$this->boardServiceValidator->check(compact('boardId'));
if ($this->boardsCache && isset($this->boardsCache[$boardId])) {
return $this->boardsCache[$boardId];
@@ -196,7 +192,7 @@ class BoardService {
$this->permissionService->checkPermission($this->boardMapper, $boardId, Acl::PERMISSION_READ);
/** @var Board $board */
$board = $this->boardMapper->find($boardId, true, true);
$board = $this->boardMapper->find((int)$boardId, true, true, $allowDeleted);
$this->boardMapper->mapOwner($board);
if ($board->getAcl() !== null) {
foreach ($board->getAcl() as $acl) {
@@ -371,7 +367,7 @@ class BoardService {
$this->boardServiceValidator->check(compact('id'));
$this->permissionService->checkPermission($this->boardMapper, $id, Acl::PERMISSION_MANAGE);
$board = $this->find($id);
$board = $this->find($id, true);
$board->setDeletedAt(0);
$board = $this->boardMapper->update($board);
$this->activityManager->triggerEvent(ActivityManager::DECK_OBJECT_BOARD, $board, ActivityManager::SUBJECT_BOARD_RESTORE);
@@ -392,7 +388,7 @@ class BoardService {
$this->boardServiceValidator->check(compact('id'));
$this->permissionService->checkPermission($this->boardMapper, $id, Acl::PERMISSION_MANAGE);
$board = $this->find($id);
$board = $this->find($id, true);
$delete = $this->boardMapper->delete($board);
return $delete;
@@ -475,7 +471,7 @@ class BoardService {
$newAcl = $this->aclMapper->insert($acl);
$this->activityManager->triggerEvent(ActivityManager::DECK_OBJECT_BOARD, $newAcl, ActivityManager::SUBJECT_BOARD_SHARE, [], $this->userId);
$this->notificationHelper->sendBoardShared((int)$boardId, $acl);
$this->notificationHelper->sendBoardShared($boardId, $acl);
$this->boardMapper->mapAcl($newAcl);
$this->changeHelper->boardChanged($boardId);
@@ -484,7 +480,7 @@ class BoardService {
// TODO: use the dispatched event for this
try {
$resourceProvider = Server::get(\OCA\Deck\Collaboration\Resources\ResourceProvider::class);
$resourceProvider = \OC::$server->query(\OCA\Deck\Collaboration\Resources\ResourceProvider::class);
$resourceProvider->invalidateAccessCache($boardId);
} catch (\Exception $e) {
}
@@ -526,14 +522,18 @@ class BoardService {
}
/**
* @throws DbException
* @param $id
* @return \OCP\AppFramework\Db\Entity
* @throws DoesNotExistException
* @throws NoPermissionException
* @throws MultipleObjectsReturnedException
* @throws ContainerExceptionInterface
* @throws NotFoundExceptionInterface
* @throws \OCA\Deck\NoPermissionException
* @throws \OCP\AppFramework\Db\MultipleObjectsReturnedException
* @throws BadRequestException
*/
public function deleteAcl(int $id): ?Acl {
public function deleteAcl($id) {
if (is_numeric($id) === false) {
throw new BadRequestException('id must be a number');
}
$this->permissionService->checkPermission($this->aclMapper, $id, Acl::PERMISSION_SHARE);
/** @var Acl $acl */
$acl = $this->aclMapper->find($id);
@@ -552,16 +552,16 @@ class BoardService {
$version = \OCP\Util::getVersion()[0];
if ($version >= 16) {
try {
$resourceProvider = Server::get(\OCA\Deck\Collaboration\Resources\ResourceProvider::class);
$resourceProvider = \OC::$server->query(\OCA\Deck\Collaboration\Resources\ResourceProvider::class);
$resourceProvider->invalidateAccessCache($acl->getBoardId());
} catch (\Exception $e) {
}
}
$delete = $this->aclMapper->delete($acl);
$deletedAcl = $this->aclMapper->delete($acl);
$this->eventDispatcher->dispatchTyped(new AclDeletedEvent($acl));
return $deletedAcl;
return $delete;
}
/**
@@ -613,7 +613,7 @@ class BoardService {
}
public function transferBoardOwnership(int $boardId, string $newOwner, bool $changeContent = false): Board {
$this->connection->beginTransaction();
\OC::$server->getDatabaseConnection()->beginTransaction();
try {
$board = $this->boardMapper->find($boardId);
$previousOwner = $board->getOwner();
@@ -622,10 +622,7 @@ class BoardService {
if (!$changeContent) {
try {
$this->addAcl($boardId, Acl::PERMISSION_TYPE_USER, $previousOwner, true, true, true);
} catch (DbException $e) {
if ($e->getReason() !== DbException::REASON_UNIQUE_CONSTRAINT_VIOLATION) {
throw $e;
}
} catch (UniqueConstraintViolationException $e) {
}
}
$this->boardMapper->transferOwnership($previousOwner, $newOwner, $boardId);
@@ -635,10 +632,10 @@ class BoardService {
$this->assignedUsersMapper->remapAssignedUser($boardId, $previousOwner, $newOwner);
$this->cardMapper->remapCardOwner($boardId, $previousOwner, $newOwner);
}
$this->connection->commit();
\OC::$server->getDatabaseConnection()->commit();
return $this->boardMapper->find($boardId);
} catch (\Throwable $e) {
$this->connection->rollBack();
\OC::$server->getDatabaseConnection()->rollBack();
throw $e;
}
}

View File

@@ -46,31 +46,27 @@ use OCA\Deck\BadRequestException;
use OCA\Deck\Validators\CardServiceValidator;
use OCP\Comments\ICommentsManager;
use OCP\EventDispatcher\IEventDispatcher;
use OCP\IRequest;
use OCP\IUserManager;
use OCP\IURLGenerator;
use Psr\Log\LoggerInterface;
class CardService {
private CardMapper $cardMapper;
private StackMapper $stackMapper;
private BoardMapper $boardMapper;
private LabelMapper $labelMapper;
private PermissionService $permissionService;
private BoardService $boardService;
private NotificationHelper $notificationHelper;
private AssignmentMapper $assignedUsersMapper;
private AttachmentService $attachmentService;
private ?string $currentUser;
private ActivityManager $activityManager;
private ICommentsManager $commentsManager;
private ChangeHelper $changeHelper;
private IEventDispatcher $eventDispatcher;
private IUserManager $userManager;
private IURLGenerator $urlGenerator;
private LoggerInterface $logger;
private IRequest $request;
private CardServiceValidator $cardServiceValidator;
private $cardMapper;
private $stackMapper;
private $boardMapper;
private $labelMapper;
private $permissionService;
private $boardService;
private $notificationHelper;
private $assignedUsersMapper;
private $attachmentService;
private $currentUser;
private $activityManager;
private $commentsManager;
private $changeHelper;
private $eventDispatcher;
private $userManager;
private $urlGenerator;
private $cardServiceValidator;
public function __construct(
CardMapper $cardMapper,
@@ -88,10 +84,8 @@ class CardService {
ChangeHelper $changeHelper,
IEventDispatcher $eventDispatcher,
IURLGenerator $urlGenerator,
LoggerInterface $logger,
IRequest $request,
CardServiceValidator $cardServiceValidator,
?string $userId
$userId
) {
$this->cardMapper = $cardMapper;
$this->stackMapper = $stackMapper;
@@ -109,8 +103,6 @@ class CardService {
$this->eventDispatcher = $eventDispatcher;
$this->currentUser = $userId;
$this->urlGenerator = $urlGenerator;
$this->logger = $logger;
$this->request = $request;
$this->cardServiceValidator = $cardServiceValidator;
}
@@ -126,7 +118,7 @@ class CardService {
$countComments = $this->commentsManager->getNumberOfCommentsForObject('deckCard', (string)$card->getId());
$card->setCommentsUnread($countUnreadComments);
$card->setCommentsCount($countComments);
$stack = $this->stackMapper->find($card->getStackId());
$board = $this->boardService->find($stack->getBoardId());
$card->setRelatedStack($stack);
@@ -144,18 +136,23 @@ class CardService {
}
/**
* @param $cardId
* @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($cardId) {
if (is_numeric($cardId) === false) {
throw new BadRequestException('card id must be a number');
}
$this->permissionService->checkPermission($this->cardMapper, $cardId, Acl::PERMISSION_READ);
$card = $this->cardMapper->find($cardId);
$assignedUsers = $this->assignedUsersMapper->findAll($card->getId());
$attachments = $this->attachmentService->findAll($cardId, true);
if ($this->request->getParam('apiVersion') === '1.0') {
if (\OC::$server->getRequest()->getParam('apiVersion') === '1.0') {
$attachments = array_filter($attachments, function ($attachment) {
return $attachment->getType() === 'deck_file';
});
@@ -170,7 +167,7 @@ class CardService {
try {
$this->permissionService->checkPermission($this->boardMapper, $boardId, Acl::PERMISSION_READ);
} catch (NoPermissionException $e) {
$this->logger->error('Unable to check permission for a previously obtained board ' . $boardId, ['exception' => $e]);
\OC::$server->getLogger()->error('Unable to check permission for a previously obtained board ' . $boardId, ['exception' => $e]);
return [];
}
$cards = $this->cardMapper->findCalendarEntries($boardId);
@@ -210,7 +207,7 @@ class CardService {
$card->setDescription($description);
$card->setDuedate($duedate);
$card = $this->cardMapper->insert($card);
$this->activityManager->triggerEvent(ActivityManager::DECK_OBJECT_CARD, $card, ActivityManager::SUBJECT_CARD_CREATE);
$this->changeHelper->cardChanged($card->getId(), false);
$this->eventDispatcher->dispatchTyped(new CardCreatedEvent($card));
@@ -239,7 +236,7 @@ class CardService {
$card = $this->cardMapper->find($id);
$card->setDeletedAt(time());
$this->cardMapper->update($card);
$this->activityManager->triggerEvent(ActivityManager::DECK_OBJECT_CARD, $card, ActivityManager::SUBJECT_CARD_DELETE);
$this->notificationHelper->markDuedateAsRead($card);
$this->changeHelper->cardChanged($card->getId(), false);
@@ -267,7 +264,7 @@ class CardService {
public function update($id, $title, $stackId, $type, $owner, $description = '', $order = 0, $duedate = null, $deletedAt = null, $archived = null) {
$this->cardServiceValidator->check(compact('id', 'title', 'stackId', 'type', 'owner', 'order'));
$this->permissionService->checkPermission($this->cardMapper, $id, Acl::PERMISSION_EDIT);
$this->permissionService->checkPermission($this->cardMapper, $id, Acl::PERMISSION_EDIT, null, true);
$this->permissionService->checkPermission($this->stackMapper, $stackId, Acl::PERMISSION_EDIT);
if ($this->boardService->isArchived($this->cardMapper, $id)) {
@@ -277,6 +274,14 @@ class CardService {
if ($archived !== null && $card->getArchived() && $archived === true) {
throw new StatusException('Operation not allowed. This card is archived.');
}
if ($card->getDeletedAt() !== 0) {
if ($deletedAt === null || $deletedAt > 0) {
// Only allow operations when restoring the card
throw new NoPermissionException('Operation not allowed. This card was deleted.');
}
}
$changes = new ChangeSet($card);
if ($card->getLastEditor() !== $this->currentUser && $card->getLastEditor() !== null) {
$this->activityManager->triggerEvent(
@@ -298,11 +303,11 @@ class CardService {
$card->setType($type);
$card->setOrder($order);
$card->setOwner($owner);
$card->setDuedate($duedate ? new \DateTime($duedate) : null);
$card->setDuedate($duedate);
$resetDuedateNotification = false;
if (
$card->getDuedate() === null ||
($card->getDuedate()) != ($changes->getBefore()->getDuedate())
(new \DateTime($card->getDuedate())) != (new \DateTime($changes->getBefore()->getDuedate()))
) {
$card->setNotified(false);
$resetDuedateNotification = true;
@@ -431,7 +436,7 @@ class CardService {
* @throws StatusException
* @throws \OCA\Deck\NoPermissionException
* @throws \OCP\AppFramework\Db\DoesNotExistException
* @throws \OCP\AppFramework\Db\MultipleObjectsReturnedException
* @throws \OCP\AppFramework\Db\
* @throws BadRequestException
*/
public function archive($id) {

View File

@@ -31,7 +31,6 @@ use OCA\Circles\Model\Circle;
use OCA\Circles\Model\Member;
use OCA\Circles\Model\Probes\CircleProbe;
use OCP\App\IAppManager;
use OCP\Server;
use Throwable;
/**
@@ -39,7 +38,7 @@ use Throwable;
* having the app disabled is properly handled
*/
class CirclesService {
private bool $circlesEnabled;
private $circlesEnabled;
private $userCircleCache = [];
@@ -59,7 +58,8 @@ class CirclesService {
try {
// Enforce current user condition since we always want the full list of members
$circlesManager = Server::get(CirclesManager::class);
/** @var CirclesManager $circlesManager */
$circlesManager = \OC::$server->get(CirclesManager::class);
$circlesManager->startSuperSession();
return $circlesManager->getCircle($circleId);
} catch (Throwable $e) {
@@ -77,7 +77,8 @@ class CirclesService {
}
try {
$circlesManager = Server::get(CirclesManager::class);
/** @var CirclesManager $circlesManager */
$circlesManager = \OC::$server->get(CirclesManager::class);
$federatedUser = $circlesManager->getFederatedUser($userId, Member::TYPE_USER);
$circlesManager->startSession($federatedUser);
$circle = $circlesManager->getCircle($circleId);
@@ -105,7 +106,8 @@ class CirclesService {
}
try {
$circlesManager = Server::get(CirclesManager::class);
/** @var CirclesManager $circlesManager */
$circlesManager = \OC::$server->get(CirclesManager::class);
$federatedUser = $circlesManager->getFederatedUser($userId, Member::TYPE_USER);
$circlesManager->startSession($federatedUser);
$probe = new CircleProbe();

View File

@@ -40,14 +40,20 @@ use OutOfBoundsException;
use function is_numeric;
class CommentService {
private ICommentsManager $commentsManager;
private IUserManager $userManager;
private CardMapper $cardMapper;
private PermissionService $permissionService;
private ILogger $logger;
private ?string $userId;
public function __construct(ICommentsManager $commentsManager, PermissionService $permissionService, CardMapper $cardMapper, IUserManager $userManager, ILogger $logger, ?string $userId) {
/**
* @var ICommentsManager
*/
private $commentsManager;
/**
* @var IUserManager
*/
private $userManager;
/** @var ILogger */
private $logger;
private $userId;
public function __construct(ICommentsManager $commentsManager, PermissionService $permissionService, CardMapper $cardMapper, IUserManager $userManager, ILogger $logger, $userId) {
$this->commentsManager = $commentsManager;
$this->permissionService = $permissionService;
$this->cardMapper = $cardMapper;
@@ -77,24 +83,17 @@ class CommentService {
}
/**
* @param string $cardId
* @param string $message
* @param string $replyTo
* @return DataResponse
* @throws BadRequestException
* @throws NotFoundException|NoPermissionException
*/
public function create(string $cardId, string $message, string $replyTo = '0'): DataResponse {
if (!is_numeric($cardId)) {
throw new BadRequestException('A valid card id must be provided');
}
public function create(int $cardId, string $message, string $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') {
try {
$comment = $this->commentsManager->get($replyTo);
if ($comment->getObjectType() !== Application::COMMENT_ENTITY_TYPE || $comment->getObjectId() !== $cardId) {
if ($comment->getObjectType() !== Application::COMMENT_ENTITY_TYPE || (int)$comment->getObjectId() !== $cardId) {
throw new CommentNotFoundException();
}
} catch (CommentNotFoundException $e) {
@@ -103,7 +102,7 @@ class CommentService {
}
try {
$comment = $this->commentsManager->create('users', $this->userId, Application::COMMENT_ENTITY_TYPE, $cardId);
$comment = $this->commentsManager->create('users', $this->userId, Application::COMMENT_ENTITY_TYPE, (string)$cardId);
$comment->setMessage($message);
$comment->setVerb('comment');
$comment->setParentId($replyTo);
@@ -139,7 +138,7 @@ class CommentService {
throw new NoPermissionException('Only authors are allowed to edit their comment.');
}
if ($comment->getParentId() !== '0') {
$this->permissionService->checkPermission($this->cardMapper, $comment->getParentId(), Acl::PERMISSION_READ);
$this->permissionService->checkPermission($this->cardMapper, (int)$comment->getParentId(), Acl::PERMISSION_READ);
}
$comment->setMessage($message);

View File

@@ -40,9 +40,9 @@ class ConfigService {
public const SETTING_BOARD_NOTIFICATION_DUE_ALL = 'all';
public const SETTING_BOARD_NOTIFICATION_DUE_DEFAULT = self::SETTING_BOARD_NOTIFICATION_DUE_ASSIGNED;
private IConfig $config;
private ?string $userId = null;
private IGroupManager $groupManager;
private $config;
private $userId;
private $groupManager;
public function __construct(
IConfig $config,
@@ -52,14 +52,11 @@ class ConfigService {
$this->config = $config;
}
public function getUserId(): ?string {
public function getUserId() {
if (!$this->userId) {
// We cannot use DI for the userId or UserSession as the ConfigService
// is initiated too early before the session is actually loaded
$user = \OCP\Server::get(IUserSession::class)->getUser();
$user = \OC::$server->get(IUserSession::class)->getUser();
$this->userId = $user ? $user->getUID() : null;
}
return $this->userId;
}
@@ -69,9 +66,7 @@ class ConfigService {
}
$data = [
'calendar' => $this->isCalendarEnabled(),
'cardDetailsInModal' => $this->isCardDetailsInModal(),
'cardIdBadge' => $this->isCardIdBadgeEnabled()
'calendar' => $this->isCalendarEnabled()
];
if ($this->groupManager->isAdmin($this->getUserId())) {
$data['groupLimit'] = $this->get('groupLimit');
@@ -79,11 +74,8 @@ class ConfigService {
return $data;
}
/**
* @return bool|array{id: string, displayname: string}[]
* @throws NoPermissionException
*/
public function get(string $key) {
public function get($key) {
$result = null;
[$scope] = explode(':', $key, 2);
switch ($scope) {
case 'groupLimit':
@@ -96,18 +88,7 @@ class ConfigService {
return false;
}
return (bool)$this->config->getUserValue($this->getUserId(), Application::APP_ID, 'calendar', true);
case 'cardDetailsInModal':
if ($this->getUserId() === null) {
return false;
}
return (bool)$this->config->getUserValue($this->getUserId(), Application::APP_ID, 'cardDetailsInModal', true);
case 'cardIdBadge':
if ($this->getUserId() === null) {
return false;
}
return (bool)$this->config->getUserValue($this->getUserId(), Application::APP_ID, 'cardIdBadge', false);
}
return false;
}
public function isCalendarEnabled(int $boardId = null): bool {
@@ -124,29 +105,6 @@ class ConfigService {
return (bool)$this->config->getUserValue($this->getUserId(), Application::APP_ID, 'board:' . $boardId . ':calendar', $defaultState);
}
public function isCardDetailsInModal(int $boardId = null): bool {
if ($this->getUserId() === null) {
return false;
}
$defaultState = (bool)$this->config->getUserValue($this->getUserId(), Application::APP_ID, 'cardDetailsInModal', true);
if ($boardId === null) {
return $defaultState;
}
return (bool)$this->config->getUserValue($this->getUserId(), Application::APP_ID, 'board:' . $boardId . ':cardDetailsInModal', $defaultState);
}
public function isCardIdBadgeEnabled(): bool {
if ($this->getUserId() === 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);
return (bool)$this->config->getUserValue($this->getUserId(), Application::APP_ID, 'cardIdBadge', $defaultState);
}
public function set($key, $value) {
if ($this->getUserId() === null) {
throw new NoPermissionException('Must be logged in to set user config');
@@ -165,14 +123,6 @@ class ConfigService {
$this->config->setUserValue($this->getUserId(), Application::APP_ID, 'calendar', (string)$value);
$result = $value;
break;
case 'cardDetailsInModal':
$this->config->setUserValue($this->getUserId(), Application::APP_ID, 'cardDetailsInModal', (string)$value);
$result = $value;
break;
case 'cardIdBadge':
$this->config->setUserValue($this->getUserId(), Application::APP_ID, 'cardIdBadge', (string)$value);
$result = $value;
break;
case 'board':
[$boardId, $boardConfigKey] = explode(':', $key);
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)) {
@@ -184,10 +134,7 @@ class ConfigService {
return $result;
}
/**
* @return string[]
*/
private function setGroupLimit(array $value): array {
private function setGroupLimit($value) {
$groups = [];
foreach ($value as $group) {
$groups[] = $group['id'];
@@ -197,7 +144,7 @@ class ConfigService {
return $groups;
}
private function getGroupLimitList(): array {
private function getGroupLimitList() {
$value = $this->config->getAppValue(Application::APP_ID, 'groupLimit', '');
$groups = explode(',', $value);
if ($value === '') {
@@ -206,10 +153,9 @@ class ConfigService {
return $groups;
}
/** @return array{id: string, displayname: string}[] */
private function getGroupLimit() {
$groups = $this->getGroupLimitList();
$groups = array_map(function (string $groupId): ?array {
$groups = array_map(function ($groupId) {
/** @var IGroup $groups */
$group = $this->groupManager->get($groupId);
if ($group === null) {

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