Compare commits
395 Commits
backport/4
...
stable24
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
93307bb247 | ||
|
|
45f2eedafe | ||
|
|
6fb0f6209c | ||
|
|
043327dc09 | ||
|
|
05aafd9ddb | ||
|
|
5f789962b4 | ||
|
|
ff2c11c796 | ||
|
|
3dee8587b7 | ||
|
|
86751c4b89 | ||
|
|
f70b31c449 | ||
|
|
52c5020ba4 | ||
|
|
1f71d1ab78 | ||
|
|
9119017ce8 | ||
|
|
0c0bb40515 | ||
|
|
0954d4d8a0 | ||
|
|
272da5406a | ||
|
|
aef06d833d | ||
|
|
71948d670e | ||
|
|
22a3efe445 | ||
|
|
6aca034222 | ||
|
|
b37ba6a409 | ||
|
|
ffb99b8010 | ||
|
|
7e33f38b4c | ||
|
|
5391689eb0 | ||
|
|
e289e05c98 | ||
|
|
efb0bef5e1 | ||
|
|
1825ae4711 | ||
|
|
c4bdffbce5 | ||
|
|
7eb57b0ed1 | ||
|
|
3516e7a9c2 | ||
|
|
5a5ee3cd4f | ||
|
|
574aa325a0 | ||
|
|
933e0b097e | ||
|
|
2a6023cf86 | ||
|
|
b1ee1ae156 | ||
|
|
892727fed0 | ||
|
|
cced84af3d | ||
|
|
f33477fe1a | ||
|
|
bafc3e5221 | ||
|
|
67ecf24d86 | ||
|
|
10bda7253e | ||
|
|
1713343dd4 | ||
|
|
c2cf3feb25 | ||
|
|
56792e048a | ||
|
|
dae3144c25 | ||
|
|
31b73fe6bb | ||
|
|
ef686bef34 | ||
|
|
50609944bc | ||
|
|
21727cfb3a | ||
|
|
a93b874b89 | ||
|
|
c573c33926 | ||
|
|
9ff9858e9e | ||
|
|
aa3c80e8d0 | ||
|
|
56c4d45712 | ||
|
|
4f67d2fe60 | ||
|
|
875ac0d6e9 | ||
|
|
741df20b49 | ||
|
|
0ac494df9d | ||
|
|
a8ce89d487 | ||
|
|
2fcbbbbc23 | ||
|
|
4f673fe598 | ||
|
|
02ba8f85ce | ||
|
|
f88d53d7f1 | ||
|
|
6a5eecea76 | ||
|
|
b61667c90b | ||
|
|
6864846d84 | ||
|
|
962256acb0 | ||
|
|
9db8c9af80 | ||
|
|
88bd868e76 | ||
|
|
3c88551ad5 | ||
|
|
0872b1b606 | ||
|
|
65242c2b6c | ||
|
|
a3527542f9 | ||
|
|
28e4b12428 | ||
|
|
628cd333f1 | ||
|
|
ccfaa4886a | ||
|
|
0ef238da0e | ||
|
|
333b904335 | ||
|
|
32a956af02 | ||
|
|
b7dad3134d | ||
|
|
42f76117f2 | ||
|
|
be1c646bd7 | ||
|
|
137fece0b8 | ||
|
|
81e5922dfd | ||
|
|
ccc216d388 | ||
|
|
3f7ab93df9 | ||
|
|
a621aacac0 | ||
|
|
bb7ed1bc31 | ||
|
|
4d0f7ddef3 | ||
|
|
bd13cc09d8 | ||
|
|
cb3925faeb | ||
|
|
ac11571a7d | ||
|
|
fd4564450b | ||
|
|
acbd42d6a4 | ||
|
|
458831dfc7 | ||
|
|
8a5098951c | ||
|
|
99260ae966 | ||
|
|
ea592251f9 | ||
|
|
6c80674ce0 | ||
|
|
060e8cd7db | ||
|
|
f05bd86be9 | ||
|
|
1ce7856cae | ||
|
|
678e4c9569 | ||
|
|
4fe4216cb5 | ||
|
|
85e7305d8d | ||
|
|
090378580c | ||
|
|
3f8efb5368 | ||
|
|
c1f2fd452b | ||
|
|
576c47bdef | ||
|
|
e907aeb8c0 | ||
|
|
c51119d051 | ||
|
|
ee72cb4660 | ||
|
|
d4f2ed263f | ||
|
|
e6f8796b63 | ||
|
|
846443d003 | ||
|
|
144c27d403 | ||
|
|
f374084989 | ||
|
|
0656e53508 | ||
|
|
7506126ef7 | ||
|
|
5214721875 | ||
|
|
34f7b726cd | ||
|
|
ea5baa5339 | ||
|
|
7c3aa301ac | ||
|
|
313bc90ce9 | ||
|
|
be06c7ab70 | ||
|
|
5964da382e | ||
|
|
eaa2facc29 | ||
|
|
13e836760a | ||
|
|
73815827e1 | ||
|
|
34107ad06f | ||
|
|
90535b3c30 | ||
|
|
312376e596 | ||
|
|
04dbe45f29 | ||
|
|
bc38340daf | ||
|
|
9b89897d74 | ||
|
|
f411181dcf | ||
|
|
080465d48a | ||
|
|
2358645037 | ||
|
|
c6545c7c00 | ||
|
|
3baa2fc1a0 | ||
|
|
fd297dc92e | ||
|
|
4608e4b77b | ||
|
|
524b8ba90e | ||
|
|
569e568136 | ||
|
|
d9c563d5bf | ||
|
|
f51956891e | ||
|
|
053ecdacee | ||
|
|
8f755ee3eb | ||
|
|
fa6d819409 | ||
|
|
fa17dfac3a | ||
|
|
92b20efa89 | ||
|
|
2849177998 | ||
|
|
0bbeedb7e3 | ||
|
|
918f22f7a6 | ||
|
|
ecac9b772b | ||
|
|
f0a4469be5 | ||
|
|
f47f4983d9 | ||
|
|
c9b8735804 | ||
|
|
c9d7647200 | ||
|
|
4a8410f0fc | ||
|
|
4fbb383c0f | ||
|
|
98361ff096 | ||
|
|
31f2ca49d5 | ||
|
|
e0e6f86801 | ||
|
|
0bc4e1fa7b | ||
|
|
0d137a372c | ||
|
|
2d1ad75f35 | ||
|
|
e381e021d6 | ||
|
|
59f0813aa5 | ||
|
|
90d14c544f | ||
|
|
02ca7acf4f | ||
|
|
0725b55773 | ||
|
|
1219c0aed0 | ||
|
|
da57b9c7e1 | ||
|
|
e67822167b | ||
|
|
20e3729b86 | ||
|
|
5c3d88dae0 | ||
|
|
2fb86b11ec | ||
|
|
5d9c504842 | ||
|
|
1610968ed7 | ||
|
|
aad4aafc03 | ||
|
|
40e3987ae6 | ||
|
|
d3a77e4a11 | ||
|
|
c93189ff9a | ||
|
|
bca7b3386c | ||
|
|
3c7cc0b09c | ||
|
|
8e35b67a0d | ||
|
|
bfb292c54d | ||
|
|
ab6c775f5c | ||
|
|
e3e9e4596a | ||
|
|
fefe77c245 | ||
|
|
42108cb223 | ||
|
|
72d7a245d2 | ||
|
|
e187a665d5 | ||
|
|
01ab23dc82 | ||
|
|
004814ace7 | ||
|
|
326a38557b | ||
|
|
6e43ed5fce | ||
|
|
8b92610f67 | ||
|
|
6cc919b62d | ||
|
|
3ff47580ad | ||
|
|
a1a2120d43 | ||
|
|
273222bb21 | ||
|
|
1255e5a6c9 | ||
|
|
dace366b6c | ||
|
|
a2f5414c48 | ||
|
|
1887527197 | ||
|
|
6d35c2f1c1 | ||
|
|
51626698c4 | ||
|
|
5ed2b23f76 | ||
|
|
502b5acb69 | ||
|
|
03b5257fea | ||
|
|
39b158f92d | ||
|
|
3163136b73 | ||
|
|
9791444167 | ||
|
|
77acf95106 | ||
|
|
6505cc99ce | ||
|
|
54d1a40eeb | ||
|
|
ab577a95eb | ||
|
|
8537d4acef | ||
|
|
d0fda8a546 | ||
|
|
031f69523d | ||
|
|
3864f29bbd | ||
|
|
ee1b32fcd7 | ||
|
|
6ad6b1680f | ||
|
|
b158d79403 | ||
|
|
2d823e3321 | ||
|
|
9a153356c2 | ||
|
|
eaf5e6ff29 | ||
|
|
585a02a3d2 | ||
|
|
30acd27c94 | ||
|
|
71db398e9a | ||
|
|
d8f44e2c94 | ||
|
|
20c8cb12e3 | ||
|
|
60eadb7035 | ||
|
|
17e71c5c9a | ||
|
|
289c0f72dd | ||
|
|
c2a326d538 | ||
|
|
438bb8cb0d | ||
|
|
b98f8e3b07 | ||
|
|
333206c549 | ||
|
|
5359ec34ef | ||
|
|
ab7da85964 | ||
|
|
7ecb215957 | ||
|
|
df89839b51 | ||
|
|
03ed40df67 | ||
|
|
ad1429b4a2 | ||
|
|
eb03cd6814 | ||
|
|
566846fe4d | ||
|
|
f4c09fefd9 | ||
|
|
bd6e632055 | ||
|
|
75ddc058b8 | ||
|
|
8558ce432a | ||
|
|
7586084980 | ||
|
|
863de715a4 | ||
|
|
04abf47653 | ||
|
|
45c9a8ba27 | ||
|
|
3d10521524 | ||
|
|
ef616f37bd | ||
|
|
e64c36ffce | ||
|
|
837d925536 | ||
|
|
feaeb90f76 | ||
|
|
2ea94a12ad | ||
|
|
c8cf8238b8 | ||
|
|
16f41f74b3 | ||
|
|
3a78eea105 | ||
|
|
8e158972f4 | ||
|
|
b12a1306eb | ||
|
|
1175c8b605 | ||
|
|
4b06ef3ac0 | ||
|
|
5f59f485f2 | ||
|
|
2f28015b91 | ||
|
|
675aff612d | ||
|
|
9ad65af60e | ||
|
|
2210c05e28 | ||
|
|
a189139a04 | ||
|
|
c6a209c63a | ||
|
|
1968d8493b | ||
|
|
a94eb0cd31 | ||
|
|
b2bbf31f46 | ||
|
|
2b959c10dd | ||
|
|
81669635c5 | ||
|
|
7350a2a811 | ||
|
|
e677cbcdac | ||
|
|
6d7461d0d1 | ||
|
|
86d94166f0 | ||
|
|
a664545ebf | ||
|
|
360bd4192b | ||
|
|
da3f2b8ee4 | ||
|
|
78aa411e09 | ||
|
|
2d318c0ac1 | ||
|
|
b58f598044 | ||
|
|
bad2a19be6 | ||
|
|
a89bdab5e3 | ||
|
|
5b2c03f733 | ||
|
|
552e10d79d | ||
|
|
40f39fa21a | ||
|
|
d5c1833638 | ||
|
|
36dd98c93e | ||
|
|
ccbec7f5ac | ||
|
|
0359e89b22 | ||
|
|
e9f8634334 | ||
|
|
1889c0c4f9 | ||
|
|
1ac501db79 | ||
|
|
c9ddd4f997 | ||
|
|
e8a0d7c47c | ||
|
|
8ace53bfe2 | ||
|
|
89c3490015 | ||
|
|
72bc2f438a | ||
|
|
1183459608 | ||
|
|
a4e6c7b746 | ||
|
|
b99fb6e8f6 | ||
|
|
f613a60f7c | ||
|
|
7c77cde669 | ||
|
|
9a7bde4cbd | ||
|
|
b807a71f1f | ||
|
|
33942236f0 | ||
|
|
3dd3301ffc | ||
|
|
0d310d317e | ||
|
|
82515e5731 | ||
|
|
4cd4707559 | ||
|
|
90c14861c4 | ||
|
|
17a51501ec | ||
|
|
86707f18dd | ||
|
|
a28cd746f1 | ||
|
|
10ad995cd0 | ||
|
|
856c5f50c4 | ||
|
|
0a9d96d964 | ||
|
|
1d15921a57 | ||
|
|
b9826cb29f | ||
|
|
94c13bbbdb | ||
|
|
549f9c9045 | ||
|
|
ac16521404 | ||
|
|
4be298f537 | ||
|
|
10dead9d9f | ||
|
|
f9cb601a99 | ||
|
|
efdd3b0056 | ||
|
|
a18f550dc1 | ||
|
|
d808b9fef7 | ||
|
|
f4742259f2 | ||
|
|
3fe5da0ee1 | ||
|
|
17ae3a9a39 | ||
|
|
1ff588a15a | ||
|
|
2bb04ef298 | ||
|
|
055dda909e | ||
|
|
f364709318 | ||
|
|
84de1c0198 | ||
|
|
413017d333 | ||
|
|
5c069e5413 | ||
|
|
9d9cdb0f95 | ||
|
|
74f45e2e54 | ||
|
|
f82c48ad29 | ||
|
|
ece052a20d | ||
|
|
8a9a36ed10 | ||
|
|
82f2a2c363 | ||
|
|
3016659c81 | ||
|
|
b9ab0f14ab | ||
|
|
2d90ed7414 | ||
|
|
5d87f05994 | ||
|
|
9ef183bcaa | ||
|
|
34f18fbd7d | ||
|
|
27fa2bbc54 | ||
|
|
03a88327a5 | ||
|
|
67ac21e688 | ||
|
|
e90f32ca32 | ||
|
|
b21e65027c | ||
|
|
b628532def | ||
|
|
e0b2755b7c | ||
|
|
9e1fbb9852 | ||
|
|
047ca3e203 | ||
|
|
bfcd5357e3 | ||
|
|
461ed6b81f | ||
|
|
ee2969fc5b | ||
|
|
ca32ebd9cb | ||
|
|
4a29617fed | ||
|
|
8eab97a92a | ||
|
|
f453f69cce | ||
|
|
8ca84147e8 | ||
|
|
5e3ddd83fc | ||
|
|
6b985463d8 | ||
|
|
25e0ae2670 | ||
|
|
614c6fbdba | ||
|
|
8efe415558 | ||
|
|
f3ad2a3709 | ||
|
|
25dd71ba04 | ||
|
|
a514e6e168 | ||
|
|
cb5553ea94 | ||
|
|
335ee31c7c | ||
|
|
9b4379727d | ||
|
|
a5fe2f59be | ||
|
|
1b58f7854e | ||
|
|
b6effa468f | ||
|
|
2b512b88c4 | ||
|
|
5cb61cba90 | ||
|
|
0657efe239 |
@@ -3,10 +3,7 @@ root = true
|
|||||||
[*]
|
[*]
|
||||||
charset = utf-8
|
charset = utf-8
|
||||||
end_of_line = lf
|
end_of_line = lf
|
||||||
indent_size = tab
|
|
||||||
indent_style = tab
|
|
||||||
insert_final_newline = true
|
insert_final_newline = true
|
||||||
trim_trailing_whitespace = true
|
|
||||||
|
|
||||||
[*.{js,vue}]
|
[*.{js,vue}]
|
||||||
indent_style = tab
|
indent_style = tab
|
||||||
|
|||||||
@@ -1,5 +1,4 @@
|
|||||||
module.exports = {
|
module.exports = {
|
||||||
root: true,
|
|
||||||
extends: [
|
extends: [
|
||||||
'@nextcloud',
|
'@nextcloud',
|
||||||
],
|
],
|
||||||
@@ -8,7 +7,6 @@ module.exports = {
|
|||||||
'jsdoc/require-param-type': ['off'],
|
'jsdoc/require-param-type': ['off'],
|
||||||
'jsdoc/check-param-names': ['off'],
|
'jsdoc/check-param-names': ['off'],
|
||||||
'jsdoc/no-undefined-types': ['off'],
|
'jsdoc/no-undefined-types': ['off'],
|
||||||
'jsdoc/require-property-description': ['off'],
|
'jsdoc/require-property-description' : ['off']
|
||||||
'import/no-named-as-default-member': ['off']
|
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|||||||
43
.github/workflows/appbuild.yml
vendored
@@ -1,43 +0,0 @@
|
|||||||
name: Package build
|
|
||||||
|
|
||||||
on:
|
|
||||||
push:
|
|
||||||
branches:
|
|
||||||
- main
|
|
||||||
- master
|
|
||||||
- stable*
|
|
||||||
|
|
||||||
jobs:
|
|
||||||
build:
|
|
||||||
runs-on: ubuntu-latest
|
|
||||||
|
|
||||||
strategy:
|
|
||||||
matrix:
|
|
||||||
node-version: [14.x]
|
|
||||||
|
|
||||||
steps:
|
|
||||||
- uses: actions/checkout@v3
|
|
||||||
- name: Use Node.js ${{ matrix.node-version }}
|
|
||||||
uses: actions/setup-node@v3
|
|
||||||
with:
|
|
||||||
node-version: ${{ matrix.node-version }}
|
|
||||||
- name: Set up npm7
|
|
||||||
run: npm i -g npm@7
|
|
||||||
- name: Setup PHP
|
|
||||||
uses: shivammathur/setup-php@2.21.2
|
|
||||||
with:
|
|
||||||
php-version: '7.4'
|
|
||||||
tools: composer
|
|
||||||
- name: install dependencies
|
|
||||||
run: |
|
|
||||||
wget https://github.com/ChristophWurst/krankerl/releases/download/v0.12.2/krankerl_0.12.2_amd64.deb
|
|
||||||
sudo dpkg -i krankerl_0.12.2_amd64.deb
|
|
||||||
- name: package
|
|
||||||
run: |
|
|
||||||
uname -a
|
|
||||||
RUST_BACKTRACE=1 krankerl --version
|
|
||||||
RUST_BACKTRACE=1 krankerl package
|
|
||||||
- uses: actions/upload-artifact@v3
|
|
||||||
with:
|
|
||||||
name: Deck app tarball
|
|
||||||
path: build/artifacts/deck.tar.gz
|
|
||||||
2
.github/workflows/appstore-build-publish.yml
vendored
@@ -66,7 +66,7 @@ jobs:
|
|||||||
run: npm i -g npm@"${{ steps.versions.outputs.npmVersion }}"
|
run: npm i -g npm@"${{ steps.versions.outputs.npmVersion }}"
|
||||||
|
|
||||||
- name: Set up php ${{ env.PHP_VERSION }}
|
- name: Set up php ${{ env.PHP_VERSION }}
|
||||||
uses: shivammathur/setup-php@2.21.2
|
uses: shivammathur/setup-php@2.18.0
|
||||||
with:
|
with:
|
||||||
php-version: ${{ env.PHP_VERSION }}
|
php-version: ${{ env.PHP_VERSION }}
|
||||||
coverage: none
|
coverage: none
|
||||||
|
|||||||
7
.github/workflows/command-rebase.yml
vendored
@@ -9,14 +9,9 @@ on:
|
|||||||
issue_comment:
|
issue_comment:
|
||||||
types: created
|
types: created
|
||||||
|
|
||||||
permissions:
|
|
||||||
contents: read
|
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
rebase:
|
rebase:
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
permissions:
|
|
||||||
contents: none
|
|
||||||
|
|
||||||
# On pull requests and if the comment starts with `/rebase`
|
# On pull requests and if the comment starts with `/rebase`
|
||||||
if: github.event.issue.pull_request != '' && startsWith(github.event.comment.body, '/rebase')
|
if: github.event.issue.pull_request != '' && startsWith(github.event.comment.body, '/rebase')
|
||||||
@@ -37,7 +32,7 @@ jobs:
|
|||||||
token: ${{ secrets.COMMAND_BOT_PAT }}
|
token: ${{ secrets.COMMAND_BOT_PAT }}
|
||||||
|
|
||||||
- name: Automatic Rebase
|
- name: Automatic Rebase
|
||||||
uses: cirrus-actions/rebase@1.7
|
uses: cirrus-actions/rebase@1.5
|
||||||
env:
|
env:
|
||||||
GITHUB_TOKEN: ${{ secrets.COMMAND_BOT_PAT }}
|
GITHUB_TOKEN: ${{ secrets.COMMAND_BOT_PAT }}
|
||||||
|
|
||||||
|
|||||||
112
.github/workflows/cypress.yml
vendored
@@ -1,112 +0,0 @@
|
|||||||
name: Cypress
|
|
||||||
|
|
||||||
on:
|
|
||||||
pull_request:
|
|
||||||
push:
|
|
||||||
branches:
|
|
||||||
- master
|
|
||||||
- stable*
|
|
||||||
|
|
||||||
env:
|
|
||||||
APP_NAME: deck
|
|
||||||
CYPRESS_baseUrl: http://localhost:8081/index.php
|
|
||||||
|
|
||||||
jobs:
|
|
||||||
cypress:
|
|
||||||
|
|
||||||
runs-on: ubuntu-latest
|
|
||||||
|
|
||||||
strategy:
|
|
||||||
fail-fast: false
|
|
||||||
matrix:
|
|
||||||
node-version: [14.x]
|
|
||||||
# containers: [1, 2, 3]
|
|
||||||
php-versions: [ '7.4' ]
|
|
||||||
databases: [ 'sqlite' ]
|
|
||||||
server-versions: [ 'stable25' ]
|
|
||||||
|
|
||||||
steps:
|
|
||||||
- name: Use Node.js ${{ matrix.node-version }}
|
|
||||||
uses: actions/setup-node@v3
|
|
||||||
with:
|
|
||||||
node-version: ${{ matrix.node-version }}
|
|
||||||
- name: Set up npm7
|
|
||||||
run: npm i -g npm@7
|
|
||||||
|
|
||||||
- name: Checkout server
|
|
||||||
uses: actions/checkout@v3
|
|
||||||
with:
|
|
||||||
repository: nextcloud/server
|
|
||||||
ref: ${{ matrix.server-versions }}
|
|
||||||
|
|
||||||
- name: Checkout submodules
|
|
||||||
shell: bash
|
|
||||||
run: |
|
|
||||||
auth_header="$(git config --local --get http.https://github.com/.extraheader)"
|
|
||||||
git submodule sync --recursive
|
|
||||||
git -c "http.extraheader=$auth_header" -c protocol.version=2 submodule update --init --force --recursive --depth=1
|
|
||||||
|
|
||||||
- name: Checkout ${{ env.APP_NAME }}
|
|
||||||
uses: actions/checkout@v3
|
|
||||||
with:
|
|
||||||
path: apps/${{ env.APP_NAME }}
|
|
||||||
|
|
||||||
- name: Set up php ${{ matrix.php-versions }}
|
|
||||||
uses: shivammathur/setup-php@2.21.2
|
|
||||||
with:
|
|
||||||
php-version: ${{ matrix.php-versions }}
|
|
||||||
extensions: mbstring, iconv, fileinfo, intl, sqlite, pdo_sqlite, zip, gd, apcu
|
|
||||||
ini-values:
|
|
||||||
apc.enable_cli=on
|
|
||||||
coverage: none
|
|
||||||
|
|
||||||
- name: Set up Nextcloud
|
|
||||||
env:
|
|
||||||
DB_PORT: 4444
|
|
||||||
PHP_CLI_SERVER_WORKERS: 10
|
|
||||||
run: |
|
|
||||||
mkdir data
|
|
||||||
php occ maintenance:install --verbose --database=${{ matrix.databases }} --database-name=nextcloud --database-host=127.0.0.1 --database-port=$DB_PORT --database-user=root --database-pass=rootpassword --admin-user admin --admin-pass admin
|
|
||||||
php occ config:system:set memcache.local --value="\\OC\\Memcache\\APCu"
|
|
||||||
php occ config:system:set debug --value=true --type=boolean
|
|
||||||
php -f index.php
|
|
||||||
php -S 0.0.0.0:8081 &
|
|
||||||
export OC_PASS=1234561
|
|
||||||
php occ user:add --password-from-env user1
|
|
||||||
php occ user:add --password-from-env user2
|
|
||||||
php occ app:enable deck
|
|
||||||
php occ app:list
|
|
||||||
cd apps/deck
|
|
||||||
composer install --no-dev
|
|
||||||
npm ci
|
|
||||||
npm run build
|
|
||||||
cd ../../
|
|
||||||
curl -v http://localhost:8081/index.php/login
|
|
||||||
|
|
||||||
- name: Cypress run
|
|
||||||
uses: cypress-io/github-action@v4
|
|
||||||
with:
|
|
||||||
record: true
|
|
||||||
parallel: false
|
|
||||||
wait-on: '${{ env.CYPRESS_baseUrl }}'
|
|
||||||
working-directory: 'apps/${{ env.APP_NAME }}'
|
|
||||||
config: defaultCommandTimeout=10000,video=false
|
|
||||||
env:
|
|
||||||
CYPRESS_RECORD_KEY: ${{ secrets.CYPRESS_RECORD_KEY }}
|
|
||||||
npm_package_name: ${{ env.APP_NAME }}
|
|
||||||
|
|
||||||
- name: Upload test failure screenshots
|
|
||||||
uses: actions/upload-artifact@v2
|
|
||||||
if: failure()
|
|
||||||
with:
|
|
||||||
name: Upload screenshots
|
|
||||||
path: apps/${{ env.APP_NAME }}/cypress/screenshots/
|
|
||||||
retention-days: 5
|
|
||||||
|
|
||||||
- name: Upload nextcloud logs
|
|
||||||
uses: actions/upload-artifact@v2
|
|
||||||
if: failure()
|
|
||||||
with:
|
|
||||||
name: Upload nextcloud log
|
|
||||||
path: data/nextcloud.log
|
|
||||||
retention-days: 5
|
|
||||||
@@ -8,20 +8,13 @@ name: Dependabot
|
|||||||
on:
|
on:
|
||||||
pull_request_target:
|
pull_request_target:
|
||||||
branches:
|
branches:
|
||||||
- main
|
|
||||||
- master
|
- master
|
||||||
- stable*
|
- stable*
|
||||||
|
|
||||||
permissions:
|
|
||||||
contents: read
|
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
auto-approve-merge:
|
auto-approve-merge:
|
||||||
if: github.actor == 'dependabot[bot]'
|
if: github.actor == 'dependabot[bot]'
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
permissions:
|
|
||||||
# for hmarr/auto-approve-action to approve PRs
|
|
||||||
pull-requests: write
|
|
||||||
|
|
||||||
steps:
|
steps:
|
||||||
# Github actions bot approve
|
# Github actions bot approve
|
||||||
|
|||||||
19
.github/workflows/fixup.yml
vendored
@@ -3,18 +3,31 @@
|
|||||||
# https://github.com/nextcloud/.github
|
# https://github.com/nextcloud/.github
|
||||||
# https://docs.github.com/en/actions/learn-github-actions/sharing-workflows-with-your-organization
|
# https://docs.github.com/en/actions/learn-github-actions/sharing-workflows-with-your-organization
|
||||||
|
|
||||||
name: Pull request checks
|
name: Block fixup and squash commits
|
||||||
|
|
||||||
on: pull_request
|
on:
|
||||||
|
pull_request:
|
||||||
|
types: [opened, ready_for_review, reopened, synchronize]
|
||||||
|
|
||||||
|
permissions:
|
||||||
|
contents: read
|
||||||
|
|
||||||
|
concurrency:
|
||||||
|
group: fixup-${{ github.head_ref || github.run_id }}
|
||||||
|
cancel-in-progress: true
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
commit-message-check:
|
commit-message-check:
|
||||||
|
if: github.event.pull_request.draft == false
|
||||||
|
|
||||||
|
permissions:
|
||||||
|
pull-requests: write
|
||||||
name: Block fixup and squash commits
|
name: Block fixup and squash commits
|
||||||
|
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
|
|
||||||
steps:
|
steps:
|
||||||
- name: Run check
|
- name: Run check
|
||||||
uses: xt0rted/block-autosquash-commits-action@v2
|
uses: skjnldsv/block-fixup-merge-action@42d26e1b536ce61e5cf467d65fb76caf4aa85acf # v1
|
||||||
with:
|
with:
|
||||||
repo-token: ${{ secrets.GITHUB_TOKEN }}
|
repo-token: ${{ secrets.GITHUB_TOKEN }}
|
||||||
|
|||||||
30
.github/workflows/integration.yml
vendored
@@ -2,14 +2,6 @@ name: Integration tests
|
|||||||
|
|
||||||
on:
|
on:
|
||||||
pull_request:
|
pull_request:
|
||||||
paths:
|
|
||||||
- '.github/workflows/integration.yml'
|
|
||||||
- 'appinfo/**'
|
|
||||||
- 'lib/**'
|
|
||||||
- 'templates/**'
|
|
||||||
- 'tests/**'
|
|
||||||
- 'composer.json'
|
|
||||||
- 'composer.lock'
|
|
||||||
push:
|
push:
|
||||||
branches:
|
branches:
|
||||||
- master
|
- master
|
||||||
@@ -27,7 +19,7 @@ jobs:
|
|||||||
matrix:
|
matrix:
|
||||||
php-versions: ['7.4']
|
php-versions: ['7.4']
|
||||||
databases: ['sqlite', 'mysql', 'pgsql']
|
databases: ['sqlite', 'mysql', 'pgsql']
|
||||||
server-versions: ['stable25']
|
server-versions: ['stable24']
|
||||||
|
|
||||||
name: php${{ matrix.php-versions }}-${{ matrix.databases }}-${{ matrix.server-versions }}
|
name: php${{ matrix.php-versions }}-${{ matrix.databases }}-${{ matrix.server-versions }}
|
||||||
|
|
||||||
@@ -62,24 +54,32 @@ jobs:
|
|||||||
auth_header="$(git config --local --get http.https://github.com/.extraheader)"
|
auth_header="$(git config --local --get http.https://github.com/.extraheader)"
|
||||||
git submodule sync --recursive
|
git submodule sync --recursive
|
||||||
git -c "http.extraheader=$auth_header" -c protocol.version=2 submodule update --init --force --recursive --depth=1
|
git -c "http.extraheader=$auth_header" -c protocol.version=2 submodule update --init --force --recursive --depth=1
|
||||||
cd build/integration && composer require --dev phpunit/phpunit:~9
|
cd build/integration && composer require --dev phpunit/phpunit:~8
|
||||||
|
|
||||||
- name: Checkout app
|
- name: Checkout app
|
||||||
uses: actions/checkout@v3
|
uses: actions/checkout@v3
|
||||||
with:
|
with:
|
||||||
path: apps/${{ env.APP_NAME }}
|
path: apps/${{ env.APP_NAME }}
|
||||||
|
|
||||||
|
- name: Checkout activity
|
||||||
|
uses: actions/checkout@c85c95e3d7251135ab7dc9ce3241c5835cc595a9 # v3.5.3
|
||||||
|
with:
|
||||||
|
repository: nextcloud/activity
|
||||||
|
ref: ${{ matrix.server-versions }}
|
||||||
|
path: apps/activity
|
||||||
|
|
||||||
- name: Set up php ${{ matrix.php-versions }}
|
- name: Set up php ${{ matrix.php-versions }}
|
||||||
uses: shivammathur/setup-php@2.21.2
|
uses: shivammathur/setup-php@2.25.4
|
||||||
with:
|
with:
|
||||||
php-version: ${{ matrix.php-versions }}
|
php-version: ${{ matrix.php-versions }}
|
||||||
tools: phpunit
|
extensions: bz2, ctype, curl, dom, fileinfo, gd, iconv, intl, json, libxml, mbstring, openssl, pcntl, posix, session, simplexml, xmlreader, xmlwriter, zip, zlib, sqlite, pdo_sqlite, mysql, pdo_mysql, pgsql, pdo_pgsql, apcu
|
||||||
extensions: mbstring, iconv, fileinfo, intl, sqlite, pdo_sqlite, mysql, pdo_mysql, pgsql, pdo_pgsql,
|
ini-values:
|
||||||
|
apc.enable_cli=on
|
||||||
coverage: none
|
coverage: none
|
||||||
|
|
||||||
- name: Set up PHPUnit
|
- name: Set up dependencies
|
||||||
working-directory: apps/${{ env.APP_NAME }}
|
working-directory: apps/${{ env.APP_NAME }}
|
||||||
run: composer i
|
run: composer i --no-dev
|
||||||
|
|
||||||
- name: Set up Nextcloud
|
- name: Set up Nextcloud
|
||||||
run: |
|
run: |
|
||||||
|
|||||||
4
.github/workflows/lint.yml
vendored
@@ -19,7 +19,7 @@ jobs:
|
|||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v3
|
- uses: actions/checkout@v3
|
||||||
- name: Set up php${{ matrix.php-versions }}
|
- name: Set up php${{ matrix.php-versions }}
|
||||||
uses: shivammathur/setup-php@2.21.2
|
uses: shivammathur/setup-php@2.18.0
|
||||||
with:
|
with:
|
||||||
php-version: ${{ matrix.php-versions }}
|
php-version: ${{ matrix.php-versions }}
|
||||||
coverage: none
|
coverage: none
|
||||||
@@ -33,7 +33,7 @@ jobs:
|
|||||||
- name: Checkout
|
- name: Checkout
|
||||||
uses: actions/checkout@v3
|
uses: actions/checkout@v3
|
||||||
- name: Set up php
|
- name: Set up php
|
||||||
uses: shivammathur/setup-php@2.21.2
|
uses: shivammathur/setup-php@2.18.0
|
||||||
with:
|
with:
|
||||||
php-version: 7.4
|
php-version: 7.4
|
||||||
coverage: none
|
coverage: none
|
||||||
|
|||||||
2
.github/workflows/nightly.yml
vendored
@@ -25,7 +25,7 @@ jobs:
|
|||||||
- name: Set up npm7
|
- name: Set up npm7
|
||||||
run: npm i -g npm@7
|
run: npm i -g npm@7
|
||||||
- name: Setup PHP
|
- name: Setup PHP
|
||||||
uses: shivammathur/setup-php@2.21.2
|
uses: shivammathur/setup-php@2.18.0
|
||||||
with:
|
with:
|
||||||
php-version: '7.4'
|
php-version: '7.4'
|
||||||
tools: composer
|
tools: composer
|
||||||
|
|||||||
15
.github/workflows/phpunit.yml
vendored
@@ -2,14 +2,6 @@ name: PHPUnit
|
|||||||
|
|
||||||
on:
|
on:
|
||||||
pull_request:
|
pull_request:
|
||||||
paths:
|
|
||||||
- '.github/workflows/phpunit.yml'
|
|
||||||
- 'appinfo/**'
|
|
||||||
- 'lib/**'
|
|
||||||
- 'templates/**'
|
|
||||||
- 'tests/**'
|
|
||||||
- 'composer.json'
|
|
||||||
- 'composer.lock'
|
|
||||||
push:
|
push:
|
||||||
branches:
|
branches:
|
||||||
- master
|
- master
|
||||||
@@ -26,9 +18,9 @@ jobs:
|
|||||||
strategy:
|
strategy:
|
||||||
fail-fast: false
|
fail-fast: false
|
||||||
matrix:
|
matrix:
|
||||||
php-versions: ['7.4', '8.0', '8.1']
|
php-versions: ['7.4', '8.0']
|
||||||
databases: ['sqlite', 'mysql', 'pgsql']
|
databases: ['sqlite', 'mysql', 'pgsql']
|
||||||
server-versions: ['stable25']
|
server-versions: ['stable24']
|
||||||
|
|
||||||
name: php${{ matrix.php-versions }}-${{ matrix.databases }}-${{ matrix.server-versions }}
|
name: php${{ matrix.php-versions }}-${{ matrix.databases }}-${{ matrix.server-versions }}
|
||||||
|
|
||||||
@@ -70,11 +62,12 @@ jobs:
|
|||||||
path: apps/${{ env.APP_NAME }}
|
path: apps/${{ env.APP_NAME }}
|
||||||
|
|
||||||
- name: Set up php ${{ matrix.php-versions }}
|
- name: Set up php ${{ matrix.php-versions }}
|
||||||
uses: shivammathur/setup-php@2.21.2
|
uses: shivammathur/setup-php@2.24.0
|
||||||
with:
|
with:
|
||||||
php-version: ${{ matrix.php-versions }}
|
php-version: ${{ matrix.php-versions }}
|
||||||
tools: phpunit
|
tools: phpunit
|
||||||
extensions: zip, gd, mbstring, iconv, fileinfo, intl, sqlite, pdo_sqlite, mysql, pdo_mysql, pgsql, pdo_pgsql
|
extensions: zip, gd, mbstring, iconv, fileinfo, intl, sqlite, pdo_sqlite, mysql, pdo_mysql, pgsql, pdo_pgsql
|
||||||
|
ini-file: development
|
||||||
coverage: none
|
coverage: none
|
||||||
|
|
||||||
- name: Set up PHPUnit
|
- name: Set up PHPUnit
|
||||||
|
|||||||
127
CHANGELOG.md
@@ -1,106 +1,59 @@
|
|||||||
# Changelog
|
# Changelog
|
||||||
All notable changes to this project will be documented in this file.
|
All notable changes to this project will be documented in this file.
|
||||||
|
|
||||||
## 1.8.5
|
## 1.7.5
|
||||||
|
|
||||||
### Fixed
|
### Fixed
|
||||||
|
|
||||||
- fix: Properly overwrite z-index of datepicker above modal [#4667](https://github.com/nextcloud/deck/pull/4667)
|
- Fix(occ): set user id for permission sevice from board service [#5074](https://github.com/nextcloud/deck/pull/5074)
|
||||||
|
- Fix small issues around delete/undo @juliushaertl [#5446](https://github.com/nextcloud/deck/pull/5446)
|
||||||
|
- Fix deleted card/board issues @juliushaertl [#5449](https://github.com/nextcloud/deck/pull/5449)
|
||||||
|
|
||||||
|
## 1.7.4
|
||||||
## 1.8.4
|
|
||||||
|
|
||||||
### Fixed
|
### Fixed
|
||||||
|
|
||||||
- fix: Use passed userid when getting attachment folder [#4540](https://github.com/nextcloud/deck/pull/4540)
|
- Gracefully handle not found card for a share [#4569](https://github.com/nextcloud/deck/pull/4569)
|
||||||
- fix: Adapt NcEmptyContent usages to new slots [#4563](https://github.com/nextcloud/deck/pull/4563)
|
- fix: Use passed userid when getting attachment folder [#4541](https://github.com/nextcloud/deck/pull/4541)
|
||||||
- Gracefully handle not found card for a share [#4568](https://github.com/nextcloud/deck/pull/4568)
|
- fix: Append datetime picker to body to avoid cut off [#4646](https://github.com/nextcloud/deck/pull/4646)
|
||||||
- allow user to toggle visibility of the calendar for a deck board [#4626](https://github.com/nextcloud/deck/pull/4626)
|
- Permanently delete deck cards marked as deleted after 5 min in a cron job [#4302](https://github.com/nextcloud/deck/pull/4302)
|
||||||
- fix: Append datetime picker to body to avoid cut off [#4645](https://github.com/nextcloud/deck/pull/4645)
|
- Fix : Overlapping expiry dates on tags [#4538](https://github.com/nextcloud/deck/pull/4538)
|
||||||
- Fix : Overlapping expiry dates on tags [#4536](https://github.com/nextcloud/deck/pull/4536)
|
- Update dependencies
|
||||||
- Better display of card dates (creation and change dates) [#4620](https://github.com/nextcloud/deck/pull/4620)
|
|
||||||
- Dependency updates
|
|
||||||
|
|
||||||
## 1.8.3
|
## 1.7.3
|
||||||
|
|
||||||
### Fixed
|
### Fixed
|
||||||
|
|
||||||
- Fix component renaming so that acl works on shares again [#4328](https://github.com/nextcloud/deck/pull/4328)
|
- feat: add validators to check values in services @juliushaertl [#4176](https://github.com/nextcloud/deck/pull/4176)
|
||||||
- Permanently delete deck cards marked as deleted after 5 min in a cron job [#4301](https://github.com/nextcloud/deck/pull/4301)
|
- Add integration test for attachment handling on cards [#4178](https://github.com/nextcloud/deck/pull/4178)
|
||||||
- Dependency updates
|
- disables autocomplete on card creation @juliushaertl [#4182](https://github.com/nextcloud/deck/pull/4182)
|
||||||
|
- minor style fixes [#4202](https://github.com/nextcloud/deck/pull/4202)
|
||||||
|
|
||||||
|
|
||||||
## 1.8.2
|
## 1.7.2
|
||||||
|
|
||||||
### Fixed
|
### Fixed
|
||||||
|
|
||||||
- minor style fixes [#4201](https://github.com/nextcloud/deck/pull/4201)
|
- Cache user membership for circles [#4132](https://github.com/nextcloud/deck/pull/4132)
|
||||||
- feat: add validators to check values in services [#4174](https://github.com/nextcloud/deck/pull/4174)
|
- Set event link also for notifications that get emitted from activities [#4118](https://github.com/nextcloud/deck/pull/4118)
|
||||||
- Add integration test for attachment handling on cards [#4179](https://github.com/nextcloud/deck/pull/4179)
|
- Fix Card menu not displaying when description is not set [#4103](https://github.com/nextcloud/deck/pull/4103)
|
||||||
- Add missing userId property [#4198](https://github.com/nextcloud/deck/pull/4198)
|
- disable Create card button while no stack is chosen [#4019](https://github.com/nextcloud/deck/pull/4019)
|
||||||
|
- to nextcloud/OCP package in stable24 [#4093](https://github.com/nextcloud/deck/pull/4093)
|
||||||
|
- Fix attachment creator name: show display name [#4037](https://github.com/nextcloud/deck/pull/4037)
|
||||||
|
- Use capped memory cache for board permissions [#3997](https://github.com/nextcloud/deck/pull/3997)
|
||||||
|
- Improve CalDAV integration performance [#3995](https://github.com/nextcloud/deck/pull/3995)
|
||||||
|
- Fetch attachment folder for the correct user during cron job [#3959](https://github.com/nextcloud/deck/pull/3959)
|
||||||
|
- Switch to 'markdown-it-task-checkbox' for rendering of task lists [#3925](https://github.com/nextcloud/deck/pull/3925)
|
||||||
|
- Prevent opening card and applyLabelFilter on card drag end [#3917](https://github.com/nextcloud/deck/pull/3917)
|
||||||
|
- Fix for issue #3637 [#3901](https://github.com/nextcloud/deck/pull/3901)
|
||||||
|
- Fix z-index for deck sidebar [#3885](https://github.com/nextcloud/deck/pull/3885)
|
||||||
|
|
||||||
## 1.8.1
|
## 1.7.1
|
||||||
|
|
||||||
### Fixed
|
### Fixed
|
||||||
|
- Align Duedate-delete icon properly - fixes nextcloud/deck#3791 [#3817](https://github.com/nextcloud/deck/pull/3817)
|
||||||
- Fix Duedate activity @nickvergessen [#4155](https://github.com/nextcloud/deck/pull/4155)
|
- Increase file count after sharing [#3806](https://github.com/nextcloud/deck/pull/3806)
|
||||||
|
- Fetch full board data after cloning [#3781](https://github.com/nextcloud/deck/pull/3781)
|
||||||
## 1.8.0
|
|
||||||
|
|
||||||
### Added
|
|
||||||
|
|
||||||
- Nextcloud 25 compatibility
|
|
||||||
- Performance improvements
|
|
||||||
- Use capped memory cache for board permissions @juliushaertl [#3980](https://github.com/nextcloud/deck/pull/3980)
|
|
||||||
- Improve CalDAV integration performance @juliushaertl [#3982](https://github.com/nextcloud/deck/pull/3982)
|
|
||||||
- Simpify query for getting shared files @juliushaertl [#3983](https://github.com/nextcloud/deck/pull/3983)
|
|
||||||
- Accessibility improvements
|
|
||||||
- Add a11y label for sidebar button @marcelklehr [#3986](https://github.com/nextcloud/deck/pull/3986)
|
|
||||||
- Improve filter popover accessibility @juliushaertl [#3820](https://github.com/nextcloud/deck/pull/3820)
|
|
||||||
- Set ids to skip to content/navigation @juliushaertl [#3924](https://github.com/nextcloud/deck/pull/3924)
|
|
||||||
- Invert icons properly in dark mode @juliushaertl [#3939](https://github.com/nextcloud/deck/pull/3939)
|
|
||||||
- Implement card reference widget @eneiluj [#4031](https://github.com/nextcloud/deck/pull/4031)
|
|
||||||
- Implement new dashboard widget interfaces @eneiluj [#4033](https://github.com/nextcloud/deck/pull/4033)
|
|
||||||
- Add related resources panel to board sharing tab sidebar @Pytal [#4000](https://github.com/nextcloud/deck/pull/4000)
|
|
||||||
|
|
||||||
### Fixed
|
|
||||||
|
|
||||||
- Fix sorting stacks [#4116](https://github.com/nextcloud/deck/pull/4116)
|
|
||||||
- Fix issue with duedate format [#4140](https://github.com/nextcloud/deck/pull/4140)
|
|
||||||
- Fix missing icon for activity rendering [#4090](https://github.com/nextcloud/deck/pull/4090)
|
|
||||||
- disables autocomplete on card creation [#4142](https://github.com/nextcloud/deck/pull/4142)
|
|
||||||
- Set event link also for notifications that get emitted from activities [#4117](https://github.com/nextcloud/deck/pull/4117)
|
|
||||||
- Fix attachment creator name: show display name @eneiluj [#4036](https://github.com/nextcloud/deck/pull/4036)
|
|
||||||
- Fix reference provider when caching @eneiluj [#4056](https://github.com/nextcloud/deck/pull/4056)
|
|
||||||
- Use global import for nextcloud-vue [#4072](https://github.com/nextcloud/deck/pull/4072)
|
|
||||||
- Disable Create card button while no stack is chosen @icewind1991 [#4014](https://github.com/nextcloud/deck/pull/4014)
|
|
||||||
- Adjust testing matrix for Nextcloud 25 on stable25 @nickvergessen [#4068](https://github.com/nextcloud/deck/pull/4068)
|
|
||||||
- Fix Card menu not displaying when description is not set @marcelklehr [#4105](https://github.com/nextcloud/deck/pull/4105)
|
|
||||||
- Reference widget adjustments for Text [#4075](https://github.com/nextcloud/deck/pull/4075)
|
|
||||||
- use OCP\Collaboration\Reference\Reference [#4078](https://github.com/nextcloud/deck/pull/4078)
|
|
||||||
- Cache user membership for circles [#4141](https://github.com/nextcloud/deck/pull/4141)
|
|
||||||
- set last modified when the card was found. Fixes #3763 @ylebre [#3796](https://github.com/nextcloud/deck/pull/3796)
|
|
||||||
- Increase file count after sharing @luka-nextcloud [#3682](https://github.com/nextcloud/deck/pull/3682)
|
|
||||||
- Align Duedate-delete icon properly - fixes nextcloud/deck#3791 @Ben-Ro [#3811](https://github.com/nextcloud/deck/pull/3811)
|
|
||||||
- Fix for issue #3637 @flummer [#3833](https://github.com/nextcloud/deck/pull/3833)
|
|
||||||
- Switch to 'markdown-it-task-checkbox' for rendering of task lists @q-wertz [#3898](https://github.com/nextcloud/deck/pull/3898)
|
|
||||||
- Make rename functions accessibly by keyboard navigation @juliushaertl [#3813](https://github.com/nextcloud/deck/pull/3813)
|
|
||||||
- Prevent opening card and applyLabelFilter on card drag end @eneiluj [#3916](https://github.com/nextcloud/deck/pull/3916)
|
|
||||||
- Inserted required property in the rename list field, to prevent the l… @mstolf [#3862](https://github.com/nextcloud/deck/pull/3862)
|
|
||||||
- Fix share provider for master changes @nickvergessen [#3942](https://github.com/nextcloud/deck/pull/3942)
|
|
||||||
- Fetch attachment folder for the correct user during cron job @juliushaertl [#3952](https://github.com/nextcloud/deck/pull/3952)
|
|
||||||
- Fix z-index for deck sidebar @Raudius [#3884](https://github.com/nextcloud/deck/pull/3884)
|
|
||||||
|
|
||||||
### Other
|
|
||||||
|
|
||||||
- Switch from OC::$server->get to OCP\Server::get @CarlSchwan [#3801](https://github.com/nextcloud/deck/pull/3801)
|
|
||||||
- Add performance section in README @eneiluj [#3830](https://github.com/nextcloud/deck/pull/3830)
|
|
||||||
- Fix static analysis by stubbing more circle methods @juliushaertl [#3900](https://github.com/nextcloud/deck/pull/3900)
|
|
||||||
- fix(docs): fix links to JSON schemas for Trello @wiktor2200 [#3872](https://github.com/nextcloud/deck/pull/3872)
|
|
||||||
- Move to OCP\Collaboration\Resources\LoadAdditionalScriptsEvent @juliushaertl [#3818](https://github.com/nextcloud/deck/pull/3818)
|
|
||||||
- Rename settings to deck settings @PVince81 [#3928](https://github.com/nextcloud/deck/pull/3928)
|
|
||||||
- SCSS cleanup @juliushaertl [#3803](https://github.com/nextcloud/deck/pull/3803)
|
|
||||||
- Hide deprecated projects in sidebar and card details by default @Pytal [#3984](https://github.com/nextcloud/deck/pull/3984)
|
|
||||||
|
|
||||||
## 1.7.0
|
## 1.7.0
|
||||||
|
|
||||||
@@ -152,7 +105,7 @@ All notable changes to this project will be documented in this file.
|
|||||||
- Adapt the card modal to upstream changes [#3764](https://github.com/nextcloud/deck/pull/3764)
|
- Adapt the card modal to upstream changes [#3764](https://github.com/nextcloud/deck/pull/3764)
|
||||||
- Fix text selection in dark mode and modal view [#3765](https://github.com/nextcloud/deck/pull/3765)
|
- Fix text selection in dark mode and modal view [#3765](https://github.com/nextcloud/deck/pull/3765)
|
||||||
- Add missing indices [#3754](https://github.com/nextcloud/deck/pull/3754)
|
- Add missing indices [#3754](https://github.com/nextcloud/deck/pull/3754)
|
||||||
|
- Handle qb mapper exception messages properly @juliushaertl [#3769](https://github.com/nextcloud/deck/pull/3769)
|
||||||
|
|
||||||
## 1.6.0-beta1
|
## 1.6.0-beta1
|
||||||
|
|
||||||
@@ -523,7 +476,7 @@ Android app team for helping to improve our REST API:
|
|||||||
- Fix comment activities on Nextcloud 15
|
- Fix comment activities on Nextcloud 15
|
||||||
- Fix issues with Edge
|
- Fix issues with Edge
|
||||||
- API: Fix numeric types that were returned as strings
|
- API: Fix numeric types that were returned as strings
|
||||||
- API: Fix If-Modified-Since header parsing
|
- API: Fix If-Modified-Since header parsing
|
||||||
|
|
||||||
|
|
||||||
## 0.5.1 - 2018-12-05
|
## 0.5.1 - 2018-12-05
|
||||||
@@ -650,7 +603,7 @@ Android app team for helping to improve our REST API:
|
|||||||
### Fixed
|
### Fixed
|
||||||
- Various frontend fixes
|
- Various frontend fixes
|
||||||
- Fix sidebar drag issues
|
- Fix sidebar drag issues
|
||||||
- Improvements for IE11
|
- Improvements for IE11
|
||||||
- Fix bug when draging a card to an empty stack
|
- Fix bug when draging a card to an empty stack
|
||||||
|
|
||||||
## 0.2.1 - 2017-07-04
|
## 0.2.1 - 2017-07-04
|
||||||
@@ -724,7 +677,7 @@ Android app team for helping to improve our REST API:
|
|||||||
|
|
||||||
### Fixed
|
### Fixed
|
||||||
- Various styling improvements
|
- Various styling improvements
|
||||||
- Fix problems with MySQL and PostgreSQL
|
- Fix problems with MySQL and PostgreSQL
|
||||||
- Select first color by default when creating boards
|
- Select first color by default when creating boards
|
||||||
- Fix error when changing board permissions
|
- Fix error when changing board permissions
|
||||||
|
|
||||||
@@ -732,9 +685,9 @@ Android app team for helping to improve our REST API:
|
|||||||
|
|
||||||
### Added
|
### Added
|
||||||
- Sharing boards with other users
|
- Sharing boards with other users
|
||||||
- Create and manage boards
|
- Create and manage boards
|
||||||
- Sort cards on stacks by drag-and-drop
|
- Sort cards on stacks by drag-and-drop
|
||||||
- Assign labels
|
- Assign labels
|
||||||
- Markdown notes for each card
|
- Markdown notes for each card
|
||||||
- Archive cards
|
- Archive cards
|
||||||
|
|
||||||
|
|||||||
17
README.md
@@ -24,9 +24,9 @@ Deck is a kanban style organization tool aimed at personal planning and project
|
|||||||
### 3rd-Party Integrations
|
### 3rd-Party Integrations
|
||||||
|
|
||||||
- [trello-to-deck](https://github.com/maxammann/trello-to-deck) - Migrates cards from Trello
|
- [trello-to-deck](https://github.com/maxammann/trello-to-deck) - Migrates cards from Trello
|
||||||
- [mail2deck](https://github.com/newroco/mail2deck) - Provides an "email in" solution
|
- [mail2deck](https://github.com/newroco/mail2deck) - Provides an "email in" solution
|
||||||
- [A-deck](https://github.com/leoossa/A-deck) - Chrome Extension that allows to create new card in selected stack based on current tab
|
- [A-deck](https://github.com/leoossa/A-deck) - Chrome Extension that allows to create new card in selected stack based on current tab
|
||||||
|
|
||||||
## Installation/Update
|
## Installation/Update
|
||||||
|
|
||||||
This app is supposed to work on the two latest Nextcloud versions.
|
This app is supposed to work on the two latest Nextcloud versions.
|
||||||
@@ -52,17 +52,6 @@ Please make sure you have installed the following dependencies: `make, which, ta
|
|||||||
|
|
||||||
Instead of setting everything up manually, you can just [download the nightly build](https://github.com/nextcloud/deck/releases/tag/nightly) instead. These builds are updated every 24 hours, and are pre-configured with all the needed dependencies.
|
Instead of setting everything up manually, you can just [download the nightly build](https://github.com/nextcloud/deck/releases/tag/nightly) instead. These builds are updated every 24 hours, and are pre-configured with all the needed dependencies.
|
||||||
|
|
||||||
## Performance limitations
|
|
||||||
|
|
||||||
Deck is not yet ready for intensive usage.
|
|
||||||
A lot of database queries are generated when the number of boards, cards and attachments is high.
|
|
||||||
For example, a user having access to 13 boards, with each board having on average 100 cards,
|
|
||||||
and each card having on average 5 attachments,
|
|
||||||
would generate 6500 database queries when doing the file related queries
|
|
||||||
which would increase the page loading time significantly.
|
|
||||||
|
|
||||||
Improvements on Nextcloud server and Deck itself will improve the situation.
|
|
||||||
|
|
||||||
## Developing
|
## Developing
|
||||||
|
|
||||||
### PHP
|
### PHP
|
||||||
@@ -71,8 +60,6 @@ Nothing to prepare, just dig into the code.
|
|||||||
|
|
||||||
### JavaScript
|
### JavaScript
|
||||||
|
|
||||||
This requires at least Node 14 and npm 7 to be installed.
|
|
||||||
|
|
||||||
Deck requires running a `make build-js` to install npm dependencies and build the JavaScript code using webpack. While developing you can also use `make watch` to rebuild everytime the code changes.
|
Deck requires running a `make build-js` to install npm dependencies and build the JavaScript code using webpack. While developing you can also use `make watch` to rebuild everytime the code changes.
|
||||||
|
|
||||||
#### Hot reloading
|
#### Hot reloading
|
||||||
|
|||||||
@@ -16,7 +16,7 @@
|
|||||||
- 🚀 Get your project organized
|
- 🚀 Get your project organized
|
||||||
|
|
||||||
</description>
|
</description>
|
||||||
<version>1.8.5</version>
|
<version>1.7.5</version>
|
||||||
<licence>agpl</licence>
|
<licence>agpl</licence>
|
||||||
<author>Julius Härtl</author>
|
<author>Julius Härtl</author>
|
||||||
<namespace>Deck</namespace>
|
<namespace>Deck</namespace>
|
||||||
@@ -34,7 +34,7 @@
|
|||||||
<database min-version="9.4">pgsql</database>
|
<database min-version="9.4">pgsql</database>
|
||||||
<database>sqlite</database>
|
<database>sqlite</database>
|
||||||
<database min-version="8.0">mysql</database>
|
<database min-version="8.0">mysql</database>
|
||||||
<nextcloud min-version="25" max-version="25"/>
|
<nextcloud min-version="24" max-version="24"/>
|
||||||
</dependencies>
|
</dependencies>
|
||||||
<background-jobs>
|
<background-jobs>
|
||||||
<job>OCA\Deck\Cron\DeleteCron</job>
|
<job>OCA\Deck\Cron\DeleteCron</job>
|
||||||
|
|||||||
@@ -19,7 +19,7 @@
|
|||||||
"symfony/event-dispatcher": "^4.0",
|
"symfony/event-dispatcher": "^4.0",
|
||||||
"vimeo/psalm": "^4.3",
|
"vimeo/psalm": "^4.3",
|
||||||
"php-parallel-lint/php-parallel-lint": "^1.2",
|
"php-parallel-lint/php-parallel-lint": "^1.2",
|
||||||
"nextcloud/ocp": "dev-stable25"
|
"nextcloud/ocp": "dev-stable24"
|
||||||
},
|
},
|
||||||
"config": {
|
"config": {
|
||||||
"optimize-autoloader": true,
|
"optimize-autoloader": true,
|
||||||
@@ -36,14 +36,14 @@
|
|||||||
"cs:check": "php-cs-fixer fix --dry-run --diff",
|
"cs:check": "php-cs-fixer fix --dry-run --diff",
|
||||||
"cs:fix": "php-cs-fixer fix",
|
"cs:fix": "php-cs-fixer fix",
|
||||||
"psalm": "psalm",
|
"psalm": "psalm",
|
||||||
"psalm:update-baseline": "psalm --update-baseline",
|
|
||||||
"psalm:fix": "psalm --alter --issues=InvalidReturnType,InvalidNullableReturnType,MismatchingDocblockParamType,MismatchingDocblockReturnType,MissingParamType,InvalidFalsableReturnType",
|
"psalm:fix": "psalm --alter --issues=InvalidReturnType,InvalidNullableReturnType,MismatchingDocblockParamType,MismatchingDocblockReturnType,MissingParamType,InvalidFalsableReturnType",
|
||||||
"test": [
|
"test": [
|
||||||
"@test:unit",
|
"@test:unit",
|
||||||
"@test:integration"
|
"@test:integration"
|
||||||
],
|
],
|
||||||
"test:unit": "phpunit -c tests/phpunit.xml",
|
"test:unit": "phpunit -c tests/phpunit.xml",
|
||||||
"test:integration": "phpunit -c tests/phpunit.integration.xml && cd tests/integration && ./run.sh"
|
"test:integration": "phpunit -c tests/phpunit.integration.xml",
|
||||||
|
"test:api": "cd tests/integration && ./run.sh"
|
||||||
},
|
},
|
||||||
"autoload-dev": {
|
"autoload-dev": {
|
||||||
"psr-4": {
|
"psr-4": {
|
||||||
|
|||||||
369
composer.lock
generated
@@ -4,7 +4,7 @@
|
|||||||
"Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies",
|
"Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies",
|
||||||
"This file is @generated automatically"
|
"This file is @generated automatically"
|
||||||
],
|
],
|
||||||
"content-hash": "445858d371d9a1c7057d0603c566966a",
|
"content-hash": "fdd039ec52f9c829403494423b527e10",
|
||||||
"packages": [
|
"packages": [
|
||||||
{
|
{
|
||||||
"name": "cogpowered/finediff",
|
"name": "cogpowered/finediff",
|
||||||
@@ -63,16 +63,16 @@
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "justinrainbow/json-schema",
|
"name": "justinrainbow/json-schema",
|
||||||
"version": "5.2.12",
|
"version": "5.2.11",
|
||||||
"source": {
|
"source": {
|
||||||
"type": "git",
|
"type": "git",
|
||||||
"url": "https://github.com/justinrainbow/json-schema.git",
|
"url": "https://github.com/justinrainbow/json-schema.git",
|
||||||
"reference": "ad87d5a5ca981228e0e205c2bc7dfb8e24559b60"
|
"reference": "2ab6744b7296ded80f8cc4f9509abbff393399aa"
|
||||||
},
|
},
|
||||||
"dist": {
|
"dist": {
|
||||||
"type": "zip",
|
"type": "zip",
|
||||||
"url": "https://api.github.com/repos/justinrainbow/json-schema/zipball/ad87d5a5ca981228e0e205c2bc7dfb8e24559b60",
|
"url": "https://api.github.com/repos/justinrainbow/json-schema/zipball/2ab6744b7296ded80f8cc4f9509abbff393399aa",
|
||||||
"reference": "ad87d5a5ca981228e0e205c2bc7dfb8e24559b60",
|
"reference": "2ab6744b7296ded80f8cc4f9509abbff393399aa",
|
||||||
"shasum": ""
|
"shasum": ""
|
||||||
},
|
},
|
||||||
"require": {
|
"require": {
|
||||||
@@ -127,9 +127,9 @@
|
|||||||
],
|
],
|
||||||
"support": {
|
"support": {
|
||||||
"issues": "https://github.com/justinrainbow/json-schema/issues",
|
"issues": "https://github.com/justinrainbow/json-schema/issues",
|
||||||
"source": "https://github.com/justinrainbow/json-schema/tree/5.2.12"
|
"source": "https://github.com/justinrainbow/json-schema/tree/5.2.11"
|
||||||
},
|
},
|
||||||
"time": "2022-04-13T08:02:27+00:00"
|
"time": "2021-07-22T09:24:00+00:00"
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"packages-dev": [
|
"packages-dev": [
|
||||||
@@ -445,16 +445,16 @@
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "composer/semver",
|
"name": "composer/semver",
|
||||||
"version": "3.3.2",
|
"version": "3.2.9",
|
||||||
"source": {
|
"source": {
|
||||||
"type": "git",
|
"type": "git",
|
||||||
"url": "https://github.com/composer/semver.git",
|
"url": "https://github.com/composer/semver.git",
|
||||||
"reference": "3953f23262f2bff1919fc82183ad9acb13ff62c9"
|
"reference": "a951f614bd64dcd26137bc9b7b2637ddcfc57649"
|
||||||
},
|
},
|
||||||
"dist": {
|
"dist": {
|
||||||
"type": "zip",
|
"type": "zip",
|
||||||
"url": "https://api.github.com/repos/composer/semver/zipball/3953f23262f2bff1919fc82183ad9acb13ff62c9",
|
"url": "https://api.github.com/repos/composer/semver/zipball/a951f614bd64dcd26137bc9b7b2637ddcfc57649",
|
||||||
"reference": "3953f23262f2bff1919fc82183ad9acb13ff62c9",
|
"reference": "a951f614bd64dcd26137bc9b7b2637ddcfc57649",
|
||||||
"shasum": ""
|
"shasum": ""
|
||||||
},
|
},
|
||||||
"require": {
|
"require": {
|
||||||
@@ -506,7 +506,7 @@
|
|||||||
"support": {
|
"support": {
|
||||||
"irc": "irc://irc.freenode.org/composer",
|
"irc": "irc://irc.freenode.org/composer",
|
||||||
"issues": "https://github.com/composer/semver/issues",
|
"issues": "https://github.com/composer/semver/issues",
|
||||||
"source": "https://github.com/composer/semver/tree/3.3.2"
|
"source": "https://github.com/composer/semver/tree/3.2.9"
|
||||||
},
|
},
|
||||||
"funding": [
|
"funding": [
|
||||||
{
|
{
|
||||||
@@ -522,7 +522,7 @@
|
|||||||
"type": "tidelift"
|
"type": "tidelift"
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"time": "2022-04-01T19:23:25+00:00"
|
"time": "2022-02-04T13:58:43+00:00"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "composer/xdebug-handler",
|
"name": "composer/xdebug-handler",
|
||||||
@@ -896,16 +896,16 @@
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "felixfbecker/language-server-protocol",
|
"name": "felixfbecker/language-server-protocol",
|
||||||
"version": "v1.5.2",
|
"version": "1.5.1",
|
||||||
"source": {
|
"source": {
|
||||||
"type": "git",
|
"type": "git",
|
||||||
"url": "https://github.com/felixfbecker/php-language-server-protocol.git",
|
"url": "https://github.com/felixfbecker/php-language-server-protocol.git",
|
||||||
"reference": "6e82196ffd7c62f7794d778ca52b69feec9f2842"
|
"reference": "9d846d1f5cf101deee7a61c8ba7caa0a975cd730"
|
||||||
},
|
},
|
||||||
"dist": {
|
"dist": {
|
||||||
"type": "zip",
|
"type": "zip",
|
||||||
"url": "https://api.github.com/repos/felixfbecker/php-language-server-protocol/zipball/6e82196ffd7c62f7794d778ca52b69feec9f2842",
|
"url": "https://api.github.com/repos/felixfbecker/php-language-server-protocol/zipball/9d846d1f5cf101deee7a61c8ba7caa0a975cd730",
|
||||||
"reference": "6e82196ffd7c62f7794d778ca52b69feec9f2842",
|
"reference": "9d846d1f5cf101deee7a61c8ba7caa0a975cd730",
|
||||||
"shasum": ""
|
"shasum": ""
|
||||||
},
|
},
|
||||||
"require": {
|
"require": {
|
||||||
@@ -946,9 +946,9 @@
|
|||||||
],
|
],
|
||||||
"support": {
|
"support": {
|
||||||
"issues": "https://github.com/felixfbecker/php-language-server-protocol/issues",
|
"issues": "https://github.com/felixfbecker/php-language-server-protocol/issues",
|
||||||
"source": "https://github.com/felixfbecker/php-language-server-protocol/tree/v1.5.2"
|
"source": "https://github.com/felixfbecker/php-language-server-protocol/tree/1.5.1"
|
||||||
},
|
},
|
||||||
"time": "2022-03-02T22:36:06+00:00"
|
"time": "2021-02-22T14:02:09+00:00"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "friendsofphp/php-cs-fixer",
|
"name": "friendsofphp/php-cs-fixer",
|
||||||
@@ -1192,16 +1192,16 @@
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "nextcloud/ocp",
|
"name": "nextcloud/ocp",
|
||||||
"version": "dev-stable25",
|
"version": "dev-stable24",
|
||||||
"source": {
|
"source": {
|
||||||
"type": "git",
|
"type": "git",
|
||||||
"url": "https://github.com/nextcloud-deps/ocp.git",
|
"url": "https://github.com/nextcloud-deps/ocp.git",
|
||||||
"reference": "1e34a80be034fe9a58057d2e756913363675bddb"
|
"reference": "0b89697ba1146d48506132c452cf548adb2a9cb8"
|
||||||
},
|
},
|
||||||
"dist": {
|
"dist": {
|
||||||
"type": "zip",
|
"type": "zip",
|
||||||
"url": "https://api.github.com/repos/nextcloud-deps/ocp/zipball/1e34a80be034fe9a58057d2e756913363675bddb",
|
"url": "https://api.github.com/repos/nextcloud-deps/ocp/zipball/0b89697ba1146d48506132c452cf548adb2a9cb8",
|
||||||
"reference": "1e34a80be034fe9a58057d2e756913363675bddb",
|
"reference": "0b89697ba1146d48506132c452cf548adb2a9cb8",
|
||||||
"shasum": ""
|
"shasum": ""
|
||||||
},
|
},
|
||||||
"require": {
|
"require": {
|
||||||
@@ -1213,7 +1213,7 @@
|
|||||||
"type": "library",
|
"type": "library",
|
||||||
"extra": {
|
"extra": {
|
||||||
"branch-alias": {
|
"branch-alias": {
|
||||||
"dev-master": "26.0.0-dev"
|
"dev-master": "24.0.0-dev"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"notification-url": "https://packagist.org/downloads/",
|
"notification-url": "https://packagist.org/downloads/",
|
||||||
@@ -1229,22 +1229,22 @@
|
|||||||
"description": "Composer package containing Nextcloud's public API (classes, interfaces)",
|
"description": "Composer package containing Nextcloud's public API (classes, interfaces)",
|
||||||
"support": {
|
"support": {
|
||||||
"issues": "https://github.com/nextcloud-deps/ocp/issues",
|
"issues": "https://github.com/nextcloud-deps/ocp/issues",
|
||||||
"source": "https://github.com/nextcloud-deps/ocp/tree/stable25"
|
"source": "https://github.com/nextcloud-deps/ocp/tree/stable24"
|
||||||
},
|
},
|
||||||
"time": "2023-05-13T00:33:04+00:00"
|
"time": "2023-03-16T00:40:04+00:00"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "nikic/php-parser",
|
"name": "nikic/php-parser",
|
||||||
"version": "v4.14.0",
|
"version": "v4.13.2",
|
||||||
"source": {
|
"source": {
|
||||||
"type": "git",
|
"type": "git",
|
||||||
"url": "https://github.com/nikic/PHP-Parser.git",
|
"url": "https://github.com/nikic/PHP-Parser.git",
|
||||||
"reference": "34bea19b6e03d8153165d8f30bba4c3be86184c1"
|
"reference": "210577fe3cf7badcc5814d99455df46564f3c077"
|
||||||
},
|
},
|
||||||
"dist": {
|
"dist": {
|
||||||
"type": "zip",
|
"type": "zip",
|
||||||
"url": "https://api.github.com/repos/nikic/PHP-Parser/zipball/34bea19b6e03d8153165d8f30bba4c3be86184c1",
|
"url": "https://api.github.com/repos/nikic/PHP-Parser/zipball/210577fe3cf7badcc5814d99455df46564f3c077",
|
||||||
"reference": "34bea19b6e03d8153165d8f30bba4c3be86184c1",
|
"reference": "210577fe3cf7badcc5814d99455df46564f3c077",
|
||||||
"shasum": ""
|
"shasum": ""
|
||||||
},
|
},
|
||||||
"require": {
|
"require": {
|
||||||
@@ -1285,9 +1285,9 @@
|
|||||||
],
|
],
|
||||||
"support": {
|
"support": {
|
||||||
"issues": "https://github.com/nikic/PHP-Parser/issues",
|
"issues": "https://github.com/nikic/PHP-Parser/issues",
|
||||||
"source": "https://github.com/nikic/PHP-Parser/tree/v4.14.0"
|
"source": "https://github.com/nikic/PHP-Parser/tree/v4.13.2"
|
||||||
},
|
},
|
||||||
"time": "2022-05-31T20:59:12+00:00"
|
"time": "2021-11-30T19:35:32+00:00"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "openlss/lib-array2xml",
|
"name": "openlss/lib-array2xml",
|
||||||
@@ -1723,24 +1723,91 @@
|
|||||||
"time": "2022-03-15T21:29:03+00:00"
|
"time": "2022-03-15T21:29:03+00:00"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "phpunit/php-code-coverage",
|
"name": "phpspec/prophecy",
|
||||||
"version": "9.2.17",
|
"version": "v1.15.0",
|
||||||
"source": {
|
"source": {
|
||||||
"type": "git",
|
"type": "git",
|
||||||
"url": "https://github.com/sebastianbergmann/php-code-coverage.git",
|
"url": "https://github.com/phpspec/prophecy.git",
|
||||||
"reference": "aa94dc41e8661fe90c7316849907cba3007b10d8"
|
"reference": "bbcd7380b0ebf3961ee21409db7b38bc31d69a13"
|
||||||
},
|
},
|
||||||
"dist": {
|
"dist": {
|
||||||
"type": "zip",
|
"type": "zip",
|
||||||
"url": "https://api.github.com/repos/sebastianbergmann/php-code-coverage/zipball/aa94dc41e8661fe90c7316849907cba3007b10d8",
|
"url": "https://api.github.com/repos/phpspec/prophecy/zipball/bbcd7380b0ebf3961ee21409db7b38bc31d69a13",
|
||||||
"reference": "aa94dc41e8661fe90c7316849907cba3007b10d8",
|
"reference": "bbcd7380b0ebf3961ee21409db7b38bc31d69a13",
|
||||||
|
"shasum": ""
|
||||||
|
},
|
||||||
|
"require": {
|
||||||
|
"doctrine/instantiator": "^1.2",
|
||||||
|
"php": "^7.2 || ~8.0, <8.2",
|
||||||
|
"phpdocumentor/reflection-docblock": "^5.2",
|
||||||
|
"sebastian/comparator": "^3.0 || ^4.0",
|
||||||
|
"sebastian/recursion-context": "^3.0 || ^4.0"
|
||||||
|
},
|
||||||
|
"require-dev": {
|
||||||
|
"phpspec/phpspec": "^6.0 || ^7.0",
|
||||||
|
"phpunit/phpunit": "^8.0 || ^9.0"
|
||||||
|
},
|
||||||
|
"type": "library",
|
||||||
|
"extra": {
|
||||||
|
"branch-alias": {
|
||||||
|
"dev-master": "1.x-dev"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"autoload": {
|
||||||
|
"psr-4": {
|
||||||
|
"Prophecy\\": "src/Prophecy"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"notification-url": "https://packagist.org/downloads/",
|
||||||
|
"license": [
|
||||||
|
"MIT"
|
||||||
|
],
|
||||||
|
"authors": [
|
||||||
|
{
|
||||||
|
"name": "Konstantin Kudryashov",
|
||||||
|
"email": "ever.zet@gmail.com",
|
||||||
|
"homepage": "http://everzet.com"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "Marcello Duarte",
|
||||||
|
"email": "marcello.duarte@gmail.com"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"description": "Highly opinionated mocking framework for PHP 5.3+",
|
||||||
|
"homepage": "https://github.com/phpspec/prophecy",
|
||||||
|
"keywords": [
|
||||||
|
"Double",
|
||||||
|
"Dummy",
|
||||||
|
"fake",
|
||||||
|
"mock",
|
||||||
|
"spy",
|
||||||
|
"stub"
|
||||||
|
],
|
||||||
|
"support": {
|
||||||
|
"issues": "https://github.com/phpspec/prophecy/issues",
|
||||||
|
"source": "https://github.com/phpspec/prophecy/tree/v1.15.0"
|
||||||
|
},
|
||||||
|
"time": "2021-12-08T12:19:24+00:00"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "phpunit/php-code-coverage",
|
||||||
|
"version": "9.2.15",
|
||||||
|
"source": {
|
||||||
|
"type": "git",
|
||||||
|
"url": "https://github.com/sebastianbergmann/php-code-coverage.git",
|
||||||
|
"reference": "2e9da11878c4202f97915c1cb4bb1ca318a63f5f"
|
||||||
|
},
|
||||||
|
"dist": {
|
||||||
|
"type": "zip",
|
||||||
|
"url": "https://api.github.com/repos/sebastianbergmann/php-code-coverage/zipball/2e9da11878c4202f97915c1cb4bb1ca318a63f5f",
|
||||||
|
"reference": "2e9da11878c4202f97915c1cb4bb1ca318a63f5f",
|
||||||
"shasum": ""
|
"shasum": ""
|
||||||
},
|
},
|
||||||
"require": {
|
"require": {
|
||||||
"ext-dom": "*",
|
"ext-dom": "*",
|
||||||
"ext-libxml": "*",
|
"ext-libxml": "*",
|
||||||
"ext-xmlwriter": "*",
|
"ext-xmlwriter": "*",
|
||||||
"nikic/php-parser": "^4.14",
|
"nikic/php-parser": "^4.13.0",
|
||||||
"php": ">=7.3",
|
"php": ">=7.3",
|
||||||
"phpunit/php-file-iterator": "^3.0.3",
|
"phpunit/php-file-iterator": "^3.0.3",
|
||||||
"phpunit/php-text-template": "^2.0.2",
|
"phpunit/php-text-template": "^2.0.2",
|
||||||
@@ -1789,7 +1856,7 @@
|
|||||||
],
|
],
|
||||||
"support": {
|
"support": {
|
||||||
"issues": "https://github.com/sebastianbergmann/php-code-coverage/issues",
|
"issues": "https://github.com/sebastianbergmann/php-code-coverage/issues",
|
||||||
"source": "https://github.com/sebastianbergmann/php-code-coverage/tree/9.2.17"
|
"source": "https://github.com/sebastianbergmann/php-code-coverage/tree/9.2.15"
|
||||||
},
|
},
|
||||||
"funding": [
|
"funding": [
|
||||||
{
|
{
|
||||||
@@ -1797,7 +1864,7 @@
|
|||||||
"type": "github"
|
"type": "github"
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"time": "2022-08-30T12:24:04+00:00"
|
"time": "2022-03-07T09:28:20+00:00"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "phpunit/php-file-iterator",
|
"name": "phpunit/php-file-iterator",
|
||||||
@@ -2042,16 +2109,16 @@
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "phpunit/phpunit",
|
"name": "phpunit/phpunit",
|
||||||
"version": "9.5.24",
|
"version": "9.5.20",
|
||||||
"source": {
|
"source": {
|
||||||
"type": "git",
|
"type": "git",
|
||||||
"url": "https://github.com/sebastianbergmann/phpunit.git",
|
"url": "https://github.com/sebastianbergmann/phpunit.git",
|
||||||
"reference": "d0aa6097bef9fd42458a9b3c49da32c6ce6129c5"
|
"reference": "12bc8879fb65aef2138b26fc633cb1e3620cffba"
|
||||||
},
|
},
|
||||||
"dist": {
|
"dist": {
|
||||||
"type": "zip",
|
"type": "zip",
|
||||||
"url": "https://api.github.com/repos/sebastianbergmann/phpunit/zipball/d0aa6097bef9fd42458a9b3c49da32c6ce6129c5",
|
"url": "https://api.github.com/repos/sebastianbergmann/phpunit/zipball/12bc8879fb65aef2138b26fc633cb1e3620cffba",
|
||||||
"reference": "d0aa6097bef9fd42458a9b3c49da32c6ce6129c5",
|
"reference": "12bc8879fb65aef2138b26fc633cb1e3620cffba",
|
||||||
"shasum": ""
|
"shasum": ""
|
||||||
},
|
},
|
||||||
"require": {
|
"require": {
|
||||||
@@ -2066,6 +2133,7 @@
|
|||||||
"phar-io/manifest": "^2.0.3",
|
"phar-io/manifest": "^2.0.3",
|
||||||
"phar-io/version": "^3.0.2",
|
"phar-io/version": "^3.0.2",
|
||||||
"php": ">=7.3",
|
"php": ">=7.3",
|
||||||
|
"phpspec/prophecy": "^1.12.1",
|
||||||
"phpunit/php-code-coverage": "^9.2.13",
|
"phpunit/php-code-coverage": "^9.2.13",
|
||||||
"phpunit/php-file-iterator": "^3.0.5",
|
"phpunit/php-file-iterator": "^3.0.5",
|
||||||
"phpunit/php-invoker": "^3.1.1",
|
"phpunit/php-invoker": "^3.1.1",
|
||||||
@@ -2080,9 +2148,13 @@
|
|||||||
"sebastian/global-state": "^5.0.1",
|
"sebastian/global-state": "^5.0.1",
|
||||||
"sebastian/object-enumerator": "^4.0.3",
|
"sebastian/object-enumerator": "^4.0.3",
|
||||||
"sebastian/resource-operations": "^3.0.3",
|
"sebastian/resource-operations": "^3.0.3",
|
||||||
"sebastian/type": "^3.1",
|
"sebastian/type": "^3.0",
|
||||||
"sebastian/version": "^3.0.2"
|
"sebastian/version": "^3.0.2"
|
||||||
},
|
},
|
||||||
|
"require-dev": {
|
||||||
|
"ext-pdo": "*",
|
||||||
|
"phpspec/prophecy-phpunit": "^2.0.1"
|
||||||
|
},
|
||||||
"suggest": {
|
"suggest": {
|
||||||
"ext-soap": "*",
|
"ext-soap": "*",
|
||||||
"ext-xdebug": "*"
|
"ext-xdebug": "*"
|
||||||
@@ -2124,7 +2196,7 @@
|
|||||||
],
|
],
|
||||||
"support": {
|
"support": {
|
||||||
"issues": "https://github.com/sebastianbergmann/phpunit/issues",
|
"issues": "https://github.com/sebastianbergmann/phpunit/issues",
|
||||||
"source": "https://github.com/sebastianbergmann/phpunit/tree/9.5.24"
|
"source": "https://github.com/sebastianbergmann/phpunit/tree/9.5.20"
|
||||||
},
|
},
|
||||||
"funding": [
|
"funding": [
|
||||||
{
|
{
|
||||||
@@ -2136,7 +2208,7 @@
|
|||||||
"type": "github"
|
"type": "github"
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"time": "2022-08-30T07:42:16+00:00"
|
"time": "2022-04-01T12:37:26+00:00"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "psr/cache",
|
"name": "psr/cache",
|
||||||
@@ -3133,16 +3205,16 @@
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "sebastian/environment",
|
"name": "sebastian/environment",
|
||||||
"version": "5.1.4",
|
"version": "5.1.3",
|
||||||
"source": {
|
"source": {
|
||||||
"type": "git",
|
"type": "git",
|
||||||
"url": "https://github.com/sebastianbergmann/environment.git",
|
"url": "https://github.com/sebastianbergmann/environment.git",
|
||||||
"reference": "1b5dff7bb151a4db11d49d90e5408e4e938270f7"
|
"reference": "388b6ced16caa751030f6a69e588299fa09200ac"
|
||||||
},
|
},
|
||||||
"dist": {
|
"dist": {
|
||||||
"type": "zip",
|
"type": "zip",
|
||||||
"url": "https://api.github.com/repos/sebastianbergmann/environment/zipball/1b5dff7bb151a4db11d49d90e5408e4e938270f7",
|
"url": "https://api.github.com/repos/sebastianbergmann/environment/zipball/388b6ced16caa751030f6a69e588299fa09200ac",
|
||||||
"reference": "1b5dff7bb151a4db11d49d90e5408e4e938270f7",
|
"reference": "388b6ced16caa751030f6a69e588299fa09200ac",
|
||||||
"shasum": ""
|
"shasum": ""
|
||||||
},
|
},
|
||||||
"require": {
|
"require": {
|
||||||
@@ -3184,7 +3256,7 @@
|
|||||||
],
|
],
|
||||||
"support": {
|
"support": {
|
||||||
"issues": "https://github.com/sebastianbergmann/environment/issues",
|
"issues": "https://github.com/sebastianbergmann/environment/issues",
|
||||||
"source": "https://github.com/sebastianbergmann/environment/tree/5.1.4"
|
"source": "https://github.com/sebastianbergmann/environment/tree/5.1.3"
|
||||||
},
|
},
|
||||||
"funding": [
|
"funding": [
|
||||||
{
|
{
|
||||||
@@ -3192,7 +3264,7 @@
|
|||||||
"type": "github"
|
"type": "github"
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"time": "2022-04-03T09:37:03+00:00"
|
"time": "2020-09-28T05:52:38+00:00"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "sebastian/exporter",
|
"name": "sebastian/exporter",
|
||||||
@@ -3624,16 +3696,16 @@
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "sebastian/type",
|
"name": "sebastian/type",
|
||||||
"version": "3.1.0",
|
"version": "3.0.0",
|
||||||
"source": {
|
"source": {
|
||||||
"type": "git",
|
"type": "git",
|
||||||
"url": "https://github.com/sebastianbergmann/type.git",
|
"url": "https://github.com/sebastianbergmann/type.git",
|
||||||
"reference": "fb44e1cc6e557418387ad815780360057e40753e"
|
"reference": "b233b84bc4465aff7b57cf1c4bc75c86d00d6dad"
|
||||||
},
|
},
|
||||||
"dist": {
|
"dist": {
|
||||||
"type": "zip",
|
"type": "zip",
|
||||||
"url": "https://api.github.com/repos/sebastianbergmann/type/zipball/fb44e1cc6e557418387ad815780360057e40753e",
|
"url": "https://api.github.com/repos/sebastianbergmann/type/zipball/b233b84bc4465aff7b57cf1c4bc75c86d00d6dad",
|
||||||
"reference": "fb44e1cc6e557418387ad815780360057e40753e",
|
"reference": "b233b84bc4465aff7b57cf1c4bc75c86d00d6dad",
|
||||||
"shasum": ""
|
"shasum": ""
|
||||||
},
|
},
|
||||||
"require": {
|
"require": {
|
||||||
@@ -3645,7 +3717,7 @@
|
|||||||
"type": "library",
|
"type": "library",
|
||||||
"extra": {
|
"extra": {
|
||||||
"branch-alias": {
|
"branch-alias": {
|
||||||
"dev-master": "3.1-dev"
|
"dev-master": "3.0-dev"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"autoload": {
|
"autoload": {
|
||||||
@@ -3668,7 +3740,7 @@
|
|||||||
"homepage": "https://github.com/sebastianbergmann/type",
|
"homepage": "https://github.com/sebastianbergmann/type",
|
||||||
"support": {
|
"support": {
|
||||||
"issues": "https://github.com/sebastianbergmann/type/issues",
|
"issues": "https://github.com/sebastianbergmann/type/issues",
|
||||||
"source": "https://github.com/sebastianbergmann/type/tree/3.1.0"
|
"source": "https://github.com/sebastianbergmann/type/tree/3.0.0"
|
||||||
},
|
},
|
||||||
"funding": [
|
"funding": [
|
||||||
{
|
{
|
||||||
@@ -3676,7 +3748,7 @@
|
|||||||
"type": "github"
|
"type": "github"
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"time": "2022-08-29T06:55:37+00:00"
|
"time": "2022-03-15T09:54:48+00:00"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "sebastian/version",
|
"name": "sebastian/version",
|
||||||
@@ -3733,16 +3805,16 @@
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "symfony/console",
|
"name": "symfony/console",
|
||||||
"version": "v5.4.12",
|
"version": "v5.4.5",
|
||||||
"source": {
|
"source": {
|
||||||
"type": "git",
|
"type": "git",
|
||||||
"url": "https://github.com/symfony/console.git",
|
"url": "https://github.com/symfony/console.git",
|
||||||
"reference": "c072aa8f724c3af64e2c7a96b796a4863d24dba1"
|
"reference": "d8111acc99876953f52fe16d4c50eb60940d49ad"
|
||||||
},
|
},
|
||||||
"dist": {
|
"dist": {
|
||||||
"type": "zip",
|
"type": "zip",
|
||||||
"url": "https://api.github.com/repos/symfony/console/zipball/c072aa8f724c3af64e2c7a96b796a4863d24dba1",
|
"url": "https://api.github.com/repos/symfony/console/zipball/d8111acc99876953f52fe16d4c50eb60940d49ad",
|
||||||
"reference": "c072aa8f724c3af64e2c7a96b796a4863d24dba1",
|
"reference": "d8111acc99876953f52fe16d4c50eb60940d49ad",
|
||||||
"shasum": ""
|
"shasum": ""
|
||||||
},
|
},
|
||||||
"require": {
|
"require": {
|
||||||
@@ -3812,7 +3884,7 @@
|
|||||||
"terminal"
|
"terminal"
|
||||||
],
|
],
|
||||||
"support": {
|
"support": {
|
||||||
"source": "https://github.com/symfony/console/tree/v5.4.12"
|
"source": "https://github.com/symfony/console/tree/v5.4.5"
|
||||||
},
|
},
|
||||||
"funding": [
|
"funding": [
|
||||||
{
|
{
|
||||||
@@ -3828,20 +3900,20 @@
|
|||||||
"type": "tidelift"
|
"type": "tidelift"
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"time": "2022-08-17T13:18:05+00:00"
|
"time": "2022-02-24T12:45:35+00:00"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "symfony/deprecation-contracts",
|
"name": "symfony/deprecation-contracts",
|
||||||
"version": "v2.5.2",
|
"version": "v2.5.0",
|
||||||
"source": {
|
"source": {
|
||||||
"type": "git",
|
"type": "git",
|
||||||
"url": "https://github.com/symfony/deprecation-contracts.git",
|
"url": "https://github.com/symfony/deprecation-contracts.git",
|
||||||
"reference": "e8b495ea28c1d97b5e0c121748d6f9b53d075c66"
|
"reference": "6f981ee24cf69ee7ce9736146d1c57c2780598a8"
|
||||||
},
|
},
|
||||||
"dist": {
|
"dist": {
|
||||||
"type": "zip",
|
"type": "zip",
|
||||||
"url": "https://api.github.com/repos/symfony/deprecation-contracts/zipball/e8b495ea28c1d97b5e0c121748d6f9b53d075c66",
|
"url": "https://api.github.com/repos/symfony/deprecation-contracts/zipball/6f981ee24cf69ee7ce9736146d1c57c2780598a8",
|
||||||
"reference": "e8b495ea28c1d97b5e0c121748d6f9b53d075c66",
|
"reference": "6f981ee24cf69ee7ce9736146d1c57c2780598a8",
|
||||||
"shasum": ""
|
"shasum": ""
|
||||||
},
|
},
|
||||||
"require": {
|
"require": {
|
||||||
@@ -3879,7 +3951,7 @@
|
|||||||
"description": "A generic function and convention to trigger deprecation notices",
|
"description": "A generic function and convention to trigger deprecation notices",
|
||||||
"homepage": "https://symfony.com",
|
"homepage": "https://symfony.com",
|
||||||
"support": {
|
"support": {
|
||||||
"source": "https://github.com/symfony/deprecation-contracts/tree/v2.5.2"
|
"source": "https://github.com/symfony/deprecation-contracts/tree/v2.5.0"
|
||||||
},
|
},
|
||||||
"funding": [
|
"funding": [
|
||||||
{
|
{
|
||||||
@@ -3895,7 +3967,7 @@
|
|||||||
"type": "tidelift"
|
"type": "tidelift"
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"time": "2022-01-02T09:53:40+00:00"
|
"time": "2021-07-12T14:48:14+00:00"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "symfony/event-dispatcher",
|
"name": "symfony/event-dispatcher",
|
||||||
@@ -3983,16 +4055,16 @@
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "symfony/event-dispatcher-contracts",
|
"name": "symfony/event-dispatcher-contracts",
|
||||||
"version": "v1.1.13",
|
"version": "v1.1.11",
|
||||||
"source": {
|
"source": {
|
||||||
"type": "git",
|
"type": "git",
|
||||||
"url": "https://github.com/symfony/event-dispatcher-contracts.git",
|
"url": "https://github.com/symfony/event-dispatcher-contracts.git",
|
||||||
"reference": "1d5cd762abaa6b2a4169d3e77610193a7157129e"
|
"reference": "01e9a4efac0ee33a05dfdf93b346f62e7d0e998c"
|
||||||
},
|
},
|
||||||
"dist": {
|
"dist": {
|
||||||
"type": "zip",
|
"type": "zip",
|
||||||
"url": "https://api.github.com/repos/symfony/event-dispatcher-contracts/zipball/1d5cd762abaa6b2a4169d3e77610193a7157129e",
|
"url": "https://api.github.com/repos/symfony/event-dispatcher-contracts/zipball/01e9a4efac0ee33a05dfdf93b346f62e7d0e998c",
|
||||||
"reference": "1d5cd762abaa6b2a4169d3e77610193a7157129e",
|
"reference": "01e9a4efac0ee33a05dfdf93b346f62e7d0e998c",
|
||||||
"shasum": ""
|
"shasum": ""
|
||||||
},
|
},
|
||||||
"require": {
|
"require": {
|
||||||
@@ -4042,7 +4114,7 @@
|
|||||||
"standards"
|
"standards"
|
||||||
],
|
],
|
||||||
"support": {
|
"support": {
|
||||||
"source": "https://github.com/symfony/event-dispatcher-contracts/tree/v1.1.13"
|
"source": "https://github.com/symfony/event-dispatcher-contracts/tree/v1.1.11"
|
||||||
},
|
},
|
||||||
"funding": [
|
"funding": [
|
||||||
{
|
{
|
||||||
@@ -4058,7 +4130,7 @@
|
|||||||
"type": "tidelift"
|
"type": "tidelift"
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"time": "2022-01-02T09:41:36+00:00"
|
"time": "2021-03-23T15:25:38+00:00"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "symfony/filesystem",
|
"name": "symfony/filesystem",
|
||||||
@@ -4258,16 +4330,16 @@
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "symfony/polyfill-ctype",
|
"name": "symfony/polyfill-ctype",
|
||||||
"version": "v1.26.0",
|
"version": "v1.25.0",
|
||||||
"source": {
|
"source": {
|
||||||
"type": "git",
|
"type": "git",
|
||||||
"url": "https://github.com/symfony/polyfill-ctype.git",
|
"url": "https://github.com/symfony/polyfill-ctype.git",
|
||||||
"reference": "6fd1b9a79f6e3cf65f9e679b23af304cd9e010d4"
|
"reference": "30885182c981ab175d4d034db0f6f469898070ab"
|
||||||
},
|
},
|
||||||
"dist": {
|
"dist": {
|
||||||
"type": "zip",
|
"type": "zip",
|
||||||
"url": "https://api.github.com/repos/symfony/polyfill-ctype/zipball/6fd1b9a79f6e3cf65f9e679b23af304cd9e010d4",
|
"url": "https://api.github.com/repos/symfony/polyfill-ctype/zipball/30885182c981ab175d4d034db0f6f469898070ab",
|
||||||
"reference": "6fd1b9a79f6e3cf65f9e679b23af304cd9e010d4",
|
"reference": "30885182c981ab175d4d034db0f6f469898070ab",
|
||||||
"shasum": ""
|
"shasum": ""
|
||||||
},
|
},
|
||||||
"require": {
|
"require": {
|
||||||
@@ -4282,7 +4354,7 @@
|
|||||||
"type": "library",
|
"type": "library",
|
||||||
"extra": {
|
"extra": {
|
||||||
"branch-alias": {
|
"branch-alias": {
|
||||||
"dev-main": "1.26-dev"
|
"dev-main": "1.23-dev"
|
||||||
},
|
},
|
||||||
"thanks": {
|
"thanks": {
|
||||||
"name": "symfony/polyfill",
|
"name": "symfony/polyfill",
|
||||||
@@ -4320,7 +4392,7 @@
|
|||||||
"portable"
|
"portable"
|
||||||
],
|
],
|
||||||
"support": {
|
"support": {
|
||||||
"source": "https://github.com/symfony/polyfill-ctype/tree/v1.26.0"
|
"source": "https://github.com/symfony/polyfill-ctype/tree/v1.25.0"
|
||||||
},
|
},
|
||||||
"funding": [
|
"funding": [
|
||||||
{
|
{
|
||||||
@@ -4336,20 +4408,20 @@
|
|||||||
"type": "tidelift"
|
"type": "tidelift"
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"time": "2022-05-24T11:49:31+00:00"
|
"time": "2021-10-20T20:35:02+00:00"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "symfony/polyfill-intl-grapheme",
|
"name": "symfony/polyfill-intl-grapheme",
|
||||||
"version": "v1.26.0",
|
"version": "v1.25.0",
|
||||||
"source": {
|
"source": {
|
||||||
"type": "git",
|
"type": "git",
|
||||||
"url": "https://github.com/symfony/polyfill-intl-grapheme.git",
|
"url": "https://github.com/symfony/polyfill-intl-grapheme.git",
|
||||||
"reference": "433d05519ce6990bf3530fba6957499d327395c2"
|
"reference": "81b86b50cf841a64252b439e738e97f4a34e2783"
|
||||||
},
|
},
|
||||||
"dist": {
|
"dist": {
|
||||||
"type": "zip",
|
"type": "zip",
|
||||||
"url": "https://api.github.com/repos/symfony/polyfill-intl-grapheme/zipball/433d05519ce6990bf3530fba6957499d327395c2",
|
"url": "https://api.github.com/repos/symfony/polyfill-intl-grapheme/zipball/81b86b50cf841a64252b439e738e97f4a34e2783",
|
||||||
"reference": "433d05519ce6990bf3530fba6957499d327395c2",
|
"reference": "81b86b50cf841a64252b439e738e97f4a34e2783",
|
||||||
"shasum": ""
|
"shasum": ""
|
||||||
},
|
},
|
||||||
"require": {
|
"require": {
|
||||||
@@ -4361,7 +4433,7 @@
|
|||||||
"type": "library",
|
"type": "library",
|
||||||
"extra": {
|
"extra": {
|
||||||
"branch-alias": {
|
"branch-alias": {
|
||||||
"dev-main": "1.26-dev"
|
"dev-main": "1.23-dev"
|
||||||
},
|
},
|
||||||
"thanks": {
|
"thanks": {
|
||||||
"name": "symfony/polyfill",
|
"name": "symfony/polyfill",
|
||||||
@@ -4401,7 +4473,7 @@
|
|||||||
"shim"
|
"shim"
|
||||||
],
|
],
|
||||||
"support": {
|
"support": {
|
||||||
"source": "https://github.com/symfony/polyfill-intl-grapheme/tree/v1.26.0"
|
"source": "https://github.com/symfony/polyfill-intl-grapheme/tree/v1.25.0"
|
||||||
},
|
},
|
||||||
"funding": [
|
"funding": [
|
||||||
{
|
{
|
||||||
@@ -4417,20 +4489,20 @@
|
|||||||
"type": "tidelift"
|
"type": "tidelift"
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"time": "2022-05-24T11:49:31+00:00"
|
"time": "2021-11-23T21:10:46+00:00"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "symfony/polyfill-intl-normalizer",
|
"name": "symfony/polyfill-intl-normalizer",
|
||||||
"version": "v1.26.0",
|
"version": "v1.25.0",
|
||||||
"source": {
|
"source": {
|
||||||
"type": "git",
|
"type": "git",
|
||||||
"url": "https://github.com/symfony/polyfill-intl-normalizer.git",
|
"url": "https://github.com/symfony/polyfill-intl-normalizer.git",
|
||||||
"reference": "219aa369ceff116e673852dce47c3a41794c14bd"
|
"reference": "8590a5f561694770bdcd3f9b5c69dde6945028e8"
|
||||||
},
|
},
|
||||||
"dist": {
|
"dist": {
|
||||||
"type": "zip",
|
"type": "zip",
|
||||||
"url": "https://api.github.com/repos/symfony/polyfill-intl-normalizer/zipball/219aa369ceff116e673852dce47c3a41794c14bd",
|
"url": "https://api.github.com/repos/symfony/polyfill-intl-normalizer/zipball/8590a5f561694770bdcd3f9b5c69dde6945028e8",
|
||||||
"reference": "219aa369ceff116e673852dce47c3a41794c14bd",
|
"reference": "8590a5f561694770bdcd3f9b5c69dde6945028e8",
|
||||||
"shasum": ""
|
"shasum": ""
|
||||||
},
|
},
|
||||||
"require": {
|
"require": {
|
||||||
@@ -4442,7 +4514,7 @@
|
|||||||
"type": "library",
|
"type": "library",
|
||||||
"extra": {
|
"extra": {
|
||||||
"branch-alias": {
|
"branch-alias": {
|
||||||
"dev-main": "1.26-dev"
|
"dev-main": "1.23-dev"
|
||||||
},
|
},
|
||||||
"thanks": {
|
"thanks": {
|
||||||
"name": "symfony/polyfill",
|
"name": "symfony/polyfill",
|
||||||
@@ -4485,7 +4557,7 @@
|
|||||||
"shim"
|
"shim"
|
||||||
],
|
],
|
||||||
"support": {
|
"support": {
|
||||||
"source": "https://github.com/symfony/polyfill-intl-normalizer/tree/v1.26.0"
|
"source": "https://github.com/symfony/polyfill-intl-normalizer/tree/v1.25.0"
|
||||||
},
|
},
|
||||||
"funding": [
|
"funding": [
|
||||||
{
|
{
|
||||||
@@ -4501,20 +4573,20 @@
|
|||||||
"type": "tidelift"
|
"type": "tidelift"
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"time": "2022-05-24T11:49:31+00:00"
|
"time": "2021-02-19T12:13:01+00:00"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "symfony/polyfill-mbstring",
|
"name": "symfony/polyfill-mbstring",
|
||||||
"version": "v1.26.0",
|
"version": "v1.25.0",
|
||||||
"source": {
|
"source": {
|
||||||
"type": "git",
|
"type": "git",
|
||||||
"url": "https://github.com/symfony/polyfill-mbstring.git",
|
"url": "https://github.com/symfony/polyfill-mbstring.git",
|
||||||
"reference": "9344f9cb97f3b19424af1a21a3b0e75b0a7d8d7e"
|
"reference": "0abb51d2f102e00a4eefcf46ba7fec406d245825"
|
||||||
},
|
},
|
||||||
"dist": {
|
"dist": {
|
||||||
"type": "zip",
|
"type": "zip",
|
||||||
"url": "https://api.github.com/repos/symfony/polyfill-mbstring/zipball/9344f9cb97f3b19424af1a21a3b0e75b0a7d8d7e",
|
"url": "https://api.github.com/repos/symfony/polyfill-mbstring/zipball/0abb51d2f102e00a4eefcf46ba7fec406d245825",
|
||||||
"reference": "9344f9cb97f3b19424af1a21a3b0e75b0a7d8d7e",
|
"reference": "0abb51d2f102e00a4eefcf46ba7fec406d245825",
|
||||||
"shasum": ""
|
"shasum": ""
|
||||||
},
|
},
|
||||||
"require": {
|
"require": {
|
||||||
@@ -4529,7 +4601,7 @@
|
|||||||
"type": "library",
|
"type": "library",
|
||||||
"extra": {
|
"extra": {
|
||||||
"branch-alias": {
|
"branch-alias": {
|
||||||
"dev-main": "1.26-dev"
|
"dev-main": "1.23-dev"
|
||||||
},
|
},
|
||||||
"thanks": {
|
"thanks": {
|
||||||
"name": "symfony/polyfill",
|
"name": "symfony/polyfill",
|
||||||
@@ -4568,7 +4640,7 @@
|
|||||||
"shim"
|
"shim"
|
||||||
],
|
],
|
||||||
"support": {
|
"support": {
|
||||||
"source": "https://github.com/symfony/polyfill-mbstring/tree/v1.26.0"
|
"source": "https://github.com/symfony/polyfill-mbstring/tree/v1.25.0"
|
||||||
},
|
},
|
||||||
"funding": [
|
"funding": [
|
||||||
{
|
{
|
||||||
@@ -4584,20 +4656,20 @@
|
|||||||
"type": "tidelift"
|
"type": "tidelift"
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"time": "2022-05-24T11:49:31+00:00"
|
"time": "2021-11-30T18:21:41+00:00"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "symfony/polyfill-php73",
|
"name": "symfony/polyfill-php73",
|
||||||
"version": "v1.26.0",
|
"version": "v1.25.0",
|
||||||
"source": {
|
"source": {
|
||||||
"type": "git",
|
"type": "git",
|
||||||
"url": "https://github.com/symfony/polyfill-php73.git",
|
"url": "https://github.com/symfony/polyfill-php73.git",
|
||||||
"reference": "e440d35fa0286f77fb45b79a03fedbeda9307e85"
|
"reference": "cc5db0e22b3cb4111010e48785a97f670b350ca5"
|
||||||
},
|
},
|
||||||
"dist": {
|
"dist": {
|
||||||
"type": "zip",
|
"type": "zip",
|
||||||
"url": "https://api.github.com/repos/symfony/polyfill-php73/zipball/e440d35fa0286f77fb45b79a03fedbeda9307e85",
|
"url": "https://api.github.com/repos/symfony/polyfill-php73/zipball/cc5db0e22b3cb4111010e48785a97f670b350ca5",
|
||||||
"reference": "e440d35fa0286f77fb45b79a03fedbeda9307e85",
|
"reference": "cc5db0e22b3cb4111010e48785a97f670b350ca5",
|
||||||
"shasum": ""
|
"shasum": ""
|
||||||
},
|
},
|
||||||
"require": {
|
"require": {
|
||||||
@@ -4606,7 +4678,7 @@
|
|||||||
"type": "library",
|
"type": "library",
|
||||||
"extra": {
|
"extra": {
|
||||||
"branch-alias": {
|
"branch-alias": {
|
||||||
"dev-main": "1.26-dev"
|
"dev-main": "1.23-dev"
|
||||||
},
|
},
|
||||||
"thanks": {
|
"thanks": {
|
||||||
"name": "symfony/polyfill",
|
"name": "symfony/polyfill",
|
||||||
@@ -4647,7 +4719,7 @@
|
|||||||
"shim"
|
"shim"
|
||||||
],
|
],
|
||||||
"support": {
|
"support": {
|
||||||
"source": "https://github.com/symfony/polyfill-php73/tree/v1.26.0"
|
"source": "https://github.com/symfony/polyfill-php73/tree/v1.25.0"
|
||||||
},
|
},
|
||||||
"funding": [
|
"funding": [
|
||||||
{
|
{
|
||||||
@@ -4663,20 +4735,20 @@
|
|||||||
"type": "tidelift"
|
"type": "tidelift"
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"time": "2022-05-24T11:49:31+00:00"
|
"time": "2021-06-05T21:20:04+00:00"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "symfony/polyfill-php80",
|
"name": "symfony/polyfill-php80",
|
||||||
"version": "v1.26.0",
|
"version": "v1.25.0",
|
||||||
"source": {
|
"source": {
|
||||||
"type": "git",
|
"type": "git",
|
||||||
"url": "https://github.com/symfony/polyfill-php80.git",
|
"url": "https://github.com/symfony/polyfill-php80.git",
|
||||||
"reference": "cfa0ae98841b9e461207c13ab093d76b0fa7bace"
|
"reference": "4407588e0d3f1f52efb65fbe92babe41f37fe50c"
|
||||||
},
|
},
|
||||||
"dist": {
|
"dist": {
|
||||||
"type": "zip",
|
"type": "zip",
|
||||||
"url": "https://api.github.com/repos/symfony/polyfill-php80/zipball/cfa0ae98841b9e461207c13ab093d76b0fa7bace",
|
"url": "https://api.github.com/repos/symfony/polyfill-php80/zipball/4407588e0d3f1f52efb65fbe92babe41f37fe50c",
|
||||||
"reference": "cfa0ae98841b9e461207c13ab093d76b0fa7bace",
|
"reference": "4407588e0d3f1f52efb65fbe92babe41f37fe50c",
|
||||||
"shasum": ""
|
"shasum": ""
|
||||||
},
|
},
|
||||||
"require": {
|
"require": {
|
||||||
@@ -4685,7 +4757,7 @@
|
|||||||
"type": "library",
|
"type": "library",
|
||||||
"extra": {
|
"extra": {
|
||||||
"branch-alias": {
|
"branch-alias": {
|
||||||
"dev-main": "1.26-dev"
|
"dev-main": "1.23-dev"
|
||||||
},
|
},
|
||||||
"thanks": {
|
"thanks": {
|
||||||
"name": "symfony/polyfill",
|
"name": "symfony/polyfill",
|
||||||
@@ -4730,7 +4802,7 @@
|
|||||||
"shim"
|
"shim"
|
||||||
],
|
],
|
||||||
"support": {
|
"support": {
|
||||||
"source": "https://github.com/symfony/polyfill-php80/tree/v1.26.0"
|
"source": "https://github.com/symfony/polyfill-php80/tree/v1.25.0"
|
||||||
},
|
},
|
||||||
"funding": [
|
"funding": [
|
||||||
{
|
{
|
||||||
@@ -4746,7 +4818,7 @@
|
|||||||
"type": "tidelift"
|
"type": "tidelift"
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"time": "2022-05-10T07:21:04+00:00"
|
"time": "2022-03-04T08:16:47+00:00"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "symfony/polyfill-php81",
|
"name": "symfony/polyfill-php81",
|
||||||
@@ -4891,22 +4963,22 @@
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "symfony/service-contracts",
|
"name": "symfony/service-contracts",
|
||||||
"version": "v2.5.2",
|
"version": "v2.5.0",
|
||||||
"source": {
|
"source": {
|
||||||
"type": "git",
|
"type": "git",
|
||||||
"url": "https://github.com/symfony/service-contracts.git",
|
"url": "https://github.com/symfony/service-contracts.git",
|
||||||
"reference": "4b426aac47d6427cc1a1d0f7e2ac724627f5966c"
|
"reference": "1ab11b933cd6bc5464b08e81e2c5b07dec58b0fc"
|
||||||
},
|
},
|
||||||
"dist": {
|
"dist": {
|
||||||
"type": "zip",
|
"type": "zip",
|
||||||
"url": "https://api.github.com/repos/symfony/service-contracts/zipball/4b426aac47d6427cc1a1d0f7e2ac724627f5966c",
|
"url": "https://api.github.com/repos/symfony/service-contracts/zipball/1ab11b933cd6bc5464b08e81e2c5b07dec58b0fc",
|
||||||
"reference": "4b426aac47d6427cc1a1d0f7e2ac724627f5966c",
|
"reference": "1ab11b933cd6bc5464b08e81e2c5b07dec58b0fc",
|
||||||
"shasum": ""
|
"shasum": ""
|
||||||
},
|
},
|
||||||
"require": {
|
"require": {
|
||||||
"php": ">=7.2.5",
|
"php": ">=7.2.5",
|
||||||
"psr/container": "^1.1",
|
"psr/container": "^1.1",
|
||||||
"symfony/deprecation-contracts": "^2.1|^3"
|
"symfony/deprecation-contracts": "^2.1"
|
||||||
},
|
},
|
||||||
"conflict": {
|
"conflict": {
|
||||||
"ext-psr": "<1.1|>=2"
|
"ext-psr": "<1.1|>=2"
|
||||||
@@ -4954,7 +5026,7 @@
|
|||||||
"standards"
|
"standards"
|
||||||
],
|
],
|
||||||
"support": {
|
"support": {
|
||||||
"source": "https://github.com/symfony/service-contracts/tree/v2.5.2"
|
"source": "https://github.com/symfony/service-contracts/tree/v2.5.0"
|
||||||
},
|
},
|
||||||
"funding": [
|
"funding": [
|
||||||
{
|
{
|
||||||
@@ -4970,7 +5042,7 @@
|
|||||||
"type": "tidelift"
|
"type": "tidelift"
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"time": "2022-05-30T19:17:29+00:00"
|
"time": "2021-11-04T16:48:04+00:00"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "symfony/stopwatch",
|
"name": "symfony/stopwatch",
|
||||||
@@ -5036,16 +5108,16 @@
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "symfony/string",
|
"name": "symfony/string",
|
||||||
"version": "v5.4.12",
|
"version": "v5.4.3",
|
||||||
"source": {
|
"source": {
|
||||||
"type": "git",
|
"type": "git",
|
||||||
"url": "https://github.com/symfony/string.git",
|
"url": "https://github.com/symfony/string.git",
|
||||||
"reference": "2fc515e512d721bf31ea76bd02fe23ada4640058"
|
"reference": "92043b7d8383e48104e411bc9434b260dbeb5a10"
|
||||||
},
|
},
|
||||||
"dist": {
|
"dist": {
|
||||||
"type": "zip",
|
"type": "zip",
|
||||||
"url": "https://api.github.com/repos/symfony/string/zipball/2fc515e512d721bf31ea76bd02fe23ada4640058",
|
"url": "https://api.github.com/repos/symfony/string/zipball/92043b7d8383e48104e411bc9434b260dbeb5a10",
|
||||||
"reference": "2fc515e512d721bf31ea76bd02fe23ada4640058",
|
"reference": "92043b7d8383e48104e411bc9434b260dbeb5a10",
|
||||||
"shasum": ""
|
"shasum": ""
|
||||||
},
|
},
|
||||||
"require": {
|
"require": {
|
||||||
@@ -5102,7 +5174,7 @@
|
|||||||
"utf8"
|
"utf8"
|
||||||
],
|
],
|
||||||
"support": {
|
"support": {
|
||||||
"source": "https://github.com/symfony/string/tree/v5.4.12"
|
"source": "https://github.com/symfony/string/tree/v5.4.3"
|
||||||
},
|
},
|
||||||
"funding": [
|
"funding": [
|
||||||
{
|
{
|
||||||
@@ -5118,7 +5190,7 @@
|
|||||||
"type": "tidelift"
|
"type": "tidelift"
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"time": "2022-08-12T17:03:11+00:00"
|
"time": "2022-01-02T09:53:40+00:00"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "theseer/tokenizer",
|
"name": "theseer/tokenizer",
|
||||||
@@ -5172,16 +5244,16 @@
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "vimeo/psalm",
|
"name": "vimeo/psalm",
|
||||||
"version": "4.27.0",
|
"version": "4.22.0",
|
||||||
"source": {
|
"source": {
|
||||||
"type": "git",
|
"type": "git",
|
||||||
"url": "https://github.com/vimeo/psalm.git",
|
"url": "https://github.com/vimeo/psalm.git",
|
||||||
"reference": "faf106e717c37b8c81721845dba9de3d8deed8ff"
|
"reference": "fc2c6ab4d5fa5d644d8617089f012f3bb84b8703"
|
||||||
},
|
},
|
||||||
"dist": {
|
"dist": {
|
||||||
"type": "zip",
|
"type": "zip",
|
||||||
"url": "https://api.github.com/repos/vimeo/psalm/zipball/faf106e717c37b8c81721845dba9de3d8deed8ff",
|
"url": "https://api.github.com/repos/vimeo/psalm/zipball/fc2c6ab4d5fa5d644d8617089f012f3bb84b8703",
|
||||||
"reference": "faf106e717c37b8c81721845dba9de3d8deed8ff",
|
"reference": "fc2c6ab4d5fa5d644d8617089f012f3bb84b8703",
|
||||||
"shasum": ""
|
"shasum": ""
|
||||||
},
|
},
|
||||||
"require": {
|
"require": {
|
||||||
@@ -5206,7 +5278,6 @@
|
|||||||
"php": "^7.1|^8",
|
"php": "^7.1|^8",
|
||||||
"sebastian/diff": "^3.0 || ^4.0",
|
"sebastian/diff": "^3.0 || ^4.0",
|
||||||
"symfony/console": "^3.4.17 || ^4.1.6 || ^5.0 || ^6.0",
|
"symfony/console": "^3.4.17 || ^4.1.6 || ^5.0 || ^6.0",
|
||||||
"symfony/polyfill-php80": "^1.25",
|
|
||||||
"webmozart/path-util": "^2.3"
|
"webmozart/path-util": "^2.3"
|
||||||
},
|
},
|
||||||
"provide": {
|
"provide": {
|
||||||
@@ -5273,27 +5344,27 @@
|
|||||||
],
|
],
|
||||||
"support": {
|
"support": {
|
||||||
"issues": "https://github.com/vimeo/psalm/issues",
|
"issues": "https://github.com/vimeo/psalm/issues",
|
||||||
"source": "https://github.com/vimeo/psalm/tree/4.27.0"
|
"source": "https://github.com/vimeo/psalm/tree/4.22.0"
|
||||||
},
|
},
|
||||||
"time": "2022-08-31T13:47:09+00:00"
|
"time": "2022-02-24T20:34:05+00:00"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "webmozart/assert",
|
"name": "webmozart/assert",
|
||||||
"version": "1.11.0",
|
"version": "1.10.0",
|
||||||
"source": {
|
"source": {
|
||||||
"type": "git",
|
"type": "git",
|
||||||
"url": "https://github.com/webmozarts/assert.git",
|
"url": "https://github.com/webmozarts/assert.git",
|
||||||
"reference": "11cb2199493b2f8a3b53e7f19068fc6aac760991"
|
"reference": "6964c76c7804814a842473e0c8fd15bab0f18e25"
|
||||||
},
|
},
|
||||||
"dist": {
|
"dist": {
|
||||||
"type": "zip",
|
"type": "zip",
|
||||||
"url": "https://api.github.com/repos/webmozarts/assert/zipball/11cb2199493b2f8a3b53e7f19068fc6aac760991",
|
"url": "https://api.github.com/repos/webmozarts/assert/zipball/6964c76c7804814a842473e0c8fd15bab0f18e25",
|
||||||
"reference": "11cb2199493b2f8a3b53e7f19068fc6aac760991",
|
"reference": "6964c76c7804814a842473e0c8fd15bab0f18e25",
|
||||||
"shasum": ""
|
"shasum": ""
|
||||||
},
|
},
|
||||||
"require": {
|
"require": {
|
||||||
"ext-ctype": "*",
|
"php": "^7.2 || ^8.0",
|
||||||
"php": "^7.2 || ^8.0"
|
"symfony/polyfill-ctype": "^1.8"
|
||||||
},
|
},
|
||||||
"conflict": {
|
"conflict": {
|
||||||
"phpstan/phpstan": "<0.12.20",
|
"phpstan/phpstan": "<0.12.20",
|
||||||
@@ -5331,9 +5402,9 @@
|
|||||||
],
|
],
|
||||||
"support": {
|
"support": {
|
||||||
"issues": "https://github.com/webmozarts/assert/issues",
|
"issues": "https://github.com/webmozarts/assert/issues",
|
||||||
"source": "https://github.com/webmozarts/assert/tree/1.11.0"
|
"source": "https://github.com/webmozarts/assert/tree/1.10.0"
|
||||||
},
|
},
|
||||||
"time": "2022-06-03T18:03:27+00:00"
|
"time": "2021-03-09T10:59:23+00:00"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "webmozart/path-util",
|
"name": "webmozart/path-util",
|
||||||
|
|||||||
@@ -1,9 +0,0 @@
|
|||||||
.icon-deck {
|
|
||||||
background-image: url(../img/deck-dark.svg);
|
|
||||||
filter: var(--background-invert-if-dark);
|
|
||||||
}
|
|
||||||
|
|
||||||
.icon-deck-white, .icon-deck.icon-white {
|
|
||||||
background-image: url(../img/deck.svg);
|
|
||||||
filter: var(--background-invert-if-dark);
|
|
||||||
}
|
|
||||||
1
css/deck.scss
Normal file
@@ -0,0 +1 @@
|
|||||||
|
@include icon-black-white('deck', 'deck', 1);
|
||||||
@@ -1,8 +1,11 @@
|
|||||||
<?php
|
/*
|
||||||
/**
|
* @copyright Copyright (c) 2016 Julius Härtl <jus@bitgrid.net>
|
||||||
* @copyright Copyright (c) 2022 Raul Ferreira Fuentes <raul@nextcloud.com>
|
|
||||||
*
|
*
|
||||||
* @author Raul Ferreira Fuentes <raul@nextcloud.com>
|
* @author Julius Härtl <jus@bitgrid.net>
|
||||||
|
* @author Artem Anufrij <artem.anufrij@live.de>
|
||||||
|
* @author Marin Treselj <marin@pixelipo.com>
|
||||||
|
* @author Oskar Kurz <oskar.kurz@gmail.com>
|
||||||
|
* @author Ryan Fletcher <ryan.fletcher@codepassion.ca>
|
||||||
*
|
*
|
||||||
* @license GNU AGPL version 3 or any later version
|
* @license GNU AGPL version 3 or any later version
|
||||||
*
|
*
|
||||||
@@ -20,26 +23,6 @@
|
|||||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
*
|
*
|
||||||
*/
|
*/
|
||||||
namespace OCA\Deck\Model;
|
|
||||||
|
|
||||||
use OCA\Deck\Db\Board;
|
@import 'icons';
|
||||||
|
@import 'print';
|
||||||
class BoardSummary extends Board {
|
|
||||||
private Board $board;
|
|
||||||
|
|
||||||
public function __construct(Board $board) {
|
|
||||||
parent::__construct();
|
|
||||||
$this->board = $board;
|
|
||||||
}
|
|
||||||
|
|
||||||
public function jsonSerialize(): array {
|
|
||||||
return [
|
|
||||||
'id' => $this->getId(),
|
|
||||||
'title' => $this->getTitle()
|
|
||||||
];
|
|
||||||
}
|
|
||||||
|
|
||||||
public function __call($name, $arguments) {
|
|
||||||
return $this->board->__call($name, $arguments);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
41
css/icons.scss
Normal file
@@ -0,0 +1,41 @@
|
|||||||
|
/**
|
||||||
|
* Custom icons
|
||||||
|
*/
|
||||||
|
@include icon-black-white('deck', 'deck', 1);
|
||||||
|
@include icon-black-white('archive', 'deck', 1);
|
||||||
|
@include icon-black-white('circles', 'deck', 1);
|
||||||
|
@include icon-black-white('clone', 'deck', 1);
|
||||||
|
@include icon-black-white('filter', 'deck', 1);
|
||||||
|
@include icon-black-white('filter_set', 'deck', 1);
|
||||||
|
@include icon-black-white('attach', 'deck', 1);
|
||||||
|
@include icon-black-white('reply', 'deck', 1);
|
||||||
|
@include icon-black-white('notifications-dark', 'deck', 1);
|
||||||
|
@include icon-black-white('description', 'deck', 1);
|
||||||
|
|
||||||
|
.icon-toggle-compact-collapsed {
|
||||||
|
@include icon-color('toggle-view-expand', 'deck', $color-black);
|
||||||
|
}
|
||||||
|
|
||||||
|
.icon-toggle-compact-expanded {
|
||||||
|
@include icon-color('toggle-view-collapse', 'deck', $color-black);
|
||||||
|
}
|
||||||
|
.icon-activity {
|
||||||
|
@include icon-color('activity-dark', 'activity', $color-black);
|
||||||
|
}
|
||||||
|
.icon-comment--unread {
|
||||||
|
@include icon-color('comment', 'actions', $color-primary, 1, true);
|
||||||
|
}
|
||||||
|
|
||||||
|
.avatardiv.circles {
|
||||||
|
background: var(--color-primary);
|
||||||
|
}
|
||||||
|
|
||||||
|
.icon-circles {
|
||||||
|
opacity: 1;
|
||||||
|
background-size: 20px;
|
||||||
|
background-position: center center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.icon-colorpicker {
|
||||||
|
background-image: url('../img/color_picker.svg');
|
||||||
|
}
|
||||||
@@ -1,17 +0,0 @@
|
|||||||
const { defineConfig } = require('cypress')
|
|
||||||
|
|
||||||
module.exports = defineConfig({
|
|
||||||
projectId: '1s7wkc',
|
|
||||||
viewportWidth: 1280,
|
|
||||||
viewportHeight: 720,
|
|
||||||
e2e: {
|
|
||||||
// We've imported your old cypress plugins here.
|
|
||||||
// You may want to clean this up later by importing these.
|
|
||||||
setupNodeEvents(on, config) {
|
|
||||||
return require('./cypress/plugins/index.js')(on, config)
|
|
||||||
},
|
|
||||||
baseUrl: 'http://nextcloud.local/index.php',
|
|
||||||
experimentalSessionAndOrigin: true,
|
|
||||||
specPattern: 'cypress/e2e/**/*.{js,jsx,ts,tsx}',
|
|
||||||
},
|
|
||||||
})
|
|
||||||
@@ -1,41 +0,0 @@
|
|||||||
import { randHash } from '../utils'
|
|
||||||
const randUser = randHash()
|
|
||||||
|
|
||||||
describe('Board', function() {
|
|
||||||
const password = 'pass123'
|
|
||||||
|
|
||||||
before(function() {
|
|
||||||
cy.nextcloudCreateUser(randUser, password)
|
|
||||||
})
|
|
||||||
|
|
||||||
beforeEach(function() {
|
|
||||||
cy.login(randUser, password)
|
|
||||||
})
|
|
||||||
|
|
||||||
it('Can create a board', function() {
|
|
||||||
const board = 'TestBoard'
|
|
||||||
|
|
||||||
cy.intercept({
|
|
||||||
method: 'POST',
|
|
||||||
url: '/index.php/apps/deck/boards',
|
|
||||||
}).as('createBoardRequest')
|
|
||||||
|
|
||||||
// Click "Add board"
|
|
||||||
cy.openLeftSidebar()
|
|
||||||
cy.get('#app-navigation-vue .app-navigation__list .app-navigation-entry')
|
|
||||||
.eq(3).find('a').first().click({ force: true })
|
|
||||||
|
|
||||||
// Type the board title
|
|
||||||
cy.get('.board-create form input[type=text]')
|
|
||||||
.type(board, { force: true })
|
|
||||||
|
|
||||||
// Submit
|
|
||||||
cy.get('.board-create form input[type=submit]')
|
|
||||||
.first().click({ force: true })
|
|
||||||
|
|
||||||
cy.wait('@createBoardRequest').its('response.statusCode').should('equal', 200)
|
|
||||||
|
|
||||||
cy.get('.app-navigation__list .app-navigation-entry__children .app-navigation-entry')
|
|
||||||
.contains(board).should('be.visible')
|
|
||||||
})
|
|
||||||
})
|
|
||||||
@@ -1,67 +0,0 @@
|
|||||||
import { randHash } from '../utils'
|
|
||||||
const randUser = randHash()
|
|
||||||
|
|
||||||
const testBoardData = {
|
|
||||||
title: 'MyBoardTest',
|
|
||||||
color: '00ff00',
|
|
||||||
stacks: [
|
|
||||||
{
|
|
||||||
title: 'TestList',
|
|
||||||
cards: [
|
|
||||||
{
|
|
||||||
title: 'Hello world',
|
|
||||||
},
|
|
||||||
],
|
|
||||||
},
|
|
||||||
],
|
|
||||||
}
|
|
||||||
|
|
||||||
describe('Card', function() {
|
|
||||||
before(function() {
|
|
||||||
cy.nextcloudCreateUser(randUser, randUser)
|
|
||||||
cy.createExampleBoard({
|
|
||||||
user: randUser,
|
|
||||||
password: randUser,
|
|
||||||
board: testBoardData,
|
|
||||||
})
|
|
||||||
})
|
|
||||||
|
|
||||||
beforeEach(function() {
|
|
||||||
cy.login(randUser, randUser)
|
|
||||||
})
|
|
||||||
|
|
||||||
it('Can show card details modal', function() {
|
|
||||||
cy.openLeftSidebar()
|
|
||||||
cy.getNavigationEntry(testBoardData.title)
|
|
||||||
.first().click({ force: true })
|
|
||||||
|
|
||||||
cy.get('.board .stack').eq(0).within(() => {
|
|
||||||
cy.get('.card:contains("Hello world")').should('be.visible').click()
|
|
||||||
})
|
|
||||||
|
|
||||||
cy.get('.modal__card').should('be.visible')
|
|
||||||
cy.get('.app-sidebar-header__maintitle').contains('Hello world')
|
|
||||||
})
|
|
||||||
|
|
||||||
it('Can add a card', function() {
|
|
||||||
const newCardTitle = 'Write some cypress tests'
|
|
||||||
|
|
||||||
cy.openLeftSidebar()
|
|
||||||
cy.getNavigationEntry(testBoardData.title)
|
|
||||||
.first().click({ force: true })
|
|
||||||
|
|
||||||
cy.get('.board .stack').eq(0).within(() => {
|
|
||||||
cy.get('.card:contains("Hello world")').should('be.visible')
|
|
||||||
|
|
||||||
cy.get('.button-vue[aria-label*="Add card"]')
|
|
||||||
.first().click()
|
|
||||||
|
|
||||||
cy.get('.stack__card-add form input#new-stack-input-main')
|
|
||||||
.type(newCardTitle)
|
|
||||||
cy.get('.stack__card-add form input[type=submit]')
|
|
||||||
.first().click()
|
|
||||||
cy.get(`.card:contains("${newCardTitle}")`).should('be.visible')
|
|
||||||
})
|
|
||||||
})
|
|
||||||
|
|
||||||
})
|
|
||||||
@@ -1,31 +0,0 @@
|
|||||||
import { randHash } from '../utils'
|
|
||||||
const randUser = randHash()
|
|
||||||
|
|
||||||
describe('Deck dashboard', function() {
|
|
||||||
const password = 'pass123'
|
|
||||||
|
|
||||||
before(function() {
|
|
||||||
cy.nextcloudCreateUser(randUser, password)
|
|
||||||
})
|
|
||||||
|
|
||||||
beforeEach(function() {
|
|
||||||
cy.login(randUser, password)
|
|
||||||
})
|
|
||||||
|
|
||||||
it('Can show the right title on the dashboard', function() {
|
|
||||||
cy.get('.board-title h2')
|
|
||||||
.should('have.length', 1).first()
|
|
||||||
.should('have.text', 'Upcoming cards')
|
|
||||||
})
|
|
||||||
|
|
||||||
it('Can see the default "Personal Board" created for user by default', function() {
|
|
||||||
const defaultBoard = 'Personal'
|
|
||||||
|
|
||||||
cy.openLeftSidebar()
|
|
||||||
cy.get('.app-navigation-entry-wrapper[icon=icon-deck]')
|
|
||||||
.find('ul.app-navigation-entry__children .app-navigation-entry:contains(' + defaultBoard + ')')
|
|
||||||
.first()
|
|
||||||
.contains(defaultBoard)
|
|
||||||
.should('be.visible')
|
|
||||||
})
|
|
||||||
})
|
|
||||||
@@ -1,30 +0,0 @@
|
|||||||
import { randHash } from '../utils'
|
|
||||||
const randUser = randHash()
|
|
||||||
|
|
||||||
describe('Stack', function() {
|
|
||||||
const board = 'TestBoard'
|
|
||||||
const password = 'pass123'
|
|
||||||
const stack = 'List 1'
|
|
||||||
|
|
||||||
before(function() {
|
|
||||||
cy.nextcloudCreateUser(randUser, password)
|
|
||||||
cy.deckCreateBoard({ user: randUser, password }, board)
|
|
||||||
})
|
|
||||||
|
|
||||||
beforeEach(function() {
|
|
||||||
cy.logout()
|
|
||||||
cy.login(randUser, password)
|
|
||||||
})
|
|
||||||
|
|
||||||
it('Can create a stack', function() {
|
|
||||||
cy.openLeftSidebar()
|
|
||||||
cy.getNavigationEntry(board)
|
|
||||||
.click({ force: true })
|
|
||||||
|
|
||||||
cy.get('#stack-add button').first().click()
|
|
||||||
cy.get('#stack-add form input#new-stack-input-main').type(stack)
|
|
||||||
cy.get('#stack-add form input[type=submit]').first().click()
|
|
||||||
|
|
||||||
cy.get('.board .stack').eq(0).contains(stack).should('be.visible')
|
|
||||||
})
|
|
||||||
})
|
|
||||||
@@ -1,5 +0,0 @@
|
|||||||
{
|
|
||||||
"name": "Using fixtures to represent data",
|
|
||||||
"email": "hello@cypress.io",
|
|
||||||
"body": "Fixtures are a great way to mock data for responses to routes"
|
|
||||||
}
|
|
||||||
@@ -1,22 +0,0 @@
|
|||||||
/// <reference types="cypress" />
|
|
||||||
// ***********************************************************
|
|
||||||
// This example plugins/index.js can be used to load plugins
|
|
||||||
//
|
|
||||||
// You can change the location of this file or turn off loading
|
|
||||||
// the plugins file with the 'pluginsFile' configuration option.
|
|
||||||
//
|
|
||||||
// You can read more here:
|
|
||||||
// https://on.cypress.io/plugins-guide
|
|
||||||
// ***********************************************************
|
|
||||||
|
|
||||||
// This function is called when a project is opened or re-opened (e.g. due to
|
|
||||||
// the project's config changing)
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @type {Cypress.PluginConfig}
|
|
||||||
*/
|
|
||||||
// eslint-disable-next-line no-unused-vars
|
|
||||||
module.exports = (on, config) => {
|
|
||||||
// `on` is used to hook into various events Cypress emits
|
|
||||||
// `config` is the resolved Cypress config
|
|
||||||
}
|
|
||||||
@@ -1,159 +0,0 @@
|
|||||||
/**
|
|
||||||
* @copyright Copyright (c) 2019 John Molakvoæ <skjnldsv@protonmail.com>
|
|
||||||
*
|
|
||||||
* @author John Molakvoæ <skjnldsv@protonmail.com>
|
|
||||||
*
|
|
||||||
* @license GNU AGPL version 3 or any later version
|
|
||||||
*
|
|
||||||
* This program is free software: you can redistribute it and/or modify
|
|
||||||
* it under the terms of the GNU Affero General Public License as
|
|
||||||
* published by the Free Software Foundation, either version 3 of the
|
|
||||||
* License, or (at your option) any later version.
|
|
||||||
*
|
|
||||||
* This program is distributed in the hope that it will be useful,
|
|
||||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
||||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
||||||
* GNU Affero General Public License for more details.
|
|
||||||
*
|
|
||||||
* You should have received a copy of the GNU Affero General Public License
|
|
||||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
|
||||||
*
|
|
||||||
*/
|
|
||||||
|
|
||||||
const url = Cypress.config('baseUrl').replace(/\/index.php\/?$/g, '')
|
|
||||||
Cypress.env('baseUrl', url)
|
|
||||||
|
|
||||||
Cypress.Commands.add('login', (user, password, route = '/apps/deck/') => {
|
|
||||||
const session = `${user}-${Date.now()}`
|
|
||||||
cy.session(session, function() {
|
|
||||||
cy.visit(route)
|
|
||||||
cy.get('input[name=user]').type(user)
|
|
||||||
cy.get('input[name=password]').type(password)
|
|
||||||
cy.get('form[name=login] [type=submit]').click()
|
|
||||||
cy.url().should('include', route)
|
|
||||||
})
|
|
||||||
cy.visit(route)
|
|
||||||
})
|
|
||||||
|
|
||||||
Cypress.Commands.add('logout', (route = '/') => {
|
|
||||||
cy.session('_guest', function() {})
|
|
||||||
})
|
|
||||||
|
|
||||||
Cypress.Commands.add('nextcloudCreateUser', (user, password) => {
|
|
||||||
cy.clearCookies()
|
|
||||||
cy.request({
|
|
||||||
method: 'POST',
|
|
||||||
url: `${Cypress.env('baseUrl')}/ocs/v1.php/cloud/users?format=json`,
|
|
||||||
form: true,
|
|
||||||
body: {
|
|
||||||
userid: user,
|
|
||||||
password,
|
|
||||||
},
|
|
||||||
auth: { user: 'admin', pass: 'admin' },
|
|
||||||
headers: {
|
|
||||||
'OCS-ApiRequest': 'true',
|
|
||||||
'Content-Type': 'application/x-www-form-urlencoded',
|
|
||||||
},
|
|
||||||
}).then((response) => {
|
|
||||||
cy.log(`Created user ${user}`, response.status)
|
|
||||||
})
|
|
||||||
})
|
|
||||||
|
|
||||||
Cypress.Commands.add('nextcloudUpdateUser', (user, password, key, value) => {
|
|
||||||
cy.request({
|
|
||||||
method: 'PUT',
|
|
||||||
url: `${Cypress.env('baseUrl')}/ocs/v2.php/cloud/users/${user}`,
|
|
||||||
form: true,
|
|
||||||
body: { key, value },
|
|
||||||
auth: { user, pass: password },
|
|
||||||
headers: {
|
|
||||||
'OCS-ApiRequest': 'true',
|
|
||||||
'Content-Type': 'application/x-www-form-urlencoded',
|
|
||||||
},
|
|
||||||
}).then((response) => {
|
|
||||||
cy.log(`Updated user ${user} ${key} to ${value}`, response.status)
|
|
||||||
})
|
|
||||||
})
|
|
||||||
|
|
||||||
Cypress.Commands.add('openLeftSidebar', () => {
|
|
||||||
cy.get('.app-navigation button.app-navigation-toggle').click()
|
|
||||||
})
|
|
||||||
|
|
||||||
Cypress.Commands.add('deckCreateBoard', ({ user, password }, title) => {
|
|
||||||
cy.login(user, password)
|
|
||||||
|
|
||||||
cy.get('.app-navigation button.app-navigation-toggle').click()
|
|
||||||
cy.get('#app-navigation-vue .app-navigation__list .app-navigation-entry')
|
|
||||||
.eq(3)
|
|
||||||
.find('a')
|
|
||||||
.first()
|
|
||||||
.click({ force: true })
|
|
||||||
|
|
||||||
cy.get('.board-create form input[type=text]').type(title, { force: true })
|
|
||||||
|
|
||||||
cy.get('.board-create form input[type=submit]')
|
|
||||||
.first()
|
|
||||||
.click({ force: true })
|
|
||||||
})
|
|
||||||
|
|
||||||
Cypress.Commands.add('deckCreateList', ({ user, password }, title) => {
|
|
||||||
cy.login(user, password)
|
|
||||||
|
|
||||||
cy.get('.app-navigation button.app-navigation-toggle').click()
|
|
||||||
cy.get('#app-navigation-vue .app-navigation__list .app-navigation-entry')
|
|
||||||
.eq(3)
|
|
||||||
.find('a.app-navigation-entry-link')
|
|
||||||
.first()
|
|
||||||
.click({ force: true })
|
|
||||||
|
|
||||||
cy.get('#stack-add button').first().click()
|
|
||||||
cy.get('#stack-add form input#new-stack-input-main').type(title)
|
|
||||||
cy.get('#stack-add form input[type=submit]').first().click()
|
|
||||||
})
|
|
||||||
|
|
||||||
Cypress.Commands.add('createExampleBoard', ({ user, password, board }) => {
|
|
||||||
cy.request({
|
|
||||||
method: 'POST',
|
|
||||||
url: `${Cypress.env('baseUrl')}/index.php/apps/deck/api/v1.0/boards`,
|
|
||||||
auth: {
|
|
||||||
user,
|
|
||||||
password,
|
|
||||||
},
|
|
||||||
body: { title: board.title, color: board.color ?? 'ff0000' },
|
|
||||||
}).then((boardResponse) => {
|
|
||||||
expect(boardResponse.status).to.eq(200)
|
|
||||||
const boardData = boardResponse.body
|
|
||||||
for (const stackIndex in board.stacks) {
|
|
||||||
const stack = board.stacks[stackIndex]
|
|
||||||
cy.request({
|
|
||||||
method: 'POST',
|
|
||||||
url: `${Cypress.env('baseUrl')}/index.php/apps/deck/api/v1.0/boards/${boardData.id}/stacks`,
|
|
||||||
auth: {
|
|
||||||
user,
|
|
||||||
password,
|
|
||||||
},
|
|
||||||
body: { title: stack.title, order: 0 },
|
|
||||||
}).then((stackResponse) => {
|
|
||||||
const stackData = stackResponse.body
|
|
||||||
for (const cardIndex in stack.cards) {
|
|
||||||
const card = stack.cards[cardIndex]
|
|
||||||
cy.request({
|
|
||||||
method: 'POST',
|
|
||||||
url: `${Cypress.env('baseUrl')}/index.php/apps/deck/api/v1.0/boards/${boardData.id}/stacks/${stackData.id}/cards`,
|
|
||||||
auth: {
|
|
||||||
user,
|
|
||||||
password,
|
|
||||||
},
|
|
||||||
body: { title: card.title },
|
|
||||||
})
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
|
||||||
})
|
|
||||||
})
|
|
||||||
|
|
||||||
Cypress.Commands.add('getNavigationEntry', (boardTitle) => {
|
|
||||||
return cy.get('.app-navigation-entry-wrapper[icon=icon-deck]')
|
|
||||||
.find('ul.app-navigation-entry__children .app-navigation-entry:contains(' + boardTitle + ')')
|
|
||||||
.find('a.app-navigation-entry-link')
|
|
||||||
})
|
|
||||||
@@ -1,20 +0,0 @@
|
|||||||
// ***********************************************************
|
|
||||||
// This example support/index.js is processed and
|
|
||||||
// loaded automatically before your test files.
|
|
||||||
//
|
|
||||||
// This is a great place to put global configuration and
|
|
||||||
// behavior that modifies Cypress.
|
|
||||||
//
|
|
||||||
// You can change the location of this file or turn off
|
|
||||||
// automatically serving support files with the
|
|
||||||
// 'supportFile' configuration option.
|
|
||||||
//
|
|
||||||
// You can read more here:
|
|
||||||
// https://on.cypress.io/configuration
|
|
||||||
// ***********************************************************
|
|
||||||
|
|
||||||
// Import commands.js using ES2015 syntax:
|
|
||||||
import './commands'
|
|
||||||
|
|
||||||
// Alternatively you can use CommonJS syntax:
|
|
||||||
// require('./commands')
|
|
||||||
@@ -1 +0,0 @@
|
|||||||
export const randHash = () => Math.random().toString(36).replace(/[^a-z]+/g, '').slice(0, 10)
|
|
||||||
@@ -90,7 +90,7 @@ Steps:
|
|||||||
* Create the configuration file
|
* Create the configuration file
|
||||||
* Execute the import informing the import file path, data file and source as `Trello JSON`
|
* Execute the import informing the import file path, data file and source as `Trello JSON`
|
||||||
|
|
||||||
Create the configuration file respecting the [JSON Schema](https://github.com/nextcloud/deck/blob/master/lib/Service/Importer/fixtures/config-trelloJson-schema.json) for import `Trello JSON`
|
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:
|
Example configuration file:
|
||||||
```json
|
```json
|
||||||
@@ -120,7 +120,7 @@ https://api.trello.com/1/members/me/boards?key={yourKey}&token={yourToken}&field
|
|||||||
This ID you will use in the configuration file in the `board` property
|
This ID you will use in the configuration file in the `board` property
|
||||||
* Create the configuration file
|
* Create the configuration file
|
||||||
|
|
||||||
Create the configuration file respecting the [JSON Schema](https://github.com/nextcloud/deck/blob/master/lib/Service/Importer/fixtures/config-trelloApi-schema.json) for import `Trello JSON`
|
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:
|
Example configuration file:
|
||||||
```json
|
```json
|
||||||
|
|||||||
@@ -1,4 +0,0 @@
|
|||||||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
|
||||||
<svg xmlns="http://www.w3.org/2000/svg" height="32" width="32" version="1.0" viewbox="0 0 32 32">
|
|
||||||
<path d="m16 1-10 18h11l-1 12 10-18h-11z"/>
|
|
||||||
</svg>
|
|
||||||
|
Before Width: | Height: | Size: 205 B |
@@ -1,4 +0,0 @@
|
|||||||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
|
||||||
<svg xmlns="http://www.w3.org/2000/svg" height="32" width="32" version="1.0" viewBox="0 0 32 32">
|
|
||||||
<path d="m16 1-10 18h11l-1 12 10-18h-11z" fill="#FFF"/>
|
|
||||||
</svg>
|
|
||||||
|
Before Width: | Height: | Size: 217 B |
1
img/archive-white.svg
Normal file
@@ -0,0 +1 @@
|
|||||||
|
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 16 16"><g transform="translate(0 -1036.362)" fill="#fff"><path d="M1.93 1041.296c-.185 0-.336.138-.336.31v9.842c0 .172.15.313.336.313h12.517c.185 0 .333-.14.333-.313v-9.842c0-.172-.148-.31-.333-.31H1.93zm4.124 1.507h4.223c.39 0 .705.314.705.704v.43c0 .39-.315.705-.705.705H6.054a.703.703 0 0 1-.705-.705v-.43c0-.39.314-.704.705-.704z"/><rect width="15.742" height="2.296" x=".136" y="1037.543" ry="0"/></g></svg>
|
||||||
|
After Width: | Height: | Size: 488 B |
@@ -1 +0,0 @@
|
|||||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 58 58" width="512" height="512"><g fill="#000"><path d="M54.319 37.839C54.762 35.918 55 33.96 55 32c0-9.095-4.631-17.377-12.389-22.153a1 1 0 1 0-1.049 1.703C48.724 15.96 53 23.604 53 32c0 1.726-.2 3.451-.573 5.147A6.992 6.992 0 0 0 51 37c-3.86 0-7 3.141-7 7s3.14 7 7 7 7-3.141 7-7a7.006 7.006 0 0 0-3.681-6.161zM38.171 54.182A23.867 23.867 0 0 1 29 56a24.047 24.047 0 0 1-17.017-7.092A6.974 6.974 0 0 0 14 44c0-3.859-3.14-7-7-7s-7 3.141-7 7 3.14 7 7 7a6.952 6.952 0 0 0 3.381-.875C15.26 55.136 21.994 58 29 58c3.435 0 6.778-.663 9.936-1.971.51-.211.753-.796.542-1.307a1.001 1.001 0 0 0-1.307-.54zM4 31.213a1 1 0 0 0 1.068-.927c.712-10.089 7.586-18.52 17.22-21.314C23.142 11.874 25.825 14 29 14c3.86 0 7-3.141 7-7s-3.14-7-7-7c-3.851 0-6.985 3.127-6.999 6.975C11.42 9.922 3.851 19.12 3.073 30.146A.999.999 0 0 0 4 31.213z"/></g></svg>
|
|
||||||
|
Before Width: | Height: | Size: 885 B |
@@ -1 +1 @@
|
|||||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 58 58" width="512" height="512"><g fill="#fff"><path d="M54.319 37.839C54.762 35.918 55 33.96 55 32c0-9.095-4.631-17.377-12.389-22.153a1 1 0 1 0-1.049 1.703C48.724 15.96 53 23.604 53 32c0 1.726-.2 3.451-.573 5.147A6.992 6.992 0 0 0 51 37c-3.86 0-7 3.141-7 7s3.14 7 7 7 7-3.141 7-7a7.006 7.006 0 0 0-3.681-6.161zM38.171 54.182A23.867 23.867 0 0 1 29 56a24.047 24.047 0 0 1-17.017-7.092A6.974 6.974 0 0 0 14 44c0-3.859-3.14-7-7-7s-7 3.141-7 7 3.14 7 7 7a6.952 6.952 0 0 0 3.381-.875C15.26 55.136 21.994 58 29 58c3.435 0 6.778-.663 9.936-1.971.51-.211.753-.796.542-1.307a1.001 1.001 0 0 0-1.307-.54zM4 31.213a1 1 0 0 0 1.068-.927c.712-10.089 7.586-18.52 17.22-21.314C23.142 11.874 25.825 14 29 14c3.86 0 7-3.141 7-7s-3.14-7-7-7c-3.851 0-6.985 3.127-6.999 6.975C11.42 9.922 3.851 19.12 3.073 30.146A.999.999 0 0 0 4 31.213z"/></g></svg>
|
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 58 58" width="512" height="512"><g fill="#000"><path d="M54.319 37.839C54.762 35.918 55 33.96 55 32c0-9.095-4.631-17.377-12.389-22.153a1 1 0 1 0-1.049 1.703C48.724 15.96 53 23.604 53 32c0 1.726-.2 3.451-.573 5.147A6.992 6.992 0 0 0 51 37c-3.86 0-7 3.141-7 7s3.14 7 7 7 7-3.141 7-7a7.006 7.006 0 0 0-3.681-6.161zM38.171 54.182A23.867 23.867 0 0 1 29 56a24.047 24.047 0 0 1-17.017-7.092A6.974 6.974 0 0 0 14 44c0-3.859-3.14-7-7-7s-7 3.141-7 7 3.14 7 7 7a6.952 6.952 0 0 0 3.381-.875C15.26 55.136 21.994 58 29 58c3.435 0 6.778-.663 9.936-1.971.51-.211.753-.796.542-1.307a1.001 1.001 0 0 0-1.307-.54zM4 31.213a1 1 0 0 0 1.068-.927c.712-10.089 7.586-18.52 17.22-21.314C23.142 11.874 25.825 14 29 14c3.86 0 7-3.141 7-7s-3.14-7-7-7c-3.851 0-6.985 3.127-6.999 6.975C11.42 9.922 3.851 19.12 3.073 30.146A.999.999 0 0 0 4 31.213z"/></g></svg>
|
||||||
|
Before Width: | Height: | Size: 885 B After Width: | Height: | Size: 885 B |
1
img/clone.svg
Normal file
@@ -0,0 +1 @@
|
|||||||
|
<svg width="16" height="16" version="1.1" xmlns="http://www.w3.org/2000/svg"><path d="M11.8 13.8H2.2V4.2h9.6m1.2 0c0-.67-.53-1.2-1.2-1.2H2.2C1.53 3 1 3.53 1 4.2v9.6c0 .67.53 1.2 1.2 1.2h9.6c.67 0 1.2-.53 1.2-1.2"/><path d="m4.2 1c-0.67 0-1.2 0.54-1.2 1.2h10.8v10.8c0.67 0 1.2-0.53 1.2-1.2v-9.6c0-0.67-0.53-1.2-1.2-1.2z"/></svg>
|
||||||
|
After Width: | Height: | Size: 327 B |
1
img/reply.svg
Normal file
@@ -0,0 +1 @@
|
|||||||
|
<svg xmlns="http://www.w3.org/2000/svg" height="16" width="16"><path d="M15 15s-.4-7.8-7-10V1L1 8l7 7v-4c5.1 0 7 4 7 4z"/></svg>
|
||||||
|
After Width: | Height: | Size: 128 B |
@@ -38,6 +38,7 @@ use OCA\Deck\Db\CardMapper;
|
|||||||
use OCA\Deck\Db\Label;
|
use OCA\Deck\Db\Label;
|
||||||
use OCA\Deck\Db\Stack;
|
use OCA\Deck\Db\Stack;
|
||||||
use OCA\Deck\Db\StackMapper;
|
use OCA\Deck\Db\StackMapper;
|
||||||
|
use OCA\Deck\NoPermissionException;
|
||||||
use OCA\Deck\Service\PermissionService;
|
use OCA\Deck\Service\PermissionService;
|
||||||
use OCP\Activity\IEvent;
|
use OCP\Activity\IEvent;
|
||||||
use OCP\Activity\IManager;
|
use OCP\Activity\IManager;
|
||||||
@@ -45,9 +46,7 @@ use OCP\AppFramework\Db\DoesNotExistException;
|
|||||||
use OCP\AppFramework\Db\MultipleObjectsReturnedException;
|
use OCP\AppFramework\Db\MultipleObjectsReturnedException;
|
||||||
use OCP\Comments\IComment;
|
use OCP\Comments\IComment;
|
||||||
use OCP\IUser;
|
use OCP\IUser;
|
||||||
use OCP\Server;
|
|
||||||
use OCP\L10N\IFactory;
|
use OCP\L10N\IFactory;
|
||||||
use Psr\Log\LoggerInterface;
|
|
||||||
|
|
||||||
class ActivityManager {
|
class ActivityManager {
|
||||||
public const DECK_NOAUTHOR_COMMENT_SYSTEM_ENFORCED = 'DECK_NOAUTHOR_COMMENT_SYSTEM_ENFORCED';
|
public const DECK_NOAUTHOR_COMMENT_SYSTEM_ENFORCED = 'DECK_NOAUTHOR_COMMENT_SYSTEM_ENFORCED';
|
||||||
@@ -55,14 +54,14 @@ class ActivityManager {
|
|||||||
public const SUBJECT_PARAMS_MAX_LENGTH = 4000;
|
public const SUBJECT_PARAMS_MAX_LENGTH = 4000;
|
||||||
public const SHORTENED_DESCRIPTION_MAX_LENGTH = 2000;
|
public const SHORTENED_DESCRIPTION_MAX_LENGTH = 2000;
|
||||||
|
|
||||||
private IManager $manager;
|
private $manager;
|
||||||
private ?string $userId;
|
private $userId;
|
||||||
private PermissionService $permissionService;
|
private $permissionService;
|
||||||
private BoardMapper $boardMapper;
|
private $boardMapper;
|
||||||
private CardMapper $cardMapper;
|
private $cardMapper;
|
||||||
private AclMapper $aclMapper;
|
private $aclMapper;
|
||||||
private StackMapper $stackMapper;
|
private $stackMapper;
|
||||||
private IFactory $l10nFactory;
|
private $l10nFactory;
|
||||||
|
|
||||||
public const DECK_OBJECT_BOARD = 'deck_board';
|
public const DECK_OBJECT_BOARD = 'deck_board';
|
||||||
public const DECK_OBJECT_CARD = 'deck_card';
|
public const DECK_OBJECT_CARD = 'deck_card';
|
||||||
@@ -116,7 +115,7 @@ class ActivityManager {
|
|||||||
StackMapper $stackMapper,
|
StackMapper $stackMapper,
|
||||||
AclMapper $aclMapper,
|
AclMapper $aclMapper,
|
||||||
IFactory $l10nFactory,
|
IFactory $l10nFactory,
|
||||||
?string $userId
|
$userId
|
||||||
) {
|
) {
|
||||||
$this->manager = $manager;
|
$this->manager = $manager;
|
||||||
$this->permissionService = $permissionsService;
|
$this->permissionService = $permissionsService;
|
||||||
@@ -312,10 +311,10 @@ class ActivityManager {
|
|||||||
try {
|
try {
|
||||||
$object = $this->findObjectForEntity($objectType, $entity);
|
$object = $this->findObjectForEntity($objectType, $entity);
|
||||||
} catch (DoesNotExistException $e) {
|
} catch (DoesNotExistException $e) {
|
||||||
Server::get(LoggerInterface::class)->error('Could not create activity entry for ' . $subject . '. Entity not found.', (array)$entity);
|
\OC::$server->getLogger()->error('Could not create activity entry for ' . $subject . '. Entity not found.', (array)$entity);
|
||||||
return null;
|
return null;
|
||||||
} catch (MultipleObjectsReturnedException $e) {
|
} catch (MultipleObjectsReturnedException $e) {
|
||||||
Server::get(LoggerInterface::class)->error('Could not create activity entry for ' . $subject . '. Entity not found.', (array)$entity);
|
\OC::$server->getLogger()->error('Could not create activity entry for ' . $subject . '. Entity not found.', (array)$entity);
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -367,15 +366,7 @@ class ActivityManager {
|
|||||||
case self::SUBJECT_CARD_USER_ASSIGN:
|
case self::SUBJECT_CARD_USER_ASSIGN:
|
||||||
case self::SUBJECT_CARD_USER_UNASSIGN:
|
case self::SUBJECT_CARD_USER_UNASSIGN:
|
||||||
$subjectParams = $this->findDetailsForCard($entity->getId(), $subject);
|
$subjectParams = $this->findDetailsForCard($entity->getId(), $subject);
|
||||||
|
break;
|
||||||
if (isset($additionalParams['after']) && $additionalParams['after'] instanceof \DateTimeInterface) {
|
|
||||||
$additionalParams['after'] = $additionalParams['after']->format('c');
|
|
||||||
}
|
|
||||||
if (isset($additionalParams['before']) && $additionalParams['before'] instanceof \DateTimeInterface) {
|
|
||||||
$additionalParams['before'] = $additionalParams['before']->format('c');
|
|
||||||
}
|
|
||||||
|
|
||||||
break;
|
|
||||||
case self::SUBJECT_ATTACHMENT_CREATE:
|
case self::SUBJECT_ATTACHMENT_CREATE:
|
||||||
case self::SUBJECT_ATTACHMENT_UPDATE:
|
case self::SUBJECT_ATTACHMENT_UPDATE:
|
||||||
case self::SUBJECT_ATTACHMENT_DELETE:
|
case self::SUBJECT_ATTACHMENT_DELETE:
|
||||||
@@ -559,4 +550,24 @@ class ActivityManager {
|
|||||||
'board' => $board
|
'board' => $board
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public function canSeeCardActivity(int $cardId): bool {
|
||||||
|
try {
|
||||||
|
$this->permissionService->checkPermission($this->cardMapper, $cardId, Acl::PERMISSION_READ);
|
||||||
|
$card = $this->cardMapper->find($cardId);
|
||||||
|
return $card->getDeletedAt() === 0;
|
||||||
|
} catch (NoPermissionException $e) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public function canSeeBoardActivity(int $boardId): bool {
|
||||||
|
try {
|
||||||
|
$this->permissionService->checkPermission($this->boardMapper, $boardId, Acl::PERMISSION_READ);
|
||||||
|
$board = $this->boardMapper->find($boardId);
|
||||||
|
return $board->getDeletedAt() === 0;
|
||||||
|
} catch (NoPermissionException $e) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -111,6 +111,9 @@ class DeckProvider implements IProvider {
|
|||||||
$event->setAuthor($author);
|
$event->setAuthor($author);
|
||||||
}
|
}
|
||||||
if ($event->getObjectType() === ActivityManager::DECK_OBJECT_BOARD) {
|
if ($event->getObjectType() === ActivityManager::DECK_OBJECT_BOARD) {
|
||||||
|
if (!$this->activityManager->canSeeBoardActivity($event->getObjectId())) {
|
||||||
|
throw new \InvalidArgumentException();
|
||||||
|
}
|
||||||
if (isset($subjectParams['board']) && $event->getObjectName() === '') {
|
if (isset($subjectParams['board']) && $event->getObjectName() === '') {
|
||||||
$event->setObject($event->getObjectType(), $event->getObjectId(), $subjectParams['board']['title']);
|
$event->setObject($event->getObjectType(), $event->getObjectId(), $subjectParams['board']['title']);
|
||||||
}
|
}
|
||||||
@@ -125,6 +128,9 @@ class DeckProvider implements IProvider {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (isset($subjectParams['card']) && $event->getObjectType() === ActivityManager::DECK_OBJECT_CARD) {
|
if (isset($subjectParams['card']) && $event->getObjectType() === ActivityManager::DECK_OBJECT_CARD) {
|
||||||
|
if (!$this->activityManager->canSeeCardActivity($event->getObjectId())) {
|
||||||
|
throw new \InvalidArgumentException();
|
||||||
|
}
|
||||||
if ($event->getObjectName() === '') {
|
if ($event->getObjectName() === '') {
|
||||||
$event->setObject($event->getObjectType(), $event->getObjectId(), $subjectParams['card']['title']);
|
$event->setObject($event->getObjectType(), $event->getObjectId(), $subjectParams['card']['title']);
|
||||||
}
|
}
|
||||||
@@ -312,19 +318,12 @@ class DeckProvider implements IProvider {
|
|||||||
$userLanguage = $this->config->getUserValue($event->getAuthor(), 'core', 'lang', $this->l10nFactory->findLanguage());
|
$userLanguage = $this->config->getUserValue($event->getAuthor(), 'core', 'lang', $this->l10nFactory->findLanguage());
|
||||||
$userLocale = $this->config->getUserValue($event->getAuthor(), 'core', 'locale', $this->l10nFactory->findLocale());
|
$userLocale = $this->config->getUserValue($event->getAuthor(), 'core', 'locale', $this->l10nFactory->findLocale());
|
||||||
$l10n = $this->l10nFactory->get('deck', $userLanguage, $userLocale);
|
$l10n = $this->l10nFactory->get('deck', $userLanguage, $userLocale);
|
||||||
if (is_array($subjectParams['after'])) {
|
$date = new \DateTime($subjectParams['after']);
|
||||||
// Unluckily there was a time when we stored jsonSerialized date objects in the database
|
$date->setTimezone(new \DateTimeZone(\date_default_timezone_get()));
|
||||||
// Broken in 1.8.0 and fixed again in 1.8.1
|
|
||||||
$date = new \DateTime($subjectParams['after']['date']);
|
|
||||||
$date->setTimezone(new \DateTimeZone(\date_default_timezone_get()));
|
|
||||||
} else {
|
|
||||||
$date = new \DateTime($subjectParams['after']);
|
|
||||||
$date->setTimezone(new \DateTimeZone(\date_default_timezone_get()));
|
|
||||||
}
|
|
||||||
$params['after'] = [
|
$params['after'] = [
|
||||||
'type' => 'highlight',
|
'type' => 'highlight',
|
||||||
'id' => 'dt:' . $subjectParams['after'],
|
'id' => 'dt:' . $subjectParams['after'],
|
||||||
'name' => $l10n->l('datetime', $date),
|
'name' => $l10n->l('datetime', $date)
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
return $params;
|
return $params;
|
||||||
|
|||||||
@@ -47,7 +47,6 @@ use OCA\Deck\Listeners\FullTextSearchEventListener;
|
|||||||
use OCA\Deck\Middleware\DefaultBoardMiddleware;
|
use OCA\Deck\Middleware\DefaultBoardMiddleware;
|
||||||
use OCA\Deck\Middleware\ExceptionMiddleware;
|
use OCA\Deck\Middleware\ExceptionMiddleware;
|
||||||
use OCA\Deck\Notification\Notifier;
|
use OCA\Deck\Notification\Notifier;
|
||||||
use OCA\Deck\Reference\CardReferenceProvider;
|
|
||||||
use OCA\Deck\Search\CardCommentProvider;
|
use OCA\Deck\Search\CardCommentProvider;
|
||||||
use OCA\Deck\Search\DeckProvider;
|
use OCA\Deck\Search\DeckProvider;
|
||||||
use OCA\Deck\Service\PermissionService;
|
use OCA\Deck\Service\PermissionService;
|
||||||
@@ -58,22 +57,20 @@ use OCP\AppFramework\Bootstrap\IBootContext;
|
|||||||
use OCP\AppFramework\Bootstrap\IBootstrap;
|
use OCP\AppFramework\Bootstrap\IBootstrap;
|
||||||
use OCP\AppFramework\Bootstrap\IRegistrationContext;
|
use OCP\AppFramework\Bootstrap\IRegistrationContext;
|
||||||
use OCP\AppFramework\Http\Events\BeforeTemplateRenderedEvent;
|
use OCP\AppFramework\Http\Events\BeforeTemplateRenderedEvent;
|
||||||
use OCP\Collaboration\Reference\RenderReferenceEvent;
|
|
||||||
use OCP\Collaboration\Resources\IProviderManager;
|
use OCP\Collaboration\Resources\IProviderManager;
|
||||||
use OCP\Comments\CommentsEntityEvent;
|
use OCP\Comments\CommentsEntityEvent;
|
||||||
use OCP\Comments\ICommentsManager;
|
use OCP\Comments\ICommentsManager;
|
||||||
use OCP\EventDispatcher\Event;
|
use OCP\EventDispatcher\Event;
|
||||||
use OCP\EventDispatcher\IEventDispatcher;
|
use OCP\EventDispatcher\IEventDispatcher;
|
||||||
use OCP\Group\Events\GroupDeletedEvent;
|
|
||||||
use OCP\IConfig;
|
use OCP\IConfig;
|
||||||
use OCP\IDBConnection;
|
use OCP\IDBConnection;
|
||||||
|
use OCP\IGroup;
|
||||||
use OCP\IGroupManager;
|
use OCP\IGroupManager;
|
||||||
use OCP\IRequest;
|
use OCP\IServerContainer;
|
||||||
use OCP\Server;
|
use OCP\IUser;
|
||||||
use OCP\IUserManager;
|
use OCP\IUserManager;
|
||||||
use OCP\Notification\IManager as NotificationManager;
|
use OCP\Notification\IManager as NotificationManager;
|
||||||
use OCP\Share\IManager;
|
use OCP\Share\IManager;
|
||||||
use OCP\User\Events\UserDeletedEvent;
|
|
||||||
use OCP\Util;
|
use OCP\Util;
|
||||||
use Psr\Container\ContainerInterface;
|
use Psr\Container\ContainerInterface;
|
||||||
|
|
||||||
@@ -82,16 +79,13 @@ class Application extends App implements IBootstrap {
|
|||||||
|
|
||||||
public const COMMENT_ENTITY_TYPE = 'deckCard';
|
public const COMMENT_ENTITY_TYPE = 'deckCard';
|
||||||
|
|
||||||
|
/** @var IServerContainer */
|
||||||
|
private $server;
|
||||||
|
|
||||||
public function __construct(array $urlParams = []) {
|
public function __construct(array $urlParams = []) {
|
||||||
parent::__construct(self::APP_ID, $urlParams);
|
parent::__construct(self::APP_ID, $urlParams);
|
||||||
|
|
||||||
// TODO move this back to ::register after fixing the autoload issue
|
$this->server = \OC::$server;
|
||||||
// (and use a listener class)
|
|
||||||
$container = $this->getContainer();
|
|
||||||
$eventDispatcher = $container->get(IEventDispatcher::class);
|
|
||||||
$eventDispatcher->addListener(RenderReferenceEvent::class, function () {
|
|
||||||
Util::addScript(self::APP_ID, self::APP_ID . '-card-reference');
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public function boot(IBootContext $context): void {
|
public function boot(IBootContext $context): void {
|
||||||
@@ -130,12 +124,8 @@ class Application extends App implements IBootstrap {
|
|||||||
$context->registerSearchProvider(CardCommentProvider::class);
|
$context->registerSearchProvider(CardCommentProvider::class);
|
||||||
$context->registerDashboardWidget(DeckWidget::class);
|
$context->registerDashboardWidget(DeckWidget::class);
|
||||||
|
|
||||||
// reference widget
|
|
||||||
$context->registerReferenceProvider(CardReferenceProvider::class);
|
|
||||||
// $context->registerEventListener(RenderReferenceEvent::class, CardReferenceListener::class);
|
|
||||||
|
|
||||||
$context->registerEventListener(BeforeTemplateRenderedEvent::class, BeforeTemplateRenderedListener::class);
|
$context->registerEventListener(BeforeTemplateRenderedEvent::class, BeforeTemplateRenderedListener::class);
|
||||||
|
|
||||||
// Event listening for full text search indexing
|
// Event listening for full text search indexing
|
||||||
$context->registerEventListener(CardCreatedEvent::class, FullTextSearchEventListener::class);
|
$context->registerEventListener(CardCreatedEvent::class, FullTextSearchEventListener::class);
|
||||||
$context->registerEventListener(CardUpdatedEvent::class, FullTextSearchEventListener::class);
|
$context->registerEventListener(CardUpdatedEvent::class, FullTextSearchEventListener::class);
|
||||||
@@ -151,43 +141,33 @@ class Application extends App implements IBootstrap {
|
|||||||
|
|
||||||
private function registerUserGroupHooks(IUserManager $userManager, IGroupManager $groupManager): void {
|
private function registerUserGroupHooks(IUserManager $userManager, IGroupManager $groupManager): void {
|
||||||
$container = $this->getContainer();
|
$container = $this->getContainer();
|
||||||
/** @var IEventDispatcher $eventDispatcher */
|
|
||||||
$eventDispatcher = $container->get(IEventDispatcher::class);
|
|
||||||
// Delete user/group acl entries when they get deleted
|
// Delete user/group acl entries when they get deleted
|
||||||
$eventDispatcher->addListener(UserDeletedEvent::class, static function (Event $event) use ($container): void {
|
$userManager->listen('\OC\User', 'postDelete', static function (IUser $user) use ($container) {
|
||||||
if (!($event instanceof UserDeletedEvent)) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
$user = $event->getUser();
|
|
||||||
// delete existing acl entries for deleted user
|
// delete existing acl entries for deleted user
|
||||||
/** @var AclMapper $aclMapper */
|
/** @var AclMapper $aclMapper */
|
||||||
$aclMapper = $container->get(AclMapper::class);
|
$aclMapper = $container->query(AclMapper::class);
|
||||||
$acls = $aclMapper->findByParticipant(Acl::PERMISSION_TYPE_USER, $user->getUID());
|
$acls = $aclMapper->findByParticipant(Acl::PERMISSION_TYPE_USER, $user->getUID());
|
||||||
foreach ($acls as $acl) {
|
foreach ($acls as $acl) {
|
||||||
$aclMapper->delete($acl);
|
$aclMapper->delete($acl);
|
||||||
}
|
}
|
||||||
// delete existing user assignments
|
// delete existing user assignments
|
||||||
$assignmentMapper = $container->get(AssignmentMapper::class);
|
$assignmentMapper = $container->query(AssignmentMapper::class);
|
||||||
$assignments = $assignmentMapper->findByParticipant($user->getUID());
|
$assignments = $assignmentMapper->findByParticipant($user->getUID());
|
||||||
foreach ($assignments as $assignment) {
|
foreach ($assignments as $assignment) {
|
||||||
$assignmentMapper->delete($assignment);
|
$assignmentMapper->delete($assignment);
|
||||||
}
|
}
|
||||||
|
|
||||||
/** @var BoardMapper $boardMapper */
|
/** @var BoardMapper $boardMapper */
|
||||||
$boardMapper = $container->get(BoardMapper::class);
|
$boardMapper = $container->query(BoardMapper::class);
|
||||||
$boards = $boardMapper->findAllByOwner($user->getUID());
|
$boards = $boardMapper->findAllByOwner($user->getUID());
|
||||||
foreach ($boards as $board) {
|
foreach ($boards as $board) {
|
||||||
$boardMapper->delete($board);
|
$boardMapper->delete($board);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
$eventDispatcher->addListener(GroupDeletedEvent::class, static function (Event $event) use ($container): void {
|
$groupManager->listen('\OC\Group', 'postDelete', static function (IGroup $group) use ($container) {
|
||||||
if (!($event instanceof GroupDeletedEvent)) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
$group = $event->getGroup();
|
|
||||||
/** @var AclMapper $aclMapper */
|
/** @var AclMapper $aclMapper */
|
||||||
$aclMapper = $container->get(AclMapper::class);
|
$aclMapper = $container->query(AclMapper::class);
|
||||||
$aclMapper->findByParticipant(Acl::PERMISSION_TYPE_GROUP, $group->getGID());
|
$aclMapper->findByParticipant(Acl::PERMISSION_TYPE_GROUP, $group->getGID());
|
||||||
$acls = $aclMapper->findByParticipant(Acl::PERMISSION_TYPE_GROUP, $group->getGID());
|
$acls = $aclMapper->findByParticipant(Acl::PERMISSION_TYPE_GROUP, $group->getGID());
|
||||||
foreach ($acls as $acl) {
|
foreach ($acls as $acl) {
|
||||||
@@ -201,7 +181,6 @@ class Application extends App implements IBootstrap {
|
|||||||
$event->addEntityCollection(self::COMMENT_ENTITY_TYPE, function ($name) {
|
$event->addEntityCollection(self::COMMENT_ENTITY_TYPE, function ($name) {
|
||||||
/** @var CardMapper */
|
/** @var CardMapper */
|
||||||
$cardMapper = $this->getContainer()->get(CardMapper::class);
|
$cardMapper = $this->getContainer()->get(CardMapper::class);
|
||||||
/** @var PermissionService $permissionService */
|
|
||||||
$permissionService = $this->getContainer()->get(PermissionService::class);
|
$permissionService = $this->getContainer()->get(PermissionService::class);
|
||||||
|
|
||||||
try {
|
try {
|
||||||
@@ -224,7 +203,7 @@ class Application extends App implements IBootstrap {
|
|||||||
$resourceManager->registerResourceProvider(ResourceProviderCard::class);
|
$resourceManager->registerResourceProvider(ResourceProviderCard::class);
|
||||||
|
|
||||||
$symfonyAdapter->addListener('\OCP\Collaboration\Resources::loadAdditionalScripts', static function () {
|
$symfonyAdapter->addListener('\OCP\Collaboration\Resources::loadAdditionalScripts', static function () {
|
||||||
if (strpos(Server::get(IRequest::class)->getPathInfo(), '/call/') === 0) {
|
if (strpos(\OC::$server->getRequest()->getPathInfo(), '/call/') === 0) {
|
||||||
// Talk integration has its own entrypoint which already includes collections handling
|
// Talk integration has its own entrypoint which already includes collections handling
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -32,23 +32,20 @@ use OCP\AppFramework\QueryException;
|
|||||||
use OCP\Collaboration\Resources\IManager;
|
use OCP\Collaboration\Resources\IManager;
|
||||||
use OCP\Collaboration\Resources\IProvider;
|
use OCP\Collaboration\Resources\IProvider;
|
||||||
use OCP\Collaboration\Resources\IResource;
|
use OCP\Collaboration\Resources\IResource;
|
||||||
use OCP\IURLGenerator;
|
|
||||||
use OCP\IUser;
|
use OCP\IUser;
|
||||||
use OCP\Server;
|
|
||||||
|
|
||||||
class ResourceProvider implements IProvider {
|
class ResourceProvider implements IProvider {
|
||||||
public const RESOURCE_TYPE = 'deck';
|
public const RESOURCE_TYPE = 'deck';
|
||||||
|
|
||||||
private BoardMapper $boardMapper;
|
private $boardMapper;
|
||||||
private PermissionService $permissionService;
|
private $permissionService;
|
||||||
private IURLGenerator $urlGenerator;
|
|
||||||
|
|
||||||
protected array $nodes = [];
|
/** @var array */
|
||||||
|
protected $nodes = [];
|
||||||
|
|
||||||
public function __construct(BoardMapper $boardMapper, PermissionService $permissionService, IURLGenerator $urlGenerator) {
|
public function __construct(BoardMapper $boardMapper, PermissionService $permissionService) {
|
||||||
$this->boardMapper = $boardMapper;
|
$this->boardMapper = $boardMapper;
|
||||||
$this->permissionService = $permissionService;
|
$this->permissionService = $permissionService;
|
||||||
$this->urlGenerator = $urlGenerator;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -73,14 +70,14 @@ class ResourceProvider implements IProvider {
|
|||||||
*/
|
*/
|
||||||
public function getResourceRichObject(IResource $resource): array {
|
public function getResourceRichObject(IResource $resource): array {
|
||||||
$board = $this->getBoard($resource);
|
$board = $this->getBoard($resource);
|
||||||
$link = $this->urlGenerator->linkToRoute('deck.page.index') . '#/board/' . $resource->getId();
|
$link = \OC::$server->getURLGenerator()->linkToRoute('deck.page.index') . '#/board/' . $resource->getId();
|
||||||
|
|
||||||
return [
|
return [
|
||||||
'type' => self::RESOURCE_TYPE,
|
'type' => self::RESOURCE_TYPE,
|
||||||
'id' => $resource->getId(),
|
'id' => $resource->getId(),
|
||||||
'name' => $board->getTitle(),
|
'name' => $board->getTitle(),
|
||||||
'link' => $link,
|
'link' => $link,
|
||||||
'iconUrl' => $this->urlGenerator->imagePath('deck', 'deck-dark.svg')
|
'iconUrl' => \OC::$server->getURLGenerator()->imagePath('deck', 'deck-dark.svg')
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -111,7 +108,7 @@ class ResourceProvider implements IProvider {
|
|||||||
|
|
||||||
private function getBoard(IResource $resource) {
|
private function getBoard(IResource $resource) {
|
||||||
try {
|
try {
|
||||||
return $this->boardMapper->find($resource->getId(), false, true);
|
return $this->boardMapper->find((int)$resource->getId(), false, true);
|
||||||
} catch (DoesNotExistException $e) {
|
} catch (DoesNotExistException $e) {
|
||||||
} catch (MultipleObjectsReturnedException $e) {
|
} catch (MultipleObjectsReturnedException $e) {
|
||||||
return null;
|
return null;
|
||||||
@@ -121,7 +118,7 @@ class ResourceProvider implements IProvider {
|
|||||||
public function invalidateAccessCache($boardId = null) {
|
public function invalidateAccessCache($boardId = null) {
|
||||||
try {
|
try {
|
||||||
/** @var IManager $resourceManager */
|
/** @var IManager $resourceManager */
|
||||||
$resourceManager = Server::get(IManager::class);
|
$resourceManager = \OC::$server->query(IManager::class);
|
||||||
} catch (QueryException $e) {
|
} catch (QueryException $e) {
|
||||||
}
|
}
|
||||||
if ($boardId !== null) {
|
if ($boardId !== null) {
|
||||||
|
|||||||
@@ -37,16 +37,24 @@ use OCP\Collaboration\Resources\IResource;
|
|||||||
use OCP\Collaboration\Resources\ResourceException;
|
use OCP\Collaboration\Resources\ResourceException;
|
||||||
use OCP\IURLGenerator;
|
use OCP\IURLGenerator;
|
||||||
use OCP\IUser;
|
use OCP\IUser;
|
||||||
use OCP\Server;
|
|
||||||
|
|
||||||
class ResourceProviderCard implements IProvider {
|
class ResourceProviderCard implements IProvider {
|
||||||
public const RESOURCE_TYPE = 'deck-card';
|
public const RESOURCE_TYPE = 'deck-card';
|
||||||
|
|
||||||
private CardMapper $cardMapper;
|
/** @var CardMapper */
|
||||||
private BoardMapper $boardMapper;
|
private $cardMapper;
|
||||||
private PermissionService $permissionService;
|
|
||||||
private IURLGenerator $urlGenerator;
|
/** @var BoardMapper */
|
||||||
protected array $nodes = [];
|
private $boardMapper;
|
||||||
|
|
||||||
|
/** @var PermissionService */
|
||||||
|
private $permissionService;
|
||||||
|
|
||||||
|
/** @var IURLGenerator */
|
||||||
|
private $urlGenerator;
|
||||||
|
|
||||||
|
/** @var array */
|
||||||
|
protected $nodes = [];
|
||||||
|
|
||||||
public function __construct(CardMapper $cardMapper, BoardMapper $boardMapper, PermissionService $permissionService, IURLGenerator $urlGenerator) {
|
public function __construct(CardMapper $cardMapper, BoardMapper $boardMapper, PermissionService $permissionService, IURLGenerator $urlGenerator) {
|
||||||
$this->cardMapper = $cardMapper;
|
$this->cardMapper = $cardMapper;
|
||||||
@@ -139,7 +147,7 @@ class ResourceProviderCard implements IProvider {
|
|||||||
public function invalidateAccessCache($cardId = null) {
|
public function invalidateAccessCache($cardId = null) {
|
||||||
try {
|
try {
|
||||||
/** @var IManager $resourceManager */
|
/** @var IManager $resourceManager */
|
||||||
$resourceManager = Server::get(IManager::class);
|
$resourceManager = \OC::$server->query(IManager::class);
|
||||||
} catch (QueryException $e) {
|
} catch (QueryException $e) {
|
||||||
}
|
}
|
||||||
if ($cardId !== null) {
|
if ($cardId !== null) {
|
||||||
|
|||||||
@@ -30,7 +30,8 @@ use Symfony\Component\Console\Input\InputOption;
|
|||||||
use Symfony\Component\Console\Output\OutputInterface;
|
use Symfony\Component\Console\Output\OutputInterface;
|
||||||
|
|
||||||
class BoardImport extends Command {
|
class BoardImport extends Command {
|
||||||
private BoardImportCommandService $boardImportCommandService;
|
/** @var BoardImportCommandService */
|
||||||
|
private $boardImportCommandService;
|
||||||
|
|
||||||
public function __construct(
|
public function __construct(
|
||||||
BoardImportCommandService $boardImportCommandService
|
BoardImportCommandService $boardImportCommandService
|
||||||
|
|||||||
@@ -27,7 +27,6 @@ use OCA\Deck\Db\AssignmentMapper;
|
|||||||
use OCA\Deck\Db\BoardMapper;
|
use OCA\Deck\Db\BoardMapper;
|
||||||
use OCA\Deck\Db\CardMapper;
|
use OCA\Deck\Db\CardMapper;
|
||||||
use OCA\Deck\Db\StackMapper;
|
use OCA\Deck\Db\StackMapper;
|
||||||
use OCA\Deck\Model\CardDetails;
|
|
||||||
use OCA\Deck\Service\BoardService;
|
use OCA\Deck\Service\BoardService;
|
||||||
use OCP\AppFramework\Db\DoesNotExistException;
|
use OCP\AppFramework\Db\DoesNotExistException;
|
||||||
use OCP\AppFramework\Db\MultipleObjectsReturnedException;
|
use OCP\AppFramework\Db\MultipleObjectsReturnedException;
|
||||||
@@ -102,9 +101,7 @@ class UserExport extends Command {
|
|||||||
$fullCard = $this->cardMapper->find($card->getId());
|
$fullCard = $this->cardMapper->find($card->getId());
|
||||||
$assignedUsers = $this->assignedUsersMapper->findAll($card->getId());
|
$assignedUsers = $this->assignedUsersMapper->findAll($card->getId());
|
||||||
$fullCard->setAssignedUsers($assignedUsers);
|
$fullCard->setAssignedUsers($assignedUsers);
|
||||||
|
$data[$board->getId()]['stacks'][$stack->getId()]['cards'][] = (array)$fullCard->jsonSerialize();
|
||||||
$cardDetails = new CardDetails($fullCard, $fullBoard);
|
|
||||||
$data[$board->getId()]['stacks'][$stack->getId()]['cards'][] = $cardDetails->jsonSerialize();
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -29,9 +29,7 @@ use OCA\Deck\Service\PermissionService;
|
|||||||
use OCA\Files\Event\LoadSidebar;
|
use OCA\Files\Event\LoadSidebar;
|
||||||
use OCA\Viewer\Event\LoadViewer;
|
use OCA\Viewer\Event\LoadViewer;
|
||||||
use OCP\AppFramework\Http\ContentSecurityPolicy;
|
use OCP\AppFramework\Http\ContentSecurityPolicy;
|
||||||
use OCP\Collaboration\Resources\LoadAdditionalScriptsEvent as CollaborationResourcesEvent;
|
|
||||||
use OCP\EventDispatcher\IEventDispatcher;
|
use OCP\EventDispatcher\IEventDispatcher;
|
||||||
use OCP\IConfig;
|
|
||||||
use OCP\IInitialStateService;
|
use OCP\IInitialStateService;
|
||||||
use OCP\IRequest;
|
use OCP\IRequest;
|
||||||
use OCP\AppFramework\Http\TemplateResponse;
|
use OCP\AppFramework\Http\TemplateResponse;
|
||||||
@@ -43,17 +41,16 @@ use OCA\Deck\Db\Acl;
|
|||||||
use OCA\Deck\Service\CardService;
|
use OCA\Deck\Service\CardService;
|
||||||
|
|
||||||
class PageController extends Controller {
|
class PageController extends Controller {
|
||||||
private PermissionService $permissionService;
|
private $permissionService;
|
||||||
private IInitialStateService $initialState;
|
private $initialState;
|
||||||
private ConfigService $configService;
|
private $configService;
|
||||||
private IEventDispatcher $eventDispatcher;
|
private $eventDispatcher;
|
||||||
private CardMapper $cardMapper;
|
private $cardMapper;
|
||||||
private IURLGenerator $urlGenerator;
|
private $urlGenerator;
|
||||||
private CardService $cardService;
|
private $cardService;
|
||||||
private IConfig $config;
|
|
||||||
|
|
||||||
public function __construct(
|
public function __construct(
|
||||||
string $AppName,
|
$AppName,
|
||||||
IRequest $request,
|
IRequest $request,
|
||||||
PermissionService $permissionService,
|
PermissionService $permissionService,
|
||||||
IInitialStateService $initialStateService,
|
IInitialStateService $initialStateService,
|
||||||
@@ -61,8 +58,7 @@ class PageController extends Controller {
|
|||||||
IEventDispatcher $eventDispatcher,
|
IEventDispatcher $eventDispatcher,
|
||||||
CardMapper $cardMapper,
|
CardMapper $cardMapper,
|
||||||
IURLGenerator $urlGenerator,
|
IURLGenerator $urlGenerator,
|
||||||
CardService $cardService,
|
CardService $cardService
|
||||||
IConfig $config
|
|
||||||
) {
|
) {
|
||||||
parent::__construct($AppName, $request);
|
parent::__construct($AppName, $request);
|
||||||
|
|
||||||
@@ -73,7 +69,6 @@ class PageController extends Controller {
|
|||||||
$this->cardMapper = $cardMapper;
|
$this->cardMapper = $cardMapper;
|
||||||
$this->urlGenerator = $urlGenerator;
|
$this->urlGenerator = $urlGenerator;
|
||||||
$this->cardService = $cardService;
|
$this->cardService = $cardService;
|
||||||
$this->config = $config;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -89,17 +84,13 @@ class PageController extends Controller {
|
|||||||
$this->initialState->provideInitialState(Application::APP_ID, 'config', $this->configService->getAll());
|
$this->initialState->provideInitialState(Application::APP_ID, 'config', $this->configService->getAll());
|
||||||
|
|
||||||
$this->eventDispatcher->dispatchTyped(new LoadSidebar());
|
$this->eventDispatcher->dispatchTyped(new LoadSidebar());
|
||||||
$this->eventDispatcher->dispatchTyped(new CollaborationResourcesEvent());
|
|
||||||
if (class_exists(LoadViewer::class)) {
|
if (class_exists(LoadViewer::class)) {
|
||||||
$this->eventDispatcher->dispatchTyped(new LoadViewer());
|
$this->eventDispatcher->dispatchTyped(new LoadViewer());
|
||||||
}
|
}
|
||||||
|
|
||||||
$response = new TemplateResponse('deck', 'main', [
|
$response = new TemplateResponse('deck', 'main');
|
||||||
'id-app-content' => '#app-content-vue',
|
|
||||||
'id-app-navigation' => '#app-navigation-vue',
|
|
||||||
]);
|
|
||||||
|
|
||||||
if ($this->config->getSystemValueBool('debug', false)) {
|
if (\OC::$server->getConfig()->getSystemValueBool('debug', false)) {
|
||||||
$csp = new ContentSecurityPolicy();
|
$csp = new ContentSecurityPolicy();
|
||||||
$csp->addAllowedConnectDomain('*');
|
$csp->addAllowedConnectDomain('*');
|
||||||
$csp->addAllowedScriptDomain('*');
|
$csp->addAllowedScriptDomain('*');
|
||||||
|
|||||||
@@ -27,7 +27,6 @@ declare(strict_types=1);
|
|||||||
namespace OCA\Deck\Controller;
|
namespace OCA\Deck\Controller;
|
||||||
|
|
||||||
use OCA\Deck\Db\Card;
|
use OCA\Deck\Db\Card;
|
||||||
use OCA\Deck\Model\CardDetails;
|
|
||||||
use OCA\Deck\Service\SearchService;
|
use OCA\Deck\Service\SearchService;
|
||||||
use OCP\AppFramework\Http\DataResponse;
|
use OCP\AppFramework\Http\DataResponse;
|
||||||
use OCP\AppFramework\OCSController;
|
use OCP\AppFramework\OCSController;
|
||||||
@@ -51,12 +50,9 @@ class SearchController extends OCSController {
|
|||||||
public function search(string $term, ?int $limit = null, ?int $cursor = null): DataResponse {
|
public function search(string $term, ?int $limit = null, ?int $cursor = null): DataResponse {
|
||||||
$cards = $this->searchService->searchCards($term, $limit, $cursor);
|
$cards = $this->searchService->searchCards($term, $limit, $cursor);
|
||||||
return new DataResponse(array_map(function (Card $card) {
|
return new DataResponse(array_map(function (Card $card) {
|
||||||
$board = $card->getRelatedBoard();
|
$json = $card->jsonSerialize();
|
||||||
$json = (new CardDetails($card, $board))->jsonSerialize();
|
|
||||||
|
|
||||||
$json['relatedBoard'] = $board;
|
|
||||||
$json['relatedStack'] = $card->getRelatedStack();
|
$json['relatedStack'] = $card->getRelatedStack();
|
||||||
|
$json['relatedBoard'] = $card->getRelatedBoard();
|
||||||
return $json;
|
return $json;
|
||||||
}, $cards));
|
}, $cards));
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -59,21 +59,20 @@ class Calendar extends ExternalCalendar {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public function getACL() {
|
public function getACL() {
|
||||||
// the calendar should always have the read and the write-properties permissions
|
|
||||||
// write-properties is needed to allow the user to toggle the visibility of shared deck calendars
|
|
||||||
$acl = [
|
$acl = [
|
||||||
[
|
[
|
||||||
'privilege' => '{DAV:}read',
|
'privilege' => '{DAV:}read',
|
||||||
'principal' => $this->getOwner(),
|
'principal' => $this->getOwner(),
|
||||||
'protected' => true,
|
'protected' => true,
|
||||||
],
|
]
|
||||||
[
|
];
|
||||||
|
if ($this->backend->checkBoardPermission($this->board->getId(), Acl::PERMISSION_MANAGE)) {
|
||||||
|
$acl[] = [
|
||||||
'privilege' => '{DAV:}write-properties',
|
'privilege' => '{DAV:}write-properties',
|
||||||
'principal' => $this->getOwner(),
|
'principal' => $this->getOwner(),
|
||||||
'protected' => true,
|
'protected' => true,
|
||||||
]
|
];
|
||||||
];
|
}
|
||||||
|
|
||||||
return $acl;
|
return $acl;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -188,18 +187,12 @@ class Calendar extends ExternalCalendar {
|
|||||||
foreach ($properties as $key => $value) {
|
foreach ($properties as $key => $value) {
|
||||||
switch ($key) {
|
switch ($key) {
|
||||||
case '{DAV:}displayname':
|
case '{DAV:}displayname':
|
||||||
if (!$this->backend->checkBoardPermission($this->board->getId(), Acl::PERMISSION_MANAGE)) {
|
|
||||||
throw new Forbidden('no permission to change the displayname');
|
|
||||||
}
|
|
||||||
if (mb_strpos($value, 'Deck: ') === 0) {
|
if (mb_strpos($value, 'Deck: ') === 0) {
|
||||||
$value = mb_substr($value, strlen('Deck: '));
|
$value = mb_substr($value, strlen('Deck: '));
|
||||||
}
|
}
|
||||||
$this->board->setTitle($value);
|
$this->board->setTitle($value);
|
||||||
break;
|
break;
|
||||||
case '{http://apple.com/ns/ical/}calendar-color':
|
case '{http://apple.com/ns/ical/}calendar-color':
|
||||||
if (!$this->backend->checkBoardPermission($this->board->getId(), Acl::PERMISSION_MANAGE)) {
|
|
||||||
throw new Forbidden('no permission to change the calendar color');
|
|
||||||
}
|
|
||||||
$color = substr($value, 1, 6);
|
$color = substr($value, 1, 6);
|
||||||
if (!preg_match('/[a-f0-9]{6}/i', $color)) {
|
if (!preg_match('/[a-f0-9]{6}/i', $color)) {
|
||||||
throw new InvalidDataException('No valid color provided');
|
throw new InvalidDataException('No valid color provided');
|
||||||
|
|||||||
@@ -26,34 +26,18 @@ declare(strict_types=1);
|
|||||||
|
|
||||||
namespace OCA\Deck\Dashboard;
|
namespace OCA\Deck\Dashboard;
|
||||||
|
|
||||||
use DateTime;
|
use OCP\Dashboard\IWidget;
|
||||||
use OCA\Deck\AppInfo\Application;
|
|
||||||
use OCA\Deck\Db\Label;
|
|
||||||
use OCA\Deck\Service\OverviewService;
|
|
||||||
use OCP\Dashboard\IAPIWidget;
|
|
||||||
use OCP\Dashboard\IButtonWidget;
|
|
||||||
use OCP\Dashboard\IIconWidget;
|
|
||||||
use OCP\Dashboard\Model\WidgetButton;
|
|
||||||
use OCP\Dashboard\Model\WidgetItem;
|
|
||||||
use OCP\IDateTimeFormatter;
|
|
||||||
use OCP\IL10N;
|
use OCP\IL10N;
|
||||||
use OCP\IURLGenerator;
|
|
||||||
use OCP\Util;
|
|
||||||
|
|
||||||
class DeckWidget implements IAPIWidget, IButtonWidget, IIconWidget {
|
class DeckWidget implements IWidget {
|
||||||
private IL10N $l10n;
|
|
||||||
private OverviewService $dashboardService;
|
|
||||||
private IURLGenerator $urlGenerator;
|
|
||||||
private IDateTimeFormatter $dateTimeFormatter;
|
|
||||||
|
|
||||||
public function __construct(IL10N $l10n,
|
/**
|
||||||
OverviewService $dashboardService,
|
* @var IL10N
|
||||||
IDateTimeFormatter $dateTimeFormatter,
|
*/
|
||||||
IURLGenerator $urlGenerator) {
|
private $l10n;
|
||||||
|
|
||||||
|
public function __construct(IL10N $l10n) {
|
||||||
$this->l10n = $l10n;
|
$this->l10n = $l10n;
|
||||||
$this->dashboardService = $dashboardService;
|
|
||||||
$this->urlGenerator = $urlGenerator;
|
|
||||||
$this->dateTimeFormatter = $dateTimeFormatter;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -84,88 +68,17 @@ class DeckWidget implements IAPIWidget, IButtonWidget, IIconWidget {
|
|||||||
return 'icon-deck';
|
return 'icon-deck';
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* @inheritDoc
|
|
||||||
*/
|
|
||||||
public function getIconUrl(): string {
|
|
||||||
return $this->urlGenerator->getAbsoluteURL(
|
|
||||||
$this->urlGenerator->imagePath(Application::APP_ID, 'deck-dark.svg')
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @inheritDoc
|
* @inheritDoc
|
||||||
*/
|
*/
|
||||||
public function getUrl(): ?string {
|
public function getUrl(): ?string {
|
||||||
return $this->urlGenerator->getAbsoluteURL(
|
return null;
|
||||||
$this->urlGenerator->linkToRoute(Application::APP_ID . '.page.index')
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @inheritDoc
|
* @inheritDoc
|
||||||
*/
|
*/
|
||||||
public function load(): void {
|
public function load(): void {
|
||||||
Util::addScript('deck', 'deck-dashboard');
|
\OCP\Util::addScript('deck', 'deck-dashboard');
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @inheritDoc
|
|
||||||
*/
|
|
||||||
public function getItems(string $userId, ?string $since = null, int $limit = 7): array {
|
|
||||||
$upcomingCards = $this->dashboardService->findUpcomingCards($userId);
|
|
||||||
$nowTimestamp = (new Datetime())->getTimestamp();
|
|
||||||
$sinceTimestamp = $since !== null ? (new Datetime($since))->getTimestamp() : null;
|
|
||||||
$upcomingCards = array_filter($upcomingCards, static function (array $card) use ($nowTimestamp, $sinceTimestamp) {
|
|
||||||
if ($card['duedate']) {
|
|
||||||
$ts = (new Datetime($card['duedate']))->getTimestamp();
|
|
||||||
return $ts > $nowTimestamp && ($sinceTimestamp === null || $ts > $sinceTimestamp);
|
|
||||||
}
|
|
||||||
return false;
|
|
||||||
});
|
|
||||||
usort($upcomingCards, static function ($a, $b) {
|
|
||||||
$a = new Datetime($a['duedate']);
|
|
||||||
$ta = $a->getTimestamp();
|
|
||||||
$b = new Datetime($b['duedate']);
|
|
||||||
$tb = $b->getTimestamp();
|
|
||||||
return ($ta > $tb) ? 1 : -1;
|
|
||||||
});
|
|
||||||
$upcomingCards = array_slice($upcomingCards, 0, $limit);
|
|
||||||
$urlGenerator = $this->urlGenerator;
|
|
||||||
$dateTimeFormatter = $this->dateTimeFormatter;
|
|
||||||
return array_map(static function (array $card) use ($urlGenerator, $dateTimeFormatter) {
|
|
||||||
$formattedDueDate = $dateTimeFormatter->formatDateTime(new DateTime($card['duedate']));
|
|
||||||
return new WidgetItem(
|
|
||||||
$card['title'] . ' (' . $formattedDueDate . ')',
|
|
||||||
implode(
|
|
||||||
', ',
|
|
||||||
array_map(static function (Label $label) {
|
|
||||||
return $label->jsonSerialize()['title'];
|
|
||||||
}, $card['labels'])
|
|
||||||
),
|
|
||||||
$urlGenerator->getAbsoluteURL(
|
|
||||||
$urlGenerator->linkToRoute(Application::APP_ID . '.page.redirectToCard', ['cardId' => $card['id']])
|
|
||||||
),
|
|
||||||
$urlGenerator->getAbsoluteURL(
|
|
||||||
$urlGenerator->imagePath(Application::APP_ID, 'deck-dark.svg')
|
|
||||||
),
|
|
||||||
$card['duedate']
|
|
||||||
);
|
|
||||||
}, $upcomingCards);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @inheritDoc
|
|
||||||
*/
|
|
||||||
public function getWidgetButtons(string $userId): array {
|
|
||||||
return [
|
|
||||||
new WidgetButton(
|
|
||||||
WidgetButton::TYPE_MORE,
|
|
||||||
$this->urlGenerator->getAbsoluteURL(
|
|
||||||
$this->urlGenerator->linkToRoute(Application::APP_ID . '.page.index')
|
|
||||||
),
|
|
||||||
$this->l10n->t('Load more')
|
|
||||||
),
|
|
||||||
];
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -33,46 +33,18 @@ class AclMapper extends DeckMapper implements IPermissionMapper {
|
|||||||
parent::__construct($db, 'deck_board_acl', Acl::class);
|
parent::__construct($db, 'deck_board_acl', Acl::class);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* @param numeric $boardId
|
|
||||||
* @param int|null $limit
|
|
||||||
* @param int|null $offset
|
|
||||||
* @return Acl[]
|
|
||||||
* @throws \OCP\DB\Exception
|
|
||||||
*/
|
|
||||||
public function findAll($boardId, $limit = null, $offset = null) {
|
public function findAll($boardId, $limit = null, $offset = null) {
|
||||||
$qb = $this->db->getQueryBuilder();
|
$sql = 'SELECT id, board_id, type, participant, permission_edit, permission_share, permission_manage FROM `*PREFIX*deck_board_acl` WHERE `board_id` = ? ';
|
||||||
$qb->select('id', 'board_id', 'type', 'participant', 'permission_edit', 'permission_share', 'permission_manage')
|
return $this->findEntities($sql, [$boardId], $limit, $offset);
|
||||||
->from('deck_board_acl')
|
|
||||||
->where($qb->expr()->eq('board_id', $qb->createNamedParameter($boardId, IQueryBuilder::PARAM_INT)))
|
|
||||||
->setMaxResults($limit)
|
|
||||||
->setFirstResult($offset);
|
|
||||||
|
|
||||||
return $this->findEntities($qb);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
public function isOwner($userId, $aclId): bool {
|
||||||
* @param numeric $userId
|
$sql = 'SELECT owner FROM `*PREFIX*deck_boards` WHERE `id` IN (SELECT board_id FROM `*PREFIX*deck_board_acl` WHERE id = ?)';
|
||||||
* @param numeric $id
|
$stmt = $this->execute($sql, [$aclId]);
|
||||||
* @return bool
|
$row = $stmt->fetch();
|
||||||
* @throws \OCP\DB\Exception
|
return ($row['owner'] === $userId);
|
||||||
*/
|
|
||||||
public function isOwner($userId, $id): bool {
|
|
||||||
$aclId = $id;
|
|
||||||
$qb = $this->db->getQueryBuilder();
|
|
||||||
$qb->select('acl.id')
|
|
||||||
->from($this->getTableName(), 'acl')
|
|
||||||
->innerJoin('acl', 'deck_boards', 'b', 'acl.board_id = b.id')
|
|
||||||
->where($qb->expr()->eq('owner', $qb->createNamedParameter($userId, IQueryBuilder::PARAM_STR)))
|
|
||||||
->andWhere($qb->expr()->eq('acl.id', $qb->createNamedParameter($aclId, IQueryBuilder::PARAM_INT)));
|
|
||||||
|
|
||||||
return count($qb->executeQuery()->fetchAll()) > 0;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* @param numeric $id
|
|
||||||
* @return int|null
|
|
||||||
*/
|
|
||||||
public function findBoardId($id): ?int {
|
public function findBoardId($id): ?int {
|
||||||
try {
|
try {
|
||||||
$entity = $this->find($id);
|
$entity = $this->find($id);
|
||||||
@@ -82,21 +54,9 @@ class AclMapper extends DeckMapper implements IPermissionMapper {
|
|||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* @param int $type
|
|
||||||
* @param string $participant
|
|
||||||
* @return Acl[]
|
|
||||||
* @throws \OCP\DB\Exception
|
|
||||||
*/
|
|
||||||
public function findByParticipant($type, $participant): array {
|
public function findByParticipant($type, $participant): array {
|
||||||
$qb = $this->db->getQueryBuilder();
|
$sql = 'SELECT * from *PREFIX*deck_board_acl WHERE type = ? AND participant = ?';
|
||||||
|
return $this->findEntities($sql, [$type, $participant]);
|
||||||
$qb->select('*')
|
|
||||||
->from($this->getTableName())
|
|
||||||
->where($qb->expr()->eq('type', $qb->createNamedParameter($type, IQueryBuilder::PARAM_INT)))
|
|
||||||
->andWhere($qb->expr()->eq('participant', $qb->createNamedParameter($participant, IQueryBuilder::PARAM_STR)));
|
|
||||||
|
|
||||||
return $this->findEntities($qb);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@@ -55,6 +55,9 @@ class AssignmentMapper extends QBMapper implements IPermissionMapper {
|
|||||||
$this->circleService = $circleService;
|
$this->circleService = $circleService;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return Assignment[]
|
||||||
|
*/
|
||||||
public function findAll(int $cardId): array {
|
public function findAll(int $cardId): array {
|
||||||
$qb = $this->db->getQueryBuilder();
|
$qb = $this->db->getQueryBuilder();
|
||||||
$qb->select('*')
|
$qb->select('*')
|
||||||
@@ -77,8 +80,8 @@ class AssignmentMapper extends QBMapper implements IPermissionMapper {
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
public function isOwner($userId, $id): bool {
|
public function isOwner($userId, $cardId): bool {
|
||||||
return $this->cardMapper->isOwner($userId, $id);
|
return $this->cardMapper->isOwner($userId, $cardId);
|
||||||
}
|
}
|
||||||
|
|
||||||
public function findBoardId($id): ?int {
|
public function findBoardId($id): ?int {
|
||||||
|
|||||||
@@ -30,6 +30,7 @@ use OCP\AppFramework\Db\MultipleObjectsReturnedException;
|
|||||||
use OCP\DB\QueryBuilder\IQueryBuilder;
|
use OCP\DB\QueryBuilder\IQueryBuilder;
|
||||||
use OCP\IDBConnection;
|
use OCP\IDBConnection;
|
||||||
use OCP\IUserManager;
|
use OCP\IUserManager;
|
||||||
|
use PDO;
|
||||||
|
|
||||||
class AttachmentMapper extends DeckMapper implements IPermissionMapper {
|
class AttachmentMapper extends DeckMapper implements IPermissionMapper {
|
||||||
private $cardMapper;
|
private $cardMapper;
|
||||||
@@ -51,53 +52,70 @@ class AttachmentMapper extends DeckMapper implements IPermissionMapper {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @param int $id
|
* @param $id
|
||||||
* @return Attachment
|
* @return Entity|Attachment
|
||||||
* @throws DoesNotExistException
|
* @throws \OCP\AppFramework\Db\DoesNotExistException
|
||||||
* @throws MultipleObjectsReturnedException
|
* @throws \OCP\AppFramework\Db\MultipleObjectsReturnedException
|
||||||
* @throws \OCP\DB\Exception
|
|
||||||
*/
|
*/
|
||||||
public function find($id) {
|
public function find($id) {
|
||||||
$qb = $this->db->getQueryBuilder();
|
$qb = $this->db->getQueryBuilder();
|
||||||
$qb->select('*')
|
$qb->select('*')
|
||||||
->from($this->getTableName())
|
->from('deck_attachment')
|
||||||
->where($qb->expr()->eq('id', $qb->createNamedParameter($id, IQueryBuilder::PARAM_INT)));
|
->where($qb->expr()->eq('id', $qb->createNamedParameter($id, IQueryBuilder::PARAM_INT)));
|
||||||
|
|
||||||
return $this->findEntity($qb);
|
$cursor = $qb->execute();
|
||||||
|
$row = $cursor->fetch(PDO::FETCH_ASSOC);
|
||||||
|
if ($row === false) {
|
||||||
|
$cursor->closeCursor();
|
||||||
|
throw new DoesNotExistException('Did expect one result but found none when executing query: ' . $qb->getSQL());
|
||||||
|
}
|
||||||
|
|
||||||
|
$row2 = $cursor->fetch();
|
||||||
|
$cursor->closeCursor();
|
||||||
|
if ($row2 !== false) {
|
||||||
|
throw new MultipleObjectsReturnedException('Did not expect more than one result when executing query: ' . $qb->getSQL());
|
||||||
|
}
|
||||||
|
|
||||||
|
return $this->mapRowToEntity($row);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* @param int $cardId
|
|
||||||
* @param string $data
|
|
||||||
* @return Attachment
|
|
||||||
* @throws DoesNotExistException
|
|
||||||
* @throws MultipleObjectsReturnedException
|
|
||||||
* @throws \OCP\DB\Exception
|
|
||||||
*/
|
|
||||||
public function findByData($cardId, $data) {
|
public function findByData($cardId, $data) {
|
||||||
$qb = $this->db->getQueryBuilder();
|
$qb = $this->db->getQueryBuilder();
|
||||||
$qb->select('*')
|
$qb->select('*')
|
||||||
->from($this->getTableName())
|
->from('deck_attachment')
|
||||||
->where($qb->expr()->eq('card_id', $qb->createNamedParameter($cardId, IQueryBuilder::PARAM_INT)))
|
->where($qb->expr()->eq('card_id', $qb->createNamedParameter($cardId, IQueryBuilder::PARAM_INT)))
|
||||||
->andWhere($qb->expr()->eq('data', $qb->createNamedParameter($data, IQueryBuilder::PARAM_STR)));
|
->andWhere($qb->expr()->eq('data', $qb->createNamedParameter($data, IQueryBuilder::PARAM_STR)));
|
||||||
|
$cursor = $qb->execute();
|
||||||
return $this->findEntity($qb);
|
$row = $cursor->fetch(PDO::FETCH_ASSOC);
|
||||||
|
if ($row === false) {
|
||||||
|
$cursor->closeCursor();
|
||||||
|
throw new DoesNotExistException('Did expect one result but found none when executing query: ' . $qb->getSQL());
|
||||||
|
}
|
||||||
|
$cursor->closeCursor();
|
||||||
|
return $this->mapRowToEntity($row);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
* Find all attachments for a card
|
||||||
|
*
|
||||||
* @param $cardId
|
* @param $cardId
|
||||||
* @return Entity[]
|
* @return array
|
||||||
* @throws \OCP\DB\Exception
|
|
||||||
*/
|
*/
|
||||||
public function findAll($cardId) {
|
public function findAll($cardId) {
|
||||||
$qb = $this->db->getQueryBuilder();
|
$qb = $this->db->getQueryBuilder();
|
||||||
$qb->select('*')
|
$qb->select('*')
|
||||||
->from($this->getTableName())
|
->from('deck_attachment')
|
||||||
->where($qb->expr()->eq('card_id', $qb->createNamedParameter($cardId, IQueryBuilder::PARAM_INT)))
|
->where($qb->expr()->eq('card_id', $qb->createNamedParameter($cardId, IQueryBuilder::PARAM_INT)))
|
||||||
->andWhere($qb->expr()->eq('deleted_at', $qb->createNamedParameter(0, IQueryBuilder::PARAM_INT)));
|
->andWhere($qb->expr()->eq('deleted_at', $qb->createNamedParameter(0, IQueryBuilder::PARAM_INT)));
|
||||||
|
|
||||||
|
|
||||||
return $this->findEntities($qb);
|
$entities = [];
|
||||||
|
$cursor = $qb->execute();
|
||||||
|
while ($row = $cursor->fetch()) {
|
||||||
|
$entities[] = $this->mapRowToEntity($row);
|
||||||
|
}
|
||||||
|
$cursor->closeCursor();
|
||||||
|
return $entities;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -110,7 +128,7 @@ class AttachmentMapper extends DeckMapper implements IPermissionMapper {
|
|||||||
$timeLimit = time() - (60 * 5);
|
$timeLimit = time() - (60 * 5);
|
||||||
$qb = $this->db->getQueryBuilder();
|
$qb = $this->db->getQueryBuilder();
|
||||||
$qb->select('*')
|
$qb->select('*')
|
||||||
->from($this->getTableName())
|
->from('deck_attachment')
|
||||||
->where($qb->expr()->gt('deleted_at', $qb->createNamedParameter(0, IQueryBuilder::PARAM_INT)));
|
->where($qb->expr()->gt('deleted_at', $qb->createNamedParameter(0, IQueryBuilder::PARAM_INT)));
|
||||||
if ($withOffset) {
|
if ($withOffset) {
|
||||||
$qb
|
$qb
|
||||||
@@ -121,7 +139,13 @@ class AttachmentMapper extends DeckMapper implements IPermissionMapper {
|
|||||||
->andWhere($qb->expr()->eq('card_id', $qb->createNamedParameter($cardId, IQueryBuilder::PARAM_INT)));
|
->andWhere($qb->expr()->eq('card_id', $qb->createNamedParameter($cardId, IQueryBuilder::PARAM_INT)));
|
||||||
}
|
}
|
||||||
|
|
||||||
return $this->findEntities($qb);
|
$entities = [];
|
||||||
|
$cursor = $qb->execute();
|
||||||
|
while ($row = $cursor->fetch()) {
|
||||||
|
$entities[] = $this->mapRowToEntity($row);
|
||||||
|
}
|
||||||
|
$cursor->closeCursor();
|
||||||
|
return $entities;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -23,14 +23,6 @@
|
|||||||
|
|
||||||
namespace OCA\Deck\Db;
|
namespace OCA\Deck\Db;
|
||||||
|
|
||||||
/**
|
|
||||||
* @method int getId()
|
|
||||||
* @method string getTitle()
|
|
||||||
* @method int getShared()
|
|
||||||
* @method bool getArchived()
|
|
||||||
* @method int getDeletedAt()
|
|
||||||
* @method int getLastModified()
|
|
||||||
*/
|
|
||||||
class Board extends RelationalEntity {
|
class Board extends RelationalEntity {
|
||||||
protected $title;
|
protected $title;
|
||||||
protected $owner;
|
protected $owner;
|
||||||
|
|||||||
@@ -42,9 +42,9 @@ class BoardMapper extends QBMapper implements IPermissionMapper {
|
|||||||
private $circlesService;
|
private $circlesService;
|
||||||
private $logger;
|
private $logger;
|
||||||
|
|
||||||
/** @var CappedMemoryCache<Board[]> */
|
/** @var CappedMemoryCache */
|
||||||
private $userBoardCache;
|
private $userBoardCache;
|
||||||
/** @var CappedMemoryCache<Board> */
|
/** @var CappedMemoryCache */
|
||||||
private $boardCache;
|
private $boardCache;
|
||||||
|
|
||||||
public function __construct(
|
public function __construct(
|
||||||
@@ -79,12 +79,14 @@ class BoardMapper extends QBMapper implements IPermissionMapper {
|
|||||||
* @throws \OCP\AppFramework\Db\MultipleObjectsReturnedException
|
* @throws \OCP\AppFramework\Db\MultipleObjectsReturnedException
|
||||||
* @throws DoesNotExistException
|
* @throws DoesNotExistException
|
||||||
*/
|
*/
|
||||||
public function find($id, $withLabels = false, $withAcl = false): Board {
|
public function find(int $id, bool $withLabels = false, bool $withAcl = false, bool $allowDeleted = false): Board {
|
||||||
if (!isset($this->boardCache[$id])) {
|
if (!isset($this->boardCache[$id])) {
|
||||||
$qb = $this->db->getQueryBuilder();
|
$qb = $this->db->getQueryBuilder();
|
||||||
|
$deletedWhere = $allowDeleted ? $qb->expr()->gte('deleted_at', $qb->createNamedParameter(0, IQueryBuilder::PARAM_INT)) : $qb->expr()->eq('deleted_at', $qb->createNamedParameter(0, IQueryBuilder::PARAM_INT));
|
||||||
$qb->select('*')
|
$qb->select('*')
|
||||||
->from('deck_boards')
|
->from('deck_boards')
|
||||||
->where($qb->expr()->eq('id', $qb->createNamedParameter($id, IQueryBuilder::PARAM_INT)))
|
->where($qb->expr()->eq('id', $qb->createNamedParameter($id, IQueryBuilder::PARAM_INT)))
|
||||||
|
->andWhere($deletedWhere)
|
||||||
->orderBy('id');
|
->orderBy('id');
|
||||||
$this->boardCache[$id] = $this->findEntity($qb);
|
$this->boardCache[$id] = $this->findEntity($qb);
|
||||||
}
|
}
|
||||||
@@ -107,47 +109,6 @@ class BoardMapper extends QBMapper implements IPermissionMapper {
|
|||||||
return $this->boardCache[$id];
|
return $this->boardCache[$id];
|
||||||
}
|
}
|
||||||
|
|
||||||
public function findBoardIds(string $userId): array {
|
|
||||||
$qb = $this->db->getQueryBuilder();
|
|
||||||
$qb->selectDistinct('b.id')
|
|
||||||
->from($this->getTableName(), 'b')
|
|
||||||
->leftJoin('b', 'deck_board_acl', 'acl', $qb->expr()->eq('b.id', 'acl.board_id'));
|
|
||||||
|
|
||||||
// Owned by the user
|
|
||||||
$qb->where($qb->expr()->andX(
|
|
||||||
$qb->expr()->eq('owner', $qb->createNamedParameter($userId, IQueryBuilder::PARAM_STR)),
|
|
||||||
));
|
|
||||||
|
|
||||||
// Shared to the user
|
|
||||||
$qb->orWhere($qb->expr()->andX(
|
|
||||||
$qb->expr()->eq('acl.participant', $qb->createNamedParameter($userId, IQueryBuilder::PARAM_STR)),
|
|
||||||
$qb->expr()->eq('acl.type', $qb->createNamedParameter(Acl::PERMISSION_TYPE_USER, IQueryBuilder::PARAM_INT)),
|
|
||||||
));
|
|
||||||
|
|
||||||
// Shared to user groups of the user
|
|
||||||
$groupIds = $this->groupManager->getUserGroupIds($this->userManager->get($userId));
|
|
||||||
if (count($groupIds) !== 0) {
|
|
||||||
$qb->orWhere($qb->expr()->andX(
|
|
||||||
$qb->expr()->in('acl.participant', $qb->createNamedParameter($groupIds, IQueryBuilder::PARAM_STR_ARRAY)),
|
|
||||||
$qb->expr()->eq('acl.type', $qb->createNamedParameter(Acl::PERMISSION_TYPE_GROUP, IQueryBuilder::PARAM_INT)),
|
|
||||||
));
|
|
||||||
}
|
|
||||||
|
|
||||||
// Shared to circles of the user
|
|
||||||
$circles = $this->circlesService->getUserCircles($userId);
|
|
||||||
if (count($circles) !== 0) {
|
|
||||||
$qb->orWhere($qb->expr()->andX(
|
|
||||||
$qb->expr()->in('acl.participant', $qb->createNamedParameter($circles, IQueryBuilder::PARAM_STR_ARRAY)),
|
|
||||||
$qb->expr()->eq('acl.type', $qb->createNamedParameter(Acl::PERMISSION_TYPE_CIRCLE, IQueryBuilder::PARAM_INT)),
|
|
||||||
));
|
|
||||||
}
|
|
||||||
|
|
||||||
$result = $qb->executeQuery();
|
|
||||||
return array_map(function (string $id) {
|
|
||||||
return (int)$id;
|
|
||||||
}, $result->fetchAll(\PDO::FETCH_COLUMN));
|
|
||||||
}
|
|
||||||
|
|
||||||
public function findAllForUser(string $userId, ?int $since = null, bool $includeArchived = true, ?int $before = null,
|
public function findAllForUser(string $userId, ?int $since = null, bool $includeArchived = true, ?int $before = null,
|
||||||
?string $term = null): array {
|
?string $term = null): array {
|
||||||
$useCache = ($since === -1 && $includeArchived === true && $before === null && $term === null);
|
$useCache = ($since === -1 && $includeArchived === true && $before === null && $term === null);
|
||||||
@@ -172,9 +133,14 @@ class BoardMapper extends QBMapper implements IPermissionMapper {
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Find all boards for a given user
|
* Find all boards for a given user
|
||||||
|
*
|
||||||
|
* @param $userId
|
||||||
|
* @param null $limit
|
||||||
|
* @param null $offset
|
||||||
|
* @return array
|
||||||
*/
|
*/
|
||||||
public function findAllByUser(string $userId, ?int $limit = null, ?int $offset = null, ?int $since = null,
|
public function findAllByUser(string $userId, ?int $limit = null, ?int $offset = null, ?int $since = null,
|
||||||
bool $includeArchived = true, ?int $before = null, ?string $term = null): array {
|
bool $includeArchived = true, ?int $before = null, ?string $term = null) {
|
||||||
// FIXME this used to be a UNION to get boards owned by $userId and the user shares in one single query
|
// FIXME this used to be a UNION to get boards owned by $userId and the user shares in one single query
|
||||||
// Is it possible with the query builder?
|
// Is it possible with the query builder?
|
||||||
$qb = $this->db->getQueryBuilder();
|
$qb = $this->db->getQueryBuilder();
|
||||||
@@ -283,9 +249,15 @@ class BoardMapper extends QBMapper implements IPermissionMapper {
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Find all boards for a given user
|
* Find all boards for a given user
|
||||||
|
*
|
||||||
|
* @param $userId
|
||||||
|
* @param $groups
|
||||||
|
* @param null $limit
|
||||||
|
* @param null $offset
|
||||||
|
* @return array
|
||||||
*/
|
*/
|
||||||
public function findAllByGroups(string $userId, array $groups, ?int $limit = null, ?int $offset = null, ?int $since = null,
|
public function findAllByGroups(string $userId, array $groups, ?int $limit = null, ?int $offset = null, ?int $since = null,
|
||||||
bool $includeArchived = true, ?int $before = null, ?string $term = null): array {
|
bool $includeArchived = true, ?int $before = null, ?string $term = null) {
|
||||||
if (count($groups) <= 0) {
|
if (count($groups) <= 0) {
|
||||||
return [];
|
return [];
|
||||||
}
|
}
|
||||||
@@ -444,8 +416,8 @@ class BoardMapper extends QBMapper implements IPermissionMapper {
|
|||||||
return parent::delete($entity);
|
return parent::delete($entity);
|
||||||
}
|
}
|
||||||
|
|
||||||
public function isOwner($userId, $id): bool {
|
public function isOwner($userId, $boardId): bool {
|
||||||
$board = $this->find($id);
|
$board = $this->find($boardId);
|
||||||
return ($board->getOwner() === $userId);
|
return ($board->getOwner() === $userId);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -27,44 +27,6 @@ use DateTime;
|
|||||||
use DateTimeZone;
|
use DateTimeZone;
|
||||||
use Sabre\VObject\Component\VCalendar;
|
use Sabre\VObject\Component\VCalendar;
|
||||||
|
|
||||||
/**
|
|
||||||
* @method string getTitle()
|
|
||||||
* @method string getDescription()
|
|
||||||
* @method string getDescriptionPrev()
|
|
||||||
* @method int getStackId()
|
|
||||||
* @method int getOrder()
|
|
||||||
* @method int getLastModified()
|
|
||||||
* @method int getCreatedAt()
|
|
||||||
* @method bool getArchived()
|
|
||||||
* @method bool getNotified()
|
|
||||||
*
|
|
||||||
* @method void setLabels(Label[] $labels)
|
|
||||||
* @method null|Label[] getLabels()
|
|
||||||
*
|
|
||||||
* @method void setAssignedUsers(Assignment[] $users)
|
|
||||||
* @method null|User[] getAssignedUsers()
|
|
||||||
*
|
|
||||||
* @method void setAttachments(Attachment[] $attachments)
|
|
||||||
* @method null|Attachment[] getAttachments()
|
|
||||||
*
|
|
||||||
* @method void setAttachmentCount(int $count)
|
|
||||||
* @method null|int getAttachmentCount()
|
|
||||||
*
|
|
||||||
* @method void setCommentsUnread(int $count)
|
|
||||||
* @method null|int getCommentsUnread()
|
|
||||||
*
|
|
||||||
* @method void setCommentsCount(int $count)
|
|
||||||
* @method null|int getCommentsCount()
|
|
||||||
*
|
|
||||||
* @method void setOwner(string $user)
|
|
||||||
* @method null|string getOwner()
|
|
||||||
*
|
|
||||||
* @method void setRelatedStack(Stack $stack)
|
|
||||||
* @method null|Stack getRelatedStack()
|
|
||||||
*
|
|
||||||
* @method void setRelatedBoard(Board $board)
|
|
||||||
* @method null|Board getRelatedBoard()
|
|
||||||
*/
|
|
||||||
class Card extends RelationalEntity {
|
class Card extends RelationalEntity {
|
||||||
public const TITLE_MAX_LENGTH = 255;
|
public const TITLE_MAX_LENGTH = 255;
|
||||||
|
|
||||||
@@ -108,7 +70,6 @@ class Card extends RelationalEntity {
|
|||||||
$this->addType('archived', 'boolean');
|
$this->addType('archived', 'boolean');
|
||||||
$this->addType('notified', 'boolean');
|
$this->addType('notified', 'boolean');
|
||||||
$this->addType('deletedAt', 'integer');
|
$this->addType('deletedAt', 'integer');
|
||||||
$this->addType('duedate', 'datetime');
|
|
||||||
$this->addRelation('labels');
|
$this->addRelation('labels');
|
||||||
$this->addRelation('assignedUsers');
|
$this->addRelation('assignedUsers');
|
||||||
$this->addRelation('attachments');
|
$this->addRelation('attachments');
|
||||||
@@ -126,6 +87,50 @@ class Card extends RelationalEntity {
|
|||||||
$this->databaseType = $type;
|
$this->databaseType = $type;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public function getDuedate($isoFormat = false) {
|
||||||
|
if ($this->duedate === null) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
$dt = new DateTime($this->duedate);
|
||||||
|
if (!$isoFormat && $this->databaseType === 'mysql') {
|
||||||
|
return $dt->format('Y-m-d H:i:s');
|
||||||
|
}
|
||||||
|
return $dt->format('c');
|
||||||
|
}
|
||||||
|
|
||||||
|
public function jsonSerialize(): array {
|
||||||
|
$json = parent::jsonSerialize();
|
||||||
|
$json['overdue'] = self::DUEDATE_FUTURE;
|
||||||
|
$due = $this->duedate ? strtotime($this->duedate) : false;
|
||||||
|
if ($due !== false) {
|
||||||
|
$today = new DateTime();
|
||||||
|
$today->setTime(0, 0);
|
||||||
|
|
||||||
|
$match_date = new DateTime($this->duedate);
|
||||||
|
|
||||||
|
$match_date->setTime(0, 0);
|
||||||
|
|
||||||
|
$diff = $today->diff($match_date);
|
||||||
|
$diffDays = (integer) $diff->format('%R%a'); // Extract days count in interval
|
||||||
|
|
||||||
|
if ($diffDays === 1) {
|
||||||
|
$json['overdue'] = self::DUEDATE_NEXT;
|
||||||
|
}
|
||||||
|
if ($diffDays === 0) {
|
||||||
|
$json['overdue'] = self::DUEDATE_NOW;
|
||||||
|
}
|
||||||
|
if ($diffDays < 0) {
|
||||||
|
$json['overdue'] = self::DUEDATE_OVERDUE;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
$json['duedate'] = $this->getDuedate(true);
|
||||||
|
unset($json['notified']);
|
||||||
|
unset($json['descriptionPrev']);
|
||||||
|
unset($json['relatedStack']);
|
||||||
|
unset($json['relatedBoard']);
|
||||||
|
return $json;
|
||||||
|
}
|
||||||
|
|
||||||
public function getCalendarObject(): VCalendar {
|
public function getCalendarObject(): VCalendar {
|
||||||
$calendar = new VCalendar();
|
$calendar = new VCalendar();
|
||||||
$event = $calendar->createComponent('VTODO');
|
$event = $calendar->createComponent('VTODO');
|
||||||
@@ -134,7 +139,7 @@ class Card extends RelationalEntity {
|
|||||||
$creationDate = new DateTime();
|
$creationDate = new DateTime();
|
||||||
$creationDate->setTimestamp($this->createdAt);
|
$creationDate->setTimestamp($this->createdAt);
|
||||||
$event->DTSTAMP = $creationDate;
|
$event->DTSTAMP = $creationDate;
|
||||||
$event->DUE = new DateTime($this->getDuedate()->format('c'), new DateTimeZone('UTC'));
|
$event->DUE = new DateTime($this->getDuedate(true), new DateTimeZone('UTC'));
|
||||||
}
|
}
|
||||||
$event->add('RELATED-TO', 'deck-stack-' . $this->getStackId());
|
$event->add('RELATED-TO', 'deck-stack-' . $this->getStackId());
|
||||||
|
|
||||||
|
|||||||
@@ -238,21 +238,6 @@ class CardMapper extends QBMapper implements IPermissionMapper {
|
|||||||
return $this->findEntities($qb);
|
return $this->findEntities($qb);
|
||||||
}
|
}
|
||||||
|
|
||||||
public function findAllByBoardId(int $boardId, ?int $limit = null, ?int $offset = null): array {
|
|
||||||
$qb = $this->db->getQueryBuilder();
|
|
||||||
$qb->select('c.*')
|
|
||||||
->from('deck_cards', 'c')
|
|
||||||
->innerJoin('c', 'deck_stacks', 's', 's.id = c.stack_id')
|
|
||||||
->innerJoin('s', 'deck_boards', 'b', 'b.id = s.board_id')
|
|
||||||
->where($qb->expr()->eq('board_id', $qb->createNamedParameter($boardId, IQueryBuilder::PARAM_INT)))
|
|
||||||
->andWhere($qb->expr()->eq('archived', $qb->createNamedParameter(false, IQueryBuilder::PARAM_BOOL)))
|
|
||||||
->setMaxResults($limit)
|
|
||||||
->setFirstResult($offset)
|
|
||||||
->orderBy('c.lastmodified')
|
|
||||||
->addOrderBy('c.id');
|
|
||||||
return $this->findEntities($qb);
|
|
||||||
}
|
|
||||||
|
|
||||||
public function findAllWithDue($boardId) {
|
public function findAllWithDue($boardId) {
|
||||||
$qb = $this->db->getQueryBuilder();
|
$qb = $this->db->getQueryBuilder();
|
||||||
$qb->select('c.*')
|
$qb->select('c.*')
|
||||||
@@ -575,10 +560,10 @@ class CardMapper extends QBMapper implements IPermissionMapper {
|
|||||||
$qb->execute();
|
$qb->execute();
|
||||||
}
|
}
|
||||||
|
|
||||||
public function isOwner($userId, $id): bool {
|
public function isOwner($userId, $cardId): bool {
|
||||||
$sql = 'SELECT owner FROM `*PREFIX*deck_boards` WHERE `id` IN (SELECT board_id FROM `*PREFIX*deck_stacks` WHERE id IN (SELECT stack_id FROM `*PREFIX*deck_cards` WHERE id = ?))';
|
$sql = 'SELECT owner FROM `*PREFIX*deck_boards` WHERE `id` IN (SELECT board_id FROM `*PREFIX*deck_stacks` WHERE id IN (SELECT stack_id FROM `*PREFIX*deck_cards` WHERE id = ?))';
|
||||||
$stmt = $this->db->prepare($sql);
|
$stmt = $this->db->prepare($sql);
|
||||||
$stmt->bindParam(1, $id, \PDO::PARAM_INT, 0);
|
$stmt->bindParam(1, $cardId, \PDO::PARAM_INT, 0);
|
||||||
$stmt->execute();
|
$stmt->execute();
|
||||||
$row = $stmt->fetch();
|
$row = $stmt->fetch();
|
||||||
return ($row['owner'] === $userId);
|
return ($row['owner'] === $userId);
|
||||||
|
|||||||
@@ -24,7 +24,6 @@
|
|||||||
namespace OCA\Deck\Db;
|
namespace OCA\Deck\Db;
|
||||||
|
|
||||||
use OCP\ICacheFactory;
|
use OCP\ICacheFactory;
|
||||||
use OCP\ICache;
|
|
||||||
use OCP\IDBConnection;
|
use OCP\IDBConnection;
|
||||||
use OCP\IRequest;
|
use OCP\IRequest;
|
||||||
|
|
||||||
@@ -32,16 +31,13 @@ class ChangeHelper {
|
|||||||
public const TYPE_BOARD = 'boardChanged';
|
public const TYPE_BOARD = 'boardChanged';
|
||||||
public const TYPE_CARD = 'cardChanged';
|
public const TYPE_CARD = 'cardChanged';
|
||||||
|
|
||||||
private IDBConnection $db;
|
private $db;
|
||||||
private ICache $cache;
|
|
||||||
private IRequest $request;
|
|
||||||
private ?string $userId;
|
|
||||||
|
|
||||||
public function __construct(
|
public function __construct(
|
||||||
IDBConnection $db,
|
IDBConnection $db,
|
||||||
ICacheFactory $cacheFactory,
|
ICacheFactory $cacheFactory,
|
||||||
IRequest $request,
|
IRequest $request,
|
||||||
?string $userId
|
$userId
|
||||||
) {
|
) {
|
||||||
$this->db = $db;
|
$this->db = $db;
|
||||||
$this->cache = $cacheFactory->createDistributed('deck_changes');
|
$this->cache = $cacheFactory->createDistributed('deck_changes');
|
||||||
|
|||||||
@@ -23,15 +23,17 @@
|
|||||||
|
|
||||||
namespace OCA\Deck\Db;
|
namespace OCA\Deck\Db;
|
||||||
|
|
||||||
use OCP\AppFramework\Db\QBMapper;
|
use OCP\AppFramework\Db\Mapper;
|
||||||
use OCP\DB\QueryBuilder\IQueryBuilder;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Class DeckMapper
|
* Class DeckMapper
|
||||||
*
|
*
|
||||||
* @package OCA\Deck\Db
|
* @package OCA\Deck\Db
|
||||||
|
* @deprecated use QBMapper
|
||||||
|
*
|
||||||
|
* TODO: Move to QBMapper once Nextcloud 14 is a minimum requirement
|
||||||
*/
|
*/
|
||||||
class DeckMapper extends QBMapper {
|
class DeckMapper extends Mapper {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @param $id
|
* @param $id
|
||||||
@@ -40,11 +42,11 @@ class DeckMapper extends QBMapper {
|
|||||||
* @throws \OCP\AppFramework\Db\DoesNotExistException
|
* @throws \OCP\AppFramework\Db\DoesNotExistException
|
||||||
*/
|
*/
|
||||||
public function find($id) {
|
public function find($id) {
|
||||||
$qb = $this->db->getQueryBuilder();
|
$sql = 'SELECT * FROM `' . $this->tableName . '` ' . 'WHERE `id` = ?';
|
||||||
$qb->select('*')
|
return $this->findEntity($sql, [$id]);
|
||||||
->from($this->getTableName())
|
}
|
||||||
->where($qb->expr()->eq('id', $qb->createNamedParameter($id, IQueryBuilder::PARAM_INT)));
|
|
||||||
|
|
||||||
return $this->findEntity($qb);
|
protected function execute($sql, array $params = [], $limit = null, $offset = null) {
|
||||||
|
return parent::execute($sql, $params, $limit, $offset);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -26,7 +26,6 @@ namespace OCA\Deck\Db;
|
|||||||
use OCP\AppFramework\Db\DoesNotExistException;
|
use OCP\AppFramework\Db\DoesNotExistException;
|
||||||
use OCP\AppFramework\Db\Entity;
|
use OCP\AppFramework\Db\Entity;
|
||||||
use OCP\AppFramework\Db\MultipleObjectsReturnedException;
|
use OCP\AppFramework\Db\MultipleObjectsReturnedException;
|
||||||
use OCP\DB\QueryBuilder\IQueryBuilder;
|
|
||||||
use OCP\IDBConnection;
|
use OCP\IDBConnection;
|
||||||
|
|
||||||
class LabelMapper extends DeckMapper implements IPermissionMapper {
|
class LabelMapper extends DeckMapper implements IPermissionMapper {
|
||||||
@@ -34,105 +33,41 @@ class LabelMapper extends DeckMapper implements IPermissionMapper {
|
|||||||
parent::__construct($db, 'deck_labels', Label::class);
|
parent::__construct($db, 'deck_labels', Label::class);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
public function findAll($boardId, $limit = null, $offset = null) {
|
||||||
* @param numeric $boardId
|
$sql = 'SELECT * FROM `*PREFIX*deck_labels` WHERE `board_id` = ? ORDER BY `id`';
|
||||||
* @param int|null $limit
|
return $this->findEntities($sql, [$boardId], $limit, $offset);
|
||||||
* @param int|null $offset
|
|
||||||
* @return Label[]
|
|
||||||
* @throws \OCP\DB\Exception
|
|
||||||
*/
|
|
||||||
public function findAll($boardId, $limit = null, $offset = null): array {
|
|
||||||
$qb = $this->db->getQueryBuilder();
|
|
||||||
$qb->select('*')
|
|
||||||
->from($this->getTableName())
|
|
||||||
->where($qb->expr()->eq('board_id', $qb->createNamedParameter($boardId, IQueryBuilder::PARAM_INT)))
|
|
||||||
->setMaxResults($limit)
|
|
||||||
->setFirstResult($offset);
|
|
||||||
return $this->findEntities($qb);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
public function delete(\OCP\AppFramework\Db\Entity $entity) {
|
||||||
* @param Entity $entity
|
|
||||||
* @return Entity
|
|
||||||
* @throws \OCP\DB\Exception
|
|
||||||
*/
|
|
||||||
public function delete(Entity $entity): Entity {
|
|
||||||
// delete assigned labels
|
// delete assigned labels
|
||||||
$this->deleteLabelAssignments($entity->getId());
|
$this->deleteLabelAssignments($entity->getId());
|
||||||
// delete label
|
// delete label
|
||||||
return parent::delete($entity);
|
return parent::delete($entity);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
public function findAssignedLabelsForCard($cardId, $limit = null, $offset = null) {
|
||||||
* @param numeric $cardId
|
$sql = 'SELECT l.*,card_id FROM `*PREFIX*deck_assigned_labels` as al INNER JOIN *PREFIX*deck_labels as l ON l.id = al.label_id WHERE `card_id` = ? ORDER BY l.id';
|
||||||
* @param int|null $limit
|
return $this->findEntities($sql, [$cardId], $limit, $offset);
|
||||||
* @param int|null $offset
|
}
|
||||||
* @return Label[]
|
public function findAssignedLabelsForBoard($boardId, $limit = null, $offset = null) {
|
||||||
* @throws \OCP\DB\Exception
|
$sql = 'SELECT c.id as card_id, l.id as id, l.title as title, l.color as color FROM `*PREFIX*deck_cards` as c ' .
|
||||||
*/
|
' INNER JOIN `*PREFIX*deck_assigned_labels` as al ON al.card_id = c.id INNER JOIN `*PREFIX*deck_labels` as l ON al.label_id = l.id WHERE board_id=? ORDER BY l.id';
|
||||||
public function findAssignedLabelsForCard($cardId, $limit = null, $offset = null): array {
|
return $this->findEntities($sql, [$boardId], $limit, $offset);
|
||||||
$qb = $this->db->getQueryBuilder();
|
|
||||||
$qb->select('l.*', 'card_id')
|
|
||||||
->from($this->getTableName(), 'l')
|
|
||||||
->innerJoin('l', 'deck_assigned_labels', 'al', 'l.id = al.label_id')
|
|
||||||
->where($qb->expr()->eq('card_id', $qb->createNamedParameter($cardId, IQueryBuilder::PARAM_INT)))
|
|
||||||
->orderBy('l.id')
|
|
||||||
->setMaxResults($limit)
|
|
||||||
->setFirstResult($offset);
|
|
||||||
|
|
||||||
return $this->findEntities($qb);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
public function insert(Entity $entity) {
|
||||||
* @param numeric $boardId
|
|
||||||
* @param int|null $limit
|
|
||||||
* @param int|null $offset
|
|
||||||
* @return Label[]
|
|
||||||
* @throws \OCP\DB\Exception
|
|
||||||
*/
|
|
||||||
public function findAssignedLabelsForBoard($boardId, $limit = null, $offset = null): array {
|
|
||||||
$qb = $this->db->getQueryBuilder();
|
|
||||||
$qb->select('l.id as id', 'l.title as title', 'l.color as color')
|
|
||||||
->selectAlias('c.id', 'card_id')
|
|
||||||
->from($this->getTableName(), 'l')
|
|
||||||
->innerJoin('l', 'deck_assigned_labels', 'al', 'al.label_id = l.id')
|
|
||||||
->innerJoin('l', 'deck_cards', 'c', 'al.card_id = c.id')
|
|
||||||
->where($qb->expr()->eq('board_id', $qb->createNamedParameter($boardId, IQueryBuilder::PARAM_INT)))
|
|
||||||
->orderBy('l.id')
|
|
||||||
->setMaxResults($limit)
|
|
||||||
->setFirstResult($offset);
|
|
||||||
|
|
||||||
return $this->findEntities($qb);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @param Entity $entity
|
|
||||||
* @return Entity
|
|
||||||
* @throws \OCP\DB\Exception
|
|
||||||
*/
|
|
||||||
public function insert(Entity $entity): Entity {
|
|
||||||
$entity->setLastModified(time());
|
$entity->setLastModified(time());
|
||||||
return parent::insert($entity);
|
return parent::insert($entity);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
public function update(Entity $entity, $updateModified = true) {
|
||||||
* @param Entity $entity
|
|
||||||
* @param bool $updateModified
|
|
||||||
* @return Entity
|
|
||||||
* @throws \OCP\DB\Exception
|
|
||||||
*/
|
|
||||||
public function update(Entity $entity, $updateModified = true): Entity {
|
|
||||||
if ($updateModified) {
|
if ($updateModified) {
|
||||||
$entity->setLastModified(time());
|
$entity->setLastModified(time());
|
||||||
}
|
}
|
||||||
return parent::update($entity);
|
return parent::update($entity);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* @param numeric $boardId
|
|
||||||
* @return array
|
|
||||||
* @throws \OCP\DB\Exception
|
|
||||||
*/
|
|
||||||
public function getAssignedLabelsForBoard($boardId) {
|
public function getAssignedLabelsForBoard($boardId) {
|
||||||
$labels = $this->findAssignedLabelsForBoard($boardId);
|
$labels = $this->findAssignedLabelsForBoard($boardId);
|
||||||
$result = [];
|
$result = [];
|
||||||
@@ -145,51 +80,27 @@ class LabelMapper extends DeckMapper implements IPermissionMapper {
|
|||||||
return $result;
|
return $result;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* @param numeric $labelId
|
|
||||||
* @return void
|
|
||||||
* @throws \OCP\DB\Exception
|
|
||||||
*/
|
|
||||||
public function deleteLabelAssignments($labelId) {
|
public function deleteLabelAssignments($labelId) {
|
||||||
$qb = $this->db->getQueryBuilder();
|
$sql = 'DELETE FROM `*PREFIX*deck_assigned_labels` WHERE label_id = ?';
|
||||||
$qb->delete('deck_assigned_labels')
|
$stmt = $this->db->prepare($sql);
|
||||||
->where($qb->expr()->eq('label_id', $qb->createNamedParameter($labelId, IQueryBuilder::PARAM_INT)));
|
$stmt->bindParam(1, $labelId, \PDO::PARAM_INT, 0);
|
||||||
$qb->executeStatement();
|
$stmt->execute();
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* @param numeric $cardId
|
|
||||||
* @return void
|
|
||||||
* @throws \OCP\DB\Exception
|
|
||||||
*/
|
|
||||||
public function deleteLabelAssignmentsForCard($cardId) {
|
public function deleteLabelAssignmentsForCard($cardId) {
|
||||||
$qb = $this->db->getQueryBuilder();
|
$sql = 'DELETE FROM `*PREFIX*deck_assigned_labels` WHERE card_id = ?';
|
||||||
$qb->delete('deck_assigned_labels')
|
$stmt = $this->db->prepare($sql);
|
||||||
->where($qb->expr()->eq('card_id', $qb->createNamedParameter($cardId, IQueryBuilder::PARAM_INT)));
|
$stmt->bindParam(1, $cardId, \PDO::PARAM_INT, 0);
|
||||||
$qb->executeStatement();
|
$stmt->execute();
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* @param string $userId
|
|
||||||
* @param numeric $labelId
|
|
||||||
* @return bool
|
|
||||||
* @throws \OCP\DB\Exception
|
|
||||||
*/
|
|
||||||
public function isOwner($userId, $labelId): bool {
|
public function isOwner($userId, $labelId): bool {
|
||||||
$qb = $this->db->getQueryBuilder();
|
$sql = 'SELECT owner FROM `*PREFIX*deck_boards` WHERE `id` IN (SELECT board_id FROM `*PREFIX*deck_labels` WHERE id = ?)';
|
||||||
$qb->select('l.id')
|
$stmt = $this->execute($sql, [$labelId]);
|
||||||
->from($this->getTableName(), 'l')
|
$row = $stmt->fetch();
|
||||||
->innerJoin('l', 'deck_boards', 'b', 'l.board_id = b.id')
|
return ($row['owner'] === $userId);
|
||||||
->where($qb->expr()->eq('l.id', $qb->createNamedParameter($labelId, IQueryBuilder::PARAM_INT)))
|
|
||||||
->andWhere($qb->expr()->eq('b.owner', $qb->createNamedParameter($userId, IQueryBuilder::PARAM_STR)));
|
|
||||||
|
|
||||||
return count($qb->executeQuery()->fetchAll()) > 0;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* @param numeric $id
|
|
||||||
* @return int|null
|
|
||||||
*/
|
|
||||||
public function findBoardId($id): ?int {
|
public function findBoardId($id): ?int {
|
||||||
try {
|
try {
|
||||||
$entity = $this->find($id);
|
$entity = $this->find($id);
|
||||||
|
|||||||
@@ -72,9 +72,6 @@ class RelationalEntity extends Entity implements \JsonSerializable {
|
|||||||
$propertyReflection = $reflection->getProperty($property);
|
$propertyReflection = $reflection->getProperty($property);
|
||||||
if (!$propertyReflection->isPrivate() && !in_array($property, $this->_resolvedProperties, true)) {
|
if (!$propertyReflection->isPrivate() && !in_array($property, $this->_resolvedProperties, true)) {
|
||||||
$json[$property] = $this->getter($property);
|
$json[$property] = $this->getter($property);
|
||||||
if ($json[$property] instanceof \DateTimeInterface) {
|
|
||||||
$json[$property] = $json[$property]->format('c');
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -26,7 +26,6 @@ namespace OCA\Deck\Db;
|
|||||||
use OCP\AppFramework\Db\DoesNotExistException;
|
use OCP\AppFramework\Db\DoesNotExistException;
|
||||||
use OCP\AppFramework\Db\Entity;
|
use OCP\AppFramework\Db\Entity;
|
||||||
use OCP\AppFramework\Db\MultipleObjectsReturnedException;
|
use OCP\AppFramework\Db\MultipleObjectsReturnedException;
|
||||||
use OCP\DB\QueryBuilder\IQueryBuilder;
|
|
||||||
use OCP\IDBConnection;
|
use OCP\IDBConnection;
|
||||||
|
|
||||||
class StackMapper extends DeckMapper implements IPermissionMapper {
|
class StackMapper extends DeckMapper implements IPermissionMapper {
|
||||||
@@ -39,112 +38,62 @@ class StackMapper extends DeckMapper implements IPermissionMapper {
|
|||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @param numeric $id
|
* @param $id
|
||||||
* @return Stack
|
|
||||||
* @throws DoesNotExistException
|
|
||||||
* @throws MultipleObjectsReturnedException
|
* @throws MultipleObjectsReturnedException
|
||||||
* @throws \OCP\DB\Exception
|
* @throws DoesNotExistException
|
||||||
*/
|
*/
|
||||||
public function find($id): Stack {
|
public function find($id): Stack {
|
||||||
$qb = $this->db->getQueryBuilder();
|
$sql = 'SELECT * FROM `*PREFIX*deck_stacks` ' .
|
||||||
$qb->select('*')
|
'WHERE `id` = ?';
|
||||||
->from($this->getTableName())
|
return $this->findEntity($sql, [$id]);
|
||||||
->where($qb->expr()->eq('id', $qb->createNamedParameter($id, IQueryBuilder::PARAM_INT)));
|
|
||||||
|
|
||||||
return $this->findEntity($qb);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @param $cardId
|
* @param $cardId
|
||||||
* @return Stack|null
|
* @return Stack|null
|
||||||
* @throws \OCP\DB\Exception
|
|
||||||
*/
|
*/
|
||||||
public function findStackFromCardId($cardId): ?Stack {
|
public function findStackFromCardId($cardId): ?Stack {
|
||||||
$qb = $this->db->getQueryBuilder();
|
$sql = <<<SQL
|
||||||
$qb->select('s.*')
|
SELECT s.*
|
||||||
->from($this->getTableName(), 's')
|
FROM `*PREFIX*deck_stacks` as `s`
|
||||||
->innerJoin('s', 'deck_cards', 'c', 's.id = c.stack_id')
|
INNER JOIN `*PREFIX*deck_cards` as `c` ON s.id = c.stack_id
|
||||||
->where($qb->expr()->eq('c.id', $qb->createNamedParameter($cardId, IQueryBuilder::PARAM_INT)));
|
WHERE c.id = ?
|
||||||
|
SQL;
|
||||||
try {
|
try {
|
||||||
return $this->findEntity($qb);
|
return $this->findEntity($sql, [$cardId]);
|
||||||
} catch (MultipleObjectsReturnedException|DoesNotExistException $e) {
|
} catch (MultipleObjectsReturnedException|DoesNotExistException $e) {
|
||||||
}
|
}
|
||||||
|
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* @param numeric $boardId
|
|
||||||
* @param int|null $limit
|
|
||||||
* @param int|null $offset
|
|
||||||
* @return Stack[]
|
|
||||||
* @throws \OCP\DB\Exception
|
|
||||||
*/
|
|
||||||
public function findAll($boardId, $limit = null, $offset = null): array {
|
|
||||||
$qb = $this->db->getQueryBuilder();
|
|
||||||
$qb->select('*')
|
|
||||||
->from($this->getTableName())
|
|
||||||
->where($qb->expr()->eq('board_id', $qb->createNamedParameter($boardId, IQueryBuilder::PARAM_INT)))
|
|
||||||
->andWhere($qb->expr()->eq('deleted_at', $qb->createNamedParameter(0, IQueryBuilder::PARAM_INT)))
|
|
||||||
->setFirstResult($offset)
|
|
||||||
->setMaxResults($limit);
|
|
||||||
|
|
||||||
return $this->findEntities($qb);
|
public function findAll($boardId, $limit = null, $offset = null) {
|
||||||
|
$sql = 'SELECT * FROM `*PREFIX*deck_stacks` WHERE `board_id` = ? AND deleted_at = 0 ORDER BY `order`, `id`';
|
||||||
|
return $this->findEntities($sql, [$boardId], $limit, $offset);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* @param numeric $boardId
|
|
||||||
* @param int|null $limit
|
|
||||||
* @param int|null $offset
|
|
||||||
* @return Stack[]
|
|
||||||
* @throws \OCP\DB\Exception
|
|
||||||
*/
|
|
||||||
public function findDeleted($boardId, $limit = null, $offset = null) {
|
public function findDeleted($boardId, $limit = null, $offset = null) {
|
||||||
$qb = $this->db->getQueryBuilder();
|
$sql = 'SELECT * FROM `*PREFIX*deck_stacks` s
|
||||||
$qb->select('*')
|
WHERE `s`.`board_id` = ? AND NOT s.deleted_at = 0';
|
||||||
->from($this->getTableName())
|
return $this->findEntities($sql, [$boardId], $limit, $offset);
|
||||||
->where($qb->expr()->eq('board_id', $qb->createNamedParameter($boardId, IQueryBuilder::PARAM_INT)))
|
|
||||||
->andWhere($qb->expr()->neq('deleted_at', $qb->createNamedParameter(0, IQueryBuilder::PARAM_INT)))
|
|
||||||
->setFirstResult($offset)
|
|
||||||
->setMaxResults($limit);
|
|
||||||
|
|
||||||
return $this->findEntities($qb);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* @param Entity $entity
|
public function delete(Entity $entity) {
|
||||||
* @return Entity
|
|
||||||
* @throws \OCP\DB\Exception
|
|
||||||
*/
|
|
||||||
public function delete(Entity $entity): Entity {
|
|
||||||
// delete cards on stack
|
// delete cards on stack
|
||||||
$this->cardMapper->deleteByStack($entity->getId());
|
$this->cardMapper->deleteByStack($entity->getId());
|
||||||
return parent::delete($entity);
|
return parent::delete($entity);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
public function isOwner($userId, $stackId): bool {
|
||||||
* @param numeric $userId
|
$sql = 'SELECT owner FROM `*PREFIX*deck_boards` WHERE `id` IN (SELECT board_id FROM `*PREFIX*deck_stacks` WHERE id = ?)';
|
||||||
* @param numeric $stackId
|
$stmt = $this->execute($sql, [$stackId]);
|
||||||
* @return bool
|
$row = $stmt->fetch();
|
||||||
* @throws \OCP\DB\Exception
|
return ($row['owner'] === $userId);
|
||||||
*/
|
|
||||||
public function isOwner($userId, $id): bool {
|
|
||||||
$qb = $this->db->getQueryBuilder();
|
|
||||||
$qb->select('s.id')
|
|
||||||
->from($this->getTableName(), 's')
|
|
||||||
->innerJoin('s', 'deck_boards', 'b', 'b.id = s.board_id')
|
|
||||||
->where($qb->expr()->eq('s.id', $qb->createNamedParameter($id, IQueryBuilder::PARAM_INT)))
|
|
||||||
->andWhere($qb->expr()->eq('owner', $qb->createNamedParameter($userId, IQueryBuilder::PARAM_STR)));
|
|
||||||
|
|
||||||
return count($qb->executeQuery()->fetchAll()) > 0;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* @param numeric $id
|
|
||||||
* @return int|null
|
|
||||||
* @throws \OCP\DB\Exception
|
|
||||||
*/
|
|
||||||
public function findBoardId($id): ?int {
|
public function findBoardId($id): ?int {
|
||||||
try {
|
try {
|
||||||
$entity = $this->find($id);
|
$entity = $this->find($id);
|
||||||
|
|||||||
@@ -1,92 +0,0 @@
|
|||||||
<?php
|
|
||||||
/**
|
|
||||||
* @copyright Copyright (c) 2022 Raul Ferreira Fuentes <raul@nextcloud.com>
|
|
||||||
*
|
|
||||||
* @author Raul Ferreira Fuentes <raul@nextcloud.com>
|
|
||||||
*
|
|
||||||
* @license GNU AGPL version 3 or any later version
|
|
||||||
*
|
|
||||||
* This program is free software: you can redistribute it and/or modify
|
|
||||||
* it under the terms of the GNU Affero General Public License as
|
|
||||||
* published by the Free Software Foundation, either version 3 of the
|
|
||||||
* License, or (at your option) any later version.
|
|
||||||
*
|
|
||||||
* This program is distributed in the hope that it will be useful,
|
|
||||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
||||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
||||||
* GNU Affero General Public License for more details.
|
|
||||||
*
|
|
||||||
* You should have received a copy of the GNU Affero General Public License
|
|
||||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
|
||||||
*
|
|
||||||
*/
|
|
||||||
namespace OCA\Deck\Model;
|
|
||||||
|
|
||||||
use DateTime;
|
|
||||||
use OCA\Deck\Db\Board;
|
|
||||||
use OCA\Deck\Db\Card;
|
|
||||||
|
|
||||||
class CardDetails extends Card {
|
|
||||||
private Card $card;
|
|
||||||
private ?Board $board;
|
|
||||||
|
|
||||||
public function __construct(Card $card, ?Board $board = null) {
|
|
||||||
parent::__construct();
|
|
||||||
$this->card = $card;
|
|
||||||
$this->board = $board;
|
|
||||||
}
|
|
||||||
|
|
||||||
public function setBoard(?Board $board): void {
|
|
||||||
$this->board = $board;
|
|
||||||
}
|
|
||||||
|
|
||||||
public function jsonSerialize(array $extras = []): array {
|
|
||||||
$array = $this->card->jsonSerialize();
|
|
||||||
unset($array['notified'], $array['descriptionPrev'], $array['relatedStack'], $array['relatedBoard']);
|
|
||||||
|
|
||||||
$array['overdue'] = $this->getDueStatus();
|
|
||||||
$this->appendBoardDetails($array);
|
|
||||||
|
|
||||||
return $array;
|
|
||||||
}
|
|
||||||
|
|
||||||
private function getDueStatus(): int {
|
|
||||||
$today = new DateTime();
|
|
||||||
$today->setTime(0, 0);
|
|
||||||
|
|
||||||
$match_date = $this->card->getDuedate();
|
|
||||||
if (!$match_date) {
|
|
||||||
return Card::DUEDATE_FUTURE;
|
|
||||||
}
|
|
||||||
$match_date->setTime(0, 0);
|
|
||||||
|
|
||||||
$diff = $today->diff($match_date);
|
|
||||||
$diffDays = (int) $diff->format('%R%a'); // Extract days count in interval
|
|
||||||
|
|
||||||
|
|
||||||
if ($diffDays === 1) {
|
|
||||||
return Card::DUEDATE_NEXT;
|
|
||||||
}
|
|
||||||
if ($diffDays === 0) {
|
|
||||||
return Card::DUEDATE_NOW;
|
|
||||||
}
|
|
||||||
if ($diffDays < 0) {
|
|
||||||
return Card::DUEDATE_OVERDUE;
|
|
||||||
}
|
|
||||||
|
|
||||||
return Card::DUEDATE_FUTURE;
|
|
||||||
}
|
|
||||||
|
|
||||||
private function appendBoardDetails(&$array): void {
|
|
||||||
if (!$this->board) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
$array['boardId'] = $this->board->id;
|
|
||||||
$array['board'] = (new BoardSummary($this->board))->jsonSerialize();
|
|
||||||
}
|
|
||||||
|
|
||||||
public function __call($name, $arguments) {
|
|
||||||
return $this->card->__call($name, $arguments);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -129,7 +129,7 @@ class NotificationHelper {
|
|||||||
->setSubject('card-overdue', [
|
->setSubject('card-overdue', [
|
||||||
$card->getTitle(), $board->getTitle()
|
$card->getTitle(), $board->getTitle()
|
||||||
])
|
])
|
||||||
->setDateTime($card->getDuedate());
|
->setDateTime(new DateTime($card->getDuedate()));
|
||||||
$this->notificationManager->notify($notification);
|
$this->notificationManager->notify($notification);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -242,7 +242,7 @@ class NotificationHelper {
|
|||||||
}
|
}
|
||||||
return $this->boards[$boardId];
|
return $this->boards[$boardId];
|
||||||
}
|
}
|
||||||
|
|
||||||
private function generateBoardShared(Board $board, string $userId): INotification {
|
private function generateBoardShared(Board $board, string $userId): INotification {
|
||||||
$notification = $this->notificationManager->createNotification();
|
$notification = $this->notificationManager->createNotification();
|
||||||
$notification
|
$notification
|
||||||
|
|||||||
@@ -114,7 +114,7 @@ class Notifier implements INotifier {
|
|||||||
$dn = $params[2];
|
$dn = $params[2];
|
||||||
}
|
}
|
||||||
$notification->setParsedSubject(
|
$notification->setParsedSubject(
|
||||||
$l->t('The card "%s" on "%s" has been assigned to you by %s.', [$params[0], $params[1], $dn])
|
(string) $l->t('The card "%s" on "%s" has been assigned to you by %s.', [$params[0], $params[1], $dn])
|
||||||
);
|
);
|
||||||
$notification->setRichSubject(
|
$notification->setRichSubject(
|
||||||
$l->t('{user} has assigned the card {deck-card} on {deck-board} to you.'),
|
$l->t('{user} has assigned the card {deck-card} on {deck-board} to you.'),
|
||||||
@@ -151,7 +151,7 @@ class Notifier implements INotifier {
|
|||||||
}
|
}
|
||||||
|
|
||||||
$notification->setParsedSubject(
|
$notification->setParsedSubject(
|
||||||
$l->t('The card "%s" on "%s" has reached its due date.', $params)
|
(string) $l->t('The card "%s" on "%s" has reached its due date.', $params)
|
||||||
);
|
);
|
||||||
$notification->setRichSubject(
|
$notification->setRichSubject(
|
||||||
$l->t('The card {deck-card} on {deck-board} has reached its due date.'),
|
$l->t('The card {deck-card} on {deck-board} has reached its due date.'),
|
||||||
@@ -189,7 +189,7 @@ class Notifier implements INotifier {
|
|||||||
$dn = $params[2];
|
$dn = $params[2];
|
||||||
}
|
}
|
||||||
$notification->setParsedSubject(
|
$notification->setParsedSubject(
|
||||||
$l->t('%s has mentioned you in a comment on "%s".', [$dn, $params[0]])
|
(string) $l->t('%s has mentioned you in a comment on "%s".', [$dn, $params[0]])
|
||||||
);
|
);
|
||||||
$notification->setRichSubject(
|
$notification->setRichSubject(
|
||||||
$l->t('{user} has mentioned you in a comment on {deck-card}.'),
|
$l->t('{user} has mentioned you in a comment on {deck-card}.'),
|
||||||
@@ -226,7 +226,7 @@ class Notifier implements INotifier {
|
|||||||
$dn = $params[1];
|
$dn = $params[1];
|
||||||
}
|
}
|
||||||
$notification->setParsedSubject(
|
$notification->setParsedSubject(
|
||||||
$l->t('The board "%s" has been shared with you by %s.', [$params[0], $dn])
|
(string) $l->t('The board "%s" has been shared with you by %s.', [$params[0], $dn])
|
||||||
);
|
);
|
||||||
$notification->setRichSubject(
|
$notification->setRichSubject(
|
||||||
$l->t('{user} has shared {deck-board} with you.'),
|
$l->t('{user} has shared {deck-board} with you.'),
|
||||||
|
|||||||
@@ -54,11 +54,22 @@ use OCP\IURLGenerator;
|
|||||||
class DeckProvider implements IFullTextSearchProvider {
|
class DeckProvider implements IFullTextSearchProvider {
|
||||||
public const DECK_PROVIDER_ID = 'deck';
|
public const DECK_PROVIDER_ID = 'deck';
|
||||||
|
|
||||||
private IL10N $l10n;
|
|
||||||
private IUrlGenerator $urlGenerator;
|
/** @var IL10N */
|
||||||
private FullTextSearchService $fullTextSearchService;
|
private $l10n;
|
||||||
private ?IRunner $runner = null;
|
|
||||||
private ?IIndexOptions $indexOptions = null;
|
/** @var IUrlGenerator */
|
||||||
|
private $urlGenerator;
|
||||||
|
|
||||||
|
/** @var FullTextSearchService */
|
||||||
|
private $fullTextSearchService;
|
||||||
|
|
||||||
|
|
||||||
|
/** @var IRunner */
|
||||||
|
private $runner;
|
||||||
|
|
||||||
|
/** @var IIndexOptions */
|
||||||
|
private $indexOptions = [];
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@@ -1,169 +0,0 @@
|
|||||||
<?php
|
|
||||||
/**
|
|
||||||
* @copyright Copyright (c) 2022 Julien Veyssier <eneiluj@posteo.net>
|
|
||||||
*
|
|
||||||
* @author Julien Veyssier <eneiluj@posteo.net>
|
|
||||||
*
|
|
||||||
* @license GNU AGPL version 3 or any later version
|
|
||||||
*
|
|
||||||
* This program is free software: you can redistribute it and/or modify
|
|
||||||
* it under the terms of the GNU Affero General Public License as
|
|
||||||
* published by the Free Software Foundation, either version 3 of the
|
|
||||||
* License, or (at your option) any later version.
|
|
||||||
*
|
|
||||||
* This program is distributed in the hope that it will be useful,
|
|
||||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
||||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
||||||
* GNU Affero General Public License for more details.
|
|
||||||
*
|
|
||||||
* You should have received a copy of the GNU Affero General Public License
|
|
||||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
|
||||||
*/
|
|
||||||
|
|
||||||
namespace OCA\Deck\Reference;
|
|
||||||
|
|
||||||
use OCA\Deck\AppInfo\Application;
|
|
||||||
use OCA\Deck\Db\Assignment;
|
|
||||||
use OCA\Deck\Db\Attachment;
|
|
||||||
use OCA\Deck\Db\Label;
|
|
||||||
use OCA\Deck\Model\CardDetails;
|
|
||||||
use OCA\Deck\Service\BoardService;
|
|
||||||
use OCA\Deck\Service\CardService;
|
|
||||||
use OCA\Deck\Service\StackService;
|
|
||||||
use OCP\Collaboration\Reference\IReference;
|
|
||||||
use OCP\Collaboration\Reference\IReferenceProvider;
|
|
||||||
use OCP\Collaboration\Reference\Reference;
|
|
||||||
use OCP\IURLGenerator;
|
|
||||||
|
|
||||||
class CardReferenceProvider implements IReferenceProvider {
|
|
||||||
private CardService $cardService;
|
|
||||||
private IURLGenerator $urlGenerator;
|
|
||||||
private BoardService $boardService;
|
|
||||||
private StackService $stackService;
|
|
||||||
private ?string $userId;
|
|
||||||
|
|
||||||
public function __construct(CardService $cardService,
|
|
||||||
BoardService $boardService,
|
|
||||||
StackService $stackService,
|
|
||||||
IURLGenerator $urlGenerator,
|
|
||||||
?string $userId) {
|
|
||||||
$this->cardService = $cardService;
|
|
||||||
$this->urlGenerator = $urlGenerator;
|
|
||||||
$this->boardService = $boardService;
|
|
||||||
$this->stackService = $stackService;
|
|
||||||
$this->userId = $userId;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @inheritDoc
|
|
||||||
*/
|
|
||||||
public function matchReference(string $referenceText): bool {
|
|
||||||
$start = $this->urlGenerator->getAbsoluteURL('/apps/' . Application::APP_ID);
|
|
||||||
$startIndex = $this->urlGenerator->getAbsoluteURL('/index.php/apps/' . Application::APP_ID);
|
|
||||||
|
|
||||||
// link example: https://nextcloud.local/index.php/apps/deck/#/board/2/card/11
|
|
||||||
$noIndexMatch = preg_match('/^' . preg_quote($start, '/') . '\/#\/board\/[0-9]+\/card\/[0-9]+$/', $referenceText) === 1;
|
|
||||||
$indexMatch = preg_match('/^' . preg_quote($startIndex, '/') . '\/#\/board\/[0-9]+\/card\/[0-9]+$/', $referenceText) === 1;
|
|
||||||
|
|
||||||
return $noIndexMatch || $indexMatch;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @inheritDoc
|
|
||||||
*/
|
|
||||||
public function resolveReference(string $referenceText): ?IReference {
|
|
||||||
if ($this->matchReference($referenceText)) {
|
|
||||||
$ids = $this->getBoardCardId($referenceText);
|
|
||||||
if ($ids !== null) {
|
|
||||||
[$boardId, $cardId] = $ids;
|
|
||||||
$card = $this->cardService->find((int) $cardId)->jsonSerialize();
|
|
||||||
$board = $this->boardService->find((int) $boardId)->jsonSerialize();
|
|
||||||
$stack = $this->stackService->find((int) $card['stackId'])->jsonSerialize();
|
|
||||||
|
|
||||||
$card = $this->sanitizeSerializedCard($card);
|
|
||||||
$board = $this->sanitizeSerializedBoard($board);
|
|
||||||
$stack = $this->sanitizeSerializedStack($stack);
|
|
||||||
|
|
||||||
$reference = new Reference($referenceText);
|
|
||||||
$reference->setRichObject(Application::APP_ID . '-card', [
|
|
||||||
'id' => $boardId . '/' . $cardId,
|
|
||||||
'card' => $card,
|
|
||||||
'board' => $board,
|
|
||||||
'stack' => $stack,
|
|
||||||
]);
|
|
||||||
return $reference;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
private function sanitizeSerializedStack(array $stack): array {
|
|
||||||
$stack['cards'] = array_map(function (CardDetails $cardDetails) {
|
|
||||||
$result = $cardDetails->jsonSerialize();
|
|
||||||
unset($result['assignedUsers']);
|
|
||||||
return $result;
|
|
||||||
}, $stack['cards']);
|
|
||||||
|
|
||||||
return $stack;
|
|
||||||
}
|
|
||||||
|
|
||||||
private function sanitizeSerializedBoard(array $board): array {
|
|
||||||
unset($board['labels']);
|
|
||||||
$board['owner'] = $board['owner']->jsonSerialize();
|
|
||||||
unset($board['acl']);
|
|
||||||
unset($board['users']);
|
|
||||||
|
|
||||||
return $board;
|
|
||||||
}
|
|
||||||
|
|
||||||
private function sanitizeSerializedCard(array $card): array {
|
|
||||||
$card['labels'] = array_map(function (Label $label) {
|
|
||||||
return $label->jsonSerialize();
|
|
||||||
}, $card['labels']);
|
|
||||||
$card['assignedUsers'] = array_map(function (Assignment $assignment) {
|
|
||||||
$result = $assignment->jsonSerialize();
|
|
||||||
$result['participant'] = $result['participant']->jsonSerialize();
|
|
||||||
return $result;
|
|
||||||
}, $card['assignedUsers']);
|
|
||||||
$card['owner'] = $card['owner']->jsonSerialize();
|
|
||||||
unset($card['relatedStack']);
|
|
||||||
unset($card['relatedBoard']);
|
|
||||||
$card['attachments'] = array_map(function (Attachment $attachment) {
|
|
||||||
return $attachment->jsonSerialize();
|
|
||||||
}, $card['attachments']);
|
|
||||||
|
|
||||||
return $card;
|
|
||||||
}
|
|
||||||
|
|
||||||
private function getBoardCardId(string $url): ?array {
|
|
||||||
$start = $this->urlGenerator->getAbsoluteURL('/apps/' . Application::APP_ID);
|
|
||||||
$startIndex = $this->urlGenerator->getAbsoluteURL('/index.php/apps/' . Application::APP_ID);
|
|
||||||
|
|
||||||
preg_match('/^' . preg_quote($start, '/') . '\/#\/board\/([0-9]+)\/card\/([0-9]+)$/', $url, $matches);
|
|
||||||
if ($matches && count($matches) > 2) {
|
|
||||||
return [$matches[1], $matches[2]];
|
|
||||||
}
|
|
||||||
|
|
||||||
preg_match('/^' . preg_quote($startIndex, '/') . '\/#\/board\/([0-9]+)\/card\/([0-9]+)$/', $url, $matches2);
|
|
||||||
if ($matches2 && count($matches2) > 2) {
|
|
||||||
return [$matches2[1], $matches2[2]];
|
|
||||||
}
|
|
||||||
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
public function getCachePrefix(string $referenceId): string {
|
|
||||||
$ids = $this->getBoardCardId($referenceId);
|
|
||||||
if ($ids !== null) {
|
|
||||||
[$boardId, $cardId] = $ids;
|
|
||||||
return $boardId . '/' . $cardId;
|
|
||||||
}
|
|
||||||
|
|
||||||
return $referenceId;
|
|
||||||
}
|
|
||||||
|
|
||||||
public function getCacheKey(string $referenceId): ?string {
|
|
||||||
return $this->userId ?? '';
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -60,23 +60,22 @@ class AttachmentService {
|
|||||||
private $activityManager;
|
private $activityManager;
|
||||||
/** @var ChangeHelper */
|
/** @var ChangeHelper */
|
||||||
private $changeHelper;
|
private $changeHelper;
|
||||||
private IUserManager $userManager;
|
/** @var IUserManager */
|
||||||
|
private $userManager;
|
||||||
/** @var AttachmentServiceValidator */
|
/** @var AttachmentServiceValidator */
|
||||||
private AttachmentServiceValidator $attachmentServiceValidator;
|
private $attachmentServiceValidator;
|
||||||
|
|
||||||
public function __construct(
|
public function __construct(AttachmentMapper $attachmentMapper,
|
||||||
AttachmentMapper $attachmentMapper,
|
CardMapper $cardMapper,
|
||||||
CardMapper $cardMapper,
|
IUserManager $userManager,
|
||||||
IUserManager $userManager,
|
ChangeHelper $changeHelper,
|
||||||
ChangeHelper $changeHelper,
|
PermissionService $permissionService,
|
||||||
PermissionService $permissionService,
|
Application $application,
|
||||||
Application $application,
|
AttachmentCacheHelper $attachmentCacheHelper,
|
||||||
AttachmentCacheHelper $attachmentCacheHelper,
|
$userId,
|
||||||
$userId,
|
IL10N $l10n,
|
||||||
IL10N $l10n,
|
ActivityManager $activityManager,
|
||||||
ActivityManager $activityManager,
|
AttachmentServiceValidator $attachmentServiceValidator) {
|
||||||
AttachmentServiceValidator $attachmentServiceValidator
|
|
||||||
) {
|
|
||||||
$this->attachmentMapper = $attachmentMapper;
|
$this->attachmentMapper = $attachmentMapper;
|
||||||
$this->cardMapper = $cardMapper;
|
$this->cardMapper = $cardMapper;
|
||||||
$this->permissionService = $permissionService;
|
$this->permissionService = $permissionService;
|
||||||
|
|||||||
@@ -24,6 +24,7 @@
|
|||||||
|
|
||||||
namespace OCA\Deck\Service;
|
namespace OCA\Deck\Service;
|
||||||
|
|
||||||
|
use Doctrine\DBAL\Exception\UniqueConstraintViolationException;
|
||||||
use OCA\Deck\Activity\ActivityManager;
|
use OCA\Deck\Activity\ActivityManager;
|
||||||
use OCA\Deck\Activity\ChangeSet;
|
use OCA\Deck\Activity\ChangeSet;
|
||||||
use OCA\Deck\AppInfo\Application;
|
use OCA\Deck\AppInfo\Application;
|
||||||
@@ -44,10 +45,8 @@ use OCA\Deck\Notification\NotificationHelper;
|
|||||||
use OCP\AppFramework\Db\DoesNotExistException;
|
use OCP\AppFramework\Db\DoesNotExistException;
|
||||||
use OCP\EventDispatcher\IEventDispatcher;
|
use OCP\EventDispatcher\IEventDispatcher;
|
||||||
use OCP\IConfig;
|
use OCP\IConfig;
|
||||||
use OCP\IDBConnection;
|
|
||||||
use OCP\IGroupManager;
|
use OCP\IGroupManager;
|
||||||
use OCP\IL10N;
|
use OCP\IL10N;
|
||||||
use OCP\DB\Exception as DbException;
|
|
||||||
use OCA\Deck\Db\Board;
|
use OCA\Deck\Db\Board;
|
||||||
use OCA\Deck\Db\BoardMapper;
|
use OCA\Deck\Db\BoardMapper;
|
||||||
use OCA\Deck\Db\LabelMapper;
|
use OCA\Deck\Db\LabelMapper;
|
||||||
@@ -55,29 +54,30 @@ use OCP\IUserManager;
|
|||||||
use OCA\Deck\BadRequestException;
|
use OCA\Deck\BadRequestException;
|
||||||
use OCA\Deck\Validators\BoardServiceValidator;
|
use OCA\Deck\Validators\BoardServiceValidator;
|
||||||
use OCP\IURLGenerator;
|
use OCP\IURLGenerator;
|
||||||
use OCP\Server;
|
|
||||||
|
|
||||||
class BoardService {
|
class BoardService {
|
||||||
private BoardMapper $boardMapper;
|
private $boardMapper;
|
||||||
private StackMapper $stackMapper;
|
private $stackMapper;
|
||||||
private LabelMapper $labelMapper;
|
private $labelMapper;
|
||||||
private AclMapper $aclMapper;
|
private $aclMapper;
|
||||||
private IConfig $config;
|
/** @var IConfig */
|
||||||
private IL10N $l10n;
|
private $config;
|
||||||
private PermissionService $permissionService;
|
private $l10n;
|
||||||
private NotificationHelper $notificationHelper;
|
private $permissionService;
|
||||||
private AssignmentMapper $assignedUsersMapper;
|
private $notificationHelper;
|
||||||
private IUserManager $userManager;
|
private $assignedUsersMapper;
|
||||||
private IGroupManager $groupManager;
|
private $userManager;
|
||||||
private ?string $userId;
|
private $groupManager;
|
||||||
private ActivityManager $activityManager;
|
private $userId;
|
||||||
private IEventDispatcher $eventDispatcher;
|
private $activityManager;
|
||||||
private ChangeHelper $changeHelper;
|
private $eventDispatcher;
|
||||||
private CardMapper $cardMapper;
|
private $changeHelper;
|
||||||
private ?array $boardsCache = null;
|
private $cardMapper;
|
||||||
private IURLGenerator $urlGenerator;
|
|
||||||
private IDBConnection $connection;
|
private $boardsCache = null;
|
||||||
private BoardServiceValidator $boardServiceValidator;
|
private $urlGenerator;
|
||||||
|
private $boardServiceValidator;
|
||||||
|
|
||||||
|
|
||||||
public function __construct(
|
public function __construct(
|
||||||
BoardMapper $boardMapper,
|
BoardMapper $boardMapper,
|
||||||
@@ -96,9 +96,8 @@ class BoardService {
|
|||||||
IEventDispatcher $eventDispatcher,
|
IEventDispatcher $eventDispatcher,
|
||||||
ChangeHelper $changeHelper,
|
ChangeHelper $changeHelper,
|
||||||
IURLGenerator $urlGenerator,
|
IURLGenerator $urlGenerator,
|
||||||
IDBConnection $connection,
|
|
||||||
BoardServiceValidator $boardServiceValidator,
|
BoardServiceValidator $boardServiceValidator,
|
||||||
?string $userId
|
$userId
|
||||||
) {
|
) {
|
||||||
$this->boardMapper = $boardMapper;
|
$this->boardMapper = $boardMapper;
|
||||||
$this->stackMapper = $stackMapper;
|
$this->stackMapper = $stackMapper;
|
||||||
@@ -117,7 +116,6 @@ class BoardService {
|
|||||||
$this->userId = $userId;
|
$this->userId = $userId;
|
||||||
$this->urlGenerator = $urlGenerator;
|
$this->urlGenerator = $urlGenerator;
|
||||||
$this->cardMapper = $cardMapper;
|
$this->cardMapper = $cardMapper;
|
||||||
$this->connection = $connection;
|
|
||||||
$this->boardServiceValidator = $boardServiceValidator;
|
$this->boardServiceValidator = $boardServiceValidator;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -128,6 +126,7 @@ class BoardService {
|
|||||||
*/
|
*/
|
||||||
public function setUserId(string $userId): void {
|
public function setUserId(string $userId): void {
|
||||||
$this->userId = $userId;
|
$this->userId = $userId;
|
||||||
|
$this->permissionService->setUserId($userId);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -182,7 +181,7 @@ class BoardService {
|
|||||||
* @throws \OCP\AppFramework\Db\MultipleObjectsReturnedException
|
* @throws \OCP\AppFramework\Db\MultipleObjectsReturnedException
|
||||||
* @throws BadRequestException
|
* @throws BadRequestException
|
||||||
*/
|
*/
|
||||||
public function find($boardId) {
|
public function find($boardId, bool $allowDeleted = false) {
|
||||||
$this->boardServiceValidator->check(compact('boardId'));
|
$this->boardServiceValidator->check(compact('boardId'));
|
||||||
if ($this->boardsCache && isset($this->boardsCache[$boardId])) {
|
if ($this->boardsCache && isset($this->boardsCache[$boardId])) {
|
||||||
return $this->boardsCache[$boardId];
|
return $this->boardsCache[$boardId];
|
||||||
@@ -193,7 +192,7 @@ class BoardService {
|
|||||||
|
|
||||||
$this->permissionService->checkPermission($this->boardMapper, $boardId, Acl::PERMISSION_READ);
|
$this->permissionService->checkPermission($this->boardMapper, $boardId, Acl::PERMISSION_READ);
|
||||||
/** @var Board $board */
|
/** @var Board $board */
|
||||||
$board = $this->boardMapper->find($boardId, true, true);
|
$board = $this->boardMapper->find((int)$boardId, true, true, $allowDeleted);
|
||||||
$this->boardMapper->mapOwner($board);
|
$this->boardMapper->mapOwner($board);
|
||||||
if ($board->getAcl() !== null) {
|
if ($board->getAcl() !== null) {
|
||||||
foreach ($board->getAcl() as $acl) {
|
foreach ($board->getAcl() as $acl) {
|
||||||
@@ -368,7 +367,7 @@ class BoardService {
|
|||||||
$this->boardServiceValidator->check(compact('id'));
|
$this->boardServiceValidator->check(compact('id'));
|
||||||
|
|
||||||
$this->permissionService->checkPermission($this->boardMapper, $id, Acl::PERMISSION_MANAGE);
|
$this->permissionService->checkPermission($this->boardMapper, $id, Acl::PERMISSION_MANAGE);
|
||||||
$board = $this->find($id);
|
$board = $this->find($id, true);
|
||||||
$board->setDeletedAt(0);
|
$board->setDeletedAt(0);
|
||||||
$board = $this->boardMapper->update($board);
|
$board = $this->boardMapper->update($board);
|
||||||
$this->activityManager->triggerEvent(ActivityManager::DECK_OBJECT_BOARD, $board, ActivityManager::SUBJECT_BOARD_RESTORE);
|
$this->activityManager->triggerEvent(ActivityManager::DECK_OBJECT_BOARD, $board, ActivityManager::SUBJECT_BOARD_RESTORE);
|
||||||
@@ -389,7 +388,7 @@ class BoardService {
|
|||||||
$this->boardServiceValidator->check(compact('id'));
|
$this->boardServiceValidator->check(compact('id'));
|
||||||
|
|
||||||
$this->permissionService->checkPermission($this->boardMapper, $id, Acl::PERMISSION_MANAGE);
|
$this->permissionService->checkPermission($this->boardMapper, $id, Acl::PERMISSION_MANAGE);
|
||||||
$board = $this->find($id);
|
$board = $this->find($id, true);
|
||||||
$delete = $this->boardMapper->delete($board);
|
$delete = $this->boardMapper->delete($board);
|
||||||
|
|
||||||
return $delete;
|
return $delete;
|
||||||
@@ -472,7 +471,7 @@ class BoardService {
|
|||||||
$newAcl = $this->aclMapper->insert($acl);
|
$newAcl = $this->aclMapper->insert($acl);
|
||||||
|
|
||||||
$this->activityManager->triggerEvent(ActivityManager::DECK_OBJECT_BOARD, $newAcl, ActivityManager::SUBJECT_BOARD_SHARE, [], $this->userId);
|
$this->activityManager->triggerEvent(ActivityManager::DECK_OBJECT_BOARD, $newAcl, ActivityManager::SUBJECT_BOARD_SHARE, [], $this->userId);
|
||||||
$this->notificationHelper->sendBoardShared((int)$boardId, $acl);
|
$this->notificationHelper->sendBoardShared($boardId, $acl);
|
||||||
$this->boardMapper->mapAcl($newAcl);
|
$this->boardMapper->mapAcl($newAcl);
|
||||||
$this->changeHelper->boardChanged($boardId);
|
$this->changeHelper->boardChanged($boardId);
|
||||||
|
|
||||||
@@ -481,7 +480,7 @@ class BoardService {
|
|||||||
|
|
||||||
// TODO: use the dispatched event for this
|
// TODO: use the dispatched event for this
|
||||||
try {
|
try {
|
||||||
$resourceProvider = Server::get(\OCA\Deck\Collaboration\Resources\ResourceProvider::class);
|
$resourceProvider = \OC::$server->query(\OCA\Deck\Collaboration\Resources\ResourceProvider::class);
|
||||||
$resourceProvider->invalidateAccessCache($boardId);
|
$resourceProvider->invalidateAccessCache($boardId);
|
||||||
} catch (\Exception $e) {
|
} catch (\Exception $e) {
|
||||||
}
|
}
|
||||||
@@ -523,12 +522,18 @@ class BoardService {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
* @param $id
|
||||||
|
* @return \OCP\AppFramework\Db\Entity
|
||||||
* @throws DoesNotExistException
|
* @throws DoesNotExistException
|
||||||
* @throws \OCA\Deck\NoPermissionException
|
* @throws \OCA\Deck\NoPermissionException
|
||||||
* @throws \OCP\AppFramework\Db\MultipleObjectsReturnedException
|
* @throws \OCP\AppFramework\Db\MultipleObjectsReturnedException
|
||||||
* @throws BadRequestException
|
* @throws BadRequestException
|
||||||
*/
|
*/
|
||||||
public function deleteAcl(int $id): bool {
|
public function deleteAcl($id) {
|
||||||
|
if (is_numeric($id) === false) {
|
||||||
|
throw new BadRequestException('id must be a number');
|
||||||
|
}
|
||||||
|
|
||||||
$this->permissionService->checkPermission($this->aclMapper, $id, Acl::PERMISSION_SHARE);
|
$this->permissionService->checkPermission($this->aclMapper, $id, Acl::PERMISSION_SHARE);
|
||||||
/** @var Acl $acl */
|
/** @var Acl $acl */
|
||||||
$acl = $this->aclMapper->find($id);
|
$acl = $this->aclMapper->find($id);
|
||||||
@@ -547,14 +552,16 @@ class BoardService {
|
|||||||
$version = \OCP\Util::getVersion()[0];
|
$version = \OCP\Util::getVersion()[0];
|
||||||
if ($version >= 16) {
|
if ($version >= 16) {
|
||||||
try {
|
try {
|
||||||
$resourceProvider = Server::get(\OCA\Deck\Collaboration\Resources\ResourceProvider::class);
|
$resourceProvider = \OC::$server->query(\OCA\Deck\Collaboration\Resources\ResourceProvider::class);
|
||||||
$resourceProvider->invalidateAccessCache($acl->getBoardId());
|
$resourceProvider->invalidateAccessCache($acl->getBoardId());
|
||||||
} catch (\Exception $e) {
|
} catch (\Exception $e) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
$delete = $this->aclMapper->delete($acl);
|
||||||
|
|
||||||
$this->eventDispatcher->dispatchTyped(new AclDeletedEvent($acl));
|
$this->eventDispatcher->dispatchTyped(new AclDeletedEvent($acl));
|
||||||
return (bool) $this->aclMapper->delete($acl);
|
|
||||||
|
return $delete;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -606,7 +613,7 @@ class BoardService {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public function transferBoardOwnership(int $boardId, string $newOwner, bool $changeContent = false): Board {
|
public function transferBoardOwnership(int $boardId, string $newOwner, bool $changeContent = false): Board {
|
||||||
$this->connection->beginTransaction();
|
\OC::$server->getDatabaseConnection()->beginTransaction();
|
||||||
try {
|
try {
|
||||||
$board = $this->boardMapper->find($boardId);
|
$board = $this->boardMapper->find($boardId);
|
||||||
$previousOwner = $board->getOwner();
|
$previousOwner = $board->getOwner();
|
||||||
@@ -615,10 +622,7 @@ class BoardService {
|
|||||||
if (!$changeContent) {
|
if (!$changeContent) {
|
||||||
try {
|
try {
|
||||||
$this->addAcl($boardId, Acl::PERMISSION_TYPE_USER, $previousOwner, true, true, true);
|
$this->addAcl($boardId, Acl::PERMISSION_TYPE_USER, $previousOwner, true, true, true);
|
||||||
} catch (DbException $e) {
|
} catch (UniqueConstraintViolationException $e) {
|
||||||
if ($e->getReason() !== DbException::REASON_UNIQUE_CONSTRAINT_VIOLATION) {
|
|
||||||
throw $e;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
$this->boardMapper->transferOwnership($previousOwner, $newOwner, $boardId);
|
$this->boardMapper->transferOwnership($previousOwner, $newOwner, $boardId);
|
||||||
@@ -628,10 +632,10 @@ class BoardService {
|
|||||||
$this->assignedUsersMapper->remapAssignedUser($boardId, $previousOwner, $newOwner);
|
$this->assignedUsersMapper->remapAssignedUser($boardId, $previousOwner, $newOwner);
|
||||||
$this->cardMapper->remapCardOwner($boardId, $previousOwner, $newOwner);
|
$this->cardMapper->remapCardOwner($boardId, $previousOwner, $newOwner);
|
||||||
}
|
}
|
||||||
$this->connection->commit();
|
\OC::$server->getDatabaseConnection()->commit();
|
||||||
return $this->boardMapper->find($boardId);
|
return $this->boardMapper->find($boardId);
|
||||||
} catch (\Throwable $e) {
|
} catch (\Throwable $e) {
|
||||||
$this->connection->rollBack();
|
\OC::$server->getDatabaseConnection()->rollBack();
|
||||||
throw $e;
|
throw $e;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -46,31 +46,27 @@ use OCA\Deck\BadRequestException;
|
|||||||
use OCA\Deck\Validators\CardServiceValidator;
|
use OCA\Deck\Validators\CardServiceValidator;
|
||||||
use OCP\Comments\ICommentsManager;
|
use OCP\Comments\ICommentsManager;
|
||||||
use OCP\EventDispatcher\IEventDispatcher;
|
use OCP\EventDispatcher\IEventDispatcher;
|
||||||
use OCP\IRequest;
|
|
||||||
use OCP\IUserManager;
|
use OCP\IUserManager;
|
||||||
use OCP\IURLGenerator;
|
use OCP\IURLGenerator;
|
||||||
use Psr\Log\LoggerInterface;
|
|
||||||
|
|
||||||
class CardService {
|
class CardService {
|
||||||
private CardMapper $cardMapper;
|
private $cardMapper;
|
||||||
private StackMapper $stackMapper;
|
private $stackMapper;
|
||||||
private BoardMapper $boardMapper;
|
private $boardMapper;
|
||||||
private LabelMapper $labelMapper;
|
private $labelMapper;
|
||||||
private PermissionService $permissionService;
|
private $permissionService;
|
||||||
private BoardService $boardService;
|
private $boardService;
|
||||||
private NotificationHelper $notificationHelper;
|
private $notificationHelper;
|
||||||
private AssignmentMapper $assignedUsersMapper;
|
private $assignedUsersMapper;
|
||||||
private AttachmentService $attachmentService;
|
private $attachmentService;
|
||||||
private ?string $currentUser;
|
private $currentUser;
|
||||||
private ActivityManager $activityManager;
|
private $activityManager;
|
||||||
private ICommentsManager $commentsManager;
|
private $commentsManager;
|
||||||
private ChangeHelper $changeHelper;
|
private $changeHelper;
|
||||||
private IEventDispatcher $eventDispatcher;
|
private $eventDispatcher;
|
||||||
private IUserManager $userManager;
|
private $userManager;
|
||||||
private IURLGenerator $urlGenerator;
|
private $urlGenerator;
|
||||||
private LoggerInterface $logger;
|
private $cardServiceValidator;
|
||||||
private IRequest $request;
|
|
||||||
private CardServiceValidator $cardServiceValidator;
|
|
||||||
|
|
||||||
public function __construct(
|
public function __construct(
|
||||||
CardMapper $cardMapper,
|
CardMapper $cardMapper,
|
||||||
@@ -88,10 +84,8 @@ class CardService {
|
|||||||
ChangeHelper $changeHelper,
|
ChangeHelper $changeHelper,
|
||||||
IEventDispatcher $eventDispatcher,
|
IEventDispatcher $eventDispatcher,
|
||||||
IURLGenerator $urlGenerator,
|
IURLGenerator $urlGenerator,
|
||||||
LoggerInterface $logger,
|
|
||||||
IRequest $request,
|
|
||||||
CardServiceValidator $cardServiceValidator,
|
CardServiceValidator $cardServiceValidator,
|
||||||
?string $userId
|
$userId
|
||||||
) {
|
) {
|
||||||
$this->cardMapper = $cardMapper;
|
$this->cardMapper = $cardMapper;
|
||||||
$this->stackMapper = $stackMapper;
|
$this->stackMapper = $stackMapper;
|
||||||
@@ -109,8 +103,6 @@ class CardService {
|
|||||||
$this->eventDispatcher = $eventDispatcher;
|
$this->eventDispatcher = $eventDispatcher;
|
||||||
$this->currentUser = $userId;
|
$this->currentUser = $userId;
|
||||||
$this->urlGenerator = $urlGenerator;
|
$this->urlGenerator = $urlGenerator;
|
||||||
$this->logger = $logger;
|
|
||||||
$this->request = $request;
|
|
||||||
$this->cardServiceValidator = $cardServiceValidator;
|
$this->cardServiceValidator = $cardServiceValidator;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -144,18 +136,23 @@ class CardService {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
* @param $cardId
|
||||||
* @return \OCA\Deck\Db\RelationalEntity
|
* @return \OCA\Deck\Db\RelationalEntity
|
||||||
* @throws \OCA\Deck\NoPermissionException
|
* @throws \OCA\Deck\NoPermissionException
|
||||||
* @throws \OCP\AppFramework\Db\DoesNotExistException
|
* @throws \OCP\AppFramework\Db\DoesNotExistException
|
||||||
* @throws \OCP\AppFramework\Db\MultipleObjectsReturnedException
|
* @throws \OCP\AppFramework\Db\MultipleObjectsReturnedException
|
||||||
* @throws BadRequestException
|
* @throws BadRequestException
|
||||||
*/
|
*/
|
||||||
public function find(int $cardId) {
|
public function find($cardId) {
|
||||||
|
if (is_numeric($cardId) === false) {
|
||||||
|
throw new BadRequestException('card id must be a number');
|
||||||
|
}
|
||||||
|
|
||||||
$this->permissionService->checkPermission($this->cardMapper, $cardId, Acl::PERMISSION_READ);
|
$this->permissionService->checkPermission($this->cardMapper, $cardId, Acl::PERMISSION_READ);
|
||||||
$card = $this->cardMapper->find($cardId);
|
$card = $this->cardMapper->find($cardId);
|
||||||
$assignedUsers = $this->assignedUsersMapper->findAll($card->getId());
|
$assignedUsers = $this->assignedUsersMapper->findAll($card->getId());
|
||||||
$attachments = $this->attachmentService->findAll($cardId, true);
|
$attachments = $this->attachmentService->findAll($cardId, true);
|
||||||
if ($this->request->getParam('apiVersion') === '1.0') {
|
if (\OC::$server->getRequest()->getParam('apiVersion') === '1.0') {
|
||||||
$attachments = array_filter($attachments, function ($attachment) {
|
$attachments = array_filter($attachments, function ($attachment) {
|
||||||
return $attachment->getType() === 'deck_file';
|
return $attachment->getType() === 'deck_file';
|
||||||
});
|
});
|
||||||
@@ -170,7 +167,7 @@ class CardService {
|
|||||||
try {
|
try {
|
||||||
$this->permissionService->checkPermission($this->boardMapper, $boardId, Acl::PERMISSION_READ);
|
$this->permissionService->checkPermission($this->boardMapper, $boardId, Acl::PERMISSION_READ);
|
||||||
} catch (NoPermissionException $e) {
|
} catch (NoPermissionException $e) {
|
||||||
$this->logger->error('Unable to check permission for a previously obtained board ' . $boardId, ['exception' => $e]);
|
\OC::$server->getLogger()->error('Unable to check permission for a previously obtained board ' . $boardId, ['exception' => $e]);
|
||||||
return [];
|
return [];
|
||||||
}
|
}
|
||||||
$cards = $this->cardMapper->findCalendarEntries($boardId);
|
$cards = $this->cardMapper->findCalendarEntries($boardId);
|
||||||
@@ -267,7 +264,7 @@ class CardService {
|
|||||||
public function update($id, $title, $stackId, $type, $owner, $description = '', $order = 0, $duedate = null, $deletedAt = null, $archived = null) {
|
public function update($id, $title, $stackId, $type, $owner, $description = '', $order = 0, $duedate = null, $deletedAt = null, $archived = null) {
|
||||||
$this->cardServiceValidator->check(compact('id', 'title', 'stackId', 'type', 'owner', 'order'));
|
$this->cardServiceValidator->check(compact('id', 'title', 'stackId', 'type', 'owner', 'order'));
|
||||||
|
|
||||||
$this->permissionService->checkPermission($this->cardMapper, $id, Acl::PERMISSION_EDIT);
|
$this->permissionService->checkPermission($this->cardMapper, $id, Acl::PERMISSION_EDIT, null, true);
|
||||||
$this->permissionService->checkPermission($this->stackMapper, $stackId, Acl::PERMISSION_EDIT);
|
$this->permissionService->checkPermission($this->stackMapper, $stackId, Acl::PERMISSION_EDIT);
|
||||||
|
|
||||||
if ($this->boardService->isArchived($this->cardMapper, $id)) {
|
if ($this->boardService->isArchived($this->cardMapper, $id)) {
|
||||||
@@ -277,6 +274,14 @@ class CardService {
|
|||||||
if ($archived !== null && $card->getArchived() && $archived === true) {
|
if ($archived !== null && $card->getArchived() && $archived === true) {
|
||||||
throw new StatusException('Operation not allowed. This card is archived.');
|
throw new StatusException('Operation not allowed. This card is archived.');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if ($card->getDeletedAt() !== 0) {
|
||||||
|
if ($deletedAt === null || $deletedAt > 0) {
|
||||||
|
// Only allow operations when restoring the card
|
||||||
|
throw new NoPermissionException('Operation not allowed. This card was deleted.');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
$changes = new ChangeSet($card);
|
$changes = new ChangeSet($card);
|
||||||
if ($card->getLastEditor() !== $this->currentUser && $card->getLastEditor() !== null) {
|
if ($card->getLastEditor() !== $this->currentUser && $card->getLastEditor() !== null) {
|
||||||
$this->activityManager->triggerEvent(
|
$this->activityManager->triggerEvent(
|
||||||
@@ -298,11 +303,11 @@ class CardService {
|
|||||||
$card->setType($type);
|
$card->setType($type);
|
||||||
$card->setOrder($order);
|
$card->setOrder($order);
|
||||||
$card->setOwner($owner);
|
$card->setOwner($owner);
|
||||||
$card->setDuedate($duedate ? new \DateTime($duedate) : null);
|
$card->setDuedate($duedate);
|
||||||
$resetDuedateNotification = false;
|
$resetDuedateNotification = false;
|
||||||
if (
|
if (
|
||||||
$card->getDuedate() === null ||
|
$card->getDuedate() === null ||
|
||||||
($card->getDuedate()) != ($changes->getBefore()->getDuedate())
|
(new \DateTime($card->getDuedate())) != (new \DateTime($changes->getBefore()->getDuedate() ?? ''))
|
||||||
) {
|
) {
|
||||||
$card->setNotified(false);
|
$card->setNotified(false);
|
||||||
$resetDuedateNotification = true;
|
$resetDuedateNotification = true;
|
||||||
@@ -431,7 +436,7 @@ class CardService {
|
|||||||
* @throws StatusException
|
* @throws StatusException
|
||||||
* @throws \OCA\Deck\NoPermissionException
|
* @throws \OCA\Deck\NoPermissionException
|
||||||
* @throws \OCP\AppFramework\Db\DoesNotExistException
|
* @throws \OCP\AppFramework\Db\DoesNotExistException
|
||||||
* @throws \OCP\AppFramework\Db\MultipleObjectsReturnedException
|
* @throws \OCP\AppFramework\Db\
|
||||||
* @throws BadRequestException
|
* @throws BadRequestException
|
||||||
*/
|
*/
|
||||||
public function archive($id) {
|
public function archive($id) {
|
||||||
|
|||||||
@@ -31,7 +31,6 @@ use OCA\Circles\Model\Circle;
|
|||||||
use OCA\Circles\Model\Member;
|
use OCA\Circles\Model\Member;
|
||||||
use OCA\Circles\Model\Probes\CircleProbe;
|
use OCA\Circles\Model\Probes\CircleProbe;
|
||||||
use OCP\App\IAppManager;
|
use OCP\App\IAppManager;
|
||||||
use OCP\Server;
|
|
||||||
use Throwable;
|
use Throwable;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -39,7 +38,7 @@ use Throwable;
|
|||||||
* having the app disabled is properly handled
|
* having the app disabled is properly handled
|
||||||
*/
|
*/
|
||||||
class CirclesService {
|
class CirclesService {
|
||||||
private bool $circlesEnabled;
|
private $circlesEnabled;
|
||||||
|
|
||||||
private $userCircleCache = [];
|
private $userCircleCache = [];
|
||||||
|
|
||||||
@@ -59,7 +58,8 @@ class CirclesService {
|
|||||||
try {
|
try {
|
||||||
|
|
||||||
// Enforce current user condition since we always want the full list of members
|
// Enforce current user condition since we always want the full list of members
|
||||||
$circlesManager = Server::get(CirclesManager::class);
|
/** @var CirclesManager $circlesManager */
|
||||||
|
$circlesManager = \OC::$server->get(CirclesManager::class);
|
||||||
$circlesManager->startSuperSession();
|
$circlesManager->startSuperSession();
|
||||||
return $circlesManager->getCircle($circleId);
|
return $circlesManager->getCircle($circleId);
|
||||||
} catch (Throwable $e) {
|
} catch (Throwable $e) {
|
||||||
@@ -77,7 +77,8 @@ class CirclesService {
|
|||||||
}
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
$circlesManager = Server::get(CirclesManager::class);
|
/** @var CirclesManager $circlesManager */
|
||||||
|
$circlesManager = \OC::$server->get(CirclesManager::class);
|
||||||
$federatedUser = $circlesManager->getFederatedUser($userId, Member::TYPE_USER);
|
$federatedUser = $circlesManager->getFederatedUser($userId, Member::TYPE_USER);
|
||||||
$circlesManager->startSession($federatedUser);
|
$circlesManager->startSession($federatedUser);
|
||||||
$circle = $circlesManager->getCircle($circleId);
|
$circle = $circlesManager->getCircle($circleId);
|
||||||
@@ -105,7 +106,8 @@ class CirclesService {
|
|||||||
}
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
$circlesManager = Server::get(CirclesManager::class);
|
/** @var CirclesManager $circlesManager */
|
||||||
|
$circlesManager = \OC::$server->get(CirclesManager::class);
|
||||||
$federatedUser = $circlesManager->getFederatedUser($userId, Member::TYPE_USER);
|
$federatedUser = $circlesManager->getFederatedUser($userId, Member::TYPE_USER);
|
||||||
$circlesManager->startSession($federatedUser);
|
$circlesManager->startSession($federatedUser);
|
||||||
$probe = new CircleProbe();
|
$probe = new CircleProbe();
|
||||||
|
|||||||
@@ -40,14 +40,20 @@ use OutOfBoundsException;
|
|||||||
use function is_numeric;
|
use function is_numeric;
|
||||||
|
|
||||||
class CommentService {
|
class CommentService {
|
||||||
private ICommentsManager $commentsManager;
|
|
||||||
private IUserManager $userManager;
|
|
||||||
private CardMapper $cardMapper;
|
|
||||||
private PermissionService $permissionService;
|
|
||||||
private ILogger $logger;
|
|
||||||
private ?string $userId;
|
|
||||||
|
|
||||||
public function __construct(ICommentsManager $commentsManager, PermissionService $permissionService, CardMapper $cardMapper, IUserManager $userManager, ILogger $logger, ?string $userId) {
|
/**
|
||||||
|
* @var ICommentsManager
|
||||||
|
*/
|
||||||
|
private $commentsManager;
|
||||||
|
/**
|
||||||
|
* @var IUserManager
|
||||||
|
*/
|
||||||
|
private $userManager;
|
||||||
|
/** @var ILogger */
|
||||||
|
private $logger;
|
||||||
|
private $userId;
|
||||||
|
|
||||||
|
public function __construct(ICommentsManager $commentsManager, PermissionService $permissionService, CardMapper $cardMapper, IUserManager $userManager, ILogger $logger, $userId) {
|
||||||
$this->commentsManager = $commentsManager;
|
$this->commentsManager = $commentsManager;
|
||||||
$this->permissionService = $permissionService;
|
$this->permissionService = $permissionService;
|
||||||
$this->cardMapper = $cardMapper;
|
$this->cardMapper = $cardMapper;
|
||||||
@@ -77,24 +83,17 @@ class CommentService {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @param string $cardId
|
|
||||||
* @param string $message
|
|
||||||
* @param string $replyTo
|
|
||||||
* @return DataResponse
|
|
||||||
* @throws BadRequestException
|
* @throws BadRequestException
|
||||||
* @throws NotFoundException|NoPermissionException
|
* @throws NotFoundException|NoPermissionException
|
||||||
*/
|
*/
|
||||||
public function create(string $cardId, string $message, string $replyTo = '0'): DataResponse {
|
public function create(int $cardId, string $message, string $replyTo = '0'): DataResponse {
|
||||||
if (!is_numeric($cardId)) {
|
|
||||||
throw new BadRequestException('A valid card id must be provided');
|
|
||||||
}
|
|
||||||
$this->permissionService->checkPermission($this->cardMapper, $cardId, Acl::PERMISSION_READ);
|
$this->permissionService->checkPermission($this->cardMapper, $cardId, Acl::PERMISSION_READ);
|
||||||
|
|
||||||
// Check if parent is a comment on the same card
|
// Check if parent is a comment on the same card
|
||||||
if ($replyTo !== '0') {
|
if ($replyTo !== '0') {
|
||||||
try {
|
try {
|
||||||
$comment = $this->commentsManager->get($replyTo);
|
$comment = $this->commentsManager->get($replyTo);
|
||||||
if ($comment->getObjectType() !== Application::COMMENT_ENTITY_TYPE || $comment->getObjectId() !== $cardId) {
|
if ($comment->getObjectType() !== Application::COMMENT_ENTITY_TYPE || (int)$comment->getObjectId() !== $cardId) {
|
||||||
throw new CommentNotFoundException();
|
throw new CommentNotFoundException();
|
||||||
}
|
}
|
||||||
} catch (CommentNotFoundException $e) {
|
} catch (CommentNotFoundException $e) {
|
||||||
@@ -103,7 +102,7 @@ class CommentService {
|
|||||||
}
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
$comment = $this->commentsManager->create('users', $this->userId, Application::COMMENT_ENTITY_TYPE, $cardId);
|
$comment = $this->commentsManager->create('users', $this->userId, Application::COMMENT_ENTITY_TYPE, (string)$cardId);
|
||||||
$comment->setMessage($message);
|
$comment->setMessage($message);
|
||||||
$comment->setVerb('comment');
|
$comment->setVerb('comment');
|
||||||
$comment->setParentId($replyTo);
|
$comment->setParentId($replyTo);
|
||||||
@@ -139,7 +138,7 @@ class CommentService {
|
|||||||
throw new NoPermissionException('Only authors are allowed to edit their comment.');
|
throw new NoPermissionException('Only authors are allowed to edit their comment.');
|
||||||
}
|
}
|
||||||
if ($comment->getParentId() !== '0') {
|
if ($comment->getParentId() !== '0') {
|
||||||
$this->permissionService->checkPermission($this->cardMapper, $comment->getParentId(), Acl::PERMISSION_READ);
|
$this->permissionService->checkPermission($this->cardMapper, (int)$comment->getParentId(), Acl::PERMISSION_READ);
|
||||||
}
|
}
|
||||||
|
|
||||||
$comment->setMessage($message);
|
$comment->setMessage($message);
|
||||||
|
|||||||
@@ -40,9 +40,9 @@ class ConfigService {
|
|||||||
public const SETTING_BOARD_NOTIFICATION_DUE_ALL = 'all';
|
public const SETTING_BOARD_NOTIFICATION_DUE_ALL = 'all';
|
||||||
public const SETTING_BOARD_NOTIFICATION_DUE_DEFAULT = self::SETTING_BOARD_NOTIFICATION_DUE_ASSIGNED;
|
public const SETTING_BOARD_NOTIFICATION_DUE_DEFAULT = self::SETTING_BOARD_NOTIFICATION_DUE_ASSIGNED;
|
||||||
|
|
||||||
private IConfig $config;
|
private $config;
|
||||||
private ?string $userId = null;
|
private $userId;
|
||||||
private IGroupManager $groupManager;
|
private $groupManager;
|
||||||
|
|
||||||
public function __construct(
|
public function __construct(
|
||||||
IConfig $config,
|
IConfig $config,
|
||||||
@@ -52,14 +52,11 @@ class ConfigService {
|
|||||||
$this->config = $config;
|
$this->config = $config;
|
||||||
}
|
}
|
||||||
|
|
||||||
public function getUserId(): ?string {
|
public function getUserId() {
|
||||||
if (!$this->userId) {
|
if (!$this->userId) {
|
||||||
// We cannot use DI for the userId or UserSession as the ConfigService
|
$user = \OC::$server->get(IUserSession::class)->getUser();
|
||||||
// is initiated too early before the session is actually loaded
|
|
||||||
$user = \OCP\Server::get(IUserSession::class)->getUser();
|
|
||||||
$this->userId = $user ? $user->getUID() : null;
|
$this->userId = $user ? $user->getUID() : null;
|
||||||
}
|
}
|
||||||
|
|
||||||
return $this->userId;
|
return $this->userId;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -78,11 +75,8 @@ class ConfigService {
|
|||||||
return $data;
|
return $data;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
public function get($key) {
|
||||||
* @return bool|array{id: string, displayname: string}[]
|
$result = null;
|
||||||
* @throws NoPermissionException
|
|
||||||
*/
|
|
||||||
public function get(string $key) {
|
|
||||||
[$scope] = explode(':', $key, 2);
|
[$scope] = explode(':', $key, 2);
|
||||||
switch ($scope) {
|
switch ($scope) {
|
||||||
case 'groupLimit':
|
case 'groupLimit':
|
||||||
@@ -96,12 +90,11 @@ class ConfigService {
|
|||||||
}
|
}
|
||||||
return (bool)$this->config->getUserValue($this->getUserId(), Application::APP_ID, 'calendar', true);
|
return (bool)$this->config->getUserValue($this->getUserId(), Application::APP_ID, 'calendar', true);
|
||||||
case 'cardDetailsInModal':
|
case 'cardDetailsInModal':
|
||||||
if ($this->getUserId() === null) {
|
if ($this->getUserId() === null) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
return (bool)$this->config->getUserValue($this->getUserId(), Application::APP_ID, 'cardDetailsInModal', true);
|
return (bool)$this->config->getUserValue($this->getUserId(), Application::APP_ID, 'cardDetailsInModal', true);
|
||||||
}
|
}
|
||||||
return false;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public function isCalendarEnabled(int $boardId = null): bool {
|
public function isCalendarEnabled(int $boardId = null): bool {
|
||||||
@@ -164,10 +157,7 @@ class ConfigService {
|
|||||||
return $result;
|
return $result;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
private function setGroupLimit($value) {
|
||||||
* @return string[]
|
|
||||||
*/
|
|
||||||
private function setGroupLimit(array $value): array {
|
|
||||||
$groups = [];
|
$groups = [];
|
||||||
foreach ($value as $group) {
|
foreach ($value as $group) {
|
||||||
$groups[] = $group['id'];
|
$groups[] = $group['id'];
|
||||||
@@ -177,7 +167,7 @@ class ConfigService {
|
|||||||
return $groups;
|
return $groups;
|
||||||
}
|
}
|
||||||
|
|
||||||
private function getGroupLimitList(): array {
|
private function getGroupLimitList() {
|
||||||
$value = $this->config->getAppValue(Application::APP_ID, 'groupLimit', '');
|
$value = $this->config->getAppValue(Application::APP_ID, 'groupLimit', '');
|
||||||
$groups = explode(',', $value);
|
$groups = explode(',', $value);
|
||||||
if ($value === '') {
|
if ($value === '') {
|
||||||
@@ -186,10 +176,9 @@ class ConfigService {
|
|||||||
return $groups;
|
return $groups;
|
||||||
}
|
}
|
||||||
|
|
||||||
/** @return array{id: string, displayname: string}[] */
|
|
||||||
private function getGroupLimit() {
|
private function getGroupLimit() {
|
||||||
$groups = $this->getGroupLimitList();
|
$groups = $this->getGroupLimitList();
|
||||||
$groups = array_map(function (string $groupId): ?array {
|
$groups = array_map(function ($groupId) {
|
||||||
/** @var IGroup $groups */
|
/** @var IGroup $groups */
|
||||||
$group = $this->groupManager->get($groupId);
|
$group = $this->groupManager->get($groupId);
|
||||||
if ($group === null) {
|
if ($group === null) {
|
||||||
@@ -210,4 +199,12 @@ class ConfigService {
|
|||||||
|
|
||||||
return $this->config->getUserValue($userId ?? $this->getUserId(), 'deck', 'attachment_folder', '/Deck');
|
return $this->config->getUserValue($userId ?? $this->getUserId(), 'deck', 'attachment_folder', '/Deck');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public function setAttachmentFolder(?string $userId = null, string $path): void {
|
||||||
|
if ($userId === null && $this->getUserId() === null) {
|
||||||
|
throw new NoPermissionException('Must be logged in get the attachment folder');
|
||||||
|
}
|
||||||
|
|
||||||
|
$this->config->setUserValue($userId ?? $this->getUserId(), 'deck', 'attachment_folder', $path);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -88,6 +88,18 @@ class DefaultBoardService {
|
|||||||
* @throws BadRequestException
|
* @throws BadRequestException
|
||||||
*/
|
*/
|
||||||
public function createDefaultBoard(string $title, string $userId, string $color) {
|
public function createDefaultBoard(string $title, string $userId, string $color) {
|
||||||
|
if ($title === false || $title === null) {
|
||||||
|
throw new BadRequestException('title must be provided');
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($userId === false || $userId === null) {
|
||||||
|
throw new BadRequestException('userId must be provided');
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($color === false || $color === null) {
|
||||||
|
throw new BadRequestException('color must be provided');
|
||||||
|
}
|
||||||
|
|
||||||
$defaultBoard = $this->boardService->create($title, $userId, $color);
|
$defaultBoard = $this->boardService->create($title, $userId, $color);
|
||||||
$defaultStacks = [];
|
$defaultStacks = [];
|
||||||
$defaultCards = [];
|
$defaultCards = [];
|
||||||
|
|||||||
@@ -219,11 +219,8 @@ class FileService implements IAttachmentService {
|
|||||||
throw new \Exception('no instance id!');
|
throw new \Exception('no instance id!');
|
||||||
}
|
}
|
||||||
$name = 'appdata_' . $instanceId;
|
$name = 'appdata_' . $instanceId;
|
||||||
/** @var \OCP\Files\Folder $appDataFolder */
|
|
||||||
$appDataFolder = $this->rootFolder->get($name);
|
$appDataFolder = $this->rootFolder->get($name);
|
||||||
/** @var \OCP\Files\Folder $appDataFolder */
|
|
||||||
$appDataFolder = $appDataFolder->get('deck');
|
$appDataFolder = $appDataFolder->get('deck');
|
||||||
/** @var \OCP\Files\Folder $cardFolder */
|
|
||||||
$cardFolder = $appDataFolder->get($folderName);
|
$cardFolder = $appDataFolder->get($folderName);
|
||||||
return $cardFolder->get($attachment->getData());
|
return $cardFolder->get($attachment->getData());
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -31,6 +31,7 @@ use OCA\Deck\Sharing\DeckShareProvider;
|
|||||||
use OCA\Deck\StatusException;
|
use OCA\Deck\StatusException;
|
||||||
use OCP\AppFramework\Http\StreamResponse;
|
use OCP\AppFramework\Http\StreamResponse;
|
||||||
use OCP\Constants;
|
use OCP\Constants;
|
||||||
|
use OCP\Files\Folder;
|
||||||
use OCP\Files\IMimeTypeDetector;
|
use OCP\Files\IMimeTypeDetector;
|
||||||
use OCP\Files\IRootFolder;
|
use OCP\Files\IRootFolder;
|
||||||
use OCP\Files\NotFoundException;
|
use OCP\Files\NotFoundException;
|
||||||
@@ -44,19 +45,18 @@ use OCP\Share\IShare;
|
|||||||
use Psr\Log\LoggerInterface;
|
use Psr\Log\LoggerInterface;
|
||||||
|
|
||||||
class FilesAppService implements IAttachmentService, ICustomAttachmentService {
|
class FilesAppService implements IAttachmentService, ICustomAttachmentService {
|
||||||
private IRequest $request;
|
private $request;
|
||||||
private IRootFolder $rootFolder;
|
private $rootFolder;
|
||||||
private DeckShareProvider $shareProvider;
|
private $shareProvider;
|
||||||
private IManager $shareManager;
|
private $shareManager;
|
||||||
private ?string $userId;
|
private $userId;
|
||||||
private ConfigService $configService;
|
private $configService;
|
||||||
private IL10N $l10n;
|
private $l10n;
|
||||||
private IPreview $preview;
|
private $preview;
|
||||||
private IMimeTypeDetector $mimeTypeDetector;
|
private $mimeTypeDetector;
|
||||||
private PermissionService $permissionService;
|
private $permissionService;
|
||||||
private CardMapper $cardMapper;
|
private $cardMapper;
|
||||||
private LoggerInterface $logger;
|
private $logger;
|
||||||
private IDBConnection $connection;
|
|
||||||
|
|
||||||
public function __construct(
|
public function __construct(
|
||||||
IRequest $request,
|
IRequest $request,
|
||||||
@@ -70,8 +70,7 @@ class FilesAppService implements IAttachmentService, ICustomAttachmentService {
|
|||||||
PermissionService $permissionService,
|
PermissionService $permissionService,
|
||||||
CardMapper $cardMapper,
|
CardMapper $cardMapper,
|
||||||
LoggerInterface $logger,
|
LoggerInterface $logger,
|
||||||
IDBConnection $connection,
|
string $userId = null
|
||||||
?string $userId
|
|
||||||
) {
|
) {
|
||||||
$this->request = $request;
|
$this->request = $request;
|
||||||
$this->l10n = $l10n;
|
$this->l10n = $l10n;
|
||||||
@@ -85,7 +84,6 @@ class FilesAppService implements IAttachmentService, ICustomAttachmentService {
|
|||||||
$this->permissionService = $permissionService;
|
$this->permissionService = $permissionService;
|
||||||
$this->cardMapper = $cardMapper;
|
$this->cardMapper = $cardMapper;
|
||||||
$this->logger = $logger;
|
$this->logger = $logger;
|
||||||
$this->connection = $connection;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public function listAttachments(int $cardId): array {
|
public function listAttachments(int $cardId): array {
|
||||||
@@ -111,7 +109,9 @@ class FilesAppService implements IAttachmentService, ICustomAttachmentService {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public function getAttachmentCount(int $cardId): int {
|
public function getAttachmentCount(int $cardId): int {
|
||||||
$qb = $this->connection->getQueryBuilder();
|
/** @var IDBConnection $qb */
|
||||||
|
$db = \OC::$server->getDatabaseConnection();
|
||||||
|
$qb = $db->getQueryBuilder();
|
||||||
$qb->select('s.id', 'f.fileid', 'f.path')
|
$qb->select('s.id', 'f.fileid', 'f.path')
|
||||||
->selectAlias('st.id', 'storage_string_id')
|
->selectAlias('st.id', 'storage_string_id')
|
||||||
->from('share', 's')
|
->from('share', 's')
|
||||||
@@ -126,7 +126,7 @@ class FilesAppService implements IAttachmentService, ICustomAttachmentService {
|
|||||||
));
|
));
|
||||||
|
|
||||||
$count = 0;
|
$count = 0;
|
||||||
$cursor = $qb->executeQuery();
|
$cursor = $qb->execute();
|
||||||
while ($data = $cursor->fetch()) {
|
while ($data = $cursor->fetch()) {
|
||||||
if ($this->shareProvider->isAccessibleResult($data)) {
|
if ($this->shareProvider->isAccessibleResult($data)) {
|
||||||
$count++;
|
$count++;
|
||||||
@@ -190,6 +190,16 @@ class FilesAppService implements IAttachmentService, ICustomAttachmentService {
|
|||||||
$folder = $userFolder->newFolder($this->configService->getAttachmentFolder());
|
$folder = $userFolder->newFolder($this->configService->getAttachmentFolder());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if ($folder->isShared()) {
|
||||||
|
$folderName = $userFolder->getNonExistingName($this->configService->getAttachmentFolder());
|
||||||
|
$folder = $userFolder->newFolder($folderName);
|
||||||
|
$this->configService->setAttachmentFolder($this->userId, $folderName);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!$folder instanceof Folder || $folder->isShared()) {
|
||||||
|
throw new NotFoundException('No target folder found');
|
||||||
|
}
|
||||||
|
|
||||||
$fileName = $folder->getNonExistingName($fileName);
|
$fileName = $folder->getNonExistingName($fileName);
|
||||||
$target = $folder->newFile($fileName);
|
$target = $folder->newFile($fileName);
|
||||||
$content = fopen($file['tmp_name'], 'rb');
|
$content = fopen($file['tmp_name'], 'rb');
|
||||||
|
|||||||
@@ -47,22 +47,34 @@ use OCP\Comments\ICommentsManager;
|
|||||||
use OCP\Comments\NotFoundException as CommentNotFoundException;
|
use OCP\Comments\NotFoundException as CommentNotFoundException;
|
||||||
use OCP\EventDispatcher\IEventDispatcher;
|
use OCP\EventDispatcher\IEventDispatcher;
|
||||||
use OCP\IUserManager;
|
use OCP\IUserManager;
|
||||||
use OCP\Server;
|
|
||||||
|
|
||||||
class BoardImportService {
|
class BoardImportService {
|
||||||
private IUserManager $userManager;
|
/** @var IUserManager */
|
||||||
private BoardMapper $boardMapper;
|
private $userManager;
|
||||||
private AclMapper $aclMapper;
|
/** @var BoardMapper */
|
||||||
private LabelMapper $labelMapper;
|
private $boardMapper;
|
||||||
private StackMapper $stackMapper;
|
/** @var AclMapper */
|
||||||
private CardMapper $cardMapper;
|
private $aclMapper;
|
||||||
private AssignmentMapper $assignmentMapper;
|
/** @var LabelMapper */
|
||||||
private AttachmentMapper $attachmentMapper;
|
private $labelMapper;
|
||||||
private ICommentsManager $commentsManager;
|
/** @var StackMapper */
|
||||||
private IEventDispatcher $eventDispatcher;
|
private $stackMapper;
|
||||||
private string $system = '';
|
/** @var CardMapper */
|
||||||
private ?ABoardImportService $systemInstance;
|
private $cardMapper;
|
||||||
private array $allowedSystems = [];
|
/** @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
|
* Data object created from config JSON
|
||||||
*
|
*
|
||||||
@@ -77,7 +89,10 @@ class BoardImportService {
|
|||||||
* @psalm-suppress PropertyNotSetInConstructor
|
* @psalm-suppress PropertyNotSetInConstructor
|
||||||
*/
|
*/
|
||||||
private $data;
|
private $data;
|
||||||
private Board $board;
|
/**
|
||||||
|
* @var Board
|
||||||
|
*/
|
||||||
|
private $board;
|
||||||
|
|
||||||
public function __construct(
|
public function __construct(
|
||||||
IUserManager $userManager,
|
IUserManager $userManager,
|
||||||
@@ -183,7 +198,7 @@ class BoardImportService {
|
|||||||
}
|
}
|
||||||
if (!is_object($this->systemInstance)) {
|
if (!is_object($this->systemInstance)) {
|
||||||
$systemClass = 'OCA\\Deck\\Service\\Importer\\Systems\\' . ucfirst($this->getSystem()) . 'Service';
|
$systemClass = 'OCA\\Deck\\Service\\Importer\\Systems\\' . ucfirst($this->getSystem()) . 'Service';
|
||||||
$this->systemInstance = Server::get($systemClass);
|
$this->systemInstance = \OC::$server->get($systemClass);
|
||||||
$this->systemInstance->setImportService($this);
|
$this->systemInstance->setImportService($this);
|
||||||
}
|
}
|
||||||
return $this->systemInstance;
|
return $this->systemInstance;
|
||||||
@@ -328,7 +343,7 @@ class BoardImportService {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public function insertAttachment(Attachment $attachment, string $content): Attachment {
|
public function insertAttachment(Attachment $attachment, string $content): Attachment {
|
||||||
$service = Server::get(FileService::class);
|
$service = \OC::$server->get(FileService::class);
|
||||||
$folder = $service->getFolder($attachment);
|
$folder = $service->getFolder($attachment);
|
||||||
|
|
||||||
if ($folder->fileExists($attachment->getData())) {
|
if ($folder->fileExists($attachment->getData())) {
|
||||||
|
|||||||
@@ -91,10 +91,12 @@ class LabelService {
|
|||||||
$this->permissionService->checkPermission(null, $boardId, Acl::PERMISSION_MANAGE);
|
$this->permissionService->checkPermission(null, $boardId, Acl::PERMISSION_MANAGE);
|
||||||
|
|
||||||
$boardLabels = $this->labelMapper->findAll($boardId);
|
$boardLabels = $this->labelMapper->findAll($boardId);
|
||||||
foreach ($boardLabels as $boardLabel) {
|
if (\is_array($boardLabels)) {
|
||||||
if ($boardLabel->getTitle() === $title) {
|
foreach ($boardLabels as $boardLabel) {
|
||||||
throw new BadRequestException('title must be unique');
|
if ($boardLabel->getTitle() === $title) {
|
||||||
break;
|
throw new BadRequestException('title must be unique');
|
||||||
|
break;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -149,13 +151,15 @@ class LabelService {
|
|||||||
$label = $this->find($id);
|
$label = $this->find($id);
|
||||||
|
|
||||||
$boardLabels = $this->labelMapper->findAll($label->getBoardId());
|
$boardLabels = $this->labelMapper->findAll($label->getBoardId());
|
||||||
foreach ($boardLabels as $boardLabel) {
|
if (\is_array($boardLabels)) {
|
||||||
if ($boardLabel->getId() === $label->getId()) {
|
foreach ($boardLabels as $boardLabel) {
|
||||||
continue;
|
if ($boardLabel->getId() === $label->getId()) {
|
||||||
}
|
continue;
|
||||||
if ($boardLabel->getTitle() === $title) {
|
}
|
||||||
throw new BadRequestException('title must be unique');
|
if ($boardLabel->getTitle() === $title) {
|
||||||
break;
|
throw new BadRequestException('title must be unique');
|
||||||
|
break;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -30,20 +30,31 @@ namespace OCA\Deck\Service;
|
|||||||
use OCA\Deck\Db\AssignmentMapper;
|
use OCA\Deck\Db\AssignmentMapper;
|
||||||
use OCA\Deck\Db\Card;
|
use OCA\Deck\Db\Card;
|
||||||
use OCA\Deck\Db\CardMapper;
|
use OCA\Deck\Db\CardMapper;
|
||||||
use OCA\Deck\Model\CardDetails;
|
|
||||||
use OCP\Comments\ICommentsManager;
|
use OCP\Comments\ICommentsManager;
|
||||||
|
use OCP\IGroupManager;
|
||||||
|
use OCA\Deck\Db\Board;
|
||||||
use OCA\Deck\Db\BoardMapper;
|
use OCA\Deck\Db\BoardMapper;
|
||||||
use OCA\Deck\Db\LabelMapper;
|
use OCA\Deck\Db\LabelMapper;
|
||||||
use OCP\IUserManager;
|
use OCP\IUserManager;
|
||||||
|
|
||||||
class OverviewService {
|
class OverviewService {
|
||||||
private BoardMapper $boardMapper;
|
|
||||||
private LabelMapper $labelMapper;
|
/** @var BoardMapper */
|
||||||
private CardMapper $cardMapper;
|
private $boardMapper;
|
||||||
private AssignmentMapper $assignedUsersMapper;
|
/** @var LabelMapper */
|
||||||
private IUserManager $userManager;
|
private $labelMapper;
|
||||||
private ICommentsManager $commentsManager;
|
/** @var CardMapper */
|
||||||
private AttachmentService $attachmentService;
|
private $cardMapper;
|
||||||
|
/** @var AssignmentMapper */
|
||||||
|
private $assignedUsersMapper;
|
||||||
|
/** @var IUserManager */
|
||||||
|
private $userManager;
|
||||||
|
/** @var IGroupManager */
|
||||||
|
private $groupManager;
|
||||||
|
/** @var ICommentsManager */
|
||||||
|
private $commentsManager;
|
||||||
|
/** @var AttachmentService */
|
||||||
|
private $attachmentService;
|
||||||
|
|
||||||
public function __construct(
|
public function __construct(
|
||||||
BoardMapper $boardMapper,
|
BoardMapper $boardMapper,
|
||||||
@@ -51,6 +62,7 @@ class OverviewService {
|
|||||||
CardMapper $cardMapper,
|
CardMapper $cardMapper,
|
||||||
AssignmentMapper $assignedUsersMapper,
|
AssignmentMapper $assignedUsersMapper,
|
||||||
IUserManager $userManager,
|
IUserManager $userManager,
|
||||||
|
IGroupManager $groupManager,
|
||||||
ICommentsManager $commentsManager,
|
ICommentsManager $commentsManager,
|
||||||
AttachmentService $attachmentService
|
AttachmentService $attachmentService
|
||||||
) {
|
) {
|
||||||
@@ -59,6 +71,7 @@ class OverviewService {
|
|||||||
$this->cardMapper = $cardMapper;
|
$this->cardMapper = $cardMapper;
|
||||||
$this->assignedUsersMapper = $assignedUsersMapper;
|
$this->assignedUsersMapper = $assignedUsersMapper;
|
||||||
$this->userManager = $userManager;
|
$this->userManager = $userManager;
|
||||||
|
$this->groupManager = $groupManager;
|
||||||
$this->commentsManager = $commentsManager;
|
$this->commentsManager = $commentsManager;
|
||||||
$this->attachmentService = $attachmentService;
|
$this->attachmentService = $attachmentService;
|
||||||
}
|
}
|
||||||
@@ -80,37 +93,62 @@ class OverviewService {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public function findAllWithDue(string $userId): array {
|
public function findAllWithDue(string $userId): array {
|
||||||
$userBoards = $this->boardMapper->findAllForUser($userId);
|
$userBoards = $this->findAllBoardsFromUser($userId);
|
||||||
$allDueCards = [];
|
$allDueCards = [];
|
||||||
foreach ($userBoards as $userBoard) {
|
foreach ($userBoards as $userBoard) {
|
||||||
$allDueCards[] = array_map(function ($card) use ($userBoard, $userId) {
|
$service = $this;
|
||||||
$this->enrich($card, $userId);
|
$allDueCards[] = array_map(static function ($card) use ($service, $userBoard, $userId) {
|
||||||
return (new CardDetails($card, $userBoard))->jsonSerialize();
|
$service->enrich($card, $userId);
|
||||||
|
$cardData = $card->jsonSerialize();
|
||||||
|
$cardData['boardId'] = $userBoard->getId();
|
||||||
|
return $cardData;
|
||||||
}, $this->cardMapper->findAllWithDue($userBoard->getId()));
|
}, $this->cardMapper->findAllWithDue($userBoard->getId()));
|
||||||
}
|
}
|
||||||
return array_merge(...$allDueCards);
|
return $allDueCards;
|
||||||
}
|
}
|
||||||
|
|
||||||
public function findUpcomingCards(string $userId): array {
|
public function findUpcomingCards(string $userId): array {
|
||||||
$userBoards = $this->boardMapper->findAllForUser($userId);
|
$userBoards = $this->findAllBoardsFromUser($userId);
|
||||||
$foundCards = [];
|
$findCards = [];
|
||||||
foreach ($userBoards as $userBoard) {
|
foreach ($userBoards as $userBoard) {
|
||||||
|
$service = $this;
|
||||||
|
|
||||||
if (count($userBoard->getAcl()) === 0) {
|
if (count($userBoard->getAcl()) === 0) {
|
||||||
// private board: get cards with due date
|
// private board: get cards with due date
|
||||||
$cards = $this->cardMapper->findAllWithDue($userBoard->getId());
|
$findCards[] = array_map(static function ($card) use ($service, $userBoard, $userId) {
|
||||||
|
$service->enrich($card, $userId);
|
||||||
|
$cardData = $card->jsonSerialize();
|
||||||
|
$cardData['boardId'] = $userBoard->getId();
|
||||||
|
return $cardData;
|
||||||
|
}, $this->cardMapper->findAllWithDue($userBoard->getId()));
|
||||||
} else {
|
} else {
|
||||||
// shared board: get all my assigned or unassigned cards
|
// shared board: get all my assigned or unassigned cards
|
||||||
$cards = $this->cardMapper->findToMeOrNotAssignedCards($userBoard->getId(), $userId);
|
$findCards[] = array_map(static function ($card) use ($service, $userBoard, $userId) {
|
||||||
|
$service->enrich($card, $userId);
|
||||||
|
$cardData = $card->jsonSerialize();
|
||||||
|
$cardData['boardId'] = $userBoard->getId();
|
||||||
|
return $cardData;
|
||||||
|
}, $this->cardMapper->findToMeOrNotAssignedCards($userBoard->getId(), $userId));
|
||||||
}
|
}
|
||||||
|
|
||||||
$foundCards[] = array_map(
|
|
||||||
function (Card $card) use ($userBoard, $userId) {
|
|
||||||
$this->enrich($card, $userId);
|
|
||||||
return (new CardDetails($card, $userBoard))->jsonSerialize();
|
|
||||||
},
|
|
||||||
$cards
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
return array_merge(...$foundCards);
|
return $findCards;
|
||||||
|
}
|
||||||
|
|
||||||
|
// FIXME: This is duplicate code with the board service
|
||||||
|
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);
|
||||||
|
$circleBoards = $this->boardMapper->findAllByCircles($userInfo['user'], null, null);
|
||||||
|
return array_unique(array_merge($userBoards, $groupBoards, $circleBoards));
|
||||||
|
}
|
||||||
|
|
||||||
|
private function getBoardPrerequisites($userId): array {
|
||||||
|
$user = $this->userManager->get($userId);
|
||||||
|
$groups = $user !== null ? $this->groupManager->getUserGroupIds($user) : [];
|
||||||
|
return [
|
||||||
|
'user' => $userId,
|
||||||
|
'groups' => $groups
|
||||||
|
];
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -23,12 +23,13 @@
|
|||||||
|
|
||||||
namespace OCA\Deck\Service;
|
namespace OCA\Deck\Service;
|
||||||
|
|
||||||
use OCP\Cache\CappedMemoryCache;
|
use OC\Cache\CappedMemoryCache;
|
||||||
use OCA\Circles\Model\Member;
|
use OCA\Circles\Model\Member;
|
||||||
use OCA\Deck\Db\Acl;
|
use OCA\Deck\Db\Acl;
|
||||||
use OCA\Deck\Db\AclMapper;
|
use OCA\Deck\Db\AclMapper;
|
||||||
use OCA\Deck\Db\Board;
|
use OCA\Deck\Db\Board;
|
||||||
use OCA\Deck\Db\BoardMapper;
|
use OCA\Deck\Db\BoardMapper;
|
||||||
|
use OCA\Deck\Db\CardMapper;
|
||||||
use OCA\Deck\Db\IPermissionMapper;
|
use OCA\Deck\Db\IPermissionMapper;
|
||||||
use OCA\Deck\Db\User;
|
use OCA\Deck\Db\User;
|
||||||
use OCA\Deck\NoPermissionException;
|
use OCA\Deck\NoPermissionException;
|
||||||
@@ -63,8 +64,8 @@ class PermissionService {
|
|||||||
/** @var array */
|
/** @var array */
|
||||||
private $users = [];
|
private $users = [];
|
||||||
|
|
||||||
private CappedMemoryCache $boardCache;
|
private $boardCache;
|
||||||
private CappedMemoryCache $permissionCache;
|
private $permissionCache;
|
||||||
|
|
||||||
public function __construct(
|
public function __construct(
|
||||||
ILogger $logger,
|
ILogger $logger,
|
||||||
@@ -97,13 +98,18 @@ class PermissionService {
|
|||||||
* @param $boardId
|
* @param $boardId
|
||||||
* @return bool|array
|
* @return bool|array
|
||||||
*/
|
*/
|
||||||
public function getPermissions($boardId) {
|
public function getPermissions($boardId, ?string $userId = null) {
|
||||||
|
if ($userId === null) {
|
||||||
|
$userId = $this->userId;
|
||||||
|
}
|
||||||
|
|
||||||
if ($cached = $this->permissionCache->get($boardId)) {
|
if ($cached = $this->permissionCache->get($boardId)) {
|
||||||
return $cached;
|
return $cached;
|
||||||
}
|
}
|
||||||
|
|
||||||
$owner = $this->userIsBoardOwner($boardId);
|
$board = $this->getBoard($boardId);
|
||||||
$acls = $this->aclMapper->findAll($boardId);
|
$owner = $this->userIsBoardOwner($boardId, $userId);
|
||||||
|
$acls = $board->getDeletedAt() === 0 ? $this->aclMapper->findAll($boardId) : [];
|
||||||
$permissions = [
|
$permissions = [
|
||||||
Acl::PERMISSION_READ => $owner || $this->userCan($acls, Acl::PERMISSION_READ),
|
Acl::PERMISSION_READ => $owner || $this->userCan($acls, Acl::PERMISSION_READ),
|
||||||
Acl::PERMISSION_EDIT => $owner || $this->userCan($acls, Acl::PERMISSION_EDIT),
|
Acl::PERMISSION_EDIT => $owner || $this->userCan($acls, Acl::PERMISSION_EDIT),
|
||||||
@@ -111,7 +117,7 @@ class PermissionService {
|
|||||||
Acl::PERMISSION_SHARE => ($owner || $this->userCan($acls, Acl::PERMISSION_SHARE))
|
Acl::PERMISSION_SHARE => ($owner || $this->userCan($acls, Acl::PERMISSION_SHARE))
|
||||||
&& (!$this->shareManager->sharingDisabledForUser($this->userId))
|
&& (!$this->shareManager->sharingDisabledForUser($this->userId))
|
||||||
];
|
];
|
||||||
$this->permissionCache->set($boardId, $permissions);
|
$this->permissionCache->set((string)$boardId, $permissions);
|
||||||
return $permissions;
|
return $permissions;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -137,13 +143,10 @@ class PermissionService {
|
|||||||
/**
|
/**
|
||||||
* check permissions for replacing dark magic middleware
|
* check permissions for replacing dark magic middleware
|
||||||
*
|
*
|
||||||
* @param $mapper IPermissionMapper|null null if $id is a boardId
|
* @param numeric $id
|
||||||
* @param $id int unique identifier of the Entity
|
|
||||||
* @param $permission int
|
|
||||||
* @return bool
|
|
||||||
* @throws NoPermissionException
|
* @throws NoPermissionException
|
||||||
*/
|
*/
|
||||||
public function checkPermission($mapper, $id, $permission, $userId = null) {
|
public function checkPermission($mapper, $id, $permission, $userId = null, bool $allowDeletedCard = false) {
|
||||||
$boardId = $id;
|
$boardId = $id;
|
||||||
if ($mapper instanceof IPermissionMapper && !($mapper instanceof BoardMapper)) {
|
if ($mapper instanceof IPermissionMapper && !($mapper instanceof BoardMapper)) {
|
||||||
$boardId = $mapper->findBoardId($id);
|
$boardId = $mapper->findBoardId($id);
|
||||||
@@ -157,12 +160,20 @@ class PermissionService {
|
|||||||
throw new NoPermissionException('Permission denied');
|
throw new NoPermissionException('Permission denied');
|
||||||
}
|
}
|
||||||
|
|
||||||
if ($this->userIsBoardOwner($boardId, $userId)) {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
try {
|
try {
|
||||||
$acls = $this->getBoard($boardId)->getAcl() ?? [];
|
$permissions = $this->getPermissions($boardId, $userId);
|
||||||
|
if ($permissions[$permission] === true) {
|
||||||
|
if (!$allowDeletedCard && $mapper instanceof CardMapper) {
|
||||||
|
$card = $mapper->find($id);
|
||||||
|
if ($card->getDeletedAt() > 0) {
|
||||||
|
throw new NoPermissionException('Card is deleted');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
$acls = $this->getBoard((int)$boardId)->getAcl() ?? [];
|
||||||
$result = $this->userCan($acls, $permission, $userId);
|
$result = $this->userCan($acls, $permission, $userId);
|
||||||
if ($result) {
|
if ($result) {
|
||||||
return true;
|
return true;
|
||||||
@@ -194,11 +205,11 @@ class PermissionService {
|
|||||||
* @throws MultipleObjectsReturnedException
|
* @throws MultipleObjectsReturnedException
|
||||||
* @throws DoesNotExistException
|
* @throws DoesNotExistException
|
||||||
*/
|
*/
|
||||||
private function getBoard($boardId): Board {
|
private function getBoard(int $boardId): Board {
|
||||||
if (!isset($this->boardCache[$boardId])) {
|
if (!isset($this->boardCache[(string)$boardId])) {
|
||||||
$this->boardCache[$boardId] = $this->boardMapper->find($boardId, false, true);
|
$this->boardCache[(string)$boardId] = $this->boardMapper->find($boardId, false, true);
|
||||||
}
|
}
|
||||||
return $this->boardCache[$boardId];
|
return $this->boardCache[(string)$boardId];
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@@ -30,32 +30,28 @@ use OCA\Deck\BadRequestException;
|
|||||||
use OCA\Deck\Db\Acl;
|
use OCA\Deck\Db\Acl;
|
||||||
use OCA\Deck\Db\AssignmentMapper;
|
use OCA\Deck\Db\AssignmentMapper;
|
||||||
use OCA\Deck\Db\BoardMapper;
|
use OCA\Deck\Db\BoardMapper;
|
||||||
use OCA\Deck\Db\Card;
|
|
||||||
use OCA\Deck\Db\CardMapper;
|
use OCA\Deck\Db\CardMapper;
|
||||||
use OCA\Deck\Db\ChangeHelper;
|
use OCA\Deck\Db\ChangeHelper;
|
||||||
use OCA\Deck\Db\LabelMapper;
|
use OCA\Deck\Db\LabelMapper;
|
||||||
use OCA\Deck\Db\Stack;
|
use OCA\Deck\Db\Stack;
|
||||||
use OCA\Deck\Db\StackMapper;
|
use OCA\Deck\Db\StackMapper;
|
||||||
use OCA\Deck\Model\CardDetails;
|
|
||||||
use OCA\Deck\NoPermissionException;
|
use OCA\Deck\NoPermissionException;
|
||||||
use OCA\Deck\StatusException;
|
use OCA\Deck\StatusException;
|
||||||
use OCA\Deck\Validators\StackServiceValidator;
|
use OCA\Deck\Validators\StackServiceValidator;
|
||||||
use Psr\Log\LoggerInterface;
|
|
||||||
|
|
||||||
class StackService {
|
class StackService {
|
||||||
private StackMapper $stackMapper;
|
private $stackMapper;
|
||||||
private CardMapper $cardMapper;
|
private $cardMapper;
|
||||||
private BoardMapper $boardMapper;
|
private $boardMapper;
|
||||||
private LabelMapper $labelMapper;
|
private $labelMapper;
|
||||||
private PermissionService $permissionService;
|
private $permissionService;
|
||||||
private BoardService $boardService;
|
private $boardService;
|
||||||
private CardService $cardService;
|
private $cardService;
|
||||||
private AssignmentMapper $assignedUsersMapper;
|
private $assignedUsersMapper;
|
||||||
private AttachmentService $attachmentService;
|
private $attachmentService;
|
||||||
private ActivityManager $activityManager;
|
private $activityManager;
|
||||||
private ChangeHelper $changeHelper;
|
private $changeHelper;
|
||||||
private LoggerInterface $logger;
|
private $stackServiceValidator;
|
||||||
private StackServiceValidator $stackServiceValidator;
|
|
||||||
|
|
||||||
public function __construct(
|
public function __construct(
|
||||||
StackMapper $stackMapper,
|
StackMapper $stackMapper,
|
||||||
@@ -68,9 +64,8 @@ class StackService {
|
|||||||
AssignmentMapper $assignedUsersMapper,
|
AssignmentMapper $assignedUsersMapper,
|
||||||
AttachmentService $attachmentService,
|
AttachmentService $attachmentService,
|
||||||
ActivityManager $activityManager,
|
ActivityManager $activityManager,
|
||||||
ChangeHelper $changeHelper,
|
StackServiceValidator $stackServiceValidator,
|
||||||
LoggerInterface $logger,
|
ChangeHelper $changeHelper
|
||||||
StackServiceValidator $stackServiceValidator
|
|
||||||
) {
|
) {
|
||||||
$this->stackMapper = $stackMapper;
|
$this->stackMapper = $stackMapper;
|
||||||
$this->boardMapper = $boardMapper;
|
$this->boardMapper = $boardMapper;
|
||||||
@@ -83,7 +78,6 @@ class StackService {
|
|||||||
$this->attachmentService = $attachmentService;
|
$this->attachmentService = $attachmentService;
|
||||||
$this->activityManager = $activityManager;
|
$this->activityManager = $activityManager;
|
||||||
$this->changeHelper = $changeHelper;
|
$this->changeHelper = $changeHelper;
|
||||||
$this->logger = $logger;
|
|
||||||
$this->stackServiceValidator = $stackServiceValidator;
|
$this->stackServiceValidator = $stackServiceValidator;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -94,13 +88,9 @@ class StackService {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
$cards = array_map(
|
foreach ($cards as $card) {
|
||||||
function (Card $card): CardDetails {
|
$this->cardService->enrich($card);
|
||||||
$this->cardService->enrich($card);
|
}
|
||||||
return new CardDetails($card);
|
|
||||||
},
|
|
||||||
$cards
|
|
||||||
);
|
|
||||||
|
|
||||||
$stack->setCards($cards);
|
$stack->setCards($cards);
|
||||||
}
|
}
|
||||||
@@ -126,18 +116,12 @@ class StackService {
|
|||||||
|
|
||||||
$this->permissionService->checkPermission($this->stackMapper, $stackId, Acl::PERMISSION_READ);
|
$this->permissionService->checkPermission($this->stackMapper, $stackId, Acl::PERMISSION_READ);
|
||||||
$stack = $this->stackMapper->find($stackId);
|
$stack = $this->stackMapper->find($stackId);
|
||||||
|
$cards = $this->cardMapper->findAll($stackId);
|
||||||
$cards = array_map(
|
foreach ($cards as $cardIndex => $card) {
|
||||||
function (Card $card): CardDetails {
|
$assignedUsers = $this->assignedUsersMapper->findAll($card->getId());
|
||||||
$assignedUsers = $this->assignedUsersMapper->findAll($card->getId());
|
$card->setAssignedUsers($assignedUsers);
|
||||||
$card->setAssignedUsers($assignedUsers);
|
$card->setAttachmentCount($this->attachmentService->count($card->getId()));
|
||||||
$card->setAttachmentCount($this->attachmentService->count($card->getId()));
|
}
|
||||||
|
|
||||||
return new CardDetails($card);
|
|
||||||
},
|
|
||||||
$this->cardMapper->findAll($stackId)
|
|
||||||
);
|
|
||||||
|
|
||||||
$stack->setCards($cards);
|
$stack->setCards($cards);
|
||||||
|
|
||||||
return $stack;
|
return $stack;
|
||||||
@@ -166,7 +150,7 @@ class StackService {
|
|||||||
try {
|
try {
|
||||||
$this->permissionService->checkPermission(null, $boardId, Acl::PERMISSION_READ);
|
$this->permissionService->checkPermission(null, $boardId, Acl::PERMISSION_READ);
|
||||||
} catch (NoPermissionException $e) {
|
} catch (NoPermissionException $e) {
|
||||||
$this->logger->error('Unable to check permission for a previously obtained board ' . $boardId, ['exception' => $e]);
|
\OC::$server->getLogger()->error('Unable to check permission for a previously obtained board ' . $boardId, ['exception' => $e]);
|
||||||
return [];
|
return [];
|
||||||
}
|
}
|
||||||
return $this->stackMapper->findAll($boardId);
|
return $this->stackMapper->findAll($boardId);
|
||||||
@@ -326,7 +310,6 @@ class StackService {
|
|||||||
$this->permissionService->checkPermission($this->stackMapper, $id, Acl::PERMISSION_MANAGE);
|
$this->permissionService->checkPermission($this->stackMapper, $id, Acl::PERMISSION_MANAGE);
|
||||||
$stackToSort = $this->stackMapper->find($id);
|
$stackToSort = $this->stackMapper->find($id);
|
||||||
$stacks = $this->stackMapper->findAll($stackToSort->getBoardId());
|
$stacks = $this->stackMapper->findAll($stackToSort->getBoardId());
|
||||||
usort($stacks, static fn (Stack $stackA, Stack $stackB) => $stackA->getOrder() - $stackB->getOrder());
|
|
||||||
$result = [];
|
$result = [];
|
||||||
$i = 0;
|
$i = 0;
|
||||||
foreach ($stacks as $stack) {
|
foreach ($stacks as $stack) {
|
||||||
|
|||||||
@@ -65,16 +65,22 @@ class DeckShareProvider implements \OCP\Share\IShareProvider {
|
|||||||
|
|
||||||
public const SHARE_TYPE_DECK_USER = IShare::TYPE_DECK_USER;
|
public const SHARE_TYPE_DECK_USER = IShare::TYPE_DECK_USER;
|
||||||
|
|
||||||
private IDBConnection $dbConnection;
|
/** @var IDBConnection */
|
||||||
private IManager $shareManager;
|
private $dbConnection;
|
||||||
private AttachmentCacheHelper $attachmentCacheHelper;
|
/** @var IManager */
|
||||||
private BoardMapper $boardMapper;
|
private $shareManager;
|
||||||
private CardMapper $cardMapper;
|
/** @var AttachmentCacheHelper */
|
||||||
private PermissionService $permissionService;
|
private $attachmentCacheHelper;
|
||||||
private ITimeFactory $timeFactory;
|
/** @var BoardMapper */
|
||||||
private IL10N $l;
|
private $boardMapper;
|
||||||
private IMimeTypeLoader $mimeTypeLoader;
|
/** @var CardMapper */
|
||||||
private ?string $userId;
|
private $cardMapper;
|
||||||
|
/** @var PermissionService */
|
||||||
|
private $permissionService;
|
||||||
|
/** @var ITimeFactory */
|
||||||
|
private $timeFactory;
|
||||||
|
/** @var IL10N */
|
||||||
|
private $l;
|
||||||
|
|
||||||
public function __construct(
|
public function __construct(
|
||||||
IDBConnection $connection,
|
IDBConnection $connection,
|
||||||
@@ -83,10 +89,7 @@ class DeckShareProvider implements \OCP\Share\IShareProvider {
|
|||||||
CardMapper $cardMapper,
|
CardMapper $cardMapper,
|
||||||
PermissionService $permissionService,
|
PermissionService $permissionService,
|
||||||
AttachmentCacheHelper $attachmentCacheHelper,
|
AttachmentCacheHelper $attachmentCacheHelper,
|
||||||
IL10N $l,
|
IL10N $l
|
||||||
ITimeFactory $timeFactory,
|
|
||||||
IMimeTypeLoader $mimeTypeLoader,
|
|
||||||
?string $userId
|
|
||||||
) {
|
) {
|
||||||
$this->dbConnection = $connection;
|
$this->dbConnection = $connection;
|
||||||
$this->shareManager = $shareManager;
|
$this->shareManager = $shareManager;
|
||||||
@@ -94,10 +97,9 @@ class DeckShareProvider implements \OCP\Share\IShareProvider {
|
|||||||
$this->cardMapper = $cardMapper;
|
$this->cardMapper = $cardMapper;
|
||||||
$this->attachmentCacheHelper = $attachmentCacheHelper;
|
$this->attachmentCacheHelper = $attachmentCacheHelper;
|
||||||
$this->permissionService = $permissionService;
|
$this->permissionService = $permissionService;
|
||||||
|
|
||||||
$this->l = $l;
|
$this->l = $l;
|
||||||
$this->timeFactory = $timeFactory;
|
$this->timeFactory = \OC::$server->get(ITimeFactory::class);
|
||||||
$this->mimeTypeLoader = $mimeTypeLoader;
|
|
||||||
$this->userId = $userId;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public static function register(IEventDispatcher $dispatcher): void {
|
public static function register(IEventDispatcher $dispatcher): void {
|
||||||
@@ -205,13 +207,13 @@ class DeckShareProvider implements \OCP\Share\IShareProvider {
|
|||||||
->setValue('file_target', $qb->createNamedParameter($target))
|
->setValue('file_target', $qb->createNamedParameter($target))
|
||||||
->setValue('permissions', $qb->createNamedParameter($permissions))
|
->setValue('permissions', $qb->createNamedParameter($permissions))
|
||||||
->setValue('token', $qb->createNamedParameter($token))
|
->setValue('token', $qb->createNamedParameter($token))
|
||||||
->setValue('stime', $qb->createNamedParameter($this->timeFactory->getTime()));
|
->setValue('stime', $qb->createNamedParameter(\OC::$server->get(ITimeFactory::class)->getTime()));
|
||||||
|
|
||||||
if ($expirationDate !== null) {
|
if ($expirationDate !== null) {
|
||||||
$qb->setValue('expiration', $qb->createNamedParameter($expirationDate, 'datetime'));
|
$qb->setValue('expiration', $qb->createNamedParameter($expirationDate, 'datetime'));
|
||||||
}
|
}
|
||||||
|
|
||||||
$qb->executeStatement();
|
$qb->execute();
|
||||||
|
|
||||||
return $qb->getLastInsertId();
|
return $qb->getLastInsertId();
|
||||||
}
|
}
|
||||||
@@ -279,7 +281,7 @@ class DeckShareProvider implements \OCP\Share\IShareProvider {
|
|||||||
$entryData = $data;
|
$entryData = $data;
|
||||||
$entryData['permissions'] = $entryData['f_permissions'];
|
$entryData['permissions'] = $entryData['f_permissions'];
|
||||||
$entryData['parent'] = $entryData['f_parent'];
|
$entryData['parent'] = $entryData['f_parent'];
|
||||||
$share->setNodeCacheEntry(Cache::cacheEntryFromData($entryData, $this->mimeTypeLoader));
|
$share->setNodeCacheEntry(Cache::cacheEntryFromData($entryData, \OC::$server->get(IMimeTypeLoader::class)));
|
||||||
}
|
}
|
||||||
return $share;
|
return $share;
|
||||||
}
|
}
|
||||||
@@ -472,14 +474,14 @@ class DeckShareProvider implements \OCP\Share\IShareProvider {
|
|||||||
'file_target' => $qb->createNamedParameter($share->getTarget()),
|
'file_target' => $qb->createNamedParameter($share->getTarget()),
|
||||||
'permissions' => $qb->createNamedParameter($share->getPermissions()),
|
'permissions' => $qb->createNamedParameter($share->getPermissions()),
|
||||||
'stime' => $qb->createNamedParameter($share->getShareTime()->getTimestamp()),
|
'stime' => $qb->createNamedParameter($share->getShareTime()->getTimestamp()),
|
||||||
])->executeStatement();
|
])->execute();
|
||||||
} else {
|
} else {
|
||||||
// Already a userroom share. Update it.
|
// Already a userroom share. Update it.
|
||||||
$qb = $this->dbConnection->getQueryBuilder();
|
$qb = $this->dbConnection->getQueryBuilder();
|
||||||
$qb->update('share')
|
$qb->update('share')
|
||||||
->set('file_target', $qb->createNamedParameter($share->getTarget()))
|
->set('file_target', $qb->createNamedParameter($share->getTarget()))
|
||||||
->where($qb->expr()->eq('id', $qb->createNamedParameter($data['id'])))
|
->where($qb->expr()->eq('id', $qb->createNamedParameter($data['id'])))
|
||||||
->executeStatement();
|
->execute();
|
||||||
}
|
}
|
||||||
|
|
||||||
return $share;
|
return $share;
|
||||||
@@ -489,7 +491,7 @@ class DeckShareProvider implements \OCP\Share\IShareProvider {
|
|||||||
* @inheritDoc
|
* @inheritDoc
|
||||||
* @returns
|
* @returns
|
||||||
*/
|
*/
|
||||||
public function getSharesInFolder($userId, Folder $node, $reshares, $shallow = true) {
|
public function getSharesInFolder($userId, Folder $node, $reshares) {
|
||||||
$qb = $this->dbConnection->getQueryBuilder();
|
$qb = $this->dbConnection->getQueryBuilder();
|
||||||
$qb->select('*')
|
$qb->select('*')
|
||||||
->from('share', 's')
|
->from('share', 's')
|
||||||
@@ -516,11 +518,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'));
|
||||||
if ($shallow) {
|
$qb->andWhere($qb->expr()->eq('f.parent', $qb->createNamedParameter($node->getId())));
|
||||||
$qb->andWhere($qb->expr()->eq('f.parent', $qb->createNamedParameter($node->getId())));
|
|
||||||
} else {
|
|
||||||
$qb->andWhere($qb->expr()->like('f.path', $qb->createNamedParameter($this->dbConnection->escapeLikeParameter($node->getInternalPath()) . '/%')));
|
|
||||||
}
|
|
||||||
|
|
||||||
$qb->orderBy('s.id');
|
$qb->orderBy('s.id');
|
||||||
|
|
||||||
@@ -634,8 +632,8 @@ class DeckShareProvider implements \OCP\Share\IShareProvider {
|
|||||||
$start = 0;
|
$start = 0;
|
||||||
while (true) {
|
while (true) {
|
||||||
/** @var IShare[] $shareSlice */
|
/** @var IShare[] $shareSlice */
|
||||||
$shareSlice = array_slice($shares, $start, 1000);
|
$shareSlice = array_slice($shares, $start, 100);
|
||||||
$start += 1000;
|
$start += 100;
|
||||||
|
|
||||||
if ($shareSlice === []) {
|
if ($shareSlice === []) {
|
||||||
break;
|
break;
|
||||||
@@ -714,15 +712,15 @@ class DeckShareProvider implements \OCP\Share\IShareProvider {
|
|||||||
* @return IShare[]
|
* @return IShare[]
|
||||||
*/
|
*/
|
||||||
public function getSharedWith($userId, $shareType, $node, $limit, $offset): array {
|
public function getSharedWith($userId, $shareType, $node, $limit, $offset): array {
|
||||||
$allBoards = $this->boardMapper->findBoardIds($userId);
|
$allBoards = $this->boardMapper->findAllForUser($userId);
|
||||||
|
|
||||||
/** @var IShare[] $shares */
|
/** @var IShare[] $shares */
|
||||||
$shares = [];
|
$shares = [];
|
||||||
|
|
||||||
$start = 0;
|
$start = 0;
|
||||||
while (true) {
|
while (true) {
|
||||||
$boards = array_slice($allBoards, $start, 1000);
|
$boards = array_slice($allBoards, $start, 100);
|
||||||
$start += 1000;
|
$start += 100;
|
||||||
|
|
||||||
if ($boards === []) {
|
if ($boards === []) {
|
||||||
break;
|
break;
|
||||||
@@ -752,6 +750,10 @@ class DeckShareProvider implements \OCP\Share\IShareProvider {
|
|||||||
$qb->andWhere($qb->expr()->eq('s.file_source', $qb->createNamedParameter($node->getId())));
|
$qb->andWhere($qb->expr()->eq('s.file_source', $qb->createNamedParameter($node->getId())));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
$boards = array_map(function (Board $board) {
|
||||||
|
return $board->getId();
|
||||||
|
}, $boards);
|
||||||
|
|
||||||
$qb->andWhere($qb->expr()->eq('s.share_type', $qb->createNamedParameter(IShare::TYPE_DECK)))
|
$qb->andWhere($qb->expr()->eq('s.share_type', $qb->createNamedParameter(IShare::TYPE_DECK)))
|
||||||
->andWhere($qb->expr()->in('db.id', $qb->createNamedParameter(
|
->andWhere($qb->expr()->in('db.id', $qb->createNamedParameter(
|
||||||
$boards,
|
$boards,
|
||||||
@@ -819,7 +821,7 @@ class DeckShareProvider implements \OCP\Share\IShareProvider {
|
|||||||
$qb->expr()->eq('s.item_type', $qb->createNamedParameter('folder'))
|
$qb->expr()->eq('s.item_type', $qb->createNamedParameter('folder'))
|
||||||
));
|
));
|
||||||
|
|
||||||
$cursor = $qb->executeQuery();
|
$cursor = $qb->execute();
|
||||||
while ($data = $cursor->fetch()) {
|
while ($data = $cursor->fetch()) {
|
||||||
if (!$this->isAccessibleResult($data)) {
|
if (!$this->isAccessibleResult($data)) {
|
||||||
continue;
|
continue;
|
||||||
@@ -834,7 +836,9 @@ class DeckShareProvider implements \OCP\Share\IShareProvider {
|
|||||||
}
|
}
|
||||||
$cursor->closeCursor();
|
$cursor->closeCursor();
|
||||||
|
|
||||||
return $this->resolveSharesForRecipient($shares, $this->userId);
|
$shares = $this->resolveSharesForRecipient($shares, \OC::$server->getUserSession()->getUser()->getUID());
|
||||||
|
|
||||||
|
return $shares;
|
||||||
}
|
}
|
||||||
|
|
||||||
public function isAccessibleResult(array $data): bool {
|
public function isAccessibleResult(array $data): bool {
|
||||||
@@ -937,7 +941,7 @@ class DeckShareProvider implements \OCP\Share\IShareProvider {
|
|||||||
$qb->expr()->eq('item_type', $qb->createNamedParameter('file')),
|
$qb->expr()->eq('item_type', $qb->createNamedParameter('file')),
|
||||||
$qb->expr()->eq('item_type', $qb->createNamedParameter('folder'))
|
$qb->expr()->eq('item_type', $qb->createNamedParameter('folder'))
|
||||||
));
|
));
|
||||||
$cursor = $qb->executeQuery();
|
$cursor = $qb->execute();
|
||||||
|
|
||||||
$users = [];
|
$users = [];
|
||||||
while ($row = $cursor->fetch()) {
|
while ($row = $cursor->fetch()) {
|
||||||
|
|||||||
@@ -29,13 +29,14 @@ namespace OCA\Deck\Sharing;
|
|||||||
use OC\Files\Filesystem;
|
use OC\Files\Filesystem;
|
||||||
use OCA\Deck\Service\ConfigService;
|
use OCA\Deck\Service\ConfigService;
|
||||||
use OCP\EventDispatcher\IEventDispatcher;
|
use OCP\EventDispatcher\IEventDispatcher;
|
||||||
use OCP\Server;
|
|
||||||
use OCP\Share\Events\VerifyMountPointEvent;
|
use OCP\Share\Events\VerifyMountPointEvent;
|
||||||
use OCP\Share\IShare;
|
use OCP\Share\IShare;
|
||||||
use Symfony\Component\EventDispatcher\GenericEvent;
|
use Symfony\Component\EventDispatcher\GenericEvent;
|
||||||
|
|
||||||
class Listener {
|
class Listener {
|
||||||
private ConfigService $configService;
|
|
||||||
|
/** @var ConfigService */
|
||||||
|
private $configService;
|
||||||
|
|
||||||
public function __construct(ConfigService $configService) {
|
public function __construct(ConfigService $configService) {
|
||||||
$this->configService = $configService;
|
$this->configService = $configService;
|
||||||
@@ -51,13 +52,13 @@ class Listener {
|
|||||||
|
|
||||||
public static function listenPreShare(GenericEvent $event): void {
|
public static function listenPreShare(GenericEvent $event): void {
|
||||||
/** @var self $listener */
|
/** @var self $listener */
|
||||||
$listener = Server::get(self::class);
|
$listener = \OC::$server->query(self::class);
|
||||||
$listener->overwriteShareTarget($event);
|
$listener->overwriteShareTarget($event);
|
||||||
}
|
}
|
||||||
|
|
||||||
public static function listenVerifyMountPointEvent(VerifyMountPointEvent $event): void {
|
public static function listenVerifyMountPointEvent(VerifyMountPointEvent $event): void {
|
||||||
/** @var self $listener */
|
/** @var self $listener */
|
||||||
$listener = Server::get(self::class);
|
$listener = \OC::$server->query(self::class);
|
||||||
$listener->overwriteMountPoint($event);
|
$listener->overwriteMountPoint($event);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -115,7 +115,7 @@ class ShareAPIHelper {
|
|||||||
*/
|
*/
|
||||||
public function canAccessShare(IShare $share, string $user): bool {
|
public function canAccessShare(IShare $share, string $user): bool {
|
||||||
try {
|
try {
|
||||||
$this->permissionService->checkPermission($this->cardMapper, $share->getSharedWith(), Acl::PERMISSION_READ, $user);
|
$this->permissionService->checkPermission($this->cardMapper, (int)$share->getSharedWith(), Acl::PERMISSION_READ, $user);
|
||||||
} catch (NoPermissionException $e) {
|
} catch (NoPermissionException $e) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|||||||
18428
package-lock.json
generated
203
package.json
@@ -1,107 +1,100 @@
|
|||||||
{
|
{
|
||||||
"name": "deck",
|
"name": "deck",
|
||||||
"description": "",
|
"description": "",
|
||||||
"version": "1.8.5",
|
"version": "1.7.5",
|
||||||
"authors": [
|
"authors": [
|
||||||
{
|
{
|
||||||
"name": "Julius Härtl",
|
"name": "Julius Härtl",
|
||||||
"email": "jus@bitgrid.net",
|
"email": "jus@bitgrid.net",
|
||||||
"role": "Developer"
|
"role": "Developer"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "Michael Weimann",
|
"name": "Michael Weimann",
|
||||||
"email": "mail@michael-weimann.eu",
|
"email": "mail@michael-weimann.eu",
|
||||||
"role": "Developer"
|
"role": "Developer"
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"license": "agpl",
|
"license": "agpl",
|
||||||
"private": true,
|
"private": true,
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"build": "NODE_ENV=production webpack --progress --config webpack.js",
|
"build": "NODE_ENV=production webpack --progress --config webpack.js",
|
||||||
"dev": "NODE_ENV=development webpack --progress --config webpack.js",
|
"dev": "NODE_ENV=development webpack --progress --config webpack.js",
|
||||||
"watch": "NODE_ENV=development webpack --progress --watch --config webpack.js",
|
"watch": "NODE_ENV=development webpack --progress --watch --config webpack.js",
|
||||||
"serve": "webpack serve --node-env development --allowed-hosts all --config webpack.js",
|
"lint": "eslint --ext .js,.vue src",
|
||||||
"lint": "eslint --ext .js,.vue src",
|
"lint:fix": "eslint --ext .js,.vue src --fix",
|
||||||
"lint:fix": "eslint --ext .js,.vue src --fix",
|
"stylelint": "stylelint src",
|
||||||
"stylelint": "stylelint src",
|
"stylelint:fix": "stylelint src --fix",
|
||||||
"stylelint:fix": "stylelint src --fix",
|
"test": "jest",
|
||||||
"test": "jest",
|
"test:coverage": "jest --coverage"
|
||||||
"test:coverage": "jest --coverage"
|
},
|
||||||
},
|
"dependencies": {
|
||||||
"dependencies": {
|
"@babel/polyfill": "^7.12.1",
|
||||||
"@babel/polyfill": "^7.12.1",
|
"@babel/runtime": "^7.17.9",
|
||||||
"@babel/runtime": "^7.19.0",
|
"@juliushaertl/vue-richtext": "^1.0.1",
|
||||||
"@juliushaertl/vue-richtext": "^1.0.1",
|
"@nextcloud/auth": "^1.3.0",
|
||||||
"@nextcloud/auth": "^2.0.0",
|
"@nextcloud/axios": "^1.9.0",
|
||||||
"@nextcloud/axios": "^2.0.0",
|
"@nextcloud/dialogs": "^3.1.2",
|
||||||
"@nextcloud/dialogs": "^3.2.0",
|
"@nextcloud/event-bus": "^2.1.1",
|
||||||
"@nextcloud/event-bus": "^3.0.2",
|
"@nextcloud/files": "^2.1.0",
|
||||||
"@nextcloud/files": "^2.1.0",
|
"@nextcloud/initial-state": "^1.2.1",
|
||||||
"@nextcloud/initial-state": "^2.0.0",
|
"@nextcloud/l10n": "^1.4.1",
|
||||||
"@nextcloud/l10n": "^1.6.0",
|
"@nextcloud/moment": "^1.2.0",
|
||||||
"@nextcloud/moment": "^1.2.1",
|
"@nextcloud/router": "^2.0.0",
|
||||||
"@nextcloud/router": "^2.0.0",
|
"@nextcloud/vue": "^5.3.1",
|
||||||
"@nextcloud/vue": "^7.3.0",
|
"@nextcloud/vue-dashboard": "^2.0.1",
|
||||||
"@nextcloud/vue-dashboard": "^2.0.1",
|
"blueimp-md5": "^2.19.0",
|
||||||
"@nextcloud/vue-richtext": "^2.0.1",
|
"dompurify": "^2.3.6",
|
||||||
"blueimp-md5": "^2.19.0",
|
"lodash": "^4.17.21",
|
||||||
"dompurify": "^2.4.0",
|
"markdown-it": "^12.3.2",
|
||||||
"lodash": "^4.17.21",
|
"markdown-it-link-attributes": "^4.0.0",
|
||||||
"markdown-it": "^13.0.1",
|
"markdown-it-task-checkbox": "^1.0.6",
|
||||||
"markdown-it-link-attributes": "^4.0.1",
|
"moment": "^2.29.2",
|
||||||
"markdown-it-task-checkbox": "^1.0.6",
|
"nextcloud-vue-collections": "^0.9.0",
|
||||||
"moment": "^2.29.4",
|
"p-queue": "^6.6.2",
|
||||||
"nextcloud-vue-collections": "^0.10.0",
|
"url-search-params-polyfill": "^8.1.1",
|
||||||
"p-queue": "^7.3.0",
|
"vue": "^2.6.14",
|
||||||
"url-search-params-polyfill": "^8.1.1",
|
"vue-at": "^2.5.0-beta.2",
|
||||||
"vue": "^2.7.9",
|
"vue-click-outside": "^1.1.0",
|
||||||
"vue-at": "^2.5.0",
|
"vue-easymde": "^2.0.0",
|
||||||
"vue-click-outside": "^1.1.0",
|
"vue-infinite-loading": "^2.4.5",
|
||||||
"vue-easymde": "^2.0.0",
|
"vue-router": "^3.5.3",
|
||||||
"vue-infinite-loading": "^2.4.5",
|
"vue-smooth-dnd": "^0.8.1",
|
||||||
"vue-material-design-icons": "^5.1.2",
|
"vuex": "^3.6.2",
|
||||||
"vue-router": "^3.6.5",
|
"vuex-router-sync": "^5.0.0"
|
||||||
"vue-smooth-dnd": "^0.8.1",
|
},
|
||||||
"vuex": "^3.6.2",
|
"browserslist": [
|
||||||
"vuex-router-sync": "^5.0.0"
|
"extends @nextcloud/browserslist-config"
|
||||||
},
|
],
|
||||||
"browserslist": [
|
"engines": {
|
||||||
"extends @nextcloud/browserslist-config"
|
"node": "^14.0.0",
|
||||||
],
|
"npm": "^7.0.0"
|
||||||
"engines": {
|
},
|
||||||
"node": "^16.0.0",
|
"devDependencies": {
|
||||||
"npm": "^7.0.0 || ^8.0.0"
|
"@nextcloud/babel-config": "^1.0.0",
|
||||||
},
|
"@nextcloud/browserslist-config": "^2.2.0",
|
||||||
"devDependencies": {
|
"@nextcloud/eslint-config": "^6.1.2",
|
||||||
"@nextcloud/babel-config": "^1.0.0",
|
"@nextcloud/stylelint-config": "^2.1.2",
|
||||||
"@nextcloud/browserslist-config": "^2.3.0",
|
"@nextcloud/webpack-vue-config": "^5.0.0",
|
||||||
"@nextcloud/eslint-config": "^8.0.0",
|
"@relative-ci/agent": "^3.1.2",
|
||||||
"@nextcloud/stylelint-config": "^2.2.0",
|
"@vue/test-utils": "^1.3.0",
|
||||||
"@nextcloud/webpack-vue-config": "^5.3.0",
|
"jest": "^27.5.1",
|
||||||
"@relative-ci/agent": "^4.1.0",
|
"jest-serializer-vue": "^2.0.2",
|
||||||
"@vue/test-utils": "^1.3.0",
|
"vue-jest": "^3.0.7"
|
||||||
"cypress": "^10.8.0",
|
},
|
||||||
"eslint-webpack-plugin": "^3.2.0",
|
"jest": {
|
||||||
"jest": "^29.0.1",
|
"moduleFileExtensions": [
|
||||||
"jest-serializer-vue": "^2.0.2",
|
"js",
|
||||||
"stylelint-webpack-plugin": "^3.3.0",
|
"vue"
|
||||||
"vue-jest": "^3.0.7",
|
],
|
||||||
"vue-template-compiler": "^2.7.9"
|
"moduleNameMapper": {
|
||||||
},
|
"^@/(.*)$": "<rootDir>/src/$1"
|
||||||
"jest": {
|
},
|
||||||
"moduleFileExtensions": [
|
"transform": {
|
||||||
"js",
|
"^.+\\.js$": "<rootDir>/node_modules/babel-jest",
|
||||||
"vue"
|
".*\\.(vue)$": "<rootDir>/node_modules/vue-jest"
|
||||||
],
|
},
|
||||||
"moduleNameMapper": {
|
"snapshotSerializers": [
|
||||||
"^@/(.*)$": "<rootDir>/src/$1"
|
"<rootDir>/node_modules/jest-serializer-vue"
|
||||||
},
|
]
|
||||||
"transform": {
|
}
|
||||||
"^.+\\.js$": "<rootDir>/node_modules/babel-jest",
|
|
||||||
".*\\.(vue)$": "<rootDir>/node_modules/vue-jest"
|
|
||||||
},
|
|
||||||
"snapshotSerializers": [
|
|
||||||
"<rootDir>/node_modules/jest-serializer-vue"
|
|
||||||
]
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
@@ -7,9 +7,6 @@
|
|||||||
xsi:schemaLocation="https://getpsalm.org/schema/config vendor/vimeo/psalm/config.xsd"
|
xsi:schemaLocation="https://getpsalm.org/schema/config vendor/vimeo/psalm/config.xsd"
|
||||||
errorBaseline="tests/psalm-baseline.xml"
|
errorBaseline="tests/psalm-baseline.xml"
|
||||||
>
|
>
|
||||||
<stubs>
|
|
||||||
<file name="tests/stub.phpstub" preloadClasses="true"/>
|
|
||||||
</stubs>
|
|
||||||
<projectFiles>
|
<projectFiles>
|
||||||
<directory name="lib" />
|
<directory name="lib" />
|
||||||
<ignoreFiles>
|
<ignoreFiles>
|
||||||
|
|||||||
59
src/App.vue
@@ -21,13 +21,14 @@
|
|||||||
-->
|
-->
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<NcContent app-name="deck" :class="{ 'nav-hidden': !navShown, 'sidebar-hidden': !sidebarRouterView }">
|
<Content id="content" app-name="deck" :class="{ 'nav-hidden': !navShown, 'sidebar-hidden': !sidebarRouterView }">
|
||||||
<AppNavigation />
|
<AppNavigation />
|
||||||
<NcAppContent>
|
<AppContent>
|
||||||
<router-view />
|
<router-view />
|
||||||
</NcAppContent>
|
</AppContent>
|
||||||
|
|
||||||
<NcModal v-if="cardDetailsInModal && $route.params.cardId"
|
<Modal
|
||||||
|
v-if="cardDetailsInModal && $route.params.cardId"
|
||||||
:clear-view-delay="0"
|
:clear-view-delay="0"
|
||||||
:title="t('deck', 'Card details')"
|
:title="t('deck', 'Card details')"
|
||||||
size="large"
|
size="large"
|
||||||
@@ -35,17 +36,18 @@
|
|||||||
<div class="modal__content modal__card">
|
<div class="modal__content modal__card">
|
||||||
<router-view name="sidebar" />
|
<router-view name="sidebar" />
|
||||||
</div>
|
</div>
|
||||||
</NcModal>
|
</Modal>
|
||||||
|
|
||||||
<router-view name="sidebar" :visible="!cardDetailsInModal || !$route.params.cardId" />
|
<router-view name="sidebar" :visible="!cardDetailsInModal || !$route.params.cardId" />
|
||||||
</NcContent>
|
</Content>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
|
|
||||||
import { mapState } from 'vuex'
|
import { mapState } from 'vuex'
|
||||||
import AppNavigation from './components/navigation/AppNavigation.vue'
|
import AppNavigation from './components/navigation/AppNavigation'
|
||||||
import { NcModal, NcContent, NcAppContent } from '@nextcloud/vue'
|
import { Modal, Content, AppContent } from '@nextcloud/vue'
|
||||||
import { BoardApi } from './services/BoardApi.js'
|
import { BoardApi } from './services/BoardApi'
|
||||||
import { emit, subscribe } from '@nextcloud/event-bus'
|
import { emit, subscribe } from '@nextcloud/event-bus'
|
||||||
|
|
||||||
const boardApi = new BoardApi()
|
const boardApi = new BoardApi()
|
||||||
@@ -54,9 +56,9 @@ export default {
|
|||||||
name: 'App',
|
name: 'App',
|
||||||
components: {
|
components: {
|
||||||
AppNavigation,
|
AppNavigation,
|
||||||
NcModal,
|
Modal,
|
||||||
NcContent,
|
Content,
|
||||||
NcAppContent,
|
AppContent,
|
||||||
},
|
},
|
||||||
provide() {
|
provide() {
|
||||||
return {
|
return {
|
||||||
@@ -128,7 +130,7 @@ export default {
|
|||||||
|
|
||||||
<style lang="scss" scoped>
|
<style lang="scss" scoped>
|
||||||
|
|
||||||
#content-vue {
|
#content {
|
||||||
#app-content {
|
#app-content {
|
||||||
transition: margin-left 100ms ease;
|
transition: margin-left 100ms ease;
|
||||||
position: relative;
|
position: relative;
|
||||||
@@ -156,37 +158,6 @@ export default {
|
|||||||
</style>
|
</style>
|
||||||
|
|
||||||
<style lang="scss">
|
<style lang="scss">
|
||||||
@import '../css/print';
|
|
||||||
|
|
||||||
.icon-activity {
|
|
||||||
background-image: url(../img/activity-dark.svg);
|
|
||||||
|
|
||||||
body[data-theme-dark] & {
|
|
||||||
background-image: url(../img/activity.svg);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.avatardiv.circles {
|
|
||||||
background: var(--color-primary);
|
|
||||||
}
|
|
||||||
|
|
||||||
.icon-circles {
|
|
||||||
background-image: url(../img/circles-dark.svg);
|
|
||||||
opacity: 1;
|
|
||||||
background-size: 20px;
|
|
||||||
background-position: center center;
|
|
||||||
}
|
|
||||||
|
|
||||||
.icon-circles-white, .icon-circles.icon-white {
|
|
||||||
background-image: url(../img/circles.svg);
|
|
||||||
opacity: 1;
|
|
||||||
background-size: 20px;
|
|
||||||
background-position: center center;
|
|
||||||
}
|
|
||||||
|
|
||||||
.icon-colorpicker {
|
|
||||||
background-image: url('../img/color_picker.svg');
|
|
||||||
}
|
|
||||||
|
|
||||||
.multiselect {
|
.multiselect {
|
||||||
width: 100%;
|
width: 100%;
|
||||||
|
|||||||
@@ -21,7 +21,7 @@
|
|||||||
-->
|
-->
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<NcModal @close="close">
|
<Modal @close="close">
|
||||||
<div id="modal-inner" :class="{ 'icon-loading': loading }">
|
<div id="modal-inner" :class="{ 'icon-loading': loading }">
|
||||||
<h1>{{ t('deck', 'Select the board to link to a project') }}</h1>
|
<h1>{{ t('deck', 'Select the board to link to a project') }}</h1>
|
||||||
<input v-model="filter" type="text" :placeholder="t('deck', 'Search by board title')">
|
<input v-model="filter" type="text" :placeholder="t('deck', 'Search by board title')">
|
||||||
@@ -38,17 +38,17 @@
|
|||||||
{{ t('deck', 'Select board') }}
|
{{ t('deck', 'Select board') }}
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
</NcModal>
|
</Modal>
|
||||||
</template>
|
</template>
|
||||||
<script>
|
<script>
|
||||||
import { NcModal } from '@nextcloud/vue'
|
import Modal from '@nextcloud/vue/dist/Components/Modal'
|
||||||
import axios from '@nextcloud/axios'
|
import axios from '@nextcloud/axios'
|
||||||
import { generateUrl } from '@nextcloud/router'
|
import { generateUrl } from '@nextcloud/router'
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
name: 'BoardSelector',
|
name: 'BoardSelector',
|
||||||
components: {
|
components: {
|
||||||
NcModal,
|
Modal,
|
||||||
},
|
},
|
||||||
data() {
|
data() {
|
||||||
return {
|
return {
|
||||||
|
|||||||
@@ -21,11 +21,11 @@
|
|||||||
-->
|
-->
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<NcModal class="card-selector" @close="close">
|
<Modal class="card-selector" @close="close">
|
||||||
<div class="modal-scroller">
|
<div class="modal-scroller">
|
||||||
<div v-if="!creating && !created" id="modal-inner" :class="{ 'icon-loading': loading }">
|
<div v-if="!creating && !created" id="modal-inner" :class="{ 'icon-loading': loading }">
|
||||||
<h3>{{ t('deck', 'Create a new card') }}</h3>
|
<h3>{{ t('deck', 'Create a new card') }}</h3>
|
||||||
<NcMultiselect v-model="selectedBoard"
|
<Multiselect v-model="selectedBoard"
|
||||||
:placeholder="t('deck', 'Select a board')"
|
:placeholder="t('deck', 'Select a board')"
|
||||||
:options="boards"
|
:options="boards"
|
||||||
:disabled="loading"
|
:disabled="loading"
|
||||||
@@ -44,9 +44,9 @@
|
|||||||
<span>{{ props.option.title }}</span>
|
<span>{{ props.option.title }}</span>
|
||||||
</span>
|
</span>
|
||||||
</template>
|
</template>
|
||||||
</NcMultiselect>
|
</Multiselect>
|
||||||
|
|
||||||
<NcMultiselect v-model="selectedStack"
|
<Multiselect v-model="selectedStack"
|
||||||
:placeholder="t('deck', 'Select a list')"
|
:placeholder="t('deck', 'Select a list')"
|
||||||
:options="stacksFromBoard"
|
:options="stacksFromBoard"
|
||||||
:max-height="100"
|
:max-height="100"
|
||||||
@@ -71,22 +71,12 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div v-else id="modal-inner">
|
<div v-else id="modal-inner">
|
||||||
<NcEmptyContent v-if="creating">
|
<EmptyContent v-if="creating" icon="icon-loading">
|
||||||
<template #icon>
|
{{ t('deck', 'Creating the new card …') }}
|
||||||
<NcLoadingIcon />
|
</EmptyContent>
|
||||||
</template>
|
<EmptyContent v-else-if="created" icon="icon-checkmark">
|
||||||
<template #title>
|
{{ t('deck', 'Card "{card}" was added to "{board}"', { card: pendingTitle, board: selectedBoard.title }) }}
|
||||||
{{ t('deck', 'Creating the new card …') }}
|
<template #desc>
|
||||||
</template>
|
|
||||||
</NcEmptyContent>
|
|
||||||
<NcEmptyContent v-else-if="created">
|
|
||||||
<template #icon>
|
|
||||||
<CardPlusOutline />
|
|
||||||
</template>
|
|
||||||
<template #title>
|
|
||||||
{{ t('deck', 'Card "{card}" was added to "{board}"', { card: pendingTitle, board: selectedBoard.title }) }}
|
|
||||||
</template>
|
|
||||||
<template #action>
|
|
||||||
<button class="primary" @click="openNewCard">
|
<button class="primary" @click="openNewCard">
|
||||||
{{ t('deck', 'Open card') }}
|
{{ t('deck', 'Open card') }}
|
||||||
</button>
|
</button>
|
||||||
@@ -94,29 +84,28 @@
|
|||||||
{{ t('deck', 'Close') }}
|
{{ t('deck', 'Close') }}
|
||||||
</button>
|
</button>
|
||||||
</template>
|
</template>
|
||||||
</NcEmptyContent>
|
</EmptyContent>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</NcModal>
|
</Modal>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
import { generateUrl } from '@nextcloud/router'
|
import { generateUrl } from '@nextcloud/router'
|
||||||
import { NcModal, NcMultiselect, NcEmptyContent, NcLoadingIcon } from '@nextcloud/vue'
|
import Modal from '@nextcloud/vue/dist/Components/Modal'
|
||||||
import CardPlusOutline from 'vue-material-design-icons/CardPlusOutline.vue'
|
import Multiselect from '@nextcloud/vue/dist/Components/Multiselect'
|
||||||
|
import EmptyContent from '@nextcloud/vue/dist/Components/EmptyContent'
|
||||||
import axios from '@nextcloud/axios'
|
import axios from '@nextcloud/axios'
|
||||||
import { CardApi } from './services/CardApi.js'
|
import { CardApi } from './services/CardApi'
|
||||||
|
|
||||||
const cardApi = new CardApi()
|
const cardApi = new CardApi()
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
name: 'CardCreateDialog',
|
name: 'CardCreateDialog',
|
||||||
components: {
|
components: {
|
||||||
NcEmptyContent,
|
EmptyContent,
|
||||||
NcModal,
|
Modal,
|
||||||
NcMultiselect,
|
Multiselect,
|
||||||
NcLoadingIcon,
|
|
||||||
CardPlusOutline,
|
|
||||||
},
|
},
|
||||||
props: {
|
props: {
|
||||||
title: {
|
title: {
|
||||||
@@ -216,7 +205,6 @@ export default {
|
|||||||
max-width: 400px;
|
max-width: 400px;
|
||||||
padding: 10px;
|
padding: 10px;
|
||||||
min-height: 200px;
|
min-height: 200px;
|
||||||
margin: auto;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.multiselect-board, .multiselect-list, input, textarea {
|
.multiselect-board, .multiselect-list, input, textarea {
|
||||||
|
|||||||
@@ -21,10 +21,10 @@
|
|||||||
-->
|
-->
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<NcModal class="card-selector" @close="close">
|
<Modal class="card-selector" @close="close">
|
||||||
<div id="modal-inner" :class="{ 'icon-loading': loading }">
|
<div id="modal-inner" :class="{ 'icon-loading': loading }">
|
||||||
<h3>{{ title }}</h3>
|
<h3>{{ title }}</h3>
|
||||||
<NcMultiselect v-model="selectedBoard"
|
<Multiselect v-model="selectedBoard"
|
||||||
:placeholder="t('deck', 'Select a board')"
|
:placeholder="t('deck', 'Select a board')"
|
||||||
:options="boards"
|
:options="boards"
|
||||||
:disabled="loading"
|
:disabled="loading"
|
||||||
@@ -42,9 +42,9 @@
|
|||||||
<span>{{ props.option.title }}</span>
|
<span>{{ props.option.title }}</span>
|
||||||
</span>
|
</span>
|
||||||
</template>
|
</template>
|
||||||
</NcMultiselect>
|
</Multiselect>
|
||||||
|
|
||||||
<NcMultiselect v-model="selectedCard"
|
<Multiselect v-model="selectedCard"
|
||||||
:placeholder="t('deck', 'Select a card')"
|
:placeholder="t('deck', 'Select a card')"
|
||||||
:options="cardsFromBoard"
|
:options="cardsFromBoard"
|
||||||
:disabled="loading || selectedBoard === ''"
|
:disabled="loading || selectedBoard === ''"
|
||||||
@@ -57,19 +57,20 @@
|
|||||||
{{ t('deck', 'Cancel') }}
|
{{ t('deck', 'Cancel') }}
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
</NcModal>
|
</Modal>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
import { generateUrl } from '@nextcloud/router'
|
import { generateUrl } from '@nextcloud/router'
|
||||||
import { NcModal, NcMultiselect } from '@nextcloud/vue'
|
import Modal from '@nextcloud/vue/dist/Components/Modal'
|
||||||
|
import Multiselect from '@nextcloud/vue/dist/Components/Multiselect'
|
||||||
import axios from '@nextcloud/axios'
|
import axios from '@nextcloud/axios'
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
name: 'CardSelector',
|
name: 'CardSelector',
|
||||||
components: {
|
components: {
|
||||||
NcModal,
|
Modal,
|
||||||
NcMultiselect,
|
Multiselect,
|
||||||
},
|
},
|
||||||
props: {
|
props: {
|
||||||
title: {
|
title: {
|
||||||
|
|||||||
@@ -36,10 +36,10 @@
|
|||||||
|
|
||||||
<script>
|
<script>
|
||||||
import RichText from '@juliushaertl/vue-richtext'
|
import RichText from '@juliushaertl/vue-richtext'
|
||||||
import { NcUserBubble } from '@nextcloud/vue'
|
import { UserBubble } from '@nextcloud/vue'
|
||||||
import moment from '@nextcloud/moment'
|
import moment from '@nextcloud/moment'
|
||||||
import DOMPurify from 'dompurify'
|
import DOMPurify from 'dompurify'
|
||||||
import relativeDate from '../mixins/relativeDate.js'
|
import relativeDate from '../mixins/relativeDate'
|
||||||
|
|
||||||
const InternalLink = {
|
const InternalLink = {
|
||||||
name: 'InternalLink',
|
name: 'InternalLink',
|
||||||
@@ -93,7 +93,7 @@ export default {
|
|||||||
break
|
break
|
||||||
case 'user':
|
case 'user':
|
||||||
parameters[key] = {
|
parameters[key] = {
|
||||||
component: NcUserBubble,
|
component: UserBubble,
|
||||||
props: {
|
props: {
|
||||||
user: parameters[key].id,
|
user: parameters[key].id,
|
||||||
displayName: parameters[key].name,
|
displayName: parameters[key].name,
|
||||||
|
|||||||
@@ -37,7 +37,7 @@
|
|||||||
<script>
|
<script>
|
||||||
import axios from '@nextcloud/axios'
|
import axios from '@nextcloud/axios'
|
||||||
import { generateOcsUrl } from '@nextcloud/router'
|
import { generateOcsUrl } from '@nextcloud/router'
|
||||||
import ActivityEntry from './ActivityEntry.vue'
|
import ActivityEntry from './ActivityEntry'
|
||||||
import InfiniteLoading from 'vue-infinite-loading'
|
import InfiniteLoading from 'vue-infinite-loading'
|
||||||
|
|
||||||
const ACTIVITY_FETCH_LIMIT = 50
|
const ACTIVITY_FETCH_LIMIT = 50
|
||||||
@@ -84,7 +84,20 @@ export default {
|
|||||||
params.append('object_id', '' + this.objectId)
|
params.append('object_id', '' + this.objectId)
|
||||||
params.append('limit', ACTIVITY_FETCH_LIMIT)
|
params.append('limit', ACTIVITY_FETCH_LIMIT)
|
||||||
|
|
||||||
const response = await axios.get(generateOcsUrl(`apps/activity/api/v2/activity/${this.filter}`) + '?' + params)
|
const response = await axios.get(
|
||||||
|
generateOcsUrl(`apps/activity/api/v2/activity/${this.filter}`) + '?' + params,
|
||||||
|
{
|
||||||
|
validateStatus: (status) => {
|
||||||
|
return (status >= 200 && status < 300) || status === 304
|
||||||
|
},
|
||||||
|
},
|
||||||
|
)
|
||||||
|
|
||||||
|
if (response.status === 304) {
|
||||||
|
this.endReached = true
|
||||||
|
return []
|
||||||
|
}
|
||||||
|
|
||||||
let activities = response.data.ocs.data
|
let activities = response.data.ocs.data
|
||||||
if (this.filter === 'deck') {
|
if (this.filter === 'deck') {
|
||||||
// We need to manually filter activities here, since currently we use two different types and there is no way
|
// We need to manually filter activities here, since currently we use two different types and there is no way
|
||||||
@@ -95,7 +108,7 @@ export default {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
this.activities.push(...activities)
|
this.activities.push(...activities)
|
||||||
if (response.data.ocs.meta.statuscode === 304 || activities.length === 0) {
|
if (activities.length === 0) {
|
||||||
this.endReached = true
|
this.endReached = true
|
||||||
return []
|
return []
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -27,21 +27,24 @@
|
|||||||
@drop.prevent="handleDropFiles">
|
@drop.prevent="handleDropFiles">
|
||||||
<slot />
|
<slot />
|
||||||
<transition name="fade" mode="out-in">
|
<transition name="fade" mode="out-in">
|
||||||
<div v-show="isDraggingOver"
|
<div
|
||||||
|
v-show="isDraggingOver"
|
||||||
class="dragover">
|
class="dragover">
|
||||||
<div class="drop-hint">
|
<div class="drop-hint">
|
||||||
<div class="drop-hint__icon"
|
<div
|
||||||
|
class="drop-hint__icon"
|
||||||
:class="{
|
:class="{
|
||||||
'icon-upload' : !isReadOnly,
|
'icon-upload' : !isReadOnly,
|
||||||
'icon-error' : isReadOnly}" />
|
'icon-error' : isReadOnly}" />
|
||||||
<h2 class="drop-hint__text">
|
<h2
|
||||||
|
class="drop-hint__text">
|
||||||
{{ dropHintText }}
|
{{ dropHintText }}
|
||||||
</h2>
|
</h2>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</transition>
|
</transition>
|
||||||
|
|
||||||
<NcModal v-if="modalShow" :title="t('deck', 'File already exists')" @close="modalShow=false">
|
<Modal v-if="modalShow" :title="t('deck', 'File already exists')" @close="modalShow=false">
|
||||||
<div class="modal__content">
|
<div class="modal__content">
|
||||||
<h2>{{ t('deck', 'File already exists') }}</h2>
|
<h2>{{ t('deck', 'File already exists') }}</h2>
|
||||||
<p>
|
<p>
|
||||||
@@ -57,13 +60,13 @@
|
|||||||
{{ t('deck', 'Keep existing file') }}
|
{{ t('deck', 'Keep existing file') }}
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
</NcModal>
|
</Modal>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
import { NcModal } from '@nextcloud/vue'
|
import { Modal } from '@nextcloud/vue'
|
||||||
import attachmentUpload from '../mixins/attachmentUpload.js'
|
import attachmentUpload from '../mixins/attachmentUpload'
|
||||||
import { loadState } from '@nextcloud/initial-state'
|
import { loadState } from '@nextcloud/initial-state'
|
||||||
|
|
||||||
let maxUploadSizeState
|
let maxUploadSizeState
|
||||||
@@ -75,7 +78,7 @@ try {
|
|||||||
|
|
||||||
export default {
|
export default {
|
||||||
name: 'AttachmentDragAndDrop',
|
name: 'AttachmentDragAndDrop',
|
||||||
components: { NcModal },
|
components: { Modal },
|
||||||
mixins: [attachmentUpload],
|
mixins: [attachmentUpload],
|
||||||
props: {
|
props: {
|
||||||
cardId: {
|
cardId: {
|
||||||
|
|||||||
@@ -25,11 +25,11 @@
|
|||||||
<div v-if="overviewName" class="board-title">
|
<div v-if="overviewName" class="board-title">
|
||||||
<div class="board-bullet icon-calendar-dark" />
|
<div class="board-bullet icon-calendar-dark" />
|
||||||
<h2>{{ overviewName }}</h2>
|
<h2>{{ overviewName }}</h2>
|
||||||
<NcActions>
|
<Actions>
|
||||||
<NcActionButton icon="icon-add" @click="clickShowAddCardModel">
|
<ActionButton icon="icon-add" @click="clickShowAddCardModel">
|
||||||
{{ t('deck', 'Add card') }}
|
{{ t('deck', 'Add card') }}
|
||||||
</NcActionButton>
|
</ActionButton>
|
||||||
</NcActions>
|
</Actions>
|
||||||
<CardCreateDialog v-if="showAddCardModal" @close="clickHideAddCardModel" />
|
<CardCreateDialog v-if="showAddCardModal" @close="clickHideAddCardModel" />
|
||||||
</div>
|
</div>
|
||||||
<div v-else-if="board" class="board-title">
|
<div v-else-if="board" class="board-title">
|
||||||
@@ -49,11 +49,11 @@
|
|||||||
<div v-if="board && canManage && !showArchived && !board.archived"
|
<div v-if="board && canManage && !showArchived && !board.archived"
|
||||||
id="stack-add"
|
id="stack-add"
|
||||||
v-click-outside="hideAddStack">
|
v-click-outside="hideAddStack">
|
||||||
<NcActions v-if="!isAddStackVisible">
|
<Actions v-if="!isAddStackVisible">
|
||||||
<NcActionButton icon="icon-add" @click.stop="showAddStack">
|
<ActionButton icon="icon-add" @click.stop="showAddStack">
|
||||||
{{ t('deck', 'Add list') }}
|
{{ t('deck', 'Add list') }}
|
||||||
</NcActionButton>
|
</ActionButton>
|
||||||
</NcActions>
|
</Actions>
|
||||||
<form v-else @submit.prevent="addNewStack()">
|
<form v-else @submit.prevent="addNewStack()">
|
||||||
<label for="new-stack-input-main" class="hidden-visually">{{ t('deck', 'Add list') }}</label>
|
<label for="new-stack-input-main" class="hidden-visually">{{ t('deck', 'Add list') }}</label>
|
||||||
<input id="new-stack-input-main"
|
<input id="new-stack-input-main"
|
||||||
@@ -70,145 +70,137 @@
|
|||||||
</form>
|
</form>
|
||||||
</div>
|
</div>
|
||||||
<div v-if="board" class="board-action-buttons">
|
<div v-if="board" class="board-action-buttons">
|
||||||
<div class="board-action-buttons__filter">
|
<Popover @show="filterVisible=true" @hide="filterVisible=false">
|
||||||
<NcPopover container=".board-action-buttons__filter"
|
<Actions slot="trigger" :title="t('deck', 'Apply filter')">
|
||||||
:placement="'bottom-end'"
|
<ActionButton v-if="isFilterActive" icon="icon-filter_set" />
|
||||||
:aria-label="t('deck', 'Active filters')"
|
<ActionButton v-else icon="icon-filter" />
|
||||||
@show="filterVisible=true"
|
</Actions>
|
||||||
@hide="filterVisible=false">
|
|
||||||
<!-- We cannot use NcActions here are the popover trigger does not update on reactive icons -->
|
|
||||||
<NcButton slot="trigger"
|
|
||||||
:title="t('deck', 'Apply filter')"
|
|
||||||
class="filter-button"
|
|
||||||
type="tertiary-no-background">
|
|
||||||
<template #icon>
|
|
||||||
<FilterIcon v-if="isFilterActive" :size="20" decorative />
|
|
||||||
<FilterOffIcon v-else :size="20" decorative />
|
|
||||||
</template>
|
|
||||||
</NcButton>
|
|
||||||
|
|
||||||
<div v-if="filterVisible" class="filter">
|
<div v-if="filterVisible" class="filter">
|
||||||
<h3>{{ t('deck', 'Filter by tag') }}</h3>
|
<h3>{{ t('deck', 'Filter by tag') }}</h3>
|
||||||
<div v-for="label in labelsSorted" :key="label.id" class="filter--item">
|
<div v-for="label in labelsSorted" :key="label.id" class="filter--item">
|
||||||
<input :id="label.id"
|
<input
|
||||||
v-model="filter.tags"
|
:id="label.id"
|
||||||
type="checkbox"
|
v-model="filter.tags"
|
||||||
class="checkbox"
|
type="checkbox"
|
||||||
:value="label.id"
|
class="checkbox"
|
||||||
@change="setFilter">
|
:value="label.id"
|
||||||
<label :for="label.id"><span class="label" :style="labelStyle(label)">{{ label.title }}</span></label>
|
@change="setFilter">
|
||||||
</div>
|
<label :for="label.id"><span class="label" :style="labelStyle(label)">{{ label.title }}</span></label>
|
||||||
|
|
||||||
<h3>{{ t('deck', 'Filter by assigned user') }}</h3>
|
|
||||||
<div class="filter--item">
|
|
||||||
<input id="unassigned"
|
|
||||||
v-model="filter.unassigned"
|
|
||||||
type="checkbox"
|
|
||||||
class="checkbox"
|
|
||||||
value="unassigned"
|
|
||||||
@change="setFilter"
|
|
||||||
@click="beforeSetFilter">
|
|
||||||
<label for="unassigned">{{ t('deck', 'Unassigned') }}</label>
|
|
||||||
</div>
|
|
||||||
<div v-for="user in board.users" :key="user.uid" class="filter--item">
|
|
||||||
<input :id="user.uid"
|
|
||||||
v-model="filter.users"
|
|
||||||
type="checkbox"
|
|
||||||
class="checkbox"
|
|
||||||
:value="user.uid"
|
|
||||||
@change="setFilter">
|
|
||||||
<label :for="user.uid"><NcAvatar :user="user.uid" :size="24" :disable-menu="true" /> {{ user.displayname }}</label>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<h3>{{ t('deck', 'Filter by due date') }}</h3>
|
|
||||||
|
|
||||||
<div class="filter--item">
|
|
||||||
<input id="overdue"
|
|
||||||
v-model="filter.due"
|
|
||||||
type="radio"
|
|
||||||
class="radio"
|
|
||||||
value="overdue"
|
|
||||||
@change="setFilter"
|
|
||||||
@click="beforeSetFilter">
|
|
||||||
<label for="overdue">{{ t('deck', 'Overdue') }}</label>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="filter--item">
|
|
||||||
<input id="dueToday"
|
|
||||||
v-model="filter.due"
|
|
||||||
type="radio"
|
|
||||||
class="radio"
|
|
||||||
value="dueToday"
|
|
||||||
@change="setFilter"
|
|
||||||
@click="beforeSetFilter">
|
|
||||||
<label for="dueToday">{{ t('deck', 'Next 24 hours') }}</label>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="filter--item">
|
|
||||||
<input id="dueWeek"
|
|
||||||
v-model="filter.due"
|
|
||||||
type="radio"
|
|
||||||
class="radio"
|
|
||||||
value="dueWeek"
|
|
||||||
@change="setFilter"
|
|
||||||
@click="beforeSetFilter">
|
|
||||||
<label for="dueWeek">{{ t('deck', 'Next 7 days') }}</label>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="filter--item">
|
|
||||||
<input id="dueMonth"
|
|
||||||
v-model="filter.due"
|
|
||||||
type="radio"
|
|
||||||
class="radio"
|
|
||||||
value="dueMonth"
|
|
||||||
@change="setFilter"
|
|
||||||
@click="beforeSetFilter">
|
|
||||||
<label for="dueMonth">{{ t('deck', 'Next 30 days') }}</label>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="filter--item">
|
|
||||||
<input id="noDue"
|
|
||||||
v-model="filter.due"
|
|
||||||
type="radio"
|
|
||||||
class="radio"
|
|
||||||
value="noDue"
|
|
||||||
@change="setFilter"
|
|
||||||
@click="beforeSetFilter">
|
|
||||||
<label for="noDue">{{ t('deck', 'No due date') }}</label>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<NcButton :disabled="!isFilterActive" :wide="true" @click="clearFilter">
|
|
||||||
{{ t('deck', 'Clear filter') }}
|
|
||||||
</NcButton>
|
|
||||||
</div>
|
</div>
|
||||||
</NcPopover>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<NcActions>
|
<h3>{{ t('deck', 'Filter by assigned user') }}</h3>
|
||||||
<NcActionButton @click="toggleShowArchived">
|
<div class="filter--item">
|
||||||
<template #icon>
|
<input
|
||||||
<ArchiveIcon :size="20" decorative />
|
id="unassigned"
|
||||||
</template>
|
v-model="filter.unassigned"
|
||||||
|
type="checkbox"
|
||||||
|
class="checkbox"
|
||||||
|
value="unassigned"
|
||||||
|
@change="setFilter"
|
||||||
|
@click="beforeSetFilter">
|
||||||
|
<label for="unassigned">{{ t('deck', 'Unassigned') }}</label>
|
||||||
|
</div>
|
||||||
|
<div v-for="user in board.users" :key="user.uid" class="filter--item">
|
||||||
|
<input
|
||||||
|
:id="user.uid"
|
||||||
|
v-model="filter.users"
|
||||||
|
type="checkbox"
|
||||||
|
class="checkbox"
|
||||||
|
:value="user.uid"
|
||||||
|
@change="setFilter">
|
||||||
|
<label :for="user.uid"><Avatar :user="user.uid" :size="24" :disable-menu="true" /> {{ user.displayname }}</label>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<h3>{{ t('deck', 'Filter by due date') }}</h3>
|
||||||
|
|
||||||
|
<div class="filter--item">
|
||||||
|
<input
|
||||||
|
id="overdue"
|
||||||
|
v-model="filter.due"
|
||||||
|
type="radio"
|
||||||
|
class="radio"
|
||||||
|
value="overdue"
|
||||||
|
@change="setFilter"
|
||||||
|
@click="beforeSetFilter">
|
||||||
|
<label for="overdue">{{ t('deck', 'Overdue') }}</label>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="filter--item">
|
||||||
|
<input
|
||||||
|
id="dueToday"
|
||||||
|
v-model="filter.due"
|
||||||
|
type="radio"
|
||||||
|
class="radio"
|
||||||
|
value="dueToday"
|
||||||
|
@change="setFilter"
|
||||||
|
@click="beforeSetFilter">
|
||||||
|
<label for="dueToday">{{ t('deck', 'Next 24 hours') }}</label>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="filter--item">
|
||||||
|
<input
|
||||||
|
id="dueWeek"
|
||||||
|
v-model="filter.due"
|
||||||
|
type="radio"
|
||||||
|
class="radio"
|
||||||
|
value="dueWeek"
|
||||||
|
@change="setFilter"
|
||||||
|
@click="beforeSetFilter">
|
||||||
|
<label for="dueWeek">{{ t('deck', 'Next 7 days') }}</label>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="filter--item">
|
||||||
|
<input
|
||||||
|
id="dueMonth"
|
||||||
|
v-model="filter.due"
|
||||||
|
type="radio"
|
||||||
|
class="radio"
|
||||||
|
value="dueMonth"
|
||||||
|
@change="setFilter"
|
||||||
|
@click="beforeSetFilter">
|
||||||
|
<label for="dueMonth">{{ t('deck', 'Next 30 days') }}</label>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="filter--item">
|
||||||
|
<input
|
||||||
|
id="noDue"
|
||||||
|
v-model="filter.due"
|
||||||
|
type="radio"
|
||||||
|
class="radio"
|
||||||
|
value="noDue"
|
||||||
|
@change="setFilter"
|
||||||
|
@click="beforeSetFilter">
|
||||||
|
<label for="noDue">{{ t('deck', 'No due date') }}</label>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<Button :disabled="!isFilterActive" @click="clearFilter">
|
||||||
|
{{ t('deck', 'Clear filter') }}
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
</Popover>
|
||||||
|
|
||||||
|
<Actions>
|
||||||
|
<ActionButton
|
||||||
|
icon="icon-archive"
|
||||||
|
@click="toggleShowArchived">
|
||||||
{{ showArchived ? t('deck', 'Hide archived cards') : t('deck', 'Show archived cards') }}
|
{{ showArchived ? t('deck', 'Hide archived cards') : t('deck', 'Show archived cards') }}
|
||||||
</NcActionButton>
|
</ActionButton>
|
||||||
<NcActionButton v-if="compactMode"
|
<ActionButton v-if="compactMode"
|
||||||
|
icon="icon-toggle-compact-collapsed"
|
||||||
@click="toggleCompactMode">
|
@click="toggleCompactMode">
|
||||||
<ArrowExpandVerticalIcon slot="icon" :size="20" decorative />
|
|
||||||
{{ t('deck', 'Toggle compact mode') }}
|
{{ t('deck', 'Toggle compact mode') }}
|
||||||
</NcActionButton>
|
</ActionButton>
|
||||||
<NcActionButton v-else
|
<ActionButton v-else
|
||||||
|
icon="icon-toggle-compact-expanded"
|
||||||
@click="toggleCompactMode">
|
@click="toggleCompactMode">
|
||||||
<ArrowCollapseVerticalIcon slot="icon" :size="20" decorative />
|
|
||||||
{{ t('deck', 'Toggle compact mode') }}
|
{{ t('deck', 'Toggle compact mode') }}
|
||||||
</NcActionButton>
|
</ActionButton>
|
||||||
</NcActions>
|
</Actions>
|
||||||
<!-- FIXME: NcActionRouter currently doesn't work as an inline action -->
|
<!-- FIXME: ActionRouter currently doesn't work as an inline action -->
|
||||||
<NcActions>
|
<Actions :title="t('deck', 'Details')">
|
||||||
<NcActionButton icon="icon-menu-sidebar"
|
<ActionButton icon="icon-menu-sidebar" @click="toggleDetailsView" />
|
||||||
:aria-label="t('deck', 'Open details')"
|
</Actions>
|
||||||
:title="t('deck', 'Details')"
|
|
||||||
@click="toggleDetailsView" />
|
|
||||||
</NcActions>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -216,29 +208,14 @@
|
|||||||
|
|
||||||
<script>
|
<script>
|
||||||
import { mapState, mapGetters } from 'vuex'
|
import { mapState, mapGetters } from 'vuex'
|
||||||
import { NcActions, NcActionButton, NcAvatar, NcButton, NcPopover } from '@nextcloud/vue'
|
import { Actions, ActionButton, Popover, Avatar } from '@nextcloud/vue'
|
||||||
import labelStyle from '../mixins/labelStyle.js'
|
import labelStyle from '../mixins/labelStyle'
|
||||||
import CardCreateDialog from '../CardCreateDialog.vue'
|
import CardCreateDialog from '../CardCreateDialog'
|
||||||
import ArchiveIcon from 'vue-material-design-icons/Archive.vue'
|
|
||||||
import FilterIcon from 'vue-material-design-icons/Filter.vue'
|
|
||||||
import FilterOffIcon from 'vue-material-design-icons/FilterOff.vue'
|
|
||||||
import ArrowCollapseVerticalIcon from 'vue-material-design-icons/ArrowCollapseVertical.vue'
|
|
||||||
import ArrowExpandVerticalIcon from 'vue-material-design-icons/ArrowExpandVertical.vue'
|
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
name: 'Controls',
|
name: 'Controls',
|
||||||
components: {
|
components: {
|
||||||
NcActions,
|
Actions, ActionButton, Popover, Avatar, CardCreateDialog,
|
||||||
NcActionButton,
|
|
||||||
NcButton,
|
|
||||||
NcPopover,
|
|
||||||
NcAvatar,
|
|
||||||
CardCreateDialog,
|
|
||||||
ArchiveIcon,
|
|
||||||
FilterIcon,
|
|
||||||
FilterOffIcon,
|
|
||||||
ArrowCollapseVerticalIcon,
|
|
||||||
ArrowExpandVerticalIcon,
|
|
||||||
},
|
},
|
||||||
mixins: [labelStyle],
|
mixins: [labelStyle],
|
||||||
props: {
|
props: {
|
||||||
@@ -281,12 +258,18 @@ export default {
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
isFilterActive() {
|
isFilterActive() {
|
||||||
return this.filter.tags.length !== 0 || this.filter.users.length !== 0 || this.filter.due !== ''
|
if (this.filter.tags.length !== 0 || this.filter.users.length !== 0 || this.filter.due !== '') {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
return false
|
||||||
},
|
},
|
||||||
labelsSorted() {
|
labelsSorted() {
|
||||||
return [...this.board.labels].sort((a, b) => (a.title < b.title) ? -1 : 1)
|
return [...this.board.labels].sort((a, b) => (a.title < b.title) ? -1 : 1)
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
beforeDestroy() {
|
||||||
|
this.setPageTitle('')
|
||||||
|
},
|
||||||
watch: {
|
watch: {
|
||||||
board(current, previous) {
|
board(current, previous) {
|
||||||
if (current?.id !== previous?.id) {
|
if (current?.id !== previous?.id) {
|
||||||
@@ -297,9 +280,6 @@ export default {
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
beforeDestroy() {
|
|
||||||
this.setPageTitle('')
|
|
||||||
},
|
|
||||||
methods: {
|
methods: {
|
||||||
beforeSetFilter(e) {
|
beforeSetFilter(e) {
|
||||||
if (this.filter.due === e.target.value) {
|
if (this.filter.due === e.target.value) {
|
||||||
@@ -380,7 +360,7 @@ export default {
|
|||||||
<style lang="scss" scoped>
|
<style lang="scss" scoped>
|
||||||
.controls {
|
.controls {
|
||||||
display: flex;
|
display: flex;
|
||||||
margin: 5px;
|
padding: 3px;
|
||||||
height: 44px;
|
height: 44px;
|
||||||
padding-left: 44px;
|
padding-left: 44px;
|
||||||
|
|
||||||
@@ -428,15 +408,18 @@ export default {
|
|||||||
|
|
||||||
.board-action-buttons {
|
.board-action-buttons {
|
||||||
display: flex;
|
display: flex;
|
||||||
|
button {
|
||||||
|
border: 0;
|
||||||
|
width: 44px;
|
||||||
|
margin: 0 0 0 -1px;
|
||||||
|
background-color: transparent;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.deck-search {
|
.deck-search {
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
justify-content: center;
|
|
||||||
input[type=search] {
|
input[type=search] {
|
||||||
background-position: 5px;
|
background-position: 5px;
|
||||||
padding-left: 24px !important;
|
padding-left: 24px;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -458,9 +441,8 @@ export default {
|
|||||||
}
|
}
|
||||||
|
|
||||||
.filter {
|
.filter {
|
||||||
width: 240px;
|
width: 250px;
|
||||||
max-height: calc(100vh - 150px);
|
max-height: 80vh;
|
||||||
position: relative;
|
|
||||||
overflow: auto;
|
overflow: auto;
|
||||||
padding: 8px;
|
padding: 8px;
|
||||||
}
|
}
|
||||||
@@ -469,23 +451,8 @@ export default {
|
|||||||
margin-top: 0px;
|
margin-top: 0px;
|
||||||
margin-bottom: 5px;
|
margin-bottom: 5px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.filter-button {
|
|
||||||
padding: 0;
|
|
||||||
border-radius: 50%;
|
|
||||||
width: 44px;
|
|
||||||
height: 44px;
|
|
||||||
|
|
||||||
&:hover, &:focus {
|
|
||||||
background-color: rgba(127,127,127,0.25) !important;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
</style>
|
</style>
|
||||||
<style lang="scss">
|
<style lang="scss">
|
||||||
.popover:focus {
|
|
||||||
outline: 2px solid var(--color-main-text);
|
|
||||||
}
|
|
||||||
|
|
||||||
.tooltip-inner.popover-inner {
|
.tooltip-inner.popover-inner {
|
||||||
text-align: left;
|
text-align: left;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -45,7 +45,6 @@ export default {
|
|||||||
#app-sidebar .icon-close {
|
#app-sidebar .icon-close {
|
||||||
z-index: 100;
|
z-index: 100;
|
||||||
}
|
}
|
||||||
|
|
||||||
.app-deck .app-sidebar {
|
.app-deck .app-sidebar {
|
||||||
z-index: 1500 !important;
|
z-index: 1500 !important;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -30,14 +30,9 @@
|
|||||||
<h2>{{ t('deck', 'Loading board') }}</h2>
|
<h2>{{ t('deck', 'Loading board') }}</h2>
|
||||||
<p />
|
<p />
|
||||||
</div>
|
</div>
|
||||||
<NcEmptyContent v-else-if="isEmpty" key="empty">
|
<EmptyContent v-else-if="isEmpty" key="empty" icon="icon-deck">
|
||||||
<template #icon>
|
{{ t('deck', 'No lists available') }}
|
||||||
<DeckIcon />
|
<template v-if="canManage" #desc>
|
||||||
</template>
|
|
||||||
<template #title>
|
|
||||||
{{ t('deck', 'No lists available') }}
|
|
||||||
</template>
|
|
||||||
<template v-if="canManage" #action>
|
|
||||||
{{ t('deck', 'Create a new list to add cards to this board') }}
|
{{ t('deck', 'Create a new list to add cards to this board') }}
|
||||||
<form @submit.prevent="addNewStack()">
|
<form @submit.prevent="addNewStack()">
|
||||||
<input id="new-stack-input-main"
|
<input id="new-stack-input-main"
|
||||||
@@ -53,7 +48,7 @@
|
|||||||
value="">
|
value="">
|
||||||
</form>
|
</form>
|
||||||
</template>
|
</template>
|
||||||
</NcEmptyContent>
|
</EmptyContent>
|
||||||
<div v-else-if="!isEmpty && !loading" key="board" class="board">
|
<div v-else-if="!isEmpty && !loading" key="board" class="board">
|
||||||
<Container lock-axix="y"
|
<Container lock-axix="y"
|
||||||
orientation="horizontal"
|
orientation="horizontal"
|
||||||
@@ -78,12 +73,11 @@
|
|||||||
|
|
||||||
import { Container, Draggable } from 'vue-smooth-dnd'
|
import { Container, Draggable } from 'vue-smooth-dnd'
|
||||||
import { mapState, mapGetters } from 'vuex'
|
import { mapState, mapGetters } from 'vuex'
|
||||||
import Controls from '../Controls.vue'
|
import Controls from '../Controls'
|
||||||
import DeckIcon from '../icons/DeckIcon.vue'
|
import Stack from './Stack'
|
||||||
import Stack from './Stack.vue'
|
import { EmptyContent } from '@nextcloud/vue'
|
||||||
import { NcEmptyContent } from '@nextcloud/vue'
|
import GlobalSearchResults from '../search/GlobalSearchResults'
|
||||||
import GlobalSearchResults from '../search/GlobalSearchResults.vue'
|
import { showError } from '../../helpers/errors'
|
||||||
import { showError } from '../../helpers/errors.js'
|
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
name: 'Board',
|
name: 'Board',
|
||||||
@@ -91,10 +85,9 @@ export default {
|
|||||||
GlobalSearchResults,
|
GlobalSearchResults,
|
||||||
Controls,
|
Controls,
|
||||||
Container,
|
Container,
|
||||||
DeckIcon,
|
|
||||||
Draggable,
|
Draggable,
|
||||||
Stack,
|
Stack,
|
||||||
NcEmptyContent,
|
EmptyContent,
|
||||||
},
|
},
|
||||||
inject: [
|
inject: [
|
||||||
'boardApi',
|
'boardApi',
|
||||||
@@ -170,8 +163,8 @@ export default {
|
|||||||
|
|
||||||
<style lang="scss" scoped>
|
<style lang="scss" scoped>
|
||||||
|
|
||||||
@import '../../css/animations';
|
@import '../../css/animations.scss';
|
||||||
@import '../../css/variables';
|
@import '../../css/variables.scss';
|
||||||
|
|
||||||
form {
|
form {
|
||||||
text-align: center;
|
text-align: center;
|
||||||
@@ -229,7 +222,6 @@ export default {
|
|||||||
padding: $stack-spacing;
|
padding: $stack-spacing;
|
||||||
overflow-x: hidden;
|
overflow-x: hidden;
|
||||||
overflow-y: auto;
|
overflow-y: auto;
|
||||||
scrollbar-gutter: stable;
|
|
||||||
padding-top: 15px;
|
padding-top: 15px;
|
||||||
margin-top: -10px;
|
margin-top: -10px;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -21,57 +21,57 @@
|
|||||||
-->
|
-->
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<NcAppSidebar v-if="board != null"
|
<AppSidebar v-if="board != null"
|
||||||
:actions="[]"
|
:actions="[]"
|
||||||
:title="board.title"
|
:title="board.title"
|
||||||
@close="closeSidebar">
|
@close="closeSidebar">
|
||||||
<NcAppSidebarTab id="sharing"
|
<AppSidebarTab id="sharing"
|
||||||
:order="0"
|
:order="0"
|
||||||
:name="t('deck', 'Sharing')"
|
:name="t('deck', 'Sharing')"
|
||||||
icon="icon-shared">
|
icon="icon-shared">
|
||||||
<SharingTabSidebar :board="board" />
|
<SharingTabSidebar :board="board" />
|
||||||
</NcAppSidebarTab>
|
</AppSidebarTab>
|
||||||
|
|
||||||
<NcAppSidebarTab id="tags"
|
<AppSidebarTab id="tags"
|
||||||
:order="1"
|
:order="1"
|
||||||
:name="t('deck', 'Tags')"
|
:name="t('deck', 'Tags')"
|
||||||
icon="icon-tag">
|
icon="icon-tag">
|
||||||
<TagsTabSidebar :board="board" />
|
<TagsTabSidebar :board="board" />
|
||||||
</NcAppSidebarTab>
|
</AppSidebarTab>
|
||||||
|
|
||||||
<NcAppSidebarTab v-if="canEdit"
|
<AppSidebarTab v-if="canEdit"
|
||||||
id="deleted"
|
id="deleted"
|
||||||
:order="2"
|
:order="2"
|
||||||
:name="t('deck', 'Deleted items')"
|
:name="t('deck', 'Deleted items')"
|
||||||
icon="icon-delete">
|
icon="icon-delete">
|
||||||
<DeletedTabSidebar :board="board" />
|
<DeletedTabSidebar :board="board" />
|
||||||
</NcAppSidebarTab>
|
</AppSidebarTab>
|
||||||
|
|
||||||
<NcAppSidebarTab v-if="hasActivity"
|
<AppSidebarTab v-if="hasActivity"
|
||||||
id="activity"
|
id="activity"
|
||||||
:order="3"
|
:order="3"
|
||||||
:name="t('deck', 'Timeline')"
|
:name="t('deck', 'Timeline')"
|
||||||
icon="icon-activity">
|
icon="icon-activity">
|
||||||
<TimelineTabSidebar :board="board" />
|
<TimelineTabSidebar :board="board" />
|
||||||
</NcAppSidebarTab>
|
</AppSidebarTab>
|
||||||
</NcAppSidebar>
|
</AppSidebar>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
import { mapState, mapGetters } from 'vuex'
|
import { mapState, mapGetters } from 'vuex'
|
||||||
import SharingTabSidebar from './SharingTabSidebar.vue'
|
import SharingTabSidebar from './SharingTabSidebar'
|
||||||
import TagsTabSidebar from './TagsTabSidebar.vue'
|
import TagsTabSidebar from './TagsTabSidebar'
|
||||||
import DeletedTabSidebar from './DeletedTabSidebar.vue'
|
import DeletedTabSidebar from './DeletedTabSidebar'
|
||||||
import TimelineTabSidebar from './TimelineTabSidebar.vue'
|
import TimelineTabSidebar from './TimelineTabSidebar'
|
||||||
import { NcAppSidebar, NcAppSidebarTab } from '@nextcloud/vue'
|
import { AppSidebar, AppSidebarTab } from '@nextcloud/vue'
|
||||||
|
|
||||||
const capabilities = window.OC.getCapabilities()
|
const capabilities = window.OC.getCapabilities()
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
name: 'BoardSidebar',
|
name: 'BoardSidebar',
|
||||||
components: {
|
components: {
|
||||||
NcAppSidebar,
|
AppSidebar,
|
||||||
NcAppSidebarTab,
|
AppSidebarTab,
|
||||||
SharingTabSidebar,
|
SharingTabSidebar,
|
||||||
TagsTabSidebar,
|
TagsTabSidebar,
|
||||||
DeletedTabSidebar,
|
DeletedTabSidebar,
|
||||||
|
|||||||
@@ -8,7 +8,8 @@
|
|||||||
<span>{{ deletedStack.title }}</span>
|
<span>{{ deletedStack.title }}</span>
|
||||||
<span class="timestamp">{{ relativeDate(deletedStack.deletedAt*1000) }}</span>
|
<span class="timestamp">{{ relativeDate(deletedStack.deletedAt*1000) }}</span>
|
||||||
</div>
|
</div>
|
||||||
<button :title="t('settings', 'Undo')"
|
<button
|
||||||
|
:title="t('settings', 'Undo')"
|
||||||
class="app-navigation-entry-deleted-button icon-history"
|
class="app-navigation-entry-deleted-button icon-history"
|
||||||
@click="stackUndoDelete(deletedStack)" />
|
@click="stackUndoDelete(deletedStack)" />
|
||||||
</li>
|
</li>
|
||||||
@@ -22,7 +23,8 @@
|
|||||||
<span>{{ deletedCard.title }}</span>
|
<span>{{ deletedCard.title }}</span>
|
||||||
<span class="timestamp">{{ relativeDate(deletedCard.deletedAt*1000) }}</span>
|
<span class="timestamp">{{ relativeDate(deletedCard.deletedAt*1000) }}</span>
|
||||||
</div>
|
</div>
|
||||||
<button :title="t('settings', 'Undo')"
|
<button
|
||||||
|
:title="t('settings', 'Undo')"
|
||||||
class="app-navigation-entry-deleted-button icon-history"
|
class="app-navigation-entry-deleted-button icon-history"
|
||||||
@click="cardUndoDelete(deletedCard)" />
|
@click="cardUndoDelete(deletedCard)" />
|
||||||
</li>
|
</li>
|
||||||
@@ -32,7 +34,7 @@
|
|||||||
|
|
||||||
<script>
|
<script>
|
||||||
import { mapState } from 'vuex'
|
import { mapState } from 'vuex'
|
||||||
import relativeDate from '../../mixins/relativeDate.js'
|
import relativeDate from '../../mixins/relativeDate'
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
name: 'DeletedTabSidebar',
|
name: 'DeletedTabSidebar',
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
<template>
|
<template>
|
||||||
<div>
|
<div>
|
||||||
<NcMultiselect v-if="canShare"
|
<Multiselect
|
||||||
|
v-if="canShare"
|
||||||
v-model="addAcl"
|
v-model="addAcl"
|
||||||
:placeholder="t('deck', 'Share board with a user, group or circle …')"
|
:placeholder="t('deck', 'Share board with a user, group or circle …')"
|
||||||
:options="formatedSharees"
|
:options="formatedSharees"
|
||||||
@@ -18,12 +19,13 @@
|
|||||||
<template #noResult>
|
<template #noResult>
|
||||||
{{ isSearching ? t('deck', 'Searching for users, groups and circles …') : t('deck', 'No participants found') }}
|
{{ isSearching ? t('deck', 'Searching for users, groups and circles …') : t('deck', 'No participants found') }}
|
||||||
</template>
|
</template>
|
||||||
</NcMultiselect>
|
</Multiselect>
|
||||||
|
|
||||||
<ul id="shareWithList"
|
<ul
|
||||||
|
id="shareWithList"
|
||||||
class="shareWithList">
|
class="shareWithList">
|
||||||
<li>
|
<li>
|
||||||
<NcAvatar :user="board.owner.uid" />
|
<Avatar :user="board.owner.uid" />
|
||||||
<span class="has-tooltip username">
|
<span class="has-tooltip username">
|
||||||
{{ board.owner.displayname }}
|
{{ board.owner.displayname }}
|
||||||
<span v-if="!isCurrentUser(board.owner.uid)" class="board-owner-label">
|
<span v-if="!isCurrentUser(board.owner.uid)" class="board-owner-label">
|
||||||
@@ -32,7 +34,7 @@
|
|||||||
</span>
|
</span>
|
||||||
</li>
|
</li>
|
||||||
<li v-for="acl in board.acl" :key="acl.id">
|
<li v-for="acl in board.acl" :key="acl.id">
|
||||||
<NcAvatar v-if="acl.type===0" :user="acl.participant.uid" />
|
<Avatar v-if="acl.type===0" :user="acl.participant.uid" />
|
||||||
<div v-if="acl.type===1" class="avatardiv icon icon-group" />
|
<div v-if="acl.type===1" class="avatardiv icon icon-group" />
|
||||||
<div v-if="acl.type===7" class="avatardiv icon icon-circles" />
|
<div v-if="acl.type===7" class="avatardiv icon icon-circles" />
|
||||||
<span class="has-tooltip username">
|
<span class="has-tooltip username">
|
||||||
@@ -41,31 +43,27 @@
|
|||||||
<span v-if="acl.type===7">{{ t('deck', '(Circle)') }}</span>
|
<span v-if="acl.type===7">{{ t('deck', '(Circle)') }}</span>
|
||||||
</span>
|
</span>
|
||||||
|
|
||||||
<NcActionCheckbox v-if="!(isCurrentUser(acl.participant.uid) && acl.type === 0) && (canManage || (canEdit && canShare))" :checked="acl.permissionEdit" @change="clickEditAcl(acl)">
|
<ActionCheckbox v-if="!(isCurrentUser(acl.participant.uid) && acl.type === 0) && (canManage || (canEdit && canShare))" :checked="acl.permissionEdit" @change="clickEditAcl(acl)">
|
||||||
{{ t('deck', 'Can edit') }}
|
{{ t('deck', 'Can edit') }}
|
||||||
</NcActionCheckbox>
|
</ActionCheckbox>
|
||||||
<NcActions v-if="!(isCurrentUser(acl.participant.uid) && acl.type === 0)" :force-menu="true">
|
<Actions v-if="!(isCurrentUser(acl.participant.uid) && acl.type === 0)" :force-menu="true">
|
||||||
<NcActionCheckbox v-if="canManage || canShare" :checked="acl.permissionShare" @change="clickShareAcl(acl)">
|
<ActionCheckbox v-if="canManage || canShare" :checked="acl.permissionShare" @change="clickShareAcl(acl)">
|
||||||
{{ t('deck', 'Can share') }}
|
{{ t('deck', 'Can share') }}
|
||||||
</NcActionCheckbox>
|
</ActionCheckbox>
|
||||||
<NcActionCheckbox v-if="canManage" :checked="acl.permissionManage" @change="clickManageAcl(acl)">
|
<ActionCheckbox v-if="canManage" :checked="acl.permissionManage" @change="clickManageAcl(acl)">
|
||||||
{{ t('deck', 'Can manage') }}
|
{{ t('deck', 'Can manage') }}
|
||||||
</NcActionCheckbox>
|
</ActionCheckbox>
|
||||||
<NcActionCheckbox v-if="acl.type === 0 && isCurrentUser(board.owner.uid)" :checked="acl.owner" @change="clickTransferOwner(acl.participant.uid)">
|
<ActionCheckbox v-if="acl.type === 0 && isCurrentUser(board.owner.uid)" :checked="acl.owner" @change="clickTransferOwner(acl.participant.uid)">
|
||||||
{{ t('deck', 'Owner') }}
|
{{ t('deck', 'Owner') }}
|
||||||
</NcActionCheckbox>
|
</ActionCheckbox>
|
||||||
<NcActionButton v-if="canManage" icon="icon-delete" @click="clickDeleteAcl(acl)">
|
<ActionButton v-if="canManage" icon="icon-delete" @click="clickDeleteAcl(acl)">
|
||||||
{{ t('deck', 'Delete') }}
|
{{ t('deck', 'Delete') }}
|
||||||
</NcActionButton>
|
</ActionButton>
|
||||||
</NcActions>
|
</Actions>
|
||||||
</li>
|
</li>
|
||||||
</ul>
|
</ul>
|
||||||
|
|
||||||
<NcRelatedResourcesPanel v-if="board.id"
|
<CollectionList v-if="board.id"
|
||||||
provider-id="deck"
|
|
||||||
:item-id="board.id" />
|
|
||||||
|
|
||||||
<CollectionList v-if="projectsEnabled && board.id"
|
|
||||||
:id="`${board.id}`"
|
:id="`${board.id}`"
|
||||||
:name="board.title"
|
:name="board.title"
|
||||||
type="deck" />
|
type="deck" />
|
||||||
@@ -73,24 +71,22 @@
|
|||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
import { NcAvatar, NcMultiselect, NcActions, NcActionButton, NcActionCheckbox, NcRelatedResourcesPanel } from '@nextcloud/vue'
|
import { Avatar, Multiselect, Actions, ActionButton, ActionCheckbox } from '@nextcloud/vue'
|
||||||
import { CollectionList } from 'nextcloud-vue-collections'
|
import { CollectionList } from 'nextcloud-vue-collections'
|
||||||
import { mapGetters, mapState } from 'vuex'
|
import { mapGetters, mapState } from 'vuex'
|
||||||
import { getCurrentUser } from '@nextcloud/auth'
|
import { getCurrentUser } from '@nextcloud/auth'
|
||||||
import { showError, showSuccess } from '@nextcloud/dialogs'
|
import { showError, showSuccess } from '@nextcloud/dialogs'
|
||||||
import { loadState } from '@nextcloud/initial-state'
|
|
||||||
import debounce from 'lodash/debounce'
|
import debounce from 'lodash/debounce'
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
name: 'SharingTabSidebar',
|
name: 'SharingTabSidebar',
|
||||||
components: {
|
components: {
|
||||||
NcAvatar,
|
Avatar,
|
||||||
NcActions,
|
Actions,
|
||||||
NcActionButton,
|
ActionButton,
|
||||||
NcActionCheckbox,
|
ActionCheckbox,
|
||||||
NcMultiselect,
|
Multiselect,
|
||||||
CollectionList,
|
CollectionList,
|
||||||
NcRelatedResourcesPanel,
|
|
||||||
},
|
},
|
||||||
props: {
|
props: {
|
||||||
board: {
|
board: {
|
||||||
@@ -105,7 +101,6 @@ export default {
|
|||||||
addAcl: null,
|
addAcl: null,
|
||||||
addAclForAPI: null,
|
addAclForAPI: null,
|
||||||
newOwner: null,
|
newOwner: null,
|
||||||
projectsEnabled: loadState('core', 'projects_enabled', false),
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
computed: {
|
computed: {
|
||||||
@@ -205,7 +200,7 @@ export default {
|
|||||||
},
|
},
|
||||||
clickTransferOwner(newOwner) {
|
clickTransferOwner(newOwner) {
|
||||||
OC.dialogs.confirmDestructive(
|
OC.dialogs.confirmDestructive(
|
||||||
t('deck', 'Are you sure you want to transfer the board {title} to {user}?', { title: this.board.title, user: newOwner }),
|
t('deck', 'Are you sure you want to transfer the board {title} for {user}?', { title: this.board.title, user: newOwner }),
|
||||||
t('deck', 'Transfer the board.'),
|
t('deck', 'Transfer the board.'),
|
||||||
{
|
{
|
||||||
type: OC.dialogs.YES_NO_BUTTONS,
|
type: OC.dialogs.YES_NO_BUTTONS,
|
||||||
@@ -219,13 +214,13 @@ export default {
|
|||||||
this.isLoading = true
|
this.isLoading = true
|
||||||
await this.$store.dispatch('transferOwnership', {
|
await this.$store.dispatch('transferOwnership', {
|
||||||
boardId: this.board.id,
|
boardId: this.board.id,
|
||||||
newOwner,
|
newOwner
|
||||||
})
|
})
|
||||||
const successMessage = t('deck', 'The board has been transferred to {user}', { user: newOwner })
|
const successMessage = t('deck', 'Transfer the board for {user} successfully', { user: newOwner })
|
||||||
showSuccess(successMessage)
|
showSuccess(successMessage)
|
||||||
this.$router.push({ name: 'main' })
|
this.$router.push({ name: 'main' })
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
const errorMessage = t('deck', 'Failed to transfer the board to {user}', { user: newOwner.user })
|
const errorMessage = t('deck', 'Failed to transfer the board for {user}', { user: newOwner.user })
|
||||||
showError(errorMessage)
|
showError(errorMessage)
|
||||||
} finally {
|
} finally {
|
||||||
this.isLoading = false
|
this.isLoading = false
|
||||||
|
|||||||