Compare commits

..

222 Commits

Author SHA1 Message Date
Julius Härtl
7e23aa63be Migrate AclMapper to QBMapper
Signed-off-by: Julius Härtl <jus@bitgrid.net>
2022-02-25 14:00:59 +01:00
dependabot[bot]
4f23ebedb6 Merge pull request #3605 from nextcloud/dependabot/npm_and_yarn/striptags-3.2.0 2022-02-25 09:15:04 +00:00
dependabot[bot]
6e8ca6c067 Bump striptags from 3.1.1 to 3.2.0
Bumps [striptags](https://github.com/ericnorris/striptags) from 3.1.1 to 3.2.0.
- [Release notes](https://github.com/ericnorris/striptags/releases)
- [Commits](https://github.com/ericnorris/striptags/compare/v3.1.1...v3.2.0)

---
updated-dependencies:
- dependency-name: striptags
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>
2022-02-25 09:02:52 +00:00
dependabot[bot]
6d6a31037e Merge pull request #3604 from nextcloud/dependabot/npm_and_yarn/path-parse-1.0.7 2022-02-25 09:02:03 +00:00
dependabot[bot]
7814d59e0c Bump path-parse from 1.0.6 to 1.0.7
Bumps [path-parse](https://github.com/jbgutierrez/path-parse) from 1.0.6 to 1.0.7.
- [Release notes](https://github.com/jbgutierrez/path-parse/releases)
- [Commits](https://github.com/jbgutierrez/path-parse/commits/v1.0.7)

---
updated-dependencies:
- dependency-name: path-parse
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>
2022-02-25 08:52:33 +00:00
Julius Härtl
cc2ff38320 Merge pull request #3603 from nextcloud/dependabot/npm_and_yarn/master/nextcloud/webpack-vue-config-5.0.0
Bump @nextcloud/webpack-vue-config from 4.3.2 to 5.0.0
2022-02-25 09:51:50 +01:00
dependabot[bot]
a43de8ec29 Bump @nextcloud/webpack-vue-config from 4.3.2 to 5.0.0
Bumps [@nextcloud/webpack-vue-config](https://github.com/nextcloud/webpack-vue-config) from 4.3.2 to 5.0.0.
- [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.3.2...v5.0.0)

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

Signed-off-by: dependabot[bot] <support@github.com>
2022-02-25 08:35:52 +00:00
Julius Härtl
a1bd914b48 Merge pull request #3599 from nextcloud/enh/time-insensitive-cron
Move DeleteCron to be time insensitive
2022-02-24 16:16:52 +01:00
Julius Härtl
e42ffa4ca2 Move DeleteCron to be time insensitive
Signed-off-by: Julius Härtl <jus@bitgrid.net>
2022-02-24 14:08:46 +01:00
dependabot[bot]
8c33760c79 Merge pull request #3594 from nextcloud/dependabot/npm_and_yarn/master/dompurify-2.3.6 2022-02-19 05:24:51 +00:00
dependabot[bot]
c5a9a0897e Bump dompurify from 2.3.5 to 2.3.6
Bumps [dompurify](https://github.com/cure53/DOMPurify) from 2.3.5 to 2.3.6.
- [Release notes](https://github.com/cure53/DOMPurify/releases)
- [Commits](https://github.com/cure53/DOMPurify/compare/2.3.5...2.3.6)

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

Signed-off-by: dependabot[bot] <support@github.com>
2022-02-19 05:07:11 +00:00
dependabot[bot]
1e32a01be4 Merge pull request #3593 from nextcloud/dependabot/composer/phpunit/phpunit-9.5.14 2022-02-19 05:06:17 +00:00
dependabot[bot]
d44f489173 Bump phpunit/phpunit from 9.5.13 to 9.5.14
Bumps [phpunit/phpunit](https://github.com/sebastianbergmann/phpunit) from 9.5.13 to 9.5.14.
- [Release notes](https://github.com/sebastianbergmann/phpunit/releases)
- [Changelog](https://github.com/sebastianbergmann/phpunit/blob/master/ChangeLog-9.5.md)
- [Commits](https://github.com/sebastianbergmann/phpunit/compare/9.5.13...9.5.14)

---
updated-dependencies:
- dependency-name: phpunit/phpunit
  dependency-type: direct:development
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2022-02-19 03:26:27 +00:00
Nextcloud bot
cfa67f1cbd [tx-robot] updated from transifex
Signed-off-by: Nextcloud bot <bot@nextcloud.com>
2022-02-19 02:37:58 +00:00
Nextcloud bot
97aa71e1fb [tx-robot] updated from transifex
Signed-off-by: Nextcloud bot <bot@nextcloud.com>
2022-02-17 02:38:48 +00:00
Julius Härtl
c3e5cf6851 Merge pull request #3539 from nextcloud/dependabot/npm_and_yarn/master/nextcloud/stylelint-config-2.1.2
Bump @nextcloud/stylelint-config from 1.0.0-beta.0 to 2.1.2
2022-02-13 15:17:34 +01:00
Julius Härtl
84d3d6b834 Merge pull request #3588 from nextcloud/dependabot/npm_and_yarn/master/nextcloud/vue-5.0.0
Bump @nextcloud/vue from 4.3.0 to 5.0.0
2022-02-13 15:17:15 +01:00
dependabot[bot]
778f0cda64 Bump @nextcloud/vue from 4.3.0 to 5.0.0
Bumps [@nextcloud/vue](https://github.com/nextcloud/nextcloud-vue) from 4.3.0 to 5.0.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.3.0...v5.0.0)

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

Signed-off-by: dependabot[bot] <support@github.com>
2022-02-12 06:02:17 +00:00
dependabot[bot]
504916608a Bump @nextcloud/stylelint-config from 1.0.0-beta.0 to 2.1.2
Bumps [@nextcloud/stylelint-config](https://github.com/nextcloud/stylelint-config) from 1.0.0-beta.0 to 2.1.2.
- [Release notes](https://github.com/nextcloud/stylelint-config/releases)
- [Changelog](https://github.com/nextcloud/stylelint-config/blob/master/CHANGELOG.md)
- [Commits](https://github.com/nextcloud/stylelint-config/commits/v2.1.2)

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

Signed-off-by: dependabot[bot] <support@github.com>
2022-02-12 06:02:15 +00:00
dependabot[bot]
33b99290c2 Merge pull request #3590 from nextcloud/dependabot/github_actions/shivammathur/setup-php-2.17.0 2022-02-12 06:01:13 +00:00
dependabot[bot]
132da8cf02 Bump shivammathur/setup-php from 2.16.0 to 2.17.0
Bumps [shivammathur/setup-php](https://github.com/shivammathur/setup-php) from 2.16.0 to 2.17.0.
- [Release notes](https://github.com/shivammathur/setup-php/releases)
- [Commits](https://github.com/shivammathur/setup-php/compare/2.16.0...2.17.0)

---
updated-dependencies:
- dependency-name: shivammathur/setup-php
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2022-02-12 04:54:31 +00:00
dependabot[bot]
3744541cd6 Merge pull request #3591 from nextcloud/dependabot/npm_and_yarn/follow-redirects-1.14.8 2022-02-12 04:53:42 +00:00
dependabot[bot]
da98ab81d6 Bump follow-redirects from 1.14.7 to 1.14.8
Bumps [follow-redirects](https://github.com/follow-redirects/follow-redirects) from 1.14.7 to 1.14.8.
- [Release notes](https://github.com/follow-redirects/follow-redirects/releases)
- [Commits](https://github.com/follow-redirects/follow-redirects/compare/v1.14.7...v1.14.8)

---
updated-dependencies:
- dependency-name: follow-redirects
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>
2022-02-12 03:39:53 +00:00
dependabot[bot]
9a9b64f448 Merge pull request #3589 from nextcloud/dependabot/npm_and_yarn/master/jest-27.5.1 2022-02-12 03:39:06 +00:00
dependabot[bot]
ed7e2fe1b9 Bump jest from 27.4.7 to 27.5.1
Bumps [jest](https://github.com/facebook/jest) from 27.4.7 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/compare/v27.4.7...v27.5.1)

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

Signed-off-by: dependabot[bot] <support@github.com>
2022-02-12 02:04:09 +00:00
dependabot[bot]
634c9d7768 Merge pull request #3583 from nextcloud/dependabot/npm_and_yarn/master/babel/runtime-7.17.2 2022-02-09 17:42:50 +00:00
dependabot[bot]
5988975324 Bump @babel/runtime from 7.16.7 to 7.17.2
Bumps [@babel/runtime](https://github.com/babel/babel/tree/HEAD/packages/babel-runtime) from 7.16.7 to 7.17.2.
- [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.17.2/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-02-09 17:28:27 +00:00
dependabot[bot]
ffd5bd784d Merge pull request #3567 from nextcloud/dependabot/npm_and_yarn/nanoid-3.2.0 2022-02-09 17:26:55 +00:00
dependabot[bot]
44b23688f5 Bump nanoid from 3.1.30 to 3.2.0
Bumps [nanoid](https://github.com/ai/nanoid) from 3.1.30 to 3.2.0.
- [Release notes](https://github.com/ai/nanoid/releases)
- [Changelog](https://github.com/ai/nanoid/blob/main/CHANGELOG.md)
- [Commits](https://github.com/ai/nanoid/compare/3.1.30...3.2.0)

---
updated-dependencies:
- dependency-name: nanoid
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>
2022-02-09 17:10:36 +00:00
dependabot[bot]
ef8c542289 Merge pull request #3575 from nextcloud/dependabot/composer/vimeo/psalm-4.20.0 2022-02-09 17:09:34 +00:00
dependabot[bot]
c0d2154e46 Bump vimeo/psalm from 4.18.1 to 4.20.0
Bumps [vimeo/psalm](https://github.com/vimeo/psalm) from 4.18.1 to 4.20.0.
- [Release notes](https://github.com/vimeo/psalm/releases)
- [Commits](https://github.com/vimeo/psalm/compare/4.18.1...4.20.0)

---
updated-dependencies:
- dependency-name: vimeo/psalm
  dependency-type: direct:development
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2022-02-09 17:00:35 +00:00
max-nextcloud
c9d34ab6df Merge pull request #3553 from nextcloud/feat/workflow-auto-update-dependabot-approve-merge.yml
Updating dependabot-approve-merge.yml workflow from template
2022-02-09 17:59:53 +01:00
Nextcloud bot
1dc62207ba Updating dependabot-approve-merge.yml workflow from template
Signed-off-by: Nextcloud bot <bot@nextcloud.com>
2022-02-09 16:36:02 +01:00
dependabot[bot]
fe007ca1d3 Merge pull request #3576 from nextcloud/dependabot/npm_and_yarn/master/relative-ci/agent-3.1.1 2022-02-09 15:02:27 +00:00
dependabot[bot]
887b1e46e7 Bump @relative-ci/agent from 3.1.0 to 3.1.1
Bumps [@relative-ci/agent](https://github.com/relative-ci/agent) from 3.1.0 to 3.1.1.
- [Release notes](https://github.com/relative-ci/agent/releases)
- [Commits](https://github.com/relative-ci/agent/compare/v3.1.0...v3.1.1)

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

Signed-off-by: dependabot[bot] <support@github.com>
2022-02-09 14:11:34 +00:00
Julius Härtl
f04fa03baa Merge pull request #3582 from nextcloud/update-master-php-enviroment
Update master php enviroment
2022-02-09 15:10:29 +01:00
Joas Schilling
106bc7f47f Update master php enviroment
Signed-off-by: Joas Schilling <coding@schilljs.com>
2022-02-09 11:37:29 +01:00
Nextcloud bot
4b5b4badd4 [tx-robot] updated from transifex
Signed-off-by: Nextcloud bot <bot@nextcloud.com>
2022-02-08 02:38:03 +00:00
Nextcloud bot
f353f1cdd4 [tx-robot] updated from transifex
Signed-off-by: Nextcloud bot <bot@nextcloud.com>
2022-02-06 02:36:57 +00:00
Nextcloud bot
1d7aa5d1f0 [tx-robot] updated from transifex
Signed-off-by: Nextcloud bot <bot@nextcloud.com>
2022-02-05 02:37:25 +00:00
Nextcloud bot
69ff35653e [tx-robot] updated from transifex
Signed-off-by: Nextcloud bot <bot@nextcloud.com>
2022-02-01 02:37:23 +00:00
Julius Härtl
2c487a4375 Merge pull request #3566 from nextcloud/dependabot/npm_and_yarn/master/relative-ci/agent-3.1.0
Bump @relative-ci/agent from 3.0.0 to 3.1.0
2022-01-31 12:05:35 +01:00
Julius Härtl
6cc2ef684f Merge pull request #3550 from nextcloud/dependabot/npm_and_yarn/master/nextcloud/axios-1.9.0
Bump @nextcloud/axios from 1.8.0 to 1.9.0
2022-01-31 12:05:08 +01:00
dependabot[bot]
fb1177fa76 Bump @nextcloud/axios from 1.8.0 to 1.9.0
Bumps [@nextcloud/axios](https://github.com/nextcloud/nextcloud-axios) from 1.8.0 to 1.9.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.8.0...v1.9.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-01-31 09:26:22 +00:00
dependabot[bot]
6347fee2a1 Bump @relative-ci/agent from 3.0.0 to 3.1.0
Bumps [@relative-ci/agent](https://github.com/relative-ci/agent) from 3.0.0 to 3.1.0.
- [Release notes](https://github.com/relative-ci/agent/releases)
- [Commits](https://github.com/relative-ci/agent/compare/v3.0.0...v3.1.0)

---
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-01-31 09:26:20 +00:00
Julius Härtl
e12b6658ec Merge pull request #3561 from nextcloud/update-master-php-testing-versions
Update master php testing versions
2022-01-31 10:24:52 +01:00
Nextcloud bot
e23027094f [tx-robot] updated from transifex
Signed-off-by: Nextcloud bot <bot@nextcloud.com>
2022-01-31 02:37:23 +00:00
dependabot[bot]
89f31635c5 Merge pull request #3565 from nextcloud/dependabot/npm_and_yarn/master/dompurify-2.3.5 2022-01-29 05:18:16 +00:00
dependabot[bot]
2cfb107167 Bump dompurify from 2.3.4 to 2.3.5
Bumps [dompurify](https://github.com/cure53/DOMPurify) from 2.3.4 to 2.3.5.
- [Release notes](https://github.com/cure53/DOMPurify/releases)
- [Commits](https://github.com/cure53/DOMPurify/compare/2.3.4...2.3.5)

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

Signed-off-by: dependabot[bot] <support@github.com>
2022-01-29 04:31:09 +00:00
dependabot[bot]
5d562db408 Merge pull request #3568 from nextcloud/dependabot/npm_and_yarn/node-fetch-2.6.7 2022-01-29 04:30:15 +00:00
dependabot[bot]
9993a198b1 Bump node-fetch from 2.6.1 to 2.6.7
Bumps [node-fetch](https://github.com/node-fetch/node-fetch) from 2.6.1 to 2.6.7.
- [Release notes](https://github.com/node-fetch/node-fetch/releases)
- [Commits](https://github.com/node-fetch/node-fetch/compare/v2.6.1...v2.6.7)

---
updated-dependencies:
- dependency-name: node-fetch
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>
2022-01-29 03:23:16 +00:00
dependabot[bot]
cf7c327816 Merge pull request #3563 from nextcloud/dependabot/composer/phpunit/phpunit-9.5.13 2022-01-29 03:22:33 +00:00
dependabot[bot]
f2c1b42811 Bump phpunit/phpunit from 9.5.12 to 9.5.13
Bumps [phpunit/phpunit](https://github.com/sebastianbergmann/phpunit) from 9.5.12 to 9.5.13.
- [Release notes](https://github.com/sebastianbergmann/phpunit/releases)
- [Changelog](https://github.com/sebastianbergmann/phpunit/blob/master/ChangeLog-9.5.md)
- [Commits](https://github.com/sebastianbergmann/phpunit/compare/9.5.12...9.5.13)

---
updated-dependencies:
- dependency-name: phpunit/phpunit
  dependency-type: direct:development
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2022-01-29 02:01:10 +00:00
Joas Schilling
b40f8609be Update master php testing versions
Signed-off-by: Joas Schilling <coding@schilljs.com>
2022-01-28 09:24:29 +01:00
Nextcloud bot
c73be045c3 [tx-robot] updated from transifex
Signed-off-by: Nextcloud bot <bot@nextcloud.com>
2022-01-24 02:37:08 +00:00
dependabot[bot]
a7243f7573 Merge pull request #3551 from nextcloud/dependabot/composer/phpunit/phpunit-9.5.12 2022-01-22 06:33:09 +00:00
dependabot[bot]
ad7f4aa22b Bump phpunit/phpunit from 9.5.11 to 9.5.12
Bumps [phpunit/phpunit](https://github.com/sebastianbergmann/phpunit) from 9.5.11 to 9.5.12.
- [Release notes](https://github.com/sebastianbergmann/phpunit/releases)
- [Changelog](https://github.com/sebastianbergmann/phpunit/blob/master/ChangeLog-9.5.md)
- [Commits](https://github.com/sebastianbergmann/phpunit/compare/9.5.11...9.5.12)

---
updated-dependencies:
- dependency-name: phpunit/phpunit
  dependency-type: direct:development
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2022-01-22 03:29:38 +00:00
dependabot[bot]
bc263b6da5 Merge pull request #3548 from nextcloud/dependabot/npm_and_yarn/master/nextcloud/webpack-vue-config-4.3.2 2022-01-22 03:28:52 +00:00
dependabot[bot]
a3953344b5 Bump @nextcloud/webpack-vue-config from 4.3.0 to 4.3.2
Bumps [@nextcloud/webpack-vue-config](https://github.com/nextcloud/webpack-vue-config) from 4.3.0 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.3.0...v4.3.2)

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

Signed-off-by: dependabot[bot] <support@github.com>
2022-01-22 02:18:24 +00:00
Luka Trovic
99973b4501 Merge pull request #3530 from nextcloud/bug/improve-contrast-link-in-markdown-note
Make links in markdown note bolder
2022-01-21 14:07:10 +01:00
Luka Trovic
4e31332fe4 fix: make links in markdown note bolder
Signed-off-by: Luka Trovic <luka@nextcloud.com>
2022-01-21 13:30:35 +01:00
Julien Veyssier
51dffdacd6 Merge pull request #3532 from nextcloud/feature/timestamps-on-created-and-modified-at-values
Timestamps on created and modified at values
2022-01-20 10:00:10 +01:00
Luka Trovic
054c5aaf8c feat: timestamps on created and modified at values
Signed-off-by: Luka Trovic <luka@nextcloud.com>
2022-01-20 09:22:37 +01:00
Julien Veyssier
4f49254cfe Merge pull request #3531 from nextcloud/bug/missing-horizontal-scrollbar-in-large-table-inside-description
Add horizontal scrollbar into the large table inside description
2022-01-19 12:13:38 +01:00
Luka Trovic
a11bf5f4ba fix: add horizontal scrollbar into the large table inside description
Signed-off-by: Luka Trovic <luka@nextcloud.com>
2022-01-19 10:16:43 +00:00
Nextcloud bot
9d3de1e576 [tx-robot] updated from transifex
Signed-off-by: Nextcloud bot <bot@nextcloud.com>
2022-01-19 02:38:10 +00:00
Julien Veyssier
b00c8a1196 Merge pull request #3541 from nextcloud/fix/check-stack-permission
Fix confusion between stackId and boardId in StackService
2022-01-18 14:28:26 +01:00
Julien Veyssier
ee3eb99cc7 fix confusion between stackId and boardId in StackService::update()
Signed-off-by: Julien Veyssier <eneiluj@posteo.net>
2022-01-18 11:24:31 +01:00
Nextcloud bot
b73462555b [tx-robot] updated from transifex
Signed-off-by: Nextcloud bot <bot@nextcloud.com>
2022-01-18 02:56:01 +00:00
Julius Härtl
24c1687857 Merge pull request #3499 from nextcloud/feature/show-board-name-in-browser-title
Show board name in browser title
2022-01-14 23:15:59 +01:00
Julius Härtl
123a5b8b13 Merge pull request #3529 from nextcloud/bugfix/noid/fix-talk-integration
Fix talk integration
2022-01-14 22:11:04 +01:00
Julius Härtl
a048d40cd7 Merge pull request #3528 from nextcloud/enh/calendar-global-setting
Add app config to toggle the default calendar setting as an admin
2022-01-14 21:48:55 +01:00
Luka Trovic
b26f61b9a3 show board name in browser title
Signed-off-by: Luka Trovic <luka@nextcloud.com>
2022-01-14 17:11:31 +01:00
Nextcloud bot
bf7203210c [tx-robot] updated from transifex
Signed-off-by: Nextcloud bot <bot@nextcloud.com>
2022-01-13 02:37:06 +00:00
Joas Schilling
53d70321da 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-12 16:20:31 +01:00
Julius Härtl
c070b18b1b Add app config to toggle the default calendar setting as an admin
Signed-off-by: Julius Härtl <jus@bitgrid.net>
2022-01-12 13:43:50 +01:00
Nextcloud bot
3446c4aa4f [tx-robot] updated from transifex
Signed-off-by: Nextcloud bot <bot@nextcloud.com>
2022-01-12 02:37:22 +00:00
Julius Härtl
19dad95c55 Merge pull request #3522 from nextcloud/docs/api-changelog
Extend API changelog
2022-01-11 19:06:50 +01:00
Julius Härtl
fc58528817 Merge pull request #3500 from nextcloud/bugfix/3312 2022-01-11 09:26:37 +01:00
Julius Härtl
ced39c9501 Extend API changelog
Signed-off-by: Julius Härtl <jus@bitgrid.net>
2022-01-11 09:13:05 +01:00
Julius Härtl
ae27e431b2 Merge pull request #3502 from nextcloud/bug/move-card-shows-deleted-boards 2022-01-11 09:00:02 +01:00
Nextcloud bot
d820d57661 [tx-robot] updated from transifex
Signed-off-by: Nextcloud bot <bot@nextcloud.com>
2022-01-11 02:37:37 +00:00
dependabot[bot]
adb90f2a51 Merge pull request #3518 from nextcloud/dependabot/npm_and_yarn/master/markdown-it-12.3.2 2022-01-10 12:45:10 +00:00
dependabot[bot]
c1e6f22fa1 Bump markdown-it from 12.3.0 to 12.3.2
Bumps [markdown-it](https://github.com/markdown-it/markdown-it) from 12.3.0 to 12.3.2.
- [Release notes](https://github.com/markdown-it/markdown-it/releases)
- [Changelog](https://github.com/markdown-it/markdown-it/blob/master/CHANGELOG.md)
- [Commits](https://github.com/markdown-it/markdown-it/compare/12.3.0...12.3.2)

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

Signed-off-by: dependabot[bot] <support@github.com>
2022-01-10 11:54:44 +00:00
Julius Härtl
c39d07db1a Merge pull request #3519 from nextcloud/dependabot/composer/vimeo/psalm-4.18.1 2022-01-10 12:53:52 +01:00
Julius Härtl
0ab5707c4f Merge pull request #3512 from simonspa/fix/parameterOrder 2022-01-10 12:01:49 +01:00
dependabot[bot]
a47110d6f7 Bump vimeo/psalm from 4.16.1 to 4.18.1
Bumps [vimeo/psalm](https://github.com/vimeo/psalm) from 4.16.1 to 4.18.1.
- [Release notes](https://github.com/vimeo/psalm/releases)
- [Commits](https://github.com/vimeo/psalm/compare/4.16.1...4.18.1)

---
updated-dependencies:
- dependency-name: vimeo/psalm
  dependency-type: direct:development
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2022-01-10 11:01:42 +00:00
Julius Härtl
2d2671fd77 Merge pull request #3516 from nextcloud/dependabot/npm_and_yarn/master/markdown-it-12.3.1 2022-01-10 12:01:11 +01:00
Julius Härtl
55cf2a6214 Merge pull request #3517 from nextcloud/dependabot/npm_and_yarn/master/nextcloud/webpack-vue-config-4.3.0 2022-01-10 12:01:04 +01:00
Julius Härtl
e488d42935 Merge pull request #3515 from nextcloud/dependabot/npm_and_yarn/master/jest-27.4.7 2022-01-10 12:00:50 +01:00
Nextcloud bot
6d8a5bc956 [tx-robot] updated from transifex
Signed-off-by: Nextcloud bot <bot@nextcloud.com>
2022-01-10 02:37:40 +00:00
dependabot[bot]
81c908a59d Bump @nextcloud/webpack-vue-config from 4.2.0 to 4.3.0
Bumps [@nextcloud/webpack-vue-config](https://github.com/nextcloud/webpack-vue-config) from 4.2.0 to 4.3.0.
- [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.2.0...v4.3.0)

---
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-01-08 04:07:21 +00:00
Nextcloud bot
84910d3d3e [tx-robot] updated from transifex
Signed-off-by: Nextcloud bot <bot@nextcloud.com>
2022-01-08 02:36:39 +00:00
dependabot[bot]
3e6ade718f Bump markdown-it from 12.3.0 to 12.3.1
Bumps [markdown-it](https://github.com/markdown-it/markdown-it) from 12.3.0 to 12.3.1.
- [Release notes](https://github.com/markdown-it/markdown-it/releases)
- [Changelog](https://github.com/markdown-it/markdown-it/blob/master/CHANGELOG.md)
- [Commits](https://github.com/markdown-it/markdown-it/compare/12.3.0...12.3.1)

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

Signed-off-by: dependabot[bot] <support@github.com>
2022-01-08 02:04:19 +00:00
dependabot[bot]
b5ed3b122a Bump jest from 27.4.5 to 27.4.7
Bumps [jest](https://github.com/facebook/jest) from 27.4.5 to 27.4.7.
- [Release notes](https://github.com/facebook/jest/releases)
- [Changelog](https://github.com/facebook/jest/blob/main/CHANGELOG.md)
- [Commits](https://github.com/facebook/jest/compare/v27.4.5...v27.4.7)

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

Signed-off-by: dependabot[bot] <support@github.com>
2022-01-08 02:02:31 +00:00
Simon Spannagel
3dfc33378d CardApiController: Fix order of optional parameters
Signed-off-by: Simon Spannagel <simonspa@kth.se>
2022-01-07 11:31:32 +01:00
Luka Trovic
e4dbd9e385 exclude deleted boards in the selection for target
Signed-off-by: Luka Trovic <luka@nextcloud.com>
2022-01-06 09:26:15 +01:00
Nextcloud bot
61910290b9 [tx-robot] updated from transifex
Signed-off-by: Nextcloud bot <bot@nextcloud.com>
2022-01-06 02:38:49 +00:00
Nextcloud bot
bce79c596b [tx-robot] updated from transifex
Signed-off-by: Nextcloud bot <bot@nextcloud.com>
2022-01-05 02:38:14 +00:00
Nextcloud bot
6be3c3fe67 [tx-robot] updated from transifex
Signed-off-by: Nextcloud bot <bot@nextcloud.com>
2022-01-04 02:38:08 +00:00
Julius Härtl
eaf6defe59 Move any circles API usage to internal service
Signed-off-by: Julius Härtl <jus@bitgrid.net>
2022-01-03 10:23:47 +01:00
Julius Härtl
bfc8222e6f Avoid blocking calendar access if something goes wrong while fetching deck entries
Signed-off-by: Julius Härtl <jus@bitgrid.net>
2022-01-03 10:23:47 +01:00
Julius Härtl
66fa241382 Merge pull request #3504 from nextcloud/dependabot/npm_and_yarn/master/babel/runtime-7.16.7 2022-01-03 08:32:27 +01:00
Julius Härtl
7b237d8cd8 Merge pull request #3505 from nextcloud/Valdnet-patch-1 2022-01-03 08:32:11 +01:00
Nextcloud bot
5fc1aba9cd [tx-robot] updated from transifex
Signed-off-by: Nextcloud bot <bot@nextcloud.com>
2022-01-03 02:38:04 +00:00
Nextcloud bot
3900a15b4b [tx-robot] updated from transifex
Signed-off-by: Nextcloud bot <bot@nextcloud.com>
2022-01-02 02:37:44 +00:00
Valdnet
cf8023855b l10n: Add dot.
Signed-off-by: Valdnet <47037905+Valdnet@users.noreply.github.com>
2022-01-01 21:47:52 +01:00
dependabot[bot]
ff41bbab7b Bump @babel/runtime from 7.16.5 to 7.16.7
Bumps [@babel/runtime](https://github.com/babel/babel/tree/HEAD/packages/babel-runtime) from 7.16.5 to 7.16.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.16.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-01-01 05:00:23 +00:00
dependabot[bot]
bec07726a7 Merge pull request #3503 from nextcloud/dependabot/github_actions/actions/setup-node-2.5.1 2022-01-01 04:23:38 +00:00
dependabot[bot]
c24e72f161 Bump actions/setup-node from 2.5.0 to 2.5.1
Bumps [actions/setup-node](https://github.com/actions/setup-node) from 2.5.0 to 2.5.1.
- [Release notes](https://github.com/actions/setup-node/releases)
- [Commits](https://github.com/actions/setup-node/compare/v2.5.0...v2.5.1)

---
updated-dependencies:
- dependency-name: actions/setup-node
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2022-01-01 03:26:30 +00:00
Nextcloud bot
6296ebb87c [tx-robot] updated from transifex
Signed-off-by: Nextcloud bot <bot@nextcloud.com>
2022-01-01 02:37:46 +00:00
Nextcloud bot
a96bb277a4 [tx-robot] updated from transifex
Signed-off-by: Nextcloud bot <bot@nextcloud.com>
2021-12-31 02:38:03 +00:00
Julius Härtl
b58913e730 Merge pull request #3182 from vitormattos/import-from-trello 2021-12-30 11:39:35 +01:00
Vitor Mattos
ccd5bce7ea Replace glob by EventDispatcher
Signed-off-by: Vitor Mattos <vitor@php.rio>
2021-12-30 11:19:50 +01:00
Vitor Mattos
f2b6934ac3 Move all classes to a sub-namespace
Signed-off-by: Vitor Mattos <vitor@php.rio>
2021-12-30 11:19:50 +01:00
Vitor Mattos
24c8b2f4aa Make error more specific
Signed-off-by: Vitor Mattos <vitor@php.rio>
2021-12-30 11:19:50 +01:00
Vitor Mattos
a3959e3cfc Update lib/Service/BoardImportCommandService.php
Co-authored-by: Julien Veyssier <eneiluj@posteo.net>

Signed-off-by: Vitor Mattos <vitor@php.rio>
2021-12-30 11:19:50 +01:00
Vitor Mattos
fda8a03c43 Remove unused codes
Signed-off-by: Vitor Mattos <vitor@php.rio>
2021-12-30 11:19:50 +01:00
Vitor Mattos
5b30577df0 Update documentation, import created
Signed-off-by: Vitor Mattos <vitor@php.rio>
2021-12-30 11:19:50 +01:00
Vitor Mattos
4561887348 Add long comments with attachment
Signed-off-by: Vitor Mattos <vitor@php.rio>
2021-12-30 11:19:49 +01:00
Vitor Mattos
e87c063076 Documentation, improvements on validation, refactor
Validate get boad
change pattern of api params
Import only one board by api
Populate data from api
Update class diagram
Update documentation
Add return when success
Sort comments
Fix order of cards
Instructions of attachments

Signed-off-by: Vitor Mattos <vitor@php.rio>
2021-12-30 11:19:49 +01:00
Vitor Mattos
202ea30090 Start implementing Trello API service
Implement name of system to import
Implement need validate data
Fix allowed system list
Start implementing Trello API service

Signed-off-by: Vitor Mattos <vitor@php.rio>
2021-12-30 11:19:49 +01:00
Vitor Mattos
c7a37ea425 Increase documentation
Signed-off-by: Vitor Mattos <vitor@php.rio>
2021-12-30 11:19:49 +01:00
Vitor Mattos
19c609540b Default valute to board property fix command flow and refactor
Fixes on getBoard tests
Refactor
Reduce psalm info
Refactor to implement pattern
Change order of methods to put all abstract first and all public first

Signed-off-by: Vitor Mattos <vitor@php.rio>
2021-12-30 11:19:49 +01:00
Vitor Mattos
6714c89220 Remove interact from command and implement bootstrap method
Signed-off-by: Vitor Mattos <vitor@php.rio>
2021-12-30 11:19:49 +01:00
Vitor Mattos
e01e4cf1a7 Create route to get json schema to validate config
Fix visibility
Make compatible with php 7.2
Remove returing instance
Increase coverage
Reduce psalm info
Throw exception if system not defined
Increment coverage

Signed-off-by: Vitor Mattos <vitor@php.rio>
2021-12-30 11:19:49 +01:00
Vitor Mattos
4138953208 Changes to make possible implement api endpoint
Update documentation
Start implementing getSystems route
Code to route getSystems
Controller to board import
Change return
Increase coverage

Signed-off-by: Vitor Mattos <vitor@php.rio>
2021-12-30 11:19:49 +01:00
Vitor Mattos
39a927de18 Clean attachment table
Clean code
Clean attachment table

Signed-off-by: Vitor Mattos <vitor@php.rio>
2021-12-30 11:19:49 +01:00
Vitor Mattos
c5d10dafb8 Import participants
Big refactor to create route
Import participants

Signed-off-by: Vitor Mattos <vitor@php.rio>
2021-12-30 11:19:48 +01:00
Vitor Mattos
fd92fc3c4d Refactor and improvements on command
Check available helpers
Default permission: view only
Moved validate setting from helper to command
Turn more easy create a importer
Docblock and improvements on interface
lcfirst on system property
Helper moved to ImporHelper folder
Moved fixtures to ImportHelper
Rename settings to config
Big refactor to move import methods to service

Signed-off-by: Vitor Mattos <vitor@php.rio>
2021-12-30 11:19:48 +01:00
Vitor Mattos
eb8bf3f22b Translations and tests
Signed-off-by: Vitor Mattos <vitor@php.rio>
2021-12-30 11:19:48 +01:00
Vitor Mattos
e28a47e9e0 Update UserExport.php
Signed-off-by: Vitor Mattos <vitor@php.rio>
2021-12-30 11:19:48 +01:00
Vitor Mattos
48df98ce67 Add new command
Clean code
Add new command
Import last modified and deleted date
Replace arrow functions by lambda functions
Add properties to class
Add dependency to composer.json
Signed-off-by: Vitor Mattos <vitor@php.rio>
Turn private methods
Add output messages and associate users to cards

Signed-off-by: Vitor Mattos <vitor@php.rio>
2021-12-30 11:19:46 +01:00
Vitor Mattos
89028c74cb Command to import from Trello
Signed-off-by: Vitor Mattos <vitor@php.rio>
2021-12-30 11:19:34 +01:00
Julius Härtl
4e5537f204 Merge pull request #3501 from nextcloud/bug/missing-translation-not-found-in-transifex 2021-12-30 09:00:20 +01:00
Luka Trovic
f74bca8c43 add a missing translation - not found in transifex
Signed-off-by: Luka Trovic <luka@nextcloud.com>
2021-12-30 08:23:14 +01:00
Julius Härtl
30e4b43e46 Merge pull request #3424 from nextcloud/dependabot/composer/nextcloud/coding-standard-1.0.0 2021-12-29 16:24:45 +01:00
Julius Härtl
596834853b Fix codestyle issues
Signed-off-by: Julius Härtl <jus@bitgrid.net>
2021-12-29 11:43:57 +01:00
dependabot[bot]
eff3c94c6a Bump nextcloud/coding-standard from 0.5.0 to 1.0.0
Bumps [nextcloud/coding-standard](https://github.com/nextcloud/coding-standard) from 0.5.0 to 1.0.0.
- [Release notes](https://github.com/nextcloud/coding-standard/releases)
- [Changelog](https://github.com/nextcloud/coding-standard/blob/master/CHANGELOG.md)
- [Commits](https://github.com/nextcloud/coding-standard/compare/v0.5.0...v1.0.0)

---
updated-dependencies:
- dependency-name: nextcloud/coding-standard
  dependency-type: direct:development
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
2021-12-29 11:43:56 +01:00
Julius Härtl
8aa3edee58 Merge pull request #3429 from Themanwhosmellslikesugar/rework/store-card-details-in-modal 2021-12-29 10:47:49 +01:00
Julius Härtl
d3404c7489 Merge pull request #3495 from nextcloud/dependabot/npm_and_yarn/master/markdown-it-link-attributes-4.0.0
Bump markdown-it-link-attributes from 3.0.0 to 4.0.0
2021-12-28 22:12:21 +01:00
dependabot[bot]
84bcd2e502 Bump markdown-it-link-attributes from 3.0.0 to 4.0.0
Bumps [markdown-it-link-attributes](https://github.com/crookedneighbor/markdown-it-link-attributes) from 3.0.0 to 4.0.0.
- [Release notes](https://github.com/crookedneighbor/markdown-it-link-attributes/releases)
- [Changelog](https://github.com/crookedneighbor/markdown-it-link-attributes/blob/main/CHANGELOG.md)
- [Commits](https://github.com/crookedneighbor/markdown-it-link-attributes/compare/v3.0.0...v4.0.0)

---
updated-dependencies:
- dependency-name: markdown-it-link-attributes
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
2021-12-28 18:07:35 +00:00
Julius Härtl
f645937b10 Merge pull request #3498 from nextcloud/bugfix/noid/composer
Update composer config
2021-12-28 19:06:32 +01:00
Julius Härtl
d9af04121d Update composer config
Signed-off-by: Julius Härtl <jus@bitgrid.net>
2021-12-28 14:45:37 +01:00
Dmitriy Ivanko
51bcbdb87d Store cardDetailsInModal in config instead of state
Signed-off-by: Dmitriy Ivanko <tmwsls12@gmail.com>
2021-12-28 14:39:25 +01:00
Julius Härtl
25dee609b5 Merge pull request #3479 from nextcloud/dependabot/npm_and_yarn/master/nextcloud/vue-4.3.0 2021-12-27 10:22:02 +01:00
Julius Härtl
14ab4597c5 Merge pull request #3481 from nextcloud/dependabot/npm_and_yarn/master/markdown-it-12.3.0 2021-12-27 10:21:55 +01:00
Julius Härtl
d3146b4019 Merge pull request #3491 from nextcloud/dependabot/composer/vimeo/psalm-4.15.0 2021-12-27 10:21:48 +01:00
dependabot[bot]
4b02be2028 Bump @nextcloud/vue from 4.2.0 to 4.3.0
Bumps [@nextcloud/vue](https://github.com/nextcloud/nextcloud-vue) from 4.2.0 to 4.3.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.3.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>
2021-12-25 03:26:42 +00:00
dependabot[bot]
476d607148 Bump markdown-it from 12.2.0 to 12.3.0
Bumps [markdown-it](https://github.com/markdown-it/markdown-it) from 12.2.0 to 12.3.0.
- [Release notes](https://github.com/markdown-it/markdown-it/releases)
- [Changelog](https://github.com/markdown-it/markdown-it/blob/master/CHANGELOG.md)
- [Commits](https://github.com/markdown-it/markdown-it/compare/12.2.0...12.3.0)

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

Signed-off-by: dependabot[bot] <support@github.com>
2021-12-25 03:26:40 +00:00
dependabot[bot]
99f17823ec Bump vimeo/psalm from 4.11.2 to 4.15.0
Bumps [vimeo/psalm](https://github.com/vimeo/psalm) from 4.11.2 to 4.15.0.
- [Release notes](https://github.com/vimeo/psalm/releases)
- [Commits](https://github.com/vimeo/psalm/compare/4.11.2...v4.15.0)

---
updated-dependencies:
- dependency-name: vimeo/psalm
  dependency-type: direct:development
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2021-12-25 03:26:39 +00:00
dependabot[bot]
ad537162c8 Merge pull request #3496 from nextcloud/dependabot/npm_and_yarn/master/nextcloud/eslint-config-6.1.2 2021-12-25 03:25:45 +00:00
dependabot[bot]
3a243b1fc7 Bump @nextcloud/eslint-config from 6.1.1 to 6.1.2
Bumps [@nextcloud/eslint-config](https://github.com/nextcloud/eslint-config) from 6.1.1 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.1...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>
2021-12-25 02:03:08 +00:00
Nextcloud bot
bfcbe0306e [tx-robot] updated from transifex
Signed-off-by: Nextcloud bot <bot@nextcloud.com>
2021-12-24 02:39:51 +00:00
Nextcloud bot
d060a842b4 [tx-robot] updated from transifex
Signed-off-by: Nextcloud bot <bot@nextcloud.com>
2021-12-22 02:38:51 +00:00
Nextcloud bot
90a3339e18 [tx-robot] updated from transifex
Signed-off-by: Nextcloud bot <bot@nextcloud.com>
2021-12-20 02:40:43 +00:00
dependabot[bot]
90287606c1 Merge pull request #3492 from nextcloud/dependabot/npm_and_yarn/master/babel/runtime-7.16.5 2021-12-18 03:41:10 +00:00
dependabot[bot]
f65b3801cc Bump @babel/runtime from 7.16.3 to 7.16.5
Bumps [@babel/runtime](https://github.com/babel/babel/tree/HEAD/packages/babel-runtime) from 7.16.3 to 7.16.5.
- [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.16.5/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>
2021-12-18 03:14:37 +00:00
Nextcloud bot
b4f35bf2fd [tx-robot] updated from transifex
Signed-off-by: Nextcloud bot <bot@nextcloud.com>
2021-12-18 02:37:53 +00:00
Nextcloud bot
a857c63b35 [tx-robot] updated from transifex
Signed-off-by: Nextcloud bot <bot@nextcloud.com>
2021-12-17 02:39:59 +00:00
dependabot[bot]
5802a31e93 Merge pull request #3488 from nextcloud/dependabot/npm_and_yarn/master/jest-27.4.5 2021-12-16 11:03:32 +00:00
dependabot[bot]
d459995df3 Bump jest from 27.4.4 to 27.4.5
Bumps [jest](https://github.com/facebook/jest) from 27.4.4 to 27.4.5.
- [Release notes](https://github.com/facebook/jest/releases)
- [Changelog](https://github.com/facebook/jest/blob/main/CHANGELOG.md)
- [Commits](https://github.com/facebook/jest/compare/v27.4.4...v27.4.5)

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

Signed-off-by: dependabot[bot] <support@github.com>
2021-12-16 09:31:32 +00:00
Julius Härtl
5be01e60fb Merge pull request #3480 from nextcloud/dependabot/npm_and_yarn/master/jest-27.4.4 2021-12-16 10:30:34 +01:00
Julius Härtl
8443549d74 Merge pull request #3470 from nextcloud/dependabot/github_actions/actions/setup-node-2.5.0 2021-12-16 10:30:15 +01:00
Julius Härtl
23e532a9c2 Merge pull request #3469 from nextcloud/dependabot/npm_and_yarn/master/nextcloud/webpack-vue-config-4.2.0 2021-12-16 10:29:55 +01:00
Julius Härtl
2a41d98c6f Merge pull request #3471 from nextcloud/dependabot/npm_and_yarn/master/jest-27.4.3 2021-12-16 10:29:44 +01:00
Julius Härtl
4c4b8f3bed Merge pull request #3487 from nextcloud/Valdnet-patch-1 2021-12-16 08:55:16 +01:00
Valdnet
1806f0817b l10n: Change to a capital letter
Signed-off-by: Valdnet <47037905+Valdnet@users.noreply.github.com>
2021-12-16 08:42:34 +01:00
Nextcloud bot
e855ef3414 [tx-robot] updated from transifex
Signed-off-by: Nextcloud bot <bot@nextcloud.com>
2021-12-16 02:38:14 +00:00
Nextcloud bot
82232e8890 [tx-robot] updated from transifex
Signed-off-by: Nextcloud bot <bot@nextcloud.com>
2021-12-12 02:38:54 +00:00
dependabot[bot]
99c880df18 Bump jest from 27.3.1 to 27.4.3
Bumps [jest](https://github.com/facebook/jest) from 27.3.1 to 27.4.3.
- [Release notes](https://github.com/facebook/jest/releases)
- [Changelog](https://github.com/facebook/jest/blob/main/CHANGELOG.md)
- [Commits](https://github.com/facebook/jest/compare/v27.3.1...v27.4.3)

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

Signed-off-by: dependabot[bot] <support@github.com>
2021-12-11 05:21:52 +00:00
dependabot[bot]
144ca0d39d Bump @nextcloud/webpack-vue-config from 4.1.4 to 4.2.0
Bumps [@nextcloud/webpack-vue-config](https://github.com/nextcloud/webpack-vue-config) from 4.1.4 to 4.2.0.
- [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.4...v4.2.0)

---
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>
2021-12-11 05:21:17 +00:00
dependabot[bot]
71fbdfeba5 Bump jest from 27.3.1 to 27.4.4
Bumps [jest](https://github.com/facebook/jest) from 27.3.1 to 27.4.4.
- [Release notes](https://github.com/facebook/jest/releases)
- [Changelog](https://github.com/facebook/jest/blob/main/CHANGELOG.md)
- [Commits](https://github.com/facebook/jest/compare/v27.3.1...v27.4.4)

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

Signed-off-by: dependabot[bot] <support@github.com>
2021-12-11 05:21:08 +00:00
dependabot[bot]
101995598b Bump actions/setup-node from 2.4.1 to 2.5.0
Bumps [actions/setup-node](https://github.com/actions/setup-node) from 2.4.1 to 2.5.0.
- [Release notes](https://github.com/actions/setup-node/releases)
- [Commits](https://github.com/actions/setup-node/compare/v2.4.1...v2.5.0)

---
updated-dependencies:
- dependency-name: actions/setup-node
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2021-12-11 05:20:48 +00:00
dependabot[bot]
f5c35729ca Merge pull request #3482 from nextcloud/dependabot/npm_and_yarn/master/dompurify-2.3.4 2021-12-11 05:20:04 +00:00
dependabot[bot]
fc1983869b Bump dompurify from 2.3.3 to 2.3.4
Bumps [dompurify](https://github.com/cure53/DOMPurify) from 2.3.3 to 2.3.4.
- [Release notes](https://github.com/cure53/DOMPurify/releases)
- [Commits](https://github.com/cure53/DOMPurify/compare/2.3.3...2.3.4)

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

Signed-off-by: dependabot[bot] <support@github.com>
2021-12-11 04:55:03 +00:00
Nextcloud bot
081b5119f5 [tx-robot] updated from transifex
Signed-off-by: Nextcloud bot <bot@nextcloud.com>
2021-12-09 02:40:03 +00:00
Nextcloud bot
a22e5f7719 [tx-robot] updated from transifex
Signed-off-by: Nextcloud bot <bot@nextcloud.com>
2021-12-05 02:43:12 +00:00
dependabot[bot]
8ef118ad0f Merge pull request #3468 from nextcloud/dependabot/npm_and_yarn/master/nextcloud/eslint-config-6.1.1 2021-12-04 03:31:28 +00:00
dependabot[bot]
86945d5030 Bump @nextcloud/eslint-config from 6.1.0 to 6.1.1
Bumps [@nextcloud/eslint-config](https://github.com/nextcloud/eslint-config) from 6.1.0 to 6.1.1.
- [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.1)

---
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>
2021-12-04 02:02:29 +00:00
Nextcloud bot
c63423c25a [tx-robot] updated from transifex
Signed-off-by: Nextcloud bot <bot@nextcloud.com>
2021-12-03 02:40:11 +00:00
Nextcloud bot
8db48106b9 [tx-robot] updated from transifex
Signed-off-by: Nextcloud bot <bot@nextcloud.com>
2021-12-01 17:22:45 +00:00
Julius Härtl
6d201a1f13 Merge pull request #3458 from nextcloud/bugfix/3451 2021-11-30 13:23:40 +01:00
Julius Härtl
5425536fc0 Merge pull request #3459 from nextcloud/bugfix/3457 2021-11-30 13:20:46 +01:00
Julius Härtl
92acaa0011 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 10:57:24 +01:00
Julius Härtl
a5b76991b8 Fix cursor generation if no results are found
Signed-off-by: Julius Härtl <jus@bitgrid.net>
2021-11-29 20:44:42 +01:00
Julius Härtl
33f5af41c8 Merge pull request #3454 from nextcloud/dependabot/composer/symfony/event-dispatcher-4.4.34 2021-11-29 20:38:44 +01:00
Julius Härtl
f5223d90a0 Merge pull request #3456 from nextcloud/dependabot/github_actions/shivammathur/setup-php-2.16.0 2021-11-29 20:38:29 +01:00
Julius Härtl
4a51335a28 Merge pull request #3455 from nextcloud/dependabot/npm_and_yarn/master/nextcloud/axios-1.8.0 2021-11-29 20:38:18 +01:00
Nextcloud bot
e5ffe95c17 [tx-robot] updated from transifex
Signed-off-by: Nextcloud bot <bot@nextcloud.com>
2021-11-27 02:37:08 +00:00
dependabot[bot]
3096b701b6 Bump shivammathur/setup-php from 2.15.0 to 2.16.0
Bumps [shivammathur/setup-php](https://github.com/shivammathur/setup-php) from 2.15.0 to 2.16.0.
- [Release notes](https://github.com/shivammathur/setup-php/releases)
- [Commits](https://github.com/shivammathur/setup-php/compare/2.15.0...2.16.0)

---
updated-dependencies:
- dependency-name: shivammathur/setup-php
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2021-11-27 02:04:50 +00:00
dependabot[bot]
caf2e688f7 Bump @nextcloud/axios from 1.7.0 to 1.8.0
Bumps [@nextcloud/axios](https://github.com/nextcloud/nextcloud-axios) from 1.7.0 to 1.8.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.8.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>
2021-11-27 02:02:57 +00:00
dependabot[bot]
3269845cfd Bump symfony/event-dispatcher from 4.4.30 to 4.4.34
Bumps [symfony/event-dispatcher](https://github.com/symfony/event-dispatcher) from 4.4.30 to 4.4.34.
- [Release notes](https://github.com/symfony/event-dispatcher/releases)
- [Changelog](https://github.com/symfony/event-dispatcher/blob/5.3/CHANGELOG.md)
- [Commits](https://github.com/symfony/event-dispatcher/compare/v4.4.30...v4.4.34)

---
updated-dependencies:
- dependency-name: symfony/event-dispatcher
  dependency-type: direct:development
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2021-11-27 02:01:03 +00:00
Nextcloud bot
5349fcc707 [tx-robot] updated from transifex
Signed-off-by: Nextcloud bot <bot@nextcloud.com>
2021-11-26 02:57:38 +00:00
Nextcloud bot
0f095e9b69 [tx-robot] updated from transifex
Signed-off-by: Nextcloud bot <bot@nextcloud.com>
2021-11-25 02:36:02 +00:00
Julius Härtl
3affa7b5ec Merge pull request #3444 from nextcloud/enh/cache-frequent-queries 2021-11-24 15:06:37 +01:00
Julius Härtl
4ec57d337b Keep API results the same as before
Signed-off-by: Julius Härtl <jus@bitgrid.net>
2021-11-24 10:12:28 +01:00
Julius Härtl
6fd83258a0 Merge pull request #3365 from nextcloud/enh/use-qbmapper-for-boards 2021-11-24 09:40:50 +01:00
Julius Härtl
901b8f2506 Avoid fetching board details multiple times
Signed-off-by: Julius Härtl <jus@bitgrid.net>
2021-11-24 09:40:30 +01:00
Julius Härtl
80388d1a88 Cache card to board id relation
Signed-off-by: Julius Härtl <jus@bitgrid.net>
2021-11-24 09:40:30 +01:00
Nextcloud bot
f90c9602b8 [tx-robot] updated from transifex
Signed-off-by: Nextcloud bot <bot@nextcloud.com>
2021-11-24 02:36:58 +00:00
Julius Härtl
f861f9e5fc Update psalm baseline
Signed-off-by: Julius Härtl <jus@bitgrid.net>
2021-11-23 23:44:50 +01:00
Julien Veyssier
bbfb155802 switch to QBMapper in BoardMapper
Signed-off-by: Julien Veyssier <eneiluj@posteo.net>
2021-11-23 22:57:50 +01:00
Julius Härtl
10ab8c8688 Merge pull request #3428 from nextcloud/bugfix/download-attachment 2021-11-22 18:39:58 +01:00
Julius Härtl
24a6d088ca Merge pull request #3436 from nextcloud/dependabot/npm_and_yarn/master/vue/test-utils-1.3.0 2021-11-22 16:39:02 +01:00
Julius Härtl
00d386dcaf Allow to download an attachment without navigating to the files app
Signed-off-by: Julius Härtl <jus@bitgrid.net>
2021-11-22 16:38:28 +01:00
Nextcloud bot
b71f91c439 [tx-robot] updated from transifex
Signed-off-by: Nextcloud bot <bot@nextcloud.com>
2021-11-20 02:53:14 +00:00
dependabot[bot]
7aa35bb728 Bump @vue/test-utils from 1.2.2 to 1.3.0
Bumps [@vue/test-utils](https://github.com/vuejs/vue-test-utils/tree/HEAD/packages/test-utils) from 1.2.2 to 1.3.0.
- [Release notes](https://github.com/vuejs/vue-test-utils/releases)
- [Changelog](https://github.com/vuejs/vue-test-utils/blob/dev/CHANGELOG.md)
- [Commits](https://github.com/vuejs/vue-test-utils/commits/v1.3.0/packages/test-utils)

---
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>
2021-11-20 02:04:23 +00:00
Julius Härtl
800412237d Merge pull request #3432 from nextcloud/bugfix/noid/description-save 2021-11-19 10:14:25 +01:00
Julius Härtl
ca411c6168 Fix event name for updating the description
Signed-off-by: Julius Härtl <jus@bitgrid.net>
2021-11-19 08:32:40 +01:00
Nextcloud bot
d414ffe937 [tx-robot] updated from transifex
Signed-off-by: Nextcloud bot <bot@nextcloud.com>
2021-11-19 02:53:33 +00:00
Julius Härtl
ad483f3613 Merge pull request #3425 from nextcloud/dependabot/npm_and_yarn/master/babel/runtime-7.16.3
Bump @babel/runtime from 7.16.0 to 7.16.3
2021-11-15 07:21:51 +01:00
Julius Härtl
8311a13275 Merge pull request #3427 from nextcloud/dependabot/npm_and_yarn/master/nextcloud/webpack-vue-config-4.1.4
Bump @nextcloud/webpack-vue-config from 4.1.2 to 4.1.4
2021-11-15 07:21:38 +01:00
Nextcloud bot
e2a7063772 [tx-robot] updated from transifex
Signed-off-by: Nextcloud bot <bot@nextcloud.com>
2021-11-15 02:35:06 +00:00
Nextcloud bot
1dbf36ae07 [tx-robot] updated from transifex
Signed-off-by: Nextcloud bot <bot@nextcloud.com>
2021-11-14 02:36:19 +00:00
Nextcloud bot
29278a51e5 [tx-robot] updated from transifex
Signed-off-by: Nextcloud bot <bot@nextcloud.com>
2021-11-13 02:36:41 +00:00
dependabot[bot]
31e48ce404 Bump @nextcloud/webpack-vue-config from 4.1.2 to 4.1.4
Bumps [@nextcloud/webpack-vue-config](https://github.com/nextcloud/webpack-vue-config) from 4.1.2 to 4.1.4.
- [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.1.4)

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

Signed-off-by: dependabot[bot] <support@github.com>
2021-11-13 02:04:54 +00:00
dependabot[bot]
404a7eb412 Bump @babel/runtime from 7.16.0 to 7.16.3
Bumps [@babel/runtime](https://github.com/babel/babel/tree/HEAD/packages/babel-runtime) from 7.16.0 to 7.16.3.
- [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.16.3/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>
2021-11-13 02:02:03 +00:00
Julius Härtl
a85a6db368 Merge pull request #3420 from nextcloud/feat/package-node-npm-engines-update 2021-11-11 16:12:42 +01:00
Nextcloud bot
9464337036 Update npm and node engines versions
Signed-off-by: Nextcloud bot <bot@nextcloud.com>
2021-11-11 13:36:52 +00:00
Vincent Petry
87676b49dd Merge pull request #3419 from nextcloud/update-master-version
Update version on master
2021-11-11 11:53:23 +01:00
Joas Schilling
6248089d8b Update version on master
Signed-off-by: Joas Schilling <coding@schilljs.com>
2021-11-11 10:51:16 +01:00
74 changed files with 8540 additions and 4829 deletions

View File

@@ -14,13 +14,13 @@ jobs:
steps:
- uses: actions/checkout@v2.4.0
- name: Use Node.js ${{ matrix.node-version }}
uses: actions/setup-node@v2.4.1
uses: actions/setup-node@v2.5.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.15.0
uses: shivammathur/setup-php@2.17.0
with:
php-version: '7.4'
tools: composer

View File

@@ -1,151 +0,0 @@
# This workflow is provided via the organization template repository
#
# https://github.com/nextcloud/.github
# https://docs.github.com/en/actions/learn-github-actions/sharing-workflows-with-your-organization
name: Build and publish app release
on:
release:
types: [published]
env:
PHP_VERSION: 7.4
jobs:
build_and_publish:
runs-on: ubuntu-latest
# Only allowed to be run on nextcloud-releases repositories
if: ${{ github.repository_owner == 'nextcloud-releases' }}
steps:
- name: Check actor permission
uses: skjnldsv/check-actor-permission@v2
with:
require: write
- name: Set app env
run: |
# Split and keep last
echo "APP_NAME=${GITHUB_REPOSITORY##*/}" >> $GITHUB_ENV
echo "APP_VERSION=${GITHUB_REF##*/}" >> $GITHUB_ENV
- name: Checkout
uses: actions/checkout@v3
with:
path: ${{ env.APP_NAME }}
- name: Get appinfo data
id: appinfo
uses: skjnldsv/xpath-action@master
with:
filename: ${{ env.APP_NAME }}/appinfo/info.xml
expression: "//info//dependencies//nextcloud/@min-version"
- name: Read package.json node and npm engines version
uses: skjnldsv/read-package-engines-version-actions@v1.2
id: versions
# Continue if no package.json
continue-on-error: true
with:
path: ${{ env.APP_NAME }}
fallbackNode: "^12"
fallbackNpm: "^6"
- name: Set up node ${{ steps.versions.outputs.nodeVersion }}
# Skip if no package.json
if: ${{ steps.versions.outputs.nodeVersion }}
uses: actions/setup-node@v3
with:
node-version: ${{ steps.versions.outputs.nodeVersion }}
- name: Set up npm ${{ steps.versions.outputs.npmVersion }}
# Skip if no package.json
if: ${{ steps.versions.outputs.npmVersion }}
run: npm i -g npm@"${{ steps.versions.outputs.npmVersion }}"
- name: Set up php ${{ env.PHP_VERSION }}
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@v1
with:
files: "${{ env.APP_NAME }}/composer.json"
- name: Install composer dependencies
if: steps.check_composer.outputs.files_exists == 'true'
run: |
cd ${{ env.APP_NAME }}
composer install --no-dev
- name: Build ${{ env.APP_NAME }}
# Skip if no package.json
if: ${{ steps.versions.outputs.nodeVersion }}
run: |
cd ${{ env.APP_NAME }}
npm ci
npm run build
- name: Install Krankerl
run: |
wget https://github.com/ChristophWurst/krankerl/releases/download/v0.13.0/krankerl_0.13.0_amd64.deb
sudo dpkg -i krankerl_0.13.0_amd64.deb
- name: Package ${{ env.APP_NAME }} ${{ env.APP_VERSION }}
# Try krankerl, fallback to makefile
run: |
cd ${{ env.APP_NAME }}
krankerl package || make appstore
- name: Checkout server ${{ fromJSON(steps.appinfo.outputs.result).nextcloud.min-version }}
continue-on-error: true
id: server-checkout
run: |
NCVERSION=${{ fromJSON(steps.appinfo.outputs.result).nextcloud.min-version }}
wget --quiet https://download.nextcloud.com/server/releases/latest-$NCVERSION.zip
unzip latest-$NCVERSION.zip
- name: Checkout server master fallback
uses: actions/checkout@v3
if: ${{ steps.server-checkout.outcome != 'success' }}
with:
repository: nextcloud/server
path: nextcloud
- name: Sign app
run: |
# Extracting release
cd ${{ env.APP_NAME }}/build/artifacts
tar -xvf ${{ env.APP_NAME }}.tar.gz
cd ../../../
# Setting up keys
echo "${{ secrets.APP_PRIVATE_KEY }}" > ${{ env.APP_NAME }}.key
wget --quiet "https://github.com/nextcloud/app-certificate-requests/raw/master/${{ env.APP_NAME }}/${{ env.APP_NAME }}.crt"
# Signing
php nextcloud/occ integrity:sign-app --privateKey=../${{ env.APP_NAME }}.key --certificate=../${{ env.APP_NAME }}.crt --path=../${{ env.APP_NAME }}/build/artifacts/${{ env.APP_NAME }}
# Rebuilding archive
cd ${{ env.APP_NAME }}/build/artifacts
tar -zcvf ${{ env.APP_NAME }}.tar.gz ${{ env.APP_NAME }}
- name: Attach tarball to github release
uses: svenstaro/upload-release-action@v2
id: attach_to_release
with:
repo_token: ${{ secrets.GITHUB_TOKEN }}
file: ${{ env.APP_NAME }}/build/artifacts/${{ env.APP_NAME }}.tar.gz
asset_name: ${{ env.APP_NAME }}-${{ env.APP_VERSION }}.tar.gz
tag: ${{ github.ref }}
overwrite: true
- name: Upload app to Nextcloud appstore
uses: nextcloud-releases/nextcloud-appstore-push-action@v1
with:
app_name: ${{ env.APP_NAME }}
appstore_token: ${{ secrets.APPSTORE_TOKEN }}
download_url: ${{ steps.attach_to_release.outputs.browser_download_url }}
app_private_key: ${{ secrets.APP_PRIVATE_KEY }}

View File

@@ -25,5 +25,5 @@ jobs:
# Nextcloud bot approve and merge request
- uses: ahmadnassri/action-dependabot-auto-merge@v2
with:
target: patch
target: minor
github-token: ${{ secrets.DEPENDABOT_AUTOMERGE_TOKEN }}

View File

@@ -19,7 +19,7 @@ jobs:
matrix:
php-versions: ['7.4']
databases: ['sqlite', 'mysql', 'pgsql']
server-versions: ['stable23']
server-versions: ['master']
name: php${{ matrix.php-versions }}-${{ matrix.databases }}-${{ matrix.server-versions }}
@@ -61,7 +61,7 @@ jobs:
path: apps/${{ env.APP_NAME }}
- name: Set up php ${{ matrix.php-versions }}
uses: shivammathur/setup-php@2.15.0
uses: shivammathur/setup-php@2.17.0
with:
php-version: ${{ matrix.php-versions }}
tools: phpunit

View File

@@ -13,13 +13,13 @@ jobs:
strategy:
matrix:
php-versions: ['7.2', '7.3', '7.4']
php-versions: ['7.4', '8.0']
name: php${{ matrix.php-versions }} lint
steps:
- uses: actions/checkout@v2.4.0
- name: Set up php${{ matrix.php-versions }}
uses: shivammathur/setup-php@2.15.0
uses: shivammathur/setup-php@2.17.0
with:
php-version: ${{ matrix.php-versions }}
coverage: none
@@ -33,7 +33,7 @@ jobs:
- name: Checkout
uses: actions/checkout@v2.4.0
- name: Set up php
uses: shivammathur/setup-php@2.15.0
uses: shivammathur/setup-php@2.17.0
with:
php-version: 7.4
coverage: none
@@ -52,7 +52,7 @@ jobs:
steps:
- uses: actions/checkout@v2.4.0
- name: Use node ${{ matrix.node-version }}
uses: actions/setup-node@v2.4.1
uses: actions/setup-node@v2.5.1
with:
node-version: ${{ matrix.node-version }}
- name: Set up npm7
@@ -74,7 +74,7 @@ jobs:
- uses: actions/checkout@v2.4.0
- name: Set up node ${{ matrix.node-version }}
uses: actions/setup-node@v2.4.1
uses: actions/setup-node@v2.5.1
with:
node-version: ${{ matrix.node-version }}

View File

@@ -19,13 +19,13 @@ jobs:
steps:
- uses: actions/checkout@v2.4.0
- name: Use Node.js ${{ matrix.node-version }}
uses: actions/setup-node@v2.4.1
uses: actions/setup-node@v2.5.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.15.0
uses: shivammathur/setup-php@2.17.0
with:
php-version: '7.4'
tools: composer

View File

@@ -14,7 +14,7 @@ jobs:
steps:
- uses: actions/checkout@v2.4.0
- name: Use Node.js ${{ matrix.node-version }}
uses: actions/setup-node@v2.4.1
uses: actions/setup-node@v2.5.1
with:
node-version: ${{ matrix.node-version }}
- name: Set up npm7

View File

@@ -18,9 +18,9 @@ jobs:
strategy:
fail-fast: false
matrix:
php-versions: ['7.3', '7.4']
php-versions: ['7.4', '8.0']
databases: ['sqlite', 'mysql', 'pgsql']
server-versions: ['stable23']
server-versions: ['master']
name: php${{ matrix.php-versions }}-${{ matrix.databases }}-${{ matrix.server-versions }}
@@ -62,7 +62,7 @@ jobs:
path: apps/${{ env.APP_NAME }}
- name: Set up php ${{ matrix.php-versions }}
uses: shivammathur/setup-php@2.15.0
uses: shivammathur/setup-php@2.17.0
with:
php-version: ${{ matrix.php-versions }}
tools: phpunit

View File

@@ -12,13 +12,13 @@ jobs:
runs-on: ubuntu-latest
strategy:
matrix:
ocp-version: [ 'dev-stable23' ]
ocp-version: [ 'dev-master' ]
name: Nextcloud ${{ matrix.ocp-version }}
steps:
- name: Checkout
uses: actions/checkout@v2.4.0
- name: Set up php
uses: shivammathur/setup-php@2.15.0
uses: shivammathur/setup-php@2.17.0
with:
php-version: 7.4
tools: composer:v1

View File

@@ -1,26 +1,10 @@
# Changelog
All notable changes to this project will be documented in this file.
## 1.6.1
### Fixed
- 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
## 1.6.0-beta1
### 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
@@ -29,11 +13,6 @@ 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

@@ -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.6.1</version>
<version>1.7.0-alpha1</version>
<licence>agpl</licence>
<author>Julius Härtl</author>
<namespace>Deck</namespace>
@@ -31,11 +31,10 @@
<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="5.5">mysql</database>
<nextcloud min-version="23" max-version="23"/>
<database min-version="8.0">mysql</database>
<nextcloud min-version="24" max-version="24"/>
</dependencies>
<background-jobs>
<job>OCA\Deck\Cron\DeleteCron</job>
@@ -44,6 +43,7 @@
</background-jobs>
<commands>
<command>OCA\Deck\Command\UserExport</command>
<command>OCA\Deck\Command\BoardImport</command>
</commands>
<activity>
<settings>

View File

@@ -25,7 +25,6 @@
return [
'routes' => [
['name' => 'page#index', 'url' => '/', 'verb' => 'GET'],
['name' => 'page#redirectToCard', 'url' => '/card/{cardId}', 'verb' => 'GET'],
// boards
['name' => 'board#index', 'url' => '/boards', 'verb' => 'GET'],
@@ -91,6 +90,10 @@ 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,20 +9,27 @@
}
],
"require": {
"cogpowered/finediff": "0.3.*"
"cogpowered/finediff": "0.3.*",
"justinrainbow/json-schema": "^5.2"
},
"require-dev": {
"roave/security-advisories": "dev-master",
"christophwurst/nextcloud": "^21@dev",
"phpunit/phpunit": "^9",
"nextcloud/coding-standard": "^0.5.0",
"nextcloud/coding-standard": "^1.0.0",
"symfony/event-dispatcher": "^4.0",
"vimeo/psalm": "^4.3",
"php-parallel-lint/php-parallel-lint": "^1.2"
},
"config": {
"optimize-autoloader": true,
"classmap-authoritative": true
"classmap-authoritative": true,
"allow-plugins": {
"composer/package-versions-deprecated": true
},
"platform": {
"php": "7.4"
}
},
"scripts": {
"lint": "find . -name \\*.php -not -path './vendor/*' -print0 | xargs -0 -n1 php -l",

1333
composer.lock generated

File diff suppressed because it is too large Load Diff

View File

@@ -96,10 +96,27 @@ If available the ETag will also be part of JSON response objects as shown below
# Changelog
## 1.0.0 (unreleased)
## 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**.
- 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
@@ -927,7 +944,8 @@ 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 |
For now only `deck_file` is supported as an attachment type.
- 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
#### Response
@@ -988,6 +1006,49 @@ 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/`.
@@ -1004,6 +1065,7 @@ 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) |
| 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)|
```
@@ -1016,6 +1078,7 @@ Deck stores user and app configuration values globally and per board. The GET en
},
"data": {
"calendar": true,
"cardDetailsInModal": true,
"groupLimit": [
{
"id": "admin",
@@ -1045,6 +1108,7 @@ Deck stores user and app configuration values globally and per board. The GET en
| --- | ----- |
| notify-due | `off`, `assigned` or `all` |
| calendar | Boolean |
| cardDetailsInModal | Boolean |
#### Example request

View File

@@ -14,6 +14,8 @@ 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)
### 1. Create my first board
In this example, we're going to create a board and share it with an other nextcloud user.
@@ -69,14 +71,80 @@ 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!
## Search
### 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/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/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
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 |
| ----------- | ----------------- | ------------------------------------------------------------ |

32
docs/implement-import.md Normal file
View File

@@ -0,0 +1,32 @@
## 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

@@ -0,0 +1,7 @@
## 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

@@ -0,0 +1,214 @@
<?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>

After

Width:  |  Height:  |  Size: 16 KiB

View File

@@ -0,0 +1,24 @@
// 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

@@ -35,7 +35,6 @@ use OCP\IConfig;
use OCP\IURLGenerator;
use OCP\IUserManager;
use OCP\L10N\IFactory;
use OCA\Deck\Service\CardService;
class DeckProvider implements IProvider {
@@ -53,10 +52,8 @@ class DeckProvider implements IProvider {
private $l10nFactory;
/** @var IConfig */
private $config;
/** @var CardService */
private $cardService;
public function __construct(IURLGenerator $urlGenerator, ActivityManager $activityManager, IUserManager $userManager, ICommentsManager $commentsManager, IFactory $l10n, IConfig $config, $userId, CardService $cardService) {
public function __construct(IURLGenerator $urlGenerator, ActivityManager $activityManager, IUserManager $userManager, ICommentsManager $commentsManager, IFactory $l10n, IConfig $config, $userId) {
$this->userId = $userId;
$this->urlGenerator = $urlGenerator;
$this->activityManager = $activityManager;
@@ -64,7 +61,6 @@ class DeckProvider implements IProvider {
$this->userManager = $userManager;
$this->l10nFactory = $l10n;
$this->config = $config;
$this->cardService = $cardService;
}
/**
@@ -135,7 +131,7 @@ class DeckProvider implements IProvider {
if (array_key_exists('board', $subjectParams)) {
$archivedParam = $subjectParams['card']['archived'] ? 'archived/' : '';
$card['link'] = $this->cardService->getRedirectUrlForCard($event->getObjectId());
$card['link'] = $this->deckUrl('/board/' . $subjectParams['board']['id'] . '/' . $archivedParam . 'card/' . $event->getObjectId());
}
$params['card'] = $card;
}

View File

@@ -0,0 +1,92 @@
<?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 {
/** @var BoardImportCommandService */
private $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

@@ -0,0 +1,85 @@
<?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

@@ -34,20 +34,12 @@ use OCP\IInitialStateService;
use OCP\IRequest;
use OCP\AppFramework\Http\TemplateResponse;
use OCP\AppFramework\Controller;
use OCA\Deck\Db\CardMapper;
use OCP\IURLGenerator;
use \OCP\AppFramework\Http\RedirectResponse;
use OCA\Deck\Db\Acl;
use OCA\Deck\Service\CardService;
class PageController extends Controller {
private $permissionService;
private $initialState;
private $configService;
private $eventDispatcher;
private $cardMapper;
private $urlGenerator;
private $cardService;
public function __construct(
$AppName,
@@ -55,10 +47,7 @@ class PageController extends Controller {
PermissionService $permissionService,
IInitialStateService $initialStateService,
ConfigService $configService,
IEventDispatcher $eventDispatcher,
CardMapper $cardMapper,
IURLGenerator $urlGenerator,
CardService $cardService
IEventDispatcher $eventDispatcher
) {
parent::__construct($AppName, $request);
@@ -66,9 +55,6 @@ class PageController extends Controller {
$this->initialState = $initialStateService;
$this->configService = $configService;
$this->eventDispatcher = $eventDispatcher;
$this->cardMapper = $cardMapper;
$this->urlGenerator = $urlGenerator;
$this->cardService = $cardService;
}
/**
@@ -99,17 +85,4 @@ class PageController extends Controller {
return $response;
}
/**
* @NoAdminRequired
* @NoCSRFRequired
*/
public function redirectToCard($cardId): RedirectResponse {
try {
$this->permissionService->checkPermission($this->cardMapper, $cardId, Acl::PERMISSION_READ);
return new RedirectResponse($this->cardService->getCardUrl($cardId));
} catch (\Exception $e) {
return new RedirectResponse($this->urlGenerator->linkToRouteAbsolute('deck.page.index'));
}
}
}

View File

@@ -24,7 +24,7 @@
namespace OCA\Deck\Cron;
use OC\BackgroundJob\Job;
use OCP\BackgroundJob\Job;
use OCA\Deck\Activity\ActivityManager;
use OCA\Deck\Db\CardMapper;

View File

@@ -24,13 +24,14 @@
namespace OCA\Deck\Cron;
use OC\BackgroundJob\Job;
use OCP\BackgroundJob\TimedJob;
use OCA\Deck\Db\AttachmentMapper;
use OCA\Deck\Db\BoardMapper;
use OCA\Deck\InvalidAttachmentType;
use OCA\Deck\Service\AttachmentService;
use OCP\BackgroundJob\IJob;
class DeleteCron extends Job {
class DeleteCron extends TimedJob {
/** @var BoardMapper */
private $boardMapper;
@@ -43,6 +44,9 @@ class DeleteCron extends Job {
$this->boardMapper = $boardMapper;
$this->attachmentService = $attachmentService;
$this->attachmentMapper = $attachmentMapper;
$this->setInterval(60 * 60 * 24);
$this->setTimeSensitivity(IJob::TIME_INSENSITIVE);
}
/**

View File

@@ -23,7 +23,7 @@
namespace OCA\Deck\Cron;
use OC\BackgroundJob\Job;
use OCP\BackgroundJob\Job;
use OCA\Deck\Db\Card;
use OCA\Deck\Db\CardMapper;
use OCA\Deck\Notification\NotificationHelper;

View File

@@ -25,23 +25,46 @@ namespace OCA\Deck\Db;
use OCP\AppFramework\Db\DoesNotExistException;
use OCP\AppFramework\Db\MultipleObjectsReturnedException;
use OCP\AppFramework\Db\QBMapper;
use OCP\IDBConnection;
class AclMapper extends DeckMapper implements IPermissionMapper {
class AclMapper extends QBMapper implements IPermissionMapper {
public function __construct(IDBConnection $db) {
parent::__construct($db, 'deck_board_acl', Acl::class);
}
public function find($id): Acl {
$query = $this->db->getQueryBuilder();
$query->select('*')
->from($this->getTableName())
->where($query->expr()->eq('id', $query->createNamedParameter($id)));
return $this->findEntity($query);
}
public function findAll($boardId, $limit = null, $offset = null) {
$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);
$query = $this->db->getQueryBuilder();
$query->select('*')
->from($this->getTableName())
->where($query->expr()->eq('board_id', $query->createNamedParameter($boardId)))
->setMaxResults($limit)
->setFirstResult($offset);
return $this->findEntities($query);
}
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);
$query = $this->db->getQueryBuilder();
$query->select('owner')
->from('deck_boards', 'b')
->innerJoin('b', $this->getTableName(), 'a', $query->expr()->eq('b.id', 'a.board_id'))
->where($query->expr()->eq('a.id', $query->createNamedParameter($aclId)));
$cursor = $query->execute();
$row = $cursor->fetch();
$cursor->closeCursor();
return is_array($row) && $row['owner'] === $userId;
}
public function findBoardId($id): ?int {
@@ -53,8 +76,13 @@ class AclMapper extends DeckMapper implements IPermissionMapper {
return null;
}
public function findByParticipant($type, $participant): array {
$sql = 'SELECT * from *PREFIX*deck_board_acl WHERE type = ? AND participant = ?';
return $this->findEntities($sql, [$type, $participant]);
public function findByParticipant(int $type, string $participant): array {
$query = $this->db->getQueryBuilder();
$query->select('*')
->from($this->getTableName())
->where($query->expr()->eq('type', $query->createNamedParameter($type)))
->andWhere($query->expr()->eq('type', $query->createNamedParameter($participant)));
return $this->findEntities($query);
}
}

View File

@@ -265,7 +265,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)))
@@ -276,7 +276,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'));

View File

@@ -0,0 +1,44 @@
<?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

@@ -0,0 +1,29 @@
<?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

@@ -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

@@ -50,7 +50,6 @@ use OCA\Deck\Db\BoardMapper;
use OCA\Deck\Db\LabelMapper;
use OCP\IUserManager;
use OCA\Deck\BadRequestException;
use OCP\IURLGenerator;
class BoardService {
private $boardMapper;
@@ -69,8 +68,8 @@ class BoardService {
private $activityManager;
private $eventDispatcher;
private $changeHelper;
private $boardsCache = null;
private $urlGenerator;
public function __construct(
@@ -88,7 +87,6 @@ class BoardService {
ActivityManager $activityManager,
IEventDispatcher $eventDispatcher,
ChangeHelper $changeHelper,
IURLGenerator $urlGenerator,
$userId
) {
$this->boardMapper = $boardMapper;
@@ -106,7 +104,6 @@ class BoardService {
$this->eventDispatcher = $eventDispatcher;
$this->changeHelper = $changeHelper;
$this->userId = $userId;
$this->urlGenerator = $urlGenerator;
}
/**
@@ -700,8 +697,4 @@ class BoardService {
}
$board->setUsers(array_values($boardUsers));
}
public function getBoardUrl($endpoint) {
return $this->urlGenerator->linkToRouteAbsolute('deck.page.index') . '#' . $endpoint;
}
}

View File

@@ -46,7 +46,6 @@ use OCA\Deck\BadRequestException;
use OCP\Comments\ICommentsManager;
use OCP\EventDispatcher\IEventDispatcher;
use OCP\IUserManager;
use OCP\IURLGenerator;
class CardService {
private $cardMapper;
@@ -64,7 +63,6 @@ class CardService {
private $changeHelper;
private $eventDispatcher;
private $userManager;
private $urlGenerator;
public function __construct(
CardMapper $cardMapper,
@@ -81,7 +79,6 @@ class CardService {
IUserManager $userManager,
ChangeHelper $changeHelper,
IEventDispatcher $eventDispatcher,
IURLGenerator $urlGenerator,
$userId
) {
$this->cardMapper = $cardMapper;
@@ -99,7 +96,6 @@ class CardService {
$this->changeHelper = $changeHelper;
$this->eventDispatcher = $eventDispatcher;
$this->currentUser = $userId;
$this->urlGenerator = $urlGenerator;
}
public function enrich($card) {
@@ -606,14 +602,4 @@ class CardService {
$this->eventDispatcher->dispatchTyped(new CardUpdatedEvent($card));
}
public function getCardUrl($cardId) {
$boardId = $this->cardMapper->findBoardId($cardId);
return $this->urlGenerator->linkToRouteAbsolute('deck.page.index') . "#/board/$boardId/card/$cardId";
}
public function getRedirectUrlForCard($cardId) {
return $this->urlGenerator->linkToRouteAbsolute('deck.page.index') . "card/$cardId";
}
}

View File

@@ -66,7 +66,8 @@ class ConfigService {
}
$data = [
'calendar' => $this->isCalendarEnabled()
'calendar' => $this->isCalendarEnabled(),
'cardDetailsInModal' => $this->isCardDetailsInModal(),
];
if ($this->groupManager->isAdmin($this->getUserId())) {
$data['groupLimit'] = $this->get('groupLimit');
@@ -88,6 +89,11 @@ 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);
}
}
@@ -96,7 +102,8 @@ class ConfigService {
return false;
}
$defaultState = (bool)$this->config->getUserValue($this->getUserId(), Application::APP_ID, 'calendar', true);
$appConfigState = $this->config->getAppValue(Application::APP_ID, 'calendar', 'yes') === 'yes';
$defaultState = (bool)$this->config->getUserValue($this->getUserId(), Application::APP_ID, 'calendar', $appConfigState);
if ($boardId === null) {
return $defaultState;
}
@@ -104,6 +111,19 @@ 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 set($key, $value) {
if ($this->getUserId() === null) {
throw new NoPermissionException('Must be logged in to set user config');
@@ -122,6 +142,10 @@ 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 '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)) {

View File

@@ -86,7 +86,7 @@ class FileService implements IAttachmentService {
* @return ISimpleFolder
* @throws NotPermittedException
*/
private function getFolder(Attachment $attachment) {
public function getFolder(Attachment $attachment) {
$folderName = 'file-card-' . (int)$attachment->getCardId();
try {
$folder = $this->appData->getFolder($folderName);

View File

@@ -0,0 +1,136 @@
<?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\Service\Importer;
use OCA\Deck\Db\Acl;
use OCA\Deck\Db\Assignment;
use OCA\Deck\Db\Board;
use OCA\Deck\Db\Card;
use OCA\Deck\Db\Label;
use OCA\Deck\Db\Stack;
use OCP\AppFramework\Db\Entity;
use OCP\Comments\IComment;
abstract class ABoardImportService {
/** @var string */
public static $name = '';
/** @var BoardImportService */
private $boardImportService;
/** @var bool */
protected $needValidateData = true;
/** @var Stack[] */
protected $stacks = [];
/** @var Label[] */
protected $labels = [];
/** @var Card[] */
protected $cards = [];
/** @var Acl[] */
protected $acls = [];
/** @var IComment[][] */
protected $comments = [];
/** @var Assignment[] */
protected $assignments = [];
/** @var string[][] */
protected $labelCardAssignments = [];
/**
* Configure import service
*
* @return void
*/
abstract public function bootstrap(): void;
abstract public function getBoard(): ?Board;
/**
* @return Acl[]
*/
abstract public function getAclList(): array;
/**
* @return Stack[]
*/
abstract public function getStacks(): array;
/**
* @return Card[]
*/
abstract public function getCards(): array;
abstract public function getCardAssignments(): array;
abstract public function getCardLabelAssignment(): array;
/**
* @return IComment[][]|array
*/
abstract public function getComments(): array;
/** @return Label[] */
abstract public function getLabels(): array;
abstract public function validateUsers(): void;
abstract public function getJsonSchemaPath(): string;
public function updateStack(string $id, Stack $stack): void {
$this->stacks[$id] = $stack;
}
public function updateCard(string $id, Card $card): void {
$this->cards[$id] = $card;
}
public function updateLabel(string $code, Label $label): void {
$this->labels[$code] = $label;
}
public function updateAcl(string $code, Acl $acl): void {
$this->acls[$code] = $acl;
}
public function updateComment(string $cardId, string $commentId, IComment $comment): void {
$this->comments[$cardId][$commentId] = $comment;
}
public function updateCardAssignment(string $cardId, string $assignmentId, Entity $assignment): void {
$this->assignments[$cardId][$assignmentId] = $assignment;
}
public function updateCardLabelsAssignment(string $cardId, string $assignmentId, string $assignment): void {
$this->labelCardAssignments[$cardId][$assignmentId] = $assignment;
}
public function setImportService(BoardImportService $service): void {
$this->boardImportService = $service;
}
public function getImportService(): BoardImportService {
return $this->boardImportService;
}
public function needValidateData(): bool {
return $this->needValidateData;
}
}

View File

@@ -0,0 +1,199 @@
<?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\Service\Importer;
use OCA\Deck\Exceptions\ConflictException;
use OCA\Deck\NotFoundException;
use Symfony\Component\Console\Command\Command;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Output\OutputInterface;
use Symfony\Component\Console\Question\ChoiceQuestion;
use Symfony\Component\Console\Question\Question;
class BoardImportCommandService extends BoardImportService {
/**
* @var Command
* @psalm-suppress PropertyNotSetInConstructor
*/
private $command;
/**
* @var InputInterface
* @psalm-suppress PropertyNotSetInConstructor
*/
private $input;
/**
* @var OutputInterface
* @psalm-suppress PropertyNotSetInConstructor
*/
private $output;
public function setCommand(Command $command): self {
$this->command = $command;
return $this;
}
public function getCommand(): Command {
return $this->command;
}
public function setInput(InputInterface $input): self {
$this->input = $input;
return $this;
}
public function getInput(): InputInterface {
return $this->input;
}
public function setOutput(OutputInterface $output): self {
$this->output = $output;
return $this;
}
public function getOutput(): OutputInterface {
return $this->output;
}
protected function validateConfig(): void {
try {
$config = $this->getInput()->getOption('config');
if (is_string($config)) {
if (!is_file($config)) {
throw new NotFoundException('It\'s not a valid config file.');
}
$config = json_decode(file_get_contents($config));
if (!$config instanceof \stdClass) {
throw new NotFoundException('Failed to parse JSON.');
}
$this->setConfigInstance($config);
}
parent::validateConfig();
return;
} catch (NotFoundException $e) {
$this->getOutput()->writeln('<error>' . $e->getMessage() . '</error>');
$helper = $this->getCommand()->getHelper('question');
$question = new Question(
"<info>You can get more info on https://deck.readthedocs.io/en/latest/User_documentation_en/#6-import-boards</info>\n" .
'Please inform a valid config json file: ',
'config.json'
);
$question->setValidator(function (string $answer) {
if (!is_file($answer)) {
throw new \RuntimeException(
'config file not found'
);
}
return $answer;
});
$configFile = $helper->ask($this->getInput(), $this->getOutput(), $question);
$this->getInput()->setOption('config', $configFile);
} catch (ConflictException $e) {
$this->getOutput()->writeln('<error>Invalid config file</error>');
$this->getOutput()->writeln(array_map(function (array $v): string {
return $v['message'];
}, $e->getData()));
$this->getOutput()->writeln('Valid schema:');
$this->getOutput()->writeln(print_r(file_get_contents($this->getJsonSchemaPath()), true));
$this->getInput()->setOption('config', '');
}
$this->validateConfig();
}
public function validateSystem(): void {
try {
parent::validateSystem();
return;
} catch (\Throwable $th) {
}
$helper = $this->getCommand()->getHelper('question');
$allowedSystems = $this->getAllowedImportSystems();
$names = array_column($allowedSystems, 'name');
$question = new ChoiceQuestion(
'Please inform a source system',
$names,
0
);
$question->setErrorMessage('System %s is invalid.');
$selectedName = $helper->ask($this->getInput(), $this->getOutput(), $question);
$className = $allowedSystems[array_flip($names)[$selectedName]]['internalName'];
$this->setSystem($className);
return;
}
protected function validateData(): void {
if (!$this->getImportSystem()->needValidateData()) {
return;
}
$data = $this->getInput()->getOption('data');
if (is_string($data)) {
$data = json_decode(file_get_contents($data));
if ($data instanceof \stdClass) {
$this->setData($data);
return;
}
}
$helper = $this->getCommand()->getHelper('question');
$question = new Question(
'Please provide a valid data json file: ',
'data.json'
);
$question->setValidator(function (string $answer) {
if (!is_file($answer)) {
throw new \RuntimeException(
'Data file not found'
);
}
return $answer;
});
$data = $helper->ask($this->getInput(), $this->getOutput(), $question);
$this->getInput()->setOption('data', $data);
$this->validateData();
}
public function bootstrap(): void {
$this->setSystem($this->getInput()->getOption('system'));
parent::bootstrap();
}
public function import(): void {
$this->getOutput()->writeln('Starting import...');
$this->bootstrap();
$this->getOutput()->writeln('Importing board...');
$this->importBoard();
$this->getOutput()->writeln('Assign users to board...');
$this->importAcl();
$this->getOutput()->writeln('Importing labels...');
$this->importLabels();
$this->getOutput()->writeln('Importing stacks...');
$this->importStacks();
$this->getOutput()->writeln('Importing cards...');
$this->importCards();
$this->getOutput()->writeln('Assign cards to labels...');
$this->assignCardsToLabels();
$this->getOutput()->writeln('Importing comments...');
$this->importComments();
$this->getOutput()->writeln('Importing participants...');
$this->importCardAssignments();
}
}

View File

@@ -0,0 +1,449 @@
<?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\Service\Importer;
use JsonSchema\Constraints\Constraint;
use JsonSchema\Validator;
use OCA\Deck\AppInfo\Application;
use OCA\Deck\BadRequestException;
use OCA\Deck\Db\AclMapper;
use OCA\Deck\Db\AssignmentMapper;
use OCA\Deck\Db\Attachment;
use OCA\Deck\Db\AttachmentMapper;
use OCA\Deck\Db\Board;
use OCA\Deck\Db\BoardMapper;
use OCA\Deck\Db\CardMapper;
use OCA\Deck\Db\LabelMapper;
use OCA\Deck\Db\StackMapper;
use OCA\Deck\Event\BoardImportGetAllowedEvent;
use OCA\Deck\Exceptions\ConflictException;
use OCA\Deck\NotFoundException;
use OCA\Deck\Service\FileService;
use OCA\Deck\Service\Importer\Systems\TrelloApiService;
use OCA\Deck\Service\Importer\Systems\TrelloJsonService;
use OCP\Comments\IComment;
use OCP\Comments\ICommentsManager;
use OCP\Comments\NotFoundException as CommentNotFoundException;
use OCP\EventDispatcher\IEventDispatcher;
use OCP\IUserManager;
class BoardImportService {
/** @var IUserManager */
private $userManager;
/** @var BoardMapper */
private $boardMapper;
/** @var AclMapper */
private $aclMapper;
/** @var LabelMapper */
private $labelMapper;
/** @var StackMapper */
private $stackMapper;
/** @var CardMapper */
private $cardMapper;
/** @var AssignmentMapper */
private $assignmentMapper;
/** @var AttachmentMapper */
private $attachmentMapper;
/** @var ICommentsManager */
private $commentsManager;
/** @var IEventDispatcher */
private $eventDispatcher;
/** @var string */
private $system = '';
/** @var null|ABoardImportService */
private $systemInstance;
/** @var array */
private $allowedSystems = [];
/**
* Data object created from config JSON
*
* @var \stdClass
* @psalm-suppress PropertyNotSetInConstructor
*/
public $config;
/**
* Data object created from JSON of origin system
*
* @var \stdClass
* @psalm-suppress PropertyNotSetInConstructor
*/
private $data;
/**
* @var Board
*/
private $board;
public function __construct(
IUserManager $userManager,
BoardMapper $boardMapper,
AclMapper $aclMapper,
LabelMapper $labelMapper,
StackMapper $stackMapper,
AssignmentMapper $assignmentMapper,
AttachmentMapper $attachmentMapper,
CardMapper $cardMapper,
ICommentsManager $commentsManager,
IEventDispatcher $eventDispatcher
) {
$this->userManager = $userManager;
$this->boardMapper = $boardMapper;
$this->aclMapper = $aclMapper;
$this->labelMapper = $labelMapper;
$this->stackMapper = $stackMapper;
$this->cardMapper = $cardMapper;
$this->assignmentMapper = $assignmentMapper;
$this->attachmentMapper = $attachmentMapper;
$this->commentsManager = $commentsManager;
$this->eventDispatcher = $eventDispatcher;
$this->board = new Board();
$this->disableCommentsEvents();
}
private function disableCommentsEvents(): void {
if (defined('PHPUNIT_RUN')) {
return;
}
$propertyEventHandlers = new \ReflectionProperty($this->commentsManager, 'eventHandlers');
$propertyEventHandlers->setAccessible(true);
$propertyEventHandlers->setValue($this->commentsManager, []);
$propertyEventHandlerClosures = new \ReflectionProperty($this->commentsManager, 'eventHandlerClosures');
$propertyEventHandlerClosures->setAccessible(true);
$propertyEventHandlerClosures->setValue($this->commentsManager, []);
}
public function import(): void {
$this->bootstrap();
try {
$this->importBoard();
$this->importAcl();
$this->importLabels();
$this->importStacks();
$this->importCards();
$this->assignCardsToLabels();
$this->importComments();
$this->importCardAssignments();
} catch (\Throwable $th) {
throw new BadRequestException($th->getMessage());
}
}
public function validateSystem(): void {
$allowedSystems = $this->getAllowedImportSystems();
$allowedSystems = array_column($allowedSystems, 'internalName');
if (!in_array($this->getSystem(), $allowedSystems)) {
throw new NotFoundException('Invalid system');
}
}
/**
* @param mixed $system
* @return self
*/
public function setSystem($system): self {
$this->system = $system;
return $this;
}
public function getSystem(): string {
return $this->system;
}
public function addAllowedImportSystem($system): self {
$this->allowedSystems[] = $system;
return $this;
}
public function getAllowedImportSystems(): array {
if (!$this->allowedSystems) {
$this->addAllowedImportSystem([
'name' => TrelloApiService::$name,
'class' => TrelloApiService::class,
'internalName' => 'TrelloApi'
]);
$this->addAllowedImportSystem([
'name' => TrelloJsonService::$name,
'class' => TrelloJsonService::class,
'internalName' => 'TrelloJson'
]);
}
$this->eventDispatcher->dispatchTyped(new BoardImportGetAllowedEvent($this));
return $this->allowedSystems;
}
public function getImportSystem(): ABoardImportService {
if (!$this->getSystem()) {
throw new NotFoundException('System to import not found');
}
if (!is_object($this->systemInstance)) {
$systemClass = 'OCA\\Deck\\Service\\Importer\\Systems\\' . ucfirst($this->getSystem()) . 'Service';
$this->systemInstance = \OC::$server->get($systemClass);
$this->systemInstance->setImportService($this);
}
return $this->systemInstance;
}
public function setImportSystem(ABoardImportService $instance): void {
$this->systemInstance = $instance;
}
public function importBoard(): void {
$board = $this->getImportSystem()->getBoard();
if ($board) {
$this->boardMapper->insert($board);
$this->board = $board;
}
}
public function getBoard(bool $reset = false): Board {
if ($reset) {
$this->board = new Board();
}
return $this->board;
}
public function importAcl(): void {
$aclList = $this->getImportSystem()->getAclList();
foreach ($aclList as $code => $acl) {
$this->aclMapper->insert($acl);
$this->getImportSystem()->updateAcl($code, $acl);
}
$this->getBoard()->setAcl($aclList);
}
public function importLabels(): void {
$labels = $this->getImportSystem()->getLabels();
foreach ($labels as $code => $label) {
$this->labelMapper->insert($label);
$this->getImportSystem()->updateLabel($code, $label);
}
$this->getBoard()->setLabels($labels);
}
public function importStacks(): void {
$stacks = $this->getImportSystem()->getStacks();
foreach ($stacks as $code => $stack) {
$this->stackMapper->insert($stack);
$this->getImportSystem()->updateStack($code, $stack);
}
$this->getBoard()->setStacks(array_values($stacks));
}
public function importCards(): void {
$cards = $this->getImportSystem()->getCards();
foreach ($cards as $code => $card) {
$createdAt = $card->getCreatedAt();
$lastModified = $card->getLastModified();
$this->cardMapper->insert($card);
$updateDate = false;
if ($createdAt && $createdAt !== $card->getCreatedAt()) {
$card->setCreatedAt($createdAt);
$updateDate = true;
}
if ($lastModified && $lastModified !== $card->getLastModified()) {
$card->setLastModified($lastModified);
$updateDate = true;
}
if ($updateDate) {
$this->cardMapper->update($card, false);
}
$this->getImportSystem()->updateCard($code, $card);
}
}
/**
* @param mixed $cardId
* @param mixed $labelId
* @return self
*/
public function assignCardToLabel($cardId, $labelId): self {
$this->cardMapper->assignLabel(
$cardId,
$labelId
);
return $this;
}
public function assignCardsToLabels(): void {
$data = $this->getImportSystem()->getCardLabelAssignment();
foreach ($data as $cardId => $assignemnt) {
foreach ($assignemnt as $assignmentId => $labelId) {
$this->assignCardToLabel(
$cardId,
$labelId
);
$this->getImportSystem()->updateCardLabelsAssignment($cardId, $assignmentId, $labelId);
}
}
}
public function importComments(): void {
$allComments = $this->getImportSystem()->getComments();
foreach ($allComments as $cardId => $comments) {
foreach ($comments as $commentId => $comment) {
$this->insertComment($cardId, $comment);
$this->getImportSystem()->updateComment($cardId, $commentId, $comment);
}
}
}
private function insertComment(string $cardId, IComment $comment): void {
$comment->setObject('deckCard', $cardId);
$comment->setVerb('comment');
// Check if parent is a comment on the same card
if ($comment->getParentId() !== '0') {
try {
$parent = $this->commentsManager->get($comment->getParentId());
if ($parent->getObjectType() !== Application::COMMENT_ENTITY_TYPE || $parent->getObjectId() !== $cardId) {
throw new CommentNotFoundException();
}
} catch (CommentNotFoundException $e) {
throw new BadRequestException('Invalid parent id: The parent comment was not found or belongs to a different card');
}
}
try {
$this->commentsManager->save($comment);
} catch (\InvalidArgumentException $e) {
throw new BadRequestException('Invalid input values');
} catch (CommentNotFoundException $e) {
throw new NotFoundException('Could not create comment.');
}
}
public function importCardAssignments(): void {
$allAssignments = $this->getImportSystem()->getCardAssignments();
foreach ($allAssignments as $cardId => $assignments) {
foreach ($assignments as $assignmentId => $assignment) {
$this->assignmentMapper->insert($assignment);
$this->getImportSystem()->updateCardAssignment($cardId, $assignmentId, $assignment);
}
}
}
public function insertAttachment(Attachment $attachment, string $content): Attachment {
$service = \OC::$server->get(FileService::class);
$folder = $service->getFolder($attachment);
if ($folder->fileExists($attachment->getData())) {
$attachment = $this->attachmentMapper->findByData($attachment->getCardId(), $attachment->getData());
throw new ConflictException('File already exists.', $attachment);
}
$target = $folder->newFile($attachment->getData());
$target->putContent($content);
$attachment = $this->attachmentMapper->insert($attachment);
$service->extendData($attachment);
return $attachment;
}
public function setData(\stdClass $data): void {
$this->data = $data;
}
public function getData(): \stdClass {
return $this->data;
}
/**
* Define a config
*
* @param string $configName
* @param mixed $value
* @return void
*/
public function setConfig(string $configName, $value): void {
if (empty((array) $this->config)) {
$this->setConfigInstance(new \stdClass);
}
$this->config->$configName = $value;
}
/**
* Get a config
*
* @param string $configName config name
* @return mixed
*/
public function getConfig(string $configName) {
if (!property_exists($this->config, $configName)) {
return;
}
return $this->config->$configName;
}
/**
* @param \stdClass $config
* @return self
*/
public function setConfigInstance($config): self {
$this->config = $config;
return $this;
}
public function getConfigInstance(): \stdClass {
return $this->config;
}
protected function validateConfig(): void {
$config = $this->getConfigInstance();
$schemaPath = $this->getJsonSchemaPath();
$validator = new Validator();
$newConfig = clone $config;
$validator->validate(
$newConfig,
(object)['$ref' => 'file://' . realpath($schemaPath)],
Constraint::CHECK_MODE_APPLY_DEFAULTS
);
if (!$validator->isValid()) {
throw new ConflictException('Invalid config file', $validator->getErrors());
}
$this->setConfigInstance($newConfig);
$this->validateOwner();
}
public function getJsonSchemaPath(): string {
return $this->getImportSystem()->getJsonSchemaPath();
}
public function validateOwner(): void {
$owner = $this->userManager->get($this->getConfig('owner'));
if (!$owner) {
throw new \LogicException('Owner "' . $this->getConfig('owner')->getUID() . '" not found on Nextcloud. Check setting json.');
}
$this->setConfig('owner', $owner);
}
protected function validateData(): void {
}
public function bootstrap(): void {
$this->validateSystem();
$this->validateConfig();
$this->validateData();
$this->getImportSystem()->bootstrap();
}
}

View File

@@ -0,0 +1,214 @@
<?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\Service\Importer\Systems;
use OCP\Http\Client\IClient;
use OCP\Http\Client\IClientService;
use OCP\IL10N;
use OCP\IURLGenerator;
use OCP\IUserManager;
use Psr\Log\LoggerInterface;
class TrelloApiService extends TrelloJsonService {
/** @var string */
public static $name = 'Trello API';
protected $needValidateData = false;
/** @var IClient */
private $httpClient;
/** @var LoggerInterface */
protected $logger;
/** @var string */
private $baseApiUrl = 'https://api.trello.com/1';
/** @var ?\stdClass[] */
private $boards;
public function __construct(
IUserManager $userManager,
IURLGenerator $urlGenerator,
IL10N $l10n,
LoggerInterface $logger,
IClientService $httpClientService
) {
parent::__construct($userManager, $urlGenerator, $l10n);
$this->logger = $logger;
$this->httpClient = $httpClientService->newClient();
}
public function bootstrap(): void {
$this->populateBoard();
$this->populateMembers();
$this->populateLabels();
$this->populateLists();
$this->populateCheckLists();
$this->populateCards();
$this->populateActions();
parent::bootstrap();
}
public function getJsonSchemaPath(): string {
return implode(DIRECTORY_SEPARATOR, [
__DIR__,
'..',
'fixtures',
'config-trelloApi-schema.json',
]);
}
private function populateActions(): void {
$data = $this->getImportService()->getData();
$data->actions = $this->doRequest(
'/boards/' . $data->id . '/actions',
[
'filter' => 'commentCard,createCard',
'fields=memberCreator,type,data,date',
'memberCreator_fields' => 'username',
'limit' => 1000
]
);
}
private function populateCards(): void {
$data = $this->getImportService()->getData();
$data->cards = $this->doRequest(
'/boards/' . $data->id . '/cards',
[
'fields' => 'id,idMembers,dateLastActivity,closed,idChecklists,name,idList,pos,desc,due,labels',
'attachments' => true,
'attachment_fields' => 'name,url,date',
'limit' => 1000
]
);
}
private function populateCheckLists(): void {
$data = $this->getImportService()->getData();
$data->checklists = $this->doRequest(
'/boards/' . $data->id . '/checkLists',
[
'fields' => 'id,idCard,name',
'checkItem_fields' => 'id,state,name',
'limit' => 1000
]
);
}
private function populateLists(): void {
$data = $this->getImportService()->getData();
$data->lists = $this->doRequest(
'/boards/' . $data->id . '/lists',
[
'fields' => 'id,name,closed',
'limit' => 1000
]
);
}
private function populateLabels(): void {
$data = $this->getImportService()->getData();
$data->labels = $this->doRequest(
'/boards/' . $data->id . '/labels',
[
'fields' => 'id,color,name',
'limit' => 1000
]
);
}
private function populateMembers(): void {
$data = $this->getImportService()->getData();
$data->members = $this->doRequest(
'/boards/' . $data->id . '/members',
[
'fields' => 'username',
'limit' => 1000
]
);
}
private function populateBoard(): void {
$toImport = $this->getImportService()->getConfig('board');
$board = $this->doRequest(
'/boards/' . $toImport,
['fields' => 'id,name']
);
if ($board instanceof \stdClass) {
$this->getImportService()->setData($board);
return;
}
throw new \Exception('Invalid board id to import');
}
/**
* @return array|\stdClass
*/
private function doRequest(string $path = '', array $queryString = []) {
$target = $this->baseApiUrl . $path;
try {
$result = $this->httpClient
->get($target, $this->getQueryString($queryString))
->getBody();
if (is_string($result)) {
$data = json_decode($result);
if (is_array($data)) {
$data = array_merge(
$data,
$this->paginate($path, $queryString, $data)
);
}
return $data;
}
throw new \Exception('Invalid return of api');
} catch (\Throwable $e) {
$this->logger->critical(
$e->getMessage(),
['app' => 'deck']
);
throw new \Exception($e->getMessage());
}
}
private function paginate(string $path = '', array $queryString = [], array $data = []): array {
if (empty($queryString['limit'])) {
return [];
}
if (count($data) < $queryString['limit']) {
return [];
}
$queryString['before'] = end($data)->id;
$return = $this->doRequest($path, $queryString);
if (is_array($return)) {
return $return;
}
throw new \Exception('Invalid return of api');
}
private function getQueryString(array $params = []): array {
$apiSettings = $this->getImportService()->getConfig('api');
$params['key'] = $apiSettings->key;
$params['token'] = $apiSettings->token;
return [
'query' => $params
];
}
}

View File

@@ -0,0 +1,400 @@
<?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\Service\Importer\Systems;
use OC\Comments\Comment;
use OCA\Deck\BadRequestException;
use OCA\Deck\Db\Acl;
use OCA\Deck\Db\Assignment;
use OCA\Deck\Db\Attachment;
use OCA\Deck\Db\Board;
use OCA\Deck\Db\Card;
use OCA\Deck\Db\Label;
use OCA\Deck\Db\Stack;
use OCA\Deck\Service\Importer\ABoardImportService;
use OCP\Comments\IComment;
use OCP\IL10N;
use OCP\IURLGenerator;
use OCP\IUser;
use OCP\IUserManager;
class TrelloJsonService extends ABoardImportService {
/** @var string */
public static $name = 'Trello JSON';
/** @var IUserManager */
private $userManager;
/** @var IURLGenerator */
private $urlGenerator;
/** @var IL10N */
private $l10n;
/** @var IUser[] */
private $members = [];
public function __construct(
IUserManager $userManager,
IURLGenerator $urlGenerator,
IL10N $l10n
) {
$this->userManager = $userManager;
$this->urlGenerator = $urlGenerator;
$this->l10n = $l10n;
}
public function bootstrap(): void {
$this->validateUsers();
}
public function getJsonSchemaPath(): string {
return implode(DIRECTORY_SEPARATOR, [
__DIR__,
'..',
'fixtures',
'config-trelloJson-schema.json',
]);
}
public function validateUsers(): void {
if (empty($this->getImportService()->getConfig('uidRelation'))) {
return;
}
foreach ($this->getImportService()->getConfig('uidRelation') as $trelloUid => $nextcloudUid) {
$user = array_filter($this->getImportService()->getData()->members, function (\stdClass $u) use ($trelloUid) {
return $u->username === $trelloUid;
});
if (!$user) {
throw new \LogicException('Trello user ' . $trelloUid . ' not found in property "members" of json data');
}
if (!is_string($nextcloudUid) && !is_numeric($nextcloudUid)) {
throw new \LogicException('User on setting uidRelation is invalid');
}
$nextcloudUid = (string) $nextcloudUid;
$this->getImportService()->getConfig('uidRelation')->$trelloUid = $this->userManager->get($nextcloudUid);
if (!$this->getImportService()->getConfig('uidRelation')->$trelloUid) {
throw new \LogicException('User on setting uidRelation not found: ' . $nextcloudUid);
}
$user = current($user);
$this->members[$user->id] = $this->getImportService()->getConfig('uidRelation')->$trelloUid;
}
}
public function getCardAssignments(): array {
$assignments = [];
foreach ($this->getImportService()->getData()->cards as $trelloCard) {
foreach ($trelloCard->idMembers as $idMember) {
if (empty($this->members[$idMember])) {
continue;
}
$assignment = new Assignment();
$assignment->setCardId($this->cards[$trelloCard->id]->getId());
$assignment->setParticipant($this->members[$idMember]->getUID());
$assignment->setType(Assignment::TYPE_USER);
$assignments[$trelloCard->id][] = $assignment;
}
}
return $assignments;
}
public function getComments(): array {
$comments = [];
foreach ($this->getImportService()->getData()->cards as $trelloCard) {
$values = array_filter(
$this->getImportService()->getData()->actions,
function (\stdClass $a) use ($trelloCard) {
return $a->type === 'commentCard' && $a->data->card->id === $trelloCard->id;
}
);
$keys = array_map(function (\stdClass $c): string {
return $c->id;
}, $values);
$trelloComments = array_combine($keys, $values);
$trelloComments = $this->sortComments($trelloComments);
foreach ($trelloComments as $commentId => $trelloComment) {
$cardId = $this->cards[$trelloCard->id]->getId();
$comment = new Comment();
if (!empty($this->getImportService()->getConfig('uidRelation')->{$trelloComment->memberCreator->username})) {
$actor = $this->getImportService()->getConfig('uidRelation')->{$trelloComment->memberCreator->username}->getUID();
} else {
$actor = $this->getImportService()->getConfig('owner')->getUID();
}
$message = $this->replaceUsernames($trelloComment->data->text);
if (mb_strlen($message, 'UTF-8') > IComment::MAX_MESSAGE_LENGTH) {
$attachment = new Attachment();
$attachment->setCardId($cardId);
$attachment->setType('deck_file');
$attachment->setCreatedBy($actor);
$attachment->setLastModified(time());
$attachment->setCreatedAt(time());
$attachment->setData('comment_' . $commentId . '.md');
$attachment = $this->getImportService()->insertAttachment($attachment, $message);
$urlToDownloadAttachment = $this->urlGenerator->linkToRouteAbsolute(
'deck.attachment.display',
[
'cardId' => $cardId,
'attachmentId' => $attachment->getId()
]
);
$message = $this->l10n->t(
"This comment has more than %s characters.\n" .
"Added as an attachment to the card with name %s.\n" .
"Accessible on URL: %s.",
[
IComment::MAX_MESSAGE_LENGTH,
'comment_' . $commentId . '.md',
$urlToDownloadAttachment
]
);
}
$comment
->setActor('users', $actor)
->setMessage($message)
->setCreationDateTime(
\DateTime::createFromFormat('Y-m-d\TH:i:s.v\Z', $trelloComment->date)
);
$comments[$cardId][$commentId] = $comment;
}
}
return $comments;
}
private function sortComments(array $comments): array {
$comparison = function (\stdClass $a, \stdClass $b): int {
if ($a->date == $b->date) {
return 0;
}
return ($a->date < $b->date) ? -1 : 1;
};
usort($comments, $comparison);
return $comments;
}
public function getCardLabelAssignment(): array {
$cardsLabels = [];
foreach ($this->getImportService()->getData()->cards as $trelloCard) {
foreach ($trelloCard->labels as $label) {
$cardId = $this->cards[$trelloCard->id]->getId();
$labelId = $this->labels[$label->id]->getId();
$cardsLabels[$cardId][] = $labelId;
}
}
return $cardsLabels;
}
public function getBoard(): Board {
$board = $this->getImportService()->getBoard();
if (empty($this->getImportService()->getData()->name)) {
throw new BadRequestException('Invalid name of board');
}
$board->setTitle($this->getImportService()->getData()->name);
$board->setOwner($this->getImportService()->getConfig('owner')->getUID());
$board->setColor($this->getImportService()->getConfig('color'));
return $board;
}
/**
* @return Label[]
*/
public function getLabels(): array {
foreach ($this->getImportService()->getData()->labels as $trelloLabel) {
$label = new Label();
if (empty($trelloLabel->name)) {
$label->setTitle('Unnamed ' . $trelloLabel->color . ' label');
} else {
$label->setTitle($trelloLabel->name);
}
$label->setColor($this->translateColor($trelloLabel->color));
$label->setBoardId($this->getImportService()->getBoard()->getId());
$this->labels[$trelloLabel->id] = $label;
}
return $this->labels;
}
/**
* @return Stack[]
*/
public function getStacks(): array {
$return = [];
foreach ($this->getImportService()->getData()->lists as $order => $list) {
$stack = new Stack();
if ($list->closed) {
$stack->setDeletedAt(time());
}
$stack->setTitle($list->name);
$stack->setBoardId($this->getImportService()->getBoard()->getId());
$stack->setOrder($order + 1);
$return[$list->id] = $stack;
}
return $return;
}
/**
* @return Card[]
*/
public function getCards(): array {
$checklists = [];
foreach ($this->getImportService()->getData()->checklists as $checklist) {
$checklists[$checklist->idCard][$checklist->id] = $this->formulateChecklistText($checklist);
}
$this->getImportService()->getData()->checklists = $checklists;
$cards = [];
foreach ($this->getImportService()->getData()->cards as $trelloCard) {
$card = new Card();
$lastModified = \DateTime::createFromFormat('Y-m-d\TH:i:s.v\Z', $trelloCard->dateLastActivity);
$card->setLastModified($lastModified->format('Y-m-d H:i:s'));
if ($trelloCard->closed) {
$card->setArchived(true);
}
if ((count($trelloCard->idChecklists) !== 0)) {
foreach ($this->getImportService()->getData()->checklists[$trelloCard->id] as $checklist) {
$trelloCard->desc .= "\n" . $checklist;
}
}
$this->appendAttachmentsToDescription($trelloCard);
$card->setTitle($trelloCard->name);
$card->setStackId($this->stacks[$trelloCard->idList]->getId());
$cardsOnStack = $this->stacks[$trelloCard->idList]->getCards();
$cardsOnStack[] = $card;
$this->stacks[$trelloCard->idList]->setCards($cardsOnStack);
$card->setType('plain');
$card->setOrder($trelloCard->pos);
$card->setOwner($this->getImportService()->getConfig('owner')->getUID());
$lastModified = \DateTime::createFromFormat('Y-m-d\TH:i:s.v\Z', $trelloCard->dateLastActivity);
$card->setLastModified($lastModified->format('U'));
$createCardDate = array_filter(
$this->getImportService()->getData()->actions,
function (\stdClass $a) use ($trelloCard) {
return $a->type === 'createCard' && $a->data->card->id === $trelloCard->id;
}
);
$createCardDate = current($createCardDate);
$createCardDate = \DateTime::createFromFormat('Y-m-d\TH:i:s.v\Z', $createCardDate->date);
if ($createCardDate) {
$card->setCreatedAt($createCardDate->format('U'));
} else {
$card->setCreatedAt($lastModified->format('U'));
}
$card->setDescription($trelloCard->desc);
if ($trelloCard->due) {
$duedate = \DateTime::createFromFormat('Y-m-d\TH:i:s.v\Z', $trelloCard->due)
->format('Y-m-d H:i:s');
$card->setDuedate($duedate);
}
$cards[$trelloCard->id] = $card;
}
return $cards;
}
/**
* @return Acl[]
*/
public function getAclList(): array {
$return = [];
foreach ($this->members as $member) {
if ($member->getUID() === $this->getImportService()->getConfig('owner')->getUID()) {
continue;
}
$acl = new Acl();
$acl->setBoardId($this->getImportService()->getBoard()->getId());
$acl->setType(Acl::PERMISSION_TYPE_USER);
$acl->setParticipant($member->getUID());
$acl->setPermissionEdit(false);
$acl->setPermissionShare(false);
$acl->setPermissionManage(false);
$return[] = $acl;
}
return $return;
}
private function translateColor(string $color): string {
switch ($color) {
case 'red':
return 'ff0000';
case 'yellow':
return 'ffff00';
case 'orange':
return 'ff6600';
case 'green':
return '00ff00';
case 'purple':
return '9900ff';
case 'blue':
return '0000ff';
case 'sky':
return '00ccff';
case 'lime':
return '00ff99';
case 'pink':
return 'ff66cc';
case 'black':
return '000000';
default:
return 'ffffff';
}
}
private function replaceUsernames(string $text): string {
foreach ($this->getImportService()->getConfig('uidRelation') as $trello => $nextcloud) {
$text = str_replace($trello, $nextcloud->getUID(), $text);
}
return $text;
}
private function checklistItem(\stdClass $item): string {
if (($item->state == 'incomplete')) {
$string_start = '- [ ]';
} else {
$string_start = '- [x]';
}
$check_item_string = $string_start . ' ' . $item->name . "\n";
return $check_item_string;
}
private function formulateChecklistText(\stdClass $checklist): string {
$checklist_string = "\n\n## {$checklist->name}\n";
foreach ($checklist->checkItems as $item) {
$checklist_item_string = $this->checklistItem($item);
$checklist_string = $checklist_string . "\n" . $checklist_item_string;
}
return $checklist_string;
}
private function appendAttachmentsToDescription(\stdClass $trelloCard): void {
if (empty($trelloCard->attachments)) {
return;
}
$trelloCard->desc .= "\n\n## {$this->l10n->t('Attachments')}\n";
$trelloCard->desc .= "| {$this->l10n->t('File')} | {$this->l10n->t('date')} |\n";
$trelloCard->desc .= "|---|---\n";
foreach ($trelloCard->attachments as $attachment) {
$name = mb_strlen($attachment->name, 'UTF-8') ? $attachment->name : $attachment->url;
$trelloCard->desc .= "| [{$name}]({$attachment->url}) | {$attachment->date} |\n";
}
}
}

View File

@@ -0,0 +1,41 @@
{
"type": "object",
"properties": {
"api": {
"type": "object",
"properties": {
"key": {
"type": "string",
"pattern": "^[0-9a-fA-F]{32}$"
},
"token": {
"type": "string",
"pattern": "^[0-9a-fA-F]{64}$"
}
}
},
"board": {
"type": "string",
"pattern": "^\\w{1,}$"
},
"uidRelation": {
"type": "object",
"comment": "Relationship between Trello and Nextcloud usernames",
"example": {
"johndoe": "admin"
}
},
"owner": {
"type": "string",
"required": true,
"comment": "Nextcloud owner username"
},
"color": {
"type": "string",
"required": true,
"pattern": "^[0-9a-fA-F]{6}$",
"comment": "Default color for the board. If you don't inform, the default color will be used.",
"default": "0800fd"
}
}
}

View File

@@ -0,0 +1,24 @@
{
"type": "object",
"properties": {
"uidRelation": {
"type": "object",
"comment": "Relationship between Trello and Nextcloud usernames",
"example": {
"johndoe": "admin"
}
},
"owner": {
"type": "string",
"required": true,
"comment": "Nextcloud owner username"
},
"color": {
"type": "string",
"required": true,
"pattern": "^[0-9a-fA-F]{6}$",
"comment": "Default color for the board. If you don't inform, the default color will be used.",
"default": "0800fd"
}
}
}

View File

@@ -138,7 +138,7 @@ class OverviewService {
private function findAllBoardsFromUser(string $userId): array {
$userInfo = $this->getBoardPrerequisites($userId);
$userBoards = $this->boardMapper->findAllByUser($userInfo['user'], null, null);
$groupBoards = $this->boardMapper->findAllByGroups($userInfo['user'], $userInfo['groups'],null, null);
$groupBoards = $this->boardMapper->findAllByGroups($userInfo['user'], $userInfo['groups'], null, null);
$circleBoards = $this->boardMapper->findAllByCircles($userInfo['user'], null, null);
return array_unique(array_merge($userBoards, $groupBoards, $circleBoards));
}

View File

@@ -500,7 +500,7 @@ class DeckShareProvider implements \OCP\Share\IShareProvider {
);
}
$qb->innerJoin('s', 'filecache' ,'f', $qb->expr()->eq('s.file_source', 'f.fileid'));
$qb->innerJoin('s', 'filecache', 'f', $qb->expr()->eq('s.file_source', 'f.fileid'));
$qb->andWhere($qb->expr()->eq('f.parent', $qb->createNamedParameter($node->getId())));
$qb->orderBy('s.id');

View File

@@ -11,4 +11,6 @@ pages:
- Nextcloud API: API-Nextcloud.md
- Developer documentation:
- Data structure: structure.md
- Import documentation:
- Implement import: implement-import.md
- Class diagram: import-class-diagram.md

8159
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@@ -1,7 +1,7 @@
{
"name": "deck",
"description": "",
"version": "1.6.1",
"version": "1.7.0-beta1",
"authors": [
{
"name": "Julius Härtl",
@@ -29,10 +29,10 @@
},
"dependencies": {
"@babel/polyfill": "^7.12.1",
"@babel/runtime": "^7.16.0",
"@babel/runtime": "^7.17.2",
"@juliushaertl/vue-richtext": "^1.0.1",
"@nextcloud/auth": "^1.3.0",
"@nextcloud/axios": "^1.7.0",
"@nextcloud/axios": "^1.9.0",
"@nextcloud/dialogs": "^3.1.2",
"@nextcloud/event-bus": "^2.1.1",
"@nextcloud/files": "^2.1.0",
@@ -40,14 +40,14 @@
"@nextcloud/l10n": "^1.4.1",
"@nextcloud/moment": "^1.1.1",
"@nextcloud/router": "^2.0.0",
"@nextcloud/vue": "^4.2.0",
"@nextcloud/vue": "^5.0.0",
"@nextcloud/vue-dashboard": "^2.0.1",
"blueimp-md5": "^2.19.0",
"dompurify": "^2.3.3",
"dompurify": "^2.3.6",
"lodash": "^4.17.21",
"markdown-it": "^12.2.0",
"markdown-it": "^12.3.2",
"markdown-it-task-lists": "^2.1.1",
"markdown-it-link-attributes": "^3.0.0",
"markdown-it-link-attributes": "^4.0.0",
"moment": "^2.29.1",
"nextcloud-vue-collections": "^0.9.0",
"p-queue": "^6.6.2",
@@ -66,18 +66,18 @@
"extends @nextcloud/browserslist-config"
],
"engines": {
"node": ">=14.0.0",
"npm": ">=7.0.0"
"node": "^14.0.0",
"npm": "^7.0.0"
},
"devDependencies": {
"@nextcloud/babel-config": "^1.0.0",
"@nextcloud/browserslist-config": "^2.2.0",
"@nextcloud/eslint-config": "^6.1.0",
"@nextcloud/stylelint-config": "^1.0.0-beta.0",
"@nextcloud/webpack-vue-config": "^4.1.2",
"@relative-ci/agent": "^3.0.0",
"@vue/test-utils": "^1.2.2",
"jest": "^27.3.1",
"@nextcloud/eslint-config": "^6.1.2",
"@nextcloud/stylelint-config": "^2.1.2",
"@nextcloud/webpack-vue-config": "^5.0.0",
"@relative-ci/agent": "^3.1.1",
"@vue/test-utils": "^1.3.0",
"jest": "^27.5.1",
"jest-serializer-vue": "^2.0.2",
"vue-jest": "^3.0.7"
},
@@ -97,4 +97,4 @@
"<rootDir>/node_modules/jest-serializer-vue"
]
}
}
}

View File

@@ -88,7 +88,6 @@ export default {
navShown: state => state.navShown,
sidebarShownState: state => state.sidebarShown,
currentBoard: state => state.currentBoard,
cardDetailsInModal: state => state.cardDetailsInModal,
}),
// TODO: properly handle sidebar showing for route subview and board sidebar
sidebarRouterView() {
@@ -98,6 +97,14 @@ export default {
sidebarShown() {
return this.sidebarRouterView || this.sidebarShownState
},
cardDetailsInModal: {
get() {
return this.$store.getters.config('cardDetailsInModal')
},
set(newValue) {
this.$store.dispatch('setConfig', { cardDetailsInModal: newValue })
},
},
},
created() {
this.$store.dispatch('loadBoards')

View File

@@ -239,6 +239,7 @@ export default {
isAddStackVisible: false,
filter: { tags: [], users: [], due: '', unassigned: false },
showAddCardModal: false,
defaultPageTitle: false,
}
},
@@ -266,11 +267,17 @@ export default {
return [...this.board.labels].sort((a, b) => (a.title < b.title) ? -1 : 1)
},
},
beforeDestroy() {
this.setPageTitle('')
},
watch: {
board(current, previous) {
if (current?.id !== previous?.id) {
this.clearFilter()
}
if (current) {
this.setPageTitle(current.title)
}
},
},
methods: {
@@ -330,6 +337,22 @@ export default {
clickHideAddCardModel() {
this.showAddCardModal = false
},
setPageTitle(title) {
if (this.defaultPageTitle === false) {
this.defaultPageTitle = window.document.title
if (this.defaultPageTitle.indexOf(' - Deck - ') !== -1) {
this.defaultPageTitle = this.defaultPageTitle.substring(this.defaultPageTitle.indexOf(' - Deck - ') + 3)
}
if (this.defaultPageTitle.indexOf('Deck - ') !== 0) {
this.defaultPageTitle = 'Deck - ' + this.defaultPageTitle
}
}
let newTitle = this.defaultPageTitle
if (title !== '') {
newTitle = `${title} - ${newTitle}`
}
window.document.title = newTitle
},
},
}
</script>

View File

@@ -162,7 +162,6 @@ export default {
]),
...mapState({
showArchived: state => state.showArchived,
cardDetailsInModal: state => state.cardDetailsInModal,
}),
cardsByStack() {
return this.$store.getters.cardsByStack(this.stack.id).filter((card) => {
@@ -175,6 +174,14 @@ export default {
dragHandleSelector() {
return this.canEdit ? null : '.no-drag'
},
cardDetailsInModal: {
get() {
return this.$store.getters.config('cardDetailsInModal')
},
set(newValue) {
this.$store.dispatch('setConfig', { cardDetailsInModal: newValue })
},
},
},
methods: {

View File

@@ -25,13 +25,14 @@
:active="tabId"
:title="title"
:subtitle="subtitle"
:subtitle-tooltip="subtitleTooltip"
:title-editable="titleEditable"
@update:titleEditable="handleUpdateTitleEditable"
@update:title="handleUpdateTitle"
@submit-title="handleSubmitTitle"
@close="closeSidebar">
<template #secondary-actions>
<ActionButton v-if="cardDetailsInModal" icon="icon-menu-sidebar" @click.stop="showModal()">
<ActionButton v-if="cardDetailsInModal" icon="icon-menu-sidebar" @click.stop="closeModal()">
{{ t('deck', 'Open in sidebar view') }}
</ActionButton>
<ActionButton v-else icon="icon-external" @click.stop="showModal()">
@@ -88,8 +89,10 @@ import CardSidebarTabAttachments from './CardSidebarTabAttachments'
import CardSidebarTabComments from './CardSidebarTabComments'
import CardSidebarTabActivity from './CardSidebarTabActivity'
import relativeDate from '../../mixins/relativeDate'
import moment from '@nextcloud/moment'
import { showError } from '@nextcloud/dialogs'
import { getLocale } from '@nextcloud/l10n'
const capabilities = window.OC.getCapabilities()
@@ -126,12 +129,12 @@ export default {
titleEditable: false,
titleEditing: '',
hasActivity: capabilities && capabilities.activity,
locale: getLocale(),
}
},
computed: {
...mapState({
currentBoard: state => state.currentBoard,
cardDetailsInModal: state => state.cardDetailsInModal,
}),
...mapGetters(['canEdit', 'assignables', 'cardActions', 'stackById']),
title() {
@@ -143,6 +146,9 @@ export default {
subtitle() {
return t('deck', 'Modified') + ': ' + this.relativeDate(this.currentCard.lastModified * 1000) + ' ' + t('deck', 'Created') + ': ' + this.relativeDate(this.currentCard.createdAt * 1000)
},
subtitleTooltip() {
return t('deck', 'Modified') + ': ' + this.formatDate(this.currentCard.lastModified) + '\n' + t('deck', 'Created') + ': ' + this.formatDate(this.currentCard.createdAt)
},
cardRichObject() {
return {
id: '' + this.currentCard.id,
@@ -152,6 +158,14 @@ export default {
link: window.location.protocol + '//' + window.location.host + generateUrl('/apps/deck/') + `#/board/${this.currentBoard.id}/card/${this.currentCard.id}`,
}
},
cardDetailsInModal: {
get() {
return this.$store.getters.config('cardDetailsInModal')
},
set(newValue) {
this.$store.dispatch('setConfig', { cardDetailsInModal: newValue })
},
},
},
methods: {
handleUpdateTitleEditable(value) {
@@ -177,7 +191,13 @@ export default {
},
showModal() {
this.$store.dispatch('setCardDetailsInModal', true)
this.$store.dispatch('setConfig', { cardDetailsInModal: true })
},
closeModal() {
this.$store.dispatch('setConfig', { cardDetailsInModal: false })
},
formatDate(timestamp) {
return moment.unix(timestamp).locale(this.locale).format('LLLL')
},
},
}

View File

@@ -224,7 +224,6 @@ export default {
computed: {
...mapState({
currentBoard: state => state.currentBoard,
cardDetailsInModal: state => state.cardDetailsInModal,
}),
...mapGetters(['canEdit', 'assignables']),
formatedAssignables() {
@@ -250,6 +249,14 @@ export default {
return assignable
})
},
cardDetailsInModal: {
get() {
return this.$store.getters.config('cardDetailsInModal')
},
set(newValue) {
this.$store.dispatch('setConfig', { cardDetailsInModal: newValue })
},
},
duedate: {
get() {
return this.card.duedate ? new Date(this.card.duedate) : null

View File

@@ -132,7 +132,6 @@ export default {
computed: {
...mapState({
currentBoard: state => state.currentBoard,
cardDetailsInModal: state => state.cardDetailsInModal,
}),
...mapGetters(['canEdit']),
attachments() {
@@ -251,6 +250,8 @@ export default {
&::v-deep .attachment-list {
flex-shrink: 1;
overflow: scroll;
max-height: 50vh;
}
}
@@ -261,6 +262,8 @@ export default {
#description-preview {
min-height: 100px;
width: auto;
overflow-x: auto;
&::v-deep {
@import './../../css/markdown';
@@ -331,4 +334,15 @@ h5 {
#app-sidebar .app-sidebar-header__desc h4 {
font-size: 12px !important;
}
.vue-easymde .cm-s-easymde .cm-link {
color: var(--color-main-text);
}
.vue-easymde .cm-s-easymde .cm-string.cm-url,
.vue-easymde .cm-s-easymde .cm-formatting.cm-link,
.vue-easymde .cm-s-easymde .cm-formatting.cm-url,
.vue-easymde .cm-s-easymde .cm-formatting.cm-image {
color: var(--color-text-maxcontrast);
}
</style>

View File

@@ -66,7 +66,11 @@
:placeholder="t('deck', 'Select a list')"
:options="stacksFromBoard"
:max-height="100"
label="title" />
label="title">
<span slot="noOptions">
{{ t('deck', 'List is empty') }}
</span>
</Multiselect>
<button :disabled="!isBoardAndStackChoosen" class="primary" @click="moveCard">
{{ t('deck', 'Move card') }}

View File

@@ -144,10 +144,10 @@ export default {
},
cardDetailsInModal: {
get() {
return this.$store.getters.cardDetailsInModal
return this.$store.getters.config('cardDetailsInModal')
},
set(newValue) {
this.$store.dispatch('setCardDetailsInModal', newValue)
this.$store.dispatch('setConfig', { cardDetailsInModal: newValue })
},
},
configCalendar: {

View File

@@ -45,7 +45,7 @@ window.addEventListener('DOMContentLoaded', () => {
window.OCP.Collaboration.registerType('deck', {
action: () => {
const BoardSelector = () => import('./BoardSelector')
return buildSelector(BoardSelector)
buildSelector(BoardSelector)
},
typeString: t('deck', 'Link to a board'),
typeIconClass: 'icon-deck',
@@ -54,7 +54,7 @@ window.addEventListener('DOMContentLoaded', () => {
window.OCP.Collaboration.registerType('deck-card', {
action: () => {
const CardSelector = () => import('./CardSelector')
return buildSelector(CardSelector)
buildSelector(CardSelector)
},
typeString: t('deck', 'Link to a card'),
typeIconClass: 'icon-deck',

View File

@@ -62,7 +62,6 @@ export default new Vuex.Store({
showArchived: false,
navShown: localStorage.getItem('deck.navShown') === 'true',
compactMode: localStorage.getItem('deck.compactMode') === 'true',
cardDetailsInModal: localStorage.getItem('deck.cardDetailsInModal') === 'true',
sidebarShown: false,
currentBoard: null,
currentCard: null,
@@ -79,9 +78,6 @@ export default new Vuex.Store({
config: state => (key) => {
return state.config[key]
},
cardDetailsInModal: state => {
return state.cardDetailsInModal
},
getSearchQuery: state => {
return state.searchQuery
},
@@ -233,10 +229,6 @@ export default new Vuex.Store({
state.compactMode = !state.compactMode
localStorage.setItem('deck.compactMode', state.compactMode)
},
setCardDetailsInModal(state) {
state.cardDetailsInModal = !state.cardDetailsInModal
localStorage.setItem('deck.cardDetailsInModal', state.cardDetailsInModal)
},
setBoards(state, boards) {
state.boards = boards
},
@@ -441,9 +433,6 @@ export default new Vuex.Store({
toggleCompactMode({ commit }) {
commit('toggleCompactMode')
},
setCardDetailsInModal({ commit }, show) {
commit('setCardDetailsInModal', show)
},
setCurrentBoard({ commit }, board) {
commit('setCurrentBoard', board)
},

View File

@@ -0,0 +1,7 @@
{
"owner": "admin",
"color": "0800fd",
"uidRelation": {
"johndoe": "johndoe"
}
}

View File

@@ -0,0 +1,582 @@
{
"id": "fakeboardidhash",
"name": "Test Board Name",
"desc": "",
"descData": null,
"closed": false,
"dateClosed": null,
"idOrganization": null,
"shortLink": "qwerty",
"powerUps": [],
"dateLastActivity": "2021-07-10T17:01:58.633Z",
"idTags": [],
"datePluginDisable": null,
"creationMethod": null,
"idBoardSource": null,
"idMemberCreator": "fakeidmemberhash",
"idEnterprise": null,
"pinned": false,
"starred": false,
"url": "https://trello.com/b/qwerty/fakeboardurl",
"prefs": {
"permissionLevel": "private",
"hideVotes": false,
"voting": "disabled",
"comments": "members",
"invitations": "members",
"selfJoin": false,
"cardCovers": true,
"isTemplate": false,
"cardAging": "regular",
"calendarFeedEnabled": false,
"background": "blue",
"backgroundImage": null,
"backgroundImageScaled": null,
"backgroundTile": false,
"backgroundBrightness": "dark",
"backgroundColor": "#0079BF",
"backgroundBottomColor": "#0079BF",
"backgroundTopColor": "#0079BF",
"canBePublic": true,
"canBeEnterprise": true,
"canBeOrg": true,
"canBePrivate": true,
"canInvite": true
},
"shortUrl": "https://trello.com/b/qwerty",
"premiumFeatures": [],
"enterpriseOwned": false,
"ixUpdate": "67",
"limits": {
"attachments": {
"perBoard": {
"status": "ok",
"disableAt": 36000,
"warnAt": 32400
},
"perCard": {
"status": "ok",
"disableAt": 1000,
"warnAt": 900
}
},
"boards": {
"totalMembersPerBoard": {
"status": "ok",
"disableAt": 1600,
"warnAt": 1440
}
},
"cards": {
"openPerBoard": {
"status": "ok",
"disableAt": 5000,
"warnAt": 4500
},
"openPerList": {
"status": "ok",
"disableAt": 5000,
"warnAt": 4500
},
"totalPerBoard": {
"status": "ok",
"disableAt": 2000000,
"warnAt": 1800000
},
"totalPerList": {
"status": "ok",
"disableAt": 1000000,
"warnAt": 900000
}
},
"checklists": {
"perBoard": {
"status": "ok",
"disableAt": 2000000,
"warnAt": 1800000
},
"perCard": {
"status": "ok",
"disableAt": 500,
"warnAt": 450
}
},
"checkItems": {
"perChecklist": {
"status": "ok",
"disableAt": 200,
"warnAt": 180
}
},
"customFields": {
"perBoard": {
"status": "ok",
"disableAt": 50,
"warnAt": 45
}
},
"customFieldOptions": {
"perField": {
"status": "ok",
"disableAt": 50,
"warnAt": 45
}
},
"labels": {
"perBoard": {
"status": "ok",
"disableAt": 1000,
"warnAt": 900
}
},
"lists": {
"openPerBoard": {
"status": "ok",
"disableAt": 500,
"warnAt": 450
},
"totalPerBoard": {
"status": "ok",
"disableAt": 3000,
"warnAt": 2700
}
},
"stickers": {
"perCard": {
"status": "ok",
"disableAt": 70,
"warnAt": 63
}
},
"reactions": {
"perAction": {
"status": "ok",
"disableAt": 1000,
"warnAt": 900
},
"uniquePerAction": {
"status": "ok",
"disableAt": 17,
"warnAt": 16
}
}
},
"subscribed": false,
"templateGallery": null,
"dateLastView": "2021-07-10T17:01:58.665Z",
"labelNames": {
"green": "",
"yellow": "",
"orange": "",
"red": "",
"purple": "",
"blue": "",
"sky": "",
"lime": "",
"pink": "",
"black": ""
},
"actions": [
{
"id": "60e9d2869efe2e1141be2798",
"idMemberCreator": "fakeidmemberhash",
"data": {
"idMember": "fakeidmemberhash",
"deactivated": false,
"card": {
"id": "hashcard7",
"name": "Name Card 7",
"idShort": 7,
"shortLink": "fakeshortlinkcard7"
},
"board": {
"id": "fakeboardidhash",
"name": "Test Board Name",
"shortLink": "qwerty"
},
"member": {
"id": "fakeidmemberhash",
"name": "John Doe"
}
},
"type": "removeMemberFromCard",
"date": "2021-07-10T17:01:58.636Z",
"appCreator": null,
"limits": {},
"member": {
"id": "fakeidmemberhash",
"username": "johndoe",
"activityBlocked": false,
"avatarHash": "fakeavatarhash",
"avatarUrl": "https://trello-members.s3.amazonaws.com/fakeidmemberhash/fakeavatarhash",
"fullName": "John Doe",
"idMemberReferrer": null,
"initials": "JD",
"nonPublic": {
"fullName": "John Doe",
"initials": "JD",
"avatarUrl": "https://trello-members.s3.amazonaws.com/fakeidmemberhash/fakeavatarhash",
"avatarHash": "fakeavatarhash"
},
"nonPublicAvailable": true
},
"memberCreator": {
"id": "fakeidmemberhash",
"username": "johndoe",
"activityBlocked": false,
"avatarHash": "fakeavatarhash",
"avatarUrl": "https://trello-members.s3.amazonaws.com/fakeidmemberhash/fakeavatarhash",
"fullName": "John Doe",
"idMemberReferrer": null,
"initials": "JD",
"nonPublic": {
"fullName": "John Doe",
"initials": "JD",
"avatarUrl": "https://trello-members.s3.amazonaws.com/fakeidmemberhash/fakeavatarhash",
"avatarHash": "fakeavatarhash"
},
"nonPublicAvailable": true
}
},
{
"id": "60e9d1832ff82d10c0cea6ba",
"idMemberCreator": "fakeidmemberhash",
"data": {
"idMember": "fakeidmemberhash",
"card": {
"id": "hashcard7",
"name": "Name Card 7",
"idShort": 7,
"shortLink": "fakeshortlinkcard7"
},
"board": {
"id": "fakeboardidhash",
"name": "Test Board Name",
"shortLink": "qwerty"
},
"member": {
"id": "fakeidmemberhash",
"name": "John Doe"
}
},
"type": "addMemberToCard",
"date": "2021-07-10T16:57:39.999Z",
"appCreator": null,
"limits": {},
"member": {
"id": "fakeidmemberhash",
"username": "johndoe",
"activityBlocked": false,
"avatarHash": "fakeavatarhash",
"avatarUrl": "https://trello-members.s3.amazonaws.com/fakeidmemberhash/fakeavatarhash",
"fullName": "John Doe",
"idMemberReferrer": null,
"initials": "JD",
"nonPublic": {
"fullName": "John Doe",
"initials": "JD",
"avatarUrl": "https://trello-members.s3.amazonaws.com/fakeidmemberhash/fakeavatarhash",
"avatarHash": "fakeavatarhash"
},
"nonPublicAvailable": true
},
"memberCreator": {
"id": "fakeidmemberhash",
"username": "johndoe",
"activityBlocked": false,
"avatarHash": "fakeavatarhash",
"avatarUrl": "https://trello-members.s3.amazonaws.com/fakeidmemberhash/fakeavatarhash",
"fullName": "John Doe",
"idMemberReferrer": null,
"initials": "JD",
"nonPublic": {
"fullName": "John Doe",
"initials": "JD",
"avatarUrl": "https://trello-members.s3.amazonaws.com/fakeidmemberhash/fakeavatarhash",
"avatarHash": "fakeavatarhash"
},
"nonPublicAvailable": true
}
},
{
"id": "59bbfc4bf36aa0270d6bfd43",
"idMemberCreator": "fakeidmemberhash",
"data": {
"board": {
"shortLink": "qwerty",
"name": "Test Board Name",
"id": "fakeboardidhash"
},
"list": {
"name": "TODO",
"id": "hashlisttodo"
},
"card": {
"shortLink": "fakeshortlinkcard7",
"idShort": 7,
"name": "Name Card 7",
"id": "hashcard7"
}
},
"type": "createCard",
"date": "2017-09-15T16:14:03.187Z",
"appCreator": null,
"limits": {},
"memberCreator": {
"id": "fakeidmemberhash",
"username": "johndoe",
"activityBlocked": false,
"avatarHash": "fakeavatarhash",
"avatarUrl": "https://trello-members.s3.amazonaws.com/fakeidmemberhash/fakeavatarhash",
"fullName": "John Doe",
"idMemberReferrer": null,
"initials": "JD",
"nonPublic": {
"fullName": "John Doe",
"initials": "JD",
"avatarUrl": "https://trello-members.s3.amazonaws.com/fakeidmemberhash/fakeavatarhash",
"avatarHash": "fakeavatarhash"
},
"nonPublicAvailable": true
}
},
{
"id": "59bbfb8e4a6f8ca35be9b82a",
"idMemberCreator": "fakeidmemberhash",
"data": {
"board": {
"shortLink": "qwerty",
"name": "Test Board Name",
"id": "fakeboardidhash"
},
"list": {
"name": "TODO",
"id": "hashlisttodo"
}
},
"type": "createList",
"date": "2017-09-15T16:10:54.714Z",
"appCreator": null,
"limits": {},
"memberCreator": {
"id": "fakeidmemberhash",
"username": "johndoe",
"activityBlocked": false,
"avatarHash": "fakeavatarhash",
"avatarUrl": "https://trello-members.s3.amazonaws.com/fakeidmemberhash/fakeavatarhash",
"fullName": "John Doe",
"idMemberReferrer": null,
"initials": "JD",
"nonPublic": {
"fullName": "John Doe",
"initials": "JD",
"avatarUrl": "https://trello-members.s3.amazonaws.com/fakeidmemberhash/fakeavatarhash",
"avatarHash": "fakeavatarhash"
},
"nonPublicAvailable": true
}
},
{
"id": "59bbfb88973b76e586edec5e",
"idMemberCreator": "fakeidmemberhash",
"data": {
"board": {
"shortLink": "qwerty",
"name": "Test Board Name",
"id": "fakeboardidhash"
}
},
"type": "createBoard",
"date": "2017-09-15T16:10:48.069Z",
"appCreator": null,
"limits": {},
"memberCreator": {
"id": "fakeidmemberhash",
"username": "johndoe",
"activityBlocked": false,
"avatarHash": "fakeavatarhash",
"avatarUrl": "https://trello-members.s3.amazonaws.com/fakeidmemberhash/fakeavatarhash",
"fullName": "John Doe",
"idMemberReferrer": null,
"initials": "JD",
"nonPublic": {
"fullName": "John Doe",
"initials": "JD",
"avatarUrl": "https://trello-members.s3.amazonaws.com/fakeidmemberhash/fakeavatarhash",
"avatarHash": "fakeavatarhash"
},
"nonPublicAvailable": true
}
}
],
"cards": [
{
"id": "hashcard7",
"address": null,
"checkItemStates": null,
"closed": false,
"coordinates": null,
"creationMethod": null,
"dateLastActivity": "2021-07-10T17:01:58.633Z",
"desc": "",
"descData": null,
"dueReminder": null,
"idBoard": "fakeboardidhash",
"idLabels": [],
"idList": "hashlisttodo",
"idMembersVoted": [],
"idShort": 7,
"idAttachmentCover": null,
"locationName": null,
"manualCoverAttachment": false,
"name": "Name Card 7",
"pos": 65535,
"shortLink": "fakeshortlinkcard7",
"isTemplate": false,
"cardRole": null,
"badges": {
"attachmentsByType": {
"trello": {
"board": 0,
"card": 0
}
},
"location": false,
"votes": 0,
"viewingMemberVoted": false,
"subscribed": false,
"fogbugz": "",
"checkItems": 0,
"checkItemsChecked": 0,
"checkItemsEarliestDue": null,
"comments": 0,
"attachments": 0,
"description": false,
"due": null,
"dueComplete": false,
"start": null
},
"dueComplete": false,
"due": null,
"email": "johndoe+card7@boards.trello.com",
"idChecklists": [],
"idMembers": [],
"labels": [],
"limits": {
"attachments": {
"perCard": {
"status": "ok",
"disableAt": 1000,
"warnAt": 900
}
},
"checklists": {
"perCard": {
"status": "ok",
"disableAt": 500,
"warnAt": 450
}
},
"stickers": {
"perCard": {
"status": "ok",
"disableAt": 70,
"warnAt": 63
}
}
},
"shortUrl": "https://trello.com/c/fakeshortlinkcard7",
"start": null,
"subscribed": false,
"url": "https://trello.com/c/fakeshortlinkcard7/7-name-card-7",
"cover": {
"idAttachment": null,
"color": null,
"idUploadedBackground": null,
"size": "normal",
"brightness": "dark",
"idPlugin": null
},
"attachments": [],
"pluginData": [],
"customFieldItems": []
}
],
"labels": [
{
"id": "59bbfb881314a339999eb855",
"idBoard": "fakeboardidhash",
"name": "",
"color": "yellow"
}
],
"lists": [
{
"id": "hashlisttodo",
"name": "TODO",
"closed": false,
"pos": 65535,
"softLimit": null,
"creationMethod": null,
"idBoard": "fakeboardidhash",
"limits": {
"cards": {
"openPerList": {
"status": "ok",
"disableAt": 5000,
"warnAt": 4500
},
"totalPerList": {
"status": "ok",
"disableAt": 1000000,
"warnAt": 900000
}
}
},
"subscribed": false
}
],
"members": [
{
"id": "fakeidmemberhash",
"bio": "",
"bioData": {
"emoji": {}
},
"confirmed": true,
"memberType": "normal",
"username": "johndoe",
"activityBlocked": false,
"avatarHash": "fakeavatarhash",
"avatarUrl": "https://trello-members.s3.amazonaws.com/fakeidmemberhash/fakeavatarhash",
"fullName": "John Doe",
"idEnterprise": null,
"idEnterprisesDeactivated": [],
"idMemberReferrer": null,
"idPremOrgsAdmin": [],
"initials": "JD",
"nonPublic": {
"fullName": "John Doe",
"initials": "JD",
"avatarUrl": "https://trello-members.s3.amazonaws.com/fakeidmemberhash/fakeavatarhash",
"avatarHash": "fakeavatarhash"
},
"nonPublicAvailable": true,
"products": [],
"url": "https://trello.com/johndoe",
"status": "disconnected"
}
],
"checklists": [],
"customFields": [],
"memberships": [
{
"id": "59bbfb88973b76e586edec5d",
"idMember": "fakeidmemberhash",
"memberType": "admin",
"unconfirmed": false,
"deactivated": false
}
],
"pluginData": []
}

View File

@@ -70,7 +70,7 @@ class BoardDatabaseTest extends \Test\TestCase {
$board->setOwner(self::TEST_USER1);
$board->setColor('000000');
$board->setLabels([]);
$created = $this->boardService->create('Test', self::TEST_USER1, '000000');
$created = $this->boardService->create('Test', self::TEST_USER1, '000000');
$id = $created->getId();
$actual = $this->boardService->find($id);
$this->assertEquals($actual->getTitle(), $board->getTitle());

View File

@@ -38,7 +38,6 @@ use OCP\L10N\IFactory;
use OCP\RichObjectStrings\IValidator;
use PHPUnit\Framework\TestCase;
use PHPUnit_Framework_MockObject_MockObject as MockObject;
use OCA\Deck\Service\CardService;
class DeckProviderTest extends TestCase {
@@ -57,9 +56,6 @@ class DeckProviderTest extends TestCase {
/** @var ICommentsManager|MockObject */
private $commentsManager;
/** @var CardService|MockObject */
private $cardService;
/** @var string */
private $userId = 'admin';
@@ -71,9 +67,7 @@ class DeckProviderTest extends TestCase {
$this->commentsManager = $this->createMock(ICommentsManager::class);
$this->l10nFactory = $this->createMock(IFactory::class);
$this->config = $this->createMock(IConfig::class);
$this->config = $this->createMock(IConfig::class);
$this->cardService = $this->createMock(CardService::class);
$this->provider = new DeckProvider($this->urlGenerator, $this->activityManager, $this->userManager, $this->commentsManager, $this->l10nFactory, $this->config, $this->userId, $this->cardService);
$this->provider = new DeckProvider($this->urlGenerator, $this->activityManager, $this->userManager, $this->commentsManager, $this->l10nFactory, $this->config, $this->userId);
}
private function mockEvent($objectType, $objectId, $objectName, $subject, $subjectParameters = []) {

View File

@@ -0,0 +1,77 @@
<?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\Helper\HelperSet;
use Symfony\Component\Console\Helper\QuestionHelper;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Output\OutputInterface;
class BoardImportTest extends \Test\TestCase {
/** @var BoardImportCommandService */
private $boardImportCommandService;
/** @var BoardImport */
private $boardImport;
public function setUp(): void {
parent::setUp();
$this->boardImportCommandService = $this->createMock(BoardImportCommandService::class);
$this->boardImport = new BoardImport(
$this->boardImportCommandService
);
$questionHelper = new QuestionHelper();
$this->boardImport->setHelperSet(
new HelperSet([
$questionHelper
])
);
}
public function testExecuteWithSuccess() {
$input = $this->createMock(InputInterface::class);
$input
->method('getOption')
->withConsecutive(
['system'],
['config']
)
->will($this->returnValueMap([
['system', 'trelloJson'],
['config', null]
]));
$output = $this->createMock(OutputInterface::class);
$output
->expects($this->once())
->method('writeLn')
->with('Done!');
$actual = $this->invokePrivate($this->boardImport, 'interact', [$input, $output]);
$this->assertNull($actual);
$actual = $this->invokePrivate($this->boardImport, 'execute', [$input, $output]);
$this->assertEquals(0, $actual);
}
}

View File

@@ -67,10 +67,10 @@ class AclMapperTest extends MapperTestUtility {
$this->boardMapper->insert($this->getBoard('MyBoard 3', 'user3'))
];
$this->acls = [
$this->aclMapper->insert($this->getAcl('user','user1', false, false, false, $this->boards[1]->getId())),
$this->aclMapper->insert($this->getAcl('user','user2', true, false, false, $this->boards[0]->getId())),
$this->aclMapper->insert($this->getAcl('user','user3', true, true, false, $this->boards[0]->getId())),
$this->aclMapper->insert($this->getAcl('user','user1', false, false, false, $this->boards[2]->getId()))
$this->aclMapper->insert($this->getAcl('user', 'user1', false, false, false, $this->boards[1]->getId())),
$this->aclMapper->insert($this->getAcl('user', 'user2', true, false, false, $this->boards[0]->getId())),
$this->aclMapper->insert($this->getAcl('user', 'user3', true, true, false, $this->boards[0]->getId())),
$this->aclMapper->insert($this->getAcl('user', 'user1', false, false, false, $this->boards[2]->getId()))
];
foreach ($this->acls as $acl) {

View File

@@ -76,10 +76,10 @@ class BoardMapperTest extends MapperTestUtility {
$this->boardMapper->insert($this->getBoard('MyBoard 3', 'user3'))
];
$this->acls = [
$this->aclMapper->insert($this->getAcl('user','user1', false, false, false, $this->boards[1]->getId())),
$this->aclMapper->insert($this->getAcl('user','user2', true, false, false, $this->boards[0]->getId())),
$this->aclMapper->insert($this->getAcl('user','user3', true, true, false, $this->boards[0]->getId())),
$this->aclMapper->insert($this->getAcl('user','user1', false, false, false, $this->boards[2]->getId()))
$this->aclMapper->insert($this->getAcl('user', 'user1', false, false, false, $this->boards[1]->getId())),
$this->aclMapper->insert($this->getAcl('user', 'user2', true, false, false, $this->boards[0]->getId())),
$this->aclMapper->insert($this->getAcl('user', 'user3', true, true, false, $this->boards[0]->getId())),
$this->aclMapper->insert($this->getAcl('user', 'user1', false, false, false, $this->boards[2]->getId()))
];
foreach ($this->acls as $acl) {

View File

@@ -176,9 +176,9 @@ class AttachmentServiceTest extends TestCase {
public function testFindAll() {
$this->mockPermission(Acl::PERMISSION_READ);
$attachments = [
$this->createAttachment('deck_file','file1'),
$this->createAttachment('deck_file','file2'),
$this->createAttachment('deck_file_invalid','file3'),
$this->createAttachment('deck_file', 'file1'),
$this->createAttachment('deck_file', 'file2'),
$this->createAttachment('deck_file_invalid', 'file3'),
];
$this->attachmentMapper->expects($this->once())
->method('findAll')
@@ -197,14 +197,14 @@ class AttachmentServiceTest extends TestCase {
public function testFindAllWithDeleted() {
$this->mockPermission(Acl::PERMISSION_READ);
$attachments = [
$this->createAttachment('deck_file','file1'),
$this->createAttachment('deck_file','file2'),
$this->createAttachment('deck_file_invalid','file3'),
$this->createAttachment('deck_file', 'file1'),
$this->createAttachment('deck_file', 'file2'),
$this->createAttachment('deck_file_invalid', 'file3'),
];
$attachmentsDeleted = [
$this->createAttachment('deck_file','file4'),
$this->createAttachment('deck_file','file5'),
$this->createAttachment('deck_file_invalid','file6'),
$this->createAttachment('deck_file', 'file4'),
$this->createAttachment('deck_file', 'file5'),
$this->createAttachment('deck_file_invalid', 'file6'),
];
$this->attachmentMapper->expects($this->once())
->method('findAll')

View File

@@ -42,7 +42,6 @@ use OCP\IUser;
use OCP\IUserManager;
use OCP\IGroupManager;
use \Test\TestCase;
use OCP\IURLGenerator;
class BoardServiceTest extends TestCase {
@@ -75,8 +74,6 @@ class BoardServiceTest extends TestCase {
/** @var IEventDispatcher */
private $eventDispatcher;
private $userId = 'admin';
/** @var IURLGenerator */
private $urlGenerator;
public function setUp(): void {
parent::setUp();
@@ -94,7 +91,6 @@ class BoardServiceTest extends TestCase {
$this->activityManager = $this->createMock(ActivityManager::class);
$this->changeHelper = $this->createMock(ChangeHelper::class);
$this->eventDispatcher = $this->createMock(IEventDispatcher::class);
$this->urlGenerator = $this->createMock(IURLGenerator::class);
$this->service = new BoardService(
$this->boardMapper,
@@ -111,7 +107,6 @@ class BoardServiceTest extends TestCase {
$this->activityManager,
$this->eventDispatcher,
$this->changeHelper,
$this->urlGenerator,
$this->userId
);
@@ -396,7 +391,7 @@ class BoardServiceTest extends TestCase {
$this->aclMapper->expects($this->once())
->method('delete')
->with($acl)
->willReturn(true);
$this->assertTrue($this->service->deleteAcl(123));
->willReturn($acl);
$this->assertEquals($acl, $this->service->deleteAcl(123));
}
}

View File

@@ -43,7 +43,6 @@ use OCP\IUserManager;
use PHPUnit\Framework\MockObject\MockObject;
use Symfony\Component\EventDispatcher\EventDispatcherInterface;
use Test\TestCase;
use OCP\IURLGenerator;
class CardServiceTest extends TestCase {
@@ -77,9 +76,6 @@ class CardServiceTest extends TestCase {
/** @var ChangeHelper|MockObject */
private $changeHelper;
/** @var IURLGenerator|MockObject */
private $urlGenerator;
public function setUp(): void {
parent::setUp();
$this->cardMapper = $this->createMock(CardMapper::class);
@@ -96,7 +92,6 @@ class CardServiceTest extends TestCase {
$this->userManager = $this->createMock(IUserManager::class);
$this->eventDispatcher = $this->createMock(IEventDispatcher::class);
$this->changeHelper = $this->createMock(ChangeHelper::class);
$this->urlGenerator = $this->createMock(IURLGenerator::class);
$this->cardService = new CardService(
$this->cardMapper,
$this->stackMapper,
@@ -112,7 +107,6 @@ class CardServiceTest extends TestCase {
$this->userManager,
$this->changeHelper,
$this->eventDispatcher,
$this->urlGenerator,
'user1'
);
}

View File

@@ -0,0 +1,199 @@
<?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\Service\Importer;
use OC\Comments\Comment;
use OCA\Deck\Db\Acl;
use OCA\Deck\Db\AclMapper;
use OCA\Deck\Db\Assignment;
use OCA\Deck\Db\AssignmentMapper;
use OCA\Deck\Db\AttachmentMapper;
use OCA\Deck\Db\BoardMapper;
use OCA\Deck\Db\Card;
use OCA\Deck\Db\CardMapper;
use OCA\Deck\Db\Label;
use OCA\Deck\Db\LabelMapper;
use OCA\Deck\Db\Stack;
use OCA\Deck\Db\StackMapper;
use OCA\Deck\Event\BoardImportGetAllowedEvent;
use OCA\Deck\Service\Importer\Systems\TrelloJsonService;
use OCP\Comments\ICommentsManager;
use OCP\EventDispatcher\IEventDispatcher;
use OCP\IDBConnection;
use OCP\IUser;
use OCP\IUserManager;
use PHPUnit\Framework\MockObject\MockObject;
class BoardImportServiceTest extends \Test\TestCase {
/** @var IDBConnection|MockObject */
protected $dbConn;
/** @var IUserManager|MockObject */
private $userManager;
/** @var BoardMapper|MockObject */
private $boardMapper;
/** @var AclMapper|MockObject */
private $aclMapper;
/** @var LabelMapper|MockObject */
private $labelMapper;
/** @var StackMapper|MockObject */
private $stackMapper;
/** @var CardMapper|MockObject */
private $cardMapper;
/** @var AssignmentMapper|MockObject */
private $assignmentMapper;
/** @var AttachmentMapper|MockObject */
private $attachmentMapper;
/** @var ICommentsManager|MockObject */
private $commentsManager;
/** @var IEventDispatcher|MockObject */
private $eventDispatcher;
/** @var TrelloJsonService|MockObject */
private $trelloJsonService;
/** @var BoardImportService|MockObject */
private $boardImportService;
public function setUp(): void {
$this->userManager = $this->createMock(IUserManager::class);
$this->boardMapper = $this->createMock(BoardMapper::class);
$this->aclMapper = $this->createMock(AclMapper::class);
$this->labelMapper = $this->createMock(LabelMapper::class);
$this->stackMapper = $this->createMock(StackMapper::class);
$this->cardMapper = $this->createMock(CardMapper::class);
$this->assignmentMapper = $this->createMock(AssignmentMapper::class);
$this->attachmentMapper = $this->createMock(AttachmentMapper::class);
$this->commentsManager = $this->createMock(ICommentsManager::class);
$this->eventDispatcher = $this->createMock(IEventDispatcher::class);
$this->boardImportService = new BoardImportService(
$this->userManager,
$this->boardMapper,
$this->aclMapper,
$this->labelMapper,
$this->stackMapper,
$this->assignmentMapper,
$this->attachmentMapper,
$this->cardMapper,
$this->commentsManager,
$this->eventDispatcher
);
$this->boardImportService->setSystem('trelloJson');
$this->eventDispatcher
->method('dispatchTyped')
->willReturnCallback(function (BoardImportGetAllowedEvent $event) {
$event->getService()->addAllowedImportSystem([
'name' => TrelloJsonService::$name,
'class' => TrelloJsonService::class,
'internalName' => 'trelloJson'
]);
});
$data = json_decode(file_get_contents(__DIR__ . '/../../../data/data-trelloJson.json'));
$this->boardImportService->setData($data);
$configFile = __DIR__ . '/../../../data/config-trelloJson.json';
$configInstance = json_decode(file_get_contents($configFile));
$this->boardImportService->setConfigInstance($configInstance);
$this->trelloJsonService = $this->createMock(TrelloJsonService::class);
$this->trelloJsonService
->method('getJsonSchemaPath')
->willReturn($configFile);
$this->boardImportService->setImportSystem($this->trelloJsonService);
$owner = $this->createMock(IUser::class);
$owner
->method('getUID')
->willReturn('admin');
$johndoe = $this->createMock(IUser::class);
$johndoe
->method('getUID')
->willReturn('johndoe');
$this->userManager
->method('get')
->withConsecutive(
['admin'],
['johndoe']
)
->willReturnonConsecutiveCalls(
$owner,
$johndoe
);
}
public function testImportSuccess() {
$this->boardMapper
->expects($this->once())
->method('insert');
$this->trelloJsonService
->method('getAclList')
->willReturn([new Acl()]);
$this->aclMapper
->expects($this->once())
->method('insert');
$this->trelloJsonService
->method('getLabels')
->willReturn([new Label()]);
$this->labelMapper
->expects($this->once())
->method('insert');
$this->trelloJsonService
->method('getStacks')
->willReturn([new Stack()]);
$this->stackMapper
->expects($this->once())
->method('insert');
$this->trelloJsonService
->method('getCards')
->willReturn([new Card()]);
$this->cardMapper
->expects($this->any())
->method('insert');
$this->trelloJsonService
->method('getComments')
->willReturn([
'fakecardid' => [new Comment()]
]);
$this->commentsManager
->expects($this->once())
->method('save');
$this->trelloJsonService
->method('getCardAssignments')
->willReturn([
'fakecardid' => [new Assignment()]
]);
$this->assignmentMapper
->expects($this->once())
->method('insert');
$actual = $this->boardImportService->import();
$this->assertNull($actual);
}
}

View File

@@ -0,0 +1,151 @@
<?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\Service\Importer\Systems;
use OCA\Deck\Service\Importer\BoardImportService;
use OCP\IL10N;
use OCP\IURLGenerator;
use OCP\IUser;
use OCP\IUserManager;
use PHPUnit\Framework\MockObject\MockObject;
class TrelloJsonServiceTest extends \Test\TestCase {
/** @var TrelloJsonService */
private $service;
/** @var IURLGenerator|MockObject */
private $urlGenerator;
/** @var IUserManager|MockObject */
private $userManager;
/** @var IL10N */
private $l10n;
public function setUp(): void {
$this->userManager = $this->createMock(IUserManager::class);
$this->urlGenerator = $this->createMock(IURLGenerator::class);
$this->l10n = $this->createMock(IL10N::class);
$this->service = new TrelloJsonService(
$this->userManager,
$this->urlGenerator,
$this->l10n
);
}
public function testValidateUsersWithoutUsers() {
$importService = $this->createMock(BoardImportService::class);
$this->service->setImportService($importService);
$actual = $this->service->validateUsers();
$this->assertNull($actual);
}
public function testValidateUsersWithInvalidUser() {
$this->expectErrorMessage('Trello user trello_user not found in property "members" of json data');
$importService = $this->createMock(BoardImportService::class);
$importService
->method('getConfig')
->willReturn([
'trello_user' => 'nextcloud_user'
]);
$importService
->method('getData')
->willReturn(json_decode('{"members": [{"username": "othre_trello_user"}]}'));
$this->service->setImportService($importService);
$actual = $this->service->validateUsers();
$this->assertInstanceOf(BoardImportTrelloJsonService::class, $actual);
}
public function testValidateUsersWithNotStringNextcloud() {
$this->expectErrorMessage('User on setting uidRelation is invalid');
$importService = $this->createMock(BoardImportService::class);
$importService
->method('getConfig')
->willReturn([
'trello_user' => []
]);
$importService
->method('getData')
->willReturn(json_decode('{"members": [{"username": "trello_user"}]}'));
$this->service->setImportService($importService);
$actual = $this->service->validateUsers();
$this->assertInstanceOf(BoardImportTrelloJsonService::class, $actual);
}
public function testValidateUsersWithNotFoundUser() {
$this->expectErrorMessage('User on setting uidRelation not found: nextcloud_user');
$importService = $this->createMock(BoardImportService::class);
$importService
->method('getConfig')
->willReturn(json_decode('{"trello_user": "nextcloud_user"}'));
$importService
->method('getData')
->willReturn(json_decode('{"members": [{"username": "trello_user"}]}'));
$this->service->setImportService($importService);
$actual = $this->service->validateUsers();
$this->assertInstanceOf(BoardImportTrelloJsonService::class, $actual);
}
public function testValidateUsersWithValidUsers() {
$importService = $this->createMock(BoardImportService::class);
$importService
->method('getConfig')
->willReturn(json_decode('{"trello_user": "nextcloud_user"}'));
$importService
->method('getData')
->willReturn(json_decode('{"members": [{"id": "fakeid", "username": "trello_user"}]}'));
$fakeUser = $this->createMock(IUser::class);
$this->userManager
->method('get')
->with('nextcloud_user')
->willReturn($fakeUser);
$this->service->setImportService($importService);
$actual = $this->service->validateUsers();
$this->assertNull($actual);
}
public function testGetBoardWithNoName() {
$this->expectErrorMessage('Invalid name of board');
$importService = $this->createMock(BoardImportService::class);
$this->service->setImportService($importService);
$this->service->getBoard();
}
public function testGetBoardWithSuccess() {
$importService = \OC::$server->get(BoardImportService::class);
$data = json_decode(file_get_contents(__DIR__ . '/../../../../data/data-trelloJson.json'));
$importService->setData($data);
$configInstance = json_decode(file_get_contents(__DIR__ . '/../../../../data/config-trelloJson.json'));
$importService->setConfigInstance($configInstance);
$owner = $this->createMock(IUser::class);
$owner
->method('getUID')
->willReturn('owner');
$importService->setConfig('owner', $owner);
$this->service->setImportService($importService);
$actual = $this->service->getBoard();
$this->assertEquals('Test Board Name', $actual->getTitle());
$this->assertEquals('owner', $actual->getOwner());
$this->assertEquals('0800fd', $actual->getColor());
}
}

View File

@@ -0,0 +1,81 @@
<?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\Db\Board;
use OCP\AppFramework\Http;
use OCP\AppFramework\Http\DataResponse;
use OCP\IRequest;
use OCA\Deck\Service\Importer\BoardImportService;
class BoardImportApiControllerTest extends \Test\TestCase {
private $appName = 'deck';
private $userId = 'admin';
/** @var BoardImportApiController */
private $controller;
/** @var BoardImportService|MockObject */
private $boardImportService;
public function setUp(): void {
parent::setUp();
$this->request = $this->createMock(IRequest::class);
$this->boardImportService = $this->createMock(BoardImportService::class);
$this->controller = new BoardImportApiController(
$this->appName,
$this->request,
$this->boardImportService,
$this->userId
);
}
public function testGetAllowedSystems() {
$allowedSystems = [
[
'name' => '',
'class' => '',
'internalName' => 'trelloJson'
]
];
$this->boardImportService
->method('getAllowedImportSystems')
->willReturn($allowedSystems);
$actual = $this->controller->getAllowedSystems();
$expected = new DataResponse($allowedSystems, HTTP::STATUS_OK);
$this->assertEquals($expected, $actual);
}
public function testImport() {
$system = 'trelloJson';
$config = [
'owner' => 'test'
];
$data = [
'name' => 'test'
];
$actual = $this->controller->import($system, $config, $data);
$board = $this->createMock(Board::class);
$this->assertInstanceOf(Board::class, $board);
$this->assertEquals(HTTP::STATUS_OK, $actual->getStatus());
}
}

View File

@@ -98,11 +98,11 @@ class CardControllerTest extends TestCase {
}
public function testAssignLabel() {
$this->cardService->expects($this->once())->method('assignLabel');
$this->controller->assignLabel(1,2);
$this->controller->assignLabel(1, 2);
}
public function testRemoveLabel() {
$this->cardService->expects($this->once())->method('removeLabel');
$this->controller->removeLabel(1,2);
$this->controller->removeLabel(1, 2);
}
public function testReorder() {

View File

@@ -24,56 +24,32 @@
namespace OCA\Deck\Controller;
use OCA\Deck\Db\CardMapper;
use OCA\Deck\Service\CardService;
use OCA\Deck\Service\ConfigService;
use OCA\Deck\Service\PermissionService;
use OCP\EventDispatcher\IEventDispatcher;
use OCP\IInitialStateService;
use OCP\IL10N;
use OCP\IRequest;
use OCP\IURLGenerator;
use PHPUnit\Framework\TestCase;
class PageControllerTest extends TestCase {
class PageControllerTest extends \Test\TestCase {
private $controller;
private $request;
private $l10n;
private $permissionService;
private $initialState;
private $configService;
private $eventDispatcher;
/**
* @var mixed|CardMapper|\PHPUnit\Framework\MockObject\MockObject
*/
private $cardMapper;
/**
* @var mixed|IURLGenerator|\PHPUnit\Framework\MockObject\MockObject
*/
private $urlGenerator;
/**
* @var mixed|CardService|\PHPUnit\Framework\MockObject\MockObject
*/
private $cardService;
public function setUp(): void {
$this->l10n = $this->createMock(IL10N::class);
$this->request = $this->createMock(IRequest::class);
$this->permissionService = $this->createMock(PermissionService::class);
$this->configService = $this->createMock(ConfigService::class);
$this->initialState = $this->createMock(IInitialStateService::class);
$this->eventDispatcher = $this->createMock(IEventDispatcher::class);
$this->cardMapper = $this->createMock(CardMapper::class);
$this->urlGenerator = $this->createMock(IURLGenerator::class);
$this->cardService = $this->createMock(CardService::class);
$this->controller = new PageController(
'deck',
$this->request,
$this->permissionService,
$this->initialState,
$this->configService,
$this->eventDispatcher,
$this->cardMapper,
$this->urlGenerator,
$this->cardService
'deck', $this->request, $this->permissionService, $this->initialState, $this->configService, $this->eventDispatcher
);
}