Compare commits
319 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
3486e0f24c | ||
|
|
9519033881 | ||
|
|
fc1f3fd10a | ||
|
|
c483b042fd | ||
|
|
3d26acd0ba | ||
|
|
dd23f7b0d3 | ||
|
|
a12043357a | ||
|
|
2bd5d5532d | ||
|
|
ef72c08c11 | ||
|
|
3c3ff391a6 | ||
|
|
29361b3ee7 | ||
|
|
a421a62fb5 | ||
|
|
2392308f58 | ||
|
|
26cc02bea1 | ||
|
|
9ecdc84148 | ||
|
|
af6a099f59 | ||
|
|
b885ec4b80 | ||
|
|
e9e8e26fb5 | ||
|
|
1e2eb5ae2f | ||
|
|
d445ae7527 | ||
|
|
cef8fe8b0d | ||
|
|
98b66d0b5d | ||
|
|
81733002e5 | ||
|
|
80b729c944 | ||
|
|
c286ce3859 | ||
|
|
f31033de72 | ||
|
|
79e83e56ba | ||
|
|
09176b3ae6 | ||
|
|
ed85991683 | ||
|
|
cbf1dc790d | ||
|
|
8a1c1c4690 | ||
|
|
0c80b60c6d | ||
|
|
0f904e65f4 | ||
|
|
e1b5d6d557 | ||
|
|
eca6b1f4f0 | ||
|
|
7dfdae86bf | ||
|
|
cf491c5ad0 | ||
|
|
63cddda8de | ||
|
|
ad17f558ce | ||
|
|
4134818a85 | ||
|
|
85a7e888e7 | ||
|
|
062025873b | ||
|
|
2262cf00c7 | ||
|
|
ec022909de | ||
|
|
8de5ef1255 | ||
|
|
c6a0cfa172 | ||
|
|
5002a3df29 | ||
|
|
cac42bac12 | ||
|
|
1bef8004ac | ||
|
|
3b9efba8ab | ||
|
|
f10e1a21fe | ||
|
|
af36323e53 | ||
|
|
9c1ae577fd | ||
|
|
532516b4c4 | ||
|
|
02f3f821b5 | ||
|
|
52cd5f9881 | ||
|
|
d718aafc74 | ||
|
|
6f475b7acc | ||
|
|
ee2fc04fa1 | ||
|
|
a5217a49d3 | ||
|
|
c241ee54fe | ||
|
|
ff4ee6ced6 | ||
|
|
e72a3d34fd | ||
|
|
120b4ad981 | ||
|
|
0d35859ab1 | ||
|
|
db9e367633 | ||
|
|
2537aaa6ea | ||
|
|
e7c15b6154 | ||
|
|
9016e5020d | ||
|
|
f8d811463e | ||
|
|
b60b5d4225 | ||
|
|
e19befa2df | ||
|
|
5ca11c7515 | ||
|
|
4ec9aa3cb6 | ||
|
|
571d764209 | ||
|
|
5230a76442 | ||
|
|
284d8642f6 | ||
|
|
6b6fbab98d | ||
|
|
cc3e946880 | ||
|
|
e131aa70cf | ||
|
|
d643ea75a2 | ||
|
|
8f04c37275 | ||
|
|
5251e69830 | ||
|
|
33f24e5add | ||
|
|
1d16341d0f | ||
|
|
8220872eba | ||
|
|
73df3588e5 | ||
|
|
dbca67dec0 | ||
|
|
9fd46c2e43 | ||
|
|
9bfc39b316 | ||
|
|
e9fb6616d9 | ||
|
|
e4a4ab5912 | ||
|
|
9e7261e5d5 | ||
|
|
2af2e7c9cc | ||
|
|
2dc9eaf74e | ||
|
|
62318fedef | ||
|
|
d8c640d0a4 | ||
|
|
47f546fb12 | ||
|
|
fc0571aeda | ||
|
|
8695641c2a | ||
|
|
894e17aaef | ||
|
|
cd3b7e7fd9 | ||
|
|
ab6d12cc5f | ||
|
|
a8db7a90f1 | ||
|
|
19b4d0ac21 | ||
|
|
1dec7be713 | ||
|
|
92a0ef904f | ||
|
|
f389f834f0 | ||
|
|
c035a6be73 | ||
|
|
d1498486eb | ||
|
|
408a19ba9a | ||
|
|
9523aa9eeb | ||
|
|
3d74323be4 | ||
|
|
3d14e3f916 | ||
|
|
cce5bd8a7d | ||
|
|
62ccab6e96 | ||
|
|
9f8e7258b5 | ||
|
|
2ca00fed08 | ||
|
|
22cb32e197 | ||
|
|
e1391efbef | ||
|
|
4633ddc153 | ||
|
|
a96ccc3bfd | ||
|
|
14bcab8c8a | ||
|
|
40f0f3d194 | ||
|
|
32e5f6dd6b | ||
|
|
5f2235a91f | ||
|
|
deca12ddd3 | ||
|
|
31d6794490 | ||
|
|
90938d5f58 | ||
|
|
0c7768b36d | ||
|
|
d14c7810ee | ||
|
|
de5153337d | ||
|
|
bd405122e2 | ||
|
|
e7d6233796 | ||
|
|
25bb72c3dc | ||
|
|
487c626a73 | ||
|
|
6b0182ea70 | ||
|
|
ca6fe65580 | ||
|
|
498ed9dfff | ||
|
|
32a2a4ebf5 | ||
|
|
4bd1b70a5e | ||
|
|
71b78e8ec3 | ||
|
|
2d6fc18218 | ||
|
|
b7554a06d2 | ||
|
|
35a8fee3b3 | ||
|
|
4fe94e65ad | ||
|
|
80fa93716b | ||
|
|
0145593cba | ||
|
|
af712ddb56 | ||
|
|
f0deb93cb7 | ||
|
|
781cfc11c7 | ||
|
|
97e45ebc69 | ||
|
|
c4bc945b1e | ||
|
|
2bd12ed7b5 | ||
|
|
658d358e70 | ||
|
|
3e6a80eb37 | ||
|
|
5408d4f9c5 | ||
|
|
6e5fd9e25a | ||
|
|
9fbdafbe73 | ||
|
|
9403fb1759 | ||
|
|
691abb02d8 | ||
|
|
3bb99a9001 | ||
|
|
74e0149a6d | ||
|
|
c5d83e662c | ||
|
|
376c7c7d07 | ||
|
|
a52664e9e4 | ||
|
|
f573abe279 | ||
|
|
975af7c056 | ||
|
|
f2456d796c | ||
|
|
c7c9302109 | ||
|
|
989db4702c | ||
|
|
9de5f12f4b | ||
|
|
9338ebb561 | ||
|
|
0d9cdc5f1e | ||
|
|
f66c71ee55 | ||
|
|
d742bc097b | ||
|
|
53386a7f1a | ||
|
|
cfbc18deb9 | ||
|
|
bdf4631504 | ||
|
|
a0f93a81d2 | ||
|
|
d9b086f146 | ||
|
|
b8b3ac3516 | ||
|
|
118959795f | ||
|
|
c93aaeb9bf | ||
|
|
f42c82274f | ||
|
|
fff9c4f7a2 | ||
|
|
3eb5ccbbe2 | ||
|
|
cc61f3d4e0 | ||
|
|
209adc1e7a | ||
|
|
e26f06be8f | ||
|
|
c99b4bf4ab | ||
|
|
296e7abf14 | ||
|
|
c6ef07ceb0 | ||
|
|
4e937bd03a | ||
|
|
84943c5889 | ||
|
|
4ed71b7a63 | ||
|
|
4ab8078103 | ||
|
|
6b75a1ab1b | ||
|
|
76e3a3a25e | ||
|
|
9503de4716 | ||
|
|
3340ccec6f | ||
|
|
a90e099113 | ||
|
|
aa69495075 | ||
|
|
11eb779beb | ||
|
|
836e2b33df | ||
|
|
c63b72bf2e | ||
|
|
c20008dacc | ||
|
|
edacda1377 | ||
|
|
2f0ad2d639 | ||
|
|
745d76c74b | ||
|
|
3b7f49de55 | ||
|
|
e336bd98b2 | ||
|
|
f62e87cffa | ||
|
|
d4d716b1f5 | ||
|
|
6e89dd48bc | ||
|
|
2bbd572cb5 | ||
|
|
17543eaf94 | ||
|
|
4f4e9fa5c7 | ||
|
|
d0060604a6 | ||
|
|
9f8df2bbd4 | ||
|
|
139cca1ae8 | ||
|
|
32054adcb5 | ||
|
|
764dd1e995 | ||
|
|
b65fde069f | ||
|
|
bec68eb5c3 | ||
|
|
8dca76f9f0 | ||
|
|
13ed30e2bc | ||
|
|
a7ff79605d | ||
|
|
b5bebac3c2 | ||
|
|
756d78f78a | ||
|
|
7e55cb18b3 | ||
|
|
b834ef664a | ||
|
|
1ee0e16f9e | ||
|
|
63434d7b63 | ||
|
|
d9f993dc5b | ||
|
|
9090c13417 | ||
|
|
0630dafef9 | ||
|
|
150ab7b83a | ||
|
|
11cc569914 | ||
|
|
eaf58e0fc9 | ||
|
|
da994c5ecd | ||
|
|
cef4cc6a4d | ||
|
|
d761323887 | ||
|
|
0857270a48 | ||
|
|
df178369c0 | ||
|
|
8c3edf077d | ||
|
|
ac477a58c3 | ||
|
|
504f977739 | ||
|
|
00e813295b | ||
|
|
300d300c45 | ||
|
|
1514f9a737 | ||
|
|
f4fe271b39 | ||
|
|
1c01881eb7 | ||
|
|
af1564d8e5 | ||
|
|
35515ce157 | ||
|
|
fd62ab7a59 | ||
|
|
1557797926 | ||
|
|
90a8b479f6 | ||
|
|
c2fae6b2d7 | ||
|
|
89a3f4fc26 | ||
|
|
7aa94c74d7 | ||
|
|
33993418ac | ||
|
|
75a2d9d54c | ||
|
|
b1a3c6b237 | ||
|
|
437704abb3 | ||
|
|
0e09548e69 | ||
|
|
37834cb926 | ||
|
|
7dd3bb49f4 | ||
|
|
fdf1eaeaed | ||
|
|
4cc88c8b64 | ||
|
|
db331ecb72 | ||
|
|
c2757bec7d | ||
|
|
e074eac092 | ||
|
|
d5166c74e5 | ||
|
|
226e3c8212 | ||
|
|
a680915a89 | ||
|
|
b8ff06e5d2 | ||
|
|
5a108b64b0 | ||
|
|
00630587af | ||
|
|
b02cf925a1 | ||
|
|
aaa26575dd | ||
|
|
aebb3cef03 | ||
|
|
18a7ae3a1a | ||
|
|
6c84c67970 | ||
|
|
d449acde30 | ||
|
|
c1e700cefa | ||
|
|
30df03948c | ||
|
|
15ea081daa | ||
|
|
0f71741c46 | ||
|
|
585f0999a3 | ||
|
|
d51f645299 | ||
|
|
ebbe4eb802 | ||
|
|
d6c94f44d9 | ||
|
|
86345a6338 | ||
|
|
2c77d8a589 | ||
|
|
16cbbd4805 | ||
|
|
8aa77679c8 | ||
|
|
e6cff5bbb6 | ||
|
|
86ed24744c | ||
|
|
01ed3625dc | ||
|
|
cc9dea1f2b | ||
|
|
b16ade905c | ||
|
|
ee1bba7d99 | ||
|
|
3407097e95 | ||
|
|
47ac3e6082 | ||
|
|
75110bed47 | ||
|
|
74f0106718 | ||
|
|
958d50d9b7 | ||
|
|
5f4aa017b6 | ||
|
|
92c2a58f50 | ||
|
|
75bf0dffe6 | ||
|
|
45a10f0840 | ||
|
|
fbf3b1cd19 | ||
|
|
0cc4151929 | ||
|
|
e261ade1bb | ||
|
|
5d7e54d419 | ||
|
|
3499858295 | ||
|
|
1615e218bd | ||
|
|
9a3b859780 |
@@ -3,10 +3,6 @@ module.exports = {
|
||||
'@nextcloud',
|
||||
],
|
||||
rules: {
|
||||
'jsdoc/require-param-description': ['off'],
|
||||
'jsdoc/require-param-type': ['off'],
|
||||
'jsdoc/check-param-names': ['off'],
|
||||
'jsdoc/no-undefined-types': ['off'],
|
||||
'jsdoc/require-property-description' : ['off']
|
||||
'valid-jsdoc': ['off'],
|
||||
},
|
||||
}
|
||||
|
||||
38
.github/dependabot.yml
vendored
38
.github/dependabot.yml
vendored
@@ -11,6 +11,19 @@ updates:
|
||||
open-pull-requests-limit: 10
|
||||
reviewers:
|
||||
- juliushaertl
|
||||
- jakobroehrl
|
||||
#- package-ecosystem: npm
|
||||
# directory: "/"
|
||||
# target-branch: "stable1.1"
|
||||
# schedule:
|
||||
# interval: weekly
|
||||
# day: saturday
|
||||
# time: "03:00"
|
||||
# timezone: Europe/Paris
|
||||
# open-pull-requests-limit: 10
|
||||
# reviewers:
|
||||
# - juliushaertl
|
||||
# - jakobroehrl
|
||||
- package-ecosystem: composer
|
||||
directory: "/"
|
||||
schedule:
|
||||
@@ -21,23 +34,8 @@ updates:
|
||||
open-pull-requests-limit: 10
|
||||
reviewers:
|
||||
- juliushaertl
|
||||
- package-ecosystem: composer
|
||||
directory: "/tests/integration"
|
||||
schedule:
|
||||
interval: weekly
|
||||
day: saturday
|
||||
time: "03:00"
|
||||
timezone: Europe/Paris
|
||||
open-pull-requests-limit: 10
|
||||
reviewers:
|
||||
- juliushaertl
|
||||
- package-ecosystem: github-actions
|
||||
directory: "/"
|
||||
schedule:
|
||||
interval: weekly
|
||||
day: saturday
|
||||
time: "03:00"
|
||||
timezone: Europe/Paris
|
||||
open-pull-requests-limit: 10
|
||||
reviewers:
|
||||
- juliushaertl
|
||||
ignore:
|
||||
- dependency-name: christophwurst/nextcloud
|
||||
versions:
|
||||
- "< 16"
|
||||
- ">= 15.a"
|
||||
|
||||
25
.github/stale.yml
vendored
Normal file
25
.github/stale.yml
vendored
Normal file
@@ -0,0 +1,25 @@
|
||||
# Number of days of inactivity before an issue becomes stale
|
||||
daysUntilStale: 60
|
||||
# Number of days of inactivity before a stale issue is closed
|
||||
daysUntilClose: 7
|
||||
# Issues with these labels will never be considered stale
|
||||
exemptLabels:
|
||||
- "1. to develop"
|
||||
- "2. developing"
|
||||
- "3. to review"
|
||||
- "discussion"
|
||||
- "bounty"
|
||||
- "bug"
|
||||
- "enhancement"
|
||||
|
||||
# Limit the number of actions per hour, from 1-30. Default is 30
|
||||
limitPerRun: 30
|
||||
|
||||
# Label to use when marking an issue as stale
|
||||
staleLabel: stale
|
||||
|
||||
# Comment to post when marking an issue as stale. Set to `false` to disable
|
||||
markComment: >
|
||||
This issue has been automatically marked as stale because it has not had
|
||||
recent activity. It will be closed if no further activity occurs. Thank you
|
||||
for your contributions.
|
||||
8
.github/workflows/appbuild.yml
vendored
8
.github/workflows/appbuild.yml
vendored
@@ -12,15 +12,15 @@ jobs:
|
||||
node-version: [14.x]
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
- uses: actions/checkout@v1
|
||||
- name: Use Node.js ${{ matrix.node-version }}
|
||||
uses: actions/setup-node@v3
|
||||
uses: actions/setup-node@v1
|
||||
with:
|
||||
node-version: ${{ matrix.node-version }}
|
||||
- name: Set up npm7
|
||||
run: npm i -g npm@7
|
||||
- name: Setup PHP
|
||||
uses: shivammathur/setup-php@2.18.0
|
||||
uses: shivammathur/setup-php@v1
|
||||
with:
|
||||
php-version: '7.4'
|
||||
tools: composer
|
||||
@@ -33,7 +33,7 @@ jobs:
|
||||
uname -a
|
||||
RUST_BACKTRACE=1 krankerl --version
|
||||
RUST_BACKTRACE=1 krankerl package
|
||||
- uses: actions/upload-artifact@v3
|
||||
- uses: actions/upload-artifact@v2
|
||||
with:
|
||||
name: Deck app tarball
|
||||
path: build/artifacts/deck.tar.gz
|
||||
|
||||
2
.github/workflows/appstore-build-publish.yml
vendored
2
.github/workflows/appstore-build-publish.yml
vendored
@@ -66,7 +66,7 @@ jobs:
|
||||
run: npm i -g npm@"${{ steps.versions.outputs.npmVersion }}"
|
||||
|
||||
- name: Set up php ${{ env.PHP_VERSION }}
|
||||
uses: shivammathur/setup-php@2.18.0
|
||||
uses: shivammathur/setup-php@v2
|
||||
with:
|
||||
php-version: ${{ env.PHP_VERSION }}
|
||||
coverage: none
|
||||
|
||||
46
.github/workflows/command-rebase.yml
vendored
46
.github/workflows/command-rebase.yml
vendored
@@ -1,46 +0,0 @@
|
||||
# This workflow is provided via the organization template repository
|
||||
#
|
||||
# https://github.com/nextcloud/.github
|
||||
# https://docs.github.com/en/actions/learn-github-actions/sharing-workflows-with-your-organization
|
||||
|
||||
name: Rebase command
|
||||
|
||||
on:
|
||||
issue_comment:
|
||||
types: created
|
||||
|
||||
jobs:
|
||||
rebase:
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
# On pull requests and if the comment starts with `/rebase`
|
||||
if: github.event.issue.pull_request != '' && startsWith(github.event.comment.body, '/rebase')
|
||||
|
||||
steps:
|
||||
- name: Add reaction on start
|
||||
uses: peter-evans/create-or-update-comment@v2
|
||||
with:
|
||||
token: ${{ secrets.COMMAND_BOT_PAT }}
|
||||
repository: ${{ github.event.repository.full_name }}
|
||||
comment-id: ${{ github.event.comment.id }}
|
||||
reaction-type: "+1"
|
||||
|
||||
- name: Checkout the latest code
|
||||
uses: actions/checkout@v3
|
||||
with:
|
||||
fetch-depth: 0
|
||||
token: ${{ secrets.COMMAND_BOT_PAT }}
|
||||
|
||||
- name: Automatic Rebase
|
||||
uses: cirrus-actions/rebase@1.5
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.COMMAND_BOT_PAT }}
|
||||
|
||||
- name: Add reaction on failure
|
||||
uses: peter-evans/create-or-update-comment@v2
|
||||
if: failure()
|
||||
with:
|
||||
token: ${{ secrets.COMMAND_BOT_PAT }}
|
||||
repository: ${{ github.event.repository.full_name }}
|
||||
comment-id: ${{ github.event.comment.id }}
|
||||
reaction-type: "-1"
|
||||
29
.github/workflows/dependabot-approve-merge.yml
vendored
29
.github/workflows/dependabot-approve-merge.yml
vendored
@@ -1,29 +0,0 @@
|
||||
# This workflow is provided via the organization template repository
|
||||
#
|
||||
# https://github.com/nextcloud/.github
|
||||
# https://docs.github.com/en/actions/learn-github-actions/sharing-workflows-with-your-organization
|
||||
|
||||
name: Dependabot
|
||||
|
||||
on:
|
||||
pull_request_target:
|
||||
branches:
|
||||
- master
|
||||
- stable*
|
||||
|
||||
jobs:
|
||||
auto-approve-merge:
|
||||
if: github.actor == 'dependabot[bot]'
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
steps:
|
||||
# Github actions bot approve
|
||||
- uses: hmarr/auto-approve-action@v2
|
||||
with:
|
||||
github-token: ${{ secrets.GITHUB_TOKEN }}
|
||||
|
||||
# Nextcloud bot approve and merge request
|
||||
- uses: ahmadnassri/action-dependabot-auto-merge@v2
|
||||
with:
|
||||
target: minor
|
||||
github-token: ${{ secrets.DEPENDABOT_AUTOMERGE_TOKEN }}
|
||||
20
.github/workflows/fixup.yml
vendored
20
.github/workflows/fixup.yml
vendored
@@ -1,20 +0,0 @@
|
||||
# This workflow is provided via the organization template repository
|
||||
#
|
||||
# https://github.com/nextcloud/.github
|
||||
# https://docs.github.com/en/actions/learn-github-actions/sharing-workflows-with-your-organization
|
||||
|
||||
name: Pull request checks
|
||||
|
||||
on: pull_request
|
||||
|
||||
jobs:
|
||||
commit-message-check:
|
||||
name: Block fixup and squash commits
|
||||
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
steps:
|
||||
- name: Run check
|
||||
uses: xt0rted/block-autosquash-commits-action@v2
|
||||
with:
|
||||
repo-token: ${{ secrets.GITHUB_TOKEN }}
|
||||
11
.github/workflows/integration.yml
vendored
11
.github/workflows/integration.yml
vendored
@@ -19,13 +19,13 @@ jobs:
|
||||
matrix:
|
||||
php-versions: ['7.4']
|
||||
databases: ['sqlite', 'mysql', 'pgsql']
|
||||
server-versions: ['stable24']
|
||||
server-versions: ['stable22']
|
||||
|
||||
name: php${{ matrix.php-versions }}-${{ matrix.databases }}-${{ matrix.server-versions }}
|
||||
|
||||
services:
|
||||
postgres:
|
||||
image: postgres:14
|
||||
image: postgres
|
||||
ports:
|
||||
- 4445:5432/tcp
|
||||
env:
|
||||
@@ -43,7 +43,7 @@ jobs:
|
||||
|
||||
steps:
|
||||
- name: Checkout server
|
||||
uses: actions/checkout@v3
|
||||
uses: actions/checkout@v2
|
||||
with:
|
||||
repository: nextcloud/server
|
||||
ref: ${{ matrix.server-versions }}
|
||||
@@ -54,15 +54,14 @@ jobs:
|
||||
auth_header="$(git config --local --get http.https://github.com/.extraheader)"
|
||||
git submodule sync --recursive
|
||||
git -c "http.extraheader=$auth_header" -c protocol.version=2 submodule update --init --force --recursive --depth=1
|
||||
cd build/integration && composer require --dev phpunit/phpunit:~8
|
||||
|
||||
- name: Checkout app
|
||||
uses: actions/checkout@v3
|
||||
uses: actions/checkout@v2
|
||||
with:
|
||||
path: apps/${{ env.APP_NAME }}
|
||||
|
||||
- name: Set up php ${{ matrix.php-versions }}
|
||||
uses: shivammathur/setup-php@2.18.0
|
||||
uses: shivammathur/setup-php@v2
|
||||
with:
|
||||
php-version: ${{ matrix.php-versions }}
|
||||
tools: phpunit
|
||||
|
||||
26
.github/workflows/lint.yml
vendored
26
.github/workflows/lint.yml
vendored
@@ -13,13 +13,13 @@ jobs:
|
||||
|
||||
strategy:
|
||||
matrix:
|
||||
php-versions: ['7.4', '8.0', '8.1']
|
||||
php-versions: ['7.3', '7.4']
|
||||
|
||||
name: php${{ matrix.php-versions }} lint
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
- uses: actions/checkout@v2
|
||||
- name: Set up php${{ matrix.php-versions }}
|
||||
uses: shivammathur/setup-php@2.18.0
|
||||
uses: shivammathur/setup-php@v1
|
||||
with:
|
||||
php-version: ${{ matrix.php-versions }}
|
||||
coverage: none
|
||||
@@ -31,9 +31,9 @@ jobs:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v3
|
||||
uses: actions/checkout@master
|
||||
- name: Set up php
|
||||
uses: shivammathur/setup-php@2.18.0
|
||||
uses: shivammathur/setup-php@master
|
||||
with:
|
||||
php-version: 7.4
|
||||
coverage: none
|
||||
@@ -50,9 +50,9 @@ jobs:
|
||||
node-version: [14.x]
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
- uses: actions/checkout@v2
|
||||
- name: Use node ${{ matrix.node-version }}
|
||||
uses: actions/setup-node@v3
|
||||
uses: actions/setup-node@v1
|
||||
with:
|
||||
node-version: ${{ matrix.node-version }}
|
||||
- name: Set up npm7
|
||||
@@ -67,16 +67,16 @@ jobs:
|
||||
|
||||
strategy:
|
||||
matrix:
|
||||
node-version: [14.x]
|
||||
node-versions: [14.x]
|
||||
|
||||
name: stylelint node${{ matrix.node-version }}
|
||||
name: stylelint node${{ matrix.node-versions }}
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
- uses: actions/checkout@v2
|
||||
|
||||
- name: Set up node ${{ matrix.node-version }}
|
||||
uses: actions/setup-node@v3
|
||||
- name: Set up node ${{ matrix.node-versions }}
|
||||
uses: actions/setup-node@v1
|
||||
with:
|
||||
node-version: ${{ matrix.node-version }}
|
||||
node-versions: ${{ matrix.node-versions }}
|
||||
|
||||
- name: Set up npm7
|
||||
run: npm i -g npm@7
|
||||
|
||||
6
.github/workflows/nightly.yml
vendored
6
.github/workflows/nightly.yml
vendored
@@ -17,15 +17,15 @@ jobs:
|
||||
node-version: [14.x]
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
- uses: actions/checkout@v1
|
||||
- name: Use Node.js ${{ matrix.node-version }}
|
||||
uses: actions/setup-node@v3
|
||||
uses: actions/setup-node@v1
|
||||
with:
|
||||
node-version: ${{ matrix.node-version }}
|
||||
- name: Set up npm7
|
||||
run: npm i -g npm@7
|
||||
- name: Setup PHP
|
||||
uses: shivammathur/setup-php@2.18.0
|
||||
uses: shivammathur/setup-php@v1
|
||||
with:
|
||||
php-version: '7.4'
|
||||
tools: composer
|
||||
|
||||
4
.github/workflows/nodejs.yml
vendored
4
.github/workflows/nodejs.yml
vendored
@@ -12,9 +12,9 @@ jobs:
|
||||
node-version: [14.x]
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
- uses: actions/checkout@v1
|
||||
- name: Use Node.js ${{ matrix.node-version }}
|
||||
uses: actions/setup-node@v3
|
||||
uses: actions/setup-node@v1
|
||||
with:
|
||||
node-version: ${{ matrix.node-version }}
|
||||
- name: Set up npm7
|
||||
|
||||
12
.github/workflows/phpunit.yml
vendored
12
.github/workflows/phpunit.yml
vendored
@@ -18,15 +18,15 @@ jobs:
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
php-versions: ['7.4', '8.0', '8.1']
|
||||
php-versions: ['7.3', '7.4']
|
||||
databases: ['sqlite', 'mysql', 'pgsql']
|
||||
server-versions: ['stable24']
|
||||
server-versions: ['stable22']
|
||||
|
||||
name: php${{ matrix.php-versions }}-${{ matrix.databases }}-${{ matrix.server-versions }}
|
||||
|
||||
services:
|
||||
postgres:
|
||||
image: postgres:14
|
||||
image: postgres
|
||||
ports:
|
||||
- 4445:5432/tcp
|
||||
env:
|
||||
@@ -44,7 +44,7 @@ jobs:
|
||||
|
||||
steps:
|
||||
- name: Checkout server
|
||||
uses: actions/checkout@v3
|
||||
uses: actions/checkout@v2
|
||||
with:
|
||||
repository: nextcloud/server
|
||||
ref: ${{ matrix.server-versions }}
|
||||
@@ -57,12 +57,12 @@ jobs:
|
||||
git -c "http.extraheader=$auth_header" -c protocol.version=2 submodule update --init --force --recursive --depth=1
|
||||
|
||||
- name: Checkout app
|
||||
uses: actions/checkout@v3
|
||||
uses: actions/checkout@v2
|
||||
with:
|
||||
path: apps/${{ env.APP_NAME }}
|
||||
|
||||
- name: Set up php ${{ matrix.php-versions }}
|
||||
uses: shivammathur/setup-php@2.18.0
|
||||
uses: shivammathur/setup-php@v2
|
||||
with:
|
||||
php-version: ${{ matrix.php-versions }}
|
||||
tools: phpunit
|
||||
|
||||
35
.github/workflows/psalm.yml
vendored
35
.github/workflows/psalm.yml
vendored
@@ -1,35 +0,0 @@
|
||||
# This workflow is provided via the organization template repository
|
||||
#
|
||||
# https://github.com/nextcloud/.github
|
||||
# https://docs.github.com/en/actions/learn-github-actions/sharing-workflows-with-your-organization
|
||||
|
||||
name: Static analysis
|
||||
|
||||
on:
|
||||
pull_request:
|
||||
push:
|
||||
branches:
|
||||
- master
|
||||
- main
|
||||
- stable*
|
||||
|
||||
jobs:
|
||||
static-analysis:
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
name: Nextcloud
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v3
|
||||
|
||||
- name: Set up php
|
||||
uses: shivammathur/setup-php@v2
|
||||
with:
|
||||
php-version: 7.4
|
||||
coverage: none
|
||||
|
||||
- name: Install dependencies
|
||||
run: composer i
|
||||
|
||||
- name: Run coding standards check
|
||||
run: composer run psalm
|
||||
31
.github/workflows/static-analysis.yml
vendored
Normal file
31
.github/workflows/static-analysis.yml
vendored
Normal file
@@ -0,0 +1,31 @@
|
||||
name: Static analysis
|
||||
|
||||
on:
|
||||
pull_request:
|
||||
push:
|
||||
branches:
|
||||
- master
|
||||
- stable*
|
||||
|
||||
jobs:
|
||||
static-psalm-analysis:
|
||||
runs-on: ubuntu-latest
|
||||
strategy:
|
||||
matrix:
|
||||
ocp-version: [ 'dev-stable22' ]
|
||||
name: Nextcloud ${{ matrix.ocp-version }}
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@master
|
||||
- name: Set up php
|
||||
uses: shivammathur/setup-php@master
|
||||
with:
|
||||
php-version: 7.4
|
||||
tools: composer:v1
|
||||
coverage: none
|
||||
- name: Install dependencies
|
||||
run: composer i
|
||||
- name: Install dependencies
|
||||
run: composer require --dev christophwurst/nextcloud:${{ matrix.ocp-version }}
|
||||
- name: Run coding standards check
|
||||
run: composer run psalm
|
||||
65
.github/workflows/update-nextcloud-ocp.yml
vendored
65
.github/workflows/update-nextcloud-ocp.yml
vendored
@@ -1,65 +0,0 @@
|
||||
# This workflow is provided via the organization template repository
|
||||
#
|
||||
# https://github.com/nextcloud/.github
|
||||
# https://docs.github.com/en/actions/learn-github-actions/sharing-workflows-with-your-organization
|
||||
|
||||
name: Update nextcloud/ocp
|
||||
|
||||
on:
|
||||
workflow_dispatch:
|
||||
schedule:
|
||||
- cron: "5 2 * * 0"
|
||||
|
||||
jobs:
|
||||
update-nextcloud-ocp:
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
branches: ["master", "stable25", "stable24", "stable23"]
|
||||
|
||||
name: update-nextcloud-ocp-${{ matrix.branches }}
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
with:
|
||||
ref: ${{ matrix.branches }}
|
||||
submodules: true
|
||||
|
||||
- name: Set up php7.4
|
||||
uses: shivammathur/setup-php@v2
|
||||
with:
|
||||
php-version: 7.4
|
||||
extensions: ctype,curl,dom,fileinfo,gd,intl,json,mbstring,openssl,pdo_sqlite,posix,sqlite,xml,zip
|
||||
coverage: none
|
||||
|
||||
- name: Composer install
|
||||
run: composer install
|
||||
|
||||
- name: Composer update nextcloud/ocp
|
||||
run: composer require --dev nextcloud/ocp:dev-${{ matrix.branches }}
|
||||
continue-on-error: true
|
||||
|
||||
- name: Reset checkout dirs
|
||||
run: |
|
||||
git clean -f 3rdparty
|
||||
git clean -f vendor
|
||||
git checkout 3rdparty vendor
|
||||
continue-on-error: true
|
||||
|
||||
- name: Create Pull Request
|
||||
uses: peter-evans/create-pull-request@v3
|
||||
with:
|
||||
token: ${{ secrets.COMMAND_BOT_PAT }}
|
||||
commit-message: Update psalm baseline
|
||||
committer: GitHub <noreply@github.com>
|
||||
author: nextcloud-command <nextcloud-command@users.noreply.github.com>
|
||||
signoff: true
|
||||
branch: automated/noid/${{ matrix.branches }}-update-nextcloud-ocp
|
||||
title: "[${{ matrix.branches }}] Update nextcloud/ocp dependency"
|
||||
body: |
|
||||
Auto-generated update of [nextcloud/ocp](https://github.com/nextcloud-deps/ocp/) dependency
|
||||
labels: |
|
||||
dependencies
|
||||
3. to review
|
||||
221
CHANGELOG.md
221
CHANGELOG.md
@@ -1,115 +1,124 @@
|
||||
# Changelog
|
||||
All notable changes to this project will be documented in this file.
|
||||
|
||||
## 1.7.2
|
||||
|
||||
### Fixed
|
||||
|
||||
- Cache user membership for circles [#4132](https://github.com/nextcloud/deck/pull/4132)
|
||||
- Set event link also for notifications that get emitted from activities [#4118](https://github.com/nextcloud/deck/pull/4118)
|
||||
- Fix Card menu not displaying when description is not set [#4103](https://github.com/nextcloud/deck/pull/4103)
|
||||
- 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.7.1
|
||||
## 1.5.8
|
||||
|
||||
### Fixed
|
||||
- Align Duedate-delete icon properly - fixes nextcloud/deck#3791 [#3817](https://github.com/nextcloud/deck/pull/3817)
|
||||
- Increase file count after sharing [#3806](https://github.com/nextcloud/deck/pull/3806)
|
||||
- Show cards after moving into another list [#3794](https://github.com/nextcloud/deck/pull/3794)
|
||||
- Fetch full board data after cloning [#3781](https://github.com/nextcloud/deck/pull/3781)
|
||||
|
||||
## 1.7.0
|
||||
|
||||
## 1.5.7
|
||||
|
||||
### Added
|
||||
|
||||
- Transfer ownership @matchish @luka-nextcloud @juliushaertl [#2496](https://github.com/nextcloud/deck/pull/2496)
|
||||
- Import from trello via CLI @vitormattos [#3182](https://github.com/nextcloud/deck/pull/3182)
|
||||
- Add app config to toggle the default calendar setting as an admin @juliushaertl [#3528](https://github.com/nextcloud/deck/pull/3528)
|
||||
- Show board name in browser title @luka-nextcloud [#3499](https://github.com/nextcloud/deck/pull/3499)
|
||||
- Move DeleteCron to be time insensitive @juliushaertl [#3599](https://github.com/nextcloud/deck/pull/3599)
|
||||
- 🚸 Shows error on board fetchData @vinicius73 [#3653](https://github.com/nextcloud/deck/pull/3653)
|
||||
- Add support for PHP 8.1 @juliushaertl [#3601](https://github.com/nextcloud/deck/pull/3601)
|
||||
- Nextcloud 24 compatibility
|
||||
- Transfer ownership @juliushaertl [#3665](https://github.com/nextcloud/deck/pull/3665)
|
||||
|
||||
### Fixed
|
||||
|
||||
- CardApiController: Fix order of optional parameters @simonspa [#3512](https://github.com/nextcloud/deck/pull/3512)
|
||||
- Exclude deleted boards in the selection for target @luka-nextcloud [#3502](https://github.com/nextcloud/deck/pull/3502)
|
||||
- Fix CalDAV blocking and modernize circles API usage @juliushaertl [#3500](https://github.com/nextcloud/deck/pull/3500)
|
||||
- Timestamps on created and modified at values @luka-nextcloud [#3532](https://github.com/nextcloud/deck/pull/3532)
|
||||
- return the selector for collections @dartcafe [#3552](https://github.com/nextcloud/deck/pull/3552)
|
||||
- Generate fixed link for activity emails @luka-nextcloud [#3611](https://github.com/nextcloud/deck/pull/3611)
|
||||
- 🐛 Fix missing files sidebar @vinicius73 [#3635](https://github.com/nextcloud/deck/pull/3635)
|
||||
- Handle description shortening more gracefully @juliushaertl [#3650](https://github.com/nextcloud/deck/pull/3650)
|
||||
- Sort boards non case sensitive @Ben-Ro [#3560](https://github.com/nextcloud/deck/pull/3560)
|
||||
- Remove unused argument from transfer ownership @juliushaertl [#3712](https://github.com/nextcloud/deck/pull/3712)
|
||||
- Fix: Check all circle shares for permissions @bink [#3625](https://github.com/nextcloud/deck/pull/3625)
|
||||
- Extend API changelog @juliushaertl [#3522](https://github.com/nextcloud/deck/pull/3522)
|
||||
- Fix talk integration @nickvergessen [#3529](https://github.com/nextcloud/deck/pull/3529)
|
||||
- Fix confusion between stackId and boardId in StackService @eneiluj [#3541](https://github.com/nextcloud/deck/pull/3541)
|
||||
- Add horizontal scrollbar into the large table inside description @luka-nextcloud [#3531](https://github.com/nextcloud/deck/pull/3531)
|
||||
- Make links in markdown note bolder @luka-nextcloud [#3530](https://github.com/nextcloud/deck/pull/3530)
|
||||
- Update master php testing versions @nickvergessen [#3561](https://github.com/nextcloud/deck/pull/3561)
|
||||
- Update master php enviroment @nickvergessen [#3582](https://github.com/nextcloud/deck/pull/3582)
|
||||
- Make insert attachment buttom easy to click @luka-nextcloud [#3612](https://github.com/nextcloud/deck/pull/3612)
|
||||
- Remove extra bullet @elitejake [#3613](https://github.com/nextcloud/deck/pull/3613)
|
||||
- l10n: Delete space @Valdnet [#3666](https://github.com/nextcloud/deck/pull/3666)
|
||||
- Update master php testing versions @nickvergessen [#3688](https://github.com/nextcloud/deck/pull/3688)
|
||||
- Fix wording to represent the code behavior @q-wertz [#3685](https://github.com/nextcloud/deck/pull/3685)
|
||||
- Fix cron jobs @nickvergessen [#3689](https://github.com/nextcloud/deck/pull/3689)
|
||||
- Update master php testing versions @nickvergessen [#3695](https://github.com/nextcloud/deck/pull/3695)
|
||||
- Optimise queries when preparing card related notifications @Raudius [#3690](https://github.com/nextcloud/deck/pull/3690)
|
||||
- Properly check for the stack AND setting board permissions @juliushaertl [#3670](https://github.com/nextcloud/deck/pull/3670)
|
||||
- Replace deprecated String.prototype.substr() @CommanderRoot [#3669](https://github.com/nextcloud/deck/pull/3669)
|
||||
- Dependency updates
|
||||
- Show cards after moving into another list [#3736](https://github.com/nextcloud/deck/pull/3736)
|
||||
- Fix paramter replacements when creating deck cards from talk messages @nickvergessen [#3683](https://github.com/nextcloud/deck/pull/3683)
|
||||
- Fix hidden attachment icon on archived cards [#3733](https://github.com/nextcloud/deck/pull/3733)
|
||||
- Adapt the card modal to upstream changes [#3764](https://github.com/nextcloud/deck/pull/3764)
|
||||
- Fix text selection in dark mode and modal view [#3765](https://github.com/nextcloud/deck/pull/3765)
|
||||
- Add missing indices [#3754](https://github.com/nextcloud/deck/pull/3754)
|
||||
- Handle qb mapper exception messages properly @juliushaertl [#3769](https://github.com/nextcloud/deck/pull/3769)
|
||||
- Fix: Check all circle shares for permissions [#3721](https://github.com/nextcloud/deck/pull/3721)
|
||||
- Add a missing translation - not found in transifex [#3707](https://github.com/nextcloud/deck/pull/3707)
|
||||
- 🐛 Fix missing files sidebar [#3642](https://github.com/nextcloud/deck/pull/3642)
|
||||
- [stable23] Use explicit cast to make use of index [#3731](https://github.com/nextcloud/deck/pull/3731)
|
||||
- Fix hidden attachment icon on archived cards [#3735](https://github.com/nextcloud/deck/pull/3735)
|
||||
- [stable23] Sort boards non case sensitive [#3739](https://github.com/nextcloud/deck/pull/3739)
|
||||
- add autofocus on board edit #3326 @juliushaertl [#3743](https://github.com/nextcloud/deck/pull/3743)
|
||||
- Fix paramter replacements when creating deck cards from talk messages @juliushaertl [#3742](https://github.com/nextcloud/deck/pull/3742)
|
||||
- Fix text selection in dark mode and modal view [#3767](https://github.com/nextcloud/deck/pull/3767)
|
||||
|
||||
## 1.6.0-beta1
|
||||
### Other
|
||||
|
||||
- Properly check for the stack AND setting board permissions [#3714](https://github.com/nextcloud/deck/pull/3714)
|
||||
- Add missing indices [#3756](https://github.com/nextcloud/deck/pull/3756)
|
||||
|
||||
|
||||
## 1.5.6
|
||||
|
||||
### Fixed
|
||||
|
||||
- Allow to download an attachment without navigating to the files app [#3441](https://api.github.com/repos/nextcloud/deck/pulls/3441)
|
||||
- Fix CalDAV blocking and modernize circles API usage [#3527](https://api.github.com/repos/nextcloud/deck/pulls/3527)
|
||||
- CardApiController: Fix order of optional parameters [#3521](https://api.github.com/repos/nextcloud/deck/pulls/3521)
|
||||
- Fix cursor generation if no results are found [#3460](https://api.github.com/repos/nextcloud/deck/pulls/3460)
|
||||
- Exclude deleted boards in the selection for target [#3524](https://api.github.com/repos/nextcloud/deck/pulls/3524)
|
||||
- Generate fixed link for activity emails [#3627](https://api.github.com/repos/nextcloud/deck/pulls/3627)
|
||||
- Make insert attachment buttom easy to click [#3615](https://api.github.com/repos/nextcloud/deck/pulls/3615)
|
||||
- Fix confusion between stackId and boardId in StackService [#3544](https://api.github.com/repos/nextcloud/deck/pulls/3544)
|
||||
|
||||
## 1.5.5
|
||||
|
||||
- Fix release asset build
|
||||
|
||||
## 1.5.4
|
||||
|
||||
### Fixed
|
||||
|
||||
- #3378 Fix menu button position in card modal
|
||||
- #3392 Use displayname instead of uid for mentions (reopened against master)
|
||||
- #3361 Improve combined search @eneiluj
|
||||
- #3381 Extend drag-and-drop zone in card sidebar @Artem4590
|
||||
- #3366 Fix optional parameter order
|
||||
- #3407 Keep exceptions http response generic
|
||||
|
||||
|
||||
## 1.5.3
|
||||
|
||||
### Fied
|
||||
|
||||
- #3317 Additional check for stacks
|
||||
|
||||
|
||||
## 1.5.2
|
||||
|
||||
### Fixed
|
||||
|
||||
- #3300 Fix print style issues
|
||||
- #3303 Delete file shares through attachments API
|
||||
- #3306 Return false instead of throwing when getting calendar setting
|
||||
|
||||
## 1.5.1 - 2021-09-03
|
||||
|
||||
### Fixed
|
||||
|
||||
- #3224 Move circle checks to a unified service and improve member checks
|
||||
- #3231 Check for null value to avoid TypeError in the group manager
|
||||
- #3264 Defer obtaining the user session in the config service
|
||||
|
||||
|
||||
## 1.5.0 - 2021-07-09
|
||||
|
||||
### Added
|
||||
|
||||
- #3177 Use async import for vue component on collections entrypoint @juliushaertl
|
||||
- #2791 Open description links in new tab @fm-sys
|
||||
- #3344 Improve combined search @eneiluj
|
||||
- #3362 Improve search performance @eneiluj
|
||||
- #2710 Due date shortcuts in the datepicker @jakobroehrl
|
||||
* Nextcloud 22 compatibility
|
||||
* [#3105](https://github.com/nextcloud/deck/pull/3105) Compatibility with Cirlces changes in 22
|
||||
* [#3147](https://github.com/nextcloud/deck/pull/3147) Add card button to the dashboard widget @jakobroehrl
|
||||
* [#2854](https://github.com/nextcloud/deck/pull/2854) Add card button in card overview @jakobroehrl
|
||||
* [#3078](https://github.com/nextcloud/deck/pull/3078) Show on shared boards unassigned cards to all users @jakobroehrl
|
||||
|
||||
### Fixed
|
||||
|
||||
- #3161 Reduce duplicate queries when fetching user boards an permissions @juliushaertl
|
||||
- #3151 Always log generic exceptions @juliushaertl
|
||||
- #3217 Move circle checks to a unified service and improve member checks @juliushaertl
|
||||
- #3225 Check for null value to avoid TypeError in the group manager @juliushaertl
|
||||
- #3263 Defer obtaining the user session in the config service @juliushaertl
|
||||
- #3294 Fix print style issues @weeman1337
|
||||
- #3299 Return false instead of throwing when getting calendar setting @juliushaertl
|
||||
- #3298 Delete file shares through attachments API @juliushaertl
|
||||
- #3343 Fix search pagination cursor @eneiluj
|
||||
- #3326 add autofocus on board edit @weeman1337
|
||||
- #3323 Extend drag-and-drop zone in card sidebar @old-green-frog
|
||||
- #3364 Fix optional parameter order @juliushaertl
|
||||
- #3324 Fix menu button position in card modal @valerydmitrieva
|
||||
- #3391 Use displayname instead of uid for mentions (reopened against master) @kffl
|
||||
- #3316 Additional check for stacks @juliushaertl
|
||||
- #3357 Revert "Fix search pagination cursor" @juliushaertl
|
||||
- #3327 Do not show both bullets and checkboxes for checklists @Themanwhosmellslikesugar
|
||||
- #3375 Show absolute dates when printing @weeman1337
|
||||
- #3376 Print assignee names @weeman1337
|
||||
- #3384 Keep exceptions http response generic @juliushaertl
|
||||
* [#2935](https://github.com/nextcloud/deck/pull/2935) Rich object string parameters for notifications @nickvergessen
|
||||
* [#2950](https://github.com/nextcloud/deck/pull/2950) Remove notification on unshare and add type hints
|
||||
* [#2983](https://github.com/nextcloud/deck/pull/2983) Fix codemirror description width
|
||||
* [#2989](https://github.com/nextcloud/deck/pull/2989) Fix unified comments search with postgres
|
||||
* [#3005](https://github.com/nextcloud/deck/pull/3005) Do not query the lookupserver when looking for sharees
|
||||
* [#3011](https://github.com/nextcloud/deck/pull/3011) L10n: Spelling unification @Valdnet
|
||||
* [#3014](https://github.com/nextcloud/deck/pull/3014) Proper error handling when fetching comments fails
|
||||
* [#3016](https://github.com/nextcloud/deck/pull/3016) Allow searching for filters without a query to match all that have a given filter set
|
||||
* [#3021](https://github.com/nextcloud/deck/pull/3021) L10n: Add word "Card" @Valdnet
|
||||
* [#3025](https://github.com/nextcloud/deck/pull/3025) Show comment counter and highlight if unread comments are available
|
||||
* [#3036](https://github.com/nextcloud/deck/pull/3036) Add link to migration tool for Trello @maxammann
|
||||
* [#3037](https://github.com/nextcloud/deck/pull/3037) Catch any error during circle detail fetching
|
||||
* [#3038](https://github.com/nextcloud/deck/pull/3038) Get attachment from the user node instead of the share source
|
||||
* [#3092](https://github.com/nextcloud/deck/pull/3092) Refactor update to have proper order of optional parameters
|
||||
* [#3113](https://github.com/nextcloud/deck/pull/3113) Use new viewer syntax with destructuring object @azul
|
||||
* [#3142](https://github.com/nextcloud/deck/pull/3142) Always pass user id in share provider
|
||||
* [#3152](https://github.com/nextcloud/deck/pull/3152) Only offer stack creation in emptycontent with proper permissions
|
||||
* [#3165](https://github.com/nextcloud/deck/pull/3165) Always log generic exceptions
|
||||
* [#3168](https://github.com/nextcloud/deck/pull/3168) Reduce duplicate queries when fetching user boards an permissions
|
||||
|
||||
|
||||
|
||||
## 1.4.0 - 2021-04-13
|
||||
@@ -146,15 +155,15 @@ All notable changes to this project will be documented in this file.
|
||||
## 1.3.0-beta2
|
||||
|
||||
### Fixed
|
||||
* [#2700](https://github.com/nextcloud/deck/pull/2700) Attempt to copy file on dropping it to deck @juliushaertl
|
||||
* [#2701](https://github.com/nextcloud/deck/pull/2701) Fix uploading files by drag and drop @juliushaertl
|
||||
* [#2700](https://github.com/nextcloud/deck/pull/2700) Attempt to copy file on dropping it to deck
|
||||
* [#2701](https://github.com/nextcloud/deck/pull/2701) Fix uploading files by drag and drop
|
||||
* [#2707](https://github.com/nextcloud/deck/pull/2707) L10n: Change to a capital letter @Valdnet
|
||||
* [#2712](https://github.com/nextcloud/deck/pull/2712) Docs: Fix table in section "GET /api/v1.0/config" @das-g
|
||||
* [#2716](https://github.com/nextcloud/deck/pull/2716) Remove repair step which is no longer needed as we cleanup properly @juliushaertl
|
||||
* [#2716](https://github.com/nextcloud/deck/pull/2716) Remove repair step which is no longer needed as we cleanup properly
|
||||
* [#2723](https://github.com/nextcloud/deck/pull/2723) Pad random color with leading zeroes @PVince81
|
||||
* [#2729](https://github.com/nextcloud/deck/pull/2729) Remove invalid activity parameters @nickvergessen
|
||||
* [#2750](https://github.com/nextcloud/deck/pull/2750) Fix deck activity emails not being translated @nickvergessen
|
||||
* [#2751](https://github.com/nextcloud/deck/pull/2751) Properly set author for activity events that are triggered by cron @juliushaertl
|
||||
* [#2751](https://github.com/nextcloud/deck/pull/2751) Properly set author for activity events that are triggered by cron
|
||||
|
||||
|
||||
## 1.2.2 - 2020-11-24
|
||||
@@ -263,31 +272,31 @@ All notable changes to this project will be documented in this file.
|
||||
### Fixed
|
||||
|
||||
|
||||
* [#2116](https://github.com/nextcloud/deck/pull/2116) Fix navigation layout issues @juliushaertl
|
||||
* [#2118](https://github.com/nextcloud/deck/pull/2118) Use proper parameter when handling attachments @juliushaertl
|
||||
* [#2116](https://github.com/nextcloud/deck/pull/2116) Fix navigation layout issues
|
||||
* [#2118](https://github.com/nextcloud/deck/pull/2118) Use proper parameter when handling attachments
|
||||
|
||||
## 1.0.4 - 2020-06-26
|
||||
|
||||
### Fixed
|
||||
|
||||
* [#2062](https://github.com/nextcloud/deck/pull/2062) Fix saving card description after toggling checkboxes @juliushaertl
|
||||
* [#2062](https://github.com/nextcloud/deck/pull/2062) Fix saving card description after toggling checkboxes
|
||||
* [#2065](https://github.com/nextcloud/deck/pull/2065) Adding CSS rule for Markdown Blockquotes @reox
|
||||
* [#2059](https://github.com/nextcloud/deck/pull/2059) Fix fetching attachments on card change @juliushaertl
|
||||
* [#2060](https://github.com/nextcloud/deck/pull/2060) Use mixing for relative date in card sidebar @juliushaertl
|
||||
* [#2059](https://github.com/nextcloud/deck/pull/2059) Fix fetching attachments on card change
|
||||
* [#2060](https://github.com/nextcloud/deck/pull/2060) Use mixing for relative date in card sidebar
|
||||
|
||||
|
||||
## 1.0.3 - 2020-06-19
|
||||
|
||||
### Fixed
|
||||
|
||||
* [#2019](https://github.com/nextcloud/deck/pull/2019) Remove old global css rule @juliushaertl
|
||||
* [#2020](https://github.com/nextcloud/deck/pull/2020) Fix navigation issue with leftover nodes @juliushaertl
|
||||
* [#2021](https://github.com/nextcloud/deck/pull/2021) Fix description issues @juliushaertl
|
||||
* [#2022](https://github.com/nextcloud/deck/pull/2022) Fix replyto issues with the comments API @juliushaertl
|
||||
* [#2027](https://github.com/nextcloud/deck/pull/2027) Allow to unassign current user from card @juliushaertl
|
||||
* [#2019](https://github.com/nextcloud/deck/pull/2019) Remove old global css rule
|
||||
* [#2020](https://github.com/nextcloud/deck/pull/2020) Fix navigation issue with leftover nodes
|
||||
* [#2021](https://github.com/nextcloud/deck/pull/2021) Fix description issues
|
||||
* [#2022](https://github.com/nextcloud/deck/pull/2022) Fix replyto issues with the comments API
|
||||
* [#2027](https://github.com/nextcloud/deck/pull/2027) Allow to unassign current user from card
|
||||
* [#2029](https://github.com/nextcloud/deck/pull/2029) Fix wording : stack -> list @cloud2018
|
||||
* [#2032](https://github.com/nextcloud/deck/pull/2032) Force order by id as second sorting key @juliushaertl
|
||||
* [#2045](https://github.com/nextcloud/deck/pull/2045) Improve label styling @juliushaertl
|
||||
* [#2032](https://github.com/nextcloud/deck/pull/2032) Force order by id as second sorting key
|
||||
* [#2045](https://github.com/nextcloud/deck/pull/2045) Improve label styling
|
||||
* [#2010](https://github.com/nextcloud/deck/pull/2010) User documentation fixes @Nyco
|
||||
* [#1998](https://github.com/nextcloud/deck/pull/1998) Add Checklist explaination to the doc @4rnoP
|
||||
|
||||
|
||||
@@ -25,8 +25,7 @@ Deck is a kanban style organization tool aimed at personal planning and project
|
||||
|
||||
- [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
|
||||
- [A-deck](https://github.com/leoossa/A-deck) - Chrome Extension that allows to create new card in selected stack based on current tab
|
||||
|
||||
|
||||
## Installation/Update
|
||||
|
||||
This app is supposed to work on the two latest Nextcloud versions.
|
||||
|
||||
@@ -7,16 +7,16 @@
|
||||
|
||||
|
||||
- 📥 Add your tasks to cards and put them in order
|
||||
- 📄 Write down additional notes in Markdown
|
||||
- 📄 Write down additional notes in markdown
|
||||
- 🔖 Assign labels for even better organization
|
||||
- 👥 Share with your team, friends or family
|
||||
- 📎 Attach files and embed them in your Markdown description
|
||||
- 📎 Attach files and embed them in your markdown description
|
||||
- 💬 Discuss with your team using comments
|
||||
- ⚡ Keep track of changes in the activity stream
|
||||
- 🚀 Get your project organized
|
||||
|
||||
</description>
|
||||
<version>1.7.2</version>
|
||||
<version>1.5.8</version>
|
||||
<licence>agpl</licence>
|
||||
<author>Julius Härtl</author>
|
||||
<namespace>Deck</namespace>
|
||||
@@ -31,10 +31,11 @@
|
||||
<screenshot>https://download.bitgrid.net/nextcloud/deck/screenshots/1.0/Deck-1.png</screenshot>
|
||||
<screenshot>https://download.bitgrid.net/nextcloud/deck/screenshots/1.0/Deck-2.png</screenshot>
|
||||
<dependencies>
|
||||
<php min-version="7.3"/>
|
||||
<database min-version="9.4">pgsql</database>
|
||||
<database>sqlite</database>
|
||||
<database min-version="8.0">mysql</database>
|
||||
<nextcloud min-version="24" max-version="24"/>
|
||||
<database min-version="5.5">mysql</database>
|
||||
<nextcloud min-version="22" max-version="22"/>
|
||||
</dependencies>
|
||||
<background-jobs>
|
||||
<job>OCA\Deck\Cron\DeleteCron</job>
|
||||
@@ -43,7 +44,6 @@
|
||||
</background-jobs>
|
||||
<commands>
|
||||
<command>OCA\Deck\Command\UserExport</command>
|
||||
<command>OCA\Deck\Command\BoardImport</command>
|
||||
<command>OCA\Deck\Command\TransferOwnership</command>
|
||||
</commands>
|
||||
<activity>
|
||||
|
||||
@@ -92,10 +92,6 @@ return [
|
||||
['name' => 'board_api#deleteAcl', 'url' => '/api/v{apiVersion}/boards/{boardId}/acl/{aclId}', 'verb' => 'DELETE'],
|
||||
['name' => 'board_api#updateAcl', 'url' => '/api/v{apiVersion}/boards/{boardId}/acl/{aclId}', 'verb' => 'PUT'],
|
||||
|
||||
['name' => 'board_import_api#getAllowedSystems', 'url' => '/api/v{apiVersion}/boards/import/getSystems','verb' => 'GET'],
|
||||
['name' => 'board_import_api#getConfigSchema', 'url' => '/api/v{apiVersion}/boards/import/config/schema/{name}','verb' => 'GET'],
|
||||
['name' => 'board_import_api#import', 'url' => '/api/v{apiVersion}/boards/import','verb' => 'POST'],
|
||||
|
||||
|
||||
['name' => 'stack_api#index', 'url' => '/api/v{apiVersion}/boards/{boardId}/stacks', 'verb' => 'GET'],
|
||||
['name' => 'stack_api#getArchived', 'url' => '/api/v{apiVersion}/boards/{boardId}/stacks/archived', 'verb' => 'GET'],
|
||||
|
||||
@@ -8,28 +8,26 @@
|
||||
"email": "jus@bitgrid.net"
|
||||
}
|
||||
],
|
||||
"config": {
|
||||
"platform": {
|
||||
"php": "7.3"
|
||||
}
|
||||
},
|
||||
"require": {
|
||||
"cogpowered/finediff": "0.3.*",
|
||||
"justinrainbow/json-schema": "^5.2"
|
||||
"cogpowered/finediff": "0.3.*"
|
||||
},
|
||||
"require-dev": {
|
||||
"roave/security-advisories": "dev-master",
|
||||
"phpunit/phpunit": "^9",
|
||||
"nextcloud/coding-standard": "^1.0.0",
|
||||
"christophwurst/nextcloud": "^22@dev",
|
||||
"phpunit/phpunit": "^8",
|
||||
"nextcloud/coding-standard": "^0.5.0",
|
||||
"symfony/event-dispatcher": "^4.0",
|
||||
"vimeo/psalm": "^4.3",
|
||||
"php-parallel-lint/php-parallel-lint": "^1.2",
|
||||
"nextcloud/ocp": "dev-stable24"
|
||||
"php-parallel-lint/php-parallel-lint": "^1.2"
|
||||
},
|
||||
"config": {
|
||||
"optimize-autoloader": true,
|
||||
"classmap-authoritative": true,
|
||||
"allow-plugins": {
|
||||
"composer/package-versions-deprecated": true
|
||||
},
|
||||
"platform": {
|
||||
"php": "7.4"
|
||||
}
|
||||
"classmap-authoritative": true
|
||||
},
|
||||
"scripts": {
|
||||
"lint": "find . -name \\*.php -not -path './vendor/*' -print0 | xargs -0 -n1 php -l",
|
||||
@@ -43,10 +41,5 @@
|
||||
],
|
||||
"test:unit": "phpunit -c tests/phpunit.xml",
|
||||
"test:integration": "phpunit -c tests/phpunit.integration.xml && cd tests/integration && ./run.sh"
|
||||
},
|
||||
"autoload-dev": {
|
||||
"psr-4": {
|
||||
"OCP\\": "vendor/nextcloud/ocp/OCP"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
1521
composer.lock
generated
1521
composer.lock
generated
File diff suppressed because it is too large
Load Diff
68
docs/API.md
68
docs/API.md
@@ -96,27 +96,10 @@ If available the ETag will also be part of JSON response objects as shown below
|
||||
|
||||
# Changelog
|
||||
|
||||
## API version 1.0
|
||||
|
||||
- Deck >=1.0.0: The maximum length of the card title has been extended from 100 to 255 characters
|
||||
- Deck >=1.0.0: The API will now return a 400 Bad request response if the length limitation of a board, stack or card title is exceeded
|
||||
|
||||
## API version 1.1
|
||||
|
||||
This API version has become available with **Deck 1.3.0**.
|
||||
## 1.0.0 (unreleased)
|
||||
|
||||
- The maximum length of the card title has been extended from 100 to 255 characters
|
||||
- The API will now return a 400 Bad request response if the length limitation of a board, stack or card title is exceeded
|
||||
- The attachments API endpoints will return other attachment types than deck_file
|
||||
- Prior to Deck version v1.3.0 (API v1.0), attachments were stored within deck. For this type of attachments `deck_file` was used as the default type of attachments
|
||||
- Starting with Deck version 1.3.0 (API v1.1) files are stored within the users regular Nextcloud files and the type `file` has been introduced for that
|
||||
|
||||
## API version 1.2 (unreleased)
|
||||
|
||||
- Endpoints for the new import functionality have been added:
|
||||
- [GET /boards/import/getSystems - Import a board](#get-boardsimportgetsystems-import-a-board)
|
||||
- [GET /boards/import/config/system/{schema} - Import a board](#get-boardsimportconfigsystemschema-import-a-board)
|
||||
- [POST /boards/import - Import a board](#post-boardsimport-import-a-board)
|
||||
|
||||
# Endpoints
|
||||
|
||||
@@ -944,8 +927,7 @@ The request can fail with a bad request response for the following reasons:
|
||||
| type | String | The type of the attachement |
|
||||
| file | Binary | File data to add as an attachment |
|
||||
|
||||
- Prior to Deck version v1.3.0 (API v1.0), attachments were stored within deck. For this type of attachments `deck_file` was used as the default type of attachments
|
||||
- Starting with Deck version 1.3.0 (API v1.1) files are stored within the users regular Nextcloud files and the type `file` has been introduced for that
|
||||
For now only `deck_file` is supported as an attachment type.
|
||||
|
||||
#### Response
|
||||
|
||||
@@ -1006,49 +988,6 @@ For now only `deck_file` is supported as an attachment type.
|
||||
|
||||
##### 200 Success
|
||||
|
||||
### GET /boards/import/getSystems - Import a board
|
||||
|
||||
#### Request parameters
|
||||
|
||||
| Parameter | Type | Description |
|
||||
| ------------ | ------- | --------------------------------------------- |
|
||||
| system | Integer | The system name. Example: trello |
|
||||
|
||||
#### Response
|
||||
|
||||
Make a request to see the json schema of system
|
||||
|
||||
```json
|
||||
{
|
||||
}
|
||||
```
|
||||
|
||||
### GET /boards/import/config/system/{schema} - Import a board
|
||||
|
||||
#### Request parameters
|
||||
|
||||
#### Response
|
||||
|
||||
```json
|
||||
[
|
||||
"trello"
|
||||
]
|
||||
```
|
||||
|
||||
### POST /boards/import - Import a board
|
||||
|
||||
#### Request parameters
|
||||
|
||||
| Parameter | Type | Description |
|
||||
| ------------ | ------- | --------------------------------------------- |
|
||||
| system | string | The allowed name of system to import from |
|
||||
| config | Object | The config object (JSON) |
|
||||
| data | Object | The data object to import (JSON) |
|
||||
|
||||
#### Response
|
||||
|
||||
##### 200 Success
|
||||
|
||||
# OCS API
|
||||
|
||||
The following endpoints are available through the Nextcloud OCS endpoint, which is available at `/ocs/v2.php/apps/deck/api/v1.0/`.
|
||||
@@ -1065,7 +1004,6 @@ Deck stores user and app configuration values globally and per board. The GET en
|
||||
| Config key | Description |
|
||||
| --- | --- |
|
||||
| calendar | Determines if the calendar/tasks integration through the CalDAV backend is enabled for the user (boolean) |
|
||||
| cardDetailsInModal | Determines if the bigger view is used (boolean) |
|
||||
| groupLimit | Determines if creating new boards is limited to certain groups of the instance. The resulting output is an array of group objects with the id and the displayname (Admin only)|
|
||||
|
||||
```
|
||||
@@ -1078,7 +1016,6 @@ Deck stores user and app configuration values globally and per board. The GET en
|
||||
},
|
||||
"data": {
|
||||
"calendar": true,
|
||||
"cardDetailsInModal": true,
|
||||
"groupLimit": [
|
||||
{
|
||||
"id": "admin",
|
||||
@@ -1108,7 +1045,6 @@ Deck stores user and app configuration values globally and per board. The GET en
|
||||
| --- | ----- |
|
||||
| notify-due | `off`, `assigned` or `all` |
|
||||
| calendar | Boolean |
|
||||
| cardDetailsInModal | Boolean |
|
||||
|
||||
#### Example request
|
||||
|
||||
|
||||
@@ -14,9 +14,7 @@ Overall, Deck is easy to use. You can create boards, add users, share the Deck,
|
||||
3. [Handle cards options](#3-handle-cards-options)
|
||||
4. [Archive old tasks](#4-archive-old-tasks)
|
||||
5. [Manage your board](#5-manage-your-board)
|
||||
6. [Import boards](#6-import-boards)
|
||||
7. [Search](#7-search)
|
||||
8. [New owner for the deck entities](#8-new-owner-for-the-deck-entities)
|
||||
6. [New owner for the deck entities](#8-new-owner-for-the-deck-entities)
|
||||
|
||||
### 1. Create my first board
|
||||
In this example, we're going to create a board and share it with an other nextcloud user.
|
||||
@@ -72,80 +70,14 @@ The **sharing tab** allows you to add users or even groups to your boards.
|
||||
**Deleted objects** allows you to return previously deleted stacks or cards.
|
||||
The **Timeline** allows you to see everything that happened in your boards. Everything!
|
||||
|
||||
### 6. Import boards
|
||||
|
||||
Importing can be done using the API or the `occ` `deck:import` command.
|
||||
|
||||
Comments with more than 1000 characters are placed as attached files to the card.
|
||||
|
||||
It is possible to import from the following sources:
|
||||
|
||||
#### Trello JSON
|
||||
|
||||
Steps:
|
||||
* Create the data file
|
||||
* Access Trello
|
||||
* go to the board you want to export
|
||||
* Follow the steps in [Trello documentation](https://help.trello.com/article/747-exporting-data-from-trello-1) and export as JSON
|
||||
* Create the configuration file
|
||||
* Execute the import informing the import file path, data file and source as `Trello JSON`
|
||||
|
||||
Create the configuration file respecting the [JSON Schema](https://github.com/nextcloud/deck/blob/master/lib/Service/fixtures/config-trelloJson-schema.json) for import `Trello JSON`
|
||||
|
||||
Example configuration file:
|
||||
```json
|
||||
{
|
||||
"owner": "admin",
|
||||
"color": "0800fd",
|
||||
"uidRelation": {
|
||||
"johndoe": "johndoe"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
**Limitations**:
|
||||
|
||||
Importing from a JSON file imports up to 1000 actions. To find out how many actions the board to be imported has, identify how many actions the JSON has.
|
||||
|
||||
#### Trello API
|
||||
|
||||
Import using API is recommended for boards with more than 1000 actions.
|
||||
|
||||
Trello makes it possible to attach links to a card. Deck does not have this feature. Attachments and attachment links are added in a markdown table at the end of the description for every imported card that has attachments in Trello.
|
||||
|
||||
* Get the API Key and API Token [here](https://developer.atlassian.com/cloud/trello/guides/rest-api/api-introduction/#authentication-and-authorization)
|
||||
* Get the ID of the board you want to import by making a request to:
|
||||
https://api.trello.com/1/members/me/boards?key={yourKey}&token={yourToken}&fields=id,name
|
||||
|
||||
This ID you will use in the configuration file in the `board` property
|
||||
* Create the configuration file
|
||||
|
||||
Create the configuration file respecting the [JSON Schema](https://github.com/nextcloud/deck/blob/master/lib/Service/fixtures/config-trelloApi-schema.json) for import `Trello JSON`
|
||||
|
||||
Example configuration file:
|
||||
```json
|
||||
{
|
||||
"owner": "admin",
|
||||
"color": "0800fd",
|
||||
"api": {
|
||||
"key": "0cc175b9c0f1b6a831c399e269772661",
|
||||
"token": "92eb5ffee6ae2fec3ad71c777531578f4a8a08f09d37b73795649038408b5f33"
|
||||
},
|
||||
"board": "8277e0910d750195b4487976",
|
||||
"uidRelation": {
|
||||
"johndoe": "johndoe"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### 7. Search
|
||||
## Search
|
||||
|
||||
Deck provides a global search either through the unified search in the Nextcloud header or with the inline search next to the board controls.
|
||||
This search allows advanced filtering of cards across all board of the logged in user.
|
||||
|
||||
For example the search `project tag:ToDo assigned:alice assigned:bob` will return all cards where the card title or description contains project **and** the tag ToDo is set **and** the user alice is assigned **and** the user bob is assigned.
|
||||
|
||||
#### Supported search filters
|
||||
### Supported search filters
|
||||
|
||||
| Filter | Operators | Query |
|
||||
| ----------- | ----------------- | ------------------------------------------------------------ |
|
||||
|
||||
@@ -1,32 +0,0 @@
|
||||
## Implement import
|
||||
|
||||
* Create a new importer class extending `ABoardImportService`
|
||||
* Create a listener for event `BoardImportGetAllowedEvent` to enable your importer.
|
||||
> You can read more about listeners on [Nextcloud](https://docs.nextcloud.com/server/latest/developer_manual/basics/events.html?highlight=event#writing-a-listener) doc.
|
||||
|
||||
Example:
|
||||
|
||||
```php
|
||||
class YourCustomImporterListener {
|
||||
public function handle(Event $event): void {
|
||||
if (!($event instanceof BoardImportGetAllowedEvent)) {
|
||||
return;
|
||||
}
|
||||
|
||||
$event->getService()->addAllowedImportSystem([
|
||||
'name' => YourCustomImporterService::$name,
|
||||
'class' => YourCustomImporterService::class,
|
||||
'internalName' => 'YourCustomImporter'
|
||||
]);
|
||||
}
|
||||
}
|
||||
```
|
||||
* Register your listener on your `Application` class like this:
|
||||
```php
|
||||
$dispatcher = $this->getContainer()->query(IEventDispatcher::class);
|
||||
$dispatcher->registerEventListener(
|
||||
BoardImportGetAllowedEvent::class,
|
||||
YourCustomImporterListener::class
|
||||
);
|
||||
```
|
||||
* Use the `lib/Service/Importer/Systems/TrelloJsonService.php` class as inspiration
|
||||
@@ -1,7 +0,0 @@
|
||||
## Import class diagram
|
||||
|
||||
Importing boards to the Deck implements the class diagram below.
|
||||
|
||||
> **NOTE**: When making any changes to the structure of the classes or implementing import from other sources, edit the `BoardImport.yuml` file
|
||||
|
||||

|
||||
@@ -1,214 +0,0 @@
|
||||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN"
|
||||
"http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
|
||||
<!-- Generated by graphviz version 2.40.1 (20161225.0304)
|
||||
-->
|
||||
<!-- Title: G Pages: 1 -->
|
||||
<svg width="417pt" height="830pt"
|
||||
viewBox="0.00 0.00 417.01 830.00" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
|
||||
<g id="graph0" class="graph" transform="scale(1 1) rotate(0) translate(4 826)">
|
||||
<title>G</title>
|
||||
<polygon fill="#ffffff" stroke="transparent" points="-4,4 -4,-826 413.012,-826 413.012,4 -4,4"/>
|
||||
<!-- A0 -->
|
||||
<g id="node1" class="node">
|
||||
<title>A0</title>
|
||||
<polygon fill="#fff8dc" stroke="#000000" points="165.909,-822 70.091,-822 70.091,-766 171.909,-766 171.909,-816 165.909,-822"/>
|
||||
<polyline fill="none" stroke="#000000" points="165.909,-822 165.909,-816 "/>
|
||||
<polyline fill="none" stroke="#000000" points="171.909,-816 165.909,-816 "/>
|
||||
<text text-anchor="middle" x="121" y="-809" font-family="Helvetica,sans-Serif" font-size="10.00" fill="#000000">Classes used on</text>
|
||||
<text text-anchor="middle" x="121" y="-797" font-family="Helvetica,sans-Serif" font-size="10.00" fill="#000000">board import.</text>
|
||||
<text text-anchor="middle" x="121" y="-785" font-family="Helvetica,sans-Serif" font-size="10.00" fill="#000000">Methods just to</text>
|
||||
<text text-anchor="middle" x="121" y="-773" font-family="Helvetica,sans-Serif" font-size="10.00" fill="#000000">illustrate.</text>
|
||||
</g>
|
||||
<!-- A1 -->
|
||||
<g id="node2" class="node">
|
||||
<title>A1</title>
|
||||
<polygon fill="none" stroke="#000000" points="108.7773,-680 23.2227,-680 23.2227,-644 108.7773,-644 108.7773,-680"/>
|
||||
<text text-anchor="middle" x="66" y="-659" font-family="Helvetica,sans-Serif" font-size="10.00" fill="#000000">ApiController</text>
|
||||
</g>
|
||||
<!-- A2 -->
|
||||
<g id="node3" class="node">
|
||||
<title>A2</title>
|
||||
<polygon fill="none" stroke="#000000" points="0,-514 0,-546 132,-546 132,-514 0,-514"/>
|
||||
<text text-anchor="start" x="9.607" y="-527" font-family="Helvetica,sans-Serif" font-size="10.00" fill="#000000">BoardImportApiController</text>
|
||||
<polygon fill="none" stroke="#000000" points="0,-458 0,-514 132,-514 132,-458 0,-458"/>
|
||||
<text text-anchor="start" x="45.8645" y="-495" font-family="Helvetica,sans-Serif" font-size="10.00" fill="#000000">+import()</text>
|
||||
<text text-anchor="start" x="16.1335" y="-483" font-family="Helvetica,sans-Serif" font-size="10.00" fill="#000000">+getAllowedSystems()</text>
|
||||
<text text-anchor="start" x="20.0185" y="-471" font-family="Helvetica,sans-Serif" font-size="10.00" fill="#000000">+getConfigSchema()</text>
|
||||
</g>
|
||||
<!-- A1->A2 -->
|
||||
<g id="edge1" class="edge">
|
||||
<title>A1->A2</title>
|
||||
<path fill="none" stroke="#000000" d="M66,-633.6693C66,-609.4424 66,-574.1663 66,-546.2238"/>
|
||||
<polygon fill="#000000" stroke="#000000" points="66,-643.957 61.5001,-633.9569 66,-638.957 66.0001,-633.957 66.0001,-633.957 66.0001,-633.957 66,-638.957 70.5001,-633.957 66,-643.957 66,-643.957"/>
|
||||
</g>
|
||||
<!-- A3 -->
|
||||
<g id="node4" class="node">
|
||||
<title>A3</title>
|
||||
<polygon fill="none" stroke="#000000" points="92,-364 92,-396 200,-396 200,-364 92,-364"/>
|
||||
<text text-anchor="start" x="101.828" y="-377" font-family="Helvetica,sans-Serif" font-size="10.00" fill="#000000">BoardImportService</text>
|
||||
<polygon fill="none" stroke="#000000" points="92,-284 92,-364 200,-364 200,-284 92,-284"/>
|
||||
<text text-anchor="start" x="125.8645" y="-345" font-family="Helvetica,sans-Serif" font-size="10.00" fill="#000000">+import()</text>
|
||||
<text text-anchor="start" x="118.9105" y="-333" font-family="Helvetica,sans-Serif" font-size="10.00" fill="#000000">+bootstrap()</text>
|
||||
<text text-anchor="start" x="105.857" y="-321" font-family="Helvetica,sans-Serif" font-size="10.00" fill="#000000">+validateSystem()</text>
|
||||
<text text-anchor="start" x="108.218" y="-309" font-family="Helvetica,sans-Serif" font-size="10.00" fill="#000000">#validateConfig()</text>
|
||||
<text text-anchor="start" x="112.107" y="-297" font-family="Helvetica,sans-Serif" font-size="10.00" fill="#000000">#validateData()</text>
|
||||
</g>
|
||||
<!-- A2->A3 -->
|
||||
<g id="edge2" class="edge">
|
||||
<title>A2->A3</title>
|
||||
<path fill="none" stroke="#000000" stroke-dasharray="5,2" d="M87.8604,-457.7328C95.8577,-441.5382 105.0823,-422.8583 113.7939,-405.2174"/>
|
||||
<polygon fill="#000000" stroke="#000000" points="118.2935,-396.1057 117.9004,-407.0646 116.0795,-400.5889 113.8656,-405.072 113.8656,-405.072 113.8656,-405.072 116.0795,-400.5889 109.8308,-403.0795 118.2935,-396.1057 118.2935,-396.1057"/>
|
||||
<text text-anchor="middle" x="88.3076" y="-434.7378" font-family="Helvetica,sans-Serif" font-size="10.00" fill="#000000">uses</text>
|
||||
</g>
|
||||
<!-- A7 -->
|
||||
<g id="node8" class="node">
|
||||
<title>A7</title>
|
||||
<polygon fill="none" stroke="#000000" points="37,-196 37,-228 129,-228 129,-196 37,-196"/>
|
||||
<text text-anchor="start" x="46.612" y="-209" font-family="Helvetica,sans-Serif" font-size="10.00" fill="#000000">TrelloApiService</text>
|
||||
<polygon fill="none" stroke="#000000" points="37,-164 37,-196 129,-196 129,-164 37,-164"/>
|
||||
<text text-anchor="start" x="53.9655" y="-177" font-family="Helvetica,sans-Serif" font-size="10.00" fill="#000000">+name:string</text>
|
||||
</g>
|
||||
<!-- A3->A7 -->
|
||||
<g id="edge6" class="edge">
|
||||
<title>A3->A7</title>
|
||||
<path fill="none" stroke="#000000" stroke-dasharray="5,2" d="M114.8609,-283.9135C107.8316,-268.5143 100.7854,-252.0928 95.0404,-237.6613"/>
|
||||
<polygon fill="#000000" stroke="#000000" points="91.2872,-228.0253 99.1098,-235.7102 93.1019,-232.6844 94.9167,-237.3434 94.9167,-237.3434 94.9167,-237.3434 93.1019,-232.6844 90.7235,-238.9767 91.2872,-228.0253 91.2872,-228.0253"/>
|
||||
<text text-anchor="middle" x="99.6759" y="-267.8975" font-family="Helvetica,sans-Serif" font-size="10.00" fill="#000000">uses</text>
|
||||
</g>
|
||||
<!-- A9 -->
|
||||
<g id="node10" class="node">
|
||||
<title>A9</title>
|
||||
<polygon fill="none" stroke="#000000" points="148,-202 148,-234 273,-234 273,-202 148,-202"/>
|
||||
<text text-anchor="start" x="170.7765" y="-215" font-family="Helvetica,sans-Serif" font-size="10.00" fill="#000000">TrelloJsonService</text>
|
||||
<polygon fill="none" stroke="#000000" points="148,-158 148,-202 273,-202 273,-158 148,-158"/>
|
||||
<text text-anchor="start" x="181.4655" y="-183" font-family="Helvetica,sans-Serif" font-size="10.00" fill="#000000">+name:string</text>
|
||||
<text text-anchor="start" x="157.981" y="-171" font-family="Helvetica,sans-Serif" font-size="10.00" fill="#000000">#needValidateData:true</text>
|
||||
</g>
|
||||
<!-- A3->A9 -->
|
||||
<g id="edge9" class="edge">
|
||||
<title>A3->A9</title>
|
||||
<path fill="none" stroke="#000000" stroke-dasharray="5,2" d="M164.3261,-283.9135C170.0039,-270.5688 176.3462,-256.4563 182.4816,-243.5365"/>
|
||||
<polygon fill="#000000" stroke="#000000" points="186.9002,-234.3677 186.6126,-245.3298 184.7295,-238.872 182.5588,-243.3762 182.5588,-243.3762 182.5588,-243.3762 184.7295,-238.872 178.505,-241.4226 186.9002,-234.3677 186.9002,-234.3677"/>
|
||||
<text text-anchor="middle" x="163.6874" y="-260.9237" font-family="Helvetica,sans-Serif" font-size="10.00" fill="#000000">uses</text>
|
||||
</g>
|
||||
<!-- A10 -->
|
||||
<g id="node11" class="node">
|
||||
<title>A10</title>
|
||||
<polygon fill="#fff8dc" stroke="#000000" points="317.7872,-362 218.2128,-362 218.2128,-318 323.7872,-318 323.7872,-356 317.7872,-362"/>
|
||||
<polyline fill="none" stroke="#000000" points="317.7872,-362 317.7872,-356 "/>
|
||||
<polyline fill="none" stroke="#000000" points="323.7872,-356 317.7872,-356 "/>
|
||||
<text text-anchor="middle" x="271" y="-349" font-family="Helvetica,sans-Serif" font-size="10.00" fill="#000000">validateSystem is</text>
|
||||
<text text-anchor="middle" x="271" y="-337" font-family="Helvetica,sans-Serif" font-size="10.00" fill="#000000">public because is</text>
|
||||
<text text-anchor="middle" x="271" y="-325" font-family="Helvetica,sans-Serif" font-size="10.00" fill="#000000">used on Api.</text>
|
||||
</g>
|
||||
<!-- A3->A10 -->
|
||||
<g id="edge11" class="edge">
|
||||
<title>A3->A10</title>
|
||||
<path fill="none" stroke="#000000" stroke-dasharray="5,2" d="M200.1992,-340C206.1915,-340 212.1837,-340 218.176,-340"/>
|
||||
</g>
|
||||
<!-- A4 -->
|
||||
<g id="node5" class="node">
|
||||
<title>A4</title>
|
||||
<polygon fill="none" stroke="#000000" points="264.1131,-812 189.8869,-812 189.8869,-776 264.1131,-776 264.1131,-812"/>
|
||||
<text text-anchor="middle" x="227" y="-791" font-family="Helvetica,sans-Serif" font-size="10.00" fill="#000000">Command</text>
|
||||
</g>
|
||||
<!-- A5 -->
|
||||
<g id="node6" class="node">
|
||||
<title>A5</title>
|
||||
<polygon fill="none" stroke="#000000" points="148,-684 148,-716 307,-716 307,-684 148,-684"/>
|
||||
<text text-anchor="start" x="199.9955" y="-697" font-family="Helvetica,sans-Serif" font-size="10.00" fill="#000000">BoardImport</text>
|
||||
<polygon fill="none" stroke="#000000" points="148,-652 148,-684 307,-684 307,-652 148,-652"/>
|
||||
<text text-anchor="start" x="157.907" y="-665" font-family="Helvetica,sans-Serif" font-size="10.00" fill="#000000">+boardImportCommandService</text>
|
||||
<polygon fill="none" stroke="#000000" points="148,-608 148,-652 307,-652 307,-608 148,-608"/>
|
||||
<text text-anchor="start" x="200.8305" y="-633" font-family="Helvetica,sans-Serif" font-size="10.00" fill="#000000">#configure()</text>
|
||||
<text text-anchor="start" x="177.76" y="-621" font-family="Helvetica,sans-Serif" font-size="10.00" fill="#000000">#execute(input,output)</text>
|
||||
</g>
|
||||
<!-- A4->A5 -->
|
||||
<g id="edge3" class="edge">
|
||||
<title>A4->A5</title>
|
||||
<path fill="none" stroke="#000000" d="M227,-765.6356C227,-751.1554 227,-733.0451 227,-716.0324"/>
|
||||
<polygon fill="#000000" stroke="#000000" points="227,-775.9227 222.5001,-765.9227 227,-770.9227 227.0001,-765.9227 227.0001,-765.9227 227.0001,-765.9227 227,-770.9227 231.5001,-765.9228 227,-775.9227 227,-775.9227"/>
|
||||
</g>
|
||||
<!-- A6 -->
|
||||
<g id="node7" class="node">
|
||||
<title>A6</title>
|
||||
<polygon fill="none" stroke="#000000" points="150,-526 150,-558 304,-558 304,-526 150,-526"/>
|
||||
<text text-anchor="start" x="159.7715" y="-539" font-family="Helvetica,sans-Serif" font-size="10.00" fill="#000000">BoardImportCommandService</text>
|
||||
<polygon fill="none" stroke="#000000" points="150,-446 150,-526 304,-526 304,-446 150,-446"/>
|
||||
<text text-anchor="start" x="199.9105" y="-507" font-family="Helvetica,sans-Serif" font-size="10.00" fill="#000000">+bootstrap()</text>
|
||||
<text text-anchor="start" x="206.8645" y="-495" font-family="Helvetica,sans-Serif" font-size="10.00" fill="#000000">+import()</text>
|
||||
<text text-anchor="start" x="186.857" y="-483" font-family="Helvetica,sans-Serif" font-size="10.00" fill="#000000">+validateSystem()</text>
|
||||
<text text-anchor="start" x="189.218" y="-471" font-family="Helvetica,sans-Serif" font-size="10.00" fill="#000000">#validateConfig()</text>
|
||||
<text text-anchor="start" x="193.107" y="-459" font-family="Helvetica,sans-Serif" font-size="10.00" fill="#000000">#validateData()</text>
|
||||
</g>
|
||||
<!-- A5->A6 -->
|
||||
<g id="edge4" class="edge">
|
||||
<title>A5->A6</title>
|
||||
<path fill="none" stroke="#000000" stroke-dasharray="5,2" d="M227,-607.8313C227,-595.0442 227,-581.2707 227,-568.0248"/>
|
||||
<polygon fill="#000000" stroke="#000000" points="227,-558.0234 231.5001,-568.0234 227,-563.0234 227.0001,-568.0234 227.0001,-568.0234 227.0001,-568.0234 227,-563.0234 222.5001,-568.0235 227,-558.0234 227,-558.0234"/>
|
||||
<text text-anchor="middle" x="218.5476" y="-586.7051" font-family="Helvetica,sans-Serif" font-size="10.00" fill="#000000">uses</text>
|
||||
</g>
|
||||
<!-- A6->A3 -->
|
||||
<g id="edge5" class="edge">
|
||||
<title>A6->A3</title>
|
||||
<path fill="none" stroke="#000000" d="M198.8975,-445.7949C192.3634,-432.7268 185.3528,-418.7057 178.6417,-405.2834"/>
|
||||
<polygon fill="#000000" stroke="#000000" points="174.0529,-396.1057 182.55,-403.0375 176.289,-400.5779 178.5251,-405.05 178.5251,-405.05 178.5251,-405.05 176.289,-400.5779 174.5001,-407.0625 174.0529,-396.1057 174.0529,-396.1057"/>
|
||||
</g>
|
||||
<!-- A7->A3 -->
|
||||
<g id="edge7" class="edge">
|
||||
<title>A7->A3</title>
|
||||
<path fill="none" stroke="#000000" stroke-dasharray="5,2" d="M102.735,-228.0253C109.5347,-241.763 117.1224,-258.3431 124.0627,-274.4849"/>
|
||||
<polygon fill="#000000" stroke="#000000" points="128.0634,-283.9135 120.0148,-276.4657 126.1104,-279.3107 124.1573,-274.7079 124.1573,-274.7079 124.1573,-274.7079 126.1104,-279.3107 128.2998,-272.9502 128.0634,-283.9135 128.0634,-283.9135"/>
|
||||
<text text-anchor="middle" x="118.307" y="-237.5757" font-family="Helvetica,sans-Serif" font-size="10.00" fill="#000000">uses</text>
|
||||
</g>
|
||||
<!-- A8 -->
|
||||
<g id="node9" class="node">
|
||||
<title>A8</title>
|
||||
<polygon fill="none" stroke="#000000" points="80,-64 80,-108 213,-108 213,-64 80,-64"/>
|
||||
<text text-anchor="start" x="117.04" y="-89" font-family="Helvetica,sans-Serif" font-size="10.00" fill="#000000"><<abstract>></text>
|
||||
<text text-anchor="start" x="98.9935" y="-77" font-family="Helvetica,sans-Serif" font-size="10.00" fill="#000000">ABoardImportService</text>
|
||||
<polygon fill="none" stroke="#000000" points="80,-32 80,-64 213,-64 213,-32 80,-32"/>
|
||||
<text text-anchor="start" x="92.036" y="-45" font-family="Helvetica,sans-Serif" font-size="10.00" fill="#000000">#needValidateData:false</text>
|
||||
<polygon fill="none" stroke="#000000" points="80,0 80,-32 213,-32 213,0 80,0"/>
|
||||
<text text-anchor="start" x="89.677" y="-13" font-family="Helvetica,sans-Serif" font-size="10.00" fill="#000000">+needValidateData():bool</text>
|
||||
</g>
|
||||
<!-- A7->A8 -->
|
||||
<g id="edge8" class="edge">
|
||||
<title>A7->A8</title>
|
||||
<path fill="none" stroke="#000000" stroke-dasharray="5,2" d="M97.2957,-163.778C103.3956,-150.029 110.7371,-133.4813 117.8485,-117.4527"/>
|
||||
<polygon fill="none" stroke="#000000" points="121.1416,-118.6605 121.9978,-108.1003 114.743,-115.8216 121.1416,-118.6605"/>
|
||||
<text text-anchor="middle" x="96.9205" y="-140.7815" font-family="Helvetica,sans-Serif" font-size="10.00" fill="#000000">implements</text>
|
||||
</g>
|
||||
<!-- A9->A3 -->
|
||||
<g id="edge10" class="edge">
|
||||
<title>A9->A3</title>
|
||||
<path fill="none" stroke="#000000" stroke-dasharray="5,2" d="M198.9952,-234.3677C194.0646,-246.7117 188.0483,-260.7568 181.8434,-274.4849"/>
|
||||
<polygon fill="#000000" stroke="#000000" points="177.5286,-283.9135 177.598,-272.9478 179.6093,-279.367 181.6899,-274.8204 181.6899,-274.8204 181.6899,-274.8204 179.6093,-279.367 185.7818,-276.693 177.5286,-283.9135 177.5286,-283.9135"/>
|
||||
<text text-anchor="middle" x="200.0654" y="-251.3391" font-family="Helvetica,sans-Serif" font-size="10.00" fill="#000000">uses</text>
|
||||
</g>
|
||||
<!-- A9->A8 -->
|
||||
<g id="edge13" class="edge">
|
||||
<title>A9->A8</title>
|
||||
<path fill="none" stroke="#000000" stroke-dasharray="5,2" d="M192.8492,-157.9466C187.2535,-145.5313 180.8796,-131.389 174.6742,-117.6209"/>
|
||||
<polygon fill="none" stroke="#000000" points="177.7167,-115.8534 170.4168,-108.1747 171.3349,-118.7297 177.7167,-115.8534"/>
|
||||
<text text-anchor="middle" x="177.6953" y="-141.8944" font-family="Helvetica,sans-Serif" font-size="10.00" fill="#000000">implements</text>
|
||||
</g>
|
||||
<!-- A11 -->
|
||||
<g id="node12" class="node">
|
||||
<title>A11</title>
|
||||
<polygon fill="#fff8dc" stroke="#000000" points="403.024,-224 290.976,-224 290.976,-168 409.024,-168 409.024,-218 403.024,-224"/>
|
||||
<polyline fill="none" stroke="#000000" points="403.024,-224 403.024,-218 "/>
|
||||
<polyline fill="none" stroke="#000000" points="409.024,-218 403.024,-218 "/>
|
||||
<text text-anchor="middle" x="350" y="-211" font-family="Helvetica,sans-Serif" font-size="10.00" fill="#000000">To create an import</text>
|
||||
<text text-anchor="middle" x="350" y="-199" font-family="Helvetica,sans-Serif" font-size="10.00" fill="#000000">to another system,</text>
|
||||
<text text-anchor="middle" x="350" y="-187" font-family="Helvetica,sans-Serif" font-size="10.00" fill="#000000">create another class</text>
|
||||
<text text-anchor="middle" x="350" y="-175" font-family="Helvetica,sans-Serif" font-size="10.00" fill="#000000">similar to this.</text>
|
||||
</g>
|
||||
<!-- A9->A11 -->
|
||||
<g id="edge12" class="edge">
|
||||
<title>A9->A11</title>
|
||||
<path fill="none" stroke="#000000" stroke-dasharray="5,2" d="M272.6172,-196C278.6627,-196 284.7083,-196 290.7538,-196"/>
|
||||
</g>
|
||||
</g>
|
||||
</svg>
|
||||
|
Before Width: | Height: | Size: 16 KiB |
@@ -1,24 +0,0 @@
|
||||
// Created using [yUML](https://github.com/jaime-olivares/vscode-yuml)
|
||||
|
||||
// {type:class}
|
||||
// {direction:topDown}
|
||||
// {generate:true}
|
||||
|
||||
[note: Classes used on board import. Methods just to illustrate. {bg:cornsilk}]
|
||||
|
||||
[ApiController]<-[BoardImportApiController|+import();+getAllowedSystems();+getConfigSchema()]
|
||||
[BoardImportApiController]uses-.->[BoardImportService|+import();+bootstrap();+validateSystem();#validateConfig();#validateData();]
|
||||
|
||||
[Command]<-[BoardImport|+boardImportCommandService|#configure();#execute(input,output)]
|
||||
[BoardImport]uses-.->[BoardImportCommandService|+bootstrap();+import();+validateSystem();#validateConfig();#validateData()]
|
||||
[BoardImportCommandService]->[BoardImportService]
|
||||
|
||||
[BoardImportService]uses-.->[TrelloApiService|+name:string]
|
||||
[TrelloApiService]uses-.->[BoardImportService]
|
||||
[TrelloApiService]implements-.-^[<<abstract>> ABoardImportService|#needValidateData:false|+needValidateData():bool]
|
||||
|
||||
[BoardImportService]uses-.->[TrelloJsonService|+name:string;#needValidateData:true]
|
||||
[TrelloJsonService]uses-.->[BoardImportService]
|
||||
[BoardImportService]-[note: validateSystem is public because is used on Api. {bg:cornsilk}]
|
||||
[TrelloJsonService]-[note: To create an import to another system, create another class similar to this. {bg:cornsilk}]
|
||||
[TrelloJsonService]implements-.-^[<<abstract>> ABoardImportService]
|
||||
@@ -183,8 +183,10 @@ OC.L10N.register(
|
||||
"Failed to transfer the board to {user}" : "Nepodařilo se předat vlastnictví tabule uživateli {user}",
|
||||
"Add a new list" : "Přidat nový sloupec",
|
||||
"Archive all cards" : "Archivovat všechny karty",
|
||||
"Unarchive all cards" : "Zrušit archivaci všech karet",
|
||||
"Delete list" : "Smazat seznam",
|
||||
"Archive all cards in this list" : "Archivovat všechny karty v tomto seznamu",
|
||||
"Unarchive all cards in this list" : "Zrušit archivaci všech karet v tomto seznamu",
|
||||
"Add a new card" : "Přidat novou kartu",
|
||||
"Card name" : "Název karty",
|
||||
"List deleted" : "Seznam smazán",
|
||||
@@ -260,6 +262,7 @@ OC.L10N.register(
|
||||
"Shared with you" : "Sdíleno s vámi",
|
||||
"Deck settings" : "Nastavení pro Deck",
|
||||
"Use bigger card view" : "Použít větší zobrazení karet",
|
||||
"Show card ID badge" : "Zobrazit odznáček s identifikátorem karty",
|
||||
"Show boards in calendar/tasks" : "Zobrazit tabule v kalendáři/úkolech",
|
||||
"Limit deck usage of groups" : "Omezit využití deck na skupiny",
|
||||
"Limiting Deck will block users not part of those groups from creating their own boards. Users will still be able to work on boards that have been shared with them." : "Omezení nastavené pro Deck brání uživatelům, kteří nejsou součástí těchto skupin, ve vytváření vlastních tabulí. Nicméně i tak ale pořád budou moci pracovat na tabulích, které jsou jim nasdíleny.",
|
||||
|
||||
@@ -181,8 +181,10 @@
|
||||
"Failed to transfer the board to {user}" : "Nepodařilo se předat vlastnictví tabule uživateli {user}",
|
||||
"Add a new list" : "Přidat nový sloupec",
|
||||
"Archive all cards" : "Archivovat všechny karty",
|
||||
"Unarchive all cards" : "Zrušit archivaci všech karet",
|
||||
"Delete list" : "Smazat seznam",
|
||||
"Archive all cards in this list" : "Archivovat všechny karty v tomto seznamu",
|
||||
"Unarchive all cards in this list" : "Zrušit archivaci všech karet v tomto seznamu",
|
||||
"Add a new card" : "Přidat novou kartu",
|
||||
"Card name" : "Název karty",
|
||||
"List deleted" : "Seznam smazán",
|
||||
@@ -258,6 +260,7 @@
|
||||
"Shared with you" : "Sdíleno s vámi",
|
||||
"Deck settings" : "Nastavení pro Deck",
|
||||
"Use bigger card view" : "Použít větší zobrazení karet",
|
||||
"Show card ID badge" : "Zobrazit odznáček s identifikátorem karty",
|
||||
"Show boards in calendar/tasks" : "Zobrazit tabule v kalendáři/úkolech",
|
||||
"Limit deck usage of groups" : "Omezit využití deck na skupiny",
|
||||
"Limiting Deck will block users not part of those groups from creating their own boards. Users will still be able to work on boards that have been shared with them." : "Omezení nastavené pro Deck brání uživatelům, kteří nejsou součástí těchto skupin, ve vytváření vlastních tabulí. Nicméně i tak ale pořád budou moci pracovat na tabulích, které jsou jim nasdíleny.",
|
||||
|
||||
@@ -183,8 +183,10 @@ OC.L10N.register(
|
||||
"Failed to transfer the board to {user}" : "Das Board konnte nicht an {user} übertragen werden",
|
||||
"Add a new list" : "Eine neue Liste hinzufügen",
|
||||
"Archive all cards" : "Alle Karten archivieren",
|
||||
"Unarchive all cards" : "Alle Karten dearchivieren",
|
||||
"Delete list" : "Liste löschen",
|
||||
"Archive all cards in this list" : "Alle Karten in dieser Liste archivieren",
|
||||
"Unarchive all cards in this list" : "Alle Karten dieser Liste dearchivieren",
|
||||
"Add a new card" : "Neue Karte hinzufügen",
|
||||
"Card name" : "Kartenname",
|
||||
"List deleted" : "Liste gelöscht",
|
||||
@@ -213,10 +215,10 @@ OC.L10N.register(
|
||||
"The title cannot be empty." : "Der Titel darf nicht leer sein.",
|
||||
"No comments yet. Begin the discussion!" : "Bislang keine Kommentare. Beginne die Diskussion!",
|
||||
"Failed to load comments" : "Das Laden der Kommentare ist fehlgeschlagen",
|
||||
"Assign a tag to this card…" : "Dieser Karte ein Schlagwort zuweisen…",
|
||||
"Assign a tag to this card…" : "Dieser Karte ein Schlagwort zuweisen …",
|
||||
"Assign to users" : "Benutzern zuweisen",
|
||||
"Assign to users/groups/circles" : "An Benutzer, Gruppen oder Kreise zuweisen",
|
||||
"Assign a user to this card…" : "Diese Karte einem Benutzer zuweisen…",
|
||||
"Assign a user to this card…" : "Diese Karte einem Benutzer zuweisen …",
|
||||
"Due date" : "Fälligkeitsdatum",
|
||||
"Set a due date" : "Ein Ablaufdatum setzen",
|
||||
"Remove due date" : "Fälligkeitsdatum löschen",
|
||||
@@ -235,7 +237,7 @@ OC.L10N.register(
|
||||
"Description" : "Beschreibung",
|
||||
"(Unsaved)" : "(nicht gespeichert)",
|
||||
"(Saving…)" : "(Speichere…)",
|
||||
"Formatting help" : "Formatierungshilfe",
|
||||
"Formatting help" : "Hilfe zur Formatierung",
|
||||
"Edit description" : "Beschreibung bearbeiten",
|
||||
"View description" : "Beschreibung anzeigen",
|
||||
"Add Attachment" : "Anhang anhängen",
|
||||
|
||||
@@ -181,8 +181,10 @@
|
||||
"Failed to transfer the board to {user}" : "Das Board konnte nicht an {user} übertragen werden",
|
||||
"Add a new list" : "Eine neue Liste hinzufügen",
|
||||
"Archive all cards" : "Alle Karten archivieren",
|
||||
"Unarchive all cards" : "Alle Karten dearchivieren",
|
||||
"Delete list" : "Liste löschen",
|
||||
"Archive all cards in this list" : "Alle Karten in dieser Liste archivieren",
|
||||
"Unarchive all cards in this list" : "Alle Karten dieser Liste dearchivieren",
|
||||
"Add a new card" : "Neue Karte hinzufügen",
|
||||
"Card name" : "Kartenname",
|
||||
"List deleted" : "Liste gelöscht",
|
||||
@@ -211,10 +213,10 @@
|
||||
"The title cannot be empty." : "Der Titel darf nicht leer sein.",
|
||||
"No comments yet. Begin the discussion!" : "Bislang keine Kommentare. Beginne die Diskussion!",
|
||||
"Failed to load comments" : "Das Laden der Kommentare ist fehlgeschlagen",
|
||||
"Assign a tag to this card…" : "Dieser Karte ein Schlagwort zuweisen…",
|
||||
"Assign a tag to this card…" : "Dieser Karte ein Schlagwort zuweisen …",
|
||||
"Assign to users" : "Benutzern zuweisen",
|
||||
"Assign to users/groups/circles" : "An Benutzer, Gruppen oder Kreise zuweisen",
|
||||
"Assign a user to this card…" : "Diese Karte einem Benutzer zuweisen…",
|
||||
"Assign a user to this card…" : "Diese Karte einem Benutzer zuweisen …",
|
||||
"Due date" : "Fälligkeitsdatum",
|
||||
"Set a due date" : "Ein Ablaufdatum setzen",
|
||||
"Remove due date" : "Fälligkeitsdatum löschen",
|
||||
@@ -233,7 +235,7 @@
|
||||
"Description" : "Beschreibung",
|
||||
"(Unsaved)" : "(nicht gespeichert)",
|
||||
"(Saving…)" : "(Speichere…)",
|
||||
"Formatting help" : "Formatierungshilfe",
|
||||
"Formatting help" : "Hilfe zur Formatierung",
|
||||
"Edit description" : "Beschreibung bearbeiten",
|
||||
"View description" : "Beschreibung anzeigen",
|
||||
"Add Attachment" : "Anhang anhängen",
|
||||
|
||||
@@ -183,8 +183,10 @@ OC.L10N.register(
|
||||
"Failed to transfer the board to {user}" : "Das Board konnte nicht auf {user} übertragen werden",
|
||||
"Add a new list" : "Eine neue Liste hinzufügen",
|
||||
"Archive all cards" : "Alle Karten archivieren",
|
||||
"Unarchive all cards" : "Alle Karten dearchivieren",
|
||||
"Delete list" : "Liste löschen",
|
||||
"Archive all cards in this list" : "Alle Karten in dieser Liste archivieren",
|
||||
"Unarchive all cards in this list" : "Alle Karten in dieser Liste dearchivieren",
|
||||
"Add a new card" : "Neue Karte hinzufügen",
|
||||
"Card name" : "Kartenname",
|
||||
"List deleted" : "Liste gelöscht",
|
||||
@@ -260,6 +262,7 @@ OC.L10N.register(
|
||||
"Shared with you" : "Mit Ihnen geteilt",
|
||||
"Deck settings" : "Deck-Einstellungen",
|
||||
"Use bigger card view" : "Größere Kartenansicht verwenden",
|
||||
"Show card ID badge" : "Abzeichen mit Karten-ID zeigen",
|
||||
"Show boards in calendar/tasks" : "Board in Kalender/Aufgaben anzeigen",
|
||||
"Limit deck usage of groups" : "Nutzung auf Gruppen einschränken",
|
||||
"Limiting Deck will block users not part of those groups from creating their own boards. Users will still be able to work on boards that have been shared with them." : "Wenn Sie Deck einschränken, können Benutzer, die nicht zu diesen Gruppen gehören, keine eigenen Boards erstellen. Die Benutzer können weiterhin an Boards arbeiten, die für sie freigegeben wurden.",
|
||||
|
||||
@@ -181,8 +181,10 @@
|
||||
"Failed to transfer the board to {user}" : "Das Board konnte nicht auf {user} übertragen werden",
|
||||
"Add a new list" : "Eine neue Liste hinzufügen",
|
||||
"Archive all cards" : "Alle Karten archivieren",
|
||||
"Unarchive all cards" : "Alle Karten dearchivieren",
|
||||
"Delete list" : "Liste löschen",
|
||||
"Archive all cards in this list" : "Alle Karten in dieser Liste archivieren",
|
||||
"Unarchive all cards in this list" : "Alle Karten in dieser Liste dearchivieren",
|
||||
"Add a new card" : "Neue Karte hinzufügen",
|
||||
"Card name" : "Kartenname",
|
||||
"List deleted" : "Liste gelöscht",
|
||||
@@ -258,6 +260,7 @@
|
||||
"Shared with you" : "Mit Ihnen geteilt",
|
||||
"Deck settings" : "Deck-Einstellungen",
|
||||
"Use bigger card view" : "Größere Kartenansicht verwenden",
|
||||
"Show card ID badge" : "Abzeichen mit Karten-ID zeigen",
|
||||
"Show boards in calendar/tasks" : "Board in Kalender/Aufgaben anzeigen",
|
||||
"Limit deck usage of groups" : "Nutzung auf Gruppen einschränken",
|
||||
"Limiting Deck will block users not part of those groups from creating their own boards. Users will still be able to work on boards that have been shared with them." : "Wenn Sie Deck einschränken, können Benutzer, die nicht zu diesen Gruppen gehören, keine eigenen Boards erstellen. Die Benutzer können weiterhin an Boards arbeiten, die für sie freigegeben wurden.",
|
||||
|
||||
@@ -183,8 +183,10 @@ OC.L10N.register(
|
||||
"Failed to transfer the board to {user}" : "Failed to transfer the board to {user}",
|
||||
"Add a new list" : "Añadir una lista nueva",
|
||||
"Archive all cards" : "Archivar todas las tarjetas",
|
||||
"Unarchive all cards" : "Desarchivar todas las tarjetas",
|
||||
"Delete list" : "Eliminar lista",
|
||||
"Archive all cards in this list" : "Archivar todas las tarjetas en esta lista",
|
||||
"Unarchive all cards in this list" : "Desarchivar todas las tarjetas en esta lista",
|
||||
"Add a new card" : "Añadir una nueva tarjeta",
|
||||
"Card name" : "Nombre de la tarjeta",
|
||||
"List deleted" : "Lista borrada",
|
||||
@@ -260,6 +262,7 @@ OC.L10N.register(
|
||||
"Shared with you" : "Compartido contigo",
|
||||
"Deck settings" : "Configuración del tablero",
|
||||
"Use bigger card view" : "Usar vista de tarjeta mayor",
|
||||
"Show card ID badge" : "Mostrar insignia de la ID de tarjeta",
|
||||
"Show boards in calendar/tasks" : "Mostrar tableros en calendario/tareas",
|
||||
"Limit deck usage of groups" : "Limitar el uso de Deck a grupos",
|
||||
"Limiting Deck will block users not part of those groups from creating their own boards. Users will still be able to work on boards that have been shared with them." : "Limitar Deck impedirá que usuarios que no formen parte de esos grupos creen sus propios tableros. Los usuarios todavía podrán trabajar en tableros que hayan sido compartidos con ellos.",
|
||||
|
||||
@@ -181,8 +181,10 @@
|
||||
"Failed to transfer the board to {user}" : "Failed to transfer the board to {user}",
|
||||
"Add a new list" : "Añadir una lista nueva",
|
||||
"Archive all cards" : "Archivar todas las tarjetas",
|
||||
"Unarchive all cards" : "Desarchivar todas las tarjetas",
|
||||
"Delete list" : "Eliminar lista",
|
||||
"Archive all cards in this list" : "Archivar todas las tarjetas en esta lista",
|
||||
"Unarchive all cards in this list" : "Desarchivar todas las tarjetas en esta lista",
|
||||
"Add a new card" : "Añadir una nueva tarjeta",
|
||||
"Card name" : "Nombre de la tarjeta",
|
||||
"List deleted" : "Lista borrada",
|
||||
@@ -258,6 +260,7 @@
|
||||
"Shared with you" : "Compartido contigo",
|
||||
"Deck settings" : "Configuración del tablero",
|
||||
"Use bigger card view" : "Usar vista de tarjeta mayor",
|
||||
"Show card ID badge" : "Mostrar insignia de la ID de tarjeta",
|
||||
"Show boards in calendar/tasks" : "Mostrar tableros en calendario/tareas",
|
||||
"Limit deck usage of groups" : "Limitar el uso de Deck a grupos",
|
||||
"Limiting Deck will block users not part of those groups from creating their own boards. Users will still be able to work on boards that have been shared with them." : "Limitar Deck impedirá que usuarios que no formen parte de esos grupos creen sus propios tableros. Los usuarios todavía podrán trabajar en tableros que hayan sido compartidos con ellos.",
|
||||
|
||||
@@ -260,6 +260,7 @@ OC.L10N.register(
|
||||
"Shared with you" : "Partagés avec vous",
|
||||
"Deck settings" : "Paramètres de Deck",
|
||||
"Use bigger card view" : "Utiliser la vue large des cartes",
|
||||
"Show card ID badge" : "Afficher la carte d'identité du badge",
|
||||
"Show boards in calendar/tasks" : "Afficher les tableaux dans les agendas/tâches",
|
||||
"Limit deck usage of groups" : "Limiter l'utilisation du tableau aux groupes",
|
||||
"Limiting Deck will block users not part of those groups from creating their own boards. Users will still be able to work on boards that have been shared with them." : "Limiter Deck empêchera les utilisateurs ne faisant pas partie de ces groupes de créer leurs propres tableaux. Ces utilisateurs pourront toujours travailler sur les tableaux qui ont été partagés avec eux.",
|
||||
|
||||
@@ -258,6 +258,7 @@
|
||||
"Shared with you" : "Partagés avec vous",
|
||||
"Deck settings" : "Paramètres de Deck",
|
||||
"Use bigger card view" : "Utiliser la vue large des cartes",
|
||||
"Show card ID badge" : "Afficher la carte d'identité du badge",
|
||||
"Show boards in calendar/tasks" : "Afficher les tableaux dans les agendas/tâches",
|
||||
"Limit deck usage of groups" : "Limiter l'utilisation du tableau aux groupes",
|
||||
"Limiting Deck will block users not part of those groups from creating their own boards. Users will still be able to work on boards that have been shared with them." : "Limiter Deck empêchera les utilisateurs ne faisant pas partie de ces groupes de créer leurs propres tableaux. Ces utilisateurs pourront toujours travailler sur les tableaux qui ont été partagés avec eux.",
|
||||
|
||||
@@ -70,6 +70,7 @@ OC.L10N.register(
|
||||
"Personal" : "Asmeniniai",
|
||||
"The card \"%s\" on \"%s\" has been assigned to you by %s." : "Kortelę \"%s\" ties \"%s\" priskyrė jums %s.",
|
||||
"The card \"%s\" on \"%s\" has reached its due date." : "Kortelė „%s“, esanti lentoje „%s“, pasiekė savo galutinį terminą.",
|
||||
"The card {deck-card} on {deck-board} has reached its due date." : "Kortelė {deck-card}, esanti lentoje {deck-board} pasiekė savo galutinio termino datą.",
|
||||
"%s has mentioned you in a comment on \"%s\"." : "%s paminėjo jus komentare ties \"%s\".",
|
||||
"The board \"%s\" has been shared with you by %s." : "Lentą \"%s\" su jumis bendrina %s.",
|
||||
"%s on %s" : "%s ant %s",
|
||||
@@ -200,9 +201,9 @@ OC.L10N.register(
|
||||
"Assign to users" : "Priskirti naudotojams",
|
||||
"Assign to users/groups/circles" : "Priskirti naudotojams/grupėms/ratams",
|
||||
"Assign a user to this card…" : "Priskirti šiai kortelei naudotoją…",
|
||||
"Due date" : "Terminas",
|
||||
"Due date" : "Galutinio termino data",
|
||||
"Set a due date" : "Nustatyti galutinį terminą",
|
||||
"Remove due date" : "Pašalinti terminą",
|
||||
"Remove due date" : "Šalinti galutinio termino datą",
|
||||
"Select Date" : "Pasirinkti datą",
|
||||
"Today" : "Šiandien",
|
||||
"Tomorrow" : "Rytoj",
|
||||
@@ -243,9 +244,9 @@ OC.L10N.register(
|
||||
"Clone board" : "Dubliuoti lentą",
|
||||
"Unarchive board" : "Išarchyvuoti lentą",
|
||||
"Archive board" : "Archyvuoti lentą",
|
||||
"Turn on due date reminders" : "Įjungti priminimus apie galutinį terminą",
|
||||
"Turn off due date reminders" : "Išjungti priminimus apie galutinį terminą",
|
||||
"Due date reminders" : "Priminimai apie galutinį terminą",
|
||||
"Turn on due date reminders" : "Įjungti priminimus apie galutinio termino datą",
|
||||
"Turn off due date reminders" : "Išjungti priminimus apie galutinio termino datą",
|
||||
"Due date reminders" : "Priminimai apie galutinio termino datą",
|
||||
"All cards" : "Visos kortelės",
|
||||
"Assigned cards" : "Priskirtos kortelės",
|
||||
"No notifications" : "Pranešimų nėra",
|
||||
@@ -257,6 +258,7 @@ OC.L10N.register(
|
||||
"Delete the board?" : "Ištrinti lentą?",
|
||||
"No due" : "Be galutinio termino",
|
||||
"No results found" : "Nerasta jokių rezultatų",
|
||||
"Due on {date}" : "Galutinis terminas {date}",
|
||||
"Link to a board" : "Susieti su lenta",
|
||||
"Link to a card" : "Susieti su kortele",
|
||||
"Create a card" : "Sukurti kortelę",
|
||||
|
||||
@@ -68,6 +68,7 @@
|
||||
"Personal" : "Asmeniniai",
|
||||
"The card \"%s\" on \"%s\" has been assigned to you by %s." : "Kortelę \"%s\" ties \"%s\" priskyrė jums %s.",
|
||||
"The card \"%s\" on \"%s\" has reached its due date." : "Kortelė „%s“, esanti lentoje „%s“, pasiekė savo galutinį terminą.",
|
||||
"The card {deck-card} on {deck-board} has reached its due date." : "Kortelė {deck-card}, esanti lentoje {deck-board} pasiekė savo galutinio termino datą.",
|
||||
"%s has mentioned you in a comment on \"%s\"." : "%s paminėjo jus komentare ties \"%s\".",
|
||||
"The board \"%s\" has been shared with you by %s." : "Lentą \"%s\" su jumis bendrina %s.",
|
||||
"%s on %s" : "%s ant %s",
|
||||
@@ -198,9 +199,9 @@
|
||||
"Assign to users" : "Priskirti naudotojams",
|
||||
"Assign to users/groups/circles" : "Priskirti naudotojams/grupėms/ratams",
|
||||
"Assign a user to this card…" : "Priskirti šiai kortelei naudotoją…",
|
||||
"Due date" : "Terminas",
|
||||
"Due date" : "Galutinio termino data",
|
||||
"Set a due date" : "Nustatyti galutinį terminą",
|
||||
"Remove due date" : "Pašalinti terminą",
|
||||
"Remove due date" : "Šalinti galutinio termino datą",
|
||||
"Select Date" : "Pasirinkti datą",
|
||||
"Today" : "Šiandien",
|
||||
"Tomorrow" : "Rytoj",
|
||||
@@ -241,9 +242,9 @@
|
||||
"Clone board" : "Dubliuoti lentą",
|
||||
"Unarchive board" : "Išarchyvuoti lentą",
|
||||
"Archive board" : "Archyvuoti lentą",
|
||||
"Turn on due date reminders" : "Įjungti priminimus apie galutinį terminą",
|
||||
"Turn off due date reminders" : "Išjungti priminimus apie galutinį terminą",
|
||||
"Due date reminders" : "Priminimai apie galutinį terminą",
|
||||
"Turn on due date reminders" : "Įjungti priminimus apie galutinio termino datą",
|
||||
"Turn off due date reminders" : "Išjungti priminimus apie galutinio termino datą",
|
||||
"Due date reminders" : "Priminimai apie galutinio termino datą",
|
||||
"All cards" : "Visos kortelės",
|
||||
"Assigned cards" : "Priskirtos kortelės",
|
||||
"No notifications" : "Pranešimų nėra",
|
||||
@@ -255,6 +256,7 @@
|
||||
"Delete the board?" : "Ištrinti lentą?",
|
||||
"No due" : "Be galutinio termino",
|
||||
"No results found" : "Nerasta jokių rezultatų",
|
||||
"Due on {date}" : "Galutinis terminas {date}",
|
||||
"Link to a board" : "Susieti su lenta",
|
||||
"Link to a card" : "Susieti su kortele",
|
||||
"Create a card" : "Sukurti kortelę",
|
||||
|
||||
@@ -183,8 +183,10 @@ OC.L10N.register(
|
||||
"Failed to transfer the board to {user}" : "Nie udało się przenieść tablicy do {user}",
|
||||
"Add a new list" : "Dodaj nową listę",
|
||||
"Archive all cards" : "Zarchiwizuj wszystkie karty",
|
||||
"Unarchive all cards" : "Przywróć wszystkie karty z archiwum",
|
||||
"Delete list" : "Usuń listę",
|
||||
"Archive all cards in this list" : "Zarchiwizuj wszystkie karty na tej liście",
|
||||
"Unarchive all cards in this list" : "Cofnij archiwizację wszystkich kart z tej listy",
|
||||
"Add a new card" : "Dodaj nową kartę",
|
||||
"Card name" : "Nazwa karty",
|
||||
"List deleted" : "Lista usunięta",
|
||||
@@ -260,6 +262,7 @@ OC.L10N.register(
|
||||
"Shared with you" : "Udostępnione Tobie",
|
||||
"Deck settings" : "Ustawienia Tablicy",
|
||||
"Use bigger card view" : "Użyj większego widoku karty",
|
||||
"Show card ID badge" : "Pokaż ID karty",
|
||||
"Show boards in calendar/tasks" : "Pokaż tablice w kalendarzu/zadaniach",
|
||||
"Limit deck usage of groups" : "Ogranicz użycie tablic dla grup",
|
||||
"Limiting Deck will block users not part of those groups from creating their own boards. Users will still be able to work on boards that have been shared with them." : "Ograniczenie Tablicy zablokuje użytkownikom z tych grup możliwość tworzenia własnych tablic. Użytkownicy nadal będą mogli pracować na tablicach, które zostały im udostępnione.",
|
||||
|
||||
@@ -181,8 +181,10 @@
|
||||
"Failed to transfer the board to {user}" : "Nie udało się przenieść tablicy do {user}",
|
||||
"Add a new list" : "Dodaj nową listę",
|
||||
"Archive all cards" : "Zarchiwizuj wszystkie karty",
|
||||
"Unarchive all cards" : "Przywróć wszystkie karty z archiwum",
|
||||
"Delete list" : "Usuń listę",
|
||||
"Archive all cards in this list" : "Zarchiwizuj wszystkie karty na tej liście",
|
||||
"Unarchive all cards in this list" : "Cofnij archiwizację wszystkich kart z tej listy",
|
||||
"Add a new card" : "Dodaj nową kartę",
|
||||
"Card name" : "Nazwa karty",
|
||||
"List deleted" : "Lista usunięta",
|
||||
@@ -258,6 +260,7 @@
|
||||
"Shared with you" : "Udostępnione Tobie",
|
||||
"Deck settings" : "Ustawienia Tablicy",
|
||||
"Use bigger card view" : "Użyj większego widoku karty",
|
||||
"Show card ID badge" : "Pokaż ID karty",
|
||||
"Show boards in calendar/tasks" : "Pokaż tablice w kalendarzu/zadaniach",
|
||||
"Limit deck usage of groups" : "Ogranicz użycie tablic dla grup",
|
||||
"Limiting Deck will block users not part of those groups from creating their own boards. Users will still be able to work on boards that have been shared with them." : "Ograniczenie Tablicy zablokuje użytkownikom z tych grup możliwość tworzenia własnych tablic. Użytkownicy nadal będą mogli pracować na tablicach, które zostały im udostępnione.",
|
||||
|
||||
23
l10n/sv.js
23
l10n/sv.js
@@ -136,6 +136,7 @@ OC.L10N.register(
|
||||
"Archived cards" : "Arkiverade kort",
|
||||
"Add list" : "Lägg till lista...",
|
||||
"List name" : "Namn på lista",
|
||||
"Active filters" : "Aktiva filter",
|
||||
"Apply filter" : "Tillämpa filter",
|
||||
"Filter by tag" : "Filtrera efter tagg",
|
||||
"Filter by assigned user" : "Filtrera efter tilldelad användare",
|
||||
@@ -175,11 +176,17 @@ OC.L10N.register(
|
||||
"Owner" : "Ägare",
|
||||
"Delete" : "Ta bort",
|
||||
"Failed to create share with {displayName}" : "Kunde inte skapa delning med {displayName}",
|
||||
"Are you sure you want to transfer the board {title} to {user}?" : "Är du säker att du vill överföra tavlan {title} till {user}?",
|
||||
"Transfer the board." : "Överför tavlan.",
|
||||
"Transfer" : "Överför",
|
||||
"The board has been transferred to {user}" : "Tavlan har överförts till {user}",
|
||||
"Failed to transfer the board to {user}" : "Kunde inte överföra tavlan till {user}",
|
||||
"Add a new list" : "Lägg till en ny lista",
|
||||
"Archive all cards" : "Arkivera alla kort",
|
||||
"Unarchive all cards" : "Ta ut alla kort ur arkivet",
|
||||
"Delete list" : "Ta bort lista",
|
||||
"Archive all cards in this list" : "Arkivera alla kort i denna lista",
|
||||
"Unarchive all cards in this list" : "Ta ut alla kort i denna lista ur arkivet",
|
||||
"Add a new card" : "Lägg till ett nytt kort",
|
||||
"Card name" : "Kortets namn",
|
||||
"List deleted" : "Listan har raderats",
|
||||
@@ -237,7 +244,9 @@ OC.L10N.register(
|
||||
"Write a description …" : "Ange en beskrivning ...",
|
||||
"Choose attachment" : "Välj bilaga",
|
||||
"(group)" : " (grupp)",
|
||||
"Todo items" : "Todo saker",
|
||||
"{count} comments, {unread} unread" : "{count} kommentarer, {unread} olästa",
|
||||
"Edit card title" : "Ändra korttitel",
|
||||
"Assign to me" : "Tilldela till mig",
|
||||
"Unassign myself" : "Ta bort från mig själv",
|
||||
"Move card" : "Flytta kort",
|
||||
@@ -251,7 +260,9 @@ OC.L10N.register(
|
||||
"All boards" : "Alla tavlor",
|
||||
"Archived boards" : "Arkiverade tavlor",
|
||||
"Shared with you" : "Delad med dig",
|
||||
"Deck settings" : "Deckinställningar",
|
||||
"Use bigger card view" : "Visa större kort",
|
||||
"Show card ID badge" : "Visa kortets ID-märke",
|
||||
"Show boards in calendar/tasks" : "Visa tavlor i kalender / uppgifter",
|
||||
"Limit deck usage of groups" : "Begränsa användningen av grupper",
|
||||
"Limiting Deck will block users not part of those groups from creating their own boards. Users will still be able to work on boards that have been shared with them." : "Begränsning av Deck blockerar användare som inte ingår i dessa grupper från att skapa egna tavlor. Användare kan dock fortfarande integrera med tavlor som har delats med dem.",
|
||||
@@ -271,13 +282,19 @@ OC.L10N.register(
|
||||
"Only assigned cards" : "Bara tilldelade kort",
|
||||
"No reminder" : "Ingen påminnelse",
|
||||
"An error occurred" : "Ett fel uppstod",
|
||||
"Are you sure you want to delete the board {title}? This will delete all the data of this board including archived cards." : "Är du säker på att du vill radera brädet {title}? Detta kommer radera all data som tillhör brädet inklusive arkiverade kort.",
|
||||
"Delete the board?" : "Ta bort tavlan?",
|
||||
"Loading filtered view" : "Laddar filtrerad vy",
|
||||
"No due" : "Inget slut",
|
||||
"Search for {searchQuery} in all boards" : "Sök efter {searchQuery} i alla tavlor",
|
||||
"No results found" : "Inga resultat funna",
|
||||
"{stack} in {board}" : "{stack} i {board}",
|
||||
"Click to expand description" : "Klicka för att utöka beskrivningen",
|
||||
"* Created on {created}\n* Last modified on {lastMod}\n* {nbAttachments} attachments\n* {nbComments} comments" : "* Skapades {created}\n* Ändrades senast {lastMod}\n* {nbAttachments} bilagor\n* {nbComments} kommentarer",
|
||||
"{nbCards} cards" : "{nbCards} kort",
|
||||
"No upcoming cards" : "Inga kommande kort",
|
||||
"upcoming cards" : "kommande kort",
|
||||
"Due on {date}" : "Går ut {date}",
|
||||
"Link to a board" : "Länka till en tavla",
|
||||
"Link to a card" : "Länka till ett kort",
|
||||
"Create a card" : "Skapa ett kort",
|
||||
@@ -290,7 +307,11 @@ OC.L10N.register(
|
||||
"Share {file} with a Deck card" : "Dela {file} med ett Deck-kort",
|
||||
"Share" : "Dela",
|
||||
"Deck is a kanban style organization tool aimed at personal planning and project organization for teams integrated with Nextcloud.\n\n\n- 📥 Add your tasks to cards and put them in order\n- 📄 Write down additional notes in markdown\n- 🔖 Assign labels for even better organization\n- 👥 Share with your team, friends or family\n- 📎 Attach files and embed them in your markdown description\n- 💬 Discuss with your team using comments\n- ⚡ Keep track of changes in the activity stream\n- 🚀 Get your project organized" : "Deck är en kanban-liknande projekt- och organiseringsapp för arbetsgrupper i Nextcloud.\n\n\n- 📥 Lägg till uppgifter på kort och ordna dem\n- 📄 Skriv anteckningar i markdown\n- 🔖 Tilldela etiketter för organisering\n- 👥 Dela med arbetsgruppen, vänner eller familj\n- 📎 Bifoga filer och bädda in dem i markdown-anteckningarna\n- 💬 Diskutera i arbetsgruppen genom kommentarer\n- ⚡ Håll koll på ändringar i aktivitetsflödet\n- 🚀 Börja organisera din arbetsgrupp nu!",
|
||||
"Are you sure you want to transfer the board {title} for {user} ?" : "Är du säker på att du vill överföra brädet {title} för {user}?",
|
||||
"Transfer the board for {user} successfully" : "Överförde brädet för {user}",
|
||||
"Failed to transfer the board for {user}" : "Misslyckades med att överföra brädet för {user}",
|
||||
"Are you sure you want to delete the board {title}? This will delete all the data of this board." : "Är du säker på att du vill radera tavla {title}? Detta kommer att radera all information från denna tavla.",
|
||||
"This week" : "Denna vecka"
|
||||
"This week" : "Denna vecka",
|
||||
"Are you sure you want to transfer the board {title} for {user}?" : "Är du säker på att du vill överföra brädet {title} för {user}?"
|
||||
},
|
||||
"nplurals=2; plural=(n != 1);");
|
||||
|
||||
23
l10n/sv.json
23
l10n/sv.json
@@ -134,6 +134,7 @@
|
||||
"Archived cards" : "Arkiverade kort",
|
||||
"Add list" : "Lägg till lista...",
|
||||
"List name" : "Namn på lista",
|
||||
"Active filters" : "Aktiva filter",
|
||||
"Apply filter" : "Tillämpa filter",
|
||||
"Filter by tag" : "Filtrera efter tagg",
|
||||
"Filter by assigned user" : "Filtrera efter tilldelad användare",
|
||||
@@ -173,11 +174,17 @@
|
||||
"Owner" : "Ägare",
|
||||
"Delete" : "Ta bort",
|
||||
"Failed to create share with {displayName}" : "Kunde inte skapa delning med {displayName}",
|
||||
"Are you sure you want to transfer the board {title} to {user}?" : "Är du säker att du vill överföra tavlan {title} till {user}?",
|
||||
"Transfer the board." : "Överför tavlan.",
|
||||
"Transfer" : "Överför",
|
||||
"The board has been transferred to {user}" : "Tavlan har överförts till {user}",
|
||||
"Failed to transfer the board to {user}" : "Kunde inte överföra tavlan till {user}",
|
||||
"Add a new list" : "Lägg till en ny lista",
|
||||
"Archive all cards" : "Arkivera alla kort",
|
||||
"Unarchive all cards" : "Ta ut alla kort ur arkivet",
|
||||
"Delete list" : "Ta bort lista",
|
||||
"Archive all cards in this list" : "Arkivera alla kort i denna lista",
|
||||
"Unarchive all cards in this list" : "Ta ut alla kort i denna lista ur arkivet",
|
||||
"Add a new card" : "Lägg till ett nytt kort",
|
||||
"Card name" : "Kortets namn",
|
||||
"List deleted" : "Listan har raderats",
|
||||
@@ -235,7 +242,9 @@
|
||||
"Write a description …" : "Ange en beskrivning ...",
|
||||
"Choose attachment" : "Välj bilaga",
|
||||
"(group)" : " (grupp)",
|
||||
"Todo items" : "Todo saker",
|
||||
"{count} comments, {unread} unread" : "{count} kommentarer, {unread} olästa",
|
||||
"Edit card title" : "Ändra korttitel",
|
||||
"Assign to me" : "Tilldela till mig",
|
||||
"Unassign myself" : "Ta bort från mig själv",
|
||||
"Move card" : "Flytta kort",
|
||||
@@ -249,7 +258,9 @@
|
||||
"All boards" : "Alla tavlor",
|
||||
"Archived boards" : "Arkiverade tavlor",
|
||||
"Shared with you" : "Delad med dig",
|
||||
"Deck settings" : "Deckinställningar",
|
||||
"Use bigger card view" : "Visa större kort",
|
||||
"Show card ID badge" : "Visa kortets ID-märke",
|
||||
"Show boards in calendar/tasks" : "Visa tavlor i kalender / uppgifter",
|
||||
"Limit deck usage of groups" : "Begränsa användningen av grupper",
|
||||
"Limiting Deck will block users not part of those groups from creating their own boards. Users will still be able to work on boards that have been shared with them." : "Begränsning av Deck blockerar användare som inte ingår i dessa grupper från att skapa egna tavlor. Användare kan dock fortfarande integrera med tavlor som har delats med dem.",
|
||||
@@ -269,13 +280,19 @@
|
||||
"Only assigned cards" : "Bara tilldelade kort",
|
||||
"No reminder" : "Ingen påminnelse",
|
||||
"An error occurred" : "Ett fel uppstod",
|
||||
"Are you sure you want to delete the board {title}? This will delete all the data of this board including archived cards." : "Är du säker på att du vill radera brädet {title}? Detta kommer radera all data som tillhör brädet inklusive arkiverade kort.",
|
||||
"Delete the board?" : "Ta bort tavlan?",
|
||||
"Loading filtered view" : "Laddar filtrerad vy",
|
||||
"No due" : "Inget slut",
|
||||
"Search for {searchQuery} in all boards" : "Sök efter {searchQuery} i alla tavlor",
|
||||
"No results found" : "Inga resultat funna",
|
||||
"{stack} in {board}" : "{stack} i {board}",
|
||||
"Click to expand description" : "Klicka för att utöka beskrivningen",
|
||||
"* Created on {created}\n* Last modified on {lastMod}\n* {nbAttachments} attachments\n* {nbComments} comments" : "* Skapades {created}\n* Ändrades senast {lastMod}\n* {nbAttachments} bilagor\n* {nbComments} kommentarer",
|
||||
"{nbCards} cards" : "{nbCards} kort",
|
||||
"No upcoming cards" : "Inga kommande kort",
|
||||
"upcoming cards" : "kommande kort",
|
||||
"Due on {date}" : "Går ut {date}",
|
||||
"Link to a board" : "Länka till en tavla",
|
||||
"Link to a card" : "Länka till ett kort",
|
||||
"Create a card" : "Skapa ett kort",
|
||||
@@ -288,7 +305,11 @@
|
||||
"Share {file} with a Deck card" : "Dela {file} med ett Deck-kort",
|
||||
"Share" : "Dela",
|
||||
"Deck is a kanban style organization tool aimed at personal planning and project organization for teams integrated with Nextcloud.\n\n\n- 📥 Add your tasks to cards and put them in order\n- 📄 Write down additional notes in markdown\n- 🔖 Assign labels for even better organization\n- 👥 Share with your team, friends or family\n- 📎 Attach files and embed them in your markdown description\n- 💬 Discuss with your team using comments\n- ⚡ Keep track of changes in the activity stream\n- 🚀 Get your project organized" : "Deck är en kanban-liknande projekt- och organiseringsapp för arbetsgrupper i Nextcloud.\n\n\n- 📥 Lägg till uppgifter på kort och ordna dem\n- 📄 Skriv anteckningar i markdown\n- 🔖 Tilldela etiketter för organisering\n- 👥 Dela med arbetsgruppen, vänner eller familj\n- 📎 Bifoga filer och bädda in dem i markdown-anteckningarna\n- 💬 Diskutera i arbetsgruppen genom kommentarer\n- ⚡ Håll koll på ändringar i aktivitetsflödet\n- 🚀 Börja organisera din arbetsgrupp nu!",
|
||||
"Are you sure you want to transfer the board {title} for {user} ?" : "Är du säker på att du vill överföra brädet {title} för {user}?",
|
||||
"Transfer the board for {user} successfully" : "Överförde brädet för {user}",
|
||||
"Failed to transfer the board for {user}" : "Misslyckades med att överföra brädet för {user}",
|
||||
"Are you sure you want to delete the board {title}? This will delete all the data of this board." : "Är du säker på att du vill radera tavla {title}? Detta kommer att radera all information från denna tavla.",
|
||||
"This week" : "Denna vecka"
|
||||
"This week" : "Denna vecka",
|
||||
"Are you sure you want to transfer the board {title} for {user}?" : "Är du säker på att du vill överföra brädet {title} för {user}?"
|
||||
},"pluralForm" :"nplurals=2; plural=(n != 1);"
|
||||
}
|
||||
@@ -183,8 +183,10 @@ OC.L10N.register(
|
||||
"Failed to transfer the board to {user}" : "Pano {user} kullanıcısına aktarılamadı",
|
||||
"Add a new list" : "Yeni liste ekle",
|
||||
"Archive all cards" : "Tüm kartları arşivle",
|
||||
"Unarchive all cards" : "Tüm kartları arşivden çıkar",
|
||||
"Delete list" : "Listeyi sil",
|
||||
"Archive all cards in this list" : "Bu listedeki tüm kartları arşivle",
|
||||
"Unarchive all cards in this list" : "Bu listedeki tüm kartları arşivden çıkar",
|
||||
"Add a new card" : "Yeni kart ekle",
|
||||
"Card name" : "Kart adı",
|
||||
"List deleted" : "Liste silindi",
|
||||
@@ -260,6 +262,7 @@ OC.L10N.register(
|
||||
"Shared with you" : "Sizinle paylaşıldı",
|
||||
"Deck settings" : "Tahta ayarları",
|
||||
"Use bigger card view" : "Daha büyük kart görünümü kullanılsın",
|
||||
"Show card ID badge" : "Kart kodu etiketi görüntülensin",
|
||||
"Show boards in calendar/tasks" : "Takvimler ve görevlerde panolar görüntülensin",
|
||||
"Limit deck usage of groups" : "Tahtayı şu gruplar kullanabilsin",
|
||||
"Limiting Deck will block users not part of those groups from creating their own boards. Users will still be able to work on boards that have been shared with them." : "Tahta kullanımı gruplar ile sınırlandığında belirtilen grupların üyesi olmayan kişiler kendi tahtalarını oluşturamaz. Bu kullanıcılar ancak kendileri ile paylaşılan tahtalar üzerinde çalışabilir.",
|
||||
|
||||
@@ -181,8 +181,10 @@
|
||||
"Failed to transfer the board to {user}" : "Pano {user} kullanıcısına aktarılamadı",
|
||||
"Add a new list" : "Yeni liste ekle",
|
||||
"Archive all cards" : "Tüm kartları arşivle",
|
||||
"Unarchive all cards" : "Tüm kartları arşivden çıkar",
|
||||
"Delete list" : "Listeyi sil",
|
||||
"Archive all cards in this list" : "Bu listedeki tüm kartları arşivle",
|
||||
"Unarchive all cards in this list" : "Bu listedeki tüm kartları arşivden çıkar",
|
||||
"Add a new card" : "Yeni kart ekle",
|
||||
"Card name" : "Kart adı",
|
||||
"List deleted" : "Liste silindi",
|
||||
@@ -258,6 +260,7 @@
|
||||
"Shared with you" : "Sizinle paylaşıldı",
|
||||
"Deck settings" : "Tahta ayarları",
|
||||
"Use bigger card view" : "Daha büyük kart görünümü kullanılsın",
|
||||
"Show card ID badge" : "Kart kodu etiketi görüntülensin",
|
||||
"Show boards in calendar/tasks" : "Takvimler ve görevlerde panolar görüntülensin",
|
||||
"Limit deck usage of groups" : "Tahtayı şu gruplar kullanabilsin",
|
||||
"Limiting Deck will block users not part of those groups from creating their own boards. Users will still be able to work on boards that have been shared with them." : "Tahta kullanımı gruplar ile sınırlandığında belirtilen grupların üyesi olmayan kişiler kendi tahtalarını oluşturamaz. Bu kullanıcılar ancak kendileri ile paylaşılan tahtalar üzerinde çalışabilir.",
|
||||
|
||||
@@ -183,8 +183,10 @@ OC.L10N.register(
|
||||
"Failed to transfer the board to {user}" : "未能將面板轉移給 {user}",
|
||||
"Add a new list" : "添加一張新清單",
|
||||
"Archive all cards" : "封存所有卡片",
|
||||
"Unarchive all cards" : "解除封存所有卡片",
|
||||
"Delete list" : "刪除清單",
|
||||
"Archive all cards in this list" : "封存此清單內的所有卡片",
|
||||
"Unarchive all cards in this list" : "解除封存所有此列表中的卡片",
|
||||
"Add a new card" : "添加一張新卡片",
|
||||
"Card name" : "卡片名稱",
|
||||
"List deleted" : "清單已被刪除",
|
||||
@@ -260,6 +262,7 @@ OC.L10N.register(
|
||||
"Shared with you" : "收到的分享",
|
||||
"Deck settings" : "看板設定",
|
||||
"Use bigger card view" : "使用較大的卡片視圖",
|
||||
"Show card ID badge" : "顯示卡片 ID 徽章",
|
||||
"Show boards in calendar/tasks" : "在日曆/任務中顯示面板",
|
||||
"Limit deck usage of groups" : "限制群組使用 Deck",
|
||||
"Limiting Deck will block users not part of those groups from creating their own boards. Users will still be able to work on boards that have been shared with them." : "限制 Deck 將阻止不屬於這些群組的用戶創建自己的面板。用戶仍然可以在與他們的面板上工作。",
|
||||
|
||||
@@ -181,8 +181,10 @@
|
||||
"Failed to transfer the board to {user}" : "未能將面板轉移給 {user}",
|
||||
"Add a new list" : "添加一張新清單",
|
||||
"Archive all cards" : "封存所有卡片",
|
||||
"Unarchive all cards" : "解除封存所有卡片",
|
||||
"Delete list" : "刪除清單",
|
||||
"Archive all cards in this list" : "封存此清單內的所有卡片",
|
||||
"Unarchive all cards in this list" : "解除封存所有此列表中的卡片",
|
||||
"Add a new card" : "添加一張新卡片",
|
||||
"Card name" : "卡片名稱",
|
||||
"List deleted" : "清單已被刪除",
|
||||
@@ -258,6 +260,7 @@
|
||||
"Shared with you" : "收到的分享",
|
||||
"Deck settings" : "看板設定",
|
||||
"Use bigger card view" : "使用較大的卡片視圖",
|
||||
"Show card ID badge" : "顯示卡片 ID 徽章",
|
||||
"Show boards in calendar/tasks" : "在日曆/任務中顯示面板",
|
||||
"Limit deck usage of groups" : "限制群組使用 Deck",
|
||||
"Limiting Deck will block users not part of those groups from creating their own boards. Users will still be able to work on boards that have been shared with them." : "限制 Deck 將阻止不屬於這些群組的用戶創建自己的面板。用戶仍然可以在與他們的面板上工作。",
|
||||
|
||||
@@ -183,8 +183,10 @@ OC.L10N.register(
|
||||
"Failed to transfer the board to {user}" : "轉移看板給 {user} 失敗",
|
||||
"Add a new list" : "新增列表",
|
||||
"Archive all cards" : "封存所有卡片",
|
||||
"Unarchive all cards" : "解除封存所有卡片",
|
||||
"Delete list" : "刪除列表",
|
||||
"Archive all cards in this list" : "封存此列表中的所有卡片",
|
||||
"Unarchive all cards in this list" : "解除封存所有此列表中的卡片",
|
||||
"Add a new card" : "新增卡片",
|
||||
"Card name" : "卡片名稱",
|
||||
"List deleted" : "列表已刪除",
|
||||
@@ -260,6 +262,7 @@ OC.L10N.register(
|
||||
"Shared with you" : "與您分享",
|
||||
"Deck settings" : "Deck 設定",
|
||||
"Use bigger card view" : "使用較大的卡片檢視",
|
||||
"Show card ID badge" : "顯示卡片 ID 徽章",
|
||||
"Show boards in calendar/tasks" : "在日曆/工作項目中顯示佈告欄",
|
||||
"Limit deck usage of groups" : "限制群組的 Deck 使用",
|
||||
"Limiting Deck will block users not part of those groups from creating their own boards. Users will still be able to work on boards that have been shared with them." : "限制 Deck 將會阻止不屬於這些群組的使用者建立自己的佈告欄。使用者仍然可以在與他們分享的佈告欄上工作。",
|
||||
|
||||
@@ -181,8 +181,10 @@
|
||||
"Failed to transfer the board to {user}" : "轉移看板給 {user} 失敗",
|
||||
"Add a new list" : "新增列表",
|
||||
"Archive all cards" : "封存所有卡片",
|
||||
"Unarchive all cards" : "解除封存所有卡片",
|
||||
"Delete list" : "刪除列表",
|
||||
"Archive all cards in this list" : "封存此列表中的所有卡片",
|
||||
"Unarchive all cards in this list" : "解除封存所有此列表中的卡片",
|
||||
"Add a new card" : "新增卡片",
|
||||
"Card name" : "卡片名稱",
|
||||
"List deleted" : "列表已刪除",
|
||||
@@ -258,6 +260,7 @@
|
||||
"Shared with you" : "與您分享",
|
||||
"Deck settings" : "Deck 設定",
|
||||
"Use bigger card view" : "使用較大的卡片檢視",
|
||||
"Show card ID badge" : "顯示卡片 ID 徽章",
|
||||
"Show boards in calendar/tasks" : "在日曆/工作項目中顯示佈告欄",
|
||||
"Limit deck usage of groups" : "限制群組的 Deck 使用",
|
||||
"Limiting Deck will block users not part of those groups from creating their own boards. Users will still be able to work on boards that have been shared with them." : "限制 Deck 將會阻止不屬於這些群組的使用者建立自己的佈告欄。使用者仍然可以在與他們分享的佈告欄上工作。",
|
||||
|
||||
@@ -31,6 +31,7 @@ use OCA\Deck\Db\Acl;
|
||||
use OCA\Deck\Db\AclMapper;
|
||||
use OCA\Deck\Db\Assignment;
|
||||
use OCA\Deck\Db\Attachment;
|
||||
use OCA\Deck\Db\AttachmentMapper;
|
||||
use OCA\Deck\Db\Board;
|
||||
use OCA\Deck\Db\BoardMapper;
|
||||
use OCA\Deck\Db\Card;
|
||||
@@ -49,15 +50,12 @@ use OCP\L10N\IFactory;
|
||||
|
||||
class ActivityManager {
|
||||
public const DECK_NOAUTHOR_COMMENT_SYSTEM_ENFORCED = 'DECK_NOAUTHOR_COMMENT_SYSTEM_ENFORCED';
|
||||
|
||||
public const SUBJECT_PARAMS_MAX_LENGTH = 4000;
|
||||
public const SHORTENED_DESCRIPTION_MAX_LENGTH = 2000;
|
||||
|
||||
private $manager;
|
||||
private $userId;
|
||||
private $permissionService;
|
||||
private $boardMapper;
|
||||
private $cardMapper;
|
||||
private $attachmentMapper;
|
||||
private $aclMapper;
|
||||
private $stackMapper;
|
||||
private $l10nFactory;
|
||||
@@ -112,6 +110,7 @@ class ActivityManager {
|
||||
BoardMapper $boardMapper,
|
||||
CardMapper $cardMapper,
|
||||
StackMapper $stackMapper,
|
||||
AttachmentMapper $attachmentMapper,
|
||||
AclMapper $aclMapper,
|
||||
IFactory $l10nFactory,
|
||||
$userId
|
||||
@@ -121,6 +120,7 @@ class ActivityManager {
|
||||
$this->boardMapper = $boardMapper;
|
||||
$this->cardMapper = $cardMapper;
|
||||
$this->stackMapper = $stackMapper;
|
||||
$this->attachmentMapper = $attachmentMapper;
|
||||
$this->aclMapper = $aclMapper;
|
||||
$this->l10nFactory = $l10nFactory;
|
||||
$this->userId = $userId;
|
||||
@@ -249,6 +249,19 @@ class ActivityManager {
|
||||
try {
|
||||
$event = $this->createEvent($objectType, $entity, $subject, $additionalParams, $author);
|
||||
if ($event !== null) {
|
||||
$json = json_encode($event->getSubjectParameters());
|
||||
if (mb_strlen($json) > 4000) {
|
||||
$params = json_decode(json_encode($event->getSubjectParameters()), true);
|
||||
|
||||
$newContent = $params['after'];
|
||||
unset($params['before'], $params['after'], $params['card']['description']);
|
||||
|
||||
$params['after'] = mb_substr($newContent, 0, 2000);
|
||||
if (mb_strlen($newContent) > 2000) {
|
||||
$params['after'] .= '...';
|
||||
}
|
||||
$event->setSubject($event->getSubject(), $params);
|
||||
}
|
||||
$this->sendToUsers($event);
|
||||
}
|
||||
} catch (\Exception $e) {
|
||||
@@ -397,31 +410,12 @@ class ActivityManager {
|
||||
|
||||
$subjectParams['author'] = $author === null ? $this->userId : $author;
|
||||
|
||||
$subjectParams = array_merge($subjectParams, $additionalParams);
|
||||
$json = json_encode($subjectParams);
|
||||
if (mb_strlen($json) > self::SUBJECT_PARAMS_MAX_LENGTH) {
|
||||
$params = json_decode(json_encode($subjectParams), true);
|
||||
|
||||
if ($subject === self::SUBJECT_CARD_UPDATE_DESCRIPTION && isset($params['after'])) {
|
||||
$newContent = $params['after'];
|
||||
unset($params['before'], $params['after'], $params['card']['description']);
|
||||
|
||||
$params['after'] = mb_substr($newContent, 0, self::SHORTENED_DESCRIPTION_MAX_LENGTH);
|
||||
if (mb_strlen($newContent) > self::SHORTENED_DESCRIPTION_MAX_LENGTH) {
|
||||
$params['after'] .= '...';
|
||||
}
|
||||
$subjectParams = $params;
|
||||
} else {
|
||||
throw new \Exception('Subject parameters too long');
|
||||
}
|
||||
}
|
||||
|
||||
$event = $this->manager->generateEvent();
|
||||
$event->setApp('deck')
|
||||
->setType($eventType)
|
||||
->setAuthor($subjectParams['author'])
|
||||
->setObject($objectType, (int)$object->getId(), $object->getTitle())
|
||||
->setSubject($subject, $subjectParams)
|
||||
->setSubject($subject, array_merge($subjectParams, $additionalParams))
|
||||
->setTimestamp(time());
|
||||
|
||||
if ($message !== null) {
|
||||
|
||||
@@ -69,7 +69,15 @@ class ChangeSet implements \JsonSerializable {
|
||||
return $this->after;
|
||||
}
|
||||
|
||||
public function jsonSerialize(): array {
|
||||
/**
|
||||
* Specify data which should be serialized to JSON
|
||||
*
|
||||
* @link http://php.net/manual/en/jsonserializable.jsonserialize.php
|
||||
* @return mixed data which can be serialized by <b>json_encode</b>,
|
||||
* which is a value of any type other than a resource.
|
||||
* @since 5.4.0
|
||||
*/
|
||||
public function jsonSerialize() {
|
||||
return [
|
||||
'before' => $this->getBefore(),
|
||||
'after' => $this->getAfter(),
|
||||
|
||||
@@ -121,7 +121,6 @@ class DeckProvider implements IProvider {
|
||||
'link' => $this->deckUrl('/board/' . $event->getObjectId()),
|
||||
];
|
||||
$params['board'] = $board;
|
||||
$event->setLink($this->deckUrl('/board/' . $event->getObjectId()));
|
||||
}
|
||||
|
||||
if (isset($subjectParams['card']) && $event->getObjectType() === ActivityManager::DECK_OBJECT_CARD) {
|
||||
@@ -135,8 +134,8 @@ class DeckProvider implements IProvider {
|
||||
];
|
||||
|
||||
if (array_key_exists('board', $subjectParams)) {
|
||||
$archivedParam = $subjectParams['card']['archived'] ? 'archived/' : '';
|
||||
$card['link'] = $this->cardService->getRedirectUrlForCard($event->getObjectId());
|
||||
$event->setLink($card['link']);
|
||||
}
|
||||
$params['card'] = $card;
|
||||
}
|
||||
|
||||
@@ -100,9 +100,6 @@ class ResourceProvider implements IProvider {
|
||||
if ($board->getOwner() === $user->getUID()) {
|
||||
return true;
|
||||
}
|
||||
if ($board->getAcl() === null) {
|
||||
return false;
|
||||
}
|
||||
return $this->permissionService->userCan($board->getAcl(), Acl::PERMISSION_READ, $user->getUID());
|
||||
}
|
||||
|
||||
|
||||
@@ -127,9 +127,6 @@ class ResourceProviderCard implements IProvider {
|
||||
if ($board->getOwner() === $user->getUID()) {
|
||||
return true;
|
||||
}
|
||||
if ($board->getAcl() === null) {
|
||||
return false;
|
||||
}
|
||||
return $this->permissionService->userCan($board->getAcl(), Acl::PERMISSION_READ, $user->getUID());
|
||||
}
|
||||
|
||||
|
||||
@@ -1,92 +0,0 @@
|
||||
<?php
|
||||
/**
|
||||
* @copyright Copyright (c) 2021 Vitor Mattos <vitor@php.rio>
|
||||
*
|
||||
* @author Vitor Mattos <vitor@php.rio>
|
||||
*
|
||||
* @license GNU AGPL version 3 or any later version
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as
|
||||
* published by the Free Software Foundation, either version 3 of the
|
||||
* License, or (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
*/
|
||||
|
||||
namespace OCA\Deck\Command;
|
||||
|
||||
use OCA\Deck\Service\Importer\BoardImportCommandService;
|
||||
use Symfony\Component\Console\Command\Command;
|
||||
use Symfony\Component\Console\Input\InputInterface;
|
||||
use Symfony\Component\Console\Input\InputOption;
|
||||
use Symfony\Component\Console\Output\OutputInterface;
|
||||
|
||||
class BoardImport extends Command {
|
||||
/** @var BoardImportCommandService */
|
||||
private $boardImportCommandService;
|
||||
|
||||
public function __construct(
|
||||
BoardImportCommandService $boardImportCommandService
|
||||
) {
|
||||
$this->boardImportCommandService = $boardImportCommandService;
|
||||
parent::__construct();
|
||||
}
|
||||
|
||||
/**
|
||||
* @return void
|
||||
*/
|
||||
protected function configure() {
|
||||
$allowedSystems = $this->boardImportCommandService->getAllowedImportSystems();
|
||||
$names = array_column($allowedSystems, 'name');
|
||||
$this
|
||||
->setName('deck:import')
|
||||
->setDescription('Import data')
|
||||
->addOption(
|
||||
'system',
|
||||
null,
|
||||
InputOption::VALUE_REQUIRED,
|
||||
'Source system for import. Available options: ' . implode(', ', $names) . '.',
|
||||
null
|
||||
)
|
||||
->addOption(
|
||||
'config',
|
||||
null,
|
||||
InputOption::VALUE_REQUIRED,
|
||||
'Configuration json file.',
|
||||
'config.json'
|
||||
)
|
||||
->addOption(
|
||||
'data',
|
||||
null,
|
||||
InputOption::VALUE_OPTIONAL,
|
||||
'Data file to import.',
|
||||
'data.json'
|
||||
)
|
||||
;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param InputInterface $input
|
||||
* @param OutputInterface $output
|
||||
*
|
||||
* @return int
|
||||
*/
|
||||
protected function execute(InputInterface $input, OutputInterface $output): int {
|
||||
$this
|
||||
->boardImportCommandService
|
||||
->setInput($input)
|
||||
->setOutput($output)
|
||||
->setCommand($this)
|
||||
->import();
|
||||
$output->writeln('Done!');
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
@@ -1,85 +0,0 @@
|
||||
<?php
|
||||
/**
|
||||
* @copyright Copyright (c) 2021 Vitor Mattos <vitor@php.rio>
|
||||
*
|
||||
* @author Vitor Mattos <vitor@php.rio>
|
||||
*
|
||||
* @license GNU AGPL version 3 or any later version
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as
|
||||
* published by the Free Software Foundation, either version 3 of the
|
||||
* License, or (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
*/
|
||||
|
||||
namespace OCA\Deck\Controller;
|
||||
|
||||
use OCA\Deck\Service\Importer\BoardImportService;
|
||||
use OCP\AppFramework\Http;
|
||||
use OCP\AppFramework\Http\DataResponse;
|
||||
use OCP\AppFramework\OCSController;
|
||||
use OCP\IRequest;
|
||||
|
||||
class BoardImportApiController extends OCSController {
|
||||
/** @var BoardImportService */
|
||||
private $boardImportService;
|
||||
/** @var string */
|
||||
private $userId;
|
||||
|
||||
public function __construct(
|
||||
string $appName,
|
||||
IRequest $request,
|
||||
BoardImportService $boardImportService,
|
||||
string $userId
|
||||
) {
|
||||
parent::__construct($appName, $request);
|
||||
$this->boardImportService = $boardImportService;
|
||||
$this->userId = $userId;
|
||||
}
|
||||
|
||||
/**
|
||||
* @NoAdminRequired
|
||||
* @CORS
|
||||
* @NoCSRFRequired
|
||||
*/
|
||||
public function import(string $system, array $config, array $data): DataResponse {
|
||||
$this->boardImportService->setSystem($system);
|
||||
$config = json_decode(json_encode($config));
|
||||
$config->owner = $this->userId;
|
||||
$this->boardImportService->setConfigInstance($config);
|
||||
$this->boardImportService->setData(json_decode(json_encode($data)));
|
||||
$this->boardImportService->import();
|
||||
return new DataResponse($this->boardImportService->getBoard(), Http::STATUS_OK);
|
||||
}
|
||||
|
||||
/**
|
||||
* @NoAdminRequired
|
||||
* @CORS
|
||||
* @NoCSRFRequired
|
||||
*/
|
||||
public function getAllowedSystems(): DataResponse {
|
||||
$allowedSystems = $this->boardImportService->getAllowedImportSystems();
|
||||
return new DataResponse($allowedSystems, Http::STATUS_OK);
|
||||
}
|
||||
|
||||
/**
|
||||
* @NoAdminRequired
|
||||
* @CORS
|
||||
* @NoCSRFRequired
|
||||
*/
|
||||
public function getConfigSchema(string $name): DataResponse {
|
||||
$this->boardImportService->setSystem($name);
|
||||
$this->boardImportService->validateSystem();
|
||||
$jsonSchemaPath = json_decode(file_get_contents($this->boardImportService->getJsonSchemaPath()));
|
||||
return new DataResponse($jsonSchemaPath, Http::STATUS_OK);
|
||||
}
|
||||
}
|
||||
@@ -24,8 +24,7 @@
|
||||
|
||||
namespace OCA\Deck\Cron;
|
||||
|
||||
use OCP\AppFramework\Utility\ITimeFactory;
|
||||
use OCP\BackgroundJob\Job;
|
||||
use OC\BackgroundJob\Job;
|
||||
use OCA\Deck\Activity\ActivityManager;
|
||||
use OCA\Deck\Db\CardMapper;
|
||||
|
||||
@@ -36,8 +35,7 @@ class CardDescriptionActivity extends Job {
|
||||
/** @var CardMapper */
|
||||
private $cardMapper;
|
||||
|
||||
public function __construct(ITimeFactory $time, ActivityManager $activityManager, CardMapper $cardMapper) {
|
||||
parent::__construct($time);
|
||||
public function __construct(ActivityManager $activityManager, CardMapper $cardMapper) {
|
||||
$this->activityManager = $activityManager;
|
||||
$this->cardMapper = $cardMapper;
|
||||
}
|
||||
|
||||
@@ -24,15 +24,13 @@
|
||||
|
||||
namespace OCA\Deck\Cron;
|
||||
|
||||
use OCP\AppFramework\Utility\ITimeFactory;
|
||||
use OCP\BackgroundJob\TimedJob;
|
||||
use OC\BackgroundJob\Job;
|
||||
use OCA\Deck\Db\AttachmentMapper;
|
||||
use OCA\Deck\Db\BoardMapper;
|
||||
use OCA\Deck\InvalidAttachmentType;
|
||||
use OCA\Deck\Service\AttachmentService;
|
||||
use OCP\BackgroundJob\IJob;
|
||||
|
||||
class DeleteCron extends TimedJob {
|
||||
class DeleteCron extends Job {
|
||||
|
||||
/** @var BoardMapper */
|
||||
private $boardMapper;
|
||||
@@ -41,14 +39,10 @@ class DeleteCron extends TimedJob {
|
||||
/** @var AttachmentMapper */
|
||||
private $attachmentMapper;
|
||||
|
||||
public function __construct(ITimeFactory $time, BoardMapper $boardMapper, AttachmentService $attachmentService, AttachmentMapper $attachmentMapper) {
|
||||
parent::__construct($time);
|
||||
public function __construct(BoardMapper $boardMapper, AttachmentService $attachmentService, AttachmentMapper $attachmentMapper) {
|
||||
$this->boardMapper = $boardMapper;
|
||||
$this->attachmentService = $attachmentService;
|
||||
$this->attachmentMapper = $attachmentMapper;
|
||||
|
||||
$this->setInterval(60 * 60 * 24);
|
||||
$this->setTimeSensitivity(IJob::TIME_INSENSITIVE);
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -23,8 +23,7 @@
|
||||
|
||||
namespace OCA\Deck\Cron;
|
||||
|
||||
use OCP\AppFramework\Utility\ITimeFactory;
|
||||
use OCP\BackgroundJob\Job;
|
||||
use OC\BackgroundJob\Job;
|
||||
use OCA\Deck\Db\Card;
|
||||
use OCA\Deck\Db\CardMapper;
|
||||
use OCA\Deck\Notification\NotificationHelper;
|
||||
@@ -41,12 +40,10 @@ class ScheduledNotifications extends Job {
|
||||
protected $logger;
|
||||
|
||||
public function __construct(
|
||||
ITimeFactory $time,
|
||||
CardMapper $cardMapper,
|
||||
NotificationHelper $notificationHelper,
|
||||
ILogger $logger
|
||||
) {
|
||||
parent::__construct($time);
|
||||
$this->cardMapper = $cardMapper;
|
||||
$this->notificationHelper = $notificationHelper;
|
||||
$this->logger = $logger;
|
||||
|
||||
@@ -52,6 +52,12 @@ class Calendar extends ExternalCalendar {
|
||||
$this->board = $board;
|
||||
|
||||
$this->principalUri = $principalUri;
|
||||
|
||||
if ($board) {
|
||||
$this->children = $this->backend->getChildren($board->getId());
|
||||
} else {
|
||||
$this->children = [];
|
||||
}
|
||||
}
|
||||
|
||||
public function getOwner() {
|
||||
@@ -116,7 +122,7 @@ class Calendar extends ExternalCalendar {
|
||||
public function getChild($name) {
|
||||
if ($this->childExists($name)) {
|
||||
$card = array_values(array_filter(
|
||||
$this->getBackendChildren(),
|
||||
$this->children,
|
||||
function ($card) use (&$name) {
|
||||
return $card->getCalendarPrefix() . '-' . $card->getId() . '.ics' === $name;
|
||||
}
|
||||
@@ -131,7 +137,7 @@ class Calendar extends ExternalCalendar {
|
||||
public function getChildren() {
|
||||
$childNames = array_map(function ($card) {
|
||||
return $card->getCalendarPrefix() . '-' . $card->getId() . '.ics';
|
||||
}, $this->getBackendChildren());
|
||||
}, $this->children);
|
||||
|
||||
$children = [];
|
||||
|
||||
@@ -142,23 +148,9 @@ class Calendar extends ExternalCalendar {
|
||||
return $children;
|
||||
}
|
||||
|
||||
private function getBackendChildren() {
|
||||
if ($this->children) {
|
||||
return $this->children;
|
||||
}
|
||||
|
||||
if ($this->board) {
|
||||
$this->children = $this->backend->getChildren($this->board->getId());
|
||||
} else {
|
||||
$this->children = [];
|
||||
}
|
||||
|
||||
return $this->children;
|
||||
}
|
||||
|
||||
public function childExists($name) {
|
||||
return count(array_filter(
|
||||
$this->getBackendChildren(),
|
||||
$this->children,
|
||||
function ($card) use (&$name) {
|
||||
return $card->getCalendarPrefix() . '-' . $card->getId() . '.ics' === $name;
|
||||
}
|
||||
|
||||
@@ -67,13 +67,13 @@ class AttachmentMapper extends DeckMapper implements IPermissionMapper {
|
||||
$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());
|
||||
throw new DoesNotExistException('Did expect one result but found none when executing' . $qb);
|
||||
}
|
||||
|
||||
$row2 = $cursor->fetch();
|
||||
$cursor->closeCursor();
|
||||
if ($row2 !== false) {
|
||||
throw new MultipleObjectsReturnedException('Did not expect more than one result when executing query: ' . $qb->getSQL());
|
||||
throw new MultipleObjectsReturnedException('Did not expect more than one result when executing' . $qb);
|
||||
}
|
||||
|
||||
return $this->mapRowToEntity($row);
|
||||
@@ -89,7 +89,7 @@ class AttachmentMapper extends DeckMapper implements IPermissionMapper {
|
||||
$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());
|
||||
throw new DoesNotExistException('Did expect one result but found none when executing' . $qb);
|
||||
}
|
||||
$cursor->closeCursor();
|
||||
return $this->mapRowToEntity($row);
|
||||
|
||||
@@ -28,10 +28,8 @@ class Board extends RelationalEntity {
|
||||
protected $owner;
|
||||
protected $color;
|
||||
protected $archived = false;
|
||||
/** @var Label[]|null */
|
||||
protected $labels = null;
|
||||
/** @var Acl[]|null */
|
||||
protected $acl = null;
|
||||
protected $labels = [];
|
||||
protected $acl = [];
|
||||
protected $permissions = [];
|
||||
protected $users = [];
|
||||
protected $shared;
|
||||
@@ -58,15 +56,11 @@ class Board extends RelationalEntity {
|
||||
$this->shared = -1;
|
||||
}
|
||||
|
||||
public function jsonSerialize(): array {
|
||||
public function jsonSerialize() {
|
||||
$json = parent::jsonSerialize();
|
||||
if ($this->shared === -1) {
|
||||
unset($json['shared']);
|
||||
}
|
||||
// FIXME: Ideally the API responses should follow the internal data structure and return null if the labels/acls have not been fetched from the db
|
||||
// however this would be a breaking change for consumers of the API
|
||||
$json['acl'] = $this->acl ?? [];
|
||||
$json['labels'] = $this->labels ?? [];
|
||||
return $json;
|
||||
}
|
||||
|
||||
@@ -74,27 +68,21 @@ class Board extends RelationalEntity {
|
||||
* @param Label[] $labels
|
||||
*/
|
||||
public function setLabels($labels) {
|
||||
$this->labels = $labels;
|
||||
foreach ($labels as $l) {
|
||||
$this->labels[] = $l;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @param Acl[] $acl
|
||||
*/
|
||||
public function setAcl($acl) {
|
||||
$this->acl = $acl;
|
||||
foreach ($acl as $a) {
|
||||
$this->acl[] = $a;
|
||||
}
|
||||
}
|
||||
|
||||
public function getETag() {
|
||||
return md5((string)$this->getLastModified());
|
||||
}
|
||||
|
||||
/** @returns Acl[]|null */
|
||||
public function getAcl(): ?array {
|
||||
return $this->acl;
|
||||
}
|
||||
|
||||
/** @returns Label[]|null */
|
||||
public function getLabels(): ?array {
|
||||
return $this->labels;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -26,14 +26,13 @@ namespace OCA\Deck\Db;
|
||||
use OC\Cache\CappedMemoryCache;
|
||||
use OCA\Deck\Service\CirclesService;
|
||||
use OCP\AppFramework\Db\DoesNotExistException;
|
||||
use OCP\AppFramework\Db\QBMapper;
|
||||
use OCP\DB\QueryBuilder\IQueryBuilder;
|
||||
use OCP\IDBConnection;
|
||||
use OCP\IUserManager;
|
||||
use OCP\IGroupManager;
|
||||
use Psr\Log\LoggerInterface;
|
||||
|
||||
class BoardMapper extends QBMapper implements IPermissionMapper {
|
||||
class BoardMapper extends DeckMapper implements IPermissionMapper {
|
||||
private $labelMapper;
|
||||
private $aclMapper;
|
||||
private $stackMapper;
|
||||
@@ -44,8 +43,6 @@ class BoardMapper extends QBMapper implements IPermissionMapper {
|
||||
|
||||
/** @var CappedMemoryCache */
|
||||
private $userBoardCache;
|
||||
/** @var CappedMemoryCache */
|
||||
private $boardCache;
|
||||
|
||||
public function __construct(
|
||||
IDBConnection $db,
|
||||
@@ -67,7 +64,6 @@ class BoardMapper extends QBMapper implements IPermissionMapper {
|
||||
$this->logger = $logger;
|
||||
|
||||
$this->userBoardCache = new CappedMemoryCache();
|
||||
$this->boardCache = new CappedMemoryCache();
|
||||
}
|
||||
|
||||
|
||||
@@ -75,52 +71,40 @@ class BoardMapper extends QBMapper implements IPermissionMapper {
|
||||
* @param $id
|
||||
* @param bool $withLabels
|
||||
* @param bool $withAcl
|
||||
* @return Board
|
||||
* @return \OCP\AppFramework\Db\Entity
|
||||
* @throws \OCP\AppFramework\Db\MultipleObjectsReturnedException
|
||||
* @throws DoesNotExistException
|
||||
*/
|
||||
public function find($id, $withLabels = false, $withAcl = false): Board {
|
||||
if (!isset($this->boardCache[$id])) {
|
||||
$qb = $this->db->getQueryBuilder();
|
||||
$qb->select('*')
|
||||
->from('deck_boards')
|
||||
->where($qb->expr()->eq('id', $qb->createNamedParameter($id, IQueryBuilder::PARAM_INT)))
|
||||
->orderBy('id');
|
||||
$this->boardCache[$id] = $this->findEntity($qb);
|
||||
}
|
||||
|
||||
// FIXME is this necessary? it was NOT done with the old mapper
|
||||
// $this->mapOwner($board);
|
||||
public function find($id, $withLabels = false, $withAcl = false) {
|
||||
$sql = 'SELECT id, title, owner, color, archived, deleted_at, last_modified FROM `*PREFIX*deck_boards` ' .
|
||||
'WHERE `id` = ?';
|
||||
$board = $this->findEntity($sql, [$id]);
|
||||
|
||||
// Add labels
|
||||
if ($withLabels && $this->boardCache[$id]->getLabels() === null) {
|
||||
if ($withLabels) {
|
||||
$labels = $this->labelMapper->findAll($id);
|
||||
$this->boardCache[$id]->setLabels($labels);
|
||||
$board->setLabels($labels);
|
||||
}
|
||||
|
||||
// Add acl
|
||||
if ($withAcl && $this->boardCache[$id]->getAcl() === null) {
|
||||
if ($withAcl) {
|
||||
$acl = $this->aclMapper->findAll($id);
|
||||
$this->boardCache[$id]->setAcl($acl);
|
||||
$board->setAcl($acl);
|
||||
}
|
||||
|
||||
return $this->boardCache[$id];
|
||||
return $board;
|
||||
}
|
||||
|
||||
public function findAllForUser(string $userId, ?int $since = null, bool $includeArchived = true, ?int $before = null,
|
||||
?string $term = null): array {
|
||||
$useCache = ($since === -1 && $includeArchived === true && $before === null && $term === null);
|
||||
public function findAllForUser(string $userId, int $since = -1, $includeArchived = true): array {
|
||||
$useCache = ($since === -1 && $includeArchived === true);
|
||||
if (!isset($this->userBoardCache[$userId]) || !$useCache) {
|
||||
$groups = $this->groupManager->getUserGroupIds(
|
||||
$this->userManager->get($userId)
|
||||
);
|
||||
$userBoards = $this->findAllByUser($userId, null, null, $since, $includeArchived, $before, $term);
|
||||
$groupBoards = $this->findAllByGroups($userId, $groups, null, null, $since, $includeArchived, $before, $term);
|
||||
$circleBoards = $this->findAllByCircles($userId, null, null, $since, $includeArchived, $before, $term);
|
||||
$userBoards = $this->findAllByUser($userId, null, null, $since, $includeArchived);
|
||||
$groupBoards = $this->findAllByGroups($userId, $groups, null, null, $since, $includeArchived);
|
||||
$circleBoards = $this->findAllByCircles($userId, null, null, $since, $includeArchived);
|
||||
$allBoards = array_unique(array_merge($userBoards, $groupBoards, $circleBoards));
|
||||
foreach ($allBoards as $board) {
|
||||
$this->boardCache[$board->getId()] = $board;
|
||||
}
|
||||
if ($useCache) {
|
||||
$this->userBoardCache[$userId] = $allBoards;
|
||||
}
|
||||
@@ -137,91 +121,19 @@ class BoardMapper extends QBMapper implements IPermissionMapper {
|
||||
* @param null $offset
|
||||
* @return array
|
||||
*/
|
||||
public function findAllByUser(string $userId, ?int $limit = null, ?int $offset = null, ?int $since = null,
|
||||
bool $includeArchived = true, ?int $before = null, ?string $term = null) {
|
||||
// FIXME this used to be a UNION to get boards owned by $userId and the user shares in one single query
|
||||
// Is it possible with the query builder?
|
||||
$qb = $this->db->getQueryBuilder();
|
||||
$qb->select('id', 'title', 'owner', 'color', 'archived', 'deleted_at', 'last_modified')
|
||||
// this does not work in MySQL/PostgreSQL
|
||||
//->selectAlias('0', 'shared')
|
||||
->from('deck_boards', 'b')
|
||||
->where($qb->expr()->eq('owner', $qb->createNamedParameter($userId, IQueryBuilder::PARAM_STR)));
|
||||
public function findAllByUser($userId, $limit = null, $offset = null, $since = -1, $includeArchived = true) {
|
||||
// FIXME: One moving to QBMapper we should allow filtering the boards probably by method chaining for additional where clauses
|
||||
$sql = 'SELECT id, title, owner, color, archived, deleted_at, 0 as shared, last_modified FROM `*PREFIX*deck_boards` WHERE owner = ? AND last_modified > ?';
|
||||
if (!$includeArchived) {
|
||||
$qb->andWhere($qb->expr()->eq('archived', $qb->createNamedParameter(false, IQueryBuilder::PARAM_BOOL)))
|
||||
->andWhere($qb->expr()->eq('deleted_at', $qb->createNamedParameter(0, IQueryBuilder::PARAM_INT)));
|
||||
$sql .= ' AND NOT archived AND deleted_at = 0';
|
||||
}
|
||||
if ($since !== null) {
|
||||
$qb->andWhere($qb->expr()->gt('last_modified', $qb->createNamedParameter($since, IQueryBuilder::PARAM_INT)));
|
||||
}
|
||||
if ($before !== null) {
|
||||
$qb->andWhere($qb->expr()->lt('last_modified', $qb->createNamedParameter($before, IQueryBuilder::PARAM_INT)));
|
||||
}
|
||||
if ($term !== null) {
|
||||
$qb->andWhere(
|
||||
$qb->expr()->iLike(
|
||||
'title',
|
||||
$qb->createNamedParameter(
|
||||
'%' . $this->db->escapeLikeParameter($term) . '%',
|
||||
IQueryBuilder::PARAM_STR
|
||||
)
|
||||
)
|
||||
);
|
||||
}
|
||||
$qb->orderBy('b.id');
|
||||
if ($limit !== null) {
|
||||
$qb->setMaxResults($limit);
|
||||
}
|
||||
if ($offset !== null) {
|
||||
$qb->setFirstResult($offset);
|
||||
}
|
||||
$entries = $this->findEntities($qb);
|
||||
foreach ($entries as $entry) {
|
||||
$entry->setShared(0);
|
||||
}
|
||||
|
||||
// shared with user
|
||||
$qb->resetQueryParts();
|
||||
$qb->select('b.id', 'title', 'owner', 'color', 'archived', 'deleted_at', 'last_modified')
|
||||
//->selectAlias('1', 'shared')
|
||||
->from('deck_boards', 'b')
|
||||
->innerJoin('b', 'deck_board_acl', 'acl', $qb->expr()->eq('b.id', 'acl.board_id'))
|
||||
->where($qb->expr()->eq('acl.participant', $qb->createNamedParameter($userId, IQueryBuilder::PARAM_STR)))
|
||||
->andWhere($qb->expr()->eq('acl.type', $qb->createNamedParameter(Acl::PERMISSION_TYPE_USER, IQueryBuilder::PARAM_INT)))
|
||||
->andWhere($qb->expr()->neq('b.owner', $qb->createNamedParameter($userId, IQueryBuilder::PARAM_STR)));
|
||||
$sql .= ' UNION ' .
|
||||
'SELECT boards.id, title, owner, color, archived, deleted_at, 1 as shared, last_modified FROM `*PREFIX*deck_boards` as boards ' .
|
||||
'JOIN `*PREFIX*deck_board_acl` as acl ON boards.id=acl.board_id WHERE acl.participant=? AND acl.type=? AND boards.owner != ? AND last_modified > ?';
|
||||
if (!$includeArchived) {
|
||||
$qb->andWhere($qb->expr()->eq('archived', $qb->createNamedParameter(false, IQueryBuilder::PARAM_BOOL)))
|
||||
->andWhere($qb->expr()->eq('deleted_at', $qb->createNamedParameter(0, IQueryBuilder::PARAM_INT)));
|
||||
$sql .= ' AND NOT archived AND deleted_at = 0';
|
||||
}
|
||||
if ($since !== null) {
|
||||
$qb->andWhere($qb->expr()->gt('last_modified', $qb->createNamedParameter($since, IQueryBuilder::PARAM_INT)));
|
||||
}
|
||||
if ($before !== null) {
|
||||
$qb->andWhere($qb->expr()->lt('last_modified', $qb->createNamedParameter($before, IQueryBuilder::PARAM_INT)));
|
||||
}
|
||||
if ($term !== null) {
|
||||
$qb->andWhere(
|
||||
$qb->expr()->iLike(
|
||||
'title',
|
||||
$qb->createNamedParameter(
|
||||
'%' . $this->db->escapeLikeParameter($term) . '%',
|
||||
IQueryBuilder::PARAM_STR
|
||||
)
|
||||
)
|
||||
);
|
||||
}
|
||||
$qb->orderBy('b.id');
|
||||
if ($limit !== null) {
|
||||
$qb->setMaxResults($limit);
|
||||
}
|
||||
if ($offset !== null) {
|
||||
$qb->setFirstResult($offset);
|
||||
}
|
||||
$sharedEntries = $this->findEntities($qb);
|
||||
foreach ($sharedEntries as $entry) {
|
||||
$entry->setShared(1);
|
||||
}
|
||||
$entries = array_merge($entries, $sharedEntries);
|
||||
$entries = $this->findEntities($sql, [$userId, $since, $userId, Acl::PERMISSION_TYPE_USER, $userId, $since], $limit, $offset);
|
||||
/* @var Board $entry */
|
||||
foreach ($entries as $entry) {
|
||||
$acl = $this->aclMapper->findAll($entry->id);
|
||||
@@ -230,19 +142,9 @@ class BoardMapper extends QBMapper implements IPermissionMapper {
|
||||
return $entries;
|
||||
}
|
||||
|
||||
public function findAllByOwner(string $userId, ?int $limit = null, ?int $offset = null) {
|
||||
$qb = $this->db->getQueryBuilder();
|
||||
$qb->select('*')
|
||||
->from('deck_boards')
|
||||
->where($qb->expr()->eq('owner', $qb->createNamedParameter($userId, IQueryBuilder::PARAM_STR)))
|
||||
->orderBy('id');
|
||||
if ($limit !== null) {
|
||||
$qb->setMaxResults($limit);
|
||||
}
|
||||
if ($offset !== null) {
|
||||
$qb->setFirstResult($offset);
|
||||
}
|
||||
return $this->findEntities($qb);
|
||||
public function findAllByOwner(string $userId, int $limit = null, int $offset = null) {
|
||||
$sql = 'SELECT * FROM `*PREFIX*deck_boards` WHERE owner = ?';
|
||||
return $this->findEntities($sql, [$userId], $limit, $offset);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -254,57 +156,23 @@ class BoardMapper extends QBMapper implements IPermissionMapper {
|
||||
* @param null $offset
|
||||
* @return array
|
||||
*/
|
||||
public function findAllByGroups(string $userId, array $groups, ?int $limit = null, ?int $offset = null, ?int $since = null,
|
||||
bool $includeArchived = true, ?int $before = null, ?string $term = null) {
|
||||
public function findAllByGroups($userId, $groups, $limit = null, $offset = null, $since = -1,$includeArchived = true) {
|
||||
if (count($groups) <= 0) {
|
||||
return [];
|
||||
}
|
||||
$qb = $this->db->getQueryBuilder();
|
||||
$qb->select('b.id', 'title', 'owner', 'color', 'archived', 'deleted_at', 'last_modified')
|
||||
//->selectAlias('2', 'shared')
|
||||
->from('deck_boards', 'b')
|
||||
->innerJoin('b', 'deck_board_acl', 'acl', $qb->expr()->eq('b.id', 'acl.board_id'))
|
||||
->where($qb->expr()->eq('acl.type', $qb->createNamedParameter(Acl::PERMISSION_TYPE_GROUP, IQueryBuilder::PARAM_INT)))
|
||||
->andWhere($qb->expr()->neq('b.owner', $qb->createNamedParameter($userId, IQueryBuilder::PARAM_STR)));
|
||||
$or = $qb->expr()->orx();
|
||||
$sql = 'SELECT boards.id, title, owner, color, archived, deleted_at, 2 as shared, last_modified FROM `*PREFIX*deck_boards` as boards ' .
|
||||
'INNER JOIN `*PREFIX*deck_board_acl` as acl ON boards.id=acl.board_id WHERE owner != ? AND type=? AND (';
|
||||
for ($i = 0, $iMax = count($groups); $i < $iMax; $i++) {
|
||||
$or->add(
|
||||
$qb->expr()->eq('acl.participant', $qb->createNamedParameter($groups[$i], IQueryBuilder::PARAM_STR))
|
||||
);
|
||||
$sql .= 'acl.participant = ? ';
|
||||
if (count($groups) > 1 && $i < count($groups) - 1) {
|
||||
$sql .= ' OR ';
|
||||
}
|
||||
}
|
||||
$qb->andWhere($or);
|
||||
$sql .= ')';
|
||||
if (!$includeArchived) {
|
||||
$qb->andWhere($qb->expr()->eq('archived', $qb->createNamedParameter(false, IQueryBuilder::PARAM_BOOL)))
|
||||
->andWhere($qb->expr()->eq('deleted_at', $qb->createNamedParameter(0, IQueryBuilder::PARAM_INT)));
|
||||
}
|
||||
if ($since !== null) {
|
||||
$qb->andWhere($qb->expr()->gt('last_modified', $qb->createNamedParameter($since, IQueryBuilder::PARAM_INT)));
|
||||
}
|
||||
if ($before !== null) {
|
||||
$qb->andWhere($qb->expr()->lt('last_modified', $qb->createNamedParameter($before, IQueryBuilder::PARAM_INT)));
|
||||
}
|
||||
if ($term !== null) {
|
||||
$qb->andWhere(
|
||||
$qb->expr()->iLike(
|
||||
'title',
|
||||
$qb->createNamedParameter(
|
||||
'%' . $this->db->escapeLikeParameter($term) . '%',
|
||||
IQueryBuilder::PARAM_STR
|
||||
)
|
||||
)
|
||||
);
|
||||
}
|
||||
$qb->orderBy('b.id');
|
||||
if ($limit !== null) {
|
||||
$qb->setMaxResults($limit);
|
||||
}
|
||||
if ($offset !== null) {
|
||||
$qb->setFirstResult($offset);
|
||||
}
|
||||
$entries = $this->findEntities($qb);
|
||||
foreach ($entries as $entry) {
|
||||
$entry->setShared(2);
|
||||
$sql .= ' AND NOT archived AND deleted_at = 0';
|
||||
}
|
||||
$entries = $this->findEntities($sql, array_merge([$userId, Acl::PERMISSION_TYPE_GROUP], $groups), $limit, $offset);
|
||||
/* @var Board $entry */
|
||||
foreach ($entries as $entry) {
|
||||
$acl = $this->aclMapper->findAll($entry->id);
|
||||
@@ -313,59 +181,25 @@ class BoardMapper extends QBMapper implements IPermissionMapper {
|
||||
return $entries;
|
||||
}
|
||||
|
||||
public function findAllByCircles(string $userId, ?int $limit = null, ?int $offset = null, ?int $since = null,
|
||||
bool $includeArchived = true, ?int $before = null, ?string $term = null) {
|
||||
public function findAllByCircles($userId, $limit = null, $offset = null, $since = -1,$includeArchived = true) {
|
||||
$circles = $this->circlesService->getUserCircles($userId);
|
||||
if (count($circles) === 0) {
|
||||
return [];
|
||||
}
|
||||
|
||||
$qb = $this->db->getQueryBuilder();
|
||||
$qb->select('b.id', 'title', 'owner', 'color', 'archived', 'deleted_at', 'last_modified')
|
||||
//->selectAlias('2', 'shared')
|
||||
->from('deck_boards', 'b')
|
||||
->innerJoin('b', 'deck_board_acl', 'acl', $qb->expr()->eq('b.id', 'acl.board_id'))
|
||||
->where($qb->expr()->eq('acl.type', $qb->createNamedParameter(Acl::PERMISSION_TYPE_CIRCLE, IQueryBuilder::PARAM_INT)))
|
||||
->andWhere($qb->expr()->neq('b.owner', $qb->createNamedParameter($userId, IQueryBuilder::PARAM_STR)));
|
||||
$or = $qb->expr()->orx();
|
||||
$sql = 'SELECT boards.id, title, owner, color, archived, deleted_at, 2 as shared, last_modified FROM `*PREFIX*deck_boards` as boards ' .
|
||||
'INNER JOIN `*PREFIX*deck_board_acl` as acl ON boards.id=acl.board_id WHERE owner != ? AND type=? AND (';
|
||||
for ($i = 0, $iMax = count($circles); $i < $iMax; $i++) {
|
||||
$or->add(
|
||||
$qb->expr()->eq('acl.participant', $qb->createNamedParameter($circles[$i], IQueryBuilder::PARAM_STR))
|
||||
);
|
||||
$sql .= 'acl.participant = ? ';
|
||||
if (count($circles) > 1 && $i < count($circles) - 1) {
|
||||
$sql .= ' OR ';
|
||||
}
|
||||
}
|
||||
$qb->andWhere($or);
|
||||
$sql .= ')';
|
||||
if (!$includeArchived) {
|
||||
$qb->andWhere($qb->expr()->eq('archived', $qb->createNamedParameter(false, IQueryBuilder::PARAM_BOOL)))
|
||||
->andWhere($qb->expr()->eq('deleted_at', $qb->createNamedParameter(0, IQueryBuilder::PARAM_INT)));
|
||||
}
|
||||
if ($since !== null) {
|
||||
$qb->andWhere($qb->expr()->gt('last_modified', $qb->createNamedParameter($since, IQueryBuilder::PARAM_INT)));
|
||||
}
|
||||
if ($before !== null) {
|
||||
$qb->andWhere($qb->expr()->lt('last_modified', $qb->createNamedParameter($before, IQueryBuilder::PARAM_INT)));
|
||||
}
|
||||
if ($term !== null) {
|
||||
$qb->andWhere(
|
||||
$qb->expr()->iLike(
|
||||
'title',
|
||||
$qb->createNamedParameter(
|
||||
'%' . $this->db->escapeLikeParameter($term) . '%',
|
||||
IQueryBuilder::PARAM_STR
|
||||
)
|
||||
)
|
||||
);
|
||||
}
|
||||
$qb->orderBy('b.id');
|
||||
if ($limit !== null) {
|
||||
$qb->setMaxResults($limit);
|
||||
}
|
||||
if ($offset !== null) {
|
||||
$qb->setFirstResult($offset);
|
||||
}
|
||||
$entries = $this->findEntities($qb);
|
||||
foreach ($entries as $entry) {
|
||||
$entry->setShared(2);
|
||||
$sql .= ' AND NOT archived AND deleted_at = 0';
|
||||
}
|
||||
$entries = $this->findEntities($sql, array_merge([$userId, Acl::PERMISSION_TYPE_CIRCLE], $circles), $limit, $offset);
|
||||
/* @var Board $entry */
|
||||
foreach ($entries as $entry) {
|
||||
$acl = $this->aclMapper->findAll($entry->id);
|
||||
@@ -374,26 +208,21 @@ class BoardMapper extends QBMapper implements IPermissionMapper {
|
||||
return $entries;
|
||||
}
|
||||
|
||||
public function findAll(): array {
|
||||
$qb = $this->db->getQueryBuilder();
|
||||
$qb->select('id')
|
||||
->from('deck_boards');
|
||||
return $this->findEntities($qb);
|
||||
public function findAll() {
|
||||
$sql = 'SELECT id from *PREFIX*deck_boards;';
|
||||
return $this->findEntities($sql);
|
||||
}
|
||||
|
||||
public function findToDelete() {
|
||||
// add buffer of 5 min
|
||||
$timeLimit = time() - (60 * 5);
|
||||
$qb = $this->db->getQueryBuilder();
|
||||
$qb->select('id', 'title', 'owner', 'color', 'archived', 'deleted_at', 'last_modified')
|
||||
->from('deck_boards')
|
||||
->where($qb->expr()->gt('deleted_at', $qb->createNamedParameter(0, IQueryBuilder::PARAM_INT)))
|
||||
->andWhere($qb->expr()->lt('deleted_at', $qb->createNamedParameter($timeLimit, IQueryBuilder::PARAM_INT)));
|
||||
return $this->findEntities($qb);
|
||||
$sql = 'SELECT id, title, owner, color, archived, deleted_at, last_modified FROM `*PREFIX*deck_boards` ' .
|
||||
'WHERE `deleted_at` > 0 AND `deleted_at` < ?';
|
||||
return $this->findEntities($sql, [$timeLimit]);
|
||||
}
|
||||
|
||||
public function delete(/** @noinspection PhpUnnecessaryFullyQualifiedNameInspection */
|
||||
\OCP\AppFramework\Db\Entity $entity): \OCP\AppFramework\Db\Entity {
|
||||
\OCP\AppFramework\Db\Entity $entity) {
|
||||
// delete acl
|
||||
$acl = $this->aclMapper->findAll($entity->getId());
|
||||
foreach ($acl as $item) {
|
||||
@@ -494,11 +323,6 @@ class BoardMapper extends QBMapper implements IPermissionMapper {
|
||||
* Reset cache for a given board or a given user
|
||||
*/
|
||||
public function flushCache(?int $boardId = null, ?string $userId = null) {
|
||||
if ($boardId) {
|
||||
unset($this->boardCache[$boardId]);
|
||||
} else {
|
||||
$this->boardCache = null;
|
||||
}
|
||||
if ($userId) {
|
||||
unset($this->userBoardCache[$userId]);
|
||||
} else {
|
||||
|
||||
@@ -50,7 +50,7 @@ class Card extends RelationalEntity {
|
||||
protected $deletedAt = 0;
|
||||
protected $commentsUnread = 0;
|
||||
protected $commentsCount = 0;
|
||||
|
||||
|
||||
protected $relatedStack = null;
|
||||
protected $relatedBoard = null;
|
||||
|
||||
@@ -78,7 +78,7 @@ class Card extends RelationalEntity {
|
||||
$this->addRelation('commentsUnread');
|
||||
$this->addRelation('commentsCount');
|
||||
$this->addResolvable('owner');
|
||||
|
||||
|
||||
$this->addRelation('relatedStack');
|
||||
$this->addRelation('relatedBoard');
|
||||
}
|
||||
@@ -98,21 +98,22 @@ class Card extends RelationalEntity {
|
||||
return $dt->format('c');
|
||||
}
|
||||
|
||||
public function jsonSerialize(): array {
|
||||
public function jsonSerialize() {
|
||||
$json = parent::jsonSerialize();
|
||||
$json['overdue'] = self::DUEDATE_FUTURE;
|
||||
$due = $this->duedate ? strtotime($this->duedate) : false;
|
||||
$due = strtotime($this->duedate);
|
||||
|
||||
$today = new DateTime();
|
||||
$today->setTime(0, 0);
|
||||
|
||||
$match_date = new DateTime($this->duedate);
|
||||
|
||||
$match_date->setTime(0, 0);
|
||||
|
||||
$diff = $today->diff($match_date);
|
||||
$diffDays = (integer) $diff->format('%R%a'); // Extract days count in interval
|
||||
|
||||
if ($due !== false) {
|
||||
$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;
|
||||
}
|
||||
|
||||
@@ -30,8 +30,6 @@ use OCA\Deck\Search\Query\SearchQuery;
|
||||
use OCP\AppFramework\Db\Entity;
|
||||
use OCP\AppFramework\Db\QBMapper;
|
||||
use OCP\DB\QueryBuilder\IQueryBuilder;
|
||||
use OCP\ICache;
|
||||
use OCP\ICacheFactory;
|
||||
use OCP\IDBConnection;
|
||||
use OCP\IGroupManager;
|
||||
use OCP\IUser;
|
||||
@@ -48,8 +46,6 @@ class CardMapper extends QBMapper implements IPermissionMapper {
|
||||
private $groupManager;
|
||||
/** @var IManager */
|
||||
private $notificationManager;
|
||||
/** @var ICache */
|
||||
private $cache;
|
||||
private $databaseType;
|
||||
private $database4ByteSupport;
|
||||
|
||||
@@ -59,7 +55,6 @@ class CardMapper extends QBMapper implements IPermissionMapper {
|
||||
IUserManager $userManager,
|
||||
IGroupManager $groupManager,
|
||||
IManager $notificationManager,
|
||||
ICacheFactory $cacheFactory,
|
||||
$databaseType = 'sqlite3',
|
||||
$database4ByteSupport = true
|
||||
) {
|
||||
@@ -68,7 +63,6 @@ class CardMapper extends QBMapper implements IPermissionMapper {
|
||||
$this->userManager = $userManager;
|
||||
$this->groupManager = $groupManager;
|
||||
$this->notificationManager = $notificationManager;
|
||||
$this->cache = $cacheFactory->createDistributed('deck-cardMapper');
|
||||
$this->databaseType = $databaseType;
|
||||
$this->database4ByteSupport = $database4ByteSupport;
|
||||
}
|
||||
@@ -81,9 +75,7 @@ class CardMapper extends QBMapper implements IPermissionMapper {
|
||||
$description = preg_replace('/[\x{10000}-\x{10FFFF}]/u', "\xEF\xBF\xBD", $entity->getDescription());
|
||||
$entity->setDescription($description);
|
||||
}
|
||||
$entity = parent::insert($entity);
|
||||
$this->cache->remove('findBoardId:' . $entity->getId());
|
||||
return $entity;
|
||||
return parent::insert($entity);
|
||||
}
|
||||
|
||||
public function update(Entity $entity, $updateModified = true): Entity {
|
||||
@@ -115,10 +107,6 @@ class CardMapper extends QBMapper implements IPermissionMapper {
|
||||
} catch (Exception $e) {
|
||||
}
|
||||
}
|
||||
// Invalidate cache when the card may be moved to a different board
|
||||
if (isset($updatedFields['stackId'])) {
|
||||
$this->cache->remove('findBoardId:' . $entity->getId());
|
||||
}
|
||||
return parent::update($entity);
|
||||
}
|
||||
|
||||
@@ -193,7 +181,6 @@ class CardMapper extends QBMapper implements IPermissionMapper {
|
||||
->from('deck_cards', 'c')
|
||||
->join('c', 'deck_stacks', 's', 's.id = c.stack_id')
|
||||
->where($qb->expr()->eq('s.board_id', $qb->createNamedParameter($boardId)))
|
||||
->andWhere($qb->expr()->eq('c.archived', $qb->createNamedParameter(false, IQueryBuilder::PARAM_BOOL)))
|
||||
->andWhere($qb->expr()->eq('c.deleted_at', $qb->createNamedParameter('0')))
|
||||
->orderBy('c.duedate')
|
||||
->setMaxResults($limit)
|
||||
@@ -266,7 +253,7 @@ class CardMapper extends QBMapper implements IPermissionMapper {
|
||||
|
||||
public function findOverdue() {
|
||||
$qb = $this->db->getQueryBuilder();
|
||||
$qb->select('id', 'title', 'duedate', 'notified')
|
||||
$qb->select('id','title','duedate','notified')
|
||||
->from('deck_cards')
|
||||
->where($qb->expr()->lt('duedate', $qb->createFunction('NOW()')))
|
||||
->andWhere($qb->expr()->eq('notified', $qb->createNamedParameter(false, IQueryBuilder::PARAM_BOOL)))
|
||||
@@ -277,7 +264,7 @@ class CardMapper extends QBMapper implements IPermissionMapper {
|
||||
|
||||
public function findUnexposedDescriptionChances() {
|
||||
$qb = $this->db->getQueryBuilder();
|
||||
$qb->select('id', 'title', 'duedate', 'notified', 'description_prev', 'last_editor', 'description')
|
||||
$qb->select('id','title','duedate','notified','description_prev','last_editor','description')
|
||||
->from('deck_cards')
|
||||
->where($qb->expr()->isNotNull('last_editor'))
|
||||
->andWhere($qb->expr()->isNotNull('description_prev'));
|
||||
@@ -492,8 +479,8 @@ class CardMapper extends QBMapper implements IPermissionMapper {
|
||||
}
|
||||
return $qb->createNamedParameter($dateTime, IQueryBuilder::PARAM_DATE);
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
public function searchRaw($boardIds, $term, $limit = null, $offset = null) {
|
||||
$qb = $this->queryCardsByBoards($boardIds)
|
||||
@@ -519,8 +506,9 @@ class CardMapper extends QBMapper implements IPermissionMapper {
|
||||
}
|
||||
|
||||
public function delete(Entity $entity): Entity {
|
||||
// delete assigned labels
|
||||
$this->labelMapper->deleteLabelAssignmentsForCard($entity->getId());
|
||||
$this->cache->remove('findBoardId:' . $entity->getId());
|
||||
// delete card
|
||||
return parent::delete($entity);
|
||||
}
|
||||
|
||||
@@ -552,29 +540,18 @@ class CardMapper extends QBMapper implements IPermissionMapper {
|
||||
public function isOwner($userId, $cardId): bool {
|
||||
$sql = 'SELECT owner FROM `*PREFIX*deck_boards` WHERE `id` IN (SELECT board_id FROM `*PREFIX*deck_stacks` WHERE id IN (SELECT stack_id FROM `*PREFIX*deck_cards` WHERE id = ?))';
|
||||
$stmt = $this->db->prepare($sql);
|
||||
$stmt->bindParam(1, $cardId, \PDO::PARAM_INT, 0);
|
||||
$stmt->bindParam(1, $cardId, \PDO::PARAM_INT);
|
||||
$stmt->execute();
|
||||
$row = $stmt->fetch();
|
||||
return ($row['owner'] === $userId);
|
||||
}
|
||||
|
||||
public function findBoardId($id): ?int {
|
||||
$result = $this->cache->get('findBoardId:' . $id);
|
||||
if ($result === null) {
|
||||
try {
|
||||
$qb = $this->db->getQueryBuilder();
|
||||
$qb->select('board_id')
|
||||
->from('deck_stacks', 's')
|
||||
->innerJoin('s', 'deck_cards', 'c', 'c.stack_id = s.id')
|
||||
->where($qb->expr()->eq('c.id', $qb->createNamedParameter($id)));
|
||||
$queryResult = $qb->executeQuery();
|
||||
$result = $queryResult->fetchOne();
|
||||
} catch (\Exception $e) {
|
||||
$result = false;
|
||||
}
|
||||
$this->cache->set('findBoardId:' . $id, $result);
|
||||
}
|
||||
return $result !== false ? $result : null;
|
||||
$sql = 'SELECT id FROM `*PREFIX*deck_boards` WHERE `id` IN (SELECT board_id FROM `*PREFIX*deck_stacks` WHERE id IN (SELECT stack_id FROM `*PREFIX*deck_cards` WHERE id = ?))';
|
||||
$stmt = $this->db->prepare($sql);
|
||||
$stmt->bindParam(1, $id, \PDO::PARAM_INT);
|
||||
$stmt->execute();
|
||||
return $stmt->fetchColumn() ?? null;
|
||||
}
|
||||
|
||||
public function mapOwner(Card &$card) {
|
||||
|
||||
@@ -83,14 +83,14 @@ class LabelMapper extends DeckMapper implements IPermissionMapper {
|
||||
public function deleteLabelAssignments($labelId) {
|
||||
$sql = 'DELETE FROM `*PREFIX*deck_assigned_labels` WHERE label_id = ?';
|
||||
$stmt = $this->db->prepare($sql);
|
||||
$stmt->bindParam(1, $labelId, \PDO::PARAM_INT, 0);
|
||||
$stmt->bindParam(1, $labelId, \PDO::PARAM_INT);
|
||||
$stmt->execute();
|
||||
}
|
||||
|
||||
public function deleteLabelAssignmentsForCard($cardId) {
|
||||
$sql = 'DELETE FROM `*PREFIX*deck_assigned_labels` WHERE card_id = ?';
|
||||
$stmt = $this->db->prepare($sql);
|
||||
$stmt->bindParam(1, $cardId, \PDO::PARAM_INT, 0);
|
||||
$stmt->bindParam(1, $cardId, \PDO::PARAM_INT);
|
||||
$stmt->execute();
|
||||
}
|
||||
|
||||
|
||||
@@ -63,7 +63,7 @@ class RelationalEntity extends Entity implements \JsonSerializable {
|
||||
* @return array serialized data
|
||||
* @throws \ReflectionException
|
||||
*/
|
||||
public function jsonSerialize(): array {
|
||||
public function jsonSerialize() {
|
||||
$properties = get_object_vars($this);
|
||||
$reflection = new \ReflectionClass($this);
|
||||
$json = [];
|
||||
|
||||
@@ -23,9 +23,7 @@
|
||||
|
||||
namespace OCA\Deck\Db;
|
||||
|
||||
use JsonSerializable;
|
||||
|
||||
class RelationalObject implements JsonSerializable {
|
||||
class RelationalObject implements \JsonSerializable {
|
||||
protected $primaryKey;
|
||||
protected $object;
|
||||
|
||||
@@ -40,7 +38,7 @@ class RelationalObject implements JsonSerializable {
|
||||
$this->object = $object;
|
||||
}
|
||||
|
||||
public function jsonSerialize(): array {
|
||||
public function jsonSerialize() {
|
||||
return array_merge(
|
||||
['primaryKey' => $this->primaryKey],
|
||||
$this->getObjectSerialization()
|
||||
@@ -53,8 +51,8 @@ class RelationalObject implements JsonSerializable {
|
||||
* @throws \Exception
|
||||
*/
|
||||
public function getObjectSerialization() {
|
||||
if ($this->object instanceof JsonSerializable) {
|
||||
return $this->object->jsonSerialize();
|
||||
if ($this->object instanceof \JsonSerializable) {
|
||||
$this->object->jsonSerialize();
|
||||
} else {
|
||||
throw new \Exception('jsonSerialize is not implemented on ' . get_class($this));
|
||||
}
|
||||
|
||||
@@ -25,13 +25,6 @@ namespace OCA\Deck\Db;
|
||||
|
||||
use Sabre\VObject\Component\VCalendar;
|
||||
|
||||
/**
|
||||
* @method int getId()
|
||||
* @method int getBoardId()
|
||||
* @method int getDeletedAt()
|
||||
* @method int getLastModified()
|
||||
* @method int getOrder()
|
||||
*/
|
||||
class Stack extends RelationalEntity {
|
||||
protected $title;
|
||||
protected $boardId;
|
||||
@@ -52,7 +45,7 @@ class Stack extends RelationalEntity {
|
||||
$this->cards = $cards;
|
||||
}
|
||||
|
||||
public function jsonSerialize(): array {
|
||||
public function jsonSerialize() {
|
||||
$json = parent::jsonSerialize();
|
||||
if (empty($this->cards)) {
|
||||
unset($json['cards']);
|
||||
|
||||
@@ -48,25 +48,6 @@ class StackMapper extends DeckMapper implements IPermissionMapper {
|
||||
return $this->findEntity($sql, [$id]);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param $cardId
|
||||
* @return Stack|null
|
||||
*/
|
||||
public function findStackFromCardId($cardId): ?Stack {
|
||||
$sql = <<<SQL
|
||||
SELECT s.*
|
||||
FROM `*PREFIX*deck_stacks` as `s`
|
||||
INNER JOIN `*PREFIX*deck_cards` as `c` ON s.id = c.stack_id
|
||||
WHERE c.id = ?
|
||||
SQL;
|
||||
try {
|
||||
return $this->findEntity($sql, [$cardId]);
|
||||
} catch (MultipleObjectsReturnedException|DoesNotExistException $e) {
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
|
||||
public function findAll($boardId, $limit = null, $offset = null) {
|
||||
$sql = 'SELECT * FROM `*PREFIX*deck_stacks` WHERE `board_id` = ? AND deleted_at = 0 ORDER BY `order`, `id`';
|
||||
|
||||
@@ -1,44 +0,0 @@
|
||||
<?php
|
||||
/*
|
||||
* @copyright Copyright (c) 2021 Vitor Mattos <vitor@php.rio>
|
||||
*
|
||||
* @author Vitor Mattos <vitor@php.rio>
|
||||
*
|
||||
* @license GNU AGPL version 3 or any later version
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as
|
||||
* published by the Free Software Foundation, either version 3 of the
|
||||
* License, or (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
*/
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
|
||||
namespace OCA\Deck\Event;
|
||||
|
||||
use OCA\Deck\Service\Importer\BoardImportService;
|
||||
use OCP\EventDispatcher\Event;
|
||||
|
||||
abstract class ABoardImportGetAllowedEvent extends Event {
|
||||
private $service;
|
||||
|
||||
public function __construct(BoardImportService $service) {
|
||||
parent::__construct();
|
||||
|
||||
$this->service = $service;
|
||||
}
|
||||
|
||||
public function getService(): BoardImportService {
|
||||
return $this->service;
|
||||
}
|
||||
}
|
||||
@@ -1,29 +0,0 @@
|
||||
<?php
|
||||
/*
|
||||
* @copyright Copyright (c) 2021 Vitor Mattos <vitor@php.rio>
|
||||
*
|
||||
* @author Vitor Mattos <vitor@php.rio>
|
||||
*
|
||||
* @license GNU AGPL version 3 or any later version
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as
|
||||
* published by the Free Software Foundation, either version 3 of the
|
||||
* License, or (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
*/
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace OCA\Deck\Event;
|
||||
|
||||
class BoardImportGetAllowedEvent extends ABoardImportGetAllowedEvent {
|
||||
}
|
||||
@@ -101,12 +101,15 @@ class Notifier implements INotifier {
|
||||
switch ($notification->getSubject()) {
|
||||
case 'card-assigned':
|
||||
$cardId = $notification->getObjectId();
|
||||
$stack = $this->stackMapper->findStackFromCardId($cardId);
|
||||
$boardId = $stack ? $stack->getBoardId() : null;
|
||||
$boardId = $this->cardMapper->findBoardId($cardId);
|
||||
if (!$boardId) {
|
||||
throw new AlreadyProcessedException();
|
||||
}
|
||||
|
||||
$card = $this->cardMapper->find($cardId);
|
||||
$stackId = $card->getStackId();
|
||||
$stack = $this->stackMapper->find($stackId);
|
||||
|
||||
$initiator = $this->userManager->get($params[2]);
|
||||
if ($initiator !== null) {
|
||||
$dn = $initiator->getDisplayName();
|
||||
@@ -144,12 +147,15 @@ class Notifier implements INotifier {
|
||||
break;
|
||||
case 'card-overdue':
|
||||
$cardId = $notification->getObjectId();
|
||||
$stack = $this->stackMapper->findStackFromCardId($cardId);
|
||||
$boardId = $stack ? $stack->getBoardId() : null;
|
||||
$boardId = $this->cardMapper->findBoardId($cardId);
|
||||
if (!$boardId) {
|
||||
throw new AlreadyProcessedException();
|
||||
}
|
||||
|
||||
$card = $this->cardMapper->find($cardId);
|
||||
$stackId = $card->getStackId();
|
||||
$stack = $this->stackMapper->find($stackId);
|
||||
|
||||
$notification->setParsedSubject(
|
||||
(string) $l->t('The card "%s" on "%s" has reached its due date.', $params)
|
||||
);
|
||||
@@ -176,12 +182,15 @@ class Notifier implements INotifier {
|
||||
break;
|
||||
case 'card-comment-mentioned':
|
||||
$cardId = $notification->getObjectId();
|
||||
$stack = $this->stackMapper->findStackFromCardId($cardId);
|
||||
$boardId = $stack ? $stack->getBoardId() : null;
|
||||
$boardId = $this->cardMapper->findBoardId($cardId);
|
||||
if (!$boardId) {
|
||||
throw new AlreadyProcessedException();
|
||||
}
|
||||
|
||||
$card = $this->cardMapper->find($cardId);
|
||||
$stackId = $card->getStackId();
|
||||
$stack = $this->stackMapper->find($stackId);
|
||||
|
||||
$initiator = $this->userManager->get($params[2]);
|
||||
if ($initiator !== null) {
|
||||
$dn = $initiator->getDisplayName();
|
||||
|
||||
@@ -33,6 +33,6 @@ use OCP\Search\SearchResultEntry;
|
||||
|
||||
class CardSearchResultEntry extends SearchResultEntry {
|
||||
public function __construct(Board $board, Stack $stack, Card $card, $urlGenerator) {
|
||||
parent::__construct('', $card->getTitle(), $board->getTitle() . ' » ' . $stack->getTitle(), $urlGenerator->linkToRouteAbsolute('deck.page.index') . '#/board/' . $board->getId() . '/card/' . $card->getId(), 'icon-deck');
|
||||
parent::__construct('', $card->getTitle(), $board->getTitle() . ' » ' . $stack->getTitle() , $urlGenerator->linkToRouteAbsolute('deck.page.index') . '#/board/' . $board->getId() . '/card/' . $card->getId(), 'icon-deck');
|
||||
}
|
||||
}
|
||||
|
||||
@@ -39,7 +39,6 @@ use OCA\Deck\StatusException;
|
||||
use OCP\AppFramework\Db\IMapperException;
|
||||
use OCP\AppFramework\Http\Response;
|
||||
use OCP\IL10N;
|
||||
use OCP\IUserManager;
|
||||
|
||||
class AttachmentService {
|
||||
private $attachmentMapper;
|
||||
@@ -59,18 +58,8 @@ class AttachmentService {
|
||||
private $activityManager;
|
||||
/** @var ChangeHelper */
|
||||
private $changeHelper;
|
||||
private IUserManager $userManager;
|
||||
|
||||
public function __construct(AttachmentMapper $attachmentMapper,
|
||||
CardMapper $cardMapper,
|
||||
IUserManager $userManager,
|
||||
ChangeHelper $changeHelper,
|
||||
PermissionService $permissionService,
|
||||
Application $application,
|
||||
AttachmentCacheHelper $attachmentCacheHelper,
|
||||
$userId,
|
||||
IL10N $l10n,
|
||||
ActivityManager $activityManager) {
|
||||
public function __construct(AttachmentMapper $attachmentMapper, CardMapper $cardMapper, ChangeHelper $changeHelper, PermissionService $permissionService, Application $application, AttachmentCacheHelper $attachmentCacheHelper, $userId, IL10N $l10n, ActivityManager $activityManager) {
|
||||
$this->attachmentMapper = $attachmentMapper;
|
||||
$this->cardMapper = $cardMapper;
|
||||
$this->permissionService = $permissionService;
|
||||
@@ -80,7 +69,6 @@ class AttachmentService {
|
||||
$this->l10n = $l10n;
|
||||
$this->activityManager = $activityManager;
|
||||
$this->changeHelper = $changeHelper;
|
||||
$this->userManager = $userManager;
|
||||
|
||||
// Register shipped attachment services
|
||||
// TODO: move this to a plugin based approach once we have different types of attachments
|
||||
@@ -139,7 +127,6 @@ class AttachmentService {
|
||||
try {
|
||||
$service = $this->getService($attachment->getType());
|
||||
$service->extendData($attachment);
|
||||
$this->addCreator($attachment);
|
||||
} catch (InvalidAttachmentType $e) {
|
||||
// Ingore invalid attachment types when extending the data
|
||||
}
|
||||
@@ -223,7 +210,6 @@ class AttachmentService {
|
||||
}
|
||||
|
||||
$service->extendData($attachment);
|
||||
$this->addCreator($attachment);
|
||||
} catch (InvalidAttachmentType $e) {
|
||||
// just store the data
|
||||
}
|
||||
@@ -327,7 +313,6 @@ class AttachmentService {
|
||||
$this->attachmentMapper->update($attachment);
|
||||
// extend data so the frontend can use it properly after creating
|
||||
$service->extendData($attachment);
|
||||
$this->addCreator($attachment);
|
||||
|
||||
$this->changeHelper->cardChanged($attachment->getCardId());
|
||||
$this->activityManager->triggerEvent(ActivityManager::DECK_OBJECT_CARD, $attachment, ActivityManager::SUBJECT_ATTACHMENT_UPDATE);
|
||||
@@ -402,28 +387,4 @@ class AttachmentService {
|
||||
}
|
||||
throw new NoPermissionException('Restore is not allowed.');
|
||||
}
|
||||
|
||||
/**
|
||||
* @param Attachment $attachment
|
||||
* @return Attachment
|
||||
* @throws \ReflectionException
|
||||
*/
|
||||
private function addCreator(Attachment $attachment): Attachment {
|
||||
$createdBy = $attachment->jsonSerialize()['createdBy'] ?? '';
|
||||
$creator = [
|
||||
'displayName' => $createdBy,
|
||||
'id' => $createdBy,
|
||||
'email' => null,
|
||||
];
|
||||
if ($this->userManager->userExists($createdBy)) {
|
||||
$user = $this->userManager->get($createdBy);
|
||||
$creator['displayName'] = $user->getDisplayName();
|
||||
$creator['email'] = $user->getEMailAddress();
|
||||
}
|
||||
$extendedData = $attachment->jsonSerialize()['extendedData'] ?? [];
|
||||
$extendedData['attachmentCreator'] = $creator;
|
||||
$attachment->setExtendedData($extendedData);
|
||||
|
||||
return $attachment;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -127,9 +127,8 @@ class BoardService {
|
||||
/**
|
||||
* Get all boards that are shared with a user, their groups or circles
|
||||
*/
|
||||
public function getUserBoards(?int $since = null, bool $includeArchived = true, ?int $before = null,
|
||||
?string $term = null): array {
|
||||
return $this->boardMapper->findAllForUser($this->userId, $since, $includeArchived, $before, $term);
|
||||
public function getUserBoards(int $since = -1, bool $includeArchived = true): array {
|
||||
return $this->boardMapper->findAllForUser($this->userId, $since, $includeArchived);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -188,11 +187,9 @@ class BoardService {
|
||||
/** @var Board $board */
|
||||
$board = $this->boardMapper->find($boardId, true, true);
|
||||
$this->boardMapper->mapOwner($board);
|
||||
if ($board->getAcl() !== null) {
|
||||
foreach ($board->getAcl() as $acl) {
|
||||
if ($acl !== null) {
|
||||
$this->boardMapper->mapAcl($acl);
|
||||
}
|
||||
foreach ($board->getAcl() as &$acl) {
|
||||
if ($acl !== null) {
|
||||
$this->boardMapper->mapAcl($acl);
|
||||
}
|
||||
}
|
||||
$permissions = $this->permissionService->matchPermissions($board);
|
||||
|
||||
@@ -114,7 +114,7 @@ class CardService {
|
||||
$countComments = $this->commentsManager->getNumberOfCommentsForObject('deckCard', (string)$card->getId());
|
||||
$card->setCommentsUnread($countUnreadComments);
|
||||
$card->setCommentsCount($countComments);
|
||||
|
||||
|
||||
$stack = $this->stackMapper->find($card->getStackId());
|
||||
$board = $this->boardService->find($stack->getBoardId());
|
||||
$card->setRelatedStack($stack);
|
||||
@@ -224,7 +224,7 @@ class CardService {
|
||||
$card->setDescription($description);
|
||||
$card->setDuedate($duedate);
|
||||
$card = $this->cardMapper->insert($card);
|
||||
|
||||
|
||||
$this->activityManager->triggerEvent(ActivityManager::DECK_OBJECT_CARD, $card, ActivityManager::SUBJECT_CARD_CREATE);
|
||||
$this->changeHelper->cardChanged($card->getId(), false);
|
||||
$this->eventDispatcher->dispatchTyped(new CardCreatedEvent($card));
|
||||
@@ -253,7 +253,7 @@ class CardService {
|
||||
$card = $this->cardMapper->find($id);
|
||||
$card->setDeletedAt(time());
|
||||
$this->cardMapper->update($card);
|
||||
|
||||
|
||||
$this->activityManager->triggerEvent(ActivityManager::DECK_OBJECT_CARD, $card, ActivityManager::SUBJECT_CARD_DELETE);
|
||||
$this->notificationHelper->markDuedateAsRead($card);
|
||||
$this->changeHelper->cardChanged($card->getId(), false);
|
||||
@@ -338,7 +338,7 @@ class CardService {
|
||||
$resetDuedateNotification = false;
|
||||
if (
|
||||
$card->getDuedate() === null ||
|
||||
(new \DateTime($card->getDuedate())) != (new \DateTime($changes->getBefore()->getDuedate() ?? ''))
|
||||
(new \DateTime($card->getDuedate())) != (new \DateTime($changes->getBefore()->getDuedate()))
|
||||
) {
|
||||
$card->setNotified(false);
|
||||
$resetDuedateNotification = true;
|
||||
|
||||
@@ -40,8 +40,6 @@ use Throwable;
|
||||
class CirclesService {
|
||||
private $circlesEnabled;
|
||||
|
||||
private $userCircleCache = [];
|
||||
|
||||
public function __construct(IAppManager $appManager) {
|
||||
$this->circlesEnabled = $appManager->isEnabledForUser('circles');
|
||||
}
|
||||
@@ -72,10 +70,6 @@ class CirclesService {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (isset($this->userCircleCache[$circleId][$userId])) {
|
||||
return $this->userCircleCache[$circleId][$userId];
|
||||
}
|
||||
|
||||
try {
|
||||
/** @var CirclesManager $circlesManager */
|
||||
$circlesManager = \OC::$server->get(CirclesManager::class);
|
||||
@@ -83,14 +77,7 @@ class CirclesService {
|
||||
$circlesManager->startSession($federatedUser);
|
||||
$circle = $circlesManager->getCircle($circleId);
|
||||
$member = $circle->getInitiator();
|
||||
$isUserInCircle = $member !== null && $member->getLevel() >= Member::LEVEL_MEMBER;
|
||||
|
||||
if (!isset($this->userCircleCache[$circleId])) {
|
||||
$this->userCircleCache[$circleId] = [];
|
||||
}
|
||||
$this->userCircleCache[$circleId][$userId] = $isUserInCircle;
|
||||
|
||||
return $isUserInCircle;
|
||||
return $member !== null && $member->getLevel() >= Member::LEVEL_MEMBER;
|
||||
} catch (Throwable $e) {
|
||||
}
|
||||
return false;
|
||||
|
||||
@@ -66,8 +66,7 @@ class ConfigService {
|
||||
}
|
||||
|
||||
$data = [
|
||||
'calendar' => $this->isCalendarEnabled(),
|
||||
'cardDetailsInModal' => $this->isCardDetailsInModal(),
|
||||
'calendar' => $this->isCalendarEnabled()
|
||||
];
|
||||
if ($this->groupManager->isAdmin($this->getUserId())) {
|
||||
$data['groupLimit'] = $this->get('groupLimit');
|
||||
@@ -89,11 +88,6 @@ class ConfigService {
|
||||
return false;
|
||||
}
|
||||
return (bool)$this->config->getUserValue($this->getUserId(), Application::APP_ID, 'calendar', true);
|
||||
case 'cardDetailsInModal':
|
||||
if ($this->getUserId() === null) {
|
||||
return false;
|
||||
}
|
||||
return (bool)$this->config->getUserValue($this->getUserId(), Application::APP_ID, 'cardDetailsInModal', true);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -111,19 +105,6 @@ class ConfigService {
|
||||
return (bool)$this->config->getUserValue($this->getUserId(), Application::APP_ID, 'board:' . $boardId . ':calendar', $defaultState);
|
||||
}
|
||||
|
||||
public function isCardDetailsInModal(int $boardId = null): bool {
|
||||
if ($this->getUserId() === null) {
|
||||
return false;
|
||||
}
|
||||
|
||||
$defaultState = (bool)$this->config->getUserValue($this->getUserId(), Application::APP_ID, 'cardDetailsInModal', true);
|
||||
if ($boardId === null) {
|
||||
return $defaultState;
|
||||
}
|
||||
|
||||
return (bool)$this->config->getUserValue($this->getUserId(), Application::APP_ID, 'board:' . $boardId . ':cardDetailsInModal', $defaultState);
|
||||
}
|
||||
|
||||
public function set($key, $value) {
|
||||
if ($this->getUserId() === null) {
|
||||
throw new NoPermissionException('Must be logged in to set user config');
|
||||
@@ -142,10 +123,6 @@ class ConfigService {
|
||||
$this->config->setUserValue($this->getUserId(), Application::APP_ID, 'calendar', (string)$value);
|
||||
$result = $value;
|
||||
break;
|
||||
case 'cardDetailsInModal':
|
||||
$this->config->setUserValue($this->getUserId(), Application::APP_ID, 'cardDetailsInModal', (string)$value);
|
||||
$result = $value;
|
||||
break;
|
||||
case 'board':
|
||||
[$boardId, $boardConfigKey] = explode(':', $key);
|
||||
if ($boardConfigKey === 'notify-due' && !in_array($value, [self::SETTING_BOARD_NOTIFICATION_DUE_ALL, self::SETTING_BOARD_NOTIFICATION_DUE_ASSIGNED, self::SETTING_BOARD_NOTIFICATION_DUE_OFF], true)) {
|
||||
|
||||
@@ -86,7 +86,7 @@ class FileService implements IAttachmentService {
|
||||
* @return ISimpleFolder
|
||||
* @throws NotPermittedException
|
||||
*/
|
||||
public function getFolder(Attachment $attachment) {
|
||||
private function getFolder(Attachment $attachment) {
|
||||
$folderName = 'file-card-' . (int)$attachment->getCardId();
|
||||
try {
|
||||
$folder = $this->appData->getFolder($folderName);
|
||||
|
||||
@@ -59,7 +59,7 @@ class FullTextSearchService {
|
||||
|
||||
/** @var CardMapper */
|
||||
private $cardMapper;
|
||||
|
||||
|
||||
public function __construct(
|
||||
BoardMapper $boardMapper, StackMapper $stackMapper, CardMapper $cardMapper
|
||||
) {
|
||||
@@ -187,6 +187,6 @@ class FullTextSearchService {
|
||||
* @return Board[]
|
||||
*/
|
||||
private function getBoardsFromUser(string $userId): array {
|
||||
return $this->boardMapper->findAllByUser($userId, null, null, null);
|
||||
return $this->boardMapper->findAllByUser($userId, null, null, -1);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,136 +0,0 @@
|
||||
<?php
|
||||
/**
|
||||
* @copyright Copyright (c) 2021 Vitor Mattos <vitor@php.rio>
|
||||
*
|
||||
* @author Vitor Mattos <vitor@php.rio>
|
||||
*
|
||||
* @license GNU AGPL version 3 or any later version
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as
|
||||
* published by the Free Software Foundation, either version 3 of the
|
||||
* License, or (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
*/
|
||||
|
||||
namespace OCA\Deck\Service\Importer;
|
||||
|
||||
use OCA\Deck\Db\Acl;
|
||||
use OCA\Deck\Db\Assignment;
|
||||
use OCA\Deck\Db\Board;
|
||||
use OCA\Deck\Db\Card;
|
||||
use OCA\Deck\Db\Label;
|
||||
use OCA\Deck\Db\Stack;
|
||||
use OCP\AppFramework\Db\Entity;
|
||||
use OCP\Comments\IComment;
|
||||
|
||||
abstract class ABoardImportService {
|
||||
/** @var string */
|
||||
public static $name = '';
|
||||
/** @var BoardImportService */
|
||||
private $boardImportService;
|
||||
/** @var bool */
|
||||
protected $needValidateData = true;
|
||||
/** @var Stack[] */
|
||||
protected $stacks = [];
|
||||
/** @var Label[] */
|
||||
protected $labels = [];
|
||||
/** @var Card[] */
|
||||
protected $cards = [];
|
||||
/** @var Acl[] */
|
||||
protected $acls = [];
|
||||
/** @var IComment[][] */
|
||||
protected $comments = [];
|
||||
/** @var Assignment[] */
|
||||
protected $assignments = [];
|
||||
/** @var string[][] */
|
||||
protected $labelCardAssignments = [];
|
||||
|
||||
/**
|
||||
* Configure import service
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
abstract public function bootstrap(): void;
|
||||
|
||||
abstract public function getBoard(): ?Board;
|
||||
|
||||
/**
|
||||
* @return Acl[]
|
||||
*/
|
||||
abstract public function getAclList(): array;
|
||||
|
||||
/**
|
||||
* @return Stack[]
|
||||
*/
|
||||
abstract public function getStacks(): array;
|
||||
|
||||
/**
|
||||
* @return Card[]
|
||||
*/
|
||||
abstract public function getCards(): array;
|
||||
|
||||
abstract public function getCardAssignments(): array;
|
||||
|
||||
abstract public function getCardLabelAssignment(): array;
|
||||
|
||||
/**
|
||||
* @return IComment[][]|array
|
||||
*/
|
||||
abstract public function getComments(): array;
|
||||
|
||||
/** @return Label[] */
|
||||
abstract public function getLabels(): array;
|
||||
|
||||
abstract public function validateUsers(): void;
|
||||
|
||||
abstract public function getJsonSchemaPath(): string;
|
||||
|
||||
public function updateStack(string $id, Stack $stack): void {
|
||||
$this->stacks[$id] = $stack;
|
||||
}
|
||||
|
||||
public function updateCard(string $id, Card $card): void {
|
||||
$this->cards[$id] = $card;
|
||||
}
|
||||
|
||||
public function updateLabel(string $code, Label $label): void {
|
||||
$this->labels[$code] = $label;
|
||||
}
|
||||
|
||||
public function updateAcl(string $code, Acl $acl): void {
|
||||
$this->acls[$code] = $acl;
|
||||
}
|
||||
|
||||
public function updateComment(string $cardId, string $commentId, IComment $comment): void {
|
||||
$this->comments[$cardId][$commentId] = $comment;
|
||||
}
|
||||
|
||||
public function updateCardAssignment(string $cardId, string $assignmentId, Entity $assignment): void {
|
||||
$this->assignments[$cardId][$assignmentId] = $assignment;
|
||||
}
|
||||
|
||||
public function updateCardLabelsAssignment(string $cardId, string $assignmentId, string $assignment): void {
|
||||
$this->labelCardAssignments[$cardId][$assignmentId] = $assignment;
|
||||
}
|
||||
|
||||
public function setImportService(BoardImportService $service): void {
|
||||
$this->boardImportService = $service;
|
||||
}
|
||||
|
||||
public function getImportService(): BoardImportService {
|
||||
return $this->boardImportService;
|
||||
}
|
||||
|
||||
public function needValidateData(): bool {
|
||||
return $this->needValidateData;
|
||||
}
|
||||
}
|
||||
@@ -1,199 +0,0 @@
|
||||
<?php
|
||||
/**
|
||||
* @copyright Copyright (c) 2021 Vitor Mattos <vitor@php.rio>
|
||||
*
|
||||
* @author Vitor Mattos <vitor@php.rio>
|
||||
*
|
||||
* @license GNU AGPL version 3 or any later version
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as
|
||||
* published by the Free Software Foundation, either version 3 of the
|
||||
* License, or (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
*/
|
||||
|
||||
namespace OCA\Deck\Service\Importer;
|
||||
|
||||
use OCA\Deck\Exceptions\ConflictException;
|
||||
use OCA\Deck\NotFoundException;
|
||||
use Symfony\Component\Console\Command\Command;
|
||||
use Symfony\Component\Console\Input\InputInterface;
|
||||
use Symfony\Component\Console\Output\OutputInterface;
|
||||
use Symfony\Component\Console\Question\ChoiceQuestion;
|
||||
use Symfony\Component\Console\Question\Question;
|
||||
|
||||
class BoardImportCommandService extends BoardImportService {
|
||||
/**
|
||||
* @var Command
|
||||
* @psalm-suppress PropertyNotSetInConstructor
|
||||
*/
|
||||
private $command;
|
||||
/**
|
||||
* @var InputInterface
|
||||
* @psalm-suppress PropertyNotSetInConstructor
|
||||
*/
|
||||
private $input;
|
||||
/**
|
||||
* @var OutputInterface
|
||||
* @psalm-suppress PropertyNotSetInConstructor
|
||||
*/
|
||||
private $output;
|
||||
|
||||
public function setCommand(Command $command): self {
|
||||
$this->command = $command;
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function getCommand(): Command {
|
||||
return $this->command;
|
||||
}
|
||||
|
||||
public function setInput(InputInterface $input): self {
|
||||
$this->input = $input;
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function getInput(): InputInterface {
|
||||
return $this->input;
|
||||
}
|
||||
|
||||
public function setOutput(OutputInterface $output): self {
|
||||
$this->output = $output;
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function getOutput(): OutputInterface {
|
||||
return $this->output;
|
||||
}
|
||||
|
||||
protected function validateConfig(): void {
|
||||
try {
|
||||
$config = $this->getInput()->getOption('config');
|
||||
if (is_string($config)) {
|
||||
if (!is_file($config)) {
|
||||
throw new NotFoundException('It\'s not a valid config file.');
|
||||
}
|
||||
$config = json_decode(file_get_contents($config));
|
||||
if (!$config instanceof \stdClass) {
|
||||
throw new NotFoundException('Failed to parse JSON.');
|
||||
}
|
||||
$this->setConfigInstance($config);
|
||||
}
|
||||
parent::validateConfig();
|
||||
return;
|
||||
} catch (NotFoundException $e) {
|
||||
$this->getOutput()->writeln('<error>' . $e->getMessage() . '</error>');
|
||||
$helper = $this->getCommand()->getHelper('question');
|
||||
$question = new Question(
|
||||
"<info>You can get more info on https://deck.readthedocs.io/en/latest/User_documentation_en/#6-import-boards</info>\n" .
|
||||
'Please inform a valid config json file: ',
|
||||
'config.json'
|
||||
);
|
||||
$question->setValidator(function (string $answer) {
|
||||
if (!is_file($answer)) {
|
||||
throw new \RuntimeException(
|
||||
'config file not found'
|
||||
);
|
||||
}
|
||||
return $answer;
|
||||
});
|
||||
$configFile = $helper->ask($this->getInput(), $this->getOutput(), $question);
|
||||
$this->getInput()->setOption('config', $configFile);
|
||||
} catch (ConflictException $e) {
|
||||
$this->getOutput()->writeln('<error>Invalid config file</error>');
|
||||
$this->getOutput()->writeln(array_map(function (array $v): string {
|
||||
return $v['message'];
|
||||
}, $e->getData()));
|
||||
$this->getOutput()->writeln('Valid schema:');
|
||||
$this->getOutput()->writeln(print_r(file_get_contents($this->getJsonSchemaPath()), true));
|
||||
$this->getInput()->setOption('config', '');
|
||||
}
|
||||
$this->validateConfig();
|
||||
}
|
||||
|
||||
public function validateSystem(): void {
|
||||
try {
|
||||
parent::validateSystem();
|
||||
return;
|
||||
} catch (\Throwable $th) {
|
||||
}
|
||||
$helper = $this->getCommand()->getHelper('question');
|
||||
$allowedSystems = $this->getAllowedImportSystems();
|
||||
$names = array_column($allowedSystems, 'name');
|
||||
$question = new ChoiceQuestion(
|
||||
'Please inform a source system',
|
||||
$names,
|
||||
0
|
||||
);
|
||||
$question->setErrorMessage('System %s is invalid.');
|
||||
$selectedName = $helper->ask($this->getInput(), $this->getOutput(), $question);
|
||||
$className = $allowedSystems[array_flip($names)[$selectedName]]['internalName'];
|
||||
$this->setSystem($className);
|
||||
return;
|
||||
}
|
||||
|
||||
protected function validateData(): void {
|
||||
if (!$this->getImportSystem()->needValidateData()) {
|
||||
return;
|
||||
}
|
||||
$data = $this->getInput()->getOption('data');
|
||||
if (is_string($data)) {
|
||||
$data = json_decode(file_get_contents($data));
|
||||
if ($data instanceof \stdClass) {
|
||||
$this->setData($data);
|
||||
return;
|
||||
}
|
||||
}
|
||||
$helper = $this->getCommand()->getHelper('question');
|
||||
$question = new Question(
|
||||
'Please provide a valid data json file: ',
|
||||
'data.json'
|
||||
);
|
||||
$question->setValidator(function (string $answer) {
|
||||
if (!is_file($answer)) {
|
||||
throw new \RuntimeException(
|
||||
'Data file not found'
|
||||
);
|
||||
}
|
||||
return $answer;
|
||||
});
|
||||
$data = $helper->ask($this->getInput(), $this->getOutput(), $question);
|
||||
$this->getInput()->setOption('data', $data);
|
||||
$this->validateData();
|
||||
}
|
||||
|
||||
public function bootstrap(): void {
|
||||
$this->setSystem($this->getInput()->getOption('system'));
|
||||
parent::bootstrap();
|
||||
}
|
||||
|
||||
public function import(): void {
|
||||
$this->getOutput()->writeln('Starting import...');
|
||||
$this->bootstrap();
|
||||
$this->getOutput()->writeln('Importing board...');
|
||||
$this->importBoard();
|
||||
$this->getOutput()->writeln('Assign users to board...');
|
||||
$this->importAcl();
|
||||
$this->getOutput()->writeln('Importing labels...');
|
||||
$this->importLabels();
|
||||
$this->getOutput()->writeln('Importing stacks...');
|
||||
$this->importStacks();
|
||||
$this->getOutput()->writeln('Importing cards...');
|
||||
$this->importCards();
|
||||
$this->getOutput()->writeln('Assign cards to labels...');
|
||||
$this->assignCardsToLabels();
|
||||
$this->getOutput()->writeln('Importing comments...');
|
||||
$this->importComments();
|
||||
$this->getOutput()->writeln('Importing participants...');
|
||||
$this->importCardAssignments();
|
||||
}
|
||||
}
|
||||
@@ -1,449 +0,0 @@
|
||||
<?php
|
||||
/**
|
||||
* @copyright Copyright (c) 2021 Vitor Mattos <vitor@php.rio>
|
||||
*
|
||||
* @author Vitor Mattos <vitor@php.rio>
|
||||
*
|
||||
* @license GNU AGPL version 3 or any later version
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as
|
||||
* published by the Free Software Foundation, either version 3 of the
|
||||
* License, or (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
*/
|
||||
|
||||
namespace OCA\Deck\Service\Importer;
|
||||
|
||||
use JsonSchema\Constraints\Constraint;
|
||||
use JsonSchema\Validator;
|
||||
use OCA\Deck\AppInfo\Application;
|
||||
use OCA\Deck\BadRequestException;
|
||||
use OCA\Deck\Db\AclMapper;
|
||||
use OCA\Deck\Db\AssignmentMapper;
|
||||
use OCA\Deck\Db\Attachment;
|
||||
use OCA\Deck\Db\AttachmentMapper;
|
||||
use OCA\Deck\Db\Board;
|
||||
use OCA\Deck\Db\BoardMapper;
|
||||
use OCA\Deck\Db\CardMapper;
|
||||
use OCA\Deck\Db\LabelMapper;
|
||||
use OCA\Deck\Db\StackMapper;
|
||||
use OCA\Deck\Event\BoardImportGetAllowedEvent;
|
||||
use OCA\Deck\Exceptions\ConflictException;
|
||||
use OCA\Deck\NotFoundException;
|
||||
use OCA\Deck\Service\FileService;
|
||||
use OCA\Deck\Service\Importer\Systems\TrelloApiService;
|
||||
use OCA\Deck\Service\Importer\Systems\TrelloJsonService;
|
||||
use OCP\Comments\IComment;
|
||||
use OCP\Comments\ICommentsManager;
|
||||
use OCP\Comments\NotFoundException as CommentNotFoundException;
|
||||
use OCP\EventDispatcher\IEventDispatcher;
|
||||
use OCP\IUserManager;
|
||||
|
||||
class BoardImportService {
|
||||
/** @var IUserManager */
|
||||
private $userManager;
|
||||
/** @var BoardMapper */
|
||||
private $boardMapper;
|
||||
/** @var AclMapper */
|
||||
private $aclMapper;
|
||||
/** @var LabelMapper */
|
||||
private $labelMapper;
|
||||
/** @var StackMapper */
|
||||
private $stackMapper;
|
||||
/** @var CardMapper */
|
||||
private $cardMapper;
|
||||
/** @var AssignmentMapper */
|
||||
private $assignmentMapper;
|
||||
/** @var AttachmentMapper */
|
||||
private $attachmentMapper;
|
||||
/** @var ICommentsManager */
|
||||
private $commentsManager;
|
||||
/** @var IEventDispatcher */
|
||||
private $eventDispatcher;
|
||||
/** @var string */
|
||||
private $system = '';
|
||||
/** @var null|ABoardImportService */
|
||||
private $systemInstance;
|
||||
/** @var array */
|
||||
private $allowedSystems = [];
|
||||
/**
|
||||
* Data object created from config JSON
|
||||
*
|
||||
* @var \stdClass
|
||||
* @psalm-suppress PropertyNotSetInConstructor
|
||||
*/
|
||||
public $config;
|
||||
/**
|
||||
* Data object created from JSON of origin system
|
||||
*
|
||||
* @var \stdClass
|
||||
* @psalm-suppress PropertyNotSetInConstructor
|
||||
*/
|
||||
private $data;
|
||||
/**
|
||||
* @var Board
|
||||
*/
|
||||
private $board;
|
||||
|
||||
public function __construct(
|
||||
IUserManager $userManager,
|
||||
BoardMapper $boardMapper,
|
||||
AclMapper $aclMapper,
|
||||
LabelMapper $labelMapper,
|
||||
StackMapper $stackMapper,
|
||||
AssignmentMapper $assignmentMapper,
|
||||
AttachmentMapper $attachmentMapper,
|
||||
CardMapper $cardMapper,
|
||||
ICommentsManager $commentsManager,
|
||||
IEventDispatcher $eventDispatcher
|
||||
) {
|
||||
$this->userManager = $userManager;
|
||||
$this->boardMapper = $boardMapper;
|
||||
$this->aclMapper = $aclMapper;
|
||||
$this->labelMapper = $labelMapper;
|
||||
$this->stackMapper = $stackMapper;
|
||||
$this->cardMapper = $cardMapper;
|
||||
$this->assignmentMapper = $assignmentMapper;
|
||||
$this->attachmentMapper = $attachmentMapper;
|
||||
$this->commentsManager = $commentsManager;
|
||||
$this->eventDispatcher = $eventDispatcher;
|
||||
$this->board = new Board();
|
||||
$this->disableCommentsEvents();
|
||||
}
|
||||
|
||||
private function disableCommentsEvents(): void {
|
||||
if (defined('PHPUNIT_RUN')) {
|
||||
return;
|
||||
}
|
||||
$propertyEventHandlers = new \ReflectionProperty($this->commentsManager, 'eventHandlers');
|
||||
$propertyEventHandlers->setAccessible(true);
|
||||
$propertyEventHandlers->setValue($this->commentsManager, []);
|
||||
|
||||
$propertyEventHandlerClosures = new \ReflectionProperty($this->commentsManager, 'eventHandlerClosures');
|
||||
$propertyEventHandlerClosures->setAccessible(true);
|
||||
$propertyEventHandlerClosures->setValue($this->commentsManager, []);
|
||||
}
|
||||
|
||||
public function import(): void {
|
||||
$this->bootstrap();
|
||||
try {
|
||||
$this->importBoard();
|
||||
$this->importAcl();
|
||||
$this->importLabels();
|
||||
$this->importStacks();
|
||||
$this->importCards();
|
||||
$this->assignCardsToLabels();
|
||||
$this->importComments();
|
||||
$this->importCardAssignments();
|
||||
} catch (\Throwable $th) {
|
||||
throw new BadRequestException($th->getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
public function validateSystem(): void {
|
||||
$allowedSystems = $this->getAllowedImportSystems();
|
||||
$allowedSystems = array_column($allowedSystems, 'internalName');
|
||||
if (!in_array($this->getSystem(), $allowedSystems)) {
|
||||
throw new NotFoundException('Invalid system');
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @param mixed $system
|
||||
* @return self
|
||||
*/
|
||||
public function setSystem($system): self {
|
||||
$this->system = $system;
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function getSystem(): string {
|
||||
return $this->system;
|
||||
}
|
||||
|
||||
public function addAllowedImportSystem($system): self {
|
||||
$this->allowedSystems[] = $system;
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function getAllowedImportSystems(): array {
|
||||
if (!$this->allowedSystems) {
|
||||
$this->addAllowedImportSystem([
|
||||
'name' => TrelloApiService::$name,
|
||||
'class' => TrelloApiService::class,
|
||||
'internalName' => 'TrelloApi'
|
||||
]);
|
||||
$this->addAllowedImportSystem([
|
||||
'name' => TrelloJsonService::$name,
|
||||
'class' => TrelloJsonService::class,
|
||||
'internalName' => 'TrelloJson'
|
||||
]);
|
||||
}
|
||||
$this->eventDispatcher->dispatchTyped(new BoardImportGetAllowedEvent($this));
|
||||
return $this->allowedSystems;
|
||||
}
|
||||
|
||||
public function getImportSystem(): ABoardImportService {
|
||||
if (!$this->getSystem()) {
|
||||
throw new NotFoundException('System to import not found');
|
||||
}
|
||||
if (!is_object($this->systemInstance)) {
|
||||
$systemClass = 'OCA\\Deck\\Service\\Importer\\Systems\\' . ucfirst($this->getSystem()) . 'Service';
|
||||
$this->systemInstance = \OC::$server->get($systemClass);
|
||||
$this->systemInstance->setImportService($this);
|
||||
}
|
||||
return $this->systemInstance;
|
||||
}
|
||||
|
||||
public function setImportSystem(ABoardImportService $instance): void {
|
||||
$this->systemInstance = $instance;
|
||||
}
|
||||
|
||||
public function importBoard(): void {
|
||||
$board = $this->getImportSystem()->getBoard();
|
||||
if ($board) {
|
||||
$this->boardMapper->insert($board);
|
||||
$this->board = $board;
|
||||
}
|
||||
}
|
||||
|
||||
public function getBoard(bool $reset = false): Board {
|
||||
if ($reset) {
|
||||
$this->board = new Board();
|
||||
}
|
||||
return $this->board;
|
||||
}
|
||||
|
||||
public function importAcl(): void {
|
||||
$aclList = $this->getImportSystem()->getAclList();
|
||||
foreach ($aclList as $code => $acl) {
|
||||
$this->aclMapper->insert($acl);
|
||||
$this->getImportSystem()->updateAcl($code, $acl);
|
||||
}
|
||||
$this->getBoard()->setAcl($aclList);
|
||||
}
|
||||
|
||||
public function importLabels(): void {
|
||||
$labels = $this->getImportSystem()->getLabels();
|
||||
foreach ($labels as $code => $label) {
|
||||
$this->labelMapper->insert($label);
|
||||
$this->getImportSystem()->updateLabel($code, $label);
|
||||
}
|
||||
$this->getBoard()->setLabels($labels);
|
||||
}
|
||||
|
||||
public function importStacks(): void {
|
||||
$stacks = $this->getImportSystem()->getStacks();
|
||||
foreach ($stacks as $code => $stack) {
|
||||
$this->stackMapper->insert($stack);
|
||||
$this->getImportSystem()->updateStack($code, $stack);
|
||||
}
|
||||
$this->getBoard()->setStacks(array_values($stacks));
|
||||
}
|
||||
|
||||
public function importCards(): void {
|
||||
$cards = $this->getImportSystem()->getCards();
|
||||
foreach ($cards as $code => $card) {
|
||||
$createdAt = $card->getCreatedAt();
|
||||
$lastModified = $card->getLastModified();
|
||||
$this->cardMapper->insert($card);
|
||||
$updateDate = false;
|
||||
if ($createdAt && $createdAt !== $card->getCreatedAt()) {
|
||||
$card->setCreatedAt($createdAt);
|
||||
$updateDate = true;
|
||||
}
|
||||
if ($lastModified && $lastModified !== $card->getLastModified()) {
|
||||
$card->setLastModified($lastModified);
|
||||
$updateDate = true;
|
||||
}
|
||||
if ($updateDate) {
|
||||
$this->cardMapper->update($card, false);
|
||||
}
|
||||
$this->getImportSystem()->updateCard($code, $card);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @param mixed $cardId
|
||||
* @param mixed $labelId
|
||||
* @return self
|
||||
*/
|
||||
public function assignCardToLabel($cardId, $labelId): self {
|
||||
$this->cardMapper->assignLabel(
|
||||
$cardId,
|
||||
$labelId
|
||||
);
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function assignCardsToLabels(): void {
|
||||
$data = $this->getImportSystem()->getCardLabelAssignment();
|
||||
foreach ($data as $cardId => $assignemnt) {
|
||||
foreach ($assignemnt as $assignmentId => $labelId) {
|
||||
$this->assignCardToLabel(
|
||||
$cardId,
|
||||
$labelId
|
||||
);
|
||||
$this->getImportSystem()->updateCardLabelsAssignment($cardId, $assignmentId, $labelId);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public function importComments(): void {
|
||||
$allComments = $this->getImportSystem()->getComments();
|
||||
foreach ($allComments as $cardId => $comments) {
|
||||
foreach ($comments as $commentId => $comment) {
|
||||
$this->insertComment($cardId, $comment);
|
||||
$this->getImportSystem()->updateComment($cardId, $commentId, $comment);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private function insertComment(string $cardId, IComment $comment): void {
|
||||
$comment->setObject('deckCard', $cardId);
|
||||
$comment->setVerb('comment');
|
||||
// Check if parent is a comment on the same card
|
||||
if ($comment->getParentId() !== '0') {
|
||||
try {
|
||||
$parent = $this->commentsManager->get($comment->getParentId());
|
||||
if ($parent->getObjectType() !== Application::COMMENT_ENTITY_TYPE || $parent->getObjectId() !== $cardId) {
|
||||
throw new CommentNotFoundException();
|
||||
}
|
||||
} catch (CommentNotFoundException $e) {
|
||||
throw new BadRequestException('Invalid parent id: The parent comment was not found or belongs to a different card');
|
||||
}
|
||||
}
|
||||
|
||||
try {
|
||||
$this->commentsManager->save($comment);
|
||||
} catch (\InvalidArgumentException $e) {
|
||||
throw new BadRequestException('Invalid input values');
|
||||
} catch (CommentNotFoundException $e) {
|
||||
throw new NotFoundException('Could not create comment.');
|
||||
}
|
||||
}
|
||||
|
||||
public function importCardAssignments(): void {
|
||||
$allAssignments = $this->getImportSystem()->getCardAssignments();
|
||||
foreach ($allAssignments as $cardId => $assignments) {
|
||||
foreach ($assignments as $assignmentId => $assignment) {
|
||||
$this->assignmentMapper->insert($assignment);
|
||||
$this->getImportSystem()->updateCardAssignment($cardId, $assignmentId, $assignment);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public function insertAttachment(Attachment $attachment, string $content): Attachment {
|
||||
$service = \OC::$server->get(FileService::class);
|
||||
$folder = $service->getFolder($attachment);
|
||||
|
||||
if ($folder->fileExists($attachment->getData())) {
|
||||
$attachment = $this->attachmentMapper->findByData($attachment->getCardId(), $attachment->getData());
|
||||
throw new ConflictException('File already exists.', $attachment);
|
||||
}
|
||||
|
||||
$target = $folder->newFile($attachment->getData());
|
||||
$target->putContent($content);
|
||||
|
||||
$attachment = $this->attachmentMapper->insert($attachment);
|
||||
|
||||
$service->extendData($attachment);
|
||||
return $attachment;
|
||||
}
|
||||
|
||||
public function setData(\stdClass $data): void {
|
||||
$this->data = $data;
|
||||
}
|
||||
|
||||
public function getData(): \stdClass {
|
||||
return $this->data;
|
||||
}
|
||||
|
||||
/**
|
||||
* Define a config
|
||||
*
|
||||
* @param string $configName
|
||||
* @param mixed $value
|
||||
* @return void
|
||||
*/
|
||||
public function setConfig(string $configName, $value): void {
|
||||
if (empty((array) $this->config)) {
|
||||
$this->setConfigInstance(new \stdClass);
|
||||
}
|
||||
$this->config->$configName = $value;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a config
|
||||
*
|
||||
* @param string $configName config name
|
||||
* @return mixed
|
||||
*/
|
||||
public function getConfig(string $configName) {
|
||||
if (!property_exists($this->config, $configName)) {
|
||||
return;
|
||||
}
|
||||
return $this->config->$configName;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param \stdClass $config
|
||||
* @return self
|
||||
*/
|
||||
public function setConfigInstance($config): self {
|
||||
$this->config = $config;
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function getConfigInstance(): \stdClass {
|
||||
return $this->config;
|
||||
}
|
||||
|
||||
protected function validateConfig(): void {
|
||||
$config = $this->getConfigInstance();
|
||||
$schemaPath = $this->getJsonSchemaPath();
|
||||
$validator = new Validator();
|
||||
$newConfig = clone $config;
|
||||
$validator->validate(
|
||||
$newConfig,
|
||||
(object)['$ref' => 'file://' . realpath($schemaPath)],
|
||||
Constraint::CHECK_MODE_APPLY_DEFAULTS
|
||||
);
|
||||
if (!$validator->isValid()) {
|
||||
throw new ConflictException('Invalid config file', $validator->getErrors());
|
||||
}
|
||||
$this->setConfigInstance($newConfig);
|
||||
$this->validateOwner();
|
||||
}
|
||||
|
||||
public function getJsonSchemaPath(): string {
|
||||
return $this->getImportSystem()->getJsonSchemaPath();
|
||||
}
|
||||
|
||||
public function validateOwner(): void {
|
||||
$owner = $this->userManager->get($this->getConfig('owner'));
|
||||
if (!$owner) {
|
||||
throw new \LogicException('Owner "' . $this->getConfig('owner')->getUID() . '" not found on Nextcloud. Check setting json.');
|
||||
}
|
||||
$this->setConfig('owner', $owner);
|
||||
}
|
||||
|
||||
protected function validateData(): void {
|
||||
}
|
||||
|
||||
public function bootstrap(): void {
|
||||
$this->validateSystem();
|
||||
$this->validateConfig();
|
||||
$this->validateData();
|
||||
$this->getImportSystem()->bootstrap();
|
||||
}
|
||||
}
|
||||
@@ -1,214 +0,0 @@
|
||||
<?php
|
||||
/**
|
||||
* @copyright Copyright (c) 2021 Vitor Mattos <vitor@php.rio>
|
||||
*
|
||||
* @author Vitor Mattos <vitor@php.rio>
|
||||
*
|
||||
* @license GNU AGPL version 3 or any later version
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as
|
||||
* published by the Free Software Foundation, either version 3 of the
|
||||
* License, or (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
*/
|
||||
|
||||
namespace OCA\Deck\Service\Importer\Systems;
|
||||
|
||||
use OCP\Http\Client\IClient;
|
||||
use OCP\Http\Client\IClientService;
|
||||
use OCP\IL10N;
|
||||
use OCP\IURLGenerator;
|
||||
use OCP\IUserManager;
|
||||
use Psr\Log\LoggerInterface;
|
||||
|
||||
class TrelloApiService extends TrelloJsonService {
|
||||
/** @var string */
|
||||
public static $name = 'Trello API';
|
||||
protected $needValidateData = false;
|
||||
/** @var IClient */
|
||||
private $httpClient;
|
||||
/** @var LoggerInterface */
|
||||
protected $logger;
|
||||
/** @var string */
|
||||
private $baseApiUrl = 'https://api.trello.com/1';
|
||||
/** @var ?\stdClass[] */
|
||||
private $boards;
|
||||
|
||||
public function __construct(
|
||||
IUserManager $userManager,
|
||||
IURLGenerator $urlGenerator,
|
||||
IL10N $l10n,
|
||||
LoggerInterface $logger,
|
||||
IClientService $httpClientService
|
||||
) {
|
||||
parent::__construct($userManager, $urlGenerator, $l10n);
|
||||
$this->logger = $logger;
|
||||
$this->httpClient = $httpClientService->newClient();
|
||||
}
|
||||
|
||||
public function bootstrap(): void {
|
||||
$this->populateBoard();
|
||||
$this->populateMembers();
|
||||
$this->populateLabels();
|
||||
$this->populateLists();
|
||||
$this->populateCheckLists();
|
||||
$this->populateCards();
|
||||
$this->populateActions();
|
||||
parent::bootstrap();
|
||||
}
|
||||
|
||||
public function getJsonSchemaPath(): string {
|
||||
return implode(DIRECTORY_SEPARATOR, [
|
||||
__DIR__,
|
||||
'..',
|
||||
'fixtures',
|
||||
'config-trelloApi-schema.json',
|
||||
]);
|
||||
}
|
||||
|
||||
private function populateActions(): void {
|
||||
$data = $this->getImportService()->getData();
|
||||
$data->actions = $this->doRequest(
|
||||
'/boards/' . $data->id . '/actions',
|
||||
[
|
||||
'filter' => 'commentCard,createCard',
|
||||
'fields=memberCreator,type,data,date',
|
||||
'memberCreator_fields' => 'username',
|
||||
'limit' => 1000
|
||||
]
|
||||
);
|
||||
}
|
||||
|
||||
private function populateCards(): void {
|
||||
$data = $this->getImportService()->getData();
|
||||
$data->cards = $this->doRequest(
|
||||
'/boards/' . $data->id . '/cards',
|
||||
[
|
||||
'fields' => 'id,idMembers,dateLastActivity,closed,idChecklists,name,idList,pos,desc,due,labels',
|
||||
'attachments' => true,
|
||||
'attachment_fields' => 'name,url,date',
|
||||
'limit' => 1000
|
||||
]
|
||||
);
|
||||
}
|
||||
|
||||
private function populateCheckLists(): void {
|
||||
$data = $this->getImportService()->getData();
|
||||
$data->checklists = $this->doRequest(
|
||||
'/boards/' . $data->id . '/checkLists',
|
||||
[
|
||||
'fields' => 'id,idCard,name',
|
||||
'checkItem_fields' => 'id,state,name',
|
||||
'limit' => 1000
|
||||
]
|
||||
);
|
||||
}
|
||||
|
||||
private function populateLists(): void {
|
||||
$data = $this->getImportService()->getData();
|
||||
$data->lists = $this->doRequest(
|
||||
'/boards/' . $data->id . '/lists',
|
||||
[
|
||||
'fields' => 'id,name,closed',
|
||||
'limit' => 1000
|
||||
]
|
||||
);
|
||||
}
|
||||
|
||||
private function populateLabels(): void {
|
||||
$data = $this->getImportService()->getData();
|
||||
$data->labels = $this->doRequest(
|
||||
'/boards/' . $data->id . '/labels',
|
||||
[
|
||||
'fields' => 'id,color,name',
|
||||
'limit' => 1000
|
||||
]
|
||||
);
|
||||
}
|
||||
|
||||
private function populateMembers(): void {
|
||||
$data = $this->getImportService()->getData();
|
||||
$data->members = $this->doRequest(
|
||||
'/boards/' . $data->id . '/members',
|
||||
[
|
||||
'fields' => 'username',
|
||||
'limit' => 1000
|
||||
]
|
||||
);
|
||||
}
|
||||
|
||||
private function populateBoard(): void {
|
||||
$toImport = $this->getImportService()->getConfig('board');
|
||||
$board = $this->doRequest(
|
||||
'/boards/' . $toImport,
|
||||
['fields' => 'id,name']
|
||||
);
|
||||
if ($board instanceof \stdClass) {
|
||||
$this->getImportService()->setData($board);
|
||||
return;
|
||||
}
|
||||
throw new \Exception('Invalid board id to import');
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array|\stdClass
|
||||
*/
|
||||
private function doRequest(string $path = '', array $queryString = []) {
|
||||
$target = $this->baseApiUrl . $path;
|
||||
try {
|
||||
$result = $this->httpClient
|
||||
->get($target, $this->getQueryString($queryString))
|
||||
->getBody();
|
||||
if (is_string($result)) {
|
||||
$data = json_decode($result);
|
||||
if (is_array($data)) {
|
||||
$data = array_merge(
|
||||
$data,
|
||||
$this->paginate($path, $queryString, $data)
|
||||
);
|
||||
}
|
||||
return $data;
|
||||
}
|
||||
throw new \Exception('Invalid return of api');
|
||||
} catch (\Throwable $e) {
|
||||
$this->logger->critical(
|
||||
$e->getMessage(),
|
||||
['app' => 'deck']
|
||||
);
|
||||
throw new \Exception($e->getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
private function paginate(string $path = '', array $queryString = [], array $data = []): array {
|
||||
if (empty($queryString['limit'])) {
|
||||
return [];
|
||||
}
|
||||
if (count($data) < $queryString['limit']) {
|
||||
return [];
|
||||
}
|
||||
$queryString['before'] = end($data)->id;
|
||||
$return = $this->doRequest($path, $queryString);
|
||||
if (is_array($return)) {
|
||||
return $return;
|
||||
}
|
||||
throw new \Exception('Invalid return of api');
|
||||
}
|
||||
|
||||
private function getQueryString(array $params = []): array {
|
||||
$apiSettings = $this->getImportService()->getConfig('api');
|
||||
$params['key'] = $apiSettings->key;
|
||||
$params['token'] = $apiSettings->token;
|
||||
return [
|
||||
'query' => $params
|
||||
];
|
||||
}
|
||||
}
|
||||
@@ -1,400 +0,0 @@
|
||||
<?php
|
||||
/**
|
||||
* @copyright Copyright (c) 2021 Vitor Mattos <vitor@php.rio>
|
||||
*
|
||||
* @author Vitor Mattos <vitor@php.rio>
|
||||
*
|
||||
* @license GNU AGPL version 3 or any later version
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as
|
||||
* published by the Free Software Foundation, either version 3 of the
|
||||
* License, or (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
*/
|
||||
|
||||
namespace OCA\Deck\Service\Importer\Systems;
|
||||
|
||||
use OC\Comments\Comment;
|
||||
use OCA\Deck\BadRequestException;
|
||||
use OCA\Deck\Db\Acl;
|
||||
use OCA\Deck\Db\Assignment;
|
||||
use OCA\Deck\Db\Attachment;
|
||||
use OCA\Deck\Db\Board;
|
||||
use OCA\Deck\Db\Card;
|
||||
use OCA\Deck\Db\Label;
|
||||
use OCA\Deck\Db\Stack;
|
||||
use OCA\Deck\Service\Importer\ABoardImportService;
|
||||
use OCP\Comments\IComment;
|
||||
use OCP\IL10N;
|
||||
use OCP\IURLGenerator;
|
||||
use OCP\IUser;
|
||||
use OCP\IUserManager;
|
||||
|
||||
class TrelloJsonService extends ABoardImportService {
|
||||
/** @var string */
|
||||
public static $name = 'Trello JSON';
|
||||
/** @var IUserManager */
|
||||
private $userManager;
|
||||
/** @var IURLGenerator */
|
||||
private $urlGenerator;
|
||||
/** @var IL10N */
|
||||
private $l10n;
|
||||
/** @var IUser[] */
|
||||
private $members = [];
|
||||
|
||||
public function __construct(
|
||||
IUserManager $userManager,
|
||||
IURLGenerator $urlGenerator,
|
||||
IL10N $l10n
|
||||
) {
|
||||
$this->userManager = $userManager;
|
||||
$this->urlGenerator = $urlGenerator;
|
||||
$this->l10n = $l10n;
|
||||
}
|
||||
|
||||
public function bootstrap(): void {
|
||||
$this->validateUsers();
|
||||
}
|
||||
|
||||
public function getJsonSchemaPath(): string {
|
||||
return implode(DIRECTORY_SEPARATOR, [
|
||||
__DIR__,
|
||||
'..',
|
||||
'fixtures',
|
||||
'config-trelloJson-schema.json',
|
||||
]);
|
||||
}
|
||||
|
||||
public function validateUsers(): void {
|
||||
if (empty($this->getImportService()->getConfig('uidRelation'))) {
|
||||
return;
|
||||
}
|
||||
foreach ($this->getImportService()->getConfig('uidRelation') as $trelloUid => $nextcloudUid) {
|
||||
$user = array_filter($this->getImportService()->getData()->members, function (\stdClass $u) use ($trelloUid) {
|
||||
return $u->username === $trelloUid;
|
||||
});
|
||||
if (!$user) {
|
||||
throw new \LogicException('Trello user ' . $trelloUid . ' not found in property "members" of json data');
|
||||
}
|
||||
if (!is_string($nextcloudUid) && !is_numeric($nextcloudUid)) {
|
||||
throw new \LogicException('User on setting uidRelation is invalid');
|
||||
}
|
||||
$nextcloudUid = (string) $nextcloudUid;
|
||||
$this->getImportService()->getConfig('uidRelation')->$trelloUid = $this->userManager->get($nextcloudUid);
|
||||
if (!$this->getImportService()->getConfig('uidRelation')->$trelloUid) {
|
||||
throw new \LogicException('User on setting uidRelation not found: ' . $nextcloudUid);
|
||||
}
|
||||
$user = current($user);
|
||||
$this->members[$user->id] = $this->getImportService()->getConfig('uidRelation')->$trelloUid;
|
||||
}
|
||||
}
|
||||
|
||||
public function getCardAssignments(): array {
|
||||
$assignments = [];
|
||||
foreach ($this->getImportService()->getData()->cards as $trelloCard) {
|
||||
foreach ($trelloCard->idMembers as $idMember) {
|
||||
if (empty($this->members[$idMember])) {
|
||||
continue;
|
||||
}
|
||||
$assignment = new Assignment();
|
||||
$assignment->setCardId($this->cards[$trelloCard->id]->getId());
|
||||
$assignment->setParticipant($this->members[$idMember]->getUID());
|
||||
$assignment->setType(Assignment::TYPE_USER);
|
||||
$assignments[$trelloCard->id][] = $assignment;
|
||||
}
|
||||
}
|
||||
return $assignments;
|
||||
}
|
||||
|
||||
public function getComments(): array {
|
||||
$comments = [];
|
||||
foreach ($this->getImportService()->getData()->cards as $trelloCard) {
|
||||
$values = array_filter(
|
||||
$this->getImportService()->getData()->actions,
|
||||
function (\stdClass $a) use ($trelloCard) {
|
||||
return $a->type === 'commentCard' && $a->data->card->id === $trelloCard->id;
|
||||
}
|
||||
);
|
||||
$keys = array_map(function (\stdClass $c): string {
|
||||
return $c->id;
|
||||
}, $values);
|
||||
$trelloComments = array_combine($keys, $values);
|
||||
$trelloComments = $this->sortComments($trelloComments);
|
||||
foreach ($trelloComments as $commentId => $trelloComment) {
|
||||
$cardId = $this->cards[$trelloCard->id]->getId();
|
||||
$comment = new Comment();
|
||||
if (!empty($this->getImportService()->getConfig('uidRelation')->{$trelloComment->memberCreator->username})) {
|
||||
$actor = $this->getImportService()->getConfig('uidRelation')->{$trelloComment->memberCreator->username}->getUID();
|
||||
} else {
|
||||
$actor = $this->getImportService()->getConfig('owner')->getUID();
|
||||
}
|
||||
$message = $this->replaceUsernames($trelloComment->data->text);
|
||||
if (mb_strlen($message, 'UTF-8') > IComment::MAX_MESSAGE_LENGTH) {
|
||||
$attachment = new Attachment();
|
||||
$attachment->setCardId($cardId);
|
||||
$attachment->setType('deck_file');
|
||||
$attachment->setCreatedBy($actor);
|
||||
$attachment->setLastModified(time());
|
||||
$attachment->setCreatedAt(time());
|
||||
$attachment->setData('comment_' . $commentId . '.md');
|
||||
$attachment = $this->getImportService()->insertAttachment($attachment, $message);
|
||||
|
||||
$urlToDownloadAttachment = $this->urlGenerator->linkToRouteAbsolute(
|
||||
'deck.attachment.display',
|
||||
[
|
||||
'cardId' => $cardId,
|
||||
'attachmentId' => $attachment->getId()
|
||||
]
|
||||
);
|
||||
$message = $this->l10n->t(
|
||||
"This comment has more than %s characters.\n" .
|
||||
"Added as an attachment to the card with name %s.\n" .
|
||||
"Accessible on URL: %s.",
|
||||
[
|
||||
IComment::MAX_MESSAGE_LENGTH,
|
||||
'comment_' . $commentId . '.md',
|
||||
$urlToDownloadAttachment
|
||||
]
|
||||
);
|
||||
}
|
||||
$comment
|
||||
->setActor('users', $actor)
|
||||
->setMessage($message)
|
||||
->setCreationDateTime(
|
||||
\DateTime::createFromFormat('Y-m-d\TH:i:s.v\Z', $trelloComment->date)
|
||||
);
|
||||
$comments[$cardId][$commentId] = $comment;
|
||||
}
|
||||
}
|
||||
return $comments;
|
||||
}
|
||||
|
||||
private function sortComments(array $comments): array {
|
||||
$comparison = function (\stdClass $a, \stdClass $b): int {
|
||||
if ($a->date == $b->date) {
|
||||
return 0;
|
||||
}
|
||||
return ($a->date < $b->date) ? -1 : 1;
|
||||
};
|
||||
|
||||
usort($comments, $comparison);
|
||||
return $comments;
|
||||
}
|
||||
|
||||
public function getCardLabelAssignment(): array {
|
||||
$cardsLabels = [];
|
||||
foreach ($this->getImportService()->getData()->cards as $trelloCard) {
|
||||
foreach ($trelloCard->labels as $label) {
|
||||
$cardId = $this->cards[$trelloCard->id]->getId();
|
||||
$labelId = $this->labels[$label->id]->getId();
|
||||
$cardsLabels[$cardId][] = $labelId;
|
||||
}
|
||||
}
|
||||
return $cardsLabels;
|
||||
}
|
||||
|
||||
public function getBoard(): Board {
|
||||
$board = $this->getImportService()->getBoard();
|
||||
if (empty($this->getImportService()->getData()->name)) {
|
||||
throw new BadRequestException('Invalid name of board');
|
||||
}
|
||||
$board->setTitle($this->getImportService()->getData()->name);
|
||||
$board->setOwner($this->getImportService()->getConfig('owner')->getUID());
|
||||
$board->setColor($this->getImportService()->getConfig('color'));
|
||||
return $board;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return Label[]
|
||||
*/
|
||||
public function getLabels(): array {
|
||||
foreach ($this->getImportService()->getData()->labels as $trelloLabel) {
|
||||
$label = new Label();
|
||||
if (empty($trelloLabel->name)) {
|
||||
$label->setTitle('Unnamed ' . $trelloLabel->color . ' label');
|
||||
} else {
|
||||
$label->setTitle($trelloLabel->name);
|
||||
}
|
||||
$label->setColor($this->translateColor($trelloLabel->color));
|
||||
$label->setBoardId($this->getImportService()->getBoard()->getId());
|
||||
$this->labels[$trelloLabel->id] = $label;
|
||||
}
|
||||
return $this->labels;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return Stack[]
|
||||
*/
|
||||
public function getStacks(): array {
|
||||
$return = [];
|
||||
foreach ($this->getImportService()->getData()->lists as $order => $list) {
|
||||
$stack = new Stack();
|
||||
if ($list->closed) {
|
||||
$stack->setDeletedAt(time());
|
||||
}
|
||||
$stack->setTitle($list->name);
|
||||
$stack->setBoardId($this->getImportService()->getBoard()->getId());
|
||||
$stack->setOrder($order + 1);
|
||||
$return[$list->id] = $stack;
|
||||
}
|
||||
return $return;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return Card[]
|
||||
*/
|
||||
public function getCards(): array {
|
||||
$checklists = [];
|
||||
foreach ($this->getImportService()->getData()->checklists as $checklist) {
|
||||
$checklists[$checklist->idCard][$checklist->id] = $this->formulateChecklistText($checklist);
|
||||
}
|
||||
$this->getImportService()->getData()->checklists = $checklists;
|
||||
|
||||
$cards = [];
|
||||
foreach ($this->getImportService()->getData()->cards as $trelloCard) {
|
||||
$card = new Card();
|
||||
$lastModified = \DateTime::createFromFormat('Y-m-d\TH:i:s.v\Z', $trelloCard->dateLastActivity);
|
||||
$card->setLastModified($lastModified->format('Y-m-d H:i:s'));
|
||||
if ($trelloCard->closed) {
|
||||
$card->setArchived(true);
|
||||
}
|
||||
if ((count($trelloCard->idChecklists) !== 0)) {
|
||||
foreach ($this->getImportService()->getData()->checklists[$trelloCard->id] as $checklist) {
|
||||
$trelloCard->desc .= "\n" . $checklist;
|
||||
}
|
||||
}
|
||||
$this->appendAttachmentsToDescription($trelloCard);
|
||||
|
||||
$card->setTitle($trelloCard->name);
|
||||
$card->setStackId($this->stacks[$trelloCard->idList]->getId());
|
||||
$cardsOnStack = $this->stacks[$trelloCard->idList]->getCards();
|
||||
$cardsOnStack[] = $card;
|
||||
$this->stacks[$trelloCard->idList]->setCards($cardsOnStack);
|
||||
$card->setType('plain');
|
||||
$card->setOrder($trelloCard->pos);
|
||||
$card->setOwner($this->getImportService()->getConfig('owner')->getUID());
|
||||
|
||||
$lastModified = \DateTime::createFromFormat('Y-m-d\TH:i:s.v\Z', $trelloCard->dateLastActivity);
|
||||
$card->setLastModified($lastModified->format('U'));
|
||||
|
||||
$createCardDate = array_filter(
|
||||
$this->getImportService()->getData()->actions,
|
||||
function (\stdClass $a) use ($trelloCard) {
|
||||
return $a->type === 'createCard' && $a->data->card->id === $trelloCard->id;
|
||||
}
|
||||
);
|
||||
$createCardDate = current($createCardDate);
|
||||
$createCardDate = \DateTime::createFromFormat('Y-m-d\TH:i:s.v\Z', $createCardDate->date);
|
||||
if ($createCardDate) {
|
||||
$card->setCreatedAt($createCardDate->format('U'));
|
||||
} else {
|
||||
$card->setCreatedAt($lastModified->format('U'));
|
||||
}
|
||||
|
||||
$card->setDescription($trelloCard->desc);
|
||||
if ($trelloCard->due) {
|
||||
$duedate = \DateTime::createFromFormat('Y-m-d\TH:i:s.v\Z', $trelloCard->due)
|
||||
->format('Y-m-d H:i:s');
|
||||
$card->setDuedate($duedate);
|
||||
}
|
||||
$cards[$trelloCard->id] = $card;
|
||||
}
|
||||
return $cards;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return Acl[]
|
||||
*/
|
||||
public function getAclList(): array {
|
||||
$return = [];
|
||||
foreach ($this->members as $member) {
|
||||
if ($member->getUID() === $this->getImportService()->getConfig('owner')->getUID()) {
|
||||
continue;
|
||||
}
|
||||
$acl = new Acl();
|
||||
$acl->setBoardId($this->getImportService()->getBoard()->getId());
|
||||
$acl->setType(Acl::PERMISSION_TYPE_USER);
|
||||
$acl->setParticipant($member->getUID());
|
||||
$acl->setPermissionEdit(false);
|
||||
$acl->setPermissionShare(false);
|
||||
$acl->setPermissionManage(false);
|
||||
$return[] = $acl;
|
||||
}
|
||||
return $return;
|
||||
}
|
||||
|
||||
private function translateColor(string $color): string {
|
||||
switch ($color) {
|
||||
case 'red':
|
||||
return 'ff0000';
|
||||
case 'yellow':
|
||||
return 'ffff00';
|
||||
case 'orange':
|
||||
return 'ff6600';
|
||||
case 'green':
|
||||
return '00ff00';
|
||||
case 'purple':
|
||||
return '9900ff';
|
||||
case 'blue':
|
||||
return '0000ff';
|
||||
case 'sky':
|
||||
return '00ccff';
|
||||
case 'lime':
|
||||
return '00ff99';
|
||||
case 'pink':
|
||||
return 'ff66cc';
|
||||
case 'black':
|
||||
return '000000';
|
||||
default:
|
||||
return 'ffffff';
|
||||
}
|
||||
}
|
||||
|
||||
private function replaceUsernames(string $text): string {
|
||||
foreach ($this->getImportService()->getConfig('uidRelation') as $trello => $nextcloud) {
|
||||
$text = str_replace($trello, $nextcloud->getUID(), $text);
|
||||
}
|
||||
return $text;
|
||||
}
|
||||
|
||||
private function checklistItem(\stdClass $item): string {
|
||||
if (($item->state == 'incomplete')) {
|
||||
$string_start = '- [ ]';
|
||||
} else {
|
||||
$string_start = '- [x]';
|
||||
}
|
||||
$check_item_string = $string_start . ' ' . $item->name . "\n";
|
||||
return $check_item_string;
|
||||
}
|
||||
|
||||
private function formulateChecklistText(\stdClass $checklist): string {
|
||||
$checklist_string = "\n\n## {$checklist->name}\n";
|
||||
foreach ($checklist->checkItems as $item) {
|
||||
$checklist_item_string = $this->checklistItem($item);
|
||||
$checklist_string = $checklist_string . "\n" . $checklist_item_string;
|
||||
}
|
||||
return $checklist_string;
|
||||
}
|
||||
|
||||
private function appendAttachmentsToDescription(\stdClass $trelloCard): void {
|
||||
if (empty($trelloCard->attachments)) {
|
||||
return;
|
||||
}
|
||||
$trelloCard->desc .= "\n\n## {$this->l10n->t('Attachments')}\n";
|
||||
$trelloCard->desc .= "| {$this->l10n->t('File')} | {$this->l10n->t('date')} |\n";
|
||||
$trelloCard->desc .= "|---|---\n";
|
||||
foreach ($trelloCard->attachments as $attachment) {
|
||||
$name = mb_strlen($attachment->name, 'UTF-8') ? $attachment->name : $attachment->url;
|
||||
$trelloCard->desc .= "| [{$name}]({$attachment->url}) | {$attachment->date} |\n";
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,41 +0,0 @@
|
||||
{
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"api": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"key": {
|
||||
"type": "string",
|
||||
"pattern": "^[0-9a-fA-F]{32}$"
|
||||
},
|
||||
"token": {
|
||||
"type": "string",
|
||||
"pattern": "^[0-9a-fA-F]{64}$"
|
||||
}
|
||||
}
|
||||
},
|
||||
"board": {
|
||||
"type": "string",
|
||||
"pattern": "^\\w{1,}$"
|
||||
},
|
||||
"uidRelation": {
|
||||
"type": "object",
|
||||
"comment": "Relationship between Trello and Nextcloud usernames",
|
||||
"example": {
|
||||
"johndoe": "admin"
|
||||
}
|
||||
},
|
||||
"owner": {
|
||||
"type": "string",
|
||||
"required": true,
|
||||
"comment": "Nextcloud owner username"
|
||||
},
|
||||
"color": {
|
||||
"type": "string",
|
||||
"required": true,
|
||||
"pattern": "^[0-9a-fA-F]{6}$",
|
||||
"comment": "Default color for the board. If you don't inform, the default color will be used.",
|
||||
"default": "0800fd"
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,24 +0,0 @@
|
||||
{
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"uidRelation": {
|
||||
"type": "object",
|
||||
"comment": "Relationship between Trello and Nextcloud usernames",
|
||||
"example": {
|
||||
"johndoe": "admin"
|
||||
}
|
||||
},
|
||||
"owner": {
|
||||
"type": "string",
|
||||
"required": true,
|
||||
"comment": "Nextcloud owner username"
|
||||
},
|
||||
"color": {
|
||||
"type": "string",
|
||||
"required": true,
|
||||
"pattern": "^[0-9a-fA-F]{6}$",
|
||||
"comment": "Default color for the board. If you don't inform, the default color will be used.",
|
||||
"default": "0800fd"
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -138,7 +138,7 @@ class OverviewService {
|
||||
private function findAllBoardsFromUser(string $userId): array {
|
||||
$userInfo = $this->getBoardPrerequisites($userId);
|
||||
$userBoards = $this->boardMapper->findAllByUser($userInfo['user'], null, null);
|
||||
$groupBoards = $this->boardMapper->findAllByGroups($userInfo['user'], $userInfo['groups'], null, null);
|
||||
$groupBoards = $this->boardMapper->findAllByGroups($userInfo['user'], $userInfo['groups'],null, null);
|
||||
$circleBoards = $this->boardMapper->findAllByCircles($userInfo['user'], null, null);
|
||||
return array_unique(array_merge($userBoards, $groupBoards, $circleBoards));
|
||||
}
|
||||
|
||||
@@ -33,6 +33,7 @@ use OCA\Deck\Db\IPermissionMapper;
|
||||
use OCA\Deck\Db\User;
|
||||
use OCA\Deck\NoPermissionException;
|
||||
use OCP\AppFramework\Db\DoesNotExistException;
|
||||
use OCP\AppFramework\Db\Entity;
|
||||
use OCP\AppFramework\Db\MultipleObjectsReturnedException;
|
||||
use OCP\IConfig;
|
||||
use OCP\IGroupManager;
|
||||
@@ -64,7 +65,6 @@ class PermissionService {
|
||||
private $users = [];
|
||||
|
||||
private $boardCache;
|
||||
private $permissionCache;
|
||||
|
||||
public function __construct(
|
||||
ILogger $logger,
|
||||
@@ -88,7 +88,6 @@ class PermissionService {
|
||||
$this->userId = $userId;
|
||||
|
||||
$this->boardCache = new CappedMemoryCache();
|
||||
$this->permissionCache = new CappedMemoryCache();
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -98,21 +97,15 @@ class PermissionService {
|
||||
* @return bool|array
|
||||
*/
|
||||
public function getPermissions($boardId) {
|
||||
if ($cached = $this->permissionCache->get($boardId)) {
|
||||
return $cached;
|
||||
}
|
||||
|
||||
$owner = $this->userIsBoardOwner($boardId);
|
||||
$acls = $this->aclMapper->findAll($boardId);
|
||||
$permissions = [
|
||||
return [
|
||||
Acl::PERMISSION_READ => $owner || $this->userCan($acls, Acl::PERMISSION_READ),
|
||||
Acl::PERMISSION_EDIT => $owner || $this->userCan($acls, Acl::PERMISSION_EDIT),
|
||||
Acl::PERMISSION_MANAGE => $owner || $this->userCan($acls, Acl::PERMISSION_MANAGE),
|
||||
Acl::PERMISSION_SHARE => ($owner || $this->userCan($acls, Acl::PERMISSION_SHARE))
|
||||
&& (!$this->shareManager->sharingDisabledForUser($this->userId))
|
||||
];
|
||||
$this->permissionCache->set($boardId, $permissions);
|
||||
return $permissions;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -124,7 +117,7 @@ class PermissionService {
|
||||
*/
|
||||
public function matchPermissions(Board $board) {
|
||||
$owner = $this->userIsBoardOwner($board->getId());
|
||||
$acls = $board->getAcl() ?? [];
|
||||
$acls = $board->getAcl();
|
||||
return [
|
||||
Acl::PERMISSION_READ => $owner || $this->userCan($acls, Acl::PERMISSION_READ),
|
||||
Acl::PERMISSION_EDIT => $owner || $this->userCan($acls, Acl::PERMISSION_EDIT),
|
||||
@@ -162,7 +155,7 @@ class PermissionService {
|
||||
}
|
||||
|
||||
try {
|
||||
$acls = $this->getBoard($boardId)->getAcl() ?? [];
|
||||
$acls = $this->getBoard($boardId)->getAcl();
|
||||
$result = $this->userCan($acls, $permission, $userId);
|
||||
if ($result) {
|
||||
return true;
|
||||
@@ -350,6 +343,5 @@ class PermissionService {
|
||||
*/
|
||||
public function setUserId(string $userId): void {
|
||||
$this->userId = $userId;
|
||||
$this->permissionCache->clear();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -90,19 +90,25 @@ class SearchService {
|
||||
}
|
||||
|
||||
public function searchBoards(string $term, ?int $limit, ?int $cursor): array {
|
||||
$boards = $this->boardService->getUserBoards(null, true, $cursor, mb_strtolower($term));
|
||||
|
||||
$boards = $this->boardService->getUserBoards();
|
||||
// get boards that have a lastmodified date which is lower than the cursor
|
||||
// and which match the search term
|
||||
$filteredBoards = array_filter($boards, static function (Board $board) use ($term, $cursor) {
|
||||
return (
|
||||
($cursor === null || $board->getLastModified() < $cursor)
|
||||
&& mb_stripos(mb_strtolower($board->getTitle()), mb_strtolower($term)) > -1
|
||||
);
|
||||
});
|
||||
// sort the boards, recently modified first
|
||||
usort($boards, function ($boardA, $boardB) {
|
||||
usort($filteredBoards, function ($boardA, $boardB) {
|
||||
$ta = $boardA->getLastModified();
|
||||
$tb = $boardB->getLastModified();
|
||||
return $ta === $tb
|
||||
? 0
|
||||
: ($ta > $tb ? -1 : 1);
|
||||
});
|
||||
|
||||
// limit the number of results
|
||||
return array_slice($boards, 0, $limit);
|
||||
return array_slice($filteredBoards, 0, $limit);
|
||||
}
|
||||
|
||||
public function searchComments(string $term, ?int $limit = null, ?int $cursor = null): array {
|
||||
|
||||
@@ -26,6 +26,7 @@ declare(strict_types=1);
|
||||
|
||||
namespace OCA\Deck\Sharing;
|
||||
|
||||
use Doctrine\DBAL\Platforms\MySQLPlatform;
|
||||
use OC\Files\Cache\Cache;
|
||||
use OCA\Deck\Cache\AttachmentCacheHelper;
|
||||
use OCA\Deck\Db\Acl;
|
||||
@@ -517,7 +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'));
|
||||
$qb->andWhere($qb->expr()->eq('f.parent', $qb->createNamedParameter($node->getId())));
|
||||
|
||||
$qb->orderBy('s.id');
|
||||
@@ -727,6 +728,13 @@ class DeckShareProvider implements \OCP\Share\IShareProvider {
|
||||
}
|
||||
|
||||
$qb = $this->dbConnection->getQueryBuilder();
|
||||
// Avoid using implicit cast in order to make use of the index in the join on MySQL/MariaDB
|
||||
// FIXME: Once >= Nextcloud 24 this can be dropped due to https://github.com/nextcloud/server/pull/30471
|
||||
if ($this->dbConnection->getDatabasePlatform() instanceof MySQLPlatform) {
|
||||
$cardIdExpression = $qb->createFunction('CAST(dc.id as CHAR)');
|
||||
} else {
|
||||
$cardIdExpression = $qb->expr()->castColumn('dc.id', IQueryBuilder::PARAM_STR);
|
||||
}
|
||||
$qb->select('s.*',
|
||||
'f.fileid', 'f.path', 'f.permissions AS f_permissions', 'f.storage', 'f.path_hash',
|
||||
'f.parent AS f_parent', 'f.name', 'f.mimetype', 'f.mimepart', 'f.size', 'f.mtime', 'f.storage_mtime',
|
||||
@@ -737,7 +745,7 @@ class DeckShareProvider implements \OCP\Share\IShareProvider {
|
||||
->orderBy('s.id')
|
||||
->leftJoin('s', 'filecache', 'f', $qb->expr()->eq('s.file_source', 'f.fileid'))
|
||||
->leftJoin('f', 'storages', 'st', $qb->expr()->eq('f.storage', 'st.numeric_id'))
|
||||
->leftJoin('s', 'deck_cards', 'dc', $qb->expr()->eq($qb->expr()->castColumn('dc.id', IQueryBuilder::PARAM_STR), 's.share_with'))
|
||||
->leftJoin('s', 'deck_cards', 'dc', $qb->expr()->eq($cardIdExpression, 's.share_with'))
|
||||
->leftJoin('dc', 'deck_stacks', 'ds', $qb->expr()->eq('dc.stack_id', 'ds.id'))
|
||||
->leftJoin('ds', 'deck_boards', 'db', $qb->expr()->eq('ds.board_id', 'db.id'));
|
||||
|
||||
@@ -798,6 +806,13 @@ class DeckShareProvider implements \OCP\Share\IShareProvider {
|
||||
$shares = [];
|
||||
|
||||
$qb = $this->dbConnection->getQueryBuilder();
|
||||
// Avoid using implicit cast in order to make use of the index in the join on MySQL/MariaDB
|
||||
// FIXME: Once >= Nextcloud 24 this can be dropped due to https://github.com/nextcloud/server/pull/30471
|
||||
if ($this->dbConnection->getDatabasePlatform() instanceof MySQLPlatform) {
|
||||
$cardIdExpression = $qb->createFunction('CAST(dc.id as CHAR)');
|
||||
} else {
|
||||
$cardIdExpression = $qb->expr()->castColumn('dc.id', IQueryBuilder::PARAM_STR);
|
||||
}
|
||||
$qb->select('s.*',
|
||||
'f.fileid', 'f.path', 'f.permissions AS f_permissions', 'f.storage', 'f.path_hash',
|
||||
'f.parent AS f_parent', 'f.name', 'f.mimetype', 'f.mimepart', 'f.size', 'f.mtime', 'f.storage_mtime',
|
||||
@@ -808,7 +823,7 @@ class DeckShareProvider implements \OCP\Share\IShareProvider {
|
||||
->orderBy('s.id')
|
||||
->leftJoin('s', 'filecache', 'f', $qb->expr()->eq('s.file_source', 'f.fileid'))
|
||||
->leftJoin('f', 'storages', 'st', $qb->expr()->eq('f.storage', 'st.numeric_id'))
|
||||
->leftJoin('s', 'deck_cards', 'dc', $qb->expr()->eq($qb->expr()->castColumn('dc.id', IQueryBuilder::PARAM_STR), 's.share_with'));
|
||||
->leftJoin('s', 'deck_cards', 'dc', $qb->expr()->eq($cardIdExpression, 's.share_with'));
|
||||
|
||||
if ($limit !== -1) {
|
||||
$qb->setMaxResults($limit);
|
||||
|
||||
@@ -30,8 +30,10 @@ use OCA\Deck\Db\Acl;
|
||||
use OCA\Deck\Db\CardMapper;
|
||||
use OCA\Deck\NoPermissionException;
|
||||
use OCA\Deck\Service\PermissionService;
|
||||
use OCP\AppFramework\Db\DoesNotExistException;
|
||||
use OCP\AppFramework\OCS\OCSNotFoundException;
|
||||
use OCP\AppFramework\Utility\ITimeFactory;
|
||||
use OCP\Files\NotFoundException;
|
||||
use OCP\IL10N;
|
||||
use OCP\IURLGenerator;
|
||||
use OCP\Share\IShare;
|
||||
@@ -53,7 +55,11 @@ class ShareAPIHelper {
|
||||
|
||||
public function formatShare(IShare $share): array {
|
||||
$result = [];
|
||||
$card = $this->cardMapper->find($share->getSharedWith());
|
||||
try {
|
||||
$card = $this->cardMapper->find($share->getSharedWith());
|
||||
} catch (DoesNotExistException $e) {
|
||||
throw new NotFoundException($e->getMessage());
|
||||
}
|
||||
$boardId = $this->cardMapper->findBoardId($card->getId());
|
||||
$result['share_with'] = $share->getSharedWith();
|
||||
$result['share_with_displayname'] = $card->getTitle();
|
||||
|
||||
@@ -31,7 +31,7 @@ namespace OCA\Deck;
|
||||
*/
|
||||
class StatusException extends \Exception {
|
||||
public function __construct($message) {
|
||||
parent::__construct($message ?? '');
|
||||
parent::__construct($message);
|
||||
}
|
||||
|
||||
public function getStatus() {
|
||||
|
||||
@@ -11,6 +11,4 @@ pages:
|
||||
- Nextcloud API: API-Nextcloud.md
|
||||
- Developer documentation:
|
||||
- Data structure: structure.md
|
||||
- Import documentation:
|
||||
- Implement import: implement-import.md
|
||||
- Class diagram: import-class-diagram.md
|
||||
|
||||
|
||||
15466
package-lock.json
generated
15466
package-lock.json
generated
File diff suppressed because it is too large
Load Diff
47
package.json
47
package.json
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"name": "deck",
|
||||
"description": "",
|
||||
"version": "1.7.2",
|
||||
"version": "1.5.8",
|
||||
"authors": [
|
||||
{
|
||||
"name": "Julius Härtl",
|
||||
@@ -29,35 +29,34 @@
|
||||
},
|
||||
"dependencies": {
|
||||
"@babel/polyfill": "^7.12.1",
|
||||
"@babel/runtime": "^7.17.9",
|
||||
"@babel/runtime": "^7.14.6",
|
||||
"@juliushaertl/vue-richtext": "^1.0.1",
|
||||
"@nextcloud/auth": "^1.3.0",
|
||||
"@nextcloud/axios": "^1.9.0",
|
||||
"@nextcloud/axios": "^1.6.0",
|
||||
"@nextcloud/dialogs": "^3.1.2",
|
||||
"@nextcloud/event-bus": "^2.1.1",
|
||||
"@nextcloud/files": "^2.1.0",
|
||||
"@nextcloud/initial-state": "^1.2.1",
|
||||
"@nextcloud/event-bus": "^2.0.0",
|
||||
"@nextcloud/files": "^2.0.0",
|
||||
"@nextcloud/initial-state": "^1.2.0",
|
||||
"@nextcloud/l10n": "^1.4.1",
|
||||
"@nextcloud/moment": "^1.2.0",
|
||||
"@nextcloud/moment": "^1.1.1",
|
||||
"@nextcloud/router": "^2.0.0",
|
||||
"@nextcloud/vue": "^5.3.1",
|
||||
"@nextcloud/vue": "^3.10.1",
|
||||
"@nextcloud/vue-dashboard": "^2.0.1",
|
||||
"blueimp-md5": "^2.19.0",
|
||||
"dompurify": "^2.3.6",
|
||||
"blueimp-md5": "^2.18.0",
|
||||
"dompurify": "^2.2.9",
|
||||
"lodash": "^4.17.21",
|
||||
"markdown-it": "^12.3.2",
|
||||
"markdown-it-link-attributes": "^4.0.0",
|
||||
"markdown-it-task-checkbox": "^1.0.6",
|
||||
"moment": "^2.29.2",
|
||||
"markdown-it": "^12.0.6",
|
||||
"markdown-it-task-lists": "^2.1.1",
|
||||
"moment": "^2.29.1",
|
||||
"nextcloud-vue-collections": "^0.9.0",
|
||||
"p-queue": "^6.6.2",
|
||||
"url-search-params-polyfill": "^8.1.1",
|
||||
"vue": "^2.6.14",
|
||||
"vue-at": "^2.5.0-beta.2",
|
||||
"vue-click-outside": "^1.1.0",
|
||||
"vue-easymde": "^2.0.0",
|
||||
"vue-easymde": "^1.4.0",
|
||||
"vue-infinite-loading": "^2.4.5",
|
||||
"vue-router": "^3.5.3",
|
||||
"vue-router": "^3.5.1",
|
||||
"vue-smooth-dnd": "^0.8.1",
|
||||
"vuex": "^3.6.2",
|
||||
"vuex-router-sync": "^5.0.0"
|
||||
@@ -70,14 +69,14 @@
|
||||
"npm": "^7.0.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@nextcloud/babel-config": "^1.0.0",
|
||||
"@nextcloud/browserslist-config": "^2.2.0",
|
||||
"@nextcloud/eslint-config": "^6.1.2",
|
||||
"@nextcloud/stylelint-config": "^2.1.2",
|
||||
"@nextcloud/webpack-vue-config": "^5.0.0",
|
||||
"@relative-ci/agent": "^3.1.2",
|
||||
"@vue/test-utils": "^1.3.0",
|
||||
"jest": "^27.5.1",
|
||||
"@nextcloud/babel-config": "^1.0.0-beta.1",
|
||||
"@nextcloud/browserslist-config": "^2.1.0",
|
||||
"@nextcloud/eslint-config": "^5.1.0",
|
||||
"@nextcloud/stylelint-config": "^1.0.0-beta.0",
|
||||
"@nextcloud/webpack-vue-config": "^4.0.3",
|
||||
"@relative-ci/agent": "^2.0.0",
|
||||
"@vue/test-utils": "^1.2.1",
|
||||
"jest": "^27.0.4",
|
||||
"jest-serializer-vue": "^2.0.2",
|
||||
"vue-jest": "^3.0.7"
|
||||
},
|
||||
|
||||
@@ -39,6 +39,7 @@
|
||||
<referencedClass name="OC\*" />
|
||||
<referencedClass name="OC" />
|
||||
<referencedClass name="OC\Security\CSP\ContentSecurityPolicyNonceManager" />
|
||||
<referencedClass name="Doctrine\DBAL\Platforms\MySQLPlatform" />
|
||||
</errorLevel>
|
||||
</UndefinedClass>
|
||||
<UndefinedDocblockClass>
|
||||
@@ -48,6 +49,8 @@
|
||||
<referencedClass name="Doctrine\DBAL\Schema\SchemaException" />
|
||||
<referencedClass name="Doctrine\DBAL\Driver\Statement" />
|
||||
<referencedClass name="Doctrine\DBAL\Schema\Table" />
|
||||
<referencedClass name="Doctrine\DBAL\Platforms\AbstractPlatform" />
|
||||
<referencedClass name="Doctrine\DBAL\Platforms\MySQLPlatform" />
|
||||
<referencedClass name="OC\Security\CSP\ContentSecurityPolicyNonceManager" />
|
||||
</errorLevel>
|
||||
</UndefinedDocblockClass>
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user